docs(workshops): planet-down cascade workshop + misc stray files
Planet-down cascade workshop (3 rounds, 5 agents): layer-by-layer generation from empty world through population overlay, city planning, and street rendering. Includes consultant review by Troblum. Also commits: pre-Sprint-35 DB backup, Claude Code team-mode tmux test log (team-test.md). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,459 @@
|
||||
---
|
||||
title: "Workshop Brief — Planet-Down Cascade"
|
||||
description: "Design the multi-layered planet-down generation algorithms where each layer's output feeds the next: empty world → population overlay → city planning → street rendering"
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: ""
|
||||
round: 0
|
||||
created: 2026-04-30
|
||||
---
|
||||
|
||||
# Planet-Down Cascade Workshop Brief
|
||||
|
||||
**Goal:** Design the full generation cascade from planetary heightmap to walkable street-level tiles. Each layer receives the previous layer's output as input. The workshop must produce algorithm specifications, data format handoffs, and implementation decisions for each layer — resolving open questions that domain specialists have surfaced but cannot close alone.
|
||||
|
||||
**Ticket:** (to be assigned at sprint planning)
|
||||
**Priority:** HIGH — this is the architectural design that drives Phase 3 and Phase 5 implementation
|
||||
**Participants:** Gestalt (systems design + fun factor), Tyre (architecture + feasibility), Paula (narrative + political depth), Burnelli-Sheldon (economics + simulation modeling)
|
||||
**Source:** Lead directive, generation-cascade workshop Rounds 1-4 (2026-04-24 to 2026-04-30)
|
||||
|
||||
---
|
||||
|
||||
## The Lead's Cascade Vision
|
||||
|
||||
*This is the authoritative design. Reproduce it verbatim here so all participants work from the same framing.*
|
||||
|
||||
**1. EMPTY WORLD** — Start from heightmap. Water simulation: river drainage from heightmap datapoints + seed to calculate river presence. Scatter lakes with biome/river dependency. Refine sub-biomes into actionable regional areas (jungle with clearings, hills, swamp). The existing heightmap rivers are overrides/labels — real rivers come from seeded drainage patterns. Keep names, re-place based on water sim. ALL DETERMINISTIC down to player scale. Result: complete natural world, no civilization.
|
||||
|
||||
**2. POPULATION OVERLAY** — Layer economics onto natural world. Where rivers meet, where rivers hit the sea, where arable land is — settlements anchor. Felled forests become farmland. Flattened hilly terrain for cities (terraced, no longer craggy). Anchored by wiki population counts and markers.json. Wider area: entire city with surrounding lands, village with farms rolling into farmland and forest. Road/rail networks inter and intra regional. Sub-settlements INCLUDED. MaintenanceAuthority on roads INCLUDED.
|
||||
|
||||
**3. CITY-LEVEL PLANNING** — "Cities Skylines" with topography. Plot farmland, wilderness, suburbs, industrial areas, commercial districts, ports, airports, train stations, highways, cultural districts. All if appropriate within size and cultural parameters. ENTIRE PHASE on its own.
|
||||
|
||||
**4. STREET-LEVEL RENDERING** — Take city-level data, render streets, buildings, scatter civilization.
|
||||
|
||||
**CRITICAL DETERMINISM RULE:** Economic sim's rolling state affects RENDERING (prosperity, repair, wealth state) NOT LAYOUT (streets, buildings locked by seed). User cannot leave and return to find different street plan because GDP changed. The same streets and buildings, just richer or poorer.
|
||||
|
||||
**CRITICAL LoD/ZOOM RULE:** First pass = automatic parameter generation. Then cascaded world render: user spawns in village X, code "zooms in" on planet, traces all cascade steps to where user sits, and with those patterns in hand (including cascades for chunks/districts around them) fills in actual street levels. Smart: react to economics flags, parse wealth/repairedness on rolling economics. DO NOT influence determinism.
|
||||
|
||||
**BUILDING LoD RULE:** Door-per-edge as boundary. Descriptor + catalog of what's behind the door. No spatial rendering of building interiors until player crosses threshold.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The generation-cascade workshop (Rounds 1-4, 2026-04-30) produced the foundational architecture and resolved the key data-model decisions. That workshop examined the pipeline from two directions: bottom-up (what exists in the codebase today) and top-down (what the lead's vision requires). The conclusion: the existing architecture (DistrictSkeleton, SeedChain, chunk streaming) maps cleanly to Layers 3 and 4 of this cascade, but Layers 1 and 2 were not designed in detail.
|
||||
|
||||
This workshop begins where the last one ended. The given facts below are locked. This workshop designs the open questions.
|
||||
|
||||
---
|
||||
|
||||
## Given Facts — Carry Forward Without Revisiting
|
||||
|
||||
### Pipeline Architecture
|
||||
|
||||
| Decision | Detail |
|
||||
|----------|--------|
|
||||
| Phase 3 = Python tooling + systems.db | All Layer 1 and Layer 2 generation runs offline in `generate_regional.py` and commits to systems.db. |
|
||||
| Phase 5 = Rust runtime + seed-derived | Layer 3 and Layer 4 generation runs in the simulation server at runtime, seed-derived, never stored. |
|
||||
| These two phases are parallelizable | Phase 5 can stub `CityGenerationContext` and wire to systems.db when Phase 3 is done. |
|
||||
| `generate_regional.py` is the third pipeline generator | Runs after `generate_atlas.py` in `make regen-db`. Produces Phase 3 systems.db tables. Has its own meta stamp. |
|
||||
| Atlas markers are the last stored layer | Everything from Layer 3 down is seed-derived at runtime. Save files store: `world_seed` + markers snapshot hash + `ChunkMutations`. |
|
||||
| No markers.json extension | City attributes are read from systems.db bodies table by cross-reference on `body_id`. markers.json stays as a pure spatial + naming store. |
|
||||
| Atlas coordinates are UI-only | The 512×256 pixel grid is a display coordinate system. Walkable world uses city-local sim-tile coordinates from (0,0). No pixel-to-sim-tile formula. |
|
||||
|
||||
### Seed Chain
|
||||
|
||||
All randomness below the atlas layer uses FNV-1a `child_seed`:
|
||||
|
||||
```rust
|
||||
fn child_seed(parent: u64, discriminant: u64) -> u64 {
|
||||
let mut h = parent ^ 0xcbf29ce484222325;
|
||||
for byte in discriminant.to_le_bytes() {
|
||||
h ^= byte as u64;
|
||||
h = h.wrapping_mul(0x100000001b3);
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
district_seed = child_seed(world_seed, district_id)
|
||||
chunk_seed = child_seed(district_seed, (cx as u64) << 32 | (cy as u64))
|
||||
```
|
||||
|
||||
Any district or chunk can be generated in isolation without generating neighbors first. Complies with D-010.
|
||||
|
||||
### Phase 3 → Phase 5 Handoff
|
||||
|
||||
```rust
|
||||
pub struct CityGenerationContext {
|
||||
pub city_id: String,
|
||||
pub political_archetype: PoliticalArchetype,
|
||||
pub prosperity_baseline: f32, // seed-locked Layer 3 output
|
||||
pub surrounding_biome: BiomeClass,
|
||||
pub road_entry_directions: Vec<CardinalDirection>,
|
||||
pub footprint_radius_km: f32,
|
||||
pub founding_orientation: FoundingOrientation,
|
||||
pub world_tier: WorldTier,
|
||||
}
|
||||
```
|
||||
|
||||
This struct is populated from systems.db at game startup. Phase 5 reads it as input to district skeleton generation. `prosperity_baseline` is the seed-locked economic baseline. The runtime economic simulation produces a separate `prosperity_current` field — these are two distinct data fields and must not be conflated in implementation.
|
||||
|
||||
### City Decomposition (D-C4)
|
||||
|
||||
- District count: `max(1, floor(city.population / 50_000))`, log-scaled for large cities
|
||||
- WorldTier district ceilings: Epicenter uncapped | Regional 8 | Backwater 4 | Passage 2 | Waypoint 1
|
||||
- Capital cities: +1 bonus district
|
||||
- Domed/cave settlement: fixed 1 district
|
||||
- Districts positioned in city-local sim tiles: district at grid (col, row) → origin `(col × 512, row × 512)`
|
||||
|
||||
### District Skeleton (Phase 1 Minimum Scope)
|
||||
|
||||
- **Stage 1 (mandatory):** Classification — WorldTier, ComplexityTier, SettingType, DistrictType, LayoutMode, `prosperity_baseline`, `perimeter_treatment`
|
||||
- **Stage 2 (mandatory):** Block grid — 4×4 BlockSkeletons with ZoningType, `density_pct`, seed
|
||||
- **Stages 3-5 deferred:** Reservations, social sites, guarantee audit — enrichment for post-walkable world
|
||||
|
||||
### WorldTier Enum (corrected)
|
||||
|
||||
```rust
|
||||
enum WorldTier { Epicenter, Regional, Backwater, Passage, Waypoint }
|
||||
// NOT: Peripheral, Connected, Core — those are wrong and must be replaced
|
||||
```
|
||||
|
||||
### GeneratorChunkData Upgrade
|
||||
|
||||
```rust
|
||||
pub struct GeneratorChunkData { pub tiles: Vec<TileEntry> } // 64×64 = 4,096 entries
|
||||
pub struct TileEntry { pub tile_id: TileId, pub walkable: bool }
|
||||
// Replaces: pub type GeneratorChunkData = Vec<bool>
|
||||
```
|
||||
|
||||
Minimum tile vocabulary: `floor_street`, `floor_interior`, `wall`.
|
||||
|
||||
### Economics: 6-Field Minimum Read Set and 10×9 Weight Table
|
||||
|
||||
Six fields queryable from systems.db per city, sufficient to produce a spatially differentiated world:
|
||||
|
||||
| # | Field | Source table |
|
||||
|---|-------|-------------|
|
||||
| 1 | `economic_role` | `bodies` |
|
||||
| 2 | `settlement_pattern` | `bodies` |
|
||||
| 3 | `economic_tier` | `system_economy` |
|
||||
| 4 | `distribution_index` | `system_economy` |
|
||||
| 5 | `corp_presence` count | `corp_presence` |
|
||||
| 6 | `headquarters_system/body` match | `corporations` |
|
||||
|
||||
10×9 DistrictType weight table (economic_role → DistrictType prior):
|
||||
|
||||
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|
||||
|--------------|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| manufacturing | 25 | 5 | 35 | 5 | 20 | 2 | 5 | 3 | 0 |
|
||||
| agricultural | 30 | 15 | 5 | 10 | 25 | 3 | 10 | 2 | 0 |
|
||||
| extraction | 20 | 5 | 30 | 5 | 30 | 0 | 5 | 5 | 0 |
|
||||
| transit | 10 | 20 | 5 | 5 | 25 | 10 | 15 | 10 | 0 |
|
||||
| research | 20 | 5 | 5 | 15 | 5 | 5 | 10 | 0 | 35 |
|
||||
| commercial | 15 | 35 | 5 | 5 | 10 | 15 | 15 | 0 | 0 |
|
||||
| service_mixed | 25 | 20 | 5 | 10 | 5 | 15 | 20 | 0 | 0 |
|
||||
| mining | 20 | 5 | 25 | 0 | 35 | 0 | 10 | 5 | 0 |
|
||||
| frontier | 35 | 10 | 10 | 5 | 20 | 5 | 15 | 0 | 0 |
|
||||
| energy | 10 | 0 | 20 | 5 | 30 | 0 | 5 | 5 | 25 |
|
||||
|
||||
`political_archetype` modifiers stack on top of these weights.
|
||||
|
||||
### Sub-Settlement Hierarchy (lead inclusion)
|
||||
|
||||
| Tier | Population | Treatment | Placement trigger |
|
||||
|------|-----------|-----------|------------------|
|
||||
| City | 10,000+ | Full district decomposition (Layer 3) | markers.json anchor + geography |
|
||||
| Town | 1,000–10,000 | Single-district equivalent | Road network + economic role |
|
||||
| Outpost | 50–1,000 | Purpose-typed single node | Corp presence + extraction trigger |
|
||||
| Waypoint | <50 | Infrastructure node, no district | Road midpoint formula |
|
||||
| Rural cluster | Isolated | Type-tagged scattered nodes | settlement_pattern + economic role |
|
||||
| Ruin | 0 | Historical footprint | Abandoned tether condition |
|
||||
|
||||
**Placement triggers:**
|
||||
- Mining camps: `economic_role ∈ {extraction, mining}` + corp_presence with qualifying commodity → 1 camp per 2 corps
|
||||
- Trade waypoints: long roads (pixel_count > threshold) → waypoint at geometric midpoint
|
||||
- Agricultural nodes: `settlement_pattern ∈ {dispersed, dispersed_rural}` + `economic_role = agricultural` → clusters at interval ∝ `1/economic_tier`
|
||||
- Shadow nodes: `collection_efficiency < 0.6` + shadow-viable corps → 1 informal settlement at city fringe
|
||||
|
||||
### Dynamic Settlements Are Latent
|
||||
|
||||
All economically plausible settlement positions are placed at Layer 2 generation time (seed-derived). Whether each is ACTIVE is determined by the economic simulation's rolling state. An inactive settlement exists spatially as ruins/empty structures — it is dark, unmaintained, and depopulated. Ghost towns are generated, not scripted.
|
||||
|
||||
### Determinism Boundary (per layer)
|
||||
|
||||
| Layer | Seed-locked (never changes) | Economics-variable (runtime updates) |
|
||||
|-------|-----------------------------|--------------------------------------|
|
||||
| 1 (Empty World) | Heightmap, river network, biome grid, pass locations | Nothing — terrain is fixed |
|
||||
| 2 (Population) | Settlement positions, road topology, sub-settlement placement, PoliticalTether | Road condition (edge case: abandoned roads degrade further) |
|
||||
| 3 (City Planning) | District positions, district types, block grid, `prosperity_baseline`, `perimeter_treatment` | `prosperity_current` (rolling sim update); building availability |
|
||||
| 4 (Street-Level) | Street positions, building footprints, door positions, all tile layout | Tile condition states (Intact/Worn/Cracked/Broken); building activity signals |
|
||||
|
||||
**The felled-forests exception:** "Felled forests → farmland" is Layer 2 regional land-use (coarse biome-cell resolution), not Layer 4 tile mutation. Regional land-use CAN update at biome-cell resolution as economics shifts. City-internal street/building geometry is permanently seed-locked.
|
||||
|
||||
### LoD Zoom-In Cascade
|
||||
|
||||
When a player spawns, the code traces the full cascade from planet level down to the player's tile position. The cascade is lazy — only what the player needs is computed:
|
||||
|
||||
```
|
||||
Planet in systems.db (Layers 1+2 data committed)
|
||||
│
|
||||
├── Player within planet rendering range?
|
||||
│ → Regional biome overlay activates for atlas UI
|
||||
│
|
||||
├── Player within city footprint range?
|
||||
│ → Generate city district grid (Layer 3 / Phase 1)
|
||||
│ → Build DistrictMap in memory
|
||||
│
|
||||
├── Player within district range (chunk load radius)?
|
||||
│ → load_chunk() fires per chunk in radius
|
||||
│ → Layer 4 / Phase 2 generates tile data from BlockSkeleton + chunk_seed
|
||||
│
|
||||
└── Player at building door threshold?
|
||||
→ Interior descriptor + catalog loaded (door-per-edge)
|
||||
→ Interior tiles generated (Phase 4+)
|
||||
```
|
||||
|
||||
The `DistrictMap` resource is built when the player enters a city's footprint radius. Cities outside player range have no `DistrictMap` — only their `CityGenerationContext` in memory from startup DB read.
|
||||
|
||||
---
|
||||
|
||||
## Workshop Sections — What Needs Design
|
||||
|
||||
### Section 1: Layer 1 — Empty World
|
||||
|
||||
**Inputs:** 512×256 heightmap (from `generate_atlas.py`), 64×32 regional biome grid (from `generate_regional.py` Phase 3 Layer B), body definitions from systems.db.
|
||||
|
||||
**Required outputs:**
|
||||
- Sub-biome variant tag per regional cell (`sub_biome_variant` column on `atlas_regional_biomes`): 3-4 variants per biome class (e.g., `tropical` → `jungle_clearing | swamp | riverine_forest | lowland_marsh`)
|
||||
- `terrain_modification_cost: f32` per regional cell — effort required to settle here (high = forest/hills; low = plains/coast)
|
||||
- River network at regional resolution: where major rivers run, confluence points, river mouths
|
||||
- River confluence identification as stored point features (settlement attractors for Layer 2)
|
||||
- Mountain pass identification: regional cells with high `terrain_roughness` adjacent to significantly lower cells
|
||||
- Geographic feature tag layer for settlement placement guidance (Paula): `RiverConfluence`, `CoastalHarbor`, `MountainPass`, `ArablePlain`, `ResourceConcentration`, `Defensible`, `NaturalBarrier`
|
||||
|
||||
**Questions to design:**
|
||||
|
||||
**L1-Q1 (Architecture):** The existing heightmap rivers (markers.json `rivers` entries) are authored overrides and labels. The lead says real rivers come from seeded drainage patterns. What is the relationship between authored river markers and the generated drainage network? Options: (a) authored rivers are replaced entirely — generated network takes over and re-places names; (b) authored rivers are anchors — the generated network is seeded to match them where they exist and fills gaps elsewhere; (c) two separate systems at different resolutions — authored for atlas display, generated for walkable world. **This must be resolved before Layer 1 can be implemented.**
|
||||
|
||||
**L1-Q2 (Resolution):** At what resolution does the river network get stored? The 64×32 regional grid gives coarse river paths sufficient for Layer 2 settlement placement. If rivers are walkable obstacles at street level (Phase 5), their exact courses need to be at tile precision. Concrete choice: (a) regional-resolution only (64×32) — rivers are navigation constraints, not tile-level obstacles; (b) stored at intermediate resolution (e.g., 512×256 pixel path like atlas roads) — sufficient for both; (c) tile-resolution derived by SeedChain at Phase 5 time — keep stored data coarse. **Answer determines whether the river data belongs in systems.db or is seed-derived at Phase 5.**
|
||||
|
||||
**L1-Q3 (Stored vs. derived):** The generated river network is fully deterministic from the heightmap + seed. Does it get committed to systems.db (like the 64×32 biome grid), or is it derived at runtime (like districts)? Core tension: the river confluence points that anchor Layer 2 settlement placement must be queryable before any game session starts. This implies at minimum the confluence points are stored, even if the full river course is seed-derived.
|
||||
|
||||
**L1-Q4 (Biome prior):** Burnelli-Sheldon proposes using `bodies.economic_role` as a soft biome probability prior in sub-biome refinement — an agricultural world gets more arable cells, an extraction world gets more rugged terrain. Is this prior applied at sub-biome resolution (nudges the `sub_biome_variant` distribution) or left to `planet_class` alone? Low-risk to include; risk is self-contradictory worlds if excluded.
|
||||
|
||||
**Minimum viable Layer 1:**
|
||||
- `sub_biome_variant` column populated on `atlas_regional_biomes`
|
||||
- `terrain_modification_cost` column populated on `atlas_regional_biomes`
|
||||
- River confluence points stored as point features in systems.db (new `atlas_geographic_features` table)
|
||||
- Mountain pass identification (candidate pass cells tagged)
|
||||
- Geographic feature tags (Paula's taxonomy) attached to regional cells
|
||||
- Defer: tile-resolution river routing, exact lake polygons, authored river vs. generated network reconciliation
|
||||
|
||||
---
|
||||
|
||||
### Section 2: Layer 2 — Population Overlay
|
||||
|
||||
**Inputs:** Layer 1 output (regional biome + geographic feature tags + river network), atlas_cities (markers.json population anchors), atlas_roads/railroads (authored infrastructure), systems.db bodies + economics tables.
|
||||
|
||||
**Required outputs:**
|
||||
- `land_use` column on all regional cells: Urban | Agricultural | Industrial | Wilderness | Corridor | Ruins | Ocean | Impassable
|
||||
- `terrain_modification` signal per cell: what was here before settlement, what it became
|
||||
- Sub-settlement positions (all latent dynamic settlements placed, seed-derived)
|
||||
- Road graph (nodes + edges with `MaintenanceAuthority`): already designed in Phase 3 Layer C
|
||||
- `TerritorialStatus` per regional cell: CoreTerritory | FrontierTerritory | ExtractiveZone | ContestZone | WildernessBuffer | AbandonedZone
|
||||
- `PoliticalTether` per sub-city settlement: Administered | EconomicallyDependent | Contested | Independent | Corporate | Abandoned
|
||||
- `FoundingOrientation` per settlement: derived from geographic trigger that caused placement
|
||||
- `political_archetype` per city: derived from geographic trigger + economic data (override column for named cities)
|
||||
- Hinterland characterization per body: how the space between cities is filled
|
||||
|
||||
**Questions to design:**
|
||||
|
||||
**L2-Q1 (Architecture, lead must resolve):** Does "geography determines shape" mean city positions in markers.json are re-evaluated by Layer 2, or do they stay fixed as anchors with Layer 2 explaining them? The generator's behavior differs significantly: (a) positions anchored — `generate_regional.py` cannot write back to markers.json, only extends systems.db with geographic context; (b) positions re-derived — generator produces settlement positions from geographic attractors, uses markers.json population counts only, may place cities at different pixel positions. The lead's phrasing ("anchored by wiki population counts and markers.json") implies (a). But "where rivers meet, where rivers hit the sea, where arable land is — settlements anchor" implies the generator knows WHY each city is where it is. **Position: anchored with geographic explanation attached — but this must be confirmed.**
|
||||
|
||||
**L2-Q2 (Sub-settlement placement):** Given a body with `economic_role = extraction` and 3 qualifying corps, where exactly do the mining camps sit? The algorithm needs spatial precision for seeded placement. Proposed algorithm: camp_position = closest regional cell to corp's `location_id` asset that has `land_use ∈ {Industrial, Wilderness}`, at seeded distance from tether city within [15%–40%] of body scale. Does this hold? What's the camp count formula?
|
||||
|
||||
**L2-Q3 (Road algorithm):** The lead says "road/rail networks inter and intra regional." markers.json has authored road pixel-paths for named highway routes. Does Layer 2 generate additional roads (for sub-settlement connections) or does it only interpret existing authored roads? Proposed resolution: authored roads remain for named routes (setting decisions — Gate Corp built this highway); Layer 2 generates local roads connecting sub-settlements to their tether cities using the road graph algorithm from Phase 3 Layer C. Do participants agree?
|
||||
|
||||
**L2-Q4 (Hinterland shapes):** Burnelli-Sheldon proposes per-`economic_role` hinterland fill (agricultural: farmland + irrigation + processing nodes; extraction: access corridors + stockpile areas; transit: dense road/rail + relay stations). How is the spatial shape of farmland determined? Proposed rule: agricultural cells cluster around agricultural-role cities + along river corridors, at density inversely proportional to `terrain_modification_cost` from Layer 1. Rough terrain stays wilderness; gentle terrain becomes farmland. Is this sufficient, or does it need a Voronoi-style region-growing algorithm?
|
||||
|
||||
**L2-Q5 (TerritorialStatus derivation):** Paula establishes TerritorialStatus as a post-processing output of settlement placement + road generation. Confirm the derivation algorithm: CoreTerritory = high settlement density + well-maintained roads throughout; FrontierTerritory = settlements placed + roads thin at distance; ExtractiveZone = settlement at ResourceConcentration + corporate presence + excellent extraction corridor + absent off-corridor; ContestZone = two settlement clusters with competing road networks in overlapping territory; WildernessBuffer = no settlement placed; AbandonedZone = settlement placed but economic basis flagged as exhausted. What are the quantitative thresholds for each?
|
||||
|
||||
**L2-Q6 (Latent settlement flag storage):** Burnelli-Sheldon raises OQ-R4-B1: where does the settlement active/ghost flag live? Options: (a) as a sim-readable component on each settlement entity in the ECS; (b) in a systems.db Phase 3 output table (settlement positions + trigger conditions + activating corp/condition reference); (c) computed entirely at runtime from `corp_presence` + `corp_financial_state.health_metric`. Recommended answer is (b) — Phase 3 stores latent settlement positions + trigger conditions; sim reads them alongside `corp_financial_state`. This integrates with `generate_regional.py` naturally.
|
||||
|
||||
**Minimum viable Layer 2:**
|
||||
- `land_use` column on all regional cells
|
||||
- Sub-settlement placement: mining camps + trade waypoints (highest frequency, most game-relevant)
|
||||
- Road graph with `MaintenanceAuthority` per edge (Phase 3 Layer C already designed)
|
||||
- `TerritorialStatus` per regional cell
|
||||
- `political_archetype` per city (derived, override column)
|
||||
- `FoundingOrientation` per city (derived from geographic trigger)
|
||||
- Defer: exact agricultural node positions (land_use tag sufficient for minimum), shadow node placement, authored vs. generated road resolution
|
||||
|
||||
---
|
||||
|
||||
### Section 3: Layer 3 — City-Level Planning
|
||||
|
||||
**Inputs:** `CityGenerationContext` struct (from systems.db at game startup), Layer 1+2 regional data via systems.db, `world_seed`.
|
||||
|
||||
**Required outputs:**
|
||||
- N-district grid per city (district positions in city-local sim tiles)
|
||||
- `DistrictSkeleton` Phase 1 Stages 1-2 for each district:
|
||||
- Stage 1: WorldTier, ComplexityTier, SettingType, DistrictType, LayoutMode, `prosperity_baseline`, `perimeter_treatment`
|
||||
- Stage 2: 4×4 BlockSkeleton grid with ZoningType, `density_pct`, seed
|
||||
- District spatial arrangement shaped by `FoundingOrientation` + `political_archetype`
|
||||
- Prosperity gradient direction and magnitude across districts
|
||||
|
||||
**Questions to design:**
|
||||
|
||||
**L3-Q1 (District grid orientation):** The district count formula (D-C4) places N districts in a grid. `FoundingOrientation` implies the grid should be oriented: a PortFacing city has its LogisticsHub/Transit districts at the water-facing edge; a DefenseFacing city has its Administrative district at the elevated center. Does `FoundingOrientation` modify the district placement grid (spatial rotation/alignment), or does it only modify the prosperity gradient direction without changing grid positions? Gestalt identifies this as an open question — the brief should scope it: (a) spatial orientation change (more complex, more legible); (b) gradient direction only (simpler, less visually obvious).
|
||||
|
||||
**L3-Q2 (Political archetype as spatial arrangement):** Paula and Gestalt both flag that `political_archetype` should shape spatial ARRANGEMENT, not just type frequency. CompanyTown = spine (facility at one end, housing radiating back). AdminCapital = center (administrative hub, residential radiating outward). FreePort = multiple nodes (no single center). Does Layer 3 implement these spatial arrangement patterns explicitly, or do they emerge from the weight table and `FoundingOrientation` rules without explicit arrangement logic? Explicit arrangement produces more consistent legibility; emergent arrangement is cheaper to implement. The brief should specify which the workshop is designing.
|
||||
|
||||
**L3-Q3 (Arterial road layout):** Does City-Level Planning produce an explicit arterial road layout within the city, or are district boundary positions the implicit road network? Explicit arterials: requires a road placement algorithm as a Layer 3 output; produces a more realistic city; consumed by Layer 4 tile generation for street positioning. Implicit streets: district boundaries imply roads exist; Layer 4 generates streets at those positions; simpler. For the minimum slice: district boundary = implicit street. For richer cities: arterials first. The brief should state which is in scope for Round 1 of this workshop.
|
||||
|
||||
**L3-Q4 (Topographic constraints on zoning):** Burnelli-Sheldon proposes that topographic constraints from Layer 1 modify the 10×9 weight table: industrial zones weighted toward flat terrain near logistics access; residential zones weighted against flood plains; LogisticsHub always adjacent to road/rail entry points. How does Layer 3 access terrain roughness at city-local resolution? The Layer 1 data is at 64×32 regional resolution — each regional cell covers multiple districts. Is sampling the regional roughness at the city's center position sufficient, or does Layer 3 need finer terrain data per district?
|
||||
|
||||
**L3-Q5 (prosperity_baseline derivation formula):** Burnelli-Sheldon's formula:
|
||||
```
|
||||
base = economic_tier / 5.0
|
||||
role_modifier: extraction -0.1, research +0.15, service_mixed +0.1, frontier -0.2
|
||||
if stratified: district_i = base + role_modifier + (prestige_rank(i) - 0.5) × 0.7
|
||||
if moderate: district_i = clamp(N(base + role_modifier, 0.1), 0.2, 0.8)
|
||||
```
|
||||
`distribution_index` controls gradient magnitude; `FoundingOrientation` controls gradient direction. Does topography also contribute (hilltop districts = historically higher prosperity)? Burnelli-Sheldon recommends: topography sets gradient direction, `distribution_index` sets gradient magnitude. Do all participants agree?
|
||||
|
||||
**L3-Q6 (Sub-settlement depth — OQ-R4-G6):** The sub-settlement hierarchy includes Towns (1,000–10,000 population, "single-district equivalent"). Does a town go through the same DistrictSkeleton generation path as a city district, or does it get a simplified code path? A unified code path is architecturally cleaner; a simplified path is cheaper for its lower content budget. If unified: the town's `CityGenerationContext` has `district_count = 1`. If simplified: separate Rust codepath with a reduced struct.
|
||||
|
||||
**L3-Q7 (Port/station direction):** Burnelli-Sheldon proposes Transit and LogisticsHub districts are placed at the city edge closest to the orbital station (if one exists). Does Layer 3 receive directional information about nearby orbital stations? This requires knowing the orbital station's atlas pixel position relative to the city's position — feasible from systems.db. Is this in scope for the minimum viable slice, or deferred?
|
||||
|
||||
**Minimum viable Layer 3:**
|
||||
- District count + grid positions (D-C4 formula)
|
||||
- Phase 1 Stage 1 classification per district (WorldTier, ComplexityTier, DistrictType from weight table, LayoutMode, `prosperity_baseline`, `perimeter_treatment`)
|
||||
- Phase 1 Stage 2 block grid (4×4 BlockSkeletons)
|
||||
- `political_archetype` consumed from systems.db (Layer 2 output)
|
||||
- Defer: `FoundingOrientation` spatial grid orientation, explicit arterial layout, topographic constraints per district, Stages 3-5
|
||||
|
||||
---
|
||||
|
||||
### Section 4: Layer 4 — Street-Level Rendering
|
||||
|
||||
**Inputs:** `DistrictSkeleton` (Layer 3 output), `district_seed` (from SeedChain), `prosperity_current` (runtime economic state), `chunk_coord`.
|
||||
|
||||
**Required outputs:**
|
||||
- 64×64 `TileEntry` grids (`GeneratorChunkData`) per chunk, on demand
|
||||
- 3 canonical tile types: `floor_street`, `floor_interior`, `wall`
|
||||
- Tile condition state per tile: `Intact | Worn | Cracked | Broken` (economics-variable, NOT seed-locked)
|
||||
- Building footprints with door positions (door-per-edge boundary)
|
||||
|
||||
**Questions to design:**
|
||||
|
||||
**L4-Q1 (Economics-variable rendering mechanism — OQ-R4-G2):** The determinism rule says tile conditions are economics-variable. Three implementation options: (a) bake `prosperity_baseline` into tile conditions at generation time, update on `prosperity_current` change event; (b) compute tile conditions live from `prosperity_current` each render frame — no cache; (c) cache condition state per chunk, invalidate when `prosperity_current` crosses a threshold. Performance and implementation complexity differ significantly. The brief needs to choose: the workshop must specify which mechanism, including whether condition states are cached per chunk or computed live.
|
||||
|
||||
**L4-Q2 (Condition update trigger):** What triggers a tile condition recalculation? Options: (a) every time `prosperity_current` changes (potentially every tick); (b) when `prosperity_current` crosses defined thresholds (e.g., Intact ≥ 0.6, Worn 0.4–0.6, Cracked 0.2–0.4, Broken < 0.2); (c) periodic recalculation (e.g., every 100 ticks). For performance: threshold crossings are correct — most districts are stable most of the time and require no recalculation.
|
||||
|
||||
**L4-Q3 (Phase 2 tile algorithm — confirmed):** Street skeleton first (door-per-block-edge defines connectivity), then building fill (density-driven rectangles within block). `density_pct` from BlockSkeleton drives building coverage. High density = packed buildings with minimal street width; low density = open space. This is confirmed from Rounds 2-3. The workshop should design the algorithm parameters: minimum street corridor width, maximum building rectangle dimensions, gap rules at block edges.
|
||||
|
||||
**L4-Q4 (Interior generation trigger — OQ-R4-G5 partial):** Door-per-edge is the confirmed building boundary. What triggers interior tile generation? Options: (a) when player entity is within N tiles of any door — pre-generates the interior before the player crosses; (b) when player entity crosses the door threshold — generates on entry; (c) pre-generated with the exterior chunk but not rendered until entered. Option (a) avoids a generation stall at the door but wastes compute for doors the player passes. Option (b) is simplest. The brief should specify.
|
||||
|
||||
**L4-Q5 ("Scatter civilization" scope):** The lead says "scatter civilization into tiles." Beyond floor/wall canonical types, what does Layer 4 place? Candidates: (a) prop spawn points (furniture, vehicles, crates) — entity system populates at runtime from these points; (b) surface decals (signage, paint) — tile variants or overlay tiles; (c) entity spawn points for NPCs. The brief should specify which of these Layer 4 is responsible for vs. what is runtime entity system placement.
|
||||
|
||||
**L4-Q6 (prosperity_delta architecture — Paula):** Paula proposes that `prosperity_index` must have two components to enable the "ghost city effect": a seed-locked `prosperity_baseline` (what the district was planned as) and a sim-driven `prosperity_delta` (what current economic state says relative to the plan). The effective prosperity = `prosperity_baseline + prosperity_delta` (clamped to [0.0, 1.0]). The gap between these two values IS the narrative: planned-for-wealth but now-poor; planned-for-rough but now-thriving. This requires the rendering system to have access to BOTH values. Does the workshop adopt this two-field model explicitly?
|
||||
|
||||
**Minimum viable Layer 4:**
|
||||
- 3 tile types generating a walkable, navigable space
|
||||
- Seed-locked layout from `district_seed` via FNV-1a
|
||||
- Chunk streaming hookup: `load_chunk()` calls tile generation via `DistrictMap`
|
||||
- `prosperity_baseline` drives initial building density selection
|
||||
- Defer: tile condition overlays (economics-variable rendering) — implement after walkable world
|
||||
- Defer: building interior generation beyond door-per-edge boundary
|
||||
- Defer: prop/decal scatter, entity spawn points
|
||||
|
||||
---
|
||||
|
||||
### Section 5: Cross-Layer Architecture
|
||||
|
||||
**Questions that span multiple layers:**
|
||||
|
||||
**CL-Q1 (`prosperity_baseline` vs. `prosperity_current` naming — Gestalt OQ-R4-G3):** The workshop must confirm these are TWO DISTINCT FIELDS with distinct names, never conflated:
|
||||
- `prosperity_baseline: f32` — Layer 3 output, seed-locked, stored in `CityGenerationContext` / atlas tables. What the district was economically designed for.
|
||||
- `prosperity_current: f32` — Runtime simulation state. What the district's current economic health is. Updated by the economic simulation.
|
||||
- `prosperity_delta: f32` — Optional derived field (`prosperity_current - prosperity_baseline`). Enables the ghost city effect.
|
||||
|
||||
**CL-Q2 (Regional land-use evolution vs. city-geometry lock):** Burnelli-Sheldon identifies this as OQ-R4-B2: when agricultural corp expands and new farmland appears, does this register as (a) a ChunkMutations entry in the save file, or (b) a runtime update to `regional_land_use` in the biome grid at coarse resolution (not a full regen-db)? The proposed answer: Layer 2 regional land-use changes at biome-cell resolution are a runtime parameter read by the Layer 4 renderer to determine what large-scale tile type fills undeveloped hinterland. This is NOT ChunkMutations (which are reserved for player-caused or explicit-event damage at tile precision). The brief needs to confirm this resolution boundary.
|
||||
|
||||
**CL-Q3 (Ruin lifecycle):** Paula proposes AbandonedZone settlements follow a deterministic decay path: prosperity_delta drops sharply → perimeter treatment degrades (Checkpoint → Fenced → Open) → landmark condition deteriorates. When does the economic simulation flag a settlement for ruination — (a) when `corp_financial_state.health_metric` drops below threshold (emergent); (b) when a lead-authored event fires (override); or (c) both, with sim as default and authored events as override? All three domain files suggest (c).
|
||||
|
||||
**CL-Q4 (Authored rivers vs. drainage network — L1-Q1 elevated):** This question spans Layers 1 and 2 and must be resolved before either layer can be designed fully. The existing markers.json `rivers` arrays are authored polylines. The lead wants rivers from seeded drainage patterns with names re-placed based on water sim. This requires a reconciliation pass. The workshop should specify: (a) what the drainage algorithm produces; (b) how authored river names are matched to generated courses; (c) whether markers.json rivers are ever modified by `generate_regional.py` or whether the generated drainage is parallel data in systems.db only.
|
||||
|
||||
---
|
||||
|
||||
## Workshop Format
|
||||
|
||||
**3 rounds:**
|
||||
|
||||
### Round 1 — Inventory and Framing
|
||||
|
||||
Each participant examines all open questions from their domain perspective. For each layer, state: what algorithm exists or can be reused, what is genuinely novel design work, what is a data/schema question only. Identify which questions are prerequisite (later questions depend on earlier answers) and which are independent.
|
||||
|
||||
Write findings to `docs/workshops/planet-down-cascade/{agent}-round1.md`.
|
||||
|
||||
### Round 2 — Algorithm Proposals
|
||||
|
||||
Based on Round 1, each participant proposes concrete algorithms for their domain questions. Proposals must specify: inputs, outputs, data format, whether code goes in Python tooling or Rust runtime. For any question where participants disagree, state the disagreement clearly — do not average positions.
|
||||
|
||||
Write proposals to `docs/workshops/planet-down-cascade/{agent}-round2.md`.
|
||||
|
||||
### Round 3 — Convergence and D-Records
|
||||
|
||||
Review all proposals. Lock algorithm choices. Produce D-record candidates. Identify remaining open questions that need lead resolution. Produce the implementation ticket dependency chain.
|
||||
|
||||
Write final positions to `docs/workshops/planet-down-cascade/{agent}-round3.md`.
|
||||
|
||||
---
|
||||
|
||||
## Required Reading
|
||||
|
||||
Before Round 1, all participants must read:
|
||||
|
||||
**Workshop outputs (carry-forwards):**
|
||||
- `docs/workshops/generation-cascade/round-1-notes.md` — layer inventory and consensus from prior workshop
|
||||
- `docs/workshops/generation-cascade/round-2-notes.md` — D-record candidates (D-C1 through D-C8 proposals)
|
||||
- `docs/workshops/generation-cascade/tyre-round3.md` — Phase 3 technical layers (generate_regional.py design)
|
||||
|
||||
**Code (what exists):**
|
||||
- `tooling/planet-gen/planet_simulation.py` — terrain simulation: elevation, temperature, moisture, biomes, rivers
|
||||
- `tooling/planet-gen/generate_atlas.py` — city placement, infrastructure, output format
|
||||
- `server/src/simulation/generator.rs` — DistrictSkeleton and all types (stubs)
|
||||
- `server/src/simulation/chunk_streaming.rs` — chunk load/unload architecture
|
||||
|
||||
**Decisions:**
|
||||
- `decisions/architecture.md` — D-010 (determinism rule), D-020 (client-server), D-031 (time system)
|
||||
- `CLAUDE.md` §Development Cascade — the phase definitions and cascade order
|
||||
|
||||
**Domain requirements (Round 4 inputs):**
|
||||
- `docs/workshops/generation-cascade/gestalt-round4.md` — systems design questions per layer
|
||||
- `docs/workshops/generation-cascade/paula-round4.md` — narrative requirements, TerritorialStatus, ghost city effect
|
||||
- `docs/workshops/generation-cascade/burnelli-sheldon-round4.md` — economics integration, latent settlements, determinism split
|
||||
|
||||
---
|
||||
|
||||
## Expected Outputs
|
||||
|
||||
1. **Algorithm specifications** for each of the 4 cascade layers — sufficient detail for a developer to implement without further design work
|
||||
2. **Data format handoffs** per layer — what goes in systems.db (Python tooling), what is seed-derived at runtime (Rust), what is a runtime simulation variable
|
||||
3. **D-records** in `decisions/` for all locked decisions (targeting D-C1 through D-C12 candidates from prior workshop plus new ones from this workshop)
|
||||
4. **Implementation ticket dependency chain** — what to build first, what is blocked behind what, which prior tickets (#899 et al.) need rescoping against the new layer model
|
||||
5. **Resolved open questions** — specifically: L1-Q1 (authored vs. drainage rivers), L2-Q1 (anchor vs. re-derive city positions), L3-Q1 (FoundingOrientation spatial effect), L4-Q1 (economics rendering mechanism), CL-Q1 (prosperity_baseline vs. current naming), CL-Q2 (regional land-use evolution resolution boundary)
|
||||
|
||||
---
|
||||
|
||||
## Participant Roles
|
||||
|
||||
| Participant | Primary domain | Layer focus |
|
||||
|-------------|---------------|-------------|
|
||||
| **Gestalt** | Systems design, fun factor, mechanical decisions | Cross-layer — evaluates each layer for interesting player choices and mechanical coherence |
|
||||
| **Tyre** | Technical architecture, feasibility, algorithm design | All layers — owns algorithm concreteness, data formats, Python vs. Rust split, performance |
|
||||
| **Paula** | Narrative legibility, political depth | Layers 1-2 (geographic triggers → political structure) + the ghost city effect at Layer 4 |
|
||||
| **Burnelli-Sheldon** | Economics simulation, quantitative modeling | Layers 2-3 (economics onto geography, zoning weights) + determinism/rendering split |
|
||||
@@ -0,0 +1,486 @@
|
||||
---
|
||||
title: "Round 1 — Burnelli-Sheldon: Economics Inventory and Amendment 6 Analysis"
|
||||
description: "Economics domain inventory for the planet-down cascade workshop. Question triage, Amendment 6 weight table critique with proposed alternative, and economic field mapping per layer."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: burnelli-sheldon
|
||||
round: 1
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Round 1 — Burnelli-Sheldon: Economics Inventory and Framing
|
||||
|
||||
This document is my Round 1 contribution to the planet-down cascade workshop. I'm
|
||||
working from the Brief, the Consultant Review amendments, the four prior generation-cascade
|
||||
rounds, and the existing systems.db schema. My job this round: inventory the open
|
||||
questions from an economics perspective, triage them by difficulty and prerequisite
|
||||
structure, and deliver a concrete alternative to the 10×9 weight table that Amendment 6
|
||||
has unlocked for revision.
|
||||
|
||||
---
|
||||
|
||||
## 1. What the Amendments Change for My Domain
|
||||
|
||||
Before the question inventory, the amendments require me to update my prior assumptions.
|
||||
|
||||
### Amendment 1 (Three-Tier Execution Model) — Clean Win for Economics
|
||||
|
||||
The brief had conflated "Layer 1-2 stored in systems.db" with "economic sim needs Layer
|
||||
1-2 data." Amendment 1 correctly separates them:
|
||||
|
||||
- **Economic simulation runs on system-level aggregates** — `economic_tier`, `corp_presence`,
|
||||
`corp_financial_state.health_metric`, commodity flows, gate connectivity. All of this
|
||||
is already in systems.db from build-time generation. The sim does not need to know
|
||||
where a mining camp sits on a planetary surface to compute inter-system commodity
|
||||
pricing.
|
||||
|
||||
- **Layer 1-2 spatial data** — drainage, settlement positions, road graphs, TerritorialStatus —
|
||||
is consumed only by Layer 3 city planning and Layer 4 tile rendering. Neither of those
|
||||
feeds back into the economic simulation; they read from it.
|
||||
|
||||
This is the correct separation. My Round 4 mapping of "economic fields at Layer 2" still
|
||||
holds — those fields are read by the Layer 2 generator as inputs to settlement placement.
|
||||
But the Layer 2 generator is now a runtime background process reading from systems.db
|
||||
snapshots, not a Python batch process. The economics data it reads is already there.
|
||||
|
||||
**What this changes:** `generate_regional.py` as designed in Tyre's Round 3 becomes a
|
||||
reference/validation tool. The algorithms I propose must be implementable in Rust on a
|
||||
background thread, without Python-specific dependencies.
|
||||
|
||||
### Amendment 3 (Fully Generative Placement) — New Question Required
|
||||
|
||||
L1-Q1, L2-Q1, and CL-Q4 (all variants of "authored rivers vs. generated network" and
|
||||
"anchor vs. re-derive city positions") are eliminated. Every city position is now
|
||||
generator-derived. This adds two new questions:
|
||||
|
||||
- **Attractor-matching algorithm**: N named cities with known economic roles must be
|
||||
matched to M geographic attractors. This is my primary Layer 2 algorithm contribution.
|
||||
- **Name reservation fulfillment**: How does the generator know which city name resolves
|
||||
to which generated position? This is an architecture question (Tyre) with economics
|
||||
constraints (I need to flag which cities are "locked" because corporate cross-references
|
||||
in systems.db point to them by name).
|
||||
|
||||
### Amendment 6 (10×9 Weight Table Unlocked) — See §4 Below
|
||||
|
||||
The weight table is no longer a given fact. The lead's challenge is correct and I'll
|
||||
address it in full in §4.
|
||||
|
||||
---
|
||||
|
||||
## 2. Question Inventory — By Status
|
||||
|
||||
### Eliminated (no workshop resolution needed)
|
||||
|
||||
| Question | Why eliminated |
|
||||
|----------|---------------|
|
||||
| L1-Q1 | Amendment 3: rivers are fully generated, no authored network |
|
||||
| L2-Q1 | Amendment 3: all city positions are generator-derived |
|
||||
| CL-Q4 | Amendment 3: same as L1-Q1 |
|
||||
|
||||
### Economics Primary — I Own These
|
||||
|
||||
| Question | Type | Prerequisite for |
|
||||
|----------|------|-----------------|
|
||||
| **L1-Q4** (biome probability prior) | Novel design | Nothing downstream blocks on this; pure quality improvement |
|
||||
| **L2-Q2** (sub-settlement placement algorithm) | Novel design + data schema | L2-Q6 (flag storage), Layer 3 corp district intensity |
|
||||
| **L2-Q4** (hinterland shape algorithm) | Novel design | Land use grid → Layer 3 city context |
|
||||
| **L2-Q5** (TerritorialStatus thresholds) | Novel design (quantitative) | Paula's political framing; snapshot accuracy |
|
||||
| **L2-Q6** (latent settlement flag storage) | Data/schema | Runtime active/ghost status across all settlement types |
|
||||
| **L3-Q4** (topographic constraints on zoning) | Novel design | Blocks L3-Q5 derivation unless we agree topo is advisory only |
|
||||
| **L3-Q5** (prosperity_baseline formula) | Novel design (quantitative) | Layer 3 minimum viable output |
|
||||
| **L3-Q7** (port/station direction) | Data/schema | Deferred — not min viable scope |
|
||||
| **Amendment 6** (district mix alternative) | Novel design | ALL of Layer 3 — this is the zoning foundation |
|
||||
| **New: Attractor-matching algorithm** | Novel design | L2-Q2 depends on it |
|
||||
|
||||
### Economics Advisory — I Contribute, Others Lead
|
||||
|
||||
| Question | Who leads | My input |
|
||||
|----------|-----------|---------|
|
||||
| L3-Q1 (FoundingOrientation grid orientation) | Gestalt | prosperity gradient direction = my input |
|
||||
| L3-Q2 (political archetype spatial arrangement) | Paula | economic role shapes arrangement alongside political type |
|
||||
| L3-Q6 (town vs. city code path) | Tyre | unified path preferred — I have no economics objection |
|
||||
| L4-Q1 (economics-variable rendering mechanism) | Tyre + Gestalt | my input: threshold-crossing, not per-tick |
|
||||
| L4-Q2 (condition update trigger) | Tyre | my input: threshold thresholds I can specify |
|
||||
| L4-Q6 (prosperity_delta architecture) | Paula | I co-own the field definition |
|
||||
| CL-Q1 (prosperity_baseline vs. current naming) | All | I proposed the field names; agree to lock them |
|
||||
| CL-Q2 (regional land-use evolution) | Tyre | I proposed the resolution boundary in R4-B2 |
|
||||
| CL-Q3 (ruin lifecycle) | Paula | Amendment 5: emergent only, no dedicated mechanics |
|
||||
|
||||
### Independent (do not block other questions)
|
||||
|
||||
L1-Q4, L3-Q7, and CL-Q3 can all be answered or deferred without blocking anything else.
|
||||
|
||||
### Prerequisite Questions (must resolve first)
|
||||
|
||||
1. **Amendment 6 / district mix** — blocks all of Layer 3 algorithm design. Everything
|
||||
about zoning, prosperity gradients, and block skeletons depends on knowing the
|
||||
district type distribution method.
|
||||
2. **Attractor-matching algorithm** (new from Amendment 3) — blocks L2-Q2 sub-settlement
|
||||
placement, since camps are placed relative to tether cities, and tether cities are now
|
||||
generator-placed.
|
||||
3. **CL-Q1** (field naming lock) — blocks all Layer 3 and Layer 4 algorithm descriptions
|
||||
that need to reference these fields.
|
||||
|
||||
---
|
||||
|
||||
## 3. What Algorithms Exist vs. What Is Novel
|
||||
|
||||
### Reusable from Prior Work
|
||||
|
||||
**From Round 3 / Round 4 — economic field mapping:**
|
||||
The `bodies`, `system_economy`, `corp_presence`, `system_fiscal`, `corp_financial_state`
|
||||
table reads are defined and stable. The 6-field minimum read set for `CityGenerationContext`
|
||||
is confirmed. These are not novel design work — they are schema reads.
|
||||
|
||||
**From Round 4 — determinism split:**
|
||||
The layout-vs-appearance split is confirmed. Seed-locked = positions. Economics-variable =
|
||||
tile conditions, lighting, activity states. This is done. CL-Q2 (regional land use
|
||||
resolution boundary) I recommended in Round 4-B2: coarse biome-cell resolution can update
|
||||
at runtime; city-internal layout never changes. I hold this position.
|
||||
|
||||
**From Round 4 — latent settlement principle:**
|
||||
Settlement positions are seed-locked at Layer 2. Active/ghost status is runtime
|
||||
economic sim state. The four settlement types (mining camps, trade waypoints,
|
||||
agricultural nodes, shadow nodes) and their trigger conditions are specified. This
|
||||
is not novel design work for Round 1 — it's a carry-forward.
|
||||
|
||||
**From the brief — D-C4 district count formula:**
|
||||
`max(1, floor(city.population / 50_000))`, log-scaled, WorldTier caps, capital bonus.
|
||||
This is locked and reusable.
|
||||
|
||||
### Novel Design Work Required This Round
|
||||
|
||||
**1. District mix algorithm (Amendment 6)** — the primary economics deliverable for
|
||||
this workshop. See §4 for my full proposal.
|
||||
|
||||
**2. Attractor-matching algorithm** — new question from Amendment 3. Economic role
|
||||
determines which geographic attractor is the right match for each city. I have a
|
||||
framework for this (comparative advantage matching); see §5.
|
||||
|
||||
**3. Prosperity gradient formula (L3-Q5)** — the formula is sketched in the brief
|
||||
but needs topographic interaction defined. My proposal: topography sets gradient
|
||||
DIRECTION, `distribution_index` sets gradient MAGNITUDE. A stratified city on a hill
|
||||
has elite zones at the top with a steep gradient. A moderate city on a hill has mixed
|
||||
zoning across elevations with a shallow gradient.
|
||||
|
||||
**4. TerritorialStatus thresholds (L2-Q5)** — quantitative thresholds need values.
|
||||
Paula owns the taxonomy; I can supply the economic proxies for each tier.
|
||||
|
||||
### Data/Schema Questions Only (no algorithm)
|
||||
|
||||
**CL-Q1** — just a naming lock. `prosperity_baseline`, `prosperity_current`,
|
||||
`prosperity_delta` (derived). I hold the definitions from my Round 4 document.
|
||||
|
||||
**L2-Q6** — latent settlement flag storage. My Round 4 recommendation: option (b),
|
||||
Phase 3 output table. In the three-tier model, this becomes a runtime background
|
||||
generation output stored in session DB, not systems.db. No change to the algorithm;
|
||||
just where the output lands.
|
||||
|
||||
**L3-Q7** — port/station direction. Feasible from systems.db `stations` table
|
||||
(`orbits_body_id` + atlas body grid coordinates). Recommended deferred for minimum
|
||||
viable scope.
|
||||
|
||||
---
|
||||
|
||||
## 4. Amendment 6: Why the 10×9 Table Is Wrong and What Replaces It
|
||||
|
||||
The lead's challenge is economically exact. I'll state the theorem clearly, then
|
||||
propose the replacement.
|
||||
|
||||
### The Theorem: Concentrated Labor Creates Service Demand
|
||||
|
||||
When workers concentrate in a settlement, service industries emerge to serve them —
|
||||
regardless of what those workers produce. This is not optional. It is a consequence
|
||||
of basic human needs. Miners need food, clothing, medical care, and — critically —
|
||||
social venues. They drink. They gamble. They fight and need someone to stitch them up.
|
||||
The company may own the tavern (company town), the union may run the recreation hall
|
||||
(labour-movement town), or it may be informal (frontier dive), but the entertainment
|
||||
function exists.
|
||||
|
||||
The 10×9 table violates this theorem by assigning zero weight to entertainment for
|
||||
mining, extraction, and energy economic roles. A real mining town with zero entertainment
|
||||
establishments either just opened yesterday or is a forced-labour camp. Neither is the
|
||||
default.
|
||||
|
||||
What the table actually encodes is what the settlement *produces*. What it should encode
|
||||
is *how* the settlement configures the services it necessarily has.
|
||||
|
||||
### The Three-Component Replacement
|
||||
|
||||
**Component 1: Population Tier — Baseline District Guarantees**
|
||||
|
||||
Every inhabited settlement receives a minimum set of district types determined by
|
||||
population. These are non-negotiable floors because the economics of human settlement
|
||||
make them inevitable:
|
||||
|
||||
| Settlement Tier | Population | Guaranteed District Types |
|
||||
|----------------|-----------|--------------------------|
|
||||
| Outpost | 50–999 | Residential + Mixed (combined commerce/basic services) |
|
||||
| Town | 1,000–9,999 | + Commercial (dedicated) |
|
||||
| City | 10,000–99,999 | + Entertainment (character varies by role) |
|
||||
| Large City | 100,000+ | + Administrative (dedicated) |
|
||||
|
||||
These floors apply *before* the economic role weights are applied. If D-C4 formula
|
||||
gives a 1-district city, the single district is Mixed (it carries all functions). If
|
||||
it gives 2 districts, one is Residential and one is Mixed-plus-specialty. At 3+
|
||||
districts, dedicated Commercial and Entertainment emerge as separable entities.
|
||||
|
||||
Note: this integrates cleanly with the D-C4 district count formula. The guarantee
|
||||
is not "always build 5 districts." It is "whatever districts you DO build, allocate
|
||||
these types first before filling with production-role specialty."
|
||||
|
||||
**Component 2: Economic Role — Multiplier on Weight, Not On/Off Switch**
|
||||
|
||||
Once the guaranteed minimums are placed, the remaining district slots are filled using
|
||||
a weight multiplier table. Key change from the 10×9 table: **no weight is ever zero**.
|
||||
Minimum weight is 0.2, meaning "present but small/rough." Maximum is 3.0. This encodes
|
||||
economic role as a proportion and character modifier, not a presence/absence gate.
|
||||
|
||||
Proposed multiplier table (replaces the 10×9 weight table):
|
||||
|
||||
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|
||||
|--------------|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| manufacturing | 1.5 | 0.4 | 2.5 | 0.5 | 1.5 | 0.4 | 0.6 | 0.5 | 0.2 |
|
||||
| agricultural | 2.0 | 0.8 | 0.4 | 0.8 | 1.8 | 0.5 | 1.2 | 0.4 | 0.2 |
|
||||
| extraction | 1.5 | 0.4 | 2.5 | 0.3 | 2.0 | 0.5 | 0.8 | 0.7 | 0.2 |
|
||||
| transit | 0.8 | 1.5 | 0.4 | 0.4 | 1.8 | 1.0 | 1.2 | 2.0 | 0.2 |
|
||||
| research | 1.5 | 0.5 | 0.4 | 1.2 | 0.4 | 0.6 | 0.8 | 0.2 | 2.5 |
|
||||
| commercial | 1.2 | 2.5 | 0.4 | 0.5 | 0.8 | 1.2 | 1.2 | 0.3 | 0.2 |
|
||||
| service_mixed | 1.8 | 1.5 | 0.4 | 0.8 | 0.5 | 1.2 | 1.5 | 0.2 | 0.2 |
|
||||
| mining | 1.5 | 0.4 | 2.0 | 0.2 | 2.5 | 0.5 | 0.7 | 0.5 | 0.2 |
|
||||
| frontier | 2.5 | 0.8 | 0.8 | 0.4 | 1.5 | 0.5 | 1.2 | 0.2 | 0.2 |
|
||||
| energy | 0.8 | 0.2 | 1.5 | 0.5 | 2.0 | 0.2 | 0.5 | 0.5 | 2.5 |
|
||||
|
||||
The mining row now has Ent = 0.5 (half baseline, but present) and Com = 0.4.
|
||||
The energy row has Ent = 0.2 (minimal, but not zero). A large energy facility has
|
||||
workers, and those workers have somewhere to socialize — it's just a spartan canteen
|
||||
and a gym, not a cultural district.
|
||||
|
||||
**How to apply the multiplier table:**
|
||||
1. Apply population tier guarantees. Mark those district slots as filled.
|
||||
2. Remaining district slots = D-C4 total count minus guaranteed minimums.
|
||||
3. Normalize the multiplier table row for this city's economic role to sum to 1.0.
|
||||
4. Distribute remaining slots by normalized weights, rounding to integers.
|
||||
5. Apply `political_archetype` modifiers on top (existing mechanic, unchanged).
|
||||
6. Apply `distribution_index` to set the prosperity gradient magnitude across the
|
||||
resulting district set.
|
||||
|
||||
**Component 3: Settlement Age — Character Modifier**
|
||||
|
||||
`bodies.founding_age_years` tells us how long a settlement has had to develop its
|
||||
services. New settlements are production-focused and sparse. Old settlements have
|
||||
had time for secondary and tertiary industries to emerge.
|
||||
|
||||
I do NOT propose that age changes district count (that's population-driven) or district
|
||||
presence (that's the guarantee floor). Age modifies the **character and quality** of
|
||||
districts within a given type:
|
||||
|
||||
| founding_age_years | Character effect |
|
||||
|-------------------|-----------------|
|
||||
| < 50 (new) | Entertainment = bare-minimum (company rec hall or single dive bar) |
|
||||
| 50–150 (established) | Entertainment = working-class functional (taverns, union halls, local sports) |
|
||||
| 150–300 (mature) | Entertainment = differentiated (high/low-end venues, cultural institutions beginning) |
|
||||
| 300+ (old) | Entertainment = layered (heritage venues, class-stratified nightlife, institutions) |
|
||||
|
||||
This is a character descriptor attached to the district, not a structural change to
|
||||
district count or type. The renderer and content system read it to choose tile variants
|
||||
and NPC archetypes. The generator doesn't need to do extra layout work for this.
|
||||
|
||||
### The Combined Guarantee: No Zero-Service Settlements
|
||||
|
||||
Under this model:
|
||||
- A mining outpost (population 200) has: Residential + Mixed. The Mixed district is
|
||||
the supply store, the first-aid shack, and the bar, packed into one cluster.
|
||||
- A mining town (population 5,000) has: Residential + Commercial + Entertainment
|
||||
(guaranteed) + Ind + Log (from weight table, dominant). The entertainment district
|
||||
is rough bars and a gambling hall, not a theatre.
|
||||
- A mining city (population 80,000) has 1–2 Industrial, 1–2 Logistics, 1 Residential,
|
||||
1 Commercial, 1 Entertainment (rough but present), 0–1 Mixed from remaining slots.
|
||||
That's 6–7 districts. All production-role character, but no zero-service gap.
|
||||
|
||||
The lead's intuition is exactly right: mining towns have rough entertainment because
|
||||
miners drink. This model produces that outcome mechanically.
|
||||
|
||||
---
|
||||
|
||||
## 5. New Question: Attractor-Matching Algorithm
|
||||
|
||||
Amendment 3 eliminates authored city positions and requires the generator to match
|
||||
N named cities (with known economic roles) to M geographic attractors. From an
|
||||
economics standpoint, this is a constraint satisfaction problem with a clear
|
||||
theoretical framework: comparative advantage assignment.
|
||||
|
||||
**The economic principle:** Each city has an economic role that implies a preferred
|
||||
attractor type. Agricultural cities prefer arable plains + water access. Mining cities
|
||||
prefer resource concentration sites + transport. Transit cities prefer natural
|
||||
chokepoints — mountain passes, river crossings, harbor narrows.
|
||||
|
||||
**Proposed matching algorithm sketch:**
|
||||
|
||||
```
|
||||
For each body_id with N named cities in atlas_cities:
|
||||
1. Run Layer 1 to completion: get attractor set M with typed features
|
||||
(RiverConfluence, CoastalHarbor, MountainPass, ArablePlain,
|
||||
ResourceConcentration, Defensible, NaturalBarrier)
|
||||
2. Score matrix: S[city_i, attractor_j] = compatibility(city.economic_role, attractor.feature_type)
|
||||
3. Hard constraints: if corporations.headquarters_body = city, that city must be
|
||||
placed at an attractor compatible with its economic role (name-locked)
|
||||
4. Solve: maximize sum of compatibility scores subject to one-city-per-attractor
|
||||
(Hungarian algorithm or greedy by largest gap if N << M)
|
||||
5. Overflow: if M < N (more cities than attractors), generate synthetic attractors
|
||||
as secondary sites (river bends, coastal plains) by seed
|
||||
```
|
||||
|
||||
**Compatibility matrix (partial — sufficient for framing):**
|
||||
|
||||
| economic_role | RiverConfl. | CoastalHarbor | MtnPass | ArablePlain | ResourceConc. | Defensible |
|
||||
|--------------|-------------|--------------|---------|-------------|---------------|------------|
|
||||
| agricultural | high | medium | low | very high | low | low |
|
||||
| extraction | low | low | medium | low | very high | medium |
|
||||
| mining | low | low | medium | low | very high | medium |
|
||||
| transit | high | high | high | low | low | medium |
|
||||
| commercial | high | high | medium | medium | low | low |
|
||||
| research | medium | medium | medium | medium | medium | high |
|
||||
| manufacturing | medium | medium | low | medium | medium | low |
|
||||
| frontier | medium | medium | medium | medium | medium | high |
|
||||
|
||||
The attractor-matching algorithm is a Layer 2 entry point. It runs after Layer 1
|
||||
completes for a body and before Layer 2 sub-settlement placement. Its output is:
|
||||
a mapping from (city_id → attractor_position) that seeds the rest of Layer 2.
|
||||
|
||||
This question is new and needs Round 2 algorithm concreteness. For now I'm flagging
|
||||
the economic framework and the constraint structure.
|
||||
|
||||
---
|
||||
|
||||
## 6. Economics Field Mapping — Revised for Three-Tier Model
|
||||
|
||||
My Round 4 field mapping remains correct in content. What changes under Amendment 1
|
||||
is *when* each field is accessed:
|
||||
|
||||
### Build-Time (systems.db — always available)
|
||||
|
||||
| Field | Table | Purpose |
|
||||
|-------|-------|---------|
|
||||
| `economic_role` | bodies | Layer 1 biome prior (advisory); Layer 2 hinterland; Layer 3 weight table |
|
||||
| `settlement_pattern` | bodies | Layer 2 population distribution |
|
||||
| `founding_age_years` | bodies | Layer 3 district character modifier |
|
||||
| `economic_tier` | system_economy | Infrastructure density (Layer 2), prosperity baseline (Layer 3) |
|
||||
| `distribution_index` | system_economy | Prosperity gradient magnitude (Layer 3) |
|
||||
| `corp_id`, `location_id`, `primary_operation` | corp_presence | Layer 2 settlement triggers; Layer 3 district intensity |
|
||||
| `health_metric` | corp_financial_state | Layer 2 settlement active/ghost seed state |
|
||||
| `collection_efficiency` | system_fiscal | Layer 2 shadow node trigger |
|
||||
| `behavioral_archetype` | corporations | Layer 3 district layout character |
|
||||
| `headquarters_body` | corporations | Name-locked city constraint (Amendment 3) |
|
||||
|
||||
### Runtime Background (Layer 1-2 generation, Rust)
|
||||
|
||||
Layer 1-2 reads from systems.db fields above and produces:
|
||||
- Attractor positions (Layer 1 output, consumed by Layer 2)
|
||||
- Settlement positions (Layer 2 output, stored in session DB)
|
||||
- TerritorialStatus and PoliticalTether (Layer 2 output)
|
||||
- Land use grid (Layer 2 output)
|
||||
|
||||
These are not economics tables — they are geography outputs that economics data
|
||||
shapes. The economic sim does not consume them.
|
||||
|
||||
### Runtime On-Demand (Layer 3-4 generation, Rust)
|
||||
|
||||
`CityGenerationContext` struct is populated from systems.db at startup — no runtime
|
||||
query. Layer 3 receives it and uses `economic_role`, `economic_tier`, `distribution_index`,
|
||||
`corp_presence` count, and `headquarters_system/body` match. That is the 6-field minimum
|
||||
read set, unchanged.
|
||||
|
||||
### Runtime Simulation Variables (read by Layer 4 renderer)
|
||||
|
||||
| Signal | Source | Effect |
|
||||
|--------|--------|--------|
|
||||
| `prosperity_current` | Economic simulation | Tile condition selection |
|
||||
| `health_metric` (updated) | Economic simulation | Corporate signage, maintenance state |
|
||||
| Active/ghost status | Economic simulation | Lighting, activity density |
|
||||
|
||||
These are not consumed by the generator. They are render parameters updated by the sim.
|
||||
|
||||
---
|
||||
|
||||
## 7. TerritorialStatus Thresholds (L2-Q5) — Quantitative Proposal
|
||||
|
||||
Paula owns the taxonomy. Here are the economic proxies I propose for threshold
|
||||
derivation:
|
||||
|
||||
| TerritorialStatus | Economic derivation |
|
||||
|------------------|---------------------|
|
||||
| **CoreTerritory** | settlement_density ≥ 1 city per province + road maintenance_authority on all connecting edges |
|
||||
| **FrontierTerritory** | settlement present but road density < 0.5 of CoreTerritory average for this economic_tier |
|
||||
| **ExtractiveZone** | `ResourceConcentration` feature + `corp_presence` with matching commodity + road to resource but sparse off-corridor |
|
||||
| **ContestZone** | Two settlement clusters within overlapping territory (distance < 2 × footprint_radius) with different `political_archetype` or dominant_faction |
|
||||
| **WildernessBuffer** | No settlement placed by Layer 2 algorithm |
|
||||
| **AbandonedZone** | Settlement placed but `corp_financial_state.health_metric < 0.2` for all operating corps at founding time |
|
||||
|
||||
These thresholds are derivable from fields already in systems.db. No new data required.
|
||||
The key economic inputs are corp presence count and health metric at seed time (not
|
||||
runtime), plus road graph density.
|
||||
|
||||
---
|
||||
|
||||
## 8. Open Questions I'm Raising for Round 2
|
||||
|
||||
**BS-Q1: Population tier floor table — is the 50-person minimum too low for guaranteed
|
||||
Entertainment?**
|
||||
|
||||
I've proposed Entertainment as guaranteed at City tier (10,000+). A 9,999-person Town
|
||||
doesn't get a dedicated Entertainment district under this model — it gets some in its
|
||||
Commercial district. Does that feel right? A 9,000-person mining town with no
|
||||
Entertainment *dedicated district* but Entertainment-character Commercial seems
|
||||
plausible. Flag this for Gestalt's "does this feel inhabited" check.
|
||||
|
||||
**BS-Q2: How does founding_age_years interact with WorldTier?**
|
||||
|
||||
A Waypoint-tier settlement by definition has population < 50. It can't be old enough to
|
||||
have cultural institutions even if `founding_age_years = 300`. The age modifier needs
|
||||
to be capped by WorldTier ceiling. Proposed: age modifier only applies at Town tier and
|
||||
above.
|
||||
|
||||
**BS-Q3: The energy row minimum Entertainment weight of 0.2 — is this adequate for
|
||||
large energy production facilities?**
|
||||
|
||||
An energy facility with 200,000 workers has a lot of people who need social services.
|
||||
At weight 0.2 against a population that produces 4 districts by D-C4, we might get zero
|
||||
Entertainment districts from the weight allocation (if 0.2 normalized against all the
|
||||
Log and Spe weights is too small). The guarantee floor rescues this — Entertainment is
|
||||
guaranteed at City tier. But I want to flag that the weight 0.2 for energy/Entertainment
|
||||
will produce the minimum possible dedicated Entertainment district, and the guaranteed
|
||||
floor + character descriptor is doing the real work here, not the weight.
|
||||
|
||||
**BS-Q4: Does the attractor-matching algorithm need to handle bodies with no
|
||||
geographic attractors?**
|
||||
|
||||
The consultant review says "if M < N, generate synthetic attractors." This is
|
||||
algorithmically straightforward. But economically: what does it mean for a body to have
|
||||
N cities and fewer geographic attractors than cities? It suggests a high-tier economic
|
||||
world where secondary manufacturing cities exist independent of natural features
|
||||
(because technology overrides geography). This is plausible for manufacturing, service,
|
||||
and commercial roles on high-`economic_tier` bodies. The synthetic attractor rule seems
|
||||
correct: place remaining cities along existing road corridors at seed-derived offsets.
|
||||
|
||||
---
|
||||
|
||||
## 9. What I Need from Other Participants
|
||||
|
||||
**From Gestalt:** The Amendment 6 population tier guarantee table should be evaluated
|
||||
against "does this produce places that feel inhabited?" at each tier. The economic
|
||||
logic says the guarantees are correct; Gestalt should verify the fun-and-legibility
|
||||
layer.
|
||||
|
||||
**From Tyre:** The attractor-matching algorithm (§5) needs a concreteness check.
|
||||
Is the Hungarian algorithm appropriate for this scale (N ≤ 30 cities per body,
|
||||
M = 50-200 attractors)? What is the performance cost?
|
||||
|
||||
**From Paula:** TerritorialStatus thresholds (§7) — do these economic proxies
|
||||
produce the political texture you need for narrative differentiation? CoreTerritory
|
||||
vs. FrontierTerritory based purely on settlement density and road maintenance seems
|
||||
thin for narrative purposes. Is there a political pressure field I'm missing?
|
||||
|
||||
**From all:** CL-Q1 (field naming lock) should be confirmed at the start of Round 2
|
||||
so all algorithm descriptions use consistent field names.
|
||||
@@ -0,0 +1,602 @@
|
||||
---
|
||||
title: "Round 2 — Burnelli-Sheldon: Economics Algorithm Proposals"
|
||||
description: "Full specification of the three-component district mix model, attractor-matching compatibility matrix, latent settlement generalization under fully generative placement, and TerritorialStatus threshold convergence."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: burnelli-sheldon
|
||||
round: 2
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Round 2 — Burnelli-Sheldon: Economics Algorithm Proposals
|
||||
|
||||
This document provides concrete algorithm specifications for the four economics assignments
|
||||
from the Round 1 notes. No positions are hedged — these are proposals for convergence.
|
||||
|
||||
---
|
||||
|
||||
## 1. Three-Component District Mix Model — Full Specification
|
||||
|
||||
### 1.1 Component 1: Population Tier Guarantees
|
||||
|
||||
The population tier guarantees establish a **mandatory district set** that is allocated
|
||||
before the economic role weight table distributes remaining slots. These are derived from
|
||||
the sub-settlement hierarchy in the brief, mapped to the D-C4 district count formula.
|
||||
|
||||
**Guaranteed district set by population tier:**
|
||||
|
||||
| Sub-settlement tier | Population | Guaranteed types | Notes |
|
||||
|--------------------|-----------|-----------------|-------|
|
||||
| Waypoint | < 50 | 1× **Mixed** | All functions fused into one district |
|
||||
| Outpost | 50–999 | **Residential** + **Mixed** | Two functional zones |
|
||||
| Town | 1,000–9,999 | **Residential** + **Commercial** | Entertainment present inside Commercial character |
|
||||
| City | 10,000–99,999 | **Residential** + **Commercial** + **Entertainment** | Dedicated entertainment justified by population |
|
||||
| Large City | 100,000+ | **Residential** + **Commercial** + **Entertainment** + **Administrative** | Bureaucratic complexity requires dedicated admin |
|
||||
|
||||
**BS-Q1 resolution (9,999-person Town without dedicated Entertainment):** Confirmed
|
||||
plausible. A Town's Commercial district contains the entertainment function — the pub,
|
||||
the social hall, the gambling den — as a character property of that district. When the
|
||||
settlement grows to City tier (10,000+), specialization makes a dedicated Entertainment
|
||||
district economically viable. The function always exists; the dedicated district is the
|
||||
marker of scale.
|
||||
|
||||
**WorldTier exception — domed/cave settlements:** Always 1 district regardless of
|
||||
population. Single district type = Mixed, carrying all functions.
|
||||
|
||||
**Capital city bonus:** +1 district slot (as per D-C4 rule) distributed by the weight
|
||||
table after guarantees are placed.
|
||||
|
||||
**When D-C4 count is less than the guaranteed set size:**
|
||||
|
||||
D-C4 can produce fewer total district slots than the guaranteed set requires. When this
|
||||
occurs, the overflow guaranteed types collapse into Mixed:
|
||||
|
||||
```
|
||||
if D_total < len(guaranteed_set):
|
||||
collapse_to_mixed():
|
||||
1 district if D_total == 1 → type = Mixed (all functions)
|
||||
2 districts if D_total == 2 → Residential + Mixed (remaining functions)
|
||||
```
|
||||
|
||||
This correctly handles outposts with D-C4 = 1 that the formula would label Passage-tier.
|
||||
|
||||
### 1.2 Component 2: Economic Role Multiplier Table
|
||||
|
||||
The weight table distributes the **remaining district slots** after the guaranteed set
|
||||
is placed. It also applies to the guaranteed set as a character modifier for *which
|
||||
variant* of each type the settlement gets — but it does not remove guaranteed types.
|
||||
|
||||
**No weight is ever zero.** Minimum value is 3 (not 0.2 as I wrote in Round 1 — I
|
||||
converted to integer weights summing to 100 per row for implementation clarity). This
|
||||
means every district type has some non-zero probability of appearing in any settlement's
|
||||
non-guaranteed slots, while the character differences between economic roles remain
|
||||
pronounced.
|
||||
|
||||
**Full weight table (integer weights, each row sums to 100):**
|
||||
|
||||
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|
||||
|--------------|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| manufacturing | 18 | 8 | 30 | 6 | 18 | 5 | 8 | 5 | 2 |
|
||||
| agricultural | 22 | 12 | 5 | 10 | 20 | 5 | 15 | 5 | 6 |
|
||||
| extraction | 16 | 7 | 28 | 4 | 24 | 5 | 9 | 5 | 2 |
|
||||
| transit | 10 | 18 | 5 | 5 | 20 | 12 | 14 | 14 | 2 |
|
||||
| research | 16 | 6 | 5 | 14 | 5 | 7 | 10 | 3 | 34 |
|
||||
| commercial | 14 | 30 | 5 | 6 | 10 | 14 | 14 | 5 | 2 |
|
||||
| service_mixed | 20 | 18 | 5 | 10 | 6 | 14 | 18 | 7 | 2 |
|
||||
| mining | 16 | 7 | 22 | 3 | 28 | 6 | 9 | 7 | 2 |
|
||||
| frontier | 26 | 10 | 10 | 5 | 18 | 8 | 16 | 5 | 2 |
|
||||
| energy | 10 | 3 | 18 | 6 | 24 | 3 | 6 | 6 | 24 |
|
||||
|
||||
**How to read this table:**
|
||||
|
||||
The mining row has Log = 28, Ind = 22 as dominant weights. After placing guaranteed
|
||||
Residential + Commercial (at Town) or + Entertainment (at City), remaining slots almost
|
||||
always produce Industrial and Logistics districts. Entertainment weight = 6 means
|
||||
occasionally a second Entertainment appears in a large mining city — a second drinking
|
||||
district for a city that grew big enough to support it. This is correct.
|
||||
|
||||
The energy row has Spe = 24, Log = 24 as dominant. Energy installations need Specialized
|
||||
facilities (reactors, control centers, research wings) and Logistics (heavy transport for
|
||||
fuel and output). Entertainment = 3 and Commercial = 3 are minimal but non-zero — large
|
||||
energy cities have a company canteen and a gym, which earns each a district representation
|
||||
at high enough population.
|
||||
|
||||
**BS-Q3 resolution (energy Entertainment weight):** At Outpost/Town scale (1-2 districts),
|
||||
energy settlements don't get a dedicated Entertainment slot from the weight table —
|
||||
Entertainment is present inside the Mixed/Commercial district instead. At City scale
|
||||
(10,000+), the guaranteed Entertainment takes effect, producing a dedicated district with
|
||||
rough-canteen character. The weight = 3 only applies to *additional* Entertainment
|
||||
districts beyond the guarantee, which are rare. This is the correct behavior.
|
||||
|
||||
**Political archetype modifiers stacked on top:**
|
||||
These remain as described in the brief. They're additive modifiers to the weight table
|
||||
row before normalization:
|
||||
|
||||
```
|
||||
CompanyTown: Adm -= 10, Ind += 10, Log += 5, Res += 5 (production-heavy, thin governance)
|
||||
AdminCapital: Adm += 20, Ent += 5, Spe += 5, Ind -= 15 (governance-heavy, civic services)
|
||||
FreePort: Com += 15, Trn += 10, Mix += 5, Adm -= 15 (commerce-heavy, light governance)
|
||||
Contested: no modifier (both political forces fight over what to build; neither wins)
|
||||
OrganicGrowth: Mix += 15, Res += 10, Ind -= 10, Adm -= 5 (unplanned, service-led growth)
|
||||
```
|
||||
|
||||
### 1.3 Component 3: Settlement Age Modifier
|
||||
|
||||
**Where founding_age_years comes from:**
|
||||
|
||||
For named cities: `bodies.founding_age_years` (INTEGER, already in the bodies schema).
|
||||
For sub-settlements without a direct bodies row, derive from parent body age:
|
||||
|
||||
| Sub-settlement type | Derived age |
|
||||
|--------------------|-----------:|
|
||||
| Mining camp | `max(5, parent_body.founding_age_years - 20)` |
|
||||
| Trade waypoint | `max(5, parent_body.founding_age_years / 3)` |
|
||||
| Agricultural node | `max(5, parent_body.founding_age_years - 10)` |
|
||||
| Shadow node | 10 (always nascent — informal, lacks permanence by definition) |
|
||||
|
||||
**BS-Q2 resolution:** Age modifier applies only at Backwater tier and above. Waypoint and
|
||||
Passage settlements are too structurally simple to differentiate by age; they get
|
||||
nascent character regardless of founding date.
|
||||
|
||||
**Age bracket classification:**
|
||||
|
||||
| founding_age_years | Age bracket | Name |
|
||||
|-------------------|-------------|------|
|
||||
| 0–49 | nascent | Raw production focus |
|
||||
| 50–149 | young | Functional |
|
||||
| 150–349 | established | Differentiated |
|
||||
| 350+ | mature | Layered |
|
||||
|
||||
**What age bracket affects:**
|
||||
|
||||
Age modifies the **character class** of each district. This propagates to three
|
||||
`DistrictSkeleton` fields:
|
||||
- `perimeter_treatment`: nascent → Open; young → Fenced; established → Walled; mature → variable (can be any)
|
||||
- `density_pct` of commercial/entertainment blocks: nascent = 0.4–0.6; young = 0.5–0.7; established = 0.6–0.8; mature = 0.6–0.9 (wide range — old cities have both dense cores and thin outskirts)
|
||||
- Content system character selector: nascent entertainment = "bare supply function"; mature entertainment = "institutional legacy"
|
||||
|
||||
**What age bracket does NOT affect:** District count. District type. Prosperity baseline.
|
||||
These are determined by Components 1 and 2 alone.
|
||||
|
||||
**Age × WorldTier cap interaction:**
|
||||
|
||||
```
|
||||
effective_age_bracket =
|
||||
if world_tier in (Waypoint, Passage): nascent # always
|
||||
elif founding_age_years < 50: nascent
|
||||
elif founding_age_years < 150: young
|
||||
elif founding_age_years < 350: established
|
||||
else: mature
|
||||
```
|
||||
|
||||
A 400-year-old Waypoint is still nascent. A 10-year-old Epicenter city is still nascent.
|
||||
The cap ensures structural simplicity wins over chronological age for small settlements.
|
||||
|
||||
### 1.4 The Combined Algorithm — Full Formula
|
||||
|
||||
```
|
||||
Algorithm: compute_district_distribution(city, body, system, terrain)
|
||||
|
||||
Inputs (all from systems.db or session DB Layer 1-2 output):
|
||||
city.population, city.world_tier, city.political_archetype
|
||||
body.economic_role, body.founding_age_years, body.settlement_pattern
|
||||
system.economic_tier, system.distribution_index
|
||||
terrain.topographic_gradient_direction # from Layer 1 output
|
||||
seed = child_seed(world_seed, city_id) # SeedChain
|
||||
|
||||
Returns: Vec<DistrictSpec>
|
||||
where DistrictSpec = { type: DistrictType, prosperity_baseline: f32,
|
||||
character_class: CharacterClass, density_pct: f32,
|
||||
perimeter_treatment: PerimeterTreatment }
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Step 1: Total district count
|
||||
D_raw = max(1, floor(city.population / 50_000)) # log-scaled for pop > 500k
|
||||
D_total = min(D_raw, WorldTier_cap[city.world_tier])
|
||||
if city.is_capital: D_total = min(D_total + 1, WorldTier_cap[city.world_tier])
|
||||
if body.settlement_pattern in (domed, cave): D_total = 1
|
||||
|
||||
Step 2: Mandatory districts from population tier
|
||||
mandatory_types = guaranteed_set(city.population, city.world_tier)
|
||||
# Apply D_total cap: if len(mandatory_types) > D_total, collapse to Mixed
|
||||
mandatory = place_guaranteed_types(mandatory_types, D_total)
|
||||
remaining_slots = D_total - len(mandatory)
|
||||
# NOTE: computation is purely local — NO queries about neighboring cities.
|
||||
# Each settlement is computed independently from its own fields and seed.
|
||||
|
||||
Step 3: Fill remaining slots from economic role weight table
|
||||
if remaining_slots > 0:
|
||||
weights = ROLE_WEIGHT_TABLE[body.economic_role] # 9-element, all > 0
|
||||
weights = apply_archetype_modifiers(weights, city.political_archetype)
|
||||
weights = renormalize_to_100(weights)
|
||||
# Sample without replacement — can produce duplicate types (second Ind, second Log)
|
||||
additional_types = seeded_weighted_sample(weights, remaining_slots, seed)
|
||||
all_types = mandatory + additional_types
|
||||
else:
|
||||
all_types = mandatory
|
||||
|
||||
Step 4: Assign age character class
|
||||
age_bracket = effective_age_bracket(body.founding_age_years, city.world_tier)
|
||||
for d in all_types:
|
||||
d.character_class = AGE_CHARACTER_TABLE[d.type][age_bracket]
|
||||
d.density_pct = age_density_range(d.type, d.character_class)[seeded_float(seed)]
|
||||
d.perimeter_treatment = age_perimeter(d.type, age_bracket)
|
||||
|
||||
Step 5: Assign prosperity gradients
|
||||
base_prosperity = economic_tier / 5.0 + ROLE_PROSPERITY_MODIFIER[body.economic_role]
|
||||
gradient_direction = terrain.topographic_gradient_direction # high point = high prosperity
|
||||
gradient_magnitude = DISTRIBUTION_INDEX_SCALE[system.distribution_index]
|
||||
# Paula's topographic modifier is additive to the base, direction set by terrain:
|
||||
# hilltop district (top 30% elevation): +0.05
|
||||
# flood-adjacent district (bottom 20% vs. sea level): -0.05
|
||||
for (i, d) in enumerate(all_types):
|
||||
positional_rank = rank_in_gradient_direction(d.grid_position, gradient_direction, len(all_types))
|
||||
topo_modifier = topo_offset(d, terrain)
|
||||
d.prosperity_baseline = clamp(
|
||||
base_prosperity + (positional_rank - 0.5) × gradient_magnitude + topo_modifier,
|
||||
0.05, 0.95
|
||||
)
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
ROLE_PROSPERITY_MODIFIER (from L3-Q5 brief formula):
|
||||
extraction: -0.10
|
||||
research: +0.15
|
||||
service_mixed: +0.10
|
||||
frontier: -0.20
|
||||
(all others: 0.00)
|
||||
|
||||
DISTRIBUTION_INDEX_SCALE:
|
||||
"stratified": 0.7 (steep gradient — 0.7 × positional rank ± 0.35 spread)
|
||||
"moderate": 0.2 (shallow gradient — ≈ Gaussian around base, σ = 0.1)
|
||||
```
|
||||
|
||||
**Self-contained confirmation:** The algorithm references only local city/body/system
|
||||
fields. There are no lookups into neighboring city data, no distance calculations to
|
||||
adjacent settlements, no regional density queries. Each city generates its district
|
||||
distribution independently from its seed and the fields above. This satisfies the
|
||||
SeedChain isolation property from D-C10.
|
||||
|
||||
---
|
||||
|
||||
## 2. Attractor-Matching: Full Compatibility Matrix
|
||||
|
||||
### 2.1 The Compatibility Matrix
|
||||
|
||||
Scale 0–10 where 0 = incompatible/excluded and 10 = preferred/ideal match.
|
||||
Geographic attractor types: the seven tags from the brief (Section 1, Layer 1 minimum
|
||||
viable) plus two additional tags identified by Paula (CoastalLowland, RiverValley) that
|
||||
emerged in Round 1 discussion.
|
||||
|
||||
| economic_role | RiverConfl | CoastalHarbor | MtnPass | ArablePlain | ResourceConc | Defensible | NaturalBarrier | CoastalLowland | RiverValley |
|
||||
|--------------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| agricultural | 8 | 5 | 2 | 10 | 1 | 2 | 0 | 7 | 8 |
|
||||
| extraction | 3 | 3 | 5 | 2 | 10 | 3 | 1 | 2 | 3 |
|
||||
| mining | 2 | 2 | 6 | 1 | 10 | 4 | 1 | 1 | 2 |
|
||||
| transit | 9 | 9 | 10 | 3 | 1 | 3 | 0 | 5 | 7 |
|
||||
| commercial | 8 | 9 | 6 | 4 | 2 | 2 | 0 | 6 | 7 |
|
||||
| research | 4 | 4 | 4 | 4 | 6 | 8 | 3 | 3 | 4 |
|
||||
| manufacturing | 7 | 6 | 3 | 5 | 5 | 2 | 0 | 6 | 6 |
|
||||
| service_mixed | 6 | 6 | 4 | 6 | 2 | 3 | 0 | 6 | 6 |
|
||||
| frontier | 5 | 5 | 5 | 4 | 5 | 6 | 3 | 4 | 4 |
|
||||
| energy | 4 | 5 | 3 | 2 | 8 | 4 | 2 | 4 | 3 |
|
||||
|
||||
### 2.2 Forbidden Combinations (score = 0)
|
||||
|
||||
These are not just low-probability — they are implausible enough that the generator
|
||||
should flag them for review rather than place silently:
|
||||
|
||||
| Economic role | Forbidden attractor | Reason |
|
||||
|--------------|--------------------|---------|
|
||||
| agricultural | NaturalBarrier | No farming in impassable terrain |
|
||||
| transit | NaturalBarrier | Transit hubs do not sit at physical barriers — they use gaps in them (MtnPass) |
|
||||
| commercial | NaturalBarrier | Commerce requires accessible flow of goods and people |
|
||||
| manufacturing | NaturalBarrier | Heavy industry requires accessible logistics |
|
||||
| service_mixed | NaturalBarrier | Services require customers who can reach them |
|
||||
|
||||
**Note on NaturalBarrier:** This attractor type is a settlement exclusion zone for
|
||||
most roles. The only roles with non-zero scores there are extraction (1), mining (1),
|
||||
research (3), frontier (3), energy (2) — all roles whose economic activities specifically
|
||||
exploit inaccessibility or operate in remote terrain.
|
||||
|
||||
### 2.3 Preferred Combinations (score = 9–10)
|
||||
|
||||
These are the "inevitable placements" Ozzie flagged — a player seeing a transit hub at
|
||||
a mountain pass should feel the placement was obvious:
|
||||
|
||||
| Economic role | Preferred attractor | Score | Narrative |
|
||||
|--------------|--------------------|----|-----------|
|
||||
| transit | MountainPass | 10 | The pass is the reason the city exists |
|
||||
| transit | CoastalHarbor | 9 | Port = transit nexus |
|
||||
| transit | RiverConfluence | 9 | River junction = historic trade/ferry crossing |
|
||||
| agricultural | ArablePlain | 10 | The land is the reason for the city |
|
||||
| extraction | ResourceConcentration | 10 | The deposit is the reason for the city |
|
||||
| mining | ResourceConcentration | 10 | Same |
|
||||
| commercial | CoastalHarbor | 9 | Ports = commercial centers historically |
|
||||
|
||||
### 2.4 Full Attractor-Matching Algorithm (reconciled from four Round 1 proposals)
|
||||
|
||||
The four Round 1 proposals (Gestalt, Tyre, Burnelli-Sheldon, Paula) are structurally
|
||||
equivalent; they differ in ordering and handling edge cases. This is the convergence:
|
||||
|
||||
```
|
||||
Algorithm: attractor_matching(cities, attractors, body, systems_db, seed)
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Phase 0: Build score matrix and validate HQ constraints
|
||||
For each city_i and attractor_j:
|
||||
S[i,j] = compat_score(city_i.economic_role, attractor_j.type)
|
||||
× quality_bonus(attractor_j)
|
||||
× population_weight(city_i.population)
|
||||
|
||||
where:
|
||||
quality_bonus = 1.2 if attractor_j is highest-quality of its type on this body
|
||||
population_weight = 1.0 + min(log10(city_i.population / 1000) × 0.10, 0.30)
|
||||
|
||||
HQ hard constraints (corp cross-references in systems.db):
|
||||
for each corp with headquarters_body = this body:
|
||||
matching_city = city with matching name in atlas_city_names
|
||||
compatible_attractors = attractors where compat_score ≥ 6
|
||||
if no compatible_attractor exists:
|
||||
FLAG for lead review: "[city_name] has no compatible attractor for [corp_role]"
|
||||
assign to best available attractor (highest score, even below 6)
|
||||
else:
|
||||
force S[matching_city, incompatible_attractor] = 0 for all incompatible
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Phase 1: Priority ordering
|
||||
Sort cities into tiers:
|
||||
Tier A: cities with HQ corp cross-reference (locked, must-place)
|
||||
Tier B: remaining named cities, sorted by population descending
|
||||
Tier C: unnamed cities (population budget, sorted by population descending)
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Phase 2: Assign Tier A cities (hard constraints first — Paula's ordering)
|
||||
For each corp in extraction-order (most geographically constrained roles first):
|
||||
economic role constraint order: extraction → mining → energy → agricultural
|
||||
→ manufacturing → transit → commercial → research
|
||||
→ service_mixed → frontier
|
||||
Assign each Tier A city to its highest-scoring available compatible attractor.
|
||||
Mark attractor as consumed.
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Phase 3: Assign Tier B and Tier C cities (Hungarian algorithm)
|
||||
Build sub-matrix S' for unassigned cities × remaining attractors.
|
||||
Solve maximum-weight bipartite matching.
|
||||
O(N³) where N ≤ 30: negligible compute.
|
||||
|
||||
Derive FoundingOrientation from assigned attractor_type:
|
||||
RiverConfluence → RiverCrossing
|
||||
CoastalHarbor → PortFacing
|
||||
MountainPass → PassControl
|
||||
ArablePlain → AgriculturalExpanse
|
||||
ResourceConc. → ExtractionTether
|
||||
Defensible → DefenseFacing
|
||||
NaturalBarrier → (fortress pattern within MountainPass or Defensible)
|
||||
CoastalLowland → AgriculturalExpanse (coastal variant)
|
||||
RiverValley → RiverCrossing (valley variant)
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Phase 4: Overflow handling
|
||||
If N_cities > M_attractors after Phase 3:
|
||||
For each unmatched city (sorted by population descending):
|
||||
Place synthetic attractor:
|
||||
position = seed_derived offset from nearest NameLocked settlement
|
||||
distance ∈ [15%, 40%] × body_scale
|
||||
biased toward nearest road corridor (if road graph available)
|
||||
type = nearest_attractor.type
|
||||
(inherits the dominant attractor character of the region)
|
||||
geographically_triggered = false
|
||||
FoundingOrientation = AdminFacing (Paula's OrganicGrowth resolution)
|
||||
|
||||
──────────────────────────────────────────────────────────────
|
||||
|
||||
Phase 5: Produce SettlementRecord per city
|
||||
settlement_class = NameLocked if Tier A
|
||||
= PopulationBudget if Tier B/C, geographically_triggered = true
|
||||
= OrganicGrowth if overflow, geographically_triggered = false
|
||||
```
|
||||
|
||||
**Ozzie's constraint enforced:** Any placement that triggers a compat_score = 0
|
||||
(forbidden combination) is blocked at Phase 0. The generator cannot silently place a
|
||||
fishing port in the mountains — it either finds a compatible attractor or flags for lead
|
||||
review. This satisfies the "feels inevitable, not arbitrary" requirement.
|
||||
|
||||
---
|
||||
|
||||
## 3. Latent Settlements Under Fully Generative Placement
|
||||
|
||||
### 3.1 What Changed
|
||||
|
||||
Under the old model, wiki-authored cities were always active. Sub-settlements were latent.
|
||||
Under Amendment 3, ALL settlements are generator-placed. The latent concept must generalize.
|
||||
|
||||
The key economic insight: **latency tracks economic justification, not authoring method.**
|
||||
A settlement is latent if its continued existence depends on economic conditions that can
|
||||
change. A settlement is non-latent if it exists for structural reasons that don't change
|
||||
with economics (a mountain pass town exists because the pass exists; that doesn't change).
|
||||
|
||||
### 3.2 Settlement Classes and Active Status Logic
|
||||
|
||||
Introduce a `SettlementClass` enum on all generated settlement records:
|
||||
|
||||
```rust
|
||||
pub enum SettlementClass {
|
||||
/// Placed by HQ corp cross-reference. The corp's HQ exists as long as the
|
||||
/// corp exists. Active if corp health_metric > 0.0 (any presence).
|
||||
NameLocked,
|
||||
|
||||
/// Placed by population budget — geographic attractor exists but name is
|
||||
/// generator-assigned. Active if nearest NameLocked settlement is economically
|
||||
/// healthy (its sponsoring corp health > 0.4). Becomes ghost if the anchor corp
|
||||
/// collapses and no other corp moves in.
|
||||
PopulationBudget,
|
||||
|
||||
/// Sub-settlements placed by economic trigger conditions:
|
||||
/// mining camps, trade waypoints, agricultural nodes.
|
||||
/// Active if triggering corp health_metric > 0.4.
|
||||
EconomicTriggered,
|
||||
|
||||
/// Overflow settlements placed at Province centroids without geographic attractor.
|
||||
/// Active if provincial avg corp health > 0.5 (needs regional economic density).
|
||||
OrganicGrowth,
|
||||
}
|
||||
```
|
||||
|
||||
**Why NameLocked is not always-active:** If the corp dissolves entirely
|
||||
(`corp_lifecycle_events.event_type = Dissolved`), the HQ city loses its economic
|
||||
anchor. The city doesn't vanish spatially (the streets and buildings remain), but
|
||||
it becomes a ghost: dark, unmaintained, repopulated by scavengers and squatters.
|
||||
The physical city persists; the economic life drains out. This is the only case where
|
||||
a NameLocked settlement becomes ghost.
|
||||
|
||||
**Why this is stronger than the old latent concept:** The old model had named cities
|
||||
as unconditionally active and sub-settlements as optionally latent. The new model
|
||||
correctly identifies that a city's activity is always economically contingent — it
|
||||
just has different trigger conditions by class. The full active/ghost spectrum applies
|
||||
to all settlement types.
|
||||
|
||||
### 3.3 Schema Addition
|
||||
|
||||
Add `settlement_class` and `geographically_triggered` to the generated settlement
|
||||
records in `BodyWorldState`:
|
||||
|
||||
```rust
|
||||
pub struct GeneratedSettlement {
|
||||
pub city_id: String,
|
||||
pub position: (f32, f32), // body-local normalized coords
|
||||
pub founding_orientation: FoundingOrientation,
|
||||
pub settlement_class: SettlementClass,
|
||||
pub geographically_triggered: bool, // Paula's NEW-Q2 disambiguation flag
|
||||
pub activating_corp: Option<String>, // corp_id for EconomicTriggered
|
||||
pub activating_condition: Option<String>, // description for OrganicGrowth
|
||||
}
|
||||
```
|
||||
|
||||
`geographically_triggered = false` is the OrganicGrowth disambiguation flag Paula
|
||||
identified. It answers "was this settlement placed because of a geographic attractor
|
||||
or because of population pressure alone?" FoundingOrientation = AdminFacing when false.
|
||||
|
||||
### 3.4 The Latent Settlement Table in Systems.db
|
||||
|
||||
My Round 1 recommendation was option (b): store latent settlement positions in a
|
||||
systems.db Phase 3 output table. Under Amendment 1 (three-tier execution), this shifts:
|
||||
latent settlement positions are runtime background generation output stored in session DB
|
||||
(as part of `BodyWorldState`), not systems.db.
|
||||
|
||||
The economic SIM still reads from systems.db for its aggregate calculations
|
||||
(corp_presence, corp_financial_state). It does not need the spatial settlement positions
|
||||
to compute economics — it just needs to emit health_metric updates that the renderer
|
||||
reads to determine active/ghost status per settlement.
|
||||
|
||||
**The handoff:** The sim emits `corp_health_changed(corp_id, new_metric)` events.
|
||||
The Layer 3-4 generator, when processing a city, queries: is the activating corp's
|
||||
health_metric above or below the threshold for this settlement class? This query is
|
||||
a single lookup into the economic sim's current state. No full re-generation is needed.
|
||||
|
||||
---
|
||||
|
||||
## 4. TerritorialStatus Economic Thresholds — Convergence Proposal
|
||||
|
||||
The Round 1 notes show three proposals (Paula, Tyre, Burnelli-Sheldon). This section
|
||||
proposes convergence thresholds that satisfy all three agents' requirements.
|
||||
|
||||
**Note on Province dimensions:** Tyre's ARCH-1 through ARCH-4 blockers include
|
||||
confirmation of Province scale. The thresholds below use Province-relative quantities
|
||||
(fraction of Province cells) to remain valid regardless of absolute Province dimensions.
|
||||
When ARCH-1 is resolved, the fractions convert to concrete cell counts.
|
||||
|
||||
### 4.1 Convergence Table
|
||||
|
||||
| TerritorialStatus | Threshold | Economic signal |
|
||||
|------------------|-----------|----------------|
|
||||
| **CoreTerritory** | ≥ 2 named settlements (City or Town) within Province boundaries AND road_coverage_fraction ≥ 0.55 across Province cells AND ≥ 1 active corp (health_metric ≥ 0.5) with presence in Province | Economically integrated, actively maintained |
|
||||
| **FrontierTerritory** | ≥ 1 settlement (any tier) in Province AND (road_coverage_fraction < 0.55 OR road_quality < 0.5 at Province boundary edges) | Settled but underdeveloped; economic reach doesn't fill the Province |
|
||||
| **ExtractiveZone** | ≥ 1 ResourceConcentration geographic feature tag in Province AND ≥ 1 corp_presence with extraction/mining primary_operation commodity AND road corridor from resource site to nearest City (even if narrow) | Productive but not balanced; the corridor is the territory |
|
||||
| **ContestZone** | ≥ 2 settlements from different political_archetype types within Province AND overlapping MaintenanceAuthority on shared road segments (or road networks crossing Province internal boundary without single authority) | Competing claims with no resolution |
|
||||
| **WildernessBuffer** | 0 settlements placed + 0 roads + no ResourceConcentration tag | Untouched; no economic vector yet |
|
||||
| **AbandonedZone** | ≥ 1 settlement with `placed_at_generation = true` in Province AND 0 active settlements at current sim evaluation (all occupying corps have health_metric < 0.2) | Was settled; economic basis collapsed |
|
||||
|
||||
### 4.2 What Changed vs. Round 1 (My Position)
|
||||
|
||||
- **CoreTerritory:** Added "≥ 1 active corp with health_metric ≥ 0.5" as a third condition.
|
||||
My Round 1 threshold was purely structural (settlement count + road maintenance). Paula's
|
||||
version required the economic health signal. She's right — a ghost city Province with
|
||||
intact roads but zero active corps is not CoreTerritory; it's AbandonedZone.
|
||||
|
||||
- **FrontierTerritory:** Adopted Paula's road quality threshold at Province edges. My Round 1
|
||||
version was vague about what "road density < 0.5" meant. Road quality < 0.5 at Province
|
||||
boundary is more testable.
|
||||
|
||||
- **ContestZone:** Adopted Tyre's "two CoreTerritory zones from different political archetypes
|
||||
overlapping" framing as the primary test. My Round 1 threshold (competing road networks)
|
||||
is now a diagnostic signal rather than the definition.
|
||||
|
||||
- **AbandonedZone:** Added Paula's `placed_at_generation = true` flag as the necessary
|
||||
condition to distinguish "abandoned" from "never settled." My Round 1 version would
|
||||
mis-classify a WildernessBuffer Province where a corp briefly established presence and
|
||||
then left; the flag provides the correct disambiguation.
|
||||
|
||||
### 4.3 Runtime Derivation
|
||||
|
||||
TerritorialStatus can be re-derived at runtime from the economic simulation's state
|
||||
without re-running the Layer 1-2 generator. The inputs are:
|
||||
|
||||
- `settlement_active_state` per settlement (from SettlementClass + corp health query)
|
||||
- `road_coverage_fraction` per Province (from Layer 2 session cache — does not change)
|
||||
- `corp_presence` + `corp_financial_state.health_metric` (from economic sim)
|
||||
|
||||
This means TerritorialStatus is a *render parameter* like `prosperity_current` — it
|
||||
reflects the current economic state and updates when the sim changes, but it does not
|
||||
trigger a layout regeneration. The Province boundaries don't move; only the status label
|
||||
changes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Open Positions and Requests for Other Agents
|
||||
|
||||
### 5.1 BS-Q1 — Final Resolution (no further input needed)
|
||||
|
||||
A 9,999-person Town without a dedicated Entertainment district is economically correct.
|
||||
Entertainment is functional inside the Commercial district at Town scale. Gestalt
|
||||
validated this in Round 1 notes (no objection to the threshold).
|
||||
|
||||
### 5.2 Topographic Modifier to prosperity_baseline (Paula + Burnelli-Sheldon)
|
||||
|
||||
Paula proposed a topographic modifier (+0.05 hilltop, -0.05 flood-adjacent). This is
|
||||
included in the formula at Step 5 of §1.4 as an additive offset on the base gradient.
|
||||
No incompatibility with my gradient direction/magnitude split. Confirmed compatible.
|
||||
|
||||
### 5.3 Gestalt: Character table feedback needed
|
||||
|
||||
The age character classes in §1.3 (AGE_CHARACTER_TABLE) propagate to `DistrictSkeleton`
|
||||
fields (perimeter_treatment, density_pct). I need Gestalt to confirm whether these
|
||||
fields are sufficient to express the character differences, or whether additional fields
|
||||
on `DistrictSkeleton` are needed. Specifically: does a "nascent entertainment district"
|
||||
vs. a "mature entertainment district" need a named field, or does density_pct + prosperity
|
||||
alone carry it?
|
||||
|
||||
### 5.4 Tyre: ARCH-3 schema question
|
||||
|
||||
The attractor-matching algorithm requires `atlas_city_names` with fields:
|
||||
`body_id`, `city_name`, `economic_role`, `population`, `hq_for_corp_id` (nullable).
|
||||
Does this replace or extend `atlas_cities`? My recommendation: extend with additional
|
||||
columns. `atlas_cities` currently stores authored pixel positions (which become obsolete
|
||||
under Amendment 3) plus name + population + kind (which are still needed). The new
|
||||
columns are: `hq_for_corp_id` (nullable FK to corporations) and `assigned_attractor_id`
|
||||
(nullable, written by Layer 2 generator to session DB, not systems.db).
|
||||
|
||||
### 5.5 Threshold for "abandoned body" condition on NameLocked settlements
|
||||
|
||||
My SettlementClass model has NameLocked cities becoming ghost when their founding corp
|
||||
dissolves. I need confirmation from Paula: does corp dissolution in the economics sim
|
||||
produce a `corp_lifecycle_events.event_type = Dissolved` record? The rendering system
|
||||
needs a way to read this state. Alternatively: `corp_financial_state.health_metric = 0.0`
|
||||
as a proxy. Which is the correct signal to read?
|
||||
@@ -0,0 +1,749 @@
|
||||
---
|
||||
title: "Round 3 — Burnelli-Sheldon: Convergence and Economics D-Records"
|
||||
description: "D-record candidates D-194 through D-199 for locked economics decisions. Data quality findings from systems.db. Mismatch threshold validation. TerritorialStatus economic threshold confirmation."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: burnelli-sheldon
|
||||
round: 3
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Round 3 — Burnelli-Sheldon: Convergence and Economics D-Records
|
||||
|
||||
D-record IDs D-194 through D-199 claimed. This document contains the candidate text for
|
||||
each. It also flags critical data quality issues I found querying systems.db that affect
|
||||
the formulas before they can go live.
|
||||
|
||||
---
|
||||
|
||||
## 0. Critical Data Quality Findings (Must Address Before Implementation)
|
||||
|
||||
Before the D-records: three data gaps in systems.db that the implementation will hit
|
||||
immediately if not flagged now.
|
||||
|
||||
### Gap 1: `bodies.founding_age_years` is NULL for All 273 Inhabited Bodies
|
||||
|
||||
Component 3 of the district mix algorithm (age modifier) depends on this field. It is
|
||||
entirely unpopulated. The D-194 algorithm includes a fallback derivation from
|
||||
`system_history.settlement_wave` (see §1.5), but this is a degraded proxy. The field
|
||||
should be populated as part of wiki content work — every inhabited body should have an
|
||||
approximate founding age.
|
||||
|
||||
**Immediate implication:** The age modifier in Phase 1 implementation will run on
|
||||
fallback values only. Character tags will be wave-derived, not body-specific.
|
||||
|
||||
**Settlement wave → approximate age mapping (fallback):**
|
||||
|
||||
| settlement_wave | Approximate founding_age_years | Age bracket |
|
||||
|----------------|-------------------------------|-------------|
|
||||
| origin | 500+ | mature |
|
||||
| wave_1 | 350 | mature |
|
||||
| wave_2 | 250 | established |
|
||||
| wave_3 | 150 | established |
|
||||
| wave_4 | 75 | young |
|
||||
| wave_5 | 30 | nascent |
|
||||
| unsettled | N/A | not inhabited |
|
||||
|
||||
This mapping preserves the intent of the modifier even without body-level data.
|
||||
|
||||
### Gap 2: `system_economy.economic_tier` and `distribution_index` are NULL for 97% of Systems
|
||||
|
||||
290 of 300 system_economy rows have NULL for both fields. The prosperity_baseline formula
|
||||
(D-197) depends on `economic_tier`. Only 10 systems have data.
|
||||
|
||||
**Fallback for economic_tier:** Derive from system gate topology + population tier.
|
||||
Gate-connected major systems = tier 4-5; backwater systems = tier 1-2. Exact formula:
|
||||
|
||||
```
|
||||
economic_tier_derived =
|
||||
if system_population >= 5_000_000_000: 5
|
||||
elif system_population >= 1_000_000_000: 4
|
||||
elif system_population >= 100_000_000: 3
|
||||
elif system_population >= 10_000_000: 2
|
||||
else: 1
|
||||
```
|
||||
|
||||
**Fallback for distribution_index:** Default to "moderate" when NULL. "Stratified" is
|
||||
the explicit exception (must be authored); moderate is the baseline.
|
||||
|
||||
These fallbacks are sound — economic tier correlates with population by construction
|
||||
in a simulation game, and moderate inequality is the default state.
|
||||
|
||||
### Gap 3: `economic_role` Values Are Not Normalized
|
||||
|
||||
The bodies table has inconsistent values:
|
||||
- `"agriculture"` (16 bodies) and `"agricultural"` (107 bodies) — same concept, two spellings
|
||||
- `"resource_extraction"` (1 body) — should be `"extraction"`
|
||||
- `"coordination"` (2 bodies, including Bunbury pop 800M) — not in the weight table
|
||||
- `"mixed-agriculture"` (1 body) — not in the weight table
|
||||
- 1 body with NULL economic_role
|
||||
|
||||
The weight table in D-194 uses canonical values. The generator must normalize before
|
||||
looking up the table:
|
||||
|
||||
| Raw value | Canonical | Rationale |
|
||||
|-----------|-----------|-----------|
|
||||
| `agriculture` | `agricultural` | Spelling normalization |
|
||||
| `resource_extraction` | `extraction` | Enum normalization |
|
||||
| `coordination` | `service_mixed` | Coordination hubs are service/administrative |
|
||||
| `mixed-agriculture` | `agricultural` | Nearest match; blended role, dominant is agricultural |
|
||||
| NULL | `service_mixed` | Safest fallback for unknown roles |
|
||||
|
||||
This normalization should be added to `import_economics.py` as a validation step.
|
||||
|
||||
---
|
||||
|
||||
## 1. D-194: Three-Component District Mix Algorithm
|
||||
|
||||
**Decision ID:** D-194
|
||||
**Domain:** architecture
|
||||
**Status:** candidate
|
||||
**Claimed by:** Burnelli-Sheldon
|
||||
|
||||
### Decision
|
||||
|
||||
Adopt a three-component model for computing the district type distribution of any
|
||||
generated settlement. The model replaces the prior 10×9 weight table (which had zero
|
||||
weights and produced implausible zero-service settlements).
|
||||
|
||||
**The three components are:**
|
||||
1. Population tier guarantees — mandatory district types by settlement size
|
||||
2. Economic role multiplier table — integer weights distributing remaining slots
|
||||
3. Settlement age character modifier — modifies district character, not count
|
||||
|
||||
### Rationale
|
||||
|
||||
The prior table encoded what a settlement *produces*, not what it *needs*. Every
|
||||
settlement with concentrated labor produces service demand regardless of economic role.
|
||||
This is not a design preference — it is a consequence of basic human economics. Miners
|
||||
drink. Workers eat. People socialize. The new model encodes this correctly:
|
||||
|
||||
- **Component 1** provides non-negotiable floors: no city above minimum population has
|
||||
zero of any essential district type.
|
||||
- **Component 2** provides proportional variation: economic role shifts the distribution
|
||||
toward production-relevant types, but cannot suppress guaranteed types below 1 slot.
|
||||
- **Component 3** provides narrative depth without structural change: the same district
|
||||
types look different in a 20-year mining camp vs. a 300-year mining city.
|
||||
|
||||
### Self-Containment Rule
|
||||
|
||||
The algorithm references only local city/body/system fields. No queries to neighboring
|
||||
city data. No distance calculations. Each settlement generates its district distribution
|
||||
independently from its seed and the fields listed below. This satisfies the SeedChain
|
||||
isolation requirement (D-010).
|
||||
|
||||
### 1.1 Population Tier Guarantees
|
||||
|
||||
| Population range | Settlement tier | Mandatory district types |
|
||||
|-----------------|----------------|-------------------------|
|
||||
| 1–999 (Outpost) | Outpost | Residential + Mixed (all functions fused if D_total = 1) |
|
||||
| 1,000–9,999 (Town) | Town | Residential + Commercial |
|
||||
| 10,000–99,999 (City) | City | + Entertainment |
|
||||
| 100,000–499,999 (Large City) | Large City | + Administrative |
|
||||
| 500,000+ (Metropolis) | Metropolis | + Industrial (if role supports), + Civic |
|
||||
|
||||
**Collapse rule:** If `D_total < len(mandatory_types)`, excess mandatory types
|
||||
merge into Mixed. A 1-district settlement always has type Mixed regardless of
|
||||
population tier — it carries all functions in one district.
|
||||
|
||||
**WorldTier override — domed/cave settlement:** Single district regardless of
|
||||
population. Type = Mixed.
|
||||
|
||||
**Special case — BS-Q1 (Town Entertainment):** Entertainment at Town scale is a
|
||||
*character property* of the Commercial district, not a dedicated slot. Dedicated
|
||||
Entertainment districts appear at City tier (10,000+) only, when specialization
|
||||
becomes economically viable at scale.
|
||||
|
||||
### 1.2 Economic Role Multiplier Table
|
||||
|
||||
Integer weights, each row sums to 100. Minimum value across all cells: **3**.
|
||||
No weight is zero — every district type can appear in any economic role, just rarely.
|
||||
|
||||
DistrictType column key: Res=Residential, Com=Commercial, Ind=Industrial, Adm=Administrative,
|
||||
Log=Logistics, Ent=Entertainment, Mix=Mixed, Trn=Transit, Spe=Specialized
|
||||
|
||||
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|
||||
|--------------|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| manufacturing | 18 | 8 | 30 | 6 | 18 | 5 | 8 | 5 | 2 |
|
||||
| agricultural | 22 | 12 | 5 | 10 | 20 | 5 | 15 | 5 | 6 |
|
||||
| extraction | 16 | 7 | 28 | 4 | 24 | 5 | 9 | 5 | 2 |
|
||||
| transit | 10 | 18 | 5 | 5 | 20 | 12 | 14 | 14 | 2 |
|
||||
| research | 16 | 6 | 5 | 14 | 5 | 7 | 10 | 3 | 34 |
|
||||
| commercial | 14 | 30 | 5 | 6 | 10 | 14 | 14 | 5 | 2 |
|
||||
| service_mixed | 20 | 18 | 5 | 10 | 6 | 14 | 18 | 7 | 2 |
|
||||
| mining | 16 | 7 | 22 | 3 | 28 | 6 | 9 | 7 | 2 |
|
||||
| frontier | 26 | 10 | 10 | 5 | 18 | 8 | 16 | 5 | 2 |
|
||||
| energy | 10 | 3 | 18 | 6 | 24 | 3 | 6 | 6 | 24 |
|
||||
|
||||
**Energy Ent = 3 note:** A large energy facility's Entertainment guarantee comes from the
|
||||
City-tier floor (10k+ population = Entertainment guaranteed), not from the weight table.
|
||||
The weight 3 only governs *additional* Entertainment districts beyond the guarantee.
|
||||
A company canteen + gym at a 200k-person energy installation earns the guaranteed slot;
|
||||
the weight 3 correctly suppresses a second Entertainment district in remaining slots.
|
||||
|
||||
**Political archetype modifiers** (stacked additive after role table, before
|
||||
renormalization):
|
||||
|
||||
| political_archetype | Modifiers |
|
||||
|--------------------|-----------|
|
||||
| CompanyTown | Adm −10, Ind +10, Log +5, Res +5 |
|
||||
| AdminCapital | Adm +20, Ent +5, Spe +5, Ind −15 |
|
||||
| FreePort | Com +15, Trn +10, Mix +5, Adm −15 |
|
||||
| Contested | No modifier (competing forces cancel) |
|
||||
| OrganicGrowth | Mix +15, Res +10, Ind −10, Adm −5 |
|
||||
|
||||
### 1.3 Settlement Age Character Modifier
|
||||
|
||||
**Source field:** `bodies.founding_age_years` (INTEGER, nullable).
|
||||
**Fallback:** `system_history.settlement_wave` → age bracket via mapping in §0 above.
|
||||
**WorldTier cap:** Age modifier applies only at Backwater tier and above. Waypoint and
|
||||
Passage settlements are always `nascent` regardless of founding date.
|
||||
|
||||
| founding_age_years | Age bracket | Effect on DistrictSkeleton |
|
||||
|-------------------|-------------|---------------------------|
|
||||
| 0–49 (or wave_5) | nascent | perimeter_treatment: Open or Temporary; density_pct: 0.4–0.6; character: raw production |
|
||||
| 50–149 (or wave_4) | young | perimeter_treatment: Fenced; density_pct: 0.5–0.7; character: functional |
|
||||
| 150–349 (or wave_2/3) | established | perimeter_treatment: Walled; density_pct: 0.6–0.8; character: differentiated |
|
||||
| 350+ (or wave_1/origin) | mature | perimeter_treatment: variable (any); density_pct: 0.6–0.9; character: layered |
|
||||
|
||||
Age modifies: `perimeter_treatment`, `density_pct`, and the content system's building
|
||||
archetype selector via `character_class`. It does NOT modify district count, district
|
||||
type, or prosperity_baseline.
|
||||
|
||||
### 1.4 The Combined Algorithm
|
||||
|
||||
```
|
||||
fn compute_district_distribution(
|
||||
city: &CityGenerationContext,
|
||||
body: &BodyRecord, // from bodies table
|
||||
system: &SystemEconomyRecord, // from system_economy (nullable fields)
|
||||
terrain_gradient: CardinalDirection, // from Layer 1 output
|
||||
seed: u64, // child_seed(world_seed, city_id)
|
||||
) -> Vec<DistrictSpec>
|
||||
|
||||
Step 1: Total district count
|
||||
D_raw = max(1, floor(city.population / 50_000)) // log-scaled for pop > 500k
|
||||
D_total = min(D_raw, WorldTier_cap[city.world_tier])
|
||||
if city.is_capital: D_total = min(D_total + 1, WorldTier_cap[city.world_tier])
|
||||
if body.settlement_pattern in (domed, cave): D_total = 1
|
||||
|
||||
Step 2: Mandatory districts from population tier guarantee table
|
||||
mandatory_types = guarantee_table[population_tier(city.population)]
|
||||
if len(mandatory_types) > D_total: collapse_excess_to_mixed(mandatory_types, D_total)
|
||||
remaining_slots = D_total - len(mandatory_types)
|
||||
|
||||
Step 3: Fill remaining slots from economic role weight table
|
||||
role = normalize_economic_role(body.economic_role) // apply gap §0 normalization
|
||||
weights = ROLE_WEIGHT_TABLE[role] // 9-element, all ≥ 3
|
||||
weights = apply_archetype_modifiers(weights, city.political_archetype)
|
||||
weights = renormalize_to_100(weights)
|
||||
additional_types = seeded_weighted_sample(weights, remaining_slots, seed)
|
||||
// sample_without_replacement = false; duplicate types allowed (second Ind, second Log)
|
||||
all_types = mandatory_types + additional_types
|
||||
|
||||
Step 4: Assign age character class
|
||||
age_bracket = effective_age_bracket(body.founding_age_years, body.settlement_wave, city.world_tier)
|
||||
for d in all_types:
|
||||
d.character_class = AGE_CHARACTER_TABLE[d.type][age_bracket]
|
||||
d.density_pct = lerp(age_density_range[d.type][age_bracket], seeded_float(seed, i))
|
||||
d.perimeter_treatment = age_perimeter_table[d.type][age_bracket]
|
||||
|
||||
Step 5: Assign prosperity gradients (see D-197 for formula)
|
||||
assign_prosperity_per_district(all_types, city, body, system, terrain_gradient)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. D-195: Attractor-Matching Compatibility Matrix
|
||||
|
||||
**Decision ID:** D-195
|
||||
**Domain:** architecture
|
||||
**Status:** candidate
|
||||
**Claimed by:** Burnelli-Sheldon
|
||||
|
||||
### Decision
|
||||
|
||||
The generator assigns named cities to geographic attractors using a scored bipartite
|
||||
matching algorithm. City-attractor compatibility is scored by the 10×7 matrix below
|
||||
(10 economic roles × 7 canonical geographic attractor types). Score scale is 0–10
|
||||
where 0 = physically impossible/forbidden and 10 = ideal/preferred.
|
||||
|
||||
### Compatibility Matrix (0–10 scale)
|
||||
|
||||
| economic_role | RiverConfl | CoastalHarbor | MtnPass | ArablePlain | ResourceConc | Defensible | NaturalBarrier |
|
||||
|--------------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| manufacturing | 7 | 6 | 3 | 5 | 5 | 2 | 0 |
|
||||
| agricultural | 8 | 5 | 2 | 10 | 1 | 2 | 0 |
|
||||
| extraction | 3 | 3 | 5 | 2 | 10 | 3 | 1 |
|
||||
| transit | 9 | 9 | 10 | 3 | 1 | 3 | 0 |
|
||||
| research | 4 | 4 | 4 | 4 | 6 | 8 | 3 |
|
||||
| commercial | 8 | 9 | 6 | 4 | 2 | 2 | 0 |
|
||||
| service_mixed | 6 | 6 | 4 | 6 | 2 | 3 | 0 |
|
||||
| mining | 2 | 2 | 6 | 1 | 10 | 4 | 1 |
|
||||
| frontier | 5 | 5 | 5 | 4 | 5 | 6 | 3 |
|
||||
| energy | 4 | 5 | 3 | 2 | 8 | 4 | 2 |
|
||||
|
||||
### Forbidden Combinations (score = 0)
|
||||
|
||||
These are physically impossible placements. The generator hard-zeros these before scoring
|
||||
and will not place them even as an overflow option:
|
||||
|
||||
| Economic role | Forbidden attractor | Reason |
|
||||
|--------------|--------------------|-|
|
||||
| manufacturing | NaturalBarrier | Heavy industry requires accessible logistics; impassable terrain precludes it |
|
||||
| agricultural | NaturalBarrier | No farming in impassable terrain |
|
||||
| transit | NaturalBarrier | Transit hubs use gaps in barriers (MountainPass), never the barrier itself |
|
||||
| commercial | NaturalBarrier | Commerce requires accessible flow of people and goods |
|
||||
| service_mixed | NaturalBarrier | Services require customers who can physically reach them |
|
||||
|
||||
Note: extraction, mining, research, frontier, and energy all have non-zero scores at
|
||||
NaturalBarrier — these roles specifically exploit or require inaccessibility.
|
||||
|
||||
### Preferred Combinations (score 9–10, "inevitable placements")
|
||||
|
||||
| Economic role | Preferred attractor | Score | Narrative |
|
||||
|--------------|--------------------|----|-----------|
|
||||
| transit | MountainPass | 10 | The pass exists; the transit city exists to serve it |
|
||||
| extraction | ResourceConcentration | 10 | The deposit exists; the city exists to work it |
|
||||
| mining | ResourceConcentration | 10 | Same |
|
||||
| agricultural | ArablePlain | 10 | The land exists; the city farms it |
|
||||
| transit | CoastalHarbor | 9 | Port = natural transit nexus |
|
||||
| transit | RiverConfluence | 9 | River junction = historic crossing + trade |
|
||||
| commercial | CoastalHarbor | 9 | Ports = commercial centers universally |
|
||||
|
||||
### Mismatch Flag Thresholds (Lead Decision Implemented)
|
||||
|
||||
The lead directed a two-tier mismatch system: score < 0.35 = warning, score < 0.15 = error.
|
||||
In the 0–10 matrix scale:
|
||||
- **Warning:** raw score < 3.5
|
||||
- **Error:** raw score < 1.5
|
||||
|
||||
**Validation against real systems.db body types:**
|
||||
|
||||
I queried systems.db to validate these thresholds produce sensible flags on real data.
|
||||
|
||||
Representative cases where warnings fire (score 2–3.5):
|
||||
- agricultural + Defensible (score 2): farming community at a defensible site — exists
|
||||
(hill forts, fortified medieval towns) but unusual. Warning is correct.
|
||||
- transit + ArablePlain (score 3): transit hub on flat farmland — plausible (railroad
|
||||
junction on the plains) but not attractor-driven. Warning invites review.
|
||||
- extraction + MountainPass (score 5): mine near a mountain pass — valid. No flag.
|
||||
|
||||
Representative cases where errors fire (score 0–1.5):
|
||||
- agricultural + NaturalBarrier (score 0): impossible. Error ✓
|
||||
- mining + ArablePlain (score 1): mine on good farmland — politically contentious, not
|
||||
the natural attractor for mining. Error = flag for lead review. Correct.
|
||||
- agricultural + MountainPass (score 2): farming at a mountain pass — warning, not
|
||||
error. Borderline: mountain villages do farm (terraced agriculture). Warning is right.
|
||||
|
||||
**Conclusion:** The 0.35/0.15 two-tier thresholds produce sensible behavior against the
|
||||
real body data. No threshold adjustment needed.
|
||||
|
||||
### Flagging Behavior
|
||||
|
||||
```rust
|
||||
match best_attractor_score / 10.0 {
|
||||
s if s < 0.15 => PlacementFlag::Error(
|
||||
format!("{} city placed at {} attractor: score {:.2} below error threshold",
|
||||
city_name, attractor_type, s)
|
||||
),
|
||||
s if s < 0.35 => PlacementFlag::Warning(
|
||||
format!("{} city placed at {} attractor: score {:.2} below warning threshold",
|
||||
city_name, attractor_type, s)
|
||||
),
|
||||
_ => PlacementFlag::None,
|
||||
}
|
||||
```
|
||||
|
||||
Flags are logged at layer 2 generation time and accessible via a generator diagnostic
|
||||
API. They do not stop generation — they surface cases where the lead may want to author
|
||||
an override.
|
||||
|
||||
---
|
||||
|
||||
## 3. D-196: SettlementClass Enum and Latent Settlement Active/Ghost Logic
|
||||
|
||||
**Decision ID:** D-196
|
||||
**Domain:** architecture
|
||||
**Status:** candidate
|
||||
**Claimed by:** Burnelli-Sheldon
|
||||
|
||||
### Decision
|
||||
|
||||
All generated settlements carry a `SettlementClass` that determines their active/ghost
|
||||
derivation logic. This generalizes the prior latent-settlement concept from sub-settlements
|
||||
only to ALL generated settlements. Every settlement can be ghost; every settlement has
|
||||
a class-specific economic condition that determines whether it is.
|
||||
|
||||
### SettlementClass Enum
|
||||
|
||||
```rust
|
||||
pub enum SettlementClass {
|
||||
/// City exists because a named corp has headquarters_body = this body.
|
||||
/// Active if corp.health_metric > 0.0 AND corp.lifecycle_state != Dissolved.
|
||||
/// Ghost only on full corp dissolution — the most durable class.
|
||||
NameLocked,
|
||||
|
||||
/// City exists to satisfy population budget without a named attractor.
|
||||
/// Active if parent NameLocked settlement health_metric > 0.4.
|
||||
/// Ghost when its economic anchor declines.
|
||||
PopulationBudget,
|
||||
|
||||
/// Sub-settlement placed by economic trigger (mining camp, trade waypoint,
|
||||
/// agricultural node, shadow node). Active if triggering corp health_metric > 0.4.
|
||||
/// Most volatile class — directly tied to a single corp's health.
|
||||
EconomicTriggered { activating_corp_id: String },
|
||||
|
||||
/// Placed at Province centroid by population pressure, no geographic attractor.
|
||||
/// Active if province_avg_corp_health > 0.5.
|
||||
/// geographically_triggered = false; FoundingOrientation = AdminFacing.
|
||||
OrganicGrowth,
|
||||
}
|
||||
```
|
||||
|
||||
### Active/Ghost Derivation per Class
|
||||
|
||||
| Class | Active condition | Ghost condition |
|
||||
|-------|-----------------|-----------------|
|
||||
| NameLocked | corp health_metric > 0.0 AND lifecycle ≠ Dissolved | Corp fully dissolved |
|
||||
| PopulationBudget | nearest NameLocked city health_metric > 0.4 | Anchor city's corp collapses |
|
||||
| EconomicTriggered | activating_corp health_metric > 0.4 | Corp distressed or dissolved |
|
||||
| OrganicGrowth | province avg corp health > 0.5 | Province-wide economic decline |
|
||||
|
||||
### What "Ghost" Means for Rendering
|
||||
|
||||
A ghost settlement does not disappear. Its streets and building footprints are
|
||||
seed-locked and persist. The rendering system reads the active/ghost flag to:
|
||||
- Switch lighting to dark/emergency-only
|
||||
- Apply maximum tile condition decay (Broken tier)
|
||||
- Clear activity entity spawn points (no NPCs walking around)
|
||||
- Maintain road and building geometry unchanged
|
||||
|
||||
The physical city remains. Economic life drains out of it. This is the correct behavior
|
||||
confirmed by Amendment 5 (ghost towns as emergent rendering consequence, not a designed
|
||||
narrative feature).
|
||||
|
||||
### `placed_at_generation` Flag
|
||||
|
||||
An additional immutable boolean `placed_at_generation: bool` is set on each Province
|
||||
when a settlement exists in that Province at Layer 2 generation time. This is the only
|
||||
way to distinguish "AbandonedZone" from "WildernessBuffer" at runtime — both have
|
||||
`settlement_count == 0` in the active state, but only AbandonedZone has
|
||||
`placed_at_generation = true`.
|
||||
|
||||
---
|
||||
|
||||
## 4. D-197: prosperity_baseline Derivation Formula
|
||||
|
||||
**Decision ID:** D-197
|
||||
**Domain:** architecture
|
||||
**Status:** candidate
|
||||
**Claimed by:** Burnelli-Sheldon
|
||||
|
||||
### Decision
|
||||
|
||||
The `prosperity_baseline` field on each `DistrictSkeleton` is a seed-locked float
|
||||
in [0.05, 0.95] derived from economic data and topographic context. It represents what
|
||||
the district was economically *designed for* — not what it is now. The runtime economic
|
||||
simulation updates `prosperity_current`; `prosperity_baseline` is never updated after
|
||||
generation.
|
||||
|
||||
### Formula
|
||||
|
||||
```
|
||||
prosperity_baseline(district_i, city, body, system, terrain) =
|
||||
|
||||
// Base: economic tier normalized to [0.0, 1.0]
|
||||
base = clamp(economic_tier(body, system) / 5.0, 0.0, 1.0)
|
||||
|
||||
// Role modifier: flat offset per economic_role
|
||||
role_mod = ROLE_PROSPERITY_MODIFIER[normalize_economic_role(body.economic_role)]
|
||||
|
||||
// Gradient: positional rank along topographic high-to-low direction
|
||||
rank = positional_gradient_rank(district_i, all_districts, terrain.gradient_direction)
|
||||
// rank ∈ [0.0, 1.0]; 1.0 = highest ground; 0.0 = lowest ground
|
||||
magnitude = DISTRIBUTION_INDEX_SCALE[distribution_index(system)]
|
||||
gradient_offset = (rank - 0.5) × magnitude
|
||||
|
||||
// Paula's topographic modifier (additive)
|
||||
topo_mod = +0.05 if district_i elevation in top 30% of city elevation range
|
||||
= -0.05 if district_i elevation in bottom 20% vs. sea level
|
||||
= 0.00 otherwise
|
||||
|
||||
prosperity_baseline_i = clamp(base + role_mod + gradient_offset + topo_mod, 0.05, 0.95)
|
||||
```
|
||||
|
||||
### Role Prosperity Modifiers
|
||||
|
||||
| economic_role | role_mod |
|
||||
|--------------|---------|
|
||||
| extraction | -0.10 |
|
||||
| mining | -0.10 |
|
||||
| frontier | -0.20 |
|
||||
| research | +0.15 |
|
||||
| service_mixed | +0.10 |
|
||||
| commercial | +0.10 |
|
||||
| transit | 0.00 |
|
||||
| manufacturing | 0.00 |
|
||||
| agricultural | 0.00 |
|
||||
| energy | 0.00 |
|
||||
|
||||
### Distribution Index Scale (gradient magnitude)
|
||||
|
||||
| distribution_index | magnitude |
|
||||
|-------------------|-----------|
|
||||
| "stratified" | 0.70 (steep gradient: ±0.35 spread across districts) |
|
||||
| "moderate" | 0.20 (shallow gradient: ±0.10 spread) |
|
||||
| NULL (fallback) | 0.20 (treat as moderate) |
|
||||
|
||||
### Economic Tier Derivation (Fallback for NULL values)
|
||||
|
||||
Per Gap 2 in §0, `system_economy.economic_tier` is NULL for 97% of systems. Fallback:
|
||||
|
||||
```
|
||||
fn economic_tier(body: &Body, system: &SystemEconomy) -> f32 {
|
||||
if let Some(tier) = system.economic_tier { return tier as f32; }
|
||||
// Fallback from body population
|
||||
match body.population {
|
||||
p if p >= 5_000_000_000 => 5.0,
|
||||
p if p >= 1_000_000_000 => 4.0,
|
||||
p if p >= 100_000_000 => 3.0,
|
||||
p if p >= 10_000_000 => 2.0,
|
||||
_ => 1.0,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Two Distinct Fields — Naming Lock (CL-Q1)
|
||||
|
||||
- `prosperity_baseline: f32` — seed-locked, never updated after generation
|
||||
- `prosperity_current: f32` — runtime simulation state, updated by economic sim
|
||||
- `prosperity_delta: f32` — always derived: `prosperity_current - prosperity_baseline`. Never stored.
|
||||
|
||||
These three names are locked. They must not be conflated in implementation. Any code
|
||||
that updates `prosperity_baseline` after generation is a bug.
|
||||
|
||||
### Tile Condition Thresholds (from `prosperity_current`)
|
||||
|
||||
Locked from Paula's Round 2 proposal (Gestalt adopted):
|
||||
|
||||
| Tile condition | prosperity_current range |
|
||||
|---------------|------------------------|
|
||||
| Intact | > 0.63 |
|
||||
| Worn | 0.43–0.63 |
|
||||
| Cracked | 0.23–0.43 |
|
||||
| Broken | < 0.23 |
|
||||
|
||||
The offset from round numbers (0.63/0.43/0.23 rather than 0.60/0.40/0.20) prevents
|
||||
boundary oscillation when prosperity fluctuates near the threshold.
|
||||
|
||||
---
|
||||
|
||||
## 5. D-198: Economic Simulation Independence from Layer 1-2 Spatial Data
|
||||
|
||||
**Decision ID:** D-198
|
||||
**Domain:** architecture
|
||||
**Status:** candidate
|
||||
**Claimed by:** Burnelli-Sheldon
|
||||
|
||||
### Decision
|
||||
|
||||
The economic simulation runs exclusively on system-level aggregate data from
|
||||
`systems.db`. It does not need Layer 1-2 spatial output (drainage, settlement positions,
|
||||
road graphs, TerritorialStatus). Layer 1-2 generators read from the economic simulation's
|
||||
state at generation time, but the relationship is one-directional: sim → Layer 1-2.
|
||||
|
||||
### What the Economic Sim Needs (from systems.db)
|
||||
|
||||
| Table | Fields used |
|
||||
|-------|------------|
|
||||
| `bodies` | economic_role, population, settlement_pattern |
|
||||
| `system_economy` | economic_tier, economic_base_primary/secondary, distribution_index |
|
||||
| `system_gates` | gate_connections, gate_topology, hop_distance_from_gateway |
|
||||
| `corp_presence` | corp_id, location_id, primary_operation |
|
||||
| `corp_financial_state` | corp_id, health_metric |
|
||||
| `commodities` | base_price, elasticity, tier, production_ubiquity |
|
||||
| `production_chains` + `chain_inputs` | supply chain topology |
|
||||
| `gate_links` | inter-system commodity flow topology |
|
||||
|
||||
All of these exist in systems.db from build-time generation. The sim reads them at
|
||||
startup and maintains its own in-memory state from there.
|
||||
|
||||
### What the Economic Sim Does NOT Need
|
||||
|
||||
- Settlement positions (latitude/longitude on body surface)
|
||||
- Road graph topology (which road connects which town)
|
||||
- TerritorialStatus per Province
|
||||
- River network
|
||||
- Geographic attractor positions
|
||||
- District boundaries
|
||||
|
||||
These are consumed by Layer 3-4 rendering only. They never feed back into the
|
||||
economic simulation. Commodity prices, corp health, and trade flow are computed at the
|
||||
system and body level — not at the settlement or district level.
|
||||
|
||||
### Why This Matters for Architecture
|
||||
|
||||
This decision confirms Amendment 1 (three-tier execution) from the consultant review.
|
||||
The economic sim can start running immediately at game startup using only systems.db.
|
||||
Layer 1-2 generation runs on background threads in parallel with the sim — there is no
|
||||
handshake or synchronization point between them. The only flow from Layer 1-2 back to
|
||||
the sim would be a player-caused event (player destroys a mine → ECS event → sim
|
||||
receives production loss), but this is handled through the ECS event bus, not through
|
||||
spatial data structures.
|
||||
|
||||
### Render Layer Reads from Sim (One-Directional)
|
||||
|
||||
```
|
||||
Economic Sim (systems.db aggregates)
|
||||
│
|
||||
├──→ corp_financial_state.health_metric
|
||||
│ consumed by: Layer 4 renderer (settlement active/ghost)
|
||||
│ consumed by: District tile condition derivation
|
||||
│
|
||||
├──→ prosperity_current (per district, derived from corp health)
|
||||
│ consumed by: Layer 4 tile variant selection
|
||||
│
|
||||
└──→ regional_land_use (coarse biome-cell resolution updates)
|
||||
consumed by: Layer 2 hinterland renderer
|
||||
NOT ChunkMutations — different resolution (biome-cell, not tile-level)
|
||||
```
|
||||
|
||||
The flow from the sim to the renderer never requires re-running Layer 1-2 generation.
|
||||
A corp declining does not move a city. It makes the city look worse.
|
||||
|
||||
---
|
||||
|
||||
## 6. D-199: 6-Field Minimum Economic Read Set for City Generation Context
|
||||
|
||||
**Decision ID:** D-199
|
||||
**Domain:** architecture
|
||||
**Status:** candidate
|
||||
**Claimed by:** Burnelli-Sheldon
|
||||
|
||||
### Decision
|
||||
|
||||
The minimum economic data set required to generate a city's district distribution is
|
||||
six fields, queryable from systems.db at game startup and stored in
|
||||
`CityGenerationContext`. No runtime sim queries are needed during city generation.
|
||||
|
||||
### The Six Fields
|
||||
|
||||
| # | Field | Source table | Used in |
|
||||
|---|-------|-------------|---------|
|
||||
| 1 | `economic_role` | `bodies` | D-194 weight table row selector |
|
||||
| 2 | `settlement_pattern` | `bodies` | D-194 domed/cave override; latent settlement placement |
|
||||
| 3 | `economic_tier` | `system_economy` | D-197 prosperity_baseline base value |
|
||||
| 4 | `distribution_index` | `system_economy` | D-197 gradient magnitude |
|
||||
| 5 | `corp_presence` count | `corp_presence` | Corporate district intensity modifier |
|
||||
| 6 | `headquarters_system/body` match | `corporations` | D-195 attractor-matching hard constraint |
|
||||
|
||||
### How They Are Loaded
|
||||
|
||||
These fields are read once at game startup for all inhabited bodies and stored in the
|
||||
`CityGenerationContext` struct. At Layer 3 (city district generation, runtime on-demand),
|
||||
the generator reads from the struct, not from the database. No database queries during
|
||||
play.
|
||||
|
||||
```rust
|
||||
pub struct CityGenerationContext {
|
||||
pub body_id: String,
|
||||
pub city_id: String,
|
||||
pub city_name: String,
|
||||
pub political_archetype: PoliticalArchetype,
|
||||
pub prosperity_baseline: f32, // seed-locked Layer 3 output (computed, not stored)
|
||||
pub surrounding_biome: BiomeClass,
|
||||
pub road_entry_directions: Vec<CardinalDirection>,
|
||||
pub footprint_radius_km: f32,
|
||||
pub founding_orientation: FoundingOrientation,
|
||||
pub world_tier: WorldTier,
|
||||
|
||||
// The six economics fields:
|
||||
pub economic_role: EconomicRole, // (1) from bodies
|
||||
pub settlement_pattern: SettlementPattern, // (2) from bodies
|
||||
pub economic_tier: u8, // (3) from system_economy (fallback: population-derived)
|
||||
pub distribution_index: DistributionIndex, // (4) from system_economy (fallback: Moderate)
|
||||
pub corp_presence_count: u32, // (5) count from corp_presence
|
||||
pub has_hq_corp: bool, // (6) whether any corp.headquarters_body = this body
|
||||
}
|
||||
```
|
||||
|
||||
### Note on Nullability
|
||||
|
||||
Per §0 Gap 2: `economic_tier` and `distribution_index` are NULL for 97% of systems.
|
||||
The struct uses fallback derivation (see D-197 §4) at load time. The `Option<>` wrapper
|
||||
is resolved to a concrete value before the struct is constructed — null never reaches
|
||||
the generator.
|
||||
|
||||
---
|
||||
|
||||
## 7. TerritorialStatus Economic Threshold Confirmation
|
||||
|
||||
The converged algorithm from Round 2 notes (§3) is confirmed correct for the economics
|
||||
layer. The priority-ordered derivation is:
|
||||
|
||||
```rust
|
||||
fn derive_territorial_status(province: &ProvinceWorldState) -> TerritorialStatus {
|
||||
if province.placed_at_generation && !province.active {
|
||||
return TerritorialStatus::AbandonedZone;
|
||||
}
|
||||
if !province.placed_at_generation && province.settlement_count == 0 {
|
||||
return TerritorialStatus::WildernessBuffer;
|
||||
}
|
||||
if province.primary_economic_activity == EconomicActivity::Extraction
|
||||
&& province.corporate_presence_score > 0.4 {
|
||||
return TerritorialStatus::ExtractiveZone;
|
||||
}
|
||||
if province.jurisdiction_overlap_score > 0.3 {
|
||||
return TerritorialStatus::ContestZone;
|
||||
}
|
||||
if province.infrastructure_quality > 0.6 && province.corporate_road_maintenance > 0.5 {
|
||||
return TerritorialStatus::CoreTerritory;
|
||||
}
|
||||
TerritorialStatus::FrontierTerritory
|
||||
}
|
||||
```
|
||||
|
||||
**Economic threshold validation:**
|
||||
|
||||
- `corporate_presence_score > 0.4` for ExtractiveZone: this maps to "at least one corp
|
||||
with health_metric > 0.4 operating an extraction commodity in this Province." Correct.
|
||||
- `infrastructure_quality > 0.6` for CoreTerritory: this is a road/settlement density
|
||||
metric from Layer 2 generation, not an economic field. It does not need updating when
|
||||
economics changes. Correct — infrastructure is seed-locked at Layer 2.
|
||||
- `corporate_road_maintenance > 0.5` for CoreTerritory: this is an economics-variable
|
||||
field (corp health drives road maintenance). TerritorialStatus can therefore transition
|
||||
from CoreTerritory to FrontierTerritory as corps decline — the roads degrade, the
|
||||
classification degrades. This is the correct emergent behavior.
|
||||
|
||||
**One clarification needed:** `corporate_presence_score` is not a field I defined in my
|
||||
prior rounds. I assume it derives from: `sum(health_metric for corps in province) / corp_count`.
|
||||
If no corps are present, score = 0. Tyre should confirm this derivation in the
|
||||
implementation spec.
|
||||
|
||||
---
|
||||
|
||||
## 8. Remaining Open Items After Round 3
|
||||
|
||||
These are items where I have a position but am waiting for other agents' confirmation:
|
||||
|
||||
**For Tyre:**
|
||||
- Confirm `corporate_presence_score` derivation formula (§7 above)
|
||||
- Confirm `atlas_city_names` schema extends (not replaces) `atlas_cities`
|
||||
- Confirm ARCH-4 body_radius_km column is the right field for area_count formula
|
||||
|
||||
**For Gestalt:**
|
||||
- Confirm whether age character classes need a named field on `DistrictSkeleton` or
|
||||
whether `density_pct` + `perimeter_treatment` + `prosperity_baseline` are sufficient
|
||||
to express the full character difference between nascent and mature districts
|
||||
|
||||
**For Paula:**
|
||||
- Confirm corp dissolution signal: is `corp_lifecycle_events.event_type = 'Dissolved'`
|
||||
the canonical flag for NameLocked settlement ghost status, or is `health_metric = 0.0`
|
||||
the correct proxy? The schema has both; which is authoritative?
|
||||
|
||||
**Data quality action items (not blocking workshop, but blocking Phase 3 implementation):**
|
||||
1. Populate `bodies.founding_age_years` across all inhabited bodies (wiki data exists for settlement dates)
|
||||
2. Populate `system_economy.economic_tier` and `distribution_index` for all systems (or confirm fallback derivation is sufficient)
|
||||
3. Normalize `economic_role` values in bodies table (add migration to import_economics.py)
|
||||
@@ -0,0 +1,167 @@
|
||||
# Consultant Review — Planet-Down Cascade Workshop Brief
|
||||
|
||||
**Reviewer:** External strategic consultant
|
||||
**Date:** 2026-04-30
|
||||
**Status:** Lead-reviewed directives for incorporation into workshop brief before Round 1
|
||||
**Scope:** Architectural amendments, unlocked decisions, new workshop requirements
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The brief as written is thorough, well-structured, and asks the right questions. The cascade vision is correct. The following amendments reflect lead decisions made during review that change the brief's foundational assumptions in three areas: the execution model, the placement architecture, and the spatial hierarchy. These are **lead directives**, not open questions — the workshop designs algorithms within them.
|
||||
|
||||
---
|
||||
|
||||
## Amendment 1: Three-Tier Execution Model
|
||||
|
||||
**The brief's two-tier Phase 3/Phase 5 split is replaced.**
|
||||
|
||||
The brief states:
|
||||
- Phase 3 = Python tooling + systems.db (offline, Layers 1-2)
|
||||
- Phase 5 = Rust runtime + seed-derived (Layers 3-4)
|
||||
|
||||
The actual architecture is three tiers:
|
||||
|
||||
| Tier | When | Language | What | Storage |
|
||||
|------|------|----------|------|---------|
|
||||
| **Build-time** | `make regen-db` | Python | Atlas generation, system-level economics, star map, aggregate specs. Everything the economic simulation needs to run. | systems.db (permanent) |
|
||||
| **Runtime background** | Game session, background threads | Rust | Layer 1-2 cascade: heightmap drainage, settlement placement, road graphs, regional cell tagging. Runs per-body. Seed-deterministic. | Session DB (reproducible from seed) |
|
||||
| **Runtime on-demand** | Player proximity trigger | Rust | Layer 3-4 cascade: city district grids, block skeletons, street tiles. Generated as player approaches. | Never stored (regenerated from seed + Layer 1-2 output) |
|
||||
|
||||
**Why:** The economic simulation runs on system-level aggregate specs (economic_role, population, corp_presence, trade capacity) that already exist in systems.db from build-time generation. It does not need Layer 1-2 spatial detail — it does not need to know where mining camps sit on a planetary surface to calculate inter-system commodity flows. Layer 1-2 regional data (drainage, settlement positions, road graphs, TerritorialStatus) is consumed only by Layer 3 city planning and Layer 4 street rendering, both of which are proximity-triggered. Therefore Layer 1-2 generation does not need to be precomputed for all ~400-500 bodies at build time.
|
||||
|
||||
**What this means for `generate_regional.py`:** The Python implementation becomes a reference and validation tool. The runtime Layer 1-2 cascade is implemented in Rust. The algorithms must be identical — same seed, same heightmap, same output. The Python version can be used for offline testing, visualization, and regression validation.
|
||||
|
||||
**Implication for the workshop:** Algorithm designs for Layers 1-2 must be specified in a form that is implementable in Rust on a background thread. No Python-only dependencies. No assumptions about offline batch processing.
|
||||
|
||||
---
|
||||
|
||||
## Amendment 2: Background Generation and Precaching
|
||||
|
||||
**Layer 1-2 generation runs on background threads, spidering outward from the player.**
|
||||
|
||||
The priority queue is event-driven, not purely proximity-driven:
|
||||
|
||||
| Priority | Trigger | Rationale |
|
||||
|----------|---------|-----------|
|
||||
| **Immediate** | Player's current body | Must complete before the player sees anything |
|
||||
| **High** | Any system referenced in player-facing content: news ticker, dialogue, mission text, corporate records, Meridian broadcasts | The moment a system name is rendered to the player, that system's cascade is queued — by the time the player reads the sentence and thinks "where is that?", the cascade should be done |
|
||||
| **Medium** | Gate-adjacent systems, spidering outward from current position | Speculative pre-generation for likely travel destinations |
|
||||
| **Low** | Everything else, breadth-first along the gate graph | Background fill |
|
||||
|
||||
**Performance envelope:** Layer 1-2 for a single body should complete in low single-digit seconds. The cascade is: read a heightmap, run drainage, find attractors, place settlements, connect roads, tag cells. This is geometry and constraint solving on a coarse grid, not heavy simulation. The workshop should design algorithms with this performance target in mind.
|
||||
|
||||
**Planetary map dependency:** The planetary map UI requires Layer 1-2 output to render (cities, rivers, roads are all generated data). This means a body's cascade must complete before the player opens that body's map. For the player's current body this is trivially satisfied (immediate priority). For remote bodies, the event-driven queue handles the common case (player clicks a system the game just mentioned). Edge case: player browses to a system that hasn't been generated yet. The UI either shows a brief loading state or a diegetic "survey data unavailable" placeholder until the cascade completes.
|
||||
|
||||
---
|
||||
|
||||
## Amendment 3: Fully Generative Placement
|
||||
|
||||
**markers.json is stripped to topographic features only. All settlement and river placement is fully generative.**
|
||||
|
||||
**What markers.json retains:** Mountain ranges, seas, major coastline features — topographic features that are defined by the heightmap and do not depend on civilization.
|
||||
|
||||
**What markers.json loses:** City positions, city coordinates, river polylines, road paths. All of these become generator output.
|
||||
|
||||
**How cities are placed:** The generator reads the heightmap, finds geographic attractors (confluences, harbors, arable plains, mountain passes), and places settlements at attractors based on the body's economic profile and population data from systems.db. The placement algorithm is a constraint satisfaction problem: N settlements with known economic roles and populations, M geographic attractors with known characteristics, find the assignment that maximizes plausibility.
|
||||
|
||||
**How rivers are placed:** The generator runs drainage simulation from the heightmap. Water flows downhill. Rivers emerge from the drainage network. No authored river positions.
|
||||
|
||||
**How names work:** The authored layer controls identity, not placement. systems.db knows that a body has N named cities with specific roles and populations. It knows river names and mountain range names. The generator assigns these names to generated features based on matching criteria. Cities that must exist by name (because corporate records reference them as headquarters locations) are name-locked. Everything else gets generated names from the culture's naming pool.
|
||||
|
||||
**The constraint set is minimal:** The only hard requirement on city placement is that corporate cross-references in systems.db must resolve — if a corporation is headquartered in a named city, that city must exist and be the kind of place where that corporation would plausibly sit. All other placement is the generator's decision.
|
||||
|
||||
**What this eliminates from the workshop's open questions:**
|
||||
- **L1-Q1** (authored rivers vs. drainage network) — eliminated. No authored rivers to reconcile.
|
||||
- **L2-Q1** (city positions anchored vs. re-derived) — eliminated. All city positions are derived.
|
||||
- **CL-Q4** (authored rivers vs. drainage network, elevated) — eliminated. Same resolution as L1-Q1.
|
||||
|
||||
**What this adds to the workshop's open questions:**
|
||||
- **New: Attractor-matching algorithm.** How does the generator match N named cities with known economic roles to M geographic attractors? What are the matching heuristics? What happens when the best attractor for a logistics hub is also the best attractor for a fishing port?
|
||||
- **New: Name reservation fulfillment.** What data structure represents the name reservations from systems.db, and at what point in the cascade does the generator fulfill them?
|
||||
|
||||
---
|
||||
|
||||
## Amendment 4: Spatial Hierarchy Definition
|
||||
|
||||
**The workshop must lock down a coherent spatial hierarchy with defined dimensions at every tier.**
|
||||
|
||||
The following eight-tier hierarchy is the lead's naming directive:
|
||||
|
||||
| Tier | Name | Scale | Defined by |
|
||||
|------|------|-------|-----------|
|
||||
| 7 | **System** | Star system | Gate graph |
|
||||
| 6 | **Body** | Planet / moon / station | systems.db |
|
||||
| 5 | **Area** | Major topographic division | Heightmap feature boundaries (coastlines, mountain ranges) |
|
||||
| 4 | **Province** | Road-map travel region | Natural boundaries from Layer 1 (drainage basins, ridgelines) |
|
||||
| 3 | **Region** | City footprint + surroundings | Decomposition formula |
|
||||
| 2 | **District** | 512×512 sim tiles | `DistrictSkeleton` |
|
||||
| 1 | **Block** | 4×4 grid within district | `BlockSkeleton` |
|
||||
| 0 | **Chunk** | 64×64 tiles | `GeneratorChunkData` |
|
||||
|
||||
**Naming is locked.** These terms replace all informal usage in the brief ("regional grid," "city-local," "atlas-level"). The workshop should use this vocabulary consistently.
|
||||
|
||||
**Dimension locking required.** The workshop must define the tile/cell dimensions at every tier and confirm they scale coherently from chunk to body. This becomes a canonical reference table — the spatial language for all future work.
|
||||
|
||||
**Body size variation is absorbed at the area tier.** Tiers 0-4 (chunk through province) have fixed dimensions. Different-sized bodies (large garden planet vs. small moon vs. different-sized planets) produce different numbers of areas. Area count is a derived field on the body, computed from body size (radius, surface area, or equivalent physical parameter). The generator reads area count and subdivides the heightmap accordingly. Everything below that subdivision is scale-invariant.
|
||||
|
||||
**Workshop question:** Define the formula that maps body size to area count. Confirm that the fixed dimensions at tiers 0-4 produce sensible results for both the largest and smallest inhabited bodies in the Reach.
|
||||
|
||||
---
|
||||
|
||||
## Amendment 5: Determinism Rule — Correct Rationale
|
||||
|
||||
**The ghost town narrative must be relegated to emergent consequence. The determinism rule's actual motivation must be stated clearly.**
|
||||
|
||||
The brief currently weaves the "ghost city effect" through multiple sections as though it were a design goal: Paula's `prosperity_delta` architecture, the latent settlement principle, CL-Q3 (ruin lifecycle). Over four rounds of workshop discussion, a hypothetical edge case was elevated to a named architectural pattern. This must be corrected before the next workshop inherits it as design intent.
|
||||
|
||||
**The determinism rule exists for one reason: the player must never see streets change when they return to a location.** Layout is seed-locked because layout changes would be visible and jarring — the player's spatial memory of a place must be reliable. That is the full motivation. It is a player experience guarantee.
|
||||
|
||||
**Economic state affects rendering** — tile condition (Intact/Worn/Cracked/Broken), repair state, activity levels, visual prosperity signals — because these are continuous visual changes that don't break spatial memory. The two-field prosperity model (`prosperity_baseline` + `prosperity_current`) is the correct implementation mechanism for this rendering variation. `prosperity_delta` (the difference between them) should always be derived, never stored.
|
||||
|
||||
**Ghost towns, ruin decay paths, and the narrative gap between planned prosperity and current prosperity are emergent consequences, not design goals.** They may happen. They may be interesting when they happen. The workshop must not design features around them. Specifically:
|
||||
|
||||
- **CL-Q3 (Ruin lifecycle)** should be scoped as an emergent rendering outcome of the prosperity system, not as a dedicated design question requiring its own decay path mechanics.
|
||||
- The "ghost city effect" framing should be removed from architectural descriptions. The correct framing is: "the determinism rule produces stable layouts; the prosperity rendering system produces visual variation; the combination can produce interesting emergent results including apparent decline."
|
||||
- The latent settlement principle stands on its own merits (all plausible positions placed at generation, active/ghost driven by economic sim) without needing the ghost town narrative to justify it.
|
||||
|
||||
---
|
||||
|
||||
## Amendment 6: 10×9 Weight Table Unlocked
|
||||
|
||||
**The 10×9 `economic_role` → `DistrictType` weight table (lines 148-160 of the brief) is removed from Given Facts and moved to an open workshop question.**
|
||||
|
||||
**Lead challenge:** The table determines district type distribution from a single axis (economic role). This produces implausible settlements at the extremes — energy-producing cities with zero Commercial and zero Entertainment districts. A mining town doesn't have zero entertainment because it's a mining town. It has *rough* entertainment because miners drink.
|
||||
|
||||
The table encodes what a city *produces*. It does not encode what a city *needs*. Every settlement with people in it needs residential space, commerce, social venues.
|
||||
|
||||
**The workshop is directed to evaluate an alternative approach:** population size and settlement age determine a baseline district mix (because people live there and need services), and economic role modifies the proportions and character of that baseline (a mining town's commercial district is rougher and smaller than a research hub's). Larger and older settlements have more variety because more people need more services. No settlement above a minimum population has zero of any essential district type.
|
||||
|
||||
The workshop may retain the weight table if it can demonstrate that it produces plausible settlements across all economic roles and WorldTiers. The workshop may replace it with a population-and-age-driven algorithm. The workshop may propose a hybrid. The requirement is that the output feels like somewhere people live.
|
||||
|
||||
---
|
||||
|
||||
## Open Items Confirmed for Workshop
|
||||
|
||||
These items from the original analysis remain open and are confirmed as workshop scope:
|
||||
|
||||
1. **Multi-body scoping.** `CityGenerationContext` needs `body_id` or body-scoped `city_id`. Confirm the cascade runs independently per body and the struct reflects this.
|
||||
|
||||
2. **Grid resolution confirmation.** The 64×32 regional grid predates the spatial hierarchy defined in Amendment 4. Confirm that data resolutions at each tier of the hierarchy are appropriate and that no tier boundary creates ambiguity (e.g., a city straddling two cells with different values).
|
||||
|
||||
3. **Player-experience validation.** The workshop participant list has no dedicated player-experience advocate (OZZIE is not included). The lead should consider whether to add OZZIE for at least one round, or assign an existing participant to explicitly hold the question: "does this produce places that feel inhabited?"
|
||||
|
||||
---
|
||||
|
||||
## Questions Eliminated by These Amendments
|
||||
|
||||
The following open questions from the original brief no longer require workshop resolution:
|
||||
|
||||
- **L1-Q1** (authored rivers vs. drainage network) — rivers are fully generated
|
||||
- **L2-Q1** (city positions anchored vs. re-derived) — all positions are derived
|
||||
- **CL-Q4** (authored rivers vs. drainage network, elevated) — same as L1-Q1
|
||||
|
||||
---
|
||||
|
||||
*This review was conducted as an external sanity check on architectural principles. The amendments above reflect lead decisions made during review. They are directives to the workshop, not suggestions.*
|
||||
@@ -0,0 +1,383 @@
|
||||
---
|
||||
title: "Gestalt — Round 1: Inventory and Framing"
|
||||
description: "Per-layer inventory of reusable algorithms, novel design work, and data/schema questions. Prerequisite dependency map. Weight table challenge analysis. Systems design framing for the planet-down cascade."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: gestalt
|
||||
round: 1
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 1 (Gestalt)
|
||||
|
||||
**Role:** Systems design inventory and framing. For each layer: what algorithm exists or can be reused, what is genuinely novel design work, what is a data/schema question only. Prerequisite dependency map. Weight table challenge from Amendment 6.
|
||||
|
||||
---
|
||||
|
||||
## Part 0: Amendment Inventory — What Changed Before Round 1 Starts
|
||||
|
||||
The consultant review rewrites several foundational assumptions. These are lead directives, not proposals. Every layer analysis below operates within them.
|
||||
|
||||
| Amendment | What changed | Effect on workshop |
|
||||
|-----------|-------------|-------------------|
|
||||
| **1: Three-tier execution** | Phase 3/Phase 5 split replaced with build-time/runtime-background/runtime-on-demand | Layer 1-2 algorithms must be Rust-implementable on background threads. Python `generate_regional.py` becomes reference + validation only. |
|
||||
| **2: Background priority queue** | Layer 1-2 generates per body on priority queue: Immediate > High (player-facing mentions) > Medium (gate-adjacent) > Low | Performance target: low single-digit seconds per body for Layers 1-2. Algorithms must be designed to that budget. |
|
||||
| **3: Fully generative placement** | markers.json stripped to topographic features only. All city positions, river polylines, road paths become generator output. | L1-Q1, L2-Q1, CL-Q4 eliminated. Two new questions added: attractor-matching algorithm, name reservation fulfillment. |
|
||||
| **4: Spatial hierarchy locked** | 8-tier naming: System > Body > Area > Province > Region > District > Block > Chunk. Body size absorbed at Area tier. Fixed dimensions tiers 0-4. | All layer resolution references must use this vocabulary. Grid resolution confirmation required before Layer 1. |
|
||||
| **5: Determinism rationale corrected** | Player spatial memory, not ghost town narrative. prosperity_delta = derived-only, never stored. CL-Q3 reduced to emergent rendering consequence. | Ghost town framing retired from architectural descriptions. Prosperity rendering split unchanged, but motivation is explicit. |
|
||||
| **6: Weight table unlocked** | 10×9 table moved to open question. Lead challenge: table encodes what a city produces, not what it needs. | Workshop must evaluate population+age-driven baseline district mix. Weight table may be retained, replaced, or hybridized — must justify. |
|
||||
|
||||
**Questions eliminated before Round 1 begins:**
|
||||
- L1-Q1 (authored rivers vs. drainage) — all rivers generated from drainage
|
||||
- L2-Q1 (city positions anchored vs. re-derived) — all positions derived
|
||||
- CL-Q4 (authored rivers elevated) — same as L1-Q1
|
||||
|
||||
**New questions added by Amendment 3:**
|
||||
- Attractor-matching algorithm (see Layer 2 section)
|
||||
- Name reservation fulfillment (see Layer 2 section)
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Layer 1 — Empty World
|
||||
|
||||
### Inventory
|
||||
|
||||
| Category | Item | Status |
|
||||
|----------|------|--------|
|
||||
| **Reusable** | `planet_simulation.py` drainage/river algorithm | Exists in Python — must port to Rust. Algorithm is the reference; outputs must be identical. |
|
||||
| **Reusable** | 64×32 regional biome grid | Already generated by `generate_atlas.py`, stored in systems.db. Layer 1 reads this as input, extends it. |
|
||||
| **Reusable** | `is_coastal`, `water_fraction`, `terrain_roughness` columns | Already on `atlas_regional_biomes`. Layer 1 adds columns to this table. |
|
||||
| **Reusable** | Heightmap (512×256) | Already generated by `generate_atlas.py`, stored in systems.db as build-time artifact. |
|
||||
| **Novel design** | Sub-biome variant classification | 3-4 variants per biome class, seed-derived. No existing algorithm. Needs a seed-driven variant selection rule per biome class. |
|
||||
| **Novel design** | `terrain_modification_cost: f32` per cell | No existing field. Needs a derivation formula from biome class + terrain_roughness + water proximity. |
|
||||
| **Novel design** | Geographic feature tag extraction | No existing algorithm for identifying RiverConfluence, CoastalHarbor, MountainPass, ArablePlain, ResourceConcentration, Defensible, NaturalBarrier from the generated drainage network + heightmap. |
|
||||
| **Novel design** | Mountain pass identification | No existing algorithm. Candidate: regional cells with high `terrain_roughness` adjacent to lower cells on opposite sides. Needs threshold. |
|
||||
| **Data/schema** | `sub_biome_variant` column on `atlas_regional_biomes` | New column, no schema conflict. |
|
||||
| **Data/schema** | `terrain_modification_cost` column on `atlas_regional_biomes` | New column. |
|
||||
| **Data/schema** | Geographic feature points storage | Under Amendment 1, this is session DB (runtime background), not systems.db. The BRIEF's proposal to store confluence points in systems.db is overridden by Amendment 1 — all Layer 1 data is runtime Rust, session DB. |
|
||||
|
||||
### Open Questions After Amendments
|
||||
|
||||
| ID | Question | Prerequisite? | Type |
|
||||
|----|----------|--------------|------|
|
||||
| **L1-Q2** | River resolution: 64×32 regional-only vs. intermediate vs. tile-resolution seed-derived | **Yes** — determines Layer 2 settlement placement precision and Layer 4 tile generation | Scope decision |
|
||||
| **L1-Q3** | Under Amendment 1: Layer 1-2 lives in session DB. "Stored confluence points" now means session DB, not systems.db. Confirm this interpretation. | **Yes** — affects what Layer 2 can query at startup vs. what requires Layer 1 to complete first | Architecture |
|
||||
| **L1-Q4** | Economic role as biome prior in sub-biome selection | No — low-risk addition, can be deferred | Design |
|
||||
| **NEW** | Spatial hierarchy dimension locking (Amendment 4) | **Yes** — Area tier dimensions determine how many areas per body; Province tier resolution determines Layer 1 grid coverage | Architecture |
|
||||
| **NEW** | Body-size to area-count formula | **Yes** — prerequisite for all layer dimension calculations | Architecture |
|
||||
|
||||
### Systems Design Assessment
|
||||
|
||||
**What Layer 1 produces for gameplay:** The legibility layer. Players read terrain to predict where civilization sits before seeing a map label. River confluences → city probability. Mountain passes → chokepoints. Coastal harbors → trade hubs.
|
||||
|
||||
**Performance constraint (Amendment 2):** Drainage simulation + sub-biome classification + feature extraction must complete in ~1-2 seconds per body budget, leaving room for Layer 2. The 64×32 grid (2,048 cells) is small — this should be fast. The constraint is the drainage algorithm, which may require iteration to convergence. Needs a performance evaluation in Round 2.
|
||||
|
||||
**The geographic feature tag set is the key output for Layer 2.** Everything else (sub-biome variants, terrain modification costs) is either enrichment or downstream-derivable. The seven feature tags (RiverConfluence, CoastalHarbor, MountainPass, ArablePlain, ResourceConcentration, Defensible, NaturalBarrier) are what Layer 2's attractor-matching algorithm uses. Getting this right is Layer 1's most critical contribution.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Layer 2 — Population Overlay
|
||||
|
||||
### Inventory
|
||||
|
||||
| Category | Item | Status |
|
||||
|----------|------|--------|
|
||||
| **Reusable** | Sub-settlement placement triggers | Fully designed (Round 3): mining camps, trade waypoints, agricultural nodes, shadow nodes. Algorithms specified. |
|
||||
| **Reusable** | Road graph algorithm (Phase 3 Layer C) | Designed in Round 3. Under Amendment 3, ALL roads are generated (authored roads eliminated). The algorithm applies to all roads, not just local connectors. |
|
||||
| **Reusable** | TerritorialStatus derivation (Paula Round 4) | Algorithm described qualitatively. Thresholds (L2-Q5) still open. |
|
||||
| **Reusable** | PoliticalTether derivation | Distance + economic data approach established. |
|
||||
| **Novel design** | **Attractor-matching algorithm** (Amendment 3 new) | Constraint satisfaction: N named cities (known economic roles, populations, corp cross-refs) → M geographic attractors (known types, locations, capacities). Biggest novel design item in this workshop. |
|
||||
| **Novel design** | **Name reservation fulfillment** (Amendment 3 new) | How does the generator assign city names from systems.db to generated positions? Hard constraints: corp HQ cities must exist by name. Everything else: match by economic plausibility + cultural naming pool. |
|
||||
| **Novel design** | Hinterland shape algorithm (L2-Q4) | The spatial SHAPE of farmland, extraction corridors, wilderness buffers. Algorithmic proposal exists (cluster around agricultural cities + river corridors at density ∝ 1/terrain_modification_cost) but not locked. |
|
||||
| **Data/schema** | `land_use` column on regional cells | Already in Layer F design (Round 3). Enum: Urban / Agricultural / Industrial / Wilderness / Corridor / Ruins / Ocean / Impassable |
|
||||
| **Data/schema** | `TerritorialStatus` per regional cell | Six-value enum confirmed. Column or separate table? Under Amendment 1: session DB. |
|
||||
| **Data/schema** | `atlas_sub_settlements` session DB table | Table design needed. Fields: position, tier, PoliticalTether, active/ghost flag, founding trigger. |
|
||||
| **Data/schema** | Latent settlement active/ghost flag (L2-Q6) | Under Amendment 1: session DB is the right answer. Sim reads session DB settlement records + corp_financial_state to determine active/ghost. |
|
||||
|
||||
### Open Questions After Amendments
|
||||
|
||||
| ID | Question | Prerequisite? | Type |
|
||||
|----|----------|--------------|------|
|
||||
| **Attractor-matching** | How does generator match N named cities with known roles to M geographic attractors? What resolves conflicts? What are the matching heuristics? | **Yes** — Layer 2 cannot place settlements without this | Novel design |
|
||||
| **Name reservation** | What data structure represents name reservations from systems.db? When in the cascade does the generator fulfill them? | **Yes** — corporate cross-references in systems.db must resolve to actual city positions | Novel design |
|
||||
| **L2-Q2** | Sub-settlement exact positioning: seeded distance from tether city within [15%-40%] of body scale? Camp count formula? | No — partially specified, refinement in Round 2 | Design |
|
||||
| **L2-Q3** | Road algorithm: under Amendment 3, all roads generated. The algorithm from Phase 3 Layer C now handles ALL inter-settlement connections. Does this require any redesign? | No — simplifies the question (no authored road reconciliation needed) | Scope clarification |
|
||||
| **L2-Q4** | Hinterland spatial shape — cluster algorithm vs. Voronoi region-growing | No — minimum viable uses land_use tag; exact shape deferred | Design |
|
||||
| **L2-Q5** | TerritorialStatus quantitative thresholds | No — can be proposed in Round 2, doesn't block minimum viable | Design |
|
||||
| **L2-Q6** | Latent settlement flag storage: session DB (under Amendment 1 — clear answer) | No — Amendment 1 resolves this in favor of session DB | Architecture |
|
||||
|
||||
### The Attractor-Matching Algorithm — Systems Design Framing
|
||||
|
||||
This is the most novel design item in the entire workshop. Let me break down what it actually requires mechanically.
|
||||
|
||||
**Inputs:**
|
||||
- N settlements from systems.db: each has {name, population, economic_role, corp_presence refs, world_tier}
|
||||
- M geographic attractors from Layer 1: each has {type, location_cell, capacity_score}
|
||||
- Hard constraints: if corp X is headquartered at city Y, city Y must be placed at an attractor of appropriate type for corp X's industry
|
||||
|
||||
**The matching problem:**
|
||||
- Hard constraints first: corp HQ cities get attractor-locking priority
|
||||
- Soft constraints: agricultural economic_role prefers ArablePlain attractors; extraction prefers ResourceConcentration; transit prefers CoastalHarbor/RiverConfluence
|
||||
- Population-proportional: larger cities should claim larger/higher-capacity attractors
|
||||
- Conflict resolution: when two cities compete for the same best attractor, resolve by population (larger wins) + economic fit score
|
||||
|
||||
**Algorithm sketch (for Round 2 proposals):**
|
||||
1. Score all (city, attractor) pairs by fit: base score from economic_role × attractor_type match, weighted by population / attractor capacity
|
||||
2. Hard constraint pass: lock corp-required assignments first
|
||||
3. Greedy assignment: highest-scored pairs assigned first, consuming attractor capacity
|
||||
4. Remainder: less-ideal placements for cities that couldn't get their preferred attractor type
|
||||
|
||||
**What this creates for gameplay:** Cities sit where they make geographic sense. A player approaching a harbor city knows before arrival that it's likely commercial/transit-oriented. The geography teaches the economic character. This is legibility.
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Layer 3 — City-Level Planning
|
||||
|
||||
### Inventory
|
||||
|
||||
| Category | Item | Status |
|
||||
|----------|------|--------|
|
||||
| **Reusable** | DistrictSkeleton classification system | Rounds 1-3 design: Stage 1 (classification), Stage 2 (block grid). |
|
||||
| **Reusable** | City decomposition formula (D-C4) | `max(1, floor(population / 50_000))` with WorldTier ceilings. Locked. |
|
||||
| **Reusable** | CityGenerationContext struct | Defined in Given Facts. Needs `body_id` field added (confirmed open item). |
|
||||
| **Reusable** | SeedChain for districts | `district_seed = child_seed(world_seed, district_id)`. Locked. |
|
||||
| **Reusable** | prosperity_baseline derivation formula | Burnelli-Sheldon Round 3. Needs topography contribution decision. |
|
||||
| **Novel design** | FoundingOrientation spatial grid orientation (L3-Q1) | Does it modify district placement positions or only gradient direction? **Prerequisite decision** before Layer 3 implementation. |
|
||||
| **Novel design** | Political archetype spatial arrangement (L3-Q2) | Explicit spine/center/nodes vs. emergent from weight table. **Prerequisite decision.** |
|
||||
| **Novel design** | **Weight table challenge** (Amendment 6) | Population+age-driven baseline evaluation. See Part 5 of this document. |
|
||||
| **Data/schema** | `body_id` on CityGenerationContext | Missing. Multi-body scoping requires it. |
|
||||
| **Data/schema** | Grid resolution confirmation | 64×32 grid predates spatial hierarchy. Province/Region tier dimensions need confirmation. |
|
||||
|
||||
### Open Questions After Amendments
|
||||
|
||||
| ID | Question | Prerequisite? | Type |
|
||||
|----|----------|--------------|------|
|
||||
| **L3-Q1** | FoundingOrientation: spatial grid rotation vs. gradient direction only | **Yes** — Layer 3 implementation blocked until resolved | Architecture |
|
||||
| **L3-Q2** | Political archetype: explicit arrangement patterns vs. emergent | **Yes** — Layer 3 implementation blocked until resolved | Design |
|
||||
| **L3-Q3** | Arterial road layout: explicit vs. district boundary implicit | No — minimum viable uses implicit (district boundary = street) | Scope |
|
||||
| **L3-Q4** | Topographic constraints on zoning: city-local terrain roughness vs. treat all cities as flat | No — deferred in minimum viable slice | Design |
|
||||
| **L3-Q5** | prosperity_baseline topographic contribution | No — gradient direction from FoundingOrientation already set; topography as secondary input | Design |
|
||||
| **L3-Q6** | Sub-settlement depth: unified DistrictSkeleton path vs. simplified codepath for towns | No — functional, but cleaner architecture choice | Architecture |
|
||||
| **L3-Q7** | Port/station direction: orbital station directional info in Layer 3 | No — deferred | Scope |
|
||||
| **Weight table** | Evaluate population+age baseline vs. retain/replace 10×9 table | **Yes** — Layer 3 generation blocked on this choice | Design |
|
||||
| **Multi-body** | Add `body_id` to CityGenerationContext | Yes — cascade runs per body, struct must reflect this | Schema |
|
||||
|
||||
### Systems Design Assessment for Layer 3
|
||||
|
||||
**The key insight from Amendment 6:** The weight table is wrong about what it's modeling. It answers "what does this city PRODUCE" not "what do people in this city NEED." These are different questions with different correct answers.
|
||||
|
||||
- What a city produces → determines the dominant character and primary district types
|
||||
- What a city needs → determines the baseline of services that exist regardless of production
|
||||
|
||||
Every populated settlement needs residential space, food access (commercial/agricultural), social venues (entertainment in some form), and some administrative presence. The weight table's mining row has zero Entertainment — but miners drink. The energy row has zero Commercial — but energy workers buy things.
|
||||
|
||||
See Part 5 for the full weight table challenge analysis.
|
||||
|
||||
**FoundingOrientation spatial orientation (L3-Q1):** This is a prerequisite, and I have a position. The spatial grid should rotate. Here's why it creates interesting gameplay:
|
||||
|
||||
A player arriving at a PortFacing city knows the logistics and transit districts are at the harbor edge. They approach from that direction to find work, access, trade. A DefenseFacing city has its administrative core at the elevated/central point — the player looking for governance contacts approaches the high ground. This directional legibility is an important gameplay mechanic. If FoundingOrientation only changes the prosperity gradient without spatial rotation, the player has no directional prediction advantage. Gradient direction change alone doesn't produce the navigation legibility that makes archetypes meaningful.
|
||||
|
||||
**My recommended answer for L3-Q1:** Spatial grid rotation. FoundingOrientation defines which edge of the district grid faces the founding attractor (water edge, resource edge, high ground, etc.) and weights district type placement toward that edge. This is more complex than gradient-only but produces legible city orientation that the player can read from approach.
|
||||
|
||||
**Political archetype spatial arrangement (L3-Q2):** My recommended answer: explicit arrangement for the primary archetypes (CompanyTown, AdminCapital, FreePort), emergent for secondary archetypes. The three primary archetypes each produce a recognizably different navigation challenge that the player can learn. Emergent-only produces less consistent legibility and wastes the investment in the archetype system.
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Layer 4 — Street-Level Rendering
|
||||
|
||||
### Inventory
|
||||
|
||||
| Category | Item | Status |
|
||||
|----------|------|--------|
|
||||
| **Reusable** | Chunk streaming architecture (`chunk_streaming.rs`) | Architecturally sound and tested. `load_chunk()` creates blank placeholders — needs hookup to generator. |
|
||||
| **Reusable** | TileEntry struct upgrade | D-C7: `Vec<TileEntry>` with `tile_id: TileId` + `walkable: bool`. Locked. |
|
||||
| **Reusable** | Phase 2 tile algorithm | Street skeleton first (door-per-block-edge), then building fill (density-driven rectangles). Confirmed Rounds 2-3. |
|
||||
| **Reusable** | SeedChain for chunks | `chunk_seed = child_seed(district_seed, (cx << 32 | cy))`. Locked. |
|
||||
| **Novel design** | Economics-variable rendering mechanism (L4-Q1) | Three options — must choose. See analysis below. |
|
||||
| **Novel design** | Condition update trigger (L4-Q2) | When/how does tile condition recalculate? |
|
||||
| **Novel design** | Interior generation trigger (L4-Q4) | Pre-fetch, on-entry, or pre-generate? |
|
||||
| **Data/schema** | Tile condition enum | `Intact | Worn | Cracked | Broken` — 4 states × 3 tile types = 12 visual variants, 3 generation types. |
|
||||
| **Data/schema** | prosperity_delta (Amendment 5) | Always derived (`prosperity_current - prosperity_baseline`), never stored. Confirm this is the rendering input. |
|
||||
|
||||
### Open Questions After Amendments
|
||||
|
||||
| ID | Question | Prerequisite? | Type |
|
||||
|----|----------|--------------|------|
|
||||
| **L4-Q1** | Economics rendering mechanism: bake+event / compute live / cache+threshold | **Yes** — implementation decision blocks tile generator design | Architecture |
|
||||
| **L4-Q2** | Condition update trigger | No (follows from L4-Q1 choice) | Design |
|
||||
| **L4-Q3** | Phase 2 algorithm parameters: street corridor width, max building rectangle dimensions, gap rules | No — existing algorithm confirmed; parameters tunable | Tuning |
|
||||
| **L4-Q4** | Interior generation trigger: pre-fetch / on-entry / pre-generate | No — on-entry (option b) is correct for minimum viable | Scope |
|
||||
| **L4-Q5** | "Scatter civilization" scope: prop spawn points vs. decals vs. entity spawn | No — deferred to post-walkable-world | Scope |
|
||||
| **L4-Q6** | prosperity_delta two-field model formal adoption | No (Amendment 5 resolves: derived only, never stored) | Data model |
|
||||
|
||||
### Systems Design Position: L4-Q1 — Economics Rendering Mechanism
|
||||
|
||||
**My recommended answer: threshold-crossing cache invalidation (option c).**
|
||||
|
||||
Reasoning:
|
||||
- Districts are economically stable most of the time. A district crossing from Intact to Worn is a notable event — it should FEEL notable because it happens rarely.
|
||||
- Thresholds: Intact ≥ 0.6, Worn 0.4-0.6, Cracked 0.2-0.4, Broken < 0.2. These match the four condition states and map to the prosperity_current value.
|
||||
- Cache per chunk: condition state computed once on load, stored in chunk's in-memory representation, invalidated when prosperity_current crosses a threshold boundary.
|
||||
- Threshold crossings are cheap to check each tick: one float comparison per district, not per chunk.
|
||||
|
||||
**What this creates for gameplay:** The transition is visible and discrete. A player who visits a district and returns later sees it has crossed a threshold — noticeably different tile conditions. This is more legible than continuous micro-degradation the player can't perceive. The discrete transition signals: "something changed here economically." That signal is meaningful.
|
||||
|
||||
**Option (b) — compute live per frame — is wrong** for a different reason: tile conditions are conceptually stable between economic events. Recomputing from prosperity_current every frame wastes cycles and produces no additional fidelity, since prosperity_current doesn't change every frame.
|
||||
|
||||
**Option (a) — bake at generation + event update — requires an event system** for prosperity changes to invalidate the baked conditions. More complex than threshold-crossing, and produces similar results.
|
||||
|
||||
### Systems Design Position: L4-Q4 — Interior Generation
|
||||
|
||||
**Recommended: option (a), pre-fetch when player is within N tiles of a door.**
|
||||
|
||||
On-entry (option b) is simpler but creates a frame stall at the door threshold. Pre-fetch avoids the stall for the common case (player walks toward a door they intend to enter). The pre-fetch radius can be tuned — 2-3 tiles is sufficient to generate ahead of player movement speed.
|
||||
|
||||
For minimum viable: on-entry is acceptable and simpler. Pre-fetch is the target behavior.
|
||||
|
||||
---
|
||||
|
||||
## Part 5: Weight Table Challenge — Systems Design Analysis
|
||||
|
||||
### What the 10×9 Table Does
|
||||
|
||||
Maps `economic_role` → DistrictType probability weights. It answers: given what this city produces, what district types are likely?
|
||||
|
||||
### What It Gets Wrong
|
||||
|
||||
It treats economic production as the sole driver of settlement composition. People who live in a settlement create demand regardless of what the settlement produces. A mining town's workers need:
|
||||
- **Residential** (where they sleep)
|
||||
- **Commercial** (basic goods, food, equipment)
|
||||
- **Entertainment** (rough taverns, gambling, fighting arenas — exactly the kind miners would have)
|
||||
|
||||
The current extraction row weights: Ent = 0. That's wrong. The extraction row weights LogisticsHub at 30 and Industrial at 30 — but zero Entertainment produces a settlement where people live with no social venues. That's not a mining town, that's a labor camp.
|
||||
|
||||
### The Proposed Alternative: Two-Layer Model
|
||||
|
||||
**Layer 1 — Population baseline:** What every settlement has because people live there.
|
||||
|
||||
| Population | Minimum district set |
|
||||
|-----------|---------------------|
|
||||
| < 1,000 (outpost) | 1 Residential + economic role type |
|
||||
| 1,000–10,000 (town) | 1 Residential, 1 Commercial/Mix, 1 primary economic type |
|
||||
| 10,000–50,000 (small city) | 2+ Residential, 1 Commercial, 1 Entertainment, remainder from economic role |
|
||||
| 50,000+ (full city) | All essential types represented; economic role scales the proportions and character |
|
||||
|
||||
WorldTier maps to district count ceiling (D-C4, locked). This doesn't change count — it changes the guaranteed minimum composition within that count.
|
||||
|
||||
**Layer 2 — Economic role character modifier:** Within each guaranteed type, the economic role determines CHARACTER, not presence.
|
||||
|
||||
| Economic role | Residential character | Commercial character | Entertainment character |
|
||||
|--------------|-----------------------|----------------------|------------------------|
|
||||
| extraction | Worker barracks, austere | Supply depot, company store | Rough taverns, gambling |
|
||||
| research | Faculty housing, lab districts | Specialist equipment, academic bookshops | Cultural venues, quiet bars |
|
||||
| commercial | Varied, well-maintained | Dense, varied retail | Upscale entertainment |
|
||||
| mining | Worker housing, dormitories | Company store, equipment | Brawling bars, fight clubs |
|
||||
| frontier | Scattered, improvised | General goods, survival gear | Campfire social, informal |
|
||||
|
||||
The character variations don't require different DistrictType labels — they're expressed through `density_pct`, `prosperity_baseline`, `perimeter_treatment`, and ultimately the tile-level building patterns.
|
||||
|
||||
### What This Produces for Gameplay
|
||||
|
||||
- **Every settlement with people feels inhabited.** No zero-entertainment mining towns.
|
||||
- **District character varies meaningfully within types.** Two Residential districts are distinguishable by their economic context.
|
||||
- **Player knowledge compounds.** Learning "extraction Commercial is always a company store" is a useful prediction. Learning "research Entertainment has specific character" helps find contacts. The archetypes become player knowledge that pays off over time.
|
||||
- **Richer emergent variety.** Same district type, different character → more distinct settlements without adding more types.
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Replace the 10×9 weight table with the two-layer model.** Retain the economic role → DistrictType proportionality concept (extraction cities DO have more Industrial and LogisticsHub), but add a guaranteed baseline floor from population, and express role differences through district character rather than district presence/absence.
|
||||
|
||||
The weight table can be repurposed as a character modifier table rather than a presence/absence table.
|
||||
|
||||
---
|
||||
|
||||
## Part 6: Prerequisite Dependency Map
|
||||
|
||||
The order in which questions must be resolved. Questions higher in this map must be answered before questions below them can be implemented.
|
||||
|
||||
```
|
||||
BEFORE ANY LAYER:
|
||||
[A] Spatial hierarchy dimensions locked (Amendment 4)
|
||||
└─ body-size to area-count formula
|
||||
└─ Province / Region tier dimensions confirmed
|
||||
|
||||
BEFORE LAYER 1:
|
||||
[B] Spatial hierarchy confirmed against 64×32 grid
|
||||
└─ Does 64×32 correspond to Province grid? Or Area grid?
|
||||
|
||||
LAYER 1 (independent of Layer 2 except via geographic feature output):
|
||||
[C] L1-Q2: River resolution decision
|
||||
[D] L1-Q3: Session DB confirmation (Amendment 1 replaces systems.db proposal)
|
||||
[E] Drainage algorithm performance evaluation (can it run in ~1s per body?)
|
||||
|
||||
BEFORE LAYER 2:
|
||||
[F] Layer 1 geographic feature tags computed (Layer 1 output)
|
||||
[G] Attractor-matching algorithm designed (Amendment 3 new)
|
||||
[H] Name reservation fulfillment data structure (Amendment 3 new)
|
||||
|
||||
LAYER 2:
|
||||
[I] L2-Q5: TerritorialStatus thresholds
|
||||
[J] L2-Q3: Road algorithm scope under Amendment 3
|
||||
|
||||
BEFORE LAYER 3:
|
||||
[K] Layer 2 political_archetype + FoundingOrientation per city (Layer 2 output)
|
||||
[L] Weight table challenge resolved (Amendment 6) — PREREQUISITE
|
||||
[M] L3-Q1: FoundingOrientation spatial grid orientation — PREREQUISITE
|
||||
[N] L3-Q2: Political archetype spatial arrangement — PREREQUISITE
|
||||
|
||||
LAYER 3:
|
||||
[O] L3-Q5: prosperity_baseline topography contribution
|
||||
[P] L3-Q6: Sub-settlement unified vs. simplified codepath
|
||||
[Q] CityGenerationContext body_id field added
|
||||
|
||||
BEFORE LAYER 4:
|
||||
[R] Layer 3 DistrictSkeleton Stages 1-2 (Layer 3 output)
|
||||
[S] L4-Q1: Economics rendering mechanism — PREREQUISITE
|
||||
|
||||
LAYER 4:
|
||||
[T] L4-Q2: Condition update trigger (follows from S)
|
||||
[U] L4-Q4: Interior generation trigger
|
||||
```
|
||||
|
||||
**Critical path:** A → B → [C, D, E] → F+G+H → [I, J] → K+L+M+N → [O, P, Q] → R+S → [T, U]
|
||||
|
||||
**Parallelizable work:**
|
||||
- Layer 1 and Layer 3 development can proceed in parallel once [A, B, L, M, N] are resolved
|
||||
- Layer 2 and Layer 4 development can proceed in parallel once their respective prerequisites are met
|
||||
- Economics simulation does NOT need Layers 1-2 spatial data (Amendment 1 rationale) — economics development proceeds independently
|
||||
|
||||
---
|
||||
|
||||
## Part 7: Questions I'm Bringing to Round 2
|
||||
|
||||
I'm not waiting for convergence on these — I'm stating my positions now for other participants to react to.
|
||||
|
||||
| Question | My position |
|
||||
|----------|-------------|
|
||||
| L3-Q1: FoundingOrientation spatial effect | Spatial grid rotation, not gradient-only. Directional legibility is a gameplay mechanic. |
|
||||
| L3-Q2: Political archetype arrangement | Explicit arrangement for primary archetypes (CompanyTown, AdminCapital, FreePort). Emergent for secondary. |
|
||||
| L4-Q1: Economics rendering mechanism | Threshold-crossing cache invalidation. Discrete visible transitions, not continuous micro-degradation. |
|
||||
| L4-Q4: Interior generation trigger | Pre-fetch within N tiles for target; on-entry for minimum viable. |
|
||||
| Amendment 6: Weight table | Replace with two-layer model. Population baseline + economic role character modifier. |
|
||||
| Attractor-matching | Constraint satisfaction: hard constraints (corp HQs) first, then scored greedy assignment by economic fit + population. |
|
||||
| L1-Q3: River confluence storage | Session DB, not systems.db. Amendment 1 makes this the correct answer. |
|
||||
| CL-Q3: Ruin lifecycle | Emergent rendering consequence only (Amendment 5 settled this). Not a design question for Round 2. |
|
||||
|
||||
---
|
||||
|
||||
## Part 8: One Question for Each Participant
|
||||
|
||||
Before Round 2 proposals, I want to understand where each participant stands on the design questions that touch their domains.
|
||||
|
||||
**For Tyre:** Amendment 1 changes the execution model significantly. What is the session DB actually? Is it SQLite (in-memory or file)? A custom Rust cache structure? How is it invalidated between game sessions? The session DB is referenced in the amendment but not defined. This affects how Layer 1-2 data is queried by Layer 3-4.
|
||||
|
||||
**For Paula:** The attractor-matching algorithm has a hard constraint: corp HQ cities must exist at name. But what about cities that are referenced by political structures (seat of government, contested region capital)? Are there additional hard constraints from the narrative layer beyond corporate cross-references?
|
||||
|
||||
**For Burnelli-Sheldon:** The weight table challenge. Do you defend the 10×9 table as producing plausible settlements at the extreme cases (energy row: 0 Commercial, 0 Entertainment), or do you see the same problem the lead identified? If you see the problem, what's your preferred resolution — baseline floor on the existing table, or full replacement?
|
||||
|
||||
**For Ozzie:** This is the first round with Ozzie on the team. My specific question: for each of the four cascade layers, what's the moment where the player should feel something? What's the "wow" the generation is building toward? I want to make sure the systems design produces those moments, not just functional generation.
|
||||
|
||||
---
|
||||
|
||||
*Gestalt — Round 1. Written 2026-05-01.*
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,674 @@
|
||||
---
|
||||
title: "Gestalt — Round 3: Convergence, D-Records, Ticket Chain"
|
||||
description: "Final convergence positions, D-record candidates D-C18 through D-C34, implementation ticket dependency chain, and remaining open questions for lead."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: gestalt
|
||||
round: 3
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 3 (Gestalt)
|
||||
|
||||
**Role:** Convergence, D-record candidates, ticket dependency chain.
|
||||
|
||||
**Lead decision incorporated:** Two-tier mismatch flagging — score < 0.35 = warning (logged, no block), score < 0.15 = error (blocks generation, requires manual review).
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Convergence — Remaining Open Items
|
||||
|
||||
### Q1: Mismatch Flag Threshold — RESOLVED (Lead Decision)
|
||||
|
||||
Two-tier system:
|
||||
- **Warning (score < 0.35):** Logged to generation log. Generation proceeds. Flags that this placement is geographically suboptimal but physically valid.
|
||||
- **Error (score < 0.15):** Blocks generation. Requires manual review. Indicates a corp HQ is being placed in a location that is physically incompatible by Paula's H1-H4 hard constraints.
|
||||
|
||||
The two-tier system correctly handles the disagreement between Tyre (0.15 only catches outright incompatibilities) and Paula (0.35 catches borderline cases). Both are right — they're describing different severity levels. The lead's resolution names them explicitly.
|
||||
|
||||
**Implementation note:** Warning-level placements still generate. The generation log accumulates warning records that can be reviewed in bulk to catch systematic issues (e.g., a body where 80% of placements are at warning level suggests the body's economic profile doesn't match its heightmap — a systems.db authoring problem, not a generation bug).
|
||||
|
||||
### Q2: founding_age → layout_mode — EVALUATED
|
||||
|
||||
**Ozzie's proposal:** Old settlements get irregular layout modes; young settlements get grid layout.
|
||||
|
||||
**My evaluation:**
|
||||
|
||||
The proposal is mechanically correct in intent but wrong in formulation. A deterministic `old = irregular, young = grid` rule produces visual noise at edge cases (an ancient CompanyTown facility block shouldn't be irregular; a brand-new frontier town shouldn't be perfect grid) and removes interesting variance.
|
||||
|
||||
**My recommendation: founding_age as a probability weight on layout_mode selection, not a deterministic assignment.**
|
||||
|
||||
The three layout modes are Grid, Organic, and Mixed. The current model selects layout_mode from district_type + political_archetype. Founding_age should add a prior that shifts the probability distribution:
|
||||
|
||||
```rust
|
||||
fn layout_mode_weights(
|
||||
district_type: DistrictType,
|
||||
political_archetype: PoliticalArchetype,
|
||||
founding_age_years: u32,
|
||||
) -> [f32; 3] { // [Grid, Organic, Mixed]
|
||||
|
||||
// Base weights from archetype (unchanged)
|
||||
let base = archetype_layout_weights(district_type, political_archetype);
|
||||
|
||||
// Age prior: older → shifts probability toward Organic
|
||||
let age_organic_bonus = match founding_age_years {
|
||||
0..=49 => 0.0,
|
||||
50..=199 => 0.1,
|
||||
200..=499 => 0.25,
|
||||
_ => 0.40, // Ancient: strong irregular prior
|
||||
};
|
||||
|
||||
// But CompanyTown districts near facility resist age influence
|
||||
// (the facility's grid layout locks the surrounding blocks)
|
||||
let age_resistance = archetype_age_resistance(district_type, political_archetype);
|
||||
let effective_bonus = age_organic_bonus * (1.0 - age_resistance);
|
||||
|
||||
[
|
||||
(base[0] - effective_bonus * 0.5).max(0.05), // Grid: loses weight with age
|
||||
(base[1] + effective_bonus).min(0.90), // Organic: gains weight with age
|
||||
base[2], // Mixed: unchanged
|
||||
]
|
||||
}
|
||||
|
||||
fn archetype_age_resistance(dt: DistrictType, pa: PoliticalArchetype) -> f32 {
|
||||
match (dt, pa) {
|
||||
(DistrictType::Industrial, PoliticalArchetype::CompanyTown) => 0.9, // grid lock
|
||||
(DistrictType::Administrative, PoliticalArchetype::AdminCapital) => 0.6,
|
||||
(_, PoliticalArchetype::FreePort) => 0.0, // freePorts absorb age fully
|
||||
_ => 0.3, // default mild resistance
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**What this produces for gameplay:**
|
||||
- Ancient mining town: Industrial districts near the mine are still grid (facility planned it); residential and mixed districts have grown organically over centuries
|
||||
- New research hub: everything is clean grid — it was planned and built recently
|
||||
- Old FreePort: every district has grown organically — no planning authority was strong enough to maintain grid
|
||||
- Ancient AdminCapital: central administrative zone stays grid (government maintained it); outer residential rings are deeply organic
|
||||
|
||||
This is interesting spatial variety, not visual noise, because the variance has an in-world rationale the player can read. **Adopting Ozzie's proposal with this probability-weight formulation.**
|
||||
|
||||
### Q3: Province Boundary Legibility — ADDRESSED
|
||||
|
||||
**Ozzie's requirement:** Province boundaries must be visible on the planetary map as natural features, not arbitrary grid lines.
|
||||
|
||||
**This requirement is already satisfied by the architecture.** Here's why:
|
||||
|
||||
Province boundaries are drainage basin divides — the ridgelines and watershed boundaries that separate one river basin from another. The Layer 1 D8 drainage algorithm produces the drainage network; Province boundaries are the Voronoi-like regions around drainage confluences, bounded by the highest-elevation cells between them.
|
||||
|
||||
On the planetary map, the player sees:
|
||||
1. **Terrain heightmap** — renders the ridgelines and basin shapes naturally as topographic relief
|
||||
2. **River network overlay** — rivers flow through Province interiors toward confluence points
|
||||
3. **Settlement density variation** — CoreTerritory Provinces have visible settlement clusters; WildernessBuffer Provinces are empty
|
||||
|
||||
The Province boundary is visible as the ridge between two river valleys. The player doesn't need a political border drawn — they see a mountain range or watershed divide that happens to also be the Province boundary. This is the geography-is-political-history principle made visible.
|
||||
|
||||
**TerritorialStatus overlay** is a separate implant UI layer (togglable by the player). When active, it color-codes Provinces by TerritorialStatus. When inactive, the player reads the same information from terrain + settlement density + road quality. Both modes should be consistent — a player who understands the geography should be able to predict TerritorialStatus without activating the overlay.
|
||||
|
||||
**Implementation note for the client team (post-this-workshop):** The planetary map renderer needs to know Province boundaries, which come from the BodyWorldState drainage data. This means the planetary map can't render full Province detail until the Layer 1-2 generation for that body completes. Ozzie's diegetic placeholder tiers (from Round 2 notes) handle the interim state.
|
||||
|
||||
### Q4: atlas_city_names Population Path — ADDRESSED
|
||||
|
||||
How do city names get into `atlas_city_names`?
|
||||
|
||||
**Answer:** The same authoring process that currently populates `atlas_cities`. The wiki content (planet and city articles) is the source. The generate_atlas.py pipeline reads wiki data and populates bodies, systems, and city data into systems.db. When the Phase 3 generator (`generate_regional.py`) was designed, it was going to add city attributes. Under Amendment 3, city positions are now runtime-generated, so `atlas_cities` needs restructuring into `atlas_city_names`.
|
||||
|
||||
**Proposed workflow:**
|
||||
- Wiki author documents a city with name, population, and any corp HQ references
|
||||
- `generate_atlas.py` reads wiki data and populates `atlas_city_names` rows
|
||||
- No new authoring tool needed — the wiki content is already the source
|
||||
|
||||
This is a scope clarification, not a design question. The `atlas_city_names` schema (from ARCH-3) accepts the data; the existing wiki import pipeline provides it.
|
||||
|
||||
### Q5: Full Multiplier Table — CONFIRMED
|
||||
|
||||
Burnelli-Sheldon's table from Round 2 notes covers the primary economic roles. Confirming the full table including missing roles, consolidated to match the bodies.economic_role enum values:
|
||||
|
||||
The table is confirmed at the values in the Round 2 notes. The lead will confirm the full table in the D-record at Round 3. The generation code should treat this table as a runtime-configurable data asset, not hardcoded constants — enabling tuning without recompilation.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: D-Record Candidates
|
||||
|
||||
This workshop adds D-C18 through D-C34 to the candidate register. D-C1 through D-C17 are from the generation-cascade workshop and carried forward as Given Facts.
|
||||
|
||||
---
|
||||
|
||||
### D-C18: Three-Tier Execution Model
|
||||
|
||||
**Decision:** Layer 1-2 generation runs as Rust runtime-background threads per body (not Python build-time). Layer 3-4 runs as Rust runtime-on-demand triggered by player proximity. Python generators (`generate_atlas.py`, `generate_regional.py`) remain build-time for system-level economics data only.
|
||||
|
||||
**Rationale:** The economic simulation runs on system-level aggregate specs already in systems.db. It does not need Layer 1-2 spatial detail. Layer 1-2 is consumed only by Layer 3-4, both proximity-triggered. Therefore Layer 1-2 does not need precomputation for all ~400-500 bodies at build time. Runtime background generation completes in ~136ms per body — well within the single-digit-second performance budget.
|
||||
|
||||
**Inputs:** systems.db (heightmaps, city names, economics), world_seed
|
||||
**Outputs:** BodyWorldState in LRU GenerationCache (in-memory, reproducible from seed)
|
||||
**Execution tier:** Architecture decision spanning all tiers
|
||||
**Maps to ticket:** Background Thread Infrastructure (T-10)
|
||||
|
||||
---
|
||||
|
||||
### D-C19: Background Generation Priority Queue
|
||||
|
||||
**Decision:** Layer 1-2 generation requests are queued by priority: Immediate (player's current body) > High (bodies referenced in player-facing text, detected by Aho-Corasick scan) > Medium (gate-adjacent bodies) > Low (all others, breadth-first along gate graph).
|
||||
|
||||
**Rationale:** The moment a body name appears in news ticker, dialogue, or corporate records, the player may travel there. Pre-generating before the player decides avoids a visible loading state. Aho-Corasick is O(N) text scanning across all body names.
|
||||
|
||||
**Inputs:** Player position, recently-rendered text buffers, gate graph adjacency
|
||||
**Outputs:** Priority-ordered generation queue feeding the rayon thread pool
|
||||
**Execution tier:** Runtime-background infrastructure
|
||||
**Maps to ticket:** Background Thread Infrastructure (T-10)
|
||||
|
||||
---
|
||||
|
||||
### D-C20: Fully Generative Placement (Amendment 3)
|
||||
|
||||
**Decision:** markers.json retains topographic features (mountain ranges, seas, coastlines) only. All city positions, river polylines, and road paths are generator outputs. `atlas_city_names` stores name reservations (identity); the generator produces positions.
|
||||
|
||||
**Rationale:** Authored positions become stale as geography generation parameters change. Fully generative positions stay consistent with generated terrain. The constraint set is minimal: corporate cross-references in systems.db must resolve; everything else is generator output.
|
||||
|
||||
**Inputs:** atlas_city_names (name reservations + HQ constraints), Layer 1 geographic features
|
||||
**Outputs:** GeneratedSettlement positions in BodyWorldState
|
||||
**Execution tier:** Runtime-background (Layer 2)
|
||||
**Maps to ticket:** atlas_city_names Schema (T-03), Layer 2 Population Overlay (T-06)
|
||||
|
||||
---
|
||||
|
||||
### D-C21: Spatial Hierarchy — Eight Tiers with Locked Dimensions
|
||||
|
||||
**Decision:** The spatial vocabulary for all generation work uses eight named tiers:
|
||||
|
||||
| Tier | Name | Dimensions | Notes |
|
||||
|------|------|-----------|-------|
|
||||
| 0 | Chunk | 64×64 sim tiles (~32m×32m) | Immutable from prior architecture |
|
||||
| 1 | Block | 128×128 sim tiles (~64m×64m) | 2×2 chunks |
|
||||
| 2 | District | 512×512 sim tiles (~256m×256m) | 4×4 blocks |
|
||||
| 3 | Region | City district grid bounding box | Semantic tier; not a storage tier |
|
||||
| 4 | Province | 1 regional grid cell (~540km×270km) | Drainage basin; watershed-bounded |
|
||||
| 5 | Area | 1 contiguous terrain feature zone | Atlas-level; body size absorbed here |
|
||||
| 6 | Body | Full surface | Varies by body_radius_km |
|
||||
| 7 | System | Star system | Gate graph |
|
||||
|
||||
Area count = `round(sqrt(body_surface_area_km2 / 6_000_000))`. Reference body (radius ~5,000km): 7 areas. Small moon (radius ~500km): 1 area.
|
||||
|
||||
**Rationale:** Consistent vocabulary prevents scope confusion across implementation teams. Tiers 0-4 have fixed dimensions; body size variation is absorbed at Area tier.
|
||||
|
||||
**Inputs:** body_radius_km from bodies table
|
||||
**Outputs:** Tier naming conventions, area_count field per body
|
||||
**Execution tier:** Build-time (area_count computed by generate_atlas.py)
|
||||
**Maps to ticket:** body_radius_km Column (T-04)
|
||||
|
||||
---
|
||||
|
||||
### D-C22: D8 Drainage Routing for River Networks
|
||||
|
||||
**Decision:** Layer 1 river network generation uses D8 drainage routing (8-directional steepest descent) on the 512×256 heightmap. River threshold: cells with flow_accumulation > 200 are river cells. Confluence nodes = cells with ≥2 incoming river tributaries above threshold. River mouths = river cells adjacent to sea-level boundary.
|
||||
|
||||
**Rationale:** D8 is the standard efficient drainage algorithm for grid-based heightmaps. It is fully deterministic from the heightmap + seed. The 200-cell threshold produces rivers visible at regional scale without excessive fragmentation. Algorithm is portable from `planet_simulation.py` Python reference.
|
||||
|
||||
**Inputs:** atlas_body_heightmaps BLOB (float32 LE, 512×256), sea_level
|
||||
**Outputs:** RiverNetwork (river_cells, confluence_nodes, river_mouths, flow_dir, accumulation) in BodyWorldState
|
||||
**Execution tier:** Runtime-background (Layer 1)
|
||||
**Maps to ticket:** Layer 1 Empty World Generator (T-05), Heightmap BLOB Storage (T-01)
|
||||
|
||||
---
|
||||
|
||||
### D-C23: Geographic Feature Tag Extraction
|
||||
|
||||
**Decision:** Seven geographic feature tags are extracted per regional cell (64×32 grid) from Layer 1 drainage and heightmap data. Tags are the inputs to Layer 2 attractor-matching.
|
||||
|
||||
| Tag | Derivation rule |
|
||||
|-----|----------------|
|
||||
| RiverConfluence | Any confluence node within cell's heightmap bbox |
|
||||
| CoastalHarbor | is_coastal && terrain_roughness < 0.3 |
|
||||
| MountainPass | terrain_roughness > 0.65 && lower cells on two opposing sides |
|
||||
| ArablePlain | terrain_roughness < 0.25 && terrestrial && arable biome class |
|
||||
| ResourceConcentration | economic_role-weighted biome prior (L1-Q4 included) |
|
||||
| Defensible | terrain_roughness > 0.5 && approach_vectors ≤ 2 |
|
||||
| NaturalBarrier | ocean || terrain_roughness > 0.85 || impassable biome |
|
||||
|
||||
**Rationale:** These seven tags capture the geographic configurations that historically produce settlements. They are the algorithmic reification of "geography is political history." Including L1-Q4 (economic role as biome prior) is low-cost and prevents self-contradictory worlds.
|
||||
|
||||
**Inputs:** RiverNetwork (from D-C22), atlas_regional_biomes, bodies.economic_role
|
||||
**Outputs:** Vec<(RegionalCellId, Vec<GeographicFeatureTag>)> in BodyWorldState
|
||||
**Execution tier:** Runtime-background (Layer 1)
|
||||
**Maps to ticket:** Layer 1 Empty World Generator (T-05)
|
||||
|
||||
---
|
||||
|
||||
### D-C24: Sub-Biome Variant Classification
|
||||
|
||||
**Decision:** Each regional cell receives a sub-biome variant tag (3-4 variants per biome class), seed-derived using FNV-1a child_seed, with economic role as a probability prior (L1-Q4 included). `terrain_modification_cost: f32` computed analytically per cell (roughness × 0.6 + biome_clearing_cost, clamped [0,1]).
|
||||
|
||||
**Rationale:** Sub-biome variants give regional cells distinct character without authoring. Terrain modification cost is the Layer 2 input that governs hinterland shape — cheap terrain becomes farmland, expensive terrain stays wilderness.
|
||||
|
||||
**Inputs:** atlas_regional_biomes (biome_class, terrain_roughness), bodies.economic_role, world_seed
|
||||
**Outputs:** sub_biome_variant, terrain_modification_cost per cell in BodyWorldState
|
||||
**Execution tier:** Runtime-background (Layer 1)
|
||||
**Maps to ticket:** Layer 1 Empty World Generator (T-05)
|
||||
|
||||
---
|
||||
|
||||
### D-C25: Attractor-Matching Algorithm — Five-Phase Pipeline
|
||||
|
||||
**Decision:** Settlement placement uses a five-phase pipeline:
|
||||
1. Score matrix + H1-H4 hard zeros (maritime/coastal/resource/slope/orbital constraints)
|
||||
2. Sort into constraint tiers: A (extraction, most constrained) → B (manufacturing) → C (service)
|
||||
3. Tier A greedy assignment (fewest valid attractors first)
|
||||
4. Hungarian maximum-weight bipartite matching on Tier B+C
|
||||
5. Synthetic attractor overflow for cities exceeding attractor supply
|
||||
|
||||
**Mismatch flagging (lead decision):** Warning (logged, no block) at score < 0.35; Error (blocks generation) at score < 0.15.
|
||||
|
||||
**FoundingOrientation** derived post-assignment: geographically_triggered cities derive from attractor_type; synthetic placements = AdminFacing.
|
||||
|
||||
**Rationale:** Priority ordering for the most-constrained cities prevents geographic mismatches from cascading. Hungarian algorithm guarantees optimal total compatibility score at N≤30, O(N³) ≈ negligible (~15ms). Synthetic attractors maintain the determinism principle (all positions placed at generation time).
|
||||
|
||||
**Inputs:** NamedCity records from atlas_city_names, GeographicAttractor list from D-C23, corp_presence from systems.db
|
||||
**Outputs:** Vec<GeneratedSettlement> (position, founding_orientation, geographically_triggered, political_tether) in BodyWorldState
|
||||
**Execution tier:** Runtime-background (Layer 2)
|
||||
**Maps to ticket:** Layer 2 Population Overlay (T-06)
|
||||
|
||||
---
|
||||
|
||||
### D-C26: SettlementClass Enum
|
||||
|
||||
**Decision:** All generated settlements have a `SettlementClass` field determining their activation conditions:
|
||||
|
||||
```rust
|
||||
enum SettlementClass {
|
||||
NameLocked, // Named in atlas_city_names; always generated; activation = always active
|
||||
PopulationBudget, // Unnamed; active if body_population_density > threshold
|
||||
EconomicTriggered, // Unnamed; active if route_traffic_score > threshold
|
||||
OrganicGrowth, // Unnamed; geographically_triggered = false
|
||||
}
|
||||
```
|
||||
|
||||
`active: bool` on GeneratedSettlement is the runtime economic sim state. `placed_at_generation: bool` on ProvinceWorldState is immutable.
|
||||
|
||||
**Rationale:** The distinction between "never settled" (WildernessBuffer) and "settled then abandoned" (AbandonedZone) requires the immutable generation-time flag. SettlementClass generalizes the latent settlement principle across all settlement types.
|
||||
|
||||
**Inputs:** NamedCity records (NameLocked), economic simulation state (EconomicTriggered), population density (PopulationBudget)
|
||||
**Outputs:** SettlementClass per GeneratedSettlement
|
||||
**Execution tier:** Layer 2 (class assignment); runtime simulation (active flag updates)
|
||||
**Maps to ticket:** Layer 2 Population Overlay (T-06)
|
||||
|
||||
---
|
||||
|
||||
### D-C27: TerritorialStatus Priority-Ordered Derivation
|
||||
|
||||
**Decision:** TerritorialStatus per Province is derived by priority-ordered classification (first match wins):
|
||||
1. AbandonedZone: placed_at_generation && !active
|
||||
2. WildernessBuffer: !placed_at_generation && settlement_count == 0
|
||||
3. ExtractiveZone: primary_economic_activity == Extraction && corporate_presence_score > 0.4
|
||||
4. ContestZone: jurisdiction_overlap_score > 0.3
|
||||
5. CoreTerritory: infrastructure_quality > 0.6 && corporate_road_maintenance > 0.5
|
||||
6. FrontierTerritory: default
|
||||
|
||||
**Rationale:** Priority ordering makes the algorithm explicit and debuggable. The single continuous threshold (infrastructure_quality > 0.6) separates Core from Frontier. Economic proxy conditions (corporate_presence_score, jurisdiction_overlap_score) reflect the underlying political economy without requiring per-cell authored data.
|
||||
|
||||
**Province boundary visualization:** Province boundaries are drainage basin divides (ridgelines between river basins from D8 routing) — they are topographic terrain features, legible as such on the planetary map without explicit border rendering. A TerritorialStatus overlay in the implant UI provides explicit color-coding when the player activates it.
|
||||
|
||||
**Inputs:** ProvinceWorldState (settlements, road_edges, geographic_features, corp_presence)
|
||||
**Outputs:** TerritorialStatus per Province in BodyWorldState
|
||||
**Execution tier:** Runtime-background (Layer 2, post-settlement placement)
|
||||
**Maps to ticket:** Layer 2 Population Overlay (T-06)
|
||||
|
||||
---
|
||||
|
||||
### D-C28: Three-Component District Mix Algorithm
|
||||
|
||||
**Decision:** District type allocation uses three components in sequence:
|
||||
|
||||
**Component 1 — Population tier guarantees (minimum required types):**
|
||||
|
||||
| Population | Guaranteed types |
|
||||
|-----------|----------------|
|
||||
| < 1,000 | Residential, Mixed |
|
||||
| 1,000–9,999 | Residential, Commercial, Mixed |
|
||||
| 10,000–99,999 | + Entertainment |
|
||||
| 100,000–499,999 | + Administrative |
|
||||
| 500,000+ | + Industrial/Civic if role-appropriate |
|
||||
|
||||
Entertainment is guaranteed at 10,000+ (City tier), not Town tier. Below 10k, entertainment exists inside Commercial/Mixed character.
|
||||
|
||||
**Component 2 — Economic role integer multiplier table (min value 3, row sums to 100):** Full table from Burnelli-Sheldon Round 2, with political archetype modifiers stacked on top (CompanyTown: Industrial +10, Administrative −10; AdminCapital: Administrative +15; FreePort: Mixed +8, Administrative −20).
|
||||
|
||||
**Component 3 — Founding age character modifier:** Age modifies prosperity_baseline and character tags. Ages: New (0-49yr), Established (50-199yr), Mature (200-499yr), Ancient (500yr+). Applies only at Town tier and above, and only at Backwater WorldTier and above.
|
||||
|
||||
**Self-containment:** Algorithm references only local city/body/system fields. No neighboring city queries.
|
||||
|
||||
**Rationale:** Every settlement with people feels inhabited (no zero-weight types; guaranteed floors). Economic role determines character and proportion, not presence. Age produces accumulated variety without destroying economic identity.
|
||||
|
||||
**Inputs:** CityGenerationContext (population, economic_role, founding_age_years, world_tier, political_archetype)
|
||||
**Outputs:** Vec<DistrictType> allocations per city
|
||||
**Execution tier:** Runtime-on-demand (Layer 3)
|
||||
**Maps to ticket:** Layer 3 City Planning (T-07)
|
||||
|
||||
---
|
||||
|
||||
### D-C29: FoundingOrientation Spatial Grid Rotation
|
||||
|
||||
**Decision:** FoundingOrientation modifies district placement positions (spatial grid rotation), not just the prosperity gradient direction. The district grid is oriented so the primary edge faces the founding geographic attractor.
|
||||
|
||||
Orientation → primary edge mapping:
|
||||
- PortFacing → South (harbor at south edge)
|
||||
- ResourceFacing → East
|
||||
- DefenseFacing → North (elevated/inland)
|
||||
- RailHeadFacing → West
|
||||
- AdminFacing → North (administrative at top)
|
||||
|
||||
**Rationale:** Directional legibility is a player mechanic — players approaching a PortFacing city from the sea should see the harbor face. Gradient-direction-only produces no approach-direction information. Spatial rotation makes city orientation readable from the planetary map.
|
||||
|
||||
**Inputs:** FoundingOrientation (from Layer 2 attractor-matching), district count, city footprint
|
||||
**Outputs:** Rotated district position grid in city-local sim tiles
|
||||
**Execution tier:** Runtime-on-demand (Layer 3)
|
||||
**Maps to ticket:** Layer 3 City Planning (T-07)
|
||||
|
||||
---
|
||||
|
||||
### D-C30: Five Explicit Political Archetype Spatial Patterns
|
||||
|
||||
**Decision:** All five political archetypes produce distinct, explicitly implemented district spatial arrangements:
|
||||
|
||||
| Archetype | Pattern | Player experience |
|
||||
|-----------|---------|-----------------|
|
||||
| CompanyTown | Spine: facility at primary edge, residential cascading back | Player feels city's posture; geometry implies surveillance |
|
||||
| AdminCapital | Radial: administrative hub at center, prosperity gradient decreasing outward | Power visible from everywhere; hub legible from any approach |
|
||||
| FreePort | Multi-node: 3-5 nodes with distinct character, no dominant center | Productively disorienting; nodes must be visually distinct landmarks |
|
||||
| Contested | Dual-center overlay: two underlying geometries from competing powers, seams visible | Player reads conflict in urban structure without text; both geometries must be legible |
|
||||
| OrganicGrowth | Irregular local density: center emerged, not declared; no spine, no hub, no nodes | Most "lived-in" feel; reads as older and more authentic |
|
||||
|
||||
**Acceptance criterion (Ozzie):** Player identifies archetype from 15 seconds of walking around, without consulting the implant UI.
|
||||
|
||||
**Rationale:** Emergent-only patterns produce inconsistent legibility and waste the investment in the archetype system. Explicit patterns create player knowledge that pays off over multiple cities. Same archetype = consistent navigation challenge = player skill progression.
|
||||
|
||||
**Inputs:** PoliticalArchetype, district_positions, district_types from D-C28
|
||||
**Outputs:** Spatially arranged district positions conforming to pattern
|
||||
**Execution tier:** Runtime-on-demand (Layer 3)
|
||||
**Maps to ticket:** Layer 3 City Planning (T-07)
|
||||
|
||||
---
|
||||
|
||||
### D-C31: founding_age → layout_mode Probability Weighting
|
||||
|
||||
**Decision:** Founding age shifts layout_mode selection probabilities (not deterministic assignment). Older settlements have higher Organic layout probability; younger settlements have higher Grid probability. Archetype-type combinations that resist age influence (e.g., CompanyTown Industrial districts) have reduced age effect.
|
||||
|
||||
Age organic bonus: 0.0 (New <50yr) / 0.1 (Established 50-199yr) / 0.25 (Mature 200-499yr) / 0.40 (Ancient 500yr+). Resistance: CompanyTown Industrial = 0.9, AdminCapital Administrative = 0.6, FreePort = 0.0, default = 0.3.
|
||||
|
||||
**Rationale:** Deterministic age-to-layout assignment produces edge case failures (ancient planned facilities shouldn't be irregular). Probability weighting encodes the design intent while preserving interesting variance. The in-world rationale is legible: ancient cities grew organically over time; planned industrial facilities maintain their layout regardless of age.
|
||||
|
||||
**Inputs:** founding_age_years, DistrictType, PoliticalArchetype
|
||||
**Outputs:** layout_mode per DistrictSkeleton
|
||||
**Execution tier:** Runtime-on-demand (Layer 3)
|
||||
**Maps to ticket:** Layer 3 City Planning (T-07)
|
||||
|
||||
---
|
||||
|
||||
### D-C32: Tile Condition Threshold Values
|
||||
|
||||
**Decision:** Tile condition derives from prosperity_current using offset thresholds:
|
||||
- Intact: prosperity_current ≥ 0.63
|
||||
- Worn: 0.43 ≤ prosperity_current < 0.63
|
||||
- Cracked: 0.23 ≤ prosperity_current < 0.43
|
||||
- Broken: prosperity_current < 0.23
|
||||
|
||||
Chunk invalidation: cached condition state regenerated when prosperity_current crosses a threshold boundary. Check per district per economic tick (one float comparison per district).
|
||||
|
||||
**Rationale:** Offset values (0.63/0.43/0.23 rather than 0.60/0.40/0.20) prevent oscillation when prosperity fluctuates near a boundary. Threshold-crossing invalidation avoids per-frame recomputation while keeping condition state current. Most districts are economically stable — invalidation is rare.
|
||||
|
||||
**Inputs:** prosperity_current per district (runtime economic sim)
|
||||
**Outputs:** TileCondition enum per chunk (cached in ChunkConditionState)
|
||||
**Execution tier:** Runtime-on-demand (Layer 4, cached)
|
||||
**Maps to ticket:** Layer 4 Street Rendering (T-08)
|
||||
|
||||
---
|
||||
|
||||
### D-C33: ARCH-1 Heightmap BLOB Storage Schema
|
||||
|
||||
**Decision:** Heightmap stored as `atlas_body_heightmaps(body_id, data BLOB)` where data is float32 little-endian, 512×256 entries = 524,288 bytes per body (~512KB). No compression. `bytemuck::cast_slice` for zero-copy deserialization in Rust.
|
||||
|
||||
**Rationale:** Storage ~200MB for 400 bodies — acceptable. No compression needed at this size. Zero-copy deserialization is O(1) and avoids allocation.
|
||||
|
||||
**Maps to ticket:** Heightmap BLOB Storage (T-01)
|
||||
|
||||
---
|
||||
|
||||
### D-C34: Background Thread Infrastructure
|
||||
|
||||
**Decision:** Layer 1-2 generation runs on a rayon thread pool via `GenerationQueue` (BinaryHeap by priority). Results stored in `GenerationCache` (LRU, 50 bodies, Arc<BodyWorldState>). Text scanning for High-priority trigger uses Aho-Corasick on system/body names. Diegetic placeholder tiers (four levels from terrain-only down to blinking cursor) handle incomplete generation states on the planetary map.
|
||||
|
||||
**Rationale:** Rayon provides work-stealing thread pool without requiring a custom executor. LRU at 50 bodies keeps memory bounded (~5MB) while covering all realistic player proximity scenarios. Aho-Corasick is O(text_length) regardless of pattern count — efficient for scanning all body names against rendered text.
|
||||
|
||||
**Maps to ticket:** Background Thread Infrastructure (T-10)
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Implementation Ticket Dependency Chain
|
||||
|
||||
Eleven tickets required. Three can start immediately in parallel; the rest are sequentially gated.
|
||||
|
||||
### Ticket Definitions
|
||||
|
||||
**T-01: Heightmap BLOB Storage (ARCH-1)**
|
||||
- Schema: `atlas_body_heightmaps(body_id, data BLOB)`
|
||||
- Python: `generate_atlas.py` writes heightmap data to this table
|
||||
- Meta stamp update for ARCH-1
|
||||
- Effort: ~1d
|
||||
- Dependencies: none
|
||||
|
||||
**T-02: BodyWorldState Bevy Resource (ARCH-2)**
|
||||
- `GenerationCache` struct with LRU(50)
|
||||
- `BodyWorldState` struct matching D-C22 outputs
|
||||
- `Arc<BodyWorldState>` wiring
|
||||
- Effort: ~1d
|
||||
- Dependencies: T-01 (heightmap field type), T-03 (city names field type), T-04 (radius field)
|
||||
|
||||
**T-03: atlas_city_names Schema (ARCH-3)**
|
||||
- DDL: `atlas_city_names(id, body_id, name, corp_id, tier_hint, reserved)`
|
||||
- Index: `idx_city_names_body`
|
||||
- Migration in `MIGRATION_SQL` (idempotent `CREATE TABLE IF NOT EXISTS`)
|
||||
- Update `generate_atlas.py` to populate from wiki city data
|
||||
- Effort: ~1d
|
||||
- Dependencies: none
|
||||
|
||||
**T-04: body_radius_km Column (ARCH-4)**
|
||||
- DDL: `ALTER TABLE bodies ADD COLUMN body_radius_km REAL`
|
||||
- Migration entry + planet_class fallback in Rust read
|
||||
- Area count formula: `round(sqrt(surface_area_km2 / 6_000_000))` derived field
|
||||
- Effort: ~0.5d
|
||||
- Dependencies: none
|
||||
|
||||
**T-05: WorldTier Enum Fix**
|
||||
- Replace `Peripheral | Connected | Core` with `Epicenter | Regional | Backwater | Passage | Waypoint`
|
||||
- Update all match arms downstream
|
||||
- Effort: ~0.5d
|
||||
- Dependencies: none (but blocks everything that uses WorldTier)
|
||||
|
||||
**T-06: Layer 1 Empty World Generator**
|
||||
- D8 drainage routing (port from `planet_simulation.py`)
|
||||
- Geographic feature tag extraction (D-C23)
|
||||
- Sub-biome variant classification (D-C24)
|
||||
- terrain_modification_cost computation
|
||||
- Outputs to BodyWorldState
|
||||
- Effort: ~2d
|
||||
- Dependencies: T-01 (heightmap read), T-02 (BodyWorldState), T-05 (WorldTier), T-10 (background thread infra)
|
||||
|
||||
**T-07: Layer 2 Population Overlay**
|
||||
- Attractor-matching five-phase pipeline (D-C25)
|
||||
- Road graph generation (MST + organic connectors + MaintenanceAuthority)
|
||||
- Sub-settlement placement
|
||||
- TerritorialStatus derivation (D-C27)
|
||||
- SettlementClass assignment (D-C26)
|
||||
- Outputs to BodyWorldState
|
||||
- Effort: ~3d
|
||||
- Dependencies: T-06 (Layer 1 output), T-03 (city names for attractor-matching)
|
||||
|
||||
**T-08: CityGenerationContext Wiring**
|
||||
- Populate CityGenerationContext from BodyWorldState + systems.db at session startup
|
||||
- Add `body_id`, `city_name`, `founding_orientation` fields (from D-C25)
|
||||
- Add `founding_age_years` field (wiki-authored or systems.db derived)
|
||||
- Add `orbital_approach_direction: Option<CardinalDirection>` (L3-Q7)
|
||||
- Effort: ~1d
|
||||
- Dependencies: T-03 (city names), T-04 (radius / area count), T-05 (WorldTier), T-07 (Layer 2 output)
|
||||
|
||||
**T-09: Layer 3 City Planning**
|
||||
- District count formula (D-C4, locked)
|
||||
- District grid layout with FoundingOrientation rotation (D-C29)
|
||||
- Political archetype spatial arrangement, all five patterns (D-C30)
|
||||
- Three-component district type allocation (D-C28)
|
||||
- founding_age → layout_mode weighting (D-C31)
|
||||
- DistrictSkeleton Stage 1 + Stage 2 generation
|
||||
- prosperity_baseline derivation (Burnelli-Sheldon formula + Paula topographic modifier)
|
||||
- Effort: ~3d
|
||||
- Dependencies: T-08 (CityGenerationContext), T-05 (WorldTier)
|
||||
|
||||
**T-10: Background Thread Infrastructure**
|
||||
- GenerationQueue (BinaryHeap by priority)
|
||||
- Rayon thread pool launcher
|
||||
- GenerationCache LRU wiring
|
||||
- SystemNameIndex with Aho-Corasick for text-triggered generation
|
||||
- Priority elevation hooks (player proximity, dialogue text scan, gate graph adjacency)
|
||||
- Diegetic placeholder state machine (four tiers) — stubs for client integration
|
||||
- Effort: ~2d
|
||||
- Dependencies: T-02 (BodyWorldState type)
|
||||
|
||||
**T-11: Layer 4 Street Rendering**
|
||||
- Street skeleton from BlockSkeleton door positions (minimum corridor widths from D-C29)
|
||||
- Building fill (density-driven rectangle placement)
|
||||
- TileEntry generation with tile_id + walkable + condition
|
||||
- ChunkConditionState with threshold-crossing invalidation (D-C32)
|
||||
- Pre-fetch interior generation trigger at N=32 tiles
|
||||
- Chunk streaming hookup: `load_chunk()` → `generate_chunk_tiles()`
|
||||
- Effort: ~2d
|
||||
- Dependencies: T-09 (DistrictSkeleton output), T-05 (WorldTier)
|
||||
|
||||
### Dependency Graph
|
||||
|
||||
```
|
||||
Phase 1 — All can start immediately in parallel:
|
||||
T-01: Heightmap BLOB Storage (~1d)
|
||||
T-03: atlas_city_names Schema (~1d)
|
||||
T-04: body_radius_km Column (~0.5d)
|
||||
T-05: WorldTier Enum Fix (~0.5d) ← BLOCKER for all Rust generation code
|
||||
|
||||
Phase 2 — After T-01, T-03, T-04 complete:
|
||||
T-02: BodyWorldState Bevy Resource (~1d)
|
||||
|
||||
Phase 3 — After T-02 complete:
|
||||
T-10: Background Thread Infra (~2d)
|
||||
|
||||
Phase 4 — After T-10 AND T-05 complete:
|
||||
T-06: Layer 1 Empty World Generator (~2d)
|
||||
|
||||
Phase 5 — After T-06 AND T-03 complete:
|
||||
T-07: Layer 2 Population Overlay (~3d)
|
||||
|
||||
Phase 6 — After T-07, T-03, T-04, T-05 complete:
|
||||
T-08: CityGenerationContext Wiring (~1d)
|
||||
|
||||
Phase 7 — After T-08 AND T-05 complete:
|
||||
T-09: Layer 3 City Planning (~3d)
|
||||
|
||||
Phase 8 — After T-09 AND T-05 complete:
|
||||
T-11: Layer 4 Street Rendering (~2d)
|
||||
|
||||
WALKABLE WORLD: T-11 complete
|
||||
```
|
||||
|
||||
### Critical Path
|
||||
|
||||
T-05 (WorldTier fix) → T-06 (Layer 1) → T-07 (Layer 2) → T-08 (Context) → T-09 (Layer 3) → T-11 (Layer 4)
|
||||
|
||||
With parallel schema setup: T-01/T-03/T-04 → T-02 → T-10 feeds into the same critical path at Phase 4.
|
||||
|
||||
**Minimum critical path duration:** T-05 (0.5d) + T-10 (2d) + T-06 (2d) + T-07 (3d) + T-08 (1d) + T-09 (3d) + T-11 (2d) = **13.5 dev-days** on a single agent. Schema work (T-01, T-02, T-03, T-04) can be parallelized off this path.
|
||||
|
||||
### Rescoping Note on Prior Tickets
|
||||
|
||||
The brief mentions "#899 et al." that need rescoping against the new layer model. The primary impact:
|
||||
|
||||
- Any ticket that assumed `generate_regional.py` would run at build-time for Layer 1-2 spatial data needs rescoping to the Rust runtime-background model (D-C18)
|
||||
- Any ticket that assumed authored city positions in markers.json needs rescoping to the attractor-matching model (D-C20)
|
||||
- Any ticket that referenced `atlas_cities` for position data needs redirecting to the new Layer 2 GeneratedSettlement output
|
||||
- Any ticket that uses the wrong WorldTier enum values is blocked until T-05 completes
|
||||
|
||||
The specific ticket numbers should be reviewed against this list and updated in the ticketing system.
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Remaining Open Questions for Lead
|
||||
|
||||
These are items the workshop identified but could not close without lead input.
|
||||
|
||||
### Lead-Required Decisions
|
||||
|
||||
| Item | What's needed |
|
||||
|------|--------------|
|
||||
| **Full multiplier table confirmation** | The integer weights table needs lead sign-off on all 8×7 values before it becomes the locked D-C28 table. Burnelli-Sheldon's Round 2 table covers primary roles; the full table should be confirmed as a D-record. |
|
||||
| **atlas_feature_names population path** | Paula's Stage 1 name fulfillment assigns names to geographic features (rivers, mountain ranges, bays). Who authors these names? The wiki content seems like the correct source — but this requires wiki authors to document feature names alongside city names. |
|
||||
| **L3-Q7 scope (orbital approach direction)** | `orbital_approach_direction: Option<CardinalDirection>` field on CityGenerationContext. Data is available (one systems.db query). Client rendering of the field is separate work. Include the field in T-08? My recommendation: yes, at zero implementation cost. |
|
||||
| **Scatter scope unlock timeline** | Prop spawn points were deferred (lead decision). The hook exists (TileEntry `spawn_category` field proposed in gestalt-round2.md). When does this become undeferred? This determines the minimum viable Layer 4 feel. |
|
||||
|
||||
### Items Closed by This Workshop (No Lead Action Required)
|
||||
|
||||
| Item | Closed by |
|
||||
|------|----------|
|
||||
| Mismatch flag threshold | Lead two-tier decision (Q1 above) |
|
||||
| founding_age → layout_mode | Probability weighting adopted (Q2 above) |
|
||||
| Province boundary legibility | Watershed-as-terrain-feature (Q3 above) |
|
||||
| atlas_city_names population path | Wiki import pipeline (Q4 above) |
|
||||
| Self-contained settlements | Confirmed by Burnelli-Sheldon + Gestalt |
|
||||
| OrganicGrowth disambiguation | geographically_triggered flag (Paula + Gestalt) |
|
||||
| Session DB vs systems.db for Layer 1-2 | BodyWorldState in-memory LRU (Tyre ARCH-2) |
|
||||
|
||||
---
|
||||
|
||||
## Part 5: D-Record Candidate Register Summary
|
||||
|
||||
| ID | Decision | Status |
|
||||
|----|---------|--------|
|
||||
| D-C1 through D-C17 | Generation-cascade workshop decisions | Given Facts in brief (not re-opened) |
|
||||
| D-C18 | Three-tier execution model | New — this workshop |
|
||||
| D-C19 | Background generation priority queue | New — this workshop |
|
||||
| D-C20 | Fully generative placement | New — this workshop |
|
||||
| D-C21 | Spatial hierarchy — eight tiers with dimensions | New — this workshop |
|
||||
| D-C22 | D8 drainage routing | New — this workshop |
|
||||
| D-C23 | Geographic feature tag extraction | New — this workshop |
|
||||
| D-C24 | Sub-biome variant classification | New — this workshop |
|
||||
| D-C25 | Attractor-matching five-phase pipeline | New — this workshop |
|
||||
| D-C26 | SettlementClass enum | New — this workshop |
|
||||
| D-C27 | TerritorialStatus priority-ordered derivation | New — this workshop |
|
||||
| D-C28 | Three-component district mix algorithm | New — this workshop |
|
||||
| D-C29 | FoundingOrientation spatial grid rotation | New — this workshop |
|
||||
| D-C30 | Five explicit political archetype spatial patterns | New — this workshop |
|
||||
| D-C31 | founding_age → layout_mode probability weighting | New — this workshop |
|
||||
| D-C32 | Tile condition threshold values (0.63/0.43/0.23) | New — this workshop |
|
||||
| D-C33 | ARCH-1 heightmap BLOB storage schema | New — this workshop |
|
||||
| D-C34 | Background thread infrastructure | New — this workshop |
|
||||
|
||||
---
|
||||
|
||||
## Part 6: What This Workshop Produced
|
||||
|
||||
The planet-down-cascade workshop set out to answer: given the four-layer cascade (Empty World → Population Overlay → City Planning → Street Rendering), what are the concrete algorithms at each layer?
|
||||
|
||||
**The workshop produced:**
|
||||
1. A complete algorithm specification for each of the four layers — pseudocode-level detail sufficient for implementation without further design work
|
||||
2. Data format handoffs per layer — what lives in systems.db (build-time), what lives in BodyWorldState (runtime-background), what is never stored (runtime-on-demand)
|
||||
3. 17 new D-record candidates (D-C18 through D-C34)
|
||||
4. An implementation ticket dependency chain (11 tickets, ~13.5 dev-days critical path)
|
||||
5. Resolved open questions: attractor-matching algorithm, TerritorialStatus thresholds, district mix algorithm, mismatch flagging thresholds, founding_age → layout_mode, Province boundary legibility
|
||||
|
||||
**The architectural pivot from the consultant review (Amendment 1) was fully absorbed.** The prior workshop's Phase 3/Phase 5 framing is replaced by a three-tier model that correctly separates build-time economics data from runtime spatial generation.
|
||||
|
||||
**The three hardest design problems — which had four competing approaches each — converged:**
|
||||
- Attractor-matching: Hungarian algorithm + priority ordering + synthetic overflow
|
||||
- TerritorialStatus: priority-ordered algorithm with single continuous threshold
|
||||
- District mix: three-component model, no zero weights, population guarantees
|
||||
|
||||
The cascade is algorithmically specified, dependency-ordered, and ready for implementation planning.
|
||||
|
||||
---
|
||||
|
||||
*Gestalt — Round 3. Written 2026-05-01.*
|
||||
@@ -0,0 +1,237 @@
|
||||
---
|
||||
title: "Round 1 — Ozzie: Player Experience Inventory"
|
||||
agent: ozzie
|
||||
workshop: planet-down-cascade
|
||||
round: 1
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Ozzie — Round 1: Player Experience Inventory
|
||||
|
||||
The consultant review nailed it in one line: "does this produce places that feel inhabited?" That's my whole job here. I'm going through every layer and asking one question: **when the player is standing there, does it feel real?**
|
||||
|
||||
---
|
||||
|
||||
## Before I Start: Amendment 6 Is the Most Important Thing in the Brief
|
||||
|
||||
Stop. Read Amendment 6 again.
|
||||
|
||||
> "A mining town doesn't have zero entertainment because it's a mining town. It has *rough* entertainment because miners drink."
|
||||
|
||||
THAT LINE. That's the soul of this whole system. The 10×9 weight table as written produces sterile cities. A city where nobody goes to drink, fight, or fall in love isn't a city. It's a diagram. The amendment challenge — population size + age drives the baseline, economic role modifies character — is the right call and I'm backing it hard. The output needs to feel like somewhere people actually chose to live.
|
||||
|
||||
I'll come back to this in my per-layer notes. But it runs through everything.
|
||||
|
||||
---
|
||||
|
||||
## Layer 1 — Empty World
|
||||
|
||||
### What this layer needs to produce for a player
|
||||
|
||||
The player never *sees* Layer 1 directly. They see it through everything above it. But if Layer 1 fails, *every* layer above it fails. A drainage network that doesn't feel like real water flow produces cities that sit in the wrong places. Cities in the wrong places produce a world that doesn't pass the smell test.
|
||||
|
||||
The wow moment for Layer 1: **the first time you open the planetary map and the rivers look RIGHT.** They flow to the sea. They join where valleys converge. The big city is on the confluence. The mining camp is in the hills above the headwaters. The player hasn't been told any of this — the geography just *explains itself.*
|
||||
|
||||
That's the payoff. The player doesn't know they're experiencing Layer 1 doing its job. They just think: this feels like a real place.
|
||||
|
||||
### What worries me
|
||||
|
||||
**Sub-biome variants (L1 open item):** The brief proposes 3-4 variants per biome class. Fine for the algorithm. But from a player standpoint: what are the *visual* differences? `jungle_clearing` vs `swamp` vs `riverine_forest` vs `lowland_marsh` — these need to be distinct at the moment of arrival. The sub-biome tag is data. The player experience is: I step off the transport in a swamp and it feels DIFFERENT from stepping off into a jungle clearing. If the sub-biome tags don't map to meaningful visual and gameplay differences, they're invisible complexity.
|
||||
|
||||
**The drainage-vs-authored question is resolved (Amendment 3).** All rivers are generated. Good. This means the drainage algorithm is the *only* truth. If it produces weird results — rivers flowing uphill, rivers that dead-end, rivers that split without converging — those aren't charming procedural quirks. They're trust-breakers. The player stops believing in the world.
|
||||
|
||||
**Mountain passes:** The brief mentions identifying pass cells. From a player experience perspective, passes should feel like *obvious* chokepoints. The road goes through the pass. That's where you build a fort, or get ambushed, or pay a toll. If the pass cells are tagged but the road doesn't actually route through them, the feature is vestigial. I'm flagging this now for Layer 2: mountain pass tags must feed road routing or they're just noise.
|
||||
|
||||
### My questions for Layer 1
|
||||
|
||||
1. **What does the player see when Layer 1 hasn't generated yet?** Amendment 2 raises this: "survey data unavailable" placeholder. Make that GOOD. Make it diegetic. A scan in progress. Static. Partial data. Not a loading spinner. That moment of watching a world emerge from noise — that's wow.
|
||||
|
||||
2. **Does terrain roughness have felt consequences at street level?** A `terrain_modification_cost` of 0.9 means the city was hard to build here. That should be *visible* in the street layout — irregular blocks, terraced buildings, streets that curve around outcrops. If high-cost terrain produces the same flat grid as low-cost terrain, we've lost the story that Layer 1 was trying to tell.
|
||||
|
||||
3. **Are river confluences visually legible?** The confluence is a settlement attractor because geography says "this is a natural crossroads." The player should feel that too. The river junction, the bridge, the dock — if you're standing at a major confluence and it just looks like two rivers that happen to cross, something went wrong.
|
||||
|
||||
---
|
||||
|
||||
## Layer 2 — Population Overlay
|
||||
|
||||
### What this layer needs to produce for a player
|
||||
|
||||
This is where the world goes from geology to *history*. The player doesn't read a lore document explaining why the city is here. The player sees the harbor, the road from the inland hills, the farmland spreading into the river delta — and they *know* why the city is here without being told.
|
||||
|
||||
THAT'S the game. Layer 2 is where emergence starts.
|
||||
|
||||
### The latent settlement principle: THIS IS THE WHOLE THING
|
||||
|
||||
Every economically plausible settlement position is placed at generation time. Active/ghost is determined by the economic simulation. The road to the closed mine still exists. The buildings of the abandoned outpost are still there.
|
||||
|
||||
I want to be clear about something: **the consultant is correct that we shouldn't design features around the ghost city narrative.** The determinism rule is about player spatial memory. But here's what I need from a player experience standpoint: **the visual difference between an active settlement and a ghost settlement must be immediately readable.** The player shouldn't need a prosperity readout to understand that this town is dying. They should see broken windows, empty warehouses, the tavern with no lights on.
|
||||
|
||||
That's not designing a ghost city feature. That's just making sure the prosperity rendering system communicates clearly. The narrative is emergent. The *communication* of that narrative is a design decision we have to make.
|
||||
|
||||
### Settlement hierarchy: the character problem
|
||||
|
||||
| Tier | Pop | Character question |
|
||||
|------|-----|-------------------|
|
||||
| City | 10K+ | Does it feel like a city? Plural districts, readable zones? |
|
||||
| Town | 1K-10K | Does it feel like a *community*, not a small city? |
|
||||
| Outpost | 50-1000 | Does it feel like a *purpose*? A camp, not a settlement? |
|
||||
| Waypoint | <50 | Does it feel like infrastructure, not habitation? |
|
||||
| Rural cluster | scattered | Does it feel like scattered farms, not a settlement? |
|
||||
| Ruin | 0 | Does it feel like *absence*, not just empty geometry? |
|
||||
|
||||
Every tier needs a distinct emotional register. A town shouldn't feel like a city with fewer buildings. It should feel like somewhere everyone knows everyone. An outpost should feel like somewhere people came for a reason and might leave tomorrow.
|
||||
|
||||
**The Amendment 6 question applies here too:** a frontier outpost with 200 miners still has a cook shack and a drinking tent. The baseline needs include that. The economic role says it's rough and temporary. Not that it's inhuman.
|
||||
|
||||
### Road network: routing through story
|
||||
|
||||
Roads are not just navigation. Roads are narrative. "This road goes to the capital" is political information. "This road is abandoned" is economic history. "This road has a corp logo on the maintenance markers" is power structure made spatial.
|
||||
|
||||
**What I want to flag for Layer 2:** every road segment has a `MaintenanceAuthority`. The player should be able to *read* who controls territory by the state of the roads. Corp roads to the mine are paved and maintained. The back roads between farming villages are muddy and cracked. The highway the government built forty years ago has potholes because nobody has the budget.
|
||||
|
||||
That's character. That's Layer 2 earning its keep in player experience terms.
|
||||
|
||||
### My questions for Layer 2
|
||||
|
||||
1. **What does the player see at the *edge* of a TerritorialStatus boundary?** Crossing from CoreTerritory into FrontierTerritory should feel like something. Road quality drops. Lighting thins out. The settlements get smaller and further apart. Is there a design mechanism for that transition, or does it just... happen differently on the other side of an invisible line?
|
||||
|
||||
2. **How does `FoundingOrientation` manifest in the approach to a city?** If a city is PortFacing, the player approaching from the water side should see the working harbor face. The player approaching from land should see the back end — warehouses, rail yards, less glamour. This is about the *direction* of the city's character, and it should be felt before you're inside it.
|
||||
|
||||
3. **Fully generative placement (Amendment 3):** cities are placed at geographic attractors. The attractor-matching algorithm is a new open question. From a player experience standpoint: the match needs to feel *inevitable*, not arbitrary. A fishing port on a river confluence is obvious. A research hub on a windswept plateau is obvious. A logistics hub at a road junction is obvious. **What makes a placement feel wrong?** If the algorithm puts a fishing port in the mountains, the world cracks. Constraint: no placement should violate geographic common sense.
|
||||
|
||||
---
|
||||
|
||||
## Layer 3 — City-Level Planning
|
||||
|
||||
### What this layer needs to produce for a player
|
||||
|
||||
This is the Cities Skylines layer. The wow moment: walking into a city for the first time and feeling the *logic* of it. You come in from the highway. There's a transit hub. Then a commercial strip. Then it opens up into something — maybe a corporate spine, maybe a chaotic market, maybe a grid of industrial blocks. The city has a shape, and the shape makes sense.
|
||||
|
||||
**Amendment 6 is critical here.** The weight table question is whether district type distribution produces plausible cities. I'm going to be direct: in the current brief, an energy city has zero Commercial and zero Entertainment. That's a city where 50,000 workers live but have nowhere to drink, buy groceries, or see a show. That's not a city. That's a labor camp. The population-plus-age baseline approach is the right fix.
|
||||
|
||||
### The spatial arrangement question (L3-Q2)
|
||||
|
||||
Paula and Gestalt both flag that `political_archetype` should shape spatial arrangement. I'm backing explicit arrangement — and here's why:
|
||||
|
||||
When you enter a CompanyTown, you should feel the corporate spine before you know it's a CompanyTown. The facility at one end. The housing in rows behind it. The admin building facing the main gate. The company store exactly halfway between the facility and the housing. You figure out the politics from the *layout* — not from a tooltip.
|
||||
|
||||
Emergent arrangement from the weight table is cheaper. But it produces cities that feel arbitrary. Explicit arrangement produces cities that feel *designed by someone who had a plan* — which is historically accurate and narratively powerful.
|
||||
|
||||
**This is worth the implementation cost.** The player's ability to read a city's politics from its layout is a core wow factor.
|
||||
|
||||
### The prosperity gradient: legibility at glance
|
||||
|
||||
The `prosperity_baseline` drives the block grid classification. High-prosperity districts have wider streets, more green space, better-maintained perimeters. Low-prosperity districts have tight blocks, cracked pavement, open perimeters because nobody bothered.
|
||||
|
||||
The gradient direction from `FoundingOrientation` + `distribution_index` matters visually. A stratified city should feel stratified when you look at it from above on the map — you can see the good side of town and the rough side. A moderate distribution should look more mixed.
|
||||
|
||||
**Question for Layer 3 (L3-Q5 extended):** Does topography contribute to prosperity gradient direction? Historically, hilltops = higher status. You build your manor up there, you see everyone below you. If a city has a prominent hill inside its footprint, the high-prosperity districts should lean that way. I want this in scope — it's the kind of detail that makes a city feel grown rather than placed.
|
||||
|
||||
### My questions for Layer 3
|
||||
|
||||
1. **What does the *approach* to a city reveal?** From the atlas map level, zooming into a city — what's the first impression? Is there a readable skyline concept (industrial chimneys vs. dome towers vs. admin spires)? Does the city's WorldTier communicate itself at a glance? A Waypoint single-district settlement should feel like a *very different* zoom-in than an Epicenter multi-district metropolis.
|
||||
|
||||
2. **Does the player ever see the district grid directly?** The district grid is a generation artifact. In-game, the player moves through streets, not district boundaries. But the district grid should produce *legible* neighborhoods. Crossing from one district into another should feel like crossing a threshold — a change in building style, street width, signage character. If district boundaries are invisible to the player, the district system is only serving generation, not experience.
|
||||
|
||||
3. **Port/station direction (L3-Q7):** Is the orbital station visible from the city? If I'm standing in the Transit district of a city that faces its orbital, I want to be able to *see* the beanstalk or the freight lifters or whatever connects surface to orbit. That spatial relationship between the city's orientation and the orbital above it — that's real wow factor, and it should influence Layer 3 layout.
|
||||
|
||||
---
|
||||
|
||||
## Layer 4 — Street-Level Rendering
|
||||
|
||||
### What this layer needs to produce for a player
|
||||
|
||||
This is where the player *lives*. Every other layer was building toward this moment: standing in a street, looking at buildings, deciding where to go. If Layer 4 fails, nothing else matters.
|
||||
|
||||
The minimum viable scope (3 tile types, walkable) is fine to *build*. It is not fine to *ship*. I'm saying this clearly: a world with only `floor_street`, `floor_interior`, and `wall` tiles will feel like a prototype. The player will feel the seams. This is a minimum for development, not a minimum for player experience.
|
||||
|
||||
### The economics-variable rendering question (L4-Q1) — THIS IS WHERE WE EARN IT
|
||||
|
||||
The consultant correctly reframes the determinism rule: it's about spatial memory, not ghost cities. Layout is locked. Rendering varies. Fine.
|
||||
|
||||
But here's the player experience implication: **the rendering variation must be *dramatic* enough to tell a story.** Intact vs. Worn vs. Cracked vs. Broken needs to produce meaningfully different-looking streets. "Slightly different tile sprites" is not dramatic. "This neighborhood is falling apart" is dramatic.
|
||||
|
||||
The threshold crossing approach (L4-Q2) is correct for performance. But the thresholds need to produce VISIBLE jumps:
|
||||
- **Intact (≥0.6):** clean streets, lit signs, maintained buildings
|
||||
- **Worn (0.4-0.6):** chipped paint, cracked pavement, some dark windows
|
||||
- **Cracked (0.2-0.4):** broken windows, debris, structural damage visible
|
||||
- **Broken (<0.2):** ruins. Structural collapses. The road is barely passable.
|
||||
|
||||
That's not subtle. GOOD. I want the player to walk into a Broken district and feel it.
|
||||
|
||||
### The "scatter civilization" question (L4-Q5) — NOT OPTIONAL
|
||||
|
||||
The brief defers prop/decal scatter and entity spawn points. I understand why — minimum viable walkable world first. But I want to flag this loudly: **an empty street grid with only floor/wall tiles has no soul.**
|
||||
|
||||
The question isn't *whether* to scatter civilization. The question is *what's the minimum scatter that makes it feel inhabited?* My answer: **street-level decals and prop spawn points are not optional features — they are the minimum viable lived-in feeling.** Furniture, vehicles, signage — the stuff that says "people were here" — must come before the world is considered playable, not after.
|
||||
|
||||
This is my strongest pushback on the deferred scope. The world can be walked before scatter is implemented. But it can't be *felt* until scatter exists.
|
||||
|
||||
### The door threshold: make it a moment
|
||||
|
||||
Door-per-edge is confirmed. The boundary between exterior and interior is a door. That threshold is an emotional beat — the moment of commitment. "I'm going in."
|
||||
|
||||
**L4-Q4 (interior generation trigger):** I want option (a) — pre-generate before the player crosses. Not because of performance, but because a generation stall at the door breaks immersion. The player steps up to a door and the world freezes for 50ms. That's enough to crack the experience. Pre-generate when the player is N tiles away, invisibly.
|
||||
|
||||
**The door itself:** what does the player see on approach? Is there a sign? A light? A locked indicator? The door-per-edge rule is architectural. The player-facing question is: does the door communicate anything about what's behind it? A descriptor + catalog is the technical answer. The player experience answer is: I should be able to look at a door and have an intuition about whether to go in.
|
||||
|
||||
### My questions for Layer 4
|
||||
|
||||
1. **What makes two buildings in the same district feel different?** The block generation algorithm fills blocks with buildings. Are all buildings in a Residential block functionally identical? If yes: the district will feel like a copy-paste. Variation within a district — different building footprints, different door positions, different widths — is what makes it feel like individual structures rather than a texture.
|
||||
|
||||
2. **Is the street itself interesting?** `floor_street` is one tile type. But streets have character — width, surface quality, markings, gutters, curbs. The minimum tile vocabulary needs to eventually expand to communicate street character. I'm not asking for this in Round 1, but I want it flagged as a player experience debt. The minimum walkable world has streets that are all the same width and surface. That's not acceptable at ship.
|
||||
|
||||
3. **What's the first thing the player sees when they spawn?** The LoD cascade traces from planet → region → district → chunk. When the player materializes in a city, what is their immediate visual field? Do they see a street? A building? The sky? The first frame of a city should be designed, not random. This may be a camera/spawn question more than a generation question, but I'm flagging it: **the first impression of every city is a moment worth designing.**
|
||||
|
||||
---
|
||||
|
||||
## Cross-Layer Player Experience Questions
|
||||
|
||||
### The hierarchy transition moments
|
||||
|
||||
The spatial hierarchy is eight tiers: System → Body → Area → Province → Region → District → Block → Chunk. From a player experience standpoint, the tiers the player *crosses* are the interesting ones. What does it feel like to cross from one Area to another? From one Province to another?
|
||||
|
||||
These transitions should be **legible without labels.** You cross a mountain range and the biome changes. You cross a river at a bridge and the road surface changes. You pass the last farm and enter wilderness. The player should feel these transitions as narrative beats, not just as invisible grid boundaries.
|
||||
|
||||
### The "survey data unavailable" moment (Amendment 2 flag)
|
||||
|
||||
When the player goes to check a world that hasn't generated yet — the diegetic placeholder. This is an opportunity. "Survey data unavailable" is bureaucratically sad and exactly right. But there are versions of this that are AMAZING:
|
||||
|
||||
- Partial scan: some terrain is visible, settlements are question marks
|
||||
- Outdated data: the old atlas shows a city that might not exist anymore
|
||||
- No data: static, a cursor blinking in an empty grid
|
||||
|
||||
This is player-facing worldbuilding for free. Don't waste it on a loading spinner.
|
||||
|
||||
### The weight table fix (Amendment 6) — player experience imperative
|
||||
|
||||
I want to put this in writing as a cross-layer concern: **every settlement above minimum population must have essential human functions.** Residential. Commerce. Social. The economic role determines the *character* of those functions, not their absence.
|
||||
|
||||
This isn't a nice-to-have. A world without it will fail the "somewhere people live" test at first contact. A player who walks into a 50,000-person industrial city with zero taverns and zero markets will immediately sense that something is wrong, even if they can't name it.
|
||||
|
||||
Population + age drives baseline. Economic role drives character. **This is the correct architecture and I want it confirmed before Round 2.**
|
||||
|
||||
---
|
||||
|
||||
## Summary: What's Prerequisite and What's Independent
|
||||
|
||||
**PREREQUISITE (earlier layers block these):**
|
||||
- Layer 1 drainage quality → everything. If rivers flow wrong, city placements feel wrong.
|
||||
- Amendment 6 weight table fix → Layer 3 plausibility. Can start in parallel but must be resolved before city generation is finalized.
|
||||
- Amendment 3 attractor-matching algorithm → Layer 2 placement plausibility. New open question, critical path.
|
||||
|
||||
**INDEPENDENT (can proceed without blocking other work):**
|
||||
- Sub-biome variant visual mapping (Layer 1 data → layer 4 visual output — design work now, implementation later)
|
||||
- Spatial hierarchy transition design (cross-layer, but doesn't block algorithm work)
|
||||
- "Survey data unavailable" diegetic placeholder design (Layer 1-2 generation gap — UI work independent of generation algorithms)
|
||||
- Door approach communication design (Layer 4 player experience — deferred implementation is fine, but the design question is: what does the door communicate?)
|
||||
|
||||
**FLAGGED FOR ROUND 2:**
|
||||
- Amendment 6: explicit algorithm proposal for population-baseline + economic-role-character district mix
|
||||
- L3-Q2: explicit spatial arrangement patterns per political_archetype (I'm advocating for explicit, not emergent)
|
||||
- L4-Q5: minimum viable scatter — what's the smallest set of prop spawn types that produces a felt inhabited space?
|
||||
- The mountain pass → road routing connection (Layer 1 tag must feed Layer 2 road generation or it's noise)
|
||||
|
||||
---
|
||||
|
||||
*Round 1 complete. This is the player experience inventory. I'll be back in Round 2 with concrete proposals on the Amendment 6 district mix, the spatial arrangement question, and the minimum viable scatter scope.*
|
||||
@@ -0,0 +1,258 @@
|
||||
---
|
||||
title: "Round 2 — Ozzie: Player Experience Evaluation"
|
||||
agent: ozzie
|
||||
workshop: planet-down-cascade
|
||||
round: 2
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Ozzie — Round 2: Player Experience Evaluation
|
||||
|
||||
The lead locked the district mix and deferred scatter. Accepted on both counts. I'll work with those parameters.
|
||||
|
||||
Here are my verdicts on the five items.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tyre's Spatial Hierarchy Dimensions — Do These Scales Feel Right?
|
||||
|
||||
Short answer: **District is perfect. Province needs a player experience rationale confirmed. Block needs clarifying.**
|
||||
|
||||
### District (~256m) — CORRECT
|
||||
|
||||
256m across. A 3-minute walk end to end. You enter a Commercial district and you know it before you've gone twenty tiles — the streets widen, the buildings cluster, the signage changes. You can see the perimeter treatment. You can see the next district over.
|
||||
|
||||
This is exactly the right scale for "neighborhood feel." The player holds the whole district in spatial memory while standing in it. They can orient themselves. That's the scale at which `DistrictType` becomes felt rather than labeled.
|
||||
|
||||
A 256m district that changes character from a 256m residential district next to it — that IS the city. THAT'S THE GAME.
|
||||
|
||||
### Block (~64m) — CLARIFY ONE THING
|
||||
|
||||
64m is a realistic city block. Good. But I want confirmation: the 4×4 BlockSkeleton grid on a District means each block is a chunk-sized unit (~32m?), not 64m. If a Block is 64m and a District is 256m, then a District is 4 blocks wide, which means a 4×4 block grid is exactly one District. That checks.
|
||||
|
||||
**The player-experience implication:** Block-level variation (one block dense, the adjacent one sparse) is what produces the texture within a district. The player navigates block by block. This is the right granularity for street-level experience. No issue here — just wanted it explicitly stated.
|
||||
|
||||
### Province (~60-200km) — CONFIRM THE PLAYER USE CASE
|
||||
|
||||
Province is the "road-map travel region" tier. I'm fine with the scale. But I want one thing confirmed for player experience: **when the player is on the regional/planetary map, are Province boundaries visible?** They should be. Not as arbitrary grid lines — as natural features. The drainage basin *is* the province. The player should be able to look at a map and trace the watershed and understand why that Province is one region and not two.
|
||||
|
||||
If Province boundaries aren't legible on the planetary map, TerritorialStatus doesn't communicate anything. The player can't see "this Province is ExtractiveZone" unless Province shapes are visible.
|
||||
|
||||
### Area (~400-1500km) — FINE AS ATLAS LAYER
|
||||
|
||||
This is the macro-geographic tier. The player experiences it as "the northern coast" vs. "the highland interior" on the atlas. Not a navigation tier. No gameplay concern here.
|
||||
|
||||
### Province vs. the 64×32 grid — ONE AMBIGUITY TO RESOLVE
|
||||
|
||||
Paula mapped "regional cell (64×32 grid)" to Province tier. If the regional grid IS the Province grid, each cell is a Province. The reference body has a 500km × 250km Province size. That's toward the high end of Tyre's "60-200km" range. Is that right? Or is one Province multiple cells? **This needs a number confirmed in Round 2.**
|
||||
|
||||
The answer matters for TerritorialStatus threshold calculation and for how many Provinces a player realistically travels through on a journey.
|
||||
|
||||
---
|
||||
|
||||
## 2. Attractor-Matching — Which Produces the Most Believable Cities?
|
||||
|
||||
I've read all four proposals. My verdict:
|
||||
|
||||
**Paula's priority ordering combined with Burnelli-Sheldon's objective function. Not either alone.**
|
||||
|
||||
Here's why.
|
||||
|
||||
### The player experience failure mode
|
||||
|
||||
A bad attractor-matching algorithm places cities that feel like they could be *anywhere*. The player flies into Korrath and lands in the Transit district and thinks: "why is there a city here?" That question should never arise. The city should *explain itself* by where it sits.
|
||||
|
||||
The worst outcome: a corp HQ assigned to a geographically inappropriate attractor because that attractor was the highest-quality one available. A deep-sea mineral extraction corp headquartered inland, away from water. The player meets the corp in the game and then visits their headquarters and thinks: "this doesn't make sense."
|
||||
|
||||
### Why Paula's priority ordering is right
|
||||
|
||||
Paula sequences: extraction corps first (most geographically constrained) → service/commercial corps last (most flexible). This is correct and instinctively true. An extraction corp MUST be near its resource. A service corp can set up anywhere profitable.
|
||||
|
||||
She also adds mismatch flagging: if the algorithm can't find a geographically appropriate attractor for a corp, it flags it for lead review instead of silently placing it wrong. This is important. Silent wrong placements are trust-breakers. I want mismatches surfaced.
|
||||
|
||||
**Paula's OrganicGrowth disambiguation:** `geographically_triggered = false` → FoundingOrientation = `AdminFacing`. These are cities that exist because of political decisions rather than geography. They sit at Province centroids or political midpoints. This is correct — the player should be able to look at an AdminFacing city and understand "someone decided to put a city here." It has the geometry of a planned city, not an evolved one.
|
||||
|
||||
### Why Burnelli-Sheldon's objective function is right
|
||||
|
||||
"Maximize aggregate plausibility." That IS the right scoring function for player experience. Not "maximize the happiness of the biggest cities" (Tyre's population-sorted approach), not "satisfy hard constraints and fill the rest" (Gestalt). Maximize the total plausibility of the whole planet.
|
||||
|
||||
The reason: a planet where every city feels 90% correctly placed is better than a planet where the top 5 cities feel 100% correctly placed but the smaller cities feel arbitrary.
|
||||
|
||||
**The synthetic attractor concept** (Burnelli-Sheldon) is specifically good. High-tier economic worlds override geography — the corp builds where it needs to build. That's historically accurate. Manchester didn't sit at a river confluence because of geographic destiny. It sat there because water wheels, then canals, then rails. The attractor was economic, not natural. Synthetic attractors at seed-derived offsets along road corridors produce exactly this: the industrial city that exists because infrastructure converged there.
|
||||
|
||||
### My recommendation
|
||||
|
||||
1. Paula's sequencing: extraction → constrained → unconstrained
|
||||
2. Burnelli-Sheldon's scoring: maximize aggregate plausibility across the whole body
|
||||
3. Mismatch flagging (Paula): surface incompatible assignments for lead review
|
||||
4. Synthetic attractors (Burnelli-Sheldon): high-tier economic worlds get them
|
||||
5. Hungarian algorithm (Burnelli-Sheldon / Tyre): correct solution, trivially fast at N ≤ 30
|
||||
|
||||
**Tyre's population-sorted greedy** is a good fallback / first implementation. **Gestalt's hard-constraint-first** is already baked into Paula's sequencing. These don't need to be chosen against each other — it's one algorithm with Paula's priority structure and Burnelli-Sheldon's scoring function.
|
||||
|
||||
---
|
||||
|
||||
## 3. Explicit Spatial Arrangement — Does Each Archetype Produce a Distinct Feel?
|
||||
|
||||
Yes. But let me be specific about what "distinct feel" actually means in player terms.
|
||||
|
||||
### CompanyTown — Spine Pattern
|
||||
|
||||
**The player experience:** Everything points toward the facility. You always know where you are relative to it. Going to work is going one direction. Going home is the other. The city has a POSTURE.
|
||||
|
||||
This is the pattern where the player is most likely to feel surveillance. The admin building faces the residential blocks. You don't know if you're being watched, but the geometry says you could be.
|
||||
|
||||
**Critical detail:** The spine must terminate in something. If the facility end of the CompanyTown spine is just another block of buildings, the pattern fails. The player needs to SEE the facility from the residential end. The spine's far terminus should be legible as "the thing this city was built for." The whole city points at it.
|
||||
|
||||
### AdminCapital — Radial Pattern
|
||||
|
||||
**The player experience:** Power is visible from everywhere. The administrative hub draws the eye from any direction. The city "faces inward."
|
||||
|
||||
The player arriving from any direction should see the hub. Streets radiate toward it. Neighborhoods frame it. Even the low-prosperity outskirts are geometrically organized around the center — they're just further from it.
|
||||
|
||||
**The interesting version:** An AdminCapital in decline. The hub is still geometrically centered, but the prosperity gradient has inverted — the center is crumbling and the outer ring is richer. The geometry says "this was power." The prosperity says "power has moved." Without designing a ghost city feature, that IS the ghost city effect. Emergent, exactly as the lead wants it.
|
||||
|
||||
### FreePort — Multi-Node Pattern
|
||||
|
||||
**The player experience:** Disorienting. In the best way.
|
||||
|
||||
There's no single center to navigate toward. The player finds the market district by accident, finds the transit hub separately, discovers a residential node tucked between them. It's a city that grew from several independent decisions, not one plan.
|
||||
|
||||
This is where the player gets LOST productively. They think they know where the transit hub is and stumble into the secondary market instead. There are more paths than expected. More connections that shouldn't be there.
|
||||
|
||||
**The critical detail:** The nodes need to be visually distinct enough to serve as landmarks. In a CompanyTown, the spine orients you. In a FreePort, landmarks do the work. If every node looks generically "mixed," the player can't navigate. The node-level identity (this node = maritime, this node = tech, this node = black market) must be legible.
|
||||
|
||||
### Contested — Overlay Pattern (Paula's addition)
|
||||
|
||||
**The player experience:** The city has TWO geometries that don't agree.
|
||||
|
||||
A road system designed by one power overlaid with a different district layout imposed by a second power. Streets that don't match buildings. A grid that suddenly shifts angle. Borders that were fought over, embedded in the urban fabric.
|
||||
|
||||
This is the most historically rich pattern. And the most interesting to navigate. The player feels the conflict in the urban structure without needing text to explain it.
|
||||
|
||||
**My concern:** This pattern requires that BOTH underlying geometries are legible. If the overlay is too subtle, it just looks like a messy city. If it's too heavy, it looks broken. The seams need to be visible — a sudden style change, a defensive wall repurposed as a property line, a plaza that was clearly designed for a different political purpose.
|
||||
|
||||
### OrganicGrowth — Irregular Local Density (Paula's addition)
|
||||
|
||||
**The player experience:** The city doesn't know what it is yet.
|
||||
|
||||
These are cities that grew from commerce and habit, not planning. The density is highest where people naturally congregated, not where a planner drew the center. The player has to figure out where things are.
|
||||
|
||||
**Distinct from CompanyTown:** No spine. You're not being aimed at anything.
|
||||
**Distinct from FreePort:** One mass of settlement, not nodes. Just... dense in the middle, spreading outward.
|
||||
**Distinct from AdminCapital:** No hub. The center emerged, it wasn't declared.
|
||||
|
||||
This pattern produces the most "lived-in" feel of all five, because it looks like human decision-making over time rather than planning. The player instinctively reads it as older and more authentic.
|
||||
|
||||
### Verdict on all five
|
||||
|
||||
All five produce genuinely distinct emotional registers. Paula is right to include all five as explicit. The investment is 30-40 lines of Rust per archetype. The return is cities that feel like different kinds of human settlements.
|
||||
|
||||
**The test:** can the player identify the archetype from 15 seconds of walking around? That should be the acceptance criterion.
|
||||
|
||||
---
|
||||
|
||||
## 4. Three-Component District Mix — Different or Same-y?
|
||||
|
||||
The lead locked this. I'm evaluating what it produces, not relitigating the choice.
|
||||
|
||||
**Does population guarantees + revised multiplier table + founding age produce differentiated settlements?**
|
||||
|
||||
Yes, on one condition: **the multiplier table values must be dramatically different from each other, not just technically non-zero.**
|
||||
|
||||
Burnelli-Sheldon's multiplier range is 0.2 to 3.0. That's a 15× spread. If I'm comparing a mining town (Entertainment multiplier 0.5) to a research hub (Entertainment multiplier presumably 2.0+), that's a 4× difference in Entertainment district probability. At that scale, the cities will feel different.
|
||||
|
||||
**The condition:** minimum values must not all cluster near 0.5. If every "low" value is 0.5 and every "high" value is 1.5, the effective range is 3× — probably not enough to feel distinct. I want to see the actual proposed multiplier table in Round 3, with the specific concern that the minimum values are genuinely low (not just "present").
|
||||
|
||||
### The BS-Q1 question: 9,999-person Town without dedicated Entertainment
|
||||
|
||||
From a player standpoint: plausible. A small town doesn't have an Entertainment DISTRICT. It has a bar. The bar is in the Mixed district, or in a corner of the Residential district. Dedicated Entertainment is a city-scale phenomenon — the neighborhood where the theaters cluster.
|
||||
|
||||
**The implication for design:** Mixed district should have higher character variation than any specialized district. A Mixed district in a mining town should feel grimy and functional. A Mixed district in a research hub should feel eclectic and slightly pretentious. The Mixed district is where economic role expresses itself most visibly in small settlements.
|
||||
|
||||
### Founding age as differentiator — THIS IS IMPORTANT
|
||||
|
||||
The three-component model adds founding age as a character modifier. This is the detail that separates otherwise-similar settlements.
|
||||
|
||||
Two 50,000-person manufacturing cities. Same economic role. Same prosperity tier. Same WorldTier. But one founded 200 years ago and one founded 15 years ago.
|
||||
|
||||
**200-year-old city:** Multiple generations of urban decisions. The original factory district is now surrounded by retrofitted housing. The early grid is visible but modified. The perimeter treatment has softened — walls where walls once made sense, now just fences.
|
||||
|
||||
**15-year-old city:** Grid is raw and recent. Industrial and residential blocks are still clean-edged. The Mixed district is thin — not enough time for it to develop organic character.
|
||||
|
||||
That's a felt difference. The player walks into the old city and it feels *layered*. The new city feels *planned*. **I want founding age to be visible in block geometry, not just character tags.** Old cities should have more irregular block shapes, more legacy structures that don't fit the current zoning. Young cities should have tighter, more uniform grids. This requires that founding age feeds `layout_mode` or `density_pct` variation, not just prosperity.
|
||||
|
||||
**This is a proposal for Round 3:** founding age should modify `layout_mode` as well as `prosperity_baseline`. Old settlements lean toward irregular layout modes; new settlements lean toward grid.
|
||||
|
||||
---
|
||||
|
||||
## 5. The "Survey Data Unavailable" Moment — What Should It Feel Like?
|
||||
|
||||
This is my favorite design question in the whole workshop.
|
||||
|
||||
The setup: player opens the planetary map for a body whose Layer 1-2 cascade hasn't completed yet. The UI has to show something. Amendment 2 mentions "diegetic 'survey data unavailable' placeholder."
|
||||
|
||||
I've been thinking about this since Round 1. Here's my proposal:
|
||||
|
||||
### The diegetic frame
|
||||
|
||||
The player is viewing the map through their implant. The implant draws on survey data — orbital scans, corporate records, academic databases. Survey data has vintages. It gets old. Some worlds are poorly documented.
|
||||
|
||||
This means the placeholder isn't just "loading" — it's the implant showing the player what it actually knows, which might be very little.
|
||||
|
||||
### Three tiers of placeholder, by what the implant actually has
|
||||
|
||||
**Tier A: Partial orbital data (most worlds)**
|
||||
The implant shows coastlines and major terrain features from orbital observation — these are cheap to acquire. Settlements are shown as question marks or "unconfirmed" markers. No roads. No rivers. The player can see the shape of the world but not the civilization.
|
||||
|
||||
This tier is active for ~3-5 seconds while Layer 1-2 generates in the background. Most of the time, the player never sees it — the cascade has already run before they open the map.
|
||||
|
||||
**Tier B: Outdated survey data (remote worlds)**
|
||||
For backwater or frontier bodies, the implant has old data. The map shows information from decades ago. The timestamp is visible: "Last comprehensive survey: 41 years ago."
|
||||
|
||||
Crucially: the old data might not match current reality. It shows settlement positions at their historical locations, but those positions may not match where the generator placed them. It shows rivers from the old orbital maps, but the river network is generated, not authored.
|
||||
|
||||
This creates a discovery tension: the map says one thing; the world will show another. Not because of ghost cities — just because the old data was imprecise and the generated world is the truth.
|
||||
|
||||
**Tier C: No data (system just mentioned in news/dialogue)**
|
||||
The implant triggered a generation request because the player just read about this world. All it has is a system location. The map shows a black circle with a blinking cursor. "Survey data unavailable. Scan in progress."
|
||||
|
||||
This is the most dramatic version. The player is about to explore somewhere they just heard about. The blinking cursor is tension. By the time they decide to go there, the map will have data.
|
||||
|
||||
### Why this matters for the game
|
||||
|
||||
These three tiers transform "loading screen" into narrative. The player isn't waiting for the game to catch up. They're experiencing the limits of information in the Settled Reach. Some worlds are documented; some aren't. Corporate worlds have better data than frontier worlds. The implant is a tool with real constraints.
|
||||
|
||||
**The tier A case is the normal experience.** Most of the time, by the time the player opens a map, the generation has already run (background threads, priority queue). They see a complete map. But when they don't — when the background threads haven't finished — the fallback isn't a spinner. It's the best available data, however incomplete.
|
||||
|
||||
### Specific design proposal
|
||||
|
||||
Show the player what the implant actually knows, in the following priority:
|
||||
1. If generation complete: full map
|
||||
2. If heightmap loaded but settlements not placed: terrain + coastlines + "settlement survey pending"
|
||||
3. If only systems.db data: known city names as points, no positions, no roads, "Positional survey pending"
|
||||
4. If nothing: blinking cursor, system timestamp, "No survey data"
|
||||
|
||||
The placeholder should read like information, not like a loading state. The player should learn something from it, even in the worst case.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Cutting Player Experience Note: The Transition Moments
|
||||
|
||||
One thing that didn't get flagged in Round 1 but shows up across multiple layers:
|
||||
|
||||
**Every tier transition is a potential wow moment.** These are the moments that aren't generated by any single layer but emerge from the transitions between layers:
|
||||
|
||||
- **Entering the atmosphere:** You see the coastlines from space. The cities are lit at night. The first moment of surface arrival should feel like landing somewhere, not like a loading screen.
|
||||
- **Entering a Province from another:** The road condition changes. The signage changes. You're in different territory now.
|
||||
- **Entering a Region (city footprint):** The density increases. The wilderness thins. You can see the city before you're in it.
|
||||
- **Entering a District:** The perimeter treatment marks the threshold. You cross it.
|
||||
- **Entering a Building:** The door threshold. The first moment of interior.
|
||||
|
||||
These transition moments are where the cascade system earns its depth with the player. They don't need designed setpiece scripting — they need each layer to correctly set up the next layer's opening condition. If Layer 2 correctly creates road condition variation at Province boundaries, the player feels the transition without any authored moment.
|
||||
|
||||
The cascade is the experience. Design each handoff so the player feels the seam.
|
||||
|
||||
---
|
||||
|
||||
*Round 2 complete. Key deliverables for Round 3: (1) confirm founding age → layout_mode connection as a proposal; (2) lock attractor-matching as Paula priority + Burnelli-Sheldon scoring; (3) confirm Province boundary legibility requirement for TerritorialStatus to be player-readable.*
|
||||
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: "Round 3 — Ozzie: Final Player Experience Review"
|
||||
agent: ozzie
|
||||
workshop: planet-down-cascade
|
||||
round: 3
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Ozzie — Round 3: Final Player Experience Review
|
||||
|
||||
Last chance. Here it is.
|
||||
|
||||
---
|
||||
|
||||
## 1. Does the Complete Package Produce Worlds That Feel Inhabited?
|
||||
|
||||
The short answer is yes, with one condition. Let me walk through why.
|
||||
|
||||
### The chain that works
|
||||
|
||||
Layer 1 gives you real geography — rivers that flow to the sea, cities that sit where they sit because of *something*. Layer 2 gives those cities explanations — the attractor-matching means every city was placed for a reason the world will show you if you look. Layer 3 gives each city a shape — five distinct archetypes producing five distinct navigational experiences, three components of district mix ensuring every settlement has essential human functions. Layer 4 gives you streets to walk.
|
||||
|
||||
The total package is: geography → civilization → city → street. Every step causes the next. The player never needs to be told why the fishing port is on the coast. The coast and the port are the same fact.
|
||||
|
||||
**That's what this workshop produced.** The causal chain is intact and automatic.
|
||||
|
||||
### The condition
|
||||
|
||||
The complete package works IF the prosperity rendering is dramatic enough to communicate what's happening.
|
||||
|
||||
The prosperity baseline / current gap is the most powerful player experience moment in this entire system, and it happens automatically — no authored ghost city feature needed. The scenario: you walk into a District with `prosperity_baseline = 0.8` (this was built to be wealthy — wide streets, large building footprints, expensive perimeter treatment) but `prosperity_current = 0.21` (the economy collapsed — the corp pulled out, the workers left, the maintenance stopped). The GEOMETRY is intact and grand. The TILE CONDITION is Broken. You walk down wide streets in ruins.
|
||||
|
||||
THAT IS THE GAME. Right there. The gap between what a place was designed to be and what it is now — readable from the architecture.
|
||||
|
||||
This happens automatically from the two-field prosperity model. The rendering system has both values. The determinism rule keeps the layout locked. The economic simulation varies the condition. The player reads the gap.
|
||||
|
||||
But it only works if the threshold levels (0.63 / 0.43 / 0.23) produce **visually dramatic differences between tiers**. Intact needs to look genuinely well-maintained. Broken needs to look like ruins — not "slightly damaged." Paula's thresholds are right. The visual differentiation at each tier must be proportionally dramatic.
|
||||
|
||||
This is my primary requirement for the rendering spec when it's written: **Broken is ruins. It is not "a bit rough."**
|
||||
|
||||
---
|
||||
|
||||
## 2. founding_age → layout_mode — The Final Case
|
||||
|
||||
I'm going to argue this clearly because it's Q2 for Round 3 and needs a decision.
|
||||
|
||||
### The player experience argument
|
||||
|
||||
Stand in two cities: a 200-year-old OrganicGrowth manufacturing town and a 15-year-old CompanyTown resource extraction facility. Same WorldTier. Same prosperity tier. Same economic role. At maximum, the current design differentiates them through prosperity_baseline offset, character tags (`legacy_infrastructure` vs. `raw_settlement`), and the political archetype's spatial pattern.
|
||||
|
||||
Walk through both cities. Without layout_mode variation driven by age, they have the same block structure. The character tags tell the generation system something. But the player doesn't read character tags — they navigate streets. They look at buildings. They see geometry.
|
||||
|
||||
**Old cities should be harder to navigate.** The streets evolved around older structures. The grid shifted. A road curves because it went around a building that was torn down two generations ago. Dead ends exist because a family refused to sell. The city has MEMORY built into its geometry.
|
||||
|
||||
**Young cities should be easy to navigate but feel raw.** The grid is perfect. The blocks are uniform. Nothing has had time to complicate the plan. The city feels like it was made by someone who had a drawing board and no history to work around.
|
||||
|
||||
This is a felt difference. The player navigates by feel. Old = organic, confusing in a good way. New = legible, cold in a productive way.
|
||||
|
||||
### The concrete proposal
|
||||
|
||||
Add an `irregularity_factor: f32` field to block-level generation. This determines how much the block edges deviate from perfect grid alignment.
|
||||
|
||||
```
|
||||
irregularity_factor = clamp(founding_age_years / 200.0, 0.05, 1.0) × age_weight
|
||||
```
|
||||
|
||||
Where `age_weight` varies by political archetype:
|
||||
- **OrganicGrowth:** 0.9 — age compounds maximally with organic tendencies
|
||||
- **FreePort:** 0.8 — multi-node grew independently; age shows
|
||||
- **AdminCapital:** 0.5 — radial structure maintained by administrative will, but edges fray with age
|
||||
- **Contested:** 0.7 — conflicting maintenance regimes produce irregular aging
|
||||
- **CompanyTown:** 0.3 — corporate planning resists aging; spine is maintained
|
||||
|
||||
The minimum value (0.05) ensures even brand-new cities have *some* irregularity — construction variance, terrain response, no city is a perfect simulation artifact.
|
||||
|
||||
**This doesn't require a new `layout_mode` enum value.** It's a continuous parameter on existing block generation. High `irregularity_factor` produces blocks that deviate from their grid positions. Low `irregularity_factor` produces clean grid blocks. The street skeleton algorithm just varies how strictly it respects the grid.
|
||||
|
||||
**For implementation:** this feeds into the Phase 2 tile algorithm as a block-edge deviation tolerance. "Streets should be within N tiles of the ideal grid position" where N scales with `irregularity_factor`. Zero tolerance = perfect grid. High tolerance = streets curve around whatever stood here first.
|
||||
|
||||
**My ask for Round 3:** lock `irregularity_factor` as a Layer 3 block generation parameter derived from `founding_age_years × archetype_age_weight`. The exact formula can be tuned in implementation. The principle must be locked now.
|
||||
|
||||
---
|
||||
|
||||
## 3. Province Boundary Legibility — What the Player Needs
|
||||
|
||||
Province = 1 regional grid cell, ~540km × 270km on the reference body. TerritorialStatus is per Province. This is where political geography lives.
|
||||
|
||||
**The requirement, stated precisely:** The player must be able to identify Province boundaries on the planetary map without labeled borders. The boundaries must be visible as terrain features.
|
||||
|
||||
### Why this is achievable from existing data
|
||||
|
||||
Province boundaries ARE watershed lines. The D8 drainage algorithm produces drainage basins. Where one basin ends and another begins IS the Province boundary. Those boundaries run along ridgelines, mountain ranges, and coastal features.
|
||||
|
||||
The generation already produces this information. The question is: does the planetary map render it?
|
||||
|
||||
**The rendering requirement:**
|
||||
- Province boundaries = watershed/ridgeline features rendered as thin terrain variation (not thick colored borders)
|
||||
- TerritorialStatus shown as Province fill color/texture (subtle; readable at a glance)
|
||||
- CoreTerritory: dense settlement markers, road lines visible
|
||||
- FrontierTerritory: scattered settlement markers, road lines thin toward Province edge
|
||||
- ExtractiveZone: industrial marker texture or tinting, road lines heavy toward resource sites
|
||||
- ContestZone: contested visual treatment (two-color, cross-hatched, or similar)
|
||||
- WildernessBuffer: empty, no markers, natural terrain only
|
||||
- AbandonedZone: reduced marker density, ruins indicator
|
||||
|
||||
**The player experience payoff:** the player zooms out to the planetary map and can read the political geography at a glance. They see where civilization is dense (CoreTerritory clusters), where it's exploitative (ExtractiveZone), where it's contested (ContestZone — the gradient between two territories), and where it's absent (WildernessBuffer). Then they zoom in to a city and everything they saw on the map has a physical manifestation at street level.
|
||||
|
||||
**This is a requirement for the map UI**, not for the generation algorithm. The generation produces the right data. The UI must render Province shapes as terrain features and TerritorialStatus as visual fill. If the map only shows cities as dots and roads as lines without Province context, TerritorialStatus is invisible to the player.
|
||||
|
||||
**File this as a player experience requirement for Phase 3 map implementation.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Survey Data Unavailable — Finalized Three-Tier Design
|
||||
|
||||
The three-tier proposal is confirmed in the Round 2 notes. Here are the final design details.
|
||||
|
||||
### The governing principle
|
||||
|
||||
The implant shows the player what it actually has. The player learns something from the placeholder, even in the worst case. Every tier conveys information. None of them convey "please wait."
|
||||
|
||||
### Tier A: Partial Orbital Data (Normal Case — 3-5 second gap)
|
||||
|
||||
**When:** Heightmap BLOB is loaded, settlements haven't been placed yet.
|
||||
|
||||
**What it shows:**
|
||||
- Terrain + coastlines from heightmap (already in memory)
|
||||
- Known city names from `atlas_city_names` displayed as named points with no confirmed positions — labeled "Position survey pending"
|
||||
- Road routes not shown
|
||||
- River courses not shown (drainage hasn't run yet)
|
||||
- Status indicator: "Surface survey in progress"
|
||||
|
||||
**The player experience:** The shape of the world is visible. The names of the cities are known. But where exactly the cities sit, and what's between them, is pending. The player sees the silhouette of the civilization, not its details.
|
||||
|
||||
Most players never see Tier A. The background thread is fast (~136ms per Tyre's estimate). By the time the player navigates to a body's map, it's almost certainly complete. Tier A is the fallback for the edge case.
|
||||
|
||||
### Tier B: Outdated Survey Data (Remote Frontier Worlds)
|
||||
|
||||
**When:** The body's cascade hasn't been triggered yet because no priority event fired for it — the player navigated there before news/dialogue/route scanning queued it.
|
||||
|
||||
**What it shows:**
|
||||
- Old orbital survey data with explicit vintage timestamp: "Last comprehensive survey: GSY [specific in-universe year]"
|
||||
- Historical settlement positions (if data exists) labeled "Unconfirmed — position data may be outdated"
|
||||
- Road routes shown as dashed lines labeled "Historical — current status unknown"
|
||||
- River courses from old orbital observation shown as approximations
|
||||
- Status indicator: "Survey data vintage — accuracy unverified"
|
||||
|
||||
**The player experience:** The map shows a historical record, not current reality. The timestamps should be specific in-universe years. "Galactic Standard Year 3411" tells the player this survey was done before the current political situation. The player knows they're looking at a world that may have changed.
|
||||
|
||||
The discovery tension: the old map shows a city in a location where the generator places a ghost town. The player arrives expecting a city, finds ruins. The map wasn't wrong — the city existed. The world changed since the survey. This is the prosperity system doing its job, communicated through the gap between historical data and generated reality.
|
||||
|
||||
**One new detail:** The vintage timestamp should affect the accuracy of the positions shown. A 5-year-old survey is accurate. A 100-year-old survey shows cities where they were, not where they are (settlement drift at the attractor-matching scale is minimal, but the player doesn't know that). Create the expectation of inaccuracy even when the data happens to match.
|
||||
|
||||
### Tier C: No Data (System Just Mentioned)
|
||||
|
||||
**When:** The Aho-Corasick scanner detected a body name in news/dialogue/documents. Generation request queued. Nothing loaded yet.
|
||||
|
||||
**What it shows:**
|
||||
- Body type (planet, moon, station) from systems.db — this is always known
|
||||
- System name and gate position — this is always known
|
||||
- Black silhouette of the body shape (generic for body type, not specific heightmap)
|
||||
- Blinking cursor
|
||||
- Status: "Survey data unavailable. Orbital scan queued."
|
||||
|
||||
**The player experience:** Maximum tension. The player just heard about this place. They open the map. They know almost nothing. The scan is in progress. By the time they decide to go there — if they decide to go there — the map will have data.
|
||||
|
||||
**Design note:** The Aho-Corasick scanner should fire the Tier C display immediately when the body name is detected, not wait for the player to open the map. If the player reads a news ticker mentioning "Vellum-Reach," the scan request fires and the map is ready to show data by the time they check it. The "scan in progress" visual should be something the player can observe if they check the map immediately after encountering the name — then see it resolve if they check again a few seconds later. The discovery of the scan completing is itself a moment.
|
||||
|
||||
### Priority Fallback Chain (Confirmed)
|
||||
|
||||
1. Generation complete → full map
|
||||
2. Heightmap loaded, settlements pending → Tier A (terrain + named points, pending status)
|
||||
3. Only systems.db data, generation queued → Tier B (historical data if available, otherwise Tier C-like)
|
||||
4. Name detected, nothing loaded → Tier C (blinking cursor, scan in progress)
|
||||
|
||||
---
|
||||
|
||||
## 5. Final Flags — Speak Now
|
||||
|
||||
### Things That Will Feel GREAT
|
||||
|
||||
**The wealthy-district-in-ruins moment.** Wide streets. Grand building footprints. Cracked tile conditions everywhere. The geometry says "this was money." The prosperity says "the money left." This happens automatically from the two-field model. It doesn't need a feature. It needs dramatic tile condition visual differentiation.
|
||||
|
||||
**Walking into a FreePort for the first time.** No spine. No hub. You think you know where the transit district is and you end up in the secondary market. The city doesn't care about making you comfortable. It grew for commerce, not for navigation. Every FreePort in the Reach will be different. Players will trade notes on how to navigate them.
|
||||
|
||||
**The road that ends at an AbandonedZone.** The road is maintained up to the Province boundary. Then the road surface degrades — the maintenance authority ran out of either money or will. The road continues into the AbandonedZone, just crumbling. The town is there. Fully generated. Dark. The player walks an unmaintained road into a ghost town that nobody scripted.
|
||||
|
||||
**The approach to a PortFacing city from the sea.** The harbor face of the city — the working front of it — visible from the approach. The Transit and Logistics districts at the water-facing edge. When the player arrives by sea, they see the city's face. When they arrive by land, they see its back. That orientation is real and it's architectural.
|
||||
|
||||
**The 200-year-old CompanyTown.** The spine is still there. The facility end is still the facility end. But two centuries of imperfect maintenance have made the streets drift. The blocks near the facility are still tight — corporate planning maintained them. The residential end of the spine has grown irregular. The city carries its history in its layout.
|
||||
|
||||
### Things That Will Feel Wrong — Failure Modes
|
||||
|
||||
**Pristine young cities.** Even new cities need minimum irregularity (I'm proposing 0.05 floor). A brand-new corporate extraction facility that looks like a perfect simulation grid is obviously procedural. Construction variance, terrain response, logistics decisions — all of these produce at least slight irregularity. Lock the minimum.
|
||||
|
||||
**District type indistinguishable at low prosperity.** When an entire city is at Broken prosperity, every district looks like ruins. The Residential ruins look the same as the Commercial ruins look the same as the Industrial ruins. This is scatter-dependent (the *stuff* in the ruins tells you what kind of district it was) and I know scatter is deferred. I'm flagging it now so when scatter is designed, it explicitly addresses district-type legibility at low prosperity. **The ruins should tell you what they were.** This goes in the scatter spec.
|
||||
|
||||
**Province size and player experience frequency.** Province at ~540km × 270km is large. Most of a player's session will be within one Province. If they're in a CoreTerritory Province, they may never encounter a WildernessBuffer Province during a typical play session. That's fine — the TerritorialStatus system is macro-geography, not moment-to-moment navigation. But it means the *intra-Province* variation needs to do work. The road quality variation within a Province — MaintenanceAuthority differences, ExtractiveZone corridors, sub-settlement condition — these carry the weight of the player's micro-experience. Make sure Layer 2 road generation produces visible intra-Province variation, not just inter-Province variation.
|
||||
|
||||
**The synthetic attractor cities.** Cities placed at synthetic attractors exist because of economic decisions, not geography. A manufacturing hub that exists because a corp needed a production facility near a transit corridor — this is historically correct and common. But the player needs a reason to believe it. Without geographic explanation, the city can feel arbitrary. The `SyntheticPlacementReason` should be visible to the player somewhere — not as a tooltip, but as urban character. A `CorpExpansion` city looks like it was planned in a board meeting: efficient, corporate, grid-perfect. A `PoliticalDecision` city looks like it was planted by a government that needed a settlement there: administrative buildings prominent, services adequate, character thin. These character differences should emerge from the synthetic attractor reason in the same way geographic attractor type informs FoundingOrientation.
|
||||
|
||||
**This is a new proposal: `SyntheticPlacementReason` should inform the default political archetype.** `CorpExpansion` → CompanyTown. `PoliticalDecision` → AdminCapital. `PopulationOverflow` → OrganicGrowth. The geography couldn't produce an archetype for this city; the reason for its existence should.
|
||||
|
||||
### Technically Correct But Emotionally Flat
|
||||
|
||||
**The founding age derivation for sub-settlements:** "mining camp = parent city age − 20." Mathematically reasonable. But a 180-year-old mining camp is ancient by mining camp standards. The character modifier system has `legacy_infrastructure` and `retrofitted` tags for old settlements. These need to translate visibly. An 180-year-old mining camp should look like multiple generations built on top of each other — original infrastructure, first renovation, second renovation, current state. The tags are correct; the visual manifestation needs to actually layer.
|
||||
|
||||
**The TerritorialStatus enum:** Six clean values that correctly classify every Province. But from a player experience standpoint, the most interesting state is the *transition between* statuses. A Province that used to be CoreTerritory and is now FrontierTerritory is very different from a Province that was always FrontierTerritory. The `placed_at_generation` flag captures the AbandonedZone case, but the "was CoreTerritory, now degraded" case produces FrontierTerritory — which looks the same as "always was frontier." This may matter in play. Something to watch during playtesting.
|
||||
|
||||
### One Final Advocacy: L3-Q7
|
||||
|
||||
Port/station direction was deferred. I'm on record that I want it. Here is the specific player experience moment it creates:
|
||||
|
||||
You're standing in the Transit district of a PortFacing city. The district is at the water-facing edge of the city. You look up at the sky — specifically, toward the orbital station above. You can see it. The beanstalk, the freight platforms, whatever the setting uses for orbital access. The city was built to face its orbital. The Transit district is as close to it as the city can get. When you stand there, the architecture is pointing at the sky.
|
||||
|
||||
That is a screenshot moment. That is a "where am I in the universe" moment. That is what this game is about.
|
||||
|
||||
Paula already designed the mechanism — one query, one lookup, one edge assignment. The implementation is minimal. I'm formally requesting that L3-Q7 be included in scope before the Phase 4 orbital viewport work begins. File it now even if it's implemented later.
|
||||
|
||||
---
|
||||
|
||||
## Summary for D-Records
|
||||
|
||||
Locking positions for the record:
|
||||
|
||||
| Item | Position |
|
||||
|------|---------|
|
||||
| `irregularity_factor` on block generation | LOCK IT. Old cities deviate from grid; new cities grid-perfect. Minimum 0.05. Archetype weights as specified above. |
|
||||
| Province boundaries on planetary map | PLAYER EXPERIENCE REQUIREMENT. Render as watershed/terrain features, not borders. TerritorialStatus as Province fill. Non-negotiable for Phase 3 map. |
|
||||
| Survey data placeholder | THREE-TIER SYSTEM as specified. Tier A: partial orbital. Tier B: vintage data with in-universe timestamp. Tier C: blinking cursor. Priority fallback chain confirmed. |
|
||||
| Broken prosperity rendering | "Broken is ruins, not slightly damaged." Lock this into the rendering spec. |
|
||||
| `SyntheticPlacementReason` → political archetype | NEW PROPOSAL. CorpExpansion → CompanyTown. PoliticalDecision → AdminCapital. PopulationOverflow → OrganicGrowth. Resolve in Phase 3 design if not here. |
|
||||
| L3-Q7 (port/station direction) | Formally requested for Phase 4 scope. Mechanism already designed by Paula. One screenshot moment. |
|
||||
|
||||
That's everything. The system is sound. Build it.
|
||||
|
||||
---
|
||||
|
||||
*Round 3 complete.*
|
||||
@@ -0,0 +1,331 @@
|
||||
---
|
||||
title: "Paula — Round 1: Narrative and Political Inventory"
|
||||
description: "Inventory and framing of all open questions from the narrative/political depth lens; amendment absorptions; prerequisite ordering; positions on Layer 1-2 political legibility requirements"
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: paula
|
||||
round: 1
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 1 (Paula)
|
||||
|
||||
**Focus:** Narrative and political depth. Layers 1-2 primary, Layer 4 prosperity rendering. Round 1 task: examine all open questions, identify what reuses vs. what is genuinely novel design work, establish prerequisite ordering from the narrative legibility requirement.
|
||||
|
||||
---
|
||||
|
||||
## Absorbing the Amendments
|
||||
|
||||
Before the layer-by-layer inventory, three amendments materially affect my Round 4 positions and must be acknowledged up front.
|
||||
|
||||
### Amendment 3 (Fully Generative Placement)
|
||||
|
||||
This is narrative-positive. My Round 4 argument was that the planet-down ordering makes FoundingOrientation readable from the geographic trigger without authoring. Amendment 3 extends this to city placement itself — cities land where geography says they should, not where a previous author put them on a pixel map. The narrative causality that I argued for in Round 4 is now architecturally guaranteed, not just recommended.
|
||||
|
||||
The amendment eliminates L1-Q1, L2-Q1, and CL-Q4. All three were questions about reconciling authored placements with generated terrain. That reconciliation problem no longer exists.
|
||||
|
||||
It adds two new questions I consider narrative-critical:
|
||||
|
||||
**NEW-Q1 (Attractor-matching priority):** The attractor-matching algorithm places N named cities at M geographic attractors. Corporate cross-references in systems.db establish that certain named cities must exist and be the kind of place where that corporation would plausibly sit. This creates a hard constraint: HQ cities must receive appropriate attractor assignments before unnamed settlements fill remaining attractors. The priority order for the matching algorithm has narrative consequences — if the algorithm can seat a corporate HQ at a `ResourceConcentration` attractor rather than a `RiverConfluence`, the city's character changes fundamentally. Getting the matching order right is not optional.
|
||||
|
||||
**NEW-Q2 (OrganicGrowth disambiguation):** Amendment 3 says placement is fully generative from geographic attractors. My Round 4 document flagged an unsolved problem: a settlement with no geographic trigger (OrganicGrowth archetype) is algorithmically identical to "no settlement placed here." Both conditions produce zero triggered attractors. The data model must distinguish them. OrganicGrowth settlements exist because a political decision overrode geographic logic — they need a flag or derivation rule that marks them as politically-placed rather than geographically-placed. This is not a new question; it was open in Round 4 and remains open.
|
||||
|
||||
### Amendment 4 (Spatial Hierarchy)
|
||||
|
||||
My Round 4 proposals referenced "regional cells" and "regional grid" language inherited from the original brief. Under the 8-tier hierarchy, I need to remap:
|
||||
|
||||
| My Round 4 language | Correct tier |
|
||||
|--------------------|-------------|
|
||||
| "Regional cell" (64×32 grid) | **Province** (tier 4) — road-map travel region defined by natural boundaries |
|
||||
| "City footprint" | **Region** (tier 3) — city footprint + surroundings |
|
||||
| "Sub-settlement" | **Region** (tier 3) or smaller depending on population tier |
|
||||
| "Atlas-level feature" | **Area** (tier 5) or **Body** (tier 6) |
|
||||
|
||||
**TerritorialStatus at Province granularity.** My proposed TerritorialStatus classification belongs at Province (tier 4), not per-cell within a Province. TerritorialStatus describes the political character of a road-map travel region — the kind of answer you give when someone asks "what's that territory like between the two cities?" One Province = one primary TerritorialStatus. The derivation algorithm reads Province-level aggregates (settlement count, road density and authority distribution, corp_presence coverage, geographic tag presence).
|
||||
|
||||
The spatial dimension table (Amendment 4 workshop question) is Tyre's domain. But I need the Province dimensions confirmed before I can specify TerritorialStatus derivation thresholds with meaningful numbers — "high settlement density" means something different in a Province covering 50km vs. 500km.
|
||||
|
||||
### Amendment 5 (Ghost City Effect Demoted)
|
||||
|
||||
This is a framing correction, not an architectural change. My Round 4 work was too fond of the ghost city as a design goal. Amendment 5 is correct: the determinism rule exists to protect the player's spatial memory, not to produce ghost towns. Ghost towns emerge when the prosperity system does its job across a settlement with a collapsed economic basis. That emergence is interesting precisely because it was not designed.
|
||||
|
||||
The two-field model (`prosperity_baseline` + `prosperity_current`, producing a derived `prosperity_delta`) is still the right rendering architecture. It is the mechanism that makes economic state visible at street level without changing layout. The ghost city effect is one possible consequence of that mechanism working correctly on an economically failed settlement. I should not design for the ghost case; I should design the mechanism well and let the case emerge.
|
||||
|
||||
My Round 4 positions on this mechanism stand. My framing of it as a "ghost city architecture" is revised. The correct framing: **the two-field prosperity model is a rendering legibility requirement** — without it, the economic simulation's state is invisible to the player at street level, and the relationship between the macroeconomic layer and the world the player walks through is severed.
|
||||
|
||||
---
|
||||
|
||||
## Layer 1: Empty World — Inventory
|
||||
|
||||
**My role:** Geographic feature tag layer is the primary Layer 1 narrative output. Everything else at Layer 1 is terrain/hydrology work for Tyre and Burnelli-Sheldon.
|
||||
|
||||
### What exists or can be reused
|
||||
|
||||
`planet_simulation.py` already computes elevation, temperature, moisture, biome classification, and a river network. The river network from the simulation is usable as-is for confluence identification — confluence points are where rivers merge, and the simulation's drainage network already produces this as a geometric property. No novel algorithm is needed for confluence identification; it is a post-processing step on the existing drainage network output.
|
||||
|
||||
Sub-biome refinement within a biome class is also not novel design. It is a conditional probability distribution over sub-biome variants, conditioned on local elevation, moisture, and temperature within the biome cell. The inputs exist; the logic is classification, not novel simulation.
|
||||
|
||||
### What is genuinely novel
|
||||
|
||||
**The geographic feature tag layer.** This does not exist. The simulation produces terrain values but does not produce named semantic tags (`RiverConfluence`, `CoastalHarbor`, `MountainPass`, `ArablePlain`, `ResourceConcentration`, `Defensible`, `NaturalBarrier`). These tags must be derived from terrain + drainage network output and stored as point or cell features. The derivation rules for each tag are novel design work:
|
||||
|
||||
- `RiverConfluence`: junction point in drainage network where two named-scale rivers meet. Threshold: both contributing rivers above minimum navigable flow at the confluence.
|
||||
- `CoastalHarbor`: coastal cell with protected geometry — bay geometry detectable from heightmap contour adjacent to ocean. Threshold: coastline curvature > threshold at sea level.
|
||||
- `MountainPass`: local saddle point in high-elevation terrain — cell with elevation above mountain threshold where adjacent cells in two cardinal directions are significantly lower (pass geometry). Threshold: elevation gap > X between saddle and neighboring valley cells.
|
||||
- `ArablePlain`: flat terrain (low roughness) above minimum fertility threshold (temperature + moisture combined index). Threshold: roughness < Y, fertility index > Z.
|
||||
- `ResourceConcentration`: this tag is NOT purely terrain-derived. Mineral outcrops and energy sources are placed by economic generation (from systems.db economic_role data). The tag should be placed at Layer 1 only where `economic_role` from systems.db indicates extraction potential — this is L1-Q4 (biome prior) applied specifically to resource attractor placement.
|
||||
- `Defensible`: elevated cell with clear sightlines — above terrain mean for the Region, with lower terrain on at least 3 of 4 cardinal neighbors. Threshold: relative elevation > W, neighbor gradient > V.
|
||||
- `NaturalBarrier`: contiguous high-roughness or deep-ocean cells that form a traversal barrier — identified as connectivity barriers in the Province-level graph. No single threshold; barrier quality is a Province-level graph property.
|
||||
|
||||
### What is a data/schema question
|
||||
|
||||
The new `atlas_geographic_features` table (point features stored in systems.db) needs to be defined. Under Amendment 1's corrected execution model, this table does NOT go in systems.db — Layer 1-2 now runs as Rust runtime background generation, storing output in the Session DB. The feature tag table belongs in the Session DB, not the build-time systems.db.
|
||||
|
||||
This has an important implication: the attractor-matching algorithm (NEW-Q1) runs at Layer 1-2 background time, not at build time. Corporate cross-reference resolution must also happen at background time. This is feasible — systems.db is available at runtime, and the background generator can read it.
|
||||
|
||||
### Open question positions (Layer 1)
|
||||
|
||||
**L1-Q2 (river resolution):** Position (c) — Province-resolution only stored in Session DB (confluence points as point features, coarse river paths as Province-level graph edges). Tile-resolution river courses are seed-derived at Phase 5. The narrative requirements for Layer 2 are satisfied by confluence point storage alone — settlement placement only needs to know WHERE confluences are, not the precise course of every tributary.
|
||||
|
||||
**L1-Q3 (stored vs. derived):** Confluence points must be stored in Session DB (Layer 1 output, consumed by Layer 2 settlement placement). Full river course derived at runtime. No systems.db entries needed for rivers — rivers are Session DB content.
|
||||
|
||||
**L1-Q4 (biome prior):** YES, apply `economic_role` as a soft sub-biome prior. The narrative justification: a world that has been exploited for extraction should feel geologically harsh; an agricultural world should feel fertile and temperate. This prior affects sub-biome variant distribution and the placement threshold for `ResourceConcentration` tags. The risk of self-contradictory worlds from excluding it is real — a `frontier` economic_role world with a tropical paradise biome distribution is dissonant without explanation.
|
||||
|
||||
**NEW-Q1 (attractor-matching priority):** This is prerequisite for Layer 2 and must be resolved before the attractor-matching algorithm can be specified. My position: HQ-reserved cities (named cities with corporate cross-references) have first priority in attractor assignment. The matching algorithm must fulfill name reservations before assigning unnamed settlement slots. A reserved HQ city without a plausible geographic attractor is a data quality problem — the economic role of the corp should constrain which attractor types are acceptable (extraction corp HQ → `ResourceConcentration` or `ArablePlain` acceptable; `CoastalHarbor` acceptable if maritime trade; `RiverConfluence` acceptable universally). The algorithm should flag mismatches for lead review rather than silently placing a mining corp HQ at a harbor attractor.
|
||||
|
||||
**Prerequisite status:** L1-Q2 and L1-Q3 are prerequisites for Layer 2 (confluence storage design must be settled before Layer 2 algorithm reads it). L1-Q4 is independent but should be resolved early to inform sub-biome distribution work. NEW-Q1 spans Layers 1-2 and must be resolved before Layer 2 algorithm design.
|
||||
|
||||
---
|
||||
|
||||
## Layer 2: Population Overlay — Inventory
|
||||
|
||||
**My role:** TerritorialStatus derivation, FoundingOrientation derivation, sub-settlement PoliticalTether, road MaintenanceAuthority. All of these are post-processing passes on the settlement placement + road generation output.
|
||||
|
||||
### What exists or can be reused
|
||||
|
||||
The road graph algorithm (Phase 3 Layer C, from prior workshop) can be reused for generating inter-settlement road topology. The amendment eliminates authored road paths, but it does not eliminate authored road identities — systems.db knows which named highway routes exist (their names and economic significance), and the generator must assign generated road edges to these named identities.
|
||||
|
||||
The settlement placement algorithm has conceptual precedent in constraint satisfaction literature (assignment problem). The specific formulation needed here — N named settlements with known economic roles and populations, M geographic attractors with known characteristics, maximize plausibility — is a priority-ordered assignment, not a pure optimization. This is novel implementation but not novel algorithm design.
|
||||
|
||||
TerritorialStatus derivation is a classification post-processing step. It reads Province-level aggregates that the settlement placement and road generation steps have already produced. The novel work is specifying the derivation thresholds.
|
||||
|
||||
### What is genuinely novel
|
||||
|
||||
**The attractor-assignment algorithm.** This produces the settlement-to-attractor mapping that makes city positions geographically legible. From a narrative standpoint, the algorithm's output must be auditable — it must be possible to explain why a given city was placed at a given attractor. The algorithm is novel and is the core design work of Layer 2.
|
||||
|
||||
My proposed assignment logic (to be detailed in Round 2):
|
||||
1. Fulfill name reservations for HQ cities in economic role order (extraction corps first — most geographically constrained; service/commercial corps last — most flexible).
|
||||
2. Assign remaining named cities by population (largest first — highest population cities have the strongest geographic pull, and the best attractors should be theirs).
|
||||
3. Place unnamed cities (generated) at remaining attractors by best-fit economic profile.
|
||||
4. OrganicGrowth settlements (the remaining population quota after all attractors are consumed) are placed at politically significant points: midpoints between two large cities, or at Province centroids for administrative capital bodies.
|
||||
|
||||
**OrganicGrowth disambiguation (NEW-Q2).** The data model must distinguish OrganicGrowth settlements from "no settlement placed." My proposed resolution: a boolean flag `geographically_triggered: bool` on every settlement record in Session DB. True = settlement was assigned to a geographic attractor. False = settlement exists because of population quota or political decision. FoundingOrientation for geographically-untriggered settlements is always `AdminFacing` — the settlement exists because someone decided it should.
|
||||
|
||||
**TerritorialStatus quantitative thresholds.** These are novel — they have no prior precedent in the codebase. Proposed thresholds (subject to iteration in Round 2):
|
||||
|
||||
| Status | Settlement count | Road coverage | Corp presence condition |
|
||||
|--------|-----------------|---------------|------------------------|
|
||||
| CoreTerritory | ≥ 2 per Province | ≥ 0.6 Province-wide | Any |
|
||||
| FrontierTerritory | ≥ 1 per Province | 0.3–0.6 OR quality < 0.5 at Province edge | Any |
|
||||
| ExtractiveZone | ≥ 1 at ResourceConcentration | extraction corridor ≥ 0.7, off-corridor < 0.3 | corp_presence > 0 |
|
||||
| ContestZone | ≥ 2 from competing road authority types | Any | Any, overlapping claim |
|
||||
| WildernessBuffer | 0 | 0 | None |
|
||||
| AbandonedZone | ≥ 1 at generation (runtime: 0 active) | Exists (maintained = false) | was present, now absent |
|
||||
|
||||
The critical design decision in this table: WildernessBuffer and AbandonedZone both have no active settlements at runtime, but their histories are different. AbandonedZone requires a `placed_at_generation: bool` marker on the Province to distinguish "never settled" from "was settled, now gone." This marker is set at Layer 2 generation time and is immutable.
|
||||
|
||||
### What is a data/schema question
|
||||
|
||||
**Road identity assignment.** systems.db holds named route records (highway names, administrative designations). The Layer 2 generator produces a road graph (nodes + edges). Road identity assignment — which generated edge segment corresponds to which named route — is a data join problem. The schema needs a road_segment table in Session DB with an optional `named_route_id` foreign key to systems.db. This is schema design work, not algorithmic design.
|
||||
|
||||
**PoliticalTether derivation.** This is direct enumeration, not algorithmic. For each sub-settlement:
|
||||
- `Corporate`: settlement placed at `ResourceConcentration` attractor AND corp_presence > 0 in parent city
|
||||
- `Administered`: settlement placed at geographic inevitability (pass waypoint, river crossing) AND parent city is AdminCapital archetype
|
||||
- `Contested`: settlement placed in ContestZone Province
|
||||
- `EconomicallyDependent`: settlement placed at agricultural or agricultural-adjacent attractor, parent city is primary market
|
||||
- `Independent`: OrganicGrowth sub-settlement with no corporate or administrative tie
|
||||
- `Abandoned`: active flag = false at runtime
|
||||
|
||||
### Open question positions (Layer 2)
|
||||
|
||||
**L2-Q2 (sub-settlement placement):** The brief's proposed algorithm (camp_position = closest regional cell to corp's location_id asset with land_use ∈ {Industrial, Wilderness}, at seeded distance from tether city within [15%–40%] of body scale) is mechanistically reasonable. The narrative concern: the placement direction from the tether city should match the `ResourceConcentration` attractor direction — a mining camp should be "out toward the resource," not "randomly offset from the city." This is an additional spatial constraint on the seeded distance formula: seed the angle toward the Resource tag, then add noise. Count formula: 1 camp per 2 qualifying corps (as proposed) is reasonable for Phase 1. Shadow node placement is correctly deferred.
|
||||
|
||||
**L2-Q3 (road algorithm):** With Amendment 3 eliminating authored road paths, the road algorithm generates all topology. Named highway routes receive their identity via post-generation assignment (the schema question above). I agree with the proposed resolution: named routes = generated topology + identity assignment from systems.db; local sub-settlement roads = generated by road graph algorithm from Phase 3 Layer C. MaintenanceAuthority derivation is unchanged from my Round 4 position.
|
||||
|
||||
**L2-Q4 (hinterland shapes):** I do not support Voronoi for hinterland fills. The narrative of farmland is rivers and gentle slopes — agricultural cells should cluster along river corridors and coastal lowlands, weighted by `terrain_modification_cost` from Layer 1 (lower cost = more likely to be farmed). A simple weighted probability assignment per Province cell is sufficient for Phase 1. Voronoi introduces region boundaries that don't correspond to anything the player would observe or that matters politically. Reserve algorithmic complexity for things that matter narratively.
|
||||
|
||||
**L2-Q5 (TerritorialStatus thresholds):** Proposed thresholds are above in the novel work section. These need iteration and Burnelli-Sheldon's input on road coverage quantification. The qualitative derivation logic I proposed in Round 4 stands; the numbers are initial proposals.
|
||||
|
||||
**L2-Q6 (latent settlement flag storage):** Option (b) confirmed — Session DB Phase 1-2 output table stores latent settlement positions + activation conditions (the specific corp or resource condition that would activate each). The economic sim evaluates activation by reading current corp_financial_state against the stored activation condition. This integrates cleanly with the background generation model (Amendment 1): the Layer 1-2 background pass populates the Session DB; the sim reads it alongside its own corp state data at runtime.
|
||||
|
||||
**Prerequisite status:** NEW-Q1 (attractor-matching) is prerequisite for all Layer 2 settlement placement. NEW-Q2 (OrganicGrowth disambiguation) is prerequisite before settlement placement algorithm is specified. L2-Q5 (TerritorialStatus thresholds) is prerequisite for producing the Province classification that Layer 3 reads. L2-Q3, L2-Q4 are independent of each other and can be resolved in parallel.
|
||||
|
||||
---
|
||||
|
||||
## Layer 3: City-Level Planning — Inventory
|
||||
|
||||
**My role:** FoundingOrientation spatial effect on district layout, political archetype as spatial arrangement, prosperity gradient direction and magnitude.
|
||||
|
||||
### What exists or can be reused
|
||||
|
||||
The DistrictSkeleton generation framework (Stages 1-2) is the existing stub. The field for `founding_orientation` is in the CityGenerationContext struct. The concept exists; the spatial application does not.
|
||||
|
||||
FoundingOrientation derivation is now trivially readable from the geographic trigger (as I argued in Round 4). No novel algorithm needed for the derivation — it is a direct lookup from the attractor tag to the orientation enum. Novel work is in the spatial application.
|
||||
|
||||
### What is genuinely novel
|
||||
|
||||
**Explicit spatial arrangement patterns by political archetype.** This is my strongest position in this workshop. The weight table alone does not produce CompanyTown spine topology. It can produce a slightly higher proportion of Industrial and Logistics districts relative to Residential, but it cannot produce the physical arrangement where the resource is at one end and everything else radiates back from it. That arrangement requires explicit logic.
|
||||
|
||||
The five arrangements I proposed in Round 4 (reformulated for Round 1 framing, not Round 2 algorithm detail):
|
||||
|
||||
- **CompanyTown spine:** District grid is oriented along a single axis with the ResourceFacing/Logistics end at the resource-adjacent edge. Districts are placed sequentially along the spine, not in a 2D grid. The spine orientation IS the FoundingOrientation direction.
|
||||
- **AdminCapital radial:** District grid expands outward from a central Administrative district. The first district placed is the Administrative district at or near the founding-feature-facing edge (DefenseFacing = high ground center; AdminFacing = geometric center). Remaining districts radiate.
|
||||
- **FreePort multi-node:** Two or three independent district clusters, each anchored around a commerce/logistics node, without a single civic center. No single-district grid — multiple sub-grids.
|
||||
- **Contested overlay:** Two partial district grids meeting at a boundary. Each grid is internally consistent with one archetype's arrangement but they meet awkwardly — the contested boundary is where grid coherence breaks down.
|
||||
- **OrganicGrowth irregular:** Districts placed by local density attractors (sub-feature geography within city footprint), no single directional arrangement.
|
||||
|
||||
This is novel design work. It requires a district placement function that accepts `political_archetype` and `founding_orientation` and produces a spatial layout rather than just sampling from a distribution.
|
||||
|
||||
**FoundingOrientation as spatial grid rotation.** My position: spatial rotation, not gradient direction only. The player should be able to look at a city map and see the PortFacing city's harbor at one end. This is the narrative payoff that justifies the planet-down ordering — the geographic rationale is physically visible in the city's layout. Gradient-direction-only produces a more uniform city layout with a prosperity slope, which is weaker narrative signal. The implementation cost is higher (the district grid must be oriented, not just a fixed NxM grid), but the narrative requirement is satisfied only by spatial rotation.
|
||||
|
||||
### What is a data/schema question
|
||||
|
||||
`prosperity_baseline` derivation formula: Burnelli-Sheldon's formula is a schema/calculation question more than a narrative question, except for one point — topographic contribution. I advocate for a small topographic modifier: hilltop districts (elevation in top 30% for the city) receive +0.05 to `prosperity_baseline`; flood-adjacent districts (elevation in bottom 20% relative to sea level for the city) receive -0.05. This is historical — high ground is consistently higher prestige in pre-industrial settlement patterns. Whether to include this modifier is the question; the magnitude is tunable.
|
||||
|
||||
### Open question positions (Layer 3)
|
||||
|
||||
**L3-Q1 (FoundingOrientation spatial effect):** Spatial rotation/alignment. Not gradient direction only. See above.
|
||||
|
||||
**L3-Q2 (political archetype as spatial arrangement):** Explicit arrangement patterns. Not emergent from weight table. See above.
|
||||
|
||||
**L3-Q5 (prosperity_baseline derivation):** Agree with Burnelli-Sheldon's formula as the base. Advocate for small topographic modifier. Topography should set gradient direction (Burnelli-Sheldon's recommendation) AND contribute a modest magnitude offset for extreme elevation.
|
||||
|
||||
**L3-Q6 (sub-settlement depth):** Unified code path — town with district_count = 1 uses the same DistrictSkeleton generation. Architectural cleanliness, no divergence in the narrative content the player encounters in a town vs. a city district.
|
||||
|
||||
**L3-Q7 (port/station direction):** Defer from minimum viable. Design the mechanism: the Layer 3 generator reads orbital station atlas position from systems.db, derives a directional vector from city center to station, maps that to the nearest cardinal edge, places Transit/LogisticsHub district at that edge. This is a one-query, one-lookup, one-edge-assignment operation. The mechanism is simple enough to be worth designing even if deferred in implementation.
|
||||
|
||||
**Prerequisite status:** L3-Q1 and L3-Q2 must be resolved before the district placement algorithm can be specified. L3-Q5 is independent and can be resolved in Round 2 alongside the economics formula. L3-Q6 is independent. L3-Q7 is deferred.
|
||||
|
||||
---
|
||||
|
||||
## Layer 4: Street-Level Rendering — Inventory
|
||||
|
||||
**My role:** Prosperity rendering mechanism (CL-Q1), condition update trigger design (L4-Q2), ruin lifecycle (CL-Q3 — now demoted).
|
||||
|
||||
### What exists or can be reused
|
||||
|
||||
The tile condition state enumeration (Intact/Worn/Cracked/Broken) does not exist as implemented code but the concept is well-defined in the brief. The economic sim already computes `prosperity_current` per entity. The rendering mechanism connects these two things.
|
||||
|
||||
### What is genuinely novel
|
||||
|
||||
**Prosperity rendering integration.** The sim produces `prosperity_current` per city/district. The tile generator receives `prosperity_baseline` at generation time. The renderer needs to derive effective prosperity (`prosperity_baseline + prosperity_delta` clamped to [0, 1]) and map it to tile condition state. This requires a handoff channel from the sim to the renderer that is neither ChunkMutations (too granular, wrong ownership) nor a full regen (too expensive). This channel design is novel.
|
||||
|
||||
My preferred mechanism: `prosperity_current` is a per-district value updated by the sim and readable by the renderer. The renderer computes `prosperity_delta = prosperity_current - prosperity_baseline` at chunk-load time. Tile condition state is cached per chunk and invalidated when `prosperity_current` crosses a threshold (Option C from brief L4-Q1). The threshold crossing check is a sim-side trigger — the sim emits a "district prosperity crossed threshold X" event; the renderer invalidates the chunk cache for that district on receipt.
|
||||
|
||||
This is clean: the sim is not involved in rendering; it only emits events at meaningful economic transitions. The renderer handles the rest.
|
||||
|
||||
### What is a data/schema question
|
||||
|
||||
`prosperity_current` storage: this lives in the ECS (per-city/per-district component), not in Session DB. It is sim state. The renderer reads it from the ECS at chunk load. No schema change needed beyond adding the prosperity component to the entity definition.
|
||||
|
||||
### Open question positions (Layer 4)
|
||||
|
||||
**L4-Q1 (economics-variable rendering mechanism):** Option (c) — cache per chunk, invalidate on threshold crossing. Option (a) (bake and update on event) is similar but requires a stored cache per chunk, which is the same as option (c). Option (b) (live computation per frame) is too expensive. The distinction between (a) and (c) is whether the cache is invalidated on any `prosperity_current` change or only on threshold crossings — threshold crossings is correct because most districts are economically stable most of the time.
|
||||
|
||||
**L4-Q2 (condition update trigger):** Threshold crossings. Proposed thresholds (offset from round numbers to avoid oscillation at boundaries):
|
||||
- Intact: `effective_prosperity ≥ 0.63`
|
||||
- Worn: `0.43 ≤ effective_prosperity < 0.63`
|
||||
- Cracked: `0.23 ≤ effective_prosperity < 0.43`
|
||||
- Broken: `effective_prosperity < 0.23`
|
||||
|
||||
**L4-Q6 (prosperity_delta):** Adopt the two-field model with `prosperity_delta` as a derived-never-stored value. Frame it as the rendering legibility requirement, not as ghost city architecture. The economic simulation's state must be legible at street level; this model is how that legibility is achieved.
|
||||
|
||||
**CL-Q3 (ruin lifecycle):** Per Amendment 5 — scope as emergent rendering outcome. When the economic sim evaluates an AbandonedZone settlement: `prosperity_current` drops to near-zero; at the next threshold crossing check, tile condition transitions toward Broken; `perimeter_treatment` degrades as a renderer-side parameter driven by prosperity (not a separate authored sequence). No designed decay path. The lifecycle is the prosperity system working correctly on an economically failed settlement.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Layer: Naming
|
||||
|
||||
The naming register framework I proposed in Round 4 (Geographic/Functional/Commemorative/Aspirational/Folk registers by settlement character) maps to the spatial hierarchy as follows:
|
||||
|
||||
- **Area/Body level (Layer 1):** Geographic placeholder names for major terrain features large enough to appear on body-atlas UI. These are pre-political register names — purely descriptive.
|
||||
- **Province/Region level (Layer 2):** Settlement names (Geographic register for confluence/harbor placements, Functional register for extraction sites, Commemorative register for administrative capitals, Folk register for agricultural clusters). DualNaming initialization for ContestZone Provinces.
|
||||
- **District level (Layer 3):** District names from (DistrictType × WorldTier) → register lookup. political_archetype modifier applied.
|
||||
- **Street level (Layer 4):** Working street names (pragmatic names used by inhabitants, which may differ from official planning names). DualNaming proliferates here in Contested cities.
|
||||
|
||||
This is a content pipeline question more than an algorithm question. The registers and pools need to be defined (Mellanie's domain). The generator only needs the lookup table and the pool references.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisite Map — What Must Be Resolved First
|
||||
|
||||
This is the narrative-legibility dependency graph:
|
||||
|
||||
```
|
||||
NEW-Q1 (attractor-matching priority)
|
||||
└── blocks: all Layer 2 settlement placement algorithm design
|
||||
└── blocks: L2-Q5 (TerritorialStatus thresholds — need Province-level aggregates)
|
||||
└── blocks: Layer 3 reads TerritorialStatus
|
||||
|
||||
NEW-Q2 (OrganicGrowth disambiguation)
|
||||
└── blocks: FoundingOrientation derivation (AdminFacing = no attractor trigger)
|
||||
└── blocks: L3-Q1 (spatial grid orientation design)
|
||||
└── blocks: L3-Q2 (explicit arrangement patterns — orientation is prerequisite)
|
||||
|
||||
L1-Q3 (confluence storage)
|
||||
└── blocks: Layer 2 attractor identification (confluences must be queryable)
|
||||
|
||||
L2-Q6 (latent settlement flag storage)
|
||||
└── blocks: AbandonedZone classification (runtime active/ghost state)
|
||||
|
||||
Amendment 4 (Province dimensions locked)
|
||||
└── blocks: L2-Q5 quantitative thresholds (density ratios depend on Province area)
|
||||
```
|
||||
|
||||
Independent questions (no narrative-legibility blocking dependency):
|
||||
- L1-Q4 (biome prior) — independent
|
||||
- L2-Q3 (road algorithm) — independent after attractor-matching
|
||||
- L2-Q4 (hinterland shapes) — independent
|
||||
- L3-Q5 (prosperity baseline formula) — independent
|
||||
- L4-Q1, L4-Q2 (rendering mechanism, condition triggers) — independent from spatial design
|
||||
|
||||
---
|
||||
|
||||
## Questions I Am Not Positioned to Answer
|
||||
|
||||
**L3-Q4 (topographic constraints on zoning at district resolution):** Tyre's domain. My input: the narrative requirement is that topography should be visible in district layout. Whether the Layer 1 64×32 data is sufficient depends on how many districts fall per regional cell — Tyre needs to confirm that district count × district size doesn't exceed the regional cell size. If it does, finer terrain data is needed.
|
||||
|
||||
**L4-Q3 (tile algorithm parameters — minimum street corridor width, max building dimensions):** Gestalt's domain primarily. My only narrative input: street width should vary by DistrictType as a planning investment signal. Administrative districts → wide boulevards. Organic Residential → narrow alleys. The width variation is not just aesthetic; it tells the player who planned this district and how much they invested.
|
||||
|
||||
**L4-Q5 ("scatter civilization" scope — prop spawn points vs. entity spawn points):** Layer 4 minimum viable should produce prop spawn points only (type-tagged spatial anchor points). Entity placement is runtime. Decal slots are a visual detail that can be addressed post-walkable-world.
|
||||
|
||||
**Amendment 4 (spatial dimension table):** Tyre and Burnelli-Sheldon own the quantitative dimension confirmation. My only requirement: Province dimensions must be confirmed before TerritorialStatus thresholds are finalized in Round 2.
|
||||
|
||||
---
|
||||
|
||||
## Summary of My Round 1 Positions
|
||||
|
||||
| Question | My position | Priority |
|
||||
|---------|------------|---------|
|
||||
| NEW-Q1 (attractor-matching) | HQ-reserved cities first, by economic role constraint; flag mismatches | PREREQUISITE |
|
||||
| NEW-Q2 (OrganicGrowth disambiguation) | `geographically_triggered: bool` flag, AdminFacing orientation | PREREQUISITE |
|
||||
| L1-Q2 (river resolution) | Province-resolution confluence points in Session DB; tile-res seed-derived | Early |
|
||||
| L1-Q3 (confluence storage) | Session DB, not systems.db | PREREQUISITE |
|
||||
| L1-Q4 (biome prior) | Yes — apply economic_role as soft sub-biome prior | Early |
|
||||
| L2-Q2 (camp placement) | Direction toward ResourceConcentration tag + seeded noise, 1 per 2 corps | Round 2 detail |
|
||||
| L2-Q4 (hinterland shapes) | No Voronoi — weighted probability along rivers + terrain cost | Independent |
|
||||
| L2-Q5 (TerritorialStatus thresholds) | Proposed thresholds above; needs Province dimensions | Round 2 finalize |
|
||||
| L2-Q6 (latent settlement flag) | Session DB table, activation condition referenced | Confirmed |
|
||||
| L3-Q1 (FoundingOrientation spatial) | Spatial rotation, not gradient-direction-only | PREREQUISITE |
|
||||
| L3-Q2 (political archetype spatial) | Explicit arrangement patterns, not emergent | PREREQUISITE |
|
||||
| L3-Q5 (prosperity_baseline) | Burnelli-Sheldon formula + small topographic modifier | Round 2 |
|
||||
| L3-Q6 (sub-settlement depth) | Unified code path, district_count = 1 | Confirmed |
|
||||
| L4-Q1 (rendering mechanism) | Cache per chunk, invalidate on threshold crossing | Independent |
|
||||
| L4-Q2 (condition triggers) | Threshold crossings at 0.63 / 0.43 / 0.23 | Independent |
|
||||
| L4-Q6 / CL-Q1 (two-field model) | Adopt — rendering legibility requirement, not ghost city architecture | Confirmed |
|
||||
| CL-Q3 (ruin lifecycle) | Emergent rendering outcome only — no designed decay path | Per Amendment 5 |
|
||||
|
||||
---
|
||||
|
||||
*Paula — Round 1. Written 2026-05-01.*
|
||||
@@ -0,0 +1,676 @@
|
||||
---
|
||||
title: "Paula — Round 2: Algorithm Proposals"
|
||||
description: "Concrete algorithm proposals: OrganicGrowth disambiguation mechanism, TerritorialStatus converged thresholds, name reservation data structure, five explicit spatial arrangement patterns, attractor-matching narrative plausibility constraints"
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: paula
|
||||
round: 2
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 2 (Paula)
|
||||
|
||||
**Focus:** Algorithm proposals. Everything in this document is a concrete specification — inputs, outputs, data formats, decision rules — not a position or preference. Where I have positions that may conflict with other agents' proposals, I state the conflict clearly rather than averaging.
|
||||
|
||||
**Lead decisions absorbed (locked, not revisited):**
|
||||
- District mix: Burnelli-Sheldon's three-component model. Self-contained settlements — no settlement above minimum population has zero essential district types.
|
||||
- Scatter (prop/decal/entity spawn): deferred.
|
||||
- L3-Q1 locked: spatial grid rotation.
|
||||
- L3-Q2 locked: explicit arrangement patterns.
|
||||
- L4-Q1 locked: threshold-crossing cache invalidation.
|
||||
- L4-Q4 locked: pre-fetch (on-entry for minimum viable).
|
||||
- CL-Q1 locked: two-field prosperity model.
|
||||
|
||||
---
|
||||
|
||||
## 1. NEW-Q2 — OrganicGrowth Disambiguation: Concrete Mechanism
|
||||
|
||||
### The problem
|
||||
|
||||
When the attractor-matching algorithm runs, some settlements are assigned to geographic attractors and some are not. The data model must distinguish:
|
||||
- A settlement assigned to a geographic attractor (`FoundingOrientation` readable from attractor type)
|
||||
- A settlement placed at a synthetically-derived position (overflow, political, economic density)
|
||||
- Specifically: an OrganicGrowth-archetype settlement (excess population, no attractor) vs. an AdminFacing-archetype settlement placed by political decision
|
||||
|
||||
My Round 1 proposal was `geographically_triggered: bool`. Round 2 refines this to an enum that carries more information without significant added complexity.
|
||||
|
||||
### Proposed mechanism: `AttractorAssignment` enum
|
||||
|
||||
```rust
|
||||
pub enum AttractorAssignment {
|
||||
/// Settlement was assigned to a geographic feature attractor.
|
||||
/// FoundingOrientation is derived directly from the attractor tag.
|
||||
Geographic {
|
||||
attractor_type: GeographicFeatureTag, // RiverConfluence | CoastalHarbor | ...
|
||||
quality_score: f32, // 0.0–1.0, how well the settlement fits
|
||||
},
|
||||
/// Settlement was placed at a seed-derived position (no natural attractor available
|
||||
/// or a political decision overrides geographic logic).
|
||||
Synthetic {
|
||||
reason: SyntheticPlacementReason,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum SyntheticPlacementReason {
|
||||
/// More named cities needed than geographic attractors available.
|
||||
/// The settlement exists because the population quota demands it.
|
||||
/// → political_archetype: OrganicGrowth, FoundingOrientation: AdminFacing
|
||||
PopulationOverflow,
|
||||
|
||||
/// systems.db explicitly marks this body as having a politically-placed settlement
|
||||
/// (a capital established away from natural advantages, a religious site, etc.)
|
||||
/// → political_archetype: AdminCapital or OrganicGrowth per economic_role
|
||||
/// → FoundingOrientation: AdminFacing
|
||||
PoliticalDecision,
|
||||
|
||||
/// High economic_tier body with manufactured-settlement density exceeding natural
|
||||
/// attractor count. Placed at seed-derived offset along existing road corridors.
|
||||
/// → political_archetype: derived from economic_role
|
||||
/// → FoundingOrientation: AdminFacing
|
||||
CorpExpansion,
|
||||
}
|
||||
```
|
||||
|
||||
### FoundingOrientation derivation from AttractorAssignment
|
||||
|
||||
```
|
||||
Geographic(CoastalHarbor | RiverConfluence) → PortFacing
|
||||
Geographic(ResourceConcentration) → ResourceFacing
|
||||
Geographic(MountainPass | Defensible) → DefenseFacing
|
||||
Geographic(ArablePlain) → AdminFacing
|
||||
(agricultural plains produce administrative service hubs, not port/resource/defense cities)
|
||||
Synthetic(_) → AdminFacing
|
||||
```
|
||||
|
||||
Note: `RailHeadFacing` is currently not derivable from a geographic attractor — transit junctions are infrastructure, not terrain features. RailHeadFacing is assigned when the road/rail graph algorithm places a settlement at a road/rail network junction during Layer 2 road generation, after the initial attractor matching. This is a second-pass assignment on top of `AttractorAssignment`.
|
||||
|
||||
### political_archetype derivation rules (summary)
|
||||
|
||||
Full derivation formula is Burnelli-Sheldon's domain. My narrative constraints:
|
||||
|
||||
```
|
||||
AttractorAssignment::Geographic(ResourceConcentration) + corp_presence_count > 0
|
||||
→ CompanyTown
|
||||
|
||||
AttractorAssignment::Geographic(CoastalHarbor | RiverConfluence) + economic_role ∈ {commercial, transit}
|
||||
→ FreePort
|
||||
|
||||
AttractorAssignment::Geographic(Defensible | MountainPass) + economic_role ∈ {administrative}
|
||||
→ AdminCapital
|
||||
|
||||
AttractorAssignment::Synthetic(PoliticalDecision)
|
||||
→ AdminCapital (the political decision is itself an administrative act)
|
||||
|
||||
AttractorAssignment::Synthetic(PopulationOverflow) + no dominant corp + economic_role ∈ {service_mixed, commercial}
|
||||
→ OrganicGrowth
|
||||
|
||||
Two settlements in ContestZone Province with competing road authority types
|
||||
→ Contested (assigned to the lower-population settlement; larger settlement keeps its primary archetype)
|
||||
```
|
||||
|
||||
### GeneratedSettlement record fields
|
||||
|
||||
The `GeneratedSettlement` struct in `BodyWorldState` requires these fields to support the mechanism:
|
||||
|
||||
```rust
|
||||
pub struct GeneratedSettlement {
|
||||
pub settlement_id: u64, // stable ID for Session DB references
|
||||
pub name: Option<String>, // None for unnamed (gets generated name)
|
||||
pub population: u32,
|
||||
pub economic_role: EconomicRole,
|
||||
pub attractor_assignment: AttractorAssignment,
|
||||
pub founding_orientation: FoundingOrientation, // derived from attractor_assignment
|
||||
pub political_archetype: PoliticalArchetype, // derived from above + economics
|
||||
pub tier: SettlementTier, // City | Town | Outpost | Waypoint | RuralCluster | Ruin
|
||||
pub active: bool, // runtime: false when economic basis collapsed
|
||||
pub placed_at_generation: bool, // immutable: true for all settlements placed at gen time
|
||||
}
|
||||
```
|
||||
|
||||
`placed_at_generation` is always `true` for settlements placed at Layer 2. At runtime, `active` can become `false`. The combination distinguishes WildernessBuffer (Province with no `placed_at_generation = true` settlements) from AbandonedZone (Province with at least one `placed_at_generation = true` settlement where all have `active = false`).
|
||||
|
||||
---
|
||||
|
||||
## 2. TerritorialStatus — Converged Thresholds
|
||||
|
||||
### Points of agreement across Round 1 proposals
|
||||
|
||||
All three proposals (Paula / Tyre / Burnelli-Sheldon) agree on:
|
||||
- WildernessBuffer = no settlements placed
|
||||
- AbandonedZone = placed-at-generation settlements, none currently active
|
||||
- ExtractiveZone requires ResourceConcentration attractor + corp presence + characteristic road pattern
|
||||
- ContestZone requires multiple settlement clusters with competing political identity
|
||||
- The distinction between CoreTerritory and FrontierTerritory is infrastructure quality, not just presence
|
||||
|
||||
The disagreements are primarily in how thresholds are expressed (absolute counts vs. relative density vs. economic_tier-relative ratios).
|
||||
|
||||
### Converged proposal
|
||||
|
||||
For Phase 1, use absolute thresholds. Burnelli-Sheldon's economic_tier-relative ratios are more accurate but require more inputs and can be added in a subsequent pass. The priority here is an algorithm that produces TerritorialStatus correctly in the common case.
|
||||
|
||||
**TerritorialStatus derivation — per Province, evaluated in priority order:**
|
||||
|
||||
```
|
||||
Input per Province:
|
||||
settlements_placed_count: u32 // count of settlements with placed_at_generation=true
|
||||
settlements_active_count: u32 // runtime: count with active=true
|
||||
has_city_tier: bool // any settlement of SettlementTier::City
|
||||
has_resource_attractor: bool // any GeneratedSettlement has Geographic(ResourceConcentration)
|
||||
corp_presence_count: u32 // distinct corps with presence in Province settlements
|
||||
road_edge_count: u32 // total road edges in Province
|
||||
road_maintained_count: u32 // edges with MaintenanceAuthority != Abandoned
|
||||
road_authority_types: Set<MaintenanceAuthority>
|
||||
extraction_corridor_quality: f32 // avg quality of road edges marked Corporate; 0.0 if none
|
||||
off_corridor_road_quality: f32 // avg quality of non-Corporate road edges; 0.0 if none
|
||||
political_archetypes_present: Set<PoliticalArchetype> // archetypes of City/Town-tier settlements
|
||||
|
||||
Rule 1 — AbandonedZone (runtime-evaluated):
|
||||
settlements_placed_count > 0 AND settlements_active_count == 0
|
||||
|
||||
Rule 2 — WildernessBuffer:
|
||||
settlements_placed_count == 0
|
||||
|
||||
Rule 3 — ExtractiveZone:
|
||||
has_resource_attractor == true
|
||||
AND corp_presence_count > 0
|
||||
AND extraction_corridor_quality > 0.60
|
||||
AND off_corridor_road_quality < 0.35
|
||||
|
||||
Rule 4 — ContestZone:
|
||||
settlements_active_count >= 2
|
||||
AND |political_archetypes_present| >= 2
|
||||
AND any two settlements in Province are of City or Town tier
|
||||
|
||||
Rule 5 — CoreTerritory:
|
||||
settlements_active_count >= 2
|
||||
AND has_city_tier == true
|
||||
AND road_maintained_count / road_edge_count >= 0.65
|
||||
|
||||
Rule 6 — FrontierTerritory (catch-all for active settlements):
|
||||
settlements_active_count >= 1
|
||||
```
|
||||
|
||||
**Notes on the ordering:**
|
||||
- AbandonedZone is checked first because it is a runtime state that overrides generation-time classification. An ExtractiveZone from which the corp has withdrawn is AbandonedZone, not still ExtractiveZone.
|
||||
- WildernessBuffer before all others because it requires zero placed settlements (the simplest case).
|
||||
- ExtractiveZone before ContestZone because a contested extraction zone is still ExtractiveZone in character — the extraction is the defining feature; the contest is a modifier.
|
||||
- ContestZone before CoreTerritory because a well-connected contested Province reads as ContestZone rather than CoreTerritory — the political conflict is the primary narrative fact.
|
||||
- FrontierTerritory as catch-all: any active settlement that doesn't qualify for a more specific status.
|
||||
|
||||
**`placed_at_generation` Province flag:**
|
||||
|
||||
```rust
|
||||
pub struct ProvinceWorldState {
|
||||
pub province_id: u64,
|
||||
pub territorial_status: TerritorialStatus, // runtime-updated
|
||||
pub placed_at_generation: bool, // immutable: set at Layer 2, never changed
|
||||
// ... other Province fields
|
||||
}
|
||||
```
|
||||
|
||||
`placed_at_generation = true` for any Province that received at least one settlement at Layer 2 generation time. This flag is the only way to distinguish AbandonedZone from WildernessBuffer at runtime.
|
||||
|
||||
### Threshold rationale
|
||||
|
||||
The `0.65` road maintenance fraction for CoreTerritory is conservative — in practice, maintained road infrastructure is a clear marker of settled political power. The `0.60/0.35` split for ExtractiveZone reflects the characteristic pattern of corporate road investment: excellent on the extraction corridor, negligible off it.
|
||||
|
||||
Province dimensions (Tyre's ~60-200km range) mean a Province can contain 1-5 settlements in most cases. The absolute settlement count thresholds (≥2 for CoreTerritory, ≥1 for FrontierTerritory) are appropriate at this scale — a single isolated city is frontier territory regardless of its internal quality.
|
||||
|
||||
---
|
||||
|
||||
## 3. Naming: Name Reservation Data Structure and Fulfillment
|
||||
|
||||
### What the authoring layer provides
|
||||
|
||||
Under Amendment 3, systems.db holds:
|
||||
- Named cities with economic roles, populations, and corp HQ associations
|
||||
- Geographic feature names (rivers, mountain ranges, seas)
|
||||
|
||||
What it does NOT hold: positions. The generator must fulfill all name reservations.
|
||||
|
||||
### Name reservation data structure
|
||||
|
||||
**New `atlas_city_names` table (replaces authored position storage in `atlas_cities`):**
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS atlas_city_names (
|
||||
body_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
population INTEGER NOT NULL,
|
||||
economic_role TEXT NOT NULL, -- matches EconomicRole enum
|
||||
hq_for_corp TEXT, -- NULL if not a HQ; corp_id from corporations table
|
||||
cultural_hint TEXT, -- optional: hints naming pool for surrounding features
|
||||
PRIMARY KEY (body_id, name)
|
||||
);
|
||||
```
|
||||
|
||||
**New `atlas_feature_names` table (geographic feature identity):**
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS atlas_feature_names (
|
||||
body_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
feature_class TEXT NOT NULL, -- 'river' | 'mountain_range' | 'ocean' | 'sea' | 'bay'
|
||||
rank_hint INTEGER, -- 1 = primary/largest, 2 = secondary; NULL = seed-assigned rank
|
||||
PRIMARY KEY (body_id, name)
|
||||
);
|
||||
```
|
||||
|
||||
**Runtime `NameReservation` struct (built from systems.db at generation start):**
|
||||
|
||||
```rust
|
||||
pub struct NameReservation {
|
||||
pub name: String,
|
||||
pub reservation_kind: ReservationKind,
|
||||
}
|
||||
|
||||
pub enum ReservationKind {
|
||||
CorpHeadquarters {
|
||||
corp_id: String,
|
||||
economic_role: EconomicRole,
|
||||
// Hard constraints on attractor type, derived from economic_role
|
||||
// (see attractor-matching constraints in Section 5)
|
||||
},
|
||||
NamedCity {
|
||||
population: u32,
|
||||
economic_role: EconomicRole,
|
||||
},
|
||||
GeographicFeature {
|
||||
feature_class: GeographicFeatureClass,
|
||||
rank_hint: Option<u32>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Fulfillment order
|
||||
|
||||
**Stage 0 (pre-generation):** Build the `Vec<NameReservation>` from systems.db for this body. Sort reservations:
|
||||
1. Corp HQ reservations (sorted by economic role constraint tightness: extraction most constrained → commercial least constrained)
|
||||
2. Named city reservations (sorted by population descending)
|
||||
3. Geographic feature reservations (rivers, then mountains, by rank_hint ascending)
|
||||
|
||||
**Stage 1 (Layer 1, during feature tag extraction):**
|
||||
For each named geographic feature reservation:
|
||||
- Match to the generated feature of that class with the matching rank (e.g., rank_hint = 1 → largest/longest river on the body)
|
||||
- If rank_hint is NULL, assign to the seed-derived Nth feature of that class (deterministic from body seed)
|
||||
- Store: `(body_id, feature_name, generated_feature_id)` in BodyWorldState
|
||||
|
||||
**Stage 2 (Layer 2, during attractor matching):**
|
||||
The attractor-matching algorithm processes settlement name reservations. For each reservation (in the sorted order from Stage 0):
|
||||
- Evaluate all available attractors against the reservation's hard constraints
|
||||
- Score each compatible (attractor, reservation) pair
|
||||
- Assign highest-scoring attractor; mark attractor as consumed
|
||||
- If no compatible attractor available: log mismatch; assign to best available ignoring hard constraints (soft placement); flag for lead review
|
||||
|
||||
**Stage 3 (Layer 2, overflow and unnamed):**
|
||||
After all named reservations are fulfilled:
|
||||
- Remaining population quota settlements receive generated names from the cultural pool for this body
|
||||
- Generated names use `cultural_hint` from the nearest named city as the culture anchor
|
||||
|
||||
**Mismatch handling:**
|
||||
|
||||
A name reservation "mismatch" occurs when:
|
||||
- A corp HQ reservation cannot be placed at a compatible attractor type
|
||||
- A named city's economic role is incompatible with all available attractors
|
||||
|
||||
Mismatch action: place at best-available attractor (never fail silently), write a log entry to `BodyWorldState.generation_log`:
|
||||
|
||||
```
|
||||
ATTRACTOR_MISMATCH: {body_id} / {reservation_name}
|
||||
Expected: {compatible_attractor_types}
|
||||
Placed at: {actual_attractor_type} (quality: {score:.2f})
|
||||
Reason: {available_attractors_at_generation}
|
||||
Action: review systems.db economic_role for {reservation_name} or add geographic feature
|
||||
```
|
||||
|
||||
This log is read-only world state — it persists in the session and can be surfaced in developer tooling.
|
||||
|
||||
### What names are generated (vs. reserved)
|
||||
|
||||
- All geographic features NOT in `atlas_feature_names` receive generated names at Stage 1
|
||||
- All cities NOT in `atlas_city_names` receive generated names at Stage 3
|
||||
- Sub-settlements (Outpost, Waypoint, RuralCluster) always receive generated names (they are never in systems.db as named reservations — sub-settlement names are emergent)
|
||||
|
||||
Generated names use the cultural pool for the body. The pool selection rule:
|
||||
- Primary pool: cultural_hint from the nearest named city reservation
|
||||
- Secondary pool: body's primary biome class as a fallback cultural flavor
|
||||
- Tertiary pool: generic Settled Reach pool (all cultures, minimal distinctiveness)
|
||||
|
||||
The name pool population (content, actual strings) is Mellanie's domain. The generator only needs the pool selection rule and the pool reference.
|
||||
|
||||
---
|
||||
|
||||
## 4. Five Spatial Arrangement Patterns
|
||||
|
||||
These are Layer 3 algorithm specifications. Each pattern takes as input the CityGenerationContext (including `founding_orientation` and `political_archetype`) and produces district grid positions in city-local sim tiles.
|
||||
|
||||
All patterns must satisfy the three-component district mix constraint (Burnelli-Sheldon, locked): every settlement has guaranteed baseline district types; economic role modifies character, not presence. The spatial patterns below arrange those mandatory districts, not eliminate them.
|
||||
|
||||
### Pattern 1: CompanyTown Spine
|
||||
|
||||
**When:** `political_archetype = CompanyTown`
|
||||
|
||||
**Principle:** The company is the city. The resource or facility is at one end; everything else is infrastructure for getting workers to it and back. The city has no civic center because it has no civic purpose — it exists for the company's operation.
|
||||
|
||||
**Spine direction:** Derived from `founding_orientation`:
|
||||
- `ResourceFacing, North` → spine runs South-North; resource/industrial end at North edge
|
||||
|
||||
**District placement algorithm:**
|
||||
|
||||
```
|
||||
1. Set spine_axis = direction from city center toward FoundingOrientation feature
|
||||
2. Sort districts along spine_axis, position 0 at the resource-facing end
|
||||
|
||||
Mandatory district sequence (applied in order along spine):
|
||||
Position 0 (resource-facing end):
|
||||
DistrictType = Industrial or LogisticsHub (whichever matches economic_role closer)
|
||||
prosperity_baseline = district_base - 0.10 (older, wear from proximity to operations)
|
||||
|
||||
Position 1:
|
||||
DistrictType = Mixed (rough — worker services, basic commerce, rougher entertainment)
|
||||
prosperity_baseline = district_base - 0.05
|
||||
|
||||
Positions 2..(N-2):
|
||||
DistrictType = Residential (denser near industrial end, more spacious at far end)
|
||||
prosperity_baseline = district_base + linear_gradient(position, 0.0 to +0.10)
|
||||
|
||||
Position N-1 (far end, away from resource):
|
||||
DistrictType = Commercial or Administrative (company office, newer development)
|
||||
prosperity_baseline = district_base + 0.10 (newest, planned)
|
||||
```
|
||||
|
||||
**Width:** Spine is 1 district wide for N ≤ 4. For N > 4, spine is 2 districts wide (parallel columns aligned to spine_axis). The wider column gets Residential; the narrower gets the character types (Industrial, Mixed, Commercial).
|
||||
|
||||
**Grid geometry:** Districts are placed at:
|
||||
```
|
||||
(col=0 or col=1, row=position_index)
|
||||
```
|
||||
where `col=0` is the primary spine, `col=1` is the secondary spine for width > 1.
|
||||
Origin: `(0, 0)` = resource-facing end of city.
|
||||
|
||||
**No civic center rule:** No Administrative district is placed at the center. If the three-component model requires an Administrative district (WorldTier ≥ Backwater), it goes at the far-end position (`N-1`), not at a central grid position.
|
||||
|
||||
---
|
||||
|
||||
### Pattern 2: AdminCapital Radial
|
||||
|
||||
**When:** `political_archetype = AdminCapital`
|
||||
|
||||
**Principle:** Power radiates from the administrative center. The fortified or palatial core is at the middle or at the prestige position (high ground, commanding view). Everything else is arranged in relation to it. You know where power lives by looking at the geometry.
|
||||
|
||||
**Center determination:**
|
||||
- `founding_orientation = DefenseFacing`: Administrative district placed at geometric center of the district grid. City radiates in all directions from that center.
|
||||
- `founding_orientation = AdminFacing`: Administrative district placed at one edge (the "prestige edge" — the edge facing the primary road approach into the city). City radiates inward from that edge.
|
||||
- Other orientations for AdminCapital: treat as `AdminFacing` (the admin capital was built with a clear face toward its administered territory).
|
||||
|
||||
**District placement algorithm (DefenseFacing radial):**
|
||||
|
||||
For N districts forming approximately a square grid:
|
||||
```
|
||||
Find center cell (for N districts, the closest thing to a 2D grid center)
|
||||
e.g., N=9 → 3×3 grid, center = (1,1)
|
||||
e.g., N=5 → irregular; center = (1,1) in a + shape
|
||||
|
||||
Center cell: Administrative
|
||||
prosperity_baseline = district_base + 0.15 (power center; oldest planned district)
|
||||
|
||||
Cells adjacent to center: Commercial, Entertainment, Mixed
|
||||
(the districts that exist to serve the administrative core)
|
||||
prosperity_baseline = district_base + 0.05
|
||||
|
||||
Cells in outer ring: Residential, Industrial, Logistics
|
||||
prosperity_baseline = district_base - 0.05 to 0.0
|
||||
```
|
||||
|
||||
**Prosperity gradient:** Decreases from center outward. The gradient is radial, not directional.
|
||||
|
||||
**District placement algorithm (AdminFacing edge-facing):**
|
||||
|
||||
```
|
||||
Prestige edge = the edge of the district grid facing the primary road approach direction
|
||||
(read from road_entry_directions in CityGenerationContext, take the direction with
|
||||
highest road quality)
|
||||
|
||||
Prestige row (row 0, facing primary approach):
|
||||
Administrative district at col floor(district_grid_width / 2)
|
||||
Adjacent in prestige row: Commercial, Mixed
|
||||
|
||||
Middle rows:
|
||||
Residential, Commercial, Entertainment
|
||||
|
||||
Back row (away from prestige edge):
|
||||
Industrial, Logistics
|
||||
|
||||
Prosperity gradient: decreases from prestige row toward back row
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Pattern 3: FreePort Multi-Node
|
||||
|
||||
**When:** `political_archetype = FreePort`
|
||||
|
||||
**Principle:** Commerce without a center. A FreePort isn't controlled; it's used. Multiple independent commerce nodes emerged at different geographic junctions (river mouth + harbor, rail junction + river crossing, etc.). Each node has its own commercial gravity. There is no civic center because no single authority built one — civic infrastructure is minimal relative to commercial infrastructure.
|
||||
|
||||
**Node count:** 2 nodes for N ≤ 6 districts. 3 nodes for N > 6.
|
||||
|
||||
**Node positions:**
|
||||
```
|
||||
Node 0: FoundingOrientation edge (primary harbor or confluence point)
|
||||
Node 1: Seed-derived position [30–50%] of district_grid_width away from Node 0,
|
||||
along the geographic feature axis (along coastline if PortFacing,
|
||||
along river direction if river confluence)
|
||||
Node 3 (if applicable): Seed-derived position approximately opposite Node 0
|
||||
(the "newer commerce" node, where development extended when Node 0 saturated)
|
||||
```
|
||||
|
||||
**Districts per node:** Divide total district count across nodes:
|
||||
```
|
||||
Node 0: ceil(N * 0.45) districts
|
||||
Node 1: ceil(N * 0.35) districts
|
||||
Node 2 (if present): remaining districts
|
||||
```
|
||||
|
||||
**Within each node:**
|
||||
```
|
||||
Node center district: Commercial or Entertainment
|
||||
prosperity_baseline: independent per node; Node 0 = oldest (potentially lower prosperity
|
||||
if worn); Node 1 = newer (possibly higher prosperity if the city is growing that direction)
|
||||
|
||||
Adjacent to node center: Mixed (dense services, rough commerce)
|
||||
|
||||
Node periphery: Residential (for the workers who service each commerce node)
|
||||
```
|
||||
|
||||
**No city-wide prosperity gradient:** Each node has its own local gradient (high at commerce center, decreasing outward). There is no single directional gradient across the entire city. This produces the "patchwork" character of free ports — pockets of wealth and roughness alternating based on which node you're near.
|
||||
|
||||
**Space between nodes:** Districts in the gap between nodes are assigned Mixed or Residential type. They are not associated with either node's commercial center — they are the "filler" that grew between commerce clusters.
|
||||
|
||||
---
|
||||
|
||||
### Pattern 4: Contested Dual-Center / Overlay
|
||||
|
||||
**When:** `political_archetype = Contested`
|
||||
|
||||
**Principle:** Two political actors each built their version of this city, and neither succeeded in removing the other. The city has two half-grids, each internally coherent with one actor's logic, meeting at a contested boundary where the coherence breaks down.
|
||||
|
||||
**Contested boundary derivation:**
|
||||
1. If the Province has a NaturalBarrier tag bisecting the city footprint: use that barrier's line as the boundary.
|
||||
2. Otherwise: boundary = the road axis perpendicular bisector (the line perpendicular to the primary road through the city, passing through the city center).
|
||||
3. Boundary position is seeded to prevent exact bisection: offset by `child_seed(city_seed, CONTESTED_BOUNDARY_DISCRIMINANT) % 25 - 12` percent from center.
|
||||
|
||||
**Each half-city arrangement:**
|
||||
|
||||
```
|
||||
Faction A's half (the half facing FoundingOrientation direction):
|
||||
Arranged according to Faction A's inferred archetype:
|
||||
- If economic_role suggests CompanyTown: spine arrangement within the half
|
||||
- If economic_role suggests AdminCapital: radial from the half's center
|
||||
- Otherwise: ordered Commercial/Residential sequence
|
||||
|
||||
Faction B's half (opposite side of boundary):
|
||||
Arranged according to Faction B's inferred archetype
|
||||
(Faction B's archetype is always different from Faction A's — a Contested city
|
||||
has two distinct political logics, not two copies of the same logic)
|
||||
```
|
||||
|
||||
**How Faction A vs. Faction B archetypes are determined:**
|
||||
- Faction A: the larger of the two corp presence clusters in the Province (by corp asset count). Faction A's archetype = the political archetype implied by Faction A corp's `economic_role`.
|
||||
- Faction B: the smaller cluster. Faction B's archetype = implied by Faction B corp's `economic_role`.
|
||||
- If no distinct corp clusters: Faction A = corporate (economic_role-derived), Faction B = administrative (government/independent).
|
||||
|
||||
**Contested boundary districts:**
|
||||
|
||||
Districts adjacent to the boundary line receive:
|
||||
```
|
||||
DistrictType = Mixed (contested zones are commercially pragmatic — both sides trade here)
|
||||
perimeter_treatment = Checkpoint or Walled
|
||||
(Checkpoint if both sides have active political authority; Walled if one side has retreated)
|
||||
prosperity_baseline = district_base - 0.15
|
||||
(contested areas are economically suppressed — no authority invests here fully)
|
||||
```
|
||||
|
||||
**Prosperity gradient:** Increases away from the contested boundary toward each faction's center. The contested middle is the economic trough; both centers are the local prosperity peaks.
|
||||
|
||||
---
|
||||
|
||||
### Pattern 5: OrganicGrowth Irregular
|
||||
|
||||
**When:** `political_archetype = OrganicGrowth`, or `AttractorAssignment::Synthetic(PopulationOverflow)`
|
||||
|
||||
**Principle:** No single authority planned this city. It grew around multiple small centers of gravity — a crossroads that attracted a market, a river bend where boats rested, a flat area where multiple villages merged. No single direction is "oldest." There is no civic center in the traditional sense; there are just the places people kept returning to.
|
||||
|
||||
**District placement:**
|
||||
|
||||
No predefined sequence. Districts are placed by local density attractors within the city footprint:
|
||||
|
||||
```
|
||||
1. Identify 2-3 local attractor points within city footprint (derived from seed):
|
||||
- Attractor 0: geometric center of city footprint (always present)
|
||||
- Attractor 1: seed-derived offset from center, [20-40%] of district_grid_width
|
||||
- Attractor 2 (for N ≥ 6): second seed-derived offset, approximately opposite Attractor 1
|
||||
|
||||
2. Assign each district to its nearest attractor (Voronoi partition by attractor proximity)
|
||||
|
||||
3. Within each attractor's cluster:
|
||||
- Attractor-center district: Commercial or Mixed
|
||||
- Adjacent districts: Residential, Entertainment, Commercial (random by seed)
|
||||
- No fixed ordering
|
||||
|
||||
4. Apply jitter to district grid positions:
|
||||
Each district position is offset by seed-derived jitter:
|
||||
jitter_x = child_seed(district_seed, JITTER_X) % (DISTRICT_TILE_WIDTH / 4) - (DISTRICT_TILE_WIDTH / 8)
|
||||
jitter_y = child_seed(district_seed, JITTER_Y) % (DISTRICT_TILE_WIDTH / 4) - (DISTRICT_TILE_WIDTH / 8)
|
||||
(Jitter is ±12.5% of district width — enough to break grid regularity without district overlap)
|
||||
```
|
||||
|
||||
**Prosperity gradient:** No city-wide gradient. Each attractor-cluster has an independent local gradient (higher at cluster center, decreasing outward). The overall effect is a prosperity map with multiple peaks and valleys — not a directional slope.
|
||||
|
||||
**Why jitter instead of full irregular placement:** Full irregular placement requires collision detection and complex position assignment. Jitter on a regular grid produces visible irregularity with O(1) computation. The player experiences the city as "not planned"; the implementation is still grid-based. This is an approximation that is improved post-Phase 1 if needed.
|
||||
|
||||
---
|
||||
|
||||
### Three-component model compatibility across all patterns
|
||||
|
||||
Burnelli-Sheldon's three components (population tier guarantees, economic role multipliers, settlement age character) apply within each pattern:
|
||||
|
||||
- **Population tier guarantees** ensure that even a CompanyTown spine has its mandatory Residential, Commercial, and Entertainment districts. The spine layout sequence above assigns these; the guarantee prevents their count from going to zero.
|
||||
- **Economic role multipliers** change the CHARACTER of the guaranteed districts. A CompanyTown's "Commercial" district is rougher and smaller than a FreePort's. The spatial pattern places it; the economic multiplier determines what kind.
|
||||
- **Settlement age character** modifies district quality without affecting spatial arrangement. Applied per district as a character modifier after spatial placement.
|
||||
|
||||
---
|
||||
|
||||
## 5. Attractor-Matching Narrative Plausibility Constraints
|
||||
|
||||
The attractor-matching algorithm (Tyre owns the implementation; Burnelli-Sheldon owns the scoring matrix) needs narrative plausibility constraints to prevent geographic nonsense. These are scoring rules from the narrative/political domain.
|
||||
|
||||
### Hard constraints (score → 0.0 if violated; settlement cannot be placed here)
|
||||
|
||||
**H1 — Port-land incompatibility:**
|
||||
`CoastalHarbor` or `RiverConfluence` attractor cannot be assigned to a settlement with `economic_role = mining` or `economic_role = extraction` UNLESS the body has coastal mineral deposits (flagged by `has_coastal_extraction` in systems.db, if that field exists, or implied by extraction corp with `location_id` at the coastal body).
|
||||
- Rationale: A mining town doesn't sit at a harbor without a reason to ship ore from that harbor. If it ships ore, it's a port-mining hybrid and the constraint relaxes.
|
||||
|
||||
**H2 — Research isolation:**
|
||||
A corp HQ settlement with `economic_role = research` cannot be assigned to `CoastalHarbor` unless the body's `planet_class` includes ocean/aquatic research biome.
|
||||
- Rationale: Research facilities in established SF settings are usually built away from commercial centers for noise isolation and security. A research HQ at a busy commercial harbor is unusual without a specific scientific rationale.
|
||||
|
||||
**H3 — Administrative terrain:**
|
||||
A corp HQ settlement with primary administrative operation (economic_role ∈ {service_mixed} at high economic_tier, or a government entity) cannot be assigned to `MountainPass` as primary attractor.
|
||||
- Rationale: Administrative power centers avoid terrain that is difficult to reach, maintain, and project from. Passes are chokepoints; they attract checkpoint authorities, not administrative capitals.
|
||||
|
||||
**H4 — Agricultural plains city size:**
|
||||
`ArablePlain` attractor can only be assigned to settlements with population ≤ 100,000 OR if the settlement is the body's primary food production hub (implied by `economic_role = agricultural`).
|
||||
- Rationale: A megacity on an agricultural plain has overwhelmed the plain with urban development — the plain is no longer the founding rationale, it's just flat terrain. Large cities at ArablePlain attractors in reality became cities for other reasons after outgrowing their agricultural origin.
|
||||
|
||||
### Soft constraints (scoring penalties applied to the compatibility matrix)
|
||||
|
||||
These modify Burnelli-Sheldon's scoring matrix with narrative-derived weights. They are additive to (or multiplicative against) the economic compatibility scores.
|
||||
|
||||
| Settlement economic_role | Attractor to avoid | Narrative penalty |
|
||||
|--------------------------|-------------------|--------------------|
|
||||
| `agricultural` | `ResourceConcentration`, `MountainPass` | -0.40 |
|
||||
| `commercial` | `ResourceConcentration`, `Defensible` | -0.25 |
|
||||
| `manufacturing` | `MountainPass`, `Defensible` | -0.20 |
|
||||
| `frontier` | `RiverConfluence`, `CoastalHarbor` | -0.10 (frontier implies isolation; established trade nodes are not frontier) |
|
||||
| `research` | `RiverConfluence` (as primary) | -0.15 (research at river confluences is possible but unusual) |
|
||||
| `energy` | `ArablePlain` (if no resource) | -0.30 (energy facilities need a resource to process, not farmland) |
|
||||
|
||||
| Settlement economic_role | Preferred attractor | Narrative bonus |
|
||||
|--------------------------|--------------------|-----------------|
|
||||
| `extraction`, `mining` | `ResourceConcentration` | +0.50 (strong positive signal) |
|
||||
| `agricultural` | `ArablePlain` | +0.40 |
|
||||
| `transit`, `commercial` | `RiverConfluence`, `CoastalHarbor` | +0.35 |
|
||||
| `frontier` | `MountainPass`, `ResourceConcentration` | +0.25 |
|
||||
| `research` | `Defensible`, `ArablePlain` (quiet) | +0.15 |
|
||||
| `manufacturing` | `CoastalHarbor`, `ArablePlain` | +0.20 |
|
||||
|
||||
### Mismatch detection and flagging
|
||||
|
||||
A placement with final compatibility score < 0.35 (after hard constraints verified and soft constraints applied) is flagged as a mismatch:
|
||||
|
||||
```
|
||||
Log entry in BodyWorldState.generation_log:
|
||||
ATTRACTOR_MISMATCH:
|
||||
body: {body_id}
|
||||
settlement: {name}
|
||||
placed_at: {attractor_type} (quality score: {score:.2f})
|
||||
economic_role: {economic_role}
|
||||
available_compatible_attractors: {count} (were there any? if 0, the body lacks this attractor type)
|
||||
recommendation: see ATTRACTOR_MISMATCH resolution guide
|
||||
```
|
||||
|
||||
Mismatch does NOT prevent generation. The city is placed at the best available attractor. The log exists so developers can identify bodies where systems.db economic_role assignments are implausible relative to generated terrain.
|
||||
|
||||
### The geographic common sense rule (from Ozzie)
|
||||
|
||||
Beyond scoring, one categorical rule applies: **a settlement's primary economic function must be physically possible at its placed attractor**. This is not a score — it is a binary check:
|
||||
|
||||
```
|
||||
fn is_physically_possible(role: EconomicRole, attractor: GeographicFeatureTag) -> bool {
|
||||
match (role, attractor) {
|
||||
// Explicit impossibilities
|
||||
(EconomicRole::Agricultural, GeographicFeatureTag::MountainPass) => false,
|
||||
(EconomicRole::Extraction | EconomicRole::Mining, GeographicFeatureTag::ArablePlain)
|
||||
if !body.has_subsurface_resources => false,
|
||||
// Everything else: physically possible even if implausible
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `is_physically_possible` returns false, the assignment is treated as a hard constraint violation (score → 0.0). This is stricter than H1-H3 above; it catches cases the scoring matrix might not cover.
|
||||
|
||||
---
|
||||
|
||||
## 6. Interaction Notes for Other Agents
|
||||
|
||||
**For Tyre:** The `AttractorAssignment` enum (Section 1) needs to be integrated into the `GeneratedSettlement` struct. The `placed_at_generation` field on `ProvinceWorldState` is required for AbandonedZone/WildernessBuffer distinction at runtime. The name reservation tables (`atlas_city_names`, `atlas_feature_names`) are schema changes — these replace the authored position storage in `atlas_cities` and add feature names.
|
||||
|
||||
**For Burnelli-Sheldon:** My soft constraint table (Section 5) is proposed as additive/multiplicative to your compatibility matrix. Please verify these don't conflict with your comparative advantage matching formula — specifically, my `-0.40` for agricultural at MountainPass should not create cases where a body has no valid attractor for an agricultural city. If that's possible (all-mountain body with an agricultural economic_role), define the fallback behavior.
|
||||
|
||||
**For Gestalt:** The Contested dual-center pattern (Section 4, Pattern 4) uses Faction A vs. Faction B archetype inference. This depends on corp cluster analysis during Layer 2, which I've sketched but which needs a concrete algorithm. Does your mechanic design for Layer 2 include corp clustering, or should I specify a simpler faction inference rule?
|
||||
|
||||
---
|
||||
|
||||
*Paula — Round 2. Written 2026-05-01.*
|
||||
@@ -0,0 +1,728 @@
|
||||
---
|
||||
title: "Paula — Round 3: Convergence and Narrative D-Records"
|
||||
description: "Convergence positions on Province boundary legibility, founding_age → layout_mode, and mismatch flagging; D-record candidates for all narrative/political decisions locked in this workshop; narrative consistency review of other participants' proposals"
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: paula
|
||||
round: 3
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 3 (Paula)
|
||||
|
||||
**Focus:** Convergence and D-record candidates. Three convergence items from Round 2 notes; six D-record candidates for narrative/political decisions; narrative consistency review of other participants' proposals.
|
||||
|
||||
**Lead decision absorbed:** Two-tier mismatch flagging — score < 0.35 = warning, score < 0.15 = error. This resolves Q1 from Round 2 notes.
|
||||
|
||||
---
|
||||
|
||||
## Convergence Items
|
||||
|
||||
### Item 1: Province Boundary Legibility (Q3)
|
||||
|
||||
**Ozzie's requirement:** Province boundaries must be visible on the planetary map as natural features (watershed lines, drainage basins), not arbitrary grid lines. If Province boundaries aren't legible, TerritorialStatus doesn't communicate to the player.
|
||||
|
||||
**Narrative position: adopt this requirement fully.**
|
||||
|
||||
Province boundaries ARE drainage basin boundaries — this is how they are defined in the spatial hierarchy (Amendment 4: Province = "road-map travel region defined by natural boundaries from Layer 1: drainage basins, ridgelines"). The Layer 1 river network at Province resolution already defines these boundaries as geographic facts. The planetary map does not need to render a separate "Province boundary" layer — it needs to render the coarse river and ridgeline network, and Province boundaries will emerge naturally from that rendering.
|
||||
|
||||
**The correct rendering chain:**
|
||||
|
||||
1. Layer 1 produces a coarse river network at Province resolution (drainage basin edges)
|
||||
2. The planetary atlas UI renders this river network as thin geographic lines
|
||||
3. Province boundaries are the visible boundaries between drainage basins — rendered by the river network, not as political overlays
|
||||
4. TerritorialStatus is a color or texture fill within each Province's geographic boundary (the space between the river lines)
|
||||
|
||||
**What the player sees:** The political map IS the geographic map. The watershed lines are both rivers and Province boundaries. This is historically accurate — rivers have always been the primary political boundaries because they are obvious, defensible, and non-arbitrary. A player looking at the atlas UI reads the river as a boundary without being told it's a boundary.
|
||||
|
||||
**Implementation note (not my domain, but relevant):** The Province-level river network needs to be stored in BodyWorldState as a renderable set of line segments (not just as a list of confluence points). This is an additional field in the Layer 1 output that the atlas UI consumes. Tyre should confirm this is included in `RiverNetwork` in BodyWorldState.
|
||||
|
||||
**D-record implication:** The TerritorialStatus D-record candidate below includes this rendering specification as part of the decision — it is not a separate UI decision, it is integral to how TerritorialStatus is designed to communicate.
|
||||
|
||||
---
|
||||
|
||||
### Item 2: founding_age → layout_mode Narrative Assessment (Q2)
|
||||
|
||||
**Ozzie's proposal:** founding age should modify `layout_mode` as well as `prosperity_baseline`. Old settlements → irregular layout modes. Young settlements → grid layout.
|
||||
|
||||
**Narrative assessment: adopt with the following constraints.**
|
||||
|
||||
The historical basis is correct and strong: planned settlements are grids (the company built this to a specification); grown settlements are organic (no one planned the whole thing, it accumulated). This is one of the most legible signals in real-world urban history — you can look at a city and tell whether it was planned or grew.
|
||||
|
||||
The narrative complication is that political_archetype already governs the spatial arrangement pattern, and some archetypes are intrinsically planned (CompanyTown, AdminCapital) while others are intrinsically organic (OrganicGrowth). Age should interact with the pattern, not override it.
|
||||
|
||||
**My proposed interaction rules:**
|
||||
|
||||
```
|
||||
CompanyTown (spine pattern):
|
||||
Always grid. Age does not introduce irregularity — the company planned it and
|
||||
maintains that plan. Age instead produces: character tags (legacy_infrastructure,
|
||||
retrofitted) and prosperity gradient aging effects. The SPINE gets more legible
|
||||
with age (maintenance history visible), not more organic.
|
||||
|
||||
AdminCapital (radial pattern):
|
||||
Core (Administrative district) always planned and maintained.
|
||||
Outer rings accumulate irregularity with age:
|
||||
layout_irregularity = clamp(founding_age_years / 500.0, 0.0, 0.6)
|
||||
Result: ancient AdminCapitals have planned cores and organic peripheries.
|
||||
This is historically very accurate (Roman city center, medieval accretion around it).
|
||||
|
||||
FreePort (multi-node pattern):
|
||||
Node centers remain planned (commerce is maintained by self-interest).
|
||||
Between-node fill becomes increasingly organic with age.
|
||||
layout_irregularity = clamp(founding_age_years / 300.0, 0.0, 0.8)
|
||||
Older FreePorts feel like the overlay of many eras.
|
||||
|
||||
Contested (dual-center overlay):
|
||||
Each faction's half uses its own archetype's age rules.
|
||||
The contested boundary zone accumulates irregularity at double rate:
|
||||
boundary_irregularity = clamp(founding_age_years / 200.0, 0.0, 1.0)
|
||||
Very old contested cities have incoherent seam zones.
|
||||
|
||||
OrganicGrowth (irregular local density):
|
||||
Already maximally irregular. Age has no additional effect on layout_mode.
|
||||
Age instead deepens the character modifiers (more legacy_infrastructure,
|
||||
more layers visible).
|
||||
```
|
||||
|
||||
**Minimum viable implementation:** `layout_irregularity: f32` as an additional field on each district (set by Layer 3 generation, derived from archetype + age interaction rules above). The block generation algorithm at Layer 4 reads this value to vary block shape regularity and street angle consistency. Value 0.0 = perfect grid; value 1.0 = fully organic street angles.
|
||||
|
||||
**This is my accepted position. I have no blocking objection to Ozzie's proposal — I'm specifying how it interacts with the five spatial arrangement patterns.**
|
||||
|
||||
---
|
||||
|
||||
### Item 3: Mismatch Flagging Integration with Name Reservation System
|
||||
|
||||
**Lead decision:** score < 0.35 = warning, score < 0.15 = error.
|
||||
|
||||
**Integration with the Stage 2 fulfillment pipeline:**
|
||||
|
||||
```
|
||||
During attractor-matching (Stage 2):
|
||||
|
||||
For each named city reservation (in priority order):
|
||||
1. Compute best available score for this reservation
|
||||
2. Assign to best available attractor regardless of score
|
||||
3. Classify the assignment:
|
||||
- score ≥ 0.35: MATCH — no flag
|
||||
- score 0.15–0.35: ATTRACTOR_WARNING — write to generation_log
|
||||
- score < 0.15: ATTRACTOR_ERROR — write to generation_log
|
||||
|
||||
For corp HQ reservations specifically (Tier A):
|
||||
- Elevate one tier: WARNING → ERROR if corp_id is referenced in active
|
||||
trade routes or supply chains in systems.db. A misplaced HQ with
|
||||
active trade dependencies is a more serious data quality problem
|
||||
than a misplaced unnamed city.
|
||||
|
||||
Generation log entry format:
|
||||
{
|
||||
level: "WARNING" | "ERROR",
|
||||
body_id: i64,
|
||||
reservation_name: String,
|
||||
placed_at: GeographicFeatureTag | "Synthetic",
|
||||
compatibility_score: f32,
|
||||
corp_id: Option<i64>,
|
||||
available_compatible_attractors: u32, // were any compatible attractors available?
|
||||
recommendation: &str // e.g. "review economic_role in atlas_city_names"
|
||||
}
|
||||
```
|
||||
|
||||
The `generation_log` in BodyWorldState is a `Vec<GenerationLogEntry>`. It persists for the session. Developer tooling should be able to dump this for quality review. This is not surfaced to the player.
|
||||
|
||||
**Q5 resolution (how city names enter atlas_city_names):** The population path is: wiki content (Miri's authored city/settlement lists in wiki articles) → `import_economics.py` reads wiki content and populates `atlas_city_names` at build time. The generator does not invent names; it fulfills name reservations from the wiki. This matches the existing pipeline pattern: `import_economics.py` already reads wiki TOML files for brands and economics data. City names are the same pattern.
|
||||
|
||||
Implication: `atlas_city_names` requires a corresponding wiki content pass before any body can have named cities. Bodies without wiki city content will have 0 name reservations — the generator places unnamed settlements at all attractors and assigns generated names from the cultural pool. This is acceptable for frontier/minor bodies; it is a content gap for major bodies.
|
||||
|
||||
---
|
||||
|
||||
## D-Record Candidates
|
||||
|
||||
These candidates require formal D-NNN IDs, which must be claimed via `tooling/db/decision claim` before filing. The candidates below are ready to claim.
|
||||
|
||||
---
|
||||
|
||||
### D-C-TS: TerritorialStatus — Enum and Derivation Algorithm
|
||||
|
||||
**Decision statement:** TerritorialStatus is a six-value enum derived per Province as a post-processing pass after Layer 2 settlement placement and road graph generation. It is runtime-updated when active settlement count changes.
|
||||
|
||||
**Rationale:** TerritorialStatus makes the political character of traversable space between major settlements legible from the planetary atlas UI and from street-level approach conditions (road quality, signage, lighting density). It cannot be authored without also authoring all settlement placement; it cannot be purely emergent at runtime without a generation-time anchor. The Layer 2 post-processing pass produces it once at generation time, and the economic sim updates it when settlement activity changes.
|
||||
|
||||
**Enum specification:**
|
||||
|
||||
```rust
|
||||
pub enum TerritorialStatus {
|
||||
CoreTerritory, // Established; maintained road infrastructure throughout Province
|
||||
FrontierTerritory, // Active settlement; infrastructure thin at Province edges
|
||||
ExtractiveZone, // Resource extraction dominant; corporate road investment visible
|
||||
ContestZone, // Two or more competing political actors; infrastructure incoherent
|
||||
WildernessBuffer, // Never settled; no settlement placed at Layer 2
|
||||
AbandonedZone, // Was settled (placed_at_generation = true); now inactive
|
||||
}
|
||||
```
|
||||
|
||||
**Derivation algorithm:**
|
||||
|
||||
```rust
|
||||
fn derive_territorial_status(province: &ProvinceWorldState) -> TerritorialStatus {
|
||||
// AbandonedZone: was settled, now empty. Checked first — runtime override.
|
||||
if province.placed_at_generation && !province.active {
|
||||
return TerritorialStatus::AbandonedZone;
|
||||
}
|
||||
// WildernessBuffer: was never settled.
|
||||
if !province.placed_at_generation {
|
||||
return TerritorialStatus::WildernessBuffer;
|
||||
}
|
||||
// ExtractiveZone: resource extraction + corporate presence + characteristic roads.
|
||||
if province.primary_economic_activity == EconomicActivity::Extraction
|
||||
&& province.corporate_presence_score > 0.4
|
||||
&& province.extraction_corridor_quality > 0.60
|
||||
&& province.off_corridor_road_quality < 0.35 {
|
||||
return TerritorialStatus::ExtractiveZone;
|
||||
}
|
||||
// ContestZone: overlapping political authority.
|
||||
if province.jurisdiction_overlap_score > 0.3 {
|
||||
return TerritorialStatus::ContestZone;
|
||||
}
|
||||
// CoreTerritory: well-connected, maintained infrastructure.
|
||||
if province.infrastructure_quality > 0.6
|
||||
&& province.corporate_road_maintenance > 0.5 {
|
||||
return TerritorialStatus::CoreTerritory;
|
||||
}
|
||||
// FrontierTerritory: active but underdeveloped. Catch-all.
|
||||
TerritorialStatus::FrontierTerritory
|
||||
}
|
||||
```
|
||||
|
||||
**Key architectural requirement:** `province.placed_at_generation: bool` is an immutable flag set at Layer 2 generation time and never modified. It is the only runtime-available signal distinguishing WildernessBuffer (never settled) from AbandonedZone (settled, now empty). `province.active: bool` is the runtime-variable field.
|
||||
|
||||
**Province boundary rendering:** Province boundaries are rendered on the planetary atlas UI as the coarse river and ridgeline network from Layer 1 (drainage basin edges). TerritorialStatus is a fill color/texture within each Province's geographic boundary. Province boundaries are geographic facts (watershed boundaries), not political overlay lines.
|
||||
|
||||
**Derivation trigger:** TerritorialStatus is re-derived per Province when `active_settlement_count` for the Province changes. The economic sim owns this trigger.
|
||||
|
||||
**Blocks:** Layer 2 road graph + settlement placement must be complete before TerritorialStatus derivation runs.
|
||||
|
||||
---
|
||||
|
||||
### D-C-FO: FoundingOrientation — Enum and Derivation
|
||||
|
||||
**Decision statement:** FoundingOrientation is a five-value enum directly readable from the `AttractorAssignment` of the settlement at Layer 2 generation time. No authoring pass required for the common case; named cities may have an override column for setting lore that explicitly contradicts geographic logic.
|
||||
|
||||
**Rationale:** In the planet-down cascade, the geographic feature that caused a settlement to exist is known at generation time (it is the assigned attractor). FoundingOrientation is a direct lookup from that feature. This eliminates the sanity-check authoring pass that would otherwise be required and makes the field deterministic from generation-time data.
|
||||
|
||||
**Enum specification:**
|
||||
|
||||
```rust
|
||||
pub enum FoundingOrientation {
|
||||
PortFacing, // Settlement grew around a harbor or river confluence
|
||||
RailHeadFacing, // Settlement grew around a transit/rail network junction
|
||||
ResourceFacing, // Settlement grew around an extraction resource site
|
||||
DefenseFacing, // Settlement grew around defensible terrain
|
||||
AdminFacing, // Settlement exists by political decision; no geographic trigger
|
||||
}
|
||||
```
|
||||
|
||||
**Derivation lookup:**
|
||||
|
||||
```
|
||||
AttractorAssignment::Geographic(CoastalHarbor | RiverConfluence) → PortFacing
|
||||
AttractorAssignment::Geographic(ResourceConcentration) → ResourceFacing
|
||||
AttractorAssignment::Geographic(MountainPass | Defensible) → DefenseFacing
|
||||
AttractorAssignment::Geographic(ArablePlain) → AdminFacing
|
||||
(agricultural plain settlements are administrative service hubs, not resource/port/defense cities)
|
||||
AttractorAssignment::Synthetic(_) → AdminFacing
|
||||
```
|
||||
|
||||
**RailHeadFacing special case:** This value is not derivable from a geographic attractor (transit junctions are infrastructure, not terrain features). `RailHeadFacing` is assigned in a second pass after road/rail graph generation, when the road algorithm identifies a settlement that sits at a road/rail network junction of high connectivity. It overrides the initial `AdminFacing` from `Synthetic` assignment when the topology warrants it.
|
||||
|
||||
**Named city override:** `atlas_city_names` may include a `founding_orientation_override` column (nullable). Non-null values override the derivation lookup. This is the authoring escape hatch for setting lore that explicitly places an administrative capital at what would otherwise read as a resource site (e.g., a corporate headquarters that was also a founding city, built at a resource site but now a genuine capital).
|
||||
|
||||
**Storage:** In `CityGenerationContext` as `founding_orientation: FoundingOrientation`, passed from Layer 2 to Layer 3 for district grid orientation.
|
||||
|
||||
---
|
||||
|
||||
### D-C-AA: AttractorAssignment — OrganicGrowth Disambiguation Mechanism
|
||||
|
||||
**Decision statement:** Every `GeneratedSettlement` carries an `AttractorAssignment` enum that records whether the settlement was placed at a geographic attractor or at a synthetic position. This is the mechanism that makes FoundingOrientation derivation unambiguous and enables OrganicGrowth political_archetype identification.
|
||||
|
||||
**Rationale:** Without this field, OrganicGrowth settlements (excess population, no attractor) and AdminFacing settlements (politically placed at a Province centroid) are algorithmically indistinguishable — both have no geographic trigger. The enum makes the distinction explicit in the generation record.
|
||||
|
||||
**Enum specification:**
|
||||
|
||||
```rust
|
||||
pub enum AttractorAssignment {
|
||||
Geographic {
|
||||
attractor_type: GeographicFeatureTag,
|
||||
quality_score: f32, // 0.0–1.0; scores below 0.35 generate a warning
|
||||
},
|
||||
Synthetic {
|
||||
reason: SyntheticPlacementReason,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum SyntheticPlacementReason {
|
||||
PopulationOverflow, // More named cities needed than geographic attractors available
|
||||
PoliticalDecision, // systems.db marks this city as politically placed
|
||||
CorpExpansion, // High economic_tier body; settlement density exceeds attractor supply
|
||||
}
|
||||
```
|
||||
|
||||
**OrganicGrowth political_archetype derivation:**
|
||||
|
||||
A settlement is assigned `PoliticalArchetype::OrganicGrowth` when:
|
||||
```
|
||||
attractor_assignment == Synthetic(PopulationOverflow)
|
||||
AND no dominant corp presence (corp_presence_count == 0 or no single corp > 40% presence)
|
||||
AND economic_role ∈ {service_mixed, commercial}
|
||||
```
|
||||
|
||||
Otherwise, synthetic settlements derive political_archetype from economic_role + corp_presence as normal.
|
||||
|
||||
**Storage:** Field on `GeneratedSettlement` in BodyWorldState. Part of the session-reproducible Layer 2 output.
|
||||
|
||||
**Interaction with SettlementClass:** Burnelli-Sheldon's `SettlementClass::OrganicGrowth` value corresponds to `AttractorAssignment::Synthetic(PopulationOverflow)` + the political_archetype derivation above. These are consistent descriptions at different levels of the architecture. Round 3 alignment note: the `OrganicGrowth` name should appear in both enums for consistency — `SettlementClass::OrganicGrowth` and `PoliticalArchetype::OrganicGrowth` — with `AttractorAssignment::Synthetic(PopulationOverflow)` as the underlying mechanism that produces both.
|
||||
|
||||
---
|
||||
|
||||
### D-C-SA: Five Spatial Arrangement Patterns
|
||||
|
||||
**Decision statement:** All five political archetypes have explicit, mandatory spatial arrangement patterns for Layer 3 district grid placement. No archetype uses the weight table alone to determine spatial arrangement. The weight table (three-component model) determines district TYPE mix; the spatial arrangement pattern determines district POSITION.
|
||||
|
||||
**Rationale:** The weight table cannot produce CompanyTown spine topology, AdminCapital radial structure, or Contested dual-center overlap. Spatial arrangement requires explicit algorithms. Without explicit patterns, the generator produces cities that are distinguishable by district type counts but not by geometry — the player cannot identify the archetype from walking around. Ozzie's acceptance criterion (15-second identification) requires geometry, not statistics.
|
||||
|
||||
**Acceptance criterion:** A player unfamiliar with the archetype system should be able to identify the city's political character from 15 seconds of walking in any district, based on street layout alone.
|
||||
|
||||
**The five patterns:**
|
||||
|
||||
**Pattern 1 — CompanyTown (Spine):**
|
||||
```
|
||||
Governing input: founding_orientation (sets spine axis direction)
|
||||
District sequence along spine axis:
|
||||
Position 0 (resource-facing end): Industrial or LogisticsHub
|
||||
Position 1: Mixed (rough services)
|
||||
Positions 2..N-2: Residential (gradient from denser near industrial to more spacious far end)
|
||||
Position N-1 (far end): Commercial or Administrative
|
||||
Spine width: 1 district for N≤4; 2 districts for N>4
|
||||
layout_irregularity: 0.0 (planned grid, increases with age per founding_age rules)
|
||||
prosperity_baseline gradient: increases from resource end toward far end (+0.10 across full length)
|
||||
```
|
||||
|
||||
**Pattern 2 — AdminCapital (Radial):**
|
||||
```
|
||||
Governing input: founding_orientation
|
||||
|
||||
DefenseFacing variant:
|
||||
Administrative district at geometric center of district grid
|
||||
Adjacent ring: Commercial, Entertainment, Mixed
|
||||
Outer ring: Residential, Industrial, Logistics
|
||||
Prosperity gradient: decreases radially from center
|
||||
|
||||
AdminFacing variant:
|
||||
Administrative district at edge facing primary road approach direction
|
||||
(primary road approach = highest-quality road in road_entry_directions)
|
||||
Middle rows: Residential, Commercial, Entertainment
|
||||
Back row (away from prestige edge): Industrial, Logistics
|
||||
Prosperity gradient: decreases from prestige row toward back row
|
||||
|
||||
layout_irregularity: 0.0 at core; increases at outer rings with age (see founding_age rules)
|
||||
```
|
||||
|
||||
**Pattern 3 — FreePort (Multi-Node):**
|
||||
```
|
||||
Node count: 2 for N≤6; 3 for N>6
|
||||
Node 0: FoundingOrientation edge (primary harbor/confluence)
|
||||
Node 1: [30–50%] of district_grid_width along geographic feature axis from Node 0
|
||||
Node 2 (if present): seed-derived position ~opposite Node 0
|
||||
|
||||
District allocation:
|
||||
Node 0: ceil(N × 0.45) districts
|
||||
Node 1: ceil(N × 0.35) districts
|
||||
Node 2 (if present): remainder
|
||||
|
||||
Within each node:
|
||||
Node center: Commercial or Entertainment
|
||||
Adjacent to center: Mixed
|
||||
Node periphery: Residential
|
||||
|
||||
layout_irregularity: 0.0 at node centers; increases between nodes with age
|
||||
Prosperity gradient: local per node, no city-wide gradient
|
||||
```
|
||||
|
||||
**Pattern 4 — Contested (Dual-Center Overlay):**
|
||||
```
|
||||
Contested boundary derivation (priority order):
|
||||
1. NaturalBarrier tag bisecting city footprint → use barrier line
|
||||
2. Road axis perpendicular bisector through city center
|
||||
3. Seed-derived offset: boundary_offset = (child_seed(city_seed, BOUNDARY) % 25 - 12)%
|
||||
(displacement of ±12% from center, preventing exact bisection)
|
||||
|
||||
Faction A (larger corp cluster or corporate faction):
|
||||
Occupies half of district grid facing FoundingOrientation direction
|
||||
Arranged by Faction A's inferred archetype (spine, radial, or service grid)
|
||||
|
||||
Faction B (smaller corp cluster or administrative faction):
|
||||
Occupies opposite half
|
||||
Arranged by Faction B's inferred archetype (different from Faction A)
|
||||
|
||||
Boundary-adjacent districts (1-district buffer on each side of boundary line):
|
||||
DistrictType: Mixed
|
||||
perimeter_treatment: Checkpoint (both active authorities) or Walled (one retreated)
|
||||
prosperity_baseline: district_base - 0.15 (contested areas suppressed)
|
||||
|
||||
layout_irregularity: each half follows its archetype's rules independently;
|
||||
boundary zone always layout_irregularity = 0.7+ (contested areas are architecturally incoherent)
|
||||
Prosperity gradient: increases away from boundary toward each faction's center
|
||||
```
|
||||
|
||||
**Pattern 5 — OrganicGrowth (Irregular Local Density):**
|
||||
```
|
||||
Local attractor points (seed-derived within city footprint):
|
||||
Attractor 0: geometric center (always present)
|
||||
Attractor 1: seed-derived offset [20–40%] of district_grid_width from center
|
||||
Attractor 2 (for N≥6): ~opposite Attractor 1
|
||||
|
||||
Voronoi partition: each district assigned to nearest attractor
|
||||
Within each attractor cluster:
|
||||
Attractor-center district: Commercial or Mixed
|
||||
Adjacent districts: Residential, Entertainment, Commercial (seed-determined sequence)
|
||||
|
||||
Grid jitter per district:
|
||||
offset_x = (child_seed(district_seed, JITTER_X) % (DISTRICT_WIDTH / 4)) - (DISTRICT_WIDTH / 8)
|
||||
offset_y = (child_seed(district_seed, JITTER_Y) % (DISTRICT_WIDTH / 4)) - (DISTRICT_WIDTH / 8)
|
||||
(±12.5% of district width; breaks regularity without requiring collision detection)
|
||||
|
||||
layout_irregularity: fixed at 0.8 (always highly organic; age has no additional effect)
|
||||
Prosperity gradient: per attractor cluster, local; no city-wide gradient
|
||||
```
|
||||
|
||||
**Three-component model compatibility:** The population tier guarantees from Burnelli-Sheldon's three-component model apply within each pattern. All five patterns must accommodate the mandatory district types for the settlement's population tier. The patterns position mandatory districts; the district mix algorithm assigns counts. Where the pattern specifies a position type (e.g., "Position 0: Industrial or LogisticsHub"), mandatory district guarantees take precedence over the position type if the guaranteed types do not include Industrial/Logistics at this population tier.
|
||||
|
||||
---
|
||||
|
||||
### D-C-NR: Name Reservation System
|
||||
|
||||
**Decision statement:** City and geographic feature identity is stored in two new systems.db tables (`atlas_city_names`, `atlas_feature_names`) populated at build time from wiki content. The runtime generator reads these as name reservations and fulfills them during Layer 1-2 generation. Name assignment follows a four-stage pipeline.
|
||||
|
||||
**Rationale:** Amendment 3 eliminated authored city positions. Named cities that appear in corporate records, faction histories, and player-facing content must still exist and be of the appropriate type. Name reservations (not positions) are the mechanism that guarantees corporate cross-references resolve while allowing positions to be fully generated.
|
||||
|
||||
**Table schemas:**
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS atlas_city_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
population INTEGER NOT NULL,
|
||||
economic_role TEXT NOT NULL, -- matches EconomicRole enum
|
||||
corp_id INTEGER REFERENCES corporations(id), -- nullable; corp HQ
|
||||
tier_hint INTEGER, -- nullable; expected WorldTier
|
||||
founding_orientation_override TEXT, -- nullable; overrides derivation lookup
|
||||
UNIQUE(body_id, name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS atlas_feature_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
feature_class TEXT NOT NULL, -- 'river' | 'mountain_range' | 'ocean' | 'sea' | 'bay'
|
||||
rank_hint INTEGER, -- 1 = primary/largest, 2 = secondary; NULL = seed-assigned
|
||||
UNIQUE(body_id, name)
|
||||
);
|
||||
```
|
||||
|
||||
**Population path:** `import_economics.py` reads wiki content (Miri's authored settlement and geographic feature lists in wiki articles) and populates both tables at build time. Bodies without wiki coverage produce 0 name reservations; the generator places unnamed settlements for all attractors and assigns generated names from the cultural pool.
|
||||
|
||||
**Four-stage fulfillment pipeline:**
|
||||
|
||||
```
|
||||
Stage 0 (build-time): Populate atlas_city_names + atlas_feature_names from wiki.
|
||||
Mark HQ-constrained names (corp_id IS NOT NULL) for priority handling.
|
||||
|
||||
Stage 1 (Layer 1 runtime — during feature tag extraction):
|
||||
For each entry in atlas_feature_names for this body:
|
||||
Match to the generated feature of that class with matching rank_hint.
|
||||
rank_hint = 1 → largest/longest feature of that class (by flow volume for rivers,
|
||||
peak elevation for mountain ranges).
|
||||
rank_hint = NULL → seed-derived rank assignment.
|
||||
Store: (body_id, feature_name, generated_feature_id) in BodyWorldState.
|
||||
|
||||
Stage 2 (Layer 2 runtime — during attractor matching):
|
||||
Sort atlas_city_names entries:
|
||||
Priority 1: entries with corp_id IS NOT NULL (HQ-constrained; Tier A assignment)
|
||||
Priority 2: remaining entries, sorted by population DESC
|
||||
For each entry in sorted order:
|
||||
Compute compatibility score against available attractors
|
||||
Assign to highest-scoring attractor
|
||||
Classify: MATCH (≥0.35) | WARNING (0.15–0.35) | ERROR (<0.15)
|
||||
Elevate WARNING → ERROR for corp_id IS NOT NULL entries with active trade routes
|
||||
Write mismatch entries to BodyWorldState.generation_log
|
||||
|
||||
Stage 3 (Layer 2 runtime — overflow and unnamed):
|
||||
Remaining population quota settlements: assign generated names from cultural pool.
|
||||
Cultural pool selection: cultural_hint from nearest fulfilled named reservation,
|
||||
or body's primary biome class as fallback.
|
||||
```
|
||||
|
||||
**Mismatch log format:**
|
||||
|
||||
```rust
|
||||
pub struct GenerationLogEntry {
|
||||
pub level: LogLevel, // Warning | Error
|
||||
pub body_id: i64,
|
||||
pub reservation_name: String,
|
||||
pub placed_at: String, // attractor_type or "Synthetic"
|
||||
pub quality_score: f32,
|
||||
pub corp_id: Option<i64>,
|
||||
pub available_compatible_count: u32,
|
||||
pub recommendation: String,
|
||||
}
|
||||
```
|
||||
|
||||
**Relationship to atlas_cities:** `atlas_cities` currently stores authored positions. After Amendment 3, positions are generated at runtime; the table is repurposed for static geographic reference data (atlas pixel coordinates for other authoring purposes) or deprecated. `atlas_city_names` is the name reservation system, not a position store.
|
||||
|
||||
---
|
||||
|
||||
### D-C-AM: Attractor-Matching Narrative Constraints
|
||||
|
||||
**Decision statement:** The attractor-matching algorithm applies narrative plausibility constraints in two tiers: physical impossibility checks (hard zeros before scoring) and narrative penalty/bonus scoring adjustments. These constraints layer on top of Burnelli-Sheldon's objective function without replacing it.
|
||||
|
||||
**Rationale:** The scoring function maximizes aggregate geographic plausibility. Without narrative constraints, the algorithm could maximize scores while still producing individual placements that are physically or narratively impossible (a fishing port in mountains; a research hub at a corporate extraction site). Narrative constraints prevent these edge cases from occurring silently.
|
||||
|
||||
**Physical impossibility check (`is_physically_possible`):**
|
||||
|
||||
Run before score matrix population. Returns `false` → score = 0.0 (settlement cannot be placed at this attractor under any circumstances):
|
||||
|
||||
```rust
|
||||
fn is_physically_possible(role: EconomicRole, attractor: GeographicFeatureTag,
|
||||
body: &BodyData) -> bool {
|
||||
match (role, attractor) {
|
||||
// Agricultural settlement on a mountain pass is physically impossible
|
||||
(Agricultural, MountainPass) => false,
|
||||
// Extraction settlement on arable plain without subsurface resources is impossible
|
||||
(Extraction | Mining, ArablePlain) if !body.has_subsurface_resources => false,
|
||||
// Port city on inland terrain is impossible
|
||||
// (CoastalHarbor and RiverConfluence already imply coast/river; no inland attractor
|
||||
// of these types can exist — this check is a safety net)
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Hard constraint rules (H1–H4, applied as score → 0.0 overrides):**
|
||||
|
||||
```
|
||||
H1 (Maritime incompatibility): Corp with primary_operation = maritime_logistics or
|
||||
fishing cannot be assigned to MountainPass or Defensible attractors.
|
||||
|
||||
H2 (Resource-type mismatch): Corp HQ with economic_role = extraction assigned to
|
||||
a ResourceConcentration attractor where the resource type does not match the
|
||||
corp's commodity_type → score = 0.0. (A lithium mining corp cannot be placed
|
||||
at an oil extraction concentration.)
|
||||
|
||||
H3 (Research isolation): Corp HQ with economic_role = research cannot be assigned
|
||||
to CoastalHarbor unless body's planet_class includes aquatic/oceanic research
|
||||
biome tag.
|
||||
|
||||
H4 (Administrative terrain): Settlement with economic_role ∈ {service_mixed} at
|
||||
economic_tier ≥ 3 (high administrative density) cannot be assigned to MountainPass
|
||||
as its sole compatible attractor. Administrative power requires accessible terrain.
|
||||
```
|
||||
|
||||
**Soft constraint scoring adjustments (additive to Burnelli-Sheldon's matrix):**
|
||||
|
||||
| economic_role | Attractor | Narrative adjustment |
|
||||
|---------------|-----------|---------------------|
|
||||
| agricultural | MountainPass, ResourceConcentration | −0.40 |
|
||||
| agricultural | ArablePlain | +0.40 |
|
||||
| commercial | ResourceConcentration, Defensible | −0.25 |
|
||||
| commercial | RiverConfluence, CoastalHarbor | +0.35 |
|
||||
| manufacturing | MountainPass, Defensible | −0.20 |
|
||||
| manufacturing | CoastalHarbor, ArablePlain | +0.20 |
|
||||
| extraction, mining | ArablePlain (no subsurface) | −0.45 |
|
||||
| extraction, mining | ResourceConcentration | +0.50 |
|
||||
| transit | ArablePlain, Defensible | −0.15 |
|
||||
| transit | RiverConfluence, CoastalHarbor | +0.35 |
|
||||
| frontier | RiverConfluence, CoastalHarbor | −0.10 |
|
||||
| frontier | MountainPass, ResourceConcentration | +0.25 |
|
||||
| research | RiverConfluence (primary) | −0.15 |
|
||||
| research | Defensible, ArablePlain | +0.15 |
|
||||
| energy | ArablePlain (no resource tag) | −0.30 |
|
||||
| energy | ResourceConcentration | +0.30 |
|
||||
|
||||
**Mismatch classification (post-assignment):**
|
||||
```
|
||||
quality_score ≥ 0.35: MATCH (no log entry)
|
||||
quality_score 0.15–0.35: WARNING (ATTRACTOR_WARNING log entry)
|
||||
quality_score < 0.15: ERROR (ATTRACTOR_ERROR log entry)
|
||||
Corp HQ mismatches: elevate one tier (WARNING→ERROR if active trade routes present)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### D-C-NL: Naming Registers — Lookup Table
|
||||
|
||||
**Decision statement:** District and settlement names are selected from named registers via a lookup table: `(DistrictType, WorldTier) → register`. Name pool population (the actual strings) is deferred content work; the register selection architecture is locked.
|
||||
|
||||
**Rationale:** The naming architecture must be locked before Phase 3 map content can be generated, because district names and street names require pools to draw from. The pools are authored content; the lookup table is an architectural decision. These can proceed in parallel once the lookup table is locked.
|
||||
|
||||
**Register definitions:**
|
||||
|
||||
```
|
||||
Geographic: Names derived from terrain features (river names, hill names, bay names)
|
||||
Used for: pre-settlement features; settlements at geographic junctions
|
||||
Functional: Names derived from what the place does (The Exchange, Processing Ward)
|
||||
Used for: industrial districts; extraction sites; transit nodes
|
||||
Commemorative: Names derived from persons or events (founders, battles, corporations)
|
||||
Used for: administrative districts; capital cities; corp-named towns
|
||||
Aspirational: Names derived from ideals or future-orientation (New [X], Unity [X])
|
||||
Used for: planned new districts; FreePort commercial nodes; frontier settlements
|
||||
Folk: Names derived from local practice or informal usage (Old Town, The Flats)
|
||||
Used for: organic districts; old residential areas; sub-settlements
|
||||
Corporate: Names derived from the controlling corporation (Corp-ward, Company Row)
|
||||
Used for: CompanyTown districts; corp extraction sites
|
||||
Dual: Two names (official + common), one from each of two competing registers
|
||||
Used for: ContestZone districts; politically disputed settlements
|
||||
```
|
||||
|
||||
**Register lookup table by (DistrictType, WorldTier):**
|
||||
|
||||
| DistrictType | Epicenter/Regional | Backwater | Passage | Waypoint |
|
||||
|-------------|-------------------|-----------|---------|---------|
|
||||
| Residential | Folk / Commemorative | Folk | Folk | Folk |
|
||||
| Commercial | Functional / Aspirational | Functional | Functional | Functional |
|
||||
| Industrial | Functional / Corporate | Functional | Functional | Functional |
|
||||
| Administrative | Commemorative | Commemorative | Commemorative | Functional |
|
||||
| Entertainment | Aspirational / Folk | Folk | Folk | Folk |
|
||||
| Mixed | Folk | Folk | Folk | Folk |
|
||||
| Logistics | Functional | Functional | Functional | Functional |
|
||||
| Transit | Functional / Geographic | Functional | Functional | Functional |
|
||||
| Civic | Commemorative / Aspirational | Commemorative | Aspirational | — |
|
||||
|
||||
**Political archetype modifier (overrides default register):**
|
||||
|
||||
```
|
||||
CompanyTown: All districts → Functional or Corporate (company names its infrastructure)
|
||||
Exception: Residential districts → Folk (workers name their own neighborhoods)
|
||||
AdminCapital: Administrative district → Commemorative (always)
|
||||
Other districts → preserve WorldTier defaults
|
||||
FreePort: Commercial districts → Aspirational (commerce names for what it offers)
|
||||
Entertainment districts → Aspirational or Folk
|
||||
Administrative districts → Functional (admin is a utility, not a monument)
|
||||
Contested: All districts in contested boundary zone → Dual register
|
||||
Each faction's half → its own archetype's register
|
||||
OrganicGrowth: All districts → Folk (no authority named anything)
|
||||
```
|
||||
|
||||
**Naming layers by cascade level:**
|
||||
|
||||
```
|
||||
Layer 1 (Empty World):
|
||||
Geographic register names only.
|
||||
Major terrain features (river systems, mountain ranges) large enough for atlas labeling
|
||||
receive pre-political geographic register names as placeholders.
|
||||
Source: atlas_feature_names table.
|
||||
|
||||
Layer 2 (Population Overlay):
|
||||
First human-assigned names at settlement creation.
|
||||
Register derived from founding trigger:
|
||||
RiverConfluence/CoastalHarbor → Geographic (the confluence was named before the city)
|
||||
ResourceConcentration + corp_presence → Corporate or Functional (resource names the place)
|
||||
Defensible/MountainPass + AdminCapital archetype → Commemorative
|
||||
ArablePlain agricultural cluster → Geographic or Folk
|
||||
Synthetic(PoliticalDecision) → Commemorative
|
||||
Geographic feature placeholder names from Layer 1 may persist as the "common name"
|
||||
while Layer 2 administrative name becomes official → DualNaming origin.
|
||||
|
||||
Layer 3 (City-Level Planning):
|
||||
District names from lookup table above.
|
||||
Political archetype modifier applied.
|
||||
|
||||
Layer 4 (Street-Level Rendering):
|
||||
Working street names (pragmatic; what inhabitants call the street).
|
||||
These may differ from official planning names in:
|
||||
- ContestZone cities (each faction has different official names)
|
||||
- Old cities (street was renamed by new authority; old name persists in use)
|
||||
- OrganicGrowth cities (streets often have no official names; working names only)
|
||||
```
|
||||
|
||||
**Name pool population:** This decision locks the register selection architecture. The actual string pools for each register are content work (Mellanie's domain). The generator needs only the pool selection rule (which register) and pool reference (which cultural pool for that register). Pool availability is a content dependency, not an architecture dependency.
|
||||
|
||||
---
|
||||
|
||||
## Narrative Consistency Review
|
||||
|
||||
### Burnelli-Sheldon's integer multiplier table
|
||||
|
||||
**Minor flag — Agricultural Commercial weight:**
|
||||
Agricultural economic_role has Commercial weight = 18. This seems high. A large agricultural city's commercial infrastructure is primarily for farming supply and produce trade — it's functional commerce, not a dense urban commercial district. 12-14 would feel more plausible without eliminating Commercial presence. Not a blocking concern; the three-component model's guaranteed minimum (Town tier includes Commercial) handles small settlements, and the 18 weight at City scale won't produce implausible results.
|
||||
|
||||
**Flag — Contested archetype modifier missing:**
|
||||
The political archetype weight modifiers table includes CompanyTown, AdminCapital, and FreePort. Contested is absent. What modifiers apply to Contested political_archetype? My Round 2 document specifies the Contested spatial pattern has suppressed prosperity at the contested boundary and mixed district types there — but the district type WEIGHT for Contested cities overall is undefined. Proposed Contested modifiers: Mixed +15, Administrative −10 (each faction's administration is partial; Mixed is the compromise). Burnelli-Sheldon should confirm or amend.
|
||||
|
||||
**Minor flag — Political archetype modifier floor interaction:**
|
||||
FreePort modifier on Administrative: −20. For Transit/Port economic_role, Administrative base weight = 7. After −20 modifier: 7 − 20 = −13 (below zero). The minimum floor of 3 must be applied after all modifiers, not before. Confirm implementation applies floor after all modifiers rather than clamping each modifier independently.
|
||||
|
||||
### Tyre's atlas_city_names schema (ARCH-3)
|
||||
|
||||
Tyre's locked schema:
|
||||
```sql
|
||||
CREATE TABLE atlas_city_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
corp_id INTEGER REFERENCES corporations(id),
|
||||
tier_hint INTEGER,
|
||||
reserved BOOLEAN NOT NULL DEFAULT 0
|
||||
);
|
||||
```
|
||||
|
||||
**Flag — Missing `population` and `economic_role` columns:**
|
||||
My D-C-NR requires `population` and `economic_role` in `atlas_city_names` for:
|
||||
- Population: needed to sort named cities for attractor assignment priority (largest first within Tier B/C)
|
||||
- economic_role: needed for attractor compatibility scoring (hard constraints H1-H4 + soft constraint table)
|
||||
|
||||
Without these columns, the name fulfillment pipeline cannot determine assignment priority or check compatibility. Proposed resolution: add both columns to Tyre's ARCH-3 schema. These values are available from wiki content at import_economics.py build time.
|
||||
|
||||
**Flag — `reserved` column purpose unclear:**
|
||||
Tyre's schema includes `reserved BOOLEAN NOT NULL DEFAULT 0`. The purpose is not specified in Round 2 notes. If this means "HQ-constrained reservation" (equivalent to `corp_id IS NOT NULL`), it is redundant with the `corp_id` column. If it means something else, clarify.
|
||||
|
||||
### Gestalt's DistrictSkeleton layout_mode
|
||||
|
||||
Gestalt's `layout_mode` is currently a fixed archetype value. Ozzie's proposal (founding_age → layout_mode) would add `layout_irregularity: f32` as an additional field. This does not conflict with the existing `layout_mode` — `layout_irregularity` is a modifier on the layout, not a replacement for `layout_mode`. Proposed resolution: `layout_mode` stays as is (fixed archetype); `layout_irregularity: f32` is added as a separate field. These are orthogonal: layout_mode says WHAT pattern; layout_irregularity says HOW regular. I'm adopting this in the D-C-SA entry (founding_age rules per archetype).
|
||||
|
||||
### SettlementClass vs. AttractorAssignment terminology
|
||||
|
||||
Burnelli-Sheldon's `SettlementClass::OrganicGrowth` and my `PoliticalArchetype::OrganicGrowth` and my `SyntheticPlacementReason::PopulationOverflow` all describe the same condition from different angles. These need consistent naming. Proposed alignment:
|
||||
|
||||
```
|
||||
SettlementClass::OrganicGrowth corresponds to:
|
||||
AttractorAssignment::Synthetic(PopulationOverflow) [mechanism]
|
||||
+ PoliticalArchetype::OrganicGrowth [political classification]
|
||||
+ FoundingOrientation::AdminFacing [spatial consequence]
|
||||
```
|
||||
|
||||
All three are consistent descriptions. The `OrganicGrowth` name should appear at the `SettlementClass` level and the `PoliticalArchetype` level; `PopulationOverflow` is the mechanism that produces it.
|
||||
|
||||
---
|
||||
|
||||
## Open Items After Round 3
|
||||
|
||||
### For lead resolution
|
||||
|
||||
**Contested archetype weight modifier:** Not specified by Burnelli-Sheldon. Proposed: Mixed +15, Administrative −10. Awaiting confirmation.
|
||||
|
||||
**L3-Q7 (port/station direction) — final disposition:** Mechanism is designed (one-query, one-lookup, one-edge-assignment). Ozzie wants implementation. Gestalt and Tyre both classified it as deferred. The mechanism is simple enough that it can be added to Layer 3 without significant additional complexity. Lead should decide: implement in Phase 1 or defer to Phase 2.
|
||||
|
||||
**atlas_city_names schema:** Tyre's ARCH-3 schema needs `population` and `economic_role` columns (my flag above). If locked as-is, the name fulfillment pipeline cannot operate correctly.
|
||||
|
||||
### For implementation tickets
|
||||
|
||||
**WorldTier enum bug:** `Peripheral | Connected | Core` → `Epicenter | Regional | Backwater | Passage | Waypoint`. First ticket, 0.5 dev-days. Blocking all downstream Layer 3-4 work.
|
||||
|
||||
**atlas_city_names + atlas_feature_names creation:** New tables in systems.db, populated by `import_economics.py` from wiki content. Blocked on wiki content having settlement lists (Miri's domain).
|
||||
|
||||
**layout_irregularity field on DistrictSkeleton:** Add `layout_irregularity: f32` derived from founding_age interaction rules. Layer 3 architecture change.
|
||||
|
||||
---
|
||||
|
||||
*Paula — Round 3. Written 2026-05-01.*
|
||||
@@ -0,0 +1,582 @@
|
||||
---
|
||||
title: "Round 1 Notes — Planet-Down Cascade Workshop"
|
||||
description: "Compiled record of Round 1 outputs: per-agent positions, agreements, disagreements, prerequisite dependency map, and open items carried into Round 2."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: qatux
|
||||
round: 1
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 1 Notes
|
||||
|
||||
**Compiled by:** Qatux
|
||||
**Round:** 1 — Inventory and Framing
|
||||
**Participants:** Gestalt, Tyre, Paula, Burnelli-Sheldon, Ozzie
|
||||
**Sources:** gestalt-round1.md, tyre-round1.md, paula-round1.md, burnelli-sheldon-round1.md, ozzie-round1.md
|
||||
|
||||
---
|
||||
|
||||
## 1. Amendment Absorption — How Each Agent Addressed the Consultant Review
|
||||
|
||||
All five agents acknowledged the consultant review amendments before their layer-by-layer inventories. Coverage was consistent.
|
||||
|
||||
### Amendment 1 (Three-Tier Execution Model)
|
||||
|
||||
**Gestalt** tabulated all six amendments and noted that Layer 1-2 algorithms must now be Rust-implementable on background threads; `generate_regional.py` becomes reference and validation only.
|
||||
|
||||
**Tyre** dedicated a "Priority 0: The Architectural Pivot" section to documenting exactly what changes from his Round 3 design. He listed: Layer 1-2 now runs as Rust runtime on background threads; settlement positions, road graph, and territorial status are derived at runtime per body and cached in session memory; `generate_atlas.py` + `planet_simulation.py` Python infrastructure stays for heightmap generation and build-time data, but spatial understanding moves to Rust.
|
||||
|
||||
**Paula** noted Amendment 1 is architecturally consistent with her Round 4 argument. The runtime background model means the attractor-matching algorithm runs at Layer 1-2 background time, not build time, with systems.db available for corp cross-reference resolution.
|
||||
|
||||
**Burnelli-Sheldon** stated Amendment 1 is a "clean win for economics" — the economic simulation runs on system-level aggregates already in systems.db. Layer 1-2 spatial data is consumed by Layer 3-4 but does not feed back into the sim.
|
||||
|
||||
**Ozzie** acknowledged the execution model change in the context of Amendment 2's priority queue; did not detail the technical split.
|
||||
|
||||
**No agent incorrectly used the old two-tier Phase 3/Phase 5 framing in any design proposal.** Tyre was the only agent who had designed against the old model and documented the correction explicitly.
|
||||
|
||||
### Amendment 3 (Fully Generative Placement)
|
||||
|
||||
All five agents acknowledged that L1-Q1, L2-Q1, and CL-Q4 are eliminated. All five noted the two new questions: attractor-matching algorithm and name reservation fulfillment.
|
||||
|
||||
**Paula** described Amendment 3 as "narrative-positive" — fully generative placement architecturally guarantees the causal chain she argued for in Round 4.
|
||||
|
||||
**Ozzie** flagged that fully generative placement makes the drainage algorithm the only source of truth for rivers; if it produces anomalies, trust breaks.
|
||||
|
||||
### Amendment 5 (Determinism Rationale Corrected)
|
||||
|
||||
**Paula** explicitly revised her Round 4 framing: the two-field model is a "rendering legibility requirement," not a "ghost city architecture." Ghost city effects are emergent consequences of the prosperity system working correctly on failed settlements.
|
||||
|
||||
**Gestalt** listed Amendment 5 and noted that CL-Q3 (ruin lifecycle) is reduced to an emergent rendering consequence; `prosperity_delta` is always derived, never stored.
|
||||
|
||||
**Ozzie** accepted the reframe but added a player experience requirement: the visual difference between an active and ghost settlement must be "immediately readable" — the player should not need a prosperity readout.
|
||||
|
||||
### Amendment 6 (Weight Table Unlocked)
|
||||
|
||||
Addressed in full in Section 4 of these notes.
|
||||
|
||||
---
|
||||
|
||||
## 2. Questions Eliminated Before Round 1
|
||||
|
||||
The following questions were eliminated by the amendments. No agent revisited them as open questions:
|
||||
|
||||
| Question | Eliminated by | Resolution |
|
||||
|----------|--------------|------------|
|
||||
| L1-Q1 (authored rivers vs. drainage network) | Amendment 3 | Rivers fully generated from drainage simulation |
|
||||
| L2-Q1 (city positions anchored vs. re-derived) | Amendment 3 | All city positions are generator-derived |
|
||||
| CL-Q4 (authored rivers, elevated) | Amendment 3 | Same resolution as L1-Q1 |
|
||||
|
||||
---
|
||||
|
||||
## 3. New Questions Added by Amendment 3
|
||||
|
||||
| Question | Who introduced | Priority |
|
||||
|----------|---------------|---------|
|
||||
| **Attractor-matching algorithm**: How does the generator match N named cities with known economic roles to M geographic attractors? | All five agents acknowledged it; Gestalt, Tyre, Burnelli-Sheldon, Paula each offered algorithm sketches | PREREQUISITE — blocks all Layer 2 settlement placement |
|
||||
| **Name reservation fulfillment**: What data structure represents name reservations from systems.db, and when does the generator fulfill them? | Gestalt, Tyre, Paula | PREREQUISITE — corporate cross-references must resolve |
|
||||
| **NEW-Q2 (OrganicGrowth disambiguation)**: How does the data model distinguish OrganicGrowth settlements from "no settlement placed"? Both conditions produce zero geographic attractors. | Paula | PREREQUISITE — blocks FoundingOrientation derivation |
|
||||
|
||||
---
|
||||
|
||||
## 4. Amendment 6 — Weight Table Challenge
|
||||
|
||||
### Consensus finding
|
||||
|
||||
**All five agents** supported the population-plus-age baseline approach over retaining the existing 10×9 table. No agent defended the existing table.
|
||||
|
||||
Gestalt, Tyre, and Burnelli-Sheldon each stated the core theorem independently: concentrated labor produces service demand regardless of what that labor produces. Miners drink.
|
||||
|
||||
Ozzie stated this as the "player experience imperative" for the entire workshop.
|
||||
|
||||
### Three independent proposals
|
||||
|
||||
The three agents who proposed concrete alternative frameworks each approached the structure differently. These are not reconciled proposals — they are distinct inventories for Round 2:
|
||||
|
||||
**Gestalt — Two-Layer Model:**
|
||||
- Layer 1: Population-tier district guarantees (minimum viable district set by settlement size: outpost / town / small city / full city tiers)
|
||||
- Layer 2: Economic role as character modifier (the character of each guaranteed district type varies by economic role, expressed through `density_pct`, `prosperity_baseline`, `perimeter_treatment`)
|
||||
- Recommends: replace the 10×9 table entirely; repurpose it as a character modifier table rather than a presence/absence table
|
||||
|
||||
**Tyre — Population+Age Baseline with Formula:**
|
||||
Specific baseline fraction formulas:
|
||||
- `baseline_residential_frac = 0.35`
|
||||
- `baseline_commercial_frac = clamp(0.10 + log10(population/1000) × 0.05, 0.10, 0.25)`
|
||||
- `baseline_entertainment_frac`: 0.0 below 10,000; 0.05 at 10k-100k; 0.10 above 100k
|
||||
- `baseline_administrative_frac`: 0.0 (Waypoint), 0.05 (Passage), 0.10 (Backwater+)
|
||||
- Remaining fraction distributed by economic role weight table (retained as "secondary allocation modifier," values renormalized)
|
||||
- Notes: weight table renormalization is "arithmetic work, not design work"
|
||||
|
||||
**Burnelli-Sheldon — Three-Component Model:**
|
||||
- Component 1: Population tier guarantees (explicit table by settlement tier: Outpost guarantees Residential+Mixed; Town adds Commercial; City adds Entertainment; Large City adds Administrative)
|
||||
- Component 2: Economic role as multiplier (not on/off). Replaces the 10×9 table with a multiplier table where no value is ever zero (minimum 0.2, maximum 3.0). Proposes a full 10×9 replacement table with explicit values for all cells
|
||||
- Component 3: Settlement age as character modifier (`founding_age_years` modifies district character and quality, not district count or type; capped by WorldTier ceiling)
|
||||
- Mining row: Ent = 0.5 in the new table. Energy row: Ent = 0.2.
|
||||
|
||||
### Points of agreement across all three proposals
|
||||
|
||||
- No settlement type should have zero of any essential district type
|
||||
- Economic role modifies the proportion and character of districts, not their presence or absence
|
||||
- Population drives the guaranteed baseline
|
||||
- Settlement age affects character, not structure
|
||||
|
||||
### Open sub-questions raised
|
||||
|
||||
Burnelli-Sheldon raised three sub-questions for Round 2:
|
||||
- **BS-Q1:** Is the 9,999-person Town without a dedicated Entertainment district plausible? (Flagged for Gestalt's "does this feel inhabited" check)
|
||||
- **BS-Q2:** How does `founding_age_years` interact with WorldTier ceiling? Proposed: age modifier only applies at Town tier and above
|
||||
- **BS-Q3:** Energy row Entertainment weight of 0.2 may produce zero dedicated Entertainment districts at some district counts; guarantee floor is doing the work, not the weight
|
||||
|
||||
Paula added: topographic modifier to `prosperity_baseline` (+0.05 for hilltop districts, -0.05 for flood-adjacent districts). This is distinct from — and additive to — Burnelli-Sheldon's gradient direction formula.
|
||||
|
||||
---
|
||||
|
||||
## 5. Attractor-Matching Algorithm — Four Positions
|
||||
|
||||
Four agents proposed algorithm frameworks. These are not identical; Round 2 must resolve them.
|
||||
|
||||
### Gestalt — Scored Greedy Assignment
|
||||
|
||||
```
|
||||
1. Score all (city, attractor) pairs: base score = attractor quality score,
|
||||
weighted by population / attractor capacity; role bonus by (economic_role, attractor_type) match
|
||||
2. Hard constraint pass: lock corp-required assignments first (score → 0.0 if incompatible)
|
||||
3. Greedy assignment: highest-scored pairs assigned first, consuming attractor capacity
|
||||
4. Overflow: less-ideal placements for cities that couldn't get preferred attractor type
|
||||
```
|
||||
Framing: "constraint satisfaction" — hard constraints first, soft constraints as scoring.
|
||||
|
||||
### Tyre — Greedy Bipartite, Hungarian Available
|
||||
|
||||
```
|
||||
1. Sort named_cities by population descending
|
||||
2. Score each (city, attractor) pair: base_score = attractor.quality_score + role_bonus + hq_constraint
|
||||
(hq_constraint: score → 0.0 if attractor_type incompatible with corp's primary_operation)
|
||||
3. Greedy assignment: for each city in sorted order, assign to highest-scored available attractor
|
||||
4. Overflow: place near existing settlements (proximity placement within [15-40%] of body scale)
|
||||
5. Derive FoundingOrientation from assigned attractor_type
|
||||
```
|
||||
Notes: O(N×M) where N ≤ 30, M = 50-100. "Negligible compute." Tyre recommends Hungarian algorithm for correctness (O(N³), trivially fast at N ≤ 30) with greedy as first implementation.
|
||||
|
||||
New schema required: `atlas_city_names` table (name + economic_role + population + hq_for_corp). Replaces authored position storage in `atlas_cities`.
|
||||
|
||||
### Burnelli-Sheldon — Comparative Advantage Matching
|
||||
|
||||
```
|
||||
1. Score matrix S[city_i, attractor_j] = compatibility(city.economic_role, attractor.feature_type)
|
||||
(provides full compatibility matrix: agricultural/very-high at ArablePlain, extraction/very-high at ResourceConcentration, etc.)
|
||||
2. Hard constraints: HQ-locked cities must use compatible attractor type
|
||||
3. Solve: maximize sum of compatibility scores, one-city-per-attractor
|
||||
(Hungarian algorithm or greedy by largest gap if N << M)
|
||||
4. Overflow: generate synthetic attractors as secondary sites (river bends, coastal plains) by seed
|
||||
```
|
||||
Framing: "comparative advantage" — each city has an economic role implying preferred attractor; matching maximizes aggregate plausibility.
|
||||
|
||||
Raised: what does it mean for a body to have more cities than geographic attractors? Proposed: high-tier economic worlds override geography (manufacturing, service, commercial roles on high-`economic_tier` bodies). Synthetic attractor placement at seed-derived offsets along existing road corridors.
|
||||
|
||||
### Paula — Priority-Ordered Assignment with Named Cities
|
||||
|
||||
```
|
||||
1. Fulfill name reservations for HQ cities in economic role order:
|
||||
extraction corps first (most geographically constrained), service/commercial corps last
|
||||
2. Assign remaining named cities by population (largest first)
|
||||
3. Place unnamed cities at remaining attractors by best-fit economic profile
|
||||
4. OrganicGrowth settlements (remaining population quota after attractors consumed):
|
||||
placed at politically significant points (Province centroids, midpoints between large cities)
|
||||
```
|
||||
Adds: mismatch flagging (corp HQ placed at geographically inappropriate attractor → flag for lead review, not silent placement). Distinguishes `geographically_triggered: bool` flag on settlement records. OrganicGrowth disambiguation: `geographically_triggered = false`, FoundingOrientation = `AdminFacing`.
|
||||
|
||||
### Ozzie — Player Experience Constraint
|
||||
|
||||
Ozzie did not propose an algorithm but stated a player experience requirement: **no placement should violate geographic common sense.** "A fishing port in the mountains cracks the world." Framing: each city placement should feel "inevitable, not arbitrary."
|
||||
|
||||
### Summary of differences
|
||||
|
||||
| Point | Gestalt | Tyre | Burnelli-Sheldon | Paula |
|
||||
|-------|---------|------|-----------------|-------|
|
||||
| Assignment method | Greedy by score | Greedy by population-sorted order; recommends Hungarian | Hungarian (maximize compatibility sum) | Priority-ordered by corp constraint → population → unnamed → OrganicGrowth |
|
||||
| Overflow handling | Less-ideal placements | Proximity near existing settlements | Synthetic attractors from seed | OrganicGrowth at Province centroids or political midpoints |
|
||||
| HQ hard constraint | Score → 0.0 if incompatible | Score → 0.0 if incompatible | Must use compatible attractor | Fill first, by economic role constraint order; flag mismatches |
|
||||
| FoundingOrientation | Implicit (derived from attractor type) | Explicit derivation step after assignment | Not addressed | Flagged as trivial lookup; OrganicGrowth → AdminFacing |
|
||||
|
||||
---
|
||||
|
||||
## 6. Spatial Hierarchy — Tyre's Dimension Proposal
|
||||
|
||||
Tyre is the only agent who proposed concrete dimensions for the full eight-tier hierarchy. No other agent disputed these numbers in Round 1; the workshop must confirm or amend in Round 2.
|
||||
|
||||
### Proposed dimension table
|
||||
|
||||
| Tier | Name | Base unit | Physical scale (reference body) | Notes |
|
||||
|------|------|-----------|---------------------------------|-------|
|
||||
| 0 | Chunk | 64×64 sim tiles | ~32m × 32m | Immutable — prior architecture depends on this |
|
||||
| 1 | Block | 128×128 sim tiles | ~64m × 64m | 2×2 chunks |
|
||||
| 2 | District | 512×512 sim tiles | ~256m × 256m | 4×4 blocks, 8×8 chunks |
|
||||
| 3 | Region | city district grid bounding box | varies per city size | Semantic tier, not a storage tier |
|
||||
| 4 | Province | 1 drainage basin on regional grid | ~60-200km | Variable — defined by watershed analysis |
|
||||
| 5 | Area | 1 contiguous terrain feature zone | ~400-1500km | Defined by terrain class connectivity |
|
||||
| 6 | Body | full surface | varies | Body size absorbed here |
|
||||
| 7 | System | star system | — | Gate graph |
|
||||
|
||||
Reference body calibration: temperate terrestrial, radius ~5,000km. Atlas heightmap pixel scale at equator: ~61km/pixel. Regional cell (8×8 pixels): ~500km × 250km.
|
||||
|
||||
### Body-size-to-area-count formula (Tyre)
|
||||
|
||||
```
|
||||
area_count = round(sqrt(body_surface_area_km2 / 6_000_000))
|
||||
```
|
||||
- Reference body (radius 5,000km, surface ~314M km²): area_count = 7
|
||||
- Small moon (radius 500km, surface ~3.14M km²): area_count = 1
|
||||
- Super-terrestrial (radius 10,000km, surface ~1.256B km²): area_count = 14
|
||||
|
||||
Schema dependency: `body_surface_area_km2` (or radius) must be in the `bodies` table. Tyre flagged this as possibly missing.
|
||||
|
||||
### Paula's vocabulary mapping
|
||||
|
||||
Paula mapped her Round 4 "regional cell" language to the new hierarchy:
|
||||
- "Regional cell (64×32 grid)" = **Province** (tier 4)
|
||||
- "City footprint" = **Region** (tier 3)
|
||||
- Sub-settlements at or below Region depending on population tier
|
||||
|
||||
Paula noted that Province dimensions must be confirmed before TerritorialStatus quantitative thresholds can be finalized.
|
||||
|
||||
---
|
||||
|
||||
## 7. Technical Blockers — Tyre's Four Prerequisites
|
||||
|
||||
Tyre identified four items that must be resolved before Layer 1-2 Rust code can be written:
|
||||
|
||||
| ID | Question | Stakes |
|
||||
|----|----------|--------|
|
||||
| **ARCH-1** | Heightmap storage format: float32 LE BLOB in new `atlas_body_heightmaps` table? Compression? | Blocks all Layer 1-2 Rust. Heightmap currently not in systems.db. Size: 512KB/body, ~200MB for 400 bodies. |
|
||||
| **ARCH-2** | Session state representation: `BodyWorldState` as Bevy Resource, LRU eviction policy? | Blocks Layer 2 → 3 handoff. Tyre proposes LRU at 50 bodies (~100MB cache target). |
|
||||
| **ARCH-3** | `atlas_city_names` schema: replaces or extends `atlas_cities`? | Blocks attractor-matching algorithm. `atlas_cities` currently stores authored positions; those are now runtime-generated. |
|
||||
| **ARCH-4** | Body physical size field in `bodies` table: present or must be added? | Blocks area-count formula. |
|
||||
|
||||
Tyre's `BodyWorldState` definition:
|
||||
```rust
|
||||
pub struct BodyWorldState {
|
||||
pub body_id: String,
|
||||
pub river_network: RiverNetwork,
|
||||
pub settlements: Vec<GeneratedSettlement>,
|
||||
pub road_graph: RoadGraph,
|
||||
pub territorial_grid: Vec<TerritorialCell>,
|
||||
}
|
||||
```
|
||||
Nothing in `BodyWorldState` is serialized. Session resume reruns generation from systems.db + world seed.
|
||||
|
||||
---
|
||||
|
||||
## 8. Layer-by-Layer Inventory Summary
|
||||
|
||||
### Layer 1 — Empty World
|
||||
|
||||
**What exists or can be reused (all agents):**
|
||||
- `planet_simulation.py`: drainage/river algorithm (Python reference; must port to Rust — Tyre)
|
||||
- 64×32 regional biome grid (`atlas_regional_biomes`): already in systems.db from build-time — Tyre
|
||||
- `is_coastal`, `water_fraction`, `terrain_roughness` columns: already on `atlas_regional_biomes` — Gestalt
|
||||
|
||||
**What is genuinely novel:**
|
||||
- D8 drainage routing in Rust (Tyre: ~50ms on 512×256 heightmap; Priority-Flood D8 algorithm)
|
||||
- Geographic feature tag extraction: the seven tags (RiverConfluence, CoastalHarbor, MountainPass, ArablePlain, ResourceConcentration, Defensible, NaturalBarrier) do not exist; derivation rules per tag are novel (Paula provides per-tag derivation criteria)
|
||||
- Sub-biome variant classification: seed-derived per cell, 3-4 variants per biome class (Gestalt, Tyre)
|
||||
- Mountain pass identification: high-roughness cell adjacent to lower cells on opposite sides; threshold not yet set (Gestalt)
|
||||
|
||||
**Paula's distinction for `ResourceConcentration`:** This tag is not purely terrain-derived. It requires `economic_role` from systems.db to set its placement threshold. This connects Layer 1 feature extraction to L1-Q4 (biome prior).
|
||||
|
||||
**Tyre's schema proposal for Layer 1:**
|
||||
- New table: `atlas_body_heightmaps` (body_id, width, height, elevation_f32_le BLOB, sea_level, seed)
|
||||
- Extended columns on `atlas_regional_biomes`: `sub_biome_variant`, `terrain_modification_cost`, `geographic_feature_tags` (JSON array)
|
||||
- Tyre proposes: `sub_biome_variant` and `terrain_modification_cost` computed at build time in `generate_atlas.py` (terrain dict is already in memory at that point), saving runtime derivation
|
||||
|
||||
**L1-Q4 (biome prior):** Three agents addressed this:
|
||||
- Tyre: **include it**; technical cost = trivial; self-contradiction risk is real
|
||||
- Paula: **include it**; narrative justification: `frontier` economic_role with tropical paradise biome is dissonant without a prior
|
||||
- Burnelli-Sheldon: **include it**; listed as economics primary question with "pure quality improvement" classification
|
||||
|
||||
No agent opposed inclusion. Gestalt classified it as "independent" (low-risk, can be deferred).
|
||||
|
||||
**Layer 1 performance (Tyre estimate):**
|
||||
- Load heightmap BLOB: ~5ms
|
||||
- D8 drainage routing: ~50ms
|
||||
- Extract river centerlines: ~20ms
|
||||
- Geographic attractor extraction: ~5ms
|
||||
- Sub-biome variant sampling: ~2ms
|
||||
- **Subtotal Layer 1: ~82ms**
|
||||
|
||||
### Layer 2 — Population Overlay
|
||||
|
||||
**What exists or can be reused:**
|
||||
- Sub-settlement placement triggers: designed in Round 3 (all agents)
|
||||
- Road graph algorithm (Phase 3 Layer C): carries forward; Amendment 3 means all roads are now generated (no authored road reconciliation needed)
|
||||
- TerritorialStatus derivation algorithm: qualitative logic established (Paula Round 4); thresholds not yet quantified
|
||||
|
||||
**What is genuinely novel:**
|
||||
- Attractor-matching algorithm (see Section 5)
|
||||
- Name reservation fulfillment data structure (see Section 5 / Tyre's `atlas_city_names` proposal)
|
||||
- OrganicGrowth disambiguation (Paula NEW-Q2)
|
||||
- TerritorialStatus quantitative thresholds (three proposals; see below)
|
||||
- Hinterland shape algorithm
|
||||
|
||||
**L2-Q3 (road algorithm under Amendment 3):** Amendment 3 eliminates authored road paths. Tyre's road algorithm generates all topology. Named highway routes receive identity via post-generation assignment. Paula notes road segment table in session DB needs an optional `named_route_id` foreign key to systems.db (schema question). No disagreement on this resolution.
|
||||
|
||||
**L2-Q4 (hinterland shapes):**
|
||||
- Paula: no Voronoi; weighted probability assignment per Province cell, clustered along river corridors and coastal lowlands, weighted by `terrain_modification_cost`
|
||||
- Burnelli-Sheldon: per-`economic_role` hinterland fill (agricultural: farmland + irrigation; extraction: access corridors; transit: dense road/rail). Did not address Voronoi question directly.
|
||||
- Gestalt: cluster around agricultural cities + river corridors at density ∝ 1/terrain_modification_cost (consistent with Paula)
|
||||
|
||||
**L2-Q5 (TerritorialStatus thresholds) — three proposals:**
|
||||
|
||||
| Status | Paula (settlement + road coverage) | Tyre (density formula) | Burnelli-Sheldon (economic proxies) |
|
||||
|--------|-------------------|-------------------|--------------------------|
|
||||
| CoreTerritory | ≥2 per Province; road coverage ≥0.6 | settlement_density >1 per 4 cells OR road within 2 cells of City/Town | settlement_density ≥1 per province + road maintenance on all connecting edges |
|
||||
| FrontierTerritory | ≥1 per Province; road 0.3-0.6 or quality <0.5 at edge | settlement + road within 5 cells, or within 3 cells of City/Town without road | settlement + road density <0.5 of CoreTerritory avg for this economic_tier |
|
||||
| ExtractiveZone | ≥1 at ResourceConcentration; extraction corridor ≥0.7, off-corridor <0.3; corp_presence >0 | ResourceConcentration attractor + Corporate corp + road marked Corporate | ResourceConcentration feature + corp_presence w/ matching commodity + road to resource |
|
||||
| ContestZone | ≥2 from competing road authority types; overlapping claim | two CoreTerritory zones from different political archetypes overlapping within 2 cells | two settlement clusters, distance <2×footprint_radius, different political_archetype or dominant_faction |
|
||||
| WildernessBuffer | 0 settlements, 0 roads | no settlement within 8 cells + no road within 3 cells | no settlement placed |
|
||||
| AbandonedZone | ≥1 at generation (runtime: 0 active), roads exist but maintained=false | settlement placed + no active corp_presence + economic basis ≤0 | settlement placed + corp_financial_state.health_metric <0.2 for all operating corps at founding time |
|
||||
|
||||
These are three different threshold specifications. Round 2 must reconcile them.
|
||||
|
||||
Paula added: AbandonedZone requires a `placed_at_generation: bool` marker on the Province to distinguish "never settled" from "was settled, now gone." This marker is set at Layer 2 generation time and is immutable.
|
||||
|
||||
**L2-Q6 (latent settlement flag storage):** All agents agreed: **session DB** (Bevy Resource / BodyWorldState), not systems.db. No agent supported a systems.db write path for runtime-variable settlement state.
|
||||
|
||||
**Layer 2 performance (Tyre):**
|
||||
- Attractor matching: ~1ms
|
||||
- A* road routing (MST): ~30ms
|
||||
- Sub-settlement placement: ~5ms
|
||||
- Territorial status propagation: ~15ms
|
||||
- **Subtotal Layer 2: ~51ms**
|
||||
- **Total Layer 1-2: ~133ms** (5× safety margin: ~650ms; still under 1 second)
|
||||
|
||||
### Layer 3 — City-Level Planning
|
||||
|
||||
**What carries forward without change (all agents):**
|
||||
- DistrictSkeleton Stages 1-2 (D-C3)
|
||||
- City decomposition formula (D-C4)
|
||||
- City-local coordinate system (D-C5)
|
||||
- SeedChain / FNV-1a (D-C6)
|
||||
- GeneratorChunkData upgrade (D-C7)
|
||||
- `prosperity_baseline` and `perimeter_treatment` on DistrictSkeleton
|
||||
|
||||
**CityGenerationContext struct update (Tyre):**
|
||||
Fields added: `body_id` (consultant open item), `city_name`, `founding_orientation` (Layer 2 output), `district_grid_width`, `district_count`. `WorldTier` corrected enum remains a blocker.
|
||||
|
||||
**WorldTier enum (Tyre):** Current code has `Peripheral | Connected | Core` (wrong). Required: `Epicenter | Regional | Backwater | Passage | Waypoint`. Tyre flagged this as still an unaddressed blocker; "first ticket, 0.5 dev-days."
|
||||
|
||||
**L3-Q1 (FoundingOrientation spatial effect):**
|
||||
|
||||
| Agent | Position |
|
||||
|-------|---------|
|
||||
| Gestalt | Spatial grid rotation. Directional legibility is a gameplay mechanic. |
|
||||
| Tyre | Spatial orientation change; `orientation_rotation: CardinalDirection` field added to district placement. |
|
||||
| Paula | Spatial rotation. "The geographic rationale is physically visible in the city's layout." |
|
||||
| Burnelli-Sheldon | Owns gradient formula; topo sets gradient direction, `distribution_index` sets magnitude. Did not explicitly address spatial rotation vs. gradient question. |
|
||||
| Ozzie | Backs explicit spatial arrangement; "PortFacing city should be legible from approach." |
|
||||
|
||||
**Consensus: spatial rotation, not gradient direction only.** Gestalt, Tyre, Paula, and Ozzie align. Burnelli-Sheldon's formula addresses gradient direction as an input to the formula; this is consistent with spatial rotation.
|
||||
|
||||
**L3-Q2 (political archetype as spatial arrangement):**
|
||||
|
||||
| Agent | Position |
|
||||
|-------|---------|
|
||||
| Gestalt | Explicit for primary three archetypes (CompanyTown, AdminCapital, FreePort). Emergent for secondary. |
|
||||
| Tyre | Explicit for three archetypes; 30-40 lines of Rust code. Same three archetypes as Gestalt. |
|
||||
| Paula | Explicit arrangement for all five archetypes: CompanyTown (spine), AdminCapital (radial), FreePort (multi-node), Contested (overlay), OrganicGrowth (irregular local density) |
|
||||
| Ozzie | Backs explicit; player should feel CompanyTown's spine "before you know it's a CompanyTown" |
|
||||
|
||||
**Difference:** Gestalt and Tyre limit explicit patterns to three primary archetypes; Paula proposes explicit patterns for all five. No agent backed emergent-only.
|
||||
|
||||
**L3-Q5 (prosperity_baseline formula):**
|
||||
- Burnelli-Sheldon's formula from Round 4 carries forward as base
|
||||
- Paula advocates for a topographic modifier: +0.05 for hilltop districts (top 30% elevation for city), -0.05 for flood-adjacent (bottom 20% relative to sea level)
|
||||
- Burnelli-Sheldon's position: topography sets gradient direction; `distribution_index` sets magnitude. The formula in the brief already specifies this.
|
||||
- Gestalt: listed L3-Q5 as independent (not prerequisite); position deferred
|
||||
- No blocking disagreement; Paula's topographic modifier is additive to Burnelli-Sheldon's formula
|
||||
|
||||
**L3-Q6 (sub-settlement codepath):** Paula and Gestalt both: unified codepath; town with `district_count = 1`. Tyre: no explicit position but did not propose a separate path. Burnelli-Sheldon: listed as "no economics objection" to unified path.
|
||||
|
||||
**L3-Q7 (port/station direction):**
|
||||
- Paula: defers from minimum viable; designs the mechanism (one-query, one-lookup, one-edge-assignment); worth designing even if deferred
|
||||
- Ozzie: wants this badly ("if I'm standing in the Transit district, I want to see the beanstalk")
|
||||
- All others: classified as deferred
|
||||
|
||||
### Layer 4 — Street-Level Rendering
|
||||
|
||||
**What carries forward without change:**
|
||||
- Chunk streaming architecture (`chunk_streaming.rs`)
|
||||
- TileEntry struct upgrade (D-C7)
|
||||
- Phase 2 tile algorithm (street skeleton first, building fill by density)
|
||||
- SeedChain for chunks
|
||||
|
||||
**L4-Q1 (economics-variable rendering mechanism):**
|
||||
|
||||
All five agents converged on option (c): threshold-crossing cache invalidation.
|
||||
- Compute-live-per-frame (option b): rejected by Tyre and Paula; too expensive; no fidelity gain
|
||||
- Bake-at-generation+event (option a): Tyre notes this is "similar to option c" but requires an event system; threshold crossings are strictly cleaner
|
||||
- **Consensus: cache per chunk, invalidate on threshold crossing**
|
||||
|
||||
Specific implementations differ:
|
||||
- Tyre: invalidation when `|prosperity_current - prosperity_snapshot| > 0.05`; `ChunkConditionState` struct with `prosperity_snapshot: f32` and `tile_conditions: Vec<TileCondition>`
|
||||
- Paula: threshold crossings at 0.63 / 0.43 / 0.23 (offset from round numbers to avoid oscillation at boundaries); sim emits "prosperity crossed threshold X" event; renderer invalidates on receipt
|
||||
- Gestalt: threshold levels at 0.6 / 0.4 / 0.2 (round numbers)
|
||||
|
||||
Slight difference: Paula proposes offset thresholds (0.63/0.43/0.23) to avoid oscillation; Gestalt proposes round numbers (0.6/0.4/0.2). Not a disagreement in principle.
|
||||
|
||||
**L4-Q4 (interior generation trigger):**
|
||||
- Gestalt: pre-fetch (option a) for target; on-entry (option b) for minimum viable
|
||||
- Tyre: pre-fetch (option a); N = 32 tiles (one chunk width)
|
||||
- Paula: pre-fetch; emphasizes this is a mechanism question, not a generation question
|
||||
- Ozzie: pre-fetch (option a); "generation stall at the door breaks immersion"
|
||||
|
||||
**Consensus: pre-fetch is target; on-entry acceptable for minimum viable.** All four agents who addressed this agree.
|
||||
|
||||
**L4-Q5 ("scatter civilization" scope) — flagged tension:**
|
||||
- Brief deferred prop/decal scatter and entity spawn points
|
||||
- Ozzie: "scatter is not optional." Street-level decals and prop spawn points are "the minimum viable lived-in feeling." An empty street grid with only floor/wall tiles "has no soul."
|
||||
- No other agent directly addressed this question in Round 1
|
||||
|
||||
This is the only point where Ozzie's position (flag deferred scope as insufficient for player experience) runs against the minimum viable slice as stated in the brief. The brief's deferral is architectural scope; Ozzie's concern is player experience quality. Round 2 should address.
|
||||
|
||||
**L4-Q6 (prosperity_delta):** All four agents who addressed it agree: two-field model (`prosperity_baseline` + `prosperity_current`); `prosperity_delta` always derived, never stored. Paula frames this as "rendering legibility requirement." Burnelli-Sheldon listed CL-Q1 as requiring only a naming lock.
|
||||
|
||||
---
|
||||
|
||||
## 9. Gestalt's Prerequisite Dependency Map
|
||||
|
||||
Gestalt produced the full dependency map for the workshop. Reproduced here for reference:
|
||||
|
||||
```
|
||||
BEFORE ANY LAYER:
|
||||
[A] Spatial hierarchy dimensions locked (Amendment 4)
|
||||
└─ body-size to area-count formula
|
||||
└─ Province / Region tier dimensions confirmed
|
||||
|
||||
BEFORE LAYER 1:
|
||||
[B] Spatial hierarchy confirmed against 64×32 grid
|
||||
└─ Does 64×32 correspond to Province grid? Or Area grid?
|
||||
|
||||
LAYER 1 (independent of Layer 2 except via geographic feature output):
|
||||
[C] L1-Q2: River resolution decision
|
||||
[D] L1-Q3: Session DB confirmation (Amendment 1 replaces systems.db)
|
||||
[E] Drainage algorithm performance evaluation
|
||||
|
||||
BEFORE LAYER 2:
|
||||
[F] Layer 1 geographic feature tags computed
|
||||
[G] Attractor-matching algorithm designed
|
||||
[H] Name reservation fulfillment data structure
|
||||
|
||||
LAYER 2:
|
||||
[I] L2-Q5: TerritorialStatus thresholds
|
||||
[J] L2-Q3: Road algorithm scope under Amendment 3
|
||||
|
||||
BEFORE LAYER 3:
|
||||
[K] Layer 2 political_archetype + FoundingOrientation per city
|
||||
[L] Weight table challenge resolved (Amendment 6)
|
||||
[M] L3-Q1: FoundingOrientation spatial grid orientation
|
||||
[N] L3-Q2: Political archetype spatial arrangement
|
||||
|
||||
LAYER 3:
|
||||
[O] L3-Q5: prosperity_baseline topography contribution
|
||||
[P] L3-Q6: Sub-settlement unified vs. simplified codepath
|
||||
[Q] CityGenerationContext body_id field added
|
||||
|
||||
BEFORE LAYER 4:
|
||||
[R] Layer 3 DistrictSkeleton Stages 1-2
|
||||
[S] L4-Q1: Economics rendering mechanism
|
||||
|
||||
LAYER 4:
|
||||
[T] L4-Q2: Condition update trigger (follows from S)
|
||||
[U] L4-Q4: Interior generation trigger
|
||||
```
|
||||
|
||||
**Critical path:** A → B → [C, D, E] → F+G+H → [I, J] → K+L+M+N → [O, P, Q] → R+S → [T, U]
|
||||
|
||||
**Parallelizable:**
|
||||
- Layer 1 and Layer 3 development can proceed in parallel once [A, B, L, M, N] resolved
|
||||
- Layer 2 and Layer 4 in parallel once their prerequisites are met
|
||||
- Economic simulation proceeds independently of Layers 1-2 spatial data
|
||||
|
||||
---
|
||||
|
||||
## 10. Ozzie's Player Experience Signals
|
||||
|
||||
Ozzie contributed a player experience inventory for each layer. Key flags that have no equivalent in other agents' outputs:
|
||||
|
||||
**Layer 1:**
|
||||
- "Survey data unavailable" when a world hasn't generated yet: make it diegetic, not a loading spinner. Suggested: partial scan, static, outdated data, cursor blinking in empty grid.
|
||||
- Terrain roughness (`terrain_modification_cost`) should be visible in street layout: irregular blocks, terraced buildings, streets curving around outcrops.
|
||||
- Mountain pass tags must feed road routing or they're vestigial.
|
||||
|
||||
**Layer 2:**
|
||||
- TerritorialStatus transitions should be physically felt: road quality drops, lighting thins, settlements shrink. Not just an invisible boundary change.
|
||||
- `MaintenanceAuthority` variations on road segments should be readable in road condition: corp roads paved, farm roads cracked.
|
||||
- "No placement should violate geographic common sense." Attractor-matching failure condition: fishing port in mountains.
|
||||
|
||||
**Layer 3:**
|
||||
- Player should feel CompanyTown spine before knowing the archetype name. Explicit spatial arrangement is required for this.
|
||||
- `FoundingOrientation` should be felt from the approach: a PortFacing city should show its harbor face to the sea approach, its warehouse face to the land approach.
|
||||
- First impression of every city when spawning should be designed, not random.
|
||||
|
||||
**Layer 4:**
|
||||
- Tile condition state at "Broken" should look like ruins: structural collapses, road barely passable. Not subtle degradation.
|
||||
- Prop/decal scatter is not optional for a "felt inhabited" space (see tension with brief's deferral in Section 8).
|
||||
- Door approach: player should have an intuition about what's behind a door before crossing the threshold.
|
||||
|
||||
---
|
||||
|
||||
## 11. Cross-Layer Questions — Status After Round 1
|
||||
|
||||
| Question | Status after Round 1 |
|
||||
|----------|---------------------|
|
||||
| **CL-Q1** (naming: `prosperity_baseline` vs `prosperity_current`) | Confirmed by all agents. Two distinct fields. `prosperity_delta` = derived, never stored. |
|
||||
| **CL-Q2** (regional land-use evolution resolution boundary) | Burnelli-Sheldon (Round 4 position held): coarse biome-cell resolution updates at runtime; city-internal layout never changes; NOT ChunkMutations. No agent disputed. |
|
||||
| **CL-Q3** (ruin lifecycle) | Demoted per Amendment 5. Paula: emergent rendering outcome only. Gestalt: Amendment 5 settled this. No agent proposed a designed decay path. |
|
||||
|
||||
---
|
||||
|
||||
## 12. Items Carried Into Round 2
|
||||
|
||||
### Must be resolved in Round 2
|
||||
|
||||
| Item | Blocking dependency |
|
||||
|------|-------------------|
|
||||
| Attractor-matching algorithm: reconcile four proposals (Gestalt / Tyre / Burnelli-Sheldon / Paula) | Blocks all Layer 2 settlement placement |
|
||||
| NEW-Q2 (OrganicGrowth disambiguation): confirm Paula's `geographically_triggered: bool` flag | Blocks FoundingOrientation derivation for non-attractor settlements |
|
||||
| Amendment 6 weight table: reconcile three proposals (Gestalt / Tyre / Burnelli-Sheldon) | Blocks Layer 3 zoning algorithm |
|
||||
| Tyre's four technical blockers (ARCH-1 through ARCH-4) | Block all Layer 1-2 Rust implementation |
|
||||
| Spatial hierarchy dimensions: workshop confirmation or amendment of Tyre's table | Blocks Province dimension used in L2-Q5 thresholds |
|
||||
|
||||
### Should be resolved in Round 2
|
||||
|
||||
| Item | Owner | Notes |
|
||||
|------|-------|-------|
|
||||
| TerritorialStatus thresholds (L2-Q5): reconcile Paula / Tyre / Burnelli-Sheldon proposals | Paula + Burnelli-Sheldon | Needs Province dimensions confirmed first |
|
||||
| Tyre's `atlas_city_names` schema: does it replace or extend `atlas_cities`? | Tyre + lead | Schema change before attractor-matching can be specified fully |
|
||||
| Paula's topographic modifier to `prosperity_baseline` | Paula + Burnelli-Sheldon | Additive to Burnelli-Sheldon's formula; compatibility to confirm |
|
||||
| L4-Q5 (scatter scope): Ozzie's "not optional" position vs. brief's deferral | Gestalt + Ozzie | Player experience minimum vs. technical minimum |
|
||||
| BS-Q1: 9,999-person Town without dedicated Entertainment — plausible? | Gestalt + Burnelli-Sheldon | Feel check |
|
||||
| BS-Q2: Age modifier + WorldTier ceiling | Burnelli-Sheldon | Proposed self-resolution: age modifier only at Town tier and above |
|
||||
| L3-Q7 (port/station direction): mechanism design vs. deferral | Paula + Ozzie | Paula designed the mechanism; Ozzie wants it badly; all others deferred |
|
||||
| Threshold values for L4-Q2: 0.6/0.4/0.2 (Gestalt) vs. 0.63/0.43/0.23 (Paula) | All | Minor; principle agreed |
|
||||
|
||||
### Questions with confirmed positions (no Round 2 design work needed)
|
||||
|
||||
| Question | Position | Who confirmed |
|
||||
|----------|---------|--------------|
|
||||
| L1-Q4 (biome prior) | Include it | Tyre, Paula, Burnelli-Sheldon (Gestalt: independent, can be deferred) |
|
||||
| L1-Q3 / L2-Q6 (session DB for storage) | Session DB, not systems.db | All five agents |
|
||||
| L3-Q1 (FoundingOrientation spatial effect) | Spatial grid rotation | Gestalt, Tyre, Paula, Ozzie |
|
||||
| L3-Q2 (political archetype arrangement) | Explicit patterns (scope differs) | All five agents back explicit |
|
||||
| L4-Q1 (rendering mechanism) | Threshold-crossing cache invalidation | All five agents |
|
||||
| L4-Q4 (interior trigger) | Pre-fetch; on-entry for minimum viable | Gestalt, Tyre, Paula, Ozzie |
|
||||
| L4-Q6 / CL-Q1 (two-field model) | `prosperity_baseline` + `prosperity_current`; `prosperity_delta` derived | All agents |
|
||||
| L3-Q6 (sub-settlement codepath) | Unified (district_count = 1) | Paula, Gestalt; others no objection |
|
||||
| CL-Q3 (ruin lifecycle) | Emergent rendering outcome; no designed decay path | All agents per Amendment 5 |
|
||||
| L2-Q3 (road algorithm) | Fully generated; named route identity via post-generation assignment | All agents |
|
||||
|
||||
---
|
||||
|
||||
*Compiled by Qatux. Sources: five Round 1 documents dated 2026-05-01. No positions added, modified, or attributed to agents who did not state them.*
|
||||
@@ -0,0 +1,544 @@
|
||||
---
|
||||
title: "Round 2 Notes — Planet-Down Cascade Workshop"
|
||||
author: qatux
|
||||
workshop: planet-down-cascade
|
||||
round: 2
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Round 2 Notes — Planet-Down Cascade Workshop
|
||||
|
||||
Compiled from five Round 2 agent files: Gestalt, Tyre, Paula, Burnelli-Sheldon, Ozzie.
|
||||
|
||||
---
|
||||
|
||||
## 1. Attractor-Matching Algorithm — CONVERGED
|
||||
|
||||
All five agents reached alignment on attractor-matching. The algorithm is a five-phase pipeline combining Paula's priority ordering with Burnelli-Sheldon's objective function and the Hungarian algorithm agreed by Gestalt and Tyre.
|
||||
|
||||
### Converged Algorithm (Five Phases)
|
||||
|
||||
**Phase 0 — Score matrix + HQ hard zeros**
|
||||
Build an N×M matrix of compatibility scores between N named cities and M geographic attractors. Hard-zero any attractor that is physically incompatible with the city's economic role (Paula's H1-H4 constraints: maritime corp must be coastal, resource corp must be near resource concentration, etc.).
|
||||
|
||||
**Phase 1 — Sort cities into three constraint tiers**
|
||||
- Tier A: Extraction/resource corps — most geographically constrained; fewest valid attractor assignments
|
||||
- Tier B: Manufacturing/industrial/transport corps — moderately constrained
|
||||
- Tier C: Service/commercial/financial corps — least constrained; most flexible
|
||||
|
||||
**Phase 2 — Tier A greedy assignment**
|
||||
Assign Tier A cities greedily, sorted by constraint tightness (fewest valid attractors first). This is Paula's insight: extraction corps must be near their resource; assign them first to prevent geographic mismatches from cascading.
|
||||
|
||||
**Phase 3 — Hungarian algorithm on remaining cities**
|
||||
Run maximum-weight bipartite matching on Tier B and C cities against remaining attractors. Burnelli-Sheldon's scoring function: maximize aggregate plausibility across the whole planet, not just the highest-weight individual assignments.
|
||||
|
||||
**Phase 4 — Synthetic attractor overflow**
|
||||
If N named cities > M geographic attractors: generate synthetic attractors (Burnelli-Sheldon) at seed-derived offsets along road corridors. `SyntheticPlacementReason`: `PopulationOverflow | PoliticalDecision | CorpExpansion`. High-tier economic worlds always get synthetic attractors regardless.
|
||||
|
||||
**Phase 5 — FoundingOrientation derivation**
|
||||
Post-assignment: if `geographically_triggered == false` (Paula), `FoundingOrientation = AdminFacing`. Cities that exist due to political decisions rather than geography get AdminFacing orientation; they sit at Province centroids or political midpoints.
|
||||
|
||||
### Attractor Assignment Data Type (Paula)
|
||||
|
||||
```rust
|
||||
enum AttractorAssignment {
|
||||
Geographic { attractor_type: FeatureTag, quality_score: f32 },
|
||||
Synthetic { reason: SyntheticPlacementReason },
|
||||
}
|
||||
|
||||
enum SyntheticPlacementReason {
|
||||
PopulationOverflow,
|
||||
PoliticalDecision,
|
||||
CorpExpansion,
|
||||
}
|
||||
```
|
||||
|
||||
### Mismatch Flag Threshold — OPEN FOR ROUND 3
|
||||
|
||||
**Tyre**: flag for lead review when best available attractor score < 0.15
|
||||
**Paula**: soft constraint table identifies mismatches at score < 0.35
|
||||
|
||||
These are not equivalent. Paula's 0.35 threshold catches more borderline cases; Tyre's 0.15 only catches outright incompatibilities. The lead must resolve which threshold governs the `flagged_for_review` behavior in Round 3.
|
||||
|
||||
### Paula's Hard Constraints (H1–H4)
|
||||
|
||||
Physical impossibility filter — hard-zeros before any scoring:
|
||||
- H1: Maritime/coastal corps cannot be assigned to inland attractors
|
||||
- H2: Resource extraction corps cannot be placed in areas lacking the target resource feature tag
|
||||
- H3: Agricultural corps cannot be placed in terrain with slope above threshold
|
||||
- H4: Deep-space station corps cannot have a planetary surface attractor (applies to orbital body placement only)
|
||||
|
||||
Paula's `is_physically_possible()` function runs before score matrix population; these are zero, not low scores.
|
||||
|
||||
### Ozzie's Player Experience Verdict
|
||||
|
||||
Paula's priority ordering + Burnelli-Sheldon's scoring function is correct. "A planet where every city feels 90% correctly placed is better than a planet where the top 5 cities feel 100% correctly placed but smaller cities feel arbitrary." Mismatch flagging is important — silent wrong placements are trust-breakers.
|
||||
|
||||
---
|
||||
|
||||
## 2. Three-Component District Mix Algorithm — CONVERGED (Lead-Locked)
|
||||
|
||||
Burnelli-Sheldon's three-component model was locked by the lead before Round 2 began. Round 2 produced the full algorithm with pseudocode, resolved all three open sub-questions, and confirmed self-containment.
|
||||
|
||||
### The Three Components
|
||||
|
||||
1. **Population tier guarantees** — minimum required district counts by population band
|
||||
2. **Economic role multiplier table** — integer weights (sum to 100, minimum value 3) per district type per economic role
|
||||
3. **Founding age character modifier** — modifies prosperity_baseline and character tags (not weights)
|
||||
|
||||
### Population Tier Guarantees
|
||||
|
||||
| Settlement Size | Required Districts |
|
||||
|---|---|
|
||||
| Village (< 1,000) | Residential, Mixed |
|
||||
| Town (1,000–9,999) | Residential, Commercial, Mixed |
|
||||
| City (10,000–99,999) | + Entertainment |
|
||||
| Large City (100,000+) | + Administrative |
|
||||
| Metropolis (500,000+) | + Industrial (if applicable), + Civic |
|
||||
|
||||
BS-Q1 resolved: Town (1k-9,999) has no dedicated Entertainment district. Entertainment at Town scale lives inside the Commercial district. Dedicated Entertainment is a city-scale phenomenon (10k+ threshold).
|
||||
|
||||
### Integer Multiplier Table (Partial — Lead to confirm full table in Round 3)
|
||||
|
||||
Each row sums to 100. Minimum value across all cells: 3 (no zero weights — any district type can appear in any economic role, just rarely).
|
||||
|
||||
| Economic Role | Residential | Commercial | Industrial | Administrative | Entertainment | Civic | Mixed |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Mining/Extraction | 35 | 12 | 28 | 8 | 3 | 5 | 9 |
|
||||
| Manufacturing | 30 | 15 | 25 | 8 | 5 | 7 | 10 |
|
||||
| Research Hub | 28 | 18 | 10 | 15 | 8 | 12 | 9 |
|
||||
| Commercial Hub | 22 | 30 | 8 | 10 | 12 | 8 | 10 |
|
||||
| Administrative | 20 | 15 | 5 | 30 | 8 | 15 | 7 |
|
||||
| Transit/Port | 25 | 20 | 18 | 7 | 5 | 5 | 20 |
|
||||
| Energy | 33 | 10 | 30 | 8 | 3 | 6 | 10 |
|
||||
| Agricultural | 35 | 18 | 10 | 5 | 7 | 8 | 17 |
|
||||
|
||||
Political archetype weight modifiers applied after role table (Burnelli-Sheldon):
|
||||
- CompanyTown: Administrative −10, Industrial +10
|
||||
- AdminCapital: Administrative +15, Commercial −8, Entertainment −7
|
||||
- FreePort: Commercial +12, Mixed +8, Administrative −20
|
||||
|
||||
BS-Q3 resolved: Energy economic role has minimum Entertainment weight of 3. The guarantee floor (≥1 Entertainment district at City scale) handles large energy cities without special-casing.
|
||||
|
||||
### Founding Age Character Modifier
|
||||
|
||||
BS-Q2 resolved: Age modifier applies only at Backwater tier and above (`WorldTier: Backwater | Waypoint | Passage | Epicenter | Regional`). Pure waypoint/passage bodies are always architecturally nascent regardless of calendar age.
|
||||
|
||||
`founding_age_years` derivation for sub-settlements (Burnelli-Sheldon):
|
||||
- Mining camp: `parent_city_age - 20`
|
||||
- Planned satellite town: `parent_city_age - 5` to `parent_city_age - 15` (seed-derived)
|
||||
- Organic suburb: `parent_city_age * 0.7` (grew after city was established)
|
||||
|
||||
Age modifiers affect:
|
||||
- `prosperity_baseline` offset (old cities start more established; new cities start raw)
|
||||
- Character tags: `legacy_infrastructure`, `retrofitted`, `modern_grid`, `raw_settlement`
|
||||
- Block shape irregularity signal (see Ozzie's Round 3 proposal below)
|
||||
|
||||
### Self-Containment Confirmed
|
||||
|
||||
The district distribution algorithm references only local city/body/system fields. No neighboring city queries. This was a hard requirement from the lead and is confirmed by Burnelli-Sheldon and cross-checked by Gestalt.
|
||||
|
||||
### SettlementClass Enum (Burnelli-Sheldon)
|
||||
|
||||
Generalizes the latent settlement concept from the brief:
|
||||
|
||||
```rust
|
||||
enum SettlementClass {
|
||||
NameLocked, // Has a name in atlas_city_names; fully generated at Layer 2
|
||||
PopulationBudget, // Unnamed; placed by population overflow; active if body_population_density > threshold
|
||||
EconomicTriggered, // Unnamed; placed by economic activity; active if route_traffic_score > threshold
|
||||
OrganicGrowth, // Unnamed; placed by geographic probability; geographically_triggered = false
|
||||
}
|
||||
```
|
||||
|
||||
`active: bool` field on `GeneratedSettlement` derived from SettlementClass conditions. `province_placed_at_generation: bool` is immutable and set at Layer 2 generation time.
|
||||
|
||||
---
|
||||
|
||||
## 3. TerritorialStatus — CONVERGED
|
||||
|
||||
All agents converged on a single priority-ordered algorithm. Paula's `placed_at_generation` flag incorporated; Burnelli-Sheldon's economic health conditions incorporated; Tyre's corporate road maintenance thresholds incorporated.
|
||||
|
||||
### Priority-Ordered Algorithm
|
||||
|
||||
```
|
||||
fn derive_territorial_status(province: &ProvinceWorldState) -> TerritorialStatus {
|
||||
// Priority 1: Explicitly abandoned (was settled, now gone)
|
||||
if province.placed_at_generation && !province.active {
|
||||
return TerritorialStatus::AbandonedZone;
|
||||
}
|
||||
// Priority 2: Genuinely uninhabited wilderness
|
||||
if !province.placed_at_generation && province.settlement_count == 0 {
|
||||
return TerritorialStatus::WildernessBuffer;
|
||||
}
|
||||
// Priority 3: Active resource extraction
|
||||
if province.primary_economic_activity == EconomicActivity::Extraction
|
||||
&& province.corporate_presence_score > 0.4 {
|
||||
return TerritorialStatus::ExtractiveZone;
|
||||
}
|
||||
// Priority 4: Contested territory (overlapping jurisdiction scores)
|
||||
if province.jurisdiction_overlap_score > 0.3 {
|
||||
return TerritorialStatus::ContestZone;
|
||||
}
|
||||
// Priority 5: Established territory
|
||||
if province.infrastructure_quality > 0.6 && province.corporate_road_maintenance > 0.5 {
|
||||
return TerritorialStatus::CoreTerritory;
|
||||
}
|
||||
// Default: frontier
|
||||
TerritorialStatus::FrontierTerritory
|
||||
}
|
||||
```
|
||||
|
||||
### Enum Values
|
||||
|
||||
```rust
|
||||
enum TerritorialStatus {
|
||||
CoreTerritory, // Established, maintained infrastructure
|
||||
FrontierTerritory, // Active but underdeveloped
|
||||
ExtractiveZone, // Resource extraction; corporate presence dominant
|
||||
ContestZone, // Overlapping jurisdiction; political pressure active
|
||||
WildernessBuffer, // Never settled; no generation at Layer 2
|
||||
AbandonedZone, // Was settled; now inactive (placed_at_generation = true, active = false)
|
||||
}
|
||||
```
|
||||
|
||||
### Key Distinction: AbandonedZone vs. WildernessBuffer
|
||||
|
||||
`placed_at_generation: bool` is the only runtime-available signal. A province that was never settled and an abandoned province both have `settlement_count == 0` — the flag is the differentiator. Gestalt confirmed this is the critical bit for ghost-city emergent behavior.
|
||||
|
||||
### Ozzie's Province Boundary Legibility Requirement
|
||||
|
||||
Ozzie flags: Province boundaries must be visible on the planetary map as natural features (drainage basin boundaries, watershed lines), not arbitrary grid lines. If Province boundaries aren't legible, TerritorialStatus doesn't communicate to the player. **This is a player experience requirement, not an optional UI decision.** To be addressed in Round 3 UI/UX pass.
|
||||
|
||||
---
|
||||
|
||||
## 4. Five Explicit Spatial Arrangement Patterns — CONVERGED
|
||||
|
||||
Gestalt updated position in Round 2: all five archetypes get explicit spatial arrangement patterns, not just three. Paula introduced Contested and OrganicGrowth in Round 1; Gestalt adopted them.
|
||||
|
||||
### CompanyTown — Spine Pattern
|
||||
|
||||
Linear axis pointing from residential to facility. Residential blocks at one end; industrial facility at the other; administrative and commercial nodes distributed along the spine. The facility must be visually legible from the residential terminus — the spine terminates in something visible.
|
||||
|
||||
Player experience (Ozzie): "Everything points toward the facility. The city has a posture." The geometry implies surveillance — the admin building faces the residential blocks.
|
||||
|
||||
### AdminCapital — Radial Pattern
|
||||
|
||||
Administrative hub at center; streets radiate outward; prosperity gradient decreases with distance from hub. All approaches to the city are oriented toward the hub.
|
||||
|
||||
Player experience (Ozzie): Power is visible from everywhere. Interesting degenerate case: AdminCapital in decline — hub is geometrically centered but prosperity gradient has inverted (center crumbling, outer ring richer). Ghost-city effect is emergent from this inversion alone, no authored ghost-city feature needed.
|
||||
|
||||
### FreePort — Multi-Node Pattern
|
||||
|
||||
Three to five nodes, each with distinct character (maritime, tech, black market, etc.). Nodes connected by secondary roads; no dominant center. Settlement grew from several independent decisions.
|
||||
|
||||
Player experience (Ozzie): Productively disorienting. Nodes must be visually distinct to serve as landmarks — if every node looks generically "mixed," the player can't navigate. Node-level identity must be legible from block character.
|
||||
|
||||
### Contested — Dual-Center Overlay Pattern (Paula)
|
||||
|
||||
Two underlying geometric patterns from two different powers, neither fully resolved. Grid that shifts angle; roads that don't match buildings; a defensive wall repurposed as a property line; a plaza designed for a different political purpose.
|
||||
|
||||
Player experience (Ozzie): The player feels the conflict in the urban structure without text explanation. Both underlying geometries must be legible — if the overlay is too subtle, it looks broken rather than historically layered. The seams must be visible.
|
||||
|
||||
### OrganicGrowth — Irregular Local Density Pattern (Paula)
|
||||
|
||||
No spine, no hub, no nodes. Density peaks where people naturally congregated. The center emerged; it wasn't declared. Distinct from CompanyTown (no spine), FreePort (one mass, not nodes), AdminCapital (center emerged, not declared).
|
||||
|
||||
Player experience (Ozzie): Most "lived-in" feel of all five. The player reads it as older and more authentic — human decision-making over time rather than planning.
|
||||
|
||||
### Acceptance Criterion (Ozzie)
|
||||
|
||||
Can the player identify the archetype from 15 seconds of walking around? This is the acceptance criterion for explicit spatial arrangement implementation. Each pattern must produce recognizable geometry at the street level.
|
||||
|
||||
---
|
||||
|
||||
## 5. Tyre's Four Blocker Schemas — LOCKED
|
||||
|
||||
All four ARCH blockers (ARCH-1 through ARCH-4) confirmed with concrete SQL DDL and Rust types. These are the implementation schemas.
|
||||
|
||||
### ARCH-1: Heightmap Storage (atlas_body_heightmaps)
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_body_heightmaps (
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
data BLOB NOT NULL, -- float32 LE, 512*256 entries = 524,288 bytes per body
|
||||
PRIMARY KEY (body_id)
|
||||
);
|
||||
```
|
||||
|
||||
Storage estimate: ~512KB/body, ~200MB for 400 bodies. Acceptable.
|
||||
|
||||
Rust read:
|
||||
```rust
|
||||
fn load_heightmap(conn: &Connection, body_id: i64) -> Result<Vec<f32>> {
|
||||
let data: Vec<u8> = conn.query_row(
|
||||
"SELECT data FROM atlas_body_heightmaps WHERE body_id = ?1",
|
||||
[body_id],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
Ok(bytemuck::cast_slice(&data).to_vec())
|
||||
}
|
||||
```
|
||||
|
||||
### ARCH-2: BodyWorldState as Bevy Resource (NOT a database)
|
||||
|
||||
```rust
|
||||
#[derive(Resource)]
|
||||
struct GenerationCache {
|
||||
entries: LruCache<i64, Arc<BodyWorldState>>, // key = body_id
|
||||
}
|
||||
|
||||
struct BodyWorldState {
|
||||
body_id: i64,
|
||||
seed: u64,
|
||||
heightmap: Vec<f32>, // 512×256, loaded from ARCH-1
|
||||
river_network: RiverNetwork, // D8 drainage routing output
|
||||
attractors: Vec<GeoAttractor>,
|
||||
settlements: Vec<GeneratedSettlement>,
|
||||
provinces: Vec<ProvinceWorldState>,
|
||||
generated_at: std::time::Instant,
|
||||
}
|
||||
```
|
||||
|
||||
LRU cache at 50 bodies (~5MB total per Tyre's estimate). `Arc<BodyWorldState>` for cheap clones across systems. Never serialized; fully reproducible from seed + systems.db.
|
||||
|
||||
### ARCH-3: City Name Table (atlas_city_names)
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_city_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
corp_id INTEGER REFERENCES corporations(id), -- nullable; corp HQ constraint
|
||||
tier_hint INTEGER, -- nullable; expected WorldTier for this city
|
||||
reserved BOOLEAN NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_city_names_body ON atlas_city_names(body_id);
|
||||
```
|
||||
|
||||
Replaces authored positions in atlas_cities. Names are reservations; positions emerge from attractor-matching at Layer 2 runtime.
|
||||
|
||||
Rust load:
|
||||
```rust
|
||||
fn load_city_names(conn: &Connection, body_id: i64) -> Result<Vec<CityNameRecord>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, name, corp_id, tier_hint FROM atlas_city_names
|
||||
WHERE body_id = ?1 ORDER BY id"
|
||||
)?;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### ARCH-4: Body Radius Column
|
||||
|
||||
```sql
|
||||
ALTER TABLE bodies ADD COLUMN body_radius_km REAL;
|
||||
```
|
||||
|
||||
Rust read with planet_class fallback:
|
||||
```rust
|
||||
fn body_radius_km(row: &Row) -> f64 {
|
||||
row.get::<_, Option<f64>>("body_radius_km")
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| default_radius_for_class(
|
||||
row.get("planet_class").unwrap_or("")
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Estimates (Tyre)
|
||||
|
||||
| Operation | Estimate | Notes |
|
||||
|---|---|---|
|
||||
| D8 drainage routing | ~50ms | 512×256 heightmap; priority-flood |
|
||||
| Attractor scoring + Hungarian | ~15ms | N≤30 cities; trivially fast |
|
||||
| Settlement placement | ~20ms | includes name fulfillment |
|
||||
| Province classification | ~40ms | TerritorialStatus + ProvinceWorldState |
|
||||
| Province WorldTier assignment | ~10ms | |
|
||||
| **Total** | **~136ms** | 5× headroom vs. 700ms budget |
|
||||
|
||||
---
|
||||
|
||||
## 6. Background Thread Architecture — LOCKED
|
||||
|
||||
Tyre's Round 2 confirmed the full background generation architecture.
|
||||
|
||||
### Rayon Thread Pool + Coordinator
|
||||
|
||||
```rust
|
||||
struct GenerationQueue {
|
||||
pending: BinaryHeap<(Priority, BodyGenRequest)>, // max-heap by priority
|
||||
in_flight: HashSet<i64>, // body_ids currently generating
|
||||
}
|
||||
|
||||
fn launch_background_generation(body_id: i64, priority: Priority) {
|
||||
rayon::spawn(move || {
|
||||
let state = generate_body_world_state(body_id);
|
||||
GENERATION_CACHE.write().insert(body_id, Arc::new(state));
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Priority ordering: player-targeted body (highest) → adjacent bodies in travel route → bodies mentioned in recent dialogue → bodies in active corp supply chains → all others.
|
||||
|
||||
### Aho-Corasick for Text Scanning (SystemNameIndex)
|
||||
|
||||
```rust
|
||||
struct SystemNameIndex {
|
||||
automaton: AhoCorasick,
|
||||
patterns: Vec<String>, // system and body names
|
||||
body_ids: Vec<i64>, // parallel to patterns
|
||||
}
|
||||
|
||||
impl SystemNameIndex {
|
||||
fn scan(&self, text: &str) -> Vec<i64> {
|
||||
self.automaton.find_iter(text)
|
||||
.map(|m| self.body_ids[m.pattern().as_usize()])
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Used to detect body name references in news tickers, NPC dialogue, and player-readable documents — triggers background generation for mentioned bodies before the player decides to travel there.
|
||||
|
||||
### Diegetic Placeholder (Ozzie's Three-Tier Proposal)
|
||||
|
||||
When the player opens a planetary map for a body whose Layer 1-2 cascade hasn't completed:
|
||||
|
||||
**Tier A (most worlds, ~3-5s gap):** Terrain + coastlines from heightmap (already loaded); settlements shown as "unconfirmed" markers. Player sees the shape of the world but not the civilization. This is the normal fallback — most maps complete before the player opens them.
|
||||
|
||||
**Tier B (frontier/remote worlds):** Old orbital survey data with visible timestamp ("Last comprehensive survey: 41 years ago"). Historical positions shown; may not match generated reality. Creates discovery tension: map says one thing; world shows another.
|
||||
|
||||
**Tier C (system just mentioned):** Black circle with blinking cursor. "Survey data unavailable. Scan in progress." Highest drama — player is about to explore somewhere they just heard about.
|
||||
|
||||
**Priority fallback chain:**
|
||||
1. Generation complete → full map
|
||||
2. Heightmap loaded, settlements not placed → terrain + coastlines + "settlement survey pending"
|
||||
3. Only systems.db data → known city names as points, no positions, "Positional survey pending"
|
||||
4. Nothing → blinking cursor + system timestamp + "No survey data"
|
||||
|
||||
Design principle (Ozzie): "The placeholder should read like information, not like a loading state. The player should learn something from it, even in the worst case."
|
||||
|
||||
---
|
||||
|
||||
## 7. Tile Condition and Prosperity — CONFIRMED
|
||||
|
||||
### Paula's Threshold Offsets (Gestalt Adopted in Round 2)
|
||||
|
||||
Tile condition derived from `prosperity_current` (not `prosperity_baseline`):
|
||||
|
||||
| Condition | Threshold |
|
||||
|---|---|
|
||||
| Intact | prosperity_current > 0.63 |
|
||||
| Worn | prosperity_current 0.43–0.63 |
|
||||
| Cracked | prosperity_current 0.23–0.43 |
|
||||
| Broken | prosperity_current < 0.23 |
|
||||
|
||||
Paula's rationale for 0.63/0.43/0.23 over Gestalt's original 0.6/0.4/0.2: offsets from round numbers prevent boundary oscillation when prosperity fluctuates near the threshold. Gestalt adopted Paula's values in Round 2.
|
||||
|
||||
### prosperity_baseline vs. prosperity_current
|
||||
|
||||
Two distinct fields. `prosperity_delta = prosperity_current - prosperity_baseline` is derived, never stored. Paula flags this as a rendering legibility requirement: the client needs both fields to show historical vs. current state.
|
||||
|
||||
---
|
||||
|
||||
## 8. Name Reservation Fulfillment (Paula) — NEW
|
||||
|
||||
Paula introduced a four-stage name fulfillment pipeline:
|
||||
|
||||
- **Stage 0 (build-time):** Build name reservation table from atlas_city_names; mark HQ-constrained names
|
||||
- **Stage 1 (Layer 1 runtime):** Assign names to geographic features (rivers, mountain passes, bays) first — these are features, not cities
|
||||
- **Stage 2 (Layer 2 attractor-matching):** Assign reserved city names to generated settlement positions during or immediately after attractor-matching; HQ-constrained names placed first (Tier A)
|
||||
- **Stage 3 (overflow):** Remaining unnamed settlements generated procedurally; remaining reserved names fulfilled if attractor match exists, otherwise deferred
|
||||
|
||||
`atlas_feature_names` table added (distinct from `atlas_city_names`):
|
||||
```sql
|
||||
CREATE TABLE atlas_feature_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
tag_hint TEXT -- nullable; expected FeatureTag for this name
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Cascade Transition Moments (Ozzie) — DESIGN NOTE
|
||||
|
||||
Ozzie identifies five tier transitions as player experience opportunities. These are not authored setpieces — they emerge from each layer correctly setting up the next layer's opening condition.
|
||||
|
||||
| Transition | What produces it |
|
||||
|---|---|
|
||||
| Entering atmosphere | Coastlines visible from space; lit cities at night (Layer 1 terrain + Layer 2 settlement positions) |
|
||||
| Crossing Province boundary | Road condition change; signage style change (TerritorialStatus + corporate road maintenance) |
|
||||
| Entering Region (city footprint) | Density increases; wilderness thins; city visible before arriving (Region extent field) |
|
||||
| Entering District | Perimeter treatment marks the threshold (District perimeter_treatment field) |
|
||||
| Entering building | Door threshold; interior lighting transition |
|
||||
|
||||
"The cascade is the experience. Design each handoff so the player feels the seam." No action required in Round 3 — this is a design note to carry forward to Phase 4 implementation.
|
||||
|
||||
---
|
||||
|
||||
## 10. WorldTier Enum — BUG FLAGGED
|
||||
|
||||
The code has wrong values: `Peripheral | Connected | Core`. Required values (from the brief and all agents): `Epicenter | Regional | Backwater | Passage | Waypoint`.
|
||||
|
||||
This is a code bug, not a design question. Needs a ticket. No Round 3 discussion required.
|
||||
|
||||
---
|
||||
|
||||
## 11. Open Questions for Round 3
|
||||
|
||||
### Q1 — Mismatch flag threshold [MUST RESOLVE]
|
||||
**Tyre:** `score < 0.15` → flagged_for_review
|
||||
**Paula:** soft constraint table flags mismatches at `score < 0.35`
|
||||
These are not compatible. The lead must choose one value (or a two-level system: warning at 0.35, hard-flag at 0.15).
|
||||
|
||||
### Q2 — founding_age → layout_mode [OZZIE PROPOSAL]
|
||||
Ozzie proposes: founding age should modify `layout_mode` as well as `prosperity_baseline`. Old settlements → irregular layout modes. Young settlements → grid layout. This would require `layout_mode` to be a weighted choice at District generation, not a fixed archetype value. Needs evaluation from Gestalt and Tyre.
|
||||
|
||||
### Q3 — Province boundary legibility [OZZIE REQUIREMENT]
|
||||
Province boundaries must be visible on the planetary map as natural features (watershed lines, drainage basins). Currently unspecified whether the planetary map renders Province boundaries. UI/UX item for Round 3.
|
||||
|
||||
### Q4 — L3-Q7: Port/station as special city type [DEFERRED TO ROUND 3]
|
||||
Not addressed in Round 2. Still open.
|
||||
|
||||
### Q5 — atlas_city_names population path [NEEDS CLARIFICATION]
|
||||
How are city names added to atlas_city_names? Hand-authored? Generated from brand files? Generated from Miri's naming system? Tyre locked the schema; Paula added Stage 0-3 fulfillment stages; neither specifies who writes the source rows before Stage 0.
|
||||
|
||||
### Q6 — Attractor multiplier table full values [NEEDS CONFIRMATION]
|
||||
The full integer multiplier table needs all economic roles confirmed, including any roles not covered in Burnelli-Sheldon's Round 2 examples. Round 3 should produce the locked table.
|
||||
|
||||
---
|
||||
|
||||
## 12. Items NOT Raised in Round 2 (Carried Forward as Locked)
|
||||
|
||||
These were settled before or during Round 1 and not reopened:
|
||||
|
||||
- **SeedChain (FNV-1a)** — locked from generation-cascade workshop
|
||||
- **D8 drainage routing** — accepted by all agents, confirmed in Tyre's ARCH-1
|
||||
- **Scatter deferred** — lead decision; Ozzie accepted; Gestalt noted `spawn_category: Option<PropCategory>` hook in TileEntry for future activation
|
||||
- **District = 256m** — confirmed by Tyre, accepted by Ozzie as correct player experience scale
|
||||
- **Block = 64m** — confirmed as 4×4 per district
|
||||
- **Province = 1 regional grid cell (~540km×270km on reference body)** — confirmed by Tyre
|
||||
- **Area = atlas layer, not navigation tier** — confirmed by Ozzie
|
||||
- **Self-contained district generation** — lead decision; confirmed by all agents
|
||||
|
||||
---
|
||||
|
||||
## Agent Positions Summary
|
||||
|
||||
| Item | Gestalt | Tyre | Paula | Burnelli-Sheldon | Ozzie |
|
||||
|---|---|---|---|---|---|
|
||||
| Attractor algorithm | Hungarian + 5 phases | Hungarian + HQ-first | Priority ordering + mismatch flags | Objective function + synthetic attractors | Paula ordering + BS scoring |
|
||||
| District mix | Three-component (confirmed) | Schema support | Accepted | Authored | Accepted |
|
||||
| TerritorialStatus thresholds | Priority-ordered (Rust code) | Priority-ordered (Rust code + corporate road) | placed_at_generation flag | Economic health conditions | Accepted |
|
||||
| Spatial patterns | All five explicit | No position | Introduced Contested + OrganicGrowth | Accepted | All five produce distinct feel |
|
||||
| ARCH blockers | Schema acceptance | All four locked | atlas_city_names schema contribution | — | — |
|
||||
| L4-Q2 thresholds | Adopted Paula's 0.63/0.43/0.23 | 0.63/0.43/0.23 | Proposed 0.63/0.43/0.23 | Accepted | — |
|
||||
| founding_age → layout_mode | Not raised | Not raised | Not raised | Age modifier only | Proposed for Round 3 |
|
||||
|
||||
---
|
||||
|
||||
*Round 2 complete. Five agents in agreement on all locked items. Two MUST-RESOLVE items for Round 3: mismatch flag threshold (Q1) and founding_age→layout_mode (Q2). WorldTier enum bug needs a ticket before implementation begins.*
|
||||
@@ -0,0 +1,451 @@
|
||||
---
|
||||
title: "Round 3 Notes — Planet-Down Cascade Workshop"
|
||||
author: qatux
|
||||
workshop: planet-down-cascade
|
||||
round: 3
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Round 3 Notes — Planet-Down Cascade Workshop
|
||||
|
||||
Compiled from five Round 3 agent files: Gestalt, Tyre, Paula, Burnelli-Sheldon, Ozzie.
|
||||
|
||||
---
|
||||
|
||||
## 1. Resolution of All Round 2 Open Items
|
||||
|
||||
### Q1 — Mismatch Flag Threshold: RESOLVED (Lead Decision)
|
||||
|
||||
**Lead decision:** Two-tier system.
|
||||
|
||||
| Tier | Threshold | Behavior |
|
||||
|------|-----------|----------|
|
||||
| Soft (`MismatchSeverity::Soft`) | score < 0.35 | Logged to generation log; placement proceeds |
|
||||
| Hard (`MismatchSeverity::Hard`) | score < 0.15 | Assignment overridden to `Synthetic { reason: PoliticalDecision }`; city placed at Province centroid; `FoundingOrientation = AdminFacing`; generation proceeds without panic |
|
||||
|
||||
**Implementation detail (Tyre):** Hard mismatch is a policy decision, not a generation failure. The city exists but was placed politically rather than geographically. The flag surfaces in developer tooling.
|
||||
|
||||
**Validation (Burnelli-Sheldon):** Thresholds validated against real systems.db body types. Representative cases:
|
||||
- agricultural + Defensible (score 2/10 = 0.20): Warning. Farming at a defensible site — unusual but real. Warning is correct.
|
||||
- agricultural + NaturalBarrier (score 0): Error. Impossible. Correct.
|
||||
- extraction + MountainPass (score 5/10 = 0.50): No flag. Valid. Correct.
|
||||
|
||||
**Paula's integration:** HQ-constrained reservations (`corp_id IS NOT NULL` with active trade routes) escalate one tier: Warning → Error. A misplaced HQ with active supply chain dependencies is a more serious data quality problem.
|
||||
|
||||
---
|
||||
|
||||
### Q2 — founding_age → layout_mode: RESOLVED (All Agents Adopted)
|
||||
|
||||
All agents adopted the principle. The implementation converges on a new field on `DistrictSkeleton`, orthogonal to `layout_mode` (which governs archetype macro-geometry and stays fixed).
|
||||
|
||||
**Tyre's enum (the stored field):**
|
||||
|
||||
```rust
|
||||
enum BlockIrregularity {
|
||||
Grid, // Regular 64m × 64m; planned geometry
|
||||
SlightlyWorn, // ~10% deviation from grid
|
||||
Irregular, // ~25% deviation; multiple planning generations visible
|
||||
Organic, // No regular grid; emerged rather than planned
|
||||
}
|
||||
```
|
||||
|
||||
Derivation (`from_age_and_archetype`): reference step size varies by archetype — CompanyTown 40 years/tier, AdminCapital 100, FreePort 60, Contested 35, OrganicGrowth 20. Each tier of `founding_age_years / step` advances one enum value.
|
||||
|
||||
**Gestalt's complementary weighting:** Within each `BlockIrregularity` tier, the age organic bonus shifts block-edge probability distributions. CompanyTown Industrial districts resist age influence (age_resistance = 0.9); FreePort districts absorb it fully (0.0). Ancient AdminCapitals maintain planned cores while outer rings grow organic.
|
||||
|
||||
**Ozzie's minimum floor:** `irregularity_factor` minimum 0.05 even for brand-new cities — construction variance means no city is a perfect simulation artifact.
|
||||
|
||||
**Paula's per-archetype rules:**
|
||||
- CompanyTown: spine always grid (corporate planning maintained); age deepens character tags not geometry
|
||||
- AdminCapital: core Administrative always planned; outer rings accumulate irregularity
|
||||
- FreePort: node centers planned; between-node fill organic with age
|
||||
- Contested: each faction's half uses its archetype's rules; boundary zone always irregular (0.7+)
|
||||
- OrganicGrowth: fixed at 0.8 irregularity; age has no additional effect (already maximally irregular)
|
||||
|
||||
**Field placement:** `block_irregularity: BlockIrregularity` added to `DistrictSkeleton`. Set at Layer 3. Consumed at Layer 4. No effect on Phase 2-3 deliverables.
|
||||
|
||||
---
|
||||
|
||||
### Q3 — Province Boundary Legibility: RESOLVED
|
||||
|
||||
**Principle agreed by all agents:** Province boundaries ARE watershed/ridgeline features produced by D8 drainage. They render as terrain, not political overlays.
|
||||
|
||||
**Technical solution (Tyre):** Pre-computed at build time by Python (`generate_atlas.py` watershed extraction), stored as float32 UV-space polylines in `atlas_province_boundaries` table. Renderer loads directly; zero dependency on Rust generation status.
|
||||
|
||||
**New schema (ARCH-5):**
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_province_boundaries (
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
province_x INTEGER NOT NULL,
|
||||
province_y INTEGER NOT NULL,
|
||||
boundary BLOB NOT NULL,
|
||||
-- float32 pairs [u0,v0, u1,v1, ...] in atlas UV space (0.0..1.0)
|
||||
PRIMARY KEY (body_id, province_x, province_y)
|
||||
);
|
||||
CREATE INDEX idx_province_bounds_body ON atlas_province_boundaries(body_id);
|
||||
```
|
||||
|
||||
Storage: ~82KB/body → ~32MB for 400 bodies.
|
||||
|
||||
**Map UI specification (Ozzie, Paula):**
|
||||
- Province boundary = thin watershed/ridgeline rendering from polyline data (not thick colored borders)
|
||||
- TerritorialStatus = Province fill color/texture (subtle, readable at glance)
|
||||
- CoreTerritory: dense settlement markers, road lines visible
|
||||
- FrontierTerritory: scattered markers, road lines thin toward Province edge
|
||||
- ExtractiveZone: industrial tinting, road lines heavy toward resource sites
|
||||
- ContestZone: contested visual treatment (two-color or cross-hatched)
|
||||
- WildernessBuffer: empty, natural terrain only
|
||||
- AbandonedZone: reduced markers, ruins indicator
|
||||
|
||||
**This is a player experience requirement (Ozzie):** Province boundary legibility is non-negotiable for TerritorialStatus to communicate to the player. Filed as a Phase 3 map implementation requirement.
|
||||
|
||||
---
|
||||
|
||||
### Q4 — atlas_city_names Population Path: RESOLVED
|
||||
|
||||
**Two-tier authorship (Tyre, Paula, Gestalt all aligned):**
|
||||
|
||||
| Tier | Source | `reserved` flag | Who authors |
|
||||
|------|--------|-----------------|-------------|
|
||||
| Authored | `wiki/worlds/{body_slug}.toml` → `[[cities]]` | `true` | Miri (wiki authors) |
|
||||
| Generated | `import_economics.py` from brand files, corp HQ data | `false` | Generator |
|
||||
|
||||
**Authoring format:**
|
||||
```toml
|
||||
# wiki/worlds/nova-kassel.toml
|
||||
[[cities]]
|
||||
name = "Port Cassidy"
|
||||
corp_slug = "meridian-transit" # nullable
|
||||
tier_hint = 3 # nullable
|
||||
reserved = true
|
||||
```
|
||||
|
||||
**Import pipeline:** `import_economics.py` reads wiki TOMLs, preserves `reserved = true` rows across reruns, deletes and regenerates `reserved = false` rows each run. Paula's Stage 0–3 fulfillment operates on both tiers at runtime.
|
||||
|
||||
**Paula's schema extension flags:** Tyre's ARCH-3 schema is missing `population` and `economic_role` columns, both required for attractor priority sorting and compatibility scoring. These must be added. The `reserved` column's purpose is clarified: it means "wiki-authored canonical name" — not equivalent to `corp_id IS NOT NULL`.
|
||||
|
||||
---
|
||||
|
||||
### Q5 / Q6 — Full Multiplier Table and Logistics Column: RESOLVED (D-194 Authoritative)
|
||||
|
||||
Burnelli-Sheldon's D-194 (real claimed ID) is the authoritative table. It has 10 economic roles × 9 district types. Tyre's table from Round 2 notes (8×8) and Tyre's Round 3 addition of Logistics are superseded by D-194.
|
||||
|
||||
**D-194 district type columns:** Residential, Commercial, Industrial, Administrative, Logistics, Entertainment, Mixed, Transit, Specialized (9 types)
|
||||
|
||||
**Burnelli-Sheldon's political archetype modifiers (locked in D-194):**
|
||||
|
||||
| political_archetype | Modifiers |
|
||||
|--------------------|-----------|
|
||||
| CompanyTown | Adm −10, Ind +10, Log +5, Res +5 |
|
||||
| AdminCapital | Adm +20, Ent +5, Spe +5, Ind −15 |
|
||||
| FreePort | Com +15, Trn +10, Mix +5, Adm −15 |
|
||||
| Contested | No modifier (competing forces cancel) |
|
||||
| OrganicGrowth | Mix +15, Res +10, Ind −10, Adm −5 |
|
||||
|
||||
Floor at 3 applied after all modifiers. Paula's FreePort/Administrative flag: FreePort modifier Adm −15 on Transit/Port base Adm = 5 → net −10 → floor applies → value = 3. Correct; floor must be applied after all modifiers are stacked.
|
||||
|
||||
**Ozzie's new proposal:** `SyntheticPlacementReason` should inform default political archetype when no geographic trigger exists: `CorpExpansion` → CompanyTown, `PoliticalDecision` → AdminCapital, `PopulationOverflow` → OrganicGrowth. Filed for Phase 3 design.
|
||||
|
||||
---
|
||||
|
||||
## 2. D-Record Consolidation
|
||||
|
||||
Six real D-record IDs were claimed by Burnelli-Sheldon via the decision CLI. All other agents used placeholder numbering (D-C-XX, ARCH-N, GEN-N, D-C-NAME). Real IDs take precedence where topics overlap.
|
||||
|
||||
### Real D-Records (Claimed, D-194 through D-199)
|
||||
|
||||
| ID | Title | Agent | Overlap with placeholders |
|
||||
|----|-------|-------|--------------------------|
|
||||
| **D-194** | Three-Component District Mix Algorithm | Burnelli-Sheldon | Supersedes D-C28, GEN-3 |
|
||||
| **D-195** | Attractor-Matching Compatibility Matrix | Burnelli-Sheldon | Sub-component of D-C25/GEN-2 (D-C25 covers the full five-phase pipeline; D-195 is the scoring matrix) |
|
||||
| **D-196** | SettlementClass Enum and Active/Ghost Logic | Burnelli-Sheldon | Supersedes D-C26, GEN-6 |
|
||||
| **D-197** | prosperity_baseline Derivation Formula | Burnelli-Sheldon | Referenced in D-C28 body but not a separate record in other agents' lists |
|
||||
| **D-198** | Economic Simulation Independence from Layer 1-2 | Burnelli-Sheldon | Implicit in D-C18 (three-tier model) but not separately named |
|
||||
| **D-199** | 6-Field Minimum Economic Read Set | Burnelli-Sheldon | Extends prior workshop's D-C17; updates CityGenerationContext struct |
|
||||
|
||||
### Placeholder D-Records Still Needing Real IDs
|
||||
|
||||
These are ready to claim via `tooling/db/decision claim D <domain> "title"`. Grouped by domain:
|
||||
|
||||
**Architecture (build-time and data model):**
|
||||
|
||||
| Placeholder | Title | Ready to claim |
|
||||
|-------------|-------|----------------|
|
||||
| D-C18 | Three-Tier Execution Model | Yes |
|
||||
| D-C21 | Spatial Hierarchy — Eight Tiers with Locked Dimensions | Yes |
|
||||
| D-C33 / ARCH-1 | Heightmap BLOB Storage Schema (atlas_body_heightmaps) | Yes |
|
||||
| ARCH-2 / D-C34 | BodyWorldState Bevy Resource + LRU Cache | Yes |
|
||||
| ARCH-4 | body_radius_km Column on bodies | Yes |
|
||||
| ARCH-5 | Province Boundary Pre-Computation (atlas_province_boundaries) | Yes — new this round |
|
||||
| GEN-9 | atlas_feature_names Schema | Yes |
|
||||
|
||||
**Generation algorithms (runtime):**
|
||||
|
||||
| Placeholder | Title | Ready to claim |
|
||||
|-------------|-------|----------------|
|
||||
| D-C19 / GEN-7 | Background Generation Priority Queue + Rayon Infrastructure | Yes |
|
||||
| D-C20 | Fully Generative Placement (Amendment 3) | Yes |
|
||||
| D-C22 / GEN-1 | D8 Priority-Flood Drainage Routing | Yes |
|
||||
| D-C23 | Geographic Feature Tag Extraction (7 tags) | Yes |
|
||||
| D-C24 | Sub-Biome Variant Classification | Yes |
|
||||
| D-C25 / GEN-2 | Attractor-Matching Five-Phase Pipeline (references D-195 for matrix) | Yes |
|
||||
| D-C27 / GEN-5 | TerritorialStatus Priority-Ordered Derivation | Yes |
|
||||
| D-C29 / D-C-FO / D-C-AA | FoundingOrientation Enum + AttractorAssignment Enum | Yes (may split) |
|
||||
| D-C30 / D-C-SA | Five Explicit Political Archetype Spatial Patterns | Yes |
|
||||
| D-C31 / GEN-4 | BlockIrregularity from founding_age (Tyre's enum) | Yes |
|
||||
| D-C32 | Tile Condition Threshold Values (0.63/0.43/0.23) | Yes |
|
||||
| GEN-8 | WorldTier Enum Bug Fix | Yes — critical, claim first |
|
||||
|
||||
**Narrative (naming and content):**
|
||||
|
||||
| Placeholder | Title | Ready to claim |
|
||||
|-------------|-------|----------------|
|
||||
| D-C-NL | Naming Registers Lookup Table (Paula) | Yes |
|
||||
|
||||
---
|
||||
|
||||
## 3. Pre-Implementation Blockers — Data Quality Flags (Burnelli-Sheldon)
|
||||
|
||||
Three data gaps in systems.db that will block implementation immediately. **These must be addressed before Phase 3 begins.**
|
||||
|
||||
### Blocker 1: `bodies.founding_age_years` NULL for All 273 Inhabited Bodies
|
||||
|
||||
Component 3 of D-194 depends on this field. It is entirely unpopulated. Phase 1 implementation will run on fallback values only.
|
||||
|
||||
**Fallback (Burnelli-Sheldon):** Derive from `system_history.settlement_wave`:
|
||||
|
||||
| settlement_wave | founding_age_years (approx) | Age bracket |
|
||||
|----------------|-----------------------------|-------------|
|
||||
| origin | 500+ | mature |
|
||||
| wave_1 | 350 | mature |
|
||||
| wave_2 | 250 | established |
|
||||
| wave_3 | 150 | established |
|
||||
| wave_4 | 75 | young |
|
||||
| wave_5 | 30 | nascent |
|
||||
| unsettled | N/A | not inhabited |
|
||||
|
||||
**Action required:** Populate `bodies.founding_age_years` from wiki content as part of wiki/content work. Every inhabited body should have an approximate founding year.
|
||||
|
||||
### Blocker 2: `system_economy.economic_tier` and `distribution_index` NULL for 97% of Systems
|
||||
|
||||
290 of 300 `system_economy` rows have NULL for both fields. D-197 (prosperity_baseline formula) depends on `economic_tier`.
|
||||
|
||||
**Fallback (Burnelli-Sheldon):**
|
||||
```
|
||||
economic_tier_derived:
|
||||
population >= 5B → 5
|
||||
population >= 1B → 4
|
||||
population >= 100M → 3
|
||||
population >= 10M → 2
|
||||
else → 1
|
||||
|
||||
distribution_index fallback: "moderate" (NULL → moderate is the baseline)
|
||||
```
|
||||
|
||||
**Action required:** Either populate `system_economy` fields from wiki data, or formally document the fallback derivation as canonical (which removes the need for authored data for most systems). The fallback is economically sound; the lead should decide if it's sufficient for release.
|
||||
|
||||
### Blocker 3: `economic_role` Values Not Normalized in bodies Table
|
||||
|
||||
Inconsistent values that break the D-194 weight table lookup:
|
||||
|
||||
| Raw value | Canonical | Bodies affected |
|
||||
|-----------|-----------|----------------|
|
||||
| `"agriculture"` | `"agricultural"` | 16 bodies |
|
||||
| `"resource_extraction"` | `"extraction"` | 1 body |
|
||||
| `"coordination"` | `"service_mixed"` | 2 bodies (including Bunbury pop 800M) |
|
||||
| `"mixed-agriculture"` | `"agricultural"` | 1 body |
|
||||
| NULL | `"service_mixed"` | 1 body |
|
||||
|
||||
**Action required:** Add normalization step to `import_economics.py` as a data validation pass. Run on `make regen-db`. This is a schema migration, not a source data authoring task.
|
||||
|
||||
---
|
||||
|
||||
## 4. Paula's Schema Consistency Flags
|
||||
|
||||
### Flag 1: atlas_city_names Missing Required Columns
|
||||
|
||||
Tyre's locked ARCH-3 schema lacks `population` and `economic_role` columns. Both are required by the Stage 2 name fulfillment pipeline:
|
||||
- `population`: needed to sort named cities for attractor assignment priority (largest first within Tier B/C)
|
||||
- `economic_role`: needed for compatibility scoring (hard constraints H1-H4 + soft constraint table)
|
||||
|
||||
**Resolution:** Add both columns to ARCH-3 schema. Paula's D-C-NR has the extended schema. The `reserved` column is clarified as "wiki-authored canonical name" (not redundant with `corp_id`).
|
||||
|
||||
### Flag 2: Paula's Agricultural Commercial Weight Concern
|
||||
|
||||
Paula flagged the Round 2 table's Agricultural Commercial weight of 18 as too high. **Resolved by D-194:** Burnelli-Sheldon's real table sets Agricultural Commercial = 12. Paula's concern is addressed.
|
||||
|
||||
### Flag 3: FreePort Modifier Floor Interaction
|
||||
|
||||
FreePort modifier on Administrative: −15 (D-194). For Transit/Port economic_role, Administrative base weight = 5. After modifier: 5 − 15 = −10 → floor at 3. **Confirmed:** floor must be applied after all modifiers are stacked, not before.
|
||||
|
||||
### Flag 4: Contested Archetype Weight Modifier
|
||||
|
||||
Paula flagged this as missing. **Resolved by D-194:** Contested = no modifier ("competing forces cancel"). This is the most narratively coherent answer — two competing planning authorities produce a district mix that reflects neither faction's priorities.
|
||||
|
||||
### Flag 5: corporate_presence_score Derivation Undefined
|
||||
|
||||
Burnelli-Sheldon flagged this in their own document: `corporate_presence_score` is used by TerritorialStatus but its derivation formula is not specified. Proposed: `sum(health_metric for corps in province) / corp_count`. Zero corps = score of 0. **Tyre must confirm in implementation spec.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Complete Ticket Dependency Chain
|
||||
|
||||
Consolidated from Gestalt's and Tyre's Round 3 tickets. Tyre's more granular breakdown takes precedence.
|
||||
|
||||
### Tier 0 — Prerequisites (No Dependencies; All Can Start Immediately in Parallel)
|
||||
|
||||
| Ticket | Work | Effort |
|
||||
|--------|------|--------|
|
||||
| BUG-WorldTier | Fix `WorldTier` enum: `Peripheral\|Connected\|Core` → `Epicenter\|Regional\|Backwater\|Passage\|Waypoint`; update all match arms | 0.5d |
|
||||
| SCHEMA-bodies | `body_radius_km REAL` column on bodies | 0.25d |
|
||||
| SCHEMA-heightmaps | `atlas_body_heightmaps` DDL | 0.25d |
|
||||
| SCHEMA-city-names | `atlas_city_names` DDL + index (extended schema with population, economic_role) | 0.25d |
|
||||
| SCHEMA-feature-names | `atlas_feature_names` DDL + index | 0.25d |
|
||||
| SCHEMA-province-bounds | `atlas_province_boundaries` DDL + index | 0.25d |
|
||||
|
||||
All SCHEMA-* tickets can be grouped into a single migration PR.
|
||||
|
||||
**BUG-WorldTier is a hard prerequisite blocker for every generation implementation ticket.**
|
||||
|
||||
### Tier 1 — Python Pipeline (Depends on Tier 0 Schemas)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| PY-heightmap-import | `generate_atlas.py`: BLOB-pack elevation float32; INSERT into `atlas_body_heightmaps` | 1d | SCHEMA-heightmaps |
|
||||
| PY-province-bounds | `generate_atlas.py`: watershed extraction from DEM; store boundary polylines | 2d | SCHEMA-province-bounds, PY-heightmap-import |
|
||||
| PY-city-names-authored | `import_economics.py`: read `wiki/worlds/*.toml` `[[cities]]`; INSERT reserved=true | 1d | SCHEMA-city-names |
|
||||
| PY-city-names-corp | `import_economics.py`: generate corp-derived name rows for under-quota bodies | 1d | PY-city-names-authored |
|
||||
| PY-body-radius | `import_economics.py`: populate `body_radius_km` | 0.5d | SCHEMA-bodies |
|
||||
| PY-economic-role-normalize | `import_economics.py`: add economic_role normalization validation step (blocker 3) | 0.5d | — |
|
||||
|
||||
### Tier 2 — Rust Type Definitions (Depends on Tier 0)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-types-worldtier | Fix WorldTier enum in Rust; update all match arms | 0.5d | BUG-WorldTier |
|
||||
| RS-types-settlement | `SettlementClass`, `MismatchSeverity`, `AttractorAssignment`, `SyntheticPlacementReason` | 0.5d | RS-types-worldtier |
|
||||
| RS-types-district | Add `BlockIrregularity` to `DistrictSkeleton`; `SpatialArchetype`, `EconomicRole`, updated `DistrictType` enum | 0.5d | — |
|
||||
| RS-types-territorial | `TerritorialStatus` with correct variants | 0.5d | — |
|
||||
| RS-types-city-ctx | Finalize `CityGenerationContext` with all new fields (see D-199) | 1d | RS-types-settlement, RS-types-territorial |
|
||||
|
||||
### Tier 3 — Rust Core (Depends on Tier 1 + Tier 2)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-heightmap-load | `load_heightmap()` via bytemuck; integration into BodyWorldState init | 0.5d | PY-heightmap-import, RS-types-city-ctx |
|
||||
| RS-body-state | `BodyWorldState` struct + `GenerationCache` Bevy Resource + LRU(50) | 1.5d | RS-heightmap-load |
|
||||
| RS-drainage | D8 priority-flood drainage; attractor extraction; RiverNetwork construction | 3d | RS-body-state |
|
||||
| RS-attractor-types | `GeographicAttractor`, `AttractorType`, `CompatibilityMatrix` | 0.5d | RS-types-settlement |
|
||||
|
||||
### Tier 4 — Generation Algorithms (Depends on Tier 3)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-attractor-assign | Five-phase attractor assignment + D-195 compatibility matrix + Hungarian + synthetic overflow + mismatch flags | 4d | RS-drainage, RS-attractor-types, PY-city-names-authored |
|
||||
| RS-district-mix | D-194 three-component district mix + locked multiplier table + archetype modifiers (floor-after-modifiers) | 3d | RS-attractor-assign, RS-types-district |
|
||||
| RS-territorial | TerritorialStatus priority-ordered derivation; ProvinceWorldState population | 2d | RS-attractor-assign |
|
||||
| RS-block-irregularity | `BlockIrregularity::from_age_and_archetype()` + DistrictSkeleton integration | 1d | RS-district-mix |
|
||||
| RS-tile-conditions | Threshold cache with 0.63/0.43/0.23 invalidation for tile conditions | 1.5d | RS-district-mix |
|
||||
|
||||
### Tier 5 — Background + UI (Depends on Tier 4)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-bg-queue | `GenerationQueue` + rayon thread pool + priority ordering | 2d | RS-body-state |
|
||||
| RS-aho-corasick | `SystemNameIndex` + text scanning → generation trigger | 1d | RS-bg-queue |
|
||||
| UI-province-bounds | Godot planetary map: load `atlas_province_boundaries`; render as natural polylines + TerritorialStatus fill | 2d | PY-province-bounds |
|
||||
|
||||
### Dependency Graph (Critical Path)
|
||||
|
||||
```
|
||||
SCHEMA-heightmaps
|
||||
→ PY-heightmap-import
|
||||
→ RS-heightmap-load
|
||||
→ RS-body-state
|
||||
→ RS-drainage
|
||||
→ RS-attractor-assign
|
||||
→ RS-district-mix
|
||||
→ RS-tile-conditions
|
||||
```
|
||||
|
||||
**Critical path effort:** 0.25 + 1 + 0.5 + 1.5 + 3 + 4 + 3 + 1.5 = **14.75 dev-days**
|
||||
|
||||
**Total effort (all tiers, parallel where possible):** ~33 dev-days
|
||||
|
||||
**Parallel acceleration (two-agent split — Python team / Rust team):** calendar time ~20 days
|
||||
|
||||
---
|
||||
|
||||
## 6. New Items Introduced in Round 3
|
||||
|
||||
### Province Boundary Pre-Computation (ARCH-5 — New)
|
||||
|
||||
Not in prior rounds. Tyre introduced the `atlas_province_boundaries` table and the build-time Python extraction approach. This is a new schema ticket (SCHEMA-province-bounds) and a new Python ticket (PY-province-bounds) that was not in any Round 1-2 discussion.
|
||||
|
||||
### Naming Registers Lookup Table (Paula — New)
|
||||
|
||||
Paula's D-C-NL introduces the full naming architecture: seven registers (Geographic, Functional, Commemorative, Aspirational, Folk, Corporate, Dual), a lookup table by (DistrictType, WorldTier), and political archetype overrides. Names themselves are content work (Mellanie's domain); the register selection architecture is a locking decision.
|
||||
|
||||
### SyntheticPlacementReason → Political Archetype Mapping (Ozzie — New Proposal)
|
||||
|
||||
Ozzie proposes that when a settlement lacks a geographic trigger, `SyntheticPlacementReason` should determine its default political archetype: `CorpExpansion` → CompanyTown, `PoliticalDecision` → AdminCapital, `PopulationOverflow` → OrganicGrowth. This is a proposal; not yet locked.
|
||||
|
||||
### economic_role Normalization Ticket (Burnelli-Sheldon — New)
|
||||
|
||||
PY-economic-role-normalize is a new ticket not identified in prior rounds. It is a pre-implementation blocker.
|
||||
|
||||
---
|
||||
|
||||
## 7. Open Items After Round 3
|
||||
|
||||
### For Lead Resolution
|
||||
|
||||
| Item | What's needed |
|
||||
|------|--------------|
|
||||
| `corporate_presence_score` derivation | Confirm formula: `sum(health_metric for corps in province) / corp_count` |
|
||||
| `SyntheticPlacementReason` → political archetype (Ozzie proposal) | Lock or defer to Phase 3 design |
|
||||
| L3-Q7 (port/station direction) | Paula designed mechanism; Ozzie formally requested for Phase 4 scope; Gestalt classified deferred; lead final call |
|
||||
| Province watershed algorithm detail spec | Before PY-province-bounds ticket begins; brief technical sidequest recommended |
|
||||
| Q4 orbital station type | `SpatialArchetype::SpaceStation` as sixth variant? Deferred; lead disposition |
|
||||
| Populate `bodies.founding_age_years` | Content work; wiki authors (Miri domain) |
|
||||
| Confirm `system_economy` fallback is sufficient for release | Formal acknowledgment that 97% NULL is acceptable with fallback derivation |
|
||||
| Contested/OrganicGrowth archetype modifiers | D-194 is locked (Contested = no modifier, OrganicGrowth = Mix+15/Res+10/Ind-10/Adm-5). This is resolved. |
|
||||
|
||||
### For Implementation Tickets
|
||||
|
||||
- BUG-WorldTier: claim and file immediately (hard prerequisite for everything)
|
||||
- All placeholder D-records: claim real IDs before `decisions/` domain files are written
|
||||
- Extended `atlas_city_names` schema: add `population` and `economic_role` before PY-city-names-authored begins
|
||||
|
||||
### Items Confirmed Closed — No Further Discussion
|
||||
|
||||
| Item | Closed by |
|
||||
|------|----------|
|
||||
| Mismatch flag threshold | Lead two-tier decision |
|
||||
| founding_age → BlockIrregularity | All agents adopted; Tyre's enum is the stored field |
|
||||
| Province boundary legibility | ARCH-5 + map UI spec |
|
||||
| atlas_city_names population path | Two-tier authorship, both via import_economics.py |
|
||||
| Full multiplier table | D-194 authoritative (10 roles × 9 types) |
|
||||
| Contested archetype modifier | D-194: no modifier |
|
||||
| OrganicGrowth archetype modifier | D-194: Mix +15, Res +10, Ind −10, Adm −5 |
|
||||
| prosperity_baseline vs. prosperity_current field naming | D-197 locks both names; delta is always derived |
|
||||
| Self-contained district generation | Confirmed D-194 |
|
||||
| SeedChain (FNV-1a) | Carried from generation-cascade workshop (D-010) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Agent Positions Summary — Round 3
|
||||
|
||||
| Item | Gestalt | Tyre | Paula | Burnelli-Sheldon | Ozzie |
|
||||
|---|---|---|---|---|---|
|
||||
| Q1 (mismatch threshold) | Lead decision absorbed | Two-tier with Synthetic override on Hard | Integration with name pipeline | Validated against real data | Accepted |
|
||||
| Q2 (founding_age) | Probability weighting | BlockIrregularity enum (stored field) | Per-archetype layout_irregularity rules | Age bracket affects character_class | Minimum 0.05 floor; archetype age_weight |
|
||||
| Q3 (Province boundaries) | Already satisfied by terrain | ARCH-5 pre-computed polylines | Rendering chain specified | Confirmed correct | Map UI spec |
|
||||
| Q4 (city names path) | Wiki import pipeline | Two-tier authorship TOML+corp | Stage 0-3 pipeline, extended schema | Fallback derivations for missing data | Accepted |
|
||||
| Full multiplier table | Confirmed (ref Burnelli-Sheldon) | 8×8 table + Logistics (superseded by D-194) | Flag on Agricultural Commercial weight | **D-194: 10×9 authoritative table** | Accepted |
|
||||
| Ticket chain | 11 tickets, 13.5d critical path | 22 tickets, 14.75d critical path | Data quality flags | Data quality blockers | PX requirements |
|
||||
|
||||
---
|
||||
|
||||
*Round 3 complete. All Round 2 open questions resolved. Six real D-records (D-194 through D-199). Three data quality pre-implementation blockers identified. Ticket chain: 22+ tickets, 14.75-day critical path. Workshop outcomes document follows.*
|
||||
@@ -0,0 +1,623 @@
|
||||
---
|
||||
title: "Tyre — Round 1: Technical Inventory and Framing"
|
||||
description: "Per-layer technical inventory for the planet-down cascade: what exists, what's novel, what's schema, prerequisite ordering, spatial hierarchy dimensions, and performance analysis"
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: planet-down-cascade
|
||||
agent: tyre
|
||||
round: 1
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Round 1: Technical Inventory and Framing
|
||||
|
||||
**Tyre — Technical Architect**
|
||||
|
||||
---
|
||||
|
||||
## Priority 0: The Architectural Pivot
|
||||
|
||||
My Round 3 output from the prior workshop designed Layers 1-2 as Python tooling (`generate_regional.py`). The consultant review overrides that entirely. This section documents exactly what changes and what carries forward, so the workshop doesn't accidentally build against the wrong model.
|
||||
|
||||
### What the pivot changes
|
||||
|
||||
**Was (my Round 3 design):**
|
||||
- Layer 1-2: Python tooling, runs in `generate_regional.py`, output committed to systems.db
|
||||
- 64×32 regional biome grid: stored in `atlas_regional_biomes`, fully populated at build time
|
||||
- Settlement positions, road graph, territorial status: all in systems.db, pre-computed offline
|
||||
|
||||
**Is now (consultant Amendment 1):**
|
||||
- Layer 1-2: Rust runtime, runs on background threads, output lives in session state
|
||||
- 64×32 regional biome grid: still built at build time (atlas UI needs it), but sub-biome enrichment and geographic feature extraction run at runtime
|
||||
- Settlement positions, road graph, territorial status: derived at runtime per body, cached in session memory
|
||||
|
||||
The Python version of Layer 1-2 becomes a reference/validation tool, not the production implementation. Both must produce identical output from the same inputs — this is a regression-testing requirement.
|
||||
|
||||
### What carries forward without change
|
||||
|
||||
- The **heightmap and 512×256 biome grid** are still produced by `generate_atlas.py` at build time. No change here.
|
||||
- The **64×32 regional biome grid** (`atlas_regional_biomes` table) is still populated at build time. I'm proposing the sub-biome columns also be computed at build time since `generate_atlas.py` already has terrain in memory — this saves the Rust runtime from re-running the coarse grid analysis.
|
||||
- The **`CityGenerationContext` struct** and the **Phase 3 → Phase 5 handoff** design from Round 3 carries forward with amendments (see Layer 3 section).
|
||||
- The **SeedChain (FNV-1a)**, **city-local coordinate system**, **GeneratorChunkData upgrade**, and **DistrictSkeleton Stages 1-2** all carry forward unchanged.
|
||||
- The **Road graph tables** (`atlas_road_nodes`, `atlas_road_edges`) — the schema carries forward, but the data is now generated at Rust runtime, not committed to systems.db.
|
||||
|
||||
### The "Session DB" question
|
||||
|
||||
The consultant uses the term "Session DB (reproducible from seed)" for Layer 1-2 output. This needs a concrete definition before implementation can begin.
|
||||
|
||||
**My recommendation:** This is not a database at all — it's a `BodyWorldState` Bevy Resource keyed by body_id, held in memory during the session.
|
||||
|
||||
```rust
|
||||
pub struct BodyWorldState {
|
||||
pub body_id: String,
|
||||
pub river_network: RiverNetwork, // regional-res river polylines + confluence points
|
||||
pub settlements: Vec<GeneratedSettlement>, // all placed settlements, latent + active
|
||||
pub road_graph: RoadGraph, // nodes + edges with maintenance authority
|
||||
pub territorial_grid: Vec<TerritorialCell>, // 64×32 cells with land_use + status
|
||||
}
|
||||
|
||||
pub struct GenerationCache {
|
||||
pub bodies: HashMap<String, BodyWorldState>,
|
||||
}
|
||||
```
|
||||
|
||||
The cache is populated by the background generation queue. The player's current body is always present. Remote bodies are populated ahead of player movement by the priority queue. Nothing is serialized — on session resume, generation reruns from systems.db inputs + world seed. This satisfies "reproducible from seed."
|
||||
|
||||
---
|
||||
|
||||
## Layer 1 — Empty World
|
||||
|
||||
### Technical inventory
|
||||
|
||||
**What already exists:**
|
||||
|
||||
`planet_simulation.py` produces:
|
||||
- `river_grid: bool (256, 512)` — which cells carry rivers
|
||||
- `rivers: list of [(row,col), ...]` — polylines in grid coords
|
||||
- `elevation: float32 (256, 512)` — the full heightmap
|
||||
- `biome: int8 (256, 512)` — biome classification per pixel
|
||||
- `surface_water: bool (256, 512)` — ocean/lake mask
|
||||
|
||||
These are Python algorithms. The drainage model is a downhill flow accumulation from moisture-seeded source cells. The output is a boolean river grid + polylines.
|
||||
|
||||
`generate_atlas.py` currently stores:
|
||||
- City positions (authored, now being replaced)
|
||||
- Road pixel-paths (authored, now being replaced)
|
||||
- Gate terminal POIs
|
||||
- Does NOT store the 512×256 heightmap as a retrievable BLOB
|
||||
|
||||
**Critical gap: the heightmap is not in systems.db.** The Rust runtime needs the heightmap to run drainage. It must be stored at build time.
|
||||
|
||||
### What needs to be built — Layer 1
|
||||
|
||||
**Schema addition (build time, Python adds to systems.db):**
|
||||
|
||||
```sql
|
||||
-- New: per-body heightmap blob for runtime drainage
|
||||
CREATE TABLE IF NOT EXISTS atlas_body_heightmaps (
|
||||
body_id TEXT PRIMARY KEY REFERENCES bodies(body_id),
|
||||
width INTEGER NOT NULL DEFAULT 512,
|
||||
height INTEGER NOT NULL DEFAULT 256,
|
||||
elevation_f32_le BLOB NOT NULL, -- packed float32 LE, row-major
|
||||
sea_level REAL NOT NULL, -- elevation threshold from simulation
|
||||
seed INTEGER NOT NULL -- body seed used for simulation
|
||||
);
|
||||
|
||||
-- Extend atlas_regional_biomes (already exists) with new columns:
|
||||
-- sub_biome_variant TEXT (3-4 values per biome class, computed at build time)
|
||||
-- terrain_modification_cost REAL (0.0 = easy to settle, 1.0 = impassable)
|
||||
-- geographic_feature_tags TEXT (JSON array of feature tags)
|
||||
```
|
||||
|
||||
`sub_biome_variant` and `terrain_modification_cost` can be computed at build time since `generate_atlas.py` has the terrain dict in memory. This is cheap to add (extend Layer B downsampling code) and saves the Rust runtime from needing to redo this analysis.
|
||||
|
||||
**Rust algorithm work (runtime, background thread):**
|
||||
|
||||
*D8 drainage routing:*
|
||||
```
|
||||
Input: elevation BLOB loaded from atlas_body_heightmaps
|
||||
Algorithm: Priority-flood D8 flow routing
|
||||
- Fill sinks using priority queue (Planchon-Darboux or similar)
|
||||
- Accumulate flow: each cell routes to lowest downhill neighbor
|
||||
- Cells with accumulation > threshold are river cells
|
||||
Output: river accumulation grid (u32 per cell)
|
||||
```
|
||||
|
||||
D8 on 131,072 cells in Rust: ~50ms. This is the most expensive Layer 1 operation and is comfortably within the performance target.
|
||||
|
||||
*Geographic attractor extraction:*
|
||||
```
|
||||
From river accumulation grid:
|
||||
- Confluence: cell where two or more upstream branches merge
|
||||
(accumulation jumps non-monotonically from upstream to current)
|
||||
- River mouth: river cell adjacent to ocean/lake cell
|
||||
- Mountain pass: high-roughness cell in atlas_regional_biomes adjacent
|
||||
to significantly lower roughness cells on opposite sides
|
||||
- Coastal harbor: coastal cell with low terrain_modification_cost
|
||||
(natural harbor = sheltered, shallow approach)
|
||||
- Arable plain: large contiguous low-roughness non-ocean region
|
||||
(flood-fill counting connected cells above water_fraction threshold)
|
||||
```
|
||||
|
||||
Output: `Vec<GeographicAttractor>` with location (grid_row, grid_col), attractor_type, and quality score.
|
||||
|
||||
*Sub-biome variant generation (seed-derived per cell):*
|
||||
|
||||
This is lightweight: for each regional cell, use FNV-1a child_seed to pick from 3-4 variants for the cell's biome_class. The `sub_biome_variant` column can be populated at build time if we want it in systems.db for the atlas UI, or derived at runtime. Given the atlas UI doesn't currently display sub-biome variants, I recommend runtime derivation with a deferred build-time population if the UI needs it.
|
||||
|
||||
### What is genuinely novel vs. reuse
|
||||
|
||||
| Work | Novel or Reuse | Notes |
|
||||
|------|---------------|-------|
|
||||
| D8 drainage routing | **Novel — new Rust code** | Python version exists as reference; must be equivalent |
|
||||
| Geographic attractor extraction | **Novel — new Rust code** | No equivalent exists |
|
||||
| Sub-biome variant derivation | **Lightweight novel** | Pure FNV-1a table lookup, ~10 lines |
|
||||
| Heightmap BLOB storage | **Schema only** | Add column + populate in generate_atlas.py |
|
||||
| `terrain_modification_cost` | **Algorithm + schema** | Derived from roughness + biome at downsampling time |
|
||||
|
||||
### Layer 1 questions requiring answers before implementation
|
||||
|
||||
**L1-TECH-1 (prerequisite, blocks all): Heightmap storage format.**
|
||||
Float32 LE blob is my recommendation. Size: 512×256×4 = 512KB per body. ~400 bodies = ~200MB uncompressed. SQLite stores BLOBs fine; DEFLATE compression in the Python write path would halve this. Should the loader decompress, or is raw float fine? Decision needed before Layer 1 Rust code can be written.
|
||||
|
||||
**L1-TECH-2: River resolution for walkable world.**
|
||||
The drainage algorithm produces a per-cell accumulation grid at 512×256 resolution. River courses at tile precision (Phase 5) can be derived from this at runtime via FNV-1a SeedChain — same pattern as chunk generation. My position: store the drainage accumulation grid at regional resolution (64×32) in the session state; derive tile-precision river courses in Phase 5 from the drainage network + chunk_seed. This is coherent with the stored-vs-derived boundary.
|
||||
|
||||
**L1-TECH-3: Economic role as biome prior (L1-Q4).**
|
||||
Burnelli-Sheldon proposes using `bodies.economic_role` to nudge sub-biome distribution toward plausibility. Technical cost: trivial (add a weight multiplier in the sub-biome variant sampling). My position: **include it**. The self-contradiction risk (agricultural world generating predominantly badlands) is real and cheap to prevent. One lookup table, one weight adjustment at sampling time.
|
||||
|
||||
**L1-TECH-4 (deferred): Lake placement.**
|
||||
The brief flags "scatter lakes with biome/river basin dependency." This is a Layer 1 enrichment, not the minimum viable slice. `planet_simulation.py` already produces `surface_water` which covers ocean/coastal lakes; inland lake placement is a future enhancement. Defer.
|
||||
|
||||
---
|
||||
|
||||
## Layer 2 — Population Overlay
|
||||
|
||||
### Technical inventory
|
||||
|
||||
**What already exists:**
|
||||
- `generate_atlas.py` has city placement (replaced by fully generative approach)
|
||||
- `generate_atlas.py` has A* road routing between cities (algorithm reusable concept, must be Rust)
|
||||
- `atlas_road_nodes`, `atlas_road_edges` schema from my Round 3 (carry forward, now populated at runtime)
|
||||
- `atlas_regional_biomes.land_use` column design from my Round 3
|
||||
|
||||
**What's new from Amendment 3 (fully generative placement):**
|
||||
- No authored city positions in markers.json anymore (markers.json stripped to topographic features)
|
||||
- Name reservation system: cities that are corporate headquarters (from `corporations` table) must be placed at plausible attractors
|
||||
- Attractor-matching algorithm: assign N named cities to M geographic attractors
|
||||
|
||||
### Attractor-matching algorithm
|
||||
|
||||
This is the most algorithmically interesting new piece. It's a bipartite assignment problem.
|
||||
|
||||
```
|
||||
Inputs:
|
||||
- named_cities: Vec<{name, economic_role, population, is_hq_for_corp}> from systems.db
|
||||
- attractors: Vec<GeographicAttractor> from Layer 1 output
|
||||
- body_seed: u64
|
||||
|
||||
Algorithm (greedy, deterministic):
|
||||
1. Sort named_cities by population descending (largest cities assigned first)
|
||||
2. Score each (city, attractor) pair:
|
||||
base_score = attractor.quality_score
|
||||
role_bonus:
|
||||
{transit, commercial} → +0.4 if attractor_type == CoastalHarbor
|
||||
{extraction, mining} → +0.3 if attractor_type == ResourceConcentration
|
||||
{agricultural} → +0.3 if attractor_type == ArablePlain
|
||||
{administrative_center} → +0.2 if attractor_type == Defensible
|
||||
any → +0.2 if attractor_type == RiverConfluence (universal settlement attractor)
|
||||
hq_constraint:
|
||||
if is_hq_for_corp AND attractor_type incompatible with corp's primary_operation:
|
||||
score → 0.0 (hard reject)
|
||||
3. Greedy assignment: for each city in sorted order, assign to highest-scored
|
||||
available attractor (remove from pool after assignment)
|
||||
4. If cities > attractors: place overflow cities near existing settlements
|
||||
(proximity placement within [15%-40%] of body scale from nearest assigned city)
|
||||
5. Derive FoundingOrientation from assigned attractor_type:
|
||||
CoastalHarbor → PortFacing
|
||||
Defensible → DefenseFacing
|
||||
ResourceConcentration → ExtractionFacing
|
||||
RiverConfluence → RiverFacing (default for most settlements)
|
||||
ArablePlain → AgrarianFacing
|
||||
|
||||
Output: Vec<PlacedCity> with (city_id, name, position_grid_row, position_grid_col, founding_orientation)
|
||||
```
|
||||
|
||||
This runs in O(N×M) where N = cities (~20-30 per body) and M = attractors (~50-100). Negligible compute.
|
||||
|
||||
**Name reservation data structure needed:**
|
||||
The matching algorithm requires knowing which cities are name-locked (corporate HQ cross-references). This currently requires a JOIN between `bodies` → `atlas_cities` → `corporations`. With fully generative placement, `atlas_cities` no longer has positions at build time — it's populated by the runtime generator. The systems.db schema needs a `atlas_city_names` table (name + economic_role + population + is_hq_for_corp) that the runtime generator reads.
|
||||
|
||||
New schema for Amendment 3:
|
||||
```sql
|
||||
-- Replace atlas_cities authored positions with name reservation table
|
||||
CREATE TABLE IF NOT EXISTS atlas_city_names (
|
||||
name_id TEXT PRIMARY KEY, -- "{body_id}/{city_index}"
|
||||
body_id TEXT NOT NULL REFERENCES bodies(body_id),
|
||||
city_name TEXT NOT NULL,
|
||||
economic_role TEXT, -- from bodies table or override
|
||||
population INTEGER NOT NULL,
|
||||
political_archetype TEXT, -- derived or authored override
|
||||
political_archetype_override TEXT, -- NULL = always derived
|
||||
prosperity_override REAL, -- NULL = always derived
|
||||
hq_for_corp TEXT, -- corp_id if this city is a HQ, else NULL
|
||||
FOREIGN KEY (hq_for_corp) REFERENCES corporations(corp_id)
|
||||
);
|
||||
```
|
||||
|
||||
This table is populated at build time from wiki body definitions. The runtime generator reads it and produces actual positions.
|
||||
|
||||
### Sub-settlement placement
|
||||
|
||||
From the brief's minimum viable Layer 2: mining camps + trade waypoints.
|
||||
|
||||
**Mining camps (ExtractionFacing sub-settlements):**
|
||||
```
|
||||
trigger: body.economic_role ∈ {extraction, mining}
|
||||
AND corp_presence has corps with relevant commodity
|
||||
count: floor(qualifying_corps / 2), minimum 1
|
||||
placement per camp:
|
||||
seed = child_seed(body_seed, camp_index)
|
||||
distance_from_tether_grid_cells = lerp(0.15, 0.40, rng_01(seed)) × body_scale_cells
|
||||
bearing = rng_0_2pi(child_seed(seed, 1))
|
||||
position = tether_city_pos + polar_to_grid(distance, bearing)
|
||||
snap to nearest Wilderness or Industrial regional cell
|
||||
```
|
||||
|
||||
**Trade waypoints:**
|
||||
```
|
||||
trigger: road edge length > WAYPOINT_ROAD_THRESHOLD (e.g., 12 regional cells)
|
||||
placement: geometric midpoint of road edge
|
||||
count: 1 per qualifying edge
|
||||
```
|
||||
|
||||
### Road graph generation (Rust)
|
||||
|
||||
Layer C road graph from my Round 3 was authored (converting pixel paths). Now it's fully generated. The algorithm:
|
||||
|
||||
```
|
||||
Input: PlacedCity list + geographic attractor list
|
||||
Algorithm:
|
||||
1. Force-connect all cities (minimum spanning tree using Euclidean distance)
|
||||
2. Add secondary connections: cities within [30%-60%] of body scale get direct roads
|
||||
if their MST path exceeds 1.5× Euclidean distance (avoids long detours)
|
||||
3. Route each edge using A* on a terrain cost grid:
|
||||
COST_WATER = impassable
|
||||
COST_MOUNTAIN (roughness > 0.7) = 10.0
|
||||
COST_RIVER = 0.5 (rivers as preferred routes — follow valleys)
|
||||
COST_FLAT = 1.0
|
||||
4. Assign MaintenanceAuthority per edge based on political context:
|
||||
Capital→RegionalCity: Administrative
|
||||
Corp tether city: Corporate
|
||||
Remote→Remote: Trade
|
||||
Abandoned destination: Abandoned
|
||||
5. Place sub-settlement nodes (waypoints at midpoints of long edges)
|
||||
|
||||
Output: RoadGraph { nodes: Vec<RoadNode>, edges: Vec<RoadEdge> }
|
||||
```
|
||||
|
||||
The terrain cost grid is the 64×32 regional grid — A* on 2048 cells is trivial even for full MST construction.
|
||||
|
||||
### TerritorialStatus derivation
|
||||
|
||||
Post-processing pass over regional cells after settlement + road placement:
|
||||
|
||||
```
|
||||
Per-cell algorithm (after road graph exists):
|
||||
if cell.water_fraction > 0.6: TerritorialStatus::Ocean
|
||||
if cell within city footprint radius: CoreTerritory
|
||||
if cell within 2 cells of road edge AND within 5 cells of nearest settlement:
|
||||
CoreTerritory if nearest settlement is City/Town
|
||||
FrontierTerritory if nearest settlement is Outpost
|
||||
ExtractiveZone if road MaintenanceAuthority == Corporate
|
||||
if cell has ResourceConcentration attractor AND Corporate corp present: ExtractiveZone
|
||||
if two settlements with overlapping road networks and different political archetypes
|
||||
both claim this cell: ContestZone
|
||||
if no settlement within 8 cells AND no road within 3 cells: WildernessBuffer
|
||||
if settlement was placed but corp_financial_state health_metric = 0 (exhausted):
|
||||
AbandonedZone
|
||||
```
|
||||
|
||||
**Quantitative thresholds for L2-Q5** — I'm proposing these as defaults tunable by workshop:
|
||||
- CoreTerritory: settlement_density > 1 city per 4 cells OR road within 2 cells of City/Town
|
||||
- FrontierTerritory: settlement present + road within 5 cells OR within 3 cells of City/Town but no road
|
||||
- ExtractiveZone: `corp_presence` with qualifying commodity + road marked Corporate
|
||||
- ContestZone: two CoreTerritory zones from different political archetypes overlap within 2 cells
|
||||
- WildernessBuffer: no settlement within 8 cells + no road within 3 cells
|
||||
- AbandonedZone: settlement placed + no active corp_presence + economic basis metric ≤ 0
|
||||
|
||||
### Latent settlement storage (L2-Q6)
|
||||
|
||||
The brief asks where the active/ghost flag lives. My answer: **in the Rust session state, not in systems.db.** The latent settlement positions are generated at runtime (seed-derived). The active/ghost state is a function of current `corp_financial_state.health_metric` — a simulation variable. Having this in systems.db would require runtime writes to the asset store, which violates the asset pipeline rules.
|
||||
|
||||
In practice: the `GenerationCache::BodyWorldState.settlements` Vec contains all latent positions. Each settlement has an `is_active: bool` flag that the economic simulation updates via event. When corp financial health drops below threshold, simulation fires an event that flips the flag. No systems.db write required.
|
||||
|
||||
### Layer 2 questions requiring answers
|
||||
|
||||
**L2-TECH-1 (prerequisite, blocks schema): `atlas_city_names` table design.**
|
||||
The fully generative model requires this table at build time. Need to confirm it replaces `atlas_cities` or extends it. My recommendation: replace — `atlas_cities` currently stores authored positions which are now runtime-generated. The build-time table is `atlas_city_names` (name + economic role + population + hq constraint). Runtime-generated positions go into `BodyWorldState`, never systems.db.
|
||||
|
||||
**L2-TECH-2: Multiple cities competing for best attractor.**
|
||||
The greedy algorithm handles this by population order. But it can produce suboptimal global assignments (largest city takes the best harbor even if a smaller city would be more plausible there). This is a design decision: optimal assignment (Hungarian algorithm, O(N³)) vs. greedy (O(NM)). For N ≤ 30, the Hungarian algorithm is trivially fast. Recommend Hungarian for correctness, greedy for first implementation.
|
||||
|
||||
**L2-TECH-3: Road routing across the 512→64 resolution boundary.**
|
||||
The road routing runs on the 64×32 regional grid. But road paths between close cities (< 2 regional cells apart) have low resolution. For the minimum slice, this is acceptable. For the Atlas UI, pixel-precision paths are desirable. Proposal: regional resolution roads for functional connectivity (Phase 2 minimum), then an optional higher-resolution routing pass for Atlas UI display.
|
||||
|
||||
---
|
||||
|
||||
## Layer 3 — City-Level Planning (carry-forward analysis)
|
||||
|
||||
Layer 3 is the Phase 1/Phase 5 Rust-runtime generation that the prior workshop designed in depth. This section covers only what changes and what new questions the consultant's amendments raise.
|
||||
|
||||
### What carries forward without change
|
||||
|
||||
- DistrictSkeleton Stages 1-2 (D-C3)
|
||||
- City-local coordinate system (D-C5)
|
||||
- SeedChain / FNV-1a (D-C6)
|
||||
- GeneratorChunkData upgrade (D-C7)
|
||||
- Prosperity + perimeter_treatment on DistrictSkeleton (from Paula, confirmed)
|
||||
- D-C4 district count formula
|
||||
|
||||
### CityGenerationContext update needed
|
||||
|
||||
The struct from my Round 3 needs amendments for the new model:
|
||||
|
||||
```rust
|
||||
pub struct CityGenerationContext {
|
||||
pub body_id: String, // NEW — consultant open item 1
|
||||
pub city_id: String,
|
||||
pub city_name: String, // NEW — for internal monologue / UI
|
||||
pub political_archetype: PoliticalArchetype,
|
||||
pub prosperity_baseline: f32, // RENAMED from prosperity_index (CL-Q1)
|
||||
pub surrounding_biome: BiomeClass,
|
||||
pub road_entry_directions: Vec<CardinalDirection>,
|
||||
pub footprint_radius_km: f32,
|
||||
pub founding_orientation: FoundingOrientation, // NEW — Layer 2 output
|
||||
pub world_tier: WorldTier, // FIX — must use correct enum
|
||||
pub district_grid_width: u8, // from D-C4 formula
|
||||
pub district_count: u8, // from D-C4 formula
|
||||
}
|
||||
```
|
||||
|
||||
This struct is populated from `BodyWorldState.settlements` (Layer 2 runtime output), not from systems.db, since cities no longer have static positions in systems.db.
|
||||
|
||||
### WorldTier enum — still a critical blocker
|
||||
|
||||
```rust
|
||||
// CURRENT (wrong):
|
||||
pub enum WorldTier { Peripheral, Connected, Core }
|
||||
|
||||
// REQUIRED:
|
||||
pub enum WorldTier { Epicenter, Regional, Backwater, Passage, Waypoint }
|
||||
```
|
||||
|
||||
This has been a known blocker since Round 1 of the prior workshop. It must be fixed before any Layer 3 code can run correctly. First ticket, 0.5 dev-days.
|
||||
|
||||
### 10×9 Weight Table — consultant amendment
|
||||
|
||||
Amendment 6 removes the weight table from Given Facts and challenges it to prove it produces plausible settlements. The critique: energy-producing cities have zero Commercial and zero Entertainment. This is wrong — people who live in mining towns still drink.
|
||||
|
||||
**Technical proposal for replacement approach:**
|
||||
|
||||
Population + settlement_age → baseline district mix (minimum viable service requirements):
|
||||
|
||||
```
|
||||
baseline_residential_frac = 0.35 (people live everywhere)
|
||||
baseline_commercial_frac = clamp(0.10 + log10(population/1000) × 0.05, 0.10, 0.25)
|
||||
baseline_entertainment_frac:
|
||||
population < 10000: 0.0
|
||||
population 10k-100k: 0.05
|
||||
population > 100k: 0.10
|
||||
baseline_administrative_frac:
|
||||
WorldTier Waypoint: 0.0
|
||||
Passage: 0.05
|
||||
Backwater+: 0.10
|
||||
```
|
||||
|
||||
Then economic role modifies the remaining fraction (after baseline is allocated):
|
||||
```
|
||||
remaining = 1.0 - baseline_total
|
||||
distribute remaining by economic_role weight table
|
||||
```
|
||||
|
||||
This guarantees no settlement above minimum population has zero of any essential district type. The weight table still governs the CHARACTER and PROPORTION of above-baseline districts. Amendment 6 is satisfied.
|
||||
|
||||
The new weight table is now a "secondary allocation modifier" for the non-baseline fraction only, which means the 10×9 table values need to be renormalized. This is arithmetic work, not design work.
|
||||
|
||||
### L3-Q1 — FoundingOrientation spatial effect
|
||||
|
||||
My position: **spatial orientation change, not just gradient direction.** The cost is low (it's a grid rotation flag, not a new algorithm) and the legibility payoff is high. A PortFacing city where the industrial districts are inland and the transit districts are on the water edge is immediately readable.
|
||||
|
||||
Implementation: add `orientation_rotation: CardinalDirection` to district placement. The district at grid position (0,0) is always "closest to FoundingOrientation direction." The weight table biases which DistrictType goes there.
|
||||
|
||||
### L3-Q2 — political_archetype as spatial arrangement
|
||||
|
||||
My position: **explicit arrangement patterns, but simple.** Three patterns, hardcoded:
|
||||
- CompanyTown: spine layout (facility at col=0 or col=max, residential fills other cols)
|
||||
- AdminCapital: center layout (administrative district forced to center grid position)
|
||||
- FreePort: distributed layout (no single forced center; Transit/Commercial districts take all 4 corners)
|
||||
- Others: emerge from weight table + FoundingOrientation
|
||||
|
||||
Adding three explicit spatial arrangement rules is 30-40 lines of Rust code. The legibility benefit is significant.
|
||||
|
||||
---
|
||||
|
||||
## Layer 4 — Street-Level Rendering (carry-forward only)
|
||||
|
||||
Layer 4 is largely settled from prior workshop work. New items only:
|
||||
|
||||
### L4-Q1 — Economics-variable rendering mechanism
|
||||
|
||||
My position: **threshold-based cache invalidation (option c from the brief).**
|
||||
|
||||
```rust
|
||||
pub struct ChunkConditionState {
|
||||
pub prosperity_snapshot: f32, // prosperity_current when last computed
|
||||
pub tile_conditions: Vec<TileCondition>, // 4096 entries
|
||||
}
|
||||
|
||||
enum TileCondition { Intact, Worn, Cracked, Broken }
|
||||
```
|
||||
|
||||
Cache invalidation rule: when `|prosperity_current - prosperity_snapshot| > 0.05`, recompute `tile_conditions` for the chunk. The 0.05 threshold means ~20 condition bands, smooth enough for visual continuity, cheap enough computationally (most chunks never recompute).
|
||||
|
||||
Compute-on-demand (option b) is too expensive — prosperity changes happen on every tick cycle for active economic systems. Bake-at-generation (option a) violates the determinism rule.
|
||||
|
||||
### L4-Q6 — prosperity_delta
|
||||
|
||||
The two-field model `(prosperity_baseline, prosperity_current)` is the correct implementation. `prosperity_delta = prosperity_current - prosperity_baseline` is always derived, never stored. The rendering system receives both fields. Agreed.
|
||||
|
||||
### L4-Q4 — Interior generation trigger
|
||||
|
||||
My position: **option (a), pre-generate when player is within N tiles of any door.** N = 32 tiles (one chunk width). This avoids a generation stall at the threshold and the compute cost is bounded — each building interior is small, and only doors within the loaded chunk radius are candidates.
|
||||
|
||||
---
|
||||
|
||||
## Spatial Hierarchy — Dimension Locking
|
||||
|
||||
The consultant directs us to lock concrete dimensions at every tier and define the formula mapping body size to area count. This is my proposal; the workshop should either confirm or amend.
|
||||
|
||||
### The dimension table
|
||||
|
||||
| Tier | Name | Base Unit | Physical Size (reference body) | Notes |
|
||||
|------|------|-----------|---------------------------------|-------|
|
||||
| 0 | Chunk | 64×64 sim tiles | 32m × 32m | Immutable — all prior architecture depends on this |
|
||||
| 1 | Block | 128×128 sim tiles | 64m × 64m | 2×2 chunks |
|
||||
| 2 | District | 512×512 sim tiles | 256m × 256m | 4×4 blocks, 8×8 chunks |
|
||||
| 3 | Region | city district grid bounding box | varies per city size | Not a fixed grid cell |
|
||||
| 4 | Province | 1 drainage basin on regional grid | ~60-200km on reference body | Defined by watershed analysis, not fixed grid |
|
||||
| 5 | Area | 1 contiguous terrain feature zone | ~400-1500km on reference body | Coastlines, continents, major highlands |
|
||||
| 6 | Body | full planet/moon/station surface | varies | Absorbed here |
|
||||
| 7 | System | star system | — | Gate graph |
|
||||
|
||||
### Reference body calibration
|
||||
|
||||
Reference body: temperate terrestrial, radius ~5000km (slightly smaller than Earth).
|
||||
Atlas heightmap: 512×256 pixels covering the full surface.
|
||||
Pixel scale at equator: ~2π × 5000 / 512 ≈ 61km per pixel.
|
||||
Regional cell (8×8 pixels): ~500km × 250km.
|
||||
|
||||
This seems large, but the 64×32 grid covers a whole planet — these cells are continent-scale. Province and Area are derived from natural boundaries on this grid, so their sizes are naturally variable.
|
||||
|
||||
**Province definition (concrete):** A Province is a drainage basin identified by watershed analysis on the 64×32 regional grid. Typical size: 2-8 regional cells = ~1000-4000km² on the reference body. For gameplay, a Province is "the region you'd see on one regional travel map."
|
||||
|
||||
**Area definition (concrete):** An Area is a connected set of regional cells with the same dominant terrain class (ocean, highland, lowland, polar). Typical: 5-15 regional cells across. One Area per continent/ocean basin.
|
||||
|
||||
### Body-size-to-area-count formula
|
||||
|
||||
```
|
||||
area_count = round(sqrt(body_surface_area_km2 / AREA_BASE_SIZE_KM2))
|
||||
|
||||
Where AREA_BASE_SIZE_KM2 = 6,000,000 km² (approximately the size of Australia —
|
||||
a plausible single continent/ocean feature)
|
||||
|
||||
For reference body (radius 5000km, surface area ~314,000,000 km²):
|
||||
area_count = round(sqrt(314,000,000 / 6,000,000)) = round(7.24) = 7
|
||||
|
||||
For small moon (radius 500km, surface area ~3,140,000 km²):
|
||||
area_count = round(sqrt(3,140,000 / 6,000,000)) = round(0.72) = 1
|
||||
|
||||
For large super-terrestrial (radius 10,000km, surface area ~1,256,000,000 km²):
|
||||
area_count = round(sqrt(1,256,000,000 / 6,000,000)) = round(14.47) = 14
|
||||
```
|
||||
|
||||
This formula is smooth, has reasonable results at the extremes, and produces integer counts. The formula should be validated against the actual bodies in the Reach when locked.
|
||||
|
||||
**Important:** The `surface_area_km2` (or body_radius) field needs to be in the `bodies` table. Currently `bodies` has `surface_gravity` and other physical fields — check whether radius/surface area is already there. If not, this is a schema addition needed before the formula can run.
|
||||
|
||||
### Region (tier 3) clarification
|
||||
|
||||
Region is NOT a fixed grid cell in the spatial hierarchy — it's the city + its immediate service area. The bounding box is `city_footprint_radius_km × 2` plus some hinterland buffer. This is computed from `CityGenerationContext.footprint_radius_km` at Layer 3 time. Region boundaries are fuzzy and can overlap between nearby cities (contested regions, per TerritorialStatus).
|
||||
|
||||
This means Region is a *semantic tier*, not a *storage tier*. Tiers 0-2 (Chunk/Block/District) are generation units. Tiers 4-5 (Province/Area) are geographic classification units. Tier 3 (Region) is a lookup concept: "which settlements are in this province" + "which province does this settlement sit in."
|
||||
|
||||
---
|
||||
|
||||
## Performance Analysis
|
||||
|
||||
Target: Layer 1-2 for a single body completes in low single-digit seconds.
|
||||
|
||||
Estimated Rust operation timings:
|
||||
|
||||
| Operation | Grid Size | Estimated Time |
|
||||
|-----------|-----------|----------------|
|
||||
| Load heightmap BLOB from SQLite | 512KB | ~5ms |
|
||||
| D8 flow routing (priority-flood) | 512×256 | ~50ms |
|
||||
| Extract river centerlines | 512×256 | ~20ms |
|
||||
| Geographic attractor extraction | 64×32 cells | ~5ms |
|
||||
| Sub-biome variant sampling | 64×32 cells | ~2ms |
|
||||
| Attractor matching (N≈25 cities) | N×M | ~1ms |
|
||||
| A* road routing (MST) | 64×32 grid, N nodes | ~30ms |
|
||||
| Sub-settlement placement | N camps | ~5ms |
|
||||
| Territorial status propagation | 64×32 cells | ~15ms |
|
||||
| **Total** | | **~133ms** |
|
||||
|
||||
133ms is well within single-digit seconds. Even with 5× safety margin for SQLite overhead and cache misses, this is ~650ms — still under 1 second. The performance target is not a concern for the minimum viable implementation.
|
||||
|
||||
**Background thread model:** Layer 1-2 for body N-1 can run while the player is on body N. The priority queue means by the time the player considers traveling to a system mentioned in news, that system's generation is likely complete.
|
||||
|
||||
**Memory budget per body state:** ~2MB per BodyWorldState (river network polylines + settlement list + road graph + territorial grid). 400-500 bodies × 2MB = 800MB-1GB peak if all bodies are cached. This is too much. The cache should use an LRU policy: keep the last N bodies generated, where N is tuned for available memory (target: 50 bodies = ~100MB). Bodies beyond LRU are dropped and regenerated on access.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions — Ordered by Blocking Priority
|
||||
|
||||
### Must resolve before Layer 1-2 Rust code can be written
|
||||
|
||||
| ID | Question | Who resolves | Stakes |
|
||||
|----|----------|-------------|--------|
|
||||
| ARCH-1 | Heightmap storage: float32 LE BLOB confirmed? Compression? Column on atlas_body_heightmaps? | Lead + Tyre | Blocks all Layer 1-2 Rust |
|
||||
| ARCH-2 | Session state representation: BodyWorldState as Bevy Resource confirmed? LRU policy? | Tyre + Gestalt | Blocks Layer 2 → 3 handoff |
|
||||
| ARCH-3 | `atlas_city_names` schema: replaces or extends `atlas_cities`? | Tyre + lead | Blocks attractor-matching |
|
||||
| ARCH-4 | Body physical size (radius/surface area) in bodies table? Add field? | Tyre + miri | Blocks area-count formula |
|
||||
|
||||
### Must resolve before Layer 2 sub-settlement code can be written
|
||||
|
||||
| ID | Question | Who resolves |
|
||||
|----|----------|-------------|
|
||||
| L2-1 | Sub-settlement exact placement: greedy (simple) or Hungarian (optimal)? | Tyre |
|
||||
| L2-2 | Road routing resolution: 64×32 only, or optional higher-res pass for Atlas UI? | Tyre + Gestalt |
|
||||
| L2-3 | TerritorialStatus thresholds: my proposed numbers above, or adjusted by Burnelli-Sheldon/Paula? | Burnelli-Sheldon + Paula |
|
||||
|
||||
### Resolved by consultant (no longer workshop questions)
|
||||
|
||||
- ~~L1-Q1~~ — Eliminated (rivers are fully generated)
|
||||
- ~~L2-Q1~~ — Eliminated (city positions are fully derived)
|
||||
- ~~CL-Q4~~ — Eliminated (same resolution as L1-Q1)
|
||||
|
||||
### Inherited open questions that still need answers
|
||||
|
||||
| Original ID | Question | My position |
|
||||
|------------|----------|-------------|
|
||||
| L1-Q2 | River resolution for walkable world | Regional (64×32) in session state; tile-precision at Phase 5 from seed |
|
||||
| L1-Q3 | Confluence points stored vs. derived | Derived at runtime from D8 output; stored in BodyWorldState only |
|
||||
| L1-Q4 | Economic role as biome prior | Include — cheap, prevents self-contradiction |
|
||||
| L2-Q3 | Road algorithm: authored vs. generated | New: fully generated; authored roads gone |
|
||||
| L2-Q5 | TerritorialStatus thresholds | My proposed thresholds above |
|
||||
| L2-Q6 | Latent settlement flag storage | In BodyWorldState (Rust session state), not systems.db |
|
||||
| L3-Q1 | FoundingOrientation spatial effect | Spatial grid orientation change (not just gradient) |
|
||||
| L3-Q2 | Political archetype as spatial arrangement | Explicit patterns for 3 archetypes |
|
||||
| L4-Q1 | Economics-variable rendering mechanism | Threshold-based cache invalidation |
|
||||
| L4-Q6 | prosperity_delta architecture | Two fields (baseline + current), delta always derived |
|
||||
| CL-Q1 | Naming: `prosperity_baseline` vs. `prosperity_current` | Two distinct fields, confirmed |
|
||||
| CL-Q2 | Regional land-use evolution boundary | Runtime parameter in BodyWorldState, not ChunkMutations |
|
||||
|
||||
---
|
||||
|
||||
## Summary: What This Round Establishes
|
||||
|
||||
The architectural pivot from Python offline to Rust runtime background is the dominant finding. The existing `generate_atlas.py` + `planet_simulation.py` Python infrastructure remains for heightmap generation and build-time data, but all of the "understanding the world" work (drainage, settlement placement, road construction, territorial analysis) moves to Rust runtime.
|
||||
|
||||
Technically this is feasible. The algorithms are geometry and constraint-solving on a 64×32 grid. The performance target (low single-digit seconds per body) is met with 5× headroom at 133ms. The memory cost is bounded by an LRU cache policy.
|
||||
|
||||
The four things the workshop must lock before Layer 1-2 implementation can start:
|
||||
1. Heightmap storage format in systems.db
|
||||
2. Session state design (BodyWorldState as Bevy Resource, LRU policy)
|
||||
3. `atlas_city_names` schema (what the runtime reads for name assignment)
|
||||
4. Physical body dimensions in systems.db (for area-count formula)
|
||||
|
||||
Everything else in Layer 1-2 is algorithm design work that can proceed after those four are locked.
|
||||
|
||||
---
|
||||
|
||||
*Tyre — Round 1. Written 2026-05-01.*
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,738 @@
|
||||
---
|
||||
title: "Tyre Round 3 — Planet-Down Cascade Workshop"
|
||||
author: tyre
|
||||
workshop: planet-down-cascade
|
||||
round: 3
|
||||
created: 2026-05-01
|
||||
---
|
||||
|
||||
# Tyre Round 3 — Planet-Down Cascade Workshop
|
||||
|
||||
## Summary
|
||||
|
||||
Round 3 convergence document. Contains D-record candidates for all locked technical decisions, resolution of four open questions from Round 2, finalized Rust types and SQL DDL, and the implementation ticket dependency chain.
|
||||
|
||||
Lead-resolved before Round 3: two-tier mismatch flagging (`score < 0.35` = warning, `score < 0.15` = error/blocks).
|
||||
|
||||
---
|
||||
|
||||
## 1. Lead-Resolved Items
|
||||
|
||||
### Q1 — Mismatch Flag Threshold (LOCKED)
|
||||
|
||||
Two-tier system:
|
||||
|
||||
- `score < 0.35`: `MismatchSeverity::Soft` — `flagged_for_review = true`; placement proceeds with warning in generation log
|
||||
- `score < 0.15`: `MismatchSeverity::Hard` — assignment overridden to `Synthetic { reason: PoliticalDecision }`; city placed at Province centroid; `FoundingOrientation = AdminFacing`; generation proceeds without panic
|
||||
|
||||
```rust
|
||||
enum MismatchSeverity {
|
||||
None, // best attractor score >= 0.35
|
||||
Soft, // 0.15 <= score < 0.35; placement proceeds with flag
|
||||
Hard, // score < 0.15; overridden to Synthetic
|
||||
}
|
||||
```
|
||||
|
||||
`Hard` mismatch is not a generation failure — it's a policy decision. The city exists but was placed politically, not geographically. The flag is surfaced in developer tooling and the eventual city history log.
|
||||
|
||||
---
|
||||
|
||||
## 2. Convergence Items — Round 3 Resolutions
|
||||
|
||||
### Q2 — founding_age → layout_mode (Ozzie's Proposal)
|
||||
|
||||
**Proposal:** founding age should influence block geometry, making old cities irregular and young cities grid-like.
|
||||
|
||||
**Technical evaluation:**
|
||||
|
||||
The five spatial archetypes (CompanyTown, AdminCapital, FreePort, Contested, OrganicGrowth) determine *macro geometry* — spine, radial, multi-node. These reflect how the city was *designed* and should not be modified by age.
|
||||
|
||||
Block geometry is orthogonal: it reflects how the city *evolved after* being designed. A CompanyTown laid out as a rigid grid in Year 1 may have irregular blocks in Year 200 as organic infill accumulated around the original plan.
|
||||
|
||||
**Resolution: ADOPT via `BlockIrregularity` field — do NOT modify `layout_mode`.**
|
||||
|
||||
`layout_mode` (archetype) stays fixed. A new `block_irregularity: BlockIrregularity` field is added to `DistrictSkeleton`, derived from `founding_age_years` and `SpatialArchetype`. This is orthogonal to the archetype — CompanyTown can be Grid or Organic depending on age.
|
||||
|
||||
```rust
|
||||
enum BlockIrregularity {
|
||||
Grid, // Regular 64m × 64m blocks; planned geometry
|
||||
SlightlyWorn, // ~10% deviation from grid; some organic infill
|
||||
Irregular, // ~25% deviation; multiple planning generations visible
|
||||
Organic, // No regular grid; emerged rather than planned
|
||||
}
|
||||
|
||||
impl BlockIrregularity {
|
||||
fn from_age_and_archetype(founding_age_years: u32, archetype: SpatialArchetype) -> Self {
|
||||
// Reference age (years to reach next irregularity tier)
|
||||
let step: u32 = match archetype {
|
||||
SpatialArchetype::CompanyTown => 40, // Company towns evolve quickly
|
||||
SpatialArchetype::AdminCapital => 100, // State capitals resist change
|
||||
SpatialArchetype::FreePort => 60,
|
||||
SpatialArchetype::Contested => 35, // Conflict accelerates irregularity
|
||||
SpatialArchetype::OrganicGrowth => 20, // Always evolving; reaches Organic fastest
|
||||
};
|
||||
match founding_age_years / step.max(1) {
|
||||
0 => Self::Grid,
|
||||
1 => Self::SlightlyWorn,
|
||||
2 => Self::Irregular,
|
||||
_ => Self::Organic,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`block_irregularity` is a `DistrictSkeleton` field, not `CityGenerationContext` — it varies per district within a city. Derivation is fully local; no cross-city queries.
|
||||
|
||||
**Phase implication:** This field is set at Layer 3 and consumed at Layer 4 (street rendering). It has no effect on Phase 2 or 3 deliverables. No rework to the existing convergence.
|
||||
|
||||
---
|
||||
|
||||
### Q3 — Province Boundary Legibility
|
||||
|
||||
**Requirement (Ozzie, non-negotiable):** Province boundaries must render as natural watershed lines on the planetary map, not arbitrary grid edges.
|
||||
|
||||
**Technical resolution:**
|
||||
|
||||
Province boundaries are ridgelines between drainage basins. The D8 drainage simulation computes flow direction per cell; ridgelines are cells where no adjacent cell drains into the current cell from the "wrong side." Extracting boundary polylines from a D8 result is O(grid_size) — standard watershed delineation.
|
||||
|
||||
**Two options evaluated:**
|
||||
|
||||
Option A: Rust runtime computes drainage → extracts boundaries → stores in savegame DB.
|
||||
Option B: Python pipeline computes drainage at build time → extracts boundaries → stores in systems.db.
|
||||
|
||||
**Resolution: Option B.** The planetary map must render Province boundaries immediately when the player opens it, before any Rust generation has run. Option A introduces a generation-status dependency in the renderer. Option B eliminates it: boundaries are pre-computed at `make regen-db` time and available on first map open.
|
||||
|
||||
`planet_simulation.py` already computes a DEM per body. Watershed boundary extraction is a post-processing step over the same data. The Python implementation does not need the full D8 precision of the Rust runtime — it's a rendering hint, not game logic.
|
||||
|
||||
**New schema:**
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_province_boundaries (
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
province_x INTEGER NOT NULL,
|
||||
province_y INTEGER NOT NULL,
|
||||
boundary BLOB NOT NULL,
|
||||
-- float32 pairs [u0,v0, u1,v1, ...] in atlas UV space (0.0..1.0)
|
||||
-- Boundary polyline tracing the natural watershed outline of this province
|
||||
PRIMARY KEY (body_id, province_x, province_y)
|
||||
);
|
||||
CREATE INDEX idx_province_bounds_body ON atlas_province_boundaries(body_id);
|
||||
```
|
||||
|
||||
Storage estimate: ~20 float32 pairs per province boundary segment × ~64 segments per body → ~82KB/body → ~32MB for 400 bodies. Acceptable.
|
||||
|
||||
**Renderer contract:**
|
||||
- On map open: load all `boundary` BLOBs for `body_id`; render each as a polyline in atlas UV space
|
||||
- No dependency on `BodyWorldState` generation status
|
||||
- The Rust L1 drainage computes the same basin topology independently for generation purposes; renderer never waits for it
|
||||
|
||||
**Pipeline change:** `generate_atlas.py` adds a watershed extraction step after heightmap storage. The DDL addition goes into `import_economics.py`'s `MIGRATION_SQL` block (runs first; creates the table); `generate_atlas.py` populates it.
|
||||
|
||||
---
|
||||
|
||||
### Q5 — atlas_city_names Population Path
|
||||
|
||||
**Question:** Who writes source rows to `atlas_city_names` before Stage 0 fulfillment?
|
||||
|
||||
**Analysis:** The schema has `corp_id` and `tier_hint` — deliberate authoring fields. But with ~400 bodies and 10–30 cities each, hand-authoring all names is not tractable.
|
||||
|
||||
**Resolution: Two-tier authorship.**
|
||||
|
||||
**Tier 1 — Authored (`reserved = true`):** World designers write named cities for canon locations in `wiki/worlds/{body_slug}.toml`. These are named places with canonical identities — they appear in lore, faction text, and player-facing narrative. Imported by `import_economics.py`. All `corp_id` and `tier_hint` populated by the author.
|
||||
|
||||
```toml
|
||||
# wiki/worlds/nova-kassel.toml
|
||||
[[cities]]
|
||||
name = "Port Cassidy"
|
||||
corp_slug = "meridian-transit" # nullable
|
||||
tier_hint = 3 # nullable; expected WorldTier integer
|
||||
reserved = true
|
||||
```
|
||||
|
||||
**Tier 2 — Generated (`reserved = false`):** `import_economics.py` generates additional name rows from the corporation brand files. Corp HQ worlds receive at least one corp-affiliated city name derived from brand name + city-name templates from the `generate_brands` output. These are placeholders — no specific lore attachment; placed by attractor-matching opportunistically.
|
||||
|
||||
Source population order in `import_economics.py`:
|
||||
1. Preserve existing `reserved = true` rows for the body across reruns
|
||||
2. Delete existing `reserved = false` rows (regenerated fresh each run)
|
||||
3. Import authored rows from `wiki/worlds/*.toml` → `reserved = true`
|
||||
4. Generate corp-derived name rows for bodies below `corp_city_quota` → `reserved = false`
|
||||
|
||||
Paula's Stage 0–3 fulfillment pipeline (runtime) operates on whatever rows exist at generation time, regardless of source.
|
||||
|
||||
---
|
||||
|
||||
### Q6 — Full Multiplier Table (Locked)
|
||||
|
||||
Adding `Logistics` as the 8th district type (warehousing, distribution, freight staging). The 7-column table from Round 2 omits this type; it's essential for Transit/Port and Manufacturing cities and has minimum 3 weight across all rows.
|
||||
|
||||
**Final locked table** (all rows sum to 100; minimum value across all cells: 3):
|
||||
|
||||
| Economic Role | Res | Com | Ind | Adm | Ent | Civ | Mix | Log |
|
||||
|-------------------|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| Mining/Extraction | 33 | 11 | 26 | 8 | 3 | 5 | 9 | 5 |
|
||||
| Manufacturing | 27 | 13 | 23 | 8 | 5 | 7 | 10 | 7 |
|
||||
| Research Hub | 26 | 17 | 9 | 15 | 8 | 12 | 9 | 4 |
|
||||
| Commercial Hub | 20 | 28 | 7 | 10 | 12 | 8 | 10 | 5 |
|
||||
| Administrative | 18 | 14 | 5 | 28 | 8 | 15 | 7 | 5 |
|
||||
| Transit/Port | 20 | 16 | 13 | 7 | 5 | 5 | 16 | 18 |
|
||||
| Energy | 30 | 9 | 27 | 8 | 3 | 6 | 10 | 7 |
|
||||
| Agricultural | 32 | 16 | 9 | 5 | 7 | 8 | 16 | 7 |
|
||||
|
||||
Row sums: 100, 100, 100, 100, 100, 100, 100, 100. Minimum cell value: 3 (Mining/Ent and Energy/Ent). Floor invariant holds.
|
||||
|
||||
**Political archetype modifiers (applied after role table; floor at 3):**
|
||||
|
||||
All five archetypes need modifiers. Round 2 specified three (CompanyTown, AdminCapital, FreePort). The two missing ones are proposed here and require lead confirmation before locking.
|
||||
|
||||
| Archetype | Adjustment |
|
||||
|-----------------|------------|
|
||||
| CompanyTown | Administrative −10, Industrial +10 |
|
||||
| AdminCapital | Administrative +15, Commercial −8, Entertainment −7 |
|
||||
| FreePort | Commercial +12, Mixed +8, Administrative −20 |
|
||||
| Contested | Mixed +10, Civic −5, Administrative −5 *(proposed — needs lead confirmation)* |
|
||||
| OrganicGrowth | Mixed +15, Commercial +5, Administrative −10, Industrial −10 *(proposed — needs lead confirmation)* |
|
||||
|
||||
Note: modifier application must re-check the floor — if any cell drops below 3 after modifier, clamp to 3 and redistribute the deficit proportionally across the row.
|
||||
|
||||
---
|
||||
|
||||
## 3. D-Record Candidates
|
||||
|
||||
These are proposed D-records. IDs must be claimed via `tooling/db/decision claim D <domain> "title"` before writing to `decisions/` domain files. All items require corresponding implementation tickets.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: Heightmap Storage Schema (ARCH-1)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** Planetary heightmaps are stored as float32 LE BLOBs in `atlas_body_heightmaps` in systems.db; written by `generate_atlas.py`; loaded into `BodyWorldState` via `bytemuck::cast_slice`.
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_body_heightmaps (
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
data BLOB NOT NULL, -- float32 LE, 512×256 = 524,288 bytes
|
||||
PRIMARY KEY (body_id)
|
||||
);
|
||||
```
|
||||
|
||||
```rust
|
||||
fn load_heightmap(conn: &Connection, body_id: i64) -> Result<Vec<f32>> {
|
||||
let data: Vec<u8> = conn.query_row(
|
||||
"SELECT data FROM atlas_body_heightmaps WHERE body_id = ?1",
|
||||
[body_id], |row| row.get(0),
|
||||
)?;
|
||||
Ok(bytemuck::cast_slice(&data).to_vec())
|
||||
}
|
||||
```
|
||||
|
||||
Storage: ~512KB/body × 400 bodies = ~200MB. Coordinate system: row 0 = north pole, 512 columns (longitude), 256 rows (latitude). `bytemuck::cast_slice` is zero-copy on native endian architectures.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: BodyWorldState as Bevy Resource (ARCH-2)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** Session-level generation state lives in a Bevy `Resource` (`GenerationCache`). Never serialized. Fully reproducible from `seed` + systems.db. LRU cap: 50 bodies.
|
||||
|
||||
```rust
|
||||
#[derive(Resource)]
|
||||
struct GenerationCache {
|
||||
entries: LruCache<i64, Arc<BodyWorldState>>,
|
||||
}
|
||||
|
||||
struct BodyWorldState {
|
||||
body_id: i64,
|
||||
seed: u64,
|
||||
heightmap: Vec<f32>, // 512×256
|
||||
river_network: RiverNetwork, // D8 drainage output (regional summary, not full grid)
|
||||
attractors: Vec<GeoAttractor>,
|
||||
settlements: Vec<GeneratedSettlement>,
|
||||
provinces: Vec<ProvinceWorldState>,
|
||||
generated_at: std::time::Instant,
|
||||
}
|
||||
```
|
||||
|
||||
Memory budget: ~5MB for 50 bodies (full 512×256 accumulation grid discarded post-extraction; only 64×32 regional summary retained). `Arc<BodyWorldState>` for cheap cross-system sharing without cache lock contention. LRU eviction is safe — evicted bodies re-generate deterministically on next access.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: City Name Reservation Schema and Population Path (ARCH-3)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** `atlas_city_names` stores name reservations, not positions. Rows come from two sources: authored TOML files (`reserved = true`) and generated corp-derived names (`reserved = false`). Both flow through `import_economics.py`. Runtime Stage 0–3 fulfillment assigns names to generated settlement positions.
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_city_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
corp_id INTEGER REFERENCES corporations(id),
|
||||
tier_hint INTEGER,
|
||||
reserved BOOLEAN NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX idx_city_names_body ON atlas_city_names(body_id);
|
||||
```
|
||||
|
||||
Source TOML: `wiki/worlds/{body_slug}.toml` → `[[cities]]` arrays. Authoring format documented in Q5 resolution above.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: Body Radius Column (ARCH-4)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** `bodies.body_radius_km` nullable REAL column; Rust reads with `planet_class` fallback.
|
||||
|
||||
```sql
|
||||
ALTER TABLE bodies ADD COLUMN body_radius_km REAL;
|
||||
```
|
||||
|
||||
```rust
|
||||
fn body_radius_km(row: &Row) -> f64 {
|
||||
row.get::<_, Option<f64>>("body_radius_km")
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| default_radius_for_class(
|
||||
row.get("planet_class").unwrap_or("")
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: Province Boundary Pre-Computation (ARCH-5)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** Province boundary polylines are computed at build time by Python (watershed extraction from DEM) and stored in `atlas_province_boundaries` in systems.db. The planetary map renderer loads these directly; no dependency on Rust generation status.
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_province_boundaries (
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
province_x INTEGER NOT NULL,
|
||||
province_y INTEGER NOT NULL,
|
||||
boundary BLOB NOT NULL,
|
||||
-- float32 pairs [u0,v0, u1,v1, ...] in atlas UV space (0.0..1.0)
|
||||
PRIMARY KEY (body_id, province_x, province_y)
|
||||
);
|
||||
CREATE INDEX idx_province_bounds_body ON atlas_province_boundaries(body_id);
|
||||
```
|
||||
|
||||
Schema added to `import_economics.py` MIGRATION_SQL (creates table). `generate_atlas.py` populates it after heightmap storage. Renderer loads by `body_id` and renders as polylines.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: D8 Priority-Flood Drainage — Layer 1 (GEN-1)
|
||||
|
||||
**Domain:** architecture (generation)
|
||||
**Decision:** Layer 1 uses D8 priority-flood drainage in Rust to derive river networks and geographic attractors from heightmaps. Target: ~50ms at 512×256.
|
||||
|
||||
Algorithm:
|
||||
1. Load heightmap from `BodyWorldState` (ARCH-2)
|
||||
2. D8 single-direction flow: each cell drains to lowest adjacent neighbor (8 directions)
|
||||
3. Priority-flood fills sinks: `BinaryHeap<(Reverse<f32>, (usize, usize))>` processes in elevation order
|
||||
4. Accumulate drainage area per cell
|
||||
5. High-accumulation cells → `RiverNetwork` segments
|
||||
6. Extract geographic attractors: `CoastalHarbor` (coastline × high drainage), `MountainPass` (low-elevation saddles), `ResourceConcentration` (tagged from systems.db), `ArablePlain` (low slope × high moisture)
|
||||
|
||||
Seed: `drainage_seed = child_seed(body_seed, "drainage")` — tie-breaking in priority-flood.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: Five-Phase Attractor Assignment — Layer 2 (GEN-2)
|
||||
|
||||
**Domain:** architecture (generation)
|
||||
**Decision:** City-to-attractor assignment: score matrix + hard zeros → tier sort → Tier A greedy → Tier B/C Hungarian → synthetic overflow.
|
||||
|
||||
Key parameters:
|
||||
- Hard-zero filter: `is_physically_possible()` (H1–H4) runs before scoring; zeros are structural, not low scores
|
||||
- Minimum non-zero score: 0.10 (no compatible attractor scores below this floor)
|
||||
- Tier A: `MiningExtraction | ResourceExtraction` economic roles (highest geographic constraint)
|
||||
- Hungarian: O(N³) maximum-weight bipartite matching; N ≤ 30 cities; defensive fallback at N > 40 (greedy for Tier C cities with score > 0.5)
|
||||
- Synthetic overflow: `SyntheticPlacementReason`: `PopulationOverflow | PoliticalDecision | CorpExpansion`
|
||||
- Mismatch thresholds (lead-resolved): Soft at 0.35; Hard (→ Synthetic override) at 0.15
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: Three-Component District Mix — Layer 3 (GEN-3)
|
||||
|
||||
**Domain:** architecture (generation)
|
||||
**Decision:** District type distribution uses three orthogonal components. Self-contained (no cross-city queries). Full locked multiplier table in Q6 resolution above.
|
||||
|
||||
Components:
|
||||
1. Population tier guarantees — minimum required district counts by population band
|
||||
2. Economic role multiplier table — integer weights (sum 100, min 3) × 8 economic roles × 8 district types
|
||||
3. Founding age character modifier — affects `prosperity_baseline` and character tags at Backwater+ WorldTier; does NOT change weights
|
||||
|
||||
Political archetype modifiers applied post-table with floor at 3.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: BlockIrregularity from founding_age — Layer 3 (GEN-4)
|
||||
|
||||
**Domain:** architecture (generation)
|
||||
**Decision:** Block geometry irregularity is a `DistrictSkeleton` field derived from `founding_age_years` × `SpatialArchetype`. Orthogonal to spatial arrangement archetype. Consumed at Layer 4.
|
||||
|
||||
Full Rust type and derivation function: see Q2 resolution above.
|
||||
|
||||
Phase 4 usage: Layer 4 tile placement uses `block_irregularity` to vary street width, corner treatments, and block subdivision patterns. `Grid` produces regular tile-aligned 64m blocks. `Organic` produces irregular boundaries with no aligned corners.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: TerritorialStatus Priority-Ordered Derivation (GEN-5)
|
||||
|
||||
**Domain:** architecture (generation)
|
||||
**Decision:** `TerritorialStatus` derives from `ProvinceWorldState` via priority-ordered algorithm. `placed_at_generation: bool` is the sole differentiator between `AbandonedZone` and `WildernessBuffer`.
|
||||
|
||||
Algorithm and enum values: locked in Round 2; see round-2-notes.md §3.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: SettlementClass Enum (GEN-6)
|
||||
|
||||
**Domain:** architecture (generation)
|
||||
**Decision:** `SettlementClass` generalizes the latent/active distinction. `active: bool` is derived from class conditions at runtime. `placed_at_generation: bool` is set at Layer 2 and immutable.
|
||||
|
||||
```rust
|
||||
enum SettlementClass {
|
||||
NameLocked, // Has a name in atlas_city_names; always active
|
||||
PopulationBudget, // Active if body_population_density > threshold
|
||||
EconomicTriggered, // Active if route_traffic_score > threshold
|
||||
OrganicGrowth, // Placed by geographic probability; geographically_triggered = false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: Background Generation Queue (GEN-7)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** Background body generation uses a rayon thread pool (not async Tokio). Priority ordering based on player position, travel routes, and body name mentions in dialogue text.
|
||||
|
||||
```rust
|
||||
struct GenerationQueue {
|
||||
pending: BinaryHeap<Reverse<GenerationRequest>>,
|
||||
in_flight: HashSet<i64>,
|
||||
completed: HashSet<String>,
|
||||
}
|
||||
```
|
||||
|
||||
Priority ordering: player-targeted body → adjacent bodies in travel route → bodies mentioned in dialogue → active corp supply chains → all others.
|
||||
|
||||
Aho-Corasick `SystemNameIndex`: pattern-matches body/system names in player-facing text (news ticker, NPC dialogue, documents) to trigger pre-generation before the player travels there.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: WorldTier Enum Bug Fix (GEN-8)
|
||||
|
||||
**Domain:** architecture (bug)
|
||||
**Decision:** `WorldTier` in `server/src/simulation/generator.rs` must be `{ Epicenter, Regional, Backwater, Passage, Waypoint }`. Current code `{ Peripheral, Connected, Core }` is wrong. This is a code bug, not a design question.
|
||||
|
||||
Affected file: `server/src/simulation/generator.rs`, `enum WorldTier` and all match arms.
|
||||
Prerequisite for: every generation implementation ticket.
|
||||
|
||||
---
|
||||
|
||||
### D-candidate: atlas_feature_names Schema (GEN-9)
|
||||
|
||||
**Domain:** architecture
|
||||
**Decision:** Geographic feature names (rivers, mountain passes, bays) are stored in `atlas_feature_names`, distinct from `atlas_city_names`. Assigned at Layer 1 name fulfillment (Stage 1 of Paula's four-stage pipeline).
|
||||
|
||||
```sql
|
||||
CREATE TABLE atlas_feature_names (
|
||||
id INTEGER PRIMARY KEY,
|
||||
body_id INTEGER NOT NULL REFERENCES bodies(id),
|
||||
name TEXT NOT NULL,
|
||||
tag_hint TEXT -- nullable; expected FeatureTag for this name
|
||||
);
|
||||
CREATE INDEX idx_feature_names_body ON atlas_feature_names(body_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Final Type Specifications
|
||||
|
||||
### CityGenerationContext — Final Fields
|
||||
|
||||
```rust
|
||||
struct CityGenerationContext {
|
||||
// Core identity
|
||||
name: String,
|
||||
body_id: i64,
|
||||
seed: u64,
|
||||
|
||||
// Location
|
||||
location: (u8, u8), // atlas grid cell
|
||||
region_size: (u8, u8), // cells the city region occupies
|
||||
|
||||
// Population and economics
|
||||
population: u64,
|
||||
economic_role: EconomicRole,
|
||||
corp_presence: Vec<CorpPresence>,
|
||||
|
||||
// Tier (enum values require GEN-8 bug fix)
|
||||
world_tier: WorldTier, // Epicenter|Regional|Backwater|Passage|Waypoint
|
||||
|
||||
// Age and character
|
||||
founding_age_years: u32,
|
||||
spatial_archetype: SpatialArchetype,
|
||||
|
||||
// Layer 2 outputs — attractor assignment
|
||||
attractor_assignment: AttractorAssignment,
|
||||
mismatch_severity: MismatchSeverity,
|
||||
|
||||
// Layer 2 outputs — settlement class
|
||||
settlement_class: SettlementClass,
|
||||
active: bool, // derived from SettlementClass conditions
|
||||
|
||||
// Layer 2 outputs — founding orientation
|
||||
geographically_triggered: bool, // false → FoundingOrientation::AdminFacing
|
||||
|
||||
// Layer 3 inputs/outputs — prosperity
|
||||
prosperity_baseline: f32, // set at generation; founding_age modulated
|
||||
prosperity_current: f32, // economics variable; tile conditions derived from this
|
||||
|
||||
// Layer 3 context
|
||||
territorial_context: TerritorialStatus,
|
||||
}
|
||||
// Note: prosperity_delta = prosperity_current - prosperity_baseline; derived, never stored
|
||||
// Note: block_irregularity lives on DistrictSkeleton (per-district), not here (per-city)
|
||||
```
|
||||
|
||||
### DistrictSkeleton — New Field
|
||||
|
||||
Add to existing `DistrictSkeleton` in `generator.rs`:
|
||||
|
||||
```rust
|
||||
// New field — derived at Layer 3 from founding_age_years + spatial_archetype
|
||||
block_irregularity: BlockIrregularity,
|
||||
```
|
||||
|
||||
### New Enum Types
|
||||
|
||||
```rust
|
||||
enum SpatialArchetype {
|
||||
CompanyTown, // Spine pattern
|
||||
AdminCapital, // Radial pattern
|
||||
FreePort, // Multi-node pattern (3–5 nodes)
|
||||
Contested, // Dual-center overlay pattern
|
||||
OrganicGrowth, // Irregular local density pattern
|
||||
}
|
||||
|
||||
enum EconomicRole {
|
||||
MiningExtraction,
|
||||
Manufacturing,
|
||||
ResearchHub,
|
||||
CommercialHub,
|
||||
Administrative,
|
||||
TransitPort,
|
||||
Energy,
|
||||
Agricultural,
|
||||
}
|
||||
|
||||
enum DistrictType {
|
||||
Residential,
|
||||
Commercial,
|
||||
Industrial,
|
||||
Administrative,
|
||||
Entertainment,
|
||||
Civic,
|
||||
MixedUse,
|
||||
Logistics, // NEW: warehousing, distribution, freight staging
|
||||
}
|
||||
|
||||
enum MismatchSeverity {
|
||||
None, // score >= 0.35
|
||||
Soft, // 0.15 <= score < 0.35
|
||||
Hard, // score < 0.15; overridden to Synthetic
|
||||
}
|
||||
```
|
||||
|
||||
### `GenerateChunkData` Upgrade (Prior Workshop Item, Still Required)
|
||||
|
||||
`GeneratorChunkData = Vec<bool>` must become `Vec<TileEntry>`. `TileEntry` needs at minimum:
|
||||
|
||||
```rust
|
||||
struct TileEntry {
|
||||
tile_type: TileType,
|
||||
walkable: bool,
|
||||
spawn_category: Option<PropCategory>, // scatter hook; None until Phase 6
|
||||
// tile condition derived at render time from prosperity_current thresholds
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. SeedChain Usage Per Layer
|
||||
|
||||
All seeds: FNV-1a `child_seed(parent, discriminant)` per D-010.
|
||||
|
||||
```
|
||||
system_seed = child_seed(world_seed, system_id)
|
||||
body_seed = child_seed(system_seed, body_id)
|
||||
|
||||
// Layer 1
|
||||
drainage_seed = child_seed(body_seed, "drainage")
|
||||
attractor_seed = child_seed(body_seed, "attractors")
|
||||
boundary_seed = child_seed(body_seed, "province_bounds")
|
||||
|
||||
// Layer 2 — iterate cities in atlas_city_names id order
|
||||
placement_seed = child_seed(body_seed, "placement")
|
||||
for city_index in sorted order:
|
||||
city_seed = child_seed(placement_seed, city_index as u64)
|
||||
// Used for: synthetic attractor offsets; tie-breaking in assignment
|
||||
|
||||
// Layer 3 — per city
|
||||
district_seed = child_seed(city_seed, "districts")
|
||||
founding_seed = child_seed(city_seed, "founding")
|
||||
prosperity_seed = child_seed(city_seed, "prosperity")
|
||||
|
||||
// Layer 4 — per district
|
||||
tile_seed = child_seed(district_seed, tile_index as u64)
|
||||
// Tile LAYOUT is seed-locked (deterministic from tile_seed)
|
||||
// Tile CONDITIONS are economics-variable via threshold-crossing cache invalidation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Performance Budget Per Layer
|
||||
|
||||
Target: < 700ms at session start (synchronous cold generation); < 143ms on-demand.
|
||||
|
||||
| Layer | Operation | Budget | Estimated |
|
||||
|-------|-----------|--------|-----------|
|
||||
| L1 | D8 drainage routing | 50ms | ~50ms |
|
||||
| L1 | Attractor extraction | 10ms | ~8ms |
|
||||
| L1 | Province boundary extraction | 5ms | ~5ms |
|
||||
| L2 | Hard-zero filter + score matrix | 5ms | ~3ms |
|
||||
| L2 | Tier A greedy assignment | 2ms | ~1ms |
|
||||
| L2 | Hungarian (Tier B/C, N ≤ 30) | 10ms | ~15ms |
|
||||
| L2 | Synthetic overflow | 2ms | ~1ms |
|
||||
| L2 | Name fulfillment Stages 1–3 | 5ms | ~5ms |
|
||||
| L3 | District mix (all cities) | 15ms | ~20ms |
|
||||
| L3 | TerritorialStatus (all provinces) | 25ms | ~25ms |
|
||||
| L3 | WorldTier assignment + prosperity | 5ms | ~10ms |
|
||||
| **Total** | | **134ms** | **~143ms** |
|
||||
| Session budget (cold start) | 700ms | | |
|
||||
| Headroom | | | 4.9× |
|
||||
|
||||
**Defensive check:** Hungarian is O(N³). At N = 30: ~15ms. At N = 50: ~400ms (exceeds L2 budget alone). If any body has > 40 named cities: switch Tier C to greedy for cities where best-available-attractor score > 0.5, then run Hungarian only on the remainder. This bound has not been hit on any current body but needs the guard.
|
||||
|
||||
---
|
||||
|
||||
## 7. Implementation Ticket Dependency Chain
|
||||
|
||||
Effort in dev-days. All `SCHEMA-*` tickets can be grouped into a single migration PR.
|
||||
|
||||
### Tier 0 — Prerequisites (No Dependencies)
|
||||
|
||||
| Ticket | Work | Effort | File(s) |
|
||||
|--------|------|--------|---------|
|
||||
| BUG-WorldTier | Fix `WorldTier` enum; update all match arms | 0.5d | `server/src/simulation/generator.rs` |
|
||||
| SCHEMA-bodies | `body_radius_km REAL` column | 0.25d | `systems-schema.sql`, `import_economics.py` MIGRATION_SQL |
|
||||
| SCHEMA-heightmaps | `atlas_body_heightmaps` DDL | 0.25d | `systems-schema.sql`, `import_economics.py` MIGRATION_SQL |
|
||||
| SCHEMA-city-names | `atlas_city_names` DDL + index | 0.25d | `systems-schema.sql`, `import_economics.py` MIGRATION_SQL |
|
||||
| SCHEMA-feature-names | `atlas_feature_names` DDL + index | 0.25d | `systems-schema.sql`, `import_economics.py` MIGRATION_SQL |
|
||||
| SCHEMA-province-bounds | `atlas_province_boundaries` DDL + index | 0.25d | `systems-schema.sql`, `import_economics.py` MIGRATION_SQL |
|
||||
|
||||
### Tier 1 — Python Pipeline (Depends on Tier 0 schemas)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| PY-heightmap-import | `generate_atlas.py`: BLOB-pack elevation float32; INSERT into `atlas_body_heightmaps` | 1d | SCHEMA-heightmaps |
|
||||
| PY-province-bounds | `generate_atlas.py`: watershed extraction from DEM; store boundary polylines | 2d | SCHEMA-province-bounds, PY-heightmap-import |
|
||||
| PY-city-names-authored | `import_economics.py`: read `wiki/worlds/*.toml` `[[cities]]`; INSERT with `reserved = true` | 1d | SCHEMA-city-names |
|
||||
| PY-city-names-corp | `import_economics.py`: generate corp-derived name rows for under-quota bodies | 1d | PY-city-names-authored |
|
||||
| PY-body-radius | `import_economics.py`: populate `body_radius_km` from planet_class defaults or authored values | 0.5d | SCHEMA-bodies |
|
||||
|
||||
### Tier 2 — Rust Type Definitions (Depends on Tier 0)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-types-worldtier | Fix `WorldTier` enum in Rust; update all match arms | 0.5d | BUG-WorldTier |
|
||||
| RS-types-settlement | `SettlementClass`, `MismatchSeverity`, `AttractorAssignment`, `SyntheticPlacementReason` | 0.5d | RS-types-worldtier |
|
||||
| RS-types-district | Add `BlockIrregularity` to `DistrictSkeleton`; `SpatialArchetype`, `EconomicRole`, `DistrictType::Logistics` | 0.5d | — |
|
||||
| RS-types-territorial | `TerritorialStatus` with correct variants | 0.5d | — |
|
||||
| RS-types-city-ctx | Finalize `CityGenerationContext` with all new fields | 1d | RS-types-settlement, RS-types-territorial |
|
||||
|
||||
### Tier 3 — Rust Core (Depends on Tier 1 + Tier 2)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-heightmap-load | `load_heightmap()` via bytemuck; integration into `BodyWorldState` init | 0.5d | PY-heightmap-import, RS-types-city-ctx |
|
||||
| RS-body-state | `BodyWorldState` struct + `GenerationCache` Bevy Resource + LRU cache | 1.5d | RS-heightmap-load |
|
||||
| RS-drainage | D8 priority-flood drainage; attractor extraction; RiverNetwork construction | 3d | RS-body-state |
|
||||
| RS-attractor-types | `GeographicAttractor`, `AttractorType`, `CompatibilityMatrix` | 0.5d | RS-types-settlement |
|
||||
|
||||
### Tier 4 — Generation Algorithms (Depends on Tier 3)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-attractor-assign | Five-phase attractor assignment + Hungarian + synthetic overflow + mismatch flags | 4d | RS-drainage, RS-attractor-types, PY-city-names-authored |
|
||||
| RS-district-mix | Three-component district mix + locked multiplier table + political archetype modifiers | 3d | RS-attractor-assign, RS-types-district |
|
||||
| RS-territorial | TerritorialStatus priority-ordered derivation; ProvinceWorldState population | 2d | RS-attractor-assign |
|
||||
| RS-block-irregularity | `BlockIrregularity::from_age_and_archetype()` + integration into district loop | 1d | RS-district-mix |
|
||||
| RS-tile-conditions | Threshold cache with invalidation for tile conditions (L4-Q1) | 1.5d | RS-district-mix |
|
||||
|
||||
### Tier 5 — Background + UI (Depends on Tier 4)
|
||||
|
||||
| Ticket | Work | Effort | Depends on |
|
||||
|--------|------|--------|------------|
|
||||
| RS-bg-queue | `GenerationQueue` + rayon thread pool + priority ordering | 2d | RS-body-state |
|
||||
| RS-aho-corasick | `SystemNameIndex` + text scanning → generation trigger | 1d | RS-bg-queue |
|
||||
| UI-province-bounds | Godot planetary map: load `atlas_province_boundaries`; render as natural polylines | 2d | PY-province-bounds |
|
||||
|
||||
### Critical Path
|
||||
|
||||
```
|
||||
SCHEMA-heightmaps
|
||||
→ PY-heightmap-import
|
||||
→ RS-heightmap-load
|
||||
→ RS-body-state
|
||||
→ RS-drainage
|
||||
→ RS-attractor-assign
|
||||
→ RS-district-mix
|
||||
→ RS-tile-conditions
|
||||
```
|
||||
|
||||
Critical path effort: 0.25 + 1 + 0.5 + 1.5 + 3 + 4 + 3 + 1.5 = **14.75 dev-days**
|
||||
|
||||
Total effort (all tiers, parallel where possible): **~33 dev-days**
|
||||
|
||||
Parallel acceleration: Tier 0 + Tier 1 + Tier 2 can all run simultaneously. On a two-agent split (Python team / Rust team), calendar time narrows to ~20 days.
|
||||
|
||||
---
|
||||
|
||||
## 8. Open Items Not Resolved in This Round
|
||||
|
||||
### Q4 — Port/Station as Special City Type
|
||||
|
||||
Not addressed. Orbital stations lack terrain; no geographic attractors; no drainage. Technical sketch: `SpatialArchetype::SpaceStation` as a sixth variant, bypassing Layer 1 entirely. `body_id` points to orbital body. District mix: no Agricultural; Logistics and Industrial dominant. Defer to lead for whether this needs a Round 4 or a separate ticket.
|
||||
|
||||
### Contested + OrganicGrowth Archetype Modifiers
|
||||
|
||||
My proposed modifiers (see Q6 above) are proposals, not locked. Lead or Burnelli-Sheldon should confirm or adjust before `RS-district-mix` is implemented.
|
||||
|
||||
### Province Watershed Algorithm Detail
|
||||
|
||||
I've specified that `generate_atlas.py` adds watershed extraction. The exact algorithm (D8 ridgeline detection, smoothing kernel, UV coordinate normalization) needs a concrete spec before `PY-province-bounds` is assigned. Recommend a brief technical sidequest with Tyre or a consultant review before that ticket starts.
|
||||
|
||||
---
|
||||
|
||||
## 9. Items Confirmed — No Further Discussion Required
|
||||
|
||||
Carried forward from Rounds 1–2, not reopened:
|
||||
|
||||
- **SeedChain (FNV-1a)** — D-010; unchanged
|
||||
- **D8 drainage routing** — locked; ARCH-1 accepted by all agents
|
||||
- **Scatter deferred** — lead decision; `spawn_category: Option<PropCategory>` hook preserved in `TileEntry`
|
||||
- **District = 256m, Block = 64m** (4×4 blocks per district) — confirmed
|
||||
- **Province = 1 regional grid cell (~540km×270km on reference body)** — confirmed
|
||||
- **Tile condition thresholds** — `prosperity_current` > 0.63 = Intact, 0.43–0.63 = Worn, 0.23–0.43 = Cracked, < 0.23 = Broken; Paula's offsets prevent boundary oscillation
|
||||
- **prosperity_delta = derived, never stored** — confirmed
|
||||
- **Self-contained district generation** — no cross-city queries; lead requirement
|
||||
- **L4-Q1: threshold-crossing cache invalidation** for tile conditions — ticket RS-tile-conditions
|
||||
- **L4-Q4: pre-fetch two ring cells ahead** of player movement — confirmed; handled in chunk_streaming.rs after RS-body-state lands
|
||||
- **Area = atlas layer, not navigation tier** — confirmed
|
||||
- **Background generation budget** — 700ms session start; ~143ms on-demand; within budget at 4.9× headroom
|
||||
|
||||
---
|
||||
|
||||
*Round 3 complete from Tyre's side. Nine D-record candidates produced. Implementation dependency chain: 33 dev-days total, 14.75 critical path. WorldTier enum bug fix (BUG-WorldTier) is the hard prerequisite blocker — no generation ticket can land without it.*
|
||||
@@ -0,0 +1,403 @@
|
||||
---
|
||||
title: "Workshop Outcomes — Planet-Down Cascade"
|
||||
author: qatux
|
||||
workshop: planet-down-cascade
|
||||
status: complete
|
||||
created: 2026-05-01
|
||||
rounds: 3
|
||||
---
|
||||
|
||||
# Planet-Down Cascade — Workshop Outcomes
|
||||
|
||||
## What This Workshop Was
|
||||
|
||||
The planet-down-cascade workshop was a three-round design sprint tasked with answering a single question: **Given the four-layer cascade (Empty World → Population Overlay → City Planning → Street Rendering), what are the concrete algorithms at each layer?**
|
||||
|
||||
The workshop was convened in response to the generation-cascade workshop's outcomes (docs/workshops/generation-cascade/workshop-outcomes.md), which specified the cascade structure but deferred the algorithm specifications. A consultant review (docs/workshops/planet-down-cascade/consultant-review-planet-down-cascade.md) produced six amendments before Round 1 began; these amendments significantly restructured the problem.
|
||||
|
||||
**Participants:** Gestalt (systems design), Tyre (technical architecture), Paula (narrative and political), Burnelli-Sheldon (economics), Ozzie (player experience).
|
||||
|
||||
**Documentation:** Qatux (this document).
|
||||
|
||||
---
|
||||
|
||||
## What the Workshop Achieved
|
||||
|
||||
**Five algorithm specifications**, each at pseudocode-level detail sufficient for implementation:
|
||||
|
||||
1. **Layer 1 (Empty World):** D8 priority-flood drainage routing from heightmap BLOBs; geographic feature tag extraction (7 tags); sub-biome variant classification.
|
||||
|
||||
2. **Layer 2 (Population Overlay):** Five-phase attractor-matching pipeline assigning named cities to geographic attractors; road graph generation; TerritorialStatus derivation per Province; SettlementClass assignment for all settlements.
|
||||
|
||||
3. **Layer 3 (City-Level Planning):** Three-component district mix (D-194); FoundingOrientation spatial grid rotation; five explicit political archetype spatial patterns; BlockIrregularity from founding age.
|
||||
|
||||
4. **Layer 4 (Street Rendering):** Tile condition derivation from prosperity_current thresholds (0.63/0.43/0.23); chunk condition cache with threshold-crossing invalidation; building fill and street skeleton from BlockSkeleton.
|
||||
|
||||
**Three data format handoffs per layer:**
|
||||
- Build-time Python → systems.db (heightmaps, name reservations, Province boundary polylines, economics data)
|
||||
- Runtime-background Rust → BodyWorldState LRU cache (Layer 1-2 outputs; reproducible from seed + systems.db)
|
||||
- Runtime-on-demand Rust → never stored (Layer 3-4 generation from BodyWorldState + CityGenerationContext)
|
||||
|
||||
**26 D-records filed (D-194 through D-219)** — all placeholder candidates claimed and written to `decisions/architecture.md` and `decisions/content.md`.
|
||||
|
||||
**An implementation ticket dependency chain:** 22+ tickets, ~14.75-day critical path, ~33 dev-days total.
|
||||
|
||||
**Three pre-implementation data quality blockers** identified (bodies.founding_age_years NULL, system_economy fields NULL, economic_role normalization needed).
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Prior Workshop
|
||||
|
||||
The generation-cascade workshop (documented in docs/workshops/generation-cascade/workshop-outcomes.md) produced the cascade structure itself: four layers, three execution tiers, SeedChain (FNV-1a), CityGenerationContext struct, and D-C1 through D-C17 as Given Facts. It explicitly deferred algorithm design to a follow-on workshop.
|
||||
|
||||
This workshop is that follow-on. The Given Facts (D-C1 through D-C17) were carried in as fixed axioms and not reopened.
|
||||
|
||||
**The most significant change from the generation-cascade workshop:** Amendment 1 (from the consultant review) replaced the two-tier Phase 3/Phase 5 model with a three-tier model: build-time Python / runtime-background Rust / runtime-on-demand Rust. Tyre documented this pivot explicitly as "Priority 0: The Architectural Pivot" in his Round 1 file. All five agents absorbed the amendment successfully.
|
||||
|
||||
Amendment 3 (fully generative placement, markers.json stripped of city positions) eliminated several previously open questions (L1-Q1, L2-Q1, CL-Q4) by making them moot.
|
||||
|
||||
---
|
||||
|
||||
## All D-Record Candidates
|
||||
|
||||
### All D-Records — Filed
|
||||
|
||||
All workshop D-record candidates have been claimed, written to `decisions/architecture.md` (D-194–D-218) and `decisions/content.md` (D-219), and confirmed.
|
||||
|
||||
| Real ID | Title | Workshop placeholder(s) |
|
||||
|---------|-------|------------------------|
|
||||
| D-194 | Three-Component District Mix Algorithm | D-C28, GEN-3 |
|
||||
| D-195 | Attractor-Matching Compatibility Matrix | (part of D-C25) |
|
||||
| D-196 | SettlementClass Enum and Active/Ghost Logic | D-C26, GEN-6 |
|
||||
| D-197 | prosperity_baseline Derivation Formula | (new) |
|
||||
| D-198 | Economic Simulation Independence from Layer 1-2 Spatial Data | (part of D-C18) |
|
||||
| D-199 | 6-Field Minimum Economic Read Set for CityGenerationContext | (new) |
|
||||
| D-200 | Three-Tier Execution Model | D-C18 |
|
||||
| D-201 | Spatial Hierarchy — Eight Tiers with Locked Dimensions | D-C21 |
|
||||
| D-202 | Heightmap BLOB Storage Schema | D-C33, ARCH-1 |
|
||||
| D-203 | BodyWorldState Bevy Resource with LRU Cache | ARCH-2, D-C34 |
|
||||
| D-204 | body_radius_km Column on bodies Table | ARCH-4 |
|
||||
| D-205 | Province Boundary Pre-Computation | ARCH-5 |
|
||||
| D-206 | Background Generation Priority Queue | D-C19, GEN-7 |
|
||||
| D-207 | Fully Generative Placement | D-C20 |
|
||||
| D-208 | D8 Priority-Flood Drainage Routing | D-C22, GEN-1 |
|
||||
| D-209 | Geographic Feature Tags — 7 Tags | D-C23 |
|
||||
| D-210 | Sub-Biome Variant Classification | D-C24 |
|
||||
| D-211 | Attractor-Matching Five-Phase Pipeline | D-C25, GEN-2 |
|
||||
| D-212 | TerritorialStatus Derivation | D-C27, GEN-5 |
|
||||
| D-213 | FoundingOrientation Enum and Grid Rotation | D-C29, D-C-FO |
|
||||
| D-214 | AttractorAssignment Enum | D-C-AA |
|
||||
| D-215 | Five Spatial Arrangement Patterns | D-C30, D-C-SA |
|
||||
| D-216 | BlockIrregularity from founding_age | D-C31, GEN-4 |
|
||||
| D-217 | Tile Condition Thresholds | D-C32 |
|
||||
| D-218 | WorldTier Enum Canonical Values | GEN-8 |
|
||||
| D-219 | Naming Registers Lookup Table | D-C-NL |
|
||||
|
||||
GEN-9 (atlas_feature_names schema) is covered by the schema ticket #903 but does not have a standalone D-record — the DDL is straightforward and doesn't require a design decision.
|
||||
|
||||
---
|
||||
|
||||
## All Locked Algorithms
|
||||
|
||||
### Layer 1: Empty World Generator
|
||||
|
||||
**D8 Drainage Routing** (Tyre ARCH-1/GEN-1; Gestalt D-C22)
|
||||
- Input: atlas_body_heightmaps BLOB (float32 LE, 512×256)
|
||||
- Algorithm: D8 single-direction steepest descent; priority-flood sink filling; flow accumulation
|
||||
- River threshold: cells with flow_accumulation > 200 are river cells
|
||||
- Outputs: RiverNetwork (river_cells, confluence_nodes, river_mouths), drainage basin boundaries
|
||||
- Performance: ~50ms per body
|
||||
- Specification: tyre-round2.md (ARCH-1), gestalt-round2.md (D-C22), tyre-round3.md (GEN-1)
|
||||
|
||||
**Geographic Feature Tag Extraction** (Gestalt D-C23)
|
||||
- 7 tags derived per regional cell (64×32 grid)
|
||||
- Derivation rules: RiverConfluence (any confluence in bbox), CoastalHarbor (coastal + roughness<0.3), MountainPass (roughness>0.65, lower cells on two sides), ArablePlain (roughness<0.25 + terrestrial + arable biome), ResourceConcentration (economic_role biome prior), Defensible (roughness>0.5 + ≤2 approach vectors), NaturalBarrier (ocean or roughness>0.85)
|
||||
- Specification: gestalt-round3.md §D-C23
|
||||
|
||||
**Sub-Biome Variant Classification** (Gestalt D-C24)
|
||||
- 3-4 variants per biome class, FNV-1a seed-derived, economic_role as probability prior
|
||||
- `terrain_modification_cost: f32` = roughness×0.6 + biome_clearing_cost, clamped [0,1]
|
||||
- Specification: gestalt-round3.md §D-C24
|
||||
|
||||
---
|
||||
|
||||
### Layer 2: Population Overlay
|
||||
|
||||
**Attractor-Matching Five-Phase Pipeline** (D-C25 + D-195)
|
||||
|
||||
Phase 0 — Score matrix: Build N×M matrix. Apply D-195 compatibility matrix (0–10 scale, 10 roles × 7 attractor types). Hard-zero physically impossible pairings (Paula H1-H4).
|
||||
|
||||
Phase 1 — Sort cities by constraint tier:
|
||||
- Tier A: extraction/mining (most constrained)
|
||||
- Tier B: manufacturing/transit (moderate)
|
||||
- Tier C: service/commercial (most flexible)
|
||||
|
||||
Phase 2 — Tier A greedy assignment: fewest-valid-attractors-first order.
|
||||
|
||||
Phase 3 — Hungarian algorithm on Tier B+C: maximum-weight bipartite matching, O(N³), N≤30.
|
||||
|
||||
Phase 4 — Synthetic overflow: `SyntheticPlacementReason { PopulationOverflow | PoliticalDecision | CorpExpansion }`.
|
||||
|
||||
Mismatch classification (lead decision): Soft warning at score < 0.35 (log, proceed). Hard error at score < 0.15 (override to Synthetic { PoliticalDecision }, place at Province centroid, AdminFacing).
|
||||
|
||||
FoundingOrientation derivation post-assignment (Paula D-C-FO): Geographic attractor type → orientation lookup. Synthetic assignments → AdminFacing.
|
||||
|
||||
- Specification: gestalt-round2.md (pseudocode), tyre-round3.md (five-phase with MismatchSeverity), paula-round3.md (D-C-AM, D-C-FO), burnelli-sheldon-round3.md (D-195)
|
||||
|
||||
**TerritorialStatus Priority-Ordered Derivation** (D-C27)
|
||||
|
||||
```rust
|
||||
fn derive_territorial_status(province: &ProvinceWorldState) -> TerritorialStatus {
|
||||
if province.placed_at_generation && !province.active { return AbandonedZone; }
|
||||
if !province.placed_at_generation && province.settlement_count == 0 { return WildernessBuffer; }
|
||||
if province.primary_economic_activity == Extraction && province.corporate_presence_score > 0.4 { return ExtractiveZone; }
|
||||
if province.jurisdiction_overlap_score > 0.3 { return ContestZone; }
|
||||
if province.infrastructure_quality > 0.6 && province.corporate_road_maintenance > 0.5 { return CoreTerritory; }
|
||||
FrontierTerritory
|
||||
}
|
||||
```
|
||||
|
||||
`placed_at_generation: bool` is the sole differentiator between AbandonedZone and WildernessBuffer. Immutable; set at Layer 2.
|
||||
|
||||
- Specification: paula-round2.md, gestalt-round2.md (Rust code), burnelli-sheldon-round3.md (D-197, threshold confirmation)
|
||||
|
||||
**SettlementClass Enum** (D-196)
|
||||
|
||||
```rust
|
||||
enum SettlementClass {
|
||||
NameLocked, // Corp HQ body; always active
|
||||
PopulationBudget, // Active if nearest anchor city health_metric > 0.4
|
||||
EconomicTriggered { activating_corp_id: String }, // Active if corp health > 0.4
|
||||
OrganicGrowth, // Active if province avg corp health > 0.5
|
||||
}
|
||||
```
|
||||
|
||||
Ghost rendering: seed-locked geometry preserved; lighting dark; tile condition Broken; no NPC spawns.
|
||||
|
||||
- Specification: burnelli-sheldon-round3.md (D-196)
|
||||
|
||||
---
|
||||
|
||||
### Layer 3: City-Level Planning
|
||||
|
||||
**Three-Component District Mix** (D-194)
|
||||
|
||||
Component 1 — Population tier guarantees:
|
||||
|
||||
| Population | Guaranteed district types |
|
||||
|-----------|--------------------------|
|
||||
| 1–999 | Residential + Mixed |
|
||||
| 1,000–9,999 | Residential + Commercial |
|
||||
| 10,000–99,999 | + Entertainment |
|
||||
| 100,000–499,999 | + Administrative |
|
||||
| 500,000+ | + Industrial/Civic if role-appropriate |
|
||||
|
||||
Component 2 — Economic role multiplier table (10 roles × 9 types; all rows sum to 100; minimum cell value 3):
|
||||
|
||||
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|
||||
|--------------|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| manufacturing | 18 | 8 | 30 | 6 | 18 | 5 | 8 | 5 | 2 |
|
||||
| agricultural | 22 | 12 | 5 | 10 | 20 | 5 | 15 | 5 | 6 |
|
||||
| extraction | 16 | 7 | 28 | 4 | 24 | 5 | 9 | 5 | 2 |
|
||||
| transit | 10 | 18 | 5 | 5 | 20 | 12 | 14 | 14 | 2 |
|
||||
| research | 16 | 6 | 5 | 14 | 5 | 7 | 10 | 3 | 34 |
|
||||
| commercial | 14 | 30 | 5 | 6 | 10 | 14 | 14 | 5 | 2 |
|
||||
| service_mixed | 20 | 18 | 5 | 10 | 6 | 14 | 18 | 7 | 2 |
|
||||
| mining | 16 | 7 | 22 | 3 | 28 | 6 | 9 | 7 | 2 |
|
||||
| frontier | 26 | 10 | 10 | 5 | 18 | 8 | 16 | 5 | 2 |
|
||||
| energy | 10 | 3 | 18 | 6 | 24 | 3 | 6 | 6 | 24 |
|
||||
|
||||
Political archetype modifiers (stacked additive, floor at 3 applied after):
|
||||
|
||||
| political_archetype | Modifiers |
|
||||
|--------------------|-----------|
|
||||
| CompanyTown | Adm −10, Ind +10, Log +5, Res +5 |
|
||||
| AdminCapital | Adm +20, Ent +5, Spe +5, Ind −15 |
|
||||
| FreePort | Com +15, Trn +10, Mix +5, Adm −15 |
|
||||
| Contested | No modifier |
|
||||
| OrganicGrowth | Mix +15, Res +10, Ind −10, Adm −5 |
|
||||
|
||||
Component 3 — Settlement age character modifier: affects `perimeter_treatment`, `density_pct`, `character_class` on DistrictSkeleton. Does NOT modify district count or type. Applies only at Backwater WorldTier and above.
|
||||
|
||||
Self-contained: no cross-city queries. Single seed per city. Specification: burnelli-sheldon-round3.md (D-194).
|
||||
|
||||
**Five Explicit Spatial Arrangement Patterns** (D-C30)
|
||||
|
||||
| Archetype | Pattern | Player acceptance criterion |
|
||||
|-----------|---------|----------------------------|
|
||||
| CompanyTown | Spine: Industrial/facility at FoundingOrientation terminus; Residential cascades back | Posture visible; city points at its facility |
|
||||
| AdminCapital | Radial: Administrative hub at center (DefenseFacing) or prestige edge (AdminFacing); prosperity decreases outward | Power visible from any approach |
|
||||
| FreePort | Multi-node: 2-3 nodes with distinct character; no dominant center | Player gets productively lost; nodes serve as landmarks |
|
||||
| Contested | Dual-center overlay: two underlying geometric plans; boundary zone Mixed with Checkpoint perimeter | Player reads the conflict in street layout |
|
||||
| OrganicGrowth | Irregular local density: Voronoi around seed attractor points; ±12.5% grid jitter | Most "lived-in" feel; reads as historically authentic |
|
||||
|
||||
Acceptance criterion (Ozzie): player identifies archetype from 15 seconds of walking without consulting implant UI.
|
||||
|
||||
Specification: paula-round3.md (D-C-SA with Rust pseudocode), gestalt-round3.md (D-C30), ozzie-round3.md §3.
|
||||
|
||||
**BlockIrregularity from founding_age** (D-C31)
|
||||
|
||||
```rust
|
||||
enum BlockIrregularity { Grid, SlightlyWorn, Irregular, Organic }
|
||||
|
||||
impl BlockIrregularity {
|
||||
fn from_age_and_archetype(founding_age_years: u32, archetype: SpatialArchetype) -> Self {
|
||||
let step = match archetype {
|
||||
CompanyTown => 40, AdminCapital => 100, FreePort => 60,
|
||||
Contested => 35, OrganicGrowth => 20,
|
||||
};
|
||||
match founding_age_years / step { 0 => Grid, 1 => SlightlyWorn, 2 => Irregular, _ => Organic }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Orthogonal to spatial arrangement archetype. Field on DistrictSkeleton. Set at Layer 3; consumed at Layer 4. Minimum irregularity: 0.05 even for brand-new cities (Ozzie).
|
||||
|
||||
Specification: tyre-round3.md (Q2 resolution, full Rust), gestalt-round3.md (D-C31), paula-round3.md (per-archetype interaction rules), ozzie-round3.md §2.
|
||||
|
||||
**FoundingOrientation Spatial Grid Rotation** (D-C29)
|
||||
|
||||
District grid oriented so primary edge faces geographic founding attractor. Orientation → primary edge mapping (Gestalt D-C29). Specification: gestalt-round3.md (D-C29), paula-round3.md (D-C-FO derivation lookup).
|
||||
|
||||
---
|
||||
|
||||
### Layer 4: Street Rendering
|
||||
|
||||
**Tile Condition Derivation** (D-C32)
|
||||
|
||||
| Condition | prosperity_current range |
|
||||
|-----------|-------------------------|
|
||||
| Intact | > 0.63 |
|
||||
| Worn | 0.43–0.63 |
|
||||
| Cracked | 0.23–0.43 |
|
||||
| Broken | < 0.23 |
|
||||
|
||||
Offset values prevent boundary oscillation (Paula Round 2 proposal; Gestalt adopted). Cache invalidated on threshold crossing (one float comparison per district per economic tick).
|
||||
|
||||
Ozzie's rendering requirement: "Broken is ruins, not slightly damaged." Rendering spec must differentiate tiers visually with dramatic difference, not subtle variation.
|
||||
|
||||
Specification: burnelli-sheldon-round3.md (D-197 §tile conditions), gestalt-round3.md (D-C32), ozzie-round3.md §1.
|
||||
|
||||
**prosperity_baseline Formula** (D-197)
|
||||
|
||||
```
|
||||
prosperity_baseline_i = clamp(
|
||||
economic_tier(body, system) / 5.0 // base [0,1]
|
||||
+ ROLE_PROSPERITY_MODIFIER[economic_role] // role offset
|
||||
+ (positional_gradient_rank - 0.5) × DISTRIBUTION_INDEX_SCALE[distribution_index] // gradient
|
||||
+ topographic_modifier // +0.05 high ground, -0.05 near sea level
|
||||
, 0.05, 0.95)
|
||||
```
|
||||
|
||||
Two distinct fields locked: `prosperity_baseline` (seed-locked, never updated), `prosperity_current` (economics-variable), `prosperity_delta` (derived, never stored). Field naming is locked; any code updating `prosperity_baseline` post-generation is a bug.
|
||||
|
||||
Specification: burnelli-sheldon-round3.md (D-197).
|
||||
|
||||
---
|
||||
|
||||
### Infrastructure: Background Generation
|
||||
|
||||
**Background Generation Architecture** (D-C19 / D-C34 / GEN-7)
|
||||
|
||||
- Rayon thread pool with `GenerationQueue` (BinaryHeap by priority)
|
||||
- Priority: Immediate (player's current body) → High (body names detected in text by Aho-Corasick) → Medium (gate-adjacent) → Low (all others)
|
||||
- `GenerationCache`: LRU(50 bodies), `Arc<BodyWorldState>`, ~5MB total
|
||||
- `SystemNameIndex`: Aho-Corasick automaton over all body/system names; scans news ticker, NPC dialogue, documents to trigger pre-generation before player travels
|
||||
|
||||
**Diegetic placeholder tiers when generation incomplete (Ozzie):**
|
||||
1. Generation complete → full map
|
||||
2. Heightmap loaded, settlements pending → terrain + coastlines + named points ("settlement survey pending")
|
||||
3. Only systems.db data → known city names as points, no positions ("Positional survey pending")
|
||||
4. Nothing → blinking cursor + timestamp + "No survey data"
|
||||
|
||||
Specification: tyre-round2.md (background thread architecture), tyre-round3.md (GEN-7), gestalt-round3.md (D-C34), ozzie-round3.md §4.
|
||||
|
||||
---
|
||||
|
||||
## Complete Ticket Dependency Chain
|
||||
|
||||
### Critical Path (14.75 dev-days)
|
||||
|
||||
```
|
||||
SCHEMA-heightmaps (0.25d)
|
||||
→ PY-heightmap-import (1d)
|
||||
→ RS-heightmap-load (0.5d)
|
||||
→ RS-body-state (1.5d)
|
||||
→ RS-drainage (3d)
|
||||
→ RS-attractor-assign (4d) ←─ also needs PY-city-names-authored
|
||||
→ RS-district-mix (3d)
|
||||
→ RS-tile-conditions (1.5d)
|
||||
→ RS-block-irregularity (1d)
|
||||
```
|
||||
|
||||
**Parallel schema foundation (Tier 0 — all start immediately):**
|
||||
|
||||
BUG-WorldTier (0.5d) | SCHEMA-bodies (0.25d) | SCHEMA-heightmaps (0.25d) | SCHEMA-city-names (0.25d) | SCHEMA-feature-names (0.25d) | SCHEMA-province-bounds (0.25d)
|
||||
|
||||
**Python pipeline (Tier 1 — after schemas):**
|
||||
|
||||
PY-heightmap-import (1d) | PY-province-bounds (2d) | PY-city-names-authored (1d) | PY-city-names-corp (1d) | PY-body-radius (0.5d) | PY-economic-role-normalize (0.5d)
|
||||
|
||||
**Rust types (Tier 2 — after BUG-WorldTier):**
|
||||
|
||||
RS-types-worldtier (0.5d) → RS-types-settlement (0.5d) | RS-types-district (0.5d) | RS-types-territorial (0.5d) → RS-types-city-ctx (1d)
|
||||
|
||||
**Rust core (Tier 3):** RS-heightmap-load → RS-body-state → RS-drainage + RS-attractor-types (parallel)
|
||||
|
||||
**Generation algorithms (Tier 4):** RS-attractor-assign → RS-district-mix → RS-territorial + RS-block-irregularity + RS-tile-conditions (parallel)
|
||||
|
||||
**Background + UI (Tier 5):** RS-bg-queue → RS-aho-corasick | UI-province-bounds (parallel, depends on PY-province-bounds)
|
||||
|
||||
**Total effort:** ~33 dev-days. Two-agent parallel (Python/Rust): ~20 calendar days.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Implementation Data Quality Blockers
|
||||
|
||||
These three issues in systems.db will prevent correct algorithm execution. They are not blocking workshop closure but are blocking Phase 3 implementation start.
|
||||
|
||||
| # | Gap | Scope | Fallback available? |
|
||||
|---|-----|-------|---------------------|
|
||||
| 1 | `bodies.founding_age_years` NULL for all 273 inhabited bodies | Content work — wiki authors (Miri) | Yes: `settlement_wave` → age bracket mapping |
|
||||
| 2 | `system_economy.economic_tier` and `distribution_index` NULL for 97% of systems | Content work or confirm fallback is canonical | Yes: population-derived fallback + "moderate" default |
|
||||
| 3 | `bodies.economic_role` values not normalized (5 variants) | Schema migration — `import_economics.py` validation step | Yes: normalization table defined |
|
||||
|
||||
---
|
||||
|
||||
## Open Questions Carried Forward
|
||||
|
||||
These were not resolved in the workshop and require lead decisions before specific implementation tickets begin.
|
||||
|
||||
| Question | Context | Blocking ticket |
|
||||
|----------|---------|----------------|
|
||||
| `corporate_presence_score` derivation formula | TerritorialStatus ExtractiveZone threshold uses this field; derivation undefined | RS-territorial |
|
||||
| L3-Q7: port/station orbital approach direction | Paula designed mechanism (one query, one lookup); Ozzie formally requested for Phase 4; Gestalt/Tyre classified deferred | None (deferred) |
|
||||
| Province watershed algorithm detail | PY-province-bounds needs concrete watershed extraction spec before work begins | PY-province-bounds |
|
||||
| Q4: orbital station as city type | `SpatialArchetype::SpaceStation` as sixth variant? | None (deferred) |
|
||||
| `SyntheticPlacementReason` → political archetype (Ozzie proposal) | Ozzie: CorpExpansion → CompanyTown, PoliticalDecision → AdminCapital, PopulationOverflow → OrganicGrowth | RS-attractor-assign (if adopted) |
|
||||
| `bodies.founding_age_years` content fill | Content work for wiki authors (Miri) | RS-district-mix (fallback acceptable for Phase 1) |
|
||||
| `system_economy` fallback sufficiency | Lead must confirm population-derived tier derivation is acceptable for release | RS-attractor-assign |
|
||||
|
||||
---
|
||||
|
||||
## D-Records: Claim and File Status
|
||||
|
||||
All 26 D-records have been claimed and written to `decisions/architecture.md` (D-194–D-218) and `decisions/content.md` (D-219). The placeholder-to-real-ID mapping is in the "All D-Records — Filed" table above. No further claims needed.
|
||||
|
||||
---
|
||||
|
||||
## Key Design Principles Locked by This Workshop
|
||||
|
||||
1. **Geography is political history.** Province boundaries are watershed lines. City positions emerge from terrain. TerritorialStatus is readable from terrain and settlement density without labels.
|
||||
|
||||
2. **Every settlement has people; people have needs.** No zero-weight district types. Every settlement above Village tier has Commercial; Entertainment is guaranteed at City scale. The weight table modulates proportion, not presence.
|
||||
|
||||
3. **Prosperity baseline is memory; prosperity current is reality.** The gap between what a place was designed to be and what it is now is the most powerful player experience signal in the system. It happens automatically from the two-field model.
|
||||
|
||||
4. **Determinism with economic variance.** Geometry is seed-locked. Tile conditions are economics-variable via threshold-crossing cache invalidation. The player can destroy a district economically without the walls moving.
|
||||
|
||||
5. **The cascade is the experience.** Each layer correctly sets up the next layer's opening condition. The transition moments (atmosphere entry, Province crossing, district threshold) are where the system pays off — no authored setpieces required.
|
||||
|
||||
---
|
||||
|
||||
*Workshop complete. Three rounds. Five agents. 26 D-records filed (D-194–D-219). All four layer algorithms specified at pseudocode level. Implementation-ready.*
|
||||
@@ -0,0 +1,84 @@
|
||||
# Team Tmux Pane Test
|
||||
|
||||
## Problem
|
||||
|
||||
`teammateMode: "tmux"` is broken in Claude Code v2.1.117-119. Agents spawn as
|
||||
invisible background processes instead of getting their own tmux panes. This is
|
||||
a regression — tmux panes worked correctly on this machine through Sprint 35
|
||||
(see `~/.claude/teams/sprint-35-*/config.json` for proof: `backendType: "tmux"`
|
||||
with real pane IDs).
|
||||
|
||||
## Relevant GitHub issues
|
||||
|
||||
- https://github.com/anthropics/claude-code/issues/51818 — teammate CLI crash on permission_response
|
||||
- https://github.com/anthropics/claude-code/issues/52337 — subagent Ink rendering crash on Edit/Write
|
||||
|
||||
## Config (already set, do not change)
|
||||
|
||||
- `.claude/settings.json` has `"teammateMode": "tmux"` and `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`
|
||||
- tmux is running, `$TMUX` is set, `TERM=tmux-256color`
|
||||
|
||||
## Version gate
|
||||
|
||||
**Always start by looking up the release notes** for any versions between the last
|
||||
tested version and the current one. Check the GitHub releases page at
|
||||
`https://github.com/anthropics/claude-code/releases` for fixes related to teams,
|
||||
tmux, teammates, or the tracked issues below. If the release notes show no relevant
|
||||
fixes, log the version as SKIPPED (no relevant fixes) and stop — do not run the
|
||||
test procedure.
|
||||
|
||||
**Check `claude --version` first.** If the version appears in the "Tested versions"
|
||||
log below, the bug was already confirmed broken on that release — do not re-test.
|
||||
Only run the test procedure on a version not yet logged.
|
||||
|
||||
### Tested versions
|
||||
|
||||
| Version | Date | Result |
|
||||
|---------|------|--------|
|
||||
| 2.1.119 | 2026-04-25 | BROKEN — no tmux pane, no team config on disk |
|
||||
| 2.1.120–122 | 2026-04-30 | SKIPPED — release notes show no teams/tmux/teammate fixes; #51818 and #52337 still open (dupes of #51855, unreleased) |
|
||||
| 2.1.123 | 2026-04-30 | PARTIAL — tmux pane spawns, config on disk correct (backendType: tmux, paneId: %23). Custom subagent_type ("qatux") missing SendMessage — can receive but not send. General-purpose subagent_type has SendMessage — two-way comms work. BOTH types fail to process shutdown_request. TeamDelete reports success but does NOT kill the tmux pane — agent process keeps running, requires manual ctrl-c + exit. |
|
||||
| 2.1.123 (workshop) | 2026-04-30 | UPDATE — full workshop run with 4 general-purpose agents. Two-way comms confirmed for all. shutdown_request processed successfully by all 4 agents (contradicts earlier single-agent test). TeamDelete cleaned up correctly. Workaround: use general-purpose subagent_type with personality baked into prompt instead of custom subagent_type. |
|
||||
|
||||
## Test procedure
|
||||
|
||||
1. Check version: `claude --version`
|
||||
2. If the version is already in the "Tested versions" table above, **stop here** —
|
||||
log nothing, do not spawn agents. Report that the version is unchanged.
|
||||
3. Create a team and spawn one agent:
|
||||
|
||||
```
|
||||
TeamCreate: team_name = "tmux-test"
|
||||
Agent: name = "qatux", subagent_type = "qatux", model = "sonnet",
|
||||
run_in_background = true,
|
||||
prompt = "You are a test agent. Message the team lead: 'tmux pane test — I am alive.' Then stay idle."
|
||||
```
|
||||
|
||||
4. Check: did a new tmux pane appear? Run `tmux list-panes -a` to verify.
|
||||
5. Check: does `~/.claude/teams/tmux-test/config.json` exist and contain `backendType: "tmux"`?
|
||||
6. Log the result in the "Tested versions" table above (version, date, BROKEN/FIXED + notes).
|
||||
7. If BROKEN, clean up any leftover team state and stop. If FIXED, proceed to "Cleanup".
|
||||
|
||||
## Expected result (when fixed)
|
||||
|
||||
- A new tmux pane opens with the Qatux session visible
|
||||
- Team config on disk shows `backendType: "tmux"` with a real pane ID
|
||||
- Agent is reachable via SendMessage
|
||||
|
||||
## Actual result (v2.1.119)
|
||||
|
||||
- No tmux pane appears
|
||||
- No team config directory created on disk
|
||||
- Agent runs as invisible background process
|
||||
|
||||
## Context
|
||||
|
||||
This blocks the generation-cascade workshop (ticket #897, Sprint 38). The full
|
||||
workshop has 3 participants (Gestalt, Tyre, Miri) + Qatux documenter, 3 rounds.
|
||||
Team and tasks are already created — once tmux panes work again, spawn the
|
||||
agents and go. See `docs/workshops/generation-cascade/BRIEF.md` for the full
|
||||
workshop brief.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Delete this file and `TeamDelete` the tmux-test team once the test passes.
|
||||
Reference in New Issue
Block a user