v0.4.7 — the switching game: track order, the cut on your own card, and four rules
Eight play reports and one design that had been written up and not built. The
through-line is switching: what a card can hold, which end of a train a cut comes
off, which way a train meets cars standing on the line, and what the board and the
log say about all of it.
TRACK ORDER FOR STANDING CARS, AND THE CUT ON YOUR OWN CARD
Two reports turned out to be one root cause. `TrackCard.standing` claimed "in track
order (§A.3)" and had no defined orientation at all, while `CrewTray.consist` does
(nose first, relative to facing) — so every transfer between them was a conversion
nothing performed. §A.3 says what it should be: cars occupy the track "in the same
order they originally held, left-to-right". Left-to-right is west-to-east, and that
is now the defined orientation of `standing` and of an industry track through
`carsOn`. It is the board's orientation, not the train's, so it does not change when
a different train touches the card.
- Setting out is batch-invariant. Four cars at once, four singles and two pairs
parked three different orders, one of them physically impossible. Successive
cuts off the same end stack up towards the engine, so the insertion point is the
train's own place in the row.
- Approaching a cut from either end now mirrors. `couples` is built nearest-first
along the direction of travel and reverses onto the nose, so the farthest car met
ends up nose-most — which is what makes a run-around worth its Move.
- A train no longer drives through its own cut. The walk began at the neighbour of
the start square and never read the start card, so a crew could set cars out and
pull straight away from them. Coupling is mandatory (§A.4) and your own square is
no exception; the cut counts against the four-car limit. Setting out off the end
you are not leaving by still works.
`CrewTray.standingWest` records where a train stands among the cars on its card — a
train may set out off both ends on one square, so which side a cut is on is not
recoverable from the array alone.
On the board, the cut is drawn split at the train — west cars left, east cars right,
engine in the gap — and each car's tooltip says whether it stands ahead of or behind
the engine. The history says which end a cut came off, and a move's button separates
"takes your own boxcar back off this card" from cars found standing on the line.
Decided: taking your own cut back on the square you are standing on is UNDOING the
drop. It is exempt from trains 3/4's per-location freight budget, X13's "drop but not
pick up" and X22's "empties only", and it refunds the budget the drop spent.
Otherwise a legal-looking drop becomes silently one-way.
Measured, 200 paired seeds, developer bot: -0.55 revenue (t = -3.63), freight revenue
1.11 -> 0.56. That cost is the bot's, not the rule's — its trains run engine-first,
so at a stub industry it sets a car out between itself and the only way out, and the
correct play is §A.5's facing-point move, which is the cross-turn planning TODO.md
already records as out of reach of any bot. Filtering self-recoupling moves out of its
options took recoupling from 625 of 1,029 set-outs to 101 of 677, and all 101 that
remain are that case. Read the number as a bot measurement, not a balance one.
THE SUPERINTENDENT'S RULING NAMES THE TRAINS IT IS ABOUT
Reported: the Superintendent could not tell which train he was clearing. The heading
asks the question now — "may Train 6 follow Train 4 onto the same Mainline card?" —
and the trains moved to the FRONT of each button, because the button splits its label
at the first em-dash and showed only the head.
AN INDUSTRY TRACK HOLDS FOUR CARS, LIKE EVERY OTHER CARD
Reported at undo 188: "we wanted to drop two cars, but were only allowed to drop one."
An industry track was built as long as its box count, so a one-box industry had room
for one car. Box count is how much WORK an industry can hold, not how much RAIL it
has. Ordinary track was the other exception, unbounded; both are gone and every card
holds four.
THE FREIGHT AGENT MAY STAGE A LOAD BEFORE THE CAR IS THERE
§6.3 asks nothing of the industry track — the empty car belongs to §9.3's Load the
car, which is the Laborer's action. The gate now lives only there, so cargo can wait
on the dock while the car to ship it in is still being switched in. Nothing can jam:
a load in a green box is waiting, not stuck.
THE TRUCK DOCK UNLOADS, AND BRINGS NOBODY
+1 inbound, no Laborer. It printed +1 outbound and +1 Laborer, which made it a
longer-host-list copy of Forklifts. Beside Packing Sheds it now does nothing at all,
and the hand tooltip says so before it is played.
Also in this release, from the days before: Mainline card tooltips computed from the
crossing rule; an Extra starts from the Division Point its number sends it to; a
modifier's suppressed grant comes back when a Whistle Post is upgraded; the Oil
Refinery and the Grocer's Warehouse ship as well as receive, per the card reference;
and the dormant defences name the attack they answer. `.claude/` is now gitignored —
it holds Claude Code's worktrees, i.e. a second checkout of this repository.
570 tests, typecheck clean. The three published replays were re-recorded twice —
legality changed, so bot play changed. Full detail in CHANGELOG.md.
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,58 @@
|
||||
# Station Master — Components and Markers
|
||||
|
||||
**Rules implementation reference: v0.4.5**
|
||||
**Scope:** non-card physical components and supplies modeled by the v0.4.5 game. Card-created facilities, workers, deck piles, hand state, timetable state, and other markers are documented with their cards or in the rules book.
|
||||
|
||||
## Rolling stock
|
||||
|
||||
Rolling stock has a type and a load state. In the interface, coloured cars are loaded and white cars are empty. The Division Yard starts with the following supply.
|
||||
|
||||
| Type | Loaded | Empty | Total | Use |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| Coach | 8 | 8 | 16 | Passenger work. |
|
||||
| Boxcar | 10 | 10 | 20 | Freight House outbound/inbound; Grocer’s Warehouse inbound. |
|
||||
| Hopper | 8 | 8 | 16 | Mine Tipple outbound; Power Plant inbound. |
|
||||
| Reefer | 5 | 5 | 10 | Packing Sheds outbound; Grocer’s Warehouse inbound. |
|
||||
| Tank car | 6 | 6 | 12 | Refinery outbound; Power Plant inbound. |
|
||||
| Caboose | 6 | 0 | 6 | Required or permitted at the rear of the applicable train cards. |
|
||||
| **Total** | **43** | **35** | **78** | |
|
||||
|
||||
The engine is not rolling stock and does not count against the four-car Crew Tray limit.
|
||||
|
||||
## The two yards
|
||||
|
||||
### Division Yard
|
||||
|
||||
The Division Yard is the active supply. It provides:
|
||||
|
||||
- loaded cars to a Freight Agent stocking a green outbound box;
|
||||
- empty cars for building up trains;
|
||||
- an empty coach when passengers detrain; and
|
||||
- an empty matching freight car when a loaded freight car begins unloading.
|
||||
|
||||
When a train completes a run or is destroyed, its caboose returns to the Division Yard. When a car is taken from the Division Yard and that yard has become entirely empty, every car from the Classification Yard moves back into the Division Yard immediately.
|
||||
|
||||
### Classification Yard
|
||||
|
||||
The Classification Yard collects used rolling stock: cars displaced by boarding passengers, cars cleared from inbound red boxes, cars removed by unjamming a facility, ordinary cars from completed or destroyed trains, and empty cars replaced by a completed outbound freight load. It is not a player-selectable source. It returns to service only when the Division Yard is empty.
|
||||
|
||||
## Crew Trays and trains
|
||||
|
||||
Crew Trays represent an engine and its coupled consist. The game creates **players + 3** free trays at setup. A tray is scarce: a due train without a free tray is held for that Stage, and Extras wait until one becomes free.
|
||||
|
||||
Within a tray, the engine can pull, push, or be between cars while switching. To depart an Office for the Mainline, it must be made up with the engine at an outer end; if it has a caboose, the caboose must be at the opposite outer end. A train may have at most four cars. A scheduled train can run under-strength but never with more than its card allows.
|
||||
|
||||
## Fedora
|
||||
|
||||
The **Fedora** is the physical marker for the Superintendent. The player with it resolves following-train clearance decisions and takes the 5-Revenue penalty for a Mainline collision caused by an unsafe clearance. The initial holder is the first player tied for the highest Superintendent setup D12 roll. The Fedora passes to the next seat to the left at each third-stage shift change.
|
||||
|
||||
## D12 and seeded randomness
|
||||
|
||||
The D12 is used by the engine for:
|
||||
|
||||
- the initial seating roll in a multi-player engine game;
|
||||
- the initial Superintendent roll;
|
||||
- a timetabled train’s timetable slot; and
|
||||
- all shuffled deck order and automatic setup selection through the same seeded random stream.
|
||||
|
||||
The rules engine uses a deterministic 32-bit seeded random generator. The same numeric seed, player configuration, house rules, and accepted intent sequence reconstruct the same game. A seed by itself is not enough when house rules differ.
|
||||
@@ -0,0 +1,155 @@
|
||||
# Station Master — Home Deck
|
||||
|
||||
**Rules implementation reference: v0.4.5**
|
||||
**Scope:** every card associated with the Home Office deck, including cards catalogued in the source but deliberately excluded from the dealt deck.
|
||||
|
||||
## Dealt card catalogue
|
||||
|
||||
The live Home Office deck contains **213 cards** in every currently supported mode. The rules for its setup, drawing, discarding, reshuffling, and hand limit are in the rules book, section 4.2.
|
||||
|
||||
| Active category | Cards |
|
||||
| --- | ---: |
|
||||
| Track | 96 |
|
||||
| Office upgrades | 14 |
|
||||
| Freight facilities | 27 |
|
||||
| Facility modifiers | 23 |
|
||||
| Train cards | 22 |
|
||||
| Enhancements | 18 |
|
||||
| Mainline modifiers | 7 |
|
||||
| Maneuvers | 6 |
|
||||
| **Total dealt** | **213** |
|
||||
|
||||
The catalogue also contains 12 space-use cards and 10 action cards. All 22 are excluded from every v0.4.5 dealt deck because their opponent-directed play rules are not implemented. They are listed at the end of this document for completeness.
|
||||
|
||||
## Track cards — 96
|
||||
|
||||
Track cards are ordinary Home Office cards, not a separate personal supply. A placed card must connect to existing rail. The Running Track is the horizontal row from Limit to Limit; cards there must carry an east–west through route. Placing track on a Limit extends the Running Track and moves that Limit outward.
|
||||
|
||||
| Card | Copies | Implemented placement facts |
|
||||
| --- | ---: | --- |
|
||||
| Straight track | 32 | East–west Operational Rail. |
|
||||
| Curved track, right | 16 | A 45° curve; can rotate 180°, but cannot flip. Right-hand geometry is fixed to the `ne_sw` diagonal. |
|
||||
| Curved track, left | 16 | A 45° curve; can rotate 180°, but cannot flip. Left-hand geometry is fixed to the `nw_se` diagonal. |
|
||||
| Turnout, right | 16 | East–west through route plus one 45° branch. It may be passed through but is not Operational Rail, so a train cannot end a Move on it. |
|
||||
| Turnout, left | 16 | Same operational rules; opposite fixed diagonal. |
|
||||
| Sharp curved track, right | 0 | Catalogued but not dealt. |
|
||||
| Sharp curved track, left | 0 | Catalogued but not dealt. |
|
||||
|
||||
A turnout may upgrade an existing straight, or a curve whose arc is exactly the turnout’s diverging arc. An upgrade is forbidden if the existing card holds standing cars or an enhancement. All other occupied squares are unavailable.
|
||||
|
||||
## Offices — 14
|
||||
|
||||
Every player begins at a Whistle Post, which is not drawn from the deck. Office cards are upgrades and must be played in sequence; they upgrade the existing Office rather than replacing its card or attached track.
|
||||
|
||||
| Card | Copies | A/D tracks | Porters | Passenger outbound/inbound slots | Other effect |
|
||||
| --- | ---: | ---: | ---: | ---: | --- |
|
||||
| Depot | 8 | 2 | 1 | 1 / 1 | Becomes a Control Point and Passenger Facility. |
|
||||
| Station | 4 | 3 | 2 | 2 / 2 | Upgrade Depot only; Control Point and Passenger Facility. |
|
||||
| Terminal | 2 | 4 | 3 | 3 / 3 | Upgrade Station only; Control Point and Passenger Facility. |
|
||||
|
||||
The Whistle Post has one A/D track, no porters, and no passenger slots. An Office upgrade preserves modifiers already applied to it.
|
||||
|
||||
## Freight facilities — 27
|
||||
|
||||
An industry may be placed only on a connected straight stub off the Running Track. Each facility begins with one Laborer and a three-box MEN | AT | WORK freight pipeline. Its industry track has capacity equal to its base outbound plus inbound capacity, with a minimum of one car.
|
||||
|
||||
No Office Area may contain a duplicate industry, or both ends of a listed lockout pair.
|
||||
|
||||
| Card | Copies | Cars handled | Flow | Base boxes | Lockout in same Office Area |
|
||||
| --- | ---: | --- | --- | --- | --- |
|
||||
| Freight House | 6 | Boxcar | Outbound and inbound | 1 out / 1 in | Freight House; Grocer’s Warehouse |
|
||||
| Mine Tipple | 6 | Hopper | Outbound | 1 out / 0 in | Power Plant |
|
||||
| Refinery | 3 | Tank car | Outbound | 1 out / 0 in | Power Plant |
|
||||
| Power Plant | 6 | Hopper or tank car | Inbound | 0 out / 1 in | Mine Tipple; Refinery |
|
||||
| Packing Sheds | 3 | Reefer | Outbound | 1 out / 0 in | Grocer’s Warehouse |
|
||||
| Grocer’s Warehouse | 3 | Boxcar or reefer | Inbound | 0 out / 1 in | Packing Sheds; Freight House |
|
||||
|
||||
## Facility modifiers — 23
|
||||
|
||||
A modifier occupies an empty square adjacent to an eligible facility. It is unique by modifier type within an Office Area. The implementation attaches it permanently to the first eligible adjacent facility found; it does not implement a per-Stage choice when one modifier touches more than one possible facility.
|
||||
|
||||
`+ out` adds green outbound capacity only where the host can load; `+ in` adds red inbound capacity only where the host can unload. A capacity increase at a freight facility also lengthens its industry track by the usable number of added slots. Worker increases always apply.
|
||||
|
||||
| Modifier | Copies | Eligible host | Actual grant |
|
||||
| --- | ---: | --- | --- |
|
||||
| Waiting Area | 3 | Any Office | +1 outbound passenger slot; +1 Porter |
|
||||
| Restaurant | 2 | Any Office | +1 outbound passenger slot; +1 Porter |
|
||||
| Hotel | 1 | Any Office | +1 outbound passenger slot; +1 Porter |
|
||||
| Truck Dock | 2 | Freight House, Packing Sheds, Grocer’s Warehouse | +1 **inbound** slot; no Laborer |
|
||||
| Railroad Express Agency | 1 | Freight House | +1 outbound slot; +1 Laborer |
|
||||
| Forklifts | 2 | Freight House, Packing Sheds | +1 outbound slot; +1 Laborer |
|
||||
| Prep Plant | 1 | Mine Tipple | +1 outbound slot; +1 Laborer |
|
||||
| Coal Piles | 1 | Mine Tipple | +1 outbound slot; +1 Laborer |
|
||||
| Conveyor Belts | 1 | Mine Tipple | +1 outbound slot; +1 Laborer |
|
||||
| Pipelines | 1 | Refinery | +1 outbound slot; +1 Laborer |
|
||||
| Oil Depot | 1 | Refinery | +1 outbound slot; +1 Laborer |
|
||||
| Viscosity Breakers | 1 | Refinery | +1 outbound slot; +1 Laborer |
|
||||
| Transmission Lines | 1 | Power Plant | +1 Laborer |
|
||||
| Rotary Dumps | 1 | Power Plant | +1 Laborer |
|
||||
| Steam Turbines | 1 | Power Plant | +1 Laborer |
|
||||
| Ice House | 2 | Packing Sheds or Grocer’s Warehouse | +1 outbound slot; +1 Laborer |
|
||||
| Local Small Groceries | 1 | Grocer’s Warehouse | +1 Laborer |
|
||||
|
||||
A bonus beside a facility that cannot use its direction is not usable and does not create a slot or lengthen the track — an outbound bonus beside an inbound-only facility, or the Truck Dock's inbound bonus beside the outbound-only Packing Sheds, which leaves that card with no effect at all. Likewise, passenger modifiers beside a Whistle Post add Porters but do not create an outbound slot until the Office becomes a Passenger Facility.
|
||||
|
||||
## Train cards — 22
|
||||
|
||||
Playing a timetabled train card rolls the seeded D12 and places its number in the first open timetable slot at or after the result, wrapping around the 12-slot chart. It then runs at that Stage every Day. An Extra is queued and made up when a Crew Tray becomes available; v0.4.5 automatically launches Extras eastbound from the Western Division Point.
|
||||
|
||||
The listed consist is a maximum, not a minimum: a train may depart with fewer cars, but must not exceed the listed categories, put a car behind a caboose, or leave with the engine buried among cars. A Crew Tray holds no more than four rolling-stock cars.
|
||||
|
||||
| Train | Speed | Direction | Listed maximum consist | Implemented special rule |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1/2 Crack Limited | Fast | 1 west / 2 east | 3 coaches | No switching; passenger work only at Terminals; expedited. |
|
||||
| 3/4 Express | Fast | 3 west / 4 east | 2 freight | May exchange at most one freight car at each grid location during its switching turn; expedited. |
|
||||
| 5/6 The Sparrow | Fast | 5 west / 6 east | 2 coaches | No switching; expedited. |
|
||||
| 7/8 Local | Slow | 7 west / 8 east | 1 freight, 1 coach | Its coach may not be set out during switching. |
|
||||
| 9/10 Heavy Freight | Slow | 9 west / 10 east | 3 freight, 1 caboose | — |
|
||||
| 11/12 Drag Freight | Slow | 11 west / 12 east | 2 freight, 1 caboose | — |
|
||||
| X13 Appleseed Extra | Slow | Player choice printed; v0.4.5 launches east | 3 empty freight, 1 caboose | May drop cars but cannot pick up. The engine enforces no pickup, but does not enforce the printed empties-only consist restriction at make-up. |
|
||||
| X14 Fruit Growers Express | Fast | Player choice printed; v0.4.5 launches east | 2 reefers, 1 caboose | Expedited. The code enforces reefers-only; the printed extra loaded-reefer pickup is not a separate rule. |
|
||||
| X15 Yard Xfer | Slow | Player choice printed; v0.4.5 launches east | 2 freight, 1 caboose | — |
|
||||
| X16 Light Engine Move | Fast | Player choice printed; v0.4.5 launches east | No cars | No switching. |
|
||||
| X17 Campaign Train | Fast | Player choice printed; v0.4.5 launches east | 1 coach | No switching. First Office arrival lays over for speeches; later arrivals are expedited. |
|
||||
| X18 Circus Train | Slow | Player choice printed; v0.4.5 launches east | 2 freight, 1 coach, 1 caboose | No switching. The first Mainline Phase in which it remains stopped awards its current Office’s player 1 Revenue. |
|
||||
| X19 Military Train | Slow | Player choice printed; v0.4.5 launches east | 1 freight, 2 coaches | No switching; no passenger work; expedited. |
|
||||
| X20 Director’s Private Car | Slow | Player choice printed; v0.4.5 launches east | 2 freight, 1 coach | No passenger work. |
|
||||
| X21 Freight Extra | Slow | Player choice printed; v0.4.5 launches east | 3 freight, 1 caboose | — |
|
||||
| X22 Pee-Dee | Slow | Player choice printed; v0.4.5 launches east | 1 caboose | May pick up empty cars only. |
|
||||
|
||||
## Enhancements — 18
|
||||
|
||||
| Card | Copies | Placement | v0.4.5 behavior |
|
||||
| --- | ---: | --- | --- |
|
||||
| Interlocking | 2 | Bare Running Track straight | When the Office is full, an inbound train is held at the Limits instead of colliding. |
|
||||
| Facing Point Locks | 2 | Any card; requires an Interlocking somewhere in the district | Blocks Derail. Derail is unavailable in v0.4.5, so this remains dormant. |
|
||||
| Yard Office | 1 | Bare Secondary Track straight | A coachless inbound train is diverted to this track instead of occupying an A/D track. |
|
||||
| Small Yard | 1 | Bare Secondary Track straight | A train may spend one switching Move here to reorder its entire consist and put the engine at the nose. |
|
||||
| Water Column | 2 | Bare Running Track straight | Removes a Watertower. Watertower cards are unavailable, so this remains dormant. |
|
||||
| Overpass | 1 | Any card | No implemented effect. |
|
||||
| Telegraph | 3 | Bare Running Track straight | Once per Day, the Superintendent may add 4 to an opposing train’s number when that makes a facing clearance safe. |
|
||||
| Telephone | 2 | On a Telegraph | Same dispatch mechanism, +8 once per Day. |
|
||||
| Radio | 2 | On a Telephone | Same dispatch mechanism, +12 once per Day. |
|
||||
| ABS Signals | 2 | Any Mainline card | Prevents rear-end collisions and holds a following train short. |
|
||||
|
||||
An enhancement requiring a bare straight cannot share that straight with another such enhancement. Telephone and Radio are the explicit stackable chain.
|
||||
|
||||
## Maneuvers — 6
|
||||
|
||||
| Card | Copies | Behavior |
|
||||
| --- | ---: | --- |
|
||||
| Red Flags | 5 | May be played at any time on a train stopped on a Mainline card. An approaching following train is held instead of moving into it. |
|
||||
| Flying Switch | 1 | During the owner’s switching option, spend one Move to roll a tail-end cut into a track-connected freight facility without moving the engine there. |
|
||||
| Poling | 0 | Catalogued but not dealt; no rule is implemented. |
|
||||
|
||||
## Catalogued but not dealt — 22 opponent-directed cards
|
||||
|
||||
### Space-use cards — 12
|
||||
|
||||
Bean House (1), Flop House (1), Watertower (1), Hobo Jungle (1), Section House (1), City Blocks (4), Engine Shops (1), Tenderloin District (1), and Engineer Cemetery (1) are excluded. Their source descriptions say they consume table space; Hobo Jungle additionally describes vandalism looting a passing boxcar. No placement or effect is available in v0.4.5.
|
||||
|
||||
### Action cards — 10
|
||||
|
||||
Derail (2), Broken Coupler (1), Railroad Crossing (1), Per Diem Inventory (1), Demurrage Charge (1), Customer Complaints (1), Vandalism (1), Hotbox (1), and Outlawed (1) are excluded. Their printed target/effect text is catalogued in the code, but `card.play` rejects the categories as not implemented. Consequently, no card can currently be played on another player.
|
||||
|
||||
There are also two **Facing Point Locks** listed among Mainline modifiers. They are treated as the same grid enhancement as Facing Point Locks above, require an Interlocking, and are included in the active 213-card total.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Station Master — Mainline Deck
|
||||
|
||||
**Rules implementation reference: v0.4.5**
|
||||
**Scope:** the tarot-sized Mainline cards placed between Offices. This is an implementation reference, not a transcription of earlier prototype rules.
|
||||
|
||||
## How Mainline cards work
|
||||
|
||||
At setup the game places one randomly selected Mainline card between each neighbouring pair of Offices and one beyond each end Office, between it and a Division Point. Thus, a game with *N* players has *N + 1* Mainline cards. The implementation selects types with replacement; it does not deal them from a shuffled finite deck.
|
||||
|
||||
A train crossing a Mainline card spends Stages, rather than moving through its printed cells. A 60 mph card costs one Stage and a 30 mph card costs two. A Slow train adds one Stage to every crossing. On Hilly terrain, any train carrying at least one coach uses the passenger rate; a train carrying no coach uses the freight rate. No crossing can take less than one Stage.
|
||||
|
||||
The card does not itself determine which player owns the adjacent Office. It is part of the shared Division.
|
||||
|
||||
## Physical card inventory in `Mainline Cards.pdf`
|
||||
|
||||
The supplied PDF has **ten** tarot-sized terrain cards. Plains appears twice; the other terrain types appear once each. It also includes two Division Point cards.
|
||||
|
||||
| Physical card | Copies in the PDF |
|
||||
| --- | ---: |
|
||||
| Plains | 2 |
|
||||
| Curves | 1 |
|
||||
| Hilly | 1 |
|
||||
| Heavy Grade | 1 |
|
||||
| Double Track | 1 |
|
||||
| Uncontrolled Siding | 1 |
|
||||
| Tunnel | 1 |
|
||||
| Trestle | 1 |
|
||||
| Interchange | 1 |
|
||||
| East Division Point | 1 |
|
||||
| West Division Point | 1 |
|
||||
|
||||
The PDF art labels this card “Yard”; this reference uses the implementation’s correct name, **Interchange**, to distinguish it from the Division Yard, Classification Yard, Yard Office, and Small Yard.
|
||||
|
||||
## Implemented Mainline card reference
|
||||
|
||||
| Card | Implemented crossing time and feature |
|
||||
| --- | --- |
|
||||
| Plains | 60 mph; one Stage for Fast, two for Slow. The implementation has one Plains *type* rather than the PDF’s two physical copies. |
|
||||
| Curves | 30 mph; two Stages for Fast, three for Slow. |
|
||||
| Hilly | Passenger train: 60 mph. Freight-only train: 30 mph. Add one Stage if Slow. |
|
||||
| Heavy Grade | Starts at 30 mph; two Stages for Fast, three for Slow. The card requires the player to select its uphill direction; v0.4.5 instead selects that direction from the game seed during setup. Grade modifiers can reduce the time, to a minimum of one Stage. |
|
||||
| Double Track | 60 mph. Printed capability: trains may pass. The traffic-resolution rule is in Rules §4.5. |
|
||||
| Uncontrolled Siding | 60 mph. Printed capability: trains may pass. The traffic-resolution rule is in Rules §4.5. |
|
||||
| Tunnel | 30 mph. |
|
||||
| Trestle | 60 mph. |
|
||||
| Interchange | 60 mph. A train may be reordered there only through the card’s printed “sort cars” concept; the current engine does **not** provide a Mainline sorting action for it. |
|
||||
|
||||
### PDF/code mismatch requiring correction
|
||||
|
||||
`src/engine/content.ts` defines nine `MAINLINE_PROFILES` types: one Plains entry plus the eight other terrain types above. `setup.ts` selects uniformly from that nine-type list. The second Plains card shown in `Mainline Cards.pdf` is therefore not represented as a duplicate card or as extra Plains weight in setup. If the PDF inventory is authoritative, the setup selection needs a second Plains entry (or an equivalent weighted selection).
|
||||
|
||||
The executable state represents East and West Division Points as fixed end nodes, not as card records. They are functionally present at the ends of the Division, but are not represented as the two PDF cards in the deck/state model.
|
||||
|
||||
## Heavy Grade modifiers
|
||||
|
||||
These cards come from the Home Office deck and are played onto a Mainline card during a player’s Draw option.
|
||||
|
||||
| Card | Copies | Placement and actual effect |
|
||||
| --- | ---: | --- |
|
||||
| Brakeman | 1 | Heavy Grade only. Reduces a downhill crossing by one Stage. |
|
||||
| Airbrakes | 1 | Heavy Grade only, and Brakeman must already be on that card. Reduces a downhill crossing by one additional Stage. |
|
||||
| Helpers | 1 | Heavy Grade only. Reduces an uphill crossing by one Stage. |
|
||||
| Realignment | 2 | May be played only onto an unoccupied Mainline card. Changes Plains → Double Track, Curves → Plains, Uncontrolled Siding → Double Track, or Trestle → Uncontrolled Siding. It cannot be played on any other card type. |
|
||||
|
||||
For the grade cards, “uphill” should be the direction selected by the player when the card is placed. In v0.4.5 it is the seeded `gradeUp` direction because setup has no player-choice step.
|
||||
|
||||
## What is not implemented
|
||||
|
||||
- There is no finite draw pile, player choice, or physical placement interaction for Mainline cards; setup selects their types automatically from the seeded random stream.
|
||||
- Interchange is catalogued as a “sort cars” card, but v0.4.5 has no operation that reorders a train on the Interchange. The Small Yard in an Office Area is the implemented sorting mechanism.
|
||||
- Heavy Grade orientation is seeded automatically rather than chosen by a player. The implementation needs a player-selection step to match the card.
|
||||
@@ -0,0 +1,290 @@
|
||||
# Station Master — Rules
|
||||
|
||||
**First-draft rules reference for v0.4.5**
|
||||
**Authority:** observed v0.4.5 code paths and tests. Where a card face, prototype document, and executable behavior differ, this document reports executable behavior and marks unimplemented material.
|
||||
|
||||
## 1. Overview and background
|
||||
|
||||
Station Master is a railroad-operations game set in the timetable-and-train-order era. Each player operates an Office Area on a shared east–west Division. The local work is building rail, switching cars, moving passengers and freight through facilities, and keeping the Office clear. The shared work is dispatching scheduled trains across the Division. The player holding the Superintendent’s Fedora decides whether to clear a following train into an occupied Subdivision; an unsafe decision can cause a collision.
|
||||
|
||||
Revenue is the score. Passenger boarding and detraining, freight loading and unloading, completed runs, collisions, and victory conditions are all resolved by the rules configured when the game is dealt.
|
||||
|
||||
This book is divided as follows:
|
||||
|
||||
1. overview and scope;
|
||||
2. definitions;
|
||||
3. creating, saving, replaying, and configuring games;
|
||||
4. the shared basic game mechanics;
|
||||
5. the implemented solitaire game;
|
||||
6. the implemented multiplayer/engine status; and
|
||||
7. FAQs and implementation limits.
|
||||
|
||||
The companion references are [Mainline deck](StationMaster-Mainline-Deck-v0.4.5.md), [Home deck](StationMaster-Home-Deck-v0.4.5.md), and [components](StationMaster-Components-v0.4.5.md).
|
||||
|
||||
## 2. Definitions
|
||||
|
||||
| Term | Meaning in v0.4.5 |
|
||||
| --- | --- |
|
||||
| A/D track | An Office arrival/departure capacity. A Whistle Post has 1; Depot, Station, and Terminal have 2, 3, and 4. |
|
||||
| Card location | A square in an Office Area grid. A train may end a switching Move only on Operational Rail. |
|
||||
| Classification Yard | The holding yard for used rolling stock. It refills the Division Yard only when that yard is completely empty. |
|
||||
| Control Point | A Depot, Station, or Terminal. It breaks the shared Mainline into Subdivisions. A Whistle Post is not one. |
|
||||
| Crew Tray | An engine plus up to four coupled rolling-stock cars. The engine itself does not count as a car. |
|
||||
| Department pile | One of three shared face-up card stacks. Draw and discard affect only the top card. |
|
||||
| Division Point | The western or eastern end of the shared Division, where trains enter and leave it. |
|
||||
| Division Yard | The active source of rolling stock. |
|
||||
| Expedited train | A train with the expedite rule. It performs Load/Unload at an Office after arriving, then attempts to depart at the end of that same Stage. |
|
||||
| Facility | A Passenger Facility at an upgraded Office or a Freight Facility at an industry. |
|
||||
| Freight Agent | The Local Operations option that stocks one outbound box, clears one inbound box, unjams one load, or ends without acting. |
|
||||
| Limits | The two signs defining an Office Area’s Running Track. They move outward when that track is extended. |
|
||||
| Local Operations | The first player-driven phase of a Stage. A player chooses one: switch, draw, or Freight Agent. |
|
||||
| Mainline | The shared cards between Offices and Division Points. |
|
||||
| MEN \| AT \| WORK | A three-box freight pipeline. Any load on it locks its industry track. |
|
||||
| Operational Rail | A card on which a train may finish a Move. Turnouts are pass-through only; a locked industry is not usable at all. |
|
||||
| Running Track | The east–west track between an Office Area’s Limits, including its Office. |
|
||||
| Secondary Track | All local rail inside the Limits that is not Running Track. |
|
||||
| Stage | One of twelve turns in a Day. Its phases are Local Operations, New Train, Mainline, Load/Unload, and Shift Change. |
|
||||
| Subdivision | Mainline between Division Points or Control Points. Clearance checks look through the whole next Subdivision. |
|
||||
| Superintendent | The player with the Fedora. The role decides same-direction clearances and rotates every three Stages. |
|
||||
| Timetabled train | A numbered train card scheduled to one of the 12 Stage slots, then due at that slot each Day. Odd numbers go west; even numbers go east. |
|
||||
|
||||
## 3. Create, save, replay, and game configuration
|
||||
|
||||
### 3.1 Starting a new game in the shipped client
|
||||
|
||||
The v0.4.5 browser page creates a **one-player solitaire Standard game**. Select **New game**, then choose:
|
||||
|
||||
1. a numeric seed, or leave it blank for a fresh browser-generated seed;
|
||||
2. a starting hand;
|
||||
3. passenger, freight, and train-transit Revenue rates.
|
||||
|
||||
The browser writes those choices into the URL. A particular game is defined by the **seed plus these house rules**, not the seed alone.
|
||||
|
||||
The three starting-hand choices are:
|
||||
|
||||
| Choice | Setup result |
|
||||
| --- | --- |
|
||||
| Three random cards | Default. Deal three cards from one shuffled deck. |
|
||||
| Six random cards | Deal six cards from one shuffled deck. The player begins over the normal hand limit and must play/discard down before ending the first Draw turn. |
|
||||
| Three random track and three random non-track cards | Shuffle track and non-track piles separately for the opening deal; then recombine and shuffle their remainders for all later draws. |
|
||||
|
||||
The three Revenue values are independently rounded and clamped to 0–5:
|
||||
|
||||
- passenger Revenue per coach, paid on boarding and again on detraining;
|
||||
- freight Revenue per load, paid on completion of an outbound load and again on completion of an inbound unload; and
|
||||
- train Revenue per transit, paid to every player when a train leaves the Division.
|
||||
|
||||
Defaults are 1 / 1 / 0. Zero turns that revenue source off; it does not prevent the underlying operation.
|
||||
|
||||
### 3.2 Deterministic setup and the seed
|
||||
|
||||
The engine uses one seeded random stream for shuffles, Mainline selection, opening D12 rolls, timetable rolls, and automatic Heavy Grade orientation. Given the same seed, configuration, player names/order, and accepted action history, it reconstructs the same state.
|
||||
|
||||
At setup it creates each Office Area as `Limit — Whistle Post — Limit`, makes the Division Yard, creates players + 3 Crew Trays, turns up three Department cards, and constructs the shared Division.
|
||||
|
||||
In a multi-player engine game, every player receives two seeded D12 rolls:
|
||||
|
||||
- The **division roll** orders seats from low west to high east; equal results put the lower player index farther east.
|
||||
- The **Superintendent roll** gives the initial Fedora to the first player tied for highest.
|
||||
|
||||
The opening deal starts at the Superintendent’s seat and proceeds left (eastward in the engine’s seat ordering).
|
||||
|
||||
### 3.3 Saving, resuming, and replaying solitaire
|
||||
|
||||
The local save format is JSON containing the seed, the accepted intent history, and the house rules. It does not serialize mutable game state. Loading recreates the game from the seed and replays every action through the rules engine. If an old history reaches an action that is no longer legal, replay stops there instead of forcing an invalid state.
|
||||
|
||||
The browser also stores the current local game and resumes it automatically when the page opens without an explicit seed in the URL. A downloaded save is named `station-master-seed<seed>-day<day>.json`.
|
||||
|
||||
**Undo is solitaire-only.** It removes the final accepted intent and rebuilds the game from the earlier history. Random outcomes are not rerolled: replaying the same action consumes the same seeded result. Undo can therefore change the player’s decision after seeing an outcome, but cannot fish for a different timetable die roll.
|
||||
|
||||
### 3.4 Engine game modes and endings
|
||||
|
||||
The engine defines Solitaire, Competitive, and Co-op modes. The browser exposes only Solitaire.
|
||||
|
||||
| Length | Target | Days |
|
||||
| --- | ---: | ---: |
|
||||
| Short | 10 | 3 |
|
||||
| Standard | 20 | 5 |
|
||||
| Campaign | 45 | 10 |
|
||||
|
||||
With `firstToTarget`, competitive mode ends when any individual reaches the target; co-op uses target × player count and total Revenue. With `highestAfterDays`, competitive mode requires the table’s combined Revenue to reach `3 × players × days`; otherwise everyone loses. If that floor is met, the highest individual score wins. Solitaire and co-op win only if their score reaches their target at the end of the length.
|
||||
|
||||
Competitive mode also ends in a collective loss after three collisions in one Day.
|
||||
|
||||
The configuration contains four optional-rule flags. Only two have engine effects: **Reduced Visibility** gives five rather than six switching Moves in Stages 1, 2, 3, 11, and 12; **Emergency Toolbox** initially sets the Red Flags hand-limit status, allowing four cards. Sister Trains and Employee Rotation are represented in configuration/state design but are not executed by v0.4.5.
|
||||
|
||||
### 3.5 Multiplayer setup status
|
||||
|
||||
There is no multiplayer lobby, room creation flow, remote server, invitation flow, or network session in v0.4.5. The engine can be called with multiple player names (and rejects a Solitaire configuration with more than one), but the delivered page always calls it for one local player. See section 6.
|
||||
|
||||
## 4. Basic game mechanics
|
||||
|
||||
### 4.1 The Stage sequence
|
||||
|
||||
Each of 12 Stages follows this sequence:
|
||||
|
||||
```text
|
||||
1. Local Operations — each player, starting with the Superintendent and proceeding left
|
||||
2. New Train — make up due timetabled trains, then queued second sections and Extras
|
||||
3. Mainline — automatic train movement in numeric order
|
||||
4. Load/Unload — each player, starting with the Superintendent and proceeding left
|
||||
5. Shift Change — expedited departures, clocks/workers, and possibly the Fedora
|
||||
```
|
||||
|
||||
At a Shift Change, Laborers and Porters reset. The Fedora moves after Stages 3, 6, 9, and 12. At Day end, dispatch-device use resets, collision count resets, the Day and Stage roll over, and victory is checked.
|
||||
|
||||
### 4.2 Local Operations: choose one option
|
||||
|
||||
On a player’s Local Operations turn, choose exactly one available option.
|
||||
|
||||
**Switch.** Select any Crew Tray currently in that player’s Office Area. It gets six Moves, or five under Reduced Visibility on the listed night Stages. A Move travels any connected distance in one direction and must end on Operational Rail. Reversing is a separate Move. A train may pass through a turnout but cannot stop on it. It may not share or pass through another train except through an Office with a free A/D track.
|
||||
|
||||
Standing cars couple automatically when the train reaches them; it may not pass them, and the resulting consist may not exceed four cars. Coupling forward places cars ahead of the engine; coupling while backing places them behind it. Setting out cars does not spend a Move, but the cut must come from an outer end of the consist and may not be left on the Office. A Small Yard can reorder a consist for one Move. Flying Switch spends one Move to roll a tail cut into a connected Freight Facility.
|
||||
|
||||
**Draw.** Take one card from the face-down Home Office or the exposed top of one Department pile. During this option, play eligible cards and/or discard cards to Department piles, then finish at the hand limit. Track, facilities, offices, modifiers, enhancements, and train cards have the placement or scheduling rules in the deck references. Mainline modifiers are played from this option as well.
|
||||
|
||||
**Freight Agent.** Make one of these operations, then the turn ends: stock one green outbound box from a matching loaded Division Yard car; clear one red inbound box to the Classification Yard; unjam one outbound, inbound, or MEN | AT | WORK load to the Classification Yard; or explicitly end without acting. Freight can be stocked only when an unclaimed empty matching car is already spotted at that industry. Passengers may wait in a green Office box without a train present.
|
||||
|
||||
Implementation note: `card.discard` is accepted by the v0.4.5 engine during Local Operations without checking that Draw was chosen. This unusual implementation behavior is not a separate published turn option.
|
||||
|
||||
### 4.3 Track, switching, and local safety
|
||||
|
||||
The Running Track cannot be broken by a curve or other card lacking its east–west through route. Local branches use turnouts and 45° curves. Cards that touch vertically connect only if their 45° legs share the correct diagonal; mere adjacency is not rail connection.
|
||||
|
||||
An industry’s track is locked while any load is on its MEN | AT | WORK pipeline. Trains cannot enter, cross, pick up from, or set cars out there. When it is unlocked, an industry track is ordinary Operating Rail and holds up to four cars, the same as any other non-turnout, non-Limits card: a crew may set out one, two, three or four cars there, whatever it is carrying. An industry’s green and red boxes are how much work it can hold, not how much rail it has, and a Modifier beside it adds a box without adding room for a car.
|
||||
|
||||
Train-card restrictions also apply while switching. No-switching trains cannot move, set out, or sort locally. Trains 3/4 share a one-freight-car budget for pickup or setout at each grid location. Trains 7/8 cannot set out their coach. X13 cannot pick up, and X22 may pick up empty cars only.
|
||||
|
||||
### 4.4 New Train phase
|
||||
|
||||
At the Stage shown on the timetable, the engine makes up the matching timetabled train if a Crew Tray is free. It starts at the Division Point appropriate to its direction. A train may receive matching loaded or empty cars from the Division Yard until its listed maximum consist is reached or no suitable car remains. It is permitted to leave under-strength.
|
||||
|
||||
A Second Section order on the due train creates another identical timetabled train behind it when a free tray is available. A played Extra is made up after timetabled trains and Second Sections when a tray is free; v0.4.5 automatically sends every Extra east from the Western Division Point.
|
||||
|
||||
### 4.5 Mainline movement and Office arrival
|
||||
|
||||
Mainline movement is automatic and processes lower train numbers first; a timetabled train outranks an Extra with the same number. A train at a Division Point, at an Office A/D track, or already crossing a Mainline card attempts its applicable movement.
|
||||
|
||||
Crossing time comes from the Mainline card and train speed. On a normal card, a train must check the entire next Subdivision before entering it:
|
||||
|
||||
- an opposing train normally blocks entry;
|
||||
- a following same-direction train asks the Superintendent to allow or deny clearance;
|
||||
- Red Flags or ABS Signals hold the follower automatically; and
|
||||
- passing cards allow entry without this occupancy check.
|
||||
|
||||
An Office arrival normally takes a free A/D track. If the Office is full, the inbound train collides and the local Office player loses 5 Revenue; Interlocking instead holds it at the Limits. A coachless inbound train may divert to a Yard Office. Cars fouling the Running Track at the Office also cause a collision.
|
||||
|
||||
An expedited train remains available for this Stage’s Load/Unload work after arriving, then attempts to depart at Shift Change. A non-expedited arrival waits until a later Mainline phase. A train departing an Office must be correctly made up: engine at one end, caboose at the far end if present.
|
||||
|
||||
When a train leaves the far Division Point, its cars are returned to yards, its Crew Tray becomes free, and each player receives the configured train-transit Revenue (zero by default).
|
||||
|
||||
### 4.6 Passenger work
|
||||
|
||||
Passenger work occurs during Load/Unload, at a Depot, Station, or Terminal. Each Porter may take one action per Stage.
|
||||
|
||||
- **Board:** replace an empty coach on an eligible train at the Office with a loaded coach from a green outbound box. The removed empty coach goes to the Classification Yard. Earn configured passenger Revenue.
|
||||
- **Detrain:** replace a loaded coach on an eligible train with an empty coach from the Division Yard, placing the loaded coach into an available red inbound box. Earn configured passenger Revenue.
|
||||
|
||||
Crack Limited trains permit passenger work at Terminals only. Military Train and Director’s Private Car permit no passenger work. A Whistle Post has no Porters.
|
||||
|
||||
### 4.7 Freight work
|
||||
|
||||
Freight work also occurs during Load/Unload. Each Laborer can take one action per Stage. Every freight facility has MEN, AT, and WORK boxes.
|
||||
|
||||
For an **outbound load**, first use the Freight Agent to put a matching loaded car into the green box, with an unclaimed empty matching car spotted on the industry track. A Laborer starts the load on MEN, then advances it to AT, then WORK, then onto the empty car. Completing the last step changes that car to loaded, sends the replaced empty car to the Classification Yard, and earns configured freight Revenue.
|
||||
|
||||
For an **inbound unload**, a matching loaded car must be spotted at an inbound-capable industry and the Division Yard must contain a matching empty car. A Laborer replaces the spotted loaded car with that empty car and starts the load on WORK. Laborers move it backward through AT and MEN into a free red inbound box. That final placement earns configured freight Revenue. Clearing the red box later with the Freight Agent pays no additional Revenue.
|
||||
|
||||
## 5. Solitaire
|
||||
|
||||
The implemented game is solitaire: one named player, one Office Area, local browser execution, Standard length, highest-after-days victory, and the default house rules unless changed in New Game. The game has no AI opponent. “Multiplayer” gameplay does not occur locally by simulating other players.
|
||||
|
||||
Solitaire-specific features are:
|
||||
|
||||
- a local browser save, automatic resume, and JSON download/load;
|
||||
- unlimited step-by-step Undo back through accepted action history; and
|
||||
- a seed/rules URL suitable for sharing or reproducing a game.
|
||||
|
||||
The active deck removes all 22 opponent-directed cards. Therefore, the defensive cards whose only purpose is to answer them (Facing Point Locks and Water Column) can be placed but have no opportunity to fire; Overpass has no effect at all. The game still includes shared-rail mechanics such as Mainline clearance, but with one player no other player can occupy the Division.
|
||||
|
||||
To win the default game, finish Day 5 with at least 20 Revenue. A result below 20 is a loss. Train-transit Revenue defaults to zero, so the default score must principally come from passenger and freight work.
|
||||
|
||||
## 6. Multiplayer
|
||||
|
||||
### 6.1 What the engine supports
|
||||
|
||||
The engine has player, seat, score, Office Area, Director/Division, phase-order, co-op, and competitive-mode data for multiple named players. It deals each player a hand, creates one Office Area per seat, starts acting order at the Superintendent and proceeds left, and models the following multiplayer-specific outcomes:
|
||||
|
||||
- the D12 seating and Superintendent rolls described in section 3;
|
||||
- individual Revenue in competitive play and shared total Revenue in co-op;
|
||||
- a competitive collective loss after three collisions in one Day;
|
||||
- a collective Revenue floor for competitive highest-after-days games; and
|
||||
- train-transit Revenue awarded to every player, if that revenue setting is nonzero.
|
||||
|
||||
The engine’s setup checks only that there is at least one player and that Solitaire has exactly one player. It does not enforce a maximum player count, although the test and design material exercise two through four players.
|
||||
|
||||
### 6.2 What is not delivered in v0.4.5
|
||||
|
||||
There is no implemented multiplayer game setup for end users: no server, lobby, invitation, room code, player join flow, authoritative remote state, or remote Session. The browser page creates a local single-player session only. Accordingly, there is no supported procedure for resuming a multiplayer game, and no multiplayer Undo.
|
||||
|
||||
The 12 space-use cards and 10 action cards are not dealt in competitive or co-op either. The engine rejects attempts to play either category. Thus, **no card can currently be played on another player**. This includes Derail, Broken Coupler, Railroad Crossing, score-penalty cards, Vandalism, Hotbox, Outlawed, and all table-space cards. Facing Point Locks and Water Column are implemented only as dormant defences for these unavailable effects.
|
||||
|
||||
### 6.3 Difference from solitaire, if a multi-player engine session is created
|
||||
|
||||
Players have separate local districts, hands, and scores, but they share the Home Office deck, Department piles, yards, timetable, Mainline, and traffic consequences. Turn order is sequential; one current actor acts at a time. The Superintendent role is attached to a player while Offices are attached to fixed seats. Employee Rotation is not active, so players do not actually change seats in v0.4.5.
|
||||
|
||||
The deck is still 213 cards and still excludes opponent-directed content. Therefore multi-player engine mode changes shared traffic, scores, turns, and win/loss evaluation—not card attacks or a remote user experience.
|
||||
|
||||
## 7. Frequently asked questions
|
||||
|
||||
### Is the same seed always the same game?
|
||||
|
||||
Only with the same house rules, player configuration/order, and action history. The seed drives all engine randomness, but the starting hand and Revenue settings also change the game.
|
||||
|
||||
### Why can’t I end my draw turn?
|
||||
|
||||
Your hand exceeds three cards, or four when the Red Flags hand-limit status is active. Play or discard down to the limit.
|
||||
|
||||
### Why can’t my train move onto a turnout?
|
||||
|
||||
It may move through a turnout but cannot end a Move there because turnouts are not Operational Rail.
|
||||
|
||||
### Why can’t a train enter my industry?
|
||||
|
||||
Its MEN | AT | WORK pipeline contains a load, which locks the track; the rail geometry does not connect; another train occupies it; mandatory coupling would exceed four cars; or four cars are already standing there, which is as many as any card holds.
|
||||
|
||||
### Why won’t an industry accept a new outbound load?
|
||||
|
||||
The green box may be full, the Division Yard may lack a matching loaded car, or there is no unclaimed empty matching car already spotted on that industry’s track.
|
||||
|
||||
### Why does an inbound freight unload require an empty car in the Division Yard?
|
||||
|
||||
The engine immediately replaces the spotted loaded car with a matching empty car. It will not create a new car from nothing.
|
||||
|
||||
### Why did an arriving train collide instead of wait?
|
||||
|
||||
The Office had no free A/D track and no Interlocking. A full Office is an automatic local collision. An Interlocking holds the arrival at the Limits.
|
||||
|
||||
### Why is a train held at an Office even though the Mainline is clear?
|
||||
|
||||
It may be on Secondary Track rather than the Office, or be badly made up: its engine is between cars or its caboose is not at the far end.
|
||||
|
||||
### Why did an expedited train leave after passenger/freight work?
|
||||
|
||||
Expedite means it departs in the Stage it arrived, but v0.4.5 waits until Shift Change so it remains present for that Stage’s Load/Unload phase.
|
||||
|
||||
### Can I choose the direction of an Extra or a Heavy Grade?
|
||||
|
||||
Not in v0.4.5. The engine launches Extras eastbound from the Western Division Point. Heavy Grade orientation is seeded automatically at setup.
|
||||
|
||||
### Can I use an Interchange to reorder a train?
|
||||
|
||||
No. The card is catalogued with a sorting concept, but there is no implemented Interchange sorting action. Small Yard is the available local sorting mechanism.
|
||||
|
||||
### Can I play attack cards on another player?
|
||||
|
||||
No. All action and space-use cards are excluded from every dealt deck in v0.4.5 and their play is rejected.
|
||||
|
||||
### Is multiplayer playable?
|
||||
|
||||
No. Multi-player state and rules-engine support exist, but the lobby, server, remote client, and opponent-directed card mechanics are not implemented.
|
||||
@@ -0,0 +1,233 @@
|
||||
# Track order for standing cars, and the cut you left on your own card
|
||||
|
||||
> **BUILT IN v0.4.7.** Kept as the record of why the model is what it is — the reproductions below
|
||||
> are the evidence for west-to-east, and none of them is re-derivable from the code that fixed them.
|
||||
>
|
||||
> Two departures from the plan as written, both recorded here so the document does not read as a
|
||||
> description of the code:
|
||||
>
|
||||
> - **Step 3 does not flip the card's cut.** The plan asked for `board-svg` to apply the tray strip's
|
||||
> `facing === 'e' ? reverse : as-is` to the standing cars too. Once `standing` is defined west to
|
||||
> east that flip would be wrong — the board is a map and draws west on the left, so the array is
|
||||
> already in drawing order. What the renderer got instead is the half that actually answers the
|
||||
> report: the row is split at the train, west cars left and east cars right, with the engine in the
|
||||
> gap, and each car's tooltip says whether it is ahead of or behind the engine.
|
||||
> - **One new field after all.** The plan argued a cut's end is *derivable* from position in the
|
||||
> array. It is not, in the one case that matters: a train may set out off both ends on the same
|
||||
> square, and nothing in the array records where the train itself stands among them. That is
|
||||
> `CrewTray.standingWest`, kept on the tray rather than the card because it describes a
|
||||
> relationship — with no train there, a cut has no near or far side.
|
||||
>
|
||||
> **Step 5 was decided (i)**: taking your own cut back on the square you are standing on is undoing
|
||||
> the drop, exempt from the printed pick-up restrictions, and it refunds the freight budget.
|
||||
|
||||
Plan for the next session. Two reported problems that turned out to be one problem.
|
||||
|
||||
---
|
||||
|
||||
## The reports
|
||||
|
||||
1. **Drops.** "If I put cars off the nose on a given track, and my next move is go forward, I need to
|
||||
couple those cars right back on. If I drop cars off the back and I move back, then I will
|
||||
automatically recouple the cars onto the back of my train. Right after dropping my cars I need to
|
||||
be able to see if those cars are ahead or behind the train."
|
||||
2. **Ordering.** "When dropping all 4 cars, order was reversed. It worked properly if we dropped cars
|
||||
individually. Also, when adding four cars, again the order was reversed."
|
||||
|
||||
---
|
||||
|
||||
## The single root cause
|
||||
|
||||
`TrackCard.standing` is a bare `RollingStock[]` whose doc comment claims "in track order (§A.3)" and
|
||||
which in fact has **no defined orientation at all**. `CrewTray.consist` *is* oriented — nose first,
|
||||
relative to `facing` — so every transfer between the two is an orientation change that nothing
|
||||
performs.
|
||||
|
||||
§A.3 states the requirement plainly:
|
||||
|
||||
> It is critical that cars are loaded into and unloaded from the Crew Tray, and occupy the track, in
|
||||
> the same order they originally held, **left-to-right**.
|
||||
|
||||
Left-to-right is west-to-east. That is the orientation `standing` is missing.
|
||||
|
||||
### Reproduced, against the working tree
|
||||
|
||||
Scratch scripts only; reproduce with a tray on a plain straight, consist `[boxcar hopper reefer
|
||||
tank]`, `engineAt = 0`.
|
||||
|
||||
**(a) Batch size changes the parked order.** Tail drops:
|
||||
|
||||
| drops | `card.standing` afterwards |
|
||||
| --- | --- |
|
||||
| `count: 4` | `[boxcar hopper reefer tank]` |
|
||||
| `count: 1` ×4 | `[tank reefer hopper boxcar]` |
|
||||
| `count: 2` ×2 | `[reefer tank boxcar hopper]` — physically impossible |
|
||||
|
||||
`carsDropped` always does `carsOn(card).push(...stock)` (`apply.ts:1657`). Each successive tail cut is
|
||||
left *outboard* of the previous one on the track, so it belongs at the other end of the array. Nose
|
||||
drops are batch-invariant by luck, so only tail drops show it.
|
||||
|
||||
**(b) Coupling is orientation-blind.** A train running **east** onto a parked cut and a train running
|
||||
**west** onto the same cut yield the **identical** consist. They must mirror. `couples` is
|
||||
accumulated in path order in `exploreMoves` (`track.ts:448`) and `unshift`ed wholesale in
|
||||
`carsCoupled` (`apply.ts:1618`), with no reference to which way the engine met the cars.
|
||||
|
||||
**(c) The display mismatch — what was actually seen on screen.** `board-svg.ts:736` draws the tray
|
||||
strip as `t.facing === 'e' ? [...items].reverse() : items`, so the nose lands on the right for an
|
||||
east-facing train (a deliberate fix, documented in the comment above it). The card's standing cars at
|
||||
`board-svg.ts:~633` are drawn in raw array order with no such flip. The same physical cut therefore
|
||||
reads one way in the tray and the other way on the card — and which one looks "reversed" flips with
|
||||
batch size *and* with train facing.
|
||||
|
||||
The round trip (drop, run away, back up, collect) is internally self-consistent today, which is why
|
||||
this survived: whatever order got parked comes back in that order. Only the picture disagreed.
|
||||
|
||||
### Why this settles the earlier design question
|
||||
|
||||
The previous session offered two models for recording which end a cut sits at:
|
||||
|
||||
- **Option A** — one cut per card plus a `standingEnd?: Port` marker. Cheap.
|
||||
- **Option B** — give `standing` a real orientation.
|
||||
|
||||
Option A leaves (a), (b) and (c) in place. **Do Option B.** With orientation defined, "which end is
|
||||
the cut at" is *derivable* from position in the array and needs no new field — so B is also the
|
||||
smaller change once the ordering bugs are counted as in scope, which they now are.
|
||||
|
||||
---
|
||||
|
||||
## The model
|
||||
|
||||
**`TrackCard.standing` (and `Facility.industryTrack.cars`, via `carsOn`) is ordered WEST to EAST.**
|
||||
|
||||
That is the printed rule, it is the convention the board already draws in, and it is the only
|
||||
orientation that is a property of the board rather than of whichever train last touched the card.
|
||||
|
||||
Two derived quantities, both cheap:
|
||||
|
||||
- **A cut's end.** With a train on the card, cars before the train in track order are at its west
|
||||
end, cars after it are at its east end. Combined with `railFacing`, that is "ahead" or "behind".
|
||||
- **Approach order.** A train travelling east meets a card's cars in array order; travelling west, in
|
||||
reverse array order.
|
||||
|
||||
The nose/tail blocking rule then reads: **a move that leaves the card through the end the cut sits at
|
||||
must couple that cut first** — onto the nose when running forward, onto the tail when reversing,
|
||||
which is what the existing `toNose: !i.reverse` already gives.
|
||||
|
||||
### The two conversions, stated once
|
||||
|
||||
- **Consist → track.** `consist` is nose-first; nose is `facing`. So an east-facing train's consist
|
||||
reads east-to-west and must be **reversed** on the way onto the card; a west-facing train's reads
|
||||
west-to-east and goes on **as-is**.
|
||||
- **Track → consist.** Build `couples` **near-to-far along the direction of travel** (reverse each
|
||||
card's array when travelling west, and visit cards in path order). Then a nose coupling is
|
||||
`unshift(...[...couples].reverse())` and a tail coupling is `push(...couples)` unchanged.
|
||||
|
||||
Check the tail case by hand: backing east into a cut, the nearest car couples closest to the existing
|
||||
tail, and `consist` runs nose-to-tail, so near-before-far pushed in order is right. Check the nose
|
||||
case: running east into a cut, the *farthest* car ends up nose-most, so the reverse is right.
|
||||
|
||||
---
|
||||
|
||||
## Work, in order
|
||||
|
||||
Each step should land green on its own.
|
||||
|
||||
### 1. Define the orientation and fix the drop
|
||||
|
||||
- `state.ts` — rewrite the `standing` doc comment to state west-to-east outright, and say `carsOn`
|
||||
inherits it. That comment is currently the thing that is wrong.
|
||||
- `apply.ts` `carsDropped` reducer (~1649) — insert at the correct end for the dropping train's
|
||||
`facing`, reversing the cut where the conversion above says to. A tail cut from an east-facing
|
||||
train goes at the **west** end of whatever is already on the card; a nose cut from the same train
|
||||
goes at the **east** end.
|
||||
- Tests: batch-invariance. `count: 4`, `1×4` and `2×2` must all produce the same `standing`, from
|
||||
both ends, at both facings. Direct regression for report (2).
|
||||
|
||||
### 2. Fix the coupling
|
||||
|
||||
- `track.ts` `exploreMoves` (~448) — build `couples` near-to-far along travel, per the conversion
|
||||
above. Needs the exit port at each hop, which the walk already has in `MoveStep`.
|
||||
- `apply.ts` `carsCoupled` (~1612) — reverse on `toNose`.
|
||||
- Tests: the mirror property. Approaching the same parked cut eastbound and westbound must give
|
||||
mirrored consists, never identical ones.
|
||||
- Tests: round trip at both facings and all batch sizes — drop, run clear, back up, collect; the
|
||||
consist must come back identical.
|
||||
|
||||
### 3. Fix the picture
|
||||
|
||||
- `view.ts` `CellView` — `cars: string[]` is flat and carries no relation to `train`. It needs enough
|
||||
for the renderer to place the cut relative to the engine. Preferred: keep `cars` in track order
|
||||
(west-to-east, matching state) and let the renderer flip, exactly as it already does for the tray
|
||||
strip.
|
||||
- `board-svg.ts` (~633) — apply the same `facing === 'e' ? reverse : as-is` flip to the cut that line
|
||||
736 applies to the tray, and lay the cut on the correct **side** of the train chip rather than in
|
||||
the fixed slot row. This is the real UI work and answers "I need to see if those cars are ahead or
|
||||
behind".
|
||||
- Tooltips: "standing here" becomes "standing ahead of the engine" / "behind the engine" when a train
|
||||
shares the card.
|
||||
|
||||
### 4. Layer on the blocking rule
|
||||
|
||||
- `track.ts` `exploreMoves` — seed the walk's `couples` with the start card's cut when the cut sits at
|
||||
the exit end. Today the walk begins at `neighbour(start, initialExit)` and never reads
|
||||
`carsOn(startCard)` at all, so a train drives through its own cut in both directions. Regularise
|
||||
the loop-back case at the same time: `couples` already picks up the start card if the walk circles
|
||||
back to it, because only `results.push`/`block` are guarded by `!sameCoord(node.coord, start)`.
|
||||
- `apply.ts` `switch.move` build (~1230) — `lifted` is `[...dest.path.map(s => s.coord), i.to]` and can
|
||||
never contain `start`. It must, or the recoupled cut is not cleared off the card.
|
||||
- `ctx.consistSize` — the start-card cut must count toward `MAX_CONSIST`, so `tooManyCars` bites at
|
||||
the start square. Its message should name the cut on your own card or it will read as nonsense.
|
||||
- Tests: encode §A.5's two worked examples move-for-move. Both must remain playable; the trailing
|
||||
example's step 1 ("drop the back two on Card F, move train to Card B") is the specific case that
|
||||
must *not* recouple, and it is the strongest single check that the rule is right.
|
||||
|
||||
### 5. Decide the printed-rule interaction — Jesse's call
|
||||
|
||||
Dropping a freight car spends the square's budget for trains 3/4 (`oneFreightPerLocation`). Recoupling
|
||||
it on the way out would then be refused with `FREIGHT_WORKED_HERE`, so the train could only ever back
|
||||
away from its own cut. Same shape for X13 `dropOnly` and X22 `pickUpEmptiesOnly`.
|
||||
|
||||
Two defensible answers:
|
||||
|
||||
- **(i)** Recoupling your own cut on the square you are standing on is *undoing the drop* — exempt
|
||||
from those restrictions, and it refunds the freight budget.
|
||||
- **(ii)** It is a pickup like any other; a train that sets out off its nose is committed to
|
||||
reversing. Harsher, but never strands a train permanently, since backing up stays legal.
|
||||
|
||||
Leaning (i): (ii) makes a legal-looking drop silently one-way, which is exactly the kind of trap the
|
||||
"why can't I move" panel exists to prevent.
|
||||
|
||||
### 6. Bot and fixtures
|
||||
|
||||
- `bot.ts` — the "drop the nose cut to un-bury the engine" heuristic (~1268) and "spot the back car at
|
||||
this facility" (~1352) now have direction consequences. The bot cannot produce an illegal move
|
||||
(legality is enforced upstream) but it will silently get worse until its scoring knows about the
|
||||
cut.
|
||||
- `public/replays/seed-*.json` will diverge — legality changes, so bot play changes. Regenerate, as
|
||||
was done last release.
|
||||
- **`docs/station-master-seed58228926-day6.json` no longer replays at all.** It stops at intent #2,
|
||||
`mainline.modify c108` → `NO_SUCH_CARD`, because uncommitted deck changes shifted card ids. It
|
||||
cannot serve as the regression case for these reports. Capture a fresh save against current code
|
||||
reproducing the same switching sequence, and keep it.
|
||||
|
||||
---
|
||||
|
||||
## Files expected to change
|
||||
|
||||
| File | Why |
|
||||
| --- | --- |
|
||||
| `src/engine/state.ts` | `standing` orientation, doc comment |
|
||||
| `src/engine/track.ts` | direction-aware `couples`; start-card cut; consist limit |
|
||||
| `src/engine/apply.ts` | `carsDropped`, `carsCoupled`, `switch.move` `lifted`, budget decision |
|
||||
| `src/sim/view.ts` | `CellView.cars` orientation and its relation to `train` |
|
||||
| `src/sim/board-svg.ts` | flip the cut, place it beside the chip, tooltips |
|
||||
| `src/sim/narrate.ts` | say which end was set out; distinguish recoupling own cut (~138) |
|
||||
| `src/sim/bot.ts` | direction-aware set-out heuristics |
|
||||
| `test/apply.test.ts`, `test/train-rules.test.ts`, `test/web.test.ts`, `test/replay.test.ts` | drop/couple batteries, §A.5 examples |
|
||||
| `public/replays/seed-*.json` | regenerate |
|
||||
|
||||
## Not in scope
|
||||
|
||||
- Leaving a coach at the Office (§A.4 / trains 7/8) — separate open item in `TODO.md`.
|
||||
- Any change to `maneuver.flyingSwitch`'s target rules. It drops onto a card with no train on it, so
|
||||
the cut has no "end" to sit at; it just needs to land in west-to-east order like any other cut.
|
||||
@@ -241,6 +241,27 @@ Freeing the Laborers freed the whole turn economy — passenger revenue doubled
|
||||
passenger rule changing. **Still 0 wins against a target of 20**, and freight is still only 13% of
|
||||
gross, so the loop runs now but does not yet carry the game.
|
||||
|
||||
### Where the empty-car requirement belongs
|
||||
|
||||
The rule above was later copied onto **stocking** as well, so `freightAgent.stockOutbound` refused a
|
||||
green box with no matching empty spotted. That is one step too early, and the designer's ruling is
|
||||
that it should never have been there:
|
||||
|
||||
> A freight agent should be able to load an outbound green box prior to having the car there that
|
||||
> matches the load that he's putting in there. But in order for the laborers to move it from the
|
||||
> green box into the men at work track, they would need to have an empty car of the appropriate type
|
||||
> waiting there.
|
||||
|
||||
Which is what §6.3 and §9.3 say between them. §6.3 lists the Freight Agent's act with no reference to
|
||||
the industry track at all; §9.3's **Load the car** is what requires "*a load in the Green Loading Box
|
||||
and an empty car of the required type on the industry's track*". So the two errands — fetch the
|
||||
cargo, spot the car — may be run in either order, and only the Laborer needs both done.
|
||||
|
||||
**Staging ahead cannot jam anything**, which is why the early gate bought nothing: a load in a green
|
||||
box is waiting, not stuck, and the lockout in §9.3 applies to loads on MEN | AT | WORK. `startableLoad`
|
||||
holds the real gate, counting cars against loads already staged or on the sign so two loads can never
|
||||
walk toward one car.
|
||||
|
||||
### Track was unplayable (found while wiring up Enhancements)
|
||||
|
||||
Moving track out of the deck into a per-player supply (§12.2) left **no way to play it**. Districts
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user