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
+34 -54
View File
@@ -12,7 +12,7 @@
import { prefs, savePrefs } from "./prefs.svelte";
import { CREATURE_ART, objectArt, TERRAIN_ART, tokenArt } from "./art";
import { local } from "./local.svelte";
import { allCardDefs, cardDef, isNumberCard, isPermanentDuration, SIDES, stepTarget, cellKey, edgeKey, isMovableObject, sightedCellsFor, stackSightTrace, type GameView, eligibleCellsFor } from "@wizwar/engine";
import { allCardDefs, cardDef, dreadDistance, isNumberCard, isPermanentDuration, SIDES, stepTarget, cellKey, edgeKey, isMovableObject, sightedCellsFor, stackSightTrace, type GameView, eligibleCellsFor } from "@wizwar/engine";
import type { CardInstance, Side } from "@wizwar/engine";
net.connect();
@@ -899,32 +899,10 @@
// Reflecting a SWAP MEET: the reflector picks the trade — or none at all.
let reflectSwapCard = $state<CardInstance | null>(null);
let reflectSwapMine = $state<string | null>(null);
const reflectMyTradables = $derived.by(() => {
if (!view || !reflectSwapCard) return [];
const opts = new Map<string, string>();
for (const c of view.yourHand) {
if (tradableHere(c.cardId)) opts.set(c.cardId, cardDef(c.cardId).name);
}
if (me?.carriedTreasureId) {
const t = view.treasures.find((t) => t.id === me.carriedTreasureId);
opts.set("treasure", `${t?.owner ?? "the"}'s treasure (carried)`);
}
return [...opts].map(([key, label]) => ({ key, label }));
});
const reflectTheirTradables = $derived.by(() => {
if (!view || !reflectSwapCard || !view.stack) return [];
const them = view.players.find((p) => p.id === view.stack!.attackerId);
if (!them) return [];
const opts = new Map<string, string>();
for (const c of them.displayed) {
if (tradableHere(c.cardId)) opts.set(c.cardId, cardDef(c.cardId).name);
}
if (them.carriedTreasureId && (reflectSwapMine === "treasure" || !me?.carriedTreasureId)) {
const t = view.treasures.find((t) => t.id === them.carriedTreasureId);
opts.set("treasure", `${t?.owner ?? "the"}'s treasure (carried)`);
}
return [...opts].map(([key, label]) => ({ key, label }));
});
const reflectMyTradables = $derived(reflectSwapCard ? tradablesOf(null, null) : []);
const reflectTheirTradables = $derived(
reflectSwapCard && view?.stack ? tradablesOf(view.stack.attackerId, reflectSwapMine) : [],
);
function castReflectSwap(pair: string) {
if (!reflectSwapCard) return;
dispatch({
@@ -940,32 +918,38 @@
if (!view) return false;
return view.deckRev >= 26 ? isMovableObject(cardId) : cardDef(cardId).cardType === "object";
}
const myTradables = $derived.by(() => {
/** What one side can put on a trade table: hand (yours) or displayed
* cards (theirs), plus a carried treasure. Their treasure is claimable
* only when yours is going the other way or your arms are free —
* `pairedPick` is what you have already staked. */
function tradablesOf(themId: string | null, pairedPick: string | null): { key: string; label: string }[] {
if (!view) return [];
const opts = new Map<string, string>();
for (const c of view.yourHand) {
if (tradableHere(c.cardId)) opts.set(c.cardId, cardDef(c.cardId).name);
}
if (me?.carriedTreasureId) {
const t = view.treasures.find((t) => t.id === me.carriedTreasureId);
opts.set("treasure", `${t?.owner ?? "the"}'s treasure (carried)`);
const treasureEntry = (treasureId: string) => {
const t = view!.treasures.find((t) => t.id === treasureId);
opts.set("treasure", `${t?.owner ?? "someone"}'s treasure (carried)`);
};
if (themId === null) {
for (const c of view.yourHand) {
if (tradableHere(c.cardId)) opts.set(c.cardId, cardDef(c.cardId).name);
}
if (me?.carriedTreasureId) treasureEntry(me.carriedTreasureId);
} else {
const them = view.players.find((p) => p.id === themId);
if (!them) return [];
for (const c of them.displayed) {
if (tradableHere(c.cardId)) opts.set(c.cardId, cardDef(c.cardId).name);
}
if (them.carriedTreasureId && (pairedPick === "treasure" || !me?.carriedTreasureId)) {
treasureEntry(them.carriedTreasureId);
}
}
return [...opts].map(([key, label]) => ({ key, label }));
});
}
const myTradables = $derived(tradablesOf(null, null));
const theirTradables = $derived.by(() => {
if (!view || !swapMeetTarget) return [];
const them = view.players.find((p) => p.id === swapMeetTarget);
if (!them) return [];
const opts = new Map<string, string>();
for (const c of them.displayed) {
if (tradableHere(c.cardId)) opts.set(c.cardId, cardDef(c.cardId).name);
}
// Their treasure is claimable unless your arms are already full.
if (them.carriedTreasureId && (swapMine === "treasure" || !me?.carriedTreasureId)) {
const t = view.treasures.find((t) => t.id === them.carriedTreasureId);
opts.set("treasure", `${t?.owner ?? "the"}'s treasure (carried)`);
}
return [...opts].map(([key, label]) => ({ key, label }));
if (!swapMeetTarget) return [];
return tradablesOf(swapMeetTarget, swapMine);
});
function castSwapMeet(theirs: string) {
if (!selectedCard || !swapMeetTarget || !swapMine) return;
@@ -1041,11 +1025,7 @@
: [],
);
const carryingTreasure = $derived(me?.carriedTreasureId != null);
const treasureHere = $derived(
view != null && me != null &&
view.treasures.some((t) => t.position && t.position.x === me.position.x &&
t.position.y === me.position.y && !t.carriedBy),
);
const treasureHere = $derived(treasuresHere.length > 0);
/** Squares a selected L.O.S./ADJACENT card can reach; null = no dimming. */
// While an LOS attack sits on the stack, draw the line it traveled — the
// answer to "how can he even see me?" when sight ran through a warp mouth.
@@ -1818,7 +1798,7 @@
{/if}
{#if isYourTurn && view.players.some((p) => p.alive && p.id !== view.you &&
view.sustained.some((e) => e.cardId === "fear" && e.targetId === p.id) && me &&
Math.abs(p.position.x - me.position.x) + Math.abs(p.position.y - me.position.y) <= 3)}
dreadDistance(view.board, p.position, me.position) <= 3)}
<div class="slip urgent">
😱 An unnatural dread grips you — FEAR requires you to move away
this turn if you can, even if your only escape is a card