Files
settled-reach/docs/workshops/test-architecture/ozzie-round3.md
T
jpmschweitzerandClaude Opus 4.6 a87c95a6eb docs(workshops): complete QA test architecture workshop
3-round workshop with 7 agents (Tyre, Dudley, Stig, Hoshe,
Justine, Gestalt, Ozzie) plus Qatux documenting. Produced:

- 59-item prioritized test backlog (60 tickets under epic #455)
- Gauntlet test world spec: 7 rooms + hub, 48 entities
- Test client binary spec (tooling/test-client/)
- Determinism fixes (3 patches, ~22 lines)
- Server --test-mode + --port 0 design
- Content cross-reference validation (9 checks)
- make pre-pr pipeline (6-step)
- 38 client tests prioritized
- Anti-tedium features (reset plate, hub teleport, WRONG button)
- Human tester walkthrough
- CI pipeline design (deferred but documented)

Sprint 8 scope: ~17.75 team-days across 26 tickets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:00:15 +01:00

521 lines
23 KiB
Markdown

# Ozzie — Round 3: Final Tester Workflow + UX Specs
**Workshop:** QA Strategy & Test Architecture
**Track:** 1 (Test World Design) — Human Tester Experience
**Date:** 2026-02-17
**Round:** 3 (Prioritization)
**Inputs:** All Round 2 outputs, round-2-notes.md
---
## 1. Human Tester Walkthrough — Final
This is the definitive step-by-step workflow. A human tester picks this up and knows exactly what to do from zero to bug report.
### Prerequisites
- Server binary built: `make build-server`
- Test client binary built: `make build-test-client` (builds `settled-reach-test-client`)
- Gauntlet content pack exists at `content/gauntlet/`
- Checklist generated: `make checklist` (produces `docs/qa/gauntlet-checklist.md`)
### Session Start
**Terminal 1 — Server:**
```bash
make test-world-headless
# Equivalent to: settled-reach-server --test-mode --port 0 --seed 42
# Server prints: LISTENING:54321
# Server waits for client connection
```
**Terminal 2 — Test Client:**
```bash
make test-client
# Equivalent to: settled-reach-test-client --connect 127.0.0.1:54321 --text
# Test client connects, receives first ObserverSnapshot
# Live terminal display appears
```
**What the tester sees on connect:**
```
╔══════════════════════════════════════════════════════════════════════╗
║ GAUNTLET TEST CLIENT v0.1 Tick: 0 TickRate: Full ║
║ Room: Central Hub Seed: 42 Session: 00:00:01 ║
╠══════════════════════════════════════════════════════════════════════╣
║ PLAYER (50,50) → North | Walk | Inventory: 0/9 ║
╠══════════════════════════════════════════════════════════════════════╣
║ ENTITIES (0 visible) ║
╠══════════════════════════════════════════════════════════════════════╣
║ FOG Clear:24 | Periph:8 | Deep:0 | Map:0 | Dark:468 ║
╠══════════════════════════════════════════════════════════════════════╣
║ EXITS: [N]Fog Theater [E]Occlusion [S]Dialogue [W]Inventory ║
╠══════════════════════════════════════════════════════════════════════╣
║ MONOLOGUE: none | DIALOGUE: none ║
╠══════════════════════════════════════════════════════════════════════╣
║ CHECKLIST: Central Hub — N/A ║
║ [F12:WRONG] [Home:Hub] [R:Reset] ║
╚══════════════════════════════════════════════════════════════════════╝
```
### Phase 1: Navigate to a Room
Tester sends movement inputs (arrow keys or WASD via `--interactive` mode, or via scripted replay file). They walk East toward the Occlusion Corridor.
As they enter the corridor connecting Hub to Occlusion Corridor:
- Room name changes: `Room: → Occlusion Corridor`
- Timer starts: `TIMER: 00:00`
- Checklist loads: `CHECKLIST: Occlusion Corridor — 0/7`
- Entities appear as they enter the player's vision cone
### Phase 2: Execute the Checklist
The tester has a printed copy of `docs/qa/gauntlet-checklist.md` taped to their monitor (or the test client shows checklist items in the status bar).
**Step 1 from checklist:** "Stand at corridor entrance (45,3), face East"
Tester moves to position. Terminal updates:
```
╠══════════════════════════════════════════════════════════════════════╣
║ ENTITIES (3) ║
║ ● npc:100 (48,3) Fwd Neutral(#4a9ebb) VISIBLE d=3 ║
║ ● obj:200 (46,2) Fwd n/a VISIBLE d=1 ║
║ ✕ npc:101 (50,7) --- --- BLOCKED ║
║ └─ wall at (49,4) blocks LOS ║
╠══════════════════════════════════════════════════════════════════════╣
```
Tester checks against the checklist:
- [x] `npc:100` (guard-1) visible, Forward sector? YES
- [x] `npc:101` (hidden-1) NOT visible? YES — blocked by wall
- [x] Corridor tiles visible, room behind wall unexplored? Check FOG line
Three items checked. Progress bar updates: `3/7 ██████░░░░░`
**Step 2:** "Walk south to (45,8), observe peripheral vision"
Tester moves. guard-1 shifts from Forward to Peripheral:
```
║ ◐ npc:100 (48,3) Periph Neutral(#4a9ebb) VISIBLE d=6 ║
```
- [x] guard-1 in Peripheral sector? YES — symbol changed from `●` to `◐`
- [x] Fog visible_count decreased? Check FOG line: was 24, now 18
**Step 3:** "Switch to Sensor perception mode"
Tester sends TogglePerceptionMode input. If working correctly, hidden-1 should appear:
```
║ ⚡ npc:101 (50,7) --- Unknown(#4a9ebb) DETECTED d=7 ║
```
- [x] hidden-1 appears in entity list with sensor data? YES
**Checklist complete: 7/7.** Timer shows `00:47`. If personal best was `00:38`, tester sees `(PB: 00:38)`.
### Phase 3: Encounter a Bug
What if step 3 fails? hidden-1 doesn't appear even in Sensor mode. The tester presses F12.
**F12 Flow:**
1. Game pauses immediately (Pause sent to server)
2. Terminal shows:
```
╔══════════════════════════════════════════════════════════════════╗
║ BUG REPORT — Tick 87 — Occlusion Corridor ║
╠══════════════════════════════════════════════════════════════════╣
║ What's wrong? (type, then Enter): ║
║ > Sensor mode doesn't detect NPC behind wall_ ║
╚══════════════════════════════════════════════════════════════════╝
```
3. Tester types one sentence, presses Enter
4. Terminal shows:
```
Bug report saved: tests/bug-reports/gauntlet-t87-20260217-084213/
report.md | snapshot.json | text_output.txt | description.txt
Resume testing. [Enter]
```
5. Game unpauses. Tester continues.
### Phase 4: Reset and Re-Test
Tester walks back to the room entrance and steps on the reset plate (2x2 amber-striped floor tile). Interaction list shows `[Reset Room]`. Tester activates it.
```
Reset: Occlusion Corridor → tick-0 state. Timer reset.
```
All entities return to starting positions. Fog reverts. Timer restarts. Checklist resets to 0/7. Tester can re-run the checklist from step 1.
### Phase 5: Move to Another Room
Tester presses `Home` → teleports to Central Hub. Walks to next room. Repeat Phase 2-4.
### Phase 6: End Session
Tester presses Ctrl+C. Test client prints:
```
══════════════════════════════════════════════════════
SESSION SUMMARY — 2026-02-17 08:55
══════════════════════════════════════════════════════
Rooms tested: 4 / 14
Total time: 12:34
Occlusion Corridor: 7/7 ███████████ 100% 00:47
Fog Theater: 5/8 ████████░░░ 63% 01:12
Inventory Warehouse: 7/7 ███████████ 100% 00:25 ★ PB
Crowd Plaza: 4/9 ██████░░░░░ 44% 01:35
Bug reports filed: 1
→ tests/bug-reports/gauntlet-t87-20260217-084213/
Stats saved: tests/gauntlet-stats.json
══════════════════════════════════════════════════════
```
Done. Developer picks up the bug report folder, has everything they need to reproduce.
---
## 2. Test Client UX Final Spec
Merges my Round 2 terminal layout with Tyre's `kind:entity_id` labels and the lead-confirmed architecture decisions.
### Display Architecture
- **Binary:** `settled-reach-test-client` at `tooling/test-client/` (separate crate, imports shared types from server)
- **Text renderer:** `tooling/test-client/src/text_renderer.rs` (or shared module within the crate)
- **Display mode:** Live-updating terminal via crossterm ANSI escape codes. NOT a TUI framework. Fixed-layout sections that refresh every tick.
- **Simultaneous logging:** Every tick appended to `gauntlet-session-{timestamp}.log` for the WRONG button and post-session review.
### Entity Label Format
Per Tyre's decision: `kind:entity_id`. Examples:
- `npc:100` — NPC with wire entity_id 100
- `obj:200` — Object with wire entity_id 200
- `player:0` — Player entity
No display names on wire. Test assertions use Gauntlet constants: `assert_entity_visible(&snapshot, GUARD_1.wire_id)`.
For human testers, the printed checklist maps IDs to names: "npc:100 = guard-1, npc:101 = hidden-1". This lives in the checklist header, not the terminal display.
### Entity Visibility Symbols
| Symbol | State | Meaning |
|--------|-------|---------|
| `●` | VISIBLE | In clear vision cone (Forward or Peripheral) |
| `◐` | REMEMBERED | In fog, previously seen, entity persists in snapshot |
| `◌` | FOGGED | Detected in fog, NOT yet recognized (grey blob state) |
| `✕` | BLOCKED | Exists but LOS blocked by wall (debug info, requires `blocked_entities` field) |
| `⚡` | RECOGNIZING | Mid-cognitive-delay (D-060), transitioning from blob to recognized |
Peripheral entities show `●` but with the `Periph` sector label — the symbol indicates visibility state, the sector label indicates where in the cone.
### Section Order (top to bottom)
1. **Header** — tick, tick rate, room name, seed, session timer
2. **Player** — position, facing, stance, inventory count
3. **Entities** — one line per entity, sorted by distance (nearest first per Tyre's format)
4. **Fog** — 5-layer tile counts (Clear/Periph/Deep/Map/Dark)
5. **Sound** — events this tick (MVP: omit if no `SoundEvent` in snapshot yet)
6. **Cognition** — active cognitive delays (MVP: derive from `pending_recognitions`)
7. **Interactions** — available verbs per entity
8. **Monologue/Dialogue** — exact text content
9. **Inventory** — slot map
10. **Status** — checklist progress, timer, PB, hotkey reminders
### MVP vs Full Display
**Sprint 8 MVP** (what ships first):
Sections 1, 2, 3, 4, 7, 8, 9, 10. These derive entirely from the existing ObserverSnapshot fields. No server changes needed.
**Sprint 9+ Full** (requires new snapshot fields):
Add sections 5 (Sound) and 6 (Cognition). These need:
- `Vec<SoundEvent>` in ObserverSnapshot (source position, type, range)
- Enhanced `pending_recognitions` with elapsed/total timing
- `blocked_entities` for the `✕ BLOCKED` display
### Refresh Behavior
- **Per-tick refresh.** Every ObserverSnapshot received triggers a full terminal redraw.
- **Cursor positioning.** Use ANSI escape `\x1b[H` (cursor home) + section rewrites. No full clear (avoids flicker).
- **Paused display.** When TickRate is Paused, display shows `[PAUSED]` in header. Still refreshes on snapshot receipt (pause state changes are snapshots).
---
## 3. WRONG Button Final Spec
### MVP — Sprint 8
The MINIMUM that makes bug reporting useful. Implementable without any server changes.
**Hotkey:** F12
**What gets captured:**
| Data | Source | Format | Size |
|------|--------|--------|------|
| Current ObserverSnapshot | Last received snapshot (already in memory) | JSON | ~2-5KB |
| Current text output | Last `format_snapshot_text()` result | Text | ~1KB |
| Tick + room + seed | From snapshot + Gauntlet constants | Part of report.md | Trivial |
| Tester description | One-line text prompt | Text | ~100 bytes |
**What does NOT ship in MVP:**
- No snapshot history ring buffer (requires 60x snapshot storage)
- No input history (requires InputMapper ring buffer)
- No screenshot (test client is terminal-only)
- No world digest (requires server-side addition)
**UX Flow (MVP):**
1. F12 pressed
2. Test client sends Pause to server
3. Terminal shows one-line prompt: `BUG: What's wrong? > _`
4. Tester types, presses Enter
5. Test client writes files to `tests/bug-reports/gauntlet-t{tick}-{timestamp}/`:
```
tests/bug-reports/gauntlet-t87-20260217-084213/
├── report.md # Human-readable summary (generated)
├── snapshot.json # ObserverSnapshot as pretty-printed JSON
├── text_output.txt # What the terminal was showing
└── description.txt # Tester's one-line description
```
6. Test client sends Unpause
7. Testing resumes
**report.md Format (MVP):**
```markdown
# Bug Report — Gauntlet
- **Tick:** 87
- **Room:** Occlusion Corridor
- **Seed:** 42
- **Date:** 2026-02-17 08:42:13
- **Description:** Sensor mode doesn't detect NPC behind wall
## Player State
Position: (45,8) facing East | Stance: Walk | Inventory: 0/9
## Entities at Time of Report
| ID | Position | Sector | Relationship | Visibility | Distance |
|----|----------|--------|-------------|------------|----------|
| npc:100 | (48,3) | Periph | Neutral | VISIBLE | 6 |
| npc:101 | (50,7) | --- | --- | BLOCKED | 7 |
## Fog State
Clear: 18 | Peripheral: 6 | Deep: 12 | Map: 0 | Dark: 464
## Checklist State
Occlusion Corridor: 5/7 — item 'occ_hidden_sensor' FAILED
## Reproduction
1. `make test-world-headless` (seed 42)
2. `make test-client`
3. Walk to Occlusion Corridor (45,8), face East
4. Switch to Sensor perception mode
5. Expected: npc:101 appears in entity list
6. Actual: npc:101 still BLOCKED
```
The reproduction steps are templated from the current checklist step + room position. A developer reads this and can reproduce in under a minute.
### Full — Sprint 9+
Everything in MVP plus:
| Addition | Source | Requires |
|----------|--------|----------|
| Snapshot history (60 ticks) | Ring buffer in test client | ~300KB memory, client-side only |
| Input history (60 inputs) | Ring buffer in test client | ~12KB memory, client-side only |
| Replay seed + input file | Combine seed + full input log | Can reproduce entire session |
| Room metadata + expected state | From checklist YAML | Client loads checklist at startup |
**Additional files in Full mode:**
```
tests/bug-reports/gauntlet-t87-20260217-084213/
├── report.md
├── snapshot.json
├── snapshot_history.jsonl # Last 60 snapshots, one per line
├── input_history.jsonl # Last 60 inputs, one per line
├── text_output.txt
├── description.txt
└── room_metadata.json # Checklist items + expected state
```
**The ring buffer is the key addition.** 60 ticks = ~6 seconds at 10 tps. When the tester presses F12, the last 6 seconds of game state are preserved. A developer can replay those 6 seconds to see exactly what happened leading up to the bug.
### Implementation Notes for Dudley (server) and Stig (client)
**Server (Dudley):** MVP requires ZERO server changes. The test client formats the existing ObserverSnapshot. Full mode also requires no server changes — the ring buffer and input history are client-side. The only server-side addition (deferred to Sprint 9+) is `blocked_entities` for the `✕ BLOCKED` display.
**Client (Stig):** The Godot client gets its own F12 handler (`bug_report.gd` autoload per Stig's R2 spec). The Godot version captures a screenshot + scene tree dump that the test client can't. Both clients write to the same `tests/bug-reports/` directory. Reports from either client are useful.
---
## 4. Quick-Test Developer Workflow
A developer just fixed the fog shader. They want to verify the Occlusion Corridor works. Total time target: **under 2 minutes.**
### The Flow
```
# Step 1: Build (incremental, ~5-10s)
make build-server
# Step 2: Start server + client (split terminal or use tmux)
make test-world-headless &
# Wait for LISTENING:port output
make test-client
# Step 3: Teleport to target room (~2s)
# Press Home (if not already in Hub)
# Walk East to Occlusion Corridor (or type room shortcut if implemented)
# Step 4: Run the specific checklist items (~30-60s)
# Move to (45,3), face East — check entities
# Move to (45,8) — check peripheral
# Toggle Sensor mode — check hidden NPC
# Step 5: Done
# If all good: Ctrl+C, session summary confirms 7/7
# If bug found: F12, one sentence, resume or Ctrl+C
```
**Total wall-clock:** Build 10s + startup 3s + navigate 5s + test 45s + exit 2s = **~65 seconds.**
### Why This Is Fast
1. **Incremental build** — only recompiles changed files (~5-10s for a shader fix)
2. **Hub-and-spoke layout** — walk directly to target room, skip everything else
3. **No server restart for re-test** — room reset plate lets them retry without killing the server
4. **No report overhead** — F12 captures everything in one press, no manual note-taking
### For Regression Verification After a Fix
The developer fixed the bug from the report. Now they want to verify:
```bash
# Option A: Manual verification (interactive)
make test-world-headless &
make test-client
# Navigate to the room, reproduce the steps from report.md
# Verify the bug is fixed
# Option B: Automated verification (replay file)
make test-world-headless &
settled-reach-test-client \
--connect 127.0.0.1:54321 \
--replay tests/bug-reports/gauntlet-t87-20260217-084213/input_history.jsonl \
--ticks 90 \
--text
# Watch the replay, verify the entity appears correctly at tick 87
```
Option B is available once the Full WRONG button ships (Sprint 9+) — it uses the captured input history as a replay file. The developer literally replays the exact sequence that triggered the bug and checks if it's fixed.
---
## 5. Anti-Tedium Priority for Sprint 8 MVP
Four features approved. Not all are equally important for Sprint 8. Here's the priority ranking.
### Sprint 8: MUST SHIP (2 features)
#### Priority 1: Room Reset Triggers
**Why first:** Without room reset, a tester who wants to re-run a room's checklist must restart the server. That's 30+ seconds of downtime every retry. Over a sprint of testing, that's hours of wasted time. Room reset is the difference between "I'll re-test that" and "I'll skip it."
**Implementation scope:**
- Server: `RoomResetTrigger` component, `RoomSnapshots` resource, `detect_room_reset` + `execute_room_reset` systems, 10-tick debounce. ~150 lines (Dudley's R2 spec).
- Client: `reset_plate` tile type in TileRenderer, "Reset Room" interaction verb. ~20 lines (Stig's R2 spec).
- Test mode only: systems only registered with `--test-mode`.
**Estimated effort:** 1-1.5 days.
#### Priority 2: WRONG Button (MVP)
**Why second:** Without the WRONG button, a tester who finds a bug has to manually describe it — writing down the tick, room, entity positions, what they expected. That's slow, error-prone, and most testers won't bother. The WRONG button makes bug reporting a 5-second action.
**Implementation scope:**
- Test client: capture current snapshot + text output, one-line prompt, write to disk. ~100 lines.
- Godot client: `bug_report.gd` autoload, F12 handler, modal prompt, snapshot + screenshot + scene tree dump. ~80 lines (Stig's R2 spec).
- No server changes.
**Estimated effort:** 1 day.
### Sprint 9: SHOULD SHIP (2 features)
#### Priority 3: Hub Teleport
**Why deferred:** Hub teleport saves walking time (~5-10 seconds per room transition). Useful, but the Gauntlet corridors are only ~10-15 tiles long. Walking is tolerable in Sprint 8. Hub teleport becomes more valuable as more rooms are added.
**Implementation scope:**
- Server: `TeleportToHub` PlayerAction variant, process in `process_player_input`. ~20 lines.
- Client: `Home` key mapping, `TeleportHub` wire action, camera snap + fade. ~30 lines.
**Estimated effort:** 0.5 days.
#### Priority 4: Room Timer + Auto-Checklist Progress
**Why deferred:** Timers and progress tracking are engagement features — they make testing more pleasant, not more possible. The Gauntlet works without them. They become valuable once the team is doing regular sprint-over-sprint testing.
**Implementation scope:**
- Test client: room detection from bounds, wall-clock timer, stats file, session summary. ~150 lines.
- Test client: checklist YAML loading, condition evaluation against snapshot, progress display. ~200 lines.
- Godot client: `GauntletProgress` overlay. ~60 lines (Stig's R2 spec).
- Stats persistence: `tests/gauntlet-stats.json`.
**Estimated effort:** 1.5-2 days.
### Summary Table
| Priority | Feature | Sprint | Effort | Why |
|----------|---------|--------|--------|-----|
| **P1** | Room Reset | **8** | 1-1.5d | Can't re-test without it |
| **P2** | WRONG Button (MVP) | **8** | 1d | Can't report bugs without it |
| P3 | Hub Teleport | 9 | 0.5d | Convenience, not necessity |
| P4 | Timer + Checklist | 9 | 1.5-2d | Engagement, not necessity |
**Sprint 8 anti-tedium budget: ~2-2.5 days.** This fits alongside the other Sprint 8 priorities (test client binary, determinism fixes, server flags).
### What About F3 Debug Overlay?
**Defer indefinitely.** Stig is right — the WRONG button captures the same data on demand. F3 as a real-time overlay is a performance cost for a marginal convenience gain. If testers ask for it after using the WRONG button for a sprint, reconsider.
---
## Summary of Round 3 Deliverables
| # | Deliverable | Status |
|---|-------------|--------|
| 1 | Human tester walkthrough — final (start to finish, 6 phases) | Complete |
| 2 | Test client UX final spec (labels, symbols, sections, MVP vs full) | Complete |
| 3 | WRONG button final spec (MVP Sprint 8 + Full Sprint 9+) | Complete |
| 4 | Quick-test developer workflow (65-second flow) | Complete |
| 5 | Anti-tedium Sprint 8 priority (Room Reset + WRONG Button MVP) | Complete |
### Build-Ready Specs
These specs are implementation-ready for the assigned developers:
- **Dudley:** Room reset server mechanism (Section 5, P1). Use your R2 `RoomResetTrigger` + `RoomSnapshots` design. No server changes needed for WRONG button MVP.
- **Stig:** Room reset tile type + WRONG button Godot client (Section 5, P1+P2). Use your R2 `reset_plate` tile + `bug_report.gd` autoload designs.
- **Tyre:** Test client binary text renderer + WRONG button integration (Section 2+3). Use your R2 `test_client.rs` design. Add the F12 capture flow from Section 3 MVP.
The human tester picks up `docs/qa/gauntlet-checklist.md`, opens two terminals, and goes. That's the goal. That's what we're shipping.