From 85a3e87d363283d43d2a977110fbb9b30ec05e10 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 18 Feb 2026 01:32:28 +0100 Subject: [PATCH] docs(sprints): add Sprint 9 Gauntlet briefing files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sprint 9 theme: Gauntlet — test infrastructure backbone with test client binary, Gauntlet rooms 1-3, golden file suite, and client QA harness. 23 tickets across server (9), client (6), CI (6), audio (2). Co-Authored-By: Claude Opus 4.6 --- docs/sprints/sprint-9/audio.md | 71 +++++++++++++++ docs/sprints/sprint-9/ci.md | 131 ++++++++++++++++++++++++++++ docs/sprints/sprint-9/client.md | 112 ++++++++++++++++++++++++ docs/sprints/sprint-9/joint.md | 147 ++++++++++++++++++++++++++++++++ docs/sprints/sprint-9/server.md | 130 ++++++++++++++++++++++++++++ 5 files changed, 591 insertions(+) create mode 100644 docs/sprints/sprint-9/audio.md create mode 100644 docs/sprints/sprint-9/ci.md create mode 100644 docs/sprints/sprint-9/client.md create mode 100644 docs/sprints/sprint-9/joint.md create mode 100644 docs/sprints/sprint-9/server.md diff --git a/docs/sprints/sprint-9/audio.md b/docs/sprints/sprint-9/audio.md new file mode 100644 index 000000000..b57b0f164 --- /dev/null +++ b/docs/sprints/sprint-9/audio.md @@ -0,0 +1,71 @@ +# Sprint 9: Gauntlet — Audio 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:** `audio` +**Agents:** Inigo (sound design) + +## Carry-over from Sprint 8 + +| # | Title | Status | Notes | +|---|-------|--------|-------| +| #440 | 6 interaction audio assets — cursor hover, weapon mode, fog recognition, implant, dialogue, confrontation dip | in_progress | In progress with Inigo. Highest priority — complete before starting #453. | + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #453 | Revisit Sprint 7 UI audio assets — trim to target durations and iterate | — (after #440) | + +## Key Decisions + +- `decisions/architecture.md` — D-068 (5-bus audio architecture: Music, Ambient, WorldSFX, PlayerActions, UISounds; AudioManager directory-scan registry) +- `decisions/architecture.md` — D-073 (zone crossfade: hard tile boundary, 1.5-2s audio tween; `AudioManager.set_zone()` stub exists in `client/scripts/autoloads/audio_manager.gd`) +- `decisions/scope.md` — D-038 (hybrid audio generation: SAO for >200ms organic sounds, manual synthesis for <200ms precise/digital UI sounds; insert-tech vs organic split from D-074) +- `decisions/perception.md` — D-069 (audio dip profiles: dialogue -7dB Ambient; confrontation -11dB Ambient + -5dB WorldSFX + 800Hz low-pass; dip tweens already implemented in AudioManager) + +## Notes + +### #440 — 6 interaction audio assets + +What exists: `client/scripts/autoloads/audio_manager.gd` is fully implemented (D-068, D-069, D-073). It uses a directory-scan registry: drop an `.ogg`, `.wav`, or `.mp3` file into `res://audio/` and it auto-registers by filename stem. No code changes needed to add assets — audio branch can add files independently of client code. + +The 5-bus routing is live: `UISounds` bus handles cursor hover and interaction feedback; `Ambient` and `WorldSFX` buses are handled by the dialogue/confrontation dip profiles. `apply_dip("confrontation")` and `apply_dip("dialogue")` are implemented. + +What to deliver: 6 audio files committed to the `audio` branch under `res://audio/`: +1. `sfx_cursor_hover.ogg` — subtle UI feedback for cursor landing on an interactable entity. Short (<200ms), precise. Manual synthesis (insert-tech aesthetic per D-074). +2. `sfx_weapon_mode.ogg` — stance shift or mode toggle cue. Digital, clean. Manual synthesis. +3. `sfx_fog_recognition.ogg` — "neural ping" as a previously-unseen entity resolves from grey blob to full color (D-060 cognitive delay completion). Crystalline, directional feel. SAO or manual synthesis. +4. `sfx_implant_open.ogg` — insert/HUD open event. Diegetic tech sound. SAO for organic texture if >200ms; manual for shorter. +5. `sfx_dialogue_start.ogg` — conversation initiates. Subtle, not jarring — dialogue already has the -7dB Ambient dip cue. Avoid competing with voice/text readability. +6. `sfx_confrontation_dip.ogg` — confrontation audio dip accompaniment. This fires when `apply_dip("confrontation")` is called. Should feel like the world narrows — compressed, focused. Plays on UISounds bus. + +Gotcha: The AudioManager calls `play()` with the asset filename stem as the key. Asset key must match exactly — e.g., file `sfx_cursor_hover.ogg` is called with `AudioManager.play("sfx_cursor_hover")`. Verify stems before committing. + +Duration targets: all 6 are UI sounds (UISounds bus) — keep under 1.0s. Dialogue and confrontation sounds should be especially short to avoid competing with text display timing. + +### #453 — Revisit Sprint 7 UI audio assets — trim to target durations and iterate + +What exists: Sprint 7 UI audio assets exist in the audio branch under `res://audio/`. They were produced as initial placeholders. Team feedback (Tyre, Gestalt) flagged that some assets exceed target durations or don't fit the insert-tech vs organic aesthetic split from D-074. + +What to deliver: Review each Sprint 7 UI asset against the D-038 / D-074 standards: +- `sfx_monologue_chime.ogg` — target 0.5-1.0s, crystalline/neural-lattice feel. Re-synthesize if duration or tone is off. +- `sfx_monologue_chime_urgent.ogg` — sharper variant, same duration range. Must feel distinctly more urgent than the base chime. +- Any other Sprint 7 UI assets — check stems against AudioManager registry, confirm routing to correct bus. + +Output: revised assets committed to `audio` branch, replacing the originals. Note which files changed and why in the PR description. If an asset is acceptable as-is, note that too. + +## Dependency Chain + +``` +#440 (6 interaction audio assets) — in progress, complete first + -> #453 (Sprint 7 asset revision, start after #440 is done) +``` + +## 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(audio): sprint 9 gauntlet — interaction SFX pack and Sprint 7 asset revisions" --description "body" --base main --head audio +``` diff --git a/docs/sprints/sprint-9/ci.md b/docs/sprints/sprint-9/ci.md new file mode 100644 index 000000000..0ad9eb5fb --- /dev/null +++ b/docs/sprints/sprint-9/ci.md @@ -0,0 +1,131 @@ +# Sprint 9: Gauntlet — CI 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:** `ci` +**Agents:** Justine (build/deploy) + +## Carry-over from Sprint 8 + +| # | Title | Status | Notes | +|---|-------|--------|-------| +| #460 | make pre-pr target | backlog | Critical — did not start in Sprint 8. Linchpin for the pre-PR validation chain. Blocks #465. | +| #464 | Content cross-reference validation (9 checks) | backlog | Critical — extends `tooling/validate-content`. Standalone from #460. | +| #465 | Fixture staleness check in make pre-pr | backlog | Blocked by #460. | +| #475 | make fixtures-client target (GDScript-generated fixtures for Rust) | backlog | #474 completed Sprint 8 — now unblocked. | + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #475 | make fixtures-client target (GDScript-generated fixtures for Rust) | — (#474 done Sprint 8) | +| #486 | make golden-diff + make golden-update targets | #485 (server) | +| #497 | Checklist YAML schema + make checklist generation | #487 (server) | + +## Key Decisions + +- `decisions/architecture.md` — D-030 (testability architecture: pre-PR chain, fixture staleness, Layer 3 integration tests; total runtime <3min for pre-PR target) +- `decisions/architecture.md` — D-020 (server binary is a standalone Rust binary; `make pre-pr` must build it as part of the chain) + +## Notes + +### #460 — make pre-pr target + +What exists: The top-level `Makefile` has `validate-content` (wraps `tooling/validate-content`) and individual `ci-server`, `ci-client` targets (see `docs/DEVOPS.md`). The `make pre-pr` target does not yet exist. + +What to deliver: `make pre-pr` in the top-level `Makefile` as an ordered chain: +1. `lint-server` (Clippy) +2. `build-server` (cargo build) +3. `test-server` (cargo nextest) +4. `lint-client` (GDScript linter if any) +5. `build-client` +6. `test-client` (gdUnit4 headless) +7. `validate-content` (schema validation) +8. Fixture staleness check (stub hook for #465) + +Total runtime target: <3 minutes on a clean build. Branch-specific variants: `make pre-pr-server` (steps 1-3 only), `make pre-pr-client` (steps 4-6 only), `make pre-pr-content` (step 7 only). These let individual teams run their own slice. + +Gotcha: Server binary must be built before Layer 3 tests (which spawn it as a subprocess per D-030). Ensure `build-server` precedes `test-server` in the chain. Use `$(MAKE)` for sub-targets to get proper error propagation. + +### #464 — Content cross-reference validation (9 checks) + +What exists: `tooling/validate-content` is an executable script that runs YAML schema validation. Extend it with a cross-reference pass. + +What to deliver: 9 cross-reference checks in `tooling/validate-content` (or a companion script it calls): +1. `canonical_id` uniqueness — no two content items share an ID +2. Relationship target resolution — every `target_id` in a relationship block resolves to a known entity +3. Location slug resolution — every `location_slug` references a known location +4. Dialogue location resolution — every dialogue `location` field resolves +5. Fact tag validity — every fact tag is in the registered tag vocabulary +6. Line pool tag coverage — every monologue line's trigger tags exist in content +7. Archetype consistency — every NPC archetype value is a known enum variant +8. Routine phase validity — every routine `phase` field is a valid DayPhase value +9. (Check 9 is Bidirectional relationship consistency — advisory warning, non-blocking, see #515 for full impl; stub it here as a TODO comment) + +Exit 0 if all checks pass (warnings OK for check 9). Exit 1 on any blocking failure. Output: per-check pass/fail with file+line reference for failures. + +### #465 — Fixture staleness check in make pre-pr + +What exists: `server/tests/gen_fixtures.rs` generates `.msgpack` files at `client/tests/fixtures/msgpack/`. The fixtures committed to git are the source of truth. If code changes alter fixture output without regenerating, client tests will fail with misleading errors. + +What to deliver: A script (or Makefile target `check-fixture-staleness`) that: +1. Runs `cargo test gen_fixtures` to regenerate fixtures into a temp location +2. `git diff` the output against `client/tests/fixtures/msgpack/` +3. Exit 1 if any fixture differs (stale fixtures = false positive in client tests) +4. Print a clear message: "Fixtures stale — run `make fixtures-regen` to update" + +Integrate as step 8 in `make pre-pr` (blocked by #460 providing the chain structure). Also expose as a standalone `make check-fixtures` target. + +### #475 — make fixtures-client target (GDScript-generated fixtures for Rust) + +What exists: #474 was completed in Sprint 8 — GDScript encoding asymmetry tests are done and validated. This target is now unblocked. It exercises the reverse direction: GDScript encoder → Rust decoder. + +What to deliver: `make fixtures-client` Makefile target that: +1. Runs the Godot headless GDScript test that generates `.msgpack` files at `server/tests/fixtures/gdscript/` +2. The Rust integration test in `server/tests/serialization.rs` (or a new file) then verifies it can decode those files + +This validates the full bidirectional protocol: Rust encodes → GDScript decodes (#474), GDScript encodes → Rust decodes (#475). Together they close the cross-encoder compatibility loop. + +### #486 — make golden-diff + make golden-update targets + +What exists: Blocked by #485 (server golden file suite). The golden file lives at `tests/golden/gauntlet_tick_10.json` (created by #485). + +What to deliver: Two Makefile targets: +- `make golden-diff`: runs the golden file test, shows a diff if the output has changed (use `diff --color` or `jq` for pretty output). Exits 1 if changed. +- `make golden-update`: regenerates `tests/golden/gauntlet_tick_10.json` from a fresh run and stages it with `git add`. Does NOT commit — leaves the diff for developer review. + +These are developer workflow targets, not CI gates. Document in `make` help output. + +### #497 — Checklist YAML schema + make checklist generation + +What exists: Blocked by #487 (server Gauntlet rooms — provides room IDs for checklist scope). Checklists live at `content/gauntlet/rooms/{room_id}/checklist.yaml` (per-room) and `content/gauntlet/cross_room_checks.yaml`. + +What to deliver: +- YAML schema definition (JSON Schema or serde-compatible struct): 7 condition types: `player_near`, `player_facing`, `entity_present`, `entity_absent`, `expected_monologue`, `expected_dialogue`, `expected_interaction_verb`. Each condition has `id`, `description`, `condition_type`, and type-specific fields. +- `make checklist-generate` Makefile target: validates all checklist YAML against the schema, outputs a summary of condition counts per room +- `make checklist-validate` target: runs the schema check standalone (useful in pre-PR chain) + +The checklist format feeds into #503 (client auto-checklist progress tracking) in Sprint 10. Design the schema with that consumer in mind — conditions should be evaluable from an `ObserverSnapshot`. + +## Dependency Chain + +``` +#460 (make pre-pr) — start immediately, no blockers + -> #465 (fixture staleness check) + +#464 (content cross-reference validation) — standalone, parallel to #460 + +#474 (done Sprint 8) -> #475 (make fixtures-client, unblocked) + +#485 (server, golden suite) -> #486 (make golden-diff + golden-update) + +#487 (server, Gauntlet rooms) -> #497 (checklist YAML schema) +``` + +## 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(ci): sprint 9 gauntlet — pre-pr chain, content validation, golden targets" --description "body" --base main --head ci +``` diff --git a/docs/sprints/sprint-9/client.md b/docs/sprints/sprint-9/client.md new file mode 100644 index 000000000..9b612e7d4 --- /dev/null +++ b/docs/sprints/sprint-9/client.md @@ -0,0 +1,112 @@ +# Sprint 9: Gauntlet — Client 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:** `client` +**Agents:** Stig (client dev), Hoshe (QA) + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #492 | Client P2 tests: remaining camera (5), entity alpha/color (4), UI (7) | — | +| #493 | Client P3 tests: z-layer ordering (4), entity lerp (3), Tyre additions (5) | — | +| #494 | Client anti-tedium tests: bug report capture, progress hidden when not gauntlet | — | +| #495 | WRONG button (F12) MVP: snapshot + text render + description | #481 (server), #490 (server) | +| #496 | Room timer + personal bests | #487 (server) | +| #516 | Fix MessagePack int_64 encoder dead code branch | — | + +## Key Decisions + +- `decisions/architecture.md` — D-020 (ObserverSnapshot protocol, client is pure renderer), D-030 (gdUnit4 test framework, JSON output, headless stable) +- `decisions/architecture.md` — D-068 (5-bus AudioManager — audio_manager.gd exists), D-066 (dual-scale grid: TILE_SIZE in constants.gd maps 0.5m sim tiles) +- `decisions/scope.md` — D-053 (movement stances — entity lerp speed tuned per stance in entity_renderer.gd) + +## Notes + +### #492 — Client P2 tests: remaining camera (5), entity alpha/color (4), UI (7) + +What exists: `client/scripts/rendering/entity_renderer.gd` handles entity alpha dimming (Peripheral vision = `Constants.PERIPHERAL_ALPHA`), entity color by kind via `Constants.color_for_entity_kind()`. Camera logic is in `client/scripts/` — check for a camera or follow node in the main scene. Existing tests at `client/tests/test_rendering.gd` and `client/tests/test_camera_anchor.gd` provide patterns. + +What to deliver: 16 new gdUnit4 tests split across categories: +- Camera (5): smoothing convergence, zoom levels, viewport clamping, follow target tracking, no-panning constraint (D-014) +- Entity alpha/color (4): `Peripheral` -> `PERIPHERAL_ALPHA`, `Forward` -> alpha 1.0, NPC color by relationship state, Player color constant +- UI (7): monologue display visible/hidden, interaction panel shows nearest verb, inventory HUD shows carried items, dialogue overlay appears on `current_dialogue` set, pause indicator on `TickRate::Paused`, recognition blob rendered for `pending_recognitions`, fog overlay covers occluded tiles + +Use `GdUnitSceneRunner` for scene lifecycle tests. Follow patterns in `client/tests/test_p0_regressions.gd` and `client/tests/test_client_p1.gd`. + +### #493 — Client P3 tests: z-layer ordering (4), entity lerp (3), Tyre additions (5) + +What exists: `entity_renderer.gd` implements framerate-independent lerp with `LERP_SPEED = 12.0` (exponential smoothing). `_entity_targets` Dictionary tracks target pixel positions. `tile_renderer.gd` and `world_renderer.gd` handle tile layers. + +What to deliver: 12 new gdUnit4 tests: +- Z-layer ordering (4): floors render below walls, walls below entities, entities below UI, verify draw order via node tree position or `z_index` +- Entity lerp (3): entity node position starts at spawn point (no lerp on first appear), position moves toward `_entity_targets` over time (mock delta), position converges within ~0.3s of real time at `LERP_SPEED = 12.0` +- Tyre additions (5): recognition transition timing (grey blob -> colored entity over `total_delay_ticks`), facing indicator rotation matches `player_facing` from snapshot, sprint snappiness (lerp arrives faster at higher lerp weight), plus two further items from Tyre's round3 notes — check `db/connectors/ticket show 493` for full list + +### #494 — Client anti-tedium tests: bug report capture, progress hidden when not gauntlet + +What exists: Sprint 9 introduces the WRONG button F12 MVP (#495) and room timer (#496). These features have "anti-tedium" guards — the progress/timer UI must not appear in non-Gauntlet mode. + +What to deliver: 2 regression tests: +- F12 bug report capture: when F12 is pressed, client pauses, capture dialog appears, saved files are produced (mock the file system or check paths are attempted) +- Gauntlet progress UI: in non-Gauntlet mode (no `gauntlet_mode` flag or room metadata), the room timer and personal-bests overlay must not be visible + +These are client-side regression tests. Add to `client/tests/test_p0_regressions.gd` or a new `test_anti_tedium.gd`. + +### #495 — WRONG button (F12) MVP: snapshot + text render + description + +What exists: Blocked by #481 (server text renderer, provides `format_snapshot_text()`) and #490 (server room reset — context for room metadata in capture). `game_state.gd` holds `current_snapshot` (full Dictionary), `current_tick`, `player_stance`. The `sim_bridge.gd` handles IPC. Audio is in `audio_manager.gd`. + +What to deliver: F12 key binding in `client/scripts/autoloads/input_mapper.gd` (or main scene). On press: +1. Send `PlayerAction::Pause` via sim_bridge +2. Show a one-line prompt: "Describe the issue:" (simple LineEdit dialog) +3. On confirm: save three files to `tests/bug-reports/YYYY-MM-DD_HH-MM-SS/`: + - `snapshot.json` — `current_snapshot` as JSON + - `render.txt` — text render output from server's `format_snapshot_text()` (request via a special IPC call, or reproduce in GDScript using snapshot data) + - `description.txt` — tester's typed description + tick/room/seed metadata +4. Send `PlayerAction::Unpause` + +Gotcha: `format_snapshot_text()` is a server-side Rust function (#481). For MVP, reproduce a simplified version client-side in GDScript using `current_snapshot` data, so this ticket is not blocked at runtime even if the test client binary isn't connected. Full fidelity (exact server text output) is a stretch goal. + +### #496 — Room timer + personal bests + +What exists: Blocked by #487 (Gauntlet rooms — provides room_id per snapshot). `game_state.gd` tracks `current_tick`. The `ObserverSnapshot` does not currently include `room_id` — this needs a server-side field or derivation from entity positions. + +What to deliver: Client-side GDScript in the main scene or a new `gauntlet_hud.gd`: +- Timer label: `TIMER: MM:SS` displayed in corner +- Timer starts on room entry event (snapshot indicates new room), resets on room reset trigger +- Personal bests loaded from / saved to `tests/gauntlet-stats.json` (FileAccess) +- Session summary printed on disconnect +- UI hidden entirely in non-Gauntlet mode + +Coordinate with server team: `ObserverSnapshot` needs a `room_id: Option` field or equivalent (may be added as part of #487/#488 server work). Use `#[serde(default)]` on the server side so the client can handle both old and new snapshots. + +### #516 — Fix MessagePack int_64 encoder dead code branch + +What exists: The GDScript MessagePack encoder has a dead code branch for `int_64` encoding. Identified as a bug in Sprint 8 backlog scan. Location: likely in the MessagePack encode/decode utility in `client/scripts/protocol/` — check for the file handling integer encoding. + +What to deliver: Fix the dead code branch, add a test case for the int_64 path, verify it encodes and decodes correctly. Small ticket (~0.5d). Do this early in the sprint as a warm-up before the larger test suites. + +## Dependency Chain + +``` +#516 (msgpack fix) — standalone, start early + +#492 (P2 tests) — standalone, parallel +#493 (P3 tests) — standalone, parallel +#494 (anti-tedium tests) — standalone (can stub #495 behavior) + +#495 (WRONG button F12) — blocked by #481 + #490 (server) + -> (unblocks #507 in Sprint 10) + +#496 (room timer) — blocked by #487 (server) +``` + +## 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(client): sprint 9 gauntlet — QA harness and F12 WRONG button" --description "body" --base main --head client +``` diff --git a/docs/sprints/sprint-9/joint.md b/docs/sprints/sprint-9/joint.md new file mode 100644 index 000000000..894cd89f9 --- /dev/null +++ b/docs/sprints/sprint-9/joint.md @@ -0,0 +1,147 @@ +# Sprint 9: Gauntlet — Joint / Integration + +**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. + +**Agents:** All implementation agents (Dudley, Stig, Hoshe, Tyre, Justine, Inigo) + +--- + +## Pre-Sprint Decisions + +No new decisions are required before Sprint 9 begins. All sprint tickets operate within confirmed decisions. Items to monitor: + +| Item | Status | Needed by | +|------|--------|-----------| +| `room_id` field in ObserverSnapshot | Server team must add as `Option` with `#[serde(default)]` (part of #487/#488 scope) | #496 (room timer, client) | +| Checklist YAML schema design | CI and server team to align on condition types before #497 starts | #497 (checklist YAML, CI) | +| `format_snapshot_text()` signature | Server team (#481) must export pub from server crate before client can use it | #495 (WRONG button, client) | + +--- + +## Cross-Team Dependencies + +``` +Server #480 (test client binary) + -> Server #481 (text renderer, pub export) + -> Client #495 (WRONG button F12 capture) + -> Server #484 (golden diff engine) + -> Server #485 (golden suite) + -> CI #486 (make golden-diff + golden-update) + +Server #487 (Gauntlet rooms 1-3) + -> Server #488 (room constants) + -> Server #490 (room reset trigger) + -> Client #495 (WRONG button — reset plate context) + -> Client #502 (room reset UX — Sprint 10) + -> Client #496 (room timer) + -> CI #497 (checklist YAML schema) + +Client #474 (done Sprint 8) + -> CI #475 (make fixtures-client, unblocked at sprint start) + +CI #460 (make pre-pr) + -> CI #465 (fixture staleness check) +``` + +**Critical path:** `#480 → #481 → #484 → #485 → #486`. This chain must start and progress in the first days of the sprint. Tyre should review #480's workspace crate structure before Dudley starts to avoid rework. + +--- + +## Sprint Completion Proof + +Sprint 9 is complete when all of the following are observable: + +**Test client and golden suite** +- `tooling/test-client/` builds cleanly as a separate workspace crate (`cargo build -p test-client`) +- Running `./tooling/test-client --connect --json` against a live server produces formatted JSON output with tick, entities, and stance data +- `make golden-diff` exits 0 (snapshot output matches committed golden file) +- `make golden-update` regenerates `tests/golden/gauntlet_tick_10.json` without error + +**Layer 3 subprocess test** +- `cargo nextest run layer3` passes: server spawns, `LISTENING:{port}` received, connection established, snapshot deserialized, all assertions green + +**Gauntlet rooms** +- Server boots with Gauntlet content and three rooms load without panic +- `room_at(x, y)` resolves a coordinate within each of the 3 room boundaries to the correct `GauntletRoom` +- Room reset on trigger: entity positions, player KG (room refs), and fog return to tick-0 state + +**Client QA** +- All P2 tests (#492), P3 tests (#493), and anti-tedium tests (#494) pass headless in `make test-client` +- F12 keypress in Godot (Gauntlet mode) pauses simulation, prompts for description, and produces 3 files in `tests/bug-reports/` +- Room timer displays in Gauntlet mode, hidden in non-Gauntlet mode + +**CI chain** +- `make pre-pr` exits 0 on a clean checkout (lint + build + test + content validate + fixture staleness) +- `make pre-pr-server` and `make pre-pr-client` exist as branch-specific variants +- Content cross-reference validation (`tooling/validate-content`) runs all 9 checks without false positives on current content + +**Audio** +- 6 interaction SFX assets committed to `audio` branch under `res://audio/` +- AudioManager registers them at startup (no-op fallback if absent — client branch unchanged) +- Sprint 7 chime assets revised and re-committed + +--- + +## Test Plan Alignment (D-030) + +Sprint 9 is Phase 3+ of the D-030 testability phases. The sprint adds the Layer 3 real subprocess test and the golden file regression suite. Both are explicit D-030 goals: + +| D-030 Phase | Target | Sprint 9 Status | +|-------------|--------|-----------------| +| Phase 1 (Sprints 1-2): test infra | Done (Sprints 1-2) | | +| Phase 2 (Sprints 3-4): monologue pipeline, info boundary negatives | Done (Sprints 3-5) | | +| Phase 3 (Sprint 5+): CauseChain verification, divergent snapshots | Gauntlet golden suite (#485) starts this | In scope | +| Layer 3: real subprocess | `server/tests/layer3.rs` (#482) | In scope | +| Pre-PR gate: <3min full chain | `make pre-pr` (#460) | In scope | +| Fixture staleness detection | `check-fixture-staleness` (#465) | In scope | +| Cross-encoder protocol validation | #474 + #475 | In scope | + +--- + +## Open Questions to Resolve Early + +No blocking Q-NNN items for Sprint 9 tickets. Monitor: + +- **`room_id` in snapshot**: Server team should confirm whether `ObserverSnapshot` gets a `room_id: Option` field in this sprint (scope of #487/#488) or whether the client derives room from entity positions. Client #496 needs this resolved by mid-sprint. +- **Test client crate location**: Confirm `tooling/test-client/` as the workspace crate path with Tyre before #480 starts. The `tooling/` directory is currently scripts only — a Cargo workspace member there is new. + +--- + +## Sequence Recommendation + +**Days 1-3 (all teams in parallel):** +- Server: start #480 immediately (no blockers). Tyre reviews workspace structure. +- Server: start #487 in parallel (no blockers). Dudley on rooms, Tyre on architecture review. +- Server: start #489 (standalone, no blockers). +- Client: start #516 (small warm-up), then #474 (fixtures ready). +- Client: start #492, #493, #494 in parallel (all standalone). +- CI: start #460 and #464 in parallel (both standalone). +- Audio: finish #440 (in progress). + +**Days 4-6:** +- Server: #481, #482 unblock once #480 is done. +- Server: #488, #490 unblock once #487 is done. +- CI: #465 unblocks once #460 is done. #475 unblocks once #474 is done. +- Client: #496 unblocks once #487 server work is done. +- Audio: start #453 once #440 is complete. + +**Days 7-10:** +- Server: #484, #485 unblock once #481 is done. +- CI: #486 unblocks once #485 is done. #497 unblocks once #487 is done. +- Client: #495 unblocks once #481 + #490 are done. + +--- + +## PR Workflow + +Each team submits their own PR from their branch. Integration is via merge to `main`. No joint branch — all tickets are single-team (see team assignments in each briefing). The `joint.md` tracks integration readiness but does not produce a separate branch. + +```bash +# Verify sprint assignment before PR +db/connectors/sprint status --team + +# Standard PR (all flags required — see CLAUDE.md) +tea pr create --repo jpmschweitzer/settled-reach --login schweitz \ + --title "feat(): sprint 9 gauntlet — " \ + --description "body" --base main --head +``` diff --git a/docs/sprints/sprint-9/server.md b/docs/sprints/sprint-9/server.md new file mode 100644 index 000000000..73c379e32 --- /dev/null +++ b/docs/sprints/sprint-9/server.md @@ -0,0 +1,130 @@ +# 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 `, `--replay `, `--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), `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 +```