--- title: "Tyre Round 1 — Technical Constraints" description: "Hard technical constraints on spatial hierarchy, chunk sizes, and streaming architecture" type: workshop status: archived workshop: generator-architecture agent: "tyre" round: 1 created: 2026-02-27 --- # Round 1: Tyre — Technical Constraints on Generator Architecture **Workshop:** Generator Architecture (#562) **Agent:** Tyre (Technical Architect) **Date:** 2026-02-27 --- ## 1. Hard Technical Constraints on Spatial Hierarchy These constraints are **non-negotiable** — they flow directly from confirmed D-records and cannot be relaxed without amending those decisions. ### 1.1 Chunk: 64×64 sim tiles (32×32 visual, 32m) — D-094 The chunk is the **streaming and serialization unit**. This is locked. | Property | Value | Source | |----------|-------|--------| | Sim tiles | 64×64 | D-094 | | Visual tiles | 32×32 | D-094, D-066 (2x retina) | | Physical size | 32m × 32m | D-066 (0.5m/sim tile) | | Memory per chunk | ~21 KB raw tile data | 64×64 tiles × 5 bytes/tile (type + flags + occupancy) ≈ 20,480 bytes | | Serialization format | MessagePack | D-020 | **Why 64×64 sim is the floor:** Shadowcasting (D-035) operates at sim resolution. A smaller chunk means more cross-chunk boundary queries during LOS computation. At 64×64, a single chunk covers the full LOS radius of most entities (~20-30 sim tiles) without requiring neighbor lookups for most casts. Halving to 32×32 sim would roughly quadruple the frequency of cross-chunk shadowcasting — measurable cost on the critical path. **Why 64×64 sim is the ceiling (for now):** Larger chunks waste bandwidth for partial visibility. The ObserverSnapshot (D-020) sends only visible state. A 128×128 chunk would mean loading 4× the data when only a corner is visible. The 64×64 sweet spot minimizes the ratio of loaded-but-invisible tiles. ### 1.2 Block: 128×128 sim tiles (2×2 chunks, 64m) — D-094 The block is the **generator planning unit**. Four chunks arranged in a 2×2 grid. | Property | Value | |----------|-------| | Sim tiles | 128×128 | | Visual tiles | 64×64 | | Chunks | 4 (2×2) | | Physical size | 64m × 64m | **Generator implication:** The block is where the generator decides the building footprint strategy. Four chunks can: - Remain independent (4 small buildings/spaces) - Merge 2 horizontally or vertically (1×2 building spanning 64×32 sim tiles) - Merge 2 in L-shape (building occupying 3 of 4 chunks with gap) - Merge all 4 (single large building spanning the full 128×128 sim tiles) This is a 2-bit decision per chunk pair (merge/don't merge on each axis), producing a tractable combinatorial space for the generator without requiring variable-size building footprints. ### 1.3 District: 512×512 sim tiles (4×4 blocks, 256m) — D-094 | Property | Value | |----------|-------| | Sim tiles | 512×512 per z-level | | Visual tiles | 256×256 | | Blocks | 16 (4×4) | | Chunks | 64 (8×8) | | Z-levels | 3 (Transit District; variable for other types) | | Memory per z-level | ~1.35 MB (64 chunks × ~21 KB) | | Memory for 3 z-levels | ~4 MB | ### 1.4 Hierarchy Depth: Exactly 4 Levels The hierarchy is **Region → District → Block → Chunk**. No more, no fewer. **Why not deeper (sub-chunk quarters)?** The workshop brief mentions a "sub-chunk quarter system" (¼ chunk = 32×32 sim tiles). *cracks knuckles* — let me be honest about what this means technically. A 32×32 sim tile quarter is 16×16 visual tiles = 16m. That's actually a reasonable building footprint (The Last Shift bar is 28×22 visual). But: 1. **The quarter is NOT a hierarchy level — it's a fill rule.** The chunk remains the streaming unit. Quarters are a layout constraint within a chunk, not a separately loaded/serialized entity. The generator decides how to fill a chunk's 64×64 sim space using quarter-aligned placement rules, but the server still loads/saves/streams the full chunk. 2. **Quarter merge rules are purely generator-side.** The server doesn't know or care about quarters after generation. It sees tiles. The quarter concept exists only during the generation pass and in the template metadata. 3. **Adding a 5th hierarchy level (quarter) to the runtime would violate D-012's streaming model.** Chunk is the streaming atom. Sub-chunk streaming would require partial chunk updates over the wire, complicating the ObserverSnapshot and the client's tile map management for zero gameplay benefit. **Recommendation:** Quarters are a **generation-time layout constraint**, not a spatial hierarchy level. The hierarchy stays at 4 levels. The quarter system is a set of placement rules the chunk-fill stage of the generator uses internally. **Why not shallower?** Removing blocks (District → Chunk directly) loses the generator's "what goes in this 64m² area" planning step. The block is where multi-chunk building footprints are decided. Without it, the generator must either think in individual chunks (losing building coherence) or in full districts (losing locality). The 2×2 block is the minimum viable planning unit for building-scale decisions. --- ## 2. Data Structure for the District Skeleton (Q-036) The district skeleton is the generator's output from the district-generation stage. It describes **what** a district contains and **where things go**, without specifying individual tiles. ### 2.1 Proposed Data Structure ```rust /// The district skeleton — atomic output of the district generation stage. /// This is a planning artifact consumed by the block/chunk fill stages. struct DistrictSkeleton { /// Unique district identifier (world-scoped) district_id: DistrictId, /// Generator seed for deterministic reproduction seed: u64, /// District classification driving template selection district_type: DistrictType, // e.g., Transit, Residential, Commercial, Industrial, Administrative, Medical /// Economic/political context from pipeline stages above context: DistrictContext, /// The 4×4 block grid — each block has a zoning assignment blocks: [[BlockSkeleton; 4]; 4], /// Social sites placed within this district (D-025) social_sites: Vec, /// Multi-block structure reservations (structures spanning >1 block) reservations: Vec, /// Access topology — gate/entrance placement and connectivity access_points: Vec, /// Corridor/thoroughfare spine connecting access points corridors: Vec, /// Z-level configuration z_levels: u8, /// Zone palette assignments (fog tints, surface colors per D-093) zone_palette: Vec, } struct DistrictContext { /// Faction controlling this district (affects templates, NPC generation) faction_control: FactionId, /// Economic prosperity tier (0-4, affects object density, building quality) prosperity: u8, /// Population density target (NPCs per block, guides NPC slot allocation) population_density: PopulationDensity, // Sparse/Normal/Dense/Packed /// Cultural ingredients (Q-032) driving visual/naming variation cultural_profile: CulturalProfile, /// Transport adjacency — which access points connect to what transport_links: Vec, } struct BlockSkeleton { /// Block position in the 4×4 grid (0-3, 0-3) position: (u8, u8), /// Primary zoning type for this block zoning: ZoningType, // Residential, Commercial, Industrial, Institutional, Mixed, Open/Park, Infrastructure /// Whether this block is claimed by a multi-block reservation reservation: Option, /// Chunk merge strategy for this block (how the 4 chunks combine) chunk_layout: ChunkLayout, /// Social sites hosted in this block (references into district's social_sites vec) hosted_sites: Vec, } /// How the 4 chunks within a block are organized enum ChunkLayout { /// All 4 chunks independent (small buildings, mixed use) Independent, /// Two chunks merged horizontally, two independent /// Contains: which pair merges (N or S row), orientation MergeH { row: MergeRow }, /// Two chunks merged vertically, two independent MergeV { col: MergeCol }, /// L-shaped merge (3 chunks), one independent LShape { corner: Corner }, /// Full merge (single large building spanning all 4 chunks) FullMerge, /// Custom layout (for multi-block reservations that span into this block) Reserved, } struct SocialSitePlacement { /// Social site identifier site_id: SocialSiteId, /// Template tag selecting from the D-025 template library template_tag: String, // e.g., "logistics_hub", "bar", "residential_cluster" /// Block(s) this site occupies blocks: Vec<(u8, u8)>, /// Specific chunk(s) within those blocks chunks: Vec, /// NPC slot allocation (how many NPCs this site supports) npc_slots: NpcSlotAllocation, /// Access tier for entry (D-028 Layer 1) access_tier: AccessTier, // Public, SemiPublic, SemiPrivate, Private, Restricted /// Triangle templates to instantiate at this site (D-024, D-087) triangles: Vec, /// Economic function (what this site does in the district economy) economic_function: EconomicFunction, } struct NpcSlotAllocation { /// Named roles (authored, specific function) named_roles: Vec, /// Generic background population slots (Tier 3) background_slots: u16, /// Total NPC capacity at peak hours peak_capacity: u16, } struct MultiBlockReservation { /// Reservation identifier id: ReservationId, /// Template for the multi-block structure template_tag: String, // e.g., "gate_terminal", "park", "stadium" /// Blocks claimed by this reservation (coordinates in the 4×4 grid) footprint: Vec<(u8, u8)>, /// Whether this reservation crosses into a neighboring district cross_district: bool, /// Z-levels occupied z_range: (u8, u8), } struct AccessPoint { /// Position on the district boundary (edge + offset) edge_position: EdgePosition, /// What this connects to (transit stop, neighboring district, gate) connects_to: ConnectionTarget, /// Access tier (public entrance, restricted, staff only) access_tier: AccessTier, /// Width in visual tiles (constrains throughput and NPC flow) width_vt: u8, } ``` ### 2.2 Size Estimate Per district skeleton: - 16 BlockSkeletons: ~16 × 64 bytes = ~1 KB - Social sites (4-8 per district): ~8 × 256 bytes = ~2 KB - Multi-block reservations (0-3): ~3 × 128 bytes = ~384 bytes - Access points + corridors: ~1 KB - Context + metadata: ~512 bytes - **Total: ~5 KB per district skeleton** For 300 worlds × avg 6 districts = 1,800 district skeletons = **~9 MB**. Trivial. Entire galaxy skeleton fits in memory. ### 2.3 Relationship to D-025 Social Sites The generator does **not** invent new social site types. It: 1. Selects from the D-025 template library based on zoning type and district context 2. Places templates onto blocks/chunks using the spatial hierarchy 3. Allocates NPC slots per template requirements 4. Wires access topology (which sites connect to which corridors) **D-025 templates are authored. Skeleton placement is generated.** The generator arranges templates, not tiles. --- ## 3. How Chunk Loading (D-012) Constrains the Spatial Hierarchy ### 3.1 Streaming Radius The player's chunk loading radius determines how much of the district is live at any time. Current constraints: | Parameter | Value | Source | |-----------|-------|--------| | Player vision range | ~20-30 sim tiles (LOS) | D-035 shadowcasting | | Sound range | Close: 5 sim tiles, Mid: 15, Far: 30 | D-018 | | Chunk size | 64 sim tiles | D-094 | **Loading strategy:** 3×3 chunk grid centered on player = 9 chunks loaded. This covers 192×192 sim tiles (96m radius in each direction from center), safely beyond max LOS range. The player never sees a chunk boundary seam. **Memory at 3×3 loading:** 9 chunks × ~21 KB = ~189 KB per z-level, ~567 KB for 3 z-levels. With entity data overlay: ~1-2 MB. Trivial. ### 3.2 Cross-Chunk Constraints on Generation The generator must guarantee **tile continuity at chunk boundaries**. When two chunks are adjacent (whether in the same block or across blocks), their edge tiles must be compatible: - Wall segments must align or leave matching gaps (doors) - Floor types must transition cleanly (corridor entering a room) - Z-level connections (stairs, ramps) must align vertically This is the hardest constraint on chunk-based generation. Two approaches: **Option A: Edge contracts.** Each chunk face exports a set of "connection points" (door positions, corridor widths). The generator plans connections at the block level, then each chunk fill respects its edge contracts. *This is what I recommend.* It's how Wave Function Collapse and similar systems handle tile boundaries. **Option B: Overlap zones.** Chunks share a 2-4 tile overlap strip with their neighbors. The generator fills the overlap first, then fills inward. Simpler conceptually but wastes tile real estate (up to 12.5% of each chunk at 4-tile overlap on all edges). **Recommendation: Edge contracts (Option A).** Each chunk face has a fixed set of connection slots (e.g., 1-3 connections per face, each defined by position + width + access tier). The block-level planning stage determines which faces connect and where. The chunk-fill stage reads its face contracts and fills interior tiles accordingly. ### 3.3 Chunk Loading vs. Generator Computation D-012 specifies that chunks load/unload around the player. For generated worlds, this means chunks must be **generatable on demand** when first entered, then cached. **Generation pipeline timing:** | Stage | When it runs | Output | |-------|-------------|--------| | Galaxy → System → District skeletons | Game start (from seed) | All 1,800 district skeletons | | Block planning (per district) | On first visit to district OR game start for home district | 16 BlockSkeletons with layouts + edge contracts | | Chunk fill (per chunk) | On entering loading radius | Tile data for one 64×64 chunk | **Chunk fill time budget:** The player moves at Walk speed = 1 tile/2 ticks = 1 tile/200ms (at 10 tps). Crossing a 64-tile chunk takes ~12.8 seconds. A new chunk enters the 3×3 loading grid roughly every 6-12 seconds. **The chunk fill generator has a budget of ~500ms per chunk** (generous — can use background thread, D-010 deterministic sim doesn't constrain client-side gen). At 64×64 = 4,096 tiles, that's ~122 microseconds per tile. Feasible. Template-based fill (stamp a pre-authored room into a quarter, decorate procedurally) will be well under budget. Full WFC at this scale takes ~10-50ms in optimized Rust. ### 3.4 Borderless Generation Implication D-012 states the boundary can be removed for borderless worlds. For the generator, this means: - District skeletons must be generatable from neighbors' edge contracts (a new district skeleton can be created when the player approaches an ungenerated district boundary) - The 4×4 block grid is the district's internal structure; the inter-district boundary is just another set of edge contracts - **The generator pipeline must be able to run the district skeleton stage for a single district in isolation, given only its neighbors' access points as input** This doesn't affect v0.1 (bounded, hand-authored) but constrains the generator architecture: district generation must be local, not global. --- ## 4. Performance Implications of Hierarchy Depth ### 4.1 Lookup Complexity Converting a sim tile position to its hierarchy location: ``` Chunk coord: (x / 64, y / 64) — 1 division Block coord: (chunk_x / 2, chunk_y / 2) — 1 division District coord: (block_x / 4, block_y / 4) — 1 division ``` All integer divisions by powers of 2 = **bit shifts**. O(1) per lookup, ~3 nanoseconds. Hierarchy depth has zero performance impact on spatial lookups. ### 4.2 Spatial Queries (Pathfinding, LOS) Pathfinding operates at sim-tile resolution within the loaded chunk grid. The hierarchy doesn't affect pathfinding cost directly. However: - **Block-level precomputation:** The generator can precompute a block-level connectivity graph (which blocks connect to which, through which access points). This gives A* a coarse-grid initial path (~16 nodes per district) before refining to tile-level within the relevant chunks. **Saves 90%+ of pathfinding work for long paths.** - **District-level precomputation:** Same idea at district scale. For cross-district travel, the pathfinder walks the district connectivity graph (~6 districts per station), then block graph, then tile graph. Three-level hierarchical A*. **Performance estimate for hierarchical A*:** | Path type | Nodes searched | Time estimate | |-----------|---------------|---------------| | Within-chunk | ~100-500 tiles | <1ms | | Within-block (cross-chunk) | 4 chunks × ~200 tiles | ~2-5ms | | Within-district (cross-block) | 16 blocks × 4 chunk entries | ~1-3ms (coarse) + ~5ms (refine) | | Cross-district | 6 districts × 16 block entries | ~2ms (coarse) + ~8ms (refine) | All well within the 100ms tick budget (D-031). The hierarchy **helps** pathfinding by providing natural coarse-graining. ### 4.3 Memory Layout The hierarchy maps naturally to a flat array with computed indices: ```rust /// All chunks in a district, flat array indexed by (x, y, z) struct DistrictChunks { /// 8×8 chunks per z-level, up to 8 z-levels chunks: Vec, // indexed as z * 64 + y * 8 + x } ``` Cache-friendly, contiguous, no pointer chasing. 64 chunks per z-level fit in ~1.3 MB — easily fits in L2 cache for spatial queries. ### 4.4 What If We Added More Levels? | Depth | Levels | Cost | Benefit | |-------|--------|------|---------| | 3 | Region → District → Chunk | Loses building-scale planning | Simpler generator | | **4** | **Region → District → Block → Chunk** | **Current. Balanced.** | **Building-scale planning + streaming** | | 5 | + Sub-chunk quarter | Quarter = extra indirection at fill time | Finer fill control | | 6 | + Room | Individual room tracking | Overkill — rooms are tile patterns | **Verdict:** 4 levels is the sweet spot. Quarters are a fill-time concept, not a hierarchy level. Going deeper adds complexity without proportional benefit. --- ## 5. v0.1 Stub Interfaces for the Generator v0.1 is hand-authored (D-036, D-093). The generator doesn't run. But the data structures and interfaces it will consume must exist as stubs now, or v0.2+ work will require a rewrite. ### 5.1 Must Stub Now (v0.1) These interfaces are needed for the hand-authored Transit District to be expressible in generator-compatible terms. This validates the data model. | Stub | What it does | Why now | |------|-------------|---------| | `DistrictSkeleton` struct | Serializable district description | The v0.1 Transit District should be representable as a DistrictSkeleton. This validates Q-036 — if the hand-authored district can be expressed as generator output, the data structure is correct. | | `ChunkData` struct | Per-chunk tile storage with edge contracts | Already partially exists for D-012 chunk loading. Needs edge contract fields added. | | `BlockSkeleton` struct | Per-block zoning + chunk layout | Validates that the 2×2 block decomposition works for the Transit District's hand-authored social sites. | | `SocialSitePlacement` struct | Template tag + block/chunk coordinates + NPC slots | Validates that D-025 social sites can be addressed within the spatial hierarchy. | | `DistrictType` enum | Transit, Residential, Commercial, etc. | Needed for Sova station's 6-district model (D-093, station profile). | | `AccessPoint` / `CorridorSpine` | Entry points and corridor network | Validates the access topology from D-093 (gate cluster → transition → terminal → bar). | **Effort estimate: ~3-4 developer-days** to define structs, serialize the Transit District as a DistrictSkeleton, and write validation tests. ### 5.2 Stub at Block/Chunk Level (v0.1-v0.2) | Stub | What it does | Target | |------|-------------|--------| | `ChunkLayout` enum | Merge strategy per block | v0.1 — needed for Transit District block decomposition | | `EdgeContract` struct | Connection points per chunk face | v0.2 — first generated chunks need this | | `ZoningType` enum | Block-level land use classification | v0.2 — drives template selection | ### 5.3 Generator Pipeline Stubs (v0.2+, Design Only Now) These are the pipeline stages themselves. v0.1 doesn't execute them, but the stage interfaces should be **designed** (not implemented) now so the pipeline architecture is validated. | Pipeline Stage | Input | Output | Implementation target | |----------------|-------|--------|----------------------| | Geography | World seed, system parameters | Planet/station type, basic terrain | v0.6+ | | Infrastructure | Geography output, transport network | Station layout (district count, positions, connections) | v0.4+ | | Zoning | Infrastructure, economic/political context | Per-block zoning assignments | v0.3+ | | Block Planning | Zoning, social site library, population targets | BlockSkeletons with ChunkLayouts + edge contracts | v0.3+ | | Chunk Fill | BlockSkeleton, edge contracts, template library | Tile data for each 64×64 chunk | v0.2 (first target) | | NPC Population | Social site placements, population density, cultural profile | NPC generation (D-024 axes, role assignments) | v0.3+ | **Critical path for Q-037:** Chunk Fill is the first generator stage to implement (v0.2) because it's the most concrete — takes a planned block and fills tiles from templates. Everything above it can be hand-specified while Chunk Fill is developed and validated. ### 5.4 Validation Strategy: Transit District as Generator Ground Truth **Recommendation:** Express the v0.1 Transit District (D-093) as a hand-authored `DistrictSkeleton` + hand-authored `ChunkData` for each of its 64 chunks. This serves as: 1. **Schema validation** — if the skeleton can't express the Transit District, the schema is wrong 2. **Generator test fixture** — future generator output is compared against the hand-authored ground truth 3. **Content pipeline test** — the skeleton → rendered map pipeline is validated end-to-end with known-good data This is *not* requiring the Transit District to be "generated." It's requiring the generator's output format to be expressive enough to describe the Transit District. If it can describe the most complex hand-authored district, it can describe anything the generator produces. --- ## 6. Technical Risk Assessment | Risk | Severity | Mitigation | |------|----------|------------| | Edge contract system produces tile discontinuities at chunk boundaries | HIGH | Comprehensive boundary tests; Transit District as test fixture validates edge alignment | | Generator can't fill chunks within 500ms budget | MEDIUM | Template stamping (not WFC) for v0.2; WFC only if templates are insufficient | | Multi-block structures create irregular block boundaries | MEDIUM | Reservation system claims blocks before fill; reserved blocks use custom layouts | | District skeleton data model doesn't survive contact with diverse district types | MEDIUM | Validate against all 6 Sova district types + 3 planetary settlement types before locking | | Cross-district structures (park spanning two districts) create coordination complexity | LOW | Cap at v0.6+; v0.1-0.5 districts are self-contained. `cross_district: bool` on reservations is the escape hatch. | | Quarter system over-complicates chunk fill | LOW | Quarters are generation-side only; if they cause problems, fall back to free-form template placement within chunks | --- ## 7. Summary of Hard Constraints 1. **Chunk = 64×64 sim tiles.** Non-negotiable. Streaming atom. (D-094) 2. **Block = 2×2 chunks.** Generator planning unit. (D-094) 3. **District = 4×4 blocks = 64 chunks.** Template composition unit. (D-094) 4. **Hierarchy = 4 levels.** Quarters are fill rules, not hierarchy levels. 5. **Edge contracts at chunk boundaries.** Required for cross-chunk tile continuity. 6. **District skeleton must express D-025 social sites.** Generator arranges templates, doesn't invent new site types. 7. **Chunk fill budget: ~500ms.** Based on player walk speed and 3×3 loading grid. 8. **District generation must be local.** Required for D-012 borderless generation future. 9. **MessagePack serialization for all generator output.** Per D-020. 10. **Deterministic from seed.** Per D-010 principle 4. Same seed → same district → same tiles. --- *Tyre — Round 1 complete. Standing by for Round 2 cross-pollination.*