--- 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)> 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 (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 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). 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` 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` (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` 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.*