Commit graph

1029 commits

Author SHA1 Message Date
Natalie
c428402698 docs(design): record Phase-1 live unit store scaffolded (fba5cdfd)
unit_slot.rs + GdGameState.presentation_units + GdUnit + SaveEnvelope v4 land the
Rust home for the units hold-out (mirrors city_slot/presentation_cities). Rust-side
foundation done + headless-proven (unit_slot 7/0, save_envelope 6/0); remaining is
the render-gated live wiring (route unit input through the delegators, reduce
UnitScript to a view). MapUnit was already rich — no model widening needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:16:33 -04:00
Natalie
f0e483397d docs(objective): record B6c headless promotion system (apply + combat effects)
Promotions were half-built headless (eligible + AI-picked but never consumed,
combat read no modifier). Now closed: MapUnit.promotions, mc-turn consume phase,
mc-combat effect registry + per-unit combat modifiers at both PvP sites. 9/18
effect types live (every combat-expressible one); 9 deferred on missing combat
substrate (auras/multi-attack/splash/movement-waivers/status-on-attack) — flagged
as separate combat-feature objectives for an owner scope call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:53:36 -04:00
Natalie
82dcffce4d docs(objective): p3-25 Phase-0 culture_stored landed; clarify unit-XP deferral
Mark CityView.culture_stored done (04763a387) — the last clean Phase-0 projection
gap. Note UnitView.experience already projects bench MapUnit XP; the deferred item
is the live-unit XP store (Phase 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:08:15 -04:00
Natalie
6332d47011 fix(infra): make the DO fleet actually work on real hardware + render host
Real-DO testing surfaced bugs the mocked tests couldn't:
- ssh key: reference shared 'mc-fleet' key via data source, not a duplicate (DO 422s on dup pubkeys).
- cmd_dist_up: fail loudly on failed apply; dist:up waits for cloud-init readiness.
- snapshot cloud-init skips runcmd -> bake authorized_keys (FLEET_PUBKEY) + 'cloud-init clean' before snapshot.
- build user passwordless sudo; apt dpkg-lock race fixed (cloud-init --wait + Lock::Timeout).
- size s-8vcpu-16gb-amd (tier max); creds via PKR_VAR env not argv.
- render host: weston+Mesa baked; ./run dist:render proven (Godot->PNG on DO, no GPU). forge:dns shortcut.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:45:29 -04:00
Natalie
9ee33f49ed chore(@projects/@magic-civilization): 📇 regen objectives dashboard (timestamp)
Auto-regenerated objectives.json; totals unchanged (298 done).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 09:47:49 -04:00
Natalie
655d25e2c1 docs(@projects/@magic-civilization): 🛡️ Rail-2 — document the two-path content divergence + track an enforcement gate
rust-source-of-truth.md: add the "two-path divergence" rule to the canonical
content store section. Content reaches the sim two ways — in-game (GDScript
DataLoader reads JSON at runtime, projection.rs:41) and headless (Rust falls back
to a compile-time include_str!/hardcode copy, dispatch.rs:410). A balance constant
hardcoded in a crate is both a Rail-2 violation and a silent second copy that
drifts from the JSON — and headless is where the AI trains. Rule: grep
public/resources + public/games/**/data for a JSON home before adding a numeric
balance const; if it exists, LOAD it (OnceLock+include_str!, never std::fs in
shared sim code). References the p3-28 ContentRegistry endgame.

p3-28: add the matching "Rail-2 verify gate (enforcement)" acceptance bullet —
tools/check-no-rust-hardcoded-content.py + a verify step to catch the next
hardcode, best landed alongside the ContentRegistry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 09:47:43 -04:00
Natalie
b4c402e766 docs(@projects/@magic-civilization): p3-26 Gap 3 DONE (equipment/crafting verified headless) + Gap 4 scope assessment
Gap 3 — Equipment/crafting: verified the full craft→equip→combat path runs
headless and Rust-authoritative (orig bullet was stale at [ ]):
  - PlayerAction::CraftEquipment → craft_equipment dispatch (materials gate +
    consume strategic_ledger + equip), 2 tests
  - recipe_phase ("recipe_refine") in END_OF_TURN_PHASES — passive crafting
    economy refines raw→quality-tiered product every self-play turn, 1 test
  - equip_combat_bonus reads boot-loaded item_combat at every combat site, 2 tests
  - boot path: set_item_combat_json FFI ← headless harness _apply_item_combat
  - MCTS AI not electing to craft = deliberate 9-kind GPU-rollout constraint,
    not a missing system
  Verified green: mc-turn + mc-player-api 557/0.

Gap 4 — Per-building queues: recorded verified assessment. Bench single-slot +
per-turn AI reselection is functionally equivalent to a FIFO build queue for the
self-play SIMULATION outcome; the multi-item queue is a live-game UI affordance
belonging to the p3-25/p3-29 projection arc. Owner scope call pending: does p3-26
require simulating a multi-item queue, or reclassify Gap 4 out of the headless bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 09:44:11 -04:00
Natalie
081cddcab3 docs(@projects/@magic-civilization): 🛤️ Rail-1 design — narrow the dual-model fork (cities ~done, units are the hold-out)
Verified the live city-projection path: api-gdext/city_slot.rs is a full ops
module over presentation_cities (rich mc_city::City) + GdCity wraps it; CityScript
is a hybrid proxy already routing to the Rust slot. So cities are largely
Rust-authoritative — the GDScript residue is just the city-centre queue +
placed_buildings. UNITS are the real Phase-1 hold-out (UnitScript fully
GDScript-authoritative, no Rust slot). Rails: bench CityState/MapUnit and the live
game are sanctioned-separate contexts (code-layering #3), so the live view must
project the RICH city / a new live-unit store — NOT the bench types. Refines the
Phase-1 plan accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 08:29:44 -04:00
Natalie
76b3e48ae3 docs(@projects/@magic-civilization): 🛤️ p3-25 — record Phase-0 projection increments + blueprint link
Re-scope p3-25's view-completeness as Phase 0 of the owner's full UI-pure-view
migration (blueprint: designs/p3-rail1-ui-pure-view-migration-design.md). Log
the two landed increments (unit movement+posture 568e43084, golden age 0d501a3d7)
and the deferred bench-model gaps (XP, culture_stored, building_queues,
placed_buildings → Phase-1 SOT flip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 08:03:51 -04:00
Natalie
d78152388a docs(@projects/@magic-civilization): 🛤️ Rail-1 endgame — UI-pure-view migration blueprint
Owner directive: remove all sim logic from GDScript; UI becomes a pure view of
the Rust game server. Phased design grounded in a verified 3-surface audit +
view.rs/projection.rs/game_state.rs/turn_*.gd ground-truth. Key finding: the
live game holds TWO unsynced authoritative stores (GDScript entities = live SOT;
Rust GdGameState = parallel copy), so this is a spine rewrite — the live game
must converge onto the headless GdPlayerApi shape. Phases: 0 projection
completeness (headless, first), 1 SOT flip, 2 live turn = end_turn(), 3 delete
GDScript sim layer, 4 render-proof.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 07:51:19 -04:00
Natalie
edc7e31b12 docs(@projects/@magic-civilization): 📊 regen objectives dashboard (p3-27 flora, p3-30 bridge)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 07:36:07 -04:00
Natalie
acf57fd05f docs(@projects/@magic-civilization): 🐺 p3-30 — record owner ruling (bridge) + bridge landed
Owner ruled the GdWildAiController bridge over an in-step substrate. Mark
acceptance #2 [~] (Rust bridge done 8696a48aa; GDScript rewire render-gated)
and spell out the remaining live-game rewire steps + the render/live-run host
needed for the parity proof.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 07:35:17 -04:00
Natalie
ca31834db0 docs(@projects/@magic-civilization): 🌿 p3-27 — flora succession confirmed subsumed by process_step
Close the flora-succession [~] bullet. Verified in engine code (not the
comment): process_step → run_tier_advancement advances tiers in-place
(par_iter_mut → tick_tiers_capped mutates slots) and returns FloraTransitions
only as a chronicle report. The headless ecology_phase applies succession via
process_step and buffers transitions for the p3-29 FloraSuccession event — no
separate mc-flora::FloraEngine pass needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:54:35 -04:00
Natalie
e477784731 docs(@projects/@magic-civilization): 🐺 p3-30 — decision core done; integration is a verified fork
stub → partial. Acceptance #1 (decide_wild_actions) + #4 (determinism) ✓ with
cited evidence (95a2e580b, mc-ai 301/0). Record the verified premise that the
headless GameState has no roaming wild-unit substrate (units implicit-owned,
wilds = lairs + encounters), making the in-step path a substrate-build that
duplicates encounters vs. the allowed GdWildAiController bridge. Recommend the
bridge; integration + .gd deletion stay render/decision-gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:53:39 -04:00
Natalie
cbc68a68c1 docs(@projects/@magic-civilization): 🔎 p3-26 Gap-2 — era max_tier cap is non-parity; fired-event surfacing is observability-only
Verified file:line: the live GDScript events modules have NO era-based max_tier
cap (0 hits) — headless flat max_tier=10 is correct parity; an era cap would
invent a rule the game lacks (gold-plating, dropped). And natural events already
fire + apply terrain effects headless; only the fired list surfacing to
TurnResult is missing (processor.rs:1117 `let _fired =`), an observability nicety
not a system gap. Confirms the headless natural-events system is functionally
complete; narrows Gap-2's real remainder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:29:41 -04:00
Natalie
ac5efa4bec docs(@projects/@magic-civilization): 🌊 p3-26/27 — close marine gap (Rust-authoritative); drop ocean-collapse as gold-plating
Verified file:line that the marine→climate feed is already complete headless:
process_climate_phase → ClimatePhysics::process_step → compute_global_stats
writes grid.ocean_dead_fraction (reef-based, physics.rs:800) and step_evaporation
consumes it (physics.rs:460), every turn. Gap-1's "marine_harvest remaining"
is CLOSED.

Correction: mc_ecology:🌊:tick_ocean_state (4-phase trophic cascade) is
wired in NEITHER the live GDExt bridge NOR the live GDScript — the live game
runs a simple fish-stock ocean_dead_fraction (marine_harvest.gd), not the
cascade. Wiring tick_ocean_state headless would build a system the live game
doesn't run (parity ≠ gap). Marked OUT/gold-plating with citations so a future
session doesn't port it. The Rust reef-based formula vs the live fish-stock
formula is a divergence; Rail-1 → Rust drives, no reconciliation owed.

Also recorded D1 ruling (distinct ItemProduced) in p3-29.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:27:29 -04:00
Natalie
a0ace92c23 docs(@projects/@magic-civilization): p3-29 — T2/T3 done; correct T5 scope + T6 keystone status
§A event emission now DONE except ItemProduced (T4, blocked on D1):
- T2 UnitHealed (158ef4d1b), T3 GoldenAgeStarted/Ended (a87ea9f4d).
Corrected T5: the buffer pattern alone is insufficient — EcologyEngine::
process_step returns only flora transitions; fauna births/deaths + biome
changes are unreported by the headless engine (live signals come from
fauna.gd). T5 needs a new mc-ecology report surface + an owner ruling on
creature-event granularity. Also marked T6 keystone DONE (was pre-landing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:20:13 -04:00
Natalie
7681def5b5 docs(@projects/@magic-civilization): p3-29 — T1 CultureResearched done; correct §B keystone (already landed)
T1 (CultureResearched event) landed in 74844f74d. Also corrected the §B
keystone status: the generic events[] in turn_result_to_dict was marked
"NOT STARTED" but actually landed with step 2 (a9b92df51, lib.rs:6573) —
verified file:line. Code wins over the drifted note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:05:22 -04:00
Natalie
b93328cd11 docs(@projects/@magic-civilization): p3-29 — correct false claim: getState() is ALREADY a rich projection
Self-correction (the prior commit's "view_json carries almost nothing renderable" was wrong — an
over-grep that only caught the yields line). Verified PlayerView (view.rs:318): CityView carries
position/owner/population/production_queue/owned_tiles(territory)/hp/focus; UnitView carries
position/hp/movement/xp/fortified/sentry; TileView carries position/biome/improvement/river/
explored+visible(fog)/owner_city; plus resources/research/culture/civics/diplomacy/score.

So the projection foundation largely EXISTS — the gap to "UI calls getState()" is the GDScript side
(renderer reads CityScript/Player entities, not PlayerView). Recast target updated: the bulk is the
GDScript rewire to consume view_json + switch the turn to end_turn(); the only genuine projection
additions are render-only extras (animation deltas/UnitMoved, VFX, player colors, minimap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 05:33:58 -04:00
Natalie
84790caf74 docs(@projects/@magic-civilization): 🎯 p3-29 corrected (v2) — UI is a pure view of getState(); folds into p3-25
Owner: "shouldn't the UI just call getState()?" — re-asserting the p3-25 directive. Both prior
framings were wrong: v1 (swap orchestrators) and v1.5 (extract each formula into an FFI the GDScript
turn calls) BOTH keep GDScript calling logic / holding state. Correct architecture (already proven by
the headless GdPlayerApi): Rust owns ALL state + runs the whole turn (end_turn); view_json = getState
is the complete render projection; UI renders it + sends act(). The dual GameState is THE bug, not a
constraint. The 4 inlined modifiers vanish when turn_processor.gd::_process_* is deleted — no
per-formula FFI extraction. Folds into p3-25 (complete the projection). First step: projection-gap
audit (what the renderer reads from entities vs what view_json carries — today: almost nothing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 05:31:48 -04:00
Natalie
6b1d92b2af docs(@projects/@magic-civilization): 🔍 p3-29 recast — audit live turn; swap→logic-extraction (drop B7)
Owner reconsidered the swap. Audit (verified file:line) shows the live turn_processor.gd already
delegates the TICK to Rust in every phase but inlines 4 MODIFIER formulas with hardcoded constants —
_process_growth (CONFIRMED divergence from mc_happiness::get_growth_modifier), _process_production
(0.75/1.2), _process_culture (1.2), _catchup_research_mult (1.5×). Recast from a big-bang state swap
to incremental logic extraction: each phase keeps its state + FFI tick; only the inlined formula
moves to its owning crate. No state migration; former B7 city-model convergence DROPPED (dual state
is legitimate — single-source is LOGIC not STATE). Worklist added; swap steps 3-5 marked superseded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 05:21:37 -04:00
Natalie
947183f3ce docs(@projects/@magic-civilization): 📊 regen objectives dashboard after p3-26/27/29 status changes
Auto-regen of objectives.json + DASHBOARD_* reflecting this session's status edits (p3-26 B-series,
p3-27 biosphere, p3-29 steps 1-2). Bookkeeping only; split out of the agent-tooling commit to keep
both atomic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 04:29:43 -04:00
Natalie
e307db755d docs(@projects/@magic-civilization): 📡 p3-29 steps 1-2 done — Rust turn emits + surfaces UI events; swap is render-gated
Step 1 (CityGrew/CityBordersExpanded/FloraSuccession) + step 2 (events in step result dict)
complete + headless-safe. Latent siege-suppress bug fixed (7f4b69eac). Remaining: the live
turn_manager swap (steps 3-5) — render-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 03:58:32 -04:00
Natalie
a9b92df51b feat(@projects/@magic-civilization): 📡 p3-29 (step 2) — surface turn events in GdTurnProcessor.step result dict
turn_result_to_dict now includes an "events" array (each TurnEvent mapped via the reused
replay::event_to_dict, now pub(crate)) — CityGrew, CityBordersExpanded, FloraSuccession,
CityBuildingCompleted, UnitCreated, CityCaptured, etc. So when turn_manager adopts
GdTurnProcessor.step (the Rail-1 swap), it can translate result["events"] → EventBus signals
and the GDScript turn orchestration can be deleted. gdext compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 03:58:01 -04:00
Natalie
91095be232 docs(@projects/@magic-civilization): p3-29 step 1 complete — Rust turn surfaces CityGrew + CityBordersExpanded + FloraSuccession
All three granular UI events the GDScript turn emitted inline are now emitted by the Rust turn
(replay value now; UI-parity ready for the swap). Remaining: step 2 (dict surface) + steps 3-5
(turn_manager → GdTurnProcessor.step + delete GDScript orchestration + render proof).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 03:28:54 -04:00
Natalie
8e17594564 feat(@projects/@magic-civilization): 🌿 p3-29 (3) — surface FloraSuccession; step-1 event enrichment complete
Third + final p3-29 step-1 event. The ecology phase (uniform fn(&mut GameState) registry
signature, no event sink) buffers its flora-succession transitions into a transient
GameState.pending_flora_events; step() drains them into the TurnResult as
TurnEvent::FloraSuccession — single-source replacement for the GDScript turn's flora_succession
signal, avoiding a 40-call-site registry-signature cascade. Surfaced through all four TurnEvent
consumers + tested (step_drains_flora_buffer_into_flora_succession_events).

p3-29 step 1 DONE: the Rust turn now emits CityGrew + CityBordersExpanded + FloraSuccession —
the granular UI events the live game's GDScript turn emitted inline. Replay value now;
UI-parity ready for the swap (steps 3-5). Events-only → golden/combat unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 03:26:11 -04:00
Natalie
dc3fc0926d docs(@projects/@magic-civilization): 🌱 p3-29 step-1 — CityGrew + CityBordersExpanded done; FloraSuccession bundled with the swap pass
The 2 high-value growth/border events are surfaced from the Rust turn (replay value now,
UI-parity at the swap). FloraSuccession deferred: the ecology phase's registry signature
(fn(&mut GameState)) has no event sink, and the registry-events refactor belongs with the
swap (steps 3-5), so flora rides with it rather than triggering a 40-call-site cascade now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 02:47:07 -04:00
Natalie
dd31de9acb docs(@projects/@magic-civilization): 🔎 p3-29 — audit: the DRY fix's bulk is event-surfacing for UI parity, not the call-swap
TurnResult.events_emitted is replay-thin (founded/captured/killed/created); live UI needs
city_grew/building_completed/border_expanded/culture_researched/flora_succession. Real p3-29
work: enrich Rust event surface (headless-safe) → surface in dict → GDScript translates →
swap+delete → render-proof. Steps 1-2 carry no live-game risk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 02:16:39 -04:00
Natalie
1a4c9c7d36 docs(@projects/@magic-civilization): 🏛️ p3-29 — Rail-1 turn unification objective (the real DRY fix)
Owner flagged the duplication: live game runs GDScript turn orchestration (turn_processor.gd
_process_* + EcologyState.tick) while headless runs mc-turn::step — two turn orchestrations.
This session built mc-turn::step into the complete single source of truth; p3-29 is the capstone:
switch turn_manager to GdTurnProcessor.step (bridge already exists at lib.rs:6354), render the
TurnResult for UI, delete the GDScript orchestration. HIGH-STAKES live-game rewrite — needs a
render proof before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 02:13:36 -04:00
Natalie
e8e58fb278 docs(@projects/@magic-civilization): ☀️🧊 p3-26 B8 — events 9/12 (solar+glacial added)
9 mc-climate event categories live; pandemic/ecological fauna via disease applier; marine via
process_step; magical→G3. Event system effectively complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 02:03:23 -04:00
Natalie
245a0af95a docs(@projects/@magic-civilization): p3-26 B6 DONE — equipment craft→equip→combat live; loot=B5 parity-optional
B6 complete: B6a (recipe refinement) + B6b core (MapUnit.equipped + combat-read + CraftEquipment
action). Full chain — refine raw→processed → craft (consume materials) → equip → unit fights
harder. Loot drop-on-death/decay (B5) is parity-optional (disabled in the live game too).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 23:46:41 -04:00
Natalie
95e289891e docs(@projects/@magic-civilization): ⚒️ p3-26 B6a DONE — resource refinement live; B6b (equipment+combat) remaining
B6a (mc_city::recipes wired: refine raw→processed into strategic_ledger each turn) complete +
verified. B6b (unit equipment + combat reads gear + loot decay) is the combat-spanning
remainder, inert without combat integration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:52:48 -04:00
Natalie
45d52c8ce6 docs(@projects/@magic-civilization): 🔎 p3-26 B6 — equipment/crafting scoped (largest subsystem, spans combat)
mc-items (item logic) + mc-city/recipes (resource refinement) exist but unwired; bench has no
ResourceStockpile, units carry no equipped items, no Craft/Equip action, combat ignores gear.
Recorded the 5-step interlocking plan (stockpile → recipe tick → unit equip + Craft → combat
reads gear → loot decay [closes B5]). No clean sub-slice — interlocks across crates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:37:22 -04:00
Natalie
14c4de8f85 docs(@projects/@magic-civilization): p3-27 — disease applier complete (o2+lair); ocean-collapse prereqs scoped
Disease applies the full tier spec now (fauna/canopy/tier/o2/lair). Ocean-collapse remains
unwired with precise prereqs recorded (global_fish_stock aggregation + registry/has_tag
reconciliation) — a scoped real task, code+tests exist in isolation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:53:20 -04:00
Natalie
59742674b8 docs(@projects/@magic-civilization): p3-26 B3 — improvement subsystem DONE (build + yield live headless)
Full chain wired + tested: BuildImprovement → pending (build_turns) → build-tick →
city_improvements → process_improvement_yields. State (cb451832e) + logic (f4e9d0211) +
FFI/harness (5d12b4bbe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:42:32 -04:00
Natalie
57725d0088 docs(@projects/@magic-civilization): 🔎 p3-26 B3 — improvement subsystem is FULLY absent headless (re-scoped)
Verified: improvement_yield_table never populated in apply_end_turn (yields no-op),
handle_build_improvement is a stub (no placement, improvement_id dropped), no GameState
improvement data. B3 is a full subsystem build (state+boot+FFI+handler+build-tick+yields),
not a build-tick tweak. Precise ecology-phase-shaped plan recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:28:47 -04:00
Natalie
00a349a388 docs(@projects/@magic-civilization): 🐟 p3-27 — marine ecology core confirmed live (ticks via process_step)
Per-tile fish/reef/mangrove dynamics already run inside EcologyEngine::process_step (headless
ecology phase); ocean_dead_fraction via climate; no live MarineHarvestScript remains. Global
ocean-collapse (tick_ocean_state) is the one unwired refinement (needs a populated
BiomeTagRegistry).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:12:24 -04:00
Natalie
05b550232b docs(@projects/@magic-civilization): 🦠 p3-27 — fauna disease applier done (plague/pandemic mortality live)
EcologyEngine::apply_disease_events strikes fauna populations in the ecology phase from the
boot-loaded event configs. Remaining bio-events: marine (fish/reef), lair_kill_chance, o2 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:06:56 -04:00
Natalie
c0c1652034 docs(@projects/@magic-civilization): 🏛️ p3-28 — modular turn architecture objective (cycle + registry done)
Records the foundation refactor: dep-cycle break (5ee312e45) + end-of-turn phase registry
(af41ea10a) done; boot-config DRY (3 layers → 1 Rust-native loader) + registry-widening
remaining.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:16:56 -04:00
Natalie
135a0e81b9 docs(@projects/@magic-civilization): 📊 p3-26 B8 7/12 (plague) + p3-27 fauna-disease-is-unwired finding
- B8 events now 7/12 (plague terrain blight added).
- Recorded that mc_ecology's disease system is config-only (no applier, only a validate-bin
  caller) — fauna plague/pandemic are a feature to write, not just wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:50:59 -04:00
Natalie
6530233048 docs(@projects/@magic-civilization): p3-27 — ecology population tick done (headless biosphere lives)
EcologyEngine now ticks every headless turn (apply_end_turn), seeds genesis + persists via
continuation-JSON, FFI + harness wired. Remaining: confirm flora-succession coverage, marine
ecology port, bio-targeting events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:18:03 -04:00
Natalie
b8d2a1f20e docs(@projects/@magic-civilization): 🦌 p3-27 — concrete biosphere-headless implementation design
Researched the real ecology/flora crate APIs and recorded a ready-to-build spec: reuse
EcologyEngine (process_step + continuation_state save/restore) + EcologyConfig::from_json;
persist the opaque continuation-JSON on GameState (worldsim_state_json) + boot-load the config
bundle (#[serde(skip)] + FFI), mirroring the climate/events pattern. Ecology is already ticked
in GdFaunaEcology for the live game; the headless TurnProcessor just needs the same wiring.
7-step plan + test strategy captured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:03:54 -04:00
Natalie
0752a51fb6 docs(@projects/@magic-civilization): p3-26 — B1+B2 done, B8 events 6/12 (parallel batch integrated)
Parallel migration batch (2 worktree agents) fully integrated + test-green:
- B1 happiness/golden-age + B2 healing → mc-turn (d5729d67c, 271/0).
- B8 events 6/12 → seismic/impact/tsunami added (afa7613fd, mc-climate 61/0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 15:47:21 -04:00
Natalie
2a360af22a docs(@projects/@magic-civilization): 🗂️ p3-26 full migration backlog + p3-27 biosphere-headless objective
Recorded the verified live-turn-vs-headless migration sweep:
- p3-26: full backlog B1-B8 (happiness/golden-age, healing, improvements-tick, government,
  loot-decay, equipment, per-building-queues, remaining 9 event categories) — each grepped
  to 0 hits in mc-turn.
- p3-27 (new): biosphere headless port — ecology population tick + flora succession +
  marine ecology (the bio simulators exist as crates + tick in the live game but not the
  headless turn); underlies the plague/pandemic/marine events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 15:20:17 -04:00
Natalie
e4a3808a19 docs(@projects/@magic-civilization): 📊 p3-26 gap 2 — honest remaining-category breakdown
3 event categories live (wildfire/drought/volcanic) on existing grid fields. Recorded the
real shape of the rest (not trivial pattern-repeats):
- seismic/impact/tsunami: geological terrain ops (elevation/crater/coastal) — partially
  portable with the current grid.
- solar/glacial: need the Rust climate physics to consume new solar_forcing/glacial_forcing
  fields (injecting a field without physics reading it is inert).
- plague/pandemic/marine: need fauna/marine subsystem integration (fauna population death,
  fish_stock/reef) — overlaps the marine_harvest port (gap-1 tail).
- magical: Game-3 deferred.

So gap 2 = a complete, verified framework + 3 categories; finishing the rest is sequenced
real work, not boilerplate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:57:51 -04:00
Natalie
750824fbbb docs(@projects/@magic-civilization): p3-26 gap 2 — wildfire+drought events LIVE in the headless sim (verified)
Verification milestone for the natural-events port:
- Dylib rebuilt — set_events_config_json FFI confirmed present (strings); boot GUT 750/0
  (dylib loads, GdPlayerApi works, no regression).
- Full path proven: harness loads event configs (DataLoader.get_ecological_events →
  set_events_config_json) → mc-turn climate phase runs process_events → wildfire/drought
  fire + reshape terrain (climate_phase_fires_natural_events deterministic).

Gap 2 now: deterministic core + config loader + dispatch + 2 categories (wildfire,
drought) live + wired end-to-end. Remaining: the other 10 categories + surfacing fired
events in the turn result/view + era-based severity cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:09:24 -04:00
Natalie
9ccc7e10ff feat(@projects/@magic-civilization): 🎲 p3-26 gap 2 (start) — deterministic event core ported to mc-climate::events
First slice of the natural/"apocalyptic" events port (M3). The deterministic primitives
every category depends on, ported from GDScript ecological_event_utils:

- hash_noise(x,y,seed) = frac(sin(x*127.1+y*311.7+seed*74.3)*43758.5453), f64 — verified
  to match the LIVE GDScript game bit-for-bit (ran it: hash_noise(10,0,1000) =
  0.67791910066535). The headless sim must match the game, NOT the TS web guide (whose
  Math.sin diverges on these large arguments — a pre-existing game-vs-guide gap, not a
  port bug; the old comment's "0.1270 from TS" golden was misleading).
- roll_severity(weights, turn_seed, channel, max_tier) — weighted tier roll with era cap.
- category_fires(base_frequency, channel, turn_seed) — the per-category dispatch gate.

4 cargo tests (GDScript-golden determinism, channel separation, severity bounds + cap,
fire gate). Source corrected: .messy is gone — the port source is the live
ecological_events.gd + handlers_a/b + public/resources/events/*.json. Next: event-config
structs/loading + dispatch + per-category handlers (wildfire first) + turn wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:35:14 -04:00
Natalie
1bdad8e497 feat(@projects/@magic-civilization): 🌪️ p3-26 gap 1 (cont.) — weather + climate effects (unit HP) in the headless turn
Extends the headless climate phase from physics-only to the full per-turn chain mirroring
the live game's _process_climate (climate → weather → effects):

- process_climate_phase now: ClimatePhysics::process_step → weather::derive_events
  (storms/heat-waves/blizzards, default thresholds = live GdWeatherPhysics) →
  apply_climate_effects.
- apply_climate_effects (extracted, testable): runs climate_effects::apply (tile effects +
  per-unit hp_loss) then fans hp_loss onto MapUnit.hp as max(0, hp - hp_loss) — exactly
  climate_effects.gd. movement_penalty surfaced but not applied to units (matches live).

Tests: apply_climate_effects_fans_hp_loss_onto_units (deterministic — unit in heat-wave
radius loses HP, unit outside unharmed) + the determinism test; mc-turn 337/0, no
regression. Gap 1 remaining: marine_harvest (ocean_dead_fraction → climate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:25:33 -04:00
Natalie
f9593c4d29 docs(@projects/@magic-civilization): 🗺️ p3-26 — roadmap to complete the headless simulator (loop done-criterion)
Owner directive: the /loop isn't finished until the SIMULATOR is complete — the headless
Rust sim must play full self-play games with ALL systems, not the reduced subset.

p3-26 enumerates the verified live-vs-headless gaps + sequenced plan:
- Gap 1: climate/environment runtime (port the marine→climate→weather→effects chain into
  mc-turn; physics already in mc-climate).
- Gap 2: natural/"apocalyptic" events (M3 milestone — port .messy ecological_events.gd,
  12 categories, deterministic per EVENT_FREQUENCY_SPEC).
- Gap 3: equipment/crafting (recipes exist; no headless Craft action).
- Gap 4: per-building build queues (dual city-model; bench has a single queue).

Corrects my earlier "apocalyptic events don't exist" — they're specced (m3-natural-events)
with a .messy reference impl, just unimplemented in Rust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 08:25:01 -04:00
Natalie
4fe3bc6091 feat(@projects/@magic-civilization): 🧪 p3-25 headless hardening — wire resource categories into mc-sim benches
Owner chose headless-only hardening over the live-game refactor (step 6 deferred).

- mc_sim::load_deposit_categories(deposits_dir) reads public/resources/deposits/*.json →
  id→category map (handles single-object or array files; skips bad files).
- dominion_bench + tournament_bench now set state.resource_categories from it after
  building GameState. These benches run the Rust TurnProcessor (process_trade_phase) but
  never loaded categories, so step-4's real sourcing had left their inter-player trades
  inert (sourcing from empty categories → no luxuries/strategics → no trades). Now bench
  trade dynamics (trade_willingness axis, gold-from-sales) form again.

Also recorded: real-game confirmation that the headless pipeline is live — the magic-civ
MCP view_json returns cities[].owned_tiles populated (step-2 territory projection running
in a real headless game on the rebuilt dylib).

Verified: mc-sim load_deposit_categories_reads_real_deposits passes; dominion_bench +
tournament_bench compile. solo_dominion (single-player, no trade partners) intentionally
not wired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:15:15 -04:00