--- title: "Sprint 23 — Server Briefing" description: "Chunk data structure, tile format loader, storyteller engagement and activation systems" type: sprint status: archived sprint: 23 team: "server" --- # Sprint 23: Terrain — Server Tasks **Goal:** Build the walkable Sova world under the authored content, complete the storyteller engagement layer, and add the debug console — clearing the path to v0.1 integration in Sprint 24. **Branch:** `server` **Agents:** Dudley (simulation), Tyre (architecture), Hoshe (QA) --- ## New Tickets | # | Title | Blocked by | |---|-------|------------| | #576 | Chunk data structure: tile-type layer and ChunkStore resource | — | | #577 | Location YAML tile format and content loader integration | #576 | | #578 | Chunk loading/unloading: streaming around player position | #576 | | #570 | EngagementRecord component — track per-NPC engagement metrics | — | | #571 | MovementHistoryBuffer resource — player path history | — | | #572 | Storyteller lifecycle rules — multi-activation, cooldown, resolution | — | | #579 | Storyteller activation pass — implement activation_pass() system | #570, #571, #572 | | #580 | Debug console server: command handler and state manipulation API | — | | #584 | Walls at LOS boundary: server — extend visible_tiles with wall margin | — | Use `tooling/db/ticket show ` for full descriptions. --- ## Key Decisions - `decisions/architecture.md` — D-012 (tile/chunk spec), D-094 (chunk 32×32, block 128×128, district 256×256), D-110 (signed z-levels: base_z i8, z_levels u8) - `decisions/content.md` — D-023 (three-tier content model, storyteller activation), D-025 (functional cluster, 15-40 tile social sites), D-093 (Sova Transit District — full spatial spec with tile dimensions), D-029 (population entanglement ratio 30/50/20) - `decisions/architecture.md` — D-010 (determinism — BTreeMap, no float non-determinism), D-031 (10 ticks/game-minute, TICKS_PER_GAME_MINUTE) --- ## Notes ### #576 — Chunk data structure: tile-type layer **What exists:** `WalkabilityMap` in `server/src/simulation/movement.rs` stores per-tile walkability as `Vec` inside private `ChunkData` structs, indexed by `ChunkCoord { cx, cy, z }`. `CHUNK_SIZE = 32`. The public API is `is_walkable(pos)` / `set_walkable(pos, bool)`. Generator types (`GeneratorChunkData`, `TileId`, etc.) are stubs in `server/src/simulation/generator.rs`. **What to deliver:** Extend `ChunkData` to carry a `TileKind` per tile. Define `TileKind` enum: `Floor | Wall | Void | Restricted`. Add `tile_kind(pos) -> TileKind` and `set_tile_kind(pos, kind)` to `WalkabilityMap`. Keep `is_walkable` / `set_walkable` API intact — many existing tests use it directly. All existing tests must pass. **Non-obvious gotcha:** `ChunkData` is private. Only `WalkabilityMap` methods are the extension surface. Do not make `ChunkData` or `TileKind` pub unnecessarily — encapsulate through the map API. ### #577 — Location YAML tile format and loader **What exists:** `Location` struct in `server/src/content/types.rs` has `tile_bounds: Option` (bounding box only) and `sightlines`. The three location YAMLs (`the-terminal.yaml`, `the-last-shift.yaml`, `maintenance-corridors.yaml`) are 2-line stubs — header comments only. `load_district()` in `server/src/content/loader.rs` already calls `load_yaml_dir::()` on the locations directory; stubs are silently skipped. **What to deliver:** Two things. First, agree the tile format with Araminta (visual team) on day 1–2 of the sprint — she cannot start authoring until you publish the format. Suggested format: `tiles: ["FFFFF", "FWWWF", "FFFFF"]` — array of strings, one char per tile, left-to-right = +x, top-to-bottom = +y. Characters: `F`=floor, `W`=wall, `V`=void, `R`=restricted. Post the agreed format as a comment block in `types.rs` before Araminta starts. Second, implement `load_location_tiles(location: &Location, origin: TilePosition, map: &mut WalkabilityMap)` and call it from the production startup path in `server/src/main.rs` after NPCs/triangles are spawned. The Gauntlet startup path (`server/src/test_world/`) must be untouched. **Dependency:** #576 must be merged first (needs `set_tile_kind` API). Unblocks #582 and #583 (Araminta's tile authoring). ### #578 — Chunk loading/unloading **What exists:** `WalkabilityMap` is pre-allocated at startup via `WalkabilityMap::new(width, height, z_levels)` which eagerly creates all chunks. `ZLevelLoadState` enum is defined in `server/src/simulation/generator.rs` with `Loaded(GeneratorChunkData) | Skeleton(FloorZone) | Ungenerated` variants but not yet used at runtime. **What to deliver:** A chunk streaming system. For v0.1 the entire hand-authored district fits in memory (256×256 visual = 8×8 chunks of 32 tiles). Pre-load all chunks at startup; the streaming system exists but covers the full map. Architecture must support future per-demand loading (v0.3+ generator). Add `ChunkLoadRadius` resource. Add a system that tracks player position → loaded chunk set → unloads distant chunks. Test: verify chunks at boundary are unloaded when player moves away. **Blocked by:** #576. ### #570 — EngagementRecord component **What exists:** Nothing. The KnowledgeGraph (`server/src/knowledge/`) tracks NPC information inventory but not engagement metrics. The storyteller activation spec in #162 requires three fields per observed entity: `observation_time_ticks`, `conversation_count`, `monologue_trigger_count`. **What to deliver:** `EngagementRecord` as a bevy ECS `Component` on NPC entities. Three fields. The perception system (`server/src/perception/observation.rs`) should increment `observation_time_ticks` per tick the NPC is in the player's LOS. The dialogue system (`server/src/simulation/dialogue.rs`) should increment `conversation_count` on conversation start. The monologue system (`server/src/simulation/monologue.rs`) should increment `monologue_trigger_count` when a monologue fires for a specific NPC context. All three writes must be additive — no overwrite. ### #571 — MovementHistoryBuffer resource **What exists:** Nothing. `TilePosition` tracks current position. No history buffer exists. **What to deliver:** `MovementHistoryBuffer` as a bevy `Resource`. Ring buffer of the player's `TilePosition` over the last `ENGAGEMENT_WINDOW_TICKS = 3000` ticks. The movement system (`server/src/simulation/movement.rs`) appends to it each tick when the player moves. Expose a query method: `npcs_copresent_in_window(threshold: i32) -> Vec` — returns NPC entities whose `TilePosition` was within `threshold` tiles of any player position in the buffer. Used by the activation pass (#579). ### #572 — Storyteller lifecycle rules **What exists:** The contamination layer fires once and sets `ContaminationActive`. No activation lifecycle spec exists. **What to deliver:** A decision/spec (can be a code comment block or a D-record filed with Qatux, not necessarily full implementation). For v0.1: single activation per session only. No concurrent activations. No cooldown needed (one and done). Triangle resolution: when a triangle reaches a terminal phase, mark it resolved; do not re-activate. Document these rules as constants and a comment in `server/src/storyteller/mod.rs`. These rules gate #579. ### #579 — Storyteller activation pass **What exists:** `server/src/storyteller/mod.rs` has `ContaminationActive`, `ContaminationEventQueue`, and `tick_contamination_activation()`. No activation pass, no engagement scoring, no `TriangleActivated` event. **What to deliver:** `activation_pass()` system implementing the 6 steps from #162. Gate → proximity query → engagement scoring → routing → module selection → emit `TriangleActivated { triangle_id }`. Add `TriangleActivated` event type and `TriangleActivatedQueue` resource. Register `activation_pass` in `StorytellerPlugin.build()` — runs after contamination is active, on 10-tick cadence. **Blocked by:** #570, #571, #572. ### #580 — Debug console server **What exists:** `PlayerAction` enum in `server/src/bridge/types.rs` has movement, interaction, save/load, pause, `TeleportToHub`. `PROTOCOL_VERSION = 17`. **What to deliver:** New `PlayerAction::DebugCommand(DebugCommandKind)` variant. `DebugCommandKind` enum with 9 variants (see ticket description). `DebugResponsePayload` added as `Option<>` to `ObserverSnapshot`. Bump `PROTOCOL_VERSION` to 18 when adding the new variant. A `handle_debug_commands` system that only executes when a `DebugEnabled` resource is true (set at startup; default true for v0.1). The system routes each `DebugCommandKind` to the appropriate ECS query or mutation. **Gotcha:** `PROTOCOL_VERSION` is checked by the client on every snapshot. Dudley must coordinate with Stig on the version bump — client #581 must update `protocol.gd` simultaneously to avoid mismatch errors. ### #584 — LOS boundary wall margin **What exists:** Observer snapshot generation is in `server/src/perception/observer/mod.rs`. `VisibilitySector` enum in `bridge/types.rs` has existing variants. `WalkabilityMap` is available via `Res`. **What to deliver:** After the standard visible tiles are computed, walk the LOS boundary and add wall tiles 1 tile beyond the cone. Use a new `VisibilitySector::BoundaryWall` variant (or reuse `LosObstructed` if that field already serves this purpose — check #514). Wall margin tiles must not affect the exploration/memory state — they are "seen now" only. --- ## Dependency Chain ``` #576 (tile-type layer) → #577 (tile format + loader) → #582 (Terminal tile map, visual) | → #583 (Last Shift + corridor maps, visual) → #578 (chunk streaming) #570 (EngagementRecord) ─┐ #571 (MovementHistory) ─┤→ #579 (activation pass) #572 (lifecycle rules) ─┘ #580 (debug server) → #581 (debug client) #584 (LOS wall margin server) → #585 (LOS wall margin client) ``` Parallel tracks: spatial chain (#576→#577→#578), storyteller chain (#570-572→#579), debug chain (#580), LOS fix (#584). All four tracks are independent and can run in parallel. --- ## PR Workflow ```bash tea pr create --repo jpmschweitzer/settled-reach --login schweitz \ --title "feat(server): chunk tile-type layer and location loader" \ --description "body" --base main --head server ``` --- ## Sprint Completion (Server Criteria) 1. `cargo test` green on server branch — all existing tests pass, new tile-kind tests pass. 2. Production startup (no gauntlet feature) boots with tile data loaded — WalkabilityMap populated from location YAML for The Terminal, The Last Shift, maintenance corridors. 3. `EngagementRecord` increments correctly — perception/dialogue/monologue all write to it. 4. `activation_pass()` fires after contamination and emits `TriangleActivated` for the highest-engagement NPC's triangle. 5. `DebugCommand::SkipToContamination` advances simulation to `CONTAMINATION_DELAY_TICKS` and the response is returned in the snapshot. 6. LOS wall margin — snapshot includes 1-tile wall data beyond the LOS cone boundary.