v0.8.0 — the board replays what everyone else did, instead of arriving rearranged

TODO #13, #15 and #18 — Gitea#20 steps 2-4 pointed at a seated player's own screen.
Every accepted intent, and every automatic phase that does anything, becomes an
ordered presentation step. A bot's whole switching turn used to land in one push;
now it arrives as a run of steps, the district panel follows whoever is acting,
and a [N behind] … [Skip] row says how far the board is from the game.

Solitaire runs the same path — one collector inside submit(), which both session
kinds already funnel through — which is where its automatic phases finally get a
visible beat.

Dwell is assigned by kind: switching holds the screen, turn bookkeeping costs
nothing, and the clock turning over earns the beat. Tunable per viewer without a
rebuild, and off entirely at pace 0.

Also: switching was the one class of action logging unattributed, and now names
its train. Reasoning, measurements and the three things that turned out wrong are
in CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6
This commit is contained in:
Jesse.Markowitz
2026-09-09 15:31:46 -04:00
co-authored by Claude Opus 5
parent 312e0301e0
commit 02289e94b8
25 changed files with 2669 additions and 145 deletions
+4 -1
View File
@@ -1642,6 +1642,7 @@ function execute(s: GameState, player: PlayerIndex, i: Intent): GameEvent[] {
const events: GameEvent[] = [
{
type: 'trayMoved',
player,
trayId: i.trayId,
from,
to: i.to,
@@ -1706,6 +1707,7 @@ function execute(s: GameState, player: PlayerIndex, i: Intent): GameEvent[] {
// decides which car is next to come off.
events.push({
type: 'carsCoupled',
player,
trayId: i.trayId,
at: i.to,
stock: dest.couples,
@@ -1726,7 +1728,7 @@ function execute(s: GameState, player: PlayerIndex, i: Intent): GameEvent[] {
const stock = i.fromNose
? tray.consist.slice(0, i.count)
: tray.consist.slice(tray.consist.length - i.count);
return [{ type: 'carsDropped', trayId: i.trayId, at: here, stock, ...(i.fromNose ? { fromNose: true } : {}) }];
return [{ type: 'carsDropped', player, trayId: i.trayId, at: here, stock, ...(i.fromNose ? { fromNose: true } : {}) }];
}
case 'switch.sortConsist': {
@@ -1735,6 +1737,7 @@ function execute(s: GameState, player: PlayerIndex, i: Intent): GameEvent[] {
return [
{
type: 'consistSorted',
player,
trayId: i.trayId,
at: here,
before: tray.consist.map((c) => ({ ...c })),