--- title: "Tyre — Round 1: Technical Architecture Inventory" description: "Layer-by-layer audit of the generation pipeline: state, data format boundaries, dependency order, testing strategy" type: workshop status: active workshop: generation-cascade agent: tyre round: 1 created: 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 6–7 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.db` — `star_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` (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` — 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 1–4 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` 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.