make turnout description more clear. fix curves from breaking running track.

This commit is contained in:
Jesse
2026-08-05 10:03:50 -04:00
parent 9c76b1a5e2
commit 1c321d4cea
9 changed files with 226 additions and 28 deletions
+98
View File
@@ -23,6 +23,7 @@ import {
allReachable,
canDropCarsAt,
canPlaceAt,
carriesThroughTrack,
exitsFrom,
hasPort,
neighbour,
@@ -803,3 +804,100 @@ describe('Operational Rail — where a train may stop and leave cars (Appendix A
}
});
});
// ---------------------------------------------------------------------------
describe('the Running Track must stay a through route', () => {
const runningArea = (): OfficeArea =>
areaFrom(
{
[coordKey(at(0, -1))]: straight(),
[coordKey(at(0, 0))]: officeCard(),
[coordKey(at(0, 1))]: straight(),
},
at(0, 0),
);
it('refuses a curve in the running row, which would dead-end the main', () => {
// A curve has ONE road — from an east or west edge round to a 45° leg — so a card of it standing
// in the Running Track stops the main dead at that square, cutting the Office off from its own
// Limits. Every other card that may stand there carries an east-west road across it.
const area = runningArea();
for (const arc of ['ne', 'nw', 'se', 'sw'] as TrackArc[]) {
assert.ok(!carriesThroughTrack(curve(arc)), `a ${arc} curve has no through road`);
assert.ok(!canPlaceAt(area, at(0, 2), curve(arc)), `a ${arc} curve must not go in the Running Track`);
}
});
it('still accepts everything that does carry the road through', () => {
const area = runningArea();
assert.ok(carriesThroughTrack(straight()));
assert.ok(carriesThroughTrack(turnout({ stem: 'w', through: 'e', diverge: 's' })));
assert.ok(carriesThroughTrack(officeCard()));
assert.ok(canPlaceAt(area, at(0, 2), straight()), 'a straight extends the main');
assert.ok(
canPlaceAt(area, at(0, 2), turnout({ stem: 'w', through: 'e', diverge: 's' })),
'a turnout keeps the road AND opens a leg — which is why a district hangs off one',
);
});
it('leaves a curve free to go anywhere off the main', () => {
const area = areaFrom(
{
[coordKey(at(0, 0))]: officeCard(),
[coordKey(at(0, 1))]: turnout({ stem: 'w', through: 'e', diverge: 's' }),
},
at(0, 0),
);
assert.ok(canPlaceAt(area, at(-1, 1), curve('ne')), 'the siding is exactly where a curve belongs');
});
});
describe('a turnout may be laid to face another, making a crossover', () => {
/** A turnout stated the way the cards read: who may come in, and where they may go. */
const facing = (stem: Port, through: Port, diverge: Port, hand: 'left' | 'right'): TrackCard => ({
geometry: { kind: 'track', geometry: 'turnout', turnout: { stem, through, diverge }, hand },
baseOperationalRail: false, standing: [], facility: null, modifiers: [], enhancements: [],
});
it('joins a south-diverging turnout to the north-diverging one beneath it', () => {
// "Traffic from the east to travel west or turn to the south", with directly beneath it "traffic
// from the west to travel east or turn to the north". The two 45° legs are one continuous rail
// across the card edge — a crossover, and the way a siding gets a track parallel to the main.
const above = facing('e', 'w', 's', 'right');
const below = facing('w', 'e', 'n', 'right');
assert.ok(joins(above, 's', below), 'the crossover does not join');
// And its mirror, which is the same move built from the other hand.
const aboveL = facing('w', 'e', 's', 'left');
const belowL = facing('e', 'w', 'n', 'left');
assert.ok(joins(aboveL, 's', belowL), 'the mirrored crossover does not join');
});
it('refuses the pair whose legs lie on opposite diagonals', () => {
// Both cards have the port and both legs meet the same point on the shared edge. They still form
// a V: one descends to the right and the other to the left.
assert.ok(!joins(facing('e', 'w', 's', 'right'), 's', facing('e', 'w', 'n', 'left')));
assert.ok(!joins(facing('w', 'e', 's', 'left'), 's', facing('w', 'e', 'n', 'right')));
});
it('lets a crew run down the crossover onto the parallel track', () => {
// The point of building one: the lower turnout carries its own east-west road, so the siding
// runs alongside the main rather than dead-ending under it.
const area = areaFrom(
{
[coordKey(at(0, -1))]: straight(),
[coordKey(at(0, 0))]: facing('w', 'e', 's', 'left'),
[coordKey(at(0, 1))]: straight(),
// Coming down the leg, the lower turnout is entered through its diverging north end, so the
// only way on is its stem — east, along the parallel track.
[coordKey(at(-1, 0))]: facing('e', 'w', 'n', 'left'),
[coordKey(at(-1, 1))]: straight(),
},
at(0, 5),
);
const dests = reachableDestinations(ctxFor(area), at(0, -1), 'e');
assert.ok(has(dests, -1, 1), 'a crew cannot reach the parallel track through the crossover');
assert.ok(!has(dests, -1, 0), 'and may not stop on the turnout itself');
});
});