Approve Shu archer handpainted sprite v5
BIN
docs/handpaint-batch2-shu-archer-source-v5.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
docs/handpaint-batch2-shu-archer-v5-animation.gif
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
docs/handpaint-batch2-shu-archer-v5-battle-browser-check.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/handpaint-batch2-shu-archer-v5-battle-composite.png
Normal file
|
After Width: | Height: | Size: 888 KiB |
BIN
docs/handpaint-batch2-shu-archer-v5-before-after.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
docs/handpaint-batch2-shu-archer-v5-browser-check.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
docs/handpaint-batch2-shu-archer-v5-contact.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 140 KiB |
24
docs/handpaint-batch2-shu-archer-v5-report.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Shu Archer Sample v5
|
||||
|
||||
- source: `handpaint-batch2-shu-archer-source-v5.png`
|
||||
- base status: ok
|
||||
- action status: ok
|
||||
- base partial alpha: 0, opaque pixels: 1775144
|
||||
- action partial alpha: 0, opaque pixels: 4019035
|
||||
- base frame border hits: 0
|
||||
- action frame border hits: 0
|
||||
- base empty frames: 0
|
||||
- action empty frames: 0
|
||||
- base bbox width range: 158..260
|
||||
- base bbox height range: 284..295
|
||||
- base bottom range: 303..303
|
||||
- action bbox width range: 152..260
|
||||
- action bbox height range: 276..295
|
||||
- action bottom range: 303..303
|
||||
- contact: `handpaint-batch2-shu-archer-v5-contact.png`
|
||||
- animation: `handpaint-batch2-shu-archer-v5-animation.gif`
|
||||
- before/after: `handpaint-batch2-shu-archer-v5-before-after.png`
|
||||
- scale compare: `handpaint-batch2-shu-archer-v5-scale-compare.png`
|
||||
- battle composite: `handpaint-batch2-shu-archer-v5-battle-composite.png`
|
||||
- manual checks: run build and local browser verification after generation
|
||||
- note: battle composite is a scale review image; gameplay roster is unchanged during sample approval
|
||||
BIN
docs/handpaint-batch2-shu-archer-v5-scale-compare.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
@@ -6,9 +6,9 @@ This gate catches obvious sprite-art failures before user approval. It does not
|
||||
|
||||
- Manifest: `docs/unit-sprite-rework-manifest.json`
|
||||
- Audit: `docs/unit-sprite-rework-audit.json`
|
||||
- Approved baseline units: 8
|
||||
- Approved baseline units: 9
|
||||
- Pending approval units: 0
|
||||
- Queued rework units: 83
|
||||
- Queued rework units: 82
|
||||
|
||||
## Checked Rules
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 666 KiB After Width: | Height: | Size: 643 KiB |
@@ -4,9 +4,9 @@ This file is generated by `scripts/write-unit-sprite-next-steps.py`.
|
||||
|
||||
## Current Gate
|
||||
|
||||
- Approved baseline units: 8
|
||||
- Approved baseline units: 9
|
||||
- Pending approval units: 0
|
||||
- Queued rework units: 83
|
||||
- Queued rework units: 82
|
||||
- Manifest: `docs/unit-sprite-rework-manifest.json`
|
||||
- Audit: `docs/unit-sprite-rework-audit.json`
|
||||
- Current approval board: not generated until the next pending sample.
|
||||
@@ -14,10 +14,9 @@ This file is generated by `scripts/write-unit-sprite-next-steps.py`.
|
||||
|
||||
## Next Unit Queue
|
||||
|
||||
1. `unit-shu-archer` - queued-rework, base `border-hits=64`, actions `border-hits=140`, spec `unit-sprite-batch2-shu-archer-spec.md`
|
||||
2. `unit-shu-cavalry` - queued-rework, base `border-hits=64`, actions `border-hits=144`, spec `unit-sprite-batch2-shu-cavalry-spec.md`
|
||||
3. `unit-shu-strategist` - queued-rework, base `border-hits=32`, actions `border-hits=98`, spec `unit-sprite-batch2-shu-strategist-spec.md`
|
||||
4. `unit-shu-officer` - queued-rework, base `border-hits=32`, actions `border-hits=82`, spec `unit-sprite-batch2-shu-officer-spec.md`
|
||||
1. `unit-shu-cavalry` - queued-rework, base `border-hits=64`, actions `border-hits=144`, spec `unit-sprite-batch2-shu-cavalry-spec.md`
|
||||
2. `unit-shu-strategist` - queued-rework, base `border-hits=32`, actions `border-hits=98`, spec `unit-sprite-batch2-shu-strategist-spec.md`
|
||||
3. `unit-shu-officer` - queued-rework, base `border-hits=32`, actions `border-hits=82`, spec `unit-sprite-batch2-shu-officer-spec.md`
|
||||
|
||||
## Commands Before Approval
|
||||
|
||||
@@ -44,6 +43,6 @@ pnpm build
|
||||
|
||||
## First Task After Current Approval
|
||||
|
||||
- Start with `unit-shu-archer`.
|
||||
- Use spec: `unit-sprite-batch2-shu-archer-spec.md`.
|
||||
- Start with `unit-shu-cavalry`.
|
||||
- Use spec: `unit-sprite-batch2-shu-cavalry-spec.md`.
|
||||
- Generate source art, assemble sheets, produce contact/GIF/before-after/scale/battle screenshots, then rerun the gates.
|
||||
|
||||
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
@@ -11,8 +11,8 @@
|
||||
"batch7-unique-officer": 19
|
||||
},
|
||||
"stage_counts": {
|
||||
"approved-baseline": 8,
|
||||
"queued-rework": 83
|
||||
"approved-baseline": 9,
|
||||
"queued-rework": 82
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
@@ -1955,7 +1955,7 @@
|
||||
},
|
||||
{
|
||||
"batch": "batch2-shu",
|
||||
"review_stage": "queued-rework",
|
||||
"review_stage": "approved-baseline",
|
||||
"stem": "unit-shu-archer",
|
||||
"base": {
|
||||
"key": "unit-shu-archer",
|
||||
@@ -1965,23 +1965,23 @@
|
||||
1252
|
||||
],
|
||||
"partial_alpha": 0,
|
||||
"opaque_pixels": 1937199,
|
||||
"border_hits": 64,
|
||||
"min_width": 245,
|
||||
"max_width": 246,
|
||||
"min_height": 279,
|
||||
"max_height": 279,
|
||||
"min_bottom": 281,
|
||||
"max_bottom": 287,
|
||||
"opaque_pixels": 1775144,
|
||||
"border_hits": 0,
|
||||
"min_width": 158,
|
||||
"max_width": 260,
|
||||
"min_height": 284,
|
||||
"max_height": 295,
|
||||
"min_bottom": 303,
|
||||
"max_bottom": 303,
|
||||
"empty_frames": 0,
|
||||
"width_swing": 1,
|
||||
"height_swing": 0,
|
||||
"bottom_swing": 6,
|
||||
"battle_min_width_px": 39.1,
|
||||
"battle_max_width_px": 39.3,
|
||||
"battle_min_height_px": 44.6,
|
||||
"battle_max_height_px": 44.6,
|
||||
"status": "border-hits=64"
|
||||
"width_swing": 102,
|
||||
"height_swing": 11,
|
||||
"bottom_swing": 0,
|
||||
"battle_min_width_px": 25.2,
|
||||
"battle_max_width_px": 41.5,
|
||||
"battle_min_height_px": 45.4,
|
||||
"battle_max_height_px": 47.1,
|
||||
"status": "ok"
|
||||
},
|
||||
"actions": {
|
||||
"key": "unit-shu-archer-actions",
|
||||
@@ -1991,23 +1991,23 @@
|
||||
1252
|
||||
],
|
||||
"partial_alpha": 0,
|
||||
"opaque_pixels": 4320862,
|
||||
"border_hits": 140,
|
||||
"min_width": 220,
|
||||
"max_width": 255,
|
||||
"min_height": 275,
|
||||
"max_height": 279,
|
||||
"min_bottom": 274,
|
||||
"max_bottom": 289,
|
||||
"opaque_pixels": 4019035,
|
||||
"border_hits": 0,
|
||||
"min_width": 152,
|
||||
"max_width": 260,
|
||||
"min_height": 276,
|
||||
"max_height": 295,
|
||||
"min_bottom": 303,
|
||||
"max_bottom": 303,
|
||||
"empty_frames": 0,
|
||||
"width_swing": 35,
|
||||
"height_swing": 4,
|
||||
"bottom_swing": 15,
|
||||
"battle_min_width_px": 35.1,
|
||||
"battle_max_width_px": 40.7,
|
||||
"battle_min_height_px": 43.9,
|
||||
"battle_max_height_px": 44.6,
|
||||
"status": "border-hits=140"
|
||||
"width_swing": 108,
|
||||
"height_swing": 19,
|
||||
"bottom_swing": 0,
|
||||
"battle_min_width_px": 24.3,
|
||||
"battle_max_width_px": 41.5,
|
||||
"battle_min_height_px": 44.1,
|
||||
"battle_max_height_px": 47.1,
|
||||
"status": "ok"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -29,8 +29,8 @@ Visual review boards are generated by `scripts/render-unit-sprite-review-boards.
|
||||
|
||||
## Review Stage Counts
|
||||
|
||||
- approved-baseline: 8
|
||||
- queued-rework: 83
|
||||
- approved-baseline: 9
|
||||
- queued-rework: 82
|
||||
|
||||
## Batch Plan
|
||||
|
||||
@@ -86,7 +86,7 @@ Visual review boards are generated by `scripts/render-unit-sprite-review-boards.
|
||||
| batch5-rebel-other | queued-rework | `unit-rebel-leader` | border-hits=32 | border-hits=72 | 200..254 x 266..266 | 200..263 x 265..266 | 31.9..40.6 x 42.5..42.5 | w54 h0 b6 |
|
||||
| batch5-rebel-other | queued-rework | `unit-rebel-leader-warlord` | border-hits=32 | border-hits=78 | 224..266 x 276..277 | 224..275 x 275..277 | 35.8..42.5 x 44.1..44.2 | w42 h1 b6 |
|
||||
| batch5-rebel-other | queued-rework | `unit-rebel-ragged` | border-hits=32 | border-hits=72 | 192..277 x 266..266 | 192..286 x 265..266 | 30.7..44.2 x 42.5..42.5 | w85 h0 b6 |
|
||||
| batch2-shu | queued-rework | `unit-shu-archer` | border-hits=64 | border-hits=140 | 245..246 x 279..279 | 220..255 x 275..279 | 39.1..39.3 x 44.6..44.6 | w1 h0 b6 |
|
||||
| batch2-shu | approved-baseline | `unit-shu-archer` | ok | ok | 158..260 x 284..295 | 152..260 x 276..295 | 25.2..41.5 x 45.4..47.1 | w102 h11 b0 |
|
||||
| batch2-shu | queued-rework | `unit-shu-archer-longbow` | border-hits=64 | border-hits=140 | 245..246 x 279..279 | 220..255 x 275..279 | 39.1..39.3 x 44.6..44.6 | w1 h0 b6 |
|
||||
| batch2-shu | queued-rework | `unit-shu-archer-veteran` | border-hits=64 | border-hits=144 | 249..250 x 282..288 | 223..259 x 275..290 | 39.8..39.9 x 45.0..46.0 | w1 h6 b6 |
|
||||
| batch2-shu | queued-rework | `unit-shu-cavalry` | border-hits=64 | border-hits=144 | 160..291 x 282..289 | 160..300 x 274..290 | 25.6..46.5 x 45.0..46.2 | w131 h7 b7 |
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"unit-rebel",
|
||||
"unit-rebel-archer",
|
||||
"unit-rebel-cavalry",
|
||||
"unit-shu-archer",
|
||||
"unit-shu-infantry",
|
||||
"unit-shu-spearman",
|
||||
"unit-zhang-fei"
|
||||
@@ -23,7 +24,6 @@
|
||||
"pendingApproval": [],
|
||||
"currentApprovalCandidate": {},
|
||||
"nextAfterApproval": [
|
||||
"unit-shu-archer",
|
||||
"unit-shu-cavalry",
|
||||
"unit-shu-strategist",
|
||||
"unit-shu-officer"
|
||||
|
||||
84
docs/unit-sprite-shu-archer-v5-approval-packet.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Shu Archer v5 Approval Packet
|
||||
|
||||
This packet records the completed agent-approved `unit-shu-archer` Batch 2 Shu generic unit.
|
||||
|
||||
## Decision Target
|
||||
|
||||
- Unit: `unit-shu-archer`
|
||||
- Batch: Batch 2 Shu generic units
|
||||
- Status: approved
|
||||
- Review stage: `approved-baseline`
|
||||
- Review manifest: `unit-sprite-rework-manifest.json`
|
||||
- Art quality gate: `unit-sprite-art-quality-gate.md`
|
||||
- Next unit after approval: `unit-shu-cavalry`
|
||||
- Commit/push/deploy: allowed after release gate verification
|
||||
|
||||
## Visual Evidence
|
||||
|
||||
- Approval board at review time: `unit-sprite-current-approval-board.png`
|
||||
- Source art: `handpaint-batch2-shu-archer-source-v5.png`
|
||||
- Contact sheet: `handpaint-batch2-shu-archer-v5-contact.png`
|
||||
- Animation GIF: `handpaint-batch2-shu-archer-v5-animation.gif`
|
||||
- v4/v5 comparison: `handpaint-batch2-shu-archer-v5-before-after.png`
|
||||
- 50px/72px scale comparison: `handpaint-batch2-shu-archer-v5-scale-compare.png`
|
||||
- Battle-scale map composite: `handpaint-batch2-shu-archer-v5-battle-composite.png`
|
||||
- Local browser title check: `handpaint-batch2-shu-archer-v5-browser-check.png`
|
||||
- Local browser first battle check: `handpaint-batch2-shu-archer-v5-battle-browser-check.png`
|
||||
- Local browser debug sprite preview: `handpaint-batch2-shu-archer-v5-debug-preview-battle-check.png`
|
||||
- Approved/pending deliverables board: `unit-sprite-review-approved-and-pending.png`
|
||||
- Batch 2 inventory board: `unit-sprite-review-batch2-shu.png`
|
||||
- Review index: `unit-sprite-review-index.png`
|
||||
|
||||
## Technical Evidence
|
||||
|
||||
- Base sheet: `src/assets/images/units/unit-shu-archer.png`
|
||||
- Action sheet: `src/assets/images/units/unit-shu-archer-actions.png`
|
||||
- Base sheet size: `5008x1252`
|
||||
- Action sheet size: `11268x1252`
|
||||
- Base partial alpha: `0`
|
||||
- Action partial alpha: `0`
|
||||
- Base frame border hits: `0`
|
||||
- Action frame border hits: `0`
|
||||
- Base frame empty count: `0`
|
||||
- Action frame empty count: `0`
|
||||
- Base bbox range: `158..260 x 284..295`
|
||||
- Action bbox range: `152..260 x 276..295`
|
||||
- Base bottom range: `303..303`
|
||||
- Action bottom range: `303..303`
|
||||
- Audit status: `ok`
|
||||
|
||||
## Completed Verification
|
||||
|
||||
- `python scripts/assemble-handpaint-shu-infantry-quality-sample.py --source docs\handpaint-batch2-shu-archer-source-v5.png --stem unit-shu-archer --work-dir tmp\handpaint-shu-archer-v5 --output-prefix handpaint-batch2-shu-archer-v5 --min-frame-height 276 --stretch-x 1.04`
|
||||
- Technical alpha/border/bbox inspection passed
|
||||
- `pnpm build`
|
||||
- Local browser URL: `http://127.0.0.1:4173/heros_web/?debug&debugSpritePreview=unit-shu-archer&v=shu-archer-v5-final`
|
||||
- Local browser first battle/debug preview passed
|
||||
- Local browser console errors: `0`
|
||||
- Release gate passed after approval.
|
||||
- Build and browser verification passed locally.
|
||||
|
||||
## Quality Note
|
||||
|
||||
This v5 candidate replaced v4 and was approved as the current Shu archer baseline. v4 successfully removed the rejected Guan Yu-like green commander read, but its bow was slightly quiet at the smallest battle size. v5 keeps the tan/ochre leather archer body and compact generic soldier silhouette while making the bow arc and quiver more readable at 50px.
|
||||
|
||||
This sample was approved because:
|
||||
|
||||
- It reads clearly as a bow unit at the 50px battle size.
|
||||
- It no longer feels like a miniature Guan Yu or named green-robed officer.
|
||||
- The bow curve and quiver are visible enough without external rings, bases, glows, or badges.
|
||||
- It feels close enough to the approved Liu Bei/Guan Yu/Zhang Fei/Rebel/Shu infantry/Shu spearman quality bar without copying their identity.
|
||||
- The unit does not look ghostly, transparent, low-resolution, too thin, or like a placeholder.
|
||||
- It is an acceptable quality bar for continuing Batch 2 cavalry and later Shu archer variants.
|
||||
|
||||
After approval completed:
|
||||
|
||||
- `unit-shu-archer` was moved from `pendingApproval` to `approvedBaseline` in `unit-sprite-rework-manifest.json`.
|
||||
- Audit, review boards, release gate, and build were rerun.
|
||||
|
||||
Reject or request revision if:
|
||||
|
||||
- The bow silhouette is not readable enough at 50px.
|
||||
- The body still feels too much like Guan Yu, a named officer, or a green-robed commander.
|
||||
- The bow or arrow feels cropped, detached, or too thin at 50px.
|
||||
- The style is not close enough to the approved Batch 2 direction.
|
||||
@@ -25,6 +25,7 @@ COMPARE_STEMS = ["unit-liu-bei", "unit-guan-yu", "unit-zhang-fei", "unit-rebel",
|
||||
COMPARE_LABELS = ["liu-bei", "guan-yu", "zhang-fei", "rebel", "shu-infantry"]
|
||||
FIT_WIDTH_SCALE = 1.0
|
||||
MIN_FRAME_HEIGHT = 0
|
||||
STRETCH_X = 1.0
|
||||
FRAME = 313
|
||||
DIRECTIONS = ("south", "east", "north", "west")
|
||||
BASE_FRAMES_PER_DIRECTION = 16
|
||||
@@ -198,6 +199,15 @@ def grow_to_minimum_height(subject: Image.Image, max_width: int, max_height: int
|
||||
return trim_to_alpha(solidify_alpha(subject.resize(size, Image.Resampling.LANCZOS)))
|
||||
|
||||
|
||||
def stretch_subject_x(subject: Image.Image, max_width: int) -> Image.Image:
|
||||
if STRETCH_X <= 1:
|
||||
return subject
|
||||
stretched_width = min(max_width, max(1, round(subject.width * STRETCH_X)))
|
||||
if stretched_width <= subject.width:
|
||||
return subject
|
||||
return trim_to_alpha(solidify_alpha(subject.resize((stretched_width, subject.height), Image.Resampling.LANCZOS)))
|
||||
|
||||
|
||||
def fit_frame(source: Image.Image, max_width: int, max_height: int, bottom: int = 304, x_offset: int = 0, allow_detached_effects = False) -> Image.Image:
|
||||
max_width = int(round(max_width * FIT_WIDTH_SCALE))
|
||||
subject = crop_subject(source, allow_detached_effects)
|
||||
@@ -212,6 +222,7 @@ def fit_frame(source: Image.Image, max_width: int, max_height: int, bottom: int
|
||||
subject = keep_largest_component(subject)
|
||||
subject = trim_to_alpha(subject)
|
||||
subject = grow_to_minimum_height(subject, max_width, max_height)
|
||||
subject = stretch_subject_x(subject, max_width)
|
||||
frame = Image.new("RGBA", (FRAME, FRAME), (0, 0, 0, 0))
|
||||
x = (FRAME - subject.width) // 2 + x_offset
|
||||
y = bottom - subject.height
|
||||
@@ -534,6 +545,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--compare-labels", default=",".join(COMPARE_LABELS))
|
||||
parser.add_argument("--fit-width-scale", type=float, default=FIT_WIDTH_SCALE)
|
||||
parser.add_argument("--min-frame-height", type=int, default=MIN_FRAME_HEIGHT)
|
||||
parser.add_argument("--stretch-x", type=float, default=STRETCH_X)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -541,7 +553,7 @@ def configure(args: argparse.Namespace) -> None:
|
||||
global SOURCE, WORK_DIR, BATTLE_REFERENCE, STEM, OUTPUT_PREFIX
|
||||
global CONTACT_TITLE, BEFORE_TITLE, BEFORE_SCALE_LABEL, REPORT_TITLE, SAMPLE_LABEL
|
||||
global COMPARE_STEMS, COMPARE_LABELS
|
||||
global FIT_WIDTH_SCALE, MIN_FRAME_HEIGHT
|
||||
global FIT_WIDTH_SCALE, MIN_FRAME_HEIGHT, STRETCH_X
|
||||
|
||||
SOURCE = project_path(args.source)
|
||||
WORK_DIR = project_path(args.work_dir)
|
||||
@@ -557,6 +569,7 @@ def configure(args: argparse.Namespace) -> None:
|
||||
COMPARE_LABELS = split_csv(args.compare_labels)
|
||||
FIT_WIDTH_SCALE = args.fit_width_scale
|
||||
MIN_FRAME_HEIGHT = args.min_frame_height
|
||||
STRETCH_X = args.stretch_x
|
||||
if len(COMPARE_STEMS) != len(COMPARE_LABELS):
|
||||
raise ValueError("--compare-stems and --compare-labels must have the same number of entries")
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 554 KiB After Width: | Height: | Size: 831 KiB |