Credibility pass: session residue swept from the rev-10 run

War-story comments become timeless constraints (carrier rule, boobytrap
tell, remove-curse prescription); the rev changelog reunites under
GameConfig.deckRev; three hand-copied cardless stack literals fold into
openCardlessStack; the visionstone one-edge loop is shared via
throughOneEdge with the edge-target hairpin exclusion stated; drain()
moves to test helpers and replaces four inline copies; the butt-head
sweep test loses its dead scaffolding and its expect(refused || true)
tautology and now asserts both branches; a process-named suite is
renamed for the behavior it pins; dead .bezel-spacer CSS and the fx
effect's fossilized cleanup story go. No behavior changes: 304 engine
tests pass, 36/36 ledgers replay, web typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-08-29 19:14:03 -04:00
co-authored by Claude Fable 5
parent f2af1e26f8
commit e93bcc15a2
11 changed files with 107 additions and 139 deletions
+4 -8
View File
@@ -423,8 +423,8 @@
}
}
/** Every targeted cast goes out through here so attachments never drop —
* the attached NUMBER included, unless the call site already carried it. */
/** Attachment plumbing for a cast command: the chosen mods and the
* attached NUMBER ride along (an explicit numberInstanceIds wins). */
function withMods<T extends Parameters<typeof net.command>[0] & { type: "cast" }>(cmd: T): T {
applyMods(cmd);
if (attachedNumber && !cmd.numberInstanceIds) {
@@ -695,7 +695,6 @@
dispatch(withMods({
type: "cast", instanceId: selectedCard.instanceId,
target: { kind: "cell", cell },
...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}),
}));
clearSelection();
return;
@@ -880,7 +879,6 @@
target: { kind: "edge", cell, side },
...(holdDoor && (selectedCard.cardId === "pick-lock" || selectedCard.cardId === "master-key")
? { params: { hold: true } } : {}),
...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}),
}));
clearSelection();
}
@@ -901,7 +899,6 @@
type: "cast", instanceId: selectedCard.instanceId,
target: { kind: "creature", creatureId },
...(selectedCard.cardId === "mega-monster" ? { params: { boost: megaBoost } } : {}),
...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}),
}));
clearSelection();
return;
@@ -958,13 +955,12 @@
swapMeetTarget = playerId;
return; // next: pick your item, then theirs
}
const cmd: Parameters<typeof net.command>[0] = {
const cmd: Parameters<typeof net.command>[0] & { type: "cast" } = {
type: "cast",
instanceId: selectedCard.instanceId,
target: { kind: "player", playerId },
};
if (attachedNumber) cmd.numberInstanceIds = [attachedNumber.instanceId];
applyMods(cmd);
withMods(cmd);
if (selectedCard.cardId === "waterbolt") {
cmd.params = { damage: wbDamage, knockback: numberTotal - wbDamage };
}
-1
View File
@@ -87,7 +87,6 @@
: { x1: x * CELL, y1: (y + 1) * CELL, x2: (x + 1) * CELL, y2: (y + 1) * CELL };
}
const SECTOR = 5;
/** Ghost slots can lie beyond the assembled maze — on any side, including
* negative coordinates (the maze renormalizes after the landing). The
+6 -9
View File
@@ -14,7 +14,7 @@
import { castRay, SIDE_ANGLE, OPPOSITE } from "./fpv/raycast";
import { deepestFacing } from "./fpv/director";
import { aimOfEvents, gatherGlides, hurledIn, shortestArc } from "./fpv/director";
import { untrack } from "svelte";
import { onDestroy, untrack } from "svelte";
import type { GameEvent, GameView, Side } from "@wizwar/engine";
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, edgeSelect = false, onpickup = null, onCreatureMove = null, onCreatureAttack = null, onbody = null, onWarpStep = null }: {
@@ -224,11 +224,10 @@
let actorPos = $state<Record<string, { x: number; y: number }>>({});
// --- The fx: each batch plays once, on its own beat. -----------------
// The effect must track ONLY the batch: the server sends `events` then
// `state` back to back, so anything else in the dependency set (me, the
// view) re-runs this before a zero-delay timer can fire — and an eager
// cleanup would cancel every spell before it left the wand. Timers die
// only with the component.
// Track ONLY the batch: the server sends `events` then `state` back to
// back, so a wider dependency set re-runs this within the same frame,
// before a zero-delay timer can fire. No per-run cleanup for the same
// reason — timers die only with the component.
let playedBatch = 0;
const fxTimers = new Set<ReturnType<typeof setTimeout>>();
$effect(() => {
@@ -250,9 +249,8 @@
fxTimers.add(starter);
}
});
return undefined;
});
$effect(() => () => fxTimers.forEach(clearTimeout));
onDestroy(() => fxTimers.forEach(clearTimeout));
// --- Other bodies glide between views. -------------------------------
let prevView: GameView | null = null;
@@ -528,7 +526,6 @@
}
.bezel-top { border-bottom: 1px solid #2a2620; }
.bezel-bottom { border-top: 1px solid #2a2620; }
.bezel-spacer { flex: 1; }
.bezel-zone { flex: 1; display: flex; align-items: center; gap: 8px; }
.bezel-center { justify-content: center; }
.bezel-right { justify-content: flex-end; }