switching paths: two routes to the same square
Builds docs/plans/switching-paths.md. A passing loop can offer two legal routes between the same two squares, coupling different cars — the engine only ever found one, an artifact of search order (Reported by Jesse, undo 379). exploreMoves now enumerates every simple route (per-path visited set, capped at 4000 frontier nodes) and dedupes on outcome — destination, entry side, and origin-tagged cars — rather than on reaching the square at all. switch.move gains an optional `via: GridCoord` naming one intermediate square on the chosen route; absent, it resolves exactly as before, so every existing save and bot decision replays identically (575/575, then 579/579 with the new tests). Threaded through the label, the action-list dedupe, the hover highlight (data-route), and the history (trayMoved.via). Ruling recorded as Gap 14 in docs/rules/open-questions.md: the player may choose the path; §A.4's "may not go around" a car does not reach a different track the player declined to enter. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAt2YCXgd5qCjBcF2x34aK
This commit is contained in:
+4
-1
@@ -126,10 +126,13 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
: 'Chose FREIGHT AGENT work — one car moved to or from a facility',
|
||||
};
|
||||
case 'trayMoved':
|
||||
// `via` rides on the event only when there was another legal route to the same square
|
||||
// (docs/plans/switching-paths.md) — so naming it here says which one the crew actually took,
|
||||
// rather than leaving a real choice invisible in the crew's own history.
|
||||
return {
|
||||
tone: 'plain',
|
||||
where: e.to,
|
||||
text: `CREW moved ${at(e.from)} → ${at(e.to)} — ${e.movesRemaining} of 6 Moves left. The crew chip on the grid carries the whole train with it.`,
|
||||
text: `CREW moved ${at(e.from)} → ${at(e.to)}${e.via ? ` via ${at(e.via)}` : ''} — ${e.movesRemaining} of 6 Moves left. The crew chip on the grid carries the whole train with it.`,
|
||||
};
|
||||
case 'carsCoupled': {
|
||||
/**
|
||||
|
||||
+14
-3
@@ -19,6 +19,7 @@ import {
|
||||
movesFor,
|
||||
ownCutFor,
|
||||
portersLeft,
|
||||
selectDestination,
|
||||
} from '../engine/apply.ts';
|
||||
import {
|
||||
ACTION_CARDS,
|
||||
@@ -736,9 +737,19 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
const tray = s.trays.get(i.trayId);
|
||||
const here = tray?.position.at === 'grid' ? tray.position.coord : null;
|
||||
let picks = '';
|
||||
let routeNote = '';
|
||||
if (here) {
|
||||
const dest = destinationsFor(s, tray!.position.at === 'grid' ? playerAtSeat(s, tray!.position.seat) : 0, i.trayId, here, i.reverse)
|
||||
.find((d) => d.coord.row === i.to.row && d.coord.col === i.to.col);
|
||||
const dests = destinationsFor(s, tray!.position.at === 'grid' ? playerAtSeat(s, tray!.position.seat) : 0, i.trayId, here, i.reverse);
|
||||
const dest = selectDestination(dests, i.to, i.via);
|
||||
// Two routes to the same square (docs/plans/switching-paths.md) would otherwise print the
|
||||
// identical button twice — "move to (0,0)" and "move to (0,0)" — and the action list drops
|
||||
// duplicate labels, silently discarding the second choice. `via` is the one thing that
|
||||
// differs in the DATA, so it is the one thing safe to print without guessing at scenery
|
||||
// this function has no other reason to know the name of.
|
||||
const atSameSquare = dests.filter((d) => d.coord.row === i.to.row && d.coord.col === i.to.col);
|
||||
if (dest && i.via && atSameSquare.length > 1) {
|
||||
routeNote = ` via ${at(i.via)}`;
|
||||
}
|
||||
if (dest && dest.couples.length > 0) {
|
||||
/**
|
||||
* NAME THE CARS THE CREW SET OUT HERE SEPARATELY. They are at the front of `couples` — the
|
||||
@@ -756,7 +767,7 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
: ` — couples ${carsLabel(dest.couples)} on the way${end}`;
|
||||
}
|
||||
}
|
||||
return `move to ${at(i.to)}${i.reverse ? ' (reverse)' : ''}${picks}`;
|
||||
return `move to ${at(i.to)}${routeNote}${i.reverse ? ' (reverse)' : ''}${picks}`;
|
||||
}
|
||||
case 'switch.dropCars': {
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user