Files
settled-reach/docs/workshops/planet-down-cascade/gestalt-round3.md
T
jpmschweitzerandClaude Opus 4.6 b9fd75b840 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>
2026-05-03 20:18:30 +02:00

675 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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,0009,999 | Residential, Commercial, Mixed |
| 10,00099,999 | + Entertainment |
| 100,000499,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.*