v0.8.0.10 — playtest fixes: clearance rulings, the log, the map, and a save file
From the first two multiplayer playtests of v0.8.0.9, each traced before fixing. The engine: - A train on a card BEHIND the one departing no longer triggers a clearance ruling or an opposite-direction bar (#26). Reproduced from the exported save: X15 was held over X18 behind it, and X18 then collided into the full Whistle Post. Games in progress holding a ruling the engine no longer asks for will not resume (28 of 40 recorded four-seat games); shipped as is at Jesse's call. - `mainlineModified` carries the card's previous kind, so the log can say what a Realignment converted (#27). The screen: - The turn chart and the Division map name the player whose move is on screen while bot turns replay, not the live actor (#25). - The owning player's name is no longer outlined by the turn arrow's stroke, which made it unreadable (#24). - A Mainline card flashes on the map when a Realignment changes it (#28). - The history is held back with the board and revealed step by step, instead of arriving whole while the board is still catching up (#29). - A ruling made by holding the office reads "Superintendent Player X" (#30), and no line names a player twice (#31). - A seated player can download their own game as a save file: the play page's Save replay button, fed by GET /api/save?token=… (#32). The StartOS action cannot do this — an action result is text only. Closes #24 Closes #25 Closes #26 Closes #27 Closes #28 Closes #29 Closes #30 Closes #31 Closes #32 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nnuCv8UodHucFfx3LWEoX
This commit is contained in:
co-authored by
Claude Opus 5
parent
76c6e103b3
commit
4adf149ba5
+43
-1
@@ -19,7 +19,7 @@
|
||||
|
||||
import type { PublicFrame } from '../sim/view.ts';
|
||||
import type { DisplayStep } from '../sim/display-step.ts';
|
||||
import { applyPublicDelta, changedPiles } from '../sim/public-delta.ts';
|
||||
import { applyPublicDelta, changedDivisionCards, changedPiles } from '../sim/public-delta.ts';
|
||||
import type { PileKey } from '../sim/public-delta.ts';
|
||||
import { dwellForStep } from '../sim/pacing.ts';
|
||||
|
||||
@@ -53,8 +53,43 @@ export type StepQueue = {
|
||||
lit(): readonly PileKey[];
|
||||
/** True while there is anything left to show. */
|
||||
busy(): boolean;
|
||||
/**
|
||||
* How many narrated lines belong to steps NOT yet shown.
|
||||
*
|
||||
* The log and the board are two different moments while the queue is behind: a push carries its
|
||||
* narration and its steps together, so every line of a bot's turn is in the history panel before the
|
||||
* board has drawn a single move of it (playtest, 2026-09-15: *"is it possible to stall history so it
|
||||
* stays in sync with the number behind?"*). Those lines are the TAIL of the log — they arrived last —
|
||||
* so the caller holds back exactly this many and reveals each as its step goes up.
|
||||
*/
|
||||
pendingLines(): number;
|
||||
/** Division nodes whose card changed in the step now on screen, for the map to flash. */
|
||||
flashing(): readonly number[];
|
||||
};
|
||||
|
||||
/**
|
||||
* WHOSE MOVE THE SCREEN IS SHOWING (Gitea#25).
|
||||
*
|
||||
* The game and the board on screen are two different moments. The server plays every bot move the
|
||||
* instant a human's turn ends (`driveBots`), so the LIVE game is nearly always waiting on the human —
|
||||
* while this queue is still replaying the bots, step by step. The turn chart and the Division map's
|
||||
* move marker read the live actor, so a table of one person and three bots said "waiting on" that
|
||||
* person throughout, against a playback row naming the bot actually moving.
|
||||
*
|
||||
* While the queue is behind or still showing a step, the answer is that step's player — `null` for an
|
||||
* automatic phase, which is "the Division is running itself". Otherwise it is the live actor, and
|
||||
* `replaying` is false so a caller can keep live-only detail, such as a ruling the game is waiting on,
|
||||
* off a screen that has not caught up with it yet.
|
||||
*/
|
||||
export function actorOnScreen(
|
||||
queue: Pick<StepQueue, 'behind' | 'busy' | 'showing'>,
|
||||
live: number | null,
|
||||
): { actor: number | null; replaying: boolean } {
|
||||
if (queue.behind() === 0 && !queue.busy()) return { actor: live, replaying: false };
|
||||
const shown = queue.showing();
|
||||
return shown === null ? { actor: live, replaying: false } : { actor: shown.player, replaying: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* `pace` is read on every step rather than captured, so changing the setting takes effect at once.
|
||||
*
|
||||
@@ -74,6 +109,7 @@ export function createStepQueue(
|
||||
let shown: PublicFrame | null = null;
|
||||
let last: DisplayStep | null = null;
|
||||
let litPiles: readonly PileKey[] = [];
|
||||
let flashedCards: readonly number[] = [];
|
||||
let pending: DisplayStep[] = [];
|
||||
/** When the step now on screen is due to give way. Null when nothing is waiting. */
|
||||
let dueAt: number | null = null;
|
||||
@@ -92,6 +128,9 @@ export function createStepQueue(
|
||||
* that gives your own steps no dwell — the display is for watching everybody else.
|
||||
*/
|
||||
litPiles = step.player !== null && step.player === viewer() ? [] : changedPiles(before, shown);
|
||||
// A Realignment changes the Division under everyone, so it is flashed for the player who did it
|
||||
// too — unlike a pile, which only tells the drawer what they already know.
|
||||
flashedCards = changedDivisionCards(before, shown);
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -101,6 +140,7 @@ export function createStepQueue(
|
||||
dueAt = null;
|
||||
// Nothing was watched arriving at this board, so nothing on it is lit.
|
||||
litPiles = [];
|
||||
flashedCards = [];
|
||||
// `last` deliberately survives: a reconnect should not blank the caption line, and the
|
||||
// sentence describing the most recent action is still true.
|
||||
},
|
||||
@@ -165,6 +205,8 @@ export function createStepQueue(
|
||||
* `dueAt` is non-null exactly while the step on screen has time left, so the two together mean
|
||||
* "there is more to come, or what is up has not had its moment yet".
|
||||
*/
|
||||
pendingLines: () => pending.reduce((n, s) => n + s.lines.length, 0),
|
||||
flashing: () => flashedCards,
|
||||
busy: () => pending.length > 0 || dueAt !== null,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user