v0.3.0 — playtest fixes, two new rules, and §7 enforced

This commit is contained in:
Jesse
2026-08-12 21:08:28 -04:00
parent 6ead39c530
commit 1bf1e95058
38 changed files with 8514 additions and 5989 deletions
+32 -3
View File
@@ -150,8 +150,17 @@ export type Facility = {
outboundBox: RollingStock[];
inboundBox: RollingStock[];
capacity: { outbound: number; inbound: number };
/** Freight only. One load per box; three boxes, so it is a pipeline (§9.1). */
menAtWork: [Load | null, Load | null, Load | null];
/**
* FREIGHT ONLY — `null` on a Passenger Facility, which is what makes that unrepresentable rather
* than merely unused.
*
* One load per box; three boxes, so it is a pipeline (§9.1). §9.2's passenger work is porters
* boarding and detraining with no pipeline at all, and the sign is printed "For Freight
* Facilities" (rules-v0.2.md:452). Every Office got a three-slot array anyway, and because it was
* an array the renderers happily drew three boxes on a Depot that has no Laborer to work them —
* reported from playtesting. Typing it away means a fourth renderer cannot reintroduce the bug.
*/
menAtWork: [Load | null, Load | null, Load | null] | null;
/** Where cars are spotted for loading and unloading. */
industryTrack: { length: number; cars: RollingStock[] };
laborers: number;
@@ -203,7 +212,8 @@ export function isOperationalRail(card: TrackCard): boolean {
* `isOperationalRail` for exactly that reason.
*/
export function isLockedByWork(card: TrackCard): boolean {
return !!card.facility && card.facility.menAtWork.some((slot) => slot !== null);
// A Passenger Facility has no pipeline, so it is never locked by freight work.
return !!card.facility?.menAtWork?.some((slot) => slot !== null);
}
// ---------------------------------------------------------------------------
@@ -263,6 +273,15 @@ export type CrewTray = {
* runs once and is gone; there is no second visit to claim it on.
*/
stopPointClaimed?: boolean;
/**
* X17 Campaign Train — "one turn at station (speeches) then expedite".
*
* It makes its speech at the first Office it reaches: that arrival is an ordinary stop, and from
* then on the train runs expedited, departing every Office in the Stage it arrives. Recorded on the
* tray for the same reason as `stopPointClaimed` — it is the TRAIN that stops, and an Extra runs
* once, so there is no later visit to hang it on.
*/
speechMade?: boolean;
};
// ---------------------------------------------------------------------------
@@ -450,6 +469,15 @@ export type TurnState = {
movesRemaining: number;
drawnThisTurn: boolean;
freightAgentUsed: boolean;
/**
* Trains 3/4 Express — "may drop or pick up ONE freight car at every location".
*
* Keyed `trayId@row,col`, counting freight cars that train has exchanged on that square this turn.
* Per LOCATION rather than per turn, so the Express can work its way along a district a car at a
* time — which is what makes it an Express rather than a train that may move one car a Stage.
* Cleared with the rest of the turn.
*/
freightWorked: Record<string, number>;
/** Set when the actor finishes; the phase driver then moves to the next player. */
done: boolean;
};
@@ -460,6 +488,7 @@ export function freshTurn(moves: number): TurnState {
movesRemaining: moves,
drawnThisTurn: false,
freightAgentUsed: false,
freightWorked: {},
done: false,
};
}