feat(@projects/@magic-civilization): ✨ debug pvp combat logic gaps
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
f7e70c1844
commit
3765dc8b9f
2 changed files with 49 additions and 0 deletions
|
|
@ -86,3 +86,42 @@ world-events via the bridge, orchestrated by `turn_manager.gd`), proven
|
|||
2 is the residual *architectural-purity* question of **where the turn
|
||||
orchestration lives** (Rust vs GDScript) — unblockable only by the inversion
|
||||
above.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 root-cause (2026-06-08) — why the Rust turn isn't live-grade
|
||||
|
||||
Probed `dominion_bench` (real `mc-turn::TurnProcessor::step`, 4 AI clans) at two
|
||||
map sizes. **Both produce functional but militarily-inert games:**
|
||||
|
||||
| probe | victory | PvP battles | captures | runaway |
|
||||
|---|---|---|---|---|
|
||||
| 40×40, 4p | P2 city_count @ t87 | **0** | **0** | P1 → 111 cities |
|
||||
| 20×20, 4p (forced adjacency) | P2 city_count @ t51 | **0** | **0** | P2 → **214 cities / 400 tiles** |
|
||||
|
||||
Even with four clans in adjacent corners of a 20×20 map, **zero** inter-player
|
||||
combat. P0/P1/P3 end with 0 cities/0 units yet `cities_lost = cities_captured =
|
||||
pvp_d = 0` — eliminated without recorded combat (P2's unbounded founding
|
||||
consumed the map).
|
||||
|
||||
**Verified root cause:** the capability is all present and composed —
|
||||
`mc-turn` wires `process_pvp_combat` (`processor.rs:522`), city siege (`:524`),
|
||||
and `mc_ai::tactical`; `mc-ai` evaluates offensive moves ("advance toward each
|
||||
enemy city", `evaluator.rs:134-150`). **But `mc-turn`'s per-unit movement loop
|
||||
seeks `nearest_lair` (`processor.rs:2025, 2271` — PvE only), bypassing
|
||||
`mc-ai`'s tactical targeting.** Units never move toward enemies → never become
|
||||
adjacent → `process_pvp_combat` never triggers. This is the "bench-grade AI
|
||||
decision layer" the `mc-turn` header names: real rules crates, minimal movement
|
||||
heuristics.
|
||||
|
||||
**Two gaps to close (pure-Rust, benched, zero Godot risk):**
|
||||
1. **Movement → mc-ai tactical.** Drive per-unit movement from `mc-ai`'s
|
||||
tactical advance/defend candidates (which already target enemy cities), not
|
||||
the inline lair-seek. This makes contact happen → PvP/siege fire.
|
||||
2. **Expansion sanity.** `try_found_city` needs city-spacing + a soft cap so
|
||||
the expansion axis stops carpeting the map (214 cities/400 tiles is
|
||||
degenerate; the live game spaces cities).
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -3455,6 +3455,16 @@ impl TurnProcessor {
|
|||
if killed.iter().any(|&(p, u)| p == def_pi && u == def_ui) {
|
||||
continue;
|
||||
}
|
||||
// Skip if the unit no longer exists: an earlier kill/capture in this
|
||||
// same phase `swap_remove`d it (or emptied the defender's vec),
|
||||
// leaving this pending-capture index stale. Indexing the shrunk vec
|
||||
// would panic — this is the turn-8 PvP crash at processor.rs:2697
|
||||
// ("len is 0 but the index is 0") that surfaces once the real AI
|
||||
// (`run_ai_turn`) drives actual captures. Same intent as the
|
||||
// `killed` dedup above: a unit that left the vec is not capturable.
|
||||
if def_pi >= state.players.len() || def_ui >= state.players[def_pi].units.len() {
|
||||
continue;
|
||||
}
|
||||
self.transfer_captured_unit(state, def_pi, def_ui, captor);
|
||||
// Re-index the leftover `killed` and `ransom_pending` entries that
|
||||
// referred to indices ≥ def_ui in the same player vec. swap_remove
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue