A scheduled train sits in the Salvage Yard under a synthetic id, and the Yard is swept back into the draw deck #23

Closed
opened 2026-09-10 10:16:07 +00:00 by Jesse.Markowitz · 0 comments
Owner

Found while fixing the Salvage tile for v0.8.0.7. The display half is fixed; this is the engine half, which is a rules question rather than a display one.

What happens

apply.ts, on trainScheduled:

s.decks.salvageYard.push(`train-${e.trainNumber}`);

That is a synthetic id, not the id of the card that was played. Nothing in s.cards matches it.

The immediate symptom was cosmetic and is now handled in sim/view.ts: cardName() resolves train-<n> to Train <n>, so the face-up pile names its top card instead of reading "a card". A test pins it.

The part that is not cosmetic

reshuffleIfDepleted() sweeps the Salvage Yard back into the Home Office deck when it runs out:

const collected = [...s.decks.salvageYard, ...s.decks.departments.flat()];

So a synthetic train-<n> can be shuffled into the draw deck and then drawn into a hand, where it is an id with no card behind it. What keepReason, handPlayable and playing it would do with that has not been established.

How reachable

Not, in ordinary play. Eight games driven to 4000 moves each across eight seeds produced zero reshuffles — the deck does not run out in a normal game. So this is latent rather than live, which is why it is filed rather than fixed.

Two defensible fixes, hence the issue

  1. Push the real card id on trainScheduled, so the Yard holds only real cards. Needs the event to carry it, and changes what a replay reconstructs.
  2. Filter synthetics in reshuffleIfDepleted, leaving the id as a marker that a train was scheduled. Smaller, but keeps a non-card in a card pile.

Which is right depends on what the synthetic id is for — whether the Salvage Yard is meant to record scheduled trains at all, or whether the played train card should be going there like any other played card. That is the call to make first.

Found while fixing the Salvage tile for v0.8.0.7. The display half is fixed; this is the engine half, which is a rules question rather than a display one. ## What happens `apply.ts`, on `trainScheduled`: ```ts s.decks.salvageYard.push(`train-${e.trainNumber}`); ``` That is a **synthetic id**, not the id of the card that was played. Nothing in `s.cards` matches it. The immediate symptom was cosmetic and is now handled in `sim/view.ts`: `cardName()` resolves `train-<n>` to `Train <n>`, so the face-up pile names its top card instead of reading "a card". A test pins it. ## The part that is not cosmetic `reshuffleIfDepleted()` sweeps the Salvage Yard back into the Home Office deck when it runs out: ```ts const collected = [...s.decks.salvageYard, ...s.decks.departments.flat()]; ``` So a synthetic `train-<n>` can be shuffled into the draw deck and then **drawn into a hand**, where it is an id with no card behind it. What `keepReason`, `handPlayable` and playing it would do with that has not been established. ## How reachable Not, in ordinary play. Eight games driven to 4000 moves each across eight seeds produced **zero** reshuffles — the deck does not run out in a normal game. So this is latent rather than live, which is why it is filed rather than fixed. ## Two defensible fixes, hence the issue 1. **Push the real card id** on `trainScheduled`, so the Yard holds only real cards. Needs the event to carry it, and changes what a replay reconstructs. 2. **Filter synthetics in `reshuffleIfDepleted`**, leaving the id as a marker that a train was scheduled. Smaller, but keeps a non-card in a card pile. Which is right depends on what the synthetic id is *for* — whether the Salvage Yard is meant to record scheduled trains at all, or whether the played train card should be going there like any other played card. That is the call to make first.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Jesse.Markowitz/station-master#23