Files
jpmschweitzerandClaude Opus 4.6 42ee1f0a0e docs(workshops): generation cascade workshop — 4 rounds, D-194 through D-218
Four-round workshop (Gestalt, Tyre, Paula, Burnelli-Sheldon, Miri)
mapping the full generation pipeline from planetary heightmap to
walkable tile. 25 D-records produced. Ticket dependency chain for
Tier 0-4 implementation identified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 20:18:17 +02:00

18 KiB
Raw Permalink Blame History

title, description, type, status, workshop, agent, round, created
title description type status workshop agent round created
Tyre — Round 1: Technical Architecture Inventory Layer-by-layer audit of the generation pipeline: state, data format boundaries, dependency order, testing strategy workshop active generation-cascade tyre 1 2026-04-30

Generation Cascade — Round 1: Technical Architecture Inventory

Tyre — Technical Architect


Summary Verdict

The pipeline has a hard cliff at Layer 5. Everything above it (galactic through city placement) is working production code. Everything below it is either stub types or missing entirely. The two-phase district generator architecture is well-designed on paper; zero generation code exists.

The thinnest vertical slice from heightmap to walkable tile — a flat single-district world with no social structure — costs roughly 67 dev-days starting from today's codebase.


Pipeline Inventory

Layer 1 — Galactic Scale

State: DONE

Property Detail
Input Wiki TOML files, body index.md frontmatter
Output systems.dbstar_systems, bodies, economics tables
Code tooling/economy-db/import_economics.py + Rust generate_brands binary
Format SQLite (file-based; read-only canonical snapshot)

No gaps. This is the upstream root for all other layers.


Layer 2 — Planetary Heightmap Simulation

State: DONE

Property Detail
Input Body definition dict (planet_class, parameters from index.md)
Output Terrain dict: NumPy arrays for elevation, temperature, moisture, surface_water, river_grid
Code tooling/planet-gen/planet_simulation.py
Format In-memory NumPy (not persisted separately; consumed immediately by atlas generator)

No gaps. Called by generate_atlas.py as simulate(body_def).


Layer 3 — Atlas City Placement and Infrastructure

State: DONE

Property Detail
Input Terrain dict (Layer 2) + body definition from systems.db
Output markers.json per body (cities, roads, rail, POIs) + atlas tables in systems.db
Code tooling/planet-gen/generate_atlas.py
Format JSON files (pixel space: 512×256 grid) + SQLite

Output schema: cities[{id, name, kind, center: [row, col], population}], roads/railroads as A* paths. Gate terminal POI placed at largest population centre.

No gaps in the atlas layer itself. The gap is downstream: nothing in the Rust runtime consumes this output.

Critical scale fact: Atlas coordinates are (row, col) in a 512×256 pixel grid. A district is 512×512 sim tiles = 256×256 visual tiles = 256m. The ratio between atlas pixels and sim tiles is undefined and unimplemented. This is the first missing translation.


Layer 4 — City Naming

State: DONE

Property Detail
Input markers.json (empty name fields)
Output markers.json (named cities and features, Gemma-driven)
Code tooling/planet-gen/gemma_naming.py
Format JSON (same file, in-place update)

No gaps.


Layer 5 — City-to-District Decomposition

State: MISSING — no design, no code, no types

This is the first gap in the entire pipeline. A city marker in the atlas is a single pixel coordinate. A walkable district is 512×512 sim tiles. There is no code or design that answers:

  • How many districts does a city of population N get?
  • What is the coordinate system mapping from atlas pixel-space to world tile-space?
  • How are district boundaries drawn between cities?
  • How are inter-city "wilderness" districts handled?
  • What district IDs are assigned, and how are they derived deterministically from the world seed?

The DistrictContext type on DistrictSkeleton is pub type DistrictContext = String — it exists to hold world position and neighboring district references, but nothing populates it.

Data format boundary: Python JSON (markers.json, 512×256 pixel coordinates) → Rust runtime (DistrictId, world tile positions). No bridge code exists.

Minimum viable implementation: A function that takes a body's atlas markers and produces a Vec<(DistrictId, world_tile_origin)> — one district per city, deterministically positioned. This is the unlock for everything below.


Layer 6 — DistrictSkeleton Generation (Phase 1)

State: STUB — types compile, zero generation code

The designed five-stage pipeline from the workshop-outcomes.md:

Stage What Code state
Stage 1: Classification WorldTier, ComplexityTier, SettingType Types exist; no generation logic
Stage 2: Block grid 4×4 BlockSkeleton, DistrictLayoutMode Types exist; no generation logic
Stage 3: Reservation MultiBlockReservation, skyscrapers, terminals Types exist; no generation logic
Stage 4: Social site + NPC SocialSitePlacement, triangles, DerivedDistrictAnalysis Types exist; no generation logic
Stage 5: Guarantee audit GuaranteeAuditResult Type is String stub

Every sub-type that requires actual content or logic is a pub type Foo = String alias:

  • DistrictContext, SocietyProfileRef, ZoneDefinition, DistrictBoundaries
  • GuaranteeAuditResult, CorridorSpine, ChunkLayout, CorridorSpine
  • Era, EraModification, LandmarkSlot, AccessPoint

WorldTier naming mismatch (bug to fix before any generation work):
generator.rs defines: Peripheral | Connected | Core
Workshop outcomes (L-3, lead decision): Epicenter | Regional | Backwater | Passage | Waypoint
These are incompatible. The workshop canonical values must replace the current enum before the generation spike begins.

Missing DistrictSkeleton fields:
The struct in generator.rs is missing three fields specified in the workshop outcomes design:

  • vertical_structure: VerticalStructure (from D-READY-11)
  • breach_only_zones: Vec<ZoneId> (from guarantee audit, Tier 2)
  • derived_analysis: DerivedDistrictAnalysis (Phase 1 computed, from Miri/Gestalt)

Minimum viable implementation: Stage 1 + Stage 2 only. Classification from WorldTier + flat 4×4 block grid with uniform zoning. No reservations, no social sites, no audit. Produces a compilable DistrictSkeleton that can be handed to Phase 2.

Workshop estimate for Phase 1 basic: ~3 dev-days.


Layer 7 — ChunkData Generation (Phase 2)

State: MISSING — no code, no type resolution

Phase 2 is the per-chunk fill step that runs when a chunk is first loaded. It reads the BlockSkeleton for the block containing the chunk and produces tile data (walls, floors, furniture placements).

GeneratorChunkData is pub type GeneratorChunkData = Vec<bool> — a flat boolean walkability array. The actual tile type system (TileId) is pub type TileId = String. No tile vocabulary exists.

The heritage grammar overlay system (D-READY-9, HeritageGrammarOverlay) does not exist as Rust code or TOML data. It was designed in the workshop but nothing has been authored.

Data format boundary: BlockSkeleton (Rust struct) → 64×64 tile grid (GeneratorChunkData). The generation logic for this conversion is entirely absent.

Minimum viable implementation: Produce a flat walkable floor tile grid for each chunk, with wall tiles on chunk boundaries. No rooms, no furniture. Just a walkable district footprint. Heritage grammar can be deferred.

Workshop estimate for Phase 2 basic: ~4 dev-days. With heritage grammar: substantially more.


Layer 8 — Chunk Streaming + Generator Integration

State: PARTIAL — streaming works, generator hookup is zero

chunk_streaming.rs correctly manages load/unload based on player position. It compiles, passes tests, and handles z-level isolation properly. The architecture is sound.

The gap: WalkabilityMap::load_chunk() creates ChunkData::new_walkable() — a fully-walkable blank placeholder. It has no knowledge of districts, no connection to any generator, and no way to distinguish "in a building" from "in open space."

For Phase 5 (generated world), load_chunk() needs to:

  1. Determine which district the chunk coordinate falls within
  2. Look up the DistrictSkeleton for that district
  3. Identify the BlockSkeleton for the block containing this chunk
  4. Run Phase 2 fill and return real tile data

None of steps 14 exist. The streaming system is a ready container waiting for content.

Minimum viable integration: A DistrictMap resource mapping ChunkCoord → DistrictId, and a Phase 2 dispatch in load_chunk(). ~1 dev-day once Phase 1 and 2 exist.


Layer 9 — Social Site Templates and Triangle System

State: DONE (runtime system, not wired to generator output)

triangle.rs is fully implemented production code:

  • FullTemplateDef, RoleSchema, SpaceSpec, TriangleDef — content schema with validation
  • generate_intra_template_triangles and generate_cross_template_triangles — working
  • tick_triangle_escalation and apply_resolve_triangle — working ECS systems
  • Tests pass, serialization correct, FNV-1a determinism enforced (D-010)

The gap: DistrictSkeleton.social_sites contains Vec<SocialSitePlacement> with template_tag: String references. Nothing reads those tags and spawns TemplateOwnership components. The link from generator output to runtime triangle population is missing.


Layer 10 — NPC Generation Spike

State: PARTIAL — proof-of-concept, not production pipeline

server/src/bin/generator_spike.rs (Sprint 25) generates NPCs from ZoneSpec + CultureProfile inputs. It produces named NPCs with traits, wants, behaviors, relationships, and want-tells. It works and demonstrates the 10-axis model is viable.

However: it is a standalone binary that bypasses ECS entirely. It operates on ZoneSpec (not DistrictSkeleton). It is not wired to the spatial generation pipeline. Its output is printed to stdout, not spawned into the ECS world.

The gap between this spike and production NPC generation: ZoneSpec must be derived from BlockSkeleton.zoning + DistrictSkeleton.society_profile, then NPCs must be spawned with TemplateOwnership components into the bevy ECS world.


Data Format Boundaries

Four boundaries, three of them currently bridgeless:

Python atlas output        → [BRIDGELESS] → Rust runtime
markers.json               →              → DistrictMap resource
  (512×256 pixel coords)   →              → (sim tile coords)

Atlas city marker          → [BRIDGELESS] → District grid
  (row, col) pixel         →              → Vec<(DistrictId, ChunkCoord)>

DistrictSkeleton (Rust)    → [BRIDGELESS] → ChunkData (Rust)
  Phase 1 output           →              → Phase 2 fill
  BlockSkeleton            →              → 64×64 tile grid

chunk_streaming.rs         → [PARTIAL]    → generator
  load_chunk()             →              → ChunkData::new_walkable() (wrong)

The only working boundary is Python atlas → systems.db (the atlas tables), which is consumed by the Godot atlas map UI but not by the game simulation server.


Strict Dependency Order

[DONE]    Layer 1-4: Atlas pipeline complete
              ↓
[MISSING] Layer 5: City-to-district decomposition     ← FIRST UNLOCK
              ↓
[STUB]    Layer 6: DistrictSkeleton Phase 1 generation
              ↓  (WorldTier enum fix is a prerequisite)
[MISSING] Layer 7: ChunkData Phase 2 fill
              ↓
[PARTIAL] Layer 8: chunk_streaming → generator wiring
              ↓
              ↓ character/apartment work gates here
[BACKLOG] #681 Apartment generator
[BACKLOG] #682 Apartment rendering

Parallelization opportunities within the blocked stack:

  • Once Layer 5 design is locked, Phase 1 (Layer 6) and content authoring (heritage grammar TOML, zone-type RON files, culture profiles) can proceed in parallel — they share no code dependency
  • Different districts can be generated in parallel once Layer 6 code exists (each district is seed-isolated)
  • Triangle template YAML authoring has no code dependency on Layer 5-8 and can proceed now

NOT blocked by generation pipeline:

  • #619 Full character customization — Phase 4 work on a hand-authored 2-floor test map
  • #694 Character creation screen — Phase 4, same rationale
  • #616 Verb vocabulary design — content design work, cascade phase 4

Ticket Analysis

Tickets with incorrect cascade position (should be formally blocked)

Ticket Title Current status Issue
#681 Apartment generator (server) backlog Needs walkable generated world (Layers 5-8 complete). Should be explicitly blocked.
#682 Apartment rendering (client) backlog Blocked by #681, which is blocked by the generation pipeline.
#615 Tycoon small business starting state backlog Feeds #681. Should be blocked behind pipeline completion.

Tickets that can proceed (Phase 4 per cascade definition)

Ticket Title Reasoning
#694 Character creation screen Phase 4: player control scheme on 2-floor test map. Does not require generated world.
#619 Full character customization Phase 4: same. Aesthetic-only, works on hand-authored map.
#616 Verb vocabulary design Content design. No code dependency on generation pipeline.

New tickets needed

These represent the actual missing implementation work:

  1. "Fix WorldTier enum values in generator.rs" — Replace Peripheral/Connected/Core with Epicenter/Regional/Backwater/Passage/Waypoint per lead decision L-3. Blocked by nothing. ~0.5 dev-days.

  2. "City-to-district decomposition — spec and implementation" — Define atlas pixel → world tile coordinate mapping, district count per city, district boundary algorithm. Produces Vec<(DistrictId, world_tile_origin)> from atlas markers. Blocks all Phase 1/2 work. ~2 dev-days.

  3. "DistrictSkeleton Phase 1 generator (Stages 1-2)" — Classification + block grid. Minimal viable Phase 1 that produces a compilable skeleton. Blocked by ticket 1 and 2. ~3 dev-days.

  4. "Add missing DistrictSkeleton fields"vertical_structure, breach_only_zones, derived_analysis from workshop spec. Can be stub types initially, but must be present for schema completeness. ~0.5 dev-days.

  5. "ChunkData Phase 2 minimal fill" — Flat walkable floor with wall boundaries. No rooms, no furniture. Blocked by ticket 3. ~2 dev-days (minimal slice).

  6. "Wire generator to chunk_streaming"DistrictMap resource + dispatch in load_chunk(). Blocked by tickets 2, 3, 5. ~1 dev-day.


Testing Strategy Per Layer

Design principle: Each layer produces deterministic output from a seed. Same seed = same output. This is the primary invariant to test at every layer.

Layer Test approach Independent?
Atlas (done) make regen-db + make check-systems-db-stamp + visual Godot inspection Yes
City-to-district decomp Unit test: given N markers, produces M districts with valid positions and no overlaps. Determinism: same seed → same output. Yes
DistrictSkeleton Unit test: same seed → identical skeleton (byte-level). Coverage test: 100 seeds → no two identical skeletons. Guarantee audit: all Tier 1 sites present for inhabited districts. Yes
ChunkData fill Gauntlet snapshot test (new Gauntlet room for generated district). Visual inspection in --test-mode SR_LIVE=1. Walkability consistency: every Phase 2 chunk must have ≥1 walkable tile. Yes
Chunk streaming integration Existing chunk_streaming tests verify load/unload logic. New: integration test verifying loaded chunks are non-blank after generator wiring. Partially (streaming logic tested; fill content not yet)

The Gauntlet test world should get a dedicated "generated district" room for Phase 2 validation once Phase 1+2 exist. Per CLAUDE.md rules, this must be a new room, not modification of existing rooms.


Implementation Priority Order

Ordered by what unblocks the most:

  1. Fix WorldTier enum — 0.5 days, unblocks all generator work (nothing runs correctly until this is right)
  2. City-to-district decomp — 2 days, the first real gap; unblocks all Phase 1/2 code
  3. DistrictSkeleton Phase 1 (Stages 1-2 only) — 3 days, produces first generated skeletons
  4. Missing DistrictSkeleton fields — 0.5 days, schema completeness
  5. ChunkData Phase 2 minimal — 2 days, produces first walkable generated tiles
  6. Chunk streaming integration — 1 day, first end-to-end walkable generated world
  7. Full Phase 2 with rooms/furniture — 4+ days (after vertical slice validated)
  8. Vertical structure / z-levels — 7 days, Phase 4/5+ (per workshop estimate)
  9. MobileChunk — 9.5 days, deferred until static districts work

Total for thinnest vertical slice (flat walkable district): ~9 dev-days

Formal gate: Character and apartment work (#681, #682, #615) should be blocked in the ticket system behind the vertical slice milestone (items 1-6 above complete).


Appendix: Key Architectural Constraints to Preserve

These are non-negotiable from the workshop and D-records:

  • D-010 determinism: All generation must consume randomness exclusively through SimRng. No std::hash::DefaultHasher. Same seed = same world, always.
  • D-010 BTreeMap: All HashMap usage in generator output types is prohibited. Use BTreeMap throughout.
  • D-108 MobileChunk: Vessel interiors use the same Phase 2 primitives as static districts. No separate generation path.
  • D-110 signed z-levels: base_z: i8 for positions, z_levels: u8 for counts. Already implemented correctly in the types.
  • L-7 XOR prohibition: In-playthrough events use DamageOverlay exclusively. No XOR reseeding.
  • Two-phase immutability: Phase 1 output is frozen. All runtime changes go through the overlay/delta layer.
  • chunk_streaming cadence: Default 10 ticks (1 game-minute). Phase 2 fill must complete within a single cadence window or be deferred to background.