fix(@projects/@magic-civilization): 🐛 fix panic in turn processor
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
3765dc8b9f
commit
9e7566a628
3 changed files with 45 additions and 9 deletions
|
|
@ -125,3 +125,37 @@ heuristics.
|
|||
Closing both is the substance of cutover step 1 — the Rust turn must produce
|
||||
games shaped like the GDScript autoplay (combat present, sane city counts)
|
||||
before any path cuts over to it. This is the genuinely multi-session leg.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 RESULT (2026-06-08) — Rust turn + real AI = live-grade. Foundation de-risked.
|
||||
|
||||
The step-1 question ("can the Rust turn be the authoritative turn?") is answered
|
||||
**yes, with evidence.**
|
||||
|
||||
**Bug fixed:** `mc-turn` panicked at `processor.rs:2697` on turn 8 whenever the
|
||||
real AI (`run_ai_turn`) drove an actual capture — a stale capture index into a
|
||||
vec emptied by an earlier same-phase kill/capture. Fixed with a bounds guard
|
||||
matching the existing `killed`-dedup intent (committed `e21381037`). 234 mc-turn
|
||||
tests pass; the one failing test (`five_players_overflow`) is pre-existing
|
||||
(fails identically on HEAD, unrelated — chip filed).
|
||||
|
||||
**Viability proven:** with the panic fixed, the previously-blocked 250-turn
|
||||
`long_game_transcript` (3 clans, slots 1+2 = real `run_ai_turn`) runs to turn 249
|
||||
and produces a **live-grade game**: **2531 units killed**, 50 cities founded,
|
||||
slot 1 winning by military dominance (47 cities / 439 units) while slot 0 is
|
||||
conquered from 8 cities down to 1/0. Real combat, conquest, city loss, a winner
|
||||
by force.
|
||||
|
||||
**The decisive contrast:** `dominion_bench` (inline `nearest_lair` AI) → **0**
|
||||
PvP, 214-city runaway. `long_game_transcript` (`run_ai_turn`) → **2531 kills**,
|
||||
47-city winner. **Same `mc-turn::step`, different AI.** The gap to live-grade was
|
||||
never the rules or the turn processor — it is purely that the bench (and any
|
||||
future authoritative path) must drive `run_ai_turn` via the controller registry,
|
||||
not `mc-turn`'s inline stub.
|
||||
|
||||
**Next increment (cutover proper):** route the bench / authoritative path through
|
||||
`mc_player_api`'s controller dispatch (`drive_ai_slot` → `run_ai_turn`),
|
||||
**retiring `mc-turn`'s inline `nearest_lair` movement** (dead-stub removal,
|
||||
no-debt). Then expansion-pacing reconciliation if 47 cities still reads high vs
|
||||
the GDScript autoplay on a matched map.
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ use crate::game_state::{GameState, MapUnit, PlayerState, TechState};
|
|||
/// [`AbstractRolloutState`].
|
||||
///
|
||||
/// Player slots are filled in `state.players` order, capped at
|
||||
/// [`MAX_PLAYERS`] (= 5). Excess players are dropped — Game 1 ships 5-clan
|
||||
/// max so this overflow path should never trigger. Missing slots stay
|
||||
/// zero-initialised (the POD's `Zeroable` default).
|
||||
/// [`MAX_PLAYERS`] (= 12, the `huge` map's `max_players`). Excess players are
|
||||
/// dropped without panic. Missing slots stay zero-initialised (the POD's
|
||||
/// `Zeroable` default).
|
||||
///
|
||||
/// Determinism: same `GameState` → byte-identical POD. The only RNG-touching
|
||||
/// field is `rng_state`, derived via `derive_step(SeedDomain::AiRollout, …)`.
|
||||
|
|
|
|||
|
|
@ -216,23 +216,25 @@ fn four_player_projection_fills_every_slot() {
|
|||
}
|
||||
}
|
||||
|
||||
// ── 4. Five-player overflow ─────────────────────────────────────────────────
|
||||
// ── 4. Player overflow ──────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn five_players_overflow_truncates_to_max_players() {
|
||||
fn overflow_truncates_to_max_players() {
|
||||
// Push MAX_PLAYERS + 1 players to exercise the truncation path: the POD has
|
||||
// exactly MAX_PLAYERS (= 12, the `huge` map's max_players) slots, so the
|
||||
// excess player must be dropped without panic or overflow.
|
||||
let mut state = GameState::default();
|
||||
for i in 0..5u8 {
|
||||
for i in 0..(MAX_PLAYERS as u8 + 1) {
|
||||
let mut p = player(i);
|
||||
p.gold = 7;
|
||||
state.players.push(p);
|
||||
}
|
||||
let pod = to_abstract_rollout_state(&state);
|
||||
// Only the first MAX_PLAYERS=4 slots are populated.
|
||||
// All MAX_PLAYERS slots are populated from the first MAX_PLAYERS players.
|
||||
for i in 0..MAX_PLAYERS {
|
||||
assert_eq!(pod.players[i].gold, 7, "slot {i} not populated");
|
||||
}
|
||||
// The 5th player is silently dropped — no panic, no overflow.
|
||||
// (Only 4 POD slots exist; nothing more to assert.)
|
||||
// The (MAX_PLAYERS+1)th player is silently dropped — no panic, no overflow.
|
||||
}
|
||||
|
||||
// ── 5. Determinism ──────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue