docs(p1-29j): 📋 File autoplay→Rust action-application objective (p1-29d unblock)
p1-29i's full-game validation pinned the root cause that gates all of Wave-C AI convergence: the autoplay AI applies city-founding/capture via GDScript (`ai_turn_bridge_dispatch.gd:170 dispatch_found_city` → CityScript.new() → EventBus.city_founded.emit), NEVER calling Rust `mc_turn::processor:: try_found_city` / `process_siege` where the balance levers live. So every Rust-side founding/siege lever (p1-29i refound_suppression, and successors) is inert by construction on the p1-29d gate surface — a Rail-1 violation on the action-application seam. Spec-only (Rail-1, scope game1, owner warcouncil, status stub). Large separate effort, gated on p2-65 (mc-state gives the bridge a persistent GdGameState to apply actions against). References p1-29d Findings A/2/3 and p1-29i's terminal result. Added to objectives README dashboard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0bace0e6ce
commit
39bf244f74
2 changed files with 123 additions and 0 deletions
|
|
@ -124,6 +124,7 @@
|
|||
| [p1-29a](p1-29a-last-stand-defense.md) | 🟡 partial | Last-stand defense — combat-strength multiplier when defender is at last city | [combat-dev](../team-leads/combat-dev.md) | 2026-05-13 |
|
||||
| [p1-29b](p1-29b-tier-gap-ai-quality.md) | ✅ done | "AI tech tier gap — structural research path quality (low-pop AI fails to reach t1+)" | [warcouncil](../team-leads/warcouncil.md) | 2026-05-07 |
|
||||
| [p1-29c](p1-29c-sole-city-research-path.md) | 🔴 stub | "Sole-city research path — lift trailing AI from tier_peak=1 to ≥2" | [unassigned](../team-leads/unassigned.md) | 2026-05-13 |
|
||||
| [p1-29j](p1-29j-autoplay-rust-action-application.md) | 🔴 stub | "Route autoplay action-application (city-founding / capture) through Rust mc_turn::processor — real unblock for p1-29d D1" | [warcouncil](../team-leads/warcouncil.md) | 2026-06-04 |
|
||||
| [p1-30](p1-30.md) | ✅ done | "Optimize `_build_tactical_state` — 8000-tile GDScript dict-build per AI turn blocks p1-22 huge-map gate" | [warcouncil](../team-leads/warcouncil.md) | 2026-05-04 |
|
||||
| [p1-31](p1-31-split-bundled-building-resources.md) | ✅ done | Split bundled `resources/buildings/<category>.json` into per-file pattern matching `resources/units/` | — | 2026-04-27 |
|
||||
| [p1-32](p1-32-food-chain-buildings.md) | ✅ done | Author the two missing food/processing buildings (sawmill, herbalist) | — | 2026-05-03 |
|
||||
|
|
|
|||
122
.project/objectives/p1-29j-autoplay-rust-action-application.md
Normal file
122
.project/objectives/p1-29j-autoplay-rust-action-application.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
id: p1-29j-autoplay-rust-action-application
|
||||
title: "Route autoplay action-application (city-founding / capture) through Rust mc_turn::processor"
|
||||
priority: p1
|
||||
status: stub
|
||||
scope: game1
|
||||
category: architecture
|
||||
owner: warcouncil
|
||||
created: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
blocked_by: []
|
||||
relates_to: [p1-29d-p1-survival, p1-29i-refound-suppression, p1-29h-stateful-tactical-decisiveness]
|
||||
---
|
||||
|
||||
## Why this exists
|
||||
|
||||
p1-29i ran the deferred full-game validation of the refound-suppression lever and resolved a
|
||||
deeper architectural root cause that gates **all** of Wave-C AI-convergence work, not just that
|
||||
one lever:
|
||||
|
||||
> The autoplay AI applies city-founding (and city capture) via **pure GDScript** —
|
||||
> `ai_turn_bridge_dispatch.gd:170 dispatch_found_city` (`CityScript.new()` →
|
||||
> `player.cities.append` → `EventBus.city_founded.emit`, lines 190–204) — and **never calls** the
|
||||
> canonical Rust `mc_turn::processor::try_found_city` / `process_siege`. So every Rust-side
|
||||
> AI/combat-balance lever that lives behind those functions (the `refound_suppression` cooldown +
|
||||
> `last_city_lost_turn` stamp, and by extension any future founding/siege gate) is **inert by
|
||||
> construction on the p1-29d gate surface.** (Grep-verified: no GDScript refound gate /
|
||||
> `last_city_lost` / cooldown exists anywhere; capture in the autoplay loop has zero matches for
|
||||
> `process_siege` in `turn_processor.gd` / dispatch.)
|
||||
|
||||
This is the same class of trap as the `process_science` / `GdTechWeb` split p1-29d Finding §2/§3
|
||||
documents: the headless Rust path and the live GDScript autoplay path are two different
|
||||
implementations of the "apply this action" step, and the gate measures the GDScript one while
|
||||
the levers land in the Rust one. **Rail 1 (Rust is the simulation source of truth) is violated on
|
||||
the action-application seam**, and that violation is precisely what makes the AI-convergence gate
|
||||
(p1-29d D1) unmovable by Rust-side levers.
|
||||
|
||||
This objective is the **real unblock for p1-29d D1**: until autoplay applies founding/capture
|
||||
through `mc_turn::processor`, data-driven combat-balance levers (p1-29i and successors) cannot
|
||||
take effect on the surface the convergence gate measures, and the AI-quality work is measuring a
|
||||
GDScript shadow of the simulation rather than the simulation itself.
|
||||
|
||||
## Scope (author-the-spec only; do NOT implement here)
|
||||
|
||||
This is a **large, separate effort** — the action-application seam touches the GDScript
|
||||
turn/dispatch layer, the `api-gdext` bridge surface, and the Rust `mc_turn::processor` action
|
||||
handlers, and it must preserve byte-identical save format + not regress the live game's
|
||||
playability. It is sequenced AFTER the p2-65 `mc-state` extraction (which gives the bridge a
|
||||
persistent `GdGameState` to apply actions against, instead of the throwaway-per-call pattern). It
|
||||
is filed now so the root cause is tracked and p1-29d/p1-29i can re-point their blockers at it; the
|
||||
implementation is its own multi-session objective.
|
||||
|
||||
## The seam (verified 2026-06-04)
|
||||
|
||||
| Action | GDScript path (today; bypasses Rust) | Rust canonical (where levers live) |
|
||||
|---|---|---|
|
||||
| Found city | `ai_turn_bridge_dispatch.gd:170 dispatch_found_city` — `CityScript.new()` → `player.cities.append` → `EventBus.city_founded.emit` | `mc_turn::processor::try_found_city` (refound gate, `last_city_lost_turn`, event emission) |
|
||||
| Capture / siege | autoplay turn loop (`turn_processor.gd` / dispatch) — direct ownership flip; **no `process_siege` call** | `mc_turn::processor::process_siege` (`cities_lost_total` increment, `last_city_lost_turn` stamp) |
|
||||
| Research | GDScript `TurnManager.get_tech_web()` → `GdTechWeb` | `mc_turn::processor::process_science` (the p1-29d §2/§3 precedent — same split) |
|
||||
|
||||
The capture/siege row is the most consequential for AI convergence: p1-29h's army-lock produces
|
||||
captures, but the *consequence* of a capture (loss stamp, elimination bookkeeping, refound gate)
|
||||
is computed differently on the two paths.
|
||||
|
||||
## Acceptance (for the future implementation — not met yet; status: stub)
|
||||
|
||||
- ✗ Autoplay city-founding routes through a Rust action that calls `mc_turn::processor::try_found_city`
|
||||
(via the `api-gdext` bridge / `mc_player_api::apply_action` surface), so `dispatch_found_city`
|
||||
becomes a thin presentation wrapper over the Rust result instead of an independent reimplementation.
|
||||
- ✗ Autoplay capture / city loss routes through `mc_turn::processor::process_siege` (or its bridge
|
||||
equivalent), so `cities_lost_total` / `last_city_lost_turn` are stamped on the gate surface.
|
||||
- ✗ Data-driven combat-balance levers behind those functions (starting with p1-29i's
|
||||
`refound_suppression.cooldown_turns`) demonstrably fire on the autoplay surface — proven by a
|
||||
controlled before/after full-game batch (same build, only the JSON value changed) showing a
|
||||
measurable behavioural delta where p1-29i measured **none**.
|
||||
- ✗ Save-format byte-identical pre/post (the action-application change must not alter on-disk shape).
|
||||
- ✗ No regression to live-game playability: GUT headless suite green, autoplay 10-seed batch
|
||||
completes, and the juiced-P0 asymmetry (p1-29d Finding A) is either preserved deliberately or
|
||||
explicitly de-juiced as part of the `AUTO_PLAY_ALL_AI` option (p1-29d §3 option (a)).
|
||||
- ✗ p1-29d D1 re-scored on the corrected surface once levers are live (this objective unblocks that
|
||||
re-score; it does not itself promise convergence — that remains balance-design-paced per the
|
||||
FINISH_GAME1_PLAN Wave-C risk box).
|
||||
|
||||
## Source-of-truth rails
|
||||
|
||||
- **Rail 1 (the one this objective restores):** action-application is simulation logic and MUST
|
||||
resolve in `mc_turn::processor` (Rust). GDScript `dispatch_*` becomes presentation — it asks the
|
||||
bridge to apply the action and renders the result; it does not compute the result.
|
||||
- **Rail 2:** combat-balance levers stay data-driven (`combat_balance.json`), loaded at runtime via
|
||||
`game_state.gd:224 _load_combat_balance_into` → `set_combat_balance_json`. This objective makes
|
||||
that already-loaded config actually reach the founding/capture code path.
|
||||
- **Save format:** byte-identical pre/post.
|
||||
|
||||
## Dependencies / sequencing
|
||||
|
||||
- **Gated on p2-65** (`mc-state` extraction): the bridge needs a persistent `GdGameState` to apply
|
||||
actions against. Today each call site instantiates a throwaway `GdGameState`, discards it after
|
||||
one Rust step, and the GDScript-side state (`player.cities`) is the real store — which is exactly
|
||||
why founding/capture were reimplemented in GDScript. p2-65 + p2-72a (canonical-render-source) make
|
||||
`GdGameState` the persistent store; this objective then routes mutations through it.
|
||||
- **Unblocks** p1-29d D1 re-score and re-validates p1-29i (and any future founding/siege lever).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Solving AI convergence itself (p1-29d / p1-29h-Phase-2 / p1-29i) — this is the *plumbing* that
|
||||
lets those levers act on the gate surface, not the balance design.
|
||||
- The learned-controller track (p1-29f/g).
|
||||
- The full PlayerScript/UnitScript/GameMap → thin-view-over-`GdGameState` refactor (p2-72a) — this
|
||||
objective is the action-application slice, which can land incrementally on top of p2-72a's state
|
||||
store.
|
||||
|
||||
## References
|
||||
|
||||
- `.project/objectives/p1-29i-refound-suppression.md` — the full-game validation that pinned this
|
||||
root cause (§"Full-game validation", §"Terminal result").
|
||||
- `.project/objectives/p1-29d-p1-survival.md` — gap analysis §"Findings A / 2 / 3" (the
|
||||
`AUTO_PLAY_ALL_AI` option and the GDScript/Rust split); its updated blocker re-points here.
|
||||
- `src/game/engine/src/modules/ai/ai_turn_bridge_dispatch.gd:170` — `dispatch_found_city`, the
|
||||
GDScript founding path that bypasses Rust `try_found_city`.
|
||||
- `src/game/engine/src/autoloads/game_state.gd:224` — `_load_combat_balance_into` (runtime JSON load).
|
||||
- `src/simulator/crates/mc-turn/src/processor.rs` — `try_found_city` / `process_siege` / `process_science`,
|
||||
the canonical Rust action handlers where balance levers live.
|
||||
Loading…
Add table
Reference in a new issue