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>
675 lines
39 KiB
Markdown
675 lines
39 KiB
Markdown
---
|
||
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.*
|