feat(thressgame-coverage): Wave 19 (close 6 production gaps, lift all fixmes)

T85: Wired Wave 12 move-gen attrs into engine.ts:getAllLegalMoves (the path the drag UI actually uses):
- BlockAllExceptKing (game-level early-return)
- BlockedPieceTypes (game-level early-return)
- MovesAs (per-piece substitution via lookupMoveGenerator)
- MovesAlsoAs (additive; deduped via dedupeMoves helper)
- MoveClassRestriction (post-filter on the entire move set)

Previously these attrs only filtered rules/turn.ts:getLegalMovesForPiece, but the production drag path goes through engine.ts. Now both paths apply identical filters.

T86: engine.ts:applyMove now honors isPawnPush:
- pushedPieceId moved to pushedTo (defender shoved forward)
- pawn moves to diagonal target square (no capture retract)
- HasMoved set on pawn
- Hook firing + turn advancement preserved

5 fixmes lifted in move-gen-attrs.spec.ts (MovesAs, MovesAlsoAs, BlockedPieceTypes, MoveClassRestriction, PawnPushesPiecesEnabled).
1 fixme lifted in orphan-primitives.spec.ts (must-class consumer now active).

E2E status: 30/30 thressgame-coverage tests PASS. 0 fixmes. 0 skips.
Unit tests: 2866 -> 2868 (+2 from new applyMove unit tests). bun run check exit 0.
This commit is contained in:
Joey Yakimowich-Payne 2026-04-26 21:39:13 -06:00
commit d5abaf13bc
No known key found for this signature in database
5 changed files with 269 additions and 10 deletions

View file

@ -487,7 +487,7 @@ async function expectEmpty(page: Page, square: string): Promise<void> {
// would pass) but the knight's legal-move set is unchanged: the
// L-shape Nb1→c3 is accepted and the diagonal Nb1→a2 is rejected,
// the OPPOSITE of what the wired-up dispatcher delivers.
test.fixme('Wave17/MovesAs: knight under MovesAs=bishop rejects L-shapes, accepts diagonals', async ({
test('Wave17/MovesAs: knight under MovesAs=bishop rejects L-shapes, accepts diagonals', async ({
browser,
}) => {
const ctxA = await browser.newContext();
@ -593,7 +593,7 @@ test.fixme('Wave17/MovesAs: knight under MovesAs=bishop rejects L-shapes, accept
// knight; the move-gen pretends it isn't there. The diagonal probe
// (Probe A) below would fail because b1→a2 is never enumerated by
// `getLegalKnightMoves`.
test.fixme('Wave17/MovesAlsoAs: knight gains diagonal moves while keeping L-shapes', async ({
test('Wave17/MovesAlsoAs: knight gains diagonal moves while keeping L-shapes', async ({
browser,
}) => {
const ctxA = await browser.newContext();
@ -787,7 +787,7 @@ test('Wave17/KingExtraReach=2: king steps two squares forward', async ({
// list. Closing this gap requires rewiring the engine to use the
// dispatcher (the natural T74-T77 follow-up), at which point this
// test should drop the `.fixme`.
test.fixme('Wave17/BlockedPieceTypes: listed types cannot move; others still move', async ({
test('Wave17/BlockedPieceTypes: listed types cannot move; others still move', async ({
browser,
}) => {
const ctxA = await browser.newContext();
@ -867,7 +867,7 @@ test.fixme('Wave17/BlockedPieceTypes: listed types cannot move; others still mov
// `findMove`. The descriptor seeds the GAME_ENTITY restriction
// correctly (readAttr confirms `class: 'capture'`); the filter
// just isn't observed at the engine layer.
test.fixme("Wave17/MoveClassRestriction: 'must capture' rejects advances, accepts captures", async ({
test("Wave17/MoveClassRestriction: 'must capture' rejects advances, accepts captures", async ({
browser,
}) => {
const ctxA = await browser.newContext();
@ -959,7 +959,7 @@ test.fixme("Wave17/MoveClassRestriction: 'must capture' rejects advances, accept
// isPawnPush branch that `rules/turn.ts:applyMove` already
// implements (relocate attacker to capSq, relocate target to
// pushTarget, no capture).
test.fixme('Wave17/PawnPushesPiecesEnabled: pawn pushes the diagonal target instead of capturing', async ({
test('Wave17/PawnPushesPiecesEnabled: pawn pushes the diagonal target instead of capturing', async ({
browser,
}) => {
const ctxA = await browser.newContext();

View file

@ -1465,7 +1465,7 @@ test('W18/spawn-marker-pair: portal-end pair at a1 ↔ h8 with mutual MarkerLink
// REJECTED — the test would then assert an attempted e2-e4 leaves
// the pawn on e2.
test.fixme(
test(
'W18/must-class: capture restriction prevents non-capture moves (consumer deferred)',
async ({ browser }) => {
const ctx = await browser.newContext();