Credibility pass: the code reads as one hand's work. Dead plumbing and copies removed, four claims the source does not support corrected, and inherited kit text re-read for this game
Engine: the facade re-exports the board in one block; describe() and the dead letters preference, void ply, the King-taken line, the identity maps and the unused imports are gone; SIDE_NAMES is the one spelling of a side; the drake's hunter comment no longer claims only four kinds reach the air. Prose: no shatranj and no sitting duck, which Gygax never wrote; the Thief statistic and the Basilisk's "one trick" are gone; lessons say what the board shows instead of repeating the panel; a Thief's leap counts four. Browser: one keyboard walk, in BoardArtwork; the lesson board and page share one game and one inspected piece; the chronicle's possessive is told, not patched by a regex; the chronicle colours and the reading layout are global rules instead of three copies; the guide and the about panel say what the code and the article actually do. Server and docs: the room server states that it is the kit's, kept whole; the skills, the deploy README and the share card name files that exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
1 parent
944e4e8306
commit
0be89a1dfe
28 files changed
+227
-340
No files matched your search
+2
-2
@@ -29,7 +29,7 @@
|
||||
//
|
||||
// Caddy serves the static site and proxies /api and /ws here. The keeper's
|
||||
// door answers only on the host itself, and only to the key in KEEPER_KEY_FILE
|
||||
// (deploy/wyrm-reseat.sh sends it):
|
||||
// (the deploy/wyrm-*.sh scripts send it):
|
||||
//
|
||||
// GET /keeper/rooms/:id who sits at the table, and when each person last moved or spoke
|
||||
// POST /keeper/rooms/:id/reseat {seat} a new token for a player locked out, as a link to send them
|
||||
@@ -389,7 +389,7 @@ server.on('upgrade', (req, socket, head) => {
|
||||
/**
|
||||
* A browser that died without closing (a crashed tab, a sleeping laptop, a dropped network) leaves its socket
|
||||
* open until TCP gives up, counted in the gallery all the while. The protocol's own ping, which every browser
|
||||
* answers by itself, finds it within two rounds.
|
||||
* answers by itself, finds it: one silent through a whole round is closed.
|
||||
*/
|
||||
const answered = new WeakMap<WebSocket, boolean>();
|
||||
setInterval(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@ const GOLD_OPENS: Input = { from: square('2c2'), to: square('2c3') };
|
||||
const SCARLET_ANSWERS: Input = { from: square('2c7'), to: square('2c6') };
|
||||
|
||||
/** The bots move once the request that made their move due has been answered: after this tick. */
|
||||
const settled = () => new Promise<void>((r) => setImmediate(r));
|
||||
const answered = () => new Promise<void>((r) => setImmediate(r));
|
||||
|
||||
/** A table of two: Ann, who opened it and plays Gold, and Bo, the game begun. */
|
||||
function table(spec: GameSpec<State, Input> = game) {
|
||||
@@ -138,7 +138,7 @@ describe('rooms', () => {
|
||||
expect(host.room.state!.setup).toBe('shifted');
|
||||
// Gold moves first, and Gold is the drake: it moves once the host's request has been answered, not inside it.
|
||||
expect(host.room.state!.moves).toHaveLength(0);
|
||||
await settled();
|
||||
await answered();
|
||||
expect(host.room.state!.moves).toHaveLength(1);
|
||||
expect(rooms.waitingOn(host.room)).toEqual(['A']);
|
||||
});
|
||||
@@ -205,13 +205,13 @@ describe('the host and the other seats', () => {
|
||||
rooms.submit(room, ann, GOLD_OPENS, 0);
|
||||
rooms.standIn(room, ann, 'B');
|
||||
// The move was Bo's; the drake makes it as soon as the host has been answered.
|
||||
await settled();
|
||||
await answered();
|
||||
expect(room.resolved).toBe(2);
|
||||
expect(rooms.waitingOn(room)).toEqual(['A']);
|
||||
expect(() => rooms.seatOf(room, bo)).toThrow(/gave your seat to a drake/);
|
||||
expect(rooms.view(room, 'A').seats[1]).toEqual({ id: 'B', name: 'Bo', bot: true, standIn: true });
|
||||
rooms.submit(room, ann, { from: square('2d2'), to: square('2d3') }, 2);
|
||||
await settled();
|
||||
await answered();
|
||||
expect(room.resolved).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
+13
-4
@@ -3,6 +3,12 @@
|
||||
// same place. Inputs wait in memory until every seat that must move has moved;
|
||||
// then the round resolves at once, bots included. Nothing here knows the
|
||||
// game beyond the GameSpec it is given.
|
||||
//
|
||||
// This is the kit's room server, kept whole so the kit's fixes land by copy.
|
||||
// Wyrm seats two and moves one side at a time, so only one seat ever owes a
|
||||
// move and the paced play of bots left alone at a table never arises here.
|
||||
// The strings a player reads say drake, the game's word for its house
|
||||
// player; the code and its comments keep the kit's word, bot.
|
||||
|
||||
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
||||
import { cleanOptions, SPECTATOR, tableSize, type GameSpec, type SeatId, type TableOptions } from '../../src/lib/game/spec';
|
||||
@@ -361,10 +367,14 @@ export class Rooms<State, Input> {
|
||||
* to hang and the bot's to come at once. The table is told as each move
|
||||
* lands, the same as any other.
|
||||
*/
|
||||
/** A room forgotten and loaded again drives itself; a timer or tick that wakes with the old one must do nothing. */
|
||||
private isTable(room: Room<State>): boolean {
|
||||
return this.rooms.get(room.id) === room;
|
||||
}
|
||||
|
||||
private botsAfterAnswering(room: Room<State>): void {
|
||||
setImmediate(() => {
|
||||
// A room forgotten and loaded again drives itself; this one is no longer the table.
|
||||
if (this.rooms.get(room.id) !== room) return;
|
||||
if (!this.isTable(room)) return;
|
||||
try {
|
||||
this.driveBots(room);
|
||||
this.botsSpeak(room, 'phase');
|
||||
@@ -403,8 +413,7 @@ export class Rooms<State, Input> {
|
||||
const hurried = this.hurried.has(room.id);
|
||||
const next = setTimeout(() => {
|
||||
this.pacing.delete(room.id);
|
||||
// A room forgotten and loaded again drives itself; this one is no longer the table.
|
||||
if (this.rooms.get(room.id) !== room) return;
|
||||
if (!this.isTable(room)) return;
|
||||
this.driveBots(room, hurried ? HURRIED_STEP : 1);
|
||||
// Played out: the bots have their say once, as after any round, which ends a game with parting words.
|
||||
if (!this.pacing.has(room.id)) this.botsSpeak(room, 'phase');
|
||||
|
||||
Reference in new issue
Block a user