v0.8.0.3 — Skip on the left, a caption that says who, and a clock that stops
stretching Three things from playing v0.8.0.2, all about the row rather than the mechanism. Skip was on the far right and a player's eye is on the countdown. Moved to the left, in front of the count. The caption said what but never who. Measured over 40 turns of a real 3-seat game, half the waiting is automatic phases — 21.0s of phases against 21.7s of other players — and a phase narrates as "Mainline", which is accurate and no answer at all to "who am I waiting on". A phase introduces itself now: "The Division: Mainline phase". A player's move already carries its name from record(), so it is left alone. The row was also hiding one step early, because it showed only while behind > 0 — which goes false exactly when the last step of a burst goes up, so the step most likely to be read lost its caption. And the speed control was stretching the clock along with the players. It was not his own move being replayed — own moves have cost nothing since v0.8.0.1 — it was the phases behind it, which put 105 seconds of clock-ticking into a 5x game. pace now scales a player's move and leaves a phase at its tabled beat, which is what the control has always claimed to do. Off still means off for both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6
This commit is contained in:
co-authored by
Claude Opus 5
parent
c10f52791e
commit
ff629c0708
+19
-5
@@ -182,10 +182,25 @@ export function dwellFor(cause: StepCause, pace = 1): number {
|
||||
* have to import `DisplayStep` back from the module that imports `StepCause` from it.
|
||||
*/
|
||||
export function dwellForStep(
|
||||
step: { cause: StepCause; lines: readonly unknown[]; frame: { table: object } },
|
||||
step: { cause: StepCause; player: number | null; lines: readonly unknown[]; frame: { table: object } },
|
||||
pace = 1,
|
||||
): number {
|
||||
if (step.lines.length > 0) return dwellFor(step.cause, pace);
|
||||
// Off means off, for the clock as much as for anybody's move.
|
||||
if (pace <= 0) return 0;
|
||||
/**
|
||||
* THE SPEED CONTROL IS ABOUT OTHER PEOPLE, NOT ABOUT THE CLOCK.
|
||||
*
|
||||
* A phase keeps its tabled beat at every speed. Measured over 40 turns of a real 3-seat game, the
|
||||
* waiting split almost evenly — 21.0s of other players against 21.0s of phases turning over — so
|
||||
* scaling both put 105 seconds of clock-ticking into a 5× game, all of it after the player's own
|
||||
* move and none of it anything to watch. Jesse, from that game: *"after my turn, when I actually
|
||||
* execute my turn, I'm still subject to that same delay before it moves on. That makes no sense."*
|
||||
*
|
||||
* The phase still gets its beat (TODO #18) — it just does not get longer because somebody wanted
|
||||
* to watch a bot shunt cars.
|
||||
*/
|
||||
const speed = step.player === null ? 1 : pace;
|
||||
if (step.lines.length > 0) return dwellFor(step.cause, speed);
|
||||
/**
|
||||
* A SILENT STEP EARNS A BEAT ONLY WHEN THE CLOCK TURNED OVER — which is TODO #18 exactly: "give
|
||||
* every phase a visible beat", for New Train, the Mainline and the shift change.
|
||||
@@ -194,12 +209,11 @@ export function dwellForStep(
|
||||
* silently killed #18: a phase can move trains without saying anything, and those steps were being
|
||||
* flashed past. "Anything that changed the board" is wrong the other way: `submit()` steps
|
||||
* `advance()` about 4.6 times per intent and most of those merely hand the turn on, so beating on
|
||||
* all of them would cost a quarter of an hour a game. The phase turning over is the thing a player
|
||||
* is being shown, and there are about 180 of those in a full game.
|
||||
* all of them would cost a quarter of an hour a game.
|
||||
*/
|
||||
const table = step.frame.table as Record<string, unknown>;
|
||||
const turned = 'phase' in table || 'phaseKey' in table || 'stage' in table || 'day' in table;
|
||||
return turned ? dwellFor(step.cause, pace) : 0;
|
||||
return turned ? dwellFor(step.cause, speed) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+34
-7
@@ -201,17 +201,23 @@ function drainIntoQueue(): void {
|
||||
* drawn — from `PublicDistrict`, which `officeSvg` can render as-is because it takes board data and
|
||||
* has never needed a private viewer.
|
||||
*/
|
||||
function renderWatching(): void {
|
||||
function renderWatching(f?: Frame): void {
|
||||
const behind = stepQueue.behind();
|
||||
const row = $('watching');
|
||||
// Collapsed whenever the board is level with the game — which in solitaire is nearly always, and
|
||||
// between turns in multiplayer too. A row that is always there would be a row nobody reads.
|
||||
if (behind === 0) {
|
||||
/**
|
||||
* VISIBLE WHILE THE BOARD IS BEHIND **OR** STILL SHOWING SOMETHING.
|
||||
*
|
||||
* It used to hide the moment `behind` hit zero — which is the moment the LAST step of a burst goes
|
||||
* up, so the one step a player was most likely to be reading about lost its caption. Collapsed
|
||||
* otherwise: in solitaire that is nearly always, and between turns in multiplayer too, and a row
|
||||
* that is always there is a row nobody reads.
|
||||
*/
|
||||
if (behind === 0 && !stepQueue.busy()) {
|
||||
row.hidden = true;
|
||||
return;
|
||||
}
|
||||
row.hidden = false;
|
||||
$('watching-behind').textContent = `${behind} behind`;
|
||||
$('watching-behind').textContent = behind === 0 ? 'catching up' : `${behind} behind`;
|
||||
/**
|
||||
* THE CAPTION IS #15, and this is where that item lands rather than as a line of its own.
|
||||
*
|
||||
@@ -220,8 +226,29 @@ function renderWatching(): void {
|
||||
* The queue IS that, so the caption simply names the step being shown, and the counter beside it
|
||||
* says how much of the wait is left.
|
||||
*/
|
||||
/**
|
||||
* WHO, THEN WHAT — Jesse, 2026-09-09: *"it didn't tell me what the actual action was, like who I
|
||||
* was waiting on or what they were doing. I knew I was behind, but I wasn't sure what I was
|
||||
* supposed to be looking for."*
|
||||
*
|
||||
* The caption was there; it was the wrong half of the sentence. Half the waiting is automatic
|
||||
* phases, whose narration reads "Mainline" — accurate, and no answer at all to "who am I waiting
|
||||
* on". So the name goes first, and a phase says so in as many words rather than leaving the reader
|
||||
* to infer that nobody is acting.
|
||||
*
|
||||
* The narrated line is used as it stands otherwise, because `record()` already prefixes it with the
|
||||
* player — "Player Bot 1 moved Train 3 (−1,−2) → (−1,1)" — so a second name would stutter.
|
||||
*/
|
||||
const showing = stepQueue.showing();
|
||||
$('watching-what').textContent = showing?.lines[0]?.text ?? '';
|
||||
const said = showing?.lines[0]?.text ?? '';
|
||||
const who =
|
||||
showing === null || showing === undefined
|
||||
? ''
|
||||
: showing.player === null
|
||||
? 'The Division'
|
||||
: (f?.players[showing.player]?.name ?? `Seat ${seatLabel(showing.player)}`);
|
||||
// A player action already names its actor; a phase does not, so it is introduced.
|
||||
$('watching-what').textContent = showing?.player === null && said !== '' ? `${who}: ${said}` : said;
|
||||
/**
|
||||
* SKIP COSTS THE ANIMATION AND NEVER THE INFORMATION.
|
||||
*
|
||||
@@ -1264,7 +1291,7 @@ function render(): void {
|
||||
|
||||
renderTurnChart(f);
|
||||
renderPresence(f);
|
||||
renderWatching();
|
||||
renderWatching(f);
|
||||
$('revenue').textContent = String(f.revenue);
|
||||
/**
|
||||
* THE OBJECTIVE, WITHOUT THE COMMENTARY.
|
||||
|
||||
+5
-1
@@ -125,6 +125,7 @@ section{background:var(--panel);border:1px solid var(--line);border-radius:7px;
|
||||
.wbehind{font-variant-numeric:tabular-nums;font-weight:700;color:#c9cee0;
|
||||
background:#22263a;border:1px solid #343a52;border-radius:10px;padding:1px 8px;white-space:nowrap}
|
||||
#watching-skip{font-size:11px;padding:2px 10px;border-color:#e0b060;color:#e0b060;flex:none}
|
||||
#watching-who{color:#c9cee0;font-weight:700}
|
||||
#presence:empty{display:none}
|
||||
/* division strip */
|
||||
#division{display:flex;gap:7px;overflow-x:auto;padding-bottom:4px}
|
||||
@@ -914,9 +915,12 @@ ul.blocked li{padding:2px 0}
|
||||
and Skip. Empty and collapsed whenever the board is level with the game, which in solitaire is
|
||||
almost always. -->
|
||||
<div id="watching" hidden>
|
||||
<!-- SKIP FIRST, on the left. It sat on the far right and a player's eye is on the countdown, not at
|
||||
the other end of the row — Jesse, 2026-09-09: "the skip button should be on the far left, in
|
||||
front of where it says [the count], so it's always close to where people are looking." -->
|
||||
<button id="watching-skip" class="ghost" type="button" title="Stop animating and jump the board to where the game actually is. Nothing is lost — every line is already in the History panel.">Skip</button>
|
||||
<span id="watching-behind" class="wbehind"></span>
|
||||
<span id="watching-what"></span>
|
||||
<button id="watching-skip" class="ghost" type="button" title="Stop animating and jump the board to where the game actually is. Nothing is lost — every line is already in the History panel.">Skip</button>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
|
||||
Reference in New Issue
Block a user