256 Commits
Author SHA1 Message Date
Eric WagonerandClaude Fable 5 f0a264147f Security hardening for public playtesting
The server code learns to distrust strangers: a 64KB WebSocket payload
cap (the ws default is 100MB — an easy OOM on a 1GB droplet), a
per-connection token-bucket rate limit, caps on concurrent sockets,
total rooms, rooms per connection, pending transfer codes, and seats
per myGames query. Player names are stripped of control characters
and bounded at 24 chars, room codes at 8, serialized commands at
16KB before they touch the append-only log. Catch-up replays — a
full game rebuild per request — get a 3-second cooldown. Unexpected
exceptions now log server-side and send strangers a bare "internal
error" instead of the exception text.

One real bug found by the sweep: myGames compared the client's raw
seat token against the stored hash, so the lobby ledger silently
matched nothing since tokens were hashed at rest — and the comparison
wasn't timing-safe either. It now goes through the same timingSafeEqual
path as every other seat check, via a new exported seatTokenValid.

The droplet tightens too: the game server binds loopback (HOST env)
so port 8787 no longer answers the internet — it was reachable
directly, plaintext, bypassing Caddy — and ufw now allows only ssh,
80, and 443. The systemd unit gains a sandbox (ProtectSystem=strict,
ProtectHome, NoNewPrivileges, PrivateTmp, MemoryMax=700M so a runaway
process is killed and restarted before it takes the box down) and
execs tsx directly instead of through npx. Caddy adds HSTS, nosniff,
frame-denial, and no-referrer headers; setup-droplet.sh records all
of it for future rebuilds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 10:08:23 -04:00
Eric WagonerandClaude Fable 5 789d927122 Daggers, rocks, blades, and wands can be set down too
The rulebook's movable-object list is wider than the stones: "magic
stones, treasure chests, the dagger, the large rock, and the
wizardblade" — and the expansion expects wands to change hands, since
"its remaining charges go with it." A new isMovableObject helper in
the engine names that full set, doDropObject honors it, and the hint
bar's "Drop it here" button follows suit. Charges are keyed by card
instance, so a dropped wand carries them to whoever picks it up —
covered by new tests, along with the refusal to drop spell cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 09:59:09 -04:00
Eric WagonerandClaude Fable 5 c04ed1e0ee Objects can be set down and picked back up
The engine always knew how — dropObject and pickUpObject were in the
command set from the start — but the table never offered a hand for
it. Now selecting a magic stone (or any object card) on your turn
shows a "Drop it here" button in the hint bar, and standing on a
square with objects lying on it offers a "Pick up <name>" stamp per
object, disabled once your actions have ended (picking anything up
ends them, per the rulebook). The chronicle narrates both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 09:55:04 -04:00
Eric WagonerandClaude Fable 5 7a231cbe1b About: the story of the game, told on the box lid
A handwritten "about this game — a labor of love" link on the box lid
opens a new About tab in the booklet: what Wiz-War is, that Tom Jolly
created it in 1983, that this is a faithful reproduction of the
out-of-print Chessex sixth edition (with its one expansion) that Eric
and his friends played for countless hours in the early nineties —
rebuilt from that very copy, card by photographed card, wall by
verified wall, with the original hand-inked token art. It names Steve
Jackson Games as the current rightsholder, states plainly that this
is an unofficial, non-commercial fan work made with love and
admiration, and closes by pointing anyone the game delights toward
the edition currently in print and a real table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 09:43:09 -04:00
Eric WagonerandClaude Fable 5 0b6ac25bc3 Prepare wizwar.kestrelsnest.social: Caddy vhost and DNS notes
Caddy on the droplet now answers for both the sslip.io hostname and
wizwar.kestrelsnest.social, fetching the latter's certificate
automatically the moment DNS resolves. The domain's authoritative
nameservers are Hover's, so the required A record must be added there;
a matching record was also placed in the (currently inert)
DigitalOcean zone for future-proofing. README updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 09:38:04 -04:00
Eric WagonerandClaude Fable 5 45666bca16 Ambushes: the async-native form of Interrupt and Opportunity Fire
Eric's diagnosis: "in the moment" interruption cards are worthless in
correspondence play — there is no moment. The answer was already in
the game: WARD is a contingency card, and ambushes generalize it. On
your turn, playing Interrupt or Opportunity Fire now arms an ambush:
commit it with an attack from your hand (plus an optional number
card) and a trigger — an opponent entering your line of sight, coming
beside you, or grabbing a treasure — and it springs automatically
when the condition occurs, whether you are watching or asleep. The
sprung attack opens the normal counteraction stack, so the victim
gets their defense (asynchronously, like any attack). Committed cards
leave your hand until the trap springs or you disarm it; ambushes are
invisible to everyone but their owner (view-level redaction), die
with their owner, stay armed if the shot is momentarily illegal, and
honor the no-combat first round. Live play in the moment still works
too — and the client now actually offers it (the old UI never let
you click those cards out of turn). The rail shows your armed traps
with a disarm control; the chronicle announces AMBUSH! when one
springs. 124 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:36:53 -04:00
Eric WagonerandClaude Fable 5 295ad2ad55 Catch-up replays and named attention for async games
"While you were away": returning to a game with unseen moves shows a
banner — "You missed N moves. Watch what happened" — that opens a
replay reel. The server rebuilds the game and captures a redacted
per-move view for the viewer (own hand only, capped at the last 200
moves); the client plays the reel on a full board with the actor and
humanized events captioned per step, auto-advancing with pause,
step-back/forward, arrow-key control, and skip-to-now. Seen progress
is tracked per room in the browser (every state broadcast now carries
the log sequence), so the banner only appears when there is genuinely
something to watch.

Attention between turns is now named, not just signaled: game
summaries carry WHY a game waits on you — your turn, counteract
(you're being attacked mid-someone-else's-turn), forced discard, or a
pending interruption — the ledger prints it ("UNDER ATTACK —
respond!"), and browser notifications say "you are under attack in
GNSK!" rather than a generic your-turn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:26:27 -04:00
Eric WagonerandClaude Fable 5 4cbf5a013e Lobby standee picker for online rooms
Waiting rooms now show the six wizard standees: tap to claim yours,
claimed ones gray out with the claimant named for screen readers, the
roster shows each player's chosen standee (or "choosing..."), and
conflicts are refused first-come. Choices sync live to everyone in
the room, resolve to first-free defaults for the undecided when the
host flips the boards, persist in the start line of the room log (so
restored games keep their colors), and flow through the same engine
config field the hotseat picker uses. Also fixed: the static-serving
realpath guard crashed the dev server when no client build exists —
it now 404s static requests instead. Verified end to end: claim,
conflict refusal, second pick, start, and in-game colorIndex 5/2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:19:42 -04:00
Eric WagonerandClaude Fable 5 77ae65b263 Pick your standee: hotseat wizards choose their color while naming
The naming round now shows the six real wizard standees; each player
taps theirs before typing their name, claimed standees gray out, and
the next player defaults to the first free one. The choice travels as
an optional colors array in the engine config, the view exposes each
player's colorIndex, and the board, score sheet, treasure chests, and
wizard art all follow the chosen color instead of seat order —
persisting through hotseat saves. Verified: Alice claimed the yellow
standee and marched onto the board as the yellow wizard with yellow
treasure chests. (Online rooms still assign by seat order; a lobby
color picker can reuse the same config field later.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:15:07 -04:00
Eric WagonerandClaude Fable 5 98d7dee5c7 Hotseat setup round: pick a count, then each wizard names themselves
The comma-separated name field is gone. Hotseat now starts with a
2-6 count picker and "Gather N wizards", which opens a naming round
in the pass-the-device style: "Wizard 1 of 3 — what is your name?",
type it, pass the device on; the last wizard's button reads "Flip the
boards" and starts the game. Duplicate and empty names are refused
in place, the roster-so-far shows beneath, and "never mind" backs
out. Verified end to end: three names entered, game started, opening
handoff went to the die-roll winner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:12:13 -04:00
Eric WagonerandClaude Fable 5 ac82c28750 Fix lobby overflow on phones: global box-sizing and wrapping rows
The box lid combined width:100% with 2.6rem side padding and no
box-sizing reset, so the card ran off the right edge of real phones
(Join, Play Here, and Claim all clipped). A global border-box reset
fixes the class of bug; the hotseat and claim rows now wrap, their
inputs go full-width on narrow screens, and the lid tightens its
padding and title below 900px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:07:32 -04:00
Eric WagonerandClaude Fable 5 30aa1e778d Tap-to-inspect: selecting a card floats an enlarged, readable copy
Selecting any card in hand now also shows a 1.55x (1.3x on phones)
enlarged rendering floating at the corner — full card text readable
without pinch-zooming — with a dismiss button, auto-reset when the
selection changes, and the board left clear for choosing a target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:06:18 -04:00
Eric WagonerandClaude Fable 5 0927836e7e Mobile pass: hand above the paperwork, dvh sizing, tight masthead
At phone width the layout now flows board -> actions -> your hand ->
turn slip -> score sheet -> chronicle, instead of burying the cards
beneath the paperwork below the fold. The board caps at 56dvh (dvh
throughout, for iOS's shifting address bar), the chronicle stops
hogging height, and the masthead holds one line. The hand is a
thumb-swipeable card row; pinch-zoom stays enabled for reading fine
card text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:00:23 -04:00
Eric WagonerandClaude Fable 5 6ac50c4d58 Harden the static file server against path traversal (security review)
The internet-facing static handler now decodes the URL (rejecting bad
encodings and null bytes), anchors the containment check with a
trailing separator so sibling-prefix directories cannot slip past,
and realpath-resolves the final file to defeat symlink escapes —
serving only what provably lives inside the built client directory.
Deployed and probed live: normal requests 200, literal and
percent-encoded traversal attempts both 403.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:56:16 -04:00
Eric WagonerandClaude Fable 5 23bdd009a0 Deploy to a DigitalOcean droplet: wizwar is on the internet
Production shape: one Node process serves the built client and the
websocket on a single port (SPA fallback, same-origin wss in the
client), with Caddy terminating auto-TLS in front. The droplet
(nyc3, $6/mo) runs it under systemd as an unprivileged user with room
files on the persistent disk at /var/lib/wizwar/rooms — deploys and
reboots cannot eat a game. deploy/ carries the one-time droplet setup
script, the systemd unit, the Caddyfile, an everyday deploy script
(build locally, rsync, install, restart), and a README. Live at
https://wizwar.104.236.96.198.sslip.io via sslip.io, so TLS needed no
DNS setup at all. Verified over the real internet: room created,
second player joined, expansion game started, hands dealt, room file
persisted. Fixed en route: rsync's unanchored "data" exclude was
stripping the engine's card database, and tsx must ship (it is the
runtime).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:54:15 -04:00
Eric WagonerandClaude Fable 5 002dbd2547 Crowded squares: every token type gets its own zone, multiples fan out
A square can legally hold terrain, a wizard or three, two treasures
(the win condition itself), floor objects, creatures, a warp token,
and a boobytrap disc all at once. The cell is now zoned: terrain
underlays the whole square, wizards fan across the top, treasures fan
across the bottom, floor objects stack bottom-left, creatures fan in
from the right, the dimensional warp shrinks into the top-left
corner, and boobytrap discs sit top-right. Two treasures on a home
base — the winning tableau — now reads as two treasures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:27:05 -04:00
Eric WagonerandClaude Fable 5 bd8ca99dc0 The real 1993 token art takes the board
All 45 tokens cropped from Eric's photos of the physical counter
sheets by connected-component segmentation (with cable/reflection
filtering, merged-blob rescue, and one hand-tuned crop for the red
treasure the charging cable was hugging). Terrain squares, monsters,
wizards, treasures, floor objects, and dimensional warps now render
the genuine hand-inked art: solid stone, thornbush, rosebush, killer
ooze, dustcloud, slime, tacks, pit, safe, skeleton, troll, wraith,
fire imp, democratic monster, shadow, alter ego, dagger, rock,
master key, magic stones and wands, the six wizard standees, and the
six color-keyed treasure chests. Wizards and creatures wear a thin
controller-color ring; art tokens cast a soft cardboard shadow;
terrain art passes clicks through to the floor while wizards and
creatures stay clickable. Every vector rendering remains in place as
automatic fallback (USE_TOKEN_ART flag plus per-kind mapping), per
Eric's request. Sprites live in packages/web/public/tokens at 144px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:26:06 -04:00
Eric WagonerandClaude Fable 5 9323399b60 Phase 3: hotseat play — the whole game in one browser
Hotseat runs the engine entirely client-side; no server is involved.
The lobby takes comma-separated names (2-6 wizards), and the game
plays through the same table UI, with one addition: a full-screen
hand-off card between actors — "pass the device to Morgana, tap when
only they can see the screen" — shown whenever the needed input moves
to another wizard (turns, counteractions, forced discards,
interrupts). Each player sees only their own hand while seated. The
game saves itself to localStorage after every command (config +
command log, replayed on resume — the server's own determinism
trick), so "set the game aside" keeps it and "abandon game" forgets
it; a Resume Hotseat button appears whenever a save exists. Fixed en
route: the engine's structuredClone cannot digest Svelte's reactive
proxies, so hotseat snapshots state before every engine call.
Verified live: 3-player game started, reloaded, resumed, turn ended,
device handed to the next wizard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:13:59 -04:00
Eric WagonerandClaude Fable 5 85b40be113 Support 2-6 players: the L, the cross, and the 3x2 from the rulebook
Three player counts join the square and the column: 3 players get the
stair/L with the AUTO WARP joining the two openings that face the
concave notch (convex openings pair geometrically — flagged as
interpretation of the diagram's letters); 5 players get the plus/cross
with tip-to-tip wraps and aisle-warp corners; 6 players get the 3x2
rectangle with straight-across wraps. All six verified layouts are
drawn on, so no count repeats a sector. Rooms accept up to six seats;
the lobby copy follows. 122 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:08:15 -04:00
Eric WagonerandClaude Fable 5 8d10dfee67 In-game help: how-to-play guide, the 6e rules, and a card library
"Help & rules" in the masthead (lobby and table alike) opens a paper
booklet with three tabs: How To Play (the client's own controls —
moving, casting, attaching numbers and modifiers, fighting, turns,
transfers), The Rules (the 6th edition rules condensed faithfully
from the verified rulebook transcription, exact numbers throughout,
with a Jolly Games colophon), and a Card Library — all 135 playable
cards rendered as the real card faces, searchable by name or text,
each labeled with its quantity and set. Escape closes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:06:51 -04:00
Eric WagonerandClaude Fable 5 0221e91ba3 Hash seat tokens at rest (security review finding)
Raw seat tokens no longer touch disk or long-lived memory: rooms store
sha-256 hashes, joins compare timing-safely, sessions keep the raw
token they authenticated with only for minting transfer phrases, and
legacy plaintext room files still load (hashed on read). Verified:
rejoin and transfer both work, and the room file contains only
hostTokenHash — no raw token anywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:03:36 -04:00
Eric WagonerandClaude Fable 5 ce8b6a315b Harden seat transfer phrases (security review finding)
Three words from 32 was ~15 bits — enumerable within a code's
lifetime against an unthrottled endpoint. Now: 64-word list, 4-word
phrases (~24 bits / 16.7M combinations), a per-connection cap of 5
failed claims, and a global backstop that voids all pending codes and
cools the endpoint after 30 failures in 10 minutes. Codes stay
speakable; a brute force now gets ~35 guesses against 16.7M before
the door closes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:00:13 -04:00
Eric WagonerandClaude Fable 5 2184721ab4 Seat transfer phrases: carry your games to another device
While seated, "transfer seat" in the masthead mints a spoken-word
one-time phrase from the game's own vocabulary (ember-troll-dagger),
good for ten minutes. Typing it into the lobby's "claim a transferred
seat" box on any other device hands over the seat's token, adds the
game to that browser's ledger, and sits you straight down at the
table. Phrases are single-use, expire, are voided by a server restart
(seats never are), and both devices keep the seat afterward — the
phone on the couch and the desktop upstairs can play the same wizard.
Verified end to end: mint, claim, rejoin, and a second claim refused.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:58:52 -04:00
Eric WagonerandClaude Fable 5 bc8013863b Phase 2: async play-by-turn — the games ledger and turn signals
The lobby is now the front door to all your games. A "your games"
ledger lists every seat this browser holds — room code, whose turn it
is (including counteraction/discard/interrupt waits, which count as
your turn), the round, and how long since the last move — with
one-click resume, a forget control, and a green highlight when a game
waits on you. The server answers a token-validated myGames query with
per-seat summaries; the client polls every 45 seconds, so turns in
other rooms reach you wherever you are. Turn signals travel three
ways: the tab title flips to "● Your turn", the favicon grows a green
dot, and — opt-in via "notify me on my turn" — a browser notification
fires when a turn becomes yours anywhere. Landing in the app now
shows the ledger rather than teleporting into the last game;
mid-game socket drops still walk straight back to the table.
Verified live: two seeded games, ledger showing "gandalf's turn" and
YOUR TURN, one-click resume into the correct game with history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:53:13 -04:00
Eric WagonerandClaude Fable 5 7cfffb8ab5 Persistent games: rooms survive server restarts by log replay
Every room is now an append-only JSONL file (data/rooms/CODE.jsonl):
a birth-certificate meta line, then every join, start, and command.
Because the engine is deterministic, seed + log IS the game — on boot
the server replays each file and reconstructs the exact state, hands,
deck order, and chronicle. Verified live: create, join, play, KILL
the server, restart ("restored 1 room(s) from disk"), rejoin with the
seat token — identical positions, deck count, and hand, with the
chronicle history redelivered redacted per player. The client
remembers its seat (name, room, token) in localStorage and walks back
to the table automatically on connect, clearing the memory if the
seat is stale; a quiet "leave table" control forgets it on purpose.
This is the foundation phase 2 (play-by-turn) sits on: games now wait
indefinitely for their players — and debugging restarts cost nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:46:23 -04:00
Eric WagonerandClaude Fable 5 89961bbb59 ADD joins two number cards on movement, as printed
"You may add two NUMBER cards together for any single action" —
movement included. The engine accepts a second movement number when an
ADD accompanies it (once per turn); the client spends your Add
automatically and says so on the button. 120 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:41:37 -04:00
Eric WagonerandClaude Fable 5 42ea9b8f2c Voluntary discards, and no card ever casts on the first click
The rulebook allows discarding cards purely to churn for new ones, but
the client had no way to start it: a "Discard cards..." action now
switches the hand into marking mode (with a "never mind" exit), and
confirming sends the discard. Alongside it, the hair-trigger casts are
gone: selecting a card never plays it anymore — untargeted spells and
stone displays get a Cast button in the hint bar, and a bare number
card gets an explicit "Play for +N movement" button. Every play is
now a two-step, which is the correct cost in a game with no undo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:35:02 -04:00
Eric WagonerandClaude Fable 5 d7afe3a5ca Letter the warp openings and show where they lead
Paired openings now share a printed letter (A, B, C ...), exactly as
the physical boards mark them — making the 2-player crossed pairings
legible at a glance. While standing on an opening, its destination
square pulses with a dashed green ring, so "leave at B, re-enter at B"
stops being a leap of faith.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:31:42 -04:00
Eric WagonerandClaude Fable 5 87a5067c77 Make wraparound warps usable: click the arrow to step through
The engine's warps worked all along (verified in all six directions),
but the client offered no way to trigger one short of clicking the
destination cell across the map. The warp arrows are now buttons: when
you stand on an opening its arrow lights green — click it to step
through. Clicking a far arrow behaves as a click on its cell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:28:51 -04:00
Eric WagonerandClaude Fable 5 3e1fe059fa Disable treasure buttons when they cannot apply
Pick Up Treasure is disabled unless an uncarried treasure sits on your
square (and your actions haven't ended); Drop Treasure unless you are
actually carrying one. The warp-step button reuses the same derived
state. Buttons stay visible for stable layout — they just stop
inviting impossible clicks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:26:20 -04:00
Eric WagonerandClaude Fable 5 8c85c13e15 Widen the room-code input so CODE fits with its letter-spacing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:25:06 -04:00
Eric WagonerandClaude Fable 5 9501a85be4 Design pass: the client becomes Eric's table
The design system is the physical set itself, taken from the scans:
card-stock cream, warm ink, and the six wizard marker colors, laid on
a dark leather tabletop. Cards in your hand are now the actual cards —
vertical corner rails (ATTACK / L.O.S. / ADJACENT), condensed gothic
titles, brown body text on cream, number cards as big bordered
numerals with per-value border styles; selecting lifts a card from
the fan. The board matches the physical tiles: ink-outlined white
brick walls on cracked flagstone, plank doors, eight-point home stars
in each wizard's color, swirled treasure discs, warp arrows. The
sidebar is paper: a turn slip, the rulebook's own hand-scrawled
life-points score sheet (Caveat over ruled lines), and a typewritten
chronicle that scrolls itself. The lobby is the box lid. Type:
Oswald condensed display / Archivo Narrow body / Courier Prime log.
Buttons are ink stamps. Board height-caps so board, actions, and the
full hand share one screen; responsive stack below 900px; reduced
motion respected. Verified with live screenshots at 1440x900.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:21:03 -04:00
Eric WagonerandClaude Fable 5 c9524a37db Thumb Of God lands: the 6th edition is 100% implemented
Eric chose the "divine meteor" redesign for the one card that cannot
be digitized faithfully (the physical version has you flick the die at
the board from six inches). Digital form: aim at a square in sight;
the die drifts 0-2 squares in a random direction, then every token in
and around the landing square — ground objects, treasures, creatures,
and wizards alike — is flung to a random nearby square. Walls mean
nothing to falling cardboard; tokens knocked off the board settle at
the nearest edge, per the original card; there is no counteraction.

With this, all 128 unique cards of the 6th edition game (69 basic +
59 Expansion Set #1) are implemented, tested, and playable online.
The "unimplemented card" guard test now points at an Expansion #2
shelf card, which is the only kind left. 119 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:05:47 -04:00
Eric WagonerandClaude Fable 5 21dcd532a3 Add Swap Home Bases — the audit's one escapee
Neutral, LOS to the other player, legal only when both home bases hold
an equal number of treasures. With this, every card in the 6th edition
game is implemented except Thumb Of God, which awaits its digital
redesign. 118 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:02:43 -04:00
Eric WagonerandClaude Fable 5 06eea72ded Expansion wave 4: 27 combat, curse, and utility cards
Fortune: GIFT FROM ABOVE (+3, no ceiling), GIFT FROM BELOW (a trap in
the deck — 3 damage on the draw, harmless in the opening deal).
Modifiers: POWER ATTACK burns life into any damage spell. Curses:
WEAKNESS (drop your treasure, take double, carry nothing), STRENGTH
(double physical dealt; the two cancel), WALKING DEAD (half a point
per space walked, forever), DISEASE (the victim becomes a carrier who
infects everyone in squares they enter), IDIOT (the victim shambles
toward their nearest own treasure, able only to counteract, until
they stand on it and ask "What am I doing here?"). Defense: EMPATHY
(attacks bite their caster too), FORCE FIELD (spell-stopping
counteraction). Mischief: MENTAL SWAP (trade whole hands), MENTAL
FORCE (march someone three spaces), BUTT-HEAD (become a goat, ram for
the distance charged), HEAVE-HO (throw your carried treasure as a
weapon), THIEF and SWAP-MEET (item larceny), CHAOS (all hands in a
pile, shuffled, redealt), ILLUSIONARY ATTACK (a fake spell that hurts
if believed), WARD (a trapped treasure bites its thief), REMOVE CURSE
(strip any duration spell, rolling to hit the shrunken or invisible),
SWARTHMORE'S ENCHANTMENT (+1 on an enchanted object). Space-time:
DIMENSIONAL WARP (paired step-through tokens), REDIRECTION (swap two
outer exits' wraparounds), BIG MAN (fills the corridor: no entry, no
punches, no casting past him), FEAR (nobody approaches within 3),
and the out-of-turn pair — INTERRUPT and OPPORTUNITY FIRE — which
open a one-action window in another player's turn. Only THUMB OF GOD
remains, awaiting its digital redesign. 117 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 23:01:42 -04:00
Eric WagonerandClaude Fable 5 3a35922a40 Expansion wave 3: twelve terrain cards
KILLER OOZE (1 damage on entry, slip on 1-2: drop treasure, 2 more,
flat on your face until you roll to stand), CREATE PIT (jumps roll a
D4 — clear it on 2-4, fall in on a 1 and climb out on later rolls),
ROSEBUSH (3-point passage, blocks sight), DUST CLOUD (a permanent
zone of blindness: direction rolls inside, no LOS through, defeats
Visionstone), FILL SQUARE WITH SLIME (entering ends your actions,
blocks sight), HANDFUL OF TACKS (adjacent-only scatter, 3 points to
cross), CREATE DOOR (a new locked door in any wall or corridor),
BOOBYTRAP (four face-down tokens, one secretly real — 4 points under
anyone but the caster; the caster's view alone marks the true token),
GLUE (objects pinned for twice the number card), SAFE (items locked
away from everyone but the creator), TRADER (swap two floor items in
sight; glue and safes hold fast), and STONE TO WATER (walls melt into
range-2 waves, stone blocks into range-4 bursts). LOS blocking is now
per-terrain-kind. The board renders all of it, boobytrap placement is
a four-click ritual, and Trader is two. 108 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 22:52:34 -04:00
Eric WagonerandClaude Fable 5 28949bdb7d Expansion wave 2: the magic wand system, plus Deja-Vu
Wands charge on first use from the number card(s) played (Amplify and
Add both work when setting charges), spend one charge per use with a
hard once-per-turn limit, stay displayed while charged, and crumble
to the discard when the last charge goes. They are isolated from
their wielder — usable under NO SPELL — and Absorb Spell cannot eat
them. BLASTER WAND fires 3-point bolts through the normal
counteraction stack; STICKY WAND webs its victim (movement -3 for a
turn, +2 to any fire damage, including hotter firewall crossings);
SHIFT WAND shoves a wizard one space in any direction straight
through stone walls; WARP WAND opens a wall section that snaps back
at the end of the turn. DEJA-VU retrieves any discard except a wand.
The wands' 5e-era "stick" subtype is renamed "wand" per Eric (the 6e
faces say Wand; Exp2 shelf cards keep their period names). Client:
charge badges, shove targeting, retrieve-by-name. 101 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 22:45:51 -04:00
Eric WagonerandClaude Fable 5 df0675c735 Expansion wave 1: the creature system and first eight monster cards
Creatures are first-class citizens: TROLL (D4 punches, 6 damage to
kill, regenerates at its creator's turn end), SKELETON (2-point
punches), WRAITH (walks through one wall a turn; its touch deals 2
and steals a random card), FIRE IMP (a stationary turret scorching
anyone in sight once per turn — including its creator — killed only
by Waterbolt or a Waterwall wave), DEMOCRATIC MONSTER (moved three
spaces by EVERY player on their turn, one claw per round), SHADOW (a
second body costing a life point per turn, destroyed by any damage),
and ALTER EGO (a stationary double). Monsters obey their creators,
move on the controller's turn, attack once per turn but never on
their creation turn (summoning IS your attack), refuse to strike
their creators, and vanish when their creator dies. Attacks can
target creatures directly (no counteraction window — monsters don't
counter); Dispel Creation un-creates them. Plus MEGA-MONSTER (double
a monster's toughness or speed), ADRENALINE (two attacks a turn),
MAD DASH, and LIFESAVER. Expansion Set #2 confirmed by Eric as a
5e-era product — marked historical-only in the data; the 6e game is
exactly base + Expansion #1 (200 cards, all verified). Lobby gains an
"include Expansion Set #1" toggle; the client renders creatures as
diamond tokens with select-move-attack interaction. 95 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 22:41:54 -04:00
Eric WagonerandClaude Fable 5 e475253fb0 Correct 21 more Expansion #1 cards from photos of the card faces
IMG_4693-4696: Killer Ooze, Mental Swap, Opportunity Fire, Idiot,
Illusionary Attack, Interrupt, Shadow (ATTACK/LOS, not a neutral
summon), Safe, Stone To Water, Remove Curse (COUNTERACTION), Rosebush,
Power Attack (NEUTRAL booster), Thumb Of God (physical-dexterity card
flagged for digital redesign), Trader, Walking Dead, Thief
(ATTACK/ADJACENT, "not a spell"), Swap Home Bases, Strength, Ward,
Boobytrap, and Weakness — all verbatim from Eric's cards with printed
corner types and targeting markings; two printed typos on the faces
noted and corrected. All data-only (none implemented yet); these
become the authority for the expansion wave. 84 tests still passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 22:33:03 -04:00
Eric WagonerandClaude Fable 5 dd89232499 Correct 12 Expansion #1 cards from photos of the actual card faces
IMG_4691-4692: Create Pit, Deja-Vu, Disease (ATTACK/ADJACENT),
Adrenaline, Big Man, Blaster Wand, Glue, Handful Of Tacks
(NEUTRAL/ADJACENT, "Not a spell"), Heave-Ho, Dust Cloud, Fill Square
With Slime, and Fire Imp — all texts now verbatim from Eric's 6e-era
Expansion Set #1 faces with printed corner types and targeting
markings. These are data-only fixes (none of the twelve is implemented
in the engine yet); the corrected texts and metadata become the
authority when the expansion wave is built. Notable for that future
work: Disease is adjacency-triggered contagion, Heave-Ho throws
carried treasures as weapons, Dust Cloud is a permanent blind-zone,
and Fire Imp is an auto-attacking turret with waterbolt/waterwall as
its counters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 22:11:47 -04:00
Eric WagonerandClaude Fable 5 5a0003f4ce Correct 15 cards against photos of the actual 6e card faces
Eric spotted mismatches between the 5e-database-derived card data and
his physical 6e cards (photos IMG_4688-4690). All 15 texts are now
verbatim from the faces. Four were mechanical, now fixed in the
engine: BLIND's 6e text adds "engage in combat" — blinded punches
flail on a die roll; PICK LOCK's face states "This is not a spell" —
the lock cards and thrown weapons are physical actions NO SPELL cannot
silence; MIST-BODY passes doors and drifts through thornbushes but
does NOT pass the maze's stone walls (was backwards); WALL OF FIRE's
counteraction mode stops a Waterbolt (previously deferred). Metadata:
UGLY is L.O.S.-marked, MASTER KEY is NEUTRAL/ADJACENT, WIZARDBLADE
and PICK LOCK carry ADJACENT markings (new `adjacent` field), LARGE
ROCK is rethrowable by any player (already engine behavior),
BRAINSTONE's face has no corner type. 84 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 21:58:17 -04:00
Eric WagonerandClaude Fable 5 1924114b6d Final wave: complete the 6th edition basic set (69/69 cards)
The hard six: AROUND THE CORNER attaches to any LOS attack and bends
the sight line through one intermediate cell. BLIND victims lurch in
die-rolled directions (bumping a wall costs the movement point, per
the card) and their attacks fly wherever the die says — hitting
whoever stands that way, or dissipating. UGLY drives every opponent
in sight fleeing along shortest paths (breadth-first, die-broken ties)
until they cannot see the caster. ILLUSION WALL is per-player reality:
each opponent rolls 50/50 the first time it matters and the wall is
real for believers forever — believers' views render it as a wall,
the caster and those who saw through it get a ghostly dashed line,
and Dispel Creation banishes it. ROTATE SECTOR turns a sector 90
degrees with every wall, door, wizard, treasure, object, firewall and
alteration turning in place (the home star, being the exact center,
never moves); RELOCATE SECTOR slides a sector anywhere that keeps all
sectors adjacent, reassembling the map and recomputing wraparounds.

Client: modifier attachment (Amplify/Add/Extend/Around The Corner),
two-stage relocate, rotation direction toggle, dashed known-illusions.
Every card in the 6th edition basic deck is now implemented.
81 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 20:29:09 -04:00
Eric WagonerandClaude Fable 5 4ab44cc535 Card wave 4: all eight magic stones and Slow Death
Stones are displayed permanents whose powers weave into the core
systems: BLOODSTONE (-1 to every hit taken, cancels Slow Death's
drip), SOULSTONE (last three points immune to spell damage — punches
and daggers still finish the job), BRAINSTONE (+2 cards now, hand
limit 9 — limits are now per-player and dynamic), POWERSTONE (+1 per
number card played, movement included), SPEEDSTONE (+1 movement,
overridden by SLOW), SHADOWSTONE (physical damage you deal feeds your
life), SHIELDSTONE (number cards become counteractions that shave
their value off damage AND duration), VISIONSTONE (LOS through any
single wall or door, at the holder's option). Damage now carries a
spell/physical kind everywhere. SLOW DEATH is a permanent curse: one
magical point per card drawn, forever — Fireball still burns stones
off their display. Re-displaying a stone is refused (no Brainstone
double-draws). 49 basic-set cards implemented, 15 remain (the hard
five: bent LOS, Blind, Illusion Wall, Ugly, sector manipulation).
72 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 20:17:29 -04:00
Eric WagonerandClaude Fable 5 2d88b4ab40 Card wave 3: terrain, thrown objects, drag, and control spells
Terrain layer: FILL SQUARE WITH STONE (impassable, blocks LOS via new
cell-blocking sight checks), THORNBUSH (enter = 1 damage + turn ends +
next turn lost; no attacking in or into a bush), WALL OF FIRE (new
firewall edge state — passable for 4 magical damage, blocks LOS,
expires with its duration), WATERWALL (instant wave: players within
two spaces washed back two, 1 damage per blocked space), and DISPEL
CREATION with provenance tracking (only conjured walls/fire/stone/
bushes dispel — printed maze is safe). Objects: DAGGER (3) and LARGE
ROCK (2) are physical throws Full Shield cannot stop; they land on the
floor and anyone may pick them up (ending their turn's actions, hand
limit enforced); DROP OBJECT forces a named object or carried treasure
to the ground; DRAG pulls floor objects, treasures, or players
straight toward the caster. Control: LOCK IN PLACE (no moving or
being moved — teleports, swaps, knockbacks and drags all respect it),
BUDDY (a pact the caster breaks by attacking), MIST-BODY (through
walls and doors, cannot attack or be attacked, still burns in
firewalls), REUSE SPELL (retrieve your last spell). Client renders
terrain, firewalls, and ground objects, with cell/edge/two-stage
targeting and card-name inputs. 40 cards implemented; 63 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 20:13:14 -04:00
Eric WagonerandClaude Fable 5 67743dd17e Card wave 2: durations, doors, teleports, modifiers; harden room auth
Duration system: sustained effects expire at the start of the caster's
turns; SLOW (movement 1, no number cards, attack every other turn),
NO SPELL, MEDUSA (paralysis + damage immunity), INVISIBLE (1-in-4 hit
roll), SHRINK (50% miss, movement 2). Doors: PICK LOCK and MASTER KEY
(displayed, reusable) unlock adjacent doors until end of turn, REMOVE
LOCK is permanent, JAM LOCK seals a door for everyone. Movement:
TELEPORT (4 spaces through walls, ends movement), PASS THROUGH WALL
charges, POWER RUN (life for spaces), SWAP (consumes movement),
GO AWAY (knockback + lost turn), TELEPORT OPPONENT. Card warfare:
CARD ERASURE (named), THOUGHT-STEAL (2 random via seeded RNG),
TELEPATH (private hand reveal), POWER DRAIN (damage feeds the caster),
SUDDEN DEATH, STONE DEAD, WIZARDBLADE (same-square, number-powered,
stays displayed). Cast modifiers: AMPLIFY doubles power/duration
(stackable x2), ADD permits two number cards, EXTEND doubles duration;
REVERSE heals instead of harms but keeps secondary effects. Counters
now also halve durations (BLUNT) and split them (REFLECTION).

Security (from review findings): room codes and game seeds now come
from node:crypto, and every seat gets a secret token — reclaiming a
name in a room requires its token, closing the impersonation hole.

29 cards implemented; 51 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 20:04:44 -04:00
Eric WagonerandClaude Fable 5 36b3ffe9a6 Wire online multiplayer: game rooms, protocol, playable Svelte client
Server: room registry with 4-letter codes, host/join/start flow, the
authoritative command loop (seed + append-only command log per room —
the replay/async foundation), and per-player redacted views and events
broadcast after every change. Client: lobby, SVG board (floors, walls,
doors, homes, color-keyed treasures and wizard tokens matching the
physical set's six colors, warp arrows), click-to-move, click-to-punch,
card hand with tooltips from verified card text, cast flow with number
card attachment and waterbolt split, edge-click targeting for wall
spells, counteract-or-pass prompt, discard flow, end-turn draw
selector, and a humanized event log. Verified end-to-end over real
websockets with two clients: join, start, private deals, moves, turn
sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 19:52:10 -04:00
Eric WagonerandClaude Fable 5 7c607ad5c7 Implement casting layer: attack stack, counteraction chain, first effects
The attack/counteraction stack: casting an attack (or punching) opens a
stack; the defender counteracts or passes, the attacker may respond
(ANTI-ANTI nullifies a counter), and resolution runs the damage
pipeline in play order. First effect wave, encoded from verified card
text: Fireball (flat 5 + destroys carried magic stones if damage gets
through), Lightning Blast (number damage + stun unless fully stopped),
Powerthrust (2 + optional number), Waterbolt (caster-chosen
damage/knockback split with push-away movement), Blunt (halve, round
result up), Absorb (-3), Full Shield (spell-only stop), Reflection
(half to both), Full Reflection (redirect), Absorb Spell (nullify and
steal the attack card), Create/Destroy Wall (dynamic edge overrides
layered over the board; collapse deals 4 to adjacent squares), Speed
(extra turn), and TRAP! on draw (lose next turn, redraw). Lost turns
skip on advance; unimplemented cards refuse to cast with a clear
error. 35 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 19:47:06 -04:00
Eric WagonerandClaude Fable 5 a8884592a4 Implement engine core: board assembly, movement, turns, combat, victory
Pure deterministic game core in @wizwar/engine: seeded RNG (mulberry32,
state in GameState so seed+commands replays identically), sector
assembly with rotation, junction merging, and wraparound warps
(configurable pairings; the 2p diagram crosses its side openings),
movement (3 + one number card), geometric line of sight, deck building
from the verified card data (asserts 125/200 totals), and the
command-to-event reducer: setup with TRAP! redraw and die-roll first
player, punching (no combat round 1, no self-attack, once per turn),
damage/death with killer-takes-cards and forced discard, treasure
stealing with both victory conditions, pick-up-ends-turn, and
end-of-turn draw. Events carry full spatial detail for future replay
rendering; private card knowledge rides on visibleTo events with a
redaction helper. 21 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 19:39:50 -04:00
Eric WagonerandClaude Fable 5 11209cdc5d Add complete token inventory from photos of Eric's counter sheets
IMG_4684-4687: all 33 token types, six double-sided wizard standees
(front and back views), and 12 color-keyed treasure tokens documented
in research/tokens-6e.md. Verification checklist is now fully closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 19:31:50 -04:00
Eric WagonerandClaude Fable 5 e62d4c118f Record board dimensions (7.5in square) and absence of errata sheet
Owner-measured: sector boards are 7.5in to a side (1.5in per space);
no errata sheet in the 6e box. Closes the physical checklist except
the optional counter-sheet inventory.

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