Compare commits

...
2 Commits
Author SHA1 Message Date
Jesse.MarkowitzandClaude Opus 5 fc40fc39ed v0.8.0.4 — take the test server's name back out of the tracked files
Both repositories allow anonymous clone — checked rather than assumed: info/refs
for git-upload-pack answers 200 for each, git-receive-pack answers 401. So
everything committed here is public, and tracked files are supposed to carry
placeholders rather than real hosts.

Ten mentions added while building v0.8.0 are now "the test server" or "the target
hardware", across CHANGELOG.md, the common-board plan's three deferral banners,
sim/pacing.ts, and two test files. Prose and comments only, no behaviour; the
quotes are untouched, because what was said about bot pacing is the part worth
keeping.

Left alone deliberately: nineteen older mentions in entries about v0.7.5, v0.7.6
and v0.7.8 and in TODO.md, since rewriting a changelog after the fact makes the
record less true; and scripts/deploy-web.ts, where the host is the functional
default for FB_URL rather than prose — turning that into a required variable
changes how deploying works and wants deciding on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6
2026-09-09 21:43:06 -04:00
Jesse.MarkowitzandClaude Opus 5 ff629c0708 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
2026-09-09 21:06:22 -04:00
8 changed files with 160 additions and 25 deletions
+73 -2
View File
@@ -19,9 +19,80 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
--- ---
## 0.8.0.4 — 2026-09-09
**Housekeeping: the test server's name is out of the ten places this session put it.**
Both of this project's repositories allow anonymous clone — checked, not assumed: `info/refs` for
`git-upload-pack` answers 200 for `station-master` and for `station-master-startos` alike, while
`git-receive-pack` answers 401. So everything committed here is public, and the standing rule is that
tracked files carry placeholders rather than real hosts.
Ten mentions added while building v0.8.0 are now "the test server" or "the target hardware":
`CHANGELOG.md`, `docs/plans/jitsi-common-board.md` (three identical deferral banners), `sim/pacing.ts`,
`test/pacing.test.ts` and `test/step-queue.test.ts`. Prose and comments only — no behaviour, and the
quotes they carry are unchanged, because what a player said about bot pacing is the part worth
keeping.
**What is deliberately left, and why it is not an oversight:**
- **Nineteen older mentions**, in entries about v0.7.5, v0.7.6 and v0.7.8 and in `TODO.md`. Rewriting
a changelog after the fact makes the record less true, and these describe verification that
genuinely happened on that machine.
- **`scripts/deploy-web.ts` is FUNCTIONAL, not prose.** It carries the host as the default for
`FB_URL`, so a placeholder there would break the deploy for the person the default exists to serve.
Same for the public address it publishes to. If those should move to required environment variables
with no default, that is a change to how deploying works and wants deciding on its own rather than
being smuggled in beside a comment sweep.
---
## 0.8.0.3 — 2026-09-09
Three things from playing v0.8.0.2, all of them about the row rather than the mechanism.
### Skip was at the wrong end of the row
Jesse: *"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."* It was on the far right, and a player's eye is on the
countdown. Moved.
### The caption said what, but never who
*"I saw 2 behind, 1 behind, and then it was caught up, but 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. **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 now introduces itself: **"The Division: ▸ Mainline phase"**. A player's move already carries
its name from `record()`, so it is left alone rather than stuttering it twice.
**And the row was hiding a step early.** It was shown only while `behind > 0` — which goes false 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. It now stays up while the queue is still showing something, and reads "catching up"
once nothing is queued behind.
### The speed control was stretching the clock, not just the other players
*"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. Since I've just done my turn, I don't need to wait after it."*
He was right, and it was not his move being replayed — own moves have cost nothing since v0.8.0.1. It
was the automatic phases behind it, which were scaling with `pace` along with everything else. At 5×
that put **105 seconds of clock-ticking** into the game, all of it after a player's own move and none
of it anything to watch.
**`pace` now scales a player's move and leaves a phase at its tabled beat.** The control is labelled
as how long another player's move is held, and that is now what it does. A phase still gets its beat
(TODO #18) and still vanishes entirely at `pace = 0`, because off has to mean off.
---
## 0.8.0.2 — 2026-09-09 ## 0.8.0.2 — 2026-09-09
Two things found by playing v0.8.0.1 on `phoenix.local`, neither of them in the mechanism itself. Two things found by playing v0.8.0.1 on the test server, neither of them in the mechanism itself.
### `?pace=` never worked, and a whole game was played at the wrong speed ### `?pace=` never worked, and a whole game was played at the wrong speed
@@ -93,7 +164,7 @@ the suite until it was declared out loud.
## 0.8.0.1 — 2026-09-09 ## 0.8.0.1 — 2026-09-09
**Bot play was way too fast.** v0.8.0 was installed on `phoenix.local` and played within the hour; **Bot play was way too fast.** v0.8.0 was installed on the test server and played within the hour;
Jesse: *"I briefly saw that it was the bot's office area then their turn was done and it pointed back Jesse: *"I briefly saw that it was the bot's office area then their turn was done and it pointed back
to my office area"*, and the countdown row appeared "very briefly". Everything else looked right — to my office area"*, and the countdown row appeared "very briefly". Everything else looked right —
the bots were visibly doing things — so this is calibration and one real bug, not a redesign. the bots were visibly doing things — so this is calibration and one real bug, not a redesign.
+3 -3
View File
@@ -888,7 +888,7 @@ Cover:
## Step 5 — Minimal visual-only Jitsi engine ## Step 5 — Minimal visual-only Jitsi engine
> **v0.9.0 — DEFERRED (2026-09-09).** Steps 5-7 are the Jitsi publisher and are explicitly held > **v0.9.0 — DEFERRED (2026-09-09).** Steps 5-7 are the Jitsi publisher and are explicitly held
> off until 0.8.0 ships and Chromium has been measured on `phoenix.local`. Nothing below has been > off until 0.8.0 ships and Chromium has been measured on the target hardware. Nothing below has been
> re-verified against the current code; it was written 2026-08-27. Two things will have moved by > re-verified against the current code; it was written 2026-08-27. Two things will have moved by
> the time it is picked up: the control channel is **SSE + POST, not a WebSocket** (§ THE RELEASE > the time it is picked up: the control channel is **SSE + POST, not a WebSocket** (§ THE RELEASE
> SPLIT), and adding Chromium takes the `.s9pk` from 63 MB to several hundred against a package > SPLIT), and adding Chromium takes the `.s9pk` from 63 MB to several hundred against a package
@@ -1028,7 +1028,7 @@ Port/adapt the sibling repository’s proven tests for:
## Step 6 — Chromium publisher supervisor ## Step 6 — Chromium publisher supervisor
> **v0.9.0 — DEFERRED (2026-09-09).** Steps 5-7 are the Jitsi publisher and are explicitly held > **v0.9.0 — DEFERRED (2026-09-09).** Steps 5-7 are the Jitsi publisher and are explicitly held
> off until 0.8.0 ships and Chromium has been measured on `phoenix.local`. Nothing below has been > off until 0.8.0 ships and Chromium has been measured on the target hardware. Nothing below has been
> re-verified against the current code; it was written 2026-08-27. Two things will have moved by > re-verified against the current code; it was written 2026-08-27. Two things will have moved by
> the time it is picked up: the control channel is **SSE + POST, not a WebSocket** (§ THE RELEASE > the time it is picked up: the control channel is **SSE + POST, not a WebSocket** (§ THE RELEASE
> SPLIT), and adding Chromium takes the `.s9pk` from 63 MB to several hundred against a package > SPLIT), and adding Chromium takes the `.s9pk` from 63 MB to several hundred against a package
@@ -1161,7 +1161,7 @@ Use fake child processes and fake control sockets to test:
## Step 7 — Configuration, lifecycle, packaging, and observability ## Step 7 — Configuration, lifecycle, packaging, and observability
> **v0.9.0 — DEFERRED (2026-09-09).** Steps 5-7 are the Jitsi publisher and are explicitly held > **v0.9.0 — DEFERRED (2026-09-09).** Steps 5-7 are the Jitsi publisher and are explicitly held
> off until 0.8.0 ships and Chromium has been measured on `phoenix.local`. Nothing below has been > off until 0.8.0 ships and Chromium has been measured on the target hardware. Nothing below has been
> re-verified against the current code; it was written 2026-08-27. Two things will have moved by > re-verified against the current code; it was written 2026-08-27. Two things will have moved by
> the time it is picked up: the control channel is **SSE + POST, not a WebSocket** (§ THE RELEASE > the time it is picked up: the control channel is **SSE + POST, not a WebSocket** (§ THE RELEASE
> SPLIT), and adding Chromium takes the `.s9pk` from 63 MB to several hundred against a package > SPLIT), and adding Chromium takes the `.s9pk` from 63 MB to several hundred against a package
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "station-master", "name": "station-master",
"version": "0.8.0.2", "version": "0.8.0.4",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Station Master — a railroad operations game", "description": "Station Master — a railroad operations game",
+20 -6
View File
@@ -54,7 +54,7 @@ export const DWELL: Record<StepKind, number> = {
* *
* WAS 250ms, WHICH WAS WRONG, and wrong in the way that mattered most: an early-game bot turn has * WAS 250ms, WHICH WAS WRONG, and wrong in the way that mattered most: an early-game bot turn has
* no switching in it at all, so it was six steps of 250ms and 0ms — **750ms for a whole turn**. * no switching in it at all, so it was six steps of 250ms and 0ms — **750ms for a whole turn**.
* Jesse, from the first real play on `phoenix.local`: *"bot play was way too fast. I briefly saw * Jesse, from the first real play on the test server: *"bot play was way too fast. I briefly saw
* that it was the bot's office area then their turn was done."* His instruction had been "start at * that it was the bot's office area then their turn was done."* His instruction had been "start at
* 1s and tune down", and that was applied only to switching while this number was invented. * 1s and tune down", and that was applied only to switching while this number was invented.
*/ */
@@ -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. * have to import `DisplayStep` back from the module that imports `StepCause` from it.
*/ */
export function dwellForStep( 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, pace = 1,
): number { ): 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 * 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. * 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 * 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 * 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 * `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 * all of them would cost a quarter of an hour a game.
* is being shown, and there are about 180 of those in a full game.
*/ */
const table = step.frame.table as Record<string, unknown>; const table = step.frame.table as Record<string, unknown>;
const turned = 'phase' in table || 'phaseKey' in table || 'stage' in table || 'day' in table; 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
View File
@@ -201,17 +201,23 @@ function drainIntoQueue(): void {
* drawn — from `PublicDistrict`, which `officeSvg` can render as-is because it takes board data and * drawn — from `PublicDistrict`, which `officeSvg` can render as-is because it takes board data and
* has never needed a private viewer. * has never needed a private viewer.
*/ */
function renderWatching(): void { function renderWatching(f?: Frame): void {
const behind = stepQueue.behind(); const behind = stepQueue.behind();
const row = $('watching'); 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. * VISIBLE WHILE THE BOARD IS BEHIND **OR** STILL SHOWING SOMETHING.
if (behind === 0) { *
* 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; row.hidden = true;
return; return;
} }
row.hidden = false; 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. * 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 * 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. * 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(); 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. * SKIP COSTS THE ANIMATION AND NEVER THE INFORMATION.
* *
@@ -1264,7 +1291,7 @@ function render(): void {
renderTurnChart(f); renderTurnChart(f);
renderPresence(f); renderPresence(f);
renderWatching(); renderWatching(f);
$('revenue').textContent = String(f.revenue); $('revenue').textContent = String(f.revenue);
/** /**
* THE OBJECTIVE, WITHOUT THE COMMENTARY. * THE OBJECTIVE, WITHOUT THE COMMENTARY.
+5 -1
View File
@@ -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; .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} 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-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} #presence:empty{display:none}
/* division strip */ /* division strip */
#division{display:flex;gap:7px;overflow-x:auto;padding-bottom:4px} #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 and Skip. Empty and collapsed whenever the board is level with the game, which in solitaire is
almost always. --> almost always. -->
<div id="watching" hidden> <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-behind" class="wbehind"></span>
<span id="watching-what"></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> </div>
<main> <main>
+23 -4
View File
@@ -52,7 +52,7 @@ describe('pacing — dwell by kind', () => {
assert.equal(kindOf('switch.end'), 'bookkeeping'); assert.equal(kindOf('switch.end'), 'bookkeeping');
/** /**
* `localOps.choose` IS AN ANNOUNCEMENT, not bookkeeping — moved 2026-09-09 after the first real * `localOps.choose` IS AN ANNOUNCEMENT, not bookkeeping — moved 2026-09-09 after the first real
* play on `phoenix.local`. It is the line reading "Player Bot 1 chose to SWITCH", the heading for * play on the test server. It is the line reading "Player Bot 1 chose to SWITCH", the heading for
* everything that follows, and at zero dwell a bot's turn began with no sign of what it was about * everything that follows, and at zero dwell a bot's turn began with no sign of what it was about
* to do. * to do.
*/ */
@@ -152,18 +152,37 @@ describe('pacing — dwell by kind', () => {
* that changed the board" beat on every turn hand-off — `submit()` steps `advance()` about 4.6 * that changed the board" beat on every turn hand-off — `submit()` steps `advance()` about 4.6
* times per intent — which came to a quarter of an hour a game. * times per intent — which came to a quarter of an hour a game.
*/ */
const silent = { cause: 'phase' as const, lines: [] as string[] }; const silent = { cause: 'phase' as const, player: null, lines: [] as string[] };
assert.equal(dwellForStep({ ...silent, frame: { table: { actor: 2 } } }), 0, 'a turn hand-off shows nothing'); assert.equal(dwellForStep({ ...silent, frame: { table: { actor: 2 } } }), 0, 'a turn hand-off shows nothing');
assert.equal(dwellForStep({ ...silent, frame: { table: {} } }), 0, 'a step that changed nothing shows nothing'); assert.equal(dwellForStep({ ...silent, frame: { table: {} } }), 0, 'a step that changed nothing shows nothing');
assert.equal(dwellForStep({ ...silent, frame: { table: { phase: 'mainline' } } }), DWELL.phase); assert.equal(dwellForStep({ ...silent, frame: { table: { phase: 'mainline' } } }), DWELL.phase);
assert.equal(dwellForStep({ ...silent, frame: { table: { stage: 4 } } }), DWELL.phase); assert.equal(dwellForStep({ ...silent, frame: { table: { stage: 4 } } }), DWELL.phase);
// Narration always earns the dwell of whatever caused it, clock or no clock. // Narration always earns the dwell of whatever caused it, clock or no clock.
assert.equal( assert.equal(
dwellForStep({ cause: 'switch.move', lines: ['moved'], frame: { table: {} } }), dwellForStep({ cause: 'switch.move', player: 1, lines: ['moved'], frame: { table: {} } }),
DWELL.switching, DWELL.switching,
); );
}); });
it('the speed control stretches other people, not the clock', () => {
/**
* Jesse, from a real 5× 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."* It was not his move being
* replayed — it was the automatic phases behind it, which were scaling with `pace` along with
* everything else. Measured over 40 turns, the waiting split almost evenly between other players
* and phases turning over, so a 5× game spent 105 seconds on the clock alone.
*/
const phase = { cause: 'phase' as const, player: null, lines: ['New Train'], frame: { table: { phase: 'newTrain' } } };
const theirs = { cause: 'switch.move' as const, player: 1, lines: ['moved'], frame: { table: {} } };
for (const pace of [1, 3, 5, 7]) {
assert.equal(dwellForStep(phase, pace), DWELL.phase, `a phase beat grew at ${pace}x`);
assert.equal(dwellForStep(theirs, pace), DWELL.switching * pace);
}
// Off still means off, for the clock as much as for anybody's move.
assert.equal(dwellForStep(phase, 0), 0);
assert.equal(dwellForStep(theirs, 0), 0);
});
it('a real switching turn is watchable in a few seconds, not tens of them', () => { it('a real switching turn is watchable in a few seconds, not tens of them', () => {
// Six moves is the engine's cap per crew ("N of 6 Moves left"), so this is the worst ordinary // Six moves is the engine's cap per crew ("N of 6 Moves left"), so this is the worst ordinary
// case for one crew: the announcement, six moves, and an end that shows nothing. // case for one crew: the announcement, six moves, and an end that shows nothing.
@@ -180,7 +199,7 @@ describe('pacing — dwell by kind', () => {
it("a bot's ordinary turn is followable, which is what the first real play was not", () => { it("a bot's ordinary turn is followable, which is what the first real play was not", () => {
/** /**
* MEASURED FROM A REAL GAME, then pinned. Jesse, after installing v0.8.0 on `phoenix.local`: * MEASURED FROM A REAL GAME, then pinned. Jesse, after installing v0.8.0 on the test server:
* *"bot play was way too fast. I briefly saw that it was the bot's office area then their turn * *"bot play was way too fast. I briefly saw that it was the bot's office area then their turn
* was done."* This is the shape that turn actually had — no switching in it at all, because * was done."* This is the shape that turn actually had — no switching in it at all, because
* switching is not legal until there is track down — and under the original values it came to * switching is not legal until there is track down — and under the original values it came to
+1 -1
View File
@@ -179,7 +179,7 @@ describe('the step queue', () => {
* REGRESSION. `busy()` was `pending.length > 0`, so the instant the final step of a burst was * REGRESSION. `busy()` was `pending.length > 0`, so the instant the final step of a burst was
* shown the queue reported idle: the animation loop stopped and the district panel snapped back * shown the queue reported idle: the animation loop stopped and the district panel snapped back
* to the viewer's own board without that step ever being looked at. Jesse, from the first real * to the viewer's own board without that step ever being looked at. Jesse, from the first real
* play on `phoenix.local`: *"I briefly saw that it was the bot's office area then their turn was * play on the test server: *"I briefly saw that it was the bot's office area then their turn was
* done and it pointed back to my office area"*, and the countdown row appeared "very briefly". * done and it pointed back to my office area"*, and the countdown row appeared "very briefly".
* *
* The panel follows `busy()`, so this is the property that keeps somebody else's board on screen * The panel follows `busy()`, so this is the property that keeps somebody else's board on screen