A crowded table: keyed threats, a compact roster, a ledger selector, host-only starts

Threats are keyed by seat as well as hand and spell, so two opponents
building the same spell no longer crash the board (regression test).
Past three wizards the rail lists the others on a line each with health,
condition and whether they have moved; a row of names turns the ledger
toward any wizard, as do pressing a warning or choosing a target; each
seat keeps one colour across the ledger, the warnings and the rail. The
result strip folds untouched wizards into one line. A full table no
longer begins by itself: the host begins at every size and may send a
bot away. Target lists carry health and condition, and an intent
sentence says what the reveal will do.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
Eric WagonerandClaude Fable 5.1 committed 2026-09-23 11:21:13 -04:00
1 parent c24fccaa35
commit 5c79a65319
19 files changed
+473 -60

No files matched your search

+7
View File
@@ -5,6 +5,7 @@
// POST /api/rooms/:id/join {name} take the next seat
// POST /api/rooms/:id/bot {token} seat a bot (any seated player may, before the duel begins)
// POST /api/rooms/:id/begin {token} the host begins the duel with the wizards seated so far
// POST /api/rooms/:id/unseat {token, seat} the host sends a bot away before the duel begins
// GET /api/rooms/:id?token= the view for that seat; without a token, the gallery's view
// POST /api/rooms/:id/turn {token, input} this seat's move
// POST /api/rooms/:id/say {token, text} table talk, from a seat to the whole room
@@ -205,6 +206,12 @@ async function handle(req: IncomingMessage, res: ServerResponse): Promise<void>
const seat = rooms.seatOf(room, body.token as string);
return send(res, 200, rooms.view(room, seat.id));
}
if (action === 'unseat') {
const token = typeof body.token === 'string' ? body.token : undefined;
rooms.unseat(room, token, body.seat);
const seat = rooms.seatOf(room, token);
return send(res, 200, rooms.view(room, seat.id));
}
if (action === 'begin') {
const token = typeof body.token === 'string' ? body.token : undefined;
rooms.begin(room, token);