docs(sprints): add Sprint 9 Gauntlet briefing files
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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<String>` 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
|
||||
```
|
||||
@@ -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<String>` 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 <port> --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<String>` 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 <team>
|
||||
|
||||
# Standard PR (all flags required — see CLAUDE.md)
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz \
|
||||
--title "feat(<scope>): sprint 9 gauntlet — <team summary>" \
|
||||
--description "body" --base main --head <branch>
|
||||
```
|
||||
@@ -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 <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
|
||||
```
|
||||
Reference in New Issue
Block a user