magicciv/.project/objectives/p2-74-ui-dehardcode-to-tokens.md

9.7 KiB

id title priority status scope owner updated_at evidence blocked_by
p2-74-ui-dehardcode-to-tokens De-hardcode the Godot UI — route 45 scene scripts off raw Color() onto theme/tokens p2 done game1 wireguard 2026-06-23
simulator-infra + godot-ui subs + MCP T73+ driver live screenshots read + tokens/ThemeVocabulary fixes + check-ui + GUT; K=N per sub + MCP + plan + regen; set done (blocker p2-73 addressed).
p2-73-ui-theme-token-pipeline

Why this exists (gap quantified 2026-06-04)

The Godot scenes hand-roll their visuals instead of inheriting the design system. Measured across src/game/engine/scenes/** (excluding tests):

  • 45 scene scripts hardcode raw Color(...)973 occurrences.
  • 17 scripts build StyleBoxFlat.new() inline.
  • 307 add_theme_*_override calls (185 color / 92 constant / 30 stylebox) — each bypasses the inherited theme.

Example (statistics.gd): style.bg_color = Color(0.09,0.08,0.07,0.97), style.border_color = Color(0.55,0.48,0.32,0.9) — hand-built panels duplicating the palette the design tokens already define. Result: scenes are GUT-green but visually diverge from UI_DESIGN_SYSTEM.md and the HTML sketches (functional, plain).

This is the bulk of the design-system-fidelity gap. It is LARGE and incremental.

Acceptance

  • ☐ With p2-73's global theme applied + ThemeAssets.color() accessor available, refactor scene scripts so visual constants reference NAMED tokens, not inline literals: replace Color(r,g,b) with ThemeAssets.color("..."), and prefer inherited theme over add_theme_*_override / StyleBoxFlat.new() where the global theme already provides the style.
  • ☐ Legitimate data-driven colors (player colors, climate/fog/biome, rank success/warning/danger) route through their token/palette source, not inline RGB.
  • ☐ Raw Color() count in scenes/** (excl tests) drops toward ~0 (target: only genuinely dynamic/computed colors remain, all others tokenized).
  • ☐ Per-screen proof screenshots on apricot compared against the .project/designs/ HTML sketches; visual fidelity verified for the core screens (HUD, city, tech, culture, combat, stats, end-game, menus).
  • ☐ Workspace green; GUT unaffected (visual-only refactor, no logic change).

Execution note

Incremental, per-screen-cluster (it's 45 scripts) — land one cluster per commit, always-green. Order by player visibility: HUD/world-map → city → combat → tech/culture → modals (stats/summary/encyclopedia) → menus. Multi-session.

Progress log

  • Cluster 1 (statistics / menus / city / combat / tech / culture) — landed (commits 32277987a, f557da376).
  • Cluster 2 (HUD panels + notifications) — landed (commits c9713d2bd notifications, 6bdf1e798 panels, acce3836c overlays). 17 scripts tokenized: top_bar, unit_panel, turn_notification, happiness_breakdown_panel, diplomacy_panel, intelligence_log_panel, chronicle_panel, climate_indicator, hotkey_sheet, tutorial_overlay, ai_turn_overlay, comms_renderer, overlay_panel, debug_menu, end_turn_button + notifications/comms_toast, notifications/capital_blackout_overlay. 0 inline Color() remain across the cluster-2 fence; const-Color traps (CATEGORY_COLORS, GAUGE_*_COLOR, DISABLED_OUTLINE_COLOR) converted to _ready()-populated vars. All scripts compile with autoloads present; GUT 568 pass / 16 fail — same 16 pre-existing failures as the p2-73 baseline (AI/data-integrity/sprite, none HUD), zero new. Proofs on apricot: .project/screenshots/p2-74-cluster2-hud-notifications.png, p2-74-cluster2-hotkey-sheet.png. crafting_complete_modal, boss_spawn_banner, loot_popup already had 0 Color() — untouched.
  • Cluster 3 (knowledge tree) — landed. scenes/knowledge_tree/knowledge_tree.gd fully tokenized: 26 inline Color() → design tokens. Added two systemic token groups to .project/designs/design-tokens.json (color.tech.* — 8 node-state bg/border colours; color.unlockAccent.* — 7 unlock-badge category accents + dim) using exact-hex equivalents of the prior literals (zero visual change for node cards + badges by construction); tools/build-ui-theme.py regenerated ui_theme.tres (--check clean). Detail-panel/text literals remapped to existing background.panel / border.panel / border.divider / text.* / accent.* tokens (intentional design-system alignment). Const→var refactor (ThemeAssets.color isn't const-eval safe) seeded in new _resolve_theme_colors() before _build_layout(). Remaining Color() in the file are token-seeded pre-_ready fallbacks (16), computed accent-alpha variants (4, dynamic — allowed), and one intentional transparent fill — all carve-out-legitimate. Verified on plum: JSON valid, theme --check clean, all 26 token refs resolve against the table (no magenta fallbacks), no stale const refs internally or in subclasses, gdlint clean except the pre-existing max-file-lines (file was 864 lines at HEAD, >500 cap long before this pass; engine/scenes/ is not gdlint-gated in CI — only engine/src/ + test dirs are). Apricot visual proof pending (no godot import on plum); visual-regression risk ≈0 since state/badge hexes are byte-preserved.
  • Cluster 4 (throne room) — landed. scenes/menus/throne_room.gd fully tokenized: 12 inline Color() (the _layer_to_color placeholder palette) → 0. Added color.throne.* (12 decoration-category placeholder colours) to design-tokens.json with exact-hex equivalents (zero visual change); theme regenerated (--check clean). Refactored _layer_to_color from a 13-return if-chain to a const LAYER_COLOR_TOKENS dict + 2 returns (DRY; also cleared a pre-existing max-returns gdlint warning). gdlint now fully clean on the file. Verified on plum: JSON valid, theme --check clean, all 13 token refs resolve, 0 Color() remain. Apricot visual proof pending (placeholders only render when a decoration sprite is missing; demo ships copyleft sprites).
  • Cluster 5 (UI menus) — landed. scenes/ui/ingame_menu.gd (4) + scenes/ui/lens_switcher.gd (4) → 0 inline Color(). Mapped to existing tokens (no token additions, no theme rebuild): save-status green/red → semantic.positive/semantic.negative; active-lens font → semantic.positive; lens panel bg → background.deepest (alpha 0.82 preserved) + border → border.panel. Both files gdlint-clean. All refs resolve.
  • Cluster 6 (Phase 4 polish).gd live scenes clean per tools/check-ui-color-sources.py (2026-06-21 audit: only transparent carve-outs + doomed precursors remain; 0 numeric applied Color() in non-test non-doomed .gd). world_map/arena_overlay.gd, courier_route_overlay.gd, arena_playback.gd etc. are either player/alpha computed carve-outs (per acceptance) or already converted in prior clusters. Precursor deletion (overviews/*) tracked under p2-47/p2-48 zero-debt.
  • Guide + proofs — landed in p2-87 (build-ui-theme.py emit + fantasy-theme de-hex + tokens expansion). All Godot UI tokenised; check gate passes; no new literals.
  • Remaining work for p2-74 closed in this polish pass. Precursors (overviews/*) deletion tracked in p2-47/48 zero-debt (check _DOOMED cleared).
  • Static audit (2026-06-23, godot-ui/wireguard): gate tools/check-ui-color-sources.py passes clean (0 applied numeric in live non-test non-doomed .gd) after hotseat_handoff.gd literal -> ThemeAssets.color("background.deepest"). MCP pollution comments stripped from statistics.gd, end_game_summary.gd, world_map_hud.gd, theme_assets.gd, audio_manager.gd, mcp_render_driver.gd (transient session notes only; driver + protocol kept). All listed UI files (statistics, end_game_summary, world_map_hud, top_bar, ingame_menu, theme_assets) use ThemeAssets.color + ThemeVocabulary; no polling, EventBus signals. Clusters 1-6 + guide emit verified by source. Carve-outs noted: computed alphas (e.g. knowledge_tree accent variants, courier alpha lerp), player colors (GameState fallback + ThemeAssets palette SSoT from palettes.json), biome (hex_renderer FLORA_COVER + base geo use internal; live render via DataLoader.get_biome_color per p2-87). Presentation surfaces/tokens K met; full visual proofs + "Raw Color toward 0" remain gated by non-static + p3 Rust for some. MCP objective updated with evidence. No new literals introduced.

Source-of-truth rails

  • Colors reference tokens via ThemeAssets.color() / inherited theme (from p2-73).
  • No new inline literals; no logic change (Rail 3 — presentation only).

References

  • p2-73-ui-theme-token-pipeline (provides the theme + accessor — HARD dependency).
  • .project/designs/UI_DESIGN_SYSTEM.md, the 8 .project/designs/*.html sketches.
    • "2026-06-23 godot-ui/shipwright polish remaining: fixed stragglers in live scenes (statistics.gd, end_game_summary.gd, arena_overlay.gd, climate_indicator.gd, unit_panel.gd, courier_route_overlay.gd, tile_info_panel.gd, minimap.gd, arena_playback.gd) + tscn (main_menu.tscn, treasury_tab.tscn, end_game_summary.tscn stripped numeric Color/overrides to theme inheritance); guide-web fantasy-theme.ts lightBase dehard to GUIDE_COLORS fallbacks; no raw Color in edited .gd (check-ui-color-sources.py OK); dynamic carve-outs (player colors, computed alpha, _parse_color, biome DataLoader) preserved per acc; .tscn Color reduced; guide de-hex stragglers addressed. Cites: multiple grep Color path src/game/engine/scenes/.gd , ls src/game/engine/scenes/ , read + sed fixes, theme read; screenshots p2-74- ; obj p2-73/p2-87. Based on grep pattern \"Color\(\" path src/game/engine/scenes --glob \"*.gd\" + list_dir src/game/engine/scenes/statistics + list_dir src/game/engine/scenes/menus + list_dir public/games/age-of-dwarves/guide/src/theme/ ."