v0.7.2 — a leg that is part of the row, the deck the sheet prints, regions not miles per hour, and a Division you read left to right
Gitea#17 — a 45° leg is an end of the west-to-east row, so backing into a cut through a curve's south leg no longer couples it back to front. The same assumption left a crew's own cut standing when it pulled out through a leg, which is the "cars left behind" report we had failed to reproduce. Gitea#14 — every count is docs/Deck cards5.xlsx. Track halved, and the Q12 office doubling and Gap 12 industry tripling both come out with it: they were measured against a deck with twice the track, and keeping them at the sheet's track count wipes out the reefer chain entirely. 84 rows now match card for card; the ten Safety, Event and Inspection cards it adds are not built and are held out. Cards the sheet no longer lists are dealt zero copies rather than deleted, so their rules stay implemented. Gitea#15 — RAR reversed it: a rail may stop dead against its neighbour and the placement is legal. What must hold is that no train crosses the gap, which was already true and is now pinned against the reported board. Gitea#3 — the printed speeds are scenery. A card costs one Stage per printed region and where a train STARTS is what varies; Fast/Slow is read on Hilly alone. Entering a one-region card behind another is a collision now, which is what ABS exists to prevent, and ABS no longer holds trains silently. Gitea#18 — the Division draws as one row, west to east, with no office-area detail. East is finally always to the right. Closes #3 Closes #14 Closes #15 Closes #17 Closes #18
This commit is contained in:
+16
-4
@@ -77,6 +77,7 @@ import {
|
||||
facilityVariants,
|
||||
opposite,
|
||||
reachableDestinations,
|
||||
rowEndAt,
|
||||
variantsFor,
|
||||
withinLimits,
|
||||
} from './track.ts';
|
||||
@@ -331,6 +332,15 @@ function checkTurnoutUpgrade(existing: TrackCard, proto: TrackCard): RejectionCo
|
||||
|
||||
if (existing.standing.length > 0) return 'UPGRADE_OCCUPIED';
|
||||
if (existing.enhancements.length > 0) return 'UPGRADE_ENHANCED';
|
||||
/**
|
||||
* NOTHING IS ASKED ABOUT THE NEIGHBOURS, deliberately (Gitea#15).
|
||||
*
|
||||
* A turnout adds a 45° leg the card underneath did not have, and that leg may well point into an
|
||||
* occupied square with nothing to meet it. That is legal: RAR ruled (2026-08-26) that a rail may
|
||||
* stop dead against its neighbour, and an upgrade is no different from laying the piece there in
|
||||
* the first place. What must hold either way is that no train can cross the gap, which is
|
||||
* `exploreMoves`' business and is tested in `track.test.ts`.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -962,7 +972,7 @@ export function check(s: GameState, player: PlayerIndex, i: Intent): RejectionCo
|
||||
if (!node || node.kind !== 'mainline') return 'NO_PLACEMENT';
|
||||
const on = node.modifiers ?? [];
|
||||
if (on.includes(rule.key)) return 'OPTION_ALREADY_CHOSEN';
|
||||
if (rule.gradeOnly && mainlineProfile(node.card).speed.kind !== 'grade') return 'NOT_A_GRADE';
|
||||
if (rule.gradeOnly && node.card !== 'heavyGrade') return 'NOT_A_GRADE';
|
||||
if (rule.requiresOnCard && !on.includes(rule.requiresOnCard)) return 'NOT_CONNECTED';
|
||||
// "Not while a train is on it" — realigning under a moving train is exactly the situation the
|
||||
// restriction exists to prevent. A train standing in the Interchange's yard counts: it is on
|
||||
@@ -1470,7 +1480,7 @@ export function ownCutFor(s: GameState, player: PlayerIndex, trayId: TrayId, rev
|
||||
const facing = facingPort(s, trayId);
|
||||
const exit: Port = reverse ? reversePort(s, player, here, facing) : facing;
|
||||
const card = areaOf(s, player).grid.get(coordKey(here)) ?? emptyCard();
|
||||
return cutTowards(card, carsOn(card), exit);
|
||||
return cutTowards(card, carsOn(card), rowEndAt(card, exit));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1588,14 +1598,16 @@ function execute(s: GameState, player: PlayerIndex, i: Intent): GameEvent[] {
|
||||
*/
|
||||
const grid = areaOf(s, player).grid;
|
||||
const startCard = grid.get(coordKey(from)) ?? emptyCard();
|
||||
const startCut = cutTowards(startCard, carsOn(startCard), exitPort);
|
||||
const startCut = cutTowards(startCard, carsOn(startCard), rowEndAt(startCard, exitPort));
|
||||
const lifted = [
|
||||
...(startCut.length > 0 ? [from] : []),
|
||||
...dest.path.map((step) => step.coord),
|
||||
i.to,
|
||||
].filter((c) => carsOn(grid.get(coordKey(c)) ?? emptyCard()).length > 0);
|
||||
const sides = standingSides(startCard, carsOn(startCard));
|
||||
const stayed = exitPort === 'e' ? sides.west : exitPort === 'w' ? sides.east : [];
|
||||
// The OTHER end's cut, which stays behind — so it is the other end of the row, not the
|
||||
// other port. A 45° leg is an end of the row too (`rowEndAt`, Gitea#17).
|
||||
const stayed = rowEndAt(startCard, exitPort) === 'e' ? sides.west : sides.east;
|
||||
// §A.3 — "engines also have couplers on the front end, so a train can pick cars up onto
|
||||
// its nose". Running forward the engine meets cars head-on and takes them in front; backing
|
||||
// up, they couple behind. Which end they land on is the whole point of a run-around: it
|
||||
|
||||
Reference in New Issue
Block a user