v0.4.0 — multiplayer Phases 0 and 1: seat and player split apart, turn state per player, the page behind a Session, and eight seat/player mix-ups fixed with tests that fail without them

This commit is contained in:
Jesse
2026-08-13 14:06:02 -04:00
parent 216006b091
commit 49f8504b05
34 changed files with 1743 additions and 526 deletions
+9 -9
View File
@@ -15,9 +15,9 @@
* panel cannot drift from the rules.
*/
import { adTrackCount, coordKey } from '../engine/state.ts';
import type { GameState, GridCoord, RollingStock, TrayId } from '../engine/state.ts';
import { canAdvanceLoad, canStartLoad, facilityCarType, laborersLeft, movesFor, portersLeft } from '../engine/apply.ts';
import { adTrackCount, coordKey, seatOf, turnOf } from '../engine/state.ts';
import type { GameState, GridCoord, PlayerIndex, RollingStock, TrayId } from '../engine/state.ts';
import { areaOf, canAdvanceLoad, canStartLoad, facilityCarType, laborersLeft, movesFor, portersLeft } from '../engine/apply.ts';
import type { GameEvent } from '../engine/events.ts';
// ---------------------------------------------------------------------------
@@ -446,10 +446,9 @@ export type Impediment = { where: string; why: string; severity: 'stuck' | 'wait
* This is the panel that should answer the standing questions: whether facilities jam, whether
* trains are held for want of a crew, whether the Office is about to cause a collision.
*/
export function impediments(s: GameState, player = 0): Impediment[] {
export function impediments(s: GameState, player: PlayerIndex = 0): Impediment[] {
const out: Impediment[] = [];
const area = s.officeAreas.get(player);
if (!area) return out;
const area = areaOf(s, player);
for (const [key, card] of area.grid) {
const f = card.facility;
@@ -512,9 +511,10 @@ export function impediments(s: GameState, player = 0): Impediment[] {
* Only while switching, and only the cards actually in the crew's way: `movesFor` reports the
* squares the movement walk reached and refused, not every square on the board.
*/
if (s.clock.phase === 'localOps' && s.turn.option === 'switch' && s.turn.movesRemaining > 0) {
const turn = turnOf(s, player);
if (s.clock.phase === 'localOps' && turn.option === 'switch' && turn.movesRemaining > 0) {
for (const [id, tray] of s.trays) {
if (tray.position.at !== 'grid' || tray.position.owner !== player) continue;
if (tray.position.at !== 'grid' || tray.position.seat !== seatOf(s, player)) continue;
const { blocked } = movesFor(s, player, id);
for (const b of blocked) {
// A turnout is not an obstruction — a train runs through one all day and simply may not
@@ -583,7 +583,7 @@ export function impediments(s: GameState, player = 0): Impediment[] {
}
// A full Office means the next arrival is an automatic collision (Gap 2d).
const cap = adTrackCount(s, player);
const cap = adTrackCount(s, seatOf(s, player));
if (area.adOccupancy.length >= cap) {
out.push({
where: 'Office',