Workshop -> decision provenance was prose-only. Adds a `decision_refs:` YAML frontmatter list (the confirmed D-records each workshop-outcomes.md touches, filtered against the governance decision set) to all 17 workshop outcomes; the two that lacked frontmatter (commodity-catalog, system-economic-specialization) get a minimal block. pql indexes the list and `SELECT fm.decision_refs` round-trips it, so "which workshops touch D-NNN" is answerable via SELECT + filter or `pql search`. decision_refs is a relevance signal (decisions a workshop discusses/produces), not a strict authorship claim — historical bare refs aren't disambiguated. Generated wiki read-only sections are left untouched. Noted in pql-requirements #5: 1.6.2 has no working DSL operator for frontmatter list-membership (`~`/`contains` error, `in` matches nothing), so membership queries need a client-side filter for now. Reproducible via tooling/pql-migrate/add_workshop_provenance.py (idempotent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
22 KiB
title, author, workshop, status, created, rounds, decision_refs
| title | author | workshop | status | created | rounds | decision_refs | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Workshop Outcomes — Planet-Down Cascade | qatux | planet-down-cascade | complete | 2026-05-01 | 3 |
|
Planet-Down Cascade — Workshop Outcomes
What This Workshop Was
The planet-down-cascade workshop was a three-round design sprint tasked with answering a single question: Given the four-layer cascade (Empty World → Population Overlay → City Planning → Street Rendering), what are the concrete algorithms at each layer?
The workshop was convened in response to the generation-cascade workshop's outcomes (docs/workshops/generation-cascade/workshop-outcomes.md), which specified the cascade structure but deferred the algorithm specifications. A consultant review (docs/workshops/planet-down-cascade/consultant-review-planet-down-cascade.md) produced six amendments before Round 1 began; these amendments significantly restructured the problem.
Participants: Gestalt (systems design), Tyre (technical architecture), Paula (narrative and political), Burnelli-Sheldon (economics), Ozzie (player experience).
Documentation: Qatux (this document).
What the Workshop Achieved
Five algorithm specifications, each at pseudocode-level detail sufficient for implementation:
-
Layer 1 (Empty World): D8 priority-flood drainage routing from heightmap BLOBs; geographic feature tag extraction (7 tags); sub-biome variant classification.
-
Layer 2 (Population Overlay): Five-phase attractor-matching pipeline assigning named cities to geographic attractors; road graph generation; TerritorialStatus derivation per Province; SettlementClass assignment for all settlements.
-
Layer 3 (City-Level Planning): Three-component district mix (D-194); FoundingOrientation spatial grid rotation; five explicit political archetype spatial patterns; BlockIrregularity from founding age.
-
Layer 4 (Street Rendering): Tile condition derivation from prosperity_current thresholds (0.63/0.43/0.23); chunk condition cache with threshold-crossing invalidation; building fill and street skeleton from BlockSkeleton.
Three data format handoffs per layer:
- Build-time Python → systems.db (heightmaps, name reservations, Province boundary polylines, economics data)
- Runtime-background Rust → BodyWorldState LRU cache (Layer 1-2 outputs; reproducible from seed + systems.db)
- Runtime-on-demand Rust → never stored (Layer 3-4 generation from BodyWorldState + CityGenerationContext)
26 D-records filed (D-194 through D-219) — all placeholder candidates claimed and written to decisions/architecture.md and decisions/content.md.
An implementation ticket dependency chain: 22+ tickets, ~14.75-day critical path, ~33 dev-days total.
Three pre-implementation data quality blockers identified (bodies.founding_age_years NULL, system_economy fields NULL, economic_role normalization needed).
Relationship to the Prior Workshop
The generation-cascade workshop (documented in docs/workshops/generation-cascade/workshop-outcomes.md) produced the cascade structure itself: four layers, three execution tiers, SeedChain (FNV-1a), CityGenerationContext struct, and D-C1 through D-C17 as Given Facts. It explicitly deferred algorithm design to a follow-on workshop.
This workshop is that follow-on. The Given Facts (D-C1 through D-C17) were carried in as fixed axioms and not reopened.
The most significant change from the generation-cascade workshop: Amendment 1 (from the consultant review) replaced the two-tier Phase 3/Phase 5 model with a three-tier model: build-time Python / runtime-background Rust / runtime-on-demand Rust. Tyre documented this pivot explicitly as "Priority 0: The Architectural Pivot" in his Round 1 file. All five agents absorbed the amendment successfully.
Amendment 3 (fully generative placement, markers.json stripped of city positions) eliminated several previously open questions (L1-Q1, L2-Q1, CL-Q4) by making them moot.
All D-Record Candidates
All D-Records — Filed
All workshop D-record candidates have been claimed, written to decisions/architecture.md (D-194–D-218) and decisions/content.md (D-219), and confirmed.
| Real ID | Title | Workshop placeholder(s) |
|---|---|---|
| D-194 | Three-Component District Mix Algorithm | D-C28, GEN-3 |
| D-195 | Attractor-Matching Compatibility Matrix | (part of D-C25) |
| D-196 | SettlementClass Enum and Active/Ghost Logic | D-C26, GEN-6 |
| D-197 | prosperity_baseline Derivation Formula | (new) |
| D-198 | Economic Simulation Independence from Layer 1-2 Spatial Data | (part of D-C18) |
| D-199 | 6-Field Minimum Economic Read Set for CityGenerationContext | (new) |
| D-200 | Three-Tier Execution Model | D-C18 |
| D-201 | Spatial Hierarchy — Eight Tiers with Locked Dimensions | D-C21 |
| D-202 | Heightmap BLOB Storage Schema | D-C33, ARCH-1 |
| D-203 | BodyWorldState Bevy Resource with LRU Cache | ARCH-2, D-C34 |
| D-204 | body_radius_km Column on bodies Table | ARCH-4 |
| D-205 | Province Boundary Pre-Computation | ARCH-5 |
| D-206 | Background Generation Priority Queue | D-C19, GEN-7 |
| D-207 | Fully Generative Placement | D-C20 |
| D-208 | D8 Priority-Flood Drainage Routing | D-C22, GEN-1 |
| D-209 | Geographic Feature Tags — 7 Tags | D-C23 |
| D-210 | Sub-Biome Variant Classification | D-C24 |
| D-211 | Attractor-Matching Five-Phase Pipeline | D-C25, GEN-2 |
| D-212 | TerritorialStatus Derivation | D-C27, GEN-5 |
| D-213 | FoundingOrientation Enum and Grid Rotation | D-C29, D-C-FO |
| D-214 | AttractorAssignment Enum | D-C-AA |
| D-215 | Five Spatial Arrangement Patterns | D-C30, D-C-SA |
| D-216 | BlockIrregularity from founding_age | D-C31, GEN-4 |
| D-217 | Tile Condition Thresholds | D-C32 |
| D-218 | WorldTier Enum Canonical Values | GEN-8 |
| D-219 | Naming Registers Lookup Table | D-C-NL |
GEN-9 (atlas_feature_names schema) is covered by the schema ticket #903 but does not have a standalone D-record — the DDL is straightforward and doesn't require a design decision.
All Locked Algorithms
Layer 1: Empty World Generator
D8 Drainage Routing (Tyre ARCH-1/GEN-1; Gestalt D-C22)
- Input: atlas_body_heightmaps BLOB (float32 LE, 512×256)
- Algorithm: D8 single-direction steepest descent; priority-flood sink filling; flow accumulation
- River threshold: cells with flow_accumulation > 200 are river cells
- Outputs: RiverNetwork (river_cells, confluence_nodes, river_mouths), drainage basin boundaries
- Performance: ~50ms per body
- Specification: tyre-round2.md (ARCH-1), gestalt-round2.md (D-C22), tyre-round3.md (GEN-1)
Geographic Feature Tag Extraction (Gestalt D-C23)
- 7 tags derived per regional cell (64×32 grid)
- Derivation rules: RiverConfluence (any confluence in bbox), CoastalHarbor (coastal + roughness<0.3), MountainPass (roughness>0.65, lower cells on two sides), ArablePlain (roughness<0.25 + terrestrial + arable biome), ResourceConcentration (economic_role biome prior), Defensible (roughness>0.5 + ≤2 approach vectors), NaturalBarrier (ocean or roughness>0.85)
- Specification: gestalt-round3.md §D-C23
Sub-Biome Variant Classification (Gestalt D-C24)
- 3-4 variants per biome class, FNV-1a seed-derived, economic_role as probability prior
terrain_modification_cost: f32= roughness×0.6 + biome_clearing_cost, clamped [0,1]- Specification: gestalt-round3.md §D-C24
Layer 2: Population Overlay
Attractor-Matching Five-Phase Pipeline (D-C25 + D-195)
Phase 0 — Score matrix: Build N×M matrix. Apply D-195 compatibility matrix (0–10 scale, 10 roles × 7 attractor types). Hard-zero physically impossible pairings (Paula H1-H4).
Phase 1 — Sort cities by constraint tier:
- Tier A: extraction/mining (most constrained)
- Tier B: manufacturing/transit (moderate)
- Tier C: service/commercial (most flexible)
Phase 2 — Tier A greedy assignment: fewest-valid-attractors-first order.
Phase 3 — Hungarian algorithm on Tier B+C: maximum-weight bipartite matching, O(N³), N≤30.
Phase 4 — Synthetic overflow: SyntheticPlacementReason { PopulationOverflow | PoliticalDecision | CorpExpansion }.
Mismatch classification (lead decision): Soft warning at score < 0.35 (log, proceed). Hard error at score < 0.15 (override to Synthetic { PoliticalDecision }, place at Province centroid, AdminFacing).
FoundingOrientation derivation post-assignment (Paula D-C-FO): Geographic attractor type → orientation lookup. Synthetic assignments → AdminFacing.
- Specification: gestalt-round2.md (pseudocode), tyre-round3.md (five-phase with MismatchSeverity), paula-round3.md (D-C-AM, D-C-FO), burnelli-sheldon-round3.md (D-195)
TerritorialStatus Priority-Ordered Derivation (D-C27)
fn derive_territorial_status(province: &ProvinceWorldState) -> TerritorialStatus {
if province.placed_at_generation && !province.active { return AbandonedZone; }
if !province.placed_at_generation && province.settlement_count == 0 { return WildernessBuffer; }
if province.primary_economic_activity == Extraction && province.corporate_presence_score > 0.4 { return ExtractiveZone; }
if province.jurisdiction_overlap_score > 0.3 { return ContestZone; }
if province.infrastructure_quality > 0.6 && province.corporate_road_maintenance > 0.5 { return CoreTerritory; }
FrontierTerritory
}
placed_at_generation: bool is the sole differentiator between AbandonedZone and WildernessBuffer. Immutable; set at Layer 2.
- Specification: paula-round2.md, gestalt-round2.md (Rust code), burnelli-sheldon-round3.md (D-197, threshold confirmation)
SettlementClass Enum (D-196)
enum SettlementClass {
NameLocked, // Corp HQ body; always active
PopulationBudget, // Active if nearest anchor city health_metric > 0.4
EconomicTriggered { activating_corp_id: String }, // Active if corp health > 0.4
OrganicGrowth, // Active if province avg corp health > 0.5
}
Ghost rendering: seed-locked geometry preserved; lighting dark; tile condition Broken; no NPC spawns.
- Specification: burnelli-sheldon-round3.md (D-196)
Layer 3: City-Level Planning
Three-Component District Mix (D-194)
Component 1 — Population tier guarantees:
| Population | Guaranteed district types |
|---|---|
| 1–999 | Residential + Mixed |
| 1,000–9,999 | Residential + Commercial |
| 10,000–99,999 | + Entertainment |
| 100,000–499,999 | + Administrative |
| 500,000+ | + Industrial/Civic if role-appropriate |
Component 2 — Economic role multiplier table (10 roles × 9 types; all rows sum to 100; minimum cell value 3):
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|---|---|---|---|---|---|---|---|---|---|
| manufacturing | 18 | 8 | 30 | 6 | 18 | 5 | 8 | 5 | 2 |
| agricultural | 22 | 12 | 5 | 10 | 20 | 5 | 15 | 5 | 6 |
| extraction | 16 | 7 | 28 | 4 | 24 | 5 | 9 | 5 | 2 |
| transit | 10 | 18 | 5 | 5 | 20 | 12 | 14 | 14 | 2 |
| research | 16 | 6 | 5 | 14 | 5 | 7 | 10 | 3 | 34 |
| commercial | 14 | 30 | 5 | 6 | 10 | 14 | 14 | 5 | 2 |
| service_mixed | 20 | 18 | 5 | 10 | 6 | 14 | 18 | 7 | 2 |
| mining | 16 | 7 | 22 | 3 | 28 | 6 | 9 | 7 | 2 |
| frontier | 26 | 10 | 10 | 5 | 18 | 8 | 16 | 5 | 2 |
| energy | 10 | 3 | 18 | 6 | 24 | 3 | 6 | 6 | 24 |
Political archetype modifiers (stacked additive, floor at 3 applied after):
| political_archetype | Modifiers |
|---|---|
| CompanyTown | Adm −10, Ind +10, Log +5, Res +5 |
| AdminCapital | Adm +20, Ent +5, Spe +5, Ind −15 |
| FreePort | Com +15, Trn +10, Mix +5, Adm −15 |
| Contested | No modifier |
| OrganicGrowth | Mix +15, Res +10, Ind −10, Adm −5 |
Component 3 — Settlement age character modifier: affects perimeter_treatment, density_pct, character_class on DistrictSkeleton. Does NOT modify district count or type. Applies only at Backwater WorldTier and above.
Self-contained: no cross-city queries. Single seed per city. Specification: burnelli-sheldon-round3.md (D-194).
Five Explicit Spatial Arrangement Patterns (D-C30)
| Archetype | Pattern | Player acceptance criterion |
|---|---|---|
| CompanyTown | Spine: Industrial/facility at FoundingOrientation terminus; Residential cascades back | Posture visible; city points at its facility |
| AdminCapital | Radial: Administrative hub at center (DefenseFacing) or prestige edge (AdminFacing); prosperity decreases outward | Power visible from any approach |
| FreePort | Multi-node: 2-3 nodes with distinct character; no dominant center | Player gets productively lost; nodes serve as landmarks |
| Contested | Dual-center overlay: two underlying geometric plans; boundary zone Mixed with Checkpoint perimeter | Player reads the conflict in street layout |
| OrganicGrowth | Irregular local density: Voronoi around seed attractor points; ±12.5% grid jitter | Most "lived-in" feel; reads as historically authentic |
Acceptance criterion (Ozzie): player identifies archetype from 15 seconds of walking without consulting implant UI.
Specification: paula-round3.md (D-C-SA with Rust pseudocode), gestalt-round3.md (D-C30), ozzie-round3.md §3.
BlockIrregularity from founding_age (D-C31)
enum BlockIrregularity { Grid, SlightlyWorn, Irregular, Organic }
impl BlockIrregularity {
fn from_age_and_archetype(founding_age_years: u32, archetype: SpatialArchetype) -> Self {
let step = match archetype {
CompanyTown => 40, AdminCapital => 100, FreePort => 60,
Contested => 35, OrganicGrowth => 20,
};
match founding_age_years / step { 0 => Grid, 1 => SlightlyWorn, 2 => Irregular, _ => Organic }
}
}
Orthogonal to spatial arrangement archetype. Field on DistrictSkeleton. Set at Layer 3; consumed at Layer 4. Minimum irregularity: 0.05 even for brand-new cities (Ozzie).
Specification: tyre-round3.md (Q2 resolution, full Rust), gestalt-round3.md (D-C31), paula-round3.md (per-archetype interaction rules), ozzie-round3.md §2.
FoundingOrientation Spatial Grid Rotation (D-C29)
District grid oriented so primary edge faces geographic founding attractor. Orientation → primary edge mapping (Gestalt D-C29). Specification: gestalt-round3.md (D-C29), paula-round3.md (D-C-FO derivation lookup).
Layer 4: Street Rendering
Tile Condition Derivation (D-C32)
| Condition | prosperity_current range |
|---|---|
| Intact | > 0.63 |
| Worn | 0.43–0.63 |
| Cracked | 0.23–0.43 |
| Broken | < 0.23 |
Offset values prevent boundary oscillation (Paula Round 2 proposal; Gestalt adopted). Cache invalidated on threshold crossing (one float comparison per district per economic tick).
Ozzie's rendering requirement: "Broken is ruins, not slightly damaged." Rendering spec must differentiate tiers visually with dramatic difference, not subtle variation.
Specification: burnelli-sheldon-round3.md (D-197 §tile conditions), gestalt-round3.md (D-C32), ozzie-round3.md §1.
prosperity_baseline Formula (D-197)
prosperity_baseline_i = clamp(
economic_tier(body, system) / 5.0 // base [0,1]
+ ROLE_PROSPERITY_MODIFIER[economic_role] // role offset
+ (positional_gradient_rank - 0.5) × DISTRIBUTION_INDEX_SCALE[distribution_index] // gradient
+ topographic_modifier // +0.05 high ground, -0.05 near sea level
, 0.05, 0.95)
Two distinct fields locked: prosperity_baseline (seed-locked, never updated), prosperity_current (economics-variable), prosperity_delta (derived, never stored). Field naming is locked; any code updating prosperity_baseline post-generation is a bug.
Specification: burnelli-sheldon-round3.md (D-197).
Infrastructure: Background Generation
Background Generation Architecture (D-C19 / D-C34 / GEN-7)
- Rayon thread pool with
GenerationQueue(BinaryHeap by priority) - Priority: Immediate (player's current body) → High (body names detected in text by Aho-Corasick) → Medium (gate-adjacent) → Low (all others)
GenerationCache: LRU(50 bodies),Arc<BodyWorldState>, ~5MB totalSystemNameIndex: Aho-Corasick automaton over all body/system names; scans news ticker, NPC dialogue, documents to trigger pre-generation before player travels
Diegetic placeholder tiers when generation incomplete (Ozzie):
- Generation complete → full map
- Heightmap loaded, settlements pending → terrain + coastlines + named points ("settlement survey pending")
- Only systems.db data → known city names as points, no positions ("Positional survey pending")
- Nothing → blinking cursor + timestamp + "No survey data"
Specification: tyre-round2.md (background thread architecture), tyre-round3.md (GEN-7), gestalt-round3.md (D-C34), ozzie-round3.md §4.
Complete Ticket Dependency Chain
Critical Path (14.75 dev-days)
SCHEMA-heightmaps (0.25d)
→ PY-heightmap-import (1d)
→ RS-heightmap-load (0.5d)
→ RS-body-state (1.5d)
→ RS-drainage (3d)
→ RS-attractor-assign (4d) ←─ also needs PY-city-names-authored
→ RS-district-mix (3d)
→ RS-tile-conditions (1.5d)
→ RS-block-irregularity (1d)
Parallel schema foundation (Tier 0 — all start immediately):
BUG-WorldTier (0.5d) | SCHEMA-bodies (0.25d) | SCHEMA-heightmaps (0.25d) | SCHEMA-city-names (0.25d) | SCHEMA-feature-names (0.25d) | SCHEMA-province-bounds (0.25d)
Python pipeline (Tier 1 — after schemas):
PY-heightmap-import (1d) | PY-province-bounds (2d) | PY-city-names-authored (1d) | PY-city-names-corp (1d) | PY-body-radius (0.5d) | PY-economic-role-normalize (0.5d)
Rust types (Tier 2 — after BUG-WorldTier):
RS-types-worldtier (0.5d) → RS-types-settlement (0.5d) | RS-types-district (0.5d) | RS-types-territorial (0.5d) → RS-types-city-ctx (1d)
Rust core (Tier 3): RS-heightmap-load → RS-body-state → RS-drainage + RS-attractor-types (parallel)
Generation algorithms (Tier 4): RS-attractor-assign → RS-district-mix → RS-territorial + RS-block-irregularity + RS-tile-conditions (parallel)
Background + UI (Tier 5): RS-bg-queue → RS-aho-corasick | UI-province-bounds (parallel, depends on PY-province-bounds)
Total effort: ~33 dev-days. Two-agent parallel (Python/Rust): ~20 calendar days.
Pre-Implementation Data Quality Blockers
These three issues in systems.db will prevent correct algorithm execution. They are not blocking workshop closure but are blocking Phase 3 implementation start.
| # | Gap | Scope | Fallback available? |
|---|---|---|---|
| 1 | bodies.founding_age_years NULL for all 273 inhabited bodies |
Content work — wiki authors (Miri) | Yes: settlement_wave → age bracket mapping |
| 2 | system_economy.economic_tier and distribution_index NULL for 97% of systems |
Content work or confirm fallback is canonical | Yes: population-derived fallback + "moderate" default |
| 3 | bodies.economic_role values not normalized (5 variants) |
Schema migration — import_economics.py validation step |
Yes: normalization table defined |
Open Questions Carried Forward
These were not resolved in the workshop and require lead decisions before specific implementation tickets begin.
| Question | Context | Blocking ticket |
|---|---|---|
corporate_presence_score derivation formula |
TerritorialStatus ExtractiveZone threshold uses this field; derivation undefined | RS-territorial |
| L3-Q7: port/station orbital approach direction | Paula designed mechanism (one query, one lookup); Ozzie formally requested for Phase 4; Gestalt/Tyre classified deferred | None (deferred) |
| Province watershed algorithm detail | PY-province-bounds needs concrete watershed extraction spec before work begins | PY-province-bounds |
| Q4: orbital station as city type | SpatialArchetype::SpaceStation as sixth variant? |
None (deferred) |
SyntheticPlacementReason → political archetype (Ozzie proposal) |
Ozzie: CorpExpansion → CompanyTown, PoliticalDecision → AdminCapital, PopulationOverflow → OrganicGrowth | RS-attractor-assign (if adopted) |
bodies.founding_age_years content fill |
Content work for wiki authors (Miri) | RS-district-mix (fallback acceptable for Phase 1) |
system_economy fallback sufficiency |
Lead must confirm population-derived tier derivation is acceptable for release | RS-attractor-assign |
D-Records: Claim and File Status
All 26 D-records have been claimed and written to decisions/architecture.md (D-194–D-218) and decisions/content.md (D-219). The placeholder-to-real-ID mapping is in the "All D-Records — Filed" table above. No further claims needed.
Key Design Principles Locked by This Workshop
-
Geography is political history. Province boundaries are watershed lines. City positions emerge from terrain. TerritorialStatus is readable from terrain and settlement density without labels.
-
Every settlement has people; people have needs. No zero-weight district types. Every settlement above Village tier has Commercial; Entertainment is guaranteed at City scale. The weight table modulates proportion, not presence.
-
Prosperity baseline is memory; prosperity current is reality. The gap between what a place was designed to be and what it is now is the most powerful player experience signal in the system. It happens automatically from the two-field model.
-
Determinism with economic variance. Geometry is seed-locked. Tile conditions are economics-variable via threshold-crossing cache invalidation. The player can destroy a district economically without the walls moving.
-
The cascade is the experience. Each layer correctly sets up the next layer's opening condition. The transition moments (atmosphere entry, Province crossing, district threshold) are where the system pays off — no authored setpieces required.
Workshop complete. Three rounds. Five agents. 26 D-records filed (D-194–D-219). All four layer algorithms specified at pseudocode level. Implementation-ready.