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
+23 -18
View File
@@ -27,7 +27,7 @@ import {
trainProfile,
} from '../engine/content.ts';
import type { Intent } from '../engine/intents.ts';
import type { Facility, GameState, TrackCard } from '../engine/state.ts';
import type { Facility, GameState, TrackCard, TurnoutOrientation } from '../engine/state.ts';
import type { Hand, TrackGeometry } from '../engine/content.ts';
import type { Port } from '../engine/track.ts';
import { connectionsFor, slopeOfPair, variantsFor } from '../engine/track.ts';
@@ -859,16 +859,17 @@ function cellDescription(card: TrackCard, officeName: string, onRunning: boolean
case 'sharpCurved': {
const arc = g.arc ?? (g.hand === 'right' ? 'se' : 'sw');
const cost = g.geometry === 'sharpCurved' ? ' · costs TWO Moves to cross' : '';
return `${arcPhrase(arc)}${cost}${slopePhrase(arc[0] as Port, arc[1] as Port)}`;
return `curve — ${curvePhrase(arc)}${cost}${slopePhrase(arc[0] as Port, arc[1] as Port)}`;
}
case 'turnout': {
const t = g.turnout;
if (!t) return 'turnout';
// §A.1 is the subtlety worth spelling out: the missing edge, not a one-way street.
return (
`turnout — stem ${compass(t.stem)}, through ${compass(t.through)}, diverges ${compass(t.diverge)} at 45° · ` +
`a train may run stem↔through or stem↔diverge, but NEVER between ${compass(t.through)} and ` +
`${compass(t.diverge)}${slopePhrase(t.stem as Port, t.diverge as Port)}`
`turnout — ${turnoutPhrase(t)} · a train coming the other way, from the ` +
`${compass(t.through)} or the ${compass(t.diverge)}, may only leave by the ` +
`${compass(t.stem)} — the two roads never join` +
`${slopePhrase(t.stem as Port, t.diverge as Port)}`
);
}
}
@@ -878,13 +879,23 @@ function cellDescription(card: TrackCard, officeName: string, onRunning: boolean
const compass = (p: string): string => ({ n: 'north', s: 'south', e: 'east', w: 'west' })[p] ?? p;
/**
* A curve, as the printed card draws it: along the centre line to a frog, then out at 45° through
* the MIDDLE of an edge. Naming both halves is what stops it reading as a quarter-circle corner.
* WHAT A TURNOUT DOES, in the words a player would use at the table.
*
* "Right-hand turnout, stem east, through west, diverges north" is three pieces of jargon and a
* compass reading, and none of it answers the only question being asked: if my train comes in from
* over there, where can it go? §A.1's rule falls straight out of the same sentence — traffic from
* the stem may take either road, and traffic arriving on either road may only leave by the stem, so
* the two roads never join.
*/
function arcPhrase(arc: string): string {
function turnoutPhrase(t: TurnoutOrientation): string {
return `allows traffic from the ${compass(t.stem)} to travel ${compass(t.through)} or turn to the ${compass(t.diverge)}`;
}
/** The same, for a curve: it has one road and no choice to make. */
function curvePhrase(arc: string): string {
const [a, b] = [arc[0] as Port, arc[1] as Port];
const [side, leg] = a === 'n' || a === 's' ? [b, a] : [a, b];
return `curve — runs ${compass(side!)} along the centre line, then leaves at 45° through the middle of the ${compass(leg!)} edge`;
return `carries traffic from the ${compass(side!)} round to the ${compass(leg!)}`;
}
/**
@@ -944,15 +955,9 @@ export function variantLabel(
): string {
const v = variantsFor(geometry, hand)[variant ?? 0];
if (!v) return '';
if (v.turnout) {
return ` — stem ${compass(v.turnout.stem)}, through ${compass(v.turnout.through)}, diverges ${compass(v.turnout.diverge)} at 45°`;
}
if (v.arc) {
const [a, b] = [v.arc[0] as Port, v.arc[1] as Port];
const [side, leg] = a === 'n' || a === 's' ? [b, a] : [a, b];
return ` — ${compass(side!)} to the middle of the ${compass(leg!)} edge`;
}
return ' — east–west';
if (v.turnout) return ` — ${turnoutPhrase(v.turnout)}`;
if (v.arc) return ` — ${curvePhrase(v.arc)}`;
return ' — straight through, east to west';
}
/**