diff --git a/.project/objectives/p2-36-data-resources-building-duplicates.md b/.project/objectives/p2-36-data-resources-building-duplicates.md new file mode 100644 index 00000000..3538a0a4 --- /dev/null +++ b/.project/objectives/p2-36-data-resources-building-duplicates.md @@ -0,0 +1,58 @@ +--- +id: p2-36 +title: Reconcile the 14 building IDs defined in both `resources/buildings/` and `data/buildings/` +priority: p2 +status: partial +scope: game1 +updated_at: 2026-04-27 +evidence: + - public/resources/buildings/ (6 wonder per-file dupes deleted: clan_moot_stone, covenant_stone, grand_observatory, hall_of_ancestors, voice_of_ages, world_pillar) + - public/games/age-of-dwarves/data/buildings/mundane_wonders.json (canonical wonder ladder, unchanged) + - public/games/age-of-dwarves/data/buildings/manifest.json (regenerated: 102 IDs, was 108) +--- + +## Summary + +After the `p1-31` per-file split, 14 building IDs are defined in **both** `public/resources/buildings/.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 | resources/ definition | data/ definition (wins) | Drift | +|---|---|---|---| +| `barracks` | cost=80, tech=military_doctrine, tier=1 | cost=50, tech=null, tier=1 | tech gate dropped | +| `forge` | cost=100, tech=smelting, tier=2 | cost=60, tech=null, tier=1 | tech gate dropped, tier dropped | +| `granary` | cost=60, tech=husbandry, tier=1 | cost=30, tech=null, tier=1 (in stub.json) | tech gate dropped | +| `library` | cost=75, tech=scholarship, tier=1 | cost=60, tech=null, tier=1 | tech gate dropped | +| `monument` | cost=40, tech=null, tier=1 | cost=30, tech=null, tier=1 | cost only | +| `siege_workshop` | cost=120, tech=mathematics, tier=2 | cost=80, tech=siege_craft, tier=2 | different tech | +| `temple` | cost=90, tech=ancestor_rites, tier=2 | cost=80, tech=null, tier=2 | tech gate dropped | +| `walls` | cost=75, tech=masonry, tier=1 | cost=70, tech=null, tier=1 | tech gate dropped | +| `clan_moot_stone` (wonder) | cost=80, tier=1 | cost=180, tier=2 (in mundane_wonders.json) | wonder cost/tier diverged | +| `covenant_stone` (wonder) | cost=355, tier=4 | cost=600, tier=6 | wonder cost/tier diverged | +| `grand_observatory` (wonder) | cost=220, tech=astronomy, tier=5 | cost=600, tech=astronomy, tier=6 | wonder cost/tier diverged | +| `hall_of_ancestors` (wonder) | cost=360, tier=4 | cost=260, tier=3 | wonder cost/tier diverged | +| `voice_of_ages` (wonder) | cost=780, tier=10 | cost=1200, tier=10 | wonder cost diverged | +| `world_pillar` (wonder) | cost=540, tier=7 | cost=1040, tech=world_theory, tier=9 | wonder cost/tech/tier diverged | + +The pattern is clear: +- **Ordinary buildings (8)**: `data/` versions are cheaper and drop the tech gate. Likely a deliberate Age-of-Dwarves "always-buildable starter" simplification. +- **Wonders (6)**: `data/buildings/mundane_wonders.json` versions are heavier-cost / higher-tier, suggesting `mundane_wonders.json` is the actual Game 1 wonder ladder and the per-file `resources/` versions are stale legacy entries. + +## Acceptance + +- ✓ Wonder duplicates resolved (6 of 14): `mundane_wonders.json` chosen as canonical per p0-04 evidence (`PlayerState.wonders_built` and `WonderId` keys come from this file). The per-file `resources/buildings/{clan_moot_stone,covenant_stone,grand_observatory,hall_of_ancestors,voice_of_ages,world_pillar}.json` deleted as dead overrides — confirmed zero source-code references to the per-file paths (only string-id lookups exist, and the IDs still resolve via `mundane_wonders.json`). +- ✗ Ordinary-building duplicates remain (8 of 14): `barracks`, `forge`, `granary`, `library`, `monument`, `siege_workshop`, `temple`, `walls`. For each, the data/ version drops the tech gate and lowers cost vs the resources/ version. Needs design call: are the data/ overrides intentional Game-1 simplification (delete resources/), or stale drift (delete data/ overrides and accept the deeper tech-gated economy)? +- ✗ All 14 duplicates removed: post-fix audit shows zero IDs defined in both layers. (8 remaining.) +- ✓ For wonders specifically: bundled `data/buildings/mundane_wonders.json` pattern won — it's the canonical wonder ladder per p0-04. Per-file resources/ wonders deleted. (Note: this leaves the wonder system on a different convention from p1-31's per-file building pattern, by design — wonders are a coherent ladder edited as a unit.) +- ✗ Game 1 balance untouched unless explicitly intended: post-wonder-cleanup balance is unchanged because the data/ side already won at runtime. The 8 remaining ordinary-building dupes will need balance review when reconciled. +- ✓ `python3 tools/validate-game-data.py` passes 0 failures post-wonder-delete: PASSED 317 / FAILED 0. +- ✗ A 10-seed `tools/autoplay-batch.sh 10 300` regression batch shows no mass behaviour shift after the remaining 8 ordinary-building dupes are reconciled. +- ✗ `data/buildings/stub.json` audit: defines `granary` as a "compatibility stub" with cost=30 (vs `resources/buildings/granary.json` cost=60 with `tech=husbandry`). Decide if stub.json belongs at all post-reconciliation — it likely becomes redundant once `granary` is reconciled. + +## Progress note (2026-04-27) + +Wonder phase completed autonomously: all 6 wonder duplicates collapsed onto `mundane_wonders.json`. The remaining 8 ordinary-building duplicates each represent a design call (sparse Game-1-friendly override vs richer engine-default with tech gate) and are deferred until that call is made. + +## Out of scope + +- Authoring new buildings (covered by `p1-32` and other authoring objectives). +- Mechanic changes to wonders (e.g. wonder slot limits) — value-only reconciliation here. +- The `data/units/stub.json` parallel issue (founder compatibility id) — file as a separate audit if it shows similar drift. diff --git a/.project/objectives/p2-38-unit-audio-cues-stubs.md b/.project/objectives/p2-38-unit-audio-cues-stubs.md new file mode 100644 index 00000000..ff49dbea --- /dev/null +++ b/.project/objectives/p2-38-unit-audio-cues-stubs.md @@ -0,0 +1,77 @@ +--- +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 +assigned_by: shipwright +blockedBy: [p1-34] +evidence: + - "56 unit JSONs in public/games/age-of-dwarves/data/units/ have audio_cues field with select (3 lines), move (2), attack (2), death (1) string arrays. Verified: `grep -c '\"audio_cues\":' *.json | grep -c ':1$'` = 56." + - "Voice differentiation per archetype confirmed via QA samples: Berserker shouts single-word rage ('BLOOD!', 'AXE-WORK!'); Shield Bearer terse declaratives ('Hold.', 'We become the place.'); Marksman runic references ('The runes agree with me.', 'THEY AGREE!'); War Ram contract metaphor ('Horns are the contract.', 'CONTRACT HONORED!'); Mountain King ancestral weight ('Ten thousand names on this plate.', 'FOR THE TEN THOUSAND!')" + - "All 56 files: valid JSON, no intra-unit duplicate lines, lines 2-8 words each" + - "No magic / Game 2/3 lore leaks. Dwarven idiom maintained across all units." + - "p1-35 (lore) + p2-36 (audio_cues) ran in parallel against the same 56 files using targeted Edits with unique anchors — no conflicts surfaced." + - "React build verification: pnpm --prefix .project/designs/app run build exits 0 (164 modules, 464KB bundle)" +remaining_work: + - "Actual .ogg audio file generation remains in asset-audio team's p2-16 audio pack pipeline. This objective shipped string content only; voice acting / TTS-generation downstream." +--- +## 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. + +This objective lands the **string content** only. Voice acting and audio +file generation are downstream (asset-audio team's existing p2-16 audio +pack work). The `audio_cues` field unblocks the audio team to know what +lines to record / TTS-generate. + +Each unit gets: +```json +"audio_cues": { + "select": ["...", "...", "..."], + "move": ["...", "..."], + "attack": ["...", "..."], + "death": ["..."] +} +``` + +Lines should reflect the unit's identity: +- Berserker: "BLOOD!" / "I do not need a shield." +- Mountain King: "The crown stands." / "Speak the names." (referring to + ten thousand clan-name engravings) +- EMP Trooper: "What runs on lightning..." / "Quietly." +- Shield Bearer: "Hold." / "We become the place." / "One step. One step." + +## Acceptance criteria + +- [ ] All 50 newly-authored dwarven military units have `audio_cues` + field with select/move/attack/death arrays +- [ ] Each `select` array has 3 lines, `move` has 2, `attack` has 2, + `death` has 1 +- [ ] Lines are 2–8 words each (snappy enough for combat UI) +- [ ] Voice matches established dwarven flavor (terse, declarative, + mountain/iron imagery) +- [ ] Unit-specific identity surfaces (a Berserker doesn't sound like a + Shield Bearer) +- [ ] No lines reference Game 2/3 lore (no magic, no spells) +- [ ] Apex units (mountain_king, doomsoul, ancestral_walker, etc.) get + weightier lines than T1 units + +## Out of scope + +- Actual `.ogg` audio file generation (asset-audio's p2-16 / p2-33 work) +- Voice actor casting / TTS personality picks +- Wild creature audio cues (separate objective) +- Existing 25-unit roster (warrior etc.) — their audio cues already + shipped via p0-21 / p2-33 + +## Notes for implementing agent + +The `audio_cues` field is additive and JSON-safe (string arrays). +Reference how the tech-file flavor lines are written +(`data/techs/dwarven_warfare.json`) for voice consistency. diff --git a/.project/objectives/p2-39-chronicle-hall-phantom-unlock.md b/.project/objectives/p2-39-chronicle-hall-phantom-unlock.md new file mode 100644 index 00000000..398c8ca6 --- /dev/null +++ b/.project/objectives/p2-39-chronicle-hall-phantom-unlock.md @@ -0,0 +1,58 @@ +--- +id: p2-39 +title: Resolve `chronicle_hall` phantom unlock in `chronicle_keeping` culture tech +priority: p2 +status: done +scope: game1 +updated_at: 2026-04-27 +evidence: + - public/resources/culture/oral_tradition.json (line 96 — `chronicle_keeping.unlocks.buildings = ["chronicle_hall"]`) + - public/games/age-of-dwarves/data/buildings/mundane_wonders.json (line 102–115 — `bardic_circle`, the actual `chronicle_keeping`-gated wonder) + - .project/objectives/p3-01-courier-diplomacy.md (cycle 3 audit that surfaced the phantom) +--- + +## 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. + +The actual building gated on `culture_required: "chronicle_keeping"` is +`bardic_circle` (mundane_wonders.json:102 — tier 4, era 2 wonder). The phantom +is a stale unlock string left over from an earlier design. + +This is a pre-existing data integrity bug, separate from p3-01's courier scope. + +## Resolution options + +**Option A — point the unlock at the real building:** rewrite +`oral_tradition.json:96` from `"chronicle_hall"` to `"bardic_circle"`. Smallest +diff. Truthful given current data. + +**Option B — author a real `chronicle_hall` building:** an era_4 normal (non-wonder) +culture-tech-unlocked institution. Distinct from `bardic_circle` (wonder, tier 4). +Bigger diff. Adds a new strategic institution and unblocks the era_4 Rune Scribe +culture path that p3-01 abandoned. + +**Default to Option A** unless explicitly requested otherwise. Option B becomes +viable only if the design wants culture-side courier production at era_4, which +is currently out of scope. + +## Acceptance criteria + +- [✓] Decide A vs B (default A). Option A chosen. +- [✓] If A: edit `public/resources/culture/oral_tradition.json:96` `"chronicle_hall"` → `"bardic_circle"`. Verify no other reference to `chronicle_hall` remains in the data tree (`grep -r chronicle_hall public/`). — Only hit in dist/guide bundle (compiled artifact, not source data). +- [✓] `python3 tools/validate-game-data.py` → 0 failed. (317 passed, 0 failed) +- [✓] Run dashboard regen. + +## Non-goals + +- Authoring or rebalancing the era_4 Rune Scribe culture path (separate scope inside p3-01 if pursued later). +- Audit of other phantom unlocks across the culture trees (out of scope; address one at a time as discovered). + +## Dependencies + +- None. Pure data integrity fix.