diff --git a/.project/objectives/p3-30-wild-creature-ai-rust-port.md b/.project/objectives/p3-30-wild-creature-ai-rust-port.md index 939a539b..666e3f7e 100644 --- a/.project/objectives/p3-30-wild-creature-ai-rust-port.md +++ b/.project/objectives/p3-30-wild-creature-ai-rust-port.md @@ -54,14 +54,16 @@ GDScript decision system. target-select, step-toward (chase), attack (iff adjacent, per the player-tactical convention), leash-return, city-drift, and leashed roam, reusing the existing `Action` taxonomy + `mc_core` hex helpers + `XorShift64`. Combat resolution stays in `mc_combat::wilds` (no formula drift). -- [ ] Driven from inside `mc_turn::TurnProcessor::step` as a turn phase (so the p3-29 swap needs - no wild carve-out), OR via a `GdWildAiController` bridge if it must stay engine-driven — - infra decides; default is in-`step`. **BLOCKED ON FORK DECISION (see Notes 2026-06-27):** the - headless `GameState` has NO roaming wild-unit substrate (units are implicit-owned per - `PlayerState::units`; no `owner == -1`; wilds are modeled as `npc_buildings` lairs + - `process_fauna_encounters`). "Drive in `step`" therefore requires first BUILDING a headless - roaming-wild substrate (spawn at lairs, store, persist) that partly duplicates the encounter - system — a real scope/architecture fork vs. the allowed `GdWildAiController` bridge path. +- [~] Driven via a **`GdWildAiController` bridge** — **OWNER RULED 2026-06-27: bridge, not the + in-`step` substrate** (the headless `GameState` has no roaming wild-unit substrate — units are + implicit-owned per `PlayerState::units`, no `owner == -1`; wilds are `npc_buildings` lairs + + `process_fauna_encounters` — so an in-`step` path would build a second wild model that duplicates + encounters). **Rust side DONE (3964e55b4):** `mc_ai::wild::decide_wild_actions_json` (JSON + `WildContextDto` contract) + `GdWildAiController` GDExtension shim (`set_rng_seed` + + `decide_actions → PackedStringArray`, the `GdAiController` envelope); cdylib links with the class + auto-registered. **Remaining (render-gated):** GDScript builds the DTO from the live game_map + + primary-layer units in `_process_wild_creatures` and dispatches the returned Actions via the + existing AI-action path. - [ ] `wild_creature_ai.gd` DELETED (or reduced to a thin bridge with zero decision logic); `_process_wild_creatures` becomes a no-op or bridge call. **(render-gated — live turn swap.)** - [x] Determinism preserved — same seed + state → same wild actions (XorShift64). Covered by @@ -108,3 +110,19 @@ without building a second wild model headless. "Everything inside `step()`" is t but a headless roamer substrate duplicating encounters is the kind of disabled/parallel system the rails warn against. Surfaced to owner; integration (A vs B) + the `.gd` deletion are the remaining work and are render/decision-gated. + +**OWNER RULED B (2026-06-27).** Bridge built + verified headless (3964e55b4): the JSON contract +(`WildContextDto`) + `decide_wild_actions_json` helper in `mc_ai::wild` (16 tests, mc-ai 305/0) + +the `GdWildAiController` shim in `api-gdext/src/ai.rs` (cdylib links, class auto-registered). The +only remaining p3-30 work is the live-game GDScript rewire, which is render-gated: +1. `turn_processor.gd::_process_wild_creatures` builds the `WildContextDto` JSON from the live + `game_map` (passable = in-bounds ∧ ¬water ∧ ¬player-occupied) + the primary-layer `owner == -1` + units + lairs (`npc_buildings_all` minus village/ruin) + city positions + the `wilds` config. +2. Calls `GdWildAiController.decide_actions(json)`; applies the returned `MoveUnit`/`AttackTarget` + records via the existing AI-action dispatch (move along path / `CombatResolver.resolve`). +3. Deletes `wild_creature_ai.gd`'s decision logic (`_act`/`_find_attack_target`/`_roam`/etc.); + `spawn_initial_creatures` stays (start/worldgen path, not a per-turn decision). +4. Render-proof: a headless/live run showing wilds still guard lairs, attack units in radius, and + roam within leash (parity vs the pre-port baseline), + GUT green. +**Deferred: needs a render/live-run host** — shipping the live-turn rewire without the parity +proof would violate the Rail UI rule + phase gate, so it is NOT done blind.