feat(@projects/@magic-civilization): ✨ finalize stacking objective design
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
7ba49aa59e
commit
4a994eb22c
3 changed files with 249 additions and 0 deletions
|
|
@ -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: <id>` 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<u32>` (`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`.
|
||||
|
|
|
|||
101
.project/objectives/p1-43b-deep-chain-authoring.md
Normal file
101
.project/objectives/p1-43b-deep-chain-authoring.md
Normal file
|
|
@ -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/<id>.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.
|
||||
|
|
@ -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: <prereq>`, 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 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue