Files
settled-reach/docs/sprints/sprint-9/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

140 lines
9.5 KiB
Markdown

---
title: "Sprint 9 — Server Briefing"
description: "Test client binary, Gauntlet rooms 1-3, golden file suite, subprocess tests"
type: sprint
status: archived
sprint: 9
team: "server"
---
# Sprint 9: Gauntlet — Server Tasks
**Goal:** Build the test infrastructure backbone — test client binary, Gauntlet rooms 1-3, golden file suite, and client QA harness — so every future sprint can be validated end-to-end.
**Branch:** `server`
**Agents:** Dudley (simulation dev), Tyre (architect), Hoshe (QA)
## Carry-over from Sprint 8
| # | Title | Status | Notes |
|---|-------|--------|-------|
| #480 | Test client binary scaffolding + CLI | backlog | Linchpin — blocks #481, #482, #483, #484. Was Sprint 8 scope but did not start. |
| #481 | Text renderer library | backlog | Blocked by #480. Needed for golden diff and WRONG button (F12). |
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #482 | Layer 3 subprocess test: server_subprocess_sends_snapshot_on_connect | #480 |
| #484 | Test client golden file comparison (JSON diff) | #480, #481 |
| #485 | Golden file test suite: gauntlet_tick_10_matches_golden | #484 |
| #487 | Gauntlet first 3 rooms (Inventory Warehouse, Occlusion Corridor, Pause Chamber) | — |
| #488 | Gauntlet room constants module (server/src/test_world/constants.rs) | #487 |
| #489 | Content runtime validation: boot + tick 10 + snapshot | — |
| #490 | Room reset trigger mechanism | #487 |
## Key Decisions
- `decisions/architecture.md` — D-020 (subprocess IPC, `ObserverSnapshot`, `PlayerInput`), D-030 (testability architecture, Layer 3 integration tests), D-010 (deterministic simulation, injectable resources)
- `decisions/scope.md` — D-065 (smuggler inventory, `CarriedBy` component — Inventory Warehouse room), D-053 (movement stances — Pause Chamber room)
- `decisions/perception.md` — D-035 (symmetric shadowcasting — Occlusion Corridor room)
## Notes
### #480 — Test client binary scaffolding + CLI (tooling/test-client/)
What exists: `server/` is a workspace crate (see `server/Cargo.toml`). The `bridge` module exports `ObserverSnapshot`, `PlayerInput`, `read_framed`, `write_framed` from `server/src/bridge/`. `TcpBridge` is in `server/src/bridge/tcp.rs`. The server binary (`server/src/main.rs`) already implements `--test-mode --port 0` and emits `LISTENING:{port}` on stdout (Sprint 8, #459).
What to deliver: A new Cargo workspace crate at `tooling/test-client/` that imports bridge types from the server crate. CLI flags: `--connect <port>`, `--replay <file>`, `--text` (text output), `--json` (JSON output), `--quiet`. This is the foundation for every subsequent test client ticket in this sprint — do it first.
Integration point: The server crate must expose `read_framed` and `write_framed` as `pub` from `server/src/bridge/framing.rs` (verify they are already public; the integration test at `server/tests/game_loop.rs` already uses them).
### #481 — Text renderer library (server/src/bridge/text_renderer.rs)
What exists: `ObserverSnapshot` is fully defined in `server/src/bridge/types.rs` (v8, protocol version 8). Fields include `entities` (Vec<VisibleEntity>), `visible_tiles`, `tick`, `game_time`, `player_stance`, `player_inventory`, etc.
What to deliver: `format_snapshot_text(&ObserverSnapshot) -> String`. Entity labels as `kind:entity_id`, sorted by distance from player position. Room name from `room_at()` helper (or "unknown" stub if room system not yet in place). Export as `pub` from the server crate for `tooling/test-client/` consumption. This is also consumed by #495 (WRONG button F12 capture on the client).
### #482 — Layer 3 subprocess test: server_subprocess_sends_snapshot_on_connect
What exists: `server/tests/game_loop.rs` already has a full IPC round-trip test using `TcpBridge::accept_on()`. The `--test-mode --port 0` + `LISTENING:{port}` pattern is live in `server/src/main.rs`. The test for #459 already validated the handshake.
What to deliver: A new test at `server/tests/layer3.rs`. Spawns the server binary with `--test-mode --port 0`, reads `LISTENING:{port}` from stdout, connects via TCP (`TcpBridge::connect()`), sends one `PlayerInput`, reads one `ObserverSnapshot`, asserts `version == PROTOCOL_VERSION`, `tick >= 0`, `entities.len() > 0`. This is D-030 Layer 3 — the real subprocess, not a mock.
Gotcha: The server binary must be pre-built before this test runs. Use `std::process::Command` with the binary path from `env!("CARGO_MANIFEST_DIR")` or a build script. Look at how `server/tests/bridge_tcp.rs` handles spawning if there is a prior pattern.
### #484 — Test client golden file comparison (JSON diff)
What exists: #480 (test client crate) must be done first. The `ObserverSnapshot` struct derives `Serialize`/`Deserialize` via serde.
What to deliver: A recursive `diff_json_values()` comparison function in `tooling/test-client/src/golden.rs`. Sorts object keys before compare (BTreeMap semantics). Pretty-prints field-level diffs on mismatch (path + expected + got). Exit code 1 on any mismatch. This is the comparison engine for #485.
### #485 — Golden file test suite: gauntlet_tick_10_matches_golden
What exists: #484 must be done. The determinism regression test at `server/tests/determinism.rs` (Sprint 8, #466) already validates tick-level determinism. Gauntlet room #487 must be done (provides the map for the replay).
What to deliver: A test in `server/tests/` (or via the test client) that runs a 10-tick deterministic replay against the Gauntlet map, serializes the resulting `ObserverSnapshot` to JSON, and compares against a committed golden file at `tests/golden/gauntlet_tick_10.json`. First run: generate and commit the golden file. Subsequent runs: fail on any deviation. This is the primary regression guard.
### #487 — Gauntlet first 3 rooms (Inventory Warehouse, Occlusion Corridor, Pause Chamber)
What exists: Content loading system is live — `server/src/content/` (ContentPlugin, ContentConfig, loader, spawn). The proof room setup in `server/src/main.rs` (`setup_proof_room()`) shows the ECS entity spawning pattern. `WalkabilityMap` is the tile collision resource. The room system needs a new `server/src/test_world/` module (Gauntlet-specific, not production content).
What to deliver: Three rooms as hybrid YAML + Rust inject:
- Inventory Warehouse (30x28, 11 entities): tests D-065 inventory system, `CarriedBy` component
- Occlusion Corridor (42x22, 4 entities): tests D-035 LOS shadowcasting, wall placement critical
- Pause Chamber (16x16, 1 entity): tests D-053 pause guard; `--test-mode` gate check
Room layout: embed in `server/src/test_world/rooms/` as YAML, with a Rust loader in `server/src/test_world/mod.rs` that spawns them via `WalkabilityMap` + ECS entities. Each room gets a unique `room_id` string. This module is Gauntlet-only — do not pollute `server/src/content/`.
Blocks: #488 (constants module), #490 (room reset), #497 (checklist YAML), #496 (room timer), #498 (next 3-4 rooms).
### #488 — Gauntlet room constants module (server/src/test_world/constants.rs)
What exists: #487 must be done first. Depends on room definitions from that ticket.
What to deliver: `GauntletRoom` struct (id, name, spawn_x, spawn_y, width, height), `GauntletEntity` struct (stable_id, kind, x, y), 8 room constants (one per planned Gauntlet room — stub the unimplemented ones), `ROOMS: &[GauntletRoom]` array, `room_at(x, y) -> Option<&GauntletRoom>` lookup. ~100 lines. These stable coordinate references are used by all Gauntlet tests to avoid magic numbers.
### #489 — Content runtime validation: boot + tick 10 + snapshot
What exists: `ContentPlugin` loads from `content/` at startup. If content schema is valid YAML but has runtime issues (e.g., NPC references broken entity IDs), the server may panic on tick rather than at load time.
What to deliver: A test at `server/tests/content_loading.rs` (or extend the existing file) that: starts the server with `ContentPlugin`, ticks 10 times, reads an `ObserverSnapshot`, asserts no panic and `version == PROTOCOL_VERSION`. This is a smoke test for production content. Standalone — does not depend on Gauntlet rooms.
### #490 — Room reset trigger mechanism
What exists: #487 (Gauntlet rooms) must be done. ECS has `WalkabilityMap`, `TilePosition`, `KnowledgeGraph` components.
What to deliver:
- `RoomResetTrigger` component: marks a tile as a reset plate
- `RoomSnapshots` resource: stores tick-0 entity state per room (positions, KG entries, inventory items)
- `execute_room_reset` system: on trigger activation, restores entity positions, player KG (room refs only), fog (room tiles), inventory items to tick-0 state
Resets: entity positions and KG, player KG (room refs only — not cross-room knowledge), fog (room tiles only), inventory items. Does NOT reset: game time, player stance, cross-room knowledge, the hub area.
Blocks: #495 (WRONG button F12 depends on reset plate context), #502 (client-side reset UX).
## Dependency Chain
```
#480 (test client binary)
-> #481 (text renderer)
-> #484 (golden diff)
-> #485 (golden suite)
-> #482 (Layer 3 subprocess test)
-> #484 (golden diff)
#487 (Gauntlet rooms 1-3)
-> #488 (room constants)
-> #490 (room reset trigger)
#489 (content runtime validation) — standalone, parallel track
```
## PR Workflow
When ready to submit, create a PR with `tea` CLI. **All flags are required** to avoid TTY prompts (see CLAUDE.md "Gitea access" section):
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): sprint 9 gauntlet — test client and gauntlet rooms" --description "body" --base main --head server
```