auto_play.gd had an `if OS.get_environment("AUTO_PLAY_ALL_AI")...` block at
class-body scope (lines 50-58) — invalid GDScript ("Unexpected if in class
body"), so the AutoPlay autoload failed to parse/load entirely: the autoplay /
RL-balance harness was non-functional and GUT logged a parse error at startup.
Relocated the (env-gated) reset into _ready where statements are legal. The vars
already default to the reset values, so behaviour is unchanged; this purely
restores the autoload to a loadable state.
Verified: gdlint parse-clean; headless boot exit 0 with no auto_play parse error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
setup(winner, reason, awards: Array[Dictionary]) rejected the untyped [] literal
("does not have the same element type as expected typed array"). Pass a typed
empty array.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
replay_viewer.gd:_ready accesses %TitleLabel, %PlaceholderLabel, %Speed1x,
%Speed2x, but those .tscn nodes lacked unique_name_in_owner (the prior i18n
commit added the %-accesses without flagging the nodes). %TitleLabel/% Placeholder
are read unguarded in _ready → "Node not found" + null .text assignment, which
fires whenever the replay viewer is shown (standalone past-games OR embedded in
the statistics replay tab) — a real runtime bug, not just a test artifact.
Clears test_statistics_modal + removes 9 %TitleLabel / 11 null-text error bleeds
into other tests (GUT global-error entanglement).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_ecology_tile_inspector + test_ecology_grudge_badge did
TileInfoPanelScript.new() / CombatPreviewScript.new() — bare scripts whose
@onready %UniqueName labels resolve to null and log "Node not found" for every
label, which GUT flags as unexpected errors. Instantiate the real .tscn instead
(tile_info_panel.tscn lacks EcologySpeciesList, so the "absent list" scenario
still holds). Clears both tests + removes ~13 cross-test %node error bleeds.
GUT: 31 → 27 failing; Node-not-found errors 22 → 9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same p2-71c guard as turn_processor: both _make_state and the barren-tile test
called add_player_militarist without set_units_runtime_catalog_json, so no player
spawned. Harvest the runtime catalog first.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add_player_militarist has a p2-71c guard that returns -1 (refuses to spawn)
unless set_units_runtime_catalog_json() is called first — otherwise MapUnit
base_moves=0 and the sim freezes at turn 0. The test never loaded the catalog,
so 0 players spawned → all downstream assertions (cities/units/wealth/fauna) read
0. _make_state now harvests the same catalog the real bridge does
(AiTurnBridge._harvest_runtime_units_json).
GUT: test_gd_turn_processor 14 → 2 failing; suite 33 → 32.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Research was completely dead in the headless simulation path (the RL trainer,
the MCP, and the hotseat driver all use it): the per-turn TurnProcessor's
tech_web_parsed was always None, so process_science never advanced anything.
0 techs ever completed despite science_per_turn climbing to 466 — units stayed
frozen at tier-1 dwarf_warrior and AI-vs-AI ground to an unbreakable stalemate.
The processor already self-drives research (auto-picks the next available tech
in topological order once a TechWeb is loaded); the only defect was nothing
loaded it.
- GameState gains tech_web_json (#[serde(skip)], boot-loaded like the AI
catalogs).
- apply_end_turn threads it into the fresh per-turn processor via
set_tech_web_json before step().
- GdPlayerApi::set_tech_web_json stamps it onto the dispatch state AFTER
load_state_json (serde(skip) means it can't ride through gs.to_json()).
- The headless harness flattens every public/resources/techs/*.json pillar
(prereqs cross pillars, so all load together) and calls the setter at boot.
Proven (hotseat self-play, seed 42): techs_done 0 → 10 → 40 → 78 → 109 by
turn 120. mc-player-api 132 tests green.
Known next layer (separate gap, not this fix): completed tech does not yet
translate to better units — production still builds only tier-1 warriors even
at 109 techs, despite higher-tier units (steam_golem, iron_sentinel, …) existing
in the data. That's a production-picker/catalog issue to chase next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- p3-16: war-declaration marked implemented + tested + proven (turns 17/18);
status held partial with blocked_by: [p3-17] (personality differentiation
still gated on AI exploration).
- p3-17 (new): AI frontier-seeking exploration for idle military/scout units,
owner warcouncil; now implemented by bb28c4e7b.
- regen objectives dashboard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Idle military units (no combat target, no locked target) fell to a garrison
patrol that kept them next to friendly cities, so the AI never physically
explored to discover the rival — starving both target-acquisition and the
war-dec discovery gate (p3-16). decide_movement now drives such units toward
the far side of the map (in a duel the rival sits opposite the player's own
holdings) with a per-unit lateral offset so a stack fans out instead of
clumping. Reuses emit_move_toward; a passable-hex set keeps moves on land;
keyed on unit.id so it stays deterministic. Runs before the garrison fallback
(an idle unit with no known enemy is more useful scouting than fortifying).
Tests: 2 new movement cases (steps toward far side; never onto impassable
water). mc-ai 276 green.
Honest measurement note: in seed-42 hotseat self-play the headline summary
barely moves — expansion (30 cities founded) already drives first contact
~turn 17, which is distance-limited (~33 hexes at 2 mp/turn), and the
unit_moved event is a noisy proxy (a position probe shows both sides' military
marching toward each other every turn while the counter sits at ~45). The
value here is correctness — idle military now seeks the frontier deterministically
rather than idling — not a dramatic self-play metric swing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The AI had no war-declaration logic — decide_tactical_actions ran
movement→combat→settle→production→citizens with no diplomacy step, and there
was no DeclareWar anywhere in mc-ai. Under the courier model (pairs start at
peace, war begins on war-dec dispatch) that meant AI-vs-AI sat at perpetual
peace: no enemy targets, armies never maneuvered, and clan aggression never
manifested (warmonger == builder).
- New decide_diplomacy step (runs first): opens hostilities against a
*discovered* rival (visible units/cities in the fog projection) once own
military clears an aggression-scaled superiority bar (thresholds::
dominance_factor — warmongers strike near parity, cautious clans need an
edge). Pure/deterministic.
- New Action::DeclareWar { target }; routed in both dispatch converters to
PlayerAction::DeclareWar → apply_declare_war → comms_dispatch::
dispatch_war_declaration (same path the human uses; sender enters War on
dispatch). Rollout apply flips the relation for lookahead fidelity.
- Made movement::{is_at_war,count_military} pub(super); refreshed the stale
is_at_war comment to the courier model (per p3-16 cleanup-alongside note).
- Tests: 5 mc-ai diplomacy cases (discovery gate, already-at-war, no-army,
aggression bar) + a dispatch round-trip. mc-ai 274 + mc-player-api 131 green.
Proven live (hotseat self-play, seed 42): war-decs dispatch on first contact
(turns 17/18). Full aggressive play is still capped by a SEPARATE gap — the AI
does not scout, so it rarely sees enemy *cities* to march on even once at war.
That exploration gap is the next limiter, tracked separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
project_tactical_player hardcoded moves_left: 2 for every unit (a stale 'bench
MapUnit doesn't model moves_left' comment) while MapUnit::movement_remaining is
the field the move dispatch actually decrements and the player view gates legal
moves on. The AI therefore believed every unit always had movement, planned
moves for already-exhausted units, and the dispatch rejected them.
Measured over a 200-turn hotseat self-play (seed 42): 'no movement points
remaining' move rejections dropped 10,862 → 0 (total controller misfires
10,972 → 110, the rest legitimate path/stacking edge cases). The AI's
world-model now matches enforcement; turns no longer churn through ~54 dead
move attempts each.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposed by a new hotseat full-game driver (drives both player seats over the
multi-slot wire, no AI dependency) — a 31-turn 2-player game surfaced these.
- mc-player-api: the AI→PlayerAction converter (apply_ai_action + the suggest
sibling) emitted the bare tactical city index ("0") for QueueProduction, but
find_city_indices needs the projector wire id "{player}_{c_idx}" — so every
AI/suggested queue_production failed UnknownCity. This silently broke the
in-box AI's production-steering, not just the wire. Emit the wire id at all
three sites; thread slot into the suggest converter; add a regression test.
Result in the playthrough: roundtrip failures 58→1, city_building_completed 0→18.
- api-gdext: advance_round_phase/end_player_round_phase did not compile at HEAD —
godot-rust 0.2.4 Array::push needs &Dictionary (AsArg); Pcg64 builds via ::seed
not ::seed_from_u64; dropped a dead rng binding. The gdext crate could not be
rebuilt from source until this.
- mc-worldsim: pub use GamePhase/RoundPhase (api-gdext references them through
mc_worldsim; they were a private re-export → E0603).
- tooling: add hotseat_playthrough.py — applies each seat's suggested actions
and flags any offered action that fails to apply, with severity triage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ranker: dedicated POI_GATES/POI_QUALITY for landmarks & lairs — they were
routed through BUILDING_GATES (roof_visible/single_building/no_front_facade),
so volcanoes, ley-confluence formations and lair camps failed every gate and
burned generations to the regen cap.
- grok_generator: _ensure_grok_sdk re-checks the import per candidate path and
raises a clear 'set GROK_BUILD_SDK_PATH' error instead of a cryptic ImportError.
- grok_generator: submit_batch generates a sprite's variants concurrently via
asyncio.gather so the client's max_concurrent semaphore is actually used.
- add test_grok_pipeline.py — 49 headless checks (factory, POI-gate routing
regression, prompt adaptation, PNG validation, starter manifest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two committed-code bugs surfaced once the concurrent session landed its changes:
- game_state.gd never declared `trade_ledger_json` (diplomacy.gd writes/reads
GameState.trade_ledger_json) → "Invalid access" on every diplomacy render, and
serialize()/deserialize() omitted both `diplomacy` and `trade_ledger_json` so
they were lost on save/load round-trip. Declared the field + added both to
serialize/deserialize.
- diplomacy.gd called GdTradeLedger.from_json("") unconditionally → "EOF while
parsing". Guard: keep the fresh empty ledger when the JSON is empty.
Clears test_diplomacy_panel entirely + the trade_ledger save round-trips.
GUT: 40 → 33 failing (trade_ledger_json "Invalid access" 18→0, from_json EOF 18→0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a GUI preferences module and refines the worker engine, operations panel,
workers page, and coverage page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The turn-notification filter bucket and its label change from "military" to
"combat" to match the entry category, with richer combat log lines (attacker vs
defender, siege HP). Updates the proof scene and unit test accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Two test-side binding errors (not Rust bugs):
- used `ClassDB.instantiate("GdGridState") + grid.call("create_grid", …)` — but
create_grid is a GdGameState method; the canonical GdGridState constructor is
the static `GdGridState.create(w, h)` (per worldsim_accumulator_fixtures.gd,
test_fauna_overlay.gd, world_map.gd). Switched both call sites.
- `fauna_index` was an untyped Array literal; Rust dict_to_tile binds it as
Array<String> and godot-rust rejects a Variant array ("expected array of type
STRING, got NIL"). Declared it `Array[String]`.
Verified: p2_58b's own binding errors are gone (set_tile_dict/create_grid/
fauna_index no longer error). Its residual GUT "Unexpected Errors" is global
pollution from other still-red tests (trade_ledger_json etc.), not this test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_gdextension_contract asserted GdTechWeb does NOT exist (mc-tech backlog),
but the crate + binding have since landed (`#[class(base=RefCounted)] GdTechWeb`
at api-gdext/src/lib.rs:7770; positive coverage in test_tech_web.gd). Per the
test's own instruction, removed the ABSENT_CLASSES sentinel and flipped
test_gd_tech_web_absent → test_gd_tech_web_present (guards against regression).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a worker abstraction (engine/worker.py) with registry wiring and a server
operations endpoint, surfaced in the GUI via new Workers and Operations pages
plus dashboard/coverage/theater/variant page updates. Refreshes the ranker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
set_palette_variant can run before set_theme populates _palettes (settings_manager
applies saved display prefs at _ready, ahead of theme load). Remember the desired
valid variant silently instead of warning; the missing-variant warning now only
fires once palettes are actually loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Same fragility as 64154c8bd, applied to the 8 other `lookup("fmt_*") % args`
call sites my i18n batches introduced (knowledge_tree tier badge, credits
entry/link, game_setup AI slot/clan, past_games entry, ransom_offers tooltip,
merge_panel not-found). Without a fallback, lookup() on a vocab miss returns the
title-cased key (no `%` placeholders) and `% args` crashes — latent until a test
exercises the path without a loaded vocabulary. Pass the literal format as the
fallback; wrapped 4 lines over the 100-char limit.
Verified: headless boot parses clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
My earlier i18n batch (07a10054f) converted literal format strings like
`"%s (%s) — Tier %d" % [...]` to `ThemeVocabulary.lookup("fmt_...") % [...]`
without a fallback. lookup() on a miss returns the title-cased key (no `%`
placeholders), so `% [args]` throws "not all arguments converted" whenever the
vocabulary isn't loaded — which is the case in GUT unit tests. This crashed
test_great_person_modal (16) and test_throne_room_great_works (6).
Pass the literal format as the fallback arg (matching the existing
lookup(key, fallback) idiom) so these are robust when vocab is absent. Wrapped
the 4 lines that exceeded the 100-char gdlint limit.
Verified: GUT string-formatting errors 41 → 0; both test scripts now green
(suite 51 → 40 failing; the rest are pre-existing/other-session).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the JSON file-loading + shape-extraction + subscription-manifest
pipeline (~184 LOC, all private to the load flow) into a DataLoaderIo helper
that operates on _data/_raw by reference — mirroring the existing _ecology /
_worlds delegation idiom. data_loader.gd 652 → 457 (under the 500 cap).
Verified: gdlint clean on both files; headless boot loads 815 entries through
the new pipeline (manifest filtering intact), exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All three statically unreachable + clean headless boot. Verified the scan is
sound (a known-live helper correctly resolves its callers):
- game_state_serialization_helpers.gd — docstring "Called by game_state.gd" is
stale; the caller was removed by the npc_buildings refactor
- turn_processor_signals.gd — no longer referenced by the turn_processor module
- achievement_tracker.gd — orphaned by the legacy-save-manager removal
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Statically unreachable (no .tscn ext_resource, no preload/load-by-path, no
class_name usage, no dynamic string-built loads) + clean headless boot verifies
no load-time breakage. Confirmed dead, with what superseded each:
- selection_manager.gd (482) — selection handled by event_bus/unit/player/
turn_processor_helpers; movement_animator.gd (67) used only by it (dead cluster)
- hex_overlay_renderer.gd (475) — superseded by overlay_renderer.gd; only
comment mentions remained
- weather_events.gd / WeatherEvents (474) — weather is Rust-side; sole "ref" was
a climate.gd config dict-key "weather_events": true, never the class
- indicator_renderer (295), river_renderer (121), road_renderer (88) — superseded
by procedural_renderer / overlay suite; zero engine refs
- ecosystem_simplified (292), fauna_simplified (124) — prototype variants; live
fauna.gd is the real one
- atmosphere_chemistry (254), water_body_finder (197), map_loader (133),
pending_actions (133) — orphaned helpers, zero engine refs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The live autoplay harness is scenes/tests/auto_play.gd (registered autoload
`res://engine/scenes/tests/auto_play.gd`). src/entities/auto_play.gd was a stale
fork — diverged from the tests/ copy at de7b8df16, never received later updates,
and is referenced by NOTHING (no preload/load/class_name; only a comment mention
in a test). Verified zero string-based loads across .gd/.tscn/.sh/.py/.tres.
Removes one of the gdlint max-file-lines violations cleanly (Commandment 9 —
dead code goes entirely). engine/src over-cap files: 12 → 11.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a Grok image backend (grok_generator.py) behind a generator factory, a
starter-set orchestrator (starter.py, orchestrate_starter.py, starter_manifest.json)
and a Grok PoC harness with proof renders. Updates the ranker/processor/composition
prompts and sprite-config; refreshes the sprite-gallery design preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
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>
Mark override→inheritance done: all font_color migrated to theme_type_variation
/ redundant-deleted / dynamic carve-outs, and the one repeated modal-panel
StyleBox extracted into shared PanelModal/PanelModalPadded variations
(ac019c060). Remaining inline StyleBoxes are single-use-custom or dynamic
carve-outs (no shared pattern left). Coverage gate guards regressions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record audit finding (no remaining base-palette dedup; guide bg tokens aliased)
and add a status header: all in-game colour derives from the single token
source, verified + gated. Remaining items are non-Godot (guide de-hex,
accessibility variants) or non-colour (StyleBox geometry, font-size).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
guide.bg{Primary,Secondary,Tertiary} held the same hexes as
background.{base,surface,raised} — the only genuine colour duplication left in
the token table. Aliased them so each base colour lives in exactly one place.
Value-preserving (resolved meta blob unchanged; ui_theme.tres byte-identical).
Audit finding: no other dedup exists in the Godot-layer tokens — tech.* are
already aliases of palette.*, and the gold/green families are distinct values,
not duplicates. The single-colour-source goal has no remaining godot-layer work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tools/check-ui-color-sources.py: fails if a hardcoded numeric Color()/Color8()
is applied to a widget in a scene (add_theme_*_override / StyleBox *_color).
Allows computed Color(accent.r,…), transparent, named constants, and var-init
fallbacks; excludes scenes/tests + the 3 precursor deletion files. Passes clean
on live scenes (exit 0). Wired into ./run verify as step 17 so a hardcoded
colour can't creep back in.
Capstone for the override→inheritance / single-colour-system work: colours in
live scenes now provably come from the design-token source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Stale May 31–Jun 8 agent worktrees, all behind main with no unique work
(every change landed or superseded). Removed checkouts, branches, and the
accidentally-committed gitlink entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first in-session magic_civ_screenshot timed out: windowed Godot spawned by
the MCP (a throttled background GUI process under Claude Code) inits far slower
than a terminal launch, exceeding the 45s connect timeout — though the driver
DID bind the port (verified 8787 open). Fixes:
- connect-first: if a rendered driver is already listening (e.g. a foreground
`MC_MCP_RENDER=1 ./run play`), connect to it directly and skip the slow,
throttled background spawn entirely.
- start timeout 45s -> 150s for the spawn fallback.
- a failed start now clears its cached promise + reaps the child so the next
tool call respawns instead of replaying the rejection until an MCP restart.
Re-verified end-to-end through the built RenderClient (spawn path): ping {ok},
screenshot -> a real 3420x1923 PNG of the live world map.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>