fix tracks to all be EW. no more NS tracks

This commit is contained in:
Jesse
2026-08-05 01:00:11 -04:00
parent 821d3cdffa
commit 2d348e1ecf
16 changed files with 1157 additions and 355 deletions
+49 -31
View File
@@ -282,24 +282,32 @@ describe('Red Flags protect a stopped train', () => {
});
describe('Flying Switch rolls a cut into an industry', () => {
/** A crew on a straight with a facility next door, mid-switch. */
/** A crew on a siding below the main, with a facility next door along the same track. */
function setup(s: GameState) {
const area = areaOf(s, 0);
// A flying switch rolls the cut ALONG THE TRACK, so the spur and the industry must be joined
// north-south. Built east-west, they are two unconnected cards that merely look adjacent.
const spur: TrackCard = {
geometry: { kind: 'track', geometry: 'straight', axis: 'ns' },
// A flying switch rolls the cut ALONG THE TRACK, so the crew and the industry must be joined by
// rail. Every run is east-west now, and the siding gets down off the main the only way there is:
//
// row 0: [office] [turnout, leg south] <- Running Track
// row -1: [curve ne] [straight, crew] [industry] <- the siding
//
const plain = (): TrackCard => ({
geometry: { kind: 'track', geometry: 'straight' },
baseOperationalRail: true,
standing: [],
facility: null,
modifiers: [],
enhancements: [],
};
area.grid.set(coordKey(at(-1, 0)), spur);
});
area.grid.set(coordKey(at(0, 1)), {
...plain(),
geometry: { kind: 'track', geometry: 'turnout', turnout: { stem: 'w', through: 'e', diverge: 's' }, hand: 'left' },
baseOperationalRail: false,
});
area.grid.set(coordKey(at(-1, 1)), { ...plain(), geometry: { kind: 'track', geometry: 'curved', arc: 'ne', hand: 'left' } });
area.grid.set(coordKey(at(-1, 2)), plain());
const industry: TrackCard = {
geometry: { kind: 'track', geometry: 'straight', axis: 'ns' },
baseOperationalRail: true,
standing: [],
...plain(),
facility: {
kind: 'freight', subtype: 'mineTipple',
allows: { outbound: true, inbound: false },
@@ -310,19 +318,14 @@ describe('Flying Switch rolls a cut into an industry', () => {
laborers: 1, porters: 0,
usedThisStage: { laborers: 0, porters: 0 },
},
modifiers: [],
enhancements: [],
};
area.grid.set(coordKey(at(-2, 0)), industry);
area.grid.set(coordKey(at(-1, 3)), industry);
s.trays.set('crew', {
id: 'crew', trainNumber: 1, trainIsExtra: false, engineAt: 0,
consist: [{ type: 'hopper', loaded: false }, { type: 'boxcar', loaded: false }],
// Facing SOUTH, down the spur. `direction` only carries east/west, so a crew on north-south
// track needs its actual port — without it the engine looks for an east exit the card has not
// got, and the crew has no legal moves at all.
direction: 'east', facing: 's',
position: { at: 'grid', owner: 0, coord: at(-1, 0) }, movesUsed: 0,
direction: 'east', facing: 'e',
position: { at: 'grid', owner: 0, coord: at(-1, 2) }, movesUsed: 0,
});
s.clock.phase = 'localOps';
s.clock.currentActor = 0;
@@ -337,7 +340,7 @@ describe('Flying Switch rolls a cut into an industry', () => {
const cardId = hand(s, 'maneuver', 'flyingSwitch');
const r = applyIntent(s, 0, {
type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 1, to: at(-2, 0),
type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 1, to: at(-1, 3),
});
assert.ok(r.ok, 'the flying switch should be accepted');
@@ -346,7 +349,7 @@ describe('Flying Switch rolls a cut into an industry', () => {
assert.equal(s.trays.get('crew')!.consist.length, 1, 'the cut leaves the consist');
assert.deepEqual(
s.trays.get('crew')!.position,
{ at: 'grid', owner: 0, coord: at(-1, 0) },
{ at: 'grid', owner: 0, coord: at(-1, 2) },
'the engine never enters the industry',
);
assert.equal(s.turn.movesRemaining, 5, 'the manoeuvre costs a Move');
@@ -366,7 +369,8 @@ describe('Flying Switch rolls a cut into an industry', () => {
const s = game();
setup(s);
const cardId = hand(s, 'maneuver', 'flyingSwitch');
// (0,0) is the Office — track-connected to the crew, but not a freight industry.
// (0,0) is the Office — track-connected to the crew back up through the turnout, but not a
// freight industry.
assert.equal(
check(s, 0, { type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 1, to: at(0, 0) }),
'CANNOT_DROP_HERE',
@@ -377,13 +381,13 @@ describe('Flying Switch rolls a cut into an industry', () => {
const s = game();
setup(s);
const area = areaOf(s, 0);
// An industry sitting beside the crew, but joined east-west while the spur runs north-south.
const stranded = { ...area.grid.get(coordKey(at(-2, 0)))! };
stranded.geometry = { kind: 'track', geometry: 'straight', axis: 'ew' };
area.grid.set(coordKey(at(-1, 1)), stranded);
// An industry sitting directly beneath the crew. Both cards are plain east-west track, so
// nothing crosses the edge between them: adjacency is not a rail.
const stranded = { ...area.grid.get(coordKey(at(-1, 3)))! };
area.grid.set(coordKey(at(-2, 2)), stranded);
const cardId = hand(s, 'maneuver', 'flyingSwitch');
assert.equal(
check(s, 0, { type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 1, to: at(-1, 1) }),
check(s, 0, { type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 1, to: at(-2, 2) }),
'NOT_CONNECTED',
);
});
@@ -393,7 +397,7 @@ describe('Flying Switch rolls a cut into an industry', () => {
setup(s);
const cardId = hand(s, 'maneuver', 'flyingSwitch');
assert.equal(
check(s, 0, { type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 5, to: at(-2, 0) }),
check(s, 0, { type: 'maneuver.flyingSwitch', cardId, trayId: 'crew', count: 5, to: at(-1, 3) }),
'CONSIST_EMPTY',
);
});
@@ -454,14 +458,28 @@ describe('the Limits sign moves with the Running Track (§2.1, Gap 4a)', () => {
'laying on the Limits sign must be how the track extends',
);
// The district hangs below the Running Track and is not bounded by the Limits at all.
// A district opens with a TURNOUT on the Running Track — no office or industry card carries a
// stub of its own — so extend onto the east sign with one.
s.turn.laidThisTurn = false;
const turnoutCol = area.limitsEast.col;
assert.ok(
applyIntent(s, 0, {
type: 'track.lay', geometry: 'turnout', hand: 'left',
placement: { row: area.runningRow, col: turnoutCol }, variant: 0,
}).ok,
'a turnout must be layable on the Limits sign',
);
// The district hangs off that turnout and is not bounded by the Limits at all. Variant 1 of a
// left-hand curve is the `ne` arc — the one on the same diagonal as the turnout's leg.
s.turn.laidThisTurn = false;
assert.equal(
check(s, 0, {
type: 'track.lay', geometry: 'straight', hand: 'none',
placement: { row: area.runningRow - 1, col: 0 }, variant: 1,
type: 'track.lay', geometry: 'curved', hand: 'left',
placement: { row: area.runningRow - 1, col: turnoutCol }, variant: 1,
}),
null,
'Secondary Track below the Office must stay legal',
'Secondary Track below the turnout must stay legal',
);
});