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
+6 -3
View File
@@ -18,7 +18,7 @@
import type { GameEvent } from '../engine/events.ts';
import type { Intent } from '../engine/intents.ts';
import type { GameState } from '../engine/state.ts';
import { areaOf, facilityCarTypes } from '../engine/apply.ts';
import { areaAtSeat, areaOf, facilityCarTypes } from '../engine/apply.ts';
import { officeProfile } from '../engine/content.ts';
// ---------------------------------------------------------------------------
@@ -121,7 +121,7 @@ export function makeFunnelProbe(player = 0): {
// A buried engine is a per-decision condition: every turn it persists is a turn the train is
// stuck, so this counts turns rather than trains.
for (const tray of s.trays.values()) {
if (tray.position.at !== 'grid' || tray.position.owner !== player) continue;
if (tray.position.at !== 'grid' || tray.position.seat !== player) continue;
if (tray.engineAt <= 0 || tray.engineAt >= tray.consist.length) continue;
funnel.buriedTurns += 1;
// Cars may never be set out at the Office (§A.4), which is where this almost always happens.
@@ -295,7 +295,10 @@ export function summarize(
const grossPassenger = rev.passengerBoard + rev.passengerDetrain;
const gross = grossFreight + grossPassenger;
const area = final.officeAreas.get(0);
// Seat 0 explicitly: these are SOLITAIRE summaries, where there is exactly one Office Area and
// seat 0 is the only player. `areaAtSeat` rather than `officeAreas.get` so the key's meaning is
// stated — a multi-player report would have to sum over seats, and would fail loudly here first.
const area = areaAtSeat(final, 0);
let freightFacilities = 0;
if (area) {
for (const card of area.grid.values()) {