Credibility pass: the seams get sanded
Scoped to everything since the last pass (3308850). Three blind
reviews, every finding verified before touching anything.
Confirmed and fixed: two identical comment-splitting insertions left
doc comments orphaned from their fields (net and local alike); a
51-line CSS fossil of the pre-extraction inline effects survived in
Board.svelte; the rev-13 miss-roll test asserted tautologies while
its comment claimed a check the code never made — it now proves the
die was consumed, and the skeleton is no longer returned as trollId;
the sprite registry's `as never` silently disabled the completeness
its annotation advertised (now a mapped type, one cast at the
dispatch seam); a dead ternary guarded a union that doesn't exist;
Bolt carried a duplicate .fork rule from a color iteration; fxTtl
contradicted three sprites' real animation lengths; the permanence
sentinel was reinvented as a magic 9000 (the engine now exports
isPermanentDuration); CELL was declared thrice (fx.ts now imports
it); App and Replay ran two divergent fx schedulers (one scheduleFx
now, cancellable — stale flourishes can no longer fire after leaving
a game); TokenArt retried missing files forever; the anti-anti
escape guards merge with the gate asymmetry explained; wall-of-fire's
rev-12 carve-out is marked; overLimit ignored a displayed BRAINSTONE
(bots over-discarded by two); botRemark's header mis-stated its own
branches; deliverGold fired on any drop, not a home-base delivery;
escape and win banter never fired from the steps that carry them.
Rejected: "as a human would" (house voice); FxGallery's dev-harness
framing (trimmed one plea, kept the facts).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4528c66057
commit
d2b40ec6f8
@@ -232,18 +232,22 @@ function thiefOfMine(view: GameView) {
|
||||
return livingEnemies(view).find((p) => carriers.has(p.id)) ?? null;
|
||||
}
|
||||
|
||||
function overLimit(view: GameView): number {
|
||||
return Math.max(0, view.yourHand.length - 7);
|
||||
/** The hand limit as this seat experiences it (a displayed BRAINSTONE
|
||||
* raises it by two — mirrors the engine's handLimit). */
|
||||
function handLimitOf(view: GameView): number {
|
||||
return me(view).displayed.some((c) => c.cardId === "brainstone") ? 9 : 7;
|
||||
}
|
||||
|
||||
function overLimit(view: GameView): number {
|
||||
return Math.max(0, view.yourHand.length - handLimitOf(view));
|
||||
}
|
||||
|
||||
/** End the turn drawing fully — shedding dead weight first if the hand is
|
||||
* too full to take the draw. doEndTurn caps the draw by the room left, so a
|
||||
* clogged hand never refreshes; only cards the brain has no play for are
|
||||
* shed (good counters and attacks are hoarded, as a human would). */
|
||||
function endTurnDrawing(view: GameView, tier: TierTraits): Command {
|
||||
const limit = me(view).displayed.some((c) => c.cardId === "brainstone") ? 9 : 7;
|
||||
const deficit = tier.draw - (limit - view.yourHand.length);
|
||||
const deficit = tier.draw - (handLimitOf(view) - view.yourHand.length);
|
||||
if (tier.sheds && deficit > 0) {
|
||||
const shed = [...view.yourHand]
|
||||
.filter((c) => discardValue(c) <= 3)
|
||||
|
||||
@@ -138,6 +138,10 @@ const WAND_CARD_IDS = ["blaster-wand", "shift-wand", "sticky-wand", "warp-wand"]
|
||||
|
||||
/** Duration meaning "for the rest of the game" ("This card is permanent."). */
|
||||
const PERMANENT_TURNS = 1_000_000_000;
|
||||
/** Whether a sustained duration means "until used/removed", not a count. */
|
||||
export function isPermanentDuration(turns: number): boolean {
|
||||
return turns >= PERMANENT_TURNS / 2;
|
||||
}
|
||||
|
||||
/** Which square contents block line of sight. */
|
||||
export const LOS_BLOCKING_CONTENT: Record<SquareContent["kind"], boolean> = {
|
||||
@@ -4760,11 +4764,11 @@ function doCounteract(
|
||||
if (card.cardId !== "anti-anti") return err("only ANTI-ANTI or ABSORB SPELL can answer a counteraction");
|
||||
if (!targetCounter) return err("no counteraction to nullify");
|
||||
// "Does not work against escape, such as SHRINK, TELEPORT, or INVISIBLE"
|
||||
// (the card face; rules rev 6 — earlier games replay their old chains).
|
||||
if ((state.config.deckRev ?? 1) >= 6 && targetCounter.card.cardId === "teleport") {
|
||||
return err("ANTI-ANTI does not work against escape");
|
||||
}
|
||||
if (targetCounter.card.cardId === "invisible") {
|
||||
// (the card face). Teleport is rev-6-gated — earlier games replay their
|
||||
// old chains; invisible needs no gate, since no earlier engine ever
|
||||
// accepted it as a counteraction for a ledger to contain.
|
||||
if (targetCounter.card.cardId === "invisible" ||
|
||||
((state.config.deckRev ?? 1) >= 6 && targetCounter.card.cardId === "teleport")) {
|
||||
return err("ANTI-ANTI does not work against escape");
|
||||
}
|
||||
takeFromHand(player, instanceId);
|
||||
@@ -4801,6 +4805,8 @@ function doPass(prev: GameState, playerId: PlayerId): CommandResult {
|
||||
stack.counters.some((c) =>
|
||||
!c.nullified &&
|
||||
(TOTAL_STOP_COUNTERS.has(c.card.cardId) ||
|
||||
// Wall-of-fire counters pre-date the total-stop rule; their stored
|
||||
// exchanges bounced, and replay so (rules rev 12).
|
||||
(rev >= 12 && c.card.cardId === "wall-of-fire")) &&
|
||||
(stack.kind === "spell" || c.card.cardId === "teleport"));
|
||||
if (!totalStop) {
|
||||
|
||||
Reference in New Issue
Block a user