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>
154 lines
11 KiB
Markdown
154 lines
11 KiB
Markdown
---
|
||
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 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<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.
|