Standardized YAML frontmatter on all 20 files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
29 KiB
title, description, type, status, workshop, agent, round, created
| title | description | type | status | workshop | agent | round | created |
|---|---|---|---|---|---|---|---|
| Ozzie — Round 2: Test Client UX + Anti-Tedium Specs | Ozzie's test client UX design and anti-tedium specifications for human tester experience | workshop | archived | test-architecture | ozzie | 2 | 2026-02-17 |
Ozzie — Round 2: Test Client UX + Anti-Tedium Specs
Workshop: QA Strategy & Test Architecture Track: 1 (Test World Design) — Human Tester Experience Date: 2026-02-17
1. Test Client Text Output Format
The test client is a Rust binary that connects to the server via TCP, receives ObserverSnapshots, and renders them as text in the terminal. This is the human tester's "eyes" when they can't (or don't want to) run the full Godot client.
Display Mode: Live-Updating Terminal (not file dumps)
Live-updating. Not per-tick dumps to a file.
Here's why: a tester watching text scroll by can react in real time. They see the NPC move, they see the fog update, they see the monologue fire. Dumping to a file means the tester has to stop the server, open the file, find the relevant tick, and compare. That's a context switch. Context switches kill testing momentum.
Architecture: The test client uses a terminal UI library (crossterm or similar) to maintain a fixed-layout display that refreshes every tick. Not a TUI framework — just cursor positioning and ANSI escape codes. The display is split into sections that update in-place.
But ALSO log to file. Every tick's text output is appended to gauntlet-session-{timestamp}.log. The live display is for the tester's eyes. The log is for the WRONG button's bug report (see Section 2.5). Both exist simultaneously.
Terminal Layout
╔══════════════════════════════════════════════════════════════════════╗
║ GAUNTLET TEST CLIENT v0.1 Tick: 42 TickRate: Full ║
║ Room: Occlusion Corridor Seed: 42 Session: 00:01:23 ║
╠══════════════════════════════════════════════════════════════════════╣
║ PLAYER (15,10) → East | Walk | HP: -- | Inventory: 2/9 ║
╠══════════════════════════════════════════════════════════════════════╣
║ ENTITIES ║
║ ● npc:guard-1 (18,10) Fwd Neutral (#4a9ebb) VISIBLE d=3 ║
║ ◐ npc:worker-2 (20,10) Fwd Unknown (#4a9ebb) REMEMBERED d=5 ║
║ ✕ npc:hidden-1 (19,12) --- Unknown (#4a9ebb) BLOCKED ║
║ └─ wall at (17,10) blocks LOS ║
╠══════════════════════════════════════════════════════════════════════╣
║ FOG Clear:31 | Peripheral:12 | Deep:58 | Map:89 | Dark:312 ║
╠══════════════════════════════════════════════════════════════════════╣
║ SOUND ║
║ 🔊 footsteps ~(22,10) — 2 entities, unhurried [MEDIUM RANGE] ║
║ └─ visual indicator rendered at fog edge (no audio asset) ║
╠══════════════════════════════════════════════════════════════════════╣
║ COGNITION ║
║ ⏳ Recognizing entity at ~(22,10): 0.3s / 0.6s elapsed ║
║ Pending monologue: "Those footsteps... that's—" ║
╠══════════════════════════════════════════════════════════════════════╣
║ INTERACTIONS ║
║ guard-1 [Talk(1) Observe(2)] d=3 in-LOS ║
╠══════════════════════════════════════════════════════════════════════╣
║ MONOLOGUE: "Quiet shift. Too quiet." ║
║ DIALOGUE: none ║
╠══════════════════════════════════════════════════════════════════════╣
║ INVENTORY: [0:keycard] [3:manifest] [_] [_] [_] [_] [_] [_] [_] ║
╠══════════════════════════════════════════════════════════════════════╣
║ CHECKLIST: Occlusion Corridor — 4/7 ██████░░░░ 57% ║
║ TIMER: 00:47 (PB: 00:38) [F12:WRONG] [Home:Hub] [R:Reset]║
╚══════════════════════════════════════════════════════════════════════╝
Section-by-Section Spec
Header Bar:
Tick: N— current simulation tickTickRate: Full/Half/Paused— simulation speedRoom: {name}— detected from player position against Gauntlet room boundsSeed: N— the SimRng seed (for bug reproduction)Session: HH:MM:SS— wall-clock time since test client connected
Player Line:
- Position
(x,y), facing arrow (→ East), stance (Walk/Sprint/Careful/Crouch), inventory count
Entities Section: Each entity on its own line. Symbols indicate visibility state:
●VISIBLE (in clear vision cone)◐REMEMBERED (in fog, previously seen)◌FOGGED (detected in fog, not yet recognized — grey blob state)✕BLOCKED (exists but LOS blocked — debug info only, requires Dudley'sblocked_entitiesfield)⚡RECOGNIZING (mid-cognitive-delay transition)
Per entity: name, position, sector (Fwd/Periph/Behind), relationship state with color hex AND semantic label, visibility state, distance.
For BLOCKED entities: indented line showing WHICH wall blocks LOS. This is the "show your work" data that lets a tester say "the wall at (17,10) should NOT be blocking LOS here" vs "the NPC is correctly hidden."
Fog Section: Five numbers matching D-059's five fog layers. At a glance, the tester sees the fog distribution. If Clear drops to 0, something is very wrong. If Dark is 0, fog isn't working.
Sound Section: EVERY sound event that fired this tick. This is the critical missing piece from Stig's Round 1 spec. D-018 defines three information quality ranges — Close (stereo, identity possible), Medium (directional, imprecise), Long (insert alerts, delayed). Each sound event shows:
- Approximate source position
- Description (footsteps, conversation murmur, alarm)
- Range classification [CLOSE/MEDIUM/LONG]
- Whether an audio asset played or only a visual indicator rendered
Cognition Section: Active cognitive delays (D-060). Shows:
- Which entity is being recognized
- Time elapsed vs total delay
- Any pending monologue text that will fire during the delay
- Recognition chime state (D-067): "chime played at onset" / "chime complete"
Interactions Section: Available verbs per entity within range. Includes priority numbers, distance, and whether the entity is currently in LOS.
Monologue/Dialogue Section:
- Monologue: the EXACT text currently displayed. Not "active" — the words.
- Dialogue: NPC name, current line, number of response options, walk-away state.
Inventory Section:
Visual slot map. Filled slots show item name. Empty slots show [_]. Matches the 9-slot grid (D-065).
Status Bar:
- Checklist progress for current room (auto-tracked, see Section 2.6)
- Room timer
- Hotkey reminders: F12 for WRONG, Home for Hub teleport, R for Reset
Data Requirements from ObserverSnapshot
The test client needs these fields that Dudley identified as missing (Round 1, Track 3 Q1):
blocked_entities: Vec<BlockedEntity>— entities not visible with the blocking wall position. Essential for the✕ BLOCKEDline. Populated only in debug/test mode to avoid production overhead.- Entity display names —
VisibleEntityneeds adisplay_name: Option<String>or the test client resolves StableIds against a loaded name table from the Gauntlet content pack. - Fog layer counts per type — the snapshot has
visible_tilesbut not the 5-layer breakdown. Either the server computes this (preferred — it has the fog state) or the test client derives it from tile visibility data. - Sound events — a
Vec<SoundEvent>with source position, type, range classification, and whether it triggered audio or visual-only feedback.
Items 1, 3, and 4 should be gated behind a --debug flag or test-mode marker on the snapshot request to avoid production overhead.
2. Anti-Tedium Feature Specs
All approved by lead. Here are the full specs.
2.1 Room Reset
Trigger: Player steps on a reset plate at the room entrance. Reset plates are 2x2 tile areas (matching D-066 visual tile size) with a distinct floor pattern (striped hazard marking — fits the station maintenance aesthetic).
UX Flow:
- Player walks onto reset plate
- Terminal displays:
⟳ RESET: Occlusion Corridor? [Enter to confirm / any move to cancel] - Player presses Enter (or sends a dedicated ResetRoom action)
- Server resets the room:
- All entities in the room return to tick-0 positions
- Entity knowledge graph entries related to this room are reverted
- Fog for room tiles reverts to initial state (unexplored for fresh rooms, or the room's default)
- Player inventory changes from this room are reverted (items taken are returned, items dropped are removed)
- Player position stays on the reset plate (they don't teleport)
- Terminal displays:
✓ Occlusion Corridor reset to tick-0 state. Room timer reset. - Room timer restarts from 0
Server-side implementation hint: Each room has a RoomState snapshot taken at Gauntlet load time. Reset = restore that snapshot for all entities within the room's bounding box. Player entity is excluded from the state restore (they stay where they are with their current stance/facing).
What does NOT reset:
- Other rooms (isolation is critical — resetting the Occlusion Corridor doesn't touch the Dialogue Room)
- Player position (stays on reset plate)
- Session timer (wall-clock time keeps running)
- Checklist progress for OTHER rooms
Edge case: What if the player is carrying an item from this room when they reset? The item is removed from inventory and returned to its tick-0 position. Terminal shows: Items returned: keycard → crate_1
2.2 Hub Teleport
Hotkey: Home key. Universal, always available, no modifier needed.
UX Flow:
- Player presses Home from any room
- Terminal displays:
⟳ Teleporting to Central Hub... - Server teleports player entity to
GAUNTLET.hub_spawnposition - Next tick's snapshot reflects new position
- Terminal displays:
✓ Central Hub. Choose a room.
No confirmation required. Hub teleport is non-destructive — it doesn't reset any room state. The player's inventory, knowledge graph, and all room states are preserved. They're just... elsewhere now.
What happens to active states:
- Active dialogue: walk-away triggers (D-064 300ms fade equivalent, dialogue fades)
- Active cognitive delay: cancelled (entity recognition interrupted by teleport)
- Active monologue: cleared (new room, new context)
- Interaction buffer: cleared (D-055 pattern — stance change clears, teleport should too)
In text mode: The display refreshes instantly to show the Hub's snapshot. Room timer pauses (the tester left the room).
2.3 WRONG Button
Hotkey: F12. THE most important anti-tedium feature. This is how testing becomes sustainable.
Full Capture Flow:
- Tester sees something wrong. Presses F12.
- Test client immediately captures:
- Current ObserverSnapshot (the exact server state at this tick)
- Last 60 ticks of snapshot history (the test client buffers these in a ring buffer)
- Last 60 ticks of player inputs (what the tester was doing)
- Current text renderer output (what the tester was seeing)
- Room metadata (which room, expected behaviors, checklist state)
- Gauntlet seed (for reproduction)
- Session timestamp (wall-clock time)
- Terminal pauses the live display and shows:
╔══════════════════════════════════════════════════════════════════╗
║ 🚨 BUG REPORT — Tick 42 — Occlusion Corridor ║
╠══════════════════════════════════════════════════════════════════╣
║ What's wrong? (one line, then Enter): ║
║ > _ ║
╚══════════════════════════════════════════════════════════════════╝
- Tester types one sentence:
NPC behind wall was visible - Presses Enter.
- Test client writes bug report to disk. Terminal shows:
✓ Bug report saved: tests/bug-reports/gauntlet-2026-02-17T08-42-13/
├── report.md (human-readable summary)
├── snapshot_current.json
├── snapshot_history.jsonl (last 60 ticks)
├── input_history.jsonl (last 60 inputs)
├── text_output.txt (what the terminal showed)
└── room_metadata.json (room name, checklist, expected state)
- Live display resumes. Tester continues testing.
Bug Report Format (report.md):
# Bug Report — Gauntlet
- **Date:** 2026-02-17 08:42:13
- **Room:** Occlusion Corridor
- **Tick:** 42
- **Seed:** 42
- **Tester description:** NPC behind wall was visible
## State at time of report
Player (15,10) facing East | Walk | Inventory: 2/9
### Entities
| Name | Position | Sector | Relationship | Visibility | Distance |
|------|----------|--------|-------------|------------|----------|
| guard-1 | (18,10) | Forward | Neutral | VISIBLE | 3 |
| worker-2 | (20,10) | Forward | Unknown | REMEMBERED | 5 |
| hidden-1 | (19,12) | --- | Unknown | **VISIBLE** | 7 |
### Expected (from room checklist)
- hidden-1 should be BLOCKED by wall at (17,10)
### Fog State
Clear: 31 | Peripheral: 12 | Deep: 58 | Map: 89 | Dark: 312
## Reproduction
1. Start Gauntlet with seed 42
2. Navigate to Occlusion Corridor
3. Stand at (15,10) facing East
4. Observe: hidden-1 at (19,12) is visible (should be blocked)
File location: tests/bug-reports/gauntlet-{ISO-timestamp}/. Each report gets its own directory. The reports accumulate across sessions — developers browse them, fix bugs, delete resolved reports.
Ring buffer size: 60 ticks = ~6 seconds at 10 tps. Enough to capture "what just happened" without burning memory. Configurable via CLI flag (--history-buffer 120 for longer captures).
2.4 Room Timer
Visual in text mode: Bottom status bar shows:
TIMER: 00:47 (PB: 00:38)
00:47— time spent in this room this run (wall-clock, not tick time)PB: 00:38— personal best for this room across all sessions
Timer starts when the player enters a room (crosses room bounding box). Timer pauses when the player leaves the room (hub teleport or walking to another room). Timer resets when the room resets (Section 2.1).
Personal bests are stored in a local file: tests/gauntlet-stats.json. Simple key-value: room name → best time in seconds. Persists across sessions. Not committed to git (it's local QA data).
{
"occlusion_corridor": { "best_seconds": 38, "runs": 12 },
"fog_theater": { "best_seconds": 52, "runs": 8 },
"inventory_warehouse": { "best_seconds": 25, "runs": 15 }
}
Why this matters for anti-tedium: "Can I beat my time?" turns testing into a personal challenge. The tester isn't just checking boxes — they're getting FASTER at checking boxes. That's intrinsic motivation. Free. No server changes needed.
2.5 Auto-Checklist Progress
How it works: Each room has a checklist.yaml co-located with its definition (matches Stig's proposal — see Section 3). The test client loads these at startup and tracks which items have been verified.
Tracking mechanism: The test client watches the ObserverSnapshot for conditions that match checklist items. When a condition is met, the checklist item is marked as "observed" (not "passed" — the tester decides if the behavior is correct).
Example checklist item:
- id: occ_hidden_npc_not_visible
description: "NPC behind wall is NOT visible in Visual mode"
condition:
entity: hidden-1
expected_visibility: blocked
player_position_near: [15, 10]
player_facing: East
When the player is near (15,10), facing East, and hidden-1 is NOT in the visible entities list, this item transitions from [ ] to [?] (observed — was it correct?). The tester explicitly confirms with a keypress or it auto-confirms if the condition matches expectation.
Terminal display:
CHECKLIST: Occlusion Corridor — 4/7 ██████░░░░ 57%
[✓] NPC behind wall: NOT visible
[✓] NPC in front: IS visible
[✓] Peripheral NPC: dimmed
[?] Sensor mode: hidden NPC detected ← auto-observed, awaiting confirm
[ ] Sound ping: directional indicator
[ ] Cognitive delay: ~0.6s timing
[ ] Recognition: monologue fires DURING delay
Auto-confirm vs manual-confirm: For objective conditions (entity visible/not-visible, inventory count, fog state), auto-confirm when the snapshot matches. For subjective conditions (timing "feels right", monologue text is appropriate), require manual confirmation (tester presses Y on the highlighted item).
Progress persists within a session but resets when the room resets (Section 2.1). Cross-session progress is stored in tests/gauntlet-stats.json alongside timer data.
3. Cross-Review: Stig's Checklist Proposal
Stig proposes: Checklists co-located with room YAML definitions, make checklist generates docs/workshops/test-architecture/gauntlet-checklist.md.
My Layer 3 (Round 1) proposed: Generated checklist from room metadata, lives at docs/qa/gauntlet-checklist.md, printable, checkboxes.
Verdict: Stig's approach is BETTER than mine. Here's why, and what's missing.
What Stig Gets Right
-
Co-location with room YAML. Checklist items live next to the things they test. When you add a new entity to the Occlusion Corridor YAML, the checklist is right there — you can't forget to add a test for it. My Round 1 proposed a separate checklist file, which would drift from the room definitions over time.
-
Auto-generation via
make checklist. Single source of truth. The markdown output is the artifact, not the source. Change the YAML, regenerate, done. -
Example format is concrete. Stig's examples ("Stand at (15,8) face East → guard-1 visible/Forward/full alpha") are actionable. A tester can follow them step-by-step.
What's Missing from Stig's Proposal
-
No machine-readable condition format. Stig's checklists are human-readable prose. For auto-tracking in the test client (Section 2.5), we need structured conditions — entity name, expected visibility state, player position, etc. The YAML needs both: a
descriptionfield (human prose) and aconditionfield (structured data). -
No subjective vs objective distinction. Some checklist items are objectively verifiable ("NPC not visible" — check the snapshot). Others are subjective ("cognitive delay feels like 0.6s" — tester judgment). The checklist YAML should tag items as
auto(snapshot-verifiable) ormanual(requires human judgment). This drives the auto-confirm vs manual-confirm behavior in the test client. -
No cross-room checklist items. Stig's examples are per-room. But Gestalt's Round 1 identified critical cross-room scenarios (sprint from Crowd Plaza into Occlusion Corridor). Where do those checklist items live? Proposal: a
cross_room_checks.yamlat the Gauntlet root level, separate from per-room checklists. -
No failure guidance. Each checklist item should include a
if_wrongfield: "If this fails, the likely cause is X. Check Y." This is the "articulate failures" requirement from the brief. Example:
- id: occ_hidden_npc_not_visible
description: "NPC behind wall is NOT visible in Visual mode"
condition:
entity: hidden-1
expected_visibility: blocked
type: auto
if_wrong: >
LOS is leaking through the wall at (17,10).
Check: symmetric shadowcasting treating wall tile as transparent?
Check: entity position exactly at (19,12)? Off-by-one puts them in LOS.
File: server/src/perception/shadowcast.rs
Proposed Merged Format
# content/gauntlet/rooms/occlusion_corridor/checklist.yaml
room: occlusion_corridor
description: "Tests LOS, shadowcasting, vision cone, and perception modes"
checks:
- id: occ_01_hidden_not_visible
description: "NPC behind wall is NOT visible in Visual mode"
type: auto
condition:
player_near: [15, 10]
player_facing: East
entity: hidden-1
expected: blocked
if_wrong: |
LOS leaking through wall at (17,10).
Check symmetric shadowcasting in server/src/perception/shadowcast.rs.
- id: occ_02_guard_visible
description: "NPC in front of wall IS visible with full alpha"
type: auto
condition:
player_near: [15, 10]
entity: guard-1
expected: visible
expected_sector: Forward
if_wrong: |
Guard should be in clear LOS at distance 3.
Check entity spawn position in room YAML.
- id: occ_03_cognitive_delay_timing
description: "Cognitive delay for fog recognition takes ~0.6s"
type: manual
guidance: |
Walk to the fog boundary. Wait for a sound event.
Count: does recognition take roughly 0.6 seconds?
The pending_recognitions field shows elapsed/total.
if_wrong: |
Cognitive delay timing off. Check D-060 values in
server/src/perception/recognition.rs.
This format serves BOTH Stig's generated markdown checklist AND my test client auto-tracking. make checklist renders the human-readable version. The test client loads the structured conditions.
4. Human Tester Workflow — Full Step-by-Step
Pre-Session Setup
# Terminal 1: Start the Gauntlet server
make test-world-headless SEED=42
# Terminal 2: Connect test client
make test-client-connect
# or: cargo run --bin gauntlet-client -- --host 127.0.0.1 --port 9876 --history-buffer 60
The test client connects, receives the first ObserverSnapshot, and displays the live terminal UI. The tester is in the Central Hub.
Typical Testing Flow
Step 1: Choose a room. Tester is in the Central Hub. The terminal shows room exits with labels:
EXITS: [N] Fog Theater | [E] Occlusion Corridor | [S] Dialogue Room | [W] Inventory
Tester sends movement input (arrow keys or WASD mapped to PlayerAction) toward the desired room. Or presses a room-select hotkey if quick-nav is implemented.
Step 2: Enter the room. Player walks through the corridor into the Occlusion Corridor. Terminal updates:
- Room name changes to "Occlusion Corridor"
- Timer starts
- Checklist section loads this room's checklist items
- Progress bar shows 0/7
Step 3: Execute the checklist. Tester moves to the specified positions and observes. The test client auto-tracks objective items:
CHECKLIST: Occlusion Corridor — 0/7
[ ] NPC behind wall: NOT visible ← move to (15,10), face East
Tester moves to (15,10), faces East. Test client checks snapshot:
hidden-1is NOT in visible entities. Condition met.
CHECKLIST: Occlusion Corridor — 1/7
[✓] NPC behind wall: NOT visible
[ ] NPC in front: IS visible ← auto-checked: guard-1 visible ✓
Wait — that auto-checked too, because guard-1 IS visible from this position. Two items checked simultaneously.
CHECKLIST: Occlusion Corridor — 2/7
[✓] NPC behind wall: NOT visible
[✓] NPC in front: IS visible
[?] Peripheral NPC: dimmed ← auto-observed, confirm? [Y/N]
Tester looks at the entity list: peripheral NPC shows ◐ PERIPHERAL. They press Y to confirm.
Step 4: Encounter a bug.
Tester switches perception mode (sends a TogglePerceptionMode action). The hidden NPC should now be detected in Sensor mode. But it's not. The entity list still shows ✕ BLOCKED.
Tester presses F12.
╔═══════════════════════════════════════════════╗
║ 🚨 BUG REPORT — Tick 87 — Occlusion Corridor ║
║ What's wrong? ║
║ > Sensor mode doesn't detect NPC behind wall_ ║
╚═══════════════════════════════════════════════╝
Tester types description, presses Enter. Bug report saved.
Step 5: Reset and retry (or move on). Tester presses R to reset the room. All entities return to tick-0. Timer resets. Checklist resets. Tester retries the perception mode test.
Or, tester presses Home to teleport to Hub and test a different room.
Step 6: Complete the session. After testing all target rooms, tester presses Ctrl+C to disconnect.
Test client prints session summary:
═══════════════════════════════════════════════════
SESSION SUMMARY — 2026-02-17 08:42
═══════════════════════════════════════════════════
Rooms visited: 4/7
Checklist coverage: 22/38 items (58%)
Occlusion Corridor: 6/7 ██████████░ 86% 00:47 (PB: 00:38)
Fog Theater: 5/8 ████████░░░ 63% 01:12 (PB: 00:52)
Inventory Warehouse: 7/7 ███████████ 100% 00:25 (PB: 00:25) ★ NEW PB
Crowd Plaza: 4/9 ██████░░░░░ 44% 01:35 (PB: --)
Bug reports filed: 2
→ tests/bug-reports/gauntlet-2026-02-17T08-42-13/
→ tests/bug-reports/gauntlet-2026-02-17T08-43-57/
Stats saved to tests/gauntlet-stats.json
═══════════════════════════════════════════════════
THAT'S a session that felt productive. The tester knows exactly what they covered, what they missed, and where the bugs are. They can hand the bug reports to a developer and say "here, everything you need is in the folder."
Quick-Test Workflow (developer fixing a specific bug)
Not every session is a full walkthrough. A developer just fixed the fog shader and wants to verify:
# Start server + client
make test-world-headless SEED=42
make test-client-connect
# Press Home (already in Hub)
# Walk to Fog Theater (or press room-select hotkey)
# Run through fog-specific checklist items
# If it passes: done in 60 seconds
# If it fails: F12, bug report, fix, repeat
Total time: under 2 minutes. THAT'S why hub-and-spoke layout matters. THAT'S why room reset matters. The developer doesn't touch any room they don't care about.
Summary
| Deliverable | Status |
|---|---|
| Test client terminal layout specification | Complete |
| Live-updating display rationale | Complete |
| Missing ObserverSnapshot fields identified | Complete (4 fields) |
| Room reset full UX flow | Complete |
| Hub teleport spec | Complete |
| WRONG button full capture flow + file format | Complete |
| Room timer + personal bests spec | Complete |
| Auto-checklist progress tracking spec | Complete |
| Cross-review of Stig's checklist: gaps identified | Complete (4 gaps) |
| Merged checklist YAML format proposed | Complete |
| Full human tester workflow (step-by-step) | Complete |
| Quick-test developer workflow | Complete |
Open Questions
- For Dudley: The
blocked_entitiesdebug field on ObserverSnapshot — is this feasible within the currentcompute_observer_snapshotpipeline? Estimated cost per tick? - For Stig: Can the Godot client also render the checklist progress overlay (my Layer 2 debug overlay from Round 1)? Or is checklist tracking test-client-only?
- For Tyre: The test client binary — should it live in
server/src/bin/gauntlet-client.rs(alongside the server binary) or in a separatetools/gauntlet-client/crate? The former shares types easily. The latter keeps the server crate focused. - For Gestalt: Your cross-room transition scenarios (Round 1) — should these have their own checklist section? Where does "sprint from Crowd Plaza into Occlusion Corridor" live in the room YAML hierarchy?