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:
parent
778ebc4129
commit
d4931a50ee
37 changed files with 6841 additions and 295 deletions
|
|
@ -9,7 +9,53 @@
|
|||
"ses_23783ab16ffeCNSrXoK1oU7I8s",
|
||||
"ses_2378026c8ffeZz47LuDzc1yyOK",
|
||||
"ses_237814da3ffetUoZjKTSOO0cB6",
|
||||
"ses_23780806affeiG673hb1eMrpsc"
|
||||
"ses_23780806affeiG673hb1eMrpsc",
|
||||
"ses_235d8c6cbffekI3rCHS6rLNdo6",
|
||||
"ses_235d7c8c3ffevhSDIGLSkMrwNO",
|
||||
"ses_235cfe743ffe2N5FSM90MQYuDD",
|
||||
"ses_235d08175ffeRq75plcCft0ZUN",
|
||||
"ses_235cf01bbffeGESSEHcB5t15WC",
|
||||
"ses_235c8fea7ffevojZ0J0zr2Zn3h",
|
||||
"ses_235c802e1ffe932t8Qwm6GILer",
|
||||
"ses_235b3ae54ffeDjc32WJJEi91I2",
|
||||
"ses_235b4a11fffeTysghRpnEw1zm1",
|
||||
"ses_235a623edffe21fs3XZx4PeUwi",
|
||||
"ses_235a6f921ffeaIzvrGGx9WTYho",
|
||||
"ses_23597b9d4ffeCpsXUNeTVJdfV7",
|
||||
"ses_2359867c3ffe466SWYlhCbvZEQ",
|
||||
"ses_2358d7a82ffeKiEG0dR1dFOyFq",
|
||||
"ses_2358cb310ffeJZX2DKGIBpMHU3",
|
||||
"ses_2357fa71fffeHqeNXgXTtw24Iy",
|
||||
"ses_235806b2cffeumryspoou0AlPO",
|
||||
"ses_2357fe8c7ffexhJQD6igSnvQxY",
|
||||
"ses_235714ec9ffeSWji2tbMppVVbq",
|
||||
"ses_235717e9fffeFSaIjU5NW3E0gP",
|
||||
"ses_23570f69effe4dsaVR8os1DSLt",
|
||||
"ses_235708647ffeO6BxMMd9LuXrpm",
|
||||
"ses_2356294f8ffeBOlgE6BrEi2AFJ",
|
||||
"ses_23561c1bdffeZRftCYw7vm4fGL",
|
||||
"ses_235612ec9ffe6IGVG3oXA8dKQb",
|
||||
"ses_23561f811fferp0KvVdRXCXdTM",
|
||||
"ses_23552d008ffeWpj9wJbHeHFtCi",
|
||||
"ses_23552196dffe4y0RAnIt3ZVMlN",
|
||||
"ses_235529f7cffe3hEu1Zg4bCXkWj",
|
||||
"ses_235524bdeffevbzmdzjNrlYQU2",
|
||||
"ses_23551e581ffepk0Fd0fBvTT8zl",
|
||||
"ses_235432315ffe3QqApGeYfIQhDC",
|
||||
"ses_23542810dffeCeEN7a6QTC7euD",
|
||||
"ses_23542b136ffeHzVyuRHQ3bGdIr",
|
||||
"ses_2353d0fb9ffe344OSbUt2h4DUg",
|
||||
"ses_23542e700ffeaNZ38Gu2rB4AMn",
|
||||
"ses_235435127ffem08ucCdVjXUvrX",
|
||||
"ses_2353a7e10ffen7xf6isCaWxTSx",
|
||||
"ses_23532bdf8ffe10tzyDRFnbKYmK",
|
||||
"ses_235327422ffea3XMbiMtidzoLl",
|
||||
"ses_235332417ffeG8us5EUAdJTLg6",
|
||||
"ses_2353249ebffeJsqU1tzcLwUjIw",
|
||||
"ses_23525bc2dffe2BqHbsMG5X7EHn",
|
||||
"ses_23526101fffeonGIpO7HY1na2X",
|
||||
"ses_235254abeffe5rnNrqeb7sDsgd",
|
||||
"ses_235251f33ffeXIhn18D3PFrX04"
|
||||
],
|
||||
"plan_name": "thressgame-coverage",
|
||||
"agent": "atlas"
|
||||
|
|
|
|||
|
|
@ -671,3 +671,46 @@ Added BlockList wrapping BlockCard with dnd-kit for sorting. Added tests verifyi
|
|||
2. Depth-4 invalid (conditional -> on-move -> conditional -> add-to-attribute) triggering `descriptor.primitives.depth.exceeded`
|
||||
3. Mixed old/new kinds at depth 3 (on-captured -> conditional -> add-aura) valid
|
||||
- `bun test packages/chess/src/modifiers/custom/validate.test.ts` passes with 16 test cases.
|
||||
|
||||
---
|
||||
|
||||
## Path-based selection refactor + nested-editing + add-child button — 2026-04-21
|
||||
|
||||
### Problem
|
||||
Nested BlockCards couldn't be selected/edited. BlockList recursion hardcoded `selectedIndex={null}` + `onSelect={() => {}}` (no-ops) because selection state was flat `number | null`. Also no visible affordance existed to add primitives inside an expanded trigger (user had to know "click parent → palette banner appears → click palette item").
|
||||
|
||||
### Solution
|
||||
- Replaced `selectedIndex: number | null` with `SelectionPath = readonly number[]` throughout VisualBuilderPane / BlockList / BlockCard.
|
||||
- `[]` = no selection; `[0]` = top-level 0; `[0, 2]` = child 2 of top-level 0 (via `params.primitives`). Arbitrary depth supported.
|
||||
- Replaced `expandedIndices: Set<number>` with `expandedPaths: Set<string>` keyed by `path.join('.')` (avoids deep-set-equality ceremony).
|
||||
- 5 new pure path walkers in VisualBuilderPane.tsx: `getNodeAtPath`, `updateAtPath`, `removeAtPath`, `appendChildAtPath`, `reorderAtPath` + `pathStartsWith` helper.
|
||||
- Deleted redundant `handleNestedReorder` / `handleNestedRemove` — consolidated into path-based versions.
|
||||
- New BlockCard prop `onAddChildClick?: () => void` renders a dashed-violet "+ Add primitive inside" button at the bottom of the nested container. BlockList wires it to `() => onSelect(thisPath)` for container primitives only (checks `primitive?.childPrimitives !== undefined`).
|
||||
- Nested-container now renders even when children-list is empty — so empty triggers STILL show the add button.
|
||||
- Nested DnD id collision guard: `nodeIds` include `basePath.join('.')` so nested SortableContexts don't share IDs.
|
||||
- `handleSelect` auto-expands container primitives so the add-child button appears immediately.
|
||||
|
||||
### Conditional `then`/`else` — documented as out of scope
|
||||
Path walker only traverses `params.primitives`. `conditional`'s separate `then`/`else` arrays are NOT selectable/editable in visual mode — same status as before this refactor. Comment in VisualBuilderPane.tsx:85-94.
|
||||
|
||||
### Edge cases handled
|
||||
- Remove subtree containing selection → `pathStartsWith` clears selection
|
||||
- Remove cleans expandedPaths via key-prefix match
|
||||
- Reorder adjusts selection index if it pointed into the reordered list
|
||||
- "Add at top level instead" button → `setSelectedPath([])`
|
||||
|
||||
### Verification
|
||||
- 27/27 visual-builder tests pass (up from 22, +5 new tests covering nested selection + add-child button)
|
||||
- `bun run check` → 166 files / 1957 tests pass
|
||||
- 0 lsp_diagnostics errors in modified files
|
||||
- No `as any`, no `@ts-ignore` introduced
|
||||
|
||||
### Files touched
|
||||
- packages/chess/src/ui/visual-builder/VisualBuilderPane.tsx (+245 / -126 lines)
|
||||
- packages/chess/src/ui/visual-builder/BlockList.tsx (+86 / -49)
|
||||
- packages/chess/src/ui/visual-builder/BlockCard.tsx (+25 / -4)
|
||||
- packages/chess/src/ui/visual-builder/{BlockCard,BlockList,VisualBuilderPane}.test.tsx (updated for new prop shapes + new tests)
|
||||
|
||||
### Pre-existing noise confirmed NOT caused by this work
|
||||
- `ParamField.snapshot.test.tsx` has 15 obsolete snapshots (T14 legacy) — untouched ParamField.tsx per user request
|
||||
- `CustomModifierEditor.mode-roundtrip.test.tsx` fails under `bun test` direct but passes under `bun run check` (vitest environment) — pre-existing localStorage mocking limitation documented at learnings.md:658
|
||||
|
|
|
|||
|
|
@ -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 }`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue