# Test Plan: Sprint 11 Combine — #507 and #522 - **Date**: 2026-02-19 - **Sprint**: 11 (Combine) - **Spec references**: D-056, D-057, D-030, D-020 - **Tickets**: #507 (WRONG button ring buffer), #522 (OQ-07 no-insert behavior) - **QA Engineer**: Hoshe --- ## #507: WRONG Button Full Captures ### Spec reference D-030 (testability), D-020 (ObserverSnapshot boundary). Sprint 11 client.md §Notes #507. ### What's changing from MVP (#495) MVP (done Sprint 9): F12 → pause → single snapshot → render.txt + snapshot.json + description.txt. Upgrade: 60-tick rolling ring buffers (inputs + snapshots), seed file, replay compatibility. ### Happy path tests 1. **Ring buffer capacity**: `_get_buffer_capacity()` returns 60. 2. **Buffer pre-allocation**: Buffer array has 60 slots immediately after `_ready()`, no lazy allocation. 3. **Input push fills buffer**: After pushing 10 ticks of inputs, buffer has 10 entries. 4. **Snapshot push fills buffer**: After pushing 10 snapshots, snapshot buffer has 10 entries. 5. **JSONL format — inputs**: `_format_inputs_jsonl()` returns N lines for N ticks pushed, each line is valid JSON array. 6. **JSONL format — snapshots**: `_format_snapshots_jsonl()` returns N lines for N ticks, each line is valid JSON. 7. **JSONL line format matches replay.rs**: Each line is a JSON array of PlayerInput objects (`[{"tick":N,"action":"..."}]`), parseable by `tooling/test-client --replay`. 8. **Empty tick flush**: An idle tick pushes an empty array `[]` to input buffer; flushes as `[]` line. 9. **seed.txt present**: `seed.txt` is written to the report directory on capture. 10. **description.txt unchanged**: Description, tick, room, stance, facing, position all still present. 11. **Directory name unchanged**: `gauntlet-t{tick}-{timestamp}/` format preserved. 12. **Existing files preserved**: `snapshot.json` and `render.txt` still written (MVP files). ### Edge cases 13. **Ring buffer circular overwrite**: After pushing 61 ticks, buffer has 60 entries (oldest evicted, newest kept). 14. **Ring buffer 60 exact**: After pushing exactly 60 ticks, all 60 present, none evicted. 15. **Pre-F12 no inputs**: Before any tick inputs are pushed, flush produces empty or correct minimal JSONL. 16. **Snapshot before any tick**: Flush with no snapshots pushed produces empty JSONL or safe fallback. 17. **Seed missing in snapshot**: If server hasn't sent seed yet, `seed.txt` is written with "unknown" or zero value (not crash). 18. **Directory creation failure**: If `user://bug-reports/` is unwritable, `push_error` fires but no crash. ### Integration tests 19. **Inputs JSONL → replay roundtrip**: JSONL produced by flush is valid input for `tooling/test-client --replay` (format matches `replay.rs` parse contract). 20. **F12 capture → file structure**: Full F12 flow produces expected directory with all 5 files: `snapshot.json`, `render.txt`, `description.txt`, `inputs.jsonl`, `snapshots.jsonl`, `seed.txt`. 21. **Ring buffer does not affect render.txt**: Text render output is unchanged from MVP. ### Performance tests 22. **No per-tick allocation**: Pushing inputs in a tight loop does not allocate new Array objects — buffer reuses pre-allocated slots. 23. **Flush is O(60)**: Flushing 60 entries completes in < 1ms. ### Regression markers - MVP `_save_report()` behavior: `snapshot.json`, `render.txt`, `description.txt` unchanged. - F12 → pause → capture → unpause lifecycle unchanged. - `test_anti_tedium.gd` tests must still pass. --- ## #522: Resolve OQ-07 — No-Insert Interaction Behavior ### Spec reference D-056 (cursor states), D-057 (entity interaction). OQ-07 resolution. ### Decision context Three options were proposed. Expected resolution: **(a) cursor reverts to default shape only, no verb labels** — "the character still physically orients to the target, but receives no information from their insert." This is the most diegetically consistent option per D-056's "diegetic test" framing. Spec contract regardless of which option is chosen: **"If the insert is off, labels disappear."** ### Happy path tests **Interaction list (z-layer 6) — diegetic test:** 1. **Insert-off hides list**: When `insert_active == false`, interaction list is not visible. 2. **Insert-on shows list**: When `insert_active == true` and verbs are present, list is visible. 3. **Re-enable restores list**: Toggling insert off then on with verbs present shows the list again. **Cursor (option a — shape changes, labels suppressed):** 4. **Insert-off still transitions cursor shape**: With `insert_active == false`, hovering over an NPC sets cursor to EntityHover state (shape changes, character physically orients). 5. **Insert-off suppresses should_show_interactions()**: `cursor.should_show_interactions()` returns false when `insert_active == false`. 6. **Insert-on restores interactions**: After `set_insert_active(true)`, `should_show_interactions()` returns true (unless in weapon mode). **Cursor (option b — full suppression, if chosen instead):** 4b. **Insert-off locks cursor to Default**: With `insert_active == false`, hovering over an NPC does NOT change cursor state. 5b. **Same `should_show_interactions()` behavior**. ### Edge cases 7. **Insert off + weapon mode**: `insert_active == false` AND `weapon_mode_active == true` — `should_show_interactions()` returns false (not double-false confusion). 8. **Insert off + Shift held**: `insert_active == false` AND `_shift_held == true` — labels STILL suppressed (insert-off trumps shift override). 9. **Insert off at startup**: Default state with insert off from the start — no state corruption. 10. **Rapid toggle**: Toggling insert on/off rapidly does not leave state machine in inconsistent state. 11. **Insert off during active hover**: If player is hovering over an NPC and insert goes off — behavior updates correctly next frame. ### Integration tests 12. **Cursor and list agree**: When `insert_active == false`, BOTH cursor's `should_show_interactions()` AND interaction list's `is_showing()` return false. They must be consistent. 13. **GameState.insert_active propagates**: Changes to `GameState.insert_active` are picked up by both systems on next update. 14. **Decision amendment recorded**: The resolved OQ-07 decision is documented in `decisions/perception.md` or `decisions/scope.md` as an amendment to D-056 or D-057. ### Regression markers - **Existing cursor state tests pass**: All 19 tests in `test_cursor_states.gd` must still pass. - **Existing interaction list tests pass**: All tests in `test_interaction_list.gd` must still pass, including `test_insert_off_hides_interaction_list`. - **Sprint suppression still works**: `test_sprint_suppresses_interaction_list` still passes. - **Weapon mode suppression unchanged**: `test_weapon_mode_suppresses_interactions` still passes. - **D-045 invariance**: Cursor behavior does NOT change by zone or narrative state. --- ## Test Files - `client/tests/test_bug_report_ring_buffer.gd` — automated tests for #507 ring buffer - `client/tests/test_insert_off_behavior.gd` — automated tests for #522 insert-off behavior ## Run Command ```bash make test-client ``` Or headless via gdUnit4: ```bash cd client && godot --headless --quit --path . addons/gdUnit4/bin/GdUnitCmdTool.gd \ --testsuites "tests/test_bug_report_ring_buffer.gd,tests/test_insert_off_behavior.gd" ``` ## Verification Checklist (fill in after implementations land) - [ ] All happy path tests pass - [ ] All edge case tests pass - [ ] All integration tests pass - [ ] Regression: `test_cursor_states.gd` — unchanged - [ ] Regression: `test_interaction_list.gd` — unchanged - [ ] Regression: `test_anti_tedium.gd` — unchanged - [ ] `decisions/perception.md` or related file updated with OQ-07 resolution - [ ] `inputs.jsonl` format verified against `tooling/test-client --replay` manually - [ ] `seed.txt` present in captured bug report