diff --git a/docs/handpaint-batch2-shu-archer-source-v5.png b/docs/handpaint-batch2-shu-archer-source-v5.png new file mode 100644 index 0000000..bac7de3 Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-source-v5.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-animation.gif b/docs/handpaint-batch2-shu-archer-v5-animation.gif new file mode 100644 index 0000000..f6db15c Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-animation.gif differ diff --git a/docs/handpaint-batch2-shu-archer-v5-battle-browser-check.png b/docs/handpaint-batch2-shu-archer-v5-battle-browser-check.png new file mode 100644 index 0000000..3018ddd Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-battle-browser-check.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-battle-composite.png b/docs/handpaint-batch2-shu-archer-v5-battle-composite.png new file mode 100644 index 0000000..0ed013a Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-battle-composite.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-before-after.png b/docs/handpaint-batch2-shu-archer-v5-before-after.png new file mode 100644 index 0000000..ff1107d Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-before-after.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-browser-check.png b/docs/handpaint-batch2-shu-archer-v5-browser-check.png new file mode 100644 index 0000000..30c6ff1 Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-browser-check.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-contact.png b/docs/handpaint-batch2-shu-archer-v5-contact.png new file mode 100644 index 0000000..cb0ecd7 Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-contact.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-debug-preview-battle-check.png b/docs/handpaint-batch2-shu-archer-v5-debug-preview-battle-check.png new file mode 100644 index 0000000..3018ddd Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-debug-preview-battle-check.png differ diff --git a/docs/handpaint-batch2-shu-archer-v5-report.md b/docs/handpaint-batch2-shu-archer-v5-report.md new file mode 100644 index 0000000..c76f996 --- /dev/null +++ b/docs/handpaint-batch2-shu-archer-v5-report.md @@ -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 diff --git a/docs/handpaint-batch2-shu-archer-v5-scale-compare.png b/docs/handpaint-batch2-shu-archer-v5-scale-compare.png new file mode 100644 index 0000000..7ed2aec Binary files /dev/null and b/docs/handpaint-batch2-shu-archer-v5-scale-compare.png differ diff --git a/docs/unit-sprite-art-quality-gate.md b/docs/unit-sprite-art-quality-gate.md index d74b258..16901f3 100644 --- a/docs/unit-sprite-art-quality-gate.md +++ b/docs/unit-sprite-art-quality-gate.md @@ -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 diff --git a/docs/unit-sprite-current-approval-board.png b/docs/unit-sprite-current-approval-board.png index f0f3748..29a16b5 100644 Binary files a/docs/unit-sprite-current-approval-board.png and b/docs/unit-sprite-current-approval-board.png differ diff --git a/docs/unit-sprite-next-steps.md b/docs/unit-sprite-next-steps.md index 124f2b8..def3866 100644 --- a/docs/unit-sprite-next-steps.md +++ b/docs/unit-sprite-next-steps.md @@ -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. diff --git a/docs/unit-sprite-review-approved-and-pending.png b/docs/unit-sprite-review-approved-and-pending.png index cc7a9d3..a60e829 100644 Binary files a/docs/unit-sprite-review-approved-and-pending.png and b/docs/unit-sprite-review-approved-and-pending.png differ diff --git a/docs/unit-sprite-review-batch2-shu.png b/docs/unit-sprite-review-batch2-shu.png index 41d7e86..e3e95b2 100644 Binary files a/docs/unit-sprite-review-batch2-shu.png and b/docs/unit-sprite-review-batch2-shu.png differ diff --git a/docs/unit-sprite-review-index.png b/docs/unit-sprite-review-index.png index 7217276..7f7e261 100644 Binary files a/docs/unit-sprite-review-index.png and b/docs/unit-sprite-review-index.png differ diff --git a/docs/unit-sprite-rework-audit.json b/docs/unit-sprite-rework-audit.json index 791d023..f431fca 100644 --- a/docs/unit-sprite-rework-audit.json +++ b/docs/unit-sprite-rework-audit.json @@ -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" } }, { diff --git a/docs/unit-sprite-rework-audit.md b/docs/unit-sprite-rework-audit.md index b20d6d5..7ec7c0c 100644 --- a/docs/unit-sprite-rework-audit.md +++ b/docs/unit-sprite-rework-audit.md @@ -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 | diff --git a/docs/unit-sprite-rework-manifest.json b/docs/unit-sprite-rework-manifest.json index eb2fad7..f6f6c2c 100644 --- a/docs/unit-sprite-rework-manifest.json +++ b/docs/unit-sprite-rework-manifest.json @@ -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" diff --git a/docs/unit-sprite-shu-archer-v5-approval-packet.md b/docs/unit-sprite-shu-archer-v5-approval-packet.md new file mode 100644 index 0000000..969bfeb --- /dev/null +++ b/docs/unit-sprite-shu-archer-v5-approval-packet.md @@ -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. diff --git a/scripts/assemble-handpaint-shu-infantry-quality-sample.py b/scripts/assemble-handpaint-shu-infantry-quality-sample.py index 1b9a02f..41e3d39 100644 --- a/scripts/assemble-handpaint-shu-infantry-quality-sample.py +++ b/scripts/assemble-handpaint-shu-infantry-quality-sample.py @@ -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") diff --git a/src/assets/images/units/unit-shu-archer-actions.png b/src/assets/images/units/unit-shu-archer-actions.png index 6cc573c..01e5f0a 100644 Binary files a/src/assets/images/units/unit-shu-archer-actions.png and b/src/assets/images/units/unit-shu-archer-actions.png differ diff --git a/src/assets/images/units/unit-shu-archer.png b/src/assets/images/units/unit-shu-archer.png index fe4dc66..e9db5b1 100644 Binary files a/src/assets/images/units/unit-shu-archer.png and b/src/assets/images/units/unit-shu-archer.png differ