More bug fixes. Show the timetable, and stop the arrival message contradicting itself. Fixed division point in labels. And highlighted discard as targets.
This commit is contained in:
+39
-22
@@ -132,7 +132,7 @@ export function divisionSvg(nodes: DivisionView[]): string {
|
||||
trains: n.trains.flat(),
|
||||
cap: n.capacity,
|
||||
tip: dp
|
||||
? 'A Division Point — trains enter and leave the Division here, and queue without limit'
|
||||
? 'A Division Point — the end of the line. Trains both enter and leave the Division here (odd numbers run west, even run east), and queue without limit'
|
||||
: `${n.label} — Mainline${n.gradeUp ? `, climbs ${n.gradeUp === 'east' ? 'east' : 'west'}` : ''}${n.modifiers.length ? ` · ${n.modifiers.join(' · ')}` : ''}`,
|
||||
owner: null,
|
||||
// A Division Point is one region — the queue trains enter and leave the Division through.
|
||||
@@ -286,12 +286,16 @@ export function divisionSvg(nodes: DivisionView[]): string {
|
||||
if (first) {
|
||||
out += `<g class="bs-stop"><line x1="${first.x - 9}" y1="${first.y + 16}" x2="${first.x - 9}" y2="${first.y + CH - 16}"/>` +
|
||||
`<line x1="${first.x - 9}" y1="${first.y + CH / 2}" x2="${first.x - 2}" y2="${first.y + CH / 2}"/></g>`;
|
||||
out += `<text class="bs-end" x="${first.x - 12}" y="${first.y + CH + 12}" text-anchor="end">trains enter</text>`;
|
||||
// BOTH ENDS DO BOTH. Odd trains run west and even trains run east (content.ts `pair`), so each
|
||||
// Division Point is a way on AND a way off. "trains enter" here and "trains leave" there said
|
||||
// the Division ran one way, which it does not — the point of the buffer stops is only that it is
|
||||
// a LINE and not a loop.
|
||||
out += `<text class="bs-end" x="${first.x - 12}" y="${first.y + CH + 12}" text-anchor="end">west end · in and out</text>`;
|
||||
}
|
||||
if (last && last !== first) {
|
||||
out += `<g class="bs-stop"><line x1="${last.x + last.w + 9}" y1="${last.y + 16}" x2="${last.x + last.w + 9}" y2="${last.y + CH - 16}"/>` +
|
||||
`<line x1="${last.x + last.w + 2}" y1="${last.y + CH / 2}" x2="${last.x + last.w + 9}" y2="${last.y + CH / 2}"/></g>`;
|
||||
out += `<text class="bs-end" x="${last.x + last.w + 12}" y="${last.y + CH + 12}">trains leave</text>`;
|
||||
out += `<text class="bs-end" x="${last.x + last.w + 12}" y="${last.y + CH + 12}">east end · in and out</text>`;
|
||||
}
|
||||
|
||||
out += '</svg>';
|
||||
@@ -443,12 +447,19 @@ export function officeSvg(
|
||||
out += `<text class="bs-coord" x="${W - 5}" y="12" text-anchor="end">${cell.row},${cell.col}</text>`;
|
||||
|
||||
/**
|
||||
* THE LOAD PIPELINE, on the card itself.
|
||||
* THE LOAD PIPELINE, on the card itself, DRAWN THE WAY IT FLOWS.
|
||||
*
|
||||
* A load crosses green -> MEN | AT | WORK -> the spotted car, and that journey is the whole of
|
||||
* freight. It was drawn only in the side panel, so advancing a load changed nothing on the card
|
||||
* the player was looking at. The printed cards put these squares under the track for exactly
|
||||
* this reason.
|
||||
* §9.3 runs loading Green → MEN → AT → WORK → the spotted car, and unloading the other way
|
||||
* entirely: car → WORK → AT → MEN → red. So green and red BOTH belong beside MEN, and the car
|
||||
* belongs beside WORK.
|
||||
*
|
||||
* This drew green, then MEN|AT|WORK, then red — putting the red box at the far right, which is
|
||||
* exactly where the car is. An unload therefore appeared to run backwards across the whole row
|
||||
* and then land on the end it had just come from. Reported after unloading at a warehouse:
|
||||
* "it went W, A, M, and then to the red box at the far right".
|
||||
*
|
||||
* Only the boxes an industry actually uses are drawn, and the arrows point the way its freight
|
||||
* really moves.
|
||||
*/
|
||||
const f = cell.facility;
|
||||
if (f) {
|
||||
@@ -459,25 +470,31 @@ export function officeSvg(
|
||||
|
||||
let bx = 7;
|
||||
const by = H - 42;
|
||||
// Green: what is waiting to be worked.
|
||||
for (let i = 0; i < Math.max(1, f.greenCap); i++) {
|
||||
out += box(bx, by, i < f.green.length, 'bs-green');
|
||||
bx += cellW + 2;
|
||||
// Green feeds INTO the sign, red comes OUT of it, so both sit on this side and their arrows
|
||||
// point opposite ways. An industry that only receives has no green boxes at all.
|
||||
if (f.allowsOut) {
|
||||
for (let i = 0; i < Math.max(1, f.greenCap); i++) {
|
||||
out += box(bx, by, i < f.green.length, 'bs-green');
|
||||
bx += cellW + 2;
|
||||
}
|
||||
out += `<text class="bs-arrow" x="${bx + 1}" y="${by + 9}">›</text>`;
|
||||
bx += 9;
|
||||
}
|
||||
out += `<text class="bs-arrow" x="${bx + 1}" y="${by + 9}">›</text>`;
|
||||
bx += 8;
|
||||
// MEN | AT | WORK — a load sits in one of these while it is being worked.
|
||||
if (f.allowsIn) {
|
||||
for (let i = 0; i < Math.max(1, f.redCap); i++) {
|
||||
out += box(bx, by, i < f.red.length, 'bs-red');
|
||||
bx += cellW + 2;
|
||||
}
|
||||
out += `<text class="bs-arrow" x="${bx + 1}" y="${by + 9}">‹</text>`;
|
||||
bx += 9;
|
||||
}
|
||||
// MEN | AT | WORK — one physical sign, worked left to right loading and right to left
|
||||
// unloading. The spotted car sits past WORK, on the siding drawn below.
|
||||
for (let i = 0; i < f.maw.length; i++) {
|
||||
out += box(bx, by, f.maw[i] !== null, 'bs-maw', ['M', 'A', 'W'][i]);
|
||||
bx += cellW + 2;
|
||||
}
|
||||
out += `<text class="bs-arrow" x="${bx + 1}" y="${by + 9}">›</text>`;
|
||||
bx += 8;
|
||||
// Red: what has come off an arriving car and is waiting to be cleared.
|
||||
for (let i = 0; i < Math.max(1, f.redCap); i++) {
|
||||
out += box(bx, by, i < f.red.length, 'bs-red');
|
||||
bx += cellW + 2;
|
||||
}
|
||||
out += `<text class="bs-arrow" x="${bx + 1}" y="${by + 9}">${f.allowsOut && f.allowsIn ? '\u21c4' : f.allowsOut ? '›' : '‹'}</text>`;
|
||||
}
|
||||
|
||||
// Standing room, drawn as the printed squares: filled means occupied.
|
||||
|
||||
+14
-1
@@ -239,9 +239,19 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
text: `Train ${e.trainNumber} HIGHBALLED — departed ${e.from} onto ${e.to}`,
|
||||
};
|
||||
case 'trainArrived':
|
||||
/**
|
||||
* AN EXPEDITED TRAIN IS NOT COMING BACK ROUND.
|
||||
*
|
||||
* This said "it will highball again next Mainline Phase, so any work must happen now" to every
|
||||
* arrival. For an expedited train that is exactly backwards — Q3 departs it in the SAME Stage
|
||||
* it arrived, so there is no turn in which to work it — and the log then contradicted itself
|
||||
* two lines later when the train left. Reported after Train 4, the Express, did precisely that.
|
||||
*/
|
||||
return {
|
||||
tone: 'plain',
|
||||
text: `Train ${e.trainNumber} ARRIVED at the ${e.office} carrying ${carsLabel(e.consist)} — it will highball again next Mainline Phase, so any work must happen now`,
|
||||
text: e.expedited
|
||||
? `Train ${e.trainNumber} ARRIVED at the ${e.office} carrying ${carsLabel(e.consist)} — and EXPEDITES, so it leaves again this same Mainline Phase; there is no turn in which to work it`
|
||||
: `Train ${e.trainNumber} ARRIVED at the ${e.office} carrying ${carsLabel(e.consist)} — it stands until the next Mainline Phase, so any work must happen before then`,
|
||||
};
|
||||
case 'trainDiverted':
|
||||
return {
|
||||
@@ -551,6 +561,9 @@ export function cuesFor(events: readonly GameEvent[]): string[] {
|
||||
// board with only a line of history to say where it went is the thing that most needs a noise.
|
||||
if (e.type === 'carsCoupled') out.push('couple');
|
||||
if (e.type === 'carsDropped' || e.type === 'flyingSwitch') out.push('drop');
|
||||
// The 1D12 that sets a train's departure Stage. It is the one roll a player makes and it landed
|
||||
// silently, so the card was gone and the answer to "when does it run?" was a line of history.
|
||||
if (e.type === 'trainScheduled') out.push('schedule');
|
||||
if (e.type === 'stageBegan') {
|
||||
// A Stage BEGINNING is the previous one ending — except the first, which is the game opening
|
||||
// and has nothing behind it. A Day boundary rings the bell only: sounding both would collide,
|
||||
|
||||
+34
-3
@@ -109,6 +109,16 @@ export type FacilityView = {
|
||||
* anywhere. Keeping the base lets the panel say "2 (1 + 1 from a Modifier)".
|
||||
*/
|
||||
base: { out: number; in: number; laborers: number };
|
||||
/**
|
||||
* Which way freight actually flows here, so the pipeline can be DRAWN in that direction.
|
||||
*
|
||||
* §9.3 runs loading Green → MEN → AT → WORK → car, and unloading the other way: car → WORK → AT →
|
||||
* MEN → red. So green and red both sit beside MEN, and the car sits beside WORK. Drawing red at
|
||||
* the far right — where the car is — made an unload look like it ran backwards across the whole
|
||||
* row and then landed on the wrong end.
|
||||
*/
|
||||
allowsOut: boolean;
|
||||
allowsIn: boolean;
|
||||
/** The Modifier cards standing beside it, by name. */
|
||||
modifiers: string[];
|
||||
/**
|
||||
@@ -322,6 +332,8 @@ function facilityView(
|
||||
porters: `${portersLeft(f)}/${f.porters}`,
|
||||
canFinish: canFinishHere(f),
|
||||
jammed: f.menAtWork.some((l) => l !== null) && !canFinishHere(f),
|
||||
allowsOut: f.allows.outbound,
|
||||
allowsIn: f.allows.inbound,
|
||||
base: baseOf(card),
|
||||
modifiers: (card.modifiers ?? []).map((m) => MODIFIER_NAMES[m] ?? prettyKey(m)),
|
||||
};
|
||||
@@ -468,8 +480,20 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
}
|
||||
case 'switch.sortConsist':
|
||||
return `re-order consist [${i.order.join(',')}]`;
|
||||
case 'freightAgent.stockOutbound':
|
||||
return `stock a ${i.carType} at ${at(i.at)}`;
|
||||
case 'freightAgent.stockOutbound': {
|
||||
/**
|
||||
* "stock a coach at (0, 0)" reads as putting a CAR on the track, and was reported as exactly
|
||||
* that confusion: no train at the Depot, so how is a coach being stocked there? It is not a
|
||||
* car on the track — it is a load taken from the Division Yard into the green Loading box,
|
||||
* waiting for a train that can carry it. For a passenger facility that load is passengers on
|
||||
* the platform.
|
||||
*/
|
||||
const f = areaOf(s, 0).grid.get(`${i.at.row},${i.at.col}`)?.facility ?? null;
|
||||
const where = f?.kind === 'passenger' ? 'onto the platform' : 'into the green Loading box';
|
||||
return i.carType === 'coach' && f?.kind === 'passenger'
|
||||
? `bring passengers ${where} at ${at(i.at)} — they wait there for a train with an empty coach`
|
||||
: `bring a ${i.carType} load ${where} at ${at(i.at)} — it waits there for a car to be spotted`;
|
||||
}
|
||||
case 'freightAgent.unjam':
|
||||
return `unjam ${i.from} at ${at(i.at)}`;
|
||||
case 'freightAgent.clearInbound':
|
||||
@@ -683,7 +707,14 @@ export function snapshot(
|
||||
const chip = trainChip(s, t.tray);
|
||||
return {
|
||||
...chip,
|
||||
label: `${chip.label} (${t.stagesRemaining})`,
|
||||
/**
|
||||
* SAY WHAT THE NUMBER IS.
|
||||
*
|
||||
* This read "TX14 (2)", a bare figure next to a train's name — and it was read as the
|
||||
* car count twice, by the same player, because that is the obvious guess. It is Stages
|
||||
* left to cross this Mainline card. Naming the unit costs three characters.
|
||||
*/
|
||||
label: `${chip.label} · ${t.stagesRemaining}⧗`,
|
||||
region: place(t),
|
||||
direction: t.direction,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user