docs(simulation-report): 📝 Update technical debt audit report documentation with latest findings and fixes

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-10 08:13:03 -07:00
parent 66fb634d58
commit 5ab6167857

View file

@ -0,0 +1,122 @@
# Architecture Tech Debt Audit — GDScript Simulation Logic
**Date:** 2026-04-09 | **Auditor:** code-explorer agent | **Scope:** Full engine/src/ scan
> **Architecture rule:** "Rust is the simulation source of truth. GDScript is the presentation layer — rendering, UI, input, signals, and thin wrappers that delegate to Rust GDExtension."
---
## Critical Findings (ranked by impact)
### 1. CRITICAL — Ecology simulation runs TWICE per turn
- `src/game/engine/src/modules/climate/climate.gd:83` → Rust `GdEcologyPhysics::process_step` (correct)
- `src/game/engine/src/modules/management/turn_processor.gd:403-406` → GDScript `EcosystemOrchestrator::process_turn`
Same tile data, two mutation passes per turn. Flora canopy/undergrowth accumulates at ~2× intended rate. The GDScript ecology (ecosystem.gd ~308 lines + flora.gd ~405 lines) was originally a "transpiler target" — the transpiler was deleted, the functions were never ported, and they're now the live simulation alongside the Rust pass.
**Fix:** Remove the GDScript ecology pass. `GdEcologyPhysics::process_step` is the canonical Rust path.
### 2. CRITICAL — 4 stub modules called by turn_processor.gd produce SILENT no-ops
| Call site | Stub | Method called | Effect |
|---|---|---|---|
| `turn_processor.gd:268` | `culture.gd` (2 lines) | `CultureScript.process_turn(city, game_map, player)` | Culture = always 0, no border expansion |
| `turn_processor.gd:273` | `happiness.gd` (2 lines) | `HappinessScript.process_turn(player, game_map)` | Happiness never updates |
| `turn_processor.gd:386` | `government.gd` (2 lines) | `GovernmentScript.process_anarchy(player)` | Anarchy never decays |
| `turn_processor.gd:299,305` | `spell_system.gd` (2 lines) | `sys.tick_overworld_casts()`, `sys.tick_enchantments()` | All spells dead |
**BONUS:** `HappinessScript.UNHAPPY_THRESHOLD` referenced at `turn_processor.gd:172` resolves to null/0 (stub has no constants), so the happiness gate for city growth is permanently disabled. Any happiness value passes the check.
**Fix per module:** Either bridge from Rust crate (mc-culture, mc-happiness, mc-magic exist) or port the reference implementation.
### 3. HIGH — turn_processor.gd has ~500 lines of simulation logic
The following phases are pure GDScript game logic, not Rust delegation:
| Phase | Lines | Should be |
|---|---|---|
| `_process_production` | L51-102 (52 lines) | mc-turn or mc-city |
| `_process_research` | L104-155 (52 lines) | mc-tech (crate exists, has TechWeb + ResearchResult) |
| `_process_growth` | L166-228 (40 lines) | mc-city |
| `_process_healing` | L180-228 (40 lines) | mc-turn |
| `_process_mana` | L231-255 (25 lines) | mc-magic |
| `_process_improvements` | L477-495 (18 lines) | mc-economy |
| `_apply_dead_zone_enchantment_decay` | L409-453 (45 lines) | mc-magic |
mc-turn::TurnProcessor::step() has its OWN Rust implementations of most of these phases (used by the bench). Two parallel pipelines exist — the bench uses Rust, the game uses GDScript.
### 4. HIGH — AI is full GDScript, bypasses mc-ai entirely
- `ai_tactical.gd` (406 lines): movement decisions, combat prediction (`_predict_combat` L292 uses simplified `30 * atk / def` — drifted from GdCombatResolver), city founding
- `ai_military.gd` (234 lines): threat scoring, army composition analysis, garrison assignment
- `ai_player.gd` (2 lines): stub, now deleted from turn_manager imports
`mc-ai` crate has `evaluator::ScoringWeights`, `mcts` module — never called from GDScript.
### 5. HIGH — ecology_db.gd is a full GDScript creature database (416 lines)
In-memory relational store (species, creatures, water_bodies, food_web) with hand-rolled CRUD, spatial indexes, and save/load serialization. `mc-ecology` crate exists but doesn't wrap this.
### 6. MEDIUM — AI combat prediction has drifted from actual resolver
`ai_tactical.gd::_predict_combat` (L292-311): `damage = 30 * atk / def_val`, ignores keywords, D20 attributes, fortification
`GdCombatResolver::resolve`: full CombatParams with keywords, D20, flanking, all modifiers
AI attack decisions use wrong damage estimates.
### 7. MEDIUM — Rust fauna pipeline is complete but disabled
`RUST_FAUNA_ENCOUNTERS` env flag is not set in `.env.production`. The entire iter 7h-7k bridge (GdTurnProcessor + GdGameState + step_encounters_only) runs zero encounters in the actual game. Comment says "iter 7l will decide which to keep" — unresolved.
---
## Rust Crate ↔ GDExtension Bridge Gap
| Crate | Has Gd* class? | GDScript consumer wired? |
|---|---|---|
| mc-core | GdGridState | YES |
| mc-climate | GdClimatePhysics, GdAtmosphericChemistry, GdClimateSpecEval | YES |
| mc-mapgen | GdMapGenerator | YES |
| mc-combat | GdCombatResolver (iter 7o) | YES (but combat_resolver.gd has Unit field drift) |
| mc-city | GdCity | PARTIAL (item production wired, other city methods not) |
| mc-items | GdItemSystem, GdLootRoller, GdStockpile, GdTreasury | YES |
| mc-turn | GdTurnProcessor, GdGameState | ENV-GATED OFF |
| mc-ecology | GdEcologyPhysics | YES (but duplicated by GDScript ecosystem.gd) |
| mc-flora | (via mc-ecology) | YES |
| mc-tech | **NONE** | Tech research runs in GDScript turn_processor.gd |
| mc-ai | **NONE** | AI runs in GDScript ai_tactical.gd / ai_military.gd |
| mc-economy | **NONE** | economy.gd is a 2-line stub |
| mc-culture | **NONE** | culture.gd is a 2-line stub |
| mc-happiness | **NONE** | happiness.gd is a 2-line stub |
| mc-magic | **NONE** | spell_system.gd is a 2-line stub |
| mc-observation | **NONE** | GdObservationStore was referenced but doesn't exist |
| mc-balance | (bench-only) | N/A |
| mc-compute | (GPU acceleration) | N/A |
**6 crates have no bridge at all.** These represent the bulk of the architecture gap.
---
## Recommended Priority Sequence
### Phase 1 — Stop the bleeding (CRITICAL fixes)
1. **Remove duplicate ecology pass** — delete `turn_processor.gd`'s `_process_ecology` call. The Rust `GdEcologyPhysics::process_step` in climate.gd is the canonical path.
2. **Enable Rust fauna encounters** — set `RUST_FAUNA_ENCOUNTERS=true` in `.env.production` and `.env.development`. The bridge was proven across 10 iterations.
### Phase 2 — Bridge the unbridged (HIGH — per-crate, parallelizable)
Each crate gets a Gd* class in api-gdext following the GdCombatResolver pattern:
3. **GdTechWeb** — bridge mc-tech, replace `_process_research` GDScript with delegation
4. **GdAiController** — bridge mc-ai, add `decide_actions(state, player_index) -> Array[Dictionary]`
5. **GdEconomy** — bridge mc-economy, replace `_process_economy` stub
6. **GdCulture** — bridge mc-culture, replace culture.gd stub
7. **GdHappiness** — bridge mc-happiness, replace happiness.gd stub
8. **GdSpellSystem** — bridge mc-magic, replace spell_system.gd stub
### Phase 3 — Retire GDScript simulation
9. **Replace turn_processor.gd phases** with per-phase Rust delegation (similar to how _process_rust_fauna_encounters delegates to RustFaunaIntegration)
10. **Retire ai_tactical.gd + ai_military.gd** — fully replaced by GdAiController
11. **Retire ecology_db.gd** — replaced by mc-ecology's creature system
### Phase 4 — Unify pipelines
12. **Make GdTurnProcessor.step() the canonical turn** — GDScript turn_manager calls ONE Rust method per player turn, receives results as dicts, dispatches EventBus signals. The 500-line turn_processor.gd becomes a 50-line signal dispatcher.