v0.4.9h — a leg that is part of the row, the deck the sheet prints, and regions not miles per hour

The 0.4.9 line's port of v0.7.2, minus Gitea#18: the Division map redraw is a
multiplayer redesign and does not belong in a build people are mid-playtest on.

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, and a crew
pulling out through a leg takes its own cut with it.

Gitea#14 — every count is docs/Deck cards5.xlsx, including removing the Q12
office doubling and the Gap 12 industry tripling.

Gitea#15 — RAR reversed it: the placement is legal; what must hold is that no
train crosses the gap.

Gitea#3 — the printed speeds are scenery. One Stage per printed region, and
where a train starts is what varies.
This commit is contained in:
Jesse.Markowitz
2026-08-26 15:21:49 -04:00
parent f684c60f2e
commit e9683cc8c5
22 changed files with 4079 additions and 3055 deletions
+94
View File
@@ -540,6 +540,100 @@ describe('placement and drop-off', () => {
assert.ok(!canPlaceAt(area, at(-1, 1), straight()), 'an east-west straight cannot meet a 45° leg');
});
describe('a rail that stops dead against its neighbour (Gitea#15)', () => {
/**
* REPORTED, THEN REVERSED. The issue first read "if a card is placed in that space, it MUST
* connect", against a right-hand curve laid at (1,-1) with an Ice House above it and a turnout
* with a north-facing leg below. **RAR reviewed it and ruled the other way (2026-08-26): the
* placement is fine, and a stub like that has a use — a siding to park cars on.**
*
* "We need to confirm, however, that trains are not allowed to traverse from the turnout below
* to that right-hand curve since the tracks do not connect." That is what these tests are: the
* rule lives in MOVEMENT, not in placement.
*
* The save cannot carry this any more — Gitea#14 took the deck from 206 cards to 121, so its
* card ids no longer exist and the history stops at the first `card.play`. The geometry is what
* mattered, and it is rebuilt here directly.
*/
/** A Modifier card — an Ice House. Not track: no ports on any edge. */
const modifierCard = (): TrackCard => ({
geometry: { kind: 'modifier', modifier: 'iceHouse' },
baseOperationalRail: false,
standing: [],
standingWest: 0,
facility: null,
modifiers: [],
enhancements: [],
});
/** A Grocer's Warehouse — a Facility, so a plain east-west through track. */
const warehouse = (): TrackCard => ({
geometry: { kind: 'facility', facility: 'grocersWarehouse' },
baseOperationalRail: true,
standing: [],
standingWest: 0,
facility: null,
modifiers: [],
enhancements: [],
});
/**
* The reported district. `withCurve` puts the disputed right-hand curve on the square; without
* it, the square is empty and the placement itself is under test.
*
* The turnout's leg goes NORTH on the `nw_se` diagonal; the curve is `ne`, which is `ne_sw` and
* has no south port at all. Two reasons the two do not join, either of which is enough.
*/
const board = (withCurve: boolean): OfficeArea =>
areaFrom(
{
[coordKey(at(0, -1))]: turnout({ stem: 'w', through: 'e', diverge: 'n' }),
[coordKey(at(0, 0))]: officeCard(),
[coordKey(at(1, 0))]: warehouse(),
[coordKey(at(2, -1))]: modifierCard(),
...(withCurve ? { [coordKey(at(1, -1))]: curve('ne') } : {}),
},
at(0, 0),
);
it('allows the reported placement, which connects on one side and nothing else', () => {
// RAR's ruling. The curve joins the warehouse to its east; its north leg faces an Ice House
// that carries no rail, and the turnout below faces its portless south edge. All legal.
assert.ok(canPlaceAt(board(false), at(1, -1), curve('ne')), 'the reported play was refused');
});
it('will not let a train cross from the turnout below onto that curve', () => {
// The confirmation the issue actually asks for. Running west out of the Office and into the
// turnout, the 45° leg goes north — and stops at the curve's blank south edge.
const dests = reachableDestinations(ctxFor(board(true)), at(0, 0), 'w');
assert.ok(!has(dests, 1, -1), 'a train drove across rails that do not meet');
});
it('still reaches the curve from the side that DOES join', () => {
// Otherwise the test above would pass on a card that is simply unreachable, which proves
// nothing. East of the curve is the warehouse, and east-west edges always meet.
const dests = reachableDestinations(ctxFor(board(true)), at(1, 0), 'w');
assert.ok(has(dests, 1, -1), 'the curve was unreachable from the side that joins');
});
it('will not let a train cross a north edge onto a card with no rail at all', () => {
// The Ice House above. A Modifier is scenery beside the rails — Jesse confirmed a rail may
// point at a building — so what stops a train is the same `joins` test, not a placement rule.
const dests = reachableDestinations(ctxFor(board(true)), at(1, 0), 'w');
assert.ok(!has(dests, 2, -1), 'a train drove into an Ice House');
});
it('still allows an exit that faces a BLANK square', () => {
// Unchanged by the reversal, and the reason a district can grow at all: a turnout laid on the
// Running Track with nothing yet beside its diverging leg is a perfectly good play.
const area = areaFrom({ [coordKey(at(0, 0))]: officeCard() }, at(0, 0));
assert.ok(
canPlaceAt(area, at(0, 1), turnout({ stem: 'w', through: 'e', diverge: 'n' })),
'a turnout whose leg faces open space was refused',
);
});
});
it('refuses a card that connects to nothing', () => {
const area = areaFrom({ [coordKey(at(0, 0))]: officeCard() }, at(0, 0));
assert.ok(!canPlaceAt(area, at(3, 3), straight()), 'orphaned track is never legal');