3598 lines
198 KiB
JSON
3598 lines
198 KiB
JSON
{
|
||
"generated_at": "2026-06-05T04:25:02Z",
|
||
"totals": {
|
||
"done": 243,
|
||
"in_progress": 1,
|
||
"missing": 1,
|
||
"oos": 29,
|
||
"partial": 21,
|
||
"stub": 8,
|
||
"superseded": 4,
|
||
"total": 307
|
||
},
|
||
"objectives": [
|
||
{
|
||
"id": "g2-01",
|
||
"title": "Ley lines — Game 2 (Age of Kzzykt)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Leylines are intuitive terrain features that Kzzykt interact with instinctively — they don't cast leylines, they live alongside them. Leylines affect tile improvements and yields and influence Kzzykt AI behavior. There is no full mana economy in Game 2; leylines are environmental, not player-managed."
|
||
},
|
||
{
|
||
"id": "g2-02",
|
||
"title": "Kzzykt playable race — Game 2 (Age of Kzzykt)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Kzzykt is the second playable race, added in Game 2. They are an insectoid bug civilization with Green MTG color affinity who intuitively use leylines. Game 1 ships with Dwarves only — Kzzykt and all subsequent races are out of scope until their respective games."
|
||
},
|
||
{
|
||
"id": "g2-03",
|
||
"title": "Kzzykt Green school of magic — Game 2 (Age of Kzzykt)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The Green school is the only school of magic in Game 2 and is optional for the player — this is where spells first enter the series. It is tied to Kzzykt's Green MTG color affinity and draws on nature, instinct, and leyline resonance. Game 2 also introduces interplanetary and spacefaring late-game progression alongside the Green school tech path."
|
||
},
|
||
{
|
||
"id": "g2-04",
|
||
"title": "Multi-GPU sharding for batch_simulate_gpu — out-of-scope (Game 2)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "> **Out of scope for Game 1 \"Age of Dwarves\".** Game 1 batch sizes (64–256 rollouts per MCTS leaf) are nowhere near single-GPU saturation on apricot's RTX 3090, and the shippable Game 1 machine profile isn't guaranteed to have two GPUs anyway. This optimization is queued for Game 2 \"Age of Kzzykt\" when deeper MCTS lookahead + larger clan-on-clan batches are expected to push single-GPU throughput to the ceiling."
|
||
},
|
||
{
|
||
"id": "g2-05",
|
||
"title": "Tectonics + lithology axes for procedural map generation (Game 2)",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-04-30",
|
||
"blocked_by": [],
|
||
"summary": "Game 1 ships with elevation as the single geological axis. Game 2's expanded scope (additional races, leylines, planet-scale events) calls for plate-tectonic simulation:"
|
||
},
|
||
{
|
||
"id": "g2-06",
|
||
"title": "Soil derivation layer — emergent soil order from rock + climate + slope (Game 2)",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-04-30",
|
||
"blocked_by": [],
|
||
"summary": "Game 1's biome classifier has no soil layer; tile yield depends on biome + deposits only. Real ecological behaviour requires soil — a Mollisol grassland is far more productive than an Aridisol grassland."
|
||
},
|
||
{
|
||
"id": "g2-07",
|
||
"title": "Flora lifecycle transitions — climate-driven succession over turns (Game 2)",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Every flora species in `public/resources/ecology/flora/species/*.json` carries a `lifecycle` object with `formed_by[]` (what it grows from) and `transforms[]` (what it becomes when conditions change). Example from `european_beech.json::lifecycle.transforms[0]`:"
|
||
},
|
||
{
|
||
"id": "g2-08",
|
||
"title": "Fauna population dynamics — habitat_min, carrying_capacity, prey availability (Game 2)",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Every Game-1 fauna species in `public/resources/ecology/fauna/species/*.json` has `habitat_min` (minimum habitat-quality threshold for survival) and `carrying_capacity` (max population fraction the tile supports). Plus `prey[]` arrays for 373 of 589 species form a real food-web DAG."
|
||
},
|
||
{
|
||
"id": "g2-09",
|
||
"title": "Flora tolerance-driven selection — drought / fire / cold tolerances feed selector (Game 2)",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Every flora species carries six independent tolerance/contribution axes: `drought_tolerance`, `fire_resistance`, `growth_rate`, `quality_tier`, `canopy_contribution`, `undergrowth_contribution`, `fungi_contribution`. Wave-C's `mc-ecology::flora_select` consumes only `quality_tier × <layer>_contribution` — the other two tolerance fields (drought, fire) are unused."
|
||
},
|
||
{
|
||
"id": "g2-10",
|
||
"title": "Fauna migration paths — seasonal range shifts, reintroduction propagation (Game 2)",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Currently fauna are placed once at map-gen and stay there. Real species migrate seasonally (`musk_ox`, `red_deer`, `narwhal`, `peregrine_falcon` — each authored with multi-biome `biomes[]` arrays implying range-shift). Plus reintroduction: extinct-then- recovering populations re-seed from neighbouring tiles (the Yellowstone wolf trophic cascade described in `grey_wolf.json` lore)."
|
||
},
|
||
{
|
||
"id": "g2-11",
|
||
"title": "Vertical city floor stack (Game 2) — OOS",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [
|
||
"g2-12"
|
||
],
|
||
"summary": "The vertical-city design in `public/games/age-of-dwarves/docs/cities/CITIES.md` proposes a 1→4 slots-per-tile ladder — a city can grow vertically (subterranean floors stacked under its surface tile) up to four levels by tier, with **intra-tile synergy** bonuses where adjacent vertical floors of complementary categories amplify each other (e.g. forge above an ore mine)."
|
||
},
|
||
{
|
||
"id": "g2-12",
|
||
"title": "Underground layer stack (Game 2) — OOS",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/terrain/VERTICAL_LAYERS.md` proposes a multi-layer GameMap: **Surface** + three subterranean layers (L1, L2, L3). Units carry a `current_layer` field and transition between layers via mine-shaft tiles. This is foundational for vertical cities (`g2-11`), underground biomes, and the Kzzykt race's natural habitat."
|
||
},
|
||
{
|
||
"id": "g3-01",
|
||
"title": "Archons — Game 3 (Age of Elves)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game3",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Archons are the magical avatar entities in Game 3 (\"Age of Elves\"). Each player has a High Archon (mana generator + casting avatar), and each of the five magic schools has a corresponding Minor Archon. Neither Game 1 nor Game 2 has Archon entities."
|
||
},
|
||
{
|
||
"id": "g3-02",
|
||
"title": "Life school spellbook — Game 3 (Age of Elves)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game3",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The Life school is one of four new magic schools added in Game 3 (\"Age of Elves\"). Life magic centers on healing, growth, sustenance, and binding — the nurturing force of the color pie. Spellbook TBD as Game 3 design progresses."
|
||
},
|
||
{
|
||
"id": "g3-03",
|
||
"title": "Death school spellbook — Game 3 (Age of Elves)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game3",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The Death school is one of four new magic schools added in Game 3 (\"Age of Elves\"). Death magic centers on entropy, reanimation, drain, and endings — the consuming force of the color pie. Spellbook TBD as Game 3 design progresses."
|
||
},
|
||
{
|
||
"id": "g3-04",
|
||
"title": "Chaos school spellbook — Game 3 (Age of Elves)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game3",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The Chaos school is one of four new magic schools added in Game 3 (\"Age of Elves\"). Chaos magic centers on disruption, transformation, wild chance, and unpredictability — the volatile force of the color pie. Spellbook TBD as Game 3 design progresses."
|
||
},
|
||
{
|
||
"id": "g3-05",
|
||
"title": "Aether school spellbook — Game 3 (Age of Elves)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game3",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The Aether school is one of four new magic schools added in Game 3 (\"Age of Elves\"). Aether magic centers on knowledge, artifice, time, and the arcane fabric itself — the intellectual force of the color pie. Spellbook TBD as Game 3 design progresses."
|
||
},
|
||
{
|
||
"id": "g3-06",
|
||
"title": "Arcane Ascension victory — Game 3 (Age of Elves)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game3",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Arcane Ascension is the fifth victory condition, available only in Game 3 (\"Age of Elves\"). It requires completing a multi-step ritual powered by all five magic schools and the player's High Archon. Game 1 has Domination + Score only (p0-08). Game 2 has no Ascension path."
|
||
},
|
||
{
|
||
"id": "g4-01",
|
||
"title": "Terran (Human) playable species — Game 4 (Age of Terrans)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game4",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Terrans (Humans) are the Game 4 species, homeworld Terra. Drawn from Imanity (NGNL rank 16 — lowest-ranked Exceed, no innate magic). Their power comes not from magic but from adaptability, psionics, and religious organization. Game 4 is where the mundane-vs-magic tension peaks: humans have no arcane ability yet dominate through faith and mental force."
|
||
},
|
||
{
|
||
"id": "g4-02",
|
||
"title": "Psionics ability system — Game 4 (Age of Terrans)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game4",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Psionics is the Terran-exclusive ability system — biological/mental in nature, not arcane. It operates outside the five magic schools introduced in Game 3. Humans have an innate psionic affinity (D&D / MoTM tradition). Psionics gives Terrans combat, diplomacy, and espionage capabilities that mirror but do not overlap with spellcasting. Bridges thematically into Game 5 (Gith are also psionic)."
|
||
},
|
||
{
|
||
"id": "g4-03",
|
||
"title": "Religious victory condition — Game 4 (Age of Terrans)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game4",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Terrans can win by spreading religious influence across civilizations. Conversion mechanics are tiered by species: non-human species of equal or higher tier resist conversion; lower-tier species (orcs, trolls, goblins) are susceptible. This creates a distinct non-military victory path unique to Terrans."
|
||
},
|
||
{
|
||
"id": "g5-01",
|
||
"title": "Phantasma playable species — Game 5 (Age of Ascension)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game5",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Phantasma (NGNL rank 2) are pure spiritual beings native to the Ethereal Plane — entities of consciousness without physical form, aligned with Death school. They are ancient and immune to Terran religious spread. As rank-2 Exceed they are second only to the One True God, making them the most powerful conventional species in the series."
|
||
},
|
||
{
|
||
"id": "g5-02",
|
||
"title": "Flügel playable species — Game 5 (Age of Ascension)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game5",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Flügel (NGNL rank 5) are angel-weapons created by the One True God, aligned with Life school. Their existence directly conflicts with the Terran religious victory condition — humans spread faith in a divine, and Flügel *were made by* the closest thing to one. They are the central narrative tension of Game 5: the faith humans spread is real, but the Flügel are its answer. Immune to religious conversion; may have a competing religious spread mechanic of their own."
|
||
},
|
||
{
|
||
"id": "g5-03",
|
||
"title": "Gith playable species (Githyanki + Githzerai) — Game 5 (Age of Ascension)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game5",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Gith (MoTM / D&D) are psionic beings native to the Astral and Ethereal planes, aligned with Aether school. Two playable races: Githyanki (militant, planar conquerors) and Githzerai (monastic, psionic monks). Bridges Game 4's psionics system into Game 5 — Gith and Terrans share a psionic language, creating a unique diplomatic and conflict dynamic. Mid-to-high tier; resistant to but not immune to Terran religious spread."
|
||
},
|
||
{
|
||
"id": "g5-04",
|
||
"title": "Demonia playable species — Game 5 (Age of Ascension)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "game5",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Demonia (NGNL rank 11) are the demon species of the Ethereal Plane, aligned with Chaos school. Low-to-mid tier; they have no coherent belief system, making them the primary beachhead for Terran religious conversion — susceptible when isolated, violently opposed when organized. They are the \"wild card\" species of Game 5: unpredictable alliances, high aggression, and the most conventional military threat."
|
||
},
|
||
{
|
||
"id": "g6-01",
|
||
"title": "Naval combat — out-of-scope (post-v10)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "post-v10",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "> **Out of scope for Game 1 \"Age of Dwarves\" and the planned Game 2-5 cycle.** Per `public/games/age-of-dwarves/data/shipping-roadmap.json`, naval combat is in the **post-v10** bucket alongside the Ethereal Plane, caravan trade routes, map editor, and mod support. Dwarves are mountain-dwelling; ships are not narratively core to Game 1's experience. Tracking here so the roadmap entry has a corresponding objective ID."
|
||
},
|
||
{
|
||
"id": "g6-02",
|
||
"title": "Caravan trade routes — out-of-scope (post-v10)",
|
||
"priority": "p3",
|
||
"status": "oos",
|
||
"scope": "post-v10",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "> **Out of scope for Game 1 \"Age of Dwarves\" and the planned Game 2-5 cycle.** Per `public/games/age-of-dwarves/data/shipping-roadmap.json`, caravan trade routes are in the **post-v10** bucket alongside naval combat (g6-01), the Ethereal Plane, map editor, and mod support. Game 1 ships peace/war + simple luxury-for-gold trade (p1-01 done); persistent unit-routed caravans are a separate, larger system."
|
||
},
|
||
{
|
||
"id": "mc-replay-followup-unit-spawn-events",
|
||
"title": "mc-turn unit-spawn event coverage — every PlayerState.units.push emits UnitCreated / CityUnitCompleted",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`Event::UnitCreated` and `Event::CityUnitCompleted` are the canonical wire markers for \"a unit just entered the world.\" Replay and transcript reconstructors (Claude Player API, headless test drivers, future debug tooling) consume those events to rebuild the unit ledger without re-running the simulator."
|
||
},
|
||
{
|
||
"id": "p0-01",
|
||
"title": "Wire MCTS into gameplay AI",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`GdMcTreeController` (Rust GDExtension) is the unconditional AI driver. `AiTurnBridge.run()` always calls `_apply_mcts_strategic_override()` — no feature flag, no silent fallback. If the extension is absent, `push_error` + `assert(false)` crashes loudly. `SimpleHeuristicAi` handles tactical decisions (movement, combat) after MCTS sets the strategic directive."
|
||
},
|
||
{
|
||
"id": "p0-02",
|
||
"title": "Five AI clan personalities drive distinct playstyles",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`ai_personalities.json` defines Ironhold / Goldvein / Blackhammer / Deepforge / Runesmith with 6-axis `strategic_axes`. `ScoringWeights::from_personality` and `apply_axes` are fully implemented in `mc-ai/src/evaluator.rs`."
|
||
},
|
||
{
|
||
"id": "p0-03",
|
||
"title": "PvP combat resolved inside the authoritative turn processor",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`mc-turn::processor` currently resolves only `LairCombat` (fauna). Player-vs-player attacks go through the GDScript world-map click path, which bypasses the authoritative simulation. MCTS rollouts (`p0-01`) need deterministic PvP in Rust."
|
||
},
|
||
{
|
||
"id": "p0-04",
|
||
"title": "World wonder tracking in PlayerState and score victory",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Wonder tracking fully wired end-to-end. Rust: `PlayerState.wonders_built: BTreeMap<WonderId, u8>`, wonder completion hooks in `process_city_production`, `calculate_score` folds tier-weighted points. GDScript UI: encyclopedia \"Wonders\" tab (filter on `flags.has(\"wonder\")`, built/unbuilt status from `GameState.wonders_built`); city screen left column shows `WondersList` of player-owned wonders with tier when non-empty. GUT: 5 tests in `test_wonders_built_ui.gd`."
|
||
},
|
||
{
|
||
"id": "p0-05",
|
||
"title": "Culture generation and border expansion",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "mc-culture went from 1-line stub to 297 LOC with CulturePool + CityCultureState, deterministic BTreeMap iteration, and 10 unit tests passing on apricot (`cargo test -p mc-culture --lib` → 10/10). GDScript wrapper `culture.gd` now delegates to `GdCulture` (the mc-culture bridge) and emits `city_border_expanded` on threshold crossing. Score victory folds culture via `SCORE_CULTURE_DIVISOR`. SimpleHeuristicAi prioritizes monument buildings when `culture_axis` is high."
|
||
},
|
||
{
|
||
"id": "p0-06",
|
||
"title": "Fold gold income / upkeep / improvement yields into turn loop",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "**Re-promoted 2026-04-17 (p0-28 closed):** `GdEconomy` bridge landed (`api-gdext/src/lib.rs` `// ── GdEconomy` section); `economy.gd` grew from 2 LOC → 162 LOC thin static wrapper; `turn_processor.gd::_process_economy` body collapsed from ~50 LOC → 5 LOC (one `Economy.process_turn(player, game_map)` call). Live game and bench both route gold income/upkeep/insolvency through `mc_economy::process_gold`. See `p0-28-gd-economy-bridge.md`."
|
||
},
|
||
{
|
||
"id": "p0-07",
|
||
"title": "Tech research costs and science pool pacing",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`mc-tech` has the prerequisite graph and unlock signals with per-tech science cost accumulation. Research gates on both prerequisites and cost; completion decrements the pool by `tech.cost` and carries overflow to the next tech."
|
||
},
|
||
{
|
||
"id": "p0-08",
|
||
"title": "Domination victory path in mc-turn::victory",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "Domination victory fires when one player captures all opponent original capitals. `victory.rs` checks domination before score; `VictoryConfig.domination_requires_all_capitals=true`. AI heuristics tuned to commit to capital assault: `DOMINANCE_FACTOR=1.25` (own_mil ≥ 1.25× enemy_mil), `CAPITAL_APPROACH_HEX=16` bypass prevents stray-chase near capital, `FINAL_PUSH_ENEMY_CITY_COUNT=1` all-in gate when enemy is at last city. GUT tests cover both tuning paths."
|
||
},
|
||
{
|
||
"id": "p0-09",
|
||
"title": "City-screen UI completeness (citizen assign, queue controls, promotion picker)",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-16",
|
||
"blocked_by": [],
|
||
"summary": "Three UI paths assumed-but-unverified:"
|
||
},
|
||
{
|
||
"id": "p0-10",
|
||
"title": "Game-completion stability — ≥7/10 seeds declare a winner",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Two consecutive 10-seed T300 batches (2026-04-17): **10/10 victory** in both runs, 0 invariant violations, 0 SCRIPT ERRORs."
|
||
},
|
||
{
|
||
"id": "p0-11",
|
||
"title": "Author the four T8–T10 mystery item drops",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-16",
|
||
"blocked_by": [],
|
||
"summary": "All four Game 1 mystery items shipped as mundane-with-magic-teaser-flavor per CLAUDE.md. Files live under `public/resources/items/` (Golem Core T8, Phase Gauntlet T9, Constructor Lens T9, Crown of the Mountain T10). Manifest and loot-table drops both wired."
|
||
},
|
||
{
|
||
"id": "p0-12",
|
||
"title": "Save / load + autosave on quit",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Save/load UI, autosave-on-quit hook, multi-slot naming, schema version with rejection of mismatches, and GDScript + Rust round-trip tests all shipped. The three serde regressions that reopened this objective on 2026-04-17 are closed:"
|
||
},
|
||
{
|
||
"id": "p0-13",
|
||
"title": "Fog of war and exploration / scout loop",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Fog-of-war is one of the core tension generators in 4X — without it the game collapses into perfect-information min-max. Rendering + `build_fog_arrays()` exist (just moved to `world_map_vision.gd` by #28). Missing: (a) sight-range formula by unit type from JSON, (b) \"has-been-seen\" memory layer (grey fog distinct from black fog), (c) explicit acceptance tests for visibility invariants."
|
||
},
|
||
{
|
||
"id": "p0-14",
|
||
"title": "Map generation, resource placement, and balanced fair starts",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-16",
|
||
"blocked_by": [],
|
||
"summary": "All four acceptance bullets verified. Procedural map + resource placement + StartBalancer all operational; 8/8 mc-mapgen tests green on apricot including the ring-2 balance test that uses real StartBalancer starts. Wild-lair exclusion at 8 hex. Settings (wild_density, num_players, map_type) all honored."
|
||
},
|
||
{
|
||
"id": "p0-15",
|
||
"title": "Happiness pool and Golden Age mechanics end-to-end",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Rust `mc-happiness::pool` is the single source of truth for all happiness simulation. All four acceptance bullets verified and green on apricot (2026-04-17): per-deposit luxury lookup via `BTreeMap<String, i32>`, Golden Age state-machine window test across `GOLDEN_AGE_DURATION` turns, growth halt + revolt thresholds, and Rust/GDScript parity via JSON round-trip. 21/21 tests pass (19 lib + 2 integration)."
|
||
},
|
||
{
|
||
"id": "p0-16",
|
||
"title": "Worker / tile-improvement build loop",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "> **Evidence drift resolved 2026-04-25 (p2-10a).** The stale duplicate > `src/game/engine/src/generation/auto_play.gd` was deleted as part of > the gdlint un-gating cleanup. The live autoload (`scenes/tests/auto_play.gd`) > uses score-based worker boost inside `_manage_production` (~lines 1285-1295); > the explicit-override pattern (`_maybe_prioritize_worker` + `WORKER_OVERRIDE_*`) > never landed in the live path. Evidence batch `.local/iter/p016b_20260417_024754/` > reflects the SCORE-BASED mechanism (since the stale file received no > dispatch). Acceptance now correctly maps to what actually"
|
||
},
|
||
{
|
||
"id": "p0-17",
|
||
"title": "Wild creature and lair clearing loop",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Full lair-clearing loop verified. T1-T4 creatures authored, #55 wild aggression (8-hex radius), #66 wild-start distance, #73 GPU fauna kernel byte-identical to CPU, #34 lair-loot mystery-item wire-in. `lair_cleared` EventBus signal declared in `event_bus.gd` and emitted from `auto_play.gd::_try_attack_adjacent_lair` when a lair is defeated in combat. Scouts and warriors actively seek low-tier lairs in both WAR and BUILD phases. 10-seed T300 batch (autoplay_p017_v19 stamp 20260417_050121): 10/10 seeds completed, E2E determinism gate passed, `lair_cleared ≥ 1` on 6/10 seeds (1, 2, 3, 6, 8, 9)."
|
||
},
|
||
{
|
||
"id": "p0-18",
|
||
"title": "Strategic resources gate unit production (empire ledger)",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Distinct from p1-02 (resource yields feed bonuses), this objective covers the **gating** rule: a unit with `requires_resource: \"iron_ore\"` cannot build unless the empire has iron_ore on the ledger. Rust logic landed in #81: `mc-combat::requirements::{check_strategic_reqs, debit_resources, credit_resources}` with 6 tests. GDScript deposit discovery hook added to `unit_manager.gd:recalculate_vision` (0→2 tile visibility triggers `EventBus.deposit_discovered` → `turn_manager.gd` credits `player.strategic_ledger`). GDScript production gate added to `turn_processor.gd` (pre-production check emits `"
|
||
},
|
||
{
|
||
"id": "p0-19",
|
||
"title": "Biome-driven collectibles → tile yields → happiness end-to-end",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-16",
|
||
"blocked_by": [],
|
||
"summary": "Biome-driven economy is plumbed end-to-end in the simulator and in the world-map tile tooltip, but the city screen (`city_screen.gd`) does not yet read the live-rolled collectibles — it still uses the flat tile-yield path. Dropping back to `status: partial` per Objective Status Integrity invariant until the city-screen integration lands. All other acceptance bullets verified passing."
|
||
},
|
||
{
|
||
"id": "p0-20",
|
||
"title": "GPU-accelerated MCTS rollouts for look-ahead decision-making",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-05",
|
||
"blocked_by": [],
|
||
"summary": "The MCTS tree (`mcts_tree.rs`) and the `mc-turn` GPU fauna pipeline are both live on `main`, but the AI cannot currently afford wide tree search: full `GridState` cloning (~12 MB at 256×256) blows out RAM long before the tree is deep enough to matter, and `TreeState::simulate()` is a 0.5 stub. This objective introduces a **GPU-batched abstract rollout** layer so the tree search can evaluate hundreds of candidate futures per leaf at single-digit-millisecond cost."
|
||
},
|
||
{
|
||
"id": "p0-20d",
|
||
"title": "GPU MCTS wall-time gate — measure on real-discrete-GPU test host",
|
||
"priority": "p1",
|
||
"status": "superseded",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-05",
|
||
"blocked_by": [],
|
||
"summary": "Phase A + B + C of `p0-20-gpu-mcts-rollouts` shipped the full architectural rewrite to main: - Live game uses `Tree<GameRolloutState>` exclusively, dispatched via `AiBackend::probe()`-routed `iterate_gpu_batched`. - Persistent staging buffer pool + 1024-rollout coalesced `wgpu::Queue::submit` (Phase B). - AI quality empirically validated as byte-identical to the pre-Phase-C `Tree<McSnapshot>` baseline (50 seeds × normal+hard, identical clan-win distribution: 35/3/4/2/3/3)."
|
||
},
|
||
{
|
||
"id": "p0-21",
|
||
"title": "Audio system capability — manifest + autoload + EventBus wiring",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The game has the full *capability* to play audio: manifest, autoload, event-signal wiring, crossfade logic, volume sliders. What's decoupled is the content — whether or not `.ogg` files exist under `assets/audio/`, the engine behaves correctly. Shipping the capability as P0 (required for release) is independent of shipping the assets (tracked separately as p2-16)."
|
||
},
|
||
{
|
||
"id": "p0-22",
|
||
"title": "Ultimate AI stress test — 5 clans, huge map, deep lookahead",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "The \"ultimate test\" is the final gate on the AI lookahead pipeline: five clan personalities competing on a map sized large enough for eight players, with MCTS + GPU batched rollouts driving every decision. The goal is to confirm the lookahead SCALES — deep trees, many expansions, genuine strategic divergence between clans at multi-clan scale — not just that it works on the 1v1 fixtures already covered by p0-02's `personality_win_balance`."
|
||
},
|
||
{
|
||
"id": "p0-23",
|
||
"title": "Sprite rendering capability — replace procedural draw_* with texture rendering",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Renderers now implement the additive-overlay design rule: `draw_circle` baseline always renders first (unconditional), then `draw_texture` overlays the sprite on top when a file exists at the resolved path. Both renderers follow this invariant."
|
||
},
|
||
{
|
||
"id": "p0-24",
|
||
"title": "Difficulty-calibrated AI progression — Easy / Normal / Hard tier-peak distributions",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "Added 2026-04-17 as part of the TTV → state-at-end metric reframe (see p0-01). The game's three AI-difficulty tiers (Easy / Normal / Hard in `difficulty.json`) must produce *measurably different* progression profiles when batched. The current MCTS + heuristic stack doesn't actually change behavior between difficulty tiers — `ai_difficulty` is read in a few Rust spots but has no empirically-validated behavioral split."
|
||
},
|
||
{
|
||
"id": "p0-25",
|
||
"title": "Game-quality metrics instrumentation — tier_peak, peak_unit_tier, wonder_count",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Added 2026-04-17 as part of the TTV → state-at-end metric reframe (see p0-01). `turn_stats.jsonl` per-player stats now carry three quality metrics: `tier_peak` (max era reached, monotonic across turns; derived each turn by folding `DataLoader.get_tech(id).era` over `player.researched_techs` in `_check_invariants`), `peak_unit_tier` (max `DataLoader.get_unit(id).tier` seen via the `EventBus.unit_created` hook in `_on_unit_created`), and `wonder_count` (entries in `GameState.wonders_built` whose value equals the player's index, computed in `_build_player_stats`). The schema declares all three wi"
|
||
},
|
||
{
|
||
"id": "p0-26",
|
||
"title": "Port tactical AI from GDScript to mc-ai (Rail-1 compliance)",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "AI decision-making currently lives in ~1,880 LOC of GDScript (`simple_heuristic_ai.gd` 1,240 LOC + `ai_tactical.gd` 405 LOC + `ai_military.gd` 233 LOC), in violation of Rail-1. This objective ports every non-UI AI decision into `mc-ai` and exposes a single `GdAiController` bridge that the gameplay turn loop drives."
|
||
},
|
||
{
|
||
"id": "p0-26b",
|
||
"title": "Port _pick_research from GDScript into mc-ai (finish Rail-1 for the AI decision surface)",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "p0-26 ported the tactical AI (movement, production, combat, settle, promotion) from GDScript into `mc-ai` and is marked done. But **research selection is the one AI decision still made in inline GDScript**: `auto_play.gd::_pick_research` (line ~1216) scores techs with hand-written per-pillar personality multipliers, and its own comment admits the gap:"
|
||
},
|
||
{
|
||
"id": "p0-27",
|
||
"title": "GdCulture bridge — live game delegates culture to mc-culture",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`mc-culture` is a fully-tested 297-LOC crate with `CulturePool` + `CityCultureState` + 10 passing unit tests, but `grep 'use mc_culture'` returned zero hits outside the crate. The live game (`culture.gd:33,62`) reimplemented the `5 + n` threshold inline in GDScript, and `mc-turn/src/processor.rs:527` did its own `player.culture_total += culture_per_turn` bypassing `CulturePool::tick_all`. Three parallel implementations of the same rule."
|
||
},
|
||
{
|
||
"id": "p0-28",
|
||
"title": "GdEconomy bridge — live game delegates gold/upkeep to mc-economy",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`mc-economy` exports `process_gold`, `Treasury`, `Stockpile` — consumed by `mc-turn/src/processor.rs::process_economy` (the bench/optimizer path) and `mc-city` (Stockpile). But the live gameplay turn runs a ~50-line `_process_economy` in `turn_processor.gd:435` that independently computes marketplace bonuses, unit upkeep, and insolvency handling. `economy.gd` itself is a 2-line empty `class Economy extends RefCounted`."
|
||
},
|
||
{
|
||
"id": "p0-29",
|
||
"title": "GdTechWeb bridge — live game delegates research to mc-tech",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`mc-tech` exports `TechWeb`, `PlayerTechState`, `ResearchResult`, `UnlockSignal`. Previously consumed only by `mc-turn/src/processor.rs::process_science` (bench/optimizer path). The live game ran a parallel ~52-line `_process_research` in `turn_processor.gd:156` duplicating cost accumulation, spell-vs-tech dispatch, and the `FORCE_UNLIMITED_RESEARCH` debug knob."
|
||
},
|
||
{
|
||
"id": "p0-30",
|
||
"title": "Remove duplicate GDScript ecology tick (single Rust source)",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "The tech-debt audit (`.project/reports/simulation/tech-debt-audit.md:11-18`, 2026-04-09) identified that ecology simulation runs **twice per turn**:"
|
||
},
|
||
{
|
||
"id": "p0-31",
|
||
"title": "Restore Rust ecology path — fix ClimateScript bugs + re-enable per-turn tick",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "p0-30 deleted the duplicate GDScript ecology pass (`ecosystem.gd`/`flora.gd`, 939 LOC) but could not close its bullet 4 (\"10-seed batch shows evolving canopy values\") because the Rust path is **also** disabled. `turn_processor.gd::_process_climate` (line 583) calls `MarineHarvestScript` only; the three sibling `process_turn` calls (`WeatherScript`, `ClimateScript`, `ClimateEffectsScript`) are commented out, citing real bugs:"
|
||
},
|
||
{
|
||
"id": "p0-32",
|
||
"title": "Restore WeatherScript + ClimateEffectsScript — per-turn weather and climate-effects",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "p0-31 restored the Rust ecology tick via `ClimateScript.process_turn` but left the two sibling `process_turn` calls in `turn_processor.gd::_process_climate` commented out (see the trailing comment on `_process_climate` after p0-31 landed). Both classes were empty stubs: calling their `process_turn` aborted `next_player` and killed the arena turn loop."
|
||
},
|
||
{
|
||
"id": "p0-33",
|
||
"title": "World-map input wiring — unit selection panel, city click, ESC/F10 menu, panel close",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "The world-map is unplayable because basic interaction is broken: clicking a unit does nothing visible, clicking a city does nothing, and there is no way to exit the game (ESC and F10 are both dead). Five discrete wiring gaps are responsible:"
|
||
},
|
||
{
|
||
"id": "p0-34",
|
||
"title": "Freepeople tribe-founding cinematic — turn -1 / 0 / 1 start sequence and Dwarf Tribe founder unit",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "Game 1 currently drops the player on turn 1 with a pre-placed founder (settler) unit, which is narratively flat: \"here is your settler, go.\" The collective wants the opening to *tell* the player why they are leading this civilization. A dwarf civilization begins when wandering freedwarves converge on a shared location and coalesce into a tribe — that tribe then founds the first city. This objective encodes that story as a three-turn cold-open (`-1 → 0 → 1`) using systems already partially in the codebase (freepeople camps, turn processor, founder units)."
|
||
},
|
||
{
|
||
"id": "p0-35",
|
||
"title": "Ecology telemetry instrumentation — flora canopy / undergrowth fields in turn_stats.jsonl",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "`turn_stats.jsonl` currently emits `aggregate.total_combats`, `player_stats.*.tier_peak` etc. (per p0-25) but no flora/ecology fields. p0-30 / p0-31 bullets about \"flora canopy values evolve in turn_stats.jsonl\" cannot empirically close without these fields."
|
||
},
|
||
{
|
||
"id": "p0-36",
|
||
"title": "Weather / climate-effects event telemetry — events.jsonl + turn_stats aggregates",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "p0-32 added `WeatherScript.process_turn` + `ClimateEffectsScript.process_turn` over the Rust `mc-climate` crate. The calls run per turn without crashing (smoke5 batch 2026-04-17 confirms), but no weather-event records reach `events.jsonl` or `turn_stats.jsonl` aggregates — p0-32 bullet 4 \"weather events visible via event log\" cannot close without this wiring."
|
||
},
|
||
{
|
||
"id": "p0-37",
|
||
"title": "Personality-emergent tactical thresholds (lift 7 hardcoded constants into axis-derived functions)",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "The p0-26 tactical port faithfully copied 7 tuning constants from `simple_heuristic_ai.gd` into Rust. They're currently flat globals that ignore personality axes and difficulty tier, which means:"
|
||
},
|
||
{
|
||
"id": "p0-38",
|
||
"title": "Inject personality-utility scores as MCTS UCB1 priors",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-24",
|
||
"blocked_by": [],
|
||
"summary": "Current MCTS selection uses classical UCB1 at tree nodes — all actions start with equal prior, exploration is driven only by visit count. `ScoringWeights` and `strategic_axes` feed the *tactical executor* and *leaf evaluator* but NOT the tree-selection step. This means MCTS explores the same branches for every clan; divergence only appears at the leaf."
|
||
},
|
||
{
|
||
"id": "p0-39",
|
||
"title": "AI tier-progression unit selection — production.rs picks tier-2+ units once tech unlocks",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "Shipwright audit 2026-04-18 of tech_web.json + research costs (requested by warcouncil session-close handoff) found the tech tree, costs, and research pacing are correct. `peak_unit_tier=1` universally is NOT a balance-data issue. Root cause is in the tactical AI's production-selection logic:"
|
||
},
|
||
{
|
||
"id": "p0-40",
|
||
"title": "Iron-ore strategic resource density — unblock tier 3-6 unit chain",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-24",
|
||
"blocked_by": [],
|
||
"summary": "Warcouncil filed 2026-04-18 after p0-39 (AI tier-progression) unlocked tier-2 units. Post-p0-39 smoke batch (`.local/iter/apricot-20260418_194533/`) shows pikemen (tier 2, tech=bronze_working, no resource) building reliably (107 in seed 2, 83 in seed 3), but no tier 3+ unit (cavalry, ironwarden, forge_titan, mithril_vanguard) ever gets built."
|
||
},
|
||
{
|
||
"id": "p0-41",
|
||
"title": "Building rally points — produced units auto-deploy to a designated hex",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-24",
|
||
"blocked_by": [],
|
||
"summary": "Unit-producing buildings (barracks and others with `can_rally: true`) can have an optional rally hex + default command (Defend / Patrol / Advance). Units produced at such a building are automatically issued a move order to the rally hex on spawn. Once they arrive they auto-join the formation at that hex (if auto_join is enabled). This is the supply pipeline that feeds formations — the rally point is how the player designates where their growing army should concentrate without micromanaging every produced unit."
|
||
},
|
||
{
|
||
"id": "p0-41a",
|
||
"title": "Rally-point smoke — produced unit gets PatrolOrder toward rally hex",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "End-to-end smoke verification for the rally-point feature (p0-41). Originally framed as needing a weston display server on apricot (\"set rally on barracks UI → produce unit → screenshot the move\"), but the same coverage is achievable via Rust unit tests against `try_spawn_unit` — the rally behavior is fully encoded in `processor.rs:768-782` and the resulting `PatrolOrder` is the contract that drives subsequent movement."
|
||
},
|
||
{
|
||
"id": "p0-42",
|
||
"title": "Formation aggregation — adjacent units link into a shaped formation with terrain reflow",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "Units in adjacent hexes (same owner, both with auto_join enabled) automatically link into a Formation. Each unit retains its own hex — no stacking. The formation has a defined shape (Line, Column, Wedge, Diamond) expressed as relative hex offsets from a leader unit. When the formation moves, a reflow solver computes target hexes for all members: if the preferred shape doesn't fit terrain (e.g. a 5-wide Line entering a 2-hex canyon), it automatically compresses to a Column and re-expands on exit. Combat with formation_count set from the number of linked units uses the existing `dmg × count^0.75"
|
||
},
|
||
{
|
||
"id": "p0-42a",
|
||
"title": "Formation aggregation smoke — formations form and evolve at runtime",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "End-to-end smoke verification for the formation aggregation feature (p0-42). Spun off from p0-42 on 2026-04-25 originally framed as needing weston (display server) but resolved 2026-04-25 via headless run — formation evidence surfaces in `game.log` via `AiTurnBridge: formations turn=N player=P count=C sizes=[...] tiers=[...]` lines, no display server required."
|
||
},
|
||
{
|
||
"id": "p0-43",
|
||
"title": "Formation AI — MCTS plans at formation level, not per-unit",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "After p0-42 lands, the MCTS strategic planner should treat formations as the atomic military entity rather than individual units. The abstract rollout state (AbstractPlayerState in mc-ai/src/abstract_state.rs) is updated to track formation count + tier + strength instead of raw unit_counts. Action candidates include CommandFormation (advance formation to hex) scored by military axis. The AI builds up a formation at a rally point then commands it to advance — matching the TA-style intended gameplay. This also makes GPU MCTS rollouts viable: M=3-8 formations per player vs N=50 individual units d"
|
||
},
|
||
{
|
||
"id": "p0-44",
|
||
"title": "Movement mode UX — Move button, path preview, right-click confirm, fog-aware pathing",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "Movement is currently a silent left-click on a reachable hex — no path shown, no confirmation step. Players expect the Civ-style flow: enter movement mode (M key or Move button), see a path preview, right-click to confirm. This objective adds the full movement-mode state machine, path rendering, fog-of-war-aware pathing, and the Move button on the unit action panel with disabled-state tooltips for all action buttons."
|
||
},
|
||
{
|
||
"id": "p0-45",
|
||
"title": "Turn processor consolidation — entities/ duplicate caused T1 SCRIPT ERROR halt",
|
||
"priority": "p0",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "Cycle-4 p2-43 (cultural-tradition research) added `_process_culture_research(player)` to `src/game/engine/src/entities/turn_processor.gd` — the WRONG file. Runtime turn loop preloads `src/game/engine/src/modules/management/turn_processor.gd` (see `src/game/engine/src/autoloads/turn_manager.gd:29`). The autoload calls `proc._process_culture_research(player)` at `turn_manager.gd:247`; management/'s instance lacks the method, so every batch halts at T1 with:"
|
||
},
|
||
{
|
||
"id": "p1-01",
|
||
"title": "Diplomacy-lite — peace/war toggle plus one trade action",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`mc-trade` now has a full diplomacy surface: `declare_war` / `offer_peace` / `evaluate_trade_offer` / `apply_trade_offer` free functions plus `DiplomacyEvent` enum and `TradeOffer` struct. `TurnProcessor` exposes `action_declare_war`, `action_offer_peace`, `action_offer_trade`, and `action_accept_trade_offer` as public methods callable from GDExtension. EA policy: AI always rejects player-initiated peace offers and gold-for-luxury offers; automated luxury swaps flow through the existing `evaluate_trades` path. Relation state machine (`Relation::Neutral/Peace/Friendly/War`) was already present "
|
||
},
|
||
{
|
||
"id": "p1-02",
|
||
"title": "Strategic resource yields feed into production bonuses",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Deposit resource definitions (iron, coal, gems, etc.) grant per-turn production/food bonuses to all owned cities when held in `strategic_ledger`. The wire-through runs via `TurnProcessor::process_deposit_yields` (called at Phase 1a2 in `step` and `step_legacy`), which iterates `deposit_yield_table: BTreeMap<String, DepositYieldEntry>` and adds `production`/`food` to each city's `prod_yield`/`food_yield` when the player's `strategic_ledger` has a non-zero count for that deposit. `QueueError::MissingResource` gates unit production at enqueue time when `requires_resource` is not in the ledger."
|
||
},
|
||
{
|
||
"id": "p1-03",
|
||
"title": "First-run tutorial / onboarding overlay",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "First-run tutorial overlay walks new players through the seven core 4X actions with a live-event chain. Each step subscribes to the matching `EventBus` signal on enter and auto-advances when the player performs the action — no click-through required, but Skip, Back, and Next remain available at every step. `SettingsManager(\"gameplay\", \"tutorial_completed\")` persists completion so the overlay never reshows unless the player hits **Replay on next start** in the options screen."
|
||
},
|
||
{
|
||
"id": "p1-05",
|
||
"title": "Balance tuning — pop_peak ≥30 median, worker improvements ≥8 min",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Post-p0-16 batch (`.local/iter/p016b_20260417_024754/`, 10 seeds T300, captured 2026-04-17 02:54): the worker-production fix for p0-16 had a large downstream lift on pop + combats. Per-seed p0_pop_peak = [58,46,76,65,77,74,53,113,73,36]; median **69**, min 36, max 113. Worker improvements per seed = [45,24,73,43,49,21,15,120,25,62]; median **44**, min **15**. Combats median **808**, techs median **39**. All four primary acceptance metrics now clear their thresholds decisively — the 29.5-vs-30 gap from score_fix3 dissolved once workers consistently drop farms."
|
||
},
|
||
{
|
||
"id": "p1-05-followup-shipwright-batch",
|
||
"title": "Shipwright autoplay-batch sign-off — luxury variance + personality win balance",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Two p1-05 acceptance bullets cannot close inside p1-05's JSON-tuning-only scope — both depend on upstream warcouncil work and require a fresh 10-seed (or 50-game) autoplay batch on apricot once that upstream lands:"
|
||
},
|
||
{
|
||
"id": "p1-06",
|
||
"title": "Options screen polish",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Options screen ships with five sections — Display, Audio, Camera & Controls, Gameplay, Game Defaults, Privacy — backed by `SettingsManager` autoload which persists to `user://settings.cfg` and applies every change live. Restore-defaults and Back buttons anchor the bottom row."
|
||
},
|
||
{
|
||
"id": "p1-07",
|
||
"title": "Chronicle notifications coverage",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`turn_notification.gd` renders the end-of-turn chronicle log. Handlers cover the full event surface (tech, wonder, city_grew / starved / founded / captured / border_expanded, building / unit completion, combat_resolved, unit_destroyed, victory, player_eliminated, era, golden age, happiness, improvement, natural events). The panel now ships a five-checkbox filter row (All + Military + Research + City + Diplomacy) with `_entry_passes_filter` gating render, plus per-entry click-to-pan: entries carrying a `hex_pos` render as Buttons that emit `EventBus.chronicle_entry_clicked(hex_pos)`, and `world"
|
||
},
|
||
{
|
||
"id": "p1-08",
|
||
"title": "Victory/defeat screen content — recap, banner, replay seed",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Victory and defeat overlays share a stats grid with 8 columns (Player / Pop / Cities / Tiles / Techs / Wonders / Units / Score), a seed-and-map recap line, and a three-button footer. Defeat screen additionally surfaces the top-scoring surviving player. Both scenes carry a Replay Same Seed button that stashes `GameState.replay_settings` and routes back to `game_setup.tscn`, where `_apply_replay_settings()` rehydrates every widget from that dict. Banner copy branches on `victory_type` via `victory_banner_domination` vs `victory_banner_score` vocabulary keys."
|
||
},
|
||
{
|
||
"id": "p1-09",
|
||
"title": "Determinism gate — same seed produces byte-identical runs",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "Determinism is foundational for save/load, replay, bug reproduction, and golden tests. Prior work fixed seed-ingestion (`game_state.gd:113-115`), migrated HashMap→BTreeMap in several crates, sorted DataLoader enumeration, and pathfinder tiebreakers. Testwright's T1 task landed `mc-mapgen/tests/determinism.rs` (389 lines) with PCG32 golden vector + seed-stable map generation, now running green in CI."
|
||
},
|
||
{
|
||
"id": "p1-10",
|
||
"title": "Game setup UX — new-game dialog, difficulty, clan preview",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All acceptance bullets now verifiable in repo:"
|
||
},
|
||
{
|
||
"id": "p1-11",
|
||
"title": "Purge build output from src/ — wasm-pack moves to .local/build/wasm/",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All 8 acceptance bullets verifiable in repo:"
|
||
},
|
||
{
|
||
"id": "p1-12",
|
||
"title": "Align every doc reference to the relocated wasm-pack output",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All 12 acceptance bullets verifiable in repo:"
|
||
},
|
||
{
|
||
"id": "p1-13",
|
||
"title": "Guide dev server boots on plum with zero-error route coverage",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All 6 acceptance bullets verifiable in repo:"
|
||
},
|
||
{
|
||
"id": "p1-14",
|
||
"title": "Gate Game 2/3/4 magic-school content behind EpisodeGate (future-game scope)",
|
||
"priority": "p1",
|
||
"status": "oos",
|
||
"scope": "game2",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Per CLAUDE.md's hard Game 1 scope rule (Dwarves only, NO magic; leylines / Green school / spacefaring → Game 2; Archons / Ascension / 5 magic schools → Game 3), no magic content may ship into the Game 1 guide. The 2026-04-17 `p2-09` scope-narrow pass deleted 10 Game 2/3 pages. But a prophylactic `Explore` audit on the same day — triggered by the Tourguide route-coverage spec catching magic-data imports in Game 1 pages that still rendered — surfaced **2 RED** and **6 YELLOW** residual leaks that survived the first purge:"
|
||
},
|
||
{
|
||
"id": "p1-15",
|
||
"title": "Deploy dev guide to https://mc.next.black.local",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All acceptance bullets verifiable:"
|
||
},
|
||
{
|
||
"id": "p1-16",
|
||
"title": "Purge Game 2/3 scope bleed from user-visible Game 1 guide copy",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "All acceptance bullets verifiable:"
|
||
},
|
||
{
|
||
"id": "p1-17",
|
||
"title": "Forgejo workflow auto-deploys dev guide on push to main",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "- ✓ `.forgejo/workflows/deploy-next.yml` authored (70 LoC). Trigger: `push: branches: [main]` + `workflow_dispatch`. Path filters scope rebuilds to `public/games/age-of-dwarves/guide/**`, `public/games/age-of-dwarves/data/**`, `public/resources/**`, `src/packages/guide/**`, `src/packages/engine-ts/**`, `src/simulator/**`, `scripts/run/deploy.sh`, and this workflow file. Concurrency group `deploy-next-main` with `cancel-in-progress: true` keeps at most one deploy in flight. Timeout 15 min. - ✓ `runs-on: [self-hosted, linux, apricot]` reuses the same runner labels as `ci.yml`; no new RUNNER_SETU"
|
||
},
|
||
{
|
||
"id": "p1-18",
|
||
"title": "Village discovery — world-map feedback (notification, reward popup, minimap ping)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "`EventBus.village_discovered(tile_pos, reward)` fires when a unit walks onto a village tile, but `_on_village_discovered` in `world_map.gd` is a no-op stub. The player receives gold silently with no visual feedback."
|
||
},
|
||
{
|
||
"id": "p1-19",
|
||
"title": "Tutorial opt-in — HUD button, disappears after turn 5, starts from Step 1",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "The first-run tutorial currently auto-shows on game start (gated by `TutorialOverlay.should_show_on_first_run()`). This is hostile to returning players and to playtesting — the tutorial interrupts real gameplay every fresh boot. Assume the player doesn't need a tutorial by default. Offer it as a button on the world-map HUD that disappears after turn 5."
|
||
},
|
||
{
|
||
"id": "p1-20",
|
||
"title": "Unit action capability registry — one source of truth for \"what can this unit do right now?",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "The game has no unified answer to *\"what actions can unit U take on turn T in state S?\"* Today the unit panel (`unit_panel.gd:19-40`) hardcodes three buttons — Fortify, Skip, Found City — and decides visibility with bespoke per-unit booleans scattered across the JSON (`can_found_city`, `can_build_improvements`, `flags: [\"ranged\"]`) and ad-hoc GDScript predicates (`is_civilian()`). Meanwhile `mc-ai/src/tactical/movement.rs` enumerates moves and attacks but has no registry for non-motion actions. UI and AI have no shared truth."
|
||
},
|
||
{
|
||
"id": "p1-21",
|
||
"title": "Unit patrol orders — standing order to loop between waypoint tiles",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-04-19",
|
||
"blocked_by": [],
|
||
"summary": "Both the human player and the AI clans need a *standing order* that keeps a unit moving along a fixed route turn after turn without per-turn micro- management. Canonical use cases: escorting a worker loop, covering a chokepoint, sweeping scout fog between two outposts."
|
||
},
|
||
{
|
||
"id": "p1-22",
|
||
"title": "MCTS per-decision wall-clock budget — bound per-turn cost on huge maps",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Spun out from p0-22 (Ultimate AI stress test) on 2026-04-25 after the 7 root-cause fixes (combat method typos, per-slot pinning, score-victory fallback, NOTIFICATION_PREDELETE, autoplay-batch.sh MCTS branch, etc.) verified the pipeline produces `outcome:victory` at T500 on the huge-map config. The remaining gap blocking `ultimate_stress: PASS` is **purely MCTS per-turn wall-clock cost on game-state complexity**: with deterministic seeds, some maps produce game states where each MCTS decision takes 30-60+ seconds (vs <5s on simpler states). Even at `PARALLEL=2 SAFETY_TIMEOUT_OVERRIDE=3600s`, sl"
|
||
},
|
||
{
|
||
"id": "p1-22a",
|
||
"title": "Huge-map AI quality — close the 4/10 → ≥5/10 decisive-game gate",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-17",
|
||
"blocked_by": [],
|
||
"summary": "The huge-map 5-clan batch (`tools/huge-map-5clan.sh`, 10 seeds, T300 limit, `MCTS_DECISION_BUDGET_MS=2000`) has landed at **4/10 victories** across three independent runs (cycle-1 pre-budget, cycle-2 post-tactical-budget, cycle-3 post-p0-20 2× GPU rollout speed). The gate is ≥5/10."
|
||
},
|
||
{
|
||
"id": "p1-23",
|
||
"title": "Restore StatsTracker — demographics overview broken in shipped builds",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "`engine/scenes/overviews/demographics.gd` (and `end_game_stats.gd`) referenced `StatsTracker.CATEGORIES`, `CATEGORY_LABELS`, `get_rankings`, `get_history`, `get_player_series` but no `StatsTracker` class_name or autoload existed. Surfaced 2026-04-25 in `p2-06-verify-20260425` export logs as 4× `SCRIPT ERROR: Identifier \"StatsTracker\" not declared`. The demographics screen was shipped broken."
|
||
},
|
||
{
|
||
"id": "p1-24",
|
||
"title": "ai_personalities.json fails to load from packed builds (all platforms) — pass JSON contents not path",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "When the Windows .exe cross-compiled via cargo-xwin runs under Wine on apricot (p2-06b smoke 2026-04-25), it floods the log with:"
|
||
},
|
||
{
|
||
"id": "p1-25",
|
||
"title": "Eliminate parse-error spam in export logs (Unit dup decl + SaveManager stray)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "Every Linux/Windows export log was emitting two families of parse errors despite producing a working binary:"
|
||
},
|
||
{
|
||
"id": "p1-26",
|
||
"title": "Tile-placement UX with effect preview — Civ7-style \\\\\\\"where does this go and what changes\\\\\\",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "When a player queues a building or tile improvement today, the placement is a black box: - **Buildings**: every building entry in `public/games/age-of-dwarves/data/buildings/*.json` has `placement: \"city\"` — buildings just appear in the city center, no tile choice, no spatial decision. - **Improvements**: a worker drops a farm/mine/road at the worker's hex; the player sees no preview of yield-delta or adjacency effects before committing."
|
||
},
|
||
{
|
||
"id": "p1-27",
|
||
"title": "Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Today the GPU MCTS path lives **inside** the `mc-ai` crate (`gpu/inner.rs`, `gpu/rollout.wgsl`, `gpu/cpu_reference.rs`) and runs in-process via the GDExtension (`GdMcTreeController`). That couples GPU lifecycle (device init, queue submission, buffer pooling, fence waits) to the game's per-turn decision call."
|
||
},
|
||
{
|
||
"id": "p1-27a",
|
||
"title": "MCTS service telemetry + parity test + huge-map wiring",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-16",
|
||
"blocked_by": [],
|
||
"summary": "Split out of `p1-27` (architectural extraction, closed `done` 2026-05-14). The service crate ships, the client/server protocol ships, the gdext fallback path ships, and `tools/run-services.sh` manages lifecycle. What did NOT ship under p1-27, and is tracked here:"
|
||
},
|
||
{
|
||
"id": "p1-27d",
|
||
"title": "Add `value_estimate_abstract` GdMcTreeController method — non-lossy MCTS service caller",
|
||
"priority": "p2",
|
||
"status": "superseded",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "**2026-04-25**: this objective is moot. The cycle 3 specialist initially shipped Option B (3-job batch + lossy conversion) which would have required this additive Option C method to give the service a non-lossy caller. They then re-revisited and shipped **Option A** instead — full `SearchAction` protocol extension that calls `Tree::simulate_parallel` server-side over the actual `McSnapshot`. No lossy conversion happens; the service is the canonical caller for the gameplay loop."
|
||
},
|
||
{
|
||
"id": "p1-28",
|
||
"title": "Culture research tree — real graph, bridge, UI",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "The culture data files (`public/games/age-of-dwarves/data/culture/manifest.json` → `public/resources/culture/*.json`) describe a six-pillar culture tree that mirrors the tech-tree shape: `id`, `name`, `pillar`, `era`, `tier`, `cost`, `requires`, `unlocks{…}`, `flavor`. The web guide already renders it via the shared `TechTreeGraph` component (`CultureTreePage.tsx`)."
|
||
},
|
||
{
|
||
"id": "p1-29",
|
||
"title": "Anti-early-domination: lift game-balance gates that p0-01 v1 measured",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Split out from p0-01's original v1 sub-gates that the AI-layer cycles (1, 2, 3) could not move because they measure emergent game-balance dynamics, not AI quality. p0-01 closed `done` 2026-04-26 against Gate v2 (3/5 v1 sub-gates pass cleanly: tier_peak=4, wonders 7/10, combats=255). The 2 v1 sub-gates that v2 reframed away need a real owner:"
|
||
},
|
||
{
|
||
"id": "p1-29a",
|
||
"title": "Last-stand defense — combat-strength multiplier when defender is at last city",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Audit-and-flip: original bullets 5 and 7 were gameplay-outcome gates whose failure mode (`p1_tier_peak=1` across the batch) is a research/AI-strategy problem, not a combat-mechanic problem. The last-stand combat multiplier and wall-HP scaling — the actual combat-side deliverables this objective owns — are durably landed in `mc-combat`, wired through `mc-turn`/`api-gdext`/GDScript, and covered by green Rust tests in both `mc-combat` and `mc-ai`. Bullets 5 and 7 moved to **Out of scope** with explicit hand-off to `p1-29c-sole-city-research-path` (owner: game-ai/warcouncil)."
|
||
},
|
||
{
|
||
"id": "p1-29b",
|
||
"title": "AI tech tier gap — structural research path quality (low-pop AI fails to reach t1+)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "Filed by cycle 47 close-out as the structural root cause for the alive-aware gate failure in p1-29a. The `tier_peak_gap ≤ 4` gate (across ALL living players at game end) failed 1/10 seeds in the cycle-45 batch even after p1-29a's last-stand multiplier landed. The failure is structural: the MCTS evaluator, rollout scorer, and tactical production allocator all apply flat weights that make sole-city players deprioritize research relative to defense and expansion. Tuning multipliers on a compound that starts near zero does not close the gap."
|
||
},
|
||
{
|
||
"id": "p1-29c-followup-empty-params-json-regression",
|
||
"title": "GdEconomy::process_turn fails — `_build_params_json` produces empty string for autoplay seeds",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-15",
|
||
"blocked_by": [],
|
||
"summary": "`p1-29c` autoplay batch ran on apricot 2026-05-14 (`autoplay_batch_p1_29c-1778813509`, DONE 20:08). All 12 seeds **FAILED** the E2E gate with hundreds of repeated errors:"
|
||
},
|
||
{
|
||
"id": "p1-29c",
|
||
"title": "Sole-city research path — lift trailing AI from tier_peak=1 to ≥2",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-27",
|
||
"blocked_by": [],
|
||
"summary": "Filed by p1-29b cycle-50 close-out (2026-05-07) and re-confirmed by p1-29a cycle-45 diagnosis (2026-05-07). p1-29b clamped p0's runaway (9/10 seeds now satisfy `tier_peak_gap ≤ 4`) but **p1 remains stuck at `tier_peak = 1` in every game**. The gap metric was tightened by capping the leader, not by lifting the trailing sole-city AI."
|
||
},
|
||
{
|
||
"id": "p1-29d-p1-survival",
|
||
"title": "P1 (trailing AI) eliminated or stalled before T100 in 10/10 seeds — upstream of action priority",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "`p1-29c` shipped sole-city research-priority uplift (`SituationalContext::sole_city_threatened` adds `+0.40 Settle / +0.20 Defend / +0.50 Research`) and was apricot-verified on batch `20260515_215705` (10/10 games produced complete turn_stats; infrastructure clean after commits `e200634df` + `8820ce04a`). The gate result:"
|
||
},
|
||
{
|
||
"id": "p1-29e-rl-divergence-mining",
|
||
"title": "RL-policy divergence mining → sole-city economy break-out (production, not science)",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "Dispatch task: mine the trained MaskablePPO policy `duel-v4-encfix-s7` (+5.5 mean reward vs the scripted MCTS baseline) for behavioural diffs vs the scripted AI, and turn them into concrete heuristic-patch candidates targeting the two in-flight blockers p1-29c (sole-city research path) and p1-29d (trailing-AI survival before T100)."
|
||
},
|
||
{
|
||
"id": "p1-29f",
|
||
"title": "learned:* controller bridge — make the trained RL policy playable in-engine",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "The pluggable controller seam exists and is exercised every batch (`mc_player_api::controllers` registry, `DEFAULT_CONTROLLER_ID = \"scripted:default\"`, `GdAiController` bridge, `mod_loader.gd` boot scan, `registered_controller_ids()` for the game-setup UI). But the **only** controllers ever registered into the engine dispatch path are `scripted:default` (in-box) and WASM/native mod controllers via `mc-mod-host`. The `learned:*` controller is documented as \"Stage 6\" in `controllers.rs` docstrings and **does not exist** as code."
|
||
},
|
||
{
|
||
"id": "p1-29g",
|
||
"title": "Re-verify Game-1 AI quality gates trained-vs-scripted (and trained-vs-trained)",
|
||
"priority": "p1",
|
||
"status": "missing",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-27",
|
||
"blocked_by": [
|
||
"p1-29f"
|
||
],
|
||
"summary": "Every Game-1 AI-quality gate to date — p1-29c (sole-city research path), p1-29d (trailing-AI survival), p1-29a (last-stand), p1-29b (tier_peak_gap), p0-24 (difficulty distributions) — has been measured **scripted-vs-scripted** (`scripted:default` in both slots, MCTS rollouts). That is the product-correct matchup for what Game 1 ships (5 scripted clan personalities), but it tells us nothing about how those gates behave against the trained RL policy, which is +5.5 mean reward stronger (p1-29e) and makes structurally different decisions (production-only economy, no research action — p1-29e F1/F2)"
|
||
},
|
||
{
|
||
"id": "p1-29h-stateful-tactical-decisiveness",
|
||
"title": "Stateful tactical decisiveness — army target-lock + commitment hysteresis + press-on-capture in mc-ai",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "`p1-29d`'s clean-surface investigation (2026-06-03) reduced the entire `p1-29a..e` \"trailing-AI never converges / balance levers never move the dial\" family to a single, precisely-characterised root cause:"
|
||
},
|
||
{
|
||
"id": "p1-29i-refound-suppression",
|
||
"title": "Refound-suppression / capture-stickiness lever — convert captures into eliminations",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "p1-29h Phase 2 isolated the elimination wall: on the fair gridded two-`scripted:default` duel (`mc-player-api/tests/p1_29h_gridded_elimination.rs`) the army-lock engages and captures land (20 captures / 160 turns) but **0 eliminations** — the loser refounds before the attacker can take the last city. p1-29h flagged refound-suppression / capture-stickiness as the candidate lever and asked for a new objective if scope warranted. It does — this is it."
|
||
},
|
||
{
|
||
"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",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "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:"
|
||
},
|
||
{
|
||
"id": "p1-30",
|
||
"title": "Optimize `_build_tactical_state` — 8000-tile GDScript dict-build per AI turn blocks p1-22 huge-map gate",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "Split out from p1-22 (MCTS per-decision wall-clock budget). p1-22 closed `partial` after cycles 2-3 shipped strategic + tactical Rust budgets (`mcts_tree::simulate_parallel` + all 5 `mc-ai/src/tactical/` submodules + `GdAiController::set_budget_ms`, 186/186 lib tests). Strategic budget verified working: p1-22-cycle-2 batch had seeds 9 and 10 reach T500 victories at max_tier=10 and 7. Tactical budget verified bounded by unit test `tactical::tests::tactical_budget_respected`."
|
||
},
|
||
{
|
||
"id": "p1-30b",
|
||
"title": "Parallel MCTS rollouts for huge-map decisive games (closes p1-22's huge-map sub-gate)",
|
||
"priority": "p1",
|
||
"status": "superseded",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-05",
|
||
"blocked_by": [],
|
||
"summary": "**Status: superseded by p0-20.**"
|
||
},
|
||
{
|
||
"id": "p1-31",
|
||
"title": "Split bundled `resources/buildings/<category>.json` into per-file pattern matching `resources/units/`",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "`public/resources/units/` follows a one-file-per-unit convention: 65 dwarf-prefixed unit files (`dwarf_warrior.json`, `dwarf_founder.json`, ...) plus generic-class siblings under `public/games/age-of-dwarves/data/units/` (`warrior.json`, `worker.json`, `archer.json`, ...). Total 150 unit IDs, each in its own file. Easy to find, easy to diff, easy to git-blame, easy to override one without disturbing others."
|
||
},
|
||
{
|
||
"id": "p1-32",
|
||
"title": "Author the two missing food/processing buildings (sawmill, herbalist)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "**Distinct buildings** from `lumber_camp` / `alchemist_workshop`."
|
||
},
|
||
{
|
||
"id": "p1-33",
|
||
"title": "Wire naval/aerial unit gates to the harbor and airfield buildings",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Declarative `requires_building: <id>` field on unit JSONs — simpler path, mirrors `tech_required`, no runtime consumption of building effect types needed."
|
||
},
|
||
{
|
||
"id": "p1-34",
|
||
"title": "Unit metadata expansion — flavor, archetype, promotion_tree, clan_affinity fields",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "The newly authored 50-unit dwarven military roster (p1-34 follow-on from the T1–T10 design pass) currently mashes mechanical role text and lore one-liner into a single `description` field. The schema is missing four high-value metadata fields the rest of the system needs:"
|
||
},
|
||
{
|
||
"id": "p1-35",
|
||
"title": "Per-unit lore paragraphs — historical/cultural context for the dwarven roster",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "The newly authored 50-unit dwarven roster has strong one-liner flavor but no paragraph-length cultural/historical context. Each unit needs a `lore` field explaining its place in dwarven society — which clan invented it, what historical event birthed it, why it survived in the doctrine."
|
||
},
|
||
{
|
||
"id": "p1-36",
|
||
"title": "AI personalities — T1–T10 build order coverage + clan_affinity routing",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/data/ai_personalities.json` currently lists hardcoded early build orders that reference only the old 10-unit roster (warrior / forge / walls / dwarf_founder). The new T1–T10 roster (50 new units) is invisible to all five AI clans — they cannot build a Shield Bearer, a Ballista Crew, a Boar Scout, or any T7+ unit."
|
||
},
|
||
{
|
||
"id": "p1-37",
|
||
"title": "mc-ai clan_affinity routing — Rust AI reads unit clan_affinity at build-decision time",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "p1-36 landed the data side: every unit JSON has a `clan_affinity` array (per p1-34's schema expansion), and `ai_personalities.json` now has tiered build orders (early / mid / late) per clan that respect clan affinity. But the **decision loop that picks units doesn't yet read either field** — it still selects from a flat priority list, so all five clans build similarly-statted armies and the Ironhold-vs-Blackhammer gameplay difference doesn't surface in actual matches."
|
||
},
|
||
{
|
||
"id": "p1-38",
|
||
"title": "Biome → economy coupling — population & luxury driven by live ecology",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Population growth and luxury supply have been decoupled from the live ecology simulation since `mc-flora` was wired up. Cities read static per-terrain food yields (`grassland.food=2`, `plains.food=1`); 70 fauna species exist purely as combat encounters with no contribution to the city economy; the `mc-happiness::get_growth_modifier` tiering (1.25 / 1.00 / 0.50 / 0.00) was computed but unused on the GDScript side. This objective re-couples the city economy to the ecology layer in four phases (C → A → B → D), each sized to land independently with its own balance regression risk."
|
||
},
|
||
{
|
||
"id": "p1-38-followup-shipwright-batch",
|
||
"title": "p1-38 follow-up — Shipwright coupled-mode 10-seed regression batch + sign-off",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Carved out of `p1-38-biome-economy-coupling.md` (closed `done` 2026-05-14 on the basis that all Rust + GDScript implementation work has shipped and only the *operational* batch + JSON flip remain). This follow-up tracks the autoplay-batch sign-off that flips `public/games/age-of-dwarves/data/balance/ecology_yields.json` `fallback_when_dormant: \"static_terrain\" → \"coupled\"` in main."
|
||
},
|
||
{
|
||
"id": "p1-39",
|
||
"title": "Port per-yield difficulty multipliers from GDScript into Rust crates (Rail-1) — research + culture",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-05",
|
||
"blocked_by": [],
|
||
"summary": "During p1-29 Round 3-5, warcouncil added a per-yield difficulty multiplier framework (gold_mult, culture_mult, luxury_mult, research_mult, production_mult, yield_per_turn_growth) plus a symmetric player handicap (Easy = player gets Hard-AI bonuses). Per Rail-1, the multiplier APPLICATION should live in Rust crates, not in GDScript turn_processor.gd / economy.gd / turn_processor_helpers.gd."
|
||
},
|
||
{
|
||
"id": "p1-40",
|
||
"title": "Collapse data/<category>/ override layer into single source of truth at resources/",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-29",
|
||
"blocked_by": [],
|
||
"summary": "Today `public/games/age-of-dwarves/data/{units,buildings,techs}/` mirrors `public/resources/{units,buildings,techs}/` with override semantics: the loader walks resources/ first, then game/data/ overwrites by id. This created three real bugs in the last few sessions: 1. Audit blind-spot: 9 \"missing\" buildings and 6 \"missing\" food/processing buildings turned out to live in resources/buildings bundled files that the per-file audit missed. 2. Silent semantic drift: 14 building IDs are defined in both layers with different cost/tech/effects; the data/ version wins by accident-of-loader-order. 3. Br"
|
||
},
|
||
{
|
||
"id": "p1-41",
|
||
"title": "Game-pack subscription manifest + loader filter (Phase B of resources/ unification)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-29",
|
||
"blocked_by": [],
|
||
"summary": "Phase A (`p1-40`) collapsed the data/ override layer into single source of truth at `public/resources/<category>/`. All 155 unit IDs and 159 building IDs now live in resources/, one file each. The data loader still walks both layers — `resources/` then `data/<category>/` — but the data/ side is now empty for those categories."
|
||
},
|
||
{
|
||
"id": "p1-42",
|
||
"title": "AI must consider the full 155-building catalog, not the hardcoded 8-id ladder",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`mc-ai/tactical/production.rs::ids` hardcodes 8 building/unit IDs (`WARRIOR`, `WORKER`, `FOUNDER`, `WALLS`, `FORGE`, `CASTLE`, `MARKETPLACE`, `GRANARY`) and the priority ladder picks among them. The human player sees all 155 buildings in the city UI (`city_buildable_helper.gd` iterates `DataLoader.get_all_buildings()` and gates by `city.can_build()`); the AI's mental model is a ~5% slice of that catalog."
|
||
},
|
||
{
|
||
"id": "p1-42a",
|
||
"title": "Reconcile capture_scoring.rs ↔ PersonalityPriors — building_priors field location",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "The cycle 5 fix on `p1-42` resolved the `projection.rs:1138` compile blocker by seeding `TacticalPlayerState { building_priors: BuildingPriors::default() }` in the `project_player` constructor. `cargo check -p mc-player-api` and `cargo test -p mc-ai --lib` both come back green."
|
||
},
|
||
{
|
||
"id": "p1-42b",
|
||
"title": "Plumb per-personality building_category_weights + wonder_priorities through mc-turn PlayerState + GDScript bridge",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`p1-42` cycle 5 shipped the catalog-driven AI building scorer (`mc-ai/tactical/production.rs::score_building`) with `RawAxes`-driven multipliers (production/wealth/aggression). The scorer reads `BuildingPriors` off `TacticalPlayerState`, but the bridge does not yet push per-personality JSON weights into `PlayerState` — `project_player` seeds `BuildingPriors::default()` (neutral) so the scorer falls through to axis multipliers."
|
||
},
|
||
{
|
||
"id": "p1-43",
|
||
"title": "Building stacking — per-category upgrade chains (military / science / culture / production / etc.)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Data layer substantially shipped; engine consumption still pending."
|
||
},
|
||
{
|
||
"id": "p1-43b",
|
||
"title": "Deep chain authoring — fill T6/T7/T8/T9/T10 building tiers across the 5 short chains",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-05",
|
||
"blocked_by": [
|
||
"p1-43a (engine + schema + chain-extension proof landed inline in p1-43)"
|
||
],
|
||
"summary": "`p1-43a` (closed inline in `p1-43-building-stacking-upgrade.md` on 2026-05-05) shipped the engine + schema layer and **5 representative new high-tier buildings** as a chain-extension proof:"
|
||
},
|
||
{
|
||
"id": "p1-43c",
|
||
"title": "p1-43 follow-ups — chain ladder authoring, AI stack scoring, city UI upgrade surface, GUT bridge test",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Remaining bullets from p1-43 that require either bulk data authoring (~21 producer building `produces:` field fills across the 14 military + 7 civilian chains) or depend on p1-42 (AI stack scoring) or need GDScript bridge wiring (UI upgrade surface + GUT bridge test)."
|
||
},
|
||
{
|
||
"id": "p1-43c-gdext-upgrade-target",
|
||
"title": "api-gdext bridge — GdBuildingRegistry::get_upgrade_target for city UI upgrade surface",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Expose a Rust→Godot bridge that lets the city UI / encyclopedia display the upgrade successor of any building without forcing GDScript to walk an inverse scan over `DataLoader.get_all_buildings()` (a Rail-3 violation: GDScript would be deriving a join that belongs in the simulation layer)."
|
||
},
|
||
{
|
||
"id": "p1-44",
|
||
"title": "Buildings produce units, not the city center — per-building production queues",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "User direction (2026-04-29): \"it would make sense to build those units in the appropriate buildings rather than the city center.\""
|
||
},
|
||
{
|
||
"id": "p1-44c",
|
||
"title": "p1-44 follow-ups — UI, AI per-building emission, themed roster, GUT, batch",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "p1-44 Phase B (cycle 29) landed the per-building queue split at the engine layer: `City.queues: BTreeMap<String, BuildingQueue>`, `tick_city_production` allocator, and the legacy flat-queue save migration. This objective tracks the remaining acceptance bullets that did not fit the Phase B tightest-scope cut."
|
||
},
|
||
{
|
||
"id": "p1-45",
|
||
"title": "Batch binary freshness: rebuild GDExt before every autoplay batch",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Autoplay batches (`tools/autoplay-batch.sh`) run Godot against the installed GDExtension binary (`engine/addons/magic_civ_physics/libmagic_civ_physics.x86_64.so`). When multiple agents or developers land Rust changes between batches, the `.so` silently goes stale — the GDScript wrappers call methods that don't exist yet in the binary, producing cryptic errors like:"
|
||
},
|
||
{
|
||
"id": "p1-46",
|
||
"title": "Terrain Dimensions Lab — fix ridginess, bind 149 flora species, add Whittaker plot",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "The Terrain Dimensions Lab at `/world-gen/lab` currently classifies the 17 base biomes and renders cross-tile flora/minerals/fauna overlays driven by 4 biome sliders + 3 toggleable overlay layers. Three known gaps:"
|
||
},
|
||
{
|
||
"id": "p1-47",
|
||
"title": "River hydrology — D6 flow analysis, hydraulic erosion, multi-hex lakes, cross-tile rivers",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Water bodies in the current map are per-hex terrain types (`ocean`, `coast`, `lake`, `inland_sea`) with no connectivity. Real geographic water is **topological**: rivers are DAGs from headwaters to ocean, lakes are multi-hex polygons, coastlines are polylines along land/water borders."
|
||
},
|
||
{
|
||
"id": "p1-48",
|
||
"title": "Flora species renderer — bind 149 species to world-map tile rendering (single source of truth)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "`public/resources/ecology/flora/species/*.json` defines **149 species** with rich schema:"
|
||
},
|
||
{
|
||
"id": "p1-49",
|
||
"title": "Fauna species renderer — 61 Game-1 species visible on encounter and lair tiles",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/data/manifests/fauna.json` whitelists **61 Game-1 species** with rich JSON schema in `public/resources/ecology/fauna/species/*.json`:"
|
||
},
|
||
{
|
||
"id": "p1-50",
|
||
"title": "Tectonic prepass — voronoi plates + boundary classification seeding elevation",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "The current `mc-mapgen` elevation field is pure fBm noise. Continents are amorphous, mountain ranges are noise-shaped blobs, and there is no geological reason for a peak to be where it is. Real continents have **plate boundaries**: mountains arc along convergent edges, rifts and mid-ocean ridges along divergent edges, transform faults run linear."
|
||
},
|
||
{
|
||
"id": "p1-51",
|
||
"title": "Worldgen canonical design docs — author the spec before any Rust",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-04-30",
|
||
"blocked_by": [],
|
||
"summary": "The Terraformer bundle (`p1-46`…`p2-51`) is an 8-objective procedural terrain pipeline spanning tectonics, hydrology, climate, ecology, RNG determinism, world-shape presets, and the design-app Terrain Dimensions Lab. Per **Rail 1** (`Rust is the simulation source of truth`, CLAUDE.md:13) and the project's three-tier doc system (canonical → engineering → JSON, per `.project/designs/README.md`), every Rust crate must mechanically implement an authored canonical specification — never the reverse."
|
||
},
|
||
{
|
||
"id": "p1-52",
|
||
"title": "api-wasm build fix — unblock WASM bundle for design-lab WASM consumption",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "**Resolved (2026-05-01).** Workspace `rand` downgraded from `0.9` to `0.8` in `[workspace.dependencies]`. This pulls `rand_core 0.6.x` → `getrandom 0.2.x` (known-good on wasm32 with the `js` feature already present in `api-wasm/Cargo.toml`), breaking the dependency chain that forced `getrandom 0.3.x`. WASM build exits 0; all three tile_*_json exports confirmed in the `.d.ts`. All tests pass."
|
||
},
|
||
{
|
||
"id": "p1-53",
|
||
"title": "Worldgen layer pages — one playground per canonical doc, mirroring the layered Earth model",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "The 7 canonical worldgen design docs at `public/games/age-of-dwarves/docs/terrain/` (plus `ECOLOGY_BINDING.md`) describe the layered Earth model the simulator implements: tectonics → erosion → hydrology → climate → biome → flora → fauna, with RNG and world-shape-presets as cross-cuts. Each doc is a 1-to-1 contract for one Rust crate's behaviour."
|
||
},
|
||
{
|
||
"id": "p1-54",
|
||
"title": "Hex direction-index translation — Rust pointy-top axial vs design-app flat-top canvas",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Two related hex-direction bugs surfaced after p1-53 Stage 2 wired the Hydrology page to live Rust output:"
|
||
},
|
||
{
|
||
"id": "p1-55",
|
||
"title": "Tech & Culture domain field — propagate categorization through Rust, Godot UI, and player analysis",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "The designs app at `.project/designs/app/` now drives `/tech-tree` and `/culture-tree` from real JSON data with a 10-domain categorization (Military / Economy / Industry / Agriculture / Governance / Culture / Science / Exploration / Engineering / Medicine), authored as `tech.domain` directly in `public/resources/techs/*.json`. Culture policies use the `pillar` field as their tab axis."
|
||
},
|
||
{
|
||
"id": "p1-56",
|
||
"title": "Civics buildings, Great Works, Specialists, Great People — wire authored data into Rust + Godot",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-14T20:05Z",
|
||
"blocked_by": [],
|
||
"summary": "A large body of city-management data was authored in the 2026-05-03 design session. The JSON content + designs-app pages exist as the canonical specification. Rust simulator and Godot UI consumers must now be wired to actually run the systems in-game."
|
||
},
|
||
{
|
||
"id": "p1-57",
|
||
"title": "Diplomacy: tribute, treaty lifecycle, magical-terrain episode gating",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "game-systems",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Three diplomacy-related systems were authored in JSON. Rust wire-up pending."
|
||
},
|
||
{
|
||
"id": "p1-58",
|
||
"title": "Ecology cognition: terrain affinity, food web, grudge memory, apex tier-10 fauna/flora",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "A unifying ecology-cognition layer was authored across flora, fauna, and wild-creature units. Adds shared `combat_profile`, `cognitive_profile`, `terrain_affinity`, and `loot_table` fields. Brings the Civ5 \"tier-10 apex predator owns the map\" experience to dwarven gameplay."
|
||
},
|
||
{
|
||
"id": "p1-59",
|
||
"title": "Hybrid merged structures — war_academy, assault_citadel, cavalry_corps, gunnery_corps",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "Priority bumped from P3 to P1 per user directive: hybrid merged structures are eligible for EA. Vertical building ladders ship in `p1-43-building-stacking-upgrade.md`; horizontal merging (this objective) ships here as a separate but EA-bound deliverable."
|
||
},
|
||
{
|
||
"id": "p1-60",
|
||
"title": "Fog-of-war end-to-end test coverage + AI fairness fix",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-18",
|
||
"blocked_by": [],
|
||
"summary": "`p0-13` (tri-state fog) and `p2-70` (Rust `mc-vision` producer) both shipped, but the contract *between* the Rust producer and its consumers — the player-API projection, the GDScript renderer, the save format, and the AI — is not under test. Today:"
|
||
},
|
||
{
|
||
"id": "p1-61",
|
||
"title": "Ecology content gap fill: sparse biomes + lineage tier holes (P1 actions from ecology-audit-gaps.md)",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "game-data",
|
||
"updated_at": "2026-05-19",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/ecology-audit-gaps.md` lists actionable content gaps in the ecology corpus (149 flora + 581 fauna species, all lineage-tagged). The P0 work (schema + tagging) is done; the P1 work — fill sparse biomes and close intra-lineage tier holes — is not. This objective lands the P1 actions only. P2 (enrichment) and P3 (T8-T10 fantasy flora) are explicit non-goals here; they get their own objectives if/when the design team prioritises them."
|
||
},
|
||
{
|
||
"id": "p2-01",
|
||
"title": "Minimap — fog reflection and unit markers",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "The minimap controller was already complete — terrain raster, per-tile fog color, per-player unit/city dots, click-to-emit `EventBus.camera_moved`, viewport-rect indicator. The gap was that nothing mounted it in `world_map.tscn` and no consumer listened for `camera_moved`. This bundle mounts the minimap in a `CanvasLayer` anchored bottom-right of the world map and wires both directions: `minimap.set_camera(bg_camera)` + `camera.set_minimap(minimap)` for viewport-rect + auto-hide at strategic zoom, and `EventBus.camera_moved → _on_minimap_click → cam.center_on(world)`."
|
||
},
|
||
{
|
||
"id": "p2-02",
|
||
"title": "Tooltips on all HUD elements",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Every interactive HUD control now carries a `tooltip_text` resolved through `ThemeVocabulary.lookup(\"tooltip_<key>\")`. The vocabulary file ships a dedicated `tooltip_*` namespace so theme packs can localize hover copy without touching scenes. Stat-row Labels also set `mouse_filter = MOUSE_FILTER_STOP` — Godot otherwise swallows hover on container-managed Labels, so without this tooltips never fire on TurnLabel / EraLabel / Gold / Science / HP / Movement rows."
|
||
},
|
||
{
|
||
"id": "p2-03",
|
||
"title": "Hotkey cheat sheet (F1 / ?)",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Non-modal hotkey cheat-sheet overlay renders **dynamically** from `InputMap.get_actions()`. Every action whose name begins with `ui_` is bucketed into one of four spec-required context columns — **Map / City / Combat / Menus** — via the `ACTION_PREFIX_BUCKET` constant. Adding a new hotkey means one InputMap action declaration in `project.godot` plus one `action_<name>` vocab entry; no changes to `hotkey_sheet.gd`."
|
||
},
|
||
{
|
||
"id": "p2-04",
|
||
"title": "Localization audit — no hardcoded strings",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "`ThemeVocabulary` is architected for localization. This objective audits every player-facing GDScript file (`.gd`) AND Godot scene file (`.tscn`) under `src/game/engine/scenes/` for hardcoded user-visible strings, routes each through `ThemeVocabulary.lookup()`, and wires a validator into `./run verify`."
|
||
},
|
||
{
|
||
"id": "p2-05",
|
||
"title": "Sub-second single-player turn latency",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-23",
|
||
"blocked_by": [],
|
||
"summary": "10-seed parallel batch completes in ~7 minutes wall-clock; single-turn latency on the RUN host is unmeasured. Target: end-of-turn processing ≤1 second on a 512-tile map with 3 AI opponents mid-game."
|
||
},
|
||
{
|
||
"id": "p2-06",
|
||
"title": "Export pipeline for Windows / macOS / Linux",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "Players need binaries. Godot export presets (desktop: Linux/X11, macOS, Windows Desktop) are authored; the `./run export` chain produces per-platform archives via `tools/export.sh` + `tools/export-single.sh`, and the `.forgejo/workflows/release.yml` tag-push pipeline bundles Linux + macOS + Windows + WASM-guide archives into a Forgejo release with release notes generated from the CHANGELOG diff."
|
||
},
|
||
{
|
||
"id": "p2-06b",
|
||
"title": "Cross-compile Windows .exe + .dll from Linux via cargo-xwin (no Windows host)",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "Originally framed as \"register a Windows runner\". Re-scoped 2026-04-25 (user pick) to **Option B: cargo-xwin cross-compile from Linux** — produces MSVC-ABI Windows binaries on the existing Linux runner, no Windows hardware required. Better ABI compatibility than mingw (especially for wgpu's d3d12 backend) and zero hardware cost."
|
||
},
|
||
{
|
||
"id": "p2-07",
|
||
"title": "Credits screen accessible from main menu",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Main menu carries a Credits button between Throne Room and Bug Report rows, routing through `Main.change_scene` into `credits.tscn`. The credits controller reads `public/games/age-of-dwarves/data/credits.json` at runtime and renders one panel per section — engine, Rust crates, fonts, sprite pipeline, contributors, special thanks — inside a ScrollContainer. Back button returns to main menu (also bound to ESC)."
|
||
},
|
||
{
|
||
"id": "p2-08",
|
||
"title": "Accessibility baseline — colorblind palette + keyboard navigation",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All acceptance bullets now verifiable in repo:"
|
||
},
|
||
{
|
||
"id": "p2-09",
|
||
"title": "Player guide web app — builds clean from source",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Shipwright released this objective per user directive. Guide-web surface is out of Shipwright's scope going forward. A separate agent will pick this up; `owner:` reset to unclaimed. Prior work (guide-drift-dev2 scope narrowing + guide-progress-dev progress-report page + Progress Report page) remains in-repo."
|
||
},
|
||
{
|
||
"id": "p2-10",
|
||
"title": "Automated regression CI gate on every push to main",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "This project ships via direct commits to `main` on a self-hosted forge at `http://10.0.0.11:3000/magicciv/magicciv` (Forgejo, port 3000). There is no PR workflow — `git log --oneline` shows zero \"Merge pull request\" commits, no feature branches, no review gate. Forgejo Actions (drone-compatible, files live in `.forgejo/workflows/`) plus a self-hosted apricot runner enforces the test suite on every push to `main`, matching the two-host workflow (CLAUDE.md: EDIT host commits, RUN host executes — apricot already is the RUN host)."
|
||
},
|
||
{
|
||
"id": "p2-10a",
|
||
"title": "CI: gdlint stage un-gated",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "The gdlint stage in `.forgejo/workflows/ci.yml` (Stage 3) currently runs with `continue-on-error: true` due to 7 structural violations that require file-splitting to resolve. This child objective tracks un-gating it so a gdlint failure hard-fails the CI pipeline. The violations are all `max-file-lines` or `max-returns` in large GDScript files (`unit_renderer.gd`, `game_state.gd`, `city.gd`, `auto_play.gd`, `turn_processor.gd`, `ai_turn_bridge.gd`). Split off from p2-10 on 2026-04-25."
|
||
},
|
||
{
|
||
"id": "p2-10b",
|
||
"title": "CI: headless GUT stage un-gated",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "The headless GUT stage in `.forgejo/workflows/ci.yml` (Stage 8) was running with `continue-on-error: true` due to 40 pre-existing test failures. All 40 triaged and resolved. Gate is now hard."
|
||
},
|
||
{
|
||
"id": "p2-10c",
|
||
"title": "Diplomacy: implement _collect_unique_luxury_ids() in happiness.gd",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`happiness.gd` is expected to expose a static helper `_collect_unique_luxury_ids(player, game_map)` that collects traded + tile-based luxury resource IDs into a sorted deduplicated array. Four tests in `test_diplomacy.gd` exercise this contract. The function was never implemented."
|
||
},
|
||
{
|
||
"id": "p2-10d",
|
||
"title": "Data: strip legacy flags/can_found_city/can_build_improvements from unit JSON",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "All unit JSON files under `public/games/age-of-dwarves/data/units/` have legacy fields `flags`, `can_found_city`, and `can_build_improvements` removed (they were superseded by the `keywords` array). The test `test_no_unit_has_legacy_flags_field` was replaced from a `pending()` stub to a real assertion loop that iterates every unit JSON file and verifies none of the three keys are present."
|
||
},
|
||
{
|
||
"id": "p2-10e",
|
||
"title": "Data: resolve duplicate IDs and dangling unlock refs in game data",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`test_data_integrity.gd` had two `pending()` stubs and the data had real dangling refs: 1. **Duplicate IDs**: `public/resources/` is the Games 2/3 master library; `public/games/age-of-dwarves/data/` overrides it for Game 1. DataLoader loads resources first, then game data overwrites — this is intentional. The test was rewritten to check for intra-pack duplicates only (same ID in two files within the same category directory), which is the correct failure mode to guard against. 2. **Dangling unlock refs**: 9 tech unlocks referenced buildings/improvements that don't exist in either source (`grand"
|
||
},
|
||
{
|
||
"id": "p2-10f",
|
||
"title": "SaveManager: fix typed array property assignment on Player/Unit deserialization",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "Four `test_save_manager.gd` tests fail with \"Invalid assignment of property or key 'X' with value of type 'Array' on a base object of type 'RefCounted (Player/Unit)'\". The affected properties are `researched_techs`, `infusions`, and others. Player/Unit declare typed arrays (e.g. `Array[String]`) but the deserializer assigns plain `Array`, causing the runtime type mismatch."
|
||
},
|
||
{
|
||
"id": "p2-10g",
|
||
"title": "CityBridge: add production_cost field to items JSON fixture",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`test_city_bridge.gd:test_happy_path_enqueue_tick_emits_item_crafted` passes. The fixture JSON in the test already contains `production_cost: 30` nested under the `production` block, which is the exact structure the Rust `GdCity::load_items_json` deserializer expects (`ItemDoc` containing `ProductionDoc`). No changes were needed — the objective description was stale."
|
||
},
|
||
{
|
||
"id": "p2-10h",
|
||
"title": "UnitRenderer: implement _build_sprite_key() helper and fix cache key test",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`test_sprite_renderer.gd` tests `_build_sprite_key(type_id, race_id, sex)` on `UnitRenderer` — a helper function that was never implemented. 5 tests use it directly. Additionally, `test_cache_populated_after_miss` fails because the expected cache key format doesn't match the actual `DrawHelpers`-managed cache key."
|
||
},
|
||
{
|
||
"id": "p2-10i",
|
||
"title": "TileTooltip: fix scene node name mismatches and collectibles text formatting",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "`test_tile_tooltip.gd` had three pending tests (not failing) for panel show/hide behavior. Collectibles text tests (1-3) were already passing via the `build_collectibles_text()` static. Panel tests (4-6) needed: instantiate via `load().instantiate()`, guard `GameState` nil access in `tile_info_panel.gd`, and use a valid terrain ID (`plains`) that exists in the tiles JSON."
|
||
},
|
||
{
|
||
"id": "p2-10j",
|
||
"title": "FogOfWar: fix recalculate_vision to not re-reveal already-seen tiles on move",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-26",
|
||
"blocked_by": [],
|
||
"summary": "The two tests were `pending()` placeholders. The production code (`world_map_vision.gd:recalculate_vision()`) is correct — it demotes visible→stale then re-promotes in-range tiles, which is standard fog-of-war behavior with no return value. The fix was implementing real test bodies using the existing `_expand_vision()` helper, which already counts stale→visible transitions correctly."
|
||
},
|
||
{
|
||
"id": "p2-10k-followup",
|
||
"title": "Workflow policy decision: how to clear the 10 max-file-lines gdlint violations",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Parent: `p2-10k-gdlint-cleanup.md` (audited and closed 2026-05-14 — see \"Out of scope\" closing note). Eleven `max-file-lines` violations remain on `gdlint src/game/engine/src/` and are blocked on a workflow-policy decision above the gdlint-cleanup lane."
|
||
},
|
||
{
|
||
"id": "p2-10k",
|
||
"title": "CI: fix 51 gdlint violations so Stage 3 is hard-green",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "As of 2026-05-04, `gdlint src/game/engine/src/` reports 51 violations across 17 files. The CI YAML comment claiming \"Hard-gated 2026-04-25\" was aspirational documentation; the rule was never verified to pass on apricot."
|
||
},
|
||
{
|
||
"id": "p2-10l-followup-gdai-set-map",
|
||
"title": "GdAiController::set_map — wire map into tactical state_json",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-15",
|
||
"blocked_by": [],
|
||
"summary": "Cluster #2 of the p2-10l GUT regression triage (3 failures in `test_ai_turn_bridge_mcts.gd`) is a production contract bug, not a test fixture issue."
|
||
},
|
||
{
|
||
"id": "p2-10l-followup-update-tile-negative-axial",
|
||
"title": "GdAiController::update_tile rejects negative axial coordinates",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-15",
|
||
"blocked_by": [],
|
||
"summary": "After `p1-29c-followup-empty-params-json-regression` was fixed (added missing `GameState.get_effective_yield_mult`), the apricot smoke batch `20260515_192445` reached **2/10 PASS** (vs 0/12 before). Real progress — games now run hundreds of turns instead of crashing on turn 1."
|
||
},
|
||
{
|
||
"id": "p2-10l",
|
||
"title": "CI: fix 15 GUT regressions so Stage 5 is hard-green",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "testwright",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "As of 2026-05-04, `bash tools/gut-headless.sh` reports 15 failing tests (500 total, 477 passing, 8 pending). The CI YAML comment claiming \"Hard-gated 2026-04-26 (p2-10b)\" was aspirational; the stage was never verified clean on apricot."
|
||
},
|
||
{
|
||
"id": "p2-11",
|
||
"title": "Version string + About screen",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "All acceptance bullets now verifiable in repo:"
|
||
},
|
||
{
|
||
"id": "p2-11a",
|
||
"title": "SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "Unit has no serialize()/deserialize() methods — infusions, equipped_items, promo_ids, keywords and other typed arrays cannot round-trip through SaveManager. City.production_queue is a GDScript-side Array with no serialize path; the Rust-backed City.to_json() does not include it. These gaps were deferred from p2-10f, which narrowed its tests to the Player serialize surface only."
|
||
},
|
||
{
|
||
"id": "p2-12",
|
||
"title": "Install weston on apricot RUN host — unblock display-server smoke tests",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "infra",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "Several P0/P1 smoke gates were originally spec'd to require a wayland display server (weston, headless backend with software rendering) on apricot for `RENDER_MODE=weston tools/autoplay-batch.sh`. Weston is now installed (system rpm) and the headless backend launches cleanly."
|
||
},
|
||
{
|
||
"id": "p2-16",
|
||
"title": "Audio assets — in-theme OSS launch pack + source ledger",
|
||
"priority": "p1",
|
||
"status": "in_progress",
|
||
"scope": "game1",
|
||
"owner": "asset-audio",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "The audio capability shipped as **p0-21** — `AudioManager`, manifest, signal wiring, volume sliders all work. The schema + categorical routing extension lands as **p2-33** (this objective is `blockedBy` that). What's missing is the actual `.ogg` files plus the source ledger that proves their licenses are clean."
|
||
},
|
||
{
|
||
"id": "p2-17",
|
||
"title": "Sprite assets — superseded index (split into p2-22 … p2-28)",
|
||
"priority": "p2",
|
||
"status": "superseded",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Split on 2026-04-17 into seven narrower objectives. The original single-file form conflated six disjoint workstreams (pipeline readiness, unit coverage × 2, building coverage, wonder coverage, city-tier coverage, and provenance ledger) and so could never satisfy `.claude/instructions/objective-integrity.md`'s \"every acceptance bullet ✓ with evidence\" gate until all ~220 sprites and the pipeline landed simultaneously. That made progress invisible and review impossible."
|
||
},
|
||
{
|
||
"id": "p2-18",
|
||
"title": "Guide web app — public hosting + deploy pipeline",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "Split out from p2-09 per user directive. Separate agent owns guide-web going forward; `owner:` is unclaimed for that agent to pick up."
|
||
},
|
||
{
|
||
"id": "p2-19",
|
||
"title": "Guide progress report page — dynamic dashboard + missing assets",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "Split out from p2-09 per user directive. Guide-web surface is owned by a separate agent going forward; Shipwright is releasing this. Work here is already shipped — `owner:` reset so the new guide-agent can claim as they see fit."
|
||
},
|
||
{
|
||
"id": "p2-20",
|
||
"title": "Fix simCachePlugin pre-warm worker — tsx can't resolve @magic-civ/physics-rs through pnpm symlink",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-17",
|
||
"blocked_by": [],
|
||
"summary": "- ✓ `src/simulator/runner-stub.mjs` (new, 3 exports) — a Node-resolver-friendly stub inside the `@magic-civ/physics-rs` package root that re-exports from the absolute `.local/build/wasm/magic_civ_physics.js` via a relative `../../` path. Node's ESM resolver follows it cleanly; tsx no longer collapses `..` segments through the pnpm symlink. - ✓ `src/simulator/package.json` gets `\"type\": \"module\"`, `\"main\": \"./runner-stub.mjs\"`, `\"files\": [\"runner-stub.mjs\"]`, and a descriptive `\"description\"` explaining that browser/Vite consumers bypass this stub via the explicit alias. - ✓ **Verified**: `node"
|
||
},
|
||
{
|
||
"id": "p2-21",
|
||
"title": "Bake pre-computed sim-cache frames into the static build",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "**Landed end-to-end for the default scenario (`base_no_magic`).** The five other canonical scenarios are a cost/benefit follow-up rather than a correctness blocker — each is ~1.1 GB and takes ~2.5 min to bake, so all 6 together are ~6.6 GB + ~15 min build. The `deploy:guide:next` pipeline now supports selective baking via `DEPLOY_BAKE_SCENARIOS=...` so the team can opt into the full set later without further plumbing."
|
||
},
|
||
{
|
||
"id": "p2-22",
|
||
"title": "Sprite generation pipeline — runnable end-to-end",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "Gate-one objective for every other `asset-sprite` child (`p2-23` … `p2-27`). Before any sprite can legitimately land in `public/games/age-of-dwarves/assets/sprites/`, the `tools/sprite-generation/` pipeline has to run cleanly end-to-end: scan game data → generate variants via the configured model → auto-rank via Sonnet vision → surface in the Theater GUI for human approval → chroma-key + resize + install with LICENSES.md row written."
|
||
},
|
||
{
|
||
"id": "p2-23",
|
||
"title": "Unit sprites — Dwarf-racial roster (m/f variants)",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Every Dwarf-controlled combat + support unit Game 1 subscribes to (`manifest.json` `subscribes.units`, resolved against `public/resources/units/<id>.json` — the non-wild entries, those with a `gender: { male, female }` block) needs matching PNG sprites in `public/games/age-of-dwarves/assets/sprites/units/` so `UnitRenderer` can overlay the sprite on top of the procedural baseline. (The objective originally cited `data/units/*.json`; unit data has since moved to the shared `resources/` library with a per-game manifest subscription.)"
|
||
},
|
||
{
|
||
"id": "p2-24",
|
||
"title": "Unit sprites — wild creatures & fauna (generic, no race/sex)",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Wild creatures (unit resources with `faction: \"wild\"` / `keywords: [\"wild\"]`, in `public/resources/units/`; the objective originally said `unit_type: \"wild\"` in `data/units/` — both the field and the path have since moved) do not participate in the race×sex permutation. `UnitRenderer` falls back to `SPRITE_LOOKUP_GENERIC_FORMAT = \"sprites/units/%s.png\"` (`unit_renderer.gd:60`) for them — a single sprite per creature id."
|
||
},
|
||
{
|
||
"id": "p2-25",
|
||
"title": "Building sprites — base game coverage (non-wonder)",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Every standard (non-wonder) building Game 1 subscribes to — `manifest.json` `subscribes.buildings`, resolved against `public/resources/buildings/<id>.json`, taking the entries WITHOUT a `wonder_type` field (wonders → `p2-26`) — needs a sprite at `public/games/age-of-dwarves/assets/sprites/buildings/<building_id>.png`. (The objective originally said `data/buildings/*.json`; building data has since moved to the shared `resources/` library with a per-game manifest subscription.) `city_renderer.gd:223` loads this flat, id-keyed path; the city-screen UI consumes the same files."
|
||
},
|
||
{
|
||
"id": "p2-26",
|
||
"title": "Mundane-wonder sprites — 24 distinct, higher-fidelity art",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Game 1's world wonders (Civ5-style, no magic schools — mundane flavour wonders) are the subscribed buildings carrying a `wonder_type` field in `public/resources/buildings/<id>.json`. (The objective originally cited `data/buildings/mundane_wonders.json` with 24 wonders; the data has since moved to the shared `resources/` library and the live count is 62.) Each needs a **distinct, higher-fidelity** sprite — a generic \"fancy building\" look drags the whole wonder system's perceived prestige. Split from `p2-25` so the quality bar can be tracked independently."
|
||
},
|
||
{
|
||
"id": "p2-27",
|
||
"title": "City population-tier sprites — city_q1 through city_q5",
|
||
"priority": "p1",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "`CityRenderer` looks up a city's sprite via `SPRITE_LOOKUP_CITY_FORMAT = \"sprites/cities/city_q%d.png\"` (`city_renderer.gd:29`) with the quality bucket computed as `clampi(city.population / CITY_QUALITY_BUCKET + 1, 1, CITY_QUALITY_MAX)` — five buckets, five PNGs. Tiny scope relative to the other children, but the key format and renderer path are distinct enough to warrant its own objective — can flip `done` early."
|
||
},
|
||
{
|
||
"id": "p2-28",
|
||
"title": "Sprite provenance ledger — LICENSES.md per-file attribution",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-04-25",
|
||
"blocked_by": [],
|
||
"summary": "Every sprite PNG that ships in `public/games/age-of-dwarves/assets/sprites/` must have a corresponding row in `public/games/age-of-dwarves/assets/sprites/LICENSES.md` recording source, license, author, URL, and SHA256. This is a cross-cutting compliance objective that runs continuously alongside the delivery children (`p2-23` … `p2-27`) — the ledger is complete exactly when every on-disk sprite has a matching row and every row points at an on-disk file."
|
||
},
|
||
{
|
||
"id": "p2-29",
|
||
"title": "Welcome modal + HomePage lore + guide theme align to the player's chosen race/gender",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "The guide already exposes a welcome modal that lets the player pick a race (Dwarf in Game 1 — `CONCRETE_RACES = ['dwarf']`) and a gender, plus a `RaceThemeProvider` that merges a per-race/per-gender palette into the styled-components theme. But the three surfaces don't line up:"
|
||
},
|
||
{
|
||
"id": "p2-30",
|
||
"title": "Consolidate duplicate page styled-components into shared PagePrimitives",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "An Explore sweep on 2026-04-18 counted ~180 redundant styled-component declarations across 15 guide pages. Each page declares its own `Card`, `CardHeader`, `CardTitle`, `StatsGrid`, `Stat`, `Badge`, `SectionLabel`, `Subtitle`, etc. that already exist (or could exist) in the shared `src/packages/guide/src/components/ui/PagePrimitives.tsx`. The duplication:"
|
||
},
|
||
{
|
||
"id": "p2-31",
|
||
"title": "Migrate guide filter + tab state from useState to URL search params",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "The Progress Report's Details tab set a precedent: its filter chip state + per-objective modal round-trip through `useSearchParams()` (see `progress-report/ObjectivesTab.tsx` + `ProgressReportPage.tsx`). Bookmarking `?tab=details&filter=partial&objective=p0-01` restores the exact view."
|
||
},
|
||
{
|
||
"id": "p2-32",
|
||
"title": "Replace hardcoded page enums with JSON data reads",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-18",
|
||
"blocked_by": [],
|
||
"summary": "Rail #2 in CLAUDE.md says \"JSON game packs are the canonical content store — neither Rust nor GDScript hardcodes game content.\" Several guide pages still violate the spirit of that rule by hand-typing data arrays in `.tsx` files. When the design changes (\"we decided it's 20 races, not 16\" — or \"Arcana tree got merged into Scholarship\"), the fix has to hop four files in TypeScript rather than editing one JSON."
|
||
},
|
||
{
|
||
"id": "p2-33",
|
||
"title": "Sound system extension — categorical fallback, variant pools, per-entity routing",
|
||
"priority": "p1",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "asset-audio",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "`AudioManager` (`p0-21`, done) ships 10 SFX events and 6 era-keyed music tracks. The current manifest is one stream per id, no variation, no fallback chain, and no story for the 91 units / 65 buildings / 600 fauna species the game ships with — every entity that ever wants a distinct sound has to add a hand-authored entry, which doesn't scale to launch."
|
||
},
|
||
{
|
||
"id": "p2-35",
|
||
"title": "Palace evolution system — longhouse → great_hall → citadel → grand_citadel + function-shedding",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "Landed end-to-end. Working assumptions adopted (no Slack call): tier names `longhouse → great_hall → citadel → grand_citadel`, tech gates `none / governance / masonry / civil_engineering`, function-shedding shipped as the simpler `replaces_when_evolved` mechanic (prior tier removed from city building list when next tier takes over) — the per-function Mason-Lodge-tech shed described in the original RFC remains a separate, larger redesign and is NOT part of v1. `max_workers` shipped as a real populated field per tier (schema sink for p2-56a)."
|
||
},
|
||
{
|
||
"id": "p2-36",
|
||
"title": "Reconcile the 14 building IDs defined in both `resources/buildings/` and `data/buildings/`",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-29",
|
||
"blocked_by": [],
|
||
"summary": "After the `p1-31` per-file split, 14 building IDs are defined in **both** `public/resources/buildings/<id>.json` (engine defaults) and `public/games/age-of-dwarves/data/buildings/<...>.json` (Age-of-Dwarves overrides). The data loader iterates `resources/` first then `data/` and overwrites by id — the data/ definition silently wins. Every duplicate currently differs in `cost`, `tech_required`, or `tier`, so the resources/ side is dead code wherever the override exists."
|
||
},
|
||
{
|
||
"id": "p2-37",
|
||
"title": "React calculator UI — surface flavor, lore, clan_affinity, archetype filter",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "tourguide",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "The combat calculator at `/calculator` and the permutations matrix at `/permutations` (in `.project/designs/app/`) currently show stat blocks but none of the rich metadata p1-34 introduces. The point of writing flavor lines and clan affinity isn't to bury them in JSON — it's to make the design legible in the tool that designers use to balance the roster."
|
||
},
|
||
{
|
||
"id": "p2-38",
|
||
"title": "Unit audio_cues stub strings — selection/move/attack lines for the dwarven roster",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "asset-audio",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "The 50-unit dwarven roster needs in-character audio cue strings — the one-liner that plays when a unit is selected, told to move, or ordered to attack. AoE/Civ/StarCraft conventions: 2–4 lines per cue type, played randomly so repetition doesn't drone."
|
||
},
|
||
{
|
||
"id": "p2-39",
|
||
"title": "Resolve `chronicle_hall` phantom unlock in `chronicle_keeping` culture tech",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-27",
|
||
"blocked_by": [],
|
||
"summary": "The `chronicle_keeping` culture tech (era_4 oral_tradition pillar) declares `unlocks.buildings = [\"chronicle_hall\"]`, but no `chronicle_hall` building file exists anywhere in `public/resources/buildings/` or `public/games/age-of-dwarves/data/buildings/`. Surfaced by the p3-01 cycle 3 audit when the era_4 Rune Scribe culture path tried to extend `chronicle_hall.enables_units` and discovered it was a phantom."
|
||
},
|
||
{
|
||
"id": "p2-43",
|
||
"title": "Culture research live-game pipeline — per-turn GDExt bridge + `culture_researched` emit",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "`EventBus.culture_researched(tradition_id, player_index)` is defined and **every downstream consumer is wired** (AudioManager handler, manifest entry `culture_researched`, the asset shipped at `public/resources/audio/sfx/ui/culture_researched.ogg`). What's missing turned out to be deeper than the original framing of this objective: the **entire per-turn culture-research path doesn't run in the live game**."
|
||
},
|
||
{
|
||
"id": "p2-43a-followup-gdscript-delegation",
|
||
"title": "Shared infra — wire GdAiController into auto_play.gd so Rail-1 bridges can be one-liners",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-15",
|
||
"blocked_by": [],
|
||
"summary": "Rail-1 ports keep landing the Rust bridge (`GdAiController::pick_*`) but cannot collapse the GDScript call site to a one-liner because `auto_play.gd` does not currently instantiate a `GdAiController` — same constraint that holds `_pick_research` inline today. This is a shared infrastructure gap, not the responsibility of any individual port objective."
|
||
},
|
||
{
|
||
"id": "p2-43a",
|
||
"title": "Rail-1 port — `_pick_culture_tradition` → mc-ai::tactical::culture_pick",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Phase A of `p2-43` landed the AI culture-tradition picker as GDScript in `auto_play.gd::_pick_culture_tradition`. This violates Rail-1 (Rust is the simulation source of truth) and is filed here as the explicit port-back follow-up."
|
||
},
|
||
{
|
||
"id": "p2-44",
|
||
"title": "AI promotion selection — auto-pick + emit unit_promoted for AI units",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-06",
|
||
"blocked_by": [],
|
||
"summary": "`EventBus.unit_promoted(unit, promotion_id)` is wired end-to-end on the audio side: the handler in `AudioManager` plays a UI confirmation chime, and `audio.json` ships the `unit_promoted` entry. But the signal is only emitted from one place: `src/game/engine/scenes/combat/promotion_picker.gd:120` — the modal the **human** uses to pick a promotion."
|
||
},
|
||
{
|
||
"id": "p2-44a",
|
||
"title": "DataLoader path mismatch — `get_promotion(\\\"trees\\\")` returns empty",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-06",
|
||
"blocked_by": [],
|
||
"summary": "DataLoader gained a `get_promotion_trees()` helper (commit `99f1049ed` \"add dataloader promotion path entry\"). `_eligible_promotion_ids` in `ai_turn_bridge_state.gd:418` now uses the new helper. Apricot batch validation NOT yet run — specialist died from API SSL error before the launch/status/fetch validation pass. Status: partial. Next step: run a 1-seed apricot smoke via p2-64 protocol and confirm `unit_promoted` events fire; if yes, flip both p2-44a and p2-44 to done."
|
||
},
|
||
{
|
||
"id": "p2-44b",
|
||
"title": "AI promotion dispatch — instrumentation pass to identify the silent gate",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-06",
|
||
"blocked_by": [],
|
||
"summary": "Added null-safety fix landed; rebuilt batch `20260506_131841` showed 0 events again. Synchronous 1-seed with `MC_AI_PROMOTION_DEBUG=1` (`20260506_135018`) revealed the chain actually works end-to-end: - `_eligible_promotion_ids` returns 5 candidates (`shock_i`, `cover_i`, …) - `units_with_choices` populates per-turn (1–3 units typical) - `dispatch_promotion_picked SUCCESS` fires **52 times** per game - `unit.veteran_level` increments from 0→1→2"
|
||
},
|
||
{
|
||
"id": "p2-45",
|
||
"title": "Player elimination reconciliation — emit `player_eliminated` on every transition",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-04-30",
|
||
"blocked_by": [],
|
||
"summary": "`EventBus.player_eliminated(player_index)` fires from exactly one place: `src/game/engine/src/modules/combat/combat_utils.gd:140` — when combat strips a player's last city. Today every elimination path goes through combat (the only way to lose your last city in Game 1 is a captor takes it), so the signal does fire in practice."
|
||
},
|
||
{
|
||
"id": "p2-46",
|
||
"title": "Past-games archive & replay viewer — `mc-replay` crate, on-disk archive, projection-based playback",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1-stretch",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "Persistent local archive of finished games, accessible from the main menu, with three surfaces:"
|
||
},
|
||
{
|
||
"id": "p2-47",
|
||
"title": "In-game statistics screens — Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories)",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1-stretch",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-07 (cycle-51)",
|
||
"blocked_by": [],
|
||
"summary": "Civ-style mid-game statistics modal opened from the HUD info button (or `F9`). Five tabs in one scene, all read-only views over the per-turn `TurnSnapshot` log produced by `mc-replay` (p3-05):"
|
||
},
|
||
{
|
||
"id": "p2-48",
|
||
"title": "End-of-game summary screen — outcome banner, standings, score graph, awards, timeline, footer actions",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1-stretch",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "Full-screen summary triggered when the game ends — by victory condition, last-clan-standing, turn-limit, or player resignation. Replaces the world-map HUD with:"
|
||
},
|
||
{
|
||
"id": "p2-48a",
|
||
"title": "End-of-game summary — GUT tests + headless proof scene",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1-stretch",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-06-04 (bridge-cse lane)",
|
||
"blocked_by": [],
|
||
"summary": "Carry-forward from p2-48 cycle 2. The Rust `compute_awards` function and `end_game_summary.gd` scene skeleton are complete. These two acceptance bullets from p2-48 remain un-met because they require a wired `.tscn`, the GdReplayPlayer bridge, and headless Godot infrastructure that blocks on `p3-05` + `p3-06`."
|
||
},
|
||
{
|
||
"id": "p2-49",
|
||
"title": "Climate axes refactor — latitude + continentality + zonal winds as first-class per-hex inputs",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-04-30",
|
||
"blocked_by": [],
|
||
"summary": "`mc-mapgen::sampleCell` (and the design-lab twin in `terrain.ts:213`) derives `cold` from `abs(row/rows - 0.5) * 2 * (1 - climate)` — an implicit latitude proxy that doesn't expose:"
|
||
},
|
||
{
|
||
"id": "p2-50",
|
||
"title": "Deterministic RNG + seed-derivation pin across mc-mapgen / mc-climate / mc-ecology",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Every terraformer-owned objective claims \"deterministic from seed\", but no objective specifies (a) which RNG, (b) the seed-mixing function, (c) the version pin that survives `cargo update`. Today, the worldgen pipeline calls `rand::thread_rng()` and `StdRng::seed_from_u64(seed)` inconsistently across crates. `StdRng` is explicitly documented as **not stable across rand versions** — saves break silently on dep bumps."
|
||
},
|
||
{
|
||
"id": "p2-51",
|
||
"title": "Player-facing world-shape parameters on new-game screen",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "The terraformer pipeline now exposes ~15 internal parameters (plate count, tectonic strength, fbm octaves, sea level, latitude gradient, continentality decay, rain-shadow factor, erosion iterations, drainage threshold, etc.). Designers tune these in the forest lab; **players see none of them**. The new-game screen ships \"Map Size\" and not much else."
|
||
},
|
||
{
|
||
"id": "p2-52",
|
||
"title": "Split terrain enum into substrate × flora-cover layers (resolve biome ontology)",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "The current `terrain.json` enum (16 IDs) conflates three orthogonal ecological layers into a single field:"
|
||
},
|
||
{
|
||
"id": "p2-53",
|
||
"title": "Action vocabulary — gap analysis between design page and shipped Rust/Godot game",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "The design page at `/unit-actions` (`.project/designs/app/src/pages/UnitActions.tsx`) curates an exemplar per unit/building category and lists per-archetype action vocabularies. Cross-checking that vocabulary against the shipped Rust action registry (`mc-core/src/action.rs::ActionKind`), the JSON capability map (`unit_actions.json`), and the Godot panel (`scenes/hud/unit_panel.gd`) reveals four classes of gap. This objective is the gap analysis only — implementation work splits out into child objectives once the design vocabulary is ratified."
|
||
},
|
||
{
|
||
"id": "p2-53a",
|
||
"title": "Sentry/Guard ActionKind — add Sentry/Unsentry to mc-core with wake-on-vision",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "wireguard",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Gap 1 from p2-53: The design page at `.project/designs/app/src/pages/UnitActions.tsx` proposes Guard (sentry posture, no stat bonus, wakes on enemy entering vision range) as distinct from Fortify (cumulative dig-in, wakes only on adjacency). The shipped game had only `Fortify`/`Unfortify`. Decision: add `ActionKind::Sentry` / `ActionKind::Unsentry` with wake-on-vision predicate in `processor.rs` turn-end phase."
|
||
},
|
||
{
|
||
"id": "p2-53a1",
|
||
"title": "Sentry bridge state pipe — extend `GdUnitActions` signature to pass `is_sentrying`",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "p2-53a shipped the Sentry/Unsentry ActionKind end-to-end EXCEPT the GDExtension bridge: `api-gdext/src/action.rs:58` and `:96` hardcode `is_sentrying: false` with TODO comments. This means the unit panel in Godot cannot show the Unsentry button (it never thinks the unit is sentrying), only the Sentry button, regardless of actual state."
|
||
},
|
||
{
|
||
"id": "p2-53b",
|
||
"title": "Building action registry — `BuildingActionKind`, `building_actions.json`, `GdBuildingActions` bridge",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Buildings have no action registry analogous to the unit pipeline (p1-20). Today, building-level player actions are scattered across `city_screen.gd` (Set Rally button), `world_map.gd` (production-queue clicks), and ad-hoc per-screen controls. There is no `BuildingActionKind` enum, no `building_actions.json` capability map, no `GdBuildingActions` bridge, and the unit panel has no visual analogue for buildings."
|
||
},
|
||
{
|
||
"id": "p2-53c",
|
||
"title": "Rally vocabulary expansion — Hold / Fortify / JoinFormation + two-waypoint Patrol",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "`BuildingRallyPoint.command` (p0-41, `mc-turn/src/game_state.rs`) and `_FORMATION_COMMANDS` (`unit_panel.gd:56`) currently support three orders by name: `defend`, `patrol`, `advance`. The named `\"patrol\"` is a marker string — it does NOT carry the two-waypoint config the unit-side Patrol uses (p1-21 `IssuePatrol`/`EditPatrol`). Treat current rally-Patrol as a stub: `try_spawn_unit` issues a `PatrolOrder` PingPong from spawn-hex to rally-hex, with no way to configure the second waypoint."
|
||
},
|
||
{
|
||
"id": "p2-53d",
|
||
"title": "Building specifics — Garrison, Repair, Toggle Active + 18 archetype-specific actions",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Once `p2-53b` lands the building action registry, this objective fills in the per-archetype handlers. Each follows the 12-touchpoint pipeline (BuildingActionKind variant + handler + JSON keyword + bridge + signal + vocab + tests)."
|
||
},
|
||
{
|
||
"id": "p2-53e",
|
||
"title": "Siege handlers (Pack/Deploy/Bombard) + Pillage UI wiring + Embark/Disembark handlers",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Three Rust `ActionKind` variants (`PackSiege`, `DeploySiege`, `Bombard`) are defined in `mc-core/src/action.rs:32` but explicitly annotated *\"no handler wired\"*. They appear in `unit_actions.json` for the `siege` keyword but `action_handlers.rs::invoke()` returns `Err(WrongTerrain)` for all three. `Embark`/`Disembark` for amphibious units are similarly stubbed. `PillageFriendly` has a handler stub but is not in `unit_panel.gd::_KIND_TO_SIGNAL`, so the button never renders."
|
||
},
|
||
{
|
||
"id": "p2-53f",
|
||
"title": "Infantry specifics — Shield Wall, Brace, Shove, Rage, Cleave, War Cry",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Six new `ActionKind` variants gating on archetype keyword (`infantry_line` for Defender, `infantry_shock` for Berserker). Each is a per-action handler + combat hook."
|
||
},
|
||
{
|
||
"id": "p2-53g",
|
||
"title": "Ranged specifics — Volley, Aimed Shot, Fire Arrows",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Three new `ActionKind` variants gating on `keywords: [\"ranged\"]`:"
|
||
},
|
||
{
|
||
"id": "p2-53h",
|
||
"title": "Cavalry specifics — Charge, Pursue, Wheel",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Three new `ActionKind` variants gating on `keywords: [\"cavalry\"]`:"
|
||
},
|
||
{
|
||
"id": "p2-53i",
|
||
"title": "Support specifics — Engineer, Pioneer, Medic, Scout",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "Largest specifics child (15 actions across 4 archetypes). Most action effects already have system support somewhere in `mc-turn` / `mc-tech` / `mc-ecology` and just need the action surface."
|
||
},
|
||
{
|
||
"id": "p2-54",
|
||
"title": "Resource visibility — three-axis (visibility/yield_gate/improvement_gate) refactor",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-02",
|
||
"blocked_by": [],
|
||
"summary": "Replace the binary `revealed_by_tech` field on resources with three orthogonal axes (`visibility` / `yield_gate` / `improvement_gate`) plus forward-compatible schema extensions for environmental indicator decorations and a per-player observation cache."
|
||
},
|
||
{
|
||
"id": "p2-54a",
|
||
"title": "Migrate deposits/*.json to three-axis visibility schema",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "`public/resources/resources.json` has been migrated to the three-axis schema (`visibility` / `yield_gate` / `improvement_gate`). However, the 30 individual deposit files in `public/resources/deposits/*.json` — which are the actual source read by GDScript (via `DataLoader`) and the guide-web (via Vite `import.meta.glob`) — still use `revealed_by_tech`."
|
||
},
|
||
{
|
||
"id": "p2-54b",
|
||
"title": "Per-player tile observation cache — flora/fauna last-observed state",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Per the user's 2026-05-01 design decision: flora and fauna are NOT omniscient-always-visible. Each player remembers what their scout/unit last saw, not the simulator's current state. Since flora evolves (forests grow/regress, swamps drain) and fauna migrates (populations shift, herds move), the displayed map for a player reflects their **last observation**, not ground truth."
|
||
},
|
||
{
|
||
"id": "p2-54c",
|
||
"title": "Renderer reads observations + indicator decorations for tech-gated resources",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "After the three-axis schema (p2-54) and per-player observation cache (p2-54b) land, the renderer must: 1. Render flora/fauna from the **player's PlayerObservations**, not the simulator's current state 2. Render `indicator_decorations` on tech-gated resource tiles (rust-red iron-oxide soil for iron, malachite stains for copper, etc.) — these are visual cues that exist before the resource's HUD icon is unlocked 3. Render the explicit resource icon when the player's tech satisfies `yield_gate`"
|
||
},
|
||
{
|
||
"id": "p2-54d",
|
||
"title": "AI tech-priority bias from visible-but-gated luxuries + indicator decorations",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-01",
|
||
"blocked_by": [],
|
||
"summary": "Per the user's 2026-05-01 design observation: **AI clans don't research the right techs to unlock luxuries**. With the three-axis schema (p2-54) and observation cache (p2-54b) in place, mc-ai's strategic policy can read each clan's visible-but-yield-gated luxuries + indicator-decorated subsurface resources in their territory and bias tech research toward unlocking them."
|
||
},
|
||
{
|
||
"id": "p2-55",
|
||
"title": "Civilian Capture / Destroy / Ransom",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Game 1 currently treats every unit-vs-unit interaction as a kill: when a 0-attack civilian (worker, founder, dwarf_founder) is hit by a hostile military unit, it dies and the world loses a strategic asset for nothing but XP. This is unsatisfying and removes a whole layer of Civ-style decision-making — the choice between *taking* an enemy's economic engine, *eliminating* it, or *extorting* gold for its return."
|
||
},
|
||
{
|
||
"id": "p2-55a",
|
||
"title": "Engineer (Great Person) capture mechanics",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [
|
||
"p2-55"
|
||
],
|
||
"summary": "Engineers (Great People) have distinct strategic value — multi-turn build actions per p2-53i — and warrant their own balance pass separate from the core civilian capture system (p2-55)."
|
||
},
|
||
{
|
||
"id": "p2-55b",
|
||
"title": "Caravan master capture mechanics",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [
|
||
"p2-55"
|
||
],
|
||
"summary": "Caravan master capture was originally deferred because of unresolved interactions with trade-route severance, gold-in-transit semantics, and post-capture reroute behaviour. Resolved 2026-05-14: persistent caravan trade routes are **post-v10 / g6-02** and not in Game 1's scope. Capture therefore reduces to the same shape as p2-55a (Engineer) — JSON-driven multiplier + the universal Specialist owner-flip + AP-reset path."
|
||
},
|
||
{
|
||
"id": "p2-55c",
|
||
"title": "Freepeople capture mechanics",
|
||
"priority": "p2",
|
||
"status": "oos",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [
|
||
"p2-55"
|
||
],
|
||
"summary": "Originally deferred because freepeople were assumed to have a multi-stage lifecycle (unlanded → settler → integration) needing its own spec before capture rules could compose with it. Resolved 2026-05-14: **the freepeople model that actually shipped in p0-34 does not include that lifecycle**, and freepeople capture is **structurally inapplicable** to the p2-55 mechanism. Closing as `oos` — scope-folded into p0-34."
|
||
},
|
||
{
|
||
"id": "p2-55d",
|
||
"title": "AI ransom accept/refuse hook in mc-turn start-of-turn",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`mc_ai::decide_ransom_response(price, self_gold, &priors) -> RansomDecision` exists as a pure decision function (committed in `aaa24df4c`, mc-ai/src/policy.rs) and is unit-tested. It is *not yet called* from `mc-turn::processor`, so AI players never actually accept or refuse pending ransom offers — captured units sit in their captive_of state until `tick()` ages them out."
|
||
},
|
||
{
|
||
"id": "p2-55e",
|
||
"title": "UnitRansomAccepted / UnitRansomExpired events on TurnResult",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-03",
|
||
"blocked_by": [],
|
||
"summary": "`TurnResult` currently surfaces three capture events (`units_captured`, `ransom_offers_created`, `civilians_destroyed`). Ransom *accept* and *expire* outcomes are not first-class events — they only surface as method-call return dicts from `accept_ransom_offer` / `refuse_ransom_offer` on the bridge, OR as a stealth `UnitCapturedEvent` when an offer ages out via `process_ransom_expiry`."
|
||
},
|
||
{
|
||
"id": "p2-55f",
|
||
"title": "Read ransom_offer_duration_turns from combat_balance.json",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`mc-turn/src/ransom.rs` defines `pub const RANSOM_OFFER_DURATION_TURNS: u32 = 3;` with a `// TODO: read from combat_balance.json` marker. The JSON file already exists at `public/games/age-of-dwarves/data/combat_balance.json` and ships the canonical `ransom_offer_duration_turns: 3` field. This objective replaces the hardcoded constant with the data-driven value to honour the project's \"JSON game packs are canonical content\" rail."
|
||
},
|
||
{
|
||
"id": "p2-56",
|
||
"title": "Worker categories (Sustenance/Construction/Wealth) + 5-tier expertise + Master/Grandmaster auras + idle decay",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "The implementation is substantially shipped and wired — the prior \"all 8 ❌\" frontmatter was stale. Verified green on apricot (RUN host): `cargo test -p mc-core` → **217 passed / 0 failed**; `cargo test -p mc-city` → **256 passed / 0 failed**. Named coverage: `worker::tests::{json_round_trips, ordering_is_stable, round_trip_via_str, unknown_category_rejected, all_constant_covers_every_variant}`; `expertise::tests::{test_xp_promotes_at_threshold, test_xp_promotes_with_overflow_cascade, test_idle_decay_demotes_below_zero, test_idle_decay_clamps_at_novice_floor, test_grandmaster_capped, test_full_"
|
||
},
|
||
{
|
||
"id": "p2-56a",
|
||
"title": "Worker category types — Sustenance / Construction / Wealth taxonomy",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [
|
||
"p2-35"
|
||
],
|
||
"summary": "`p2-35` introduced specialist slots in cities. The current implementation treats every specialist as a flat string id, which prevents per-category aggregations (e.g. \"total Sustenance throughput\", \"Construction-bound bonuses\"). The design in `public/games/age-of-dwarves/docs/cities/SPECIALISTS.md` calls for three orthogonal worker categories — **Sustenance** (food/healing), **Construction** (production/engineering), **Wealth** (commerce/research) — that drive expertise ladders (see `p2-56b`) and aura propagation (`p2-56c`)."
|
||
},
|
||
{
|
||
"id": "p2-56b",
|
||
"title": "Expertise tier progression — 5-tier specialist XP ladder",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "Specialists in `public/games/age-of-dwarves/docs/cities/SPECIALISTS.md` progress through a 5-tier expertise ladder: **Novice → Apprentice → Journeyman → Master → Grandmaster**. Each tier scales the specialist's per-turn yield contribution and unlocks aura behaviour (handled in `p2-56c`). XP is earned per turn proportional to the assigned tile's yield in the specialist's `WorkerCategory`; XP decays each turn the specialist is idle (no slot assignment)."
|
||
},
|
||
{
|
||
"id": "p2-56c",
|
||
"title": "Master / Grandmaster auras — adjacent-slot yield propagation",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [
|
||
"p2-56b"
|
||
],
|
||
"summary": "Per `public/games/age-of-dwarves/docs/cities/SPECIALISTS.md`, specialists at the **Master** and **Grandmaster** tiers project a per-`WorkerCategory` aura onto adjacent slots within the same city, granting a flat additive yield bonus to specialists in those slots. This rewards intentional clustering of high-tier workers and creates non-trivial city-design choices."
|
||
},
|
||
{
|
||
"id": "p2-57",
|
||
"title": "Production-chain typed resources — raw → processed pipelines wired into mc-city",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/cities/PRODUCTION_CHAIN.md` specifies multi-stage pipelines: raw inputs (iron ore, timber, grain) flow through processing buildings (smelter → forge, mill → bakery) into finished goods (steel, bread, weapons), and the finished-good stockpile gates the *quality tier* of units / buildings produced from that city. Today `mc-city::production` tracks a single scalar production yield — no typed inputs, no processing edges, no stockpile coupling to unit quality."
|
||
},
|
||
{
|
||
"id": "p2-57a",
|
||
"title": "Typed resource stockpile — raw vs processed taxonomy",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "The economy currently treats resources as a flat `HashMap<String, i64>` over the GDExt boundary. The design in `public/games/age-of-dwarves/docs/economy/RESOURCES.md` distinguishes **raw** resources (iron_ore, timber, grain) from **processed** resources (steel, planks, bread), and downstream gameplay (`p2-57b` building consume/produce edges) needs to query \"do I have N units of raw iron_ore\" without string-matching."
|
||
},
|
||
{
|
||
"id": "p2-57b",
|
||
"title": "Building consume/produce edges — stockpile coupled to unit quality",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-15",
|
||
"blocked_by": [],
|
||
"summary": "Per `public/games/age-of-dwarves/docs/economy/RESOURCES.md`, buildings declare `consumes: [{ resource, per_turn }]` and `produces: [{ resource, per_turn }]` edges that operate on the typed `ResourceStockpile` (`p2-57a`). When a producer building lacks its consumed inputs, its output unit is downgraded one quality tier (Veteran→Regular→Levy) — coupling the economy to military quality without a parallel quality system."
|
||
},
|
||
{
|
||
"id": "p2-57c-mc-units-quality-consumer",
|
||
"title": "mc-units quality consumer — turn QualityTier into unit stat deltas (gives quality_chain a contract)",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "`p2-57b` shipped the recipe tick + atomic withdrawal + `QualityTier` stamp (`mc-city/src/recipes.rs`: `tick_recipes`, `stamp_unit_quality`, `tick_and_stamp`, `StampedUnit { unit_id, quality }`), 4/5 bullets ✓. Its **last bullet** — authoring `quality_chain: { veteran, regular, levy }` on every producible unit JSON — is **architecturally blocked**, not effort-blocked, as the `game-data` lane found on 2026-06-03 (STOP/REPORT):"
|
||
},
|
||
{
|
||
"id": "p2-58",
|
||
"title": "Ambient encounter rolls per tile moved — fauna_density × ecology_tier",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/ecology-gameplay.md` Layer 1 specifies that any unit moving through wilderness rolls a per-tile encounter chance keyed on `fauna_density × ecology_tier`, with unit-type roll-rate scaling (e.g., scouts trip fewer encounters, large armies trip more). Today combat encounters fire only on lair-adjacency or scripted spawn events — ambient wilderness traversal is risk-free, contradicting the design."
|
||
},
|
||
{
|
||
"id": "p2-58a",
|
||
"title": "TileState fauna fields — fauna_density + fauna_index for AmbientTileCtx",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "Adds `fauna_density: f32` and `fauna_index: Vec<SpeciesId>` to `TileState` in mc-core so `AmbientTileCtx` (mc-ecology) can be populated from the live GameState in the per-tile-moved encounter hook (p2-58b)."
|
||
},
|
||
{
|
||
"id": "p2-58b",
|
||
"title": "Ambient encounter hook — mc-turn::movement calls roll_ambient_encounter per tile step",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "With `TileState.fauna_density` and `TileState.fauna_index` now populated (p2-58a), the per-tile-moved hook in `mc-turn::movement` (or `processor.rs` movement phase) can build `AmbientTileCtx` from the live `GameState` and call `mc_ecology::encounter::roll_ambient_encounter(...)`."
|
||
},
|
||
{
|
||
"id": "p2-59",
|
||
"title": "Pioneer escort mechanic — protection rules vs ambient encounters",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "Once ambient encounter rolls (`p2-58`) trip on every tile moved, unescorted pioneers / settlers become unviable in mid/high ecology-tier wilderness. `public/games/age-of-dwarves/docs/units/SPECIALISTS.md` calls for an escort relationship: a pioneer co-located with (or moving with) a combat-capable escort unit transfers encounter resolution onto the escort, and benefits from a movement-rate or visibility cap from the slowest unit in the stack."
|
||
},
|
||
{
|
||
"id": "p2-60",
|
||
"title": "Weather / observation lens switcher in the Godot HUD",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "godot-ui",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/WEATHER_HISTORY.md` describes a \"lens\" model: the player toggles the world view between `default`, `weather`, `temperature`, `precipitation`, `wind`, `pressure`, `fauna_density`, `flora_density`, etc., each rendering an overlay sourced from the corresponding `tile_meta` field. Today the renderer reads only the base biome tint — no UI affordance exists to switch lens, and the renderer does not consume the weather/observation channels even though `mc-climate` populates them."
|
||
},
|
||
{
|
||
"id": "p2-61",
|
||
"title": "Bind mc-observation gate_bits to player tech state — recording gates per-field",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "`mc-observation` already carries a per-field `gate_bits` mask that determines which `tile_meta` channels a player is *allowed* to record. Today the mask defaults to all-on for every player — every field is recorded regardless of whether the civilization has researched the corresponding tech (cartography, meteorology, geology, ecology). The intent in `WEATHER_HISTORY.md` and `ECOLOGY_BINDING.md` is that, for example, precipitation overlay only populates after the player researches Meteorology; without it, the lens shows \"no data\"."
|
||
},
|
||
{
|
||
"id": "p2-62",
|
||
"title": "Procedural unit/building renderer — alpha-only visual substitute",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "asset-sprite",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "This exists so alpha doesn't block on sprite generation. The full sprite pipeline (`p2-22..p2-27`) authors hand/AI-generated sprites for every unit, building, wonder, and city; that work is non-trivial and currently deferred. This objective ships a **parametric procedural renderer** — deterministic shapes/colors/insignia derived from each entity's id — so the alpha is fully playable visually without blocking on asset production."
|
||
},
|
||
{
|
||
"id": "p2-63",
|
||
"title": "mc-flora generation: migrate biome filter to substrate_climate-aware path",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-13",
|
||
"blocked_by": [],
|
||
"summary": "Authored flora JSON files have migrated from a top-level `biomes: [...]` array to the `substrate_climate` ontology (see `p2-52-substrate-flora-cover-ontology-split.md`). The biome-filter loop in `mc-flora/src/generation.rs` was not updated, so the `AuthoredSpeciesFile.biomes` field is now empty for every authored file and the candidate-pool query returns nothing."
|
||
},
|
||
{
|
||
"id": "p2-64",
|
||
"title": "Apricot async batch protocol — launch / status / fetch decoupling",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "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."
|
||
},
|
||
{
|
||
"id": "p2-65",
|
||
"title": "Extract `GameState` and pending-queue data types into a dedicated `mc-state` crate",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "> **Progress (2026-06-04 Wave 3):** Phase 0a (CombatBalance unify → mc-core) + > Phase 0b (ScoringWeights → mc-core) DONE in prior waves. **Phase 0c DONE this > wave** — the four `mc_ai::tactical` data types that `GameState`/`PlayerState` > carry relocated to `mc_core::tactical_types` (the cycle vector that blocked > `mc-state` from holding `GameState`). Status stays `stub`: none of the 9 > acceptance bullets are met yet (mc-state crate does not exist). Phase 1 > (skeleton) → Phase 3 (move `game_state.rs`) is the next chunk; see the > Wave-3 audit section + Phase plan."
|
||
},
|
||
{
|
||
"id": "p2-66",
|
||
"title": "World-map visual proof scene that actually renders",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-13",
|
||
"blocked_by": [],
|
||
"summary": "`src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn` is the canonical \"boot a real game session and screenshot the world map\" proof scene used by the phase-gate protocol. Today it produces a fully-black 1920×1080 frame even after the prerequisite fixes below, so the proof-screenshot rail (`tools/screenshot.sh` / direct flatpak launch) cannot validate the gameplay surface against `HEX_GEOMETRY.md` and the worldmap-rendering design docs."
|
||
},
|
||
{
|
||
"id": "p2-67",
|
||
"title": "Claude-driven player API — programmatic player + Agent-SDK adapter",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "A Claude Agent SDK process should be able to **play a real game of Magic Civilization vs. the production AI**, taking authentic player-equivalent actions one at a time and reading game state from data — not from screen scraping. Each turn is a sequence of discrete actions (\"open city, queue warrior, close city, move unit, end turn\"), the same flow the human UI exercises."
|
||
},
|
||
{
|
||
"id": "p2-67-followup-legal-actions",
|
||
"title": "PlayerView.legal_actions — populate full per-unit / per-city / empire-level legal-action enumerators",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`PlayerView.legal_actions` is the canonical \"what can this player do right now?\" enumerator on the Claude Player API wire surface. It is the contract that lets a headless agent (Claude, MCTS rollout, scripted test driver) pick actions without re-implementing the legality rules that the GDScript UI already encodes (which buttons are enabled, which queue items are buildable, which hexes are walkable)."
|
||
},
|
||
{
|
||
"id": "p2-67-followup-mcts-tactical-state-impl",
|
||
"title": "TreeState impl for TacticalState — wire real MCTS into the AI decision path",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "After the night's bug-fix pass (Bugs 1-5 closed, simulation fully playable, last-survivor victory firing), the question \"can Claude beat the hardest AI?\" hit a deeper architectural finding:"
|
||
},
|
||
{
|
||
"id": "p2-68",
|
||
"title": "mc-ai headless turn driver — GameState projector/applicator + run_ai_turn",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`p2-67 Phase 10` blocked here: the brief assumed a function `mc_ai::run_ai_turn(state: &mut GameState, player, &TechWeb, &Personalities) -> u32` existed and only needed to be wired in place of the scripted heuristic in `mc-player-api::dispatch::run_scripted_ai_turn`. It does not."
|
||
},
|
||
{
|
||
"id": "p2-69",
|
||
"title": "Port GdMcTreeController to mc-player-api AI driver (DRY consolidation)",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`api-gdext/src/ai.rs` is broken on main: `GdMcTreeController::choose_action{,_with_stats}` reference `mc_turn::snapshot::{McAction, McSnapshot}` and `mc_ai::mcts_tree::{rollout_snapshot, Tree}` — all removed in a prior MCTS refactor when the rollout engine moved into `mc-mcts-service`. The compile error blocks `cargo check --workspace` and is the sole gate on three p2-68 acceptance bullets:"
|
||
},
|
||
{
|
||
"id": "p2-70",
|
||
"title": "mc-vision — per-player tile visibility producer (Rust)",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`p2-67 Phase 12` (real per-tile fog of war in the Claude Player API projection) blocked here. The original spec named `mc_observation::ObservationStore` as the source of visibility; it isn't — that store is per-player climate observation history (temperature/moisture/wind), not tile visibility. The per-player visibility producer currently lives only in GDScript (`src/game/engine/src/modules/vision/Vision.gd`)."
|
||
},
|
||
{
|
||
"id": "p2-71",
|
||
"title": "Bench projector enrichment — make MCTS see a real tactical surface",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`p2-67 Phase 13` blocked partly here. The AI is correctly wired through `project_tactical → run_ai_turn → apply_ai_action` (p2-68 Waves 1+3+4 + p2-69), and Phase 11's `TurnProcessor::step` proves Claude's slot ticks per-turn — but the production AI returns *empty* action chains past turn 0."
|
||
},
|
||
{
|
||
"id": "p2-71b",
|
||
"title": "Militarist starter widening — add a settler/founder unit so FoundCity fires",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [],
|
||
"summary": "`p2-71` (bench projector enrichment) shipped catalog/personality flow end-to-end and proved the AI emits one `EnqueueBuild` per AI slot on turn 1. Turns 2-5 emit zero because:"
|
||
},
|
||
{
|
||
"id": "p2-72",
|
||
"title": "GdPlayerApi → render bridge (visualise the API-held game world)",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-11",
|
||
"blocked_by": [
|
||
"p2-72a"
|
||
],
|
||
"summary": "`p2-67 Phase 13` (Claude-vs-AI demo with screenshots every 5 turns) blocked partly here."
|
||
},
|
||
{
|
||
"id": "p2-72-option-b",
|
||
"title": "Option B render bridge — proof scene rehydrates GDScript from GdPlayerApi each turn",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-12",
|
||
"blocked_by": [],
|
||
"summary": "`p2-72a` (canonical render source) revealed itself as a 10-14+ week architectural project. The user pivoted to Option B for the immediate Phase 13 screenshot deliverable — a proof scene that bypasses the full extraction by rehydrating GDScript-side entities from `GdPlayerApi.dump_state_json` each turn."
|
||
},
|
||
{
|
||
"id": "p2-72a-building-entity-port",
|
||
"title": "Port NPC Building entity (lairs/villages/ruins) into Rust",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "The bridge-cse lane did **not** execute the npc_buildings-array-removal increment. Reason: it is the highest-regression file in the lane (`game_state.gd`, touching the live world map which `project_standin_blockers_worldmap.md` already flags as fragile) and the increment requires the full `build-gdext.sh` + GUT + visual-verification cycle (screenshot tail, operator-gated). It was correctly the first-to-cut item under budget. Precise resume map for the next lane (verified by grep 2026-06-03):"
|
||
},
|
||
{
|
||
"id": "p2-72a",
|
||
"title": "Make `GdGameState` the canonical render source",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-12",
|
||
"blocked_by": [],
|
||
"summary": "**Status flipped `open → blocked`.** Wave 1 audit tripped the spec's named save/load hard-stop. Details in `## Wave 1 audit` below."
|
||
},
|
||
{
|
||
"id": "p2-72a-pre-strip",
|
||
"title": "Strip Game 2/3 magic/ascension/ley fields from Game 1 runtime",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-12",
|
||
"blocked_by": [],
|
||
"summary": "GDScript-side strip executed per Option-A-with-Option-B-inert-storage scope."
|
||
},
|
||
{
|
||
"id": "p2-72a-save-format-migration",
|
||
"title": "Decouple save format from GDScript-class shape",
|
||
"priority": "p2",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-05-12",
|
||
"blocked_by": [
|
||
"p2-72a-gdgamestate-canonical-render-source"
|
||
],
|
||
"summary": "Rust-owned save surface landed. GDScript-side `SaveManager` rewrite + GUT test port **blocked** on Stage 4 (`p2-72a-gdgamestate-canonical-render-source`) — see § Stage 3 wall below."
|
||
},
|
||
{
|
||
"id": "p2-72b-promote-playerstate-cities-to-city",
|
||
"title": "Parallel-field cities synthesis at Godot bridge (Option C)",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "simulator-infra",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "**Status flipped `open` → `blocked` before any code changes.** Option C as specified cannot land as written because the per-instance `GdCity` architecture invalidates the implicit \"small presentation field set\" premise the spec relies on. User decision required between three resolution paths (below) before this objective can proceed."
|
||
},
|
||
{
|
||
"id": "p2-73-ui-theme-token-pipeline",
|
||
"title": "UI theme pipeline — generate ui_theme.tres from design-tokens.json + apply globally",
|
||
"priority": "p2",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "godot-engine",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [],
|
||
"summary": "The project has a real design system (`.project/designs/design-tokens.json` + `UI_DESIGN_SYSTEM.md` + 8 HTML sketches + a React design-gallery app), and the **React guide consumes it cleanly** (`guide/src/theme/fantasy-theme.ts`). The **Godot game does not**:"
|
||
},
|
||
{
|
||
"id": "p2-74-ui-dehardcode-to-tokens",
|
||
"title": "De-hardcode the Godot UI — route 45 scene scripts off raw Color() onto theme/tokens",
|
||
"priority": "p2",
|
||
"status": "stub",
|
||
"scope": "game1",
|
||
"owner": "godot-ui",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [
|
||
"p2-73-ui-theme-token-pipeline"
|
||
],
|
||
"summary": "The Godot scenes hand-roll their visuals instead of inheriting the design system. Measured across `src/game/engine/scenes/**` (excluding tests):"
|
||
},
|
||
{
|
||
"id": "p3-01",
|
||
"title": "Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1-stretch",
|
||
"owner": "envoy",
|
||
"updated_at": "2026-04-29",
|
||
"blocked_by": [],
|
||
"summary": "Game 1 ships diplomacy-lite: peace/war toggle plus a single bilateral luxury↔gold trade action (`mc-trade`). This objective expands the diplomatic surface with two trade options gated on physical infrastructure rather than instant agreement, so information itself becomes a strategic resource that decays with distance and tech:"
|
||
},
|
||
{
|
||
"id": "p3-03",
|
||
"title": "Courier route resolver — real hex pathfinding, per-tier movement, severable infrastructure",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1-stretch",
|
||
"owner": "envoy",
|
||
"updated_at": "2026-04-28",
|
||
"blocked_by": [],
|
||
"summary": "p3-01 cycle 4 landed the **types** for courier-gated diplomacy (`DiplomaticAgreement` enum, `OpenBordersAgreement`, `SharedMapAgreement`, `CourierRoute`, `CourierMapView` trait, `step_shared_map_agreements` driver, six event payloads). It also landed three lifecycle integration tests against a `MockMap` fixture that hard-codes intercept probability."
|
||
},
|
||
{
|
||
"id": "p3-04",
|
||
"title": "Per-hex improvement layer in `mc-core` / `mc-turn` — anchor improvements at (col,row)",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1-stretch",
|
||
"owner": "envoy",
|
||
"updated_at": "2026-04-28",
|
||
"blocked_by": [],
|
||
"summary": "Improvements ship as data files (`public/resources/improvements/*.json`) but the simulation has no per-hex anchor for them. Improvements currently live on `PlayerState.city_improvements: Vec<Vec<String>>` (per-player / per-city, unanchored). The grid's per-tile struct stores terrain only — no improvement slot."
|
||
},
|
||
{
|
||
"id": "p3-05a",
|
||
"title": "Civic state wrapper — typed CivicState added to PlayerState",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "The civic system in `public/games/age-of-dwarves/docs/civics/CIVICS.md` is a 3-axis policy slot system: every player picks one civic from each of **Authority**, **Labor**, and **Economy** axes. Today no such state exists in the simulator. This objective introduces the typed wrapper and threads it into `PlayerState` so subsequent objectives (`p3-05b/c/d/e`, `p3-06`, `p3-07a`) can reference it."
|
||
},
|
||
{
|
||
"id": "p3-05a-gdext-bridge",
|
||
"title": "GDExt bridge for CivicState — GdPlayer::civic query surface",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "warcouncil",
|
||
"updated_at": "2026-06-04",
|
||
"blocked_by": [
|
||
"p3-05a",
|
||
"p3-05e"
|
||
],
|
||
"summary": "`p3-05a` landed the typed `mc_core::CivicState` and wired it into `mc-turn::PlayerState`. The GDExt query surface (`civic(axis: String) -> Dictionary`) was split out of p3-05a because the civic UI consumer doesn't exist until `p3-05e` (modifier propagation) and `p3-07a` (civic UI) are in flight. This objective adds the bridge once a consumer is ready."
|
||
},
|
||
{
|
||
"id": "p3-05b",
|
||
"title": "Authority axis civics catalog",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [
|
||
"p3-05a"
|
||
],
|
||
"summary": "The Authority axis in `public/games/age-of-dwarves/docs/civics/CIVICS.md` covers civics governing the relationship between rulers and governed: **Tribal Council**, **Clan Hold**, **Hereditary Crown**, **Dwarven Republic**, **Engineer-Magnates**. Each carries unique modifiers (happiness, war-weariness, golden-age trigger amplifiers, taxation ceilings). This objective authors the JSON catalog files; modifier propagation is `p3-05e`."
|
||
},
|
||
{
|
||
"id": "p3-05c",
|
||
"title": "Labor axis civics catalog",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [
|
||
"p3-05a"
|
||
],
|
||
"summary": "The Labor axis in `public/games/age-of-dwarves/docs/civics/CIVICS.md` covers how work is organised: **Kin-Bond Labor**, **Indentured Crews**, **Guild Apprenticeship**, **Free Tradesmen**, **Automated Forges**. Each carries modifiers to specialist throughput, building cost, worker XP rate, and so on."
|
||
},
|
||
{
|
||
"id": "p3-05d",
|
||
"title": "Economy axis civics catalog",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [
|
||
"p3-05a"
|
||
],
|
||
"summary": "The Economy axis in `public/games/age-of-dwarves/docs/civics/CIVICS.md` covers the wealth-distribution model: **Communal Stores**, **Tribute Economy**, **Mercantile Markets**, **Industrial Capitalism**, **Planned Economy**. Each axis choice modulates inequality (`p3-07a`), trade yields, and stockpile turnover."
|
||
},
|
||
{
|
||
"id": "p3-05e",
|
||
"title": "Civic modifier propagation — apply civic effects to per-city yields",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [],
|
||
"summary": "With the three axis catalogs (`p3-05b/c/d`) and `CivicState` (`p3-05a`) in place, this objective wires civic modifiers into the per-city yield computation in `mc-economy` and `mc-city`. Modifier semantics are documented in `public/games/age-of-dwarves/docs/civics/CIVICS.md` — additive vs multiplicative, per-yield vs global, application order."
|
||
},
|
||
{
|
||
"id": "p3-06",
|
||
"title": "Civic anarchy — 5-turn anarchy on axis switch",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-14",
|
||
"blocked_by": [],
|
||
"summary": "Per `public/games/age-of-dwarves/docs/civics/CIVICS.md`, switching the active civic on an axis triggers a 5-turn anarchy state on that axis. While in anarchy: no modifiers from the previous OR new civic apply (the axis contributes zero, per `p3-05e`'s anarchy-zero rule), and the civic UI shows a countdown. After 5 turns the new civic activates."
|
||
},
|
||
{
|
||
"id": "p3-07a",
|
||
"title": "CV-of-wealth + Authority amplifier → inequality stat",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [
|
||
"p3-05b"
|
||
],
|
||
"summary": "`public/games/age-of-dwarves/docs/economy/CAPITALISM_CASCADE.md` defines a derived `inequality` stat per realm: the coefficient of variation (CV = stddev / mean) of city wealth, multiplied by the Authority-axis amplifier (different civics scale inequality differently). This stat then feeds the four damage channels in `p3-07b`."
|
||
},
|
||
{
|
||
"id": "p3-07b",
|
||
"title": "Four damage channels — Land/Water/Magic/Air emission from inequality",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [
|
||
"p3-07a"
|
||
],
|
||
"summary": "Per `public/games/age-of-dwarves/docs/economy/CAPITALISM_CASCADE.md`, the realm's `inequality` stat (`p3-07a`) emits damage proportionally across four named channels: **Land** (soil degradation, deforestation), **Water** (river pollution, aquifer drawdown), **Magic** (mana well exhaustion — note: Game-1 stub channel only, no live magic system), **Air** (smog, climate drift)."
|
||
},
|
||
{
|
||
"id": "p3-10a",
|
||
"title": "Lair assault mode — enter-and-clear",
|
||
"priority": "p3",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-06-03",
|
||
"blocked_by": [
|
||
"p0-17"
|
||
],
|
||
"summary": "`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`."
|
||
},
|
||
{
|
||
"id": "p3-10b",
|
||
"title": "Lair siege mode — multi-turn pressure from adjacent",
|
||
"priority": "p3",
|
||
"status": "partial",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-05",
|
||
"blocked_by": [
|
||
"p3-10a"
|
||
],
|
||
"summary": "Promoted K=0→4 of 5. The siege mechanic is now **LIVE in the turn loop** (`TurnProcessor::process_lair_sieges`, Phase 5d in `step`), driven by a data-driven `LairSiegeConfig` loaded from `lair_combat_modes.json` (per-tier resistance + tuning, Rail 2). Pressure accumulates under besieger adjacency, decays unattended, surrenders + drops loot at threshold, and round-trips through save/load (verified). 4 named acceptance tests + 3 config-loader tests + 5 lib + 4 integration green; full mc-turn 240 lib, mc-combat 143 lib, serde_roundtrip 6/6; `cargo check --workspace` exit 0 (apricot 2026-06-04). F"
|
||
},
|
||
{
|
||
"id": "p3-10c",
|
||
"title": "Lair raid mode — grab-and-exit",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "combat-dev",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [
|
||
"p3-10a"
|
||
],
|
||
"summary": "Per `public/games/age-of-dwarves/docs/combat/LAIRS.md`, Raid mode is a single-turn opportunistic strike: the attacker enters, takes a small loot fraction, and attempts to disengage on the same turn. Higher chance of partial success but lower total reward; lair remains active and may pursue."
|
||
},
|
||
{
|
||
"id": "p3-11",
|
||
"title": "Pioneer & Engineer action-point pool",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-13",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/units/SPECIALISTS.md` proposes an action-point pool for Pioneer and Engineer instead of a single per-turn action. Tier-scaled capacity — 6 (T1), 10 (T2), 14 (T3) — drives:"
|
||
},
|
||
{
|
||
"id": "p3-12",
|
||
"title": "Fauna combat stat derivation — regenerate from traits",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "terraformer",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/FAUNA_COMBAT_STATS.md` documents the formulas for deriving fauna combat stats (HP, attack, defense, armor_type, attack_type, movement) from intrinsic species traits (size_*, diet_*, locomotion_*, social_*) and `ecology_tier`. Today the stat fields in `public/resources/ecology/fauna/species/*.json` are hand-authored and drift from the formulas. This objective makes those fields **derived** — regenerated from traits by a single tool, marked as derived in JSON, and gated by a pre-commit check so they cannot drift again."
|
||
},
|
||
{
|
||
"id": "p3-13a",
|
||
"title": "Extend meteorological events — drought, flood, dust_storm",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-04",
|
||
"blocked_by": [
|
||
"p0-36"
|
||
],
|
||
"summary": "`p0-36` shipped the baseline `mc-climate::derive_weather_events` returning rain/snow/wind events from the per-turn weather sample. The design in `public/games/age-of-dwarves/docs/terrain/CLIMATE.md` calls for a wider event vocabulary; this objective extends the meteorological set with **drought** (sustained low precipitation over N turns), **flood** (precipitation spike on saturated tile), and **dust_storm** (high wind on arid tile)."
|
||
},
|
||
{
|
||
"id": "p3-13b",
|
||
"title": "Geological events — earthquake, volcanic_eruption, landslide",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "shipwright",
|
||
"updated_at": "2026-05-13",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/terrain/TECTONICS.md` provides per-tile `mountain_proximity` and plate-boundary classification. This objective wires those into a `mc-tectonics::derive_geological_events(turn, world) -> Vec<GeologicalEvent>` step, emitting **earthquake** (boundary-adjacent low-prob roll), **volcanic_eruption** (active-volcano tiles), **landslide** (high-slope + saturated, joins flood from `p3-13a`)."
|
||
},
|
||
{
|
||
"id": "p3-13c",
|
||
"title": "Biological events — plague, bloom, migration_pulse",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-13",
|
||
"blocked_by": [],
|
||
"summary": "`public/games/age-of-dwarves/docs/ECOLOGY_BINDING.md` describes biological dynamics riding on the flora/fauna indexes. This objective adds three turn-derived biological events: **plague** (pop-density × low-medical), **bloom** (favourable climate window → flora yield spike), **migration_pulse** (fauna density wave through corridors)."
|
||
},
|
||
{
|
||
"id": "p3-13d",
|
||
"title": "Anomalous events — aurora, fog_bank, thermal_anomaly",
|
||
"priority": "p3",
|
||
"status": "done",
|
||
"scope": "game1",
|
||
"owner": "unassigned",
|
||
"updated_at": "2026-05-07",
|
||
"blocked_by": [],
|
||
"summary": "The fourth event family in `public/games/age-of-dwarves/docs/terrain/CLIMATE.md` covers anomalous/atmospheric phenomena that surface as flavour and observation hooks (per `WEATHER_HISTORY.md`'s anomaly lens) without significant gameplay damage in Game 1: **aurora** (high-latitude visual), **fog_bank** (visibility reduction over N turns on humid tiles), **thermal_anomaly** (statistical outlier flagged by the anomaly lens)."
|
||
}
|
||
],
|
||
"blocked": [
|
||
{
|
||
"id": "g2-11",
|
||
"blockedBy": [
|
||
"g2-12"
|
||
]
|
||
},
|
||
{
|
||
"id": "p1-29g",
|
||
"blockedBy": [
|
||
"p1-29f"
|
||
]
|
||
},
|
||
{
|
||
"id": "p1-43b",
|
||
"blockedBy": [
|
||
"p1-43a (engine + schema + chain-extension proof landed inline in p1-43)"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-55a",
|
||
"blockedBy": [
|
||
"p2-55"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-55b",
|
||
"blockedBy": [
|
||
"p2-55"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-55c",
|
||
"blockedBy": [
|
||
"p2-55"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-56a",
|
||
"blockedBy": [
|
||
"p2-35"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-56c",
|
||
"blockedBy": [
|
||
"p2-56b"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-72",
|
||
"blockedBy": [
|
||
"p2-72a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-72a-save-format-migration",
|
||
"blockedBy": [
|
||
"p2-72a-gdgamestate-canonical-render-source"
|
||
]
|
||
},
|
||
{
|
||
"id": "p2-74-ui-dehardcode-to-tokens",
|
||
"blockedBy": [
|
||
"p2-73-ui-theme-token-pipeline"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-05a-gdext-bridge",
|
||
"blockedBy": [
|
||
"p3-05a",
|
||
"p3-05e"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-05b",
|
||
"blockedBy": [
|
||
"p3-05a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-05c",
|
||
"blockedBy": [
|
||
"p3-05a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-05d",
|
||
"blockedBy": [
|
||
"p3-05a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-07a",
|
||
"blockedBy": [
|
||
"p3-05b"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-07b",
|
||
"blockedBy": [
|
||
"p3-07a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-10a",
|
||
"blockedBy": [
|
||
"p0-17"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-10b",
|
||
"blockedBy": [
|
||
"p3-10a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-10c",
|
||
"blockedBy": [
|
||
"p3-10a"
|
||
]
|
||
},
|
||
{
|
||
"id": "p3-13a",
|
||
"blockedBy": [
|
||
"p0-36"
|
||
]
|
||
}
|
||
],
|
||
"remaining_by_lead": [
|
||
{
|
||
"owner": "simulator-infra",
|
||
"remaining": 7
|
||
},
|
||
{
|
||
"owner": "warcouncil",
|
||
"remaining": 7
|
||
},
|
||
{
|
||
"owner": "asset-sprite",
|
||
"remaining": 6
|
||
},
|
||
{
|
||
"owner": "shipwright",
|
||
"remaining": 4
|
||
},
|
||
{
|
||
"owner": "unassigned",
|
||
"remaining": 4
|
||
},
|
||
{
|
||
"owner": "asset-audio",
|
||
"remaining": 1
|
||
},
|
||
{
|
||
"owner": "godot-ui",
|
||
"remaining": 1
|
||
},
|
||
{
|
||
"owner": "testwright",
|
||
"remaining": 1
|
||
}
|
||
]
|
||
}
|