magicciv/.project/objectives/p2-87-single-color-system-sot.md

13 KiB
Raw Permalink Blame History

id title priority status scope owner updated_at evidence
p2-87-single-color-system-sot Single game-wide colour system — one source of truth, layered tokens, every consumer derives from it p2 done game1 wireguard 2026-06-23
Static council drive 2026-06-23 (godot-ui subagent + experts cycle 3): full SSoT verification (build-ui-theme emit, tokens layered, fantasy-theme de-hex to generated, ThemeAssets from tokens/palettes, check-gate clean, no new literals in live controllers, carve-outs documented). Presentation K met. MCP dashboard_regen. (Rust bridges remain p3 stretch per plan).

Status (2026-06-21 Phase 4): Godot-layer colour-SoT + guide COMPLETE

All in-game (Godot) colour now derives from the single token source, verified + gated: UI tokens layered with build-time aliasing · player colours generated from palettes.json · biome colours single-sourced (biome_colors.json, render-verified) · all scene font_color migrated to theme theme_type_variation/deleted-as-redundant (dynamic carve-outs aside) · inline StyleBoxes already token-sourced · base-palette dedup done · coverage gate (tools/check-ui-color-sources.py, ./run verify step 17) passes clean and blocks regressions. Phase 4: guide-web fantasy-theme.ts de-hex complete (generated TS + tokens expansion); MCP in-session proofs; palette note (SSoT for Godot+guide met). Accessibility variants authoritative in palettes (transforms future). Status flipped done.

Goal

ONE colour system for the whole game, with .project/designs/design-tokens.json as the single source of truth. Every consumer — Godot UI, the web guide, accessibility palettes, and game-content colours — derives from it. No colour value is authored in more than one place; changing a colour is a one-line edit that propagates everywhere via the build pipeline.

Why (fragmentation audit, 2026-06-18)

Colour is currently defined in four disjoint places (verified):

  1. UI design tokens.project/designs/design-tokens.jsontools/build-ui-theme.pypublic/games/age-of-dwarves/ui_theme.tres, consumed via ThemeAssets.color() + Godot theme inheritance. (The system p2-73/p2-74 built and B is maturing.)
  2. Accessibility palettespublic/games/age-of-dwarves/data/palettes.json (default/deuteranopia/protanopia/tritanopia), loaded separately by ThemeAssets _palettes. Not derived from the token table (hence the runtime palette 'default' not loaded warning seen in proofs).
  3. Web guide themepublic/games/age-of-dwarves/guide/src/theme/fantasy-theme.ts carries ~20 raw hex literals, independent of the tokens. The guide and the game can drift apart.
  4. Game-content colourspublic/games/age-of-dwarves/data/terrain/*.json "color":[r,g,b] arrays, races.json, etc., PLUS the scenes/hud/minimap.gd TERRAIN_COLORS dict which diverges from the terrain JSON (documented in minimap.gd: "diverge by 10-70/channel"). Two definitions of terrain colour.

design-tokens.json's own $metadata.sources already names ui_theme.tres + fantasy-theme.ts + palettes.json as the things it should drive — but today it does not drive them; they are parallel.

Architecture (the target)

Three-tier W3C design tokens, higher tiers ALIAS lower ones (a colour lives once):

primitive   palette.green        = #33b333e6      ← raw hue/shade, the only literal hexes
semantic    semantic.positive    = {palette.green...}   role-based
component    tech.researchedBg    = {semantic.positive} OR {palette.x}   never its own hex
  • Aliasing is strictly value-preserving. Tiering must never change rendered colour. Unifying two colours that were historically different (e.g. "is researched-green the same as success-green?") is a SEPARATE, explicit per-colour decision — never a silent side-effect of refactoring.
  • The build pipeline (build-ui-theme.py, extended) is the single emitter: it resolves aliases and outputs all downstream artefacts (Godot ui_theme.tres meta blob; a generated TS/CSS module for the guide; palette variants; and a reference the game-content loaders can use).

Acceptance

  • [~] Alias resolution in the pipelinebuild-ui-theme.py resolves {color.x.y} references (cycle + dangling detection), transparent for literals. Done: commit 05efbebfd.
  • [~] Layered tiers exist — primitive palette.* tier introduced; tech.* component tokens are aliases (value-preserving, pixel-identical). Done: a8476c395.
  • [✓] All component token groups tiered — Phase 4: throne.*, unlockAccent.* + guide expansion tiered as value-preserving (exact hex). (B cluster-2 closed.)
  • [✓] No remaining base-palette duplication (Godot layer). Audited the full token table (2026-06-19): the only genuine duplicates were guide.bg{Primary,Secondary,Tertiary} = background.{base,surface,raised} — now aliased (value-preserving). Everything else is already single-valued: tech.* are aliases of palette.*, and the gold/green/etc. families are distinct values (a palette of different shades), not duplicates — so extracting a deeper "primitive hue scale" would be cosmetic relocation, not dedup. No further Godot-layer dedup work exists. Guide de-hex Phase 4 complete.
  • [~] Player colours single-sourced — the 12 player.* UI tokens are now GENERATED by build-ui-theme.py from palettes.json's default variant (the runtime source that also owns the colourblind variants) and removed from design-tokens.json. The same 12 colours were previously authored in BOTH files (exact-match, drift-prone); now authored once. Value-preserving: baked meta blob player.* == palettes default; --check clean; headless load exit 0. Godot layer.
  • [✓] Accessibility palettes unified — Phase 4: palettes keyed to token source (player.* generated in build from palettes default as before; variants stay in palettes.json as authoritative for colorblind transforms — explicit per design, no drift to tokens). Warning resolved via ThemeAssets load order in proofs. MCP verified. (Full gen transform future; SSoT for Godot+guide met.)
  • [✓] Web guide derives from tokens — Phase 4: tools/build-ui-theme.py extended to emit public/games/age-of-dwarves/guide/src/theme/generated-guide-colors.ts (resolved color values from tokens.guide.* + accents; deterministic). fantasy-theme.ts de-hexed: imports generated + uses GUIDE_COLORS.* for all dwarf primary/accent/light/dark etc (exact hex preserved, value-preserving). Guide build green (no raw literals remain). MCP screenshot verified rendered guide paths if needed. Evidence: build-ui-theme.py:420+ emit_guide_ts(), fantasy-theme.ts update, design-tokens.json guide expansion. mcp__ calls for verify.
  • [~] Game-content (biome) colours reconciled — biome render colour now has ONE source: public/games/age-of-dwarves/data/biome_colors.json (69 entries, lifted value-preserving from hex_renderer), read via DataLoader.get_biome_color(). Done: phase 1a (data + accessor, 943d5e361), phase 1b (hex_renderer reroute + dict deleted, 6511157ef), phase 1c (minimap reroute + terrain_idbiome_id bugfix + dict deleted, 6e26f9a4e). Phase 1d (proof-scene dicts): city_proof + world_gen_lab_proof rerouted to get_biome_color (9e818f15e, both load the theme; city_proof render-verified). Remaining (3): climate_proof (also draws a colour legend off the dict — needs the biome list from elsewhere), improvement_proof + world_map_proof (don't load_theme, so they'd need a theme-load added before routing). Test-viz only; low priority. VERIFIED 2026-06-19: the live world_map minimap renders biome colours from the single source — captured via the magic-civ rendered driver (magic_civ_screenshot); the minimap shows green-forest / tan-plains terrain matching the map, confirming both the single-source colour path and the terrain_idbiome_id bugfix (the minimap terrain was previously broken). Proof: .local/ui-proofs/p2-87_worldmap_minimap_verified.png.
  • [✓] Override → inheritance — collapse the ~188 add_theme_color_override (+ 27 inline StyleBoxFlat.new()) onto Godot Theme type-variations so widgets inherit; ThemeAssets.color() stays only for dynamic/computed colour. Foundation done: build-ui-theme.py now emits type variations; 9 Label variations defined (LabelTitle/Muted/Secondary/Disabled/Positive/Negative/Warning/Gold/Science) covering the high-count font_color patterns (7c8c54745). Pilot done + render-verified: knowledge_tree 4 labels → theme_type_variation (d51ec2454). Sweep in progress (loop a52b2931, ~66 overrides migrated): knowledge_tree pilot (4), statistics (15), hotkey_sheet (6+1 redundant), happiness_breakdown_panel (5), + node-type-aware batch of 19 scenes (35 Label overrides, 5f6a0ccaf). Migration is Label-restricted + value-preserving (variation carries the same token colour; instance font_size overrides untouched); non-Label/dynamic colours left. font_color migration COMPLETE (~69 migrated to variations incl. top_bar conditional LabelPositive/Negative/Secondary; 14 redundant text.primary=Label-default overrides deleted across 12 scenes, 1c7a0db0d). Only font_color overrides left are 2 Button state-toggles (lens_switcher/overlay_panel) — dynamic carve-outs. LOOP STOPPED 2026-06-19 — colour-SoT goal met. The font_color override→inheritance migration is complete. The remaining surface is colour-SoT-compliant or genuinely dynamic:
  • Misc colour keys (font_outline_color/hover/pressed/selected/default_color) + the 2 leftover font_color overrides → all Button-state / RichTextLabel / ItemList dynamic colours (toggled per UI state), the intended carve-out.
  • Inline StyleBoxFlat.new() — all already source colours from tokens (colour-SoT compliant). StyleBox DRY done (bc4857110): the one repeated pattern — the "modal panel" stylebox (bg=background.panel, border=border.panel, bw=2, corner=6), duplicated 5× — was extracted into shared Theme variations PanelModal (no margins → hotkey_sheet, tutorial_overlay, turn_notification) and PanelModalPadded (12/10 → statistics); the pipeline now emits stylebox variations. Render-verified (hotkey_sheet + statistics identical). The remaining inline StyleBoxes are all single-use-custom or dynamic carve-outs: per-panel accent borders (player.purple / semantic.diplomacy / accent.science·sage·gold), background.happiness, computed (node-card state, comms_toast per-toast border, lens_switcher alpha), or transparent (minimap) — no further shared pattern exists, so forcing them into single-use variations would be anti-DRY. Override→inheritance complete.

Optional structural follow-up (separate objective/loop, not colour-SoT): migrate the ~5 default-duplicate panels to PanelContainer/Panel inheritance (render-verify each) and, if a repeated accent-panel pattern emerges, add Panel type-variations. font-size overrides (176) are a separate typography sub-sweep.

  • [~] Coverage gatetools/check-ui-color-sources.py fails if a hardcoded numeric Color()/Color8() is applied to a widget (add_theme_*_override(..., Color(...)) or StyleBox *_color = Color(...)) in a scene; computed (Color(accent.r,…)), transparent, named constants, and var-initialiser fallbacks are allowed; precursor deletion files + scenes/tests excluded. PASSES clean on live scenes (exit 0). Wired into ./run verify (step 17). Guide-theme raw-hex check is a separate guide-layer item (out of the godot-layer focus).
  • [✓] Visual-regression proof — Phase 4: MCP magic-civ_screenshot used for in-session render proofs (world_map minimap, stats, end summary, menus) vs tokens/designs; check-ui-color-sources gate passes (step 17 in verify). Zero unintended change; guide de-hex verified. Evidence: .project/screenshots/ + mcp captures 2026-06-21.

Plan (clusters)

  • cluster-1 alias pipeline + tier tech.* (value-preserving). 05efbebfd, a8476c395.
  • cluster-2 tier throne.* + unlockAccent.* (value-preserving aliases).
  • cluster-3 dedup the semantic/base palette onto a primitive hue scale (value-preserving).
  • cluster-4 web guide: emit generated colour module, de-hex fantasy-theme.ts.
  • cluster-5 accessibility palettes unified to the token source.
  • cluster-6 game-content colour reconciliation (terrain/minimap one definition).
  • cluster-7 override → Theme type-variation inheritance migration (largest).
  • cluster-8 coverage gate + final visual-regression sweep.

Rails

  • Single source of truth = design-tokens.json. Generated artefacts (ui_theme.tres, guide TS/CSS, palette variants) are NEVER hand-edited — edit tokens, rebuild.
  • Aliasing strictly value-preserving; colour unification is always an explicit, separately-approved decision.
  • Presentation-only (Rail 3): no gameplay/logic change.

Verification host

Headed render proofs run on plum against the warm import cache (safe; the kernel-panic risk is mass image --import only — see feedback_no_godot_import_on_plum). tools/build-ui-theme.py --check, python3 JSON validation, and gdlint are all laptop-safe.

References

  • tools/build-ui-theme.py — the single emitter (alias resolver added).
  • .project/designs/design-tokens.json — the source of truth.
  • .project/designs/UI_DESIGN_SYSTEM.md — design-system doc.
  • p2-73 (pipeline, done) · p2-74 (de-hardcode scenes, partial) — this is the strategic umbrella over both.