pxl8/docs/plans/2026-03-02-bsp-chunk-streaming-design.md

81 lines
4.1 KiB
Markdown
Raw Normal View History

# BSP Chunk Streaming Design
Minecraft-style infinite procedural world using BSP geometry. The world is a grid of BSP chunks that generate and stream as the player explores. Both interior (dungeons, courtyards) and exterior (hills, cliffs) terrain use BSP representation.
## Chunk Addressing
`ChunkId::Bsp(u32)` becomes `ChunkId::Bsp { cx: i32, cz: i32 }`. Each chunk covers a 16x16 cell region (1024x1024 world units). World-space origin of chunk `(cx, cz)` is `(cx * 1024.0, cz * 1024.0)`.
The `ChunkMessage` transport header already has `cx/cy/cz` fields (currently unused) — we start populating them. The `id` field becomes `hash(cx, cz)` for fragment assembly deduplication.
## Procgen — Biome Selection
The server holds a `world_seed: u64`. For each chunk at `(cx, cz)`:
1. Sample noise at `(cx * scale, cz * scale)` with the world seed
2. Map noise value to biome:
- **Dungeon** (noise < -0.2): Indoor rooms with corridors, walls, ceiling. Existing `generate_rooms` logic.
- **Courtyard** (noise -0.2 to 0.2): Central open area with wing rooms. Existing `generate_courtyard` logic.
- **Exterior** (noise > 0.2): Open terrain with height variation, no ceiling, cliff walls at steep changes.
3. Per-chunk seed: `hash(world_seed, cx, cz)` for deterministic RNG.
4. All vertex positions emitted in world space: offset by `(cx * 1024.0, cz * 1024.0)`.
## Procgen — Exterior Terrain
New `generate_exterior` function:
- Each cell gets a floor height from fine-grained noise at world-space position
- All cells open (no ceiling)
- Walls emitted between adjacent cells with height difference > 32 units (cliff faces)
- No walls at chunk edges — floor extends to boundary, noise is continuous across chunks
- Single directional light (sunlight) instead of per-room point lights
- Materials 7/8/9 (ground/cliff/grass)
## Server Streaming
Track player's chunk coordinate: `chunk_cx = floor(player.x / 1024)`, `chunk_cz = floor(player.z / 1024)`.
Each tick, compute 3x3 ring around player vs `ClientChunkState.known`:
- **New chunks in range**: Generate lazily, queue for streaming
- **Chunks leaving range**: No action — client LRU handles eviction
- **Active chunk**: The chunk the player stands in. `CHUNK_ENTER` sent when it changes.
Streaming priority: player's chunk bursts at 64 fragments/tick, neighbors at 8 fragments/tick.
## Client — Multi-Chunk Rendering & Collision
**World struct**: Add `loaded_chunks[9]` array for the 3x3 ring. `active_chunk` remains the player's chunk (collision).
**Sync**: Iterate chunk cache for the 3x3 ring around player's chunk coordinate. Populate `loaded_chunks`.
**Rendering**: Iterate `loaded_chunks`, call `pxl8_bsp_render` for each. Vertices are in world space — no transform needed. Frustum-cull entire chunks before BSP rendering (chunk AABB: 1024x128x1024).
**Collision**: `pxl8_sim_trace` checks `active_chunk` BSP. Swap active chunk when player crosses boundary. Cross-chunk collision is a future refinement.
**BSP render state**: Store `pxl8_bsp_render_state*` inside each `pxl8_world_chunk`, created on arrival, destroyed on eviction.
## Wire Protocol
No new message types. Existing messages gain spatial meaning:
- `ChunkMessage.cx/cz`: populated with grid coordinates
- `CHUNK_ENTER`: repacked payload as `cx: i32, cz: i32` (8 bytes)
- Fragment assembly keyed by `(cx, cz)` instead of `id`
## Removals
- `generate_connected`, `LayoutMode::Connected` — replaced by biome system
- `remap_bsp_materials` — each biome sets its own materials
- `carve_corridor_h_offset` / `carve_corridor_v_offset` — no more combined grids
- `main.rs` single-BSP generation on spawn — replaced by streaming around player position
## What Stays
- `generate_rooms` / `generate_courtyard` — used as chunk-level biome generators
- Door system (`pxl8_sim_door`, `door_angle`, `entities.fnl`) — chunk-local feature
- `grid_to_bsp` pipeline — gains `origin_x/origin_z` parameter for world-space offset
- `grid_to_bsp` heights parameter for exterior terrain elevation
- BSP PVS, cell portals — per-chunk visibility still works
- Chunk cache (512 entries), fragment assembly, UDP transport