v0.8.0.7 — the Salvage Yard had nothing to say, and phases too little time to read
The Salvage Yard was face up all along; its tile just read "a card". apply.ts pushes a synthetic train-<n> id on trainScheduled, nothing in s.cards matches it, and cardName fell through to its default — and since a train is scheduled several times a Day that id is on top most of the time. Measured before touching anything: the tile read "a card" from the opening frame through 60 pushes while its depth climbed from 2 to 8. cardName resolves it now, in sim/view.ts, because this is a name. The engine half is filed as Gitea#23 rather than fixed here. reshuffleIfDepleted sweeps the Salvage Yard back into the draw deck, so that synthetic id can be shuffled in and drawn into a hand as an id with no card behind it. Eight games driven to 4000 moves across eight seeds produced zero reshuffles, so it is latent; there are two defensible fixes and the choice turns on what the synthetic id is for, which is not a call to make while fixing a label. And phases scale with the speed control again, damped to a third of the rate. They were pinned in v0.8.0.3 because scaling them walled off a player's own turn; pinned turns out to be too short to read at 10x. Damped satisfies both: 1x unchanged, 10x lands exactly on the four-times guess. Bounded because phase beats cluster rather than accumulate — 1.0 per push on average, 4 at worst, so the wait after a move is ~2.4s typical. 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
64e8ce584f
commit
d0e5091824
+23
-1
@@ -177,6 +177,28 @@ export function dwellFor(cause: StepCause, pace = 1): number {
|
||||
return Math.round(DWELL[kindOf(cause)] * Math.min(MAX_PACE, Math.max(0, pace)));
|
||||
}
|
||||
|
||||
/**
|
||||
* How much of the speed control a PHASE gets — damped, not the full multiplier.
|
||||
*
|
||||
* Phases were pinned at their tabled beat in v0.8.0.3, because scaling them with everything else put
|
||||
* a wall of clock-ticking after a player's own move. That was right about the cost and wrong about
|
||||
* the need: at 10× the caption row goes past faster than the sentence on it can be read. Jesse,
|
||||
* 2026-09-10: *"phases displayed on the upper line go by too quickly still. Should be 4 times as
|
||||
* long — at a guess. Maybe use the speed multiplier for that too?"*
|
||||
*
|
||||
* So they scale, at a third of the rate. That lands exactly on his guess — 10× gives a phase four
|
||||
* times its tabled beat — while leaving 1× untouched, and it stays affordable because phase beats
|
||||
* cluster rather than accumulate: measured over 60 pushes, a push carries **1.0 phase beat on
|
||||
* average and 4 at worst**, so the wait after a move goes to ~2.4s typical and ~10s at its very
|
||||
* worst rather than the minutes a full multiplier would have cost.
|
||||
*
|
||||
* Below 1× it simply follows the multiplier: somebody asking for everything faster means the phases
|
||||
* too.
|
||||
*/
|
||||
function phaseSpeed(pace: number): number {
|
||||
return pace <= 1 ? pace : 1 + (pace - 1) / 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long to show one STEP — the form the queue actually uses.
|
||||
*
|
||||
@@ -204,7 +226,7 @@ export function dwellForStep(
|
||||
* 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;
|
||||
const speed = step.player === null ? phaseSpeed(pace) : 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
|
||||
|
||||
@@ -1791,6 +1791,23 @@ export function snapshot(
|
||||
|
||||
/** A card id turned into something a person can read. */
|
||||
export function cardName(s: GameState, id: string): string {
|
||||
/**
|
||||
* A SCHEDULED TRAIN IS IN THE SALVAGE YARD UNDER A SYNTHETIC ID, and without this the pile that is
|
||||
* supposed to be face up reads "a card".
|
||||
*
|
||||
* `trainScheduled` pushes `train-<number>` rather than the id of the card that was played
|
||||
* (`apply.ts`), so there is nothing in `s.cards` to look up — and since a train is scheduled
|
||||
* several times a Day, that synthetic id is on top of the Salvage Yard most of the time. Reported
|
||||
* by Jesse 2026-09-10 as "why is salvage deck not face up. I should see the card played onto
|
||||
* salvage": it was face up all along and simply had nothing to say.
|
||||
*
|
||||
* Resolved here rather than in the engine because this is a NAME, which is this file's job. Whether
|
||||
* the engine should be pushing a real card id instead is a separate question with a separate
|
||||
* consequence — the Salvage Yard is swept back into the draw deck when it runs out — and is filed
|
||||
* rather than answered in passing.
|
||||
*/
|
||||
const scheduled = /^train-(\d+)$/.exec(id);
|
||||
if (scheduled) return `Train ${scheduled[1]}`;
|
||||
const k = s.cards.get(id)?.kind;
|
||||
if (!k) return 'a card';
|
||||
switch (k.kind) {
|
||||
|
||||
Reference in New Issue
Block a user