Commit graph

1479 commits

Author SHA1 Message Date
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
autocommit
40bb2b1f2c test(p1-29i): geometry ensemble — robust elimination lift, corrected baseline framing
A single-seed sweep is uninformative (scripted path barely responds to rng), so
add a geometry ensemble over INITIAL CONDITIONS: start-distance {4,5,6} ×
attacker-warriors {3,4,5} = 9 surfaces, at cooldown {0,3,5,8}.

Result (elimination-rate X/9):
  cd=0 (baseline): 5/9   (attacker-wins 5 / def 4)
  cd=3:            7/9   (att 7 / def 2)
  cd=5:            8/9   (att 7 / def 2)   <- peak
  cd=8:            6/9   (att 5 / def 4)

The refound cooldown produces a ROBUST lift (soft hump peaking cd 3-5, every
value >= baseline; cd=5 weakly dominates cd=0 cell-by-cell — better in 3/9,
worse in 0/9), shifting outcomes toward the heavier attacker. This overturns the
single-seed pessimism in the prior commit.

CORRECTED PREMISE: p1-29h's cited "20 captures / 0 eliminations" was a
single-GEOMETRY artifact (the dist=5/w=4 cell). Eliminations already occur in
5/9 baseline geometries — the lever RAISES an existing rate (5/9 -> 8/9), it
does not unlock elimination. p1-29h elimination bullet updated to reflect this.

DELIBERATELY NOT DONE (honest, evidence-bounded):
- No cd value authored into combat_balance.json — the lift is gridded-micro-
  surface only (9 geometries, 1 seed each); a live balance value needs the
  full-game 10-seed batch (tools/p1-survival-score.py, the multi-seed-tournament
  rule). The cd response is also an unexplained hump (cd=8 -> 6/9). Lever stays
  defaulted off.
- p1-29d NOT re-scored as converged — its gate is the full-game multi-gate
  scorecard (D1: P1 elim<=T100 OR stalled, 10/10), a different + heavier surface
  not run this pass. The brief's "re-score p1-29d" is gated on that measurement.

Tests (apricot): gridded harness non-ignored 1/1; ensemble + sweep run via
--ignored; cargo check --workspace 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:38:27 -07:00
autocommit
a49e24c969 feat(p1-29i): post-capture refound-suppression lever (defaulted off) + measurement
p1-29h Phase 2 isolated the elimination wall. A per-player diagnostic added to
the gridded harness shows per_player_min_cities=[1,1] — both empires ARE pressed
to one city but neither is eliminated; the loser instantly refounds. That
[1,1]-but-no-kill signal makes refound-suppression the correctly-targeted lever.

Lever (data-driven, Rail 2, DEFAULTED OFF — zero live-balance change):
- mc_core::CombatBalance::refound_suppression { cooldown_turns: u32 } (default 0).
- mc_turn::PlayerState::last_city_lost_turn stamped at the capture site.
- processor::try_found_city refuses a replacement while
  turn - last_city_lost_turn < cooldown_turns (0 short-circuits).

Measurement (single-seed sweep, 160t): cooldown 0->0 elim, 5->1, 10->0, 20->0,
40->0. The lone cd=5 elimination is NOT credited as convergence — it is
single-seed noise: non-monotone (more suppression -> fewer eliminations), the
WINNER flips slot-to-slot across the sweep (chaotic-snowball perturbation, not a
lever response), and it fails p1-29h's literal gate. The lever demonstrably
suppresses refounding (founds trend down) but suppression ALONE does not robustly
convert captures into eliminations. Honest measured-negative per the brief; NO
non-zero combat_balance.json value authored, p1-29d NOT re-scored as converged.

Tests (apricot): mc-core 262 lib; gridded harness non-ignored 1/1; cargo check
--workspace 0; cargo test --workspace --no-run 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:29:04 -07:00
autocommit
9ddd40abe6 fix(p2-57c): add combat field to remaining UnitStats test constructors
The mc_units::UnitStats `combat: CombatStats` field added in cd339ff7d broke
four test-only catalog constructors that cargo check --workspace does not
compile (test targets are skipped by check): mc-player-api common/mod.rs and
the three capture_* integration tests. Adds the field (warrior gets its real
60/12/1 JSON line; the rest default to zero). cargo test --workspace --no-run
now exit 0 on apricot.

Gate lesson: after a catalog/struct field change, run
`cargo test --workspace --no-run`, not just `cargo check`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:18:10 -07:00
autocommit
05ad5fae86 feat(p3-10b): wire lair siege into the live turn loop
tick_siege/decay_siege + SiegeState (persisted on GameState::siege_pressure)
shipped earlier but were dead code — never called from the turn loop. This
adds the per-turn lair-siege phase + a data-driven config loader.

- mc-turn/src/lair_siege.rs: LairSiegeConfig loader from
  lair_combat_modes.json::siege.siege_pressure (per-tier resistance + tuning,
  Rail 2 — consumes authored data, no fabricated per-lair siege_resistance);
  tick_one_lair pure helper; LairSiegeEvent.
- processor::process_lair_sieges (Phase 5d in step): iterates siege_pressure,
  detects besieger adjacency, ticks/decays, surrenders + rolls loot + drops
  the entry, logs to TurnResult::lair_siege_log. authored_lair_siege_config()
  embeds the JSON build-time + OnceLock (same pattern as encounter_rates).
- resolve_lair_combat Siege arm stays single-shot-NotImplemented BY DESIGN
  (multi-turn state can't flow through LairAssaultParams -> AssaultOutcome;
  same precedent as resolve_raid's separate entry) — doc'd to point at the
  live per-turn phase.

Honest boundary: nothing in the live loop INITIATES a siege yet (a player
picks Siege via the GdLair bridge / UI mode picker — godot-ui follow-up that
owns GdLair::begin_siege). The phase is exercised by tests + the future
bridge; mechanic is live, initiation is the remaining dependency. GDExt
begin_siege/siege_pressure bullet stays open (K=4/5).

Tests (apricot): lair_siege lib 5/5 (incl. named pressure_accumulates_then_
surrenders + pressure_decays_when_unattended) + config loaders 3; integration
lair_siege_phase 4/4 (through processor.step + save/load round-trip); mc-turn
240 lib, mc-combat 143 lib, serde_roundtrip 6/6; cargo check --workspace 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:10:58 -07:00
autocommit
beac17f4e2 chore(p2-47): sync statistics_proof TabBar highlight with driven tab
When the proof drives _on_tab_changed directly, the TabBar current_tab
does not update (in-game the player clicks the TabBar, which does). Set
_tab_bar.current_tab in the proof loop so the captured screenshots show
the correct tab highlighted. Cosmetic; product behaviour unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:57:58 -07:00
autocommit
cd9f2b9e0d feat(p2-48): author end_game_summary.tscn + wire it to the game-over flow
The EndGameSummary script existed but its .tscn did not, and nothing
instantiated it — the summary scene was orphaned, so no screen showed
at game-over for any GameOverReason. Author end_game_summary.tscn with
all 14 @onready %-named nodes the script requires, and instantiate it
in main.gd under a persistent CanvasLayer (layer 25): the control
self-connects to EventBus.game_over and unhides itself for every reason
(LastSurvivor, ConditionMet, TurnLimit, Resigned).

Add the three feature-local GameState members the footer handlers
dereference (read_only_mode, pending_replay_game_id, clear()) — without
them 2/5 footer buttons null-crashed; referenced only by this feature
(+ replay_viewer reads pending_replay_game_id). read_only_mode is a
flag with no enforcement consumer yet (honest partial).

Rewrite end_game_summary_proof from the old _draw() mockup to BOOT the
real scene for all four GameOverReason variants; add GUT tests
test_award_computation (3/3) + test_end_game_footer_actions (5/5).

GUT full default suite on apricot: 645/693 pass, only deltas vs the
pre-change baseline are +8 (these new tests) and -1 (a pre-existing
detached-node bug fixed in test_statistics_modal) — no regressions.
All four end-game variants proof-rendered on apricot.lan and reviewed.

Production game_over emission (mc-turn) and a GdReplayPlayer::get_awards
bridge stay Rust-lane; the live awards path shows the pending notice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:57:45 -07:00
autocommit
843db4116a feat(p2-47): author statistics.tscn wrapper so the 5-tab modal opens
The 709-line self-building StatisticsModal script existed but its scene
asset did not — world_map.gd and ingame_menu.gd both push_overlay
"statistics.tscn", so F9 / info-button / Stats-menu dead-ended at a
missing resource. Author the thin one-node Control wrapper with the
script attached; both entry points now open the modal.

Also add statistics_proof.{gd,tscn} (boots the real wrapper, seeds a
4-clan 12-turn StatsTracker fixture, captures one screenshot per tab)
and fix a pre-existing detached-node bug in test_statistics_modal's
close-fallback test (called _ready()+_on_close() on a node never added
to the tree). GUT: test_statistics_modal 8/8 green on apricot. All 5
tabs proof-rendered on apricot.lan and reviewed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:50:00 -07:00
autocommit
5503f04b25 feat(p3-10a): GdLair::assault GDExtension bridge over resolve_assault
New api-gdext/src/lair.rs exposing GdLair::assault(...) #[func], registered
in lib.rs. Mirrors the GdLootRoller JSON-string pattern (Rail 3 — file IO
stays in GDScript, the bridge only marshals):

  assault(attackers_json, defenders_json, loot_tier_json, lair_id,
          lair_tier, turn_seed, defender_terrain_bonus) -> Dictionary

GDScript reads public/resources/lairs/loot/tier_NN.json + builds the
stack/defender JSON; the bridge parses (build_params — Godot-free,
unit-tested) into LairAssaultParams, calls mc_combat::resolve_assault, and
returns a cleared/repulsed/withdrawn outcome Dictionary (error, never panic,
on malformed JSON).

The Assault/Siege/Raid UI mode picker is a godot-ui follow-up, noted in the
objective — NOT built in this Rust lane.

Tests (apricot): GdLair 6/6 (cleared+guaranteed-loot, repulsed via real
wild_combat_stats(8,huge,carnivore), withdrawn, params-assembly, 2 parse-error
paths); api-gdext lib 29/29; cargo check --workspace exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:49:46 -07:00
autocommit
cd339ff7dd feat(p2-57c): wire production-quality consumer into the live spawn path
Closes p2-57c bullet-2's apply half and p2-57b's pipeline live-loop gap.

- mc_units::UnitStats gains a flattened CombatStats { hp, max_hp?, attack,
  defense, ranged_attack, range } (serde-default; the unit JSON already
  authors these — the catalog was dropping them on load).
- mc-turn processor::resolve_spawn_combat resolves a spawning unit's base
  combat line from the units catalog BY unit_id, then applies the stamped
  QualityTier delta from combat_balance.quality_deltas (Rail 2). Both
  try_spawn_unit and spawn_unit_typed call it.
- Fixes a latent live bug: try_spawn_unit hardcoded 60/12/1 on EVERY unit
  type, so queued non-warriors spawned with warrior stats.

Honest scope: the apply half is now live; the stockpile->tier STAMP source
(per-city typed ResourceStockpile p2-57a + per-unit gating p2-57b) is not
wired into process_city_production, so live units carry quality:None today.
Bullet 2 stays partial (apply proven; stamp-source half gated on infra).

Tests (apricot): mc-turn 235 lib, mc-units 12, mc-city 262, mc-combat 217,
mc-core 143; new quality_spawn_live_processor 3/3; cargo check --workspace 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:43:38 -07:00
autocommit
eaec14406d refactor(p2-65): 🧱 Phase 0c — relocate 4 tactical data types to mc-core (break mc-state cycle vector)
Pre-flight for the mc-state extraction found Phase 0 under-scoped: GameState/
PlayerState carry 4 mc_ai::tactical typed fields (TacticalMemory, BuildingPriors,
TacticalUnitSpec, TacticalBuildingSpec), so mc-state (forbids mc-ai dep) could
not hold GameState until they moved. All verified data-only.

- New mc_core::tactical_types holds the 4 structs verbatim + is_buildable +
  default_tier + 3 unit tests (serde round-trip, memory state-machine, gates).
- mc-ai memory.rs/state.rs re-export from mc-core (pub use) — every existing
  mc_ai::tactical::* path still resolves; duplicate defs/tests removed.
- mc-turn game_state.rs 5 type refs → mc_core::* (incl ScoringWeights),
  forward-looking for the game_state→mc-state move. Save format byte-identical.

Independently kills the irregular mc-turn→mc-ai data dependency the spec Context
flags. Gates green on apricot: check --workspace --tests clean; mc-core
tactical_types 3/3; mc-ai all green; mc-turn 235 lib; mc-player-api 126+integ.
Pre-existing five_players_overflow failure (MAX_PLAYERS 4→12 stale; touches no
moved type; fails on HEAD) is not a regression. p2-65 stays stub (mc-state crate
not yet created — Phase 1+).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:14:13 -07:00
autocommit
4f2f15219a feat(p1-29h): 🧪 gridded fair-duel surface — army-lock engages, elimination measured-negative
Phase 2 (bullets 5+6). Builds option (b): a gridded GameState Rust harness so
compute_vision populates and both combatant slots run the scripted-default
controller through the persistent drive_ai_slot mem::take seam (fair two-
scripted:default — a passive ender drives every other slot via apply_end_turn).

Findings on the fair surface (160t): lock ENGAGES (ever_committed=true,
committed_with_target=true), 20 CityCaptured fire, but 0 eliminations and both
empires grow (15 vs 22 cities). min_total_cities never dips below start despite
20 captures (38 refounds offset every loss). Verdict: targeting lock works;
bottleneck is capture-stickiness / refound-suppression — confirms p1-29d's
indecisive-war root cause + the spec's weight-insensitivity risk, empirically.

- gridded_fair_surface_engages_army_lock: green always-on guard (vision>0 +
  lock engages) — the Phase-1-on-production-seam coverage the gridless
  p1_29h_persistent_memory_seam guard could not provide.
- fair_scripted_duel_elimination_measurement (#[ignore]): records the 160t
  signals; elimination NOT asserted (measured-negative, bullet 3 stays open).

p1-29h stays partial K=5/6; resume note redirects next wave at refound-
suppression, not the targeting lock. mc-player-api 126 + integration green;
cargo check --workspace --tests clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:50:22 -07:00
autocommit
a7a1fc89f3 test(mc-player-api): persistent tactical-memory seam guard + p1-29h → partial
Phase 2 of p1-29h. Adds the only regression coverage for the persistent
memory seam: `apply_end_turn → drive_ai_slot` (the mem::take + write-back
that survives the per-turn TacticalState snapshot). The mc-ai movement test
calls decide_movement directly and never touches this seam, so this guards
the dispatch threading itself — drives 60 turns without panic, reads the
memory field back, asserts coherence (committed ⇒ has a target).

Phase 2 measurement (bullets 3/5/6) NOT met — objective flips stub → partial
(K=3/6, bullets 1/2/4 cited from Phase 1's green mc-ai tests). Verified
finding recorded in the objective + test: the persistent army-lock lives ONLY
on the drive_ai_slot/apply_end_turn path; both existing full-game drivers
bypass it (p1-clean-baseline.py clones memory via suggest(); full_game_transcript
passes a transient default). The seam test's gridless GameState::default()
fixture yields an EMPTY vision set (sees_own_city=false, visible_tiles=0), so
no enemy city is ever visible and want_attack never fires — engagement needs a
real grid+vision surface (Godot autoplay / AUTO_PLAY_ALL_AI driving end_turn).
Lock engagement on full visibility is already proven by the mc-ai movement test.

cargo check --workspace --tests green on apricot; seam test 1/1 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:48:10 -07:00
autocommit
8caa0446de feat(mc-ai): cross-turn tactical memory + army target-lock hysteresis (p1-29h Phase 1)
Ports the decisive-war mechanic from the juiced harness auto_play.gd:1109-1189
into the shipping Rust AI so the scripted controller fields it natively
(removing the dependency on the GDScript harness for decisiveness).

Capability (acceptance bullets 1, 2, 4):
- New `mc_ai::tactical::TacticalMemory { locked_target, commitment_turns }` —
  the cross-turn persistence channel. Lives on `mc_turn::PlayerState`
  (`#[serde(skip)]`, transient), NOT on the per-turn TacticalState snapshot
  (which round-trips FFI as JSON — a field there would mean GDScript shadow
  state, violating Rail 1) and NOT on the controller (a stateless shared
  singleton). Borrowed `&mut` by `dispatch::drive_ai_slot` via mem::take and
  threaded through `decide_turn → run_ai_turn → decide_tactical_actions →
  decide_movement` — the pure-Rust path p1-29d measures, zero GDScript.
- `TacticalMemory::resolve` folds the auto_play state machine: acquire nearest
  target when attacking, HOLD through tactical-score wobble for the commitment
  window (hysteresis), PRESS ON to the next objective when the locked target
  falls (capture → don't disperse), clear when all enemy cities are gone.
- `decide_movement` computes the army-wide lock once per turn (step 5b) and
  every non-adjacent military unit drives on it instead of per-unit greedy
  re-targeting — so captures get finished into eliminations.
- Commitment length is personality-derived (`thresholds::commitment_turns`,
  aggression 0.6 / grudge 0.4; axis=5 reproduces auto_play's =5) — Rail 2
  (ai_personalities.json axes), not a hardcoded constant.
- AiController trait gains `&mut TacticalMemory`; scripted threads it, mod
  (wasm/native) + learned + FFI-shim controllers take a documented transient.

Tests (cargo test -p mc-ai green: 278 lib, all integration; workspace --tests
checks clean on apricot):
- 10 `memory::` unit tests: acquire/hold/expire/press-on/clear/determinism.
- 2 `thresholds::commitment_turns` tests (auto_play baseline + personality).
- 1 `movement::army_lock_concentrates_and_persists_across_turns` integration
  test: a PERSISTED memory across two real decide_movement calls concentrates
  3 units on ONE locked city and holds it through a want_attack=false turn.
- No regression: mc-turn 235/235, mc-player-api 126+, mc-mod-host all green.

Phase 1 is the clean green commit boundary. Objective stays 🟡 partial:
bullets 3 (≥1 elimination measured), 5 (AUTO_PLAY_ALL_AI / asymmetric harness),
6 (p1-29d re-score) are Phase 2 (batch validation on apricot), not yet done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:25:39 -07:00
autocommit
ae06b42e15 feat(api-gdext): GdGameState::civic read-side query surface (p3-05a-gdext-bridge)
Adds the read-side civic query the civics UI needs, completing the bridge
split out of p3-05a. `GdGameState::civic(pi, axis) -> Dictionary` returns
`{ choice, anarchy_turns_remaining, in_anarchy }`, riding on GdGameState
next to request_civic_switch/get_anarchy_turns_remaining (the canonical
per-player accessor surface — there is no per-GdPlayer shim in the tree).

`choice` is serialised via serde with quotes stripped (symmetric with the
request_civic_switch parse path): named AxisChoice variants → snake_case,
Anarchy → "anarchy", untagged Custom(id) → id verbatim. Unknown axis or
out-of-range pi → empty Dictionary.

Tests (green headless on apricot.lan):
- engine/tests/unit/civics/test_civic_query_bridge.gd — 7/7
- engine/tests/integration/test_gdextension_contract.gd — civic in the
  GdGameState method contract; test_gd_game_state_has_expected_methods passes
- cargo check --workspace green; build-gdext.sh release build green

Objective p3-05a-gdext-bridge: stub → done (4/4 acceptance, cited).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 08:54:10 -07:00
autocommit
4718172732 feat(simulator): unit quality consumer (p2-57c) + sim state
- mc-turn/quality.rs: apply_quality(UnitStats, QualityTier, &QualityDeltas)
- mc-core combat_balance: QualityDeltas/StatDelta global rule + per-unit override
- MapUnit.quality persistence field (serde-default, save-safe)
- quality_spawn_divergence test (producer→tier→consumer pipeline)
- validate-game-data: validate_unit_quality_chain (contract for p2-57b)
- captures converged prior-session sim state (lair loot, replay visibility, worker categories) already integrated on main

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 04:39:57 -07:00
autocommit
e09d54d847 feat(standin-sprites): Add proof sprite capture script and update sprite UID consistency
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 06:03:33 -07:00
autocommit
5b099829d2 test(scenes): Add test scene and test logic for world proof functionality
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 06:03:33 -07:00
autocommit
5a10a638bd feat(generation): Implement new UID generation/validation for unit rendering with updated metadata handling
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 06:03:32 -07:00
autocommit
7aec731e80 remove(entities): 🔥 Remove combat utility functions from GDScript after migration to Rust-based resolver
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 06:03:32 -07:00
autocommit
92850560f8 test(engine): Add unit tests for stand-in sprite coverage, including sprite loading, rendering, and edge cases
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:46:13 -07:00
autocommit
92fb20b4a9 test(scenes-test): Add test scene with stand-in sprite validation logic including sprite node setup and assertions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:46:13 -07:00
autocommit
2093c985b4 test(ai-controller): Add minimal building catalog test data for AI controller JSON parsing validation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:23:57 -07:00
autocommit
aec0d32c7c feat(ai): Enhance AI controller interface with improved methods/traits for better robustness and intuitive API usage
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:02:39 -07:00
autocommit
5e4909a4b3 test(mc-turn): Add comprehensive test cases for caravan, engineer, and PvP turn capture scenarios
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:02:39 -07:00
autocommit
fbed62f60f feat(mc-ai): Add parity tests for research picking and enhance evaluator logic in Monte Carlo AI simulations
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:02:38 -07:00
autocommit
de7b8df166 feat(auto-play): Improve AI research selection and auto-play logic with refined turn processing and expanded test coverage
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:02:38 -07:00
autocommit
e89bd48904 test(integration): Add integration test case for Pioneer escort mechanic with unique identifier validation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:29:08 -07:00
autocommit
516c4abdd2 feat(escort-controller): Introduce EscortController class with escort mechanics, update unit_panel.gd for escort status display, and add event bus bindings for escort events
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:29:08 -07:00
autocommit
c2629d5653 feat(turn): Implement pioneer escort action handling, game state updates, and dynamic encounter rate adjustments
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:29:08 -07:00
autocommit
05795cc3f3 feat(api-gdext): Add new learned controller structs, traits, and training functions to the simulator API extension
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:06:43 -07:00
autocommit
11c9b71a02 feat(mc-player-api): Add routing logic for learned controller slots in dispatch system
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:06:43 -07:00
autocommit
20e7788da5 feat(learned): Implement ONNX model inference and encoder for learned controller with parity tests and fixtures
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:06:43 -07:00
autocommit
5319a6c385 deps-upgrade(mc-player-api): ⬆️ Update dependencies in Cargo.toml and regenerate Cargo.lock for mc-player-api
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:06:43 -07:00
Natalie
7f657a2cf9 feat(engine): add comms system UIDs and survival objective details
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-31 13:14:49 -06:00
Natalie
a4fdd7b782 feat(@projects/@magic-civilization): add survival scorecard tooling
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-29 19:47:24 -06:00
autocommit
f0ae52a746 feat(tactical): Implement sole-city economy strategy to prioritize production for a single critical city during threats
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-27 20:26:00 -07:00
autocommit
e5a084ac49 feat(courier-resolver): Update routing logic with new strategies and optimizations for CourierResolver
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
3e09fd33a8 refactor(mc-player-api): ♻️ Remove deprecated envelope dispatch function and clean up projection logic in comms_dispatch.rs and projection.rs
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
383f58c2da feat(headless): Add suggest endpoint to handle game action/content suggestions in headless player API
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
d19db31ccd feat(api-gdext): Introduce suggest endpoints in player_api.rs for GDExt simulator autocompletion
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
d968ecb2ca feat(player-api): Add suggest module and wire protocol serialization for player API suggestions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
4fbe306c53 test(mc-player-api): Add comprehensive test cases for phase 6 turn loop behavior, including validation of player actions, state transitions, and turn progression
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
69133d5a4e feat(mc-player-api): Introduce message types and dispatch logic for player API communication
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
5f0d8ddc08 refactor(comms): ♻️ Optimize and refactor comms_event_dispatcher and event_bus autoloads for improved event handling efficiency and better Godot engine integration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
e55e8c5ec6 feat(replay): Add Godot engine replay extensions and enhance event handling for replay processing
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
39aa1f25e2 feat(mc-turn): Update turn-based event processing logic and adjust test wiring for event collection
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
59503ecf8e feat(mc-player-api): Implement Phase 6 turn loop communication dispatch and action handling in the player API
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00
autocommit
023538214c fix(mc-comms): 🐛 Update blackout timeout configs and add capital blackout test assertions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-26 02:21:14 -07:00