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

11 KiB
Raw Blame History

title, description, type, status, sprint, team
title description type status sprint team
Sprint 23 — Server Briefing Chunk data structure, tile format loader, storyteller engagement and activation systems sprint archived 23 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

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.