feat(thressgame-coverage): Wave 8 (WS protocol v2 + suspended execution + request-choice)

- T43: WS protocol v2 schema; protocolVersion field; RequestChoice/SubmitChoice/ProtocolVersionMismatch messages; v1 backward-compat
- T44: server-side request-choice broadcast on push; submit-choice validation (kind/forPlayer/value-type); ordered LIFO matching
- T45: PendingChoices stack on GAME_ENTITY; pushPendingChoice/popPendingChoice/peekPendingChoice helpers; serializePendingChoice (Map<->Array roundtrip); MAX_CHOICE_DEPTH=8 enforced
- T46: submitChoiceAndResume(engine, choiceId, value); descriptor-by-id lookup; bindings restored; remaining primitives executed via runPrimitives from primitiveIndex+1
- T47: request-choice primitive; SuspendedExecution exception mechanism; dispatcher catches and stops sibling iteration; deterministic choiceId via session counter
- T48: AutoChoiceResolver test transport (answersByKind / answersById); drainPendingChoices LIFO walk
- T49: server-side choice timeout enforcement; auto-resolve to first-option-per-kind; disconnect handler (forfeit / pause)
- T50: ChoiceTimeoutPolicy on GAME_ENTITY (timeout-with-default | no-timeout); CreateGameRequest extended; default 60s

Tests: 2533 -> 2658 (+125). bun run check exit 0.
This commit is contained in:
Joey Yakimowich-Payne 2026-04-26 11:54:24 -06:00
commit d4931a50ee
No known key found for this signature in database
37 changed files with 6841 additions and 295 deletions

View file

@ -1434,7 +1434,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
> **WAVE 8 — WS PROTOCOL v2 + SUSPENDED EXECUTION**: highest-risk wave. Each task is its own commit; integration tests at the end.
- [ ] 43. WS protocol v2 schema
- [x] 43. WS protocol v2 schema
**What to do**:
- Edit `packages/server/src/protocol.ts`: add new message types `RequestChoiceMessage` (server→client: `{ kind: "request-choice", choiceId: string, prompt: { kind: "piece"|"square"|"column"|"row"|"coin-flip"|"rps", filter?, forPlayer: Color, timeout?: number } }`) and `SubmitChoiceMessage` (client→server: `{ kind: "submit-choice", choiceId: string, value: unknown }`)
@ -1449,7 +1449,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-43-protocol-v2.txt`
**Commit**: YES — `feat(server): WS protocol v2 schema (request-choice + version negotiation)`
- [ ] 44. Server-side request-choice broadcast + validation
- [x] 44. Server-side request-choice broadcast + validation
**What to do**:
- Edit `packages/server/src/ws.ts` (or equivalent ws handler): when game state has a pendingChoices entry, server sends `RequestChoiceMessage` to the targeted player on connect/reconnect
@ -1463,7 +1463,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-44-server-choice.txt`
**Commit**: YES — `feat(server): request-choice broadcast + validation`
- [ ] 45. Stack-based pendingChoices state on GAME_ENTITY + serializer
- [x] 45. Stack-based pendingChoices state on GAME_ENTITY + serializer
**What to do**:
- Add attr `PendingChoices: readonly PendingChoice[]` to ChessAttrMap. PendingChoice = `{ choiceId: string, descriptorId: string, triggerPath: readonly number[], primitiveIndex: number, bindings: Record<string, JsonValue>, kind, prompt, forPlayer, timeout?: number, expiresAtTimestamp?: number }`
@ -1477,7 +1477,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-45-pending-choices.txt`
**Commit**: YES — `feat(chess): pendingChoices stack on GAME_ENTITY`
- [ ] 46. Suspended-execution resume in integration preset
- [x] 46. Suspended-execution resume in integration preset
**What to do**:
- Edit integration preset's `performAction` hook: when action is `submit-choice`, pop top PendingChoice, restore bindings into a fresh PrimitiveApplyContext, resume runPrimitives at saved `triggerPath` + `primitiveIndex + 1` (skip past the request-choice that caused suspension), inject the submitted value as binding (key matches request-choice's `bind` param)
@ -1490,7 +1490,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-46-resume.txt`
**Commit**: YES — `feat(chess): suspended execution resume`
- [ ] 47. request-choice primitive
- [x] 47. request-choice primitive
**What to do**:
- Create `packages/chess/src/modifiers/primitives/request-choice.ts`: kind "request-choice", schema `{ kind: "piece"|"square"|"column"|"row"|"coin-flip"|"rps", forPlayer: "chooser"|"opponent"|"both", filter?, bind: string, then: NodeArray }`
@ -1505,7 +1505,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-47-request-choice.txt`
**Commit**: YES — `feat(chess): request-choice primitive`
- [ ] 48. Deterministic auto-resolver test transport
- [x] 48. Deterministic auto-resolver test transport
**What to do**:
- Create `packages/chess/src/__fixtures__/test-choice-resolver.ts`: a test-only WS transport mock that auto-resolves PendingChoices according to a deterministic policy:
@ -1522,7 +1522,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-48-test-resolver.txt`
**Commit**: YES — `test(chess): deterministic auto-resolver test transport`
- [ ] 49. Choice timeout + disconnect handler
- [x] 49. Choice timeout + disconnect handler
**What to do**:
- Edit ws.ts: when PendingChoice has `timeout` field, server schedules a timer; on expiry, server auto-submits the "first valid option" as the choice and resumes
@ -1536,7 +1536,7 @@ Max Concurrent: 8 (Waves 5+6+7+9 overlap)
**QA Scenarios**: `.sisyphus/evidence/task-49-timeout-disconnect.txt`
**Commit**: YES — `feat(server): choice timeout + disconnect handler`
- [ ] 50. Game settings: choiceTimeout in CreateGameRequest
- [x] 50. Game settings: choiceTimeout in CreateGameRequest
**What to do**:
- Edit `packages/server/src/protocol.ts` CreateGameRequest schema: add `choiceTimeout: { mode: "timeout-with-default", seconds: number } | { mode: "no-timeout" }` field; default = `{ mode: "timeout-with-default", seconds: 60 }`