feat(objectives): add rust port stubs for culture pick and async batch

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-05 14:18:10 -04:00
parent 6216d97a76
commit 17d0f4cd6c
6 changed files with 67 additions and 24 deletions

View file

@ -269,6 +269,7 @@
| [p2-38](p2-38-unit-audio-cues-stubs.md) | ✅ done | P2 | Unit audio_cues stub strings — selection/move/attack lines for the dwarven roster | [asset-audio](../team-leads/asset-audio.md) | 🟢 |
| [p2-39](p2-39-chronicle-hall-phantom-unlock.md) | ✅ done | P2 | Resolve `chronicle_hall` phantom unlock in `chronicle_keeping` culture tech | — | 🟢 |
| [p2-43](p2-43-culture-research-completion-event.md) | 🟡 partial | P2 | Culture research live-game pipeline — per-turn GDExt bridge + `culture_researched` emit | — | 🟢 |
| [p2-43a](p2-43a-rust-port-culture-pick.md) | 🔴 stub | P3 | Rail-1 port — `_pick_culture_tradition` → mc-ai::tactical::culture_pick | — | 🟢 |
| [p2-44](p2-44-ai-promotion-selection.md) | 🟡 partial | P2 | AI promotion selection — auto-pick + emit unit_promoted for AI units | — | 🟢 |
| [p2-45](p2-45-elimination-reconciliation.md) | ✅ done | P2 | Player elimination reconciliation — emit `player_eliminated` on every transition | — | 🟢 |
| [p2-46](p2-46-past-games-archive-replay-viewer.md) | 🟡 partial | P2 | Past-games archive & replay viewer — `mc-replay` crate, on-disk archive, projection-based playback | [shipwright](../team-leads/shipwright.md) | 🟢 |
@ -311,6 +312,7 @@
| [p2-61](p2-61-observation-recording-gates-from-tech.md) | 🔴 stub | P2 | Bind mc-observation gate_bits to player tech state — recording gates per-field | [unassigned](../team-leads/unassigned.md) | 🟢 |
| [p2-62](p2-62-procedural-unit-and-building-renderer.md) | ✅ done | P2 | Procedural unit/building renderer — alpha-only visual substitute | [asset-sprite](../team-leads/asset-sprite.md) | 🟢 |
| [p2-63](p2-63-mc-flora-biome-substrate-migration.md) | 🔴 stub | P2 | mc-flora generation: migrate biome filter to substrate_climate-aware path | [unassigned](../team-leads/unassigned.md) | 🟢 |
| [p2-64](p2-64-apricot-async-batch-protocol.md) | 🟡 partial | P2 | Apricot async batch protocol — launch / status / fetch decoupling | [simulator-infra](../team-leads/simulator-infra.md) | 🟢 |
| [p3-01](p3-01-courier-diplomacy.md) | ✅ done | P3 | Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units | [envoy](../team-leads/envoy.md) | 🟢 |
| [p3-03](p3-03-courier-route-resolver.md) | ✅ done | P3 | Courier route resolver — real hex pathfinding, per-tier movement, severable infrastructure | [envoy](../team-leads/envoy.md) | 🟢 |
| [p3-04](p3-04-per-hex-improvement-layer.md) | ✅ done | P3 | Per-hex improvement layer in `mc-core` / `mc-turn` — anchor improvements at (col,row) | [envoy](../team-leads/envoy.md) | 🟢 |
@ -322,7 +324,7 @@
| [p3-06](p3-06-civic-anarchy-and-axis-switching.md) | 🟡 partial | P3 | Civic anarchy — 5-turn anarchy on axis switch | [unassigned](../team-leads/unassigned.md) | 🟢 |
| [p3-07a](p3-07a-cv-wealth-and-authority-amplifier.md) | 🔴 stub | P3 | CV-of-wealth + Authority amplifier → inequality stat | [unassigned](../team-leads/unassigned.md) | 🔒 p3-05b |
| [p3-07b](p3-07b-four-damage-channels.md) | 🔴 stub | P3 | Four damage channels — Land/Water/Magic/Air emission from inequality | [unassigned](../team-leads/unassigned.md) | 🔒 p3-07a |
| [p3-10a](p3-10a-lair-assault-mode.md) | 🔴 stub | P3 | Lair assault mode — enter-and-clear | [unassigned](../team-leads/unassigned.md) | 🟢 |
| [p3-10a](p3-10a-lair-assault-mode.md) | 🟡 partial | P3 | Lair assault mode — enter-and-clear | [unassigned](../team-leads/unassigned.md) | 🟢 |
| [p3-10b](p3-10b-lair-siege-mode.md) | 🔴 stub | P3 | Lair siege mode — multi-turn pressure from adjacent | [unassigned](../team-leads/unassigned.md) | 🔒 p3-10a |
| [p3-10c](p3-10c-lair-raid-mode.md) | 🔴 stub | P3 | Lair raid mode — grab-and-exit | [unassigned](../team-leads/unassigned.md) | 🔒 p3-10a |
| [p3-11](p3-11-pioneer-engineer-action-points.md) | 🟡 partial | P3 | Pioneer & Engineer action-point pool | [unassigned](../team-leads/unassigned.md) | 🟢 |

View file

@ -16,9 +16,9 @@
|---|---|---|---|---|---|---|---|
| **P0** | 0 | 0 | 0 | 0 | 0 | 44 | 44 |
| **P1** | 1 | 13 | 2 | 6 | 1 | 50 | 73 |
| **P2** | 0 | 11 | 11 | 0 | 6 | 58 | 86 |
| **P3 (oos)** | 0 | 6 | 10 | 0 | 21 | 5 | 42 |
| **total** | **1** | **30** | **23** | **6** | **28** | **157** | **245** |
| **P2** | 0 | 12 | 11 | 0 | 6 | 58 | 87 |
| **P3 (oos)** | 0 | 7 | 10 | 0 | 21 | 5 | 43 |
| **total** | **1** | **32** | **23** | **6** | **28** | **157** | **247** |
</td><td valign='top' style='padding-left:2em'>
@ -29,7 +29,7 @@
| [unassigned](../team-leads/unassigned.md) | 26 |
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
| [shipwright](../team-leads/shipwright.md) | 5 |
| [simulator-infra](../team-leads/simulator-infra.md) | 3 |
| [simulator-infra](../team-leads/simulator-infra.md) | 4 |
| [testwright](../team-leads/testwright.md) | 3 |
| [combat-dev](../team-leads/combat-dev.md) | 2 |
| [warcouncil](../team-leads/warcouncil.md) | 2 |
@ -79,7 +79,7 @@
|---|---|---|---|---|---|---|
| [p2-10](p2-10-regression-ci-gate.md) | 🟡 partial | Automated regression CI gate on every push to main | — | [testwright](../team-leads/testwright.md) | 2026-05-04 | 🟢 unblocked |
| [p2-18](p2-18-guide-public-deployment.md) | 🟡 partial | Guide web app — public hosting + deploy pipeline | — | — | 2026-04-17 | 🟢 unblocked |
| [p2-43](p2-43-culture-research-completion-event.md) | 🟡 partial | Culture research live-game pipeline — per-turn GDExt bridge + `culture_researched` emit | — | — | 2026-05-04 | 🟢 unblocked |
| [p2-43](p2-43-culture-research-completion-event.md) | 🟡 partial | Culture research live-game pipeline — per-turn GDExt bridge + `culture_researched` emit | — | — | 2026-05-05 | 🟢 unblocked |
| [p2-44](p2-44-ai-promotion-selection.md) | 🟡 partial | AI promotion selection — auto-pick + emit unit_promoted for AI units | — | — | 2026-05-04 | 🟢 unblocked |
| [p2-46](p2-46-past-games-archive-replay-viewer.md) | 🟡 partial | Past-games archive & replay viewer — `mc-replay` crate, on-disk archive, projection-based playback | — | [shipwright](../team-leads/shipwright.md) | 2026-05-05 | 🟢 unblocked |
| [p2-47](p2-47-in-game-statistics-screens.md) | 🟡 partial | In-game statistics screens — Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories) | — | [shipwright](../team-leads/shipwright.md) | 2026-05-03 | 🟢 unblocked |
@ -88,6 +88,7 @@
| [p2-56c](p2-56c-master-grandmaster-auras.md) | 🟡 partial | Master / Grandmaster auras — adjacent-slot yield propagation | — | [unassigned](../team-leads/unassigned.md) | 2026-05-04 | 🟢 unblocked |
| [p2-57a](p2-57a-typed-resource-stockpile.md) | 🟡 partial | Typed resource stockpile — raw vs processed taxonomy | — | [unassigned](../team-leads/unassigned.md) | 2026-05-04 | 🟢 unblocked |
| [p2-58](p2-58-ambient-encounter-rolls.md) | 🟡 partial | Ambient encounter rolls per tile moved — fauna_density × ecology_tier | — | [unassigned](../team-leads/unassigned.md) | 2026-05-05 | 🟢 unblocked |
| [p2-64](p2-64-apricot-async-batch-protocol.md) | 🟡 partial | Apricot async batch protocol — launch / status / fetch decoupling | — | [simulator-infra](../team-leads/simulator-infra.md) | 2026-05-05 | 🟢 unblocked |
| [p2-10k](p2-10k-gdlint-cleanup.md) | 🔴 stub | CI: fix 51 gdlint violations so Stage 3 is hard-green | — | [testwright](../team-leads/testwright.md) | 2026-05-04 | 🟢 unblocked |
| [p2-10l](p2-10l-gut-regression-triage.md) | 🔴 stub | CI: fix 15 GUT regressions so Stage 5 is hard-green | — | [testwright](../team-leads/testwright.md) | 2026-05-04 | 🟢 unblocked |
| [p2-55d](p2-55d-ai-ransom-decision-hook.md) | 🔴 stub | AI ransom accept/refuse hook in mc-turn start-of-turn | — | — | 2026-05-03 | 🟢 unblocked |

View file

@ -1,13 +1,13 @@
{
"generated_at": "2026-05-05T16:06:46Z",
"generated_at": "2026-05-05T18:13:24Z",
"totals": {
"done": 157,
"in_progress": 1,
"partial": 30,
"partial": 32,
"stub": 23,
"missing": 6,
"oos": 28,
"total": 245
"total": 247
},
"objectives": [
{
@ -1760,7 +1760,7 @@
"priority": "p2",
"status": "partial",
"scope": "game1",
"updated_at": "2026-05-04",
"updated_at": "2026-05-05",
"blocked_by": [],
"summary": "`EventBus.culture_researched(tradition_id, player_index)` is defined and\n**every downstream consumer is wired** (AudioManager handler, manifest\nentry `culture_researched`, the asset shipped at\n`public/resources/audio/sfx/ui/culture_researched.ogg`). What's missing\nturned out to be deeper than the original framing of this objective: the\n**entire per-turn culture-research path doesn't run in the live game**.\n\n### Trace\n\n- `turn_manager.gd:246` calls `_process_culture(player, game_map)`\n- `turn_processor.gd:360 _process_culture` only handles **border\n expansion** via `city.process_culture_with_modifier()` — no\n tradition-research accumulator\n- `processor.rs:604 process_culture_research` (Rust mc-turn) **does**\n drive tradition completion via `mc_culture::CultureResearchResult`,\n but it lives in the bench / legacy-headless path, not in the\n GDScript-driven live-game per-turn\n- Tech has a Rust GDExt method `tech_web.process_research(player_dict,\n yields, mult) → {new_progress, new_researching, completed_tech}` that\n GDScript calls in `turn_processor.gd::_process_research` — **no\n equivalent exists for culture**\n\nSo in the playable game today: `culture_research_progress` never\nincrements, `researched_traditions` never grows, no completion event\never fires. `p1-28` shipped the UI and the data graph but not the\nruntime accumulator."
},
@ -2229,6 +2229,17 @@
"blocked_by": [],
"summary": "Authored flora JSON files have migrated from a top-level `biomes: [...]`\narray to the `substrate_climate` ontology\n(see `p2-52-substrate-flora-cover-ontology-split.md`). The biome-filter\nloop in `mc-flora/src/generation.rs` was not updated, so the\n`AuthoredSpeciesFile.biomes` field is now empty for every authored file\nand the candidate-pool query returns nothing.\n\nTwo pre-existing regression tests in `mc-flora` document the gap:\n\n- `load_authored_returns_species_for_known_biome` (generation.rs:645)\n- `generate_flora_for_biome_more_species_with_authored_files`\n (generation.rs:695)\n\nBoth fail because the biome filter at `generation.rs:508-510` still\nchecks `raw.biomes.iter().any(|b| b == biome_id)` while the JSON now\nencodes biome eligibility through `substrate_climate` blocks the loader\ndoes not currently inspect."
},
{
"id": "p2-64",
"title": "Apricot async batch protocol — launch / status / fetch decoupling",
"priority": "p2",
"status": "partial",
"scope": "game1",
"owner": "simulator-infra",
"updated_at": "2026-05-05",
"blocked_by": [],
"summary": ""
},
{
"id": "g2-01",
"title": "Ley lines — Game 2 (Age of Kzzykt)",
@ -2446,6 +2457,16 @@
"blocked_by": [],
"summary": "Persistent trade-route units (caravans, traders) that travel between owned cities OR between own-city and foreign-city, generate per-turn gold/resource yields tied to distance and city-pair characteristics, and can be plundered by enemy units. Distinct from p1-01's instantaneous luxury-for-gold trade modal."
},
{
"id": "p2-43a",
"title": "Rail-1 port — `_pick_culture_tradition` → mc-ai::tactical::culture_pick",
"priority": "p3",
"status": "stub",
"scope": "game1",
"updated_at": "2026-05-03",
"blocked_by": [],
"summary": "Phase A of `p2-43` landed the AI culture-tradition picker as GDScript in\n`auto_play.gd::_pick_culture_tradition`. This violates Rail-1 (Rust is\nthe simulation source of truth) and is filed here as the explicit\nport-back follow-up.\n\nMirror the shape of `mc-ai::tactical::pick_promotion`:\n\n- New module `src/simulator/crates/mc-ai/src/tactical/culture_pick.rs`\n with `pub fn pick_culture_tradition(state: &PlayerState, available: &[TraditionId], priors: &PersonalityPriors) -> Option<TraditionId>`.\n- Extend `PersonalityPriors` (in `policy.rs`) with `culture_pillar_weights: BTreeMap<PillarId, f32>` and a single\n `culture_cost_bias: f32` knob — no parallel structs, no stringly maps.\n- Bridge through `GdAiController::pick_culture_tradition(player_dict, available_array)` in\n `api-gdext/src/ai.rs` (alongside the existing promotion bridge).\n- Replace the `_pick_culture_tradition` body in `auto_play.gd` with a\n one-liner delegating to the bridge. Delete the local scoring code —\n Zero-Tech-Debt rail forbids leaving the GDScript shadow.\n- GUT test asserts the bridge returns the same id sequence the Phase A\n GDScript would have, using a fixed personality + tradition graph.\n- `cargo test -p mc-ai test_culture_pick_personality_weighting` green."
},
{
"id": "p2-55f",
"title": "Read ransom_offer_duration_turns from combat_balance.json",
@ -2596,10 +2617,10 @@
"id": "p3-10a",
"title": "Lair assault mode — enter-and-clear",
"priority": "p3",
"status": "stub",
"status": "partial",
"scope": "game1",
"owner": "unassigned",
"updated_at": "2026-05-03",
"updated_at": "2026-05-05",
"blocked_by": [
"p0-17"
],
@ -2845,7 +2866,7 @@
},
{
"owner": "simulator-infra",
"remaining": 3
"remaining": 4
},
{
"owner": "testwright",

View file

@ -2,7 +2,7 @@
id: p2-43a
title: "Rail-1 port — `_pick_culture_tradition` → mc-ai::tactical::culture_pick"
priority: p3
status: open
status: stub
scope: game1
updated_at: 2026-05-03
evidence:

View file

@ -2,15 +2,28 @@
id: p2-64
title: Apricot async batch protocol — launch / status / fetch decoupling
priority: p2
status: stub
status: partial
scope: game1
category: infra
owner: simulator-infra
created: 2026-05-05
updated_at: 2026-05-05
evidence:
- scripts/apricot-run.sh launch/status/fetch sub-modes
- scripts/apricot-async-smoke.sh
- tooling/claude/dot-claude/instructions/canonical-commands.md
blocked_by: []
follow_ups: []
---
## p2-64 close-out (2026-05-05)
Three new sub-modes added to `scripts/apricot-run.sh`:
- `launch <mode> <args>` — writes a per-stamp launcher.sh into `~/.cache/mc-batches/<stamp>/`, starts it under `systemd-run --user --collect --unit=mc-batch-<stamp>`. Returns immediately with `STAMP=<value>` on stdout.
- `status <stamp>` — single ssh `ConnectTimeout=5` probe with three lightweight `ls | wc -l`-style checks; emits one-line JSON (`state``running|complete|failed|unreachable`).
- `fetch <stamp>``rsync -a --partial`; resumable across drops; exit 1 if not yet complete.
- Documentation in script header + canonical-commands.md (already committed `5a57a6ac6`).
- Smoke test at `scripts/apricot-async-smoke.sh`.
**Status: partial.** Implementation landed; needs at least one real-batch validation run (the smoke test exists but a true intermittent-connectivity scenario hasn't been exercised yet). Close to done.
Existing synchronous modes (`smoke`, `huge-map-5clan`, `ai-quality-baseline-pre-c`, etc.) keep working — `launch` is a wrapper, not a replacement.
## Context

View file

@ -1,16 +1,22 @@
---
id: p3-10a
title: "Lair assault mode — enter-and-clear"
title: Lair assault mode — enter-and-clear
priority: p3
status: stub
status: partial
scope: game1
category: combat
owner: unassigned
created: 2026-05-03
updated_at: 2026-05-03
updated_at: 2026-05-05
evidence:
- src/simulator/crates/mc-core/src/lair.rs
- src/simulator/crates/mc-combat/src/lair.rs
- mc-core lair.rs serde + ord + default tests
blocked_by: [p0-17]
follow_ups: []
---
## p3-10a close-out (2026-05-05)
`mc_core::lair::LairCombatMode` typed enum landed (variants `Assault | Siege | Raid`, snake_case serde, derive `Default` = Assault for backward compat with existing lair-clear callers). `mc_combat::lair` accepts the mode parameter at 7 existing call sites; passing `Default::default()` preserves p0-17 behavior. Serde round-trip + Ord-consistency tests + default-mode test land in `mc-core::lair::tests`.
**Status: partial.** Assault mode is the existing-behavior typed-default — no new combat resolution logic shipped (the existing path IS the assault). Siege (p3-10b) and Raid (p3-10c) implementations are the structural follow-ups; both would extend `mc_combat::lair::resolve_lair_combat` with mode-specific branches.
## Context