Credibility pass: the accretion sanded smooth

Blind reviews over d2b40ec..HEAD (engine / web / server+deploy). Orphaned
doc comments rejoined their functions; the swap-meet tradables collapsed
from four pasted deriveds to one (killing the "the's treasure" label);
FEAR's aura and banner now share the engine's own dreadDistance; the
long-press peek got one home; rulebook.ts stopped wearing JSON quotes;
process-named tests and war-story comments now state the constraints they
pin; the protocol doc caught up to its handlers; verify-ledgers.sh can
actually count failures; the runbook learned about the determinism gate
and the nightly backups. No behavior changes — 255 tests and all 48
production ledgers replay identically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-21 10:58:08 -04:00
co-authored by Claude Fable 5
parent 3445d12206
commit 6adcbe23b5
17 changed files with 209 additions and 204 deletions
+10 -10
View File
@@ -256,6 +256,12 @@ function bestWallCrossing(
return best;
}
/** Square-filling nuisances for path denial, best blocker first. */
const DENIAL_FILLERS = [
"fill-square-with-stone", "thornbush", "rosebush", "create-pit",
"fill-square-with-slime", "killer-ooze", "handful-of-tacks", "dust-cloud",
];
/**
* Path denial: when an enemy's march threatens something dear — they carry
* one of the clockwork's treasures home, or close on its gold lying on the
@@ -265,12 +271,6 @@ function bestWallCrossing(
* would accept are offered (sighted, empty, off homes and warp tokens), and
* only when the detour costs the enemy 3+ extra steps.
*/
/** Square-filling nuisances for path denial, best blocker first. */
const DENIAL_FILLERS = [
"fill-square-with-stone", "thornbush", "rosebush", "create-pit",
"fill-square-with-slime", "killer-ooze", "handful-of-tacks", "dust-cloud",
];
function pathDenial(view: GameView): Command | null {
const wall = inHand(view, "create-wall") ?? inHand(view, "illusion-wall");
const jam = inHand(view, "jam-lock");
@@ -531,7 +531,8 @@ function roadworkPlan(
}
}
}
return best !== null ? (best as { cmd: Command; total: number }).cmd : null;
if (best !== null) return (best as { cmd: Command; total: number }).cmd;
return null;
}
/**
@@ -1308,14 +1309,14 @@ export function automatonCommand(
view.sustained.some((e) => e.cardId === "fear" && e.targetId === p.id) &&
Math.abs(p.position.x - self.position.x) + Math.abs(p.position.y - self.position.y) <= 3);
for (const source of dreadful) {
const here = Math.abs(source.position.x - self.position.x) + Math.abs(source.position.y - self.position.y);
const distNow = Math.abs(source.position.x - self.position.x) + Math.abs(source.position.y - self.position.y);
let best: { dir: Side; d: number } | null = null;
for (const dir of SIDES) {
const t = stepTarget(view.board, self.position, dir);
if (t.kind === "blocked") continue;
if (view.squareContents[cellKey(t.to)]?.kind === "stone") continue;
const d = Math.abs(source.position.x - t.to.x) + Math.abs(source.position.y - t.to.y);
if (d > here && (best === null || d > best.d)) best = { dir, d };
if (d > distNow && (best === null || d > best.d)) best = { dir, d };
}
if (best) return { type: "move", direction: best.dir };
// Dead end: the card excuses what cannot be done; march on normally.
@@ -1391,7 +1392,6 @@ export function automatonCommand(
}
// Standing on a warp token whose far side is closer to the goal: step in.
{
const here = cellKey(self.position);
const pair = view.dimWarps.find((w) => cellKey(w.a) === here || cellKey(w.b) === here);
if (pair) {
const dest = cellKey(pair.a) === here ? pair.b : pair.a;
+12 -12
View File
@@ -958,7 +958,7 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
if (ctx.fullyStopped || !ctx.defender.alive) return;
if (isLockedInPlace(ctx.state, ctx.defender.id)) return;
const to = ctx.stack.params?.cell;
if (!to) return; // an old ambush sprang it with no destination: fizzle
if (!to) return; // pre-rev-35 ambushes carry no destination: fizzle
const from = ctx.defender.position;
ctx.defender.position = to;
ctx.events.push({
@@ -2077,7 +2077,7 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
if (ctx.fullyStopped || !ctx.defender.alive) return;
if (isLockedInPlace(ctx.state, ctx.defender.id)) return;
const to = ctx.stack.params?.cell;
if (!to) return; // an old ambush sprang it with no destination: fizzle
if (!to) return; // pre-rev-35 ambushes carry no destination: fizzle
if (walkingDistance(ctx.state, ctx.defender.position, to) > 3) return;
const from = ctx.defender.position;
ctx.defender.position = to;
@@ -2586,9 +2586,8 @@ function waveForce(state: GameState, range: number, dist: number): number {
/** A collapsing waterwall wave from an edge: wash players back `range`. */
function waveFromEdge(state: GameState, events: GameEvent[], cell: Cell, side: Side, range: number, reason = "rushing water"): void {
const away = (s2: Side): Side => (s2 === "N" ? "S" : s2 === "S" ? "N" : s2 === "E" ? "W" : "E");
const pushes: { start: Cell; dir: Side }[] = [
{ start: cell, dir: away(side) },
{ start: cell, dir: opposite(side) },
{ start: neighbor(cell, side), dir: side },
];
for (const { start, dir } of pushes) {
@@ -3734,23 +3733,24 @@ function takeFromHand(p: PlayerState, instanceId: string): CardInstance | null {
// --- Movement ---------------------------------------------------------------
/**
* FEAR: "no player or monster will move to within 3 spaces of you." True
* for every WILLING move walking, warp steps, self-teleports, commanded
* monsters (rules rev 32 widened it beyond walking wizards; forced movement
* such as knockback or a wave is not willing and passes).
*/
/**
* FEAR's measure FAQ: "as a Wizard walks (if he could walk through
* walls), not diagonally." Orthogonal steps, walls ignored and the maze
* wraps, so stepping off one rim continues from the opposite one.
* Exported so the client draws the aura by the same yardstick.
*/
function dreadDistance(board: AssembledBoard, a: Cell, b: Cell): number {
export function dreadDistance(board: AssembledBoard, a: Cell, b: Cell): number {
const dx = Math.abs(a.x - b.x);
const dy = Math.abs(a.y - b.y);
return Math.min(dx, board.width - dx) + Math.min(dy, board.height - dy);
}
/**
* FEAR: "no player or monster will move to within 3 spaces of you." True
* for every WILLING move walking, warp steps, self-teleports, commanded
* monsters (rules rev 32 widened it beyond walking wizards; forced movement
* such as knockback or a wave is not willing and passes).
*/
function fearRepels(state: GameState, moverId: PlayerId | null, from: Cell, to: Cell): boolean {
// The wrap joined the measure at rev 32; older ledgers were recorded
// under the flat diamond and replay so.
@@ -4271,7 +4271,6 @@ function touchesEdge(position: Cell, cell: Cell, side: Side): boolean {
return cellKey(position) === cellKey(cell) || cellKey(position) === cellKey(neighbor(cell, side));
}
/** Arm (or stand down) the WARD trap on your treasures. Your secret. */
/** The owner's answer to a hanging grab: spring the WARD, or let them go. */
function doWardChoice(prev: GameState, play: boolean): CommandResult {
const state = clone(prev);
@@ -4293,6 +4292,7 @@ function doWardChoice(prev: GameState, play: boolean): CommandResult {
return { ok: true, state, events };
}
/** Arm (or stand down) the WARD trap on your treasures. Your secret. */
function doArmWard(prev: GameState, armed: boolean): CommandResult {
// Rev 31 reads the card literally: the Ward is played in the moment of
// the grab, never set ahead. Older games keep their arming and replay so.