Files
settled-reach/docs/workshops/generation-cascade/tyre-round1.md
T
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

339 lines
18 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: "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 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.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<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.