From 4a994eb22c73d4d8ba6d428ce9435664274d9e44 Mon Sep 17 00:00:00 2001 From: Natalie Date: Tue, 5 May 2026 11:10:06 -0400 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=A8=20finalize=20stacking=20objective=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../p1-43-building-stacking-upgrade.md | 102 ++++++++++++++++++ .../objectives/p1-43b-deep-chain-authoring.md | 101 +++++++++++++++++ .../age-of-dwarves/docs/BUILDING_SCHEMA.md | 46 ++++++++ 3 files changed, 249 insertions(+) create mode 100644 .project/objectives/p1-43b-deep-chain-authoring.md diff --git a/.project/objectives/p1-43-building-stacking-upgrade.md b/.project/objectives/p1-43-building-stacking-upgrade.md index bb1656d9..78b7ecaf 100644 --- a/.project/objectives/p1-43-building-stacking-upgrade.md +++ b/.project/objectives/p1-43-building-stacking-upgrade.md @@ -229,3 +229,105 @@ Wonders override to `amplify` regardless of category. - ✓ Verification: all 184 building JSONs deserialize via `mc-city::BuildingDef` (test `test_all_authored_buildings_deserialize`, `cargo test -p mc-city` passes). Bullets remaining: 6 (data ladder authoring, AI catalog scoring, GDScript bridge wiring + UI surface, GUT bridge test, validator cross-ref). Engine + schema reconciliation closed. + +## p1-43a close-out (2026-05-05) + +All six locked design questions implemented; the engine + schema layer of the +stacking objective is now complete. Bulk authoring of the ~35 high-tier +buildings deferred to the follow-up `p1-43b`. + +### Q1–Q6 decisions (locked) + +- **Q1 — Chain coverage.** Drafted 8-category table kept. Short chains (food, + wealth, culture, production, medical) extended via NEW high-tier buildings. + Science + defense already had 6–8 tiers, no new authoring there. +- **Q2 — Cost.** Both paths allowed. Direct build pays full `cost`. Upgrade + from prerequisite pays `cost − prereq.cost + upgrade_fee`. New `upgrade_fee` + field on the building schema; default `null` = 15% of `cost` rounded. +- **Q3 — Effects inheritance (additive at author-time).** Each tier's + `effects` array is authored as the SUM of all ancestor effects + the + marginal contribution. The simulator does NOT re-sum predecessors at runtime + (that would double-count parallel ladders). +- **Q4 — No slot pool.** Constraint is natural economy: tiles, population, + production capacity, treasury. No `slot_capacity` schema field. +- **Q5 — Fold `market` into `marketplace`.** `market.json` deleted; its + distinguishing content (`tech_required: trade_routes`, `produces: + caravan_master`, `gpp_trade +1`, `tradeswright` specialist slot) merged into + `marketplace.json`. All five external `"market"` references rewritten: + `guild_hall.requires_existing`, `vault_of_seals.requires_buildings_all_cities`, + `specialists/specialists.json::merchant.employed_in`, `techs/agriculture.json::trade_routes.unlocks.buildings`, + `ai_personalities.json` (4 occurrences across `goldvein` + `runepriest`), + `manifests/buildings.json`. +- **Q6 — Vertical-only.** Hybrid merged structures (cross-category fusion) + remains `p1-59` and is out of scope for `p1-43`. + +### Schema + validator + engine + +- `public/games/age-of-dwarves/data/schemas/building.schema.json`: new + `upgrade_fee: number|null` field (`>= 0`). Other three stacking fields + (`requires_existing`, `consumes_existing`, `stack_mode`) already present + from cycle 5. +- `tools/validate-game-data.py::validate_building_requires_existing`: + cross-references every `requires_existing: ` against + `public/resources/buildings/*.json`. New self-test fixture + (`requires_existing: "nonexistent"`) raises a hard error. Self-test + passes; full run reports zero failures in the new section across 185 known + building ids. +- `mc-city::BuildingDef::upgrade_fee: Option` (`src/simulator/crates/mc-city/src/building.rs`). +- `mc-city::stacking::compute_construction_cost(def, lvn_cost, prereq_cost, has_prereq) -> u32` (`src/simulator/crates/mc-city/src/stacking.rs`). + Saturating arithmetic; default fee = `(lvn_cost * 0.15).round()` when + `def.upgrade_fee` is `None`. `BuildingDef::cost` is intentionally NOT a + Rust field (cost lives in JSON; the function takes it as a parameter so + callers thread the cost lookup themselves). + +### 5 chain-extension proof buildings + +Authored under `public/resources/buildings/`, each carrying +`requires_existing` to its predecessor and `upgrade_fee` set, `effects` array +sums all ancestor tiers per Q3: + +| ID | Tier | Category | `requires_existing` | `consumes_existing` | +|---|---|---|---|---| +| `hydroponic_farm` | 5 | food | `watermill` | `true` | +| `bazaar` | 5 | infrastructure (wealth) | `guild_hall` | `false` | +| `grand_chronicle` | 7 | culture | `bardic_circle` | `false` | +| `gravity_press` | 9 | production | `mithril_forge` | `false` | +| `apothecarium` | 5 | infrastructure (medical) | `hospital` | `true` | + +All five round-trip via `BuildingDef` (`test_all_authored_buildings_deserialize` +passes). Manifest `public/games/age-of-dwarves/data/manifests/buildings.json` +updated. + +### Tests (cycle 24) + +- `test_direct_build_pays_full_cost`, `test_upgrade_pays_delta_plus_fee`, + `test_upgrade_cost_saturates_when_prereq_costlier` — new in + `mc-city::stacking::tests`. +- `consumes_existing_removes_predecessor`, `upgrade_chain_consumes_predecessor_at_completion` + — pre-existing, still green. +- `cargo test -p mc-city --lib` — 146/146 pass (incl. new cost tests + + `test_all_authored_buildings_deserialize` covering 185 building JSONs). +- `cargo test -p mc-core --lib` — 211/211 pass. +- `python3 tools/validate-game-data.py --self-test` — all 4 golden bad-data + tests pass, including the new `requires_existing` cross-ref check. + +### Authoring rule documented + +`public/games/age-of-dwarves/docs/BUILDING_SCHEMA.md` extended with: +- `upgrade_fee` field row in the Stacking table. +- "Cost calculation" subsection (Q2 — full vs delta+fee). +- "Effects inheritance authoring rule" subsection (Q3 — author-time additive + inheritance, with worked food-chain example). +- "Slot constraint" subsection (Q4 — no artificial pool). + +### Follow-up filed + +`p1-43b-deep-chain-authoring.md` (status: stub, scope: game1) covers the +remaining ~35 high-tier building JSONs across food, wealth, culture, +production, and medical chains — see that objective for the full slate. + +### Status + +`p1-43` remains `partial`. Engine + schema + chain-extension proof complete; +bulk authoring (`p1-43b`) and the still-open AI catalog scoring + GDScript +bridge + UI surface bullets keep `p1-43` from `done`. diff --git a/.project/objectives/p1-43b-deep-chain-authoring.md b/.project/objectives/p1-43b-deep-chain-authoring.md new file mode 100644 index 00000000..5578152e --- /dev/null +++ b/.project/objectives/p1-43b-deep-chain-authoring.md @@ -0,0 +1,101 @@ +--- +id: p1-43b +title: Deep chain authoring — fill T6/T7/T8/T9/T10 building tiers across the 5 short chains +priority: p1 +status: stub +scope: game1 +updated_at: 2026-05-05 +blocked_by: + - p1-43a (engine + schema + chain-extension proof landed inline in p1-43) +evidence: [] +--- + +## 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 | Tier | Chain | +|---|---|---| +| `hydroponic_farm` | 5 | food | +| `bazaar` | 5 | wealth | +| `grand_chronicle` | 7 | culture | +| `gravity_press` | 9 | production | +| `apothecarium` | 5 | medical | + +This objective (`p1-43b`) covers the **remaining ~35 high-tier buildings** +needed to extend each short chain to ~8 tiers, per Q1's locked design. + +## Scope — buildings to author + +Each tier carries `requires_existing` pointing at its predecessor and +`upgrade_fee` (or omits to use the 15% default). Per Q3, each tier's +`effects` array is authored as the cumulative sum of all ancestors plus the +marginal contribution. + +### Food chain (T1=granary → T2=watermill → T5=hydroponic_farm shipped) + +- T6 candidate: `terraced_irrigation` — additional yield + drought-resistance +- T7 candidate: `subterranean_pasture` — meat/dairy + livestock specialist +- T9 candidate: `synthetic_grange` — industrial-era food multiplier + +### Wealth chain (T2=marketplace → T4=guild_hall → T5=bazaar shipped) + +- T6 candidate: `mercantile_exchange` — trade route capacity + banking +- T7 candidate: `royal_bank` — gold percent multiplier + specialist +- T9 candidate: `world_clearinghouse` — empire-wide gold percent + +### Culture chain (T1=monument → T2=gathering_hall → T3=great_hall → T4=bardic_circle → T7=grand_chronicle shipped) + +- T5 candidate: `clan_atelier` (NB: distinct from existing wonder `deep_atelier`) — culture + great-work slots +- T8 candidate: `hall_of_voices` — happiness + great-person spawn rate +- T9 candidate: `eternal_record` — empire-wide culture multiplier + +### Production chain (T1=forge → T2=iron_forge → T3=dwarf_deep_forge → T5=steam_forge → T6=tempering_forge → T7=mithril_forge → T9=gravity_press shipped) + +- T8 candidate: `industrial_smelter` — production + wonder-build percent +- T10 candidate: `adamantine_press` — caps the chain alongside existing `adamantine_foundry` + +### Medical chain (T1=barber → T2=clinic → T3=hospital → T5=apothecarium shipped) + +- T7 candidate: `field_hospital_corps` — heal-in-city + global heal bonus +- T9 candidate: `royal_infirmary` — happiness + plague-resistance + great person +- T10 candidate: `eternal_sanctum` — empire-wide unit heal cap + +### Optional military chain extensions + +`p1-43a`'s 14 weapon-class tables (already on record in the parent objective) +identified Lv2 gaps for `bow/crossbow` (`bolt_range` → ?) and `marksman` +(`marksman_lodge` → ?). If `p1-43b` widens scope, fill those producer-chain +gaps too. + +## Acceptance + +- ✗ Each new building authored as its own JSON file under + `public/resources/buildings/.json`. +- ✗ Manifest `public/games/age-of-dwarves/data/manifests/buildings.json` + extended (alphabetical sort). +- ✗ Each building carries: `tier`, `category`, `cost`, `upkeep`, `effects` + (cumulative per Q3), `requires_existing`, `upgrade_fee`, + `consumes_existing` (default `false` unless replacement is intended), + `stack_mode: "amplify"` (single-stream chains), `produces` (where the chain + trains units), `specialist_slots` (where the role exists), and `sprite`. +- ✗ `tools/validate-game-data.py` reports zero new failures. +- ✗ `cargo test -p mc-city --lib test_all_authored_buildings_deserialize` + remains green. + +## Out of scope + +- Engine logic — already shipped in `p1-43a`. +- AI catalog scoring (separate bullet under `p1-43`, blocked by `p1-42`). +- GDScript bridge / UI surface (separate bullets under `p1-43`). +- Sprite art generation — sprite paths are authored as filenames; bitmap + generation is a separate sprite-pipeline task. +- Hybrid merged structures (`p1-59`). + +## Closing + +When all ~35 buildings are authored, validator + Rust deserialize green, and +the manifest is up to date: flip status to `done` and `p1-43` itself can +flip to `done` once the open AI / UI bullets are also closed. diff --git a/public/games/age-of-dwarves/docs/BUILDING_SCHEMA.md b/public/games/age-of-dwarves/docs/BUILDING_SCHEMA.md index b03beb69..b9b61df2 100644 --- a/public/games/age-of-dwarves/docs/BUILDING_SCHEMA.md +++ b/public/games/age-of-dwarves/docs/BUILDING_SCHEMA.md @@ -223,8 +223,54 @@ defaults preserve pre-`p1-43` "binary" semantics. |---|---|---|---| | `requires_existing` | string\|null | `null` | Predecessor building id that must already be present in the city before this building can be queued. Production gate enforced by `City::can_build` via `mc-city::stacking::check_requires_existing`. | | `consumes_existing` | bool | `false` | When `true` AND `requires_existing` is set, completing this building removes the predecessor id from the city's building list (replacement upgrade). When `false`, both predecessor and successor coexist (parallel ladder). | +| `upgrade_fee` | number\|null | `null` | (`p1-43a` Q2) Additional gold cost charged on the upgrade-from-prerequisite path. `null` means "use the engine default": 15% of `cost`, rounded. Authors set a concrete number to override. Ignored when `requires_existing` is `null` (no upgrade path exists). | | `stack_mode` | enum | `"single"` | One of `"parallel" \| "amplify" \| "single"`. See table below. | +### Cost calculation (`p1-43a` Q2) + +A building reachable through a `requires_existing` ladder may be queued via two +paths: + +| Path | Cost paid | +|---|---| +| **Direct build** (predecessor not in city) | full `cost` | +| **Upgrade from predecessor** (predecessor present, `consumes_existing` true) | `cost − prereq.cost + upgrade_fee` (saturating) | + +Engine entry point: `mc-city::stacking::compute_construction_cost(def, lvn_cost, prereq_cost, has_prereq)`. +Default fee constant: `mc-city::stacking::DEFAULT_UPGRADE_FEE_FRACTION = 0.15`. + +### Effects inheritance authoring rule (`p1-43a` Q3) + +**Effect inheritance is author-time, not runtime.** When a building declares +`requires_existing: `, its `effects` array MUST be authored as the +SUM of all ancestor tiers' effects PLUS the marginal contribution at this +tier. This means a city holding only the upper tier (after `consumes_existing` +removed the predecessor) still benefits from the entire chain — because the +upper tier's authored numbers already absorb the chain. + +Concrete example — food chain `granary` (T1) → `mill` (T2) → `watermill` (T2) → `hydroponic_farm` (T5): + +```text +granary : food +1 +mill : food +1, food_percent 0.05 (granary +1 absorbed → effective +2 +5%) +watermill : food +2, food_percent 0.15 (mill numbers absorbed → effective +2 +15%) +hydroponic_farm : food +5, food_percent 0.25, (watermill absorbed → marginal +3 +10%) + food_per_river_tile +1, + famine_resistance +1 +``` + +The validator and Rust runtime do **not** re-sum predecessor effects — that +would double-count cities running parallel ladders (`consumes_existing: false`). +This rule keeps a single canonical effect-list per tier and avoids +reconciliation logic in the simulator. + +### Slot constraint (`p1-43a` Q4) + +There is **no artificial per-city slot pool** for the upgrade ladders. The +constraint surface is the natural economy: tiles owned, city population +(specialist allocation), production capacity, and gold treasury. No +`slot_capacity` or `max_concurrent_chains` field on the schema. + ### `stack_mode` values | Value | Semantics | Used by |