4.8 KiB
4.8 KiB
| id | title | priority | status | scope | owner | updated_at | evidence | blocked_by | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| p2-62 | Procedural unit/building renderer — alpha-only visual substitute | p2 | done | game1 | asset-sprite | 2026-05-04 |
|
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.
The procedural renderer and the sprite pipeline will coexist behind a boot-time toggle (MC_USE_PROCEDURAL_SPRITES=1) so designers can A/B them when sprite assets land.
Acceptance
- ✓
src/game/engine/src/world/procedural_renderer.gd(canonical layout usessrc/, notscripts/) exposesrender_unit(unit_id) -> Texture2Dandrender_building(building_id) -> Texture2D(thin wrappers at lines 79-86) over the broadermake_unit_texture / make_building_texture / make_wonder_texture / make_city_texturesurface (lines 91-153). Output keyed deterministically offidvia_stable_hash(line 158):hash(s) & 0x7fffffff. Race colour pulls frompublic/resources/races/<race_id>.jsonwith an 8-colour fallback palette when race is unknown. - ✓
MC_USE_PROCEDURAL_SPRITESresolved viaOS.get_environmentinis_force_procedural()(procedural_renderer.gdlines 64-69) and consulted by the renderer's wiring point inunit_renderer_draw.gd::cache_unit_sprites(lines 173-178) andget_unit_sprite(lines 200-211). When1the authored sprite path is skipped entirely; when unset/0, authored sprites win and procedural is the missing-asset fallback. NoEntityVisualRegistryshim was introduced — the codebase has no such registry; the integration point is the existing UnitRenderer cache hook, documented inengine/docs/PROCEDURAL_RENDERER.md. - ✓ Distinct silhouettes per role classified by
unit_idsubstring (_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 viaImage.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
- Rust crate: none — this is presentation-only and explicitly carved out from Rail 3 because no simulation logic is involved. Procedural seed comes from the entity's
idstring hashed in GDScript. - JSON path: reads
public/resources/units/*.jsonandpublic/resources/buildings/*.jsonfor theidand (optionally) acategoryhint to pick a visual family. No new authored data. - mc-core wrapper: not applicable — render is GDScript-internal.
Out of scope
- Sprite-asset pipeline (
p2-22..p2-27) — that's the eventual replacement; both can coexist. - Animation — static texture only.
- City/wonder vignettes — flat token render only.
References
public/games/age-of-dwarves/docs/UI_DESIGN.md- Replaced-by (eventually):
p2-22,p2-23,p2-24,p2-25,p2-26,p2-27