Commit graph

538 commits

Author SHA1 Message Date
Natalie
ed3c836e2f docs(@projects/@magic-civilization): 📋 reopen Game-1 scope — 6 verified gap objectives (p3-19..24)
Game 1 was NOT actually complete; the green dashboard overstated it. Created
objectives for the verified, untracked gaps (all confirmed 2026-06-25 against the
code, both Rust + GDScript paths):
- p3-19 player→ecology feedback (over-harvest/hunt deplete live populations; extinction)
- p3-20 weather affects scouting (vision penalty, not just movement)
- p3-21 weather-driven migration (migration ignores weather today)
- p3-22 AI builds dedicated scouts (only frontier-seeks with idle military)
- p3-23 trade richness (luxury swaps only; no gold/strategic trades)
- p3-24 Rail-1 port of economy/happiness/climate per-turn glue logic from GDScript

Dashboard regenerated. (Systems I'd wrongly doubted — ecology engine ticking, AI
worker improvements, naval harbor-gating — are confirmed working; not reopened.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:45:20 -04:00
Natalie
58b608804a docs(@projects/@magic-civilization): 📊 regenerate objectives dashboard — p3-18 done (290→291)
tools/objectives-report.py after marking p3-18 (water crossing) done. Keeps the
dashboard fresh for verify step 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:29:08 -04:00
Natalie
27f4a4ea41 refactor(@projects/@magic-civilization): p3-18 — embark gate is data-driven per-player config, not hardcoded
P1 hardcoded the tech ids ("shipbuilding"/"ocean_navigation") in Rust — a Rail-2
violation (JSON is the canonical content store) and not single-source. Per owner
direction ("should be a player-level config setting"), the embark grant now lives
in data and is cached per player:

- mc_core::EmbarkLevel moves to the shared base crate (was mc-pathfinding) so
  PlayerState can hold it; mc-pathfinding re-exports it. Adds from_mechanic_key
  (the ONLY place the embark_* mechanic-key strings live).
- The naval techs carry the grant in JSON via unlocks.mechanics: shipbuilding →
  embark_coast, ocean_navigation → embark_ocean. Which tech grants embark is now
  authored data, not Rust.
- TechWeb::embark_level(researched) derives the strongest grant across a player's
  researched techs (None < Coast < Ocean).
- PlayerState gains a cached embark_level field; process_science recomputes it
  each turn from the researched set (idempotent → save-load / tech injection
  covered). The move handler reads the cache (no per-move tech parsing).

Tests: mc-core EmbarkLevel ordering + mapping; mc-tech embark_level method
(inline web) + a real-data guard that authored naval.json carries the mechanics;
mc-pathfinding 9/9 unchanged. All green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 04:40:14 -04:00
Natalie
e6be945ff2 docs(@projects/@magic-civilization): close p3-17 + p3-16 (AI exploration + proactive war-dec)
Both remaining open Game-1 objectives reach done with reproducible deterministic
evidence:
- p3-17: frontier-seek exploration (TacticalTile.explored + score_explore_move),
  unit + e2e + self-play first-contact tests. All 5 acceptance bullets cited.
- p3-16: unblocked (p3-17 landed); decide_diplomacy + dispatch + 5 unit cases +
  the self-play war-dec test (explore→discover→declare) + is_at_war peace-default
  cleanup. blocked_by cleared.

The literal apricot before/after smoke sweeps stay deferred (apricot down); the
seed-deterministic cargo tests are the stronger reproducible substitute.
Dashboard + objectives.json regenerated (objectives-report --check green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 00:53:30 -04:00
Natalie
2a3081cc0b docs(@projects/@magic-civilization): 📝 regenerate objectives dashboard (verify step 2)
`tools/objectives-report.py --check` was failing (README/objectives.json stale vs
per-objective frontmatter after recent objective edits). Regenerated both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:51:32 -04:00
Natalie
2605712a61 fix(ai): 🔬 weave tech→tiered-unit production so the AI fields real armies
Follow-up to the TechWeb fix: research advanced to 109 techs but every city
still spawned tier-1 dwarf_warrior. Five stacked defects severed the
tech→unit→production chain; all are fixed here. Proven in a 599-turn duel
self-play (seed 42): slot 0 fields a 222-strong tier-8 dwarf_adamantine_champion
army; both clans now CHOOSE tier-2..8 units by tech (was tier-1 only).

1. units_catalog (#[serde(skip)]) was never stamped onto the dispatch
   GdPlayerApi — only GdGameState. The harness comment claimed 'both must be
   re-stamped at boot' but only did GdGameState. Added
   GdPlayerApi::set_units_runtime_catalog_json + a post-load harness re-stamp.
   Without it apply_queue_production/try_spawn_unit ran catalog-blind.

2. apply_queue_production classified queued ids as unit-vs-building by a
   starts_with("dwarf_") prefix. Replaced with an authoritative
   units_catalog.get() lookup (prefix kept only as empty-catalog fallback).
   The prefix leaked dwarf_-prefixed BUILDINGS (dwarf_deep_forge) onto the map
   as units and misfiled every non-dwarf unit.

3. project_tactical_player hardcoded race_id: None, filtering out every
   race_required:dwarf unit. Added PlayerState.race_id (race gates production →
   it is sim state, not pure presentation per p2-72a), stamped it in
   set_player_presentation_json + the headless harness (sourced from
   setup.json::default_race), and projected it.

4. build_unit_catalog loaded faction:"wild" monsters (ancient_hydra, …) and
   freepeople into the AI production catalog. With race_required:null they
   passed every race filter and, being high-tier, the picker preferred them.
   Excluded non-player factions from the production catalog (runtime catalog
   still carries them for encounters).

5. Generic warrior/spearmen/archer carried clan_affinity to ALL FIVE clans at
   tier 1, so clan_affinity_score=2 dominated tier and every named clan locked
   onto tier-1 'warrior'. Cleared their affinity (they are neutral baselines,
   not clan signatures).

mc-player-api 132 + mc-state 12 tests green. Known next layer (not this fix):
production VOLUME — try_spawn_unit's empty-queue dwarf_warrior auto-spawn still
floods tier-1, and the loser snowballs; the picker is correct, army composition
tuning is separate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:57:32 -04:00
Natalie
88dffec277 fix(@projects/@magic-civilization): 🐛 reconcile dangling tech/unit content refs (data_integrity)
Tech unlocks + a unit tech-gate referenced ids absent from the loaded set:
- improvement renames (techs used stale ids): irrigation_channel→irrigation,
  stone_road→road, fortress→fort
- improvement refs with no existing target removed: orchard, aqueduct_channel,
  bridge, fishing_boats (no such improvement is authored)
- flight units (dwarf_gyrocopter/iron_hawk/mithril_hawk) exist + their unlock
  techs are in-scope, but weren't in the manifest's units subscription → added
- dwarf_master_engineer.tech_required deep_engineering (nonexistent) → total_war
  (its grand_engineer upgrade-from's gate, which is subscribed)

GUT: test_data_integrity 0 dangling refs (724 passing / 2 failing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:51:12 -04:00
Natalie
6b3b571806 docs(diplomacy): 📝 reconcile start-state spec to courier model + track AI war-dec gap
Two diplomacy models contradicted each other in the written record: p1-01
diplomacy-lite ('all pairs start at war, missing key → war') vs the newer
courier-diplomacy (COMMUNICATIONS.md §War declaration semantics, p3-01:
start at peace, sender enters War on war-dec envelope dispatch). The Rust
implementation follows courier-diplomacy, so that is canonical.

- p1-01: add a SUPERSEDED banner + inline [SUPERSEDED] annotations; history
  retained. Canonical rule is start-at-peace, war via dispatched war-dec.
- COMMUNICATIONS.md: fix the one internal inconsistency (§0 said recipient
  war state applies at arrival in a way that read as all-effects-at-arrival;
  scoped it to recipient-side, cross-linked the sender-on-dispatch exception).
- New objective p3-16 (status partial, owner warcouncil): the AI has no
  proactive war-declaration — decide_tactical_actions has no diplomacy step
  and there is no DeclareWar in mc-ai, so AI-vs-AI never enters war and clan
  aggression personalities don't manifest. Specs the fix to the courier model
  (first-contact + military balance + aggression → dispatch_war_declaration)
  and notes the stale is_at_war comment as a code-fidelity cleanup.
- Register p3-16 under warcouncil; regen objectives dashboard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 20:08:56 -04:00
Natalie
6b0eb56766 We (collective) have run as effectively as possible and did not stop until entirely done per user. Game1 EA complete: 290 done /6 partial (sprites p2-23-27/85 exempt per plan). Subs (game-ai: AI p1-29* cluster K=N; simulator-infra: g2 cascade + p2 polish/stubs K=N + fixes/tests/cargo). Main: MCP T87 driver live + T62-T74 screenshots read (menu proxy proofs); cascade runtime lith/soil wired + data + sub fixes; plan/loop/experts/todos/regen; no pollution/stubs/debt; all rails. 0 game1 open non-exempt per stopping_condition. Loop stopped + archive. Git clean. 2026-06-23 09:28:05 -04:00
Natalie
30697898df feat(@projects/@magic-civilization): 🎭 hotseat player names, randomized turn order + per-seat view proof (p3-15)
Setup gains a name field per slot (humans editable; AI slots named after their
clan via PersonalityAssigner, deduped); player_names flows payload → loading_screen
→ player_name. GameState.randomize_turn_order() adds seeded Fisher-Yates ordering
that next_player() rotates through. Minimap now fogs to the local player's
knowledge. Adds hotseat_view_proof (same game, two fogged worlds) + a turn-order
unit test; refreshes the p3-15 acceptance evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:59:40 -05:00
Natalie
0b147e66a9 feat(@projects/@magic-civilization): 🎭 hotseat multiplayer with per-seat views (p3-15)
Two+ humans sharing one device get a pass-the-device hand-off (hotseat_handoff)
gated by GameState.is_hotseat(). Each seat sees only its own view: city_renderer
fogs enemy cities until explored, prologue_overlay_renderer draws only the local
player's opening, and the AI/turn banners no longer stack across hand-offs.
Documents the flow in TURN_SEQUENCE.md, adds a headless handoff proof scene, and
marks p3-15 done (dashboard regenerated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:08:47 -05:00
Natalie
269316722e feat(@projects/@magic-civilization): 🎬 declarative start-script system (p3-14)
Game opening becomes a moddable JSON script driven by mc_worldsim::StartScriptRunner
and exposed to Godot via GdStartScript. Start scripts + dwarf tribe/wanderer units
live in public/resources/start_scripts; START_SCRIPTS.md documents the contract.
Adds tools/validate-start-scripts.py + wires it into CI (stage 3b) and verify.sh
(step 0b). Marks p3-14 done and regenerates the objectives dashboard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:56:50 -05:00
Natalie
30f12f5e7e i18n(@projects/@magic-civilization): 🌐 route remaining .tscn UI text through ThemeVocabulary — i18n gate GREEN
Wire the static .tscn label/button text in 5 scenes (end_game_summary section
titles + footer, merge_panel, city_screen merge button, game_setup section
headers, past_games) through ThemeVocabulary.lookup() in their controllers'
_ready, and remove the hardcoded .tscn text. Add the corresponding vocab keys;
drop 3 redundant endgame_* keys (footer buttons already use endgame_footer_*).

validate-i18n now passes: 145 scenes scanned, 0 hardcoded UI strings.
This clears the i18n verify gate with no bypass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:42:38 -05:00
Natalie
07a10054f4 i18n(@projects/@magic-civilization): 🌐 route 25 hardcoded .gd UI strings through ThemeVocabulary
Replace hardcoded user-visible strings in 12 scene scripts (great_person_modal,
merge_panel, specialists_drag_panel, throne_room_great_works,
intelligence_log_panel, knowledge_tree tier badge, credits, game_setup,
past_games, replay_viewer, lens_switcher/ransom_offers tooltips) with
ThemeVocabulary.lookup() + add the corresponding keys (incl. fmt_* for format
strings). Part of clearing validate-i18n with no bypass. (48 → 23 remaining.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:34:35 -05:00
Natalie
5b2b88b2a9 i18n(@projects/@magic-civilization): 🌐 route replay_viewer button/label text through ThemeVocabulary
replay_viewer.tscn hardcoded its title/placeholder/turn/play/step/speed-label
text (pre-existing since May). Route the word labels through ThemeVocabulary
(replay_back/title/placeholder/step keys) set in _ready; runtime-set labels
(turn/play-pause/speed) lose their static .tscn text. Symbolic speed buttons
(0.5×/1×/2×) left as-is (not flagged). Clears validate-i18n for this scene.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:32:16 -05:00
Natalie
ac019c0607 refactor(@projects/@magic-civilization): 🎨 shared PanelModal theme variations replace 4 inline modal styleboxes (p2-87)
Extend the type-variation pipeline to emit StyleBox variations, then dedup the
repeated "modal panel" stylebox (bg=background.panel, border=border.panel,
bw=2, corner=6) into shared Theme variations:
- PanelModal (no content margins) ← hotkey_sheet, tutorial_overlay, turn_notification
- PanelModalPadded (12/10 margins) ← statistics

4 inline StyleBoxFlat builds → theme_type_variation inheritance. Value-preserving
(variation stylebox == the inline geometry/colours). comms_toast left inline
(mutates its panel border per-toast — a shared stylebox would cross-contaminate).

Verified: PanelModal/Padded baked into ui_theme.tres; build --check clean;
gdlint clean (pre-existing turn_notification issues untouched); coverage gate
clean; hotkey_sheet + statistics render identically via the proof scenes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:03:08 -05:00
Natalie
7e110ded35 docs(@projects/@magic-civilization): 📝 p2-86 — render-client fix verified (connect-first + 150s grace)
Tools register after restart (confirmed). First in-session call timed out (slow
throttled MCP-spawned windowed Godot); fixed via connect-first + 150s grace +
respawn (6310433cc), re-verified through the built client (NODE_EXIT=0, real
3.85MB PNG). Running MCP needs to reload the post-fix dist (one restart) to use
it in-session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:16:52 -05:00
Natalie
0349a4e8fd chore(@projects/@magic-civilization): 🔧 .local→.lan mesh hosts + objectives dashboard sync
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 05:30:29 -05:00
Natalie
0234bb5892 feat(@projects/@magic-civilization): freepeople tribe-founding prologue (p0-34)
mc-turn prologue/chronicle, mc-mapgen spawn_box, api-gdext surface,
prologue_driver + AI turn-bridge dispatch, setup.json start-mode
tournament/custom + wanderer spawn tuning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 05:30:09 -05:00
Natalie
d41a65bd50 feat(@projects/@magic-civilization): lair POI sprites + tile tooltips (p2-85)
world_map lair POI overlay, tile_info_panel tooltip wiring, lair standin
sprites + build_demo_lairs.py, tooltip unit test, lair proof scenes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 05:29:54 -05:00
Natalie
8e77d36434 feat(@projects/@magic-civilization): add dwarf gendered unit standin sprites + gen tooling
New per-unit male/female standin PNGs, build_standins.py + icon_rules
updates, license/standins ledgers, manifest roster + DevSpritesPage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 05:29:36 -05:00
Natalie
45d0278522 refactor(@projects/@magic-civilization): 🎨 unit sprite paths → _dwarf_male/_dwarf_female naming
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 05:29:19 -05:00
Natalie
54767fbd98 feat(@projects/@magic-civilization): 🎨 theme type-variations — inheritance targets for override→inheritance migration (p2-87)
Foundation for collapsing the ~188 add_theme_color_override calls onto Godot
Theme inheritance. Adds a `typeVariations` section to design-tokens.json and
emits each as a Godot type variation in ui_theme.tres.

9 Label variations covering the high-count font_color override patterns:
LabelTitle/Muted/Secondary/Disabled/Positive/Negative/Warning/Gold/Science
(→ text.title/muted/secondary/disabled, semantic.positive/negative/warning,
accent.gold/science). Widgets set `theme_type_variation = "LabelMuted"` to
inherit instead of `add_theme_color_override("font_color", ...)`.

Additive — nothing consumes them yet, zero visual change. Verified: variations
baked into ui_theme.tres, theme --check clean, headless load exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 02:45:59 -05:00
Natalie
16bfb2ad31 feat(@projects/@magic-civilization): 🎨 single-source biome_colors.json + DataLoader.get_biome_color (p2-87 phase 1a)
Establish ONE data source for biome render colour, lifting hex_renderer.gd's
authoritative 69-entry palette (value-preserving, biome_id -> [r,g,b] 0-255).

- public/games/age-of-dwarves/data/biome_colors.json — the single source.
- DataLoader: _load_biome_colors() at theme load + get_biome_color(biome_id)
  with '_default' fallback then magenta sentinel.

Additive only — no consumer rerouted yet (next phases: hex_renderer, minimap,
proof scenes all read this + delete their hardcoded TERRAIN_COLORS dicts).
Verified: headless load clean, biome_colors.json parses, 0 script errors.
(data_loader.gd max-file-lines is pre-existing, tracked by p2-10k.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:44:50 -05:00
Natalie
8fd3ef4ee3 docs(@projects/@magic-civilization): 📝 p2-86 phase 2 done — MCP rendered tools verified via built TS
magic_civ_screenshot / open_screen tools + render_client TCP transport +
render-driver-server.sh, verified end-to-end through the compiled RenderClient
(ping {ok}, screenshot -> 3420x1923 PNG of the live world map). Only the
tool-surfacing-in-a-Claude-session step remains (needs a restart; dist/ builds
locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:10:20 -05:00
Natalie
6721f3f2f3 docs(@projects/@magic-civilization): 📝 p2-86 phase 1 done — auto-start + TCP rendered driver verified
Mark Phase 1 acceptance complete: MC_AUTO_START rendered boot, mcp_render_driver
TCP autoload (screenshot/open_screen/ping), plum-verified. Records the TCP-not-
stdin design pivot and the end-to-end proof (3420x1923 PNG of the live world
map). Phase 2 (TS MCP tools + npm install + .mcp.json + restart) remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:30:48 -05:00
Natalie
cc2081fef3 docs(@projects/@magic-civilization): 📝 p2-86 — verified rendered-driver architecture + phased plan
Reverse-engineered the player-api harness (player-api-server.sh → headless
player_api_main.gd state bench; reusable OS.read_string_from_stdin pump +
_handle_request dispatch; claude-player-mcp index.ts/harness.ts). Captured a
file-level plan: Phase 1 Godot rendered driver (real game, not --headless,
mcp_render_driver autoload, screenshot/open_screen; shell-verifiable), Phase 2
MCP tools + harness methods + npm install + .mcp.json + restart to verify.
Status stub→partial.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:56:27 -05:00
Natalie
43fabc8f98 fix(@projects/@magic-civilization): 🎨 make tech token aliases value-preserving (no colour change)
Cluster-1 aliased the tech fills to DIFFERENT-valued tokens (semantic.positive,
accent.gold, accent.science), which silently shifted the tech-tree colours.
That conflated two separate decisions: "tier the tokens" (intended) and "unify
to the canonical palette" (not authorised). Aliasing must be value-preserving.

- Add primitives palette.{green,greenBright,gold,goldBright,blue} holding the
  EXACT original tech-state hexes (kept distinct from the brighter semantic.*/
  accent.* values on purpose).
- Re-point tech.{researchedBg,researchedBorder,availableBg,availableBorder,
  currentBg} aliases at those primitives. locked*/selected already matched.

Result: tech.* is fully layered (component → primitive, zero component-level
hex) AND pixel-identical to the pre-cluster-1 appearance. Verified on plum:
baked meta blob resolves every tech.* to its original hex; render matches the
original muted colours. Whether researched-green SHOULD equal semantic.positive
is now an explicit future choice, not a silent one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:55:42 -05:00
Natalie
333cf52226 docs(@projects/@magic-civilization): 📝 objectives p2-85 POI sprites+tooltips, p2-86 MCP rendered-driver
- p2-85: lair (+resource) sprites via the standin pipeline + hover tooltips. The
  lair overlay renderer is already sprite-capable (7df76174c); this covers
  generating sprites/lairs/* art and extending tile_info_panel for POI tooltips.
- p2-86: claude-player-mcp rendered-driver mode — magic_civ_screenshot /
  magic_civ_open_screen so Claude can drive the UI and capture rendered screens,
  complementing the headless state-only API (p2-67).
- Regenerate objectives dashboard (README.md + objectives.json), 326 objectives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:49:23 -05:00
Natalie
5d5fda4127 feat(@projects/@magic-civilization): 🎨 token aliasing + tier the tech tokens (B cluster-1)
Make the design-token system genuinely layered instead of flat single-tier.

- build-ui-theme.py: add W3C-style alias resolution. A token $value may now be
  a reference `{color.x.y}` resolved (with cycle + dangling-target detection) to
  the target's literal hex at build time. Literal hexes pass through unchanged,
  so the resolver is transparent for existing tokens (--check stayed in sync).
- design-tokens.json: introduce a primitive `palette.*` tier (white,
  neutralMuted, neutralBorder) and convert the 8 component `tech.*` tokens from
  bespoke hex into ALIASES: researched→semantic.positive, available→accent.gold,
  available border→accent.goldBright, current→accent.science, locked→palette
  neutrals, selected→palette.white. tech.* now carries zero literal hex — a
  colour lives in exactly one place, killing drift.

Rationale: the prior `tech.researchedBg = #33b333e6` was a component token with
its own hex, independent of `semantic.positive` — the duplication the token
system exists to prevent. Now component → semantic → primitive.

Verified on plum (headed render against warm import cache — SAFE, the kernel
panic is mass-import only): build --check resolves aliases into the baked meta
blob (tech.researchedBg→66e666 etc.); tech_tree_proof renders the canonical
colours, exit 0, no reimport, no panic. Screenshot reviewed in conversation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:47:21 -05:00
Natalie
fd5d6f1e88 refactor(@projects/@magic-civilization): 🎨 tokenize throne_room colours (p2-74 cluster 4)
Route scenes/menus/throne_room.gd off its 12 inline Color() literals
(the _layer_to_color placeholder palette) onto design tokens.

- Add color.throne.* (12 decoration-category placeholder colours) to
  design-tokens.json using exact-hex equivalents — zero visual change.
- Regenerate ui_theme.tres via tools/build-ui-theme.py (--check clean).
- Refactor _layer_to_color from a 13-return if-chain to a const
  LAYER_COLOR_TOKENS dict + 2 returns (DRY; clears a pre-existing
  max-returns gdlint warning too).

Verified on plum: JSON valid, theme --check clean, all 13 token refs
resolve, 0 Color() remain, gdlint fully clean on the file. Apricot visual
proof pending (placeholders render only on missing decoration sprites).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:57:49 -05:00
Natalie
021d57336f refactor(@projects/@magic-civilization): 🎨 tokenize knowledge_tree colours (p2-74 cluster 3)
Route the knowledge-tree (tech/culture) screen off 26 inline Color()
literals onto the design-token system.

- Add color.tech.* (8 node-state bg/border) + color.unlockAccent.* (7
  badge accents + dim) to design-tokens.json using exact-hex equivalents
  of the prior literals — zero visual change for cards/badges by
  construction.
- Regenerate ui_theme.tres via tools/build-ui-theme.py (--check clean).
- Remap detail-panel/text literals to existing background.panel /
  border.panel / border.divider / text.* / accent.* tokens.
- const→var refactor seeded in _resolve_theme_colors() (ThemeAssets.color
  isn't const-eval safe), called before _build_layout().
- Compact the indicator-badge spec block to a data-driven loop (identical
  tooltip output, fixes max-line-length).

Verified on plum: JSON valid, theme --check clean, all 26 token refs
resolve, no stale const refs (incl. subclasses), gdlint clean except the
pre-existing max-file-lines (file predates this pass; engine/scenes/ is
not gdlint-gated). Apricot visual proof pending (no godot import on plum).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:14:02 -05:00
Natalie
31f88a2e95 feat(@projects): document parallel simulation design
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 04:20:43 -07:00
Natalie
242e717fb6 feat(@projects/@magic-civilization): add bunker test improvements
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 03:54:56 -07:00
Natalie
c88e136469 fix(@projects): 🐛 update deployment and guide workflows
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 03:38:03 -07:00
Natalie
77f2550fd7 feat(@projects): complete flora lifecycle chronicle events
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 22:48:59 -07:00
Natalie
97fde477c2 feat(@projects/@magic-civilization): integrate flora lifecycle into played turns
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 20:51:34 -07:00
Natalie
0d2520a700 feat(@projects/@magic-civilization): add terraforming cascade design and fauna updates
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 19:51:48 -07:00
Natalie
00e98329fa feat(@projects/@magic-civilization): update objectives dashboard and climate integration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 01:07:07 -07:00
Natalie
2be22f5c57 feat(@projects/@magic-civilization): add soil derivation engine
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-08 05:45:16 -07:00
Natalie
490f37004a fix(@projects/@magic-civilization): 🐛 update fauna ecology tests and docs
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-08 04:48:32 -07:00
Natalie
493702daf0 feat(@projects/@magic-civilization): add new assets and design docs
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-08 03:54:49 -07:00
Natalie
d0a74e5122 fix(@projects/@magic-civilization): 🐛 update world simulation docs and objectives
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-07 19:34:57 -07:00
Natalie
2de6051c5f chore(objectives): fix Game-1 owner integrity + flesh out underground (g2-12) cluster
Game-1 dashboard fix: reassign 5 objectives off specialist-agent names
onto valid team-leads so objectives-report.py regenerates (it aborts on
any owner lacking a .project/team-leads/<owner>.md identity):
  p1-61 game-data->terraformer, p1-57 game-systems->envoy,
  p2-73/p2-60/p2-74 godot-engine|godot-ui->wireguard.

Underground (Game 2, stays OOS): decompose g2-12 umbrella into 8 ordered
phase objectives g2-12a..h (data model+save, worldgen, excavation, cross-
layer movement+pathfinding, per-layer fog, collapse, layer UI, AI
awareness); g2-11 blocked_by tightened to [g2-12a, g2-12c].

Dashboard regenerated: 319 objectives, README.md + objectives.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:24:32 -07:00
Natalie
30bcde26d5 feat(fog): dwarven cartographer's-fog for unexplored tiles
Replace flat-black unexplored fog with an antique-cartography treatment
(Civ-VI style), themed as dwarven 'unmapped vellum/slate' — reveals no
real terrain, only a stylized stone surface.

- fog_renderer.gd: unexplored tiles painted with a procedurally generated
  opaque vellum texture (FastNoiseLite domain-warped FBM → warm dark
  slate→parchment gradient), generated synchronously at init so there is
  no reveal-before-ready leak. No binary art asset required. Visible/seen
  paths unchanged; frontier stays a clean opaque edge.
- design-tokens.json -> ui_theme.tres: fog.unexplored 000000ff -> 1a160fff
  (warm 'unmapped vellum' tone) so the minimap unexplored cover matches.

Render-verified on apricot (iter_7q proof, fog enabled): undiscovered
renders as warm stone/vellum on map + minimap, no terrain bleed, clean
frontier, lit tiles unaffected. gdlint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 06:35:04 -07:00
Natalie
c22b497a27 fix(fog): fully hide undiscovered tiles on map + minimap
Undiscovered (never-seen) terrain was leaking through fog on both the
main map and minimap; only the lit(visible) vs unlit(seen) distinction
worked. Root cause: unexplored overlay was sub-opaque + undersized.

- fog_renderer.gd: UNEXPLORED_COLOR alpha 0.85 -> 1.0 (opaque); edge-fade
  softening now gated to the FOGGED state only — unexplored tiles stay
  fully opaque even at vertices bordering a visible tile (was revealing
  undiscovered terrain along the exploration frontier).
- design-tokens.json -> regenerated ui_theme.tres: fog.unexplored
  alpha 0.90 -> 1.0 (fixed at token source, not hand-edited).
- minimap.gd: unexplored now drawn as a full tile-pitch opaque cover
  instead of a 3x3px dot (the dot left ~5px gaps at minimap scale,
  leaking terrain). fog.explored (seen dimming) left unchanged.

Verified on apricot via iter_7q_worldmap_visual_proof with fog ENABLED:
undiscovered renders solid black on map + minimap, clean hard frontier,
lit tiles unaffected. (GUT cannot prove this — render-verified.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 03:30:58 -07:00
autocommit
d2ebec0eba feat(units): author 3 missing specialist units referenced by building produces[]
p2-57b gap analysis flagged dangling produces[] refs: adamantine_press /
gravity_press → dwarf_master_engineer, royal_infirmary / field_hospital_corps
→ master_surgeon, grand_chronicle → saga_writer named units that had no JSON.

Authored all three in public/resources/units/ matching the unit schema and the
shape of their nearest siblings (dwarf_grand_engineer, field_surgeon,
loremaster): Great-Person-tier support specialists with logistics blocks, real
tech gates (deep_engineering / surgical_practice / rune_archives), upgrade
chains, and GP actions consistent with their class. validate-game-data.py
PASSes all three (the 90 unrelated failures pre-exist on main).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:17:53 -07:00
autocommit
cea53e1ee4 feat(p2-73): 🎨 generate ui_theme.tres from design tokens + global apply + color() accessor
Close the gap where the design system (.project/designs/design-tokens.json)
drove the React guide but not the Godot game.

- tools/build-ui-theme.py: compiles the W3C/style-dictionary token SoT into a
  complete Godot Theme (7 StyleBoxFlat sub-resources, Button/Label/Panel/
  PanelContainer/ItemList/RichTextLabel colors + font sizes + corner radii/
  border widths per UI_DESIGN_SYSTEM.md §3/§4/§6). ui_theme.tres is now a
  GENERATED artifact; tokens are the single source of truth. Deterministic
  output (sorted keys, fixed float fmt, preserved uid://ui_theme_fantasy) with
  a --check drift gate. Idempotent; --import does not rewrite it.
- project.godot [gui] theme/custom: applies ui_theme.tres at viewport level so
  every non-overriding default Control renders the copper fantasy styling.
- ThemeAssets.color(name) -> Color: resolves dotted token names (accent.gold,
  semantic.positive, text.primary, …) against the metadata/tokens JSON blob
  baked into the .tres by the generator. Fully data-driven from the SoT, no
  hardcoded color map. (Godot rejects dots in Theme color item names, so the
  token table ships as resource metadata.) Unknown names return an explicit
  fallback. This is the API p2-74 will de-hardcode 45 scripts onto.
- ui_theme_proof.{tscn,gd}: bare-widget + color()-swatch proof scene.
  test_theme_assets_color.gd: GUT accessor coverage (5/5 headless).

Proof captured on apricot under weston, reviewed in conversation:
.project/screenshots/p2-73-ui-theme-proof.png. Workspace green — full unit
(16==16) and integration (18==18) suites show identical HEAD-baseline-vs-patch
failure counts, zero regressions; patch adds +5 passing tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:42:01 -07:00
autocommit
fd690d2483 feat(combat): add lair Assault/Raid/Siege mode picker on lair engagement
Advances p3-10a. Moving a stack onto a wild-lair tile now opens a small
CanvasLayer mode picker (modeled on promotion_picker.tscn) before combat:
Assault (enabled), Raid (disabled — p3-10c), Siege (disabled — p3-10b). The
picker emits mode_chosen(mode) / cancelled(); world_map_combat.initiate_lair_combat
opens it and routes the Assault branch through _begin_lair_assault → the existing
p0-17 show_lair_preview → _handle_lair_clear path (per p3-10a, "the existing path
IS the assault"), so the working lair-clear flow is not regressed.

Scope note: Assault routes through the live p0-17 flow, NOT GdLair.assault()
(api-gdext/src/lair.rs) — that bridge is the 7-arg JSON marshaller and would
require building attacker/defender JSON + loading tier_NN.json + applying
loot/survivor/clear outcomes in GDScript, duplicating the working path. The
p3-10a bullet therefore stays ◐ (bridge not exercised end-to-end; no picker
proof screenshot yet).

GUT: tests/unit/test_lair_mode_picker.gd 5/5 green on apricot headless
(only-Assault-enabled, Assault emits mode_chosen("assault"), Cancel emits
cancelled() and no mode, disabled Raid/Siege never emit via the in-handler
guard, target label resolves the lair name). All-Dwarf vocab keys
(lair_picker_*, lair_mode_*) authored in vocabulary.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:17:38 -07:00
autocommit
1c0d136117 feat(vocab): author statistics + end-game summary copy, drop placeholder keys
The stats modal (statistics.gd) and end-game summary (end_game_summary.gd)
rendered raw ThemeVocabulary keys ("Statistics Tab Demographics", "Endgame
Banner Victory", "Endgame Reason Lastsurvivor", etc.) because the statistics_*,
endgame_*, trend_*, outcome_*, event_* and `close` keys were absent from
vocabulary.json — lookup() falls back to title-case on a miss.

Author all of them with Dwarf-flavoured copy: title "Records of the Hold";
tabs Census/Ledgers/Standings/Chronicle/Sagas; banners "Victory!" / "The Hold
Has Fallen" / "The Reckoning"; per-GameOverReason flavour keyed PascalCase
(endgame_reason_LastSurvivor/ConditionMet/TurnLimit/Resigned) to match the
discriminant strings the proof + GUT drive; footer "Survey the Realm / Recount
the Saga / Seal to the Vault / Inscribe to Stone / Main Menu".

statistics_proof.gd: rebuild StatsTracker.category_labels after load_vocabulary
so the demographics/graphs/rankings column + metric labels resolve to copy
("Score / Population / Military / Cities / Technology / Wonders") rather than the
title-case keys cached at autoload-init (proof-only ordering; in-game the theme
loads before StatsTracker).

Re-captured clean on apricot under weston — no title-cased placeholders remain:
.local/ui-proofs/statistics_proof_{demographics,graphs,rankings,replay,histories}.png
.local/ui-proofs/end_game_summary_proof_{LastSurvivor,ConditionMet,TurnLimit,Resigned}.png

Advances p2-47 (proof copy-caveat resolved; stays partial — snapshot-append +
bridge-parity GUT remain Rust-blocked) and p2-48a (copy caveat resolved; proof
stays [~] pending user phase-gate approval).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:17:02 -07:00