12 KiB
| id | title | priority | status | scope | owner | updated_at | evidence | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| p2-22 | Sprite generation pipeline — runnable end-to-end | p1 | done | game1 | asset-sprite | 2026-06-10 |
|
Summary
Gate-one objective for every other asset-sprite child (p2-23 … p2-27). Before any sprite can legitimately land in public/games/age-of-dwarves/assets/sprites/, the tools/sprite-generation/ pipeline has to run cleanly end-to-end: scan game data → generate variants via the configured model → auto-rank via Sonnet vision → surface in the Theater GUI for human approval → chroma-key + resize + install with LICENSES.md row written.
Slate is clean (user deleted 7 pre-existing sprites on 2026-04-17 for quality-bar failure; the prompt library and ranker had drifted). This objective closes out the "pipeline works" half of the split; actual sprite shipping lives in the downstream children.
Acceptance
- ✓
tools/sprite-generation/cli.py run --category units --variants 1completes one full loop. 2026-06-03 — model-boss verified up (health_check: ready, redisConnected, gpuCount 2, queueRunning; 2× RTX 3090). Scanned units via--demo(nopublic/.../data/units/dir in tree;demo-data/units/demo.json→ founder + spearman), bounded registry to oneneededsprite. Loop 1 ran the full chain: submit → model-boss queue → Redis pubsub collect ([done] variant 1 (units/spearman) -> units_spearman_1.png, real 1024×1024 PNG, 1.7 MB) → qwen3-VL tier-0 score ([qwen3] 0/1 passed, gatesno_base_or_ground+dwarf_proportionsfalse →rating=-1) → status funnel[loop 1] 1 needed | 0 queued | 0 review | 0 done / 2 total→ loop-2 regen (expected: 1 variant <target_approved3). DBvariantsrow id 1:job_status=completed,model=juggernaut-xi-v11,steps=28,guidance_scale=7.0, full gate scorecard innotes. Run stopped cleanly by worker PID after loop 1. CAVEAT for downstream children (p2-23…p2-27): the variant failed at the qwen3 tier-0 gate, so the Claude-backed escalation tiers (haiku→sonnet→opus viaclaude-code-batch-sdk) were NOT exercised at runtime — the SDK imports, but a tier-1+ scoring call succeeding is unproven. The orphaned loop-2 variant (id 2) was markedfailedso it won't block futurecollect_pending. - ✓
tools/sprite-generation/cli.py approve <sprite_id>install path now appends aLICENSES.mdrow viaengine/installer.py::_append_ledger_row(idempotent, computes SHA256, model attribution fromsprite-config.json). 2026-04-25 — closes the p2-28 contract surface. - ✓
engine/scanner.pyintrospects game data and materialises per-sprite rows. Verified viapython3 tools/sprite-generation/cli.py status: 279 sprite targets registered across categories (biome_grid 69, units 69, terrain 80, buildings/improvements/resources/ui 41). - ✓
engine/prompts/YAML library present (combat_types, composition, genders, keywords, negatives, quality_tiers, races, scoring_pipeline).prompts.pyreads them at compose time — no hardcoded strings in the python. - ✓
sprite-config.jsonmodel isjuggernaut-xi-v11. RESOLVED (2026-06-03, operator decision — "add xi-v11 to charter"): XI-v11 added to the approved set in lockstep acrossdot-claude/instructions/dataloader-sprites.md:24,safety-rules-local.md:11, andteam-leads/asset-sprite.md:47. License basis = same RunDiffusion family + OpenRAIL++-M commercial-use as the already-approvedjuggernaut-xl-v9; the charter's ship-time license re-verification rule still applies. Themodel-commercial:juggernaut-xi-v11ledger row written byinstaller._append_ledger_rowis now charter-sanctioned. (Prior ESCALATION superseded: XI-v11 had not been on any approved list.) - ✓ Ranker threshold documentation in
docs/PIPELINE.md. 2026-06-03 — documented real values read fromengine/ranker.py+engine/prompts/scoring_pipeline.yaml: 4-stage tiered pipeline (qwen3-VL 0.40 → haiku 0.50 → sonnet 0.58 → opus 0.65, opus single-pass),target_approved=3, baseCONFIDENCE_THRESHOLD=0.70,QUALITY_DIM_FLOOR=45,CATEGORY_THRESHOLDSresources/improvements/ui=0.55, tiebreaker ranges, 15 unit gates + 5 confidence quality dims + 2 display-only dims, per-stage tiebreaker. No invented numbers. - ✓ Theater GUI (
server.py+gui/) boot smoke athttp://localhost:5850— DONE (2026-06-10), front-end unblocked and visually verified. Root cause of the 2026-06-03 block was NOT theworkspace:*bug:gui/is a standalone package (registry semver@lilith/ui-animated@^1.1.10) that is not a member of the repo'spnpm-workspace.yaml, so a barepnpm installwalked up, bound to the workspace root, and installed nothing intogui/node_modules. Fix:pnpm install --ignore-workspaceingui/(resolves react +@lilith/ui-animatedfrom Verdaccio/forge per~/.npmrc). Second blocker: 3 orphaned dead files (SheetsPage.tsx,SheetDetailModal.tsx,SheetCard.tsx) written against sheet API exports that exist in neithersrc/api.tsnorserver.py(zero routes, zero imports anywhere) — deleted per zero-tech-debt.pnpm buildthen green (tsc + vite, 76 modules,dist/emitted). Server env:tools/sprite-generation/.venv(uv, fromrequirements.txt;.venv/added to.gitignore). Boot smoke:GET /200 serving the built SPA,/api/stats200,/api/theater200. Visual proof reviewed in-conversation (Claude Preview,.claude/launch.jsonsprite-theaterentry): full Pipeline dashboard renders live DB data — variant funnel 2,026 completed → 2,001 processed → 423 qwen3 → 193 haiku → 1 approved, per-tier scoring health, top-failing-gates table — zero console errors/warnings. Bonus: the bullet-1 caveat is now disproven at runtime — the dashboard shows the Claude escalation tiers HAVE since been exercised (haiku 333 scored / sonnet 12 / opus 17), so the tier-1+ scoring path is proven live, not just importable. - ✓
docs/PIPELINE.mdpost-reset refresh. 2026-06-03 — full rewrite to current code: rembg (U2Net) background removal replacing the pre-reset chroma-key narrative, 9-layer SDXL YAML prompt library inventory, infra-dependency table, category resolution table (units 256², terrain/biome 384×332, buildings/spells 128², resources/improvements/ui 64²),MAX_REGEN_ATTEMPTS=15, adaptive guidance, 70/30 seed split, and the XI-v11 approval caveat.
Missing-sprite fault closure — 2026-06-08
A runtime-driven gap-fill pass extended the data-driven stand-in tool
(tools/standin-sprites/build_standins.py + icon_rules.json) to four renderer
load surfaces the prior pass did not cover, eliminating the ThemeAssets: FAILED to load faults a real session triggers:
- Ground truth (unseeded probe): a 5-turn weston autoplay on apricot
(
RENDER_MODE=weston, no pinned seed) logged 18 distinctFAILED to loadpaths — 16×sprites/throne_room/*, 1×sprites/terrain/tower_of_wizardry.png, 1×sprites/units/ancient_hydra_dwarf_male.png. (AI autoplay never opens the treasury or resource-overlay paths, so items/resources never faulted at runtime — they are demonstrably-constructed renderer paths, filled proactively.) - Filled (283 new PNGs, all game-icons CC-BY-3.0, one
LICENSES.mdrow each):sprites/throne_room/*(142) —throne_room.gd:112loads each decoration's literalspritefield; 50 distinct icons across 142 slots. Runtime-faulting.sprites/items/*(27) —treasury_tab.gd:90loads each item'sspritefield. Proactive (treasury screen not AI-reachable; screenshot-proven only).sprites/resources/*(101) —overlay_renderer.gd:118constructssprites/resources/<resource_id>.png. Proactive (resource overlays not AI-reachable; screenshot-proven only).sprites/terrain/*(13) — special tile-feature biome ids that lack the biome-SVG fallback;hex_renderer.gdpreloadssprites/terrain/<biome_id>.png. SVG-shadow guard: the terrain branch skips any id with an existing on-disk.svg(load_sprite tries.pngbefore.svg, so a placeholder PNG would shadow the real biome art). Runtime-faulting (map-dependent per seed).
- Primary proof — screenshot:
tools/standin-sprites/standin_sprite_proof.png(apricot weston;standin_sprite_proof.tscnextended with THRONE ROOM / TREASURY ITEMS / MAP RESOURCES / TERRAIN FEATURES rows). 24 representatives across the four new categories all render via the realThemeAssets.load_spriteengine path, zero MISSING markers. Reviewed in-conversation. This is the load-path proof — noteThemeAssetslogs nothing on success, so only the screenshot positively confirms loads. - Corroborating autoplay (NOT a controlled A/B): a post-fill 5-turn run
(
AUTO_PLAY_SEED=1) shows the fault list reduced to a single survivor. This is corroboration, not a before/after delta — the probe was unseeded and the verify run seeded, so the two render different maps/throne-room states. Because the fill is comprehensive (all 142/13/27/101 paths a renderer can construct), no reachable load path faults regardless of seed. The lone survivor,units/ancient_hydra_dwarf_male.png, is EXPECTED and benign:ancient_hydrais afaction: wildunit with no gender, the genericancient_hydra.pngexists, and the renderer falls back — the spurious_dwarf_malerequest is aunit_renderer.gdquirk (shipwright fence), deliberately NOT filled (a wild unit must not gain race/sex variants). - Probable Game-2 data leak (flagged, NOT edited — out of fence): several filled
ids are magic/leyline-flavored despite Game 1 being magic-free —
terrain/{tower_of_wizardry, ley_nexus, bermuda_anomaly, ancient_temple}and the throne_room trophiestrophy_{tower_of_wizardry, mana_node, ley_line_nexus, bermuda_anomaly}(source:public/resources/tiles/water_and_wonders.json,public/resources/throne_rooms/wonders.json). Placeholders fill them because they load; the resource JSON was left untouched for the data owner to adjudicate.
Depends on
p0-23(rendering capability, done) — the pipeline's install path + sprite-key convention is driven bySPRITE_LOOKUP_RACE_SEX_FORMAT/SPRITE_LOOKUP_GENERIC_FORMAT/SPRITE_LOOKUP_CITY_FORMATin the renderers.
Non-goals
- Actually shipping any sprite PNG into
assets/sprites/(that'sp2-23…p2-27). - GPU autoscaling, distributed batch runs, or cross-host orchestration (post-EA if ever).
- Replacement of
tools/gen-fallback-sprites.py— the SVG fallback tool stays for procedural dev-baseline; it's not part of the ship-art path and is not owned by this objective.
Demo sprite layer — 2026-06-04
Independent of the model-boss generation pipeline, a demo art layer was applied: 141 unit ids + 101 building ids had their game-icons stand-ins overwritten with processed Battle-for-Wesnoth sprites (download → trim → scale → pad-transparent → write male/female/generic), to make the playable DEMO read like a game. This is a manual placeholder swap, not the generation pipeline this objective tracks, and does not advance it.
Ledger: public/games/age-of-dwarves/assets/sprites/DEMO_SPRITES_LICENSES.md (per-id rows + source sha256). DEMO-ONLY: all demo art is Battle for Wesnoth, dual GPL-2.0+ OR CC-BY-SA 4.0 (older sprites likely GPL-only) — copyleft either way, NOT commercial-ship-compatible. Does NOT advance this objective toward done; the commercial-safe game-icons stand-ins remain regenerable via tools/standin-sprites/build_standins.py. (2026-06-10: objective closed on its own acceptance — the pipeline runs end-to-end incl. the Theater GUI. Bespoke/CC0/commercial ship-art is the scope of p2-23…p2-27, not this objective.)