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:
co-authored by
Claude Fable 5
parent
3445d12206
commit
6adcbe23b5
+34
-54
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user