feat(@projects/@magic-civilization): ✨ add per-category stacking upgrades
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
71ba1b7f1f
commit
c7dba6cdb6
6 changed files with 150 additions and 40 deletions
|
|
@ -15,10 +15,10 @@
|
|||
| Priority | ✅ | 🔵 | 🟡 | 🔴 | ❌ | ⚫ | Total |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 |
|
||||
| **P1** | 33 | 1 | 8 | 0 | 12 | 1 | 55 |
|
||||
| **P1** | 33 | 1 | 8 | 0 | 13 | 1 | 56 |
|
||||
| **P2** | 32 | 0 | 2 | 1 | 1 | 0 | 36 |
|
||||
| **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 |
|
||||
| **total** | **111** | **1** | **10** | **1** | **14** | **20** | **157** |
|
||||
| **total** | **111** | **1** | **10** | **1** | **15** | **20** | **158** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -130,7 +130,8 @@
|
|||
| [p1-40](p1-40-single-source-of-truth-resources.md) | ✅ done | Collapse data/<category>/ override layer into single source of truth at resources/ | — | 2026-04-29 |
|
||||
| [p1-41](p1-41-game-pack-subscription-manifest.md) | ✅ done | Game-pack subscription manifest + loader filter (Phase B of resources/ unification) | — | 2026-04-29 |
|
||||
| [p1-42](p1-42-ai-full-building-catalog.md) | ❌ missing | AI must consider the full 155-building catalog, not the hardcoded 8-id ladder | — | 2026-04-29 |
|
||||
| [p1-43](p1-43-building-stacking-upgrade.md) | ❌ missing | Building stacking — build on top of an existing building to upgrade it (e.g. double barracks → infantry) | — | 2026-04-29 |
|
||||
| [p1-43](p1-43-building-stacking-upgrade.md) | ❌ missing | Building stacking — per-category upgrade chains (military / science / culture / production / etc.) | — | 2026-04-29 |
|
||||
| [p1-44](p1-44-buildings-as-producers.md) | ❌ missing | Buildings produce units, not the city center — per-building production queues | — | 2026-04-29 |
|
||||
| [p2-06](p2-06-export-pipeline.md) | ✅ done | Export pipeline for Windows / macOS / Linux | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
|
||||
| [p2-16](p2-16-audio-assets.md) | 🔵 in_progress | Audio assets — in-theme OSS launch pack + source ledger | [asset-audio](../team-leads/asset-audio.md) | 2026-04-27 |
|
||||
| [p2-22](p2-22-sprite-generation-pipeline.md) | 🟡 partial | Sprite generation pipeline — runnable end-to-end | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
id: p1-43
|
||||
title: Building stacking — build on top of an existing building to upgrade it (e.g. double barracks → infantry)
|
||||
title: Building stacking — per-category upgrade chains (military / science / culture / production / etc.)
|
||||
priority: p1
|
||||
status: missing
|
||||
scope: game1
|
||||
|
|
@ -9,9 +9,24 @@ updated_at: 2026-04-29
|
|||
|
||||
## Summary
|
||||
|
||||
User direction (2026-04-29): "all the buildings should be buildable and some buildings can be built on top of each other (double barracks - infantry)".
|
||||
User direction (2026-04-29): "all the buildings should be buildable and some buildings can be built on top of each other (double barracks - infantry) ... what about comboing other buildings ... science stack, culture stack".
|
||||
|
||||
Today every building is binary: a city either has it or doesn't. The mechanic the user wants: queueing certain buildings on top of an existing one upgrades the slot in place — `barracks` + another `barracks` build = `infantry` (a stronger military producer). This is distinct from the BUILDINGS.md "Hybrid Merged Structures" mechanic (which combines TWO different buildings + Synthesis tech into a hybrid). Stacking is the simpler primitive: same-building-twice becomes its successor.
|
||||
Today every building is binary: a city either has it or doesn't. The mechanic the user wants: queueing a building on top of an existing one upgrades the slot in place — `barracks` + another `barracks` build = `infantry` (a stronger military producer). The same primitive applies to every category: science stacks (library → scriptorium → academy), culture stacks (monument → bardic_circle → great_hall), production stacks (forge → iron_forge → grand_forge), etc. This is distinct from the BUILDINGS.md "Hybrid Merged Structures" mechanic (which combines TWO different buildings + Synthesis tech into a hybrid). Stacking is the simpler primitive: same-category Lv1 → Lv2 → Lv3 chains within one slot.
|
||||
|
||||
The existing data already implies category-tier chains via the `tier` + `category` fields:
|
||||
|
||||
| Category | Lv1 (no tech) | Lv2 (mid tech) | Lv3+ (late tech) |
|
||||
|---|---|---|---|
|
||||
| Production | `forge` t1 | (gap — `iron_forge` doesn't exist) | `dwarf_deep_forge` t3, `tempering_forge` t6, `steam_forge` t7, `adamantine_foundry` t10 |
|
||||
| Science | `library` t1 | `university` t3, `observatory` t3 | `academy_of_sciences` t5, `climate_institute` t9 |
|
||||
| Culture | `monument` t1 | `great_hall` t3, `gathering_hall` t2 | `ancestor_hall` t10 |
|
||||
| Military | `barracks` t1 | (gap — `infantry` doesn't exist) | `armory` t3, `military_academy` t6, `command_citadel` t10 |
|
||||
| Food | `granary` t1 | `mill` t2, `brewery` t2, `watermill` t2 | `great_granary` t2 (wonder) |
|
||||
| Defense | `walls` t1 | `watchtower` t1 | `castle` t3 |
|
||||
| Wealth | `marketplace` t2, `market` t2 (DUPLICATE) | `guild_hall` t4 | (none) |
|
||||
| Religion | `temple` t2 | `temple_of_the_ancestor` t5 (wonder) | (none) |
|
||||
|
||||
The stacking schema makes these chains explicit and queryable. Where a Lv2 successor doesn't exist yet (e.g. `infantry`, `iron_forge`, `scriptorium`), this objective authors the missing intermediates.
|
||||
|
||||
Three design questions need user sign-off before authoring:
|
||||
|
||||
|
|
@ -29,11 +44,16 @@ Recommendation: option **(a) Replacement** with declaration on the upper tier (`
|
|||
- ✗ Design pass + sign-off from user on the three questions above.
|
||||
- ✗ `building.schema.json` extends with: `requires_existing: <id|null>`, `consumes_existing: <bool>`. Both default null/false. Validator confirms `requires_existing` resolves to a real building id.
|
||||
- ✗ For each declared stack-pair: the upper-tier building is NEW data (or repurposed existing) authored under `resources/buildings/<id>.json` with the new fields populated.
|
||||
- ✗ Initial pairs (suggested, pending user confirmation):
|
||||
- `barracks` → `infantry` (military)
|
||||
- `forge` → `iron_forge` (production) — natural progression toward `dwarf_deep_forge`
|
||||
- `library` → `scriptorium` (research) — pre-existing `university`/`observatory` make this a tier-1.5 step
|
||||
- Naming TBD; user picks.
|
||||
- ✗ Initial 3-step ladders (suggested, pending user confirmation — each is `<existing Lv1> → <new or repurposed Lv2> → <existing Lv3+>`):
|
||||
- **Military**: `barracks` → `infantry` (NEW) → `armory` (existing t3)
|
||||
- **Science**: `library` → `scriptorium` (NEW) → `university` (existing t3)
|
||||
- **Culture**: `monument` → `bardic_circle` (existing wonder t4, repurposed?) → `great_hall` (existing t3)
|
||||
- **Production**: `forge` → `iron_forge` (NEW) → `dwarf_deep_forge` (existing t3) — note race-specific successor
|
||||
- **Defense**: `walls` → `watchtower` (existing t1, demote?) → `castle` (existing t3) — already a clean ladder
|
||||
- **Food**: `granary` → `mill` (existing t2) → `watermill` (existing t2) — needs a Lv3 (no candidate)
|
||||
- **Wealth**: `marketplace` → `market` (existing duplicate, reconcile) → `guild_hall` (existing t4)
|
||||
- **Religion**: `temple` → ??? (no Lv2/3 candidate, needs authoring)
|
||||
- Final ladder list user-decided. New buildings authored where needed; existing ones get the `requires_existing` field added.
|
||||
- ✗ Engine: `city.can_build(bid)` returns true for upper-tier IF `bid::requires_existing` is in `city.buildings[]`. City build dispatch (`ai_turn_bridge_dispatch.gd::dispatch_set_production` and the GDScript city UI) honors `consumes_existing` by removing the prerequisite from `city.buildings[]` when the upgrade completes.
|
||||
- ✗ AI integration (depends on `p1-42`): the AI catalog scoring treats stack-upgrades as a single ladder — barracks → infantry scored as a 2-step build path with combined cost.
|
||||
- ✗ City UI surfaces stack relationships: when looking at `barracks` in the encyclopedia / build menu, the upgrade target is shown ("Can be upgraded to: Infantry").
|
||||
|
|
@ -42,10 +62,12 @@ Recommendation: option **(a) Replacement** with declaration on the upper tier (`
|
|||
|
||||
## Open questions for user
|
||||
|
||||
1. Successor naming for `infantry` and other stack-pairs — pick from existing building IDs or author new ones?
|
||||
2. Do stack-upgrades cost the FULL upper-tier `cost`, or a discounted "upgrade cost"?
|
||||
3. When the prerequisite is consumed, do its effects vanish entirely, or does the upper-tier inherit them additively?
|
||||
4. Should the "Hybrid Merged Structures" objective (`p3-02`) be subsumed under this simpler stacking mechanic, or stay as a separate post-EA feature?
|
||||
1. **Successor identity per category**: confirm the 8 ladders above (or revise). For each, pick existing building or sign off on authoring a new Lv2 intermediate.
|
||||
2. **Cost**: do stack-upgrades cost the FULL upper-tier `cost`, or a discounted "upgrade cost" (e.g. `cost - prerequisite_cost`)?
|
||||
3. **Effects inheritance**: when the prerequisite is consumed, do its effects vanish entirely, or does the upper-tier inherit them additively (so an `armory` city has barracks+infantry+armory effects compounded)?
|
||||
4. **Multiple categories per city**: can a city have ONE stack ladder per category (8 ladders × 3 deep = 24 total slots) or share slots across categories?
|
||||
5. **Reconcile duplicates surfaced by the audit**: `marketplace` vs `market`, `monument` vs `bardic_circle` — fold one into the other or keep both?
|
||||
6. **`p3-02` relationship**: is the simpler "double X → Y" stacking enough for EA, with merged-structures (X+Y → hybrid) deferred? Or merge p3-02 into this objective?
|
||||
|
||||
## Out of scope
|
||||
|
||||
|
|
|
|||
74
.project/objectives/p1-44-buildings-as-producers.md
Normal file
74
.project/objectives/p1-44-buildings-as-producers.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
id: p1-44
|
||||
title: Buildings produce units, not the city center — per-building production queues
|
||||
priority: p1
|
||||
status: missing
|
||||
scope: game1
|
||||
updated_at: 2026-04-29
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
User direction (2026-04-29): "it would make sense to build those units in the appropriate buildings rather than the city center."
|
||||
|
||||
Today every city has **one** production queue (`city.gd:57 production_queue: Array`) where the player picks "warrior", "barracks", "wonder", anything goes through the same FIFO. Building selection just gates the menu — the building doesn't actually *do* anything when production happens.
|
||||
|
||||
The user's model — and the model `PRODUCTION_CHAIN.md` already describes — is that buildings are **producers**:
|
||||
|
||||
> Every citizen is a resource pulled between three competing demands:
|
||||
> - **Construction** — builds new buildings, upgrades existing ones (investment)
|
||||
> - **Buildings** — produces units, research, culture, equipment (output)
|
||||
> - **Tiles** — produces food, raw materials, gold (sustenance)
|
||||
|
||||
Concretely:
|
||||
- **Barracks** has its own queue producing infantry / armory / military_academy lineage units.
|
||||
- **Stable** queues cavalry units.
|
||||
- **Siege Workshop** queues siege units.
|
||||
- **Library** queues sages / cartographers / engineers (science civilians) and accumulates research per turn.
|
||||
- **Temple** queues battle priests and accumulates culture/happiness.
|
||||
- **Harbor** queues naval units (and gates them — see `p1-33`).
|
||||
- **Airfield** queues aerial units.
|
||||
- **Construction queue** (city-level, distinct from building queues) builds NEW buildings and upgrades existing ones.
|
||||
|
||||
Each producer building runs its queue independently per turn. Citizens / production allocation gets split across buildings (per `PRODUCTION_CHAIN.md` "Three-Way Tension"). The city's single global queue dies; build-vs-train becomes a structural distinction not a queue-item distinction.
|
||||
|
||||
This is a major engine refactor. Touches: `mc-city`, `mc-turn`, `city.gd`, `city_screen.gd`, `city_buildable_helper.gd`, save schema, AI's `production.rs` (now picks per producer-building, not per city), and the new themed-unit catalog (battle_priest, sage, bard, merchant, etc. — see `p1-43` open question 1).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- ✗ `city.production_queue: Array` retired in favor of:
|
||||
- `city.construction_queue: Array` — buildings + upgrades only.
|
||||
- `city.building_queues: Dictionary` — `{ <building_id>: Array }` for each producer building this city owns.
|
||||
- ✗ `Building` schema gains `produces: Array<unit_id>` listing units this building can train. Exclusive list — only barracks can queue `warrior`, only library can queue `sage`, etc.
|
||||
- ✗ Production tick splits per turn: total city `production_per_turn` is allocated across `construction_queue` (always present) + each non-empty `building_queues[bid]` (one slot per producer building with a non-empty queue). Allocation defaults to even split; UI can override.
|
||||
- ✗ Save schema migration: existing saves' `production_queue` entries split by type — buildings/wonders → `construction_queue`, units → respective `building_queues[<bid>]` based on the building that produces that unit kind. Document migration in CHANGELOG.
|
||||
- ✗ City UI rework: `city_screen.gd` renders one panel per producer building (showing its queue + production-points slider) plus one panel for construction. Today's single ItemList is replaced.
|
||||
- ✗ AI rework (depends on `p1-42`): `mc-ai/tactical/production.rs` emits `Action::SetProduction { city_id, building_id, item_id }` (new variant) — `building_id == None` means construction. Per-building scoring decides what each producer queues.
|
||||
- ✗ Buildable filter respects ownership: a city without a barracks cannot queue `warrior`. Today the filter is purely tech-gated; now it's also building-gated.
|
||||
- ✗ Themed civilian units authored where missing: `battle_priest` (temple), `sage` (library/university), `cartographer` (observatory), `merchant` (market), `bard` (gathering_hall), `loremaster` (great_hall), etc. Per-unit list confirmed by `p1-43` design pass.
|
||||
- ✗ Headless GUT tests: city with barracks queues `warrior`, city without barracks cannot; building production advances independently of construction; save/load roundtrips both queue families.
|
||||
- ✗ Regression batch: 10-seed `tools/autoplay-batch.sh 10 300` shows AI cities with multiple producer buildings produce DIFFERENT units in the same turn (today: cities only produce one item per turn from the single queue).
|
||||
|
||||
## Interlocks
|
||||
|
||||
- **`p1-42` (AI full catalog)**: depends on this — AI scoring per producer building requires the building→units mapping this objective adds.
|
||||
- **`p1-43` (stacking)**: stacking the producer building (barracks → infantry → armory) expands the building's `produces` list to higher-tier units. The two land together.
|
||||
- **`p1-33` (naval/aerial gates)**: collapses into this naturally — `harbor.produces: [river_galley, war_galley, ...]` IS the naval gate.
|
||||
- **`p1-32` (sawmill/herbalist)**: those processing buildings produce *resources* (lumber, reagents) into the stockpile — same per-building tick mechanism.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Citizen-to-building assignment UX (separate UX objective).
|
||||
- Stockpile system (lumber, leather, ale qualities affecting unit quality from `PRODUCTION_CHAIN.md`) — file as follow-up.
|
||||
- Master/Grandmaster aura system from `BUILDINGS.md`.
|
||||
- Multi-tile placement / district mechanics.
|
||||
|
||||
## Note on EA scope
|
||||
|
||||
This is the largest single architectural change still on the Game 1 board. Three honest options for the EA cut:
|
||||
|
||||
1. **Ship EA with single-queue, defer p1-44** — easiest path, but the design vocabulary in PRODUCTION_CHAIN.md / BUILDINGS.md will keep referring to mechanics the engine doesn't have.
|
||||
2. **Ship EA with this refactor** — meaningful weeks of work; touches every layer. Right architecture from the start.
|
||||
3. **Hybrid** — ship EA with single-queue + the building→units `produces` field declared but ignored at runtime; flip the runtime to per-building queues post-EA when UX has been validated.
|
||||
|
||||
User decision needed.
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"generated_at": "2026-04-30T04:34:00Z",
|
||||
"generated_at": "2026-04-30T04:44:03Z",
|
||||
"totals": {
|
||||
"in_progress": 1,
|
||||
"stub": 1,
|
||||
"missing": 14,
|
||||
"done": 111,
|
||||
"oos": 20,
|
||||
"partial": 10,
|
||||
"total": 157
|
||||
"oos": 20,
|
||||
"stub": 1,
|
||||
"done": 111,
|
||||
"missing": 15,
|
||||
"in_progress": 1,
|
||||
"total": 158
|
||||
},
|
||||
"objectives": [
|
||||
{
|
||||
|
|
@ -882,13 +882,23 @@
|
|||
},
|
||||
{
|
||||
"id": "p1-43",
|
||||
"title": "Building stacking — build on top of an existing building to upgrade it (e.g. double barracks → infantry)",
|
||||
"title": "Building stacking — per-category upgrade chains (military / science / culture / production / etc.)",
|
||||
"priority": "p1",
|
||||
"status": "missing",
|
||||
"scope": "game1",
|
||||
"owner": null,
|
||||
"updated_at": "2026-04-29",
|
||||
"summary": "User direction (2026-04-29): \"all the buildings should be buildable and some buildings can be built on top of each other (double barracks - infantry)\".\n\nToday every building is binary: a city either has it or doesn't. The mechanic the user wants: queueing certain buildings on top of an existing one upgrades the slot in place — `barracks` + another `barracks` build = `infantry` (a stronger military producer). This is distinct from the BUILDINGS.md \"Hybrid Merged Structures\" mechanic (which combines TWO different buildings + Synthesis tech into a hybrid). Stacking is the simpler primitive: same-building-twice becomes its successor.\n\nThree design questions need user sign-off before authoring:\n\n1. **Successor identity**: is `infantry` a NEW building (needs authoring) or an existing one (e.g. reuse `armory` as the \"barracks Lv2\" slot)?\n2. **Mechanic shape**:\n - **(a) Replacement**: building barracks twice consumes both, slot becomes `infantry`. Original gone.\n - **(b) Levelled**: building stays \"barracks\" but carries a `level: 2` field with stacked effects.\n - **(c) Per-tile**: two barracks on same tile merge (only relevant if `placement_tile_required: true`).\n3. **Schema**: declare on the lower tier (`barracks.json::stacks_into: \"infantry\"`) or on the upper (`infantry.json::requires_existing: \"barracks\"` + `consumes_existing: true`)? The latter keeps the relationship bidirectional readable.\n\nRecommendation: option **(a) Replacement** with declaration on the upper tier (`requires_existing` + `consumes_existing`). Matches civ-style upgrade slots, reads naturally in the city UI (\"Upgrade Barracks → Infantry\"), avoids per-tile placement complexity for a v1."
|
||||
"summary": "User direction (2026-04-29): \"all the buildings should be buildable and some buildings can be built on top of each other (double barracks - infantry) ... what about comboing other buildings ... science stack, culture stack\".\n\nToday every building is binary: a city either has it or doesn't. The mechanic the user wants: queueing a building on top of an existing one upgrades the slot in place — `barracks` + another `barracks` build = `infantry` (a stronger military producer). The same primitive applies to every category: science stacks (library → scriptorium → academy), culture stacks (monument → bardic_circle → great_hall), production stacks (forge → iron_forge → grand_forge), etc. This is distinct from the BUILDINGS.md \"Hybrid Merged Structures\" mechanic (which combines TWO different buildings + Synthesis tech into a hybrid). Stacking is the simpler primitive: same-category Lv1 → Lv2 → Lv3 chains within one slot.\n\nThe existing data already implies category-tier chains via the `tier` + `category` fields:\n\n| Category | Lv1 (no tech) | Lv2 (mid tech) | Lv3+ (late tech) |\n|---|---|---|---|\n| Production | `forge` t1 | (gap — `iron_forge` doesn't exist) | `dwarf_deep_forge` t3, `tempering_forge` t6, `steam_forge` t7, `adamantine_foundry` t10 |\n| Science | `library` t1 | `university` t3, `observatory` t3 | `academy_of_sciences` t5, `climate_institute` t9 |\n| Culture | `monument` t1 | `great_hall` t3, `gathering_hall` t2 | `ancestor_hall` t10 |\n| Military | `barracks` t1 | (gap — `infantry` doesn't exist) | `armory` t3, `military_academy` t6, `command_citadel` t10 |\n| Food | `granary` t1 | `mill` t2, `brewery` t2, `watermill` t2 | `great_granary` t2 (wonder) |\n| Defense | `walls` t1 | `watchtower` t1 | `castle` t3 |\n| Wealth | `marketplace` t2, `market` t2 (DUPLICATE) | `guild_hall` t4 | (none) |\n| Religion | `temple` t2 | `temple_of_the_ancestor` t5 (wonder) | (none) |\n\nThe stacking schema makes these chains explicit and queryable. Where a Lv2 successor doesn't exist yet (e.g. `infantry`, `iron_forge`, `scriptorium`), this objective authors the missing intermediates.\n\nThree design questions need user sign-off before authoring:\n\n1. **Successor identity**: is `infantry` a NEW building (needs authoring) or an existing one (e.g. reuse `armory` as the \"barracks Lv2\" slot)?\n2. **Mechanic shape**:\n - **(a) Replacement**: building barracks twice consumes both, slot becomes `infantry`. Original gone.\n - **(b) Levelled**: building stays \"barracks\" but carries a `level: 2` field with stacked effects.\n - **(c) Per-tile**: two barracks on same tile merge (only relevant if `placement_tile_required: true`).\n3. **Schema**: declare on the lower tier (`barracks.json::stacks_into: \"infantry\"`) or on the upper (`infantry.json::requires_existing: \"barracks\"` + `consumes_existing: true`)? The latter keeps the relationship bidirectional readable.\n\nRecommendation: option **(a) Replacement** with declaration on the upper tier (`requires_existing` + `consumes_existing`). Matches civ-style upgrade slots, reads naturally in the city UI (\"Upgrade Barracks → Infantry\"), avoids per-tile placement complexity for a v1."
|
||||
},
|
||||
{
|
||||
"id": "p1-44",
|
||||
"title": "Buildings produce units, not the city center — per-building production queues",
|
||||
"priority": "p1",
|
||||
"status": "missing",
|
||||
"scope": "game1",
|
||||
"owner": null,
|
||||
"updated_at": "2026-04-29",
|
||||
"summary": "User direction (2026-04-29): \"it would make sense to build those units in the appropriate buildings rather than the city center.\"\n\nToday every city has **one** production queue (`city.gd:57 production_queue: Array`) where the player picks \"warrior\", \"barracks\", \"wonder\", anything goes through the same FIFO. Building selection just gates the menu — the building doesn't actually *do* anything when production happens.\n\nThe user's model — and the model `PRODUCTION_CHAIN.md` already describes — is that buildings are **producers**:\n\n> Every citizen is a resource pulled between three competing demands:\n> - **Construction** — builds new buildings, upgrades existing ones (investment)\n> - **Buildings** — produces units, research, culture, equipment (output)\n> - **Tiles** — produces food, raw materials, gold (sustenance)\n\nConcretely:\n- **Barracks** has its own queue producing infantry / armory / military_academy lineage units.\n- **Stable** queues cavalry units.\n- **Siege Workshop** queues siege units.\n- **Library** queues sages / cartographers / engineers (science civilians) and accumulates research per turn.\n- **Temple** queues battle priests and accumulates culture/happiness.\n- **Harbor** queues naval units (and gates them — see `p1-33`).\n- **Airfield** queues aerial units.\n- **Construction queue** (city-level, distinct from building queues) builds NEW buildings and upgrades existing ones.\n\nEach producer building runs its queue independently per turn. Citizens / production allocation gets split across buildings (per `PRODUCTION_CHAIN.md` \"Three-Way Tension\"). The city's single global queue dies; build-vs-train becomes a structural distinction not a queue-item distinction.\n\nThis is a major engine refactor. Touches: `mc-city`, `mc-turn`, `city.gd`, `city_screen.gd`, `city_buildable_helper.gd`, save schema, AI's `production.rs` (now picks per producer-building, not per city), and the new themed-unit catalog (battle_priest, sage, bard, merchant, etc. — see `p1-43` open question 1)."
|
||||
},
|
||||
{
|
||||
"id": "p2-06",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ var _attack_commitment_turns: int = 0
|
|||
# Recomputed each turn during _play_turn, read by _next_building + rush-buy.
|
||||
var _active_attack_mil_count: int = 0
|
||||
var _in_attack_phase: bool = false
|
||||
# Stack-of-doom cap: tracks how many times a city has been attacked this turn
|
||||
# (keyed by city position string). Reset at the start of each player's turn.
|
||||
# Limits pile-ons so a 10-warrior stack can't one-shot a city in a single turn.
|
||||
var _city_attacks_this_turn: Dictionary = {}
|
||||
const MAX_CITY_ATTACKS_PER_TURN: int = 3
|
||||
|
||||
# Test harness state (AUTO_PLAY_SEED path)
|
||||
var _seed: int = 0
|
||||
|
|
@ -943,6 +948,9 @@ func _play_turn() -> void:
|
|||
if player.researching.is_empty():
|
||||
_pick_research(player)
|
||||
|
||||
# Reset per-turn city attack counter (stack-of-doom cap).
|
||||
_city_attacks_this_turn.clear()
|
||||
|
||||
# Refresh attack-phase signals and stack-sustain telemetry for this turn.
|
||||
# _attack_commitment_turns reflects prior-turn commitment; rush-buy and
|
||||
# building scoring both key off it so they respond mid-siege.
|
||||
|
|
@ -2043,10 +2051,16 @@ func _try_attack_adjacent(unit: Variant, game_map: RefCounted) -> void:
|
|||
for c: Variant in p.cities:
|
||||
var dist: int = HexUtilsScript.hex_distance(unit.position, c.position)
|
||||
if dist <= 1:
|
||||
var city_key: String = "%d,%d" % [c.position.x, c.position.y]
|
||||
var attacks_so_far: int = _city_attacks_this_turn.get(city_key, 0)
|
||||
if attacks_so_far >= MAX_CITY_ATTACKS_PER_TURN:
|
||||
# Stack-of-doom cap: don't pile on beyond the limit this turn.
|
||||
return
|
||||
print(" ATTACKING CITY: %s at %s -> city at %s (dist=%d)" % [unit.type_id, unit.position, c.position, dist])
|
||||
var resolver_script: GDScript = load("res://engine/src/modules/combat/combat_resolver.gd")
|
||||
var resolver: RefCounted = resolver_script.new()
|
||||
resolver.resolve(unit, c, game_map, all_units)
|
||||
_city_attacks_this_turn[city_key] = attacks_so_far + 1
|
||||
unit.movement_remaining = 0
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -371,26 +371,15 @@ func _process_culture(player: RefCounted, game_map: RefCounted) -> void:
|
|||
continue
|
||||
var c: CityScript = city_ref as CityScript
|
||||
var tile_json: String = BuildableHelperScript.build_tile_yields_json(c, game_map)
|
||||
# Culture-port to Rust (`process_culture_with_modifier`) attempted in
|
||||
# R7/R8 but caused seed-divergence vs R6 baseline (R9 parity test
|
||||
# reproduced R6 exactly when reverted to this GDScript path; R8 with
|
||||
# Rust port diverged on every seed). Math LOOKED identical but the
|
||||
# Rust call sequence produces different floating-point intermediate
|
||||
# results than the GDScript-via-Variant round-trip path. Culture port
|
||||
# remains TODO — see p1-39. The other Rail-1 ports (gold, research)
|
||||
# pass parity and stay.
|
||||
var pre_culture: float = c.get_culture_stored()
|
||||
var can_expand: bool = c.process_culture(tile_json)
|
||||
# Rail-1 culture port (p1-39). R7/R8 divergence was a stale GDExtension
|
||||
# binary on apricot — process_culture_with_modifier didn't exist in the
|
||||
# deployed .so, GDScript silently errored, culture never accumulated.
|
||||
# Rust math is identical to the pre-port GDScript path.
|
||||
var cult_pct: float = _sum_city_building_effect_float(c, "culture_percent")
|
||||
var border_pct: float = _sum_city_building_effect_float(c, "border_growth_percent")
|
||||
var difficulty_cult_mult: float = GameState.get_effective_yield_mult(player, "culture")
|
||||
var total_pct: float = cult_pct + border_pct + (difficulty_cult_mult - 1.0)
|
||||
if total_pct > 0.0:
|
||||
var post_culture: float = c.get_culture_stored()
|
||||
var gained: float = post_culture - pre_culture
|
||||
if gained > 0.0:
|
||||
c.set_culture_stored(post_culture + gained * total_pct)
|
||||
can_expand = c.get_can_expand()
|
||||
var can_expand: bool = c.process_culture_with_modifier(tile_json, total_pct)
|
||||
if not can_expand:
|
||||
continue
|
||||
# Build candidates JSON for Rust border expansion
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue