Files
settled-reach/docs/sprints/sprint-23/server.md
T
jpmschweitzerandClaude Opus 4.6 bccdcfcdcb docs(docs): add frontmatter to all sprint briefings
Standardized YAML frontmatter on all 115 sprint briefing files across
sprints 1-26 with title, description, type, status, sprint number, and
team fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:15:45 +01:00

154 lines
11 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: "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 <id>` 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<bool>` 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<TileBounds>` (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::<Location>()` on the locations directory; stubs are silently skipped.
**What to deliver:** Two things. First, agree the tile format with Araminta (visual team) on day 12 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<Entity>` — 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<WalkabilityMap>`.
**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.