Commit Graph
176 Commits
Author SHA1 Message Date
Eric Wagoner 7bd82d7679 Merge branch 'automatons' 2026-08-16 18:34:43 -04:00
Eric WagonerandClaude Fable 5 b9c784a10c Difficulty without stupidity: apprentice, adept, archmage
Tiers degrade resources and repertoire, never judgment — the design
constraint was that no tier may ever look dumb. The APPRENTICE draws
one card a turn instead of two (a poorer wizard, not a worse one),
spends counters only on heavy hits (thrift, not blindness), and
carries a modest spellbook: damage, summons, stones, keys, and
treasure play, with no afflictions, amplifies, ambushes, guarding
tricks, or deja-vu. The ADEPT draws fully and knows everything except
ambushes and amplify. The ARCHMAGE is the full curriculum. Every card
any tier plays, it plays correctly.

The lobby workshop gains a tier picker beside the temperaments
(default adept); the tier persists with the seat, shows in roster and
scoresheet ("⚙ apprentice mystery"), and rides the drive loop.
Measured where it should matter: in berserker combat mirrors the
archmage beats the apprentice two to one, while pure treasure races
stay honest — the handicap lives in the card exchanges a human
actually feels, pinned deterministically in the tournament suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:59:11 -04:00
Eric WagonerandClaude Fable 5 a6ede6ca1e The clockwork reads the whole deck
Every playable card now has a place in the automaton's mind. The
damage table completes: lightning blast, wizardblade, and power drain
scale with numbers (and are skipped without one), disease bites in
the shared square, the dagger's true three points corrected. A new
affliction book casts the miseries — blind, slow, medusa, no-spell,
lock-in-place, walking dead, slow death, idiot, thought steal,
go-away — with a number for the duration, and WEAKNESS is saved for
whoever carries stolen gold. AMPLIFY doubles the heavy spells.

The counteraction ladder runs the full rack: REVERSE eats the big
blasts, ABSORB SPELL steals the good ones, FULL SHIELD, FULL
REFLECTION, REMOVE CURSE refuses afflictions at the door, REFLECTION
halves, ABSORB soaks the mid-range, BLUNT, EMPATHY out of berserker
spite, teleport escapes, shieldstone numbers.

Housekeeping: every stone hits the table the turn it is drawn, GIFT
FROM ABOVE is cashed instantly, curses get scrubbed with REMOVE
CURSE, LIFESAVER goes up at three players, a SAFE or GLUE guards
floor gold with enemies near, and DEJA-VU pulls the best attack back
from the discard when the hand runs dry of violence.

Table talk verified live ("ACQUISITION COMPLETE." mid-probe) — the
silence was timing, not plumbing — and the remark odds rise to half.
Three-way tournaments tell true stories now: the hunter wins on
gold, the worrier survives to win by attrition, and the berserker
learns that aggression in a three-way gets you buried first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:41:12 -04:00
Eric WagonerandClaude Fable 5 3eda686e29 The full clockwork curriculum: keys, vengeance, spellcraft, and voice
Every play-strength item, in one education. DOORS: the pathfinder
walks through doors it can open — already-opened ones freely, locked
ones when holding Master Key or Pick Lock, casting the key at the
door on the path before stepping through (and a loop where the
clockwork re-keyed an already-open door forever is fixed: opened
doors read as open). TREASURE DEFENSE: a wizard carrying the
automaton's gold becomes the priority — chased over all objectives,
shaken down with DROP OBJECT when held, and attacked first. WIDER
SPELLCRAFT: blaster wands charged by number and fired; teleport as
escape when wounded and hunted, and as a counteraction clear of big
incoming spells; SPEED cast on sight; WARD armed to guard the gold;
shieldstone displayed so spare numbers soak small hits; ANTI-ANTI
pressed against counters (never against escapes); the berserker grows
BIG with an enemy near, the worrier fades invisible or slams a
CREATE WALL in its pursuer's face; ambushes armed from held
interrupts — the worrier trapping its doorstep, the others trapping
the treasure. Tournament suite green across all temperaments.

And the clockwork speaks: sparing, temperament-voiced table talk on
its own deeds — "ACQUISITION COMPLETE.", "SCHEDULED DEMISE:
DELIVERED.", "good wall. safe wall." — through the same chat ledger
as everyone else. The tally gains "games fought against the
clockwork", and automatons no longer pollute the count of wizards
seated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:10:47 -04:00
Eric WagonerandClaude Fable 5 2c3e622ce5 Rev 9: REDIRECTION connects the exits you choose
Playtesting read the card right and the engine had it wrong. "Placing
the A tokens on one set of exits & the B tokens on the other" means
the two exits you mark BECOME a pair — and their former partners pair
with each other — not that the two exits trade destinations. Under
rev 9, click the exit and then the exit you want it to connect TO
(the hint now says so); already-connected exits are refused. Earlier
games keep the swap their logs were validated against. Test pins the
full four-way rewiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:40:35 -04:00
Eric WagonerandClaude Fable 5 ca16faac73 Rev 9: REDIRECTION connects the exits you choose
Playtesting read the card right and the engine had it wrong. "Placing
the A tokens on one set of exits & the B tokens on the other" means
the two exits you mark BECOME a pair — and their former partners pair
with each other — not that the two exits trade destinations. Under
rev 9, click the exit and then the exit you want it to connect TO
(the hint now says so); already-connected exits are refused. Earlier
games keep the swap their logs were validated against. Test pins the
full four-way rewiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:40:22 -04:00
Eric WagonerandClaude Fable 5 1aafb9e837 The mystery machine keeps its mood to itself
"Random" read as random ACTIONS, and a revealed random pick is barely
a mystery anyway. The fourth workshop button is now "mystery": the
server rolls the temperament and keeps it — the join line records it
for replay, the drive loop plays it, but the room tells the table
only "mystery". Roster and scoresheet show ⚙ mystery; the machine's
behavior is the only tell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:36:21 -04:00
Eric WagonerandClaude Fable 5 af75a024ff A random pull from the workshop, and temperaments on the scoresheet
The lobby's automaton row gains "random" (the server already rolled
when no style was named — now the choice is offered). And each
automaton's temperament shows beside its name on the in-game
scoresheet in the handwritten face, so you always know whether the
machine across the maze wants your gold or your blood.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:32:06 -04:00
Eric WagonerandClaude Fable 5 5a407a1e4c The clockwork learns numbers, summons, creatures — and temperament
The automaton now plays number cards where they matter: attached to
value-scaled attacks (waterbolt, powerthrust) choosing the biggest
for damage, and played for movement when the goal is just out of
stride. It raises its monsters when no wizard is in spell range and
commands them every turn — creatures march at enemies by the same
hazard-shy BFS and maul whoever shares their square. Targets are
picked by lowest life.

And it has moods. The HUNTER plays the classic game: gold first,
violence when convenient. The BERSERKER hunts wizards over treasure
and finishes games by last-wizard-standing. The WORRIER paths around
enemies, counters at a lower threshold, shields against chaos, and
never brawls. Hosts pick the temperament when seating one (or the
workshop assigns at random); it persists with the seat, shows in the
roster, and the tournament harness proves every pairing finishes.
Across ten seeds of berserker versus hunter: six treasure wins to
four kills — the moods play genuinely different games.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:29:36 -04:00
Eric WagonerandClaude Fable 5 d9922da4df Number cards ride along on edge and cell casts
The edge-click dispatch attached the tapped number card only for
wall attacks, so the warp wand's first use could never receive its
charges ("a wand's first use needs a number card" no matter what),
and wall of fire silently burned one turn instead of its NUMBER.
Cell-target casts had the same gap for duration terrain. The attached
number now rides every edge and cell cast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:21:54 -04:00
Eric WagonerandClaude Fable 5 4ef3ee5641 Drop playwright scratch output from tracking
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:21:46 -04:00
Eric WagonerandClaude Fable 5 01101ca24a Ignore playwright scratch output
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:21:28 -04:00
Eric WagonerandClaude Fable 5 75313154ad Number cards ride along on edge and cell casts
The edge-click dispatch attached the tapped number card only for
wall attacks, so the warp wand's first use could never receive its
charges ("a wand's first use needs a number card" no matter what),
and wall of fire silently burned one turn instead of its NUMBER.
Cell-target casts had the same gap for duration terrain. The attached
number now rides every edge and cell cast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:21:12 -04:00
Eric WagonerandClaude Fable 5 3842307bf0 The clockwork plays at a watchable pace
One command per beat — 1.5 seconds, the replay's 1x cadence — each
broadcast with its board state as it lands, starting a beat after
the human's own action settles. The pump is per-room, re-entrancy
guarded, and stops the moment the maze wants a human (or the game
ends). Watching an automaton stalk your treasure across the maze is
now a spectacle instead of a teleport.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:17:28 -04:00
Eric WagonerandClaude Fable 5 ab52628d2f The automatons awaken (branch only — not for the public droplet yet)
Phase 4 begins. The automaton is a pure function in the engine —
automatonCommand(view) — playing from its own redacted GameView, the
same information a human seat receives: hidden hands stay hidden from
the clockwork. It ranks simple damage spells, counters what hurts
(full shield at 3+, reflection at 4+, blunt at 2+), discards its
worst cards by a value order, BFS-pathfinds to enemy treasures and
home again, refuses to path through hazards, brawls when there is
nothing to steal, and always has a safe fallback; the server's drive
loop steps any bot-held seat through the same runCommand path as
humans, so bot commands log, persist, replay, and broadcast like
anyone's.

Hosts seat them pre-start with "⚙ seat an automaton" (Automaton,
Automaton II, ... V); bot seats persist as tokenless join lines and
restore on boot. Proven three ways: bot-vs-bot engine games conclude
across seeds in 8-14 rounds (~100 commands — human-scale), a full
four-automaton table finishes, and a live websocket game of human
vs. automaton ended with the clockwork carrying two treasures home
through a do-nothing opponent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:11:54 -04:00
Eric WagonerandClaude Fable 5 5802e1df05 Table talk keeps its place in the chronicle
Live chat interleaved correctly, but a reload sank every past message
to the bottom: the rejoin sent the whole game history first and the
chat history after. Talk is now a tableTalk event in the room's event
stream at the moment it was said — the room file already preserved
the interleaving, since chat lines append between command lines — so
rejoining players read the banter between the battle lines where it
happened. The separate chatHistory message retires; unread counting
rides the event stream instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:00:51 -04:00
Eric WagonerandClaude Fable 5 ef661c79ad The die rolls in the open
Every player-facing D4 roll now lands in the chronicle as pips with
its purpose: "🎲 alice rolls a 3 — aiming at the unseen — only a 1
finds them." A shared rollD4 helper threads the generic dieRolled
event through blind staggers and swings, pit leaps, ooze and pit
struggles, and the invisible/shrink miss rolls — the table sees the
die land, exactly as it would in person. Rolls whose numbers already
showed (the troll's swing, the misdirection direction, the opening
roll-off) keep their lines.

And the physical die's other job — "any 50-50 call" — gets its
button: 🎲 beside the say-box in online games (server-rolled,
published through the chat ledger so it persists, replays, and counts
toward unread badges) and under the chronicle in hotseat, rolled on
the device. Chronicle only; no game state touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:57:28 -04:00
Eric WagonerandClaude Fable 5 d6d34887e3 The whole punch list: rev 8, general FAQ, and four promoted UX items
Rules rev 8 closes the last two fidelity threads. A SPEED bonus turn
burns a turn of duration spells on the hastened wizard — recipient-
counted, per the FAQ's most obscure ruling — with self-cast durations
already burning through the normal turn-start sweep (expiry extracted
into expireEffect so both paths share the cleanup). And nothing can
be created on a DIMENSIONAL WARP token, as the card face always said.

The general-topic FAQ sections (Combat, Line of Sight, Monsters,
Treasures, and five more) join the rules tab verbatim under a double-
ruled divider, completing the FAQ's absorption: card rulings on the
cards, general rulings in the rules.

The four promoted UX items: cell-target spells now dim ineligible
squares (creations mirror emptySquareTarget from the viewer's
knowledge, summons want clear sighted squares, teleport BFSes its
four spaces exactly as the engine does, stone-to-water lights only
stone, dispel lights only creations); hotseat games get the full
replay reel, each step seen from its actor's own seat; a "stop
announcing attacks on this device" link in the fanfare modal (undo
lives beside the notification toggle in the lobby); the corner
inspector now serves only the selected casting card, with every
examine-a-card peek unified on the centered treatment; and the dev
socket URL keys on vite's port instead of hijacking every localhost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:45:59 -04:00
Eric WagonerandClaude Fable 5 f657144127 Discard-pile cards enlarge above the pile, like the library's
Tapping a discarded card summoned the corner inspector beneath the
pile's own overlay — same layering trap the card library had. The
pile now enlarges its card centered above itself, FAQ seal included,
dismissed with a tap anywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:37:18 -04:00
Eric WagonerandClaude Fable 5 5375941ed3 Every card wears its FAQ seal; library cards enlarge above the booklet
The FAQ affordance moves onto the card itself: a small dark seal in
the corner of any card with official rulings, everywhere cards render
— hand, inspector, peek, final reveal, discard pile, card library —
opening the rulings modal on tap (the link, never the text: card
faces stay verbatim card text). The separate inspector buttons and
library chips retire in its favor.

And clicking a library card mid-game no longer summons the App-level
inspector beneath the help modal, greyed out and useless — the
library enlarges its card in its own overlay, centered above the
booklet, seal included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:34:43 -04:00
Eric WagonerandClaude Fable 5 4833a229e1 The table's reading prevails: open sides carry sight
An hour of rules archaeology, summarized: DIMENSIONAL WARP denies
LOS explicitly (its tokens stay sightless); the AUTO WARP describes
how to trace sight across a join; and on the lettered wraparounds
the texts are silent — no grant, no denial. The FAQ's Permawarp
rulings even have area effects counting distance through warps. Where
the texts are silent, the edition's own table breaks the tie, and the
table says the open sides see. Board openings carry sight in every
revision (a permissive change: every stored game's log replays
clean); the brief rev-7 restriction and its legacy flag are gone,
along with the sophistry that justified them. The adjacency geometry
stands. The friend's sticky-wand shot through the C opening was legal
after all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:29:09 -04:00
Eric WagonerandClaude Fable 5 493f3bc567 Rev 7: sight passes only through the aisle warps
The cards settle what the morning's feature got wrong. DIMENSIONAL
WARP: "There is no L.O.S. through the warp." The AUTO WARP: "for all
purposes, treat the connected board edges as though they are
adjacent" — an explicit grant, for edges that physically meet at a
corner. The lettered wraparounds get no grant at all: their rule is
about walking. So the pattern is that warps carry movement, not
sight, unless the text says otherwise — and only the aisle corners
say otherwise.

Warps now carry an aisle flag (the 3-player AUTO WARP and the
5-player corner arcs); hasWarpLineOfSight honors only those, with
the adjacency geometry — diagonals through the corner mouth —
unchanged from this morning where it rightfully applies. Games begun
before rev 7 keep the wider sight their logged commands were
validated against, via a legacy flag boardView stamps from the
game's rules revision. Tests pin all three states: lettered mouths
see nothing, aisle mouths see the corner fan, legacy games see
everything they used to.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:25:00 -04:00
Eric WagonerandClaude Fable 5 8af8962330 Warp sight honors "as though they were adjacent" — diagonals included
Playtesting caught the gap with a sticky wand: a target visible
through the opening but one square off the corridor axis was refused.
The colinear-only model was stricter than the rulebook, whose actual
instruction is to treat the connected edges as adjacent boards with a
straight center-to-center line — and a straight line through a
one-cell opening may run diagonally, exactly as through a doorway.

hasWarpLineOfSight now abuts the far side virtually at the mouth
(rotating when the pairing turns a corner, as aisle warps do), finds
where the center-to-center line crosses the rim, requires that
crossing to fall strictly within the open mouth (corner-grazing stays
blocked — the strict reading), and checks the two legs against real
walls on each side of the seam. hasLineOfSight's core became a shared
segmentClear to serve both legs. The colinear corridor cases remain
as the special case they always were; a new test pins that off-axis
squares are visible through mouths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:20:46 -04:00
Eric WagonerandClaude Fable 5 53edf9734e Uncharged wands say what they need
"Opportunity Fire isn't letting me use a wand" — the window and the
wand both worked (verified headless); what refused him was the wand's
own first-use rule, which players from wandless editions have never
met: charges are set by a NUMBER card on first use. Selecting an
uncharged wand now says exactly that in the hint strip, before the
engine has to say no.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:14:15 -04:00
Eric WagonerandClaude Fable 5 cef383f352 Decorative board shapes stop eating clicks
The home-base star polygon had no pointer-events rule, so clicking
the middle of a home square hit the star instead of the floor — you
had to aim around its points to move or target there. The star and
every other decorative vector (terrain fallbacks, treasure circles,
dim warp rings) are now click-transparent; the floor rect beneath
owns every tap, as the token-art images always did.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:07:23 -04:00
Eric WagonerandClaude Fable 5 bc0c69de11 The designer's rulings, one tap from the card
Tom Jolly's 2002 Rules FAQ settled three table disputes this week
from a text file only I could read. Now it lives in the game: 95
cards gained their FAQ sections (extracted verbatim from the official
document, alias-matched — Slime, Mistbody, Buck and friends resolved
to their proper cards), joining the handful already present for 102
cards with rulings in all. The card faces stay pure card text; an
"Official FAQ" button appears under any enlarged card that has
rulings — the hand inspector, the peek overlay, and as an FAQ chip in
the card library — opening a booklet modal with the rulings and their
provenance: "the cards overrule the rules, and the designer overrules
the table."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:05:27 -04:00
Eric WagonerandClaude Fable 5 6de4254aad Card text scales to fit — every word on the face
Long card texts were clamped at nine lines and cut off mid-rule,
which is how the Speed argument started: the deciding sentence was
in the hidden part. The type now sizes itself to the text length in
six steps (0.6rem down to 0.375rem for the 550-char Firefly Stick),
and the line clamp is gone. Verified against Fear, the longest card
in play at 482 characters: every word on the face.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 14:58:14 -04:00
Eric WagonerandClaude Fable 5 de446c2ad5 Counteraction answers follow the FAQ: absorb reflections, not shields
"It's not letting me cast Absorb Spell on your Full Shield" — and
Jolly's FAQ says exactly why: "ABSORB SPELL will work on FULL
REFLECTION or REFLECTION, but not FULL SHIELD, as that is not used
against you, but cast upon the player using it." The refusal stands,
now with that reason in the error — and the neighboring case the
engine wrongly refused is fixed: an attacker may answer a reflection
with ABSORB SPELL, nullifying it and taking the reflection card into
their hand, with the original spell then landing unturned.

And the card face corrects my own hours-old work: ANTI-ANTI "does not
work against escape, such as SHRINK, TELEPORT, or INVISIBLE" — so
under rules rev 6 it can no longer pin a teleport escape. Earlier
games keep their stored chains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 14:51:14 -04:00
Eric WagonerandClaude Fable 5 31a727d02c Refusals persist in the chronicle, not just the fading toast
"When I cast Speed, it doesn't seem to do anything" — the engine had
refused correctly (actions end when you pick up an object) and said
so in a toast, but a toast fades in five seconds and leaves no trace.
Refusals now also land in the chronicle as "— reason —" lines, the
way hotseat games always recorded them, so the answer to "why did
nothing happen" survives long enough to be read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 14:37:21 -04:00
Eric WagonerandClaude Fable 5 df52e9b53a Table talk: chat woven into the chronicle
A say-box under the chronicle sends table talk to the room; messages
land in the event log itself, styled as parchment asides — banter
between the battle lines, the way it happens at a real table. Talk is
seat-authenticated, stripped and capped at 300 chars, rate-limited by
the existing bucket, persisted as chat lines in the room file (the
game replay ignores them; the room keeps its last 500), and replayed
to anyone joining. Async games get unread badges in the lobby ledger
(💬3), cleared by watching the table, and catch-up replays speak the
lines at the step where they were said. Public to the whole room, no
whispers — and no chat in hotseat, where the table talks for itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 14:24:22 -04:00
Eric WagonerandClaude Fable 5 ed9585aa59 Five fidelity gaps closed: teleport escapes, slime traps, the Big Man
moves like a giant, and the boards are diagram-verified

TELEPORT as a counteraction (official FAQ: "the attack has no chance
of hitting you"): the defender names an escape square within four
spaces, the escape resolves before anything lands, and an ANTI-ANTI
pins their boots to the floor. Additive — no revision gate needed.

FILL SQUARE WITH SLIME holds spells now: an attack cast at the slime
sticks in the gel (leaving the discard pile), springs once at whoever
is inside or next enters, and counteractions against the trapped
blast cannot touch its caster — reflections vanish into the ooze. A
five-point waterbolt washes the slime and its cargo away, and the
waterwall waves clear slime from their path.

BIG MAN, under rules rev 5, finally moves like the card says: he
pushes players and monsters down the corridor ahead of him (stuck or
unpushable occupants block his advance), steps over a pit, tacks, or
killer ooze for two movement points without ever entering the square
(click two cells beyond the hazard), and monsters may not enter his
square. All gated so stored games replay under their own rules.

The boards were never wrong — the rulebook's Set-Up Diagram photo
confirms every pairing the code already had: 2p crossed, 3p stair
with the Aisle Warp arc, 4p/6p straight-across, 5p plus with all four
corner arcs. The stale TODOs are gone, replaced by tests pinning each
letter pair, and relocation's "only opposite board edges connect"
(which discards the aisle warp) is pinned too. Wall of Fire vs
Waterbolt turned out to be implemented and tested all along — its
TODO comment was the only thing wrong.

Also: hotseat saves now request durable storage (iOS evicts
unprotected origins under pressure).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 14:17:37 -04:00
Eric WagonerandClaude Fable 5 28bc91c3ac Room codes also dodge orphaned files on disk
makeRoomCode checked only the in-memory map, which covers every live
and restored room — but a file that failed to restore stays on disk
with no map entry, and a new room taking its code would append a
second game into the orphaned file, corrupting both. The generator
now rerolls on disk presence too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 14:02:04 -04:00
Eric WagonerandClaude Fable 5 bed29c4992 Cache headers end the stale-bundle era; finished games rest their buttons
The static server sent no Cache-Control at all, so browsers
heuristically cached index.html — and a stale index.html pins its
user to last deploy's hashed assets no matter how often they reload.
That is how "watch the whole game" could show someone an app without
the feature minutes after it shipped. Hashed assets now cache forever
(immutable); everything else revalidates.

And per playtesting: a finished game offers no actions — isYourTurn
now requires the playing phase, which retires the pick-up/drop/punch/
discard/end-turn row the moment the trophy drops.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:57:10 -04:00
Eric WagonerandClaude Fable 5 cdcd1de71a Watch the whole game from the deal
The seed + command log has been the whole game all along; now it can
be watched. Finished games offer "⟲ Watch the whole game" beside the
face-up hands: the server replays every command from seq 0 through
the same redacted catch-up pipeline (the 200-step window remains a
live-game courtesy; full replays wait for the game to finish), and
the reel plays it titled "The whole tale, from the deal" with new
1×/2×/4× speed controls for the long tellings. Each viewer rewatches
from their own seat — hidden information stays hidden in the retelling
exactly as it was at the table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:38:24 -04:00
Eric WagonerandClaude Fable 5 37c38d5c1a The tally says what "longest game" counts
142 "moves" was really 142 logged commands — steps, spells, passes,
end-turns alike. The line now says so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:35:59 -04:00
Eric WagonerandClaude Fable 5 f00ef75fbd The chronicle names creatures, not their ids
"The creature-1 takes 2 damage" — the creature events carried only
internal ids, so the log printed them. creatureAttacked, Touched, and
Damaged now carry the creature's kind, and the lines read like a
table: "The Wraith strikes!", "The Troll swings (rolled 3)!", "The
Skeleton takes 2 damage."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:30:02 -04:00
Eric WagonerandClaude Fable 5 0ebae03b1c The discard pile lies face-up, and the chronicle names what falls
At the table the discards sit face-up for anyone to leaf through. The
view now carries the pile (deck order stays secret), the "discards N"
count in the rail became a link, and it opens a booklet-styled reader:
every cast-away card newest first, tap any to enlarge. And the
chronicle stops mumbling "discards 1 card(s)" — it names names, so a
wraith's theft reads "bob discards Fireball," exactly what a table
would see as the card hits the pile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:28:55 -04:00
Eric WagonerandClaude Fable 5 d1eace9530 Rules rev 4: a creature's blow can be counteracted
The WRAITH card assumes the window exists — "REFLECTIONs used on the
wraith's touch will damage the wraith" — but creature damage applied
instantly, so BLUNT had nothing to catch, as playtesting found. Under
revision 4 every creature blow against a wizard opens the same
counteraction stack a spell does: the wraith's entry touch, the
democratic monster's claw, and commanded troll/skeleton/shadow
attacks. The blow's damage rides the stack; BLUNT halves it (round
up); reflections work by name against creatures — half back for
REFLECTION, the whole blow for FULL REFLECTION — landing on the
creature, not its controller; FULL SHIELD correctly does nothing
("does not stop any physical attack"); the wraith's card theft is a
secondary effect that lands only if damage does. No aim-miss rolls
against invisible or shrunk defenders — the creature is already in
the square. The attack fanfare shows the creature's own card.

Earlier revisions keep the instant touch so every stored game — and
the live one that found this — replays unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:25:45 -04:00
Eric WagonerandClaude Fable 5 3f3e6d9853 The table survives synthetic spell ids — fixes the blank screen
The Sticky Wand attaches its webs as a sustained effect with the
engine-internal id "sticky-web", which is not a printed card — and
cardDef throws on unknown ids. The scoresheet's new spell chips and
the chronicle's settles-over/wears-off lines both called it on
sustained ids, so the first webbing killed every render: mid-game the
actions bar died, and after a reload the whole table came up blank.
A spellName lookup now names synthetic effects properly ("Sticky
Wand webs") and falls back to the raw id rather than ever throwing,
and the chip peek only opens ids the card library can actually show.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:11:06 -04:00
Eric WagonerandClaude Fable 5 45803a2664 Live interrupts announce themselves
Interrupt and Opportunity Fire were fully playable out of turn — tap
the card while another wizard acts — but nothing said so, and cards
are otherwise inert out of turn, so playtesters never tried. Now,
whenever the moment is live (someone else's turn, no attack or chaos
pending, past round one, not hotseat), those cards wear a pulsing
golden glow in your hand and the rail says to tap one. The glow
respects prefers-reduced-motion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:07:22 -04:00
Eric WagonerandClaude Fable 5 b210cffa76 Zero-damage attacks stop lying about being stopped
DROP OBJECT resolved correctly — the treasure fell — but the
chronicle announced "The attack is completely stopped," because the
attackResolved event branded any attack that dealt zero damage as
stopped. Utility attacks deal zero by design; only an attack that was
TRYING to wound counts as stopped when nothing lands. Tests pin both
sides: uncountered DROP OBJECT reports unstopped and drops the
treasure; FULL SHIELD reports stopped and the treasure stays carried.

Not deployed — a live game is in progress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:58:16 -04:00
Eric WagonerandClaude Fable 5 7e3e00bf97 Rules rev 3: Ward is a choice, Chaos honors the shield
WARD's text says "you may play at that time" — under rules revision 3
the choice is made by setting the trap: an armWard command toggles it
on your own turn (your secret; a private wardSet event and a rail
note), and the spring fires only while set, consuming the card and
the arming together. Async games keep their agency without an
interrupt window, exactly as the ambush system solved this before.

CHAOS gains its printed interactions: "FULL SHIELD removes a player
from participation" — a defender's shield sits them out instead of
stopping the spell, and after the counter chain every bystander gets
a shield window in seat order (chaosPending; queue head owes a
response in lobby summaries, the rail says whose hand hangs in the
balance) before the pile forms among the unshielded. "REFLECTIONS
have no effect" — both reflections are refused as counters. ABSORB
SPELL still eats the whole thing through the existing absorb path.

Both changes are frozen behind deckRev 3 so every stored game — and
the live one — replays byte-for-byte under its own rules. Five new
tests cover armed/unarmed wards, shielded defenders and bystanders,
the reflection refusal, and the legacy auto-ward.

Not deployed — a live game is in progress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:55:58 -04:00
Eric WagonerandClaude Fable 5 41c384274d Walls and doors fall to sustained assault
"It is possible, though time-consuming, to punch a wall down. A wall
takes 20 points of damage to destroy; a door takes 15. Any attack
against an inanimate object counts as your one attack for the turn."
Damage accumulates per edge in wallDamage (remapped through sector
rotations, public in the view), fed two ways: a punchWall command for
the bare-fisted (1 point, from a square touching the edge) and attack
spells cast at an edge target — LOS to the wall for L.O.S. cards,
touching it for same-square cards, amplify and power-attack honored,
wand charges spent, no counteractions since stonework plays none.
Thrown daggers and rocks clatter to the floor at the foot of the
wall. At the threshold the edge opens through the same override path
destroy-wall uses.

On the table: damaged walls wear spreading cracks, an attack card's
hint offers "or a wall line to batter it" with the edge layer live,
and a "Punch a wall…" stamp arms a click-the-wall mode. The chronicle
counts the blows: "alice batters the wall with bare fists — 3/20."

Not deployed — a live game is in progress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:48:49 -04:00
Eric WagonerandClaude Fable 5 64454ffc3e Sight travels through the wraparound openings
"If casting a spell, or checking line of sight through the AUTO WARP,
treat it as a straight line, and the two connected boards as though
they were adjacent" — and the lettered openings reconnect edges the
same way. hasWarpLineOfSight models it: the line must run straight
down the corridor, out one mouth, and in through the paired mouth,
with both corridor legs clear and neither mouth filled solid. The new
sightBetween (direct or through-a-warp) is now the game's LOS check
everywhere — spells, ambush triggers, Around The Corner, the
visionstone sweep, and the targeting dim on the client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:43:57 -04:00
Eric WagonerandClaude Fable 5 fce3adc340 "Buy the edition currently in print" now takes you there
The About page's closing plea links straight to Steve Jackson Games'
Wiz-War page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:11:03 -04:00
Eric WagonerandClaude Fable 5 abe635674f The About page tells you where to send the ravens
A "Send word" section with the keeper's addresses: email, Bluesky,
Mastodon, and the home roost at kestrelsnest.social — for feedback,
bug reports, and faint praise alike.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:10:16 -04:00
Eric WagonerandClaude Fable 5 efec82e88d Two-player decks shed LIFESAVER, as the card itself instructs
"Not applicable in a 2-player game." — the card face. New games deal
from deck revision 2, which removes it when exactly two wizards sit
down; three or more keep it. The revision travels in GameConfig and
the persisted start line, and stored games without one replay against
the original build — changing an existing game's deck composition
would scramble its deterministic replay. Test pins all three cases:
removed at two, present at three, present in legacy two-player games.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 12:07:28 -04:00
Eric WagonerandClaude Fable 5 26c4b6efdf Link previews point at the real domain
The Hover A record resolves, so og:image and twitter:image move from
the sslip.io fallback to wizwar.kestrelsnest.social.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 11:57:10 -04:00
Eric WagonerandClaude Fable 5 695307daa8 Credibility pass: one voice, no scars
A three-reviewer sweep for tells of piecemeal machine generation,
every finding verified against the code before touching it. No
behavior changes; the full suite passes unchanged (plus two
strengthened pins).

Engine: removed four void-silenced fossils (a parseEdgeKey call
voided where it stood, stoneEffect's ignored cardId parameter, the
actualTarget remnant in doCast, a voided loop variable in shadow
upkeep); fixed the initialize-then-overwrite narration in
spawnCreature; replaced a filter(() => false) no-op; waterwall now
rides waveFromEdge instead of carrying its own verbatim copy (and the
single-caller washBack wrapper went with it); blind wall-bumps and
LOS blockers each collapsed to one implementation; the wand-id list
and the "permanent" duration sentinel became named constants; the
ambush number local no longer shadows the imported numberValue
function; assorted reviewer-aimed phrasings rewritten as the
constraints they guard.

Server/deploy: the protocol header now documents all eleven message
types; dropped an eslint pragma with no eslint, a test script with no
tests, and an rsync exclude anchored at a path that never existed
(the real data/ dir now excluded); the Caddy vhost has one source of
truth; stale "pending DNS" note removed — the record resolves.

Web: ~90 lines of CSS swallowed verbatim into a mobile media query
deduplicated; the reduced-motion guard on the board now actually
stops the marked-cell pulse; one shared color module replaces two
drifted palettes; an orphaned doc comment rejoined its function.

Tests: the ten-times-pasted helper block became test/helpers.ts;
wave-numbered files renamed for the behaviors they pin; deliberation
comments and void-ed corpses of unwritten assertions deleted; silent
seed-dependent early-returns now fail loudly; one assertion that
compared a value to itself now pins the home-translation it meant to;
the stored-log single-number command form gained the explicit
compatibility test it deserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 11:56:55 -04:00
Eric WagonerandClaude Fable 5 f62fcf2510 Harden the hotseat tally endpoint
Security review of the accumulator commit found three holes, all in
the unauthenticated hotseat ping. Worst: Number(undefined) is NaN,
and NaN survives Math.min/max — one malformed report would have
poisoned commandsPlayed and friends permanently (NaN serializes to
null). All numeric fields now pass through a NaN-proof clamp with a
fallback. The dedupe ledger caps at 50k hotseat entries so spammed
random ids cannot grow stats.json without bound, and each connection
may deliver at most 20 reports — a real device finishes a handful of
games; a firehose is abuse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 11:41:20 -04:00