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>
18 KiB
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 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,DistrictBoundariesGuaranteeAuditResult,CorridorSpine,ChunkLayout,CorridorSpineEra,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:
- Determine which district the chunk coordinate falls within
- Look up the
DistrictSkeletonfor that district - Identify the
BlockSkeletonfor the block containing this chunk - 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 validationgenerate_intra_template_trianglesandgenerate_cross_template_triangles— workingtick_triangle_escalationandapply_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:
-
"Fix WorldTier enum values in generator.rs" — Replace
Peripheral/Connected/CorewithEpicenter/Regional/Backwater/Passage/Waypointper lead decision L-3. Blocked by nothing. ~0.5 dev-days. -
"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. -
"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.
-
"Add missing DistrictSkeleton fields" —
vertical_structure,breach_only_zones,derived_analysisfrom workshop spec. Can be stub types initially, but must be present for schema completeness. ~0.5 dev-days. -
"ChunkData Phase 2 minimal fill" — Flat walkable floor with wall boundaries. No rooms, no furniture. Blocked by ticket 3. ~2 dev-days (minimal slice).
-
"Wire generator to chunk_streaming" —
DistrictMapresource + dispatch inload_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:
- Fix WorldTier enum — 0.5 days, unblocks all generator work (nothing runs correctly until this is right)
- City-to-district decomp — 2 days, the first real gap; unblocks all Phase 1/2 code
- DistrictSkeleton Phase 1 (Stages 1-2 only) — 3 days, produces first generated skeletons
- Missing DistrictSkeleton fields — 0.5 days, schema completeness
- ChunkData Phase 2 minimal — 2 days, produces first walkable generated tiles
- Chunk streaming integration — 1 day, first end-to-end walkable generated world
- Full Phase 2 with rooms/furniture — 4+ days (after vertical slice validated)
- Vertical structure / z-levels — 7 days, Phase 4/5+ (per workshop estimate)
- 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. Nostd::hash::DefaultHasher. Same seed = same world, always. - D-010 BTreeMap: All
HashMapusage in generator output types is prohibited. UseBTreeMapthroughout. - 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: i8for positions,z_levels: u8for counts. Already implemented correctly in the types. - L-7 XOR prohibition: In-playthrough events use
DamageOverlayexclusively. 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.