diff --git a/.project/objectives/DASHBOARD_CATEGORIES.md b/.project/objectives/DASHBOARD_CATEGORIES.md index 2bd72c50..666d25b6 100644 --- a/.project/objectives/DASHBOARD_CATEGORIES.md +++ b/.project/objectives/DASHBOARD_CATEGORIES.md @@ -307,7 +307,7 @@ | [p2-59](p2-59-pioneer-escort-mechanic.md) | 🔴 stub | P2 | Pioneer escort mechanic — protection rules vs ambient encounters | [unassigned](../team-leads/unassigned.md) | 🔒 p2-58 | | [p2-60](p2-60-weather-lens-godot-ui.md) | 🔴 stub | P2 | Weather / observation lens switcher in the Godot HUD | [unassigned](../team-leads/unassigned.md) | 🟢 | | [p2-61](p2-61-observation-recording-gates-from-tech.md) | 🔴 stub | P2 | Bind mc-observation gate_bits to player tech state — recording gates per-field | [unassigned](../team-leads/unassigned.md) | 🟢 | -| [p2-62](p2-62-procedural-unit-and-building-renderer.md) | 🔴 stub | P2 | Procedural unit/building renderer — alpha-only visual substitute | [unassigned](../team-leads/unassigned.md) | 🟢 | +| [p2-62](p2-62-procedural-unit-and-building-renderer.md) | ✅ done | P2 | Procedural unit/building renderer — alpha-only visual substitute | [asset-sprite](../team-leads/asset-sprite.md) | 🟢 | | [p2-63](p2-63-mc-flora-biome-substrate-migration.md) | 🔴 stub | P2 | mc-flora generation: migrate biome filter to substrate_climate-aware path | [unassigned](../team-leads/unassigned.md) | 🟢 | | [p3-01](p3-01-courier-diplomacy.md) | ✅ done | P3 | Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units | [envoy](../team-leads/envoy.md) | 🟢 | | [p3-02](p3-02-hybrid-merged-structures.md) | ❌ missing | P3 | Hybrid merged structures — war_academy, assault_citadel, cavalry_corps, gunnery_corps | — | 🟢 | diff --git a/.project/objectives/DASHBOARD_COMPLETED.md b/.project/objectives/DASHBOARD_COMPLETED.md index 688cef33..2fa266ea 100644 --- a/.project/objectives/DASHBOARD_COMPLETED.md +++ b/.project/objectives/DASHBOARD_COMPLETED.md @@ -164,6 +164,7 @@ | [p2-54d](p2-54d-ai-tech-priority-from-visibility.md) | AI tech-priority bias from visible-but-gated luxuries + indicator decorations | — | [terraformer](../team-leads/terraformer.md) | 2026-05-01 | | [p2-56a](p2-56a-worker-category-types.md) | Worker category types — Sustenance / Construction / Wealth taxonomy | — | [unassigned](../team-leads/unassigned.md) | 2026-05-04 | | [p2-56b](p2-56b-expertise-tier-progression.md) | Expertise tier progression — 5-tier specialist XP ladder | — | [simulator-infra](../team-leads/simulator-infra.md) | 2026-05-04 | +| [p2-62](p2-62-procedural-unit-and-building-renderer.md) | Procedural unit/building renderer — alpha-only visual substitute | — | [asset-sprite](../team-leads/asset-sprite.md) | 2026-05-04 | ## P3 diff --git a/.project/objectives/README.md b/.project/objectives/README.md index 13a128ae..1037051b 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -16,9 +16,9 @@ |---|---|---|---|---|---|---|---| | **P0** | 0 | 0 | 0 | 0 | 0 | 43 | 43 | | **P1** | 1 | 13 | 3 | 6 | 1 | 48 | 72 | -| **P2** | 0 | 8 | 13 | 0 | 6 | 57 | 84 | +| **P2** | 0 | 8 | 12 | 0 | 6 | 58 | 84 | | **P3 (oos)** | 0 | 0 | 18 | 1 | 21 | 3 | 43 | -| **total** | **1** | **21** | **34** | **7** | **28** | **151** | **242** | +| **total** | **1** | **21** | **33** | **7** | **28** | **152** | **242** | @@ -26,7 +26,7 @@ | Team Lead | Remaining | |---|---| -| [unassigned](../team-leads/unassigned.md) | 29 | +| [unassigned](../team-leads/unassigned.md) | 28 | | [asset-sprite](../team-leads/asset-sprite.md) | 6 | | [shipwright](../team-leads/shipwright.md) | 5 | | [warcouncil](../team-leads/warcouncil.md) | 5 | @@ -95,7 +95,6 @@ | [p2-58](p2-58-ambient-encounter-rolls.md) | 🔴 stub | Ambient encounter rolls per tile moved — fauna_density × ecology_tier | — | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | 🟢 unblocked | | [p2-60](p2-60-weather-lens-godot-ui.md) | 🔴 stub | Weather / observation lens switcher in the Godot HUD | — | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | 🟢 unblocked | | [p2-61](p2-61-observation-recording-gates-from-tech.md) | 🔴 stub | Bind mc-observation gate_bits to player tech state — recording gates per-field | — | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | 🟢 unblocked | -| [p2-62](p2-62-procedural-unit-and-building-renderer.md) | 🔴 stub | Procedural unit/building renderer — alpha-only visual substitute | — | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | 🟢 unblocked | | [p2-63](p2-63-mc-flora-biome-substrate-migration.md) | 🔴 stub | mc-flora generation: migrate biome filter to substrate_climate-aware path | — | [unassigned](../team-leads/unassigned.md) | 2026-05-04 | 🟢 unblocked | | [p2-57b](p2-57b-consume-produce-edges.md) | 🔴 stub | Building consume/produce edges — stockpile coupled to unit quality | — | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | 🔒 p2-57a | | [p2-59](p2-59-pioneer-escort-mechanic.md) | 🔴 stub | Pioneer escort mechanic — protection rules vs ambient encounters | — | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | 🔒 p2-58 | diff --git a/.project/objectives/objectives.json b/.project/objectives/objectives.json index 2cfd1753..c2c23ba3 100644 --- a/.project/objectives/objectives.json +++ b/.project/objectives/objectives.json @@ -1,10 +1,10 @@ { - "generated_at": "2026-05-04T07:06:06Z", + "generated_at": "2026-05-04T07:11:16Z", "totals": { - "done": 151, + "done": 152, "in_progress": 1, "partial": 21, - "stub": 34, + "stub": 33, "missing": 7, "oos": 28, "total": 242 @@ -2167,10 +2167,10 @@ "id": "p2-62", "title": "Procedural unit/building renderer — alpha-only visual substitute", "priority": "p2", - "status": "stub", + "status": "done", "scope": "game1", - "owner": "unassigned", - "updated_at": "2026-05-03", + "owner": "asset-sprite", + "updated_at": "2026-05-04", "blocked_by": [], "summary": "" }, @@ -2801,7 +2801,7 @@ "remaining_by_lead": [ { "owner": "unassigned", - "remaining": 29 + "remaining": 28 }, { "owner": "asset-sprite", diff --git a/.project/objectives/p2-62-procedural-unit-and-building-renderer.md b/.project/objectives/p2-62-procedural-unit-and-building-renderer.md index cfb56109..7187d56d 100644 --- a/.project/objectives/p2-62-procedural-unit-and-building-renderer.md +++ b/.project/objectives/p2-62-procedural-unit-and-building-renderer.md @@ -1,17 +1,21 @@ --- id: p2-62 -title: "Procedural unit/building renderer — alpha-only visual substitute" +title: Procedural unit/building renderer — alpha-only visual substitute priority: p2 -status: stub +status: done scope: game1 -category: ui -owner: unassigned -created: 2026-05-03 -updated_at: 2026-05-03 +owner: asset-sprite +updated_at: 2026-05-04 +evidence: + - "src/game/engine/src/world/procedural_renderer.gd:79-153 (render_unit/render_building wrappers + make_*_texture surface)" + - "src/game/engine/src/world/procedural_renderer.gd:64-69 (MC_USE_PROCEDURAL_SPRITES toggle)" + - "src/game/engine/src/rendering/unit_renderer_draw.gd:173-215 (procedural fallback wiring)" + - "src/game/engine/tests/unit/test_procedural_renderer.gd:132-148 (20-id pixel-identical determinism gate; 16/16 tests pass on apricot)" + - src/game/engine/scenes/tests/procedural_renderer_proof.tscn (proof scene) + - "~/Desktop/magic_civ_p2-62_procedural_renderer.png (proof screenshot, 20 units / 10 buildings / 5 wonders / 5 cities)" + - src/game/engine/docs/PROCEDURAL_RENDERER.md (visual conventions doc) blocked_by: [] -follow_ups: [] --- - ## Context This exists so alpha doesn't block on sprite generation. The full sprite pipeline (`p2-22..p2-27`) authors hand/AI-generated sprites for every unit, building, wonder, and city; that work is non-trivial and currently deferred. This objective ships a **parametric procedural renderer** — deterministic shapes/colors/insignia derived from each entity's id — so the alpha is fully playable visually without blocking on asset production. @@ -20,11 +24,11 @@ The procedural renderer and the sprite pipeline will coexist behind a boot-time ## Acceptance -- ❌ `src/game/engine/scripts/world/procedural_renderer.gd` exposes `render_unit(unit_id) -> Texture2D` and `render_building(building_id) -> Texture2D`. Output keyed deterministically off `id` (hash-seeded shape primitives + color ramp). -- ❌ Boot resolves `MC_USE_PROCEDURAL_SPRITES` via OS.get_environment; if `1` (default in alpha) the procedural renderer is registered with `EntityVisualRegistry`. If `0`, the sprite-asset path (`p2-22..p2-27`) is used; both code paths coexist. -- ❌ Distinct visuals for Pioneer / Warrior / Archer / Worker / Wagon at a glance — silhouette + insignia + dominant color all driven by `id`. -- ❌ Buildings render with consistent footprints per category; wonders carry a distinguishing rarity halo. -- ❌ Headless GUT test renders 20 known unit ids twice and asserts pixel-identical output (determinism). +- ✓ `src/game/engine/src/world/procedural_renderer.gd` (canonical layout uses `src/`, not `scripts/`) exposes `render_unit(unit_id) -> Texture2D` and `render_building(building_id) -> Texture2D` (thin wrappers at lines 79-86) over the broader `make_unit_texture / make_building_texture / make_wonder_texture / make_city_texture` surface (lines 91-153). Output keyed deterministically off `id` via `_stable_hash` (line 158): `hash(s) & 0x7fffffff`. Race colour pulls from `public/resources/races/.json` with an 8-colour fallback palette when race is unknown. +- ✓ `MC_USE_PROCEDURAL_SPRITES` resolved via `OS.get_environment` in `is_force_procedural()` (`procedural_renderer.gd` lines 64-69) and consulted by the renderer's wiring point in `unit_renderer_draw.gd::cache_unit_sprites` (lines 173-178) and `get_unit_sprite` (lines 200-211). When `1` the authored sprite path is skipped entirely; when unset/0, authored sprites win and procedural is the missing-asset fallback. No `EntityVisualRegistry` shim was introduced — the codebase has no such registry; the integration point is the existing UnitRenderer cache hook, documented in `engine/docs/PROCEDURAL_RENDERER.md`. +- ✓ Distinct silhouettes per role classified by `unit_id` substring (`_classify_unit_role`, lines 174-198): warrior=sword, archer=bow+arrow, scout=triangle, worker=hammer, founder=house, wagon=body+wheels, cavalry=lance, siege=engine+barrel, naval=hull+mast, generic=seed dot pattern. Race colour drives the base disk; gender insignia (Venus/Mars/neutral glyph) sits in the top-left corner (`_paint_gender_insignia`, lines 348-360). Proof screenshot shows all 20 distinguishable at a glance: `~/Desktop/magic_civ_p2-62_procedural_renderer.png`. +- ✓ Buildings: trapezoid wall + triangle roof + door + 1-3 windows, roof colour by category from `BUILDING_CATEGORY_COLOURS` (8 categories, line 32). Wonders: 3 distinct shape families (tower / dome / ziggurat, line 142) plus an amber halo disk (`_paint_wonder_halo`, line 397) — visible behind every wonder in the proof screenshot. +- ✓ Headless GUT — `tests/unit/test_procedural_renderer.gd::test_twenty_unit_ids_pixel_identical_across_calls` (lines 132-148) renders 20 known unit ids twice (cache cleared between calls) and asserts pixel-identical bytes via `Image.get_data()` comparison. Full suite: 16/16 tests pass, 42/42 asserts on apricot Godot 4.6.2 headless — confirmed run on canonical checkout, isolated with `-gprefix=test_procedural`. ## Source-of-truth rails diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json index 0d8a3091..a9e6d6ac 100644 --- a/public/games/age-of-dwarves/data/objectives.json +++ b/public/games/age-of-dwarves/data/objectives.json @@ -1,12 +1,12 @@ { - "generated_at": "2026-05-04T07:05:22Z", + "generated_at": "2026-05-04T07:10:55Z", "totals": { - "oos": 28, "missing": 7, - "partial": 22, - "done": 149, + "done": 151, + "partial": 21, + "oos": 28, "in_progress": 1, - "stub": 34, + "stub": 33, "total": 241 }, "objectives": [ @@ -1434,10 +1434,10 @@ "id": "p2-11a", "title": "\"SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path\"", "priority": "p2", - "status": "partial", + "status": "done", "scope": "game1", "owner": null, - "updated_at": "2026-05-03", + "updated_at": "2026-05-04", "summary": "Unit has no serialize()/deserialize() methods — infusions, equipped_items, promo_ids, keywords and other typed arrays cannot round-trip through SaveManager. City.production_queue is a GDScript-side Array with no serialize path; the Rust-backed City.to_json() does not include it. These gaps were deferred from p2-10f, which narrowed its tests to the Player serialize surface only." }, { @@ -1974,10 +1974,10 @@ "id": "p2-62", "title": "\"Procedural unit/building renderer — alpha-only visual substitute\"", "priority": "p2", - "status": "stub", + "status": "done", "scope": "game1", - "owner": "unassigned", - "updated_at": "2026-05-03", + "owner": "asset-sprite", + "updated_at": "2026-05-04", "summary": "" }, {