p2p-lockstep-kit-session 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/session/index.d.ts +9 -8
- package/dist/session/index.js +20 -10
- package/dist/session/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/serialization-smoke.mjs +15 -30
package/dist/session/index.d.ts
CHANGED
|
@@ -82,12 +82,12 @@ interface GameStateSnapshot {
|
|
|
82
82
|
turn: number;
|
|
83
83
|
history: TurnEntry[];
|
|
84
84
|
lastStart: PlayerLabel | null;
|
|
85
|
-
pendingAction:
|
|
85
|
+
pendingAction: PendingAction;
|
|
86
86
|
connected: boolean;
|
|
87
87
|
}
|
|
88
88
|
interface GameEvent {
|
|
89
89
|
type: 'READY' | 'START' | 'MOVE' | 'GAME_OVER' | 'UNDO' | 'RESTART' | 'OFFLINE' | 'ONLINE' | 'SYNC' | 'ERROR';
|
|
90
|
-
payload?:
|
|
90
|
+
payload?: unknown;
|
|
91
91
|
from?: 'local' | 'remote';
|
|
92
92
|
timestamp?: number;
|
|
93
93
|
}
|
|
@@ -97,7 +97,7 @@ interface IGameObserver {
|
|
|
97
97
|
onConnectionChange?(connected: boolean): void;
|
|
98
98
|
onError?(error: {
|
|
99
99
|
message: string;
|
|
100
|
-
context?:
|
|
100
|
+
context?: unknown;
|
|
101
101
|
}): void;
|
|
102
102
|
}
|
|
103
103
|
interface IStateObserver {
|
|
@@ -144,7 +144,7 @@ declare class GameStateObserver {
|
|
|
144
144
|
notifyConnectionChange(connected: boolean): void;
|
|
145
145
|
notifyError(error: {
|
|
146
146
|
message: string;
|
|
147
|
-
context?:
|
|
147
|
+
context?: unknown;
|
|
148
148
|
}): void;
|
|
149
149
|
getSnapshot(): GameStateSnapshot | null;
|
|
150
150
|
getObserverCount(): number;
|
|
@@ -166,9 +166,10 @@ declare class UINotificationAdapter implements IStateObserver {
|
|
|
166
166
|
type TurnEntry = {
|
|
167
167
|
turn: number;
|
|
168
168
|
player: 'local' | 'remote';
|
|
169
|
-
move?:
|
|
169
|
+
move?: unknown;
|
|
170
170
|
};
|
|
171
171
|
type PlayerLabel = 'local' | 'remote';
|
|
172
|
+
type PendingAction = 'undo' | 'restart' | null;
|
|
172
173
|
declare class State {
|
|
173
174
|
private local;
|
|
174
175
|
private remote;
|
|
@@ -206,8 +207,8 @@ declare class State {
|
|
|
206
207
|
* so we automatically find and apply it.
|
|
207
208
|
*/
|
|
208
209
|
dispatch(player: PlayerLabel, action: SessionEvent, to?: SessionState): void;
|
|
209
|
-
setPendingAction(action:
|
|
210
|
-
getPendingAction():
|
|
210
|
+
setPendingAction(action: PendingAction): void;
|
|
211
|
+
getPendingAction(): PendingAction;
|
|
211
212
|
setPendingUndoCount(count: 1 | 2 | null): void;
|
|
212
213
|
getPendingUndoCount(): 1 | 2 | null;
|
|
213
214
|
setLastStart(player: PlayerLabel | null): void;
|
|
@@ -364,4 +365,4 @@ declare const createSession: (networkClient: NetworkClient, sid?: string) => {
|
|
|
364
365
|
send: (message: SessionMessage) => void;
|
|
365
366
|
};
|
|
366
367
|
|
|
367
|
-
export { DefaultGamePlugin, type GameEvent, type GameState, GameStateObserver, type GameStateSnapshot, type IGameObserver, type IGamePlugin, type ISessionActions, type IStateObserver, StateObserverManager, UINotificationAdapter, type ValidationResult, buildGameStateSnapshot, createSession };
|
|
368
|
+
export { DefaultGamePlugin, type GameEvent, type GameState, GameStateObserver, type GameStateSnapshot, type IGameObserver, type IGamePlugin, type ISessionActions, type IStateObserver, type PendingAction, type PlayerLabel, type SessionEvent, type SessionState, StateObserverManager, type TurnEntry, UINotificationAdapter, type ValidationResult, buildGameStateSnapshot, createSession };
|
package/dist/session/index.js
CHANGED
|
@@ -4,11 +4,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4
4
|
|
|
5
5
|
// utils/logger.ts
|
|
6
6
|
var logWith = (level) => (message, meta) => {
|
|
7
|
+
const write = level === "debug" ? console.info : console[level];
|
|
7
8
|
if (meta !== void 0) {
|
|
8
|
-
|
|
9
|
+
write(message, meta);
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
write(message);
|
|
12
13
|
};
|
|
13
14
|
var consoleLogger = {
|
|
14
15
|
debug: logWith("debug"),
|
|
@@ -308,9 +309,9 @@ function buildGameStateSnapshot(state, connected = false) {
|
|
|
308
309
|
}
|
|
309
310
|
var UINotificationAdapter = class {
|
|
310
311
|
constructor(stateRef, uiObserver, getConnected = () => false) {
|
|
311
|
-
this
|
|
312
|
-
this
|
|
313
|
-
this
|
|
312
|
+
__publicField(this, "stateRef", stateRef);
|
|
313
|
+
__publicField(this, "uiObserver", uiObserver);
|
|
314
|
+
__publicField(this, "getConnected", getConnected);
|
|
314
315
|
__publicField(this, "lastNotificationTime", 0);
|
|
315
316
|
__publicField(this, "notificationThrottleMs", 0);
|
|
316
317
|
}
|
|
@@ -821,8 +822,8 @@ var State = class {
|
|
|
821
822
|
// session/net.ts
|
|
822
823
|
var NetClient = class {
|
|
823
824
|
constructor(client, bus, peerId) {
|
|
824
|
-
this
|
|
825
|
-
this
|
|
825
|
+
__publicField(this, "client", client);
|
|
826
|
+
__publicField(this, "bus", bus);
|
|
826
827
|
__publicField(this, "localPeerId");
|
|
827
828
|
__publicField(this, "remotePeerId");
|
|
828
829
|
__publicField(this, "isConnected", false);
|
|
@@ -1399,6 +1400,15 @@ var sync = (command) => {
|
|
|
1399
1400
|
if (command.type !== "SYNC_STATE") {
|
|
1400
1401
|
return;
|
|
1401
1402
|
}
|
|
1403
|
+
if (command.from === "local") {
|
|
1404
|
+
const payload = buildSyncPayload();
|
|
1405
|
+
send({ type: "SYNC_STATE", from: "", payload });
|
|
1406
|
+
consoleLogger.debug("[session:sync] state pushed", payload);
|
|
1407
|
+
if (isInSyncRecovery()) {
|
|
1408
|
+
restoreFromPayload(payload, false);
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1402
1412
|
restoreFromPayload(command.payload || {}, true);
|
|
1403
1413
|
};
|
|
1404
1414
|
|
|
@@ -1561,8 +1571,8 @@ var offline = (command) => {
|
|
|
1561
1571
|
return;
|
|
1562
1572
|
}
|
|
1563
1573
|
state.dispatch("remote", "ONLINE", "syncing");
|
|
1564
|
-
bus.emit("
|
|
1565
|
-
consoleLogger.debug("[session:connection] remote online, sync
|
|
1574
|
+
bus.emit("SYNC_STATE", void 0, "local");
|
|
1575
|
+
consoleLogger.debug("[session:connection] remote online, sync state pushed");
|
|
1566
1576
|
};
|
|
1567
1577
|
|
|
1568
1578
|
// session/handlers/busRegister.ts
|
|
@@ -1583,7 +1593,7 @@ var registerHandlers = (bus) => {
|
|
|
1583
1593
|
// session/actions.ts
|
|
1584
1594
|
var LocalActionsAPI = class {
|
|
1585
1595
|
constructor(bus) {
|
|
1586
|
-
this
|
|
1596
|
+
__publicField(this, "bus", bus);
|
|
1587
1597
|
}
|
|
1588
1598
|
ready() {
|
|
1589
1599
|
this.bus.emit("READY");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../utils/logger.ts","../../utils/serialization/json.ts","../../utils/protocol/session.ts","../../session/commandBus.ts","../../session/state/fsm.ts","../../session/observer/index.ts","../../session/state/state.ts","../../session/net.ts","../../session/context.ts","../../session/handlers/ready.ts","../../session/handlers/start.ts","../../session/handlers/move.ts","../../session/handlers/request.ts","../../session/handlers/sync.ts","../../session/handlers/undo.ts","../../session/handlers/restart.ts","../../session/handlers/offLine.ts","../../session/handlers/busRegister.ts","../../session/actions.ts","../../session/index.ts"],"sourcesContent":["export type Logger = {\n debug: (message: string, meta?: unknown) => void;\n info: (message: string, meta?: unknown) => void;\n warn: (message: string, meta?: unknown) => void;\n error: (message: string, meta?: unknown) => void;\n};\n\nconst logWith =\n (level: 'debug' | 'info' | 'warn' | 'error') =>\n (message: string, meta?: unknown) => {\n if (meta !== undefined) {\n // eslint-disable-next-line no-console\n console[level](message, meta);\n return;\n }\n // eslint-disable-next-line no-console\n console[level](message);\n };\n\nexport const consoleLogger: Logger = {\n debug: logWith('debug'),\n info: logWith('info'),\n warn: logWith('warn'),\n error: logWith('error'),\n};\n","export type Serialized = string;\n\nexport const encode = (value: unknown): Serialized => JSON.stringify(value);\n\nexport const decode = <T>(raw: Serialized): T => {\n if (typeof raw !== 'string') {\n throw new TypeError('decode expects a serialized string');\n }\n return JSON.parse(raw) as T;\n};\n\nexport const decodeSafe = <T>(\n raw: unknown,\n): { ok: true; value: T } | { ok: false; error: unknown } => {\n if (typeof raw !== 'string') {\n return {\n ok: false,\n error: new TypeError('decodeSafe expects a serialized string'),\n };\n }\n\n try {\n return { ok: true, value: JSON.parse(raw) as T };\n } catch (error) {\n return { ok: false, error };\n }\n};\n","import { decodeSafe } from '../serialization';\n\nexport type SessionMessageType =\n | 'READY'\n | 'START'\n | 'MOVE'\n | 'UNDO'\n | 'RESTART'\n | 'APPROVE'\n | 'REJECT'\n | 'REJOIN'\n | 'SYNC_REQUEST'\n | 'SYNC_STATE'\n | 'OFFLINE'\n | 'ONLINE'\n | 'GAME_OVER';\n\nexport type SessionMessage = {\n type: SessionMessageType;\n from?: string;\n seq?: number;\n sid?: string;\n turn?: number;\n stateHash?: string;\n payload?: any;\n};\n\nexport const parseSessionMessage = (\n data: unknown,\n): (Partial<SessionMessage> & { type?: string }) | null => {\n if (typeof data !== 'string') {\n if (!data || typeof data !== 'object') {\n return null;\n }\n return data as Partial<SessionMessage> & { type?: string };\n }\n\n const decoded = decodeSafe<Partial<SessionMessage> & { type?: string }>(\n data,\n );\n if (!decoded.ok || !decoded.value || typeof decoded.value !== 'object') {\n return null;\n }\n\n return decoded.value;\n};\n","import { consoleLogger, SessionMessage, SessionMessageType } from '../utils';\n\nexport type CommandOrigin = 'local' | 'remote';\nexport type BusMessageType =\n | SessionMessageType\n | 'OFFLINE'\n | 'ONLINE'\n | 'GAME_OVER';\nexport type BusMessage = Omit<SessionMessage, 'type'> & {\n type: BusMessageType;\n};\nexport type CommandListener = (message: BusMessage) => Promise<void> | void;\n\ntype HandlerMap = Partial<Record<BusMessageType, CommandListener>>;\n\nexport class CommandBus {\n private handlers: HandlerMap = {};\n private processingQueue: Promise<void> = Promise.resolve();\n\n public emit(\n type: BusMessageType,\n payload?: unknown,\n from: CommandOrigin = 'local',\n ): void {\n this.dispatch({ type, payload, from });\n }\n\n public register(type: BusMessageType, handler: CommandListener): void {\n this.handlers[type] = handler;\n consoleLogger.debug(`[session:bus] registered ${type}`);\n }\n\n public dispatch(message: BusMessage): void {\n this.processingQueue = this.processingQueue.then(async () => {\n consoleLogger.debug(`[session:bus] dispatch ${message.type}`, {\n from: message.from,\n payload: message.payload,\n turn: message.turn,\n sid: message.sid,\n });\n\n const handler = this.handlers[message.type];\n if (handler) {\n try {\n await handler(message);\n consoleLogger.debug(`[session:bus] handled ${message.type}`, {\n from: message.from,\n });\n } catch (err) {\n console.error(`[CommandBus] Error in ${message.type}:`, err);\n }\n return;\n }\n\n consoleLogger.debug(`[session:bus] no handler for ${message.type}`, {\n from: message.from,\n });\n });\n }\n}\n","export type SessionState =\n | 'idle'\n | 'ready'\n | 'could_start'\n | 'turn'\n | 'remote_turn'\n | 'approving'\n | 'waiting_approval'\n | 'syncing'\n | 'offline';\n\nexport type SessionEvent =\n | 'REMOTE_READY'\n | 'READY'\n | 'START'\n | 'REMOTE_START'\n | 'MOVE'\n | 'REMOTE_MOVE'\n | 'UNDO'\n | 'REMOTE_UNDO'\n | 'RESTART'\n | 'REMOTE_RESTART'\n | 'APPROVE'\n | 'REJECT'\n | 'GAME_OVER'\n | 'REJOIN'\n | 'SYNC'\n | 'SYNC_COMPLETE'\n | 'OFFLINE'\n | 'ONLINE';\n\nexport type Transition = {\n from: SessionState;\n event: SessionEvent;\n to: SessionState;\n};\n\nconst transitions: Transition[] = [\n // Lobby readiness\n { from: 'idle', event: 'READY', to: 'ready' },\n { from: 'ready', event: 'READY', to: 'idle' },\n { from: 'idle', event: 'REMOTE_READY', to: 'could_start' },\n { from: 'could_start', event: 'REMOTE_READY', to: 'idle' },\n { from: 'ready', event: 'REJECT', to: 'idle' },\n { from: 'could_start', event: 'REJECT', to: 'idle' },\n\n // Match start / turn assignment\n { from: 'ready', event: 'REMOTE_START', to: 'turn' },\n { from: 'ready', event: 'REMOTE_START', to: 'remote_turn' },\n { from: 'could_start', event: 'START', to: 'turn' },\n { from: 'could_start', event: 'START', to: 'remote_turn' },\n\n // Turn swapping after moves\n { from: 'turn', event: 'MOVE', to: 'remote_turn' },\n { from: 'remote_turn', event: 'REMOTE_MOVE', to: 'turn' },\n { from: 'turn', event: 'REJECT', to: 'turn' },\n { from: 'remote_turn', event: 'REJECT', to: 'remote_turn' },\n\n // Requests initiated by local player (undo/restart)\n { from: 'turn', event: 'UNDO', to: 'waiting_approval' },\n { from: 'remote_turn', event: 'UNDO', to: 'waiting_approval' },\n { from: 'turn', event: 'RESTART', to: 'waiting_approval' },\n { from: 'remote_turn', event: 'RESTART', to: 'waiting_approval' },\n\n // Requests coming from remote (we need to approve)\n { from: 'turn', event: 'REMOTE_UNDO', to: 'approving' },\n { from: 'remote_turn', event: 'REMOTE_UNDO', to: 'approving' },\n { from: 'turn', event: 'REMOTE_RESTART', to: 'approving' },\n { from: 'remote_turn', event: 'REMOTE_RESTART', to: 'approving' },\n\n // Approval outcomes when we were waiting for the peer.\n { from: 'waiting_approval', event: 'APPROVE', to: 'turn' },\n { from: 'waiting_approval', event: 'REJECT', to: 'turn' },\n { from: 'waiting_approval', event: 'REJECT', to: 'remote_turn' },\n\n // Approval outcomes when we were confirming the peer's request.\n // The target turn is explicit because resumeTurn decides who continues.\n { from: 'approving', event: 'APPROVE', to: 'remote_turn' },\n { from: 'approving', event: 'REJECT', to: 'remote_turn' },\n { from: 'approving', event: 'REJECT', to: 'turn' },\n\n // Game end resets back to lobby idle\n { from: 'turn', event: 'GAME_OVER', to: 'idle' },\n { from: 'remote_turn', event: 'GAME_OVER', to: 'idle' },\n\n // Rejoin/sync flows\n { from: 'turn', event: 'SYNC', to: 'syncing' },\n { from: 'remote_turn', event: 'SYNC', to: 'syncing' },\n { from: 'waiting_approval', event: 'SYNC', to: 'syncing' },\n { from: 'approving', event: 'SYNC', to: 'syncing' },\n { from: 'idle', event: 'SYNC', to: 'syncing' },\n { from: 'ready', event: 'SYNC', to: 'syncing' },\n { from: 'could_start', event: 'SYNC', to: 'syncing' },\n { from: 'syncing', event: 'SYNC_COMPLETE', to: 'turn' },\n { from: 'syncing', event: 'SYNC_COMPLETE', to: 'remote_turn' },\n\n // Connection state\n { from: 'idle', event: 'OFFLINE', to: 'offline' },\n { from: 'ready', event: 'OFFLINE', to: 'offline' },\n { from: 'could_start', event: 'OFFLINE', to: 'offline' },\n { from: 'turn', event: 'OFFLINE', to: 'offline' },\n { from: 'remote_turn', event: 'OFFLINE', to: 'offline' },\n { from: 'waiting_approval', event: 'OFFLINE', to: 'offline' },\n { from: 'approving', event: 'OFFLINE', to: 'offline' },\n { from: 'syncing', event: 'OFFLINE', to: 'offline' },\n { from: 'offline', event: 'ONLINE', to: 'syncing' },\n];\n// only receive 'to' from state. never receive from handlers.\nconst nextState = (\n state: SessionState,\n event: SessionEvent,\n to?: SessionState, // next turn or pending action\n): SessionState => {\n if (to) {\n if (\n !!transitions.find(\n (t) => t.from === state && t.event === event && t.to === to,\n )\n ) {\n return to;\n } else {\n return state;\n }\n } else {\n const hit = transitions.find((t) => t.from === state && t.event === event);\n return hit ? hit.to : state;\n }\n};\n\nconst hasNextState = (\n state: SessionState,\n action: SessionEvent,\n to?: SessionState,\n): boolean => {\n if (to) {\n return !!transitions.find(\n (t) => t.from === state && t.event === action && t.to === to,\n );\n }\n return !!transitions.find((t) => t.from === state && t.event === action);\n};\n\nexport class SessionFsm {\n private state: SessionState;\n\n constructor(state: SessionState = 'idle') {\n this.state = state;\n }\n\n public getState(): SessionState {\n return this.state;\n }\n\n public hasNextState(event: SessionEvent, to?: SessionState): boolean {\n return hasNextState(this.state, event, to);\n }\n\n public dispatch(action: SessionEvent, to?: SessionState) {\n this.state = nextState(this.state, action, to);\n }\n}\n","import type { PlayerLabel, TurnEntry, State } from '../state/state';\nimport type { SessionState } from '../state/fsm';\nimport { consoleLogger } from '../../utils';\n\nexport interface GameStateSnapshot {\n localState: SessionState;\n remoteState: SessionState;\n turn: number;\n history: TurnEntry[];\n lastStart: PlayerLabel | null;\n pendingAction: 'undo' | 'restart' | null;\n connected: boolean;\n}\n\nexport interface GameEvent {\n type:\n | 'READY'\n | 'START'\n | 'MOVE'\n | 'GAME_OVER'\n | 'UNDO'\n | 'RESTART'\n | 'OFFLINE'\n | 'ONLINE'\n | 'SYNC'\n | 'ERROR';\n payload?: any;\n from?: 'local' | 'remote';\n timestamp?: number;\n}\n\nexport interface IGameObserver {\n onStateChange(snapshot: GameStateSnapshot): void;\n onGameEvent(event: GameEvent): void;\n onConnectionChange?(connected: boolean): void;\n onError?(error: { message: string; context?: any }): void;\n}\n\nexport interface IStateObserver {\n onStateChanged?(): void;\n onHistoryChanged?(): void;\n onGameReset?(): void;\n}\n\nexport interface IGamePlugin {\n validateMove(move: unknown, gameState: GameState): ValidationResult;\n checkWin(gameState: GameState, history: TurnEntry[]): PlayerLabel | null;\n initialize?(): void;\n cleanup?(): void;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n reason?: string;\n}\n\nexport interface GameState {\n history: TurnEntry[];\n localState: 'turn' | 'remote_turn' | string;\n remoteState: 'turn' | 'remote_turn' | string;\n turn: number;\n lastStart: PlayerLabel | null;\n}\n\nexport class DefaultGamePlugin implements IGamePlugin {\n validateMove(): ValidationResult {\n return { valid: true };\n }\n checkWin(): PlayerLabel | null {\n return null;\n }\n}\n\nexport class StateObserverManager {\n private observers: Set<IStateObserver> = new Set();\n\n subscribe(observer: IStateObserver): void {\n this.observers.add(observer);\n }\n\n unsubscribe(observer: IStateObserver): void {\n this.observers.delete(observer);\n }\n\n notifyStateChanged(): void {\n for (const observer of this.observers) {\n try {\n observer.onStateChanged?.();\n } catch (err) {\n console.error('[StateObserver]', err);\n }\n }\n }\n\n notifyHistoryChanged(): void {\n for (const observer of this.observers) {\n try {\n observer.onHistoryChanged?.();\n } catch (err) {\n console.error('[StateObserver]', err);\n }\n }\n }\n\n notifyGameReset(): void {\n for (const observer of this.observers) {\n try {\n observer.onGameReset?.();\n } catch (err) {\n console.error('[StateObserver]', err);\n }\n }\n }\n}\n\nexport class GameStateObserver {\n private observers: Set<IGameObserver> = new Set();\n private currentSnapshot: GameStateSnapshot | null = null;\n\n subscribe(observer: IGameObserver): () => void {\n this.observers.add(observer);\n return () => {\n this.observers.delete(observer);\n };\n }\n\n unsubscribe(observer: IGameObserver): void {\n this.observers.delete(observer);\n }\n\n notifyStateChange(snapshot: GameStateSnapshot): void {\n this.currentSnapshot = snapshot;\n for (const observer of this.observers) {\n try {\n observer.onStateChange(snapshot);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n notifyGameEvent(event: GameEvent): void {\n event.timestamp = Date.now();\n for (const observer of this.observers) {\n try {\n observer.onGameEvent(event);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n notifyConnectionChange(connected: boolean): void {\n for (const observer of this.observers) {\n try {\n observer.onConnectionChange?.(connected);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n notifyError(error: { message: string; context?: any }): void {\n for (const observer of this.observers) {\n try {\n observer.onError?.(error);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n getSnapshot(): GameStateSnapshot | null {\n return this.currentSnapshot;\n }\n\n getObserverCount(): number {\n return this.observers.size;\n }\n}\n\nexport function buildGameStateSnapshot(\n state: State,\n connected: boolean = false,\n): GameStateSnapshot {\n return {\n localState: state.getState('local'),\n remoteState: state.getState('remote'),\n turn: state.getTurnCount(),\n history: state.getHistory(),\n lastStart: state.getLastStart(),\n pendingAction: state.getPendingAction(),\n connected,\n };\n}\n\nexport class UINotificationAdapter implements IStateObserver {\n private lastNotificationTime = 0;\n private notificationThrottleMs = 0;\n\n constructor(\n private stateRef: State,\n private uiObserver: GameStateObserver,\n private getConnected: () => boolean = () => false,\n ) {}\n\n onStateChanged(): void {\n const now = Date.now();\n if (this.lastNotificationTime + this.notificationThrottleMs > now) return;\n this.lastNotificationTime = now;\n\n const snapshot = buildGameStateSnapshot(this.stateRef, this.getConnected());\n consoleLogger.debug('[session:observer] state snapshot', {\n local: snapshot.localState,\n remote: snapshot.remoteState,\n turn: snapshot.turn,\n history: snapshot.history.length,\n pending: snapshot.pendingAction,\n connected: snapshot.connected,\n });\n this.uiObserver.notifyStateChange(snapshot);\n }\n\n onHistoryChanged(): void {\n this.onStateChanged();\n }\n\n onGameReset(): void {\n this.onStateChanged();\n }\n\n emitEvent(event: Omit<GameEvent, 'timestamp'>): void {\n this.uiObserver.notifyGameEvent(event as GameEvent);\n }\n}\n","import { SessionEvent, SessionFsm, SessionState } from './fsm';\nimport type {\n IGamePlugin,\n GameState,\n ValidationResult,\n IStateObserver,\n} from '../observer';\nimport { DefaultGamePlugin, StateObserverManager } from '../observer';\nimport { consoleLogger } from '../../utils';\n\nexport type TurnEntry = {\n turn: number;\n player: 'local' | 'remote';\n move?: any;\n};\n\nexport type PlayerLabel = 'local' | 'remote';\n\nexport class State {\n // will update map when multi-players (>=3)\n private local = new SessionFsm('idle');\n private remote = new SessionFsm('idle');\n // for compare remote is same people or not\n private readonly localId: string | null = null;\n private remoteId: string | null = null;\n // store all actions\n private readonly history: TurnEntry[] = [];\n // pending some state\n private pendingAction: 'undo' | 'restart' | null = null;\n private pendingUndoCount: 1 | 2 | null = null;\n private resumeTurn: PlayerLabel | null = null;\n private lastStart: PlayerLabel | null = null;\n\n // Game plugin for rule validation and win checking\n private gamePlugin: IGamePlugin = new DefaultGamePlugin();\n\n // Internal state observer for UI notifications\n private stateObserverManager = new StateObserverManager();\n\n constructor(id: string | null, remoteId: string | null) {\n if (id) {\n this.localId = id;\n }\n if (remoteId) {\n this.remoteId = remoteId;\n }\n consoleLogger.debug('[session:state] created', { localId: id, remoteId });\n }\n\n /**\n * Register an internal observer (like plugin pattern)\n * Use this to connect State mutations to UI updates\n */\n public subscribeStateObserver(observer: IStateObserver): void {\n this.stateObserverManager.subscribe(observer);\n }\n\n // ...existing code...\n\n public getId(): string | null {\n return this.localId;\n }\n\n public getremoteId(): string | null {\n return this.remoteId;\n }\n\n public setremoteId(id: string) {\n this.remoteId = id;\n consoleLogger.debug('[session:state] remote id set', { remoteId: id });\n }\n\n public getState(player: PlayerLabel): SessionState {\n return this.getPlayerFsm(player).getState();\n }\n\n public getTurnCount(): number {\n return this.history.length + 1;\n }\n\n public getHistory(): TurnEntry[] {\n return this.history.slice();\n }\n\n public replaceHistory(entries: TurnEntry[]): void {\n consoleLogger.debug('[session:history] replace', { count: entries.length });\n this.clearHistory();\n entries.forEach((entry) => {\n this.pushHistory({\n turn: entry.turn,\n player: entry.player,\n move: entry.move,\n });\n });\n }\n\n public clearHistory(): void {\n const count = this.history.length;\n this.history.splice(0, this.history.length);\n consoleLogger.debug('[session:history] clear', { count });\n this.notifyHistoryChanged();\n }\n\n public pushHistory(entry: TurnEntry): void {\n this.history.push(entry);\n consoleLogger.debug('[session:history] push', {\n turn: entry.turn,\n player: entry.player,\n move: entry.move,\n count: this.history.length,\n });\n this.notifyHistoryChanged();\n }\n\n public popHistory(): TurnEntry | null {\n const entry = this.history.pop() ?? null;\n if (entry) {\n consoleLogger.debug('[session:history] pop', {\n turn: entry.turn,\n player: entry.player,\n move: entry.move,\n count: this.history.length,\n });\n this.notifyHistoryChanged();\n }\n return entry;\n }\n\n public canAction(player: PlayerLabel, action: SessionEvent): boolean {\n return this.getPlayerFsm(player).hasNextState(action);\n }\n\n /**\n * Dispatch an action and automatically determine target state if unique\n * Only use explicit 'to' parameter for ambiguous transitions (APPROVE, REJECT, etc.)\n *\n * For most actions (READY, MOVE, START, etc.), there's only one valid transition,\n * so we automatically find and apply it.\n */\n public dispatch(\n player: PlayerLabel,\n action: SessionEvent,\n to?: SessionState,\n ): void {\n const before = this.getState(player);\n this.getPlayerFsm(player).dispatch(action, to);\n const after = this.getState(player);\n consoleLogger.debug(`[session:fsm] ${player} ${action}`, {\n from: before,\n to: after,\n requested: to,\n local: this.getState('local'),\n remote: this.getState('remote'),\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.notifyStateChanged();\n }\n\n public setPendingAction(action: 'undo' | 'restart' | null) {\n consoleLogger.debug('[session:state] pending action set', {\n from: this.pendingAction,\n to: action,\n });\n this.pendingAction = action;\n }\n\n public getPendingAction() {\n return this.pendingAction;\n }\n\n public setPendingUndoCount(count: 1 | 2 | null) {\n consoleLogger.debug('[session:state] pending undo count set', {\n from: this.pendingUndoCount,\n to: count,\n });\n this.pendingUndoCount = count;\n }\n\n public getPendingUndoCount(): 1 | 2 | null {\n return this.pendingUndoCount;\n }\n\n public setLastStart(player: PlayerLabel | null) {\n consoleLogger.debug('[session:state] last start set', {\n from: this.lastStart,\n to: player,\n });\n this.lastStart = player;\n }\n\n public getLastStart(): PlayerLabel | null {\n return this.lastStart;\n }\n\n public setResumeTurn(player: PlayerLabel | null) {\n consoleLogger.debug('[session:state] resume turn set', {\n from: this.resumeTurn,\n to: player,\n });\n this.resumeTurn = player;\n }\n\n public getResumeTurn(): PlayerLabel | null {\n return this.resumeTurn;\n }\n\n private getPlayerFsm(player: PlayerLabel): SessionFsm {\n return player === 'local' ? this.local : this.remote;\n }\n\n private notifyStateChanged(): void {\n consoleLogger.debug('[session:state] notify state changed', {\n local: this.getState('local'),\n remote: this.getState('remote'),\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.stateObserverManager.notifyStateChanged();\n }\n\n private notifyHistoryChanged(): void {\n consoleLogger.debug('[session:state] notify history changed', {\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.stateObserverManager.notifyHistoryChanged();\n }\n\n private notifyGameReset(): void {\n consoleLogger.debug('[session:state] notify game reset');\n this.stateObserverManager.notifyGameReset();\n }\n\n private dispatchPair(\n localAction: SessionEvent,\n localTo: SessionState,\n remoteAction: SessionEvent,\n remoteTo: SessionState,\n ): void {\n const before = {\n local: this.local.getState(),\n remote: this.remote.getState(),\n };\n this.local.dispatch(localAction, localTo);\n this.remote.dispatch(remoteAction, remoteTo);\n consoleLogger.debug('[session:fsm] pair dispatch', {\n before,\n after: {\n local: this.local.getState(),\n remote: this.remote.getState(),\n },\n localAction,\n localTo,\n remoteAction,\n remoteTo,\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.notifyStateChanged();\n }\n\n // ===== Helper Methods for Undo/Restart Request Handling =====\n\n /**\n * Save game state snapshot for undo/restart operations\n */\n private gameSnapshot: unknown = null;\n\n public saveGameSnapshot(snapshot: unknown): void {\n this.gameSnapshot = snapshot;\n consoleLogger.debug('[session:state] game snapshot saved', { snapshot });\n }\n\n public getGameSnapshot(): unknown {\n return this.gameSnapshot;\n }\n\n public clearGameSnapshot(): void {\n this.gameSnapshot = null;\n consoleLogger.debug('[session:state] game snapshot cleared');\n }\n\n /**\n * Check if there's a pending action (undo/restart)\n */\n public hasPendingAction(): boolean {\n return this.pendingAction !== null;\n }\n\n /**\n * Clear all pending states (called after approval/rejection)\n */\n public clearPendingStates(): void {\n consoleLogger.debug('[session:state] pending states cleared', {\n pending: this.pendingAction,\n pendingUndoCount: this.pendingUndoCount,\n resumeTurn: this.resumeTurn,\n });\n this.pendingAction = null;\n this.pendingUndoCount = null;\n this.resumeTurn = null;\n this.notifyStateChanged();\n }\n\n /**\n * Initialize undo request with undo count and current turn holder\n */\n public initializeUndoRequest(\n undoCount: 1 | 2,\n resumeTurn: PlayerLabel,\n ): void {\n this.pendingAction = 'undo';\n this.pendingUndoCount = undoCount;\n this.resumeTurn = resumeTurn;\n consoleLogger.debug('[session:state] undo request initialized', {\n undoCount,\n resumeTurn,\n });\n }\n\n /**\n * Initialize restart request with resume turn\n */\n public initializeRestartRequest(resumeTurn: PlayerLabel): void {\n this.pendingAction = 'restart';\n this.resumeTurn = resumeTurn;\n consoleLogger.debug('[session:state] restart request initialized', {\n resumeTurn,\n });\n }\n\n /**\n * Check if pending action is undo\n */\n public isPendingUndo(): boolean {\n return this.pendingAction === 'undo';\n }\n\n /**\n * Check if pending action is restart\n */\n public isPendingRestart(): boolean {\n return this.pendingAction === 'restart';\n }\n\n /**\n * Apply undo by popping history N times\n */\n public applyUndo(count: 1 | 2 = 1): void {\n consoleLogger.debug('[session:history] apply undo', { count });\n for (let i = 0; i < count; i++) {\n this.popHistory();\n }\n }\n\n /**\n * Reset game state to initial (for restart)\n */\n public resetGame(): void {\n consoleLogger.debug('[session:state] reset game', {\n local: this.getState('local'),\n remote: this.getState('remote'),\n history: this.history.length,\n lastStart: this.lastStart,\n pending: this.pendingAction,\n });\n this.clearHistory();\n this.local = new SessionFsm('idle');\n this.remote = new SessionFsm('idle');\n this.lastStart = null;\n this.pendingAction = null;\n this.pendingUndoCount = null;\n this.resumeTurn = null;\n this.notifyGameReset();\n this.notifyStateChanged();\n }\n\n /**\n * Save start player for rejoin flow\n */\n public recordStartPlayer(player: PlayerLabel): void {\n this.lastStart = player;\n consoleLogger.debug('[session:state] start player recorded', { player });\n }\n\n /**\n * Get move to undo from history\n */\n public getLastMove(): TurnEntry | null {\n return this.history.length > 0\n ? this.history[this.history.length - 1]\n : null;\n }\n\n // ===== Specialized FSM Dispatch Methods =====\n\n /**\n * Dispatch APPROVE action with automatic target state resolution\n * Multiple valid transitions exist - use state context to determine target\n */\n public dispatchApprove(): void {\n const localState = this.local.getState();\n if (localState === 'waiting_approval') {\n // Local requested the action; peer approved it.\n this.dispatchPair('APPROVE', 'turn', 'APPROVE', 'remote_turn');\n } else if (localState === 'approving') {\n // Peer requested the action; local approved it.\n this.dispatchPair('APPROVE', 'remote_turn', 'APPROVE', 'turn');\n }\n }\n\n /**\n * Dispatch REJECT action with automatic target state resolution\n * Multiple valid transitions exist - use resumeTurn to determine who continues\n */\n public dispatchReject(): void {\n const localState = this.local.getState();\n\n if (localState === 'waiting_approval' || localState === 'approving') {\n // resumeTurn tells us who should have turn after rejection\n const localTarget = this.resumeTurn === 'local' ? 'turn' : 'remote_turn';\n const remoteTarget = this.resumeTurn === 'local' ? 'remote_turn' : 'turn';\n this.dispatchPair('REJECT', localTarget, 'REJECT', remoteTarget);\n }\n }\n\n /**\n * Dispatch START action with automatic target state resolution\n * Determines who plays first based on starter parameter\n */\n public dispatchStart(firstPlayer: PlayerLabel): void {\n const before = {\n local: this.local.getState(),\n remote: this.remote.getState(),\n lastStart: this.lastStart,\n };\n if (firstPlayer === 'local') {\n this.local.dispatch('START', 'turn');\n this.remote.dispatch('START', 'remote_turn');\n this.lastStart = 'local';\n } else {\n this.local.dispatch('START', 'remote_turn');\n this.remote.dispatch('START', 'turn');\n this.lastStart = 'remote';\n }\n consoleLogger.debug('[session:fsm] start dispatch', {\n before,\n firstPlayer,\n after: {\n local: this.local.getState(),\n remote: this.remote.getState(),\n lastStart: this.lastStart,\n },\n });\n this.notifyStateChanged();\n }\n\n /**\n * Dispatch SYNC_COMPLETE with automatic target state resolution\n * Based on who should have the turn after sync\n */\n public dispatchSyncComplete(nextPlayer: PlayerLabel): void {\n if (nextPlayer === 'local') {\n this.dispatchPair(\n 'SYNC_COMPLETE',\n 'turn',\n 'SYNC_COMPLETE',\n 'remote_turn',\n );\n } else {\n this.dispatchPair(\n 'SYNC_COMPLETE',\n 'remote_turn',\n 'SYNC_COMPLETE',\n 'turn',\n );\n }\n this.resumeTurn = null;\n }\n\n // ===== Game Plugin Integration (Proxy Pattern) =====\n\n /**\n * Set the game plugin for rule validation and win checking\n * @param plugin Implementation of IGamePlugin\n */\n public setGamePlugin(plugin: IGamePlugin): void {\n this.gamePlugin = plugin;\n consoleLogger.debug('[session:plugin] game plugin set', {\n hasInitialize: Boolean(plugin.initialize),\n hasCleanup: Boolean(plugin.cleanup),\n });\n if (plugin.initialize) {\n plugin.initialize();\n }\n }\n\n /**\n * Get current game plugin\n */\n public getGamePlugin(): IGamePlugin {\n return this.gamePlugin;\n }\n\n /**\n * Validate a move using the game plugin\n * Called by move handler to check if move is legal\n * @param move The move data to validate\n * @returns Validation result with reason if invalid\n */\n public validateMove(move: unknown): ValidationResult {\n const gameState = this.buildGameState();\n const result = this.gamePlugin.validateMove(move, gameState);\n consoleLogger.debug('[session:plugin] validate move', {\n move,\n result,\n local: gameState.localState,\n remote: gameState.remoteState,\n turn: gameState.turn,\n history: gameState.history.length,\n });\n return result;\n }\n\n /**\n * Check if game has ended (someone won)\n * Called by move handler after move is applied\n * @returns Winner (local/remote) or null if game continues\n */\n public checkWin(): PlayerLabel | null {\n const gameState = this.buildGameState();\n const winner = this.gamePlugin.checkWin(gameState, this.getHistory());\n consoleLogger.debug('[session:plugin] check win', {\n winner,\n turn: gameState.turn,\n history: gameState.history.length,\n });\n return winner;\n }\n\n /**\n * Cleanup when game ends (for plugin to reset internal state)\n */\n public cleanupGame(): void {\n if (this.gamePlugin.cleanup) {\n this.gamePlugin.cleanup();\n }\n consoleLogger.debug('[session:plugin] cleanup game');\n }\n\n /**\n * Build game state for plugin\n * @private\n */\n private buildGameState(): GameState {\n return {\n history: this.getHistory(),\n localState: this.getState('local'),\n remoteState: this.getState('remote'),\n turn: this.getTurnCount(),\n lastStart: this.getLastStart(),\n };\n }\n}\n","import type { NetworkClient } from 'p2p-lockstep-kit-network';\nimport { parseSessionMessage, type SessionMessage } from '../utils';\nimport type { BusMessageType, CommandBus } from './commandBus';\n\n/**\n * Network client wrapper that bridges NetworkClient with CommandBus\n * Handles message encoding/decoding and connection state monitoring\n */\nexport class NetClient {\n private localPeerId: string | null;\n private remotePeerId: string | null;\n private isConnected: boolean = false;\n private connectionChangeListener: (isConnected: boolean) => void = () => {};\n private mediaStateListener: (active: boolean) => void = () => {};\n\n public constructor(\n private readonly client: NetworkClient,\n private readonly bus: CommandBus,\n peerId: string | null,\n ) {\n this.localPeerId = peerId ?? null;\n this.remotePeerId = null;\n\n // Forward incoming messages to the command bus\n this.client.onMessage((data) => {\n const message = parseSessionMessage(data);\n if (!message || typeof message !== 'object' || !message.type) {\n return;\n }\n this.bus.dispatch({\n ...(message as SessionMessage),\n type: message.type as BusMessageType,\n from: 'remote',\n });\n });\n\n // Monitor connection state and emit ONLINE/OFFLINE events\n this.client.onStateChange((state) => {\n const wasConnected = this.isConnected;\n this.isConnected = state === 'connected';\n\n // Notify listeners of connection state change\n this.connectionChangeListener(this.isConnected);\n\n // Only emit ONLINE/OFFLINE once when state changes\n if (this.isConnected && !wasConnected) {\n this.bus.emit('ONLINE', undefined, 'local');\n } else if (!this.isConnected && wasConnected) {\n this.bus.emit('OFFLINE', undefined, 'local');\n }\n });\n\n // Monitor remote media stream availability\n this.client.onRemoteStream((stream) => {\n const active =\n !!stream &&\n stream.getTracks().some((track) => track.readyState === 'live');\n this.mediaStateListener(active);\n });\n }\n\n /**\n * Send a message to the remote peer\n * Drops message if not connected and logs warning\n */\n public send(message: SessionMessage) {\n if (!this.isConnected) {\n console.warn(\n '[NetClient] Cannot send message: not connected',\n message.type,\n );\n return;\n }\n\n const enriched: SessionMessage = {\n ...message,\n from: message.from ?? this.localPeerId ?? '',\n };\n this.client.send(enriched);\n }\n\n /**\n * Update local and remote peer IDs\n */\n public setPeerIds(ids: { local?: string | null; remote?: string | null }) {\n if (ids.local !== undefined) {\n this.localPeerId = ids.local;\n }\n if (ids.remote !== undefined) {\n this.remotePeerId = ids.remote;\n }\n }\n\n /**\n * Get current peer IDs\n */\n public getPeerIds() {\n return { local: this.localPeerId, remote: this.remotePeerId };\n }\n\n /**\n * Check if currently connected to peer\n */\n public getIsConnected(): boolean {\n return this.isConnected;\n }\n\n /**\n * Monitor connection state changes\n * @param handler Called when connection state changes (true=connected, false=disconnected)\n */\n public onConnectionChange(handler: (isConnected: boolean) => void) {\n this.connectionChangeListener = handler;\n // Immediately call with current state if already have a state\n handler(this.isConnected);\n }\n\n /**\n * Monitor remote media stream state changes\n * @param handler Called when remote media becomes available or unavailable\n */\n public onMediaStateChange(handler: (active: boolean) => void) {\n this.mediaStateListener = handler;\n }\n}\n\nexport const createNetClient = (\n client: NetworkClient,\n bus: CommandBus,\n peerId: string | null,\n) => new NetClient(client, bus, peerId);\n","import type { State } from './state/state';\nimport type { CommandBus } from './commandBus';\nimport type { SessionMessage } from '../utils';\nimport { NetClient } from './net';\n\n/**\n * Global session context holder\n * Stores the current session's dependencies for handler access\n * Used by handlers to retrieve state, bus, and network client\n */\nclass SessionContext {\n private readonly state: State;\n private readonly bus: CommandBus;\n private readonly net: NetClient;\n private readonly sid?: string;\n\n constructor(state: State, bus: CommandBus, net: NetClient, sid?: string) {\n this.state = state;\n this.bus = bus;\n this.net = net;\n this.sid = sid;\n }\n\n getState() {\n return this.state;\n }\n\n getBus() {\n return this.bus;\n }\n\n getNet() {\n return this.net;\n }\n\n getSid() {\n return this.sid;\n }\n}\n\nlet instance: SessionContext | null = null;\n\n/**\n * Initialize the global session context\n * Must be called before handlers use context getters\n * @throws Error if context is accessed before initialization\n */\nexport const initializeContext = (\n state: State,\n bus: CommandBus,\n net: NetClient,\n sid?: string,\n) => {\n instance = new SessionContext(state, bus, net, sid);\n};\n\n/**\n * Reset the session context (for testing)\n * @internal Used by tests to clean up between test cases\n */\nexport const resetContext = () => {\n instance = null;\n};\n\n/**\n * Get the current session context\n * @throws Error if context has not been initialized\n * @internal Use getState(), getBus(), etc. instead\n */\nconst requireContext = () => {\n if (!instance) {\n throw new Error(\n '[SessionContext] Not initialized. Call initializeContext() first.',\n );\n }\n return instance;\n};\n\n/**\n * Get the current game state\n * @throws Error if context has not been initialized\n */\nexport const getState = () => requireContext().getState();\n\n/**\n * Get the command bus\n * @throws Error if context has not been initialized\n */\nexport const getBus = () => requireContext().getBus();\n\n/**\n * Get the network client\n * @throws Error if context has not been initialized\n * @internal Prefer using send() for sending messages\n */\nexport const getNet = () => requireContext().getNet();\n\n/**\n * Get the session ID\n * @throws Error if context has not been initialized\n */\nexport const getSid = () => requireContext().getSid();\n\n/**\n * Send a session message to the remote peer\n * @throws Error if context has not been initialized\n */\nexport const send = (message: SessionMessage) =>\n requireContext().getNet().send(message);\n\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getSid, getState, send } from '../context';\nimport { consoleLogger, type SessionMessage } from '../../utils';\n\n/**\n * Handle player ready status notification\n *\n * For most cases, there's only one valid FSM transition:\n * - READY: idle → ready (for initiator)\n * - REMOTE_READY: idle → could_start (for peer)\n *\n * The dispatch() method automatically finds the unique transition.\n */\nexport const ready: CommandListener = (command) => {\n const state = getState();\n const bus = getBus();\n const localSid = getSid();\n consoleLogger.debug('[session:ready] received', {\n from: command.from,\n sid: (command as any).sid,\n localSid,\n local: state.getState('local'),\n remote: state.getState('remote'),\n });\n\n if (command.from === 'local') {\n // Local player initiating READY\n if (!state.canAction('local', 'READY')) {\n console.warn('[Ready] Cannot dispatch READY from current state', {\n state: state.getState('local'),\n });\n return;\n }\n\n state.dispatch('local', 'READY');\n state.dispatch('remote', 'REMOTE_READY');\n\n const message: SessionMessage = {\n type: 'READY',\n sid: localSid,\n };\n send(message);\n consoleLogger.debug('[session:ready] local toggled', {\n local: state.getState('local'),\n remote: state.getState('remote'),\n });\n return;\n }\n\n // Remote player sent READY message\n const remoteSid = (command as any).sid;\n\n // Validate session ID\n if (!remoteSid || localSid !== remoteSid) {\n console.warn('[Ready] Session ID mismatch', {\n local: localSid,\n remote: remoteSid,\n });\n bus.emit('REJECT', { reason: 'sid-mismatch' }, 'local');\n return;\n }\n\n // Check if transition is valid\n if (!state.canAction('remote', 'READY')) {\n console.warn('[Ready] Cannot dispatch READY for remote peer', {\n state: state.getState('remote'),\n });\n return;\n }\n\n // Execute state transitions\n state.dispatch('remote', 'READY');\n state.dispatch('local', 'REMOTE_READY');\n consoleLogger.debug('[session:ready] remote toggled', {\n local: state.getState('local'),\n remote: state.getState('remote'),\n });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport type { PlayerLabel } from '../state/state';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Determine next starter (turn order rotation)\n * If no previous starter, randomly pick one\n * Otherwise, alternate between local and remote\n */\nconst getNextStarter = (lastStarter: PlayerLabel | null): PlayerLabel => {\n if (!lastStarter) {\n return Math.random() < 0.5 ? 'local' : 'remote';\n }\n return lastStarter === 'local' ? 'remote' : 'local';\n};\n\n/**\n * Handle game start request\n *\n * Determines who plays first and transitions both FSMs accordingly.\n */\nexport const start: CommandListener = (command) => {\n const state = getState();\n consoleLogger.debug('[session:start] received', {\n from: command.from,\n payload: command.payload,\n local: state.getState('local'),\n remote: state.getState('remote'),\n lastStart: state.getLastStart(),\n });\n\n if (command.from === 'local') {\n // Local player initiating START\n if (\n !state.canAction('local', 'START') ||\n !state.canAction('remote', 'REMOTE_START')\n ) {\n console.warn('[Start] Cannot START from current state', {\n localState: state.getState('local'),\n remoteState: state.getState('remote'),\n });\n return;\n }\n\n const nextStarter = getNextStarter(state.getLastStart());\n const localTarget = nextStarter === 'local' ? 'turn' : 'remote_turn';\n const remoteTarget = nextStarter === 'local' ? 'remote_turn' : 'turn';\n\n if (state.getHistory().length > 0) {\n state.clearHistory();\n consoleLogger.debug('[session:start] cleared previous match history');\n }\n state.setLastStart(nextStarter);\n state.dispatch('local', 'START', localTarget);\n state.dispatch('remote', 'REMOTE_START', remoteTarget);\n\n // Send message with starter info (encoded as 'sender'/'receiver')\n send({\n type: 'START',\n payload: { starter: nextStarter === 'local' ? 'sender' : 'receiver' },\n });\n consoleLogger.debug('[session:start] local started', { nextStarter });\n return;\n }\n\n // Remote player sent START message\n const starterInfo = (command as any).payload?.starter;\n\n if (!starterInfo) {\n console.warn('[Start] Invalid START message format', { payload: command });\n return;\n }\n\n // Check if transition is valid\n if (\n !state.canAction('local', 'REMOTE_START') ||\n !state.canAction('remote', 'START')\n ) {\n console.warn('[Start] Cannot START from current state', {\n localState: state.getState('local'),\n remoteState: state.getState('remote'),\n });\n return;\n }\n\n // Decode from the receiver's perspective: the sender is the remote peer.\n const starter = starterInfo === 'sender' ? 'remote' : 'local';\n const localTarget = starter === 'local' ? 'turn' : 'remote_turn';\n const remoteTarget = starter === 'local' ? 'remote_turn' : 'turn';\n\n if (state.getHistory().length > 0) {\n state.clearHistory();\n consoleLogger.debug('[session:start] cleared previous match history');\n }\n state.setLastStart(starter);\n state.dispatch('local', 'REMOTE_START', localTarget);\n state.dispatch('remote', 'START', remoteTarget);\n consoleLogger.debug('[session:start] remote started', { starter });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport { consoleLogger, type SessionMessage } from '../../utils';\n\n/**\n * Handle player move in game\n *\n * Flow:\n * 1. Validate move using game plugin (rule checking)\n * 2. Dispatch state transitions\n * 3. Record move in history\n * 4. Check win condition using game plugin\n * 5. Always send MOVE to peer, then let both peers check GAME_OVER locally\n *\n * The game plugin is injected via state.setGamePlugin(), allowing\n * different games to provide their own rule validation and win logic.\n */\nexport const move: CommandListener = (command) => {\n const state = getState();\n const movePayload = command.payload;\n\n consoleLogger.debug('[session:move] received', {\n from: command.from,\n payload: movePayload,\n local: state.getState('local'),\n remote: state.getState('remote'),\n turn: state.getTurnCount(),\n history: state.getHistory().length,\n });\n\n if (command.from === 'local') {\n // Local player making a move\n if (!state.canAction('local', 'MOVE')) {\n console.warn('[Move] Cannot MOVE from current state', {\n state: state.getState('local'),\n });\n return;\n }\n\n // ===== PROXY POINT 1: Validate move using game plugin =====\n const validation = state.validateMove(movePayload);\n if (!validation.valid) {\n console.warn('[Move] Move validation failed', {\n reason: validation.reason,\n move: movePayload,\n });\n // Could send REJECT to inform peer, but for now silently return\n return;\n }\n\n // Dispatch state transitions\n state.dispatch('local', 'MOVE');\n state.dispatch('remote', 'REMOTE_MOVE');\n\n // Record move in history\n const turn = state.getTurnCount();\n state.pushHistory({\n turn,\n player: 'local',\n move: movePayload,\n });\n\n const message: SessionMessage = {\n type: 'MOVE',\n turn,\n payload: movePayload,\n };\n send(message);\n consoleLogger.debug('[session:move] local move sent', {\n turn,\n payload: movePayload,\n });\n\n // ===== PROXY POINT 2: Check win condition using game plugin =====\n const winner = state.checkWin();\n if (winner) {\n // Game ended - someone won\n consoleLogger.debug('[session:move] game over detected', {\n winner,\n turn,\n });\n\n // Transition both FSMs back to idle (game ready to restart)\n state.dispatch('local', 'GAME_OVER');\n state.dispatch('remote', 'GAME_OVER');\n state.cleanupGame();\n consoleLogger.debug('[session:move] local game over applied', {\n winner,\n turn,\n });\n return;\n }\n\n return;\n }\n\n // Remote player made a move\n if (!state.canAction('remote', 'MOVE')) {\n console.warn('[Move] Cannot MOVE for remote player', {\n state: state.getState('remote'),\n });\n return;\n }\n\n // ===== PROXY POINT 1: Validate remote move =====\n const validation = state.validateMove(movePayload);\n if (!validation.valid) {\n console.warn('[Move] Remote move validation failed', {\n reason: validation.reason,\n move: movePayload,\n });\n return;\n }\n\n // Dispatch state transitions\n state.dispatch('remote', 'MOVE');\n state.dispatch('local', 'REMOTE_MOVE');\n\n // Record move in history\n const turn = state.getTurnCount();\n state.pushHistory({\n turn,\n player: 'remote',\n move: movePayload,\n });\n\n // ===== PROXY POINT 2: Check win condition =====\n const winner = state.checkWin();\n if (winner) {\n // Game ended - someone won\n consoleLogger.debug('[session:move] game over detected', { winner, turn });\n\n // Transition both FSMs back to idle (game ready to restart)\n state.dispatch('local', 'GAME_OVER');\n state.dispatch('remote', 'GAME_OVER');\n state.cleanupGame();\n consoleLogger.debug('[session:move] remote game over applied', {\n winner,\n turn,\n });\n return;\n }\n\n consoleLogger.debug('[session:move] remote move applied', {\n turn,\n payload: movePayload,\n });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\ntype RequestAction = 'undo' | 'restart';\ntype RequestPayload = {\n action?: string;\n reason?: string;\n};\n\nconst isRequestAction = (value: unknown): value is RequestAction =>\n value === 'undo' || value === 'restart';\n\n/**\n * Handle approval/rejection of pending requests (undo/restart)\n *\n * When local player approves/rejects a pending request:\n * - Apply undo/restart state changes (clear history, pop moves)\n * - Use dispatchApprove/dispatchReject for complex state transitions\n * - Notify peer of decision\n *\n * When remote player approves/rejects local's request:\n * - Similar flow but opposite state transitions\n */\nexport const request: CommandListener = (command) => {\n if (command.type !== 'APPROVE' && command.type !== 'REJECT') {\n return;\n }\n\n const state = getState();\n const payload = command.payload as RequestPayload | undefined;\n const pendingAction = state.getPendingAction();\n const action =\n pendingAction ??\n (command.from === 'local' &&\n command.type === 'REJECT' &&\n isRequestAction(payload?.action)\n ? payload.action\n : null);\n consoleLogger.debug('[session:request] received', {\n type: command.type,\n from: command.from,\n action,\n local: state.getState('local'),\n remote: state.getState('remote'),\n history: state.getHistory().length,\n });\n\n if (!action) {\n console.warn('[Request] No pending action', { commandType: command.type });\n return;\n }\n\n // Verify payload matches pending action\n if (payload?.action && payload.action !== action) {\n console.warn('[Request] Action mismatch', {\n pending: action,\n payload: payload.action,\n });\n return;\n }\n\n if (command.from === 'local') {\n if (!pendingAction && command.type === 'REJECT') {\n send({\n type: 'REJECT',\n payload: { action, reason: payload?.reason ?? 'rejected' },\n });\n consoleLogger.debug('[session:request] local auto rejected', {\n action,\n reason: payload?.reason,\n });\n return;\n }\n\n if (command.type === 'APPROVE') {\n // Local player approves the request\n if (!state.canAction('local', 'APPROVE')) {\n console.warn('[Request] Cannot APPROVE from current state');\n return;\n }\n\n // Use special method for complex APPROVE transition\n state.dispatchApprove();\n\n // Apply the approved action (undo or restart)\n if (action === 'undo') {\n state.applyUndo(state.getPendingUndoCount() ?? 1);\n } else if (action === 'restart') {\n state.resetGame();\n }\n\n send({ type: 'APPROVE', payload: { action } });\n state.clearPendingStates();\n consoleLogger.debug('[session:request] local approved', { action });\n return;\n }\n\n // REJECT from local\n if (!state.canAction('local', 'REJECT')) {\n console.warn('[Request] Cannot REJECT from current state');\n return;\n }\n\n // Use special method for complex REJECT transition\n state.dispatchReject();\n\n send({\n type: 'REJECT',\n payload: { action, reason: payload?.reason ?? 'rejected' },\n });\n state.clearPendingStates();\n consoleLogger.debug('[session:request] local rejected', { action });\n return;\n }\n\n // Remote player responded to local's request\n if (command.type === 'APPROVE') {\n // Remote player approves\n if (!state.canAction('local', 'APPROVE')) {\n console.warn(\n '[Request] Cannot APPROVE from current state (remote approved)',\n );\n return;\n }\n\n // Use special method for complex APPROVE transition\n state.dispatchApprove();\n\n // Apply the approved action (undo or restart)\n if (action === 'undo') {\n state.applyUndo(state.getPendingUndoCount() ?? 1);\n } else if (action === 'restart') {\n state.resetGame();\n }\n\n state.clearPendingStates();\n consoleLogger.debug('[session:request] remote approved', { action });\n return;\n }\n\n // REJECT from remote\n if (!state.canAction('local', 'REJECT')) {\n console.warn(\n '[Request] Cannot REJECT from current state (remote rejected)',\n );\n state.clearPendingStates();\n return;\n }\n\n // Use special method for complex REJECT transition\n state.dispatchReject();\n state.clearPendingStates();\n consoleLogger.debug('[session:request] remote rejected', { action });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport type { PlayerLabel, TurnEntry } from '../state/state';\nimport { consoleLogger } from '../../utils';\n\nconst fromPeerPerspective = (player: PlayerLabel): PlayerLabel =>\n player === 'local' ? 'remote' : 'local';\n\ntype SyncPayload = {\n history?: TurnEntry[];\n lastStart?: PlayerLabel | null;\n turn?: PlayerLabel;\n resumeTurn?: PlayerLabel | null;\n};\n\nconst getCurrentTurn = (): PlayerLabel => {\n const state = getState();\n return (\n state.getResumeTurn() ??\n (state.getState('local') === 'turn' ? 'local' : 'remote')\n );\n};\n\nconst buildSyncPayload = (): SyncPayload => {\n const state = getState();\n const turn = getCurrentTurn();\n\n return {\n history: state.getHistory(),\n lastStart: state.getLastStart(),\n turn,\n resumeTurn: state.getResumeTurn(),\n };\n};\n\nconst isInSyncRecovery = (): boolean => {\n const state = getState();\n return (\n state.getState('local') === 'syncing' ||\n state.getState('remote') === 'syncing' ||\n state.getState('remote') === 'offline' ||\n state.getResumeTurn() !== null\n );\n};\n\nconst enterSyncState = (): boolean => {\n const state = getState();\n\n if (state.getState('local') !== 'syncing') {\n if (!state.canAction('local', 'SYNC')) {\n consoleLogger.debug('[session:sync] local cannot enter sync', {\n local: state.getState('local'),\n });\n return false;\n }\n state.dispatch('local', 'SYNC', 'syncing');\n }\n\n if (state.getState('remote') === 'offline') {\n if (!state.canAction('remote', 'ONLINE')) {\n consoleLogger.debug('[session:sync] offline remote cannot enter sync', {\n remote: state.getState('remote'),\n });\n return false;\n }\n state.dispatch('remote', 'ONLINE', 'syncing');\n } else if (state.getState('remote') !== 'syncing') {\n if (!state.canAction('remote', 'SYNC')) {\n consoleLogger.debug('[session:sync] remote cannot enter sync', {\n remote: state.getState('remote'),\n });\n return false;\n }\n state.dispatch('remote', 'SYNC', 'syncing');\n }\n\n return (\n state.getState('local') === 'syncing' &&\n state.getState('remote') === 'syncing'\n );\n};\n\nconst restoreFromPayload = (\n payload: SyncPayload,\n mapPeerLabels: boolean,\n): void => {\n const state = getState();\n const mapPlayer = mapPeerLabels\n ? fromPeerPerspective\n : (player: PlayerLabel) => player;\n\n if (payload.history && payload.history.length > 0) {\n state.replaceHistory(\n payload.history.map((entry) => ({\n ...entry,\n player: mapPlayer(entry.player),\n })),\n );\n } else {\n state.clearHistory();\n }\n\n if (payload.lastStart) {\n state.setLastStart(mapPlayer(payload.lastStart));\n } else {\n state.setLastStart(null);\n }\n\n const nextPlayer = payload.resumeTurn\n ? mapPlayer(payload.resumeTurn)\n : payload.turn\n ? mapPlayer(payload.turn)\n : getCurrentTurn();\n\n consoleLogger.debug('[session:sync] state restored', {\n historyLength: state.getHistory().length,\n lastStart: state.getLastStart(),\n nextTurnPlayer: nextPlayer,\n mapped: mapPeerLabels,\n });\n\n if (!enterSyncState()) {\n return;\n }\n\n state.dispatchSyncComplete(nextPlayer);\n};\n\n/**\n * Handle game state synchronization after disconnect/reconnect\n *\n * SYNC_REQUEST: Initiator sends sync request, responder sends back complete game state\n * SYNC_STATE: Received game state, restore it, and transition both players to correct turn\n *\n * Synced data:\n * - history: All moves in order\n * - lastStart: Who started the last match (for turn rotation)\n * - turn: Current turn holder (to determine resume turn)\n * - resumeTurn: Who should have turn after sync (saved before disconnect)\n *\n * All player labels in SYNC_STATE are from the sender's perspective, so the\n * receiver maps local <-> remote before applying them.\n *\n * Flow:\n * 1. Local disconnects -> offline handler records resumeTurn\n * 2. Local reconnects -> online handler sends SYNC_REQUEST\n * 3. Remote responds with SYNC_STATE (history, lastStart, turn, resumeTurn)\n * 4. Local receives SYNC_STATE -> restores everything, calls dispatchSyncComplete\n * 5. Both FSMs now in correct 'turn'/'remote_turn' state\n */\nexport const sync: CommandListener = (command) => {\n const state = getState();\n consoleLogger.debug('[session:sync] received', {\n type: command.type,\n from: command.from,\n payload: command.payload,\n local: state.getState('local'),\n remote: state.getState('remote'),\n history: state.getHistory().length,\n resumeTurn: state.getResumeTurn(),\n });\n\n if (command.type === 'SYNC_REQUEST') {\n if (command.from === 'local') {\n // Local player initiated sync (after reconnection)\n if (\n state.getState('local') !== 'syncing' &&\n !state.canAction('local', 'SYNC')\n ) {\n console.warn('[Sync] Cannot SYNC from current state');\n return;\n }\n\n // Both transition to syncing state. Local may already be syncing when\n // OFFLINE abandoned a pending approval before ONLINE arrived.\n if (state.getState('local') !== 'syncing') {\n state.dispatch('local', 'SYNC', 'syncing');\n }\n if (state.getState('remote') !== 'syncing') {\n state.dispatch('remote', 'SYNC', 'syncing');\n }\n\n // Send request to peer (peer will respond with SYNC_STATE)\n send({ type: 'SYNC_REQUEST', from: '', payload: command.payload });\n consoleLogger.debug('[session:sync] request sent');\n return;\n }\n\n // Remote initiated sync. Send our current timeline first, then also close\n // our own FSM recovery path; the peer might be the only side requesting.\n const payload = buildSyncPayload();\n send({ type: 'SYNC_STATE', from: '', payload });\n consoleLogger.debug('[session:sync] state sent', payload);\n if (isInSyncRecovery()) {\n restoreFromPayload(payload, false);\n }\n return;\n }\n\n if (command.type !== 'SYNC_STATE') {\n return;\n }\n\n restoreFromPayload((command.payload as SyncPayload) || {}, true);\n};\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Handle undo request from local or remote player\n *\n * Validates undo is legal (enough history, no pending action, valid state)\n * and initiates undo request with peer approval flow.\n *\n * Undo always rolls back the requester's last move:\n * - requester has just moved -> undo 1 ply\n * - requester has already received a reply -> undo 2 plies\n *\n * If the request is rejected, the game resumes from the pre-request turn.\n * Records pending undo state for approval/rejection handling by request handler.\n */\nexport const undo: CommandListener = (command) => {\n if (command.type !== 'UNDO') {\n return;\n }\n\n const state = getState();\n const bus = getBus();\n consoleLogger.debug('[session:undo] received', {\n from: command.from,\n local: state.getState('local'),\n remote: state.getState('remote'),\n pending: state.getPendingAction(),\n history: state.getHistory().length,\n });\n\n if (command.from === 'local') {\n // Validate no pending action\n if (state.hasPendingAction()) {\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'UNDO')) {\n console.warn('[Undo] Cannot UNDO from current state');\n return;\n }\n\n // If it is our turn, our last move is two plies back. If it is the\n // peer's turn, our last move is the latest ply.\n const localState = state.getState('local');\n const undoCount = localState === 'turn' ? 2 : 1;\n const rejectResumePlayer = localState === 'turn' ? 'local' : 'remote';\n\n // Validate history is long enough\n if (state.getHistory().length < undoCount) {\n console.warn('[Undo] Not enough history to undo', { count: undoCount });\n return;\n }\n\n // Store the pre-request turn for REJECT. APPROVE uses requester ownership\n // in dispatchApprove(), then applies the history rollback.\n state.initializeUndoRequest(undoCount as 1 | 2, rejectResumePlayer);\n\n // Transition to approval waiting state\n state.dispatch('local', 'UNDO');\n state.dispatch('remote', 'REMOTE_UNDO');\n\n send({ type: 'UNDO', payload: { count: undoCount } });\n consoleLogger.debug('[session:undo] local requested', { undoCount });\n return;\n }\n\n // Remote player requested undo\n if (state.hasPendingAction()) {\n bus.emit('REJECT', { action: 'undo', reason: 'busy' }, 'local');\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'REMOTE_UNDO')) {\n console.warn('[Undo] Cannot accept remote UNDO request');\n bus.emit('REJECT', { action: 'undo', reason: 'invalid_state' }, 'local');\n return;\n }\n\n // Extract undo count from payload\n const payload = command.payload as { count?: number } | undefined;\n const count = payload?.count === 2 ? 2 : 1;\n\n // Validate count value\n if (payload?.count && payload.count !== 1 && payload.count !== 2) {\n bus.emit('REJECT', { action: 'undo', reason: 'invalid' }, 'local');\n return;\n }\n\n // Validate history is long enough\n if (state.getHistory().length < count) {\n bus.emit('REJECT', { action: 'undo', reason: 'no_history' }, 'local');\n return;\n }\n\n // Store the pre-request turn for REJECT. APPROVE uses requester ownership\n // in dispatchApprove(), then applies the history rollback.\n const resumePlayer = state.getState('local') === 'turn' ? 'local' : 'remote';\n\n // Initialize pending undo state\n state.initializeUndoRequest(count as 1 | 2, resumePlayer);\n\n // Transition to approval waiting state (remote initiated, local approving)\n state.dispatch('local', 'REMOTE_UNDO');\n state.dispatch('remote', 'UNDO');\n consoleLogger.debug('[session:undo] remote requested', {\n count,\n resumePlayer,\n });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Handle restart request from local or remote player\n *\n * Validates restart is legal (no pending action, valid state)\n * and initiates restart request with peer approval flow.\n *\n * Restart clears all history but keeps player order for next match.\n */\nexport const restart: CommandListener = (command) => {\n if (command.type !== 'RESTART') {\n return;\n }\n\n const state = getState();\n const bus = getBus();\n consoleLogger.debug('[session:restart] received', {\n from: command.from,\n local: state.getState('local'),\n remote: state.getState('remote'),\n pending: state.getPendingAction(),\n history: state.getHistory().length,\n });\n\n if (command.from === 'local') {\n // Validate no pending action\n if (state.hasPendingAction()) {\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'RESTART')) {\n console.warn('[Restart] Cannot RESTART from current state');\n return;\n }\n\n // Determine who will go first in next match\n const resumePlayer =\n state.getState('local') === 'turn' ? 'local' : 'remote';\n\n // Initialize pending restart state\n state.initializeRestartRequest(resumePlayer);\n\n // Transition to approval waiting state\n state.dispatch('local', 'RESTART');\n state.dispatch('remote', 'REMOTE_RESTART');\n\n send({ type: 'RESTART' });\n consoleLogger.debug('[session:restart] local requested', { resumePlayer });\n return;\n }\n\n // Remote player requested restart\n if (state.hasPendingAction()) {\n bus.emit('REJECT', { action: 'restart', reason: 'busy' }, 'local');\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'REMOTE_RESTART')) {\n console.warn('[Restart] Cannot accept remote RESTART request');\n bus.emit('REJECT', { action: 'restart', reason: 'invalid_state' }, 'local');\n return;\n }\n\n // Determine who will go first in next match\n const resumePlayer = state.getState('local') === 'turn' ? 'local' : 'remote';\n\n // Initialize pending restart state\n state.initializeRestartRequest(resumePlayer);\n\n // Transition to approval waiting state (remote initiated, local approving)\n state.dispatch('local', 'REMOTE_RESTART');\n state.dispatch('remote', 'RESTART');\n consoleLogger.debug('[session:restart] remote requested', { resumePlayer });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getState } from '../context';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Handle connection state changes (OFFLINE/ONLINE)\n *\n * OFFLINE: Record current turn state before disconnecting\n * ONLINE: Trigger sync to restore game state after reconnect\n *\n * Flow:\n * 1. OFFLINE arrives -> save resumeTurn, transition to offline\n * 2. ONLINE arrives -> transition to syncing, emit SYNC_REQUEST\n * 3. sync handler takes over -> restore history and turn assignment\n */\nexport const offline: CommandListener = (command) => {\n if (command.type !== 'OFFLINE' && command.type !== 'ONLINE') {\n return;\n }\n\n const state = getState();\n const bus = getBus();\n consoleLogger.debug('[session:connection] received', {\n type: command.type,\n from: command.from,\n local: state.getState('local'),\n remote: state.getState('remote'),\n pending: state.getPendingAction(),\n resumeTurn: state.getResumeTurn(),\n });\n\n if (command.type === 'OFFLINE') {\n // A disconnect makes an in-flight approval unverifiable. Model this as\n // sync recovery: local waits in syncing, remote stays offline until ONLINE.\n if (state.hasPendingAction()) {\n const resumeTurn = state.getResumeTurn();\n\n if (state.canAction('local', 'SYNC')) {\n state.dispatch('local', 'SYNC', 'syncing');\n }\n state.clearPendingStates();\n state.setResumeTurn(resumeTurn);\n }\n\n // Peer disconnected - save current turn state for later recovery\n if (!state.canAction('remote', 'OFFLINE')) {\n console.warn('[Offline] Cannot transition to OFFLINE from current state');\n return;\n }\n\n // Record who had the turn before going offline\n const currentTurn =\n state.getResumeTurn() ??\n (state.getState('local') === 'turn' ? 'local' : 'remote');\n state.setResumeTurn(currentTurn);\n\n // Transition to offline state\n state.dispatch('remote', 'OFFLINE', 'offline');\n consoleLogger.debug('[session:connection] remote offline', { currentTurn });\n return;\n }\n\n // ONLINE only means \"reconnected\" if the session FSM was already offline.\n // Initial WebRTC connection is handled by the connection observer, not this\n // recovery path.\n if (state.getState('remote') !== 'offline') {\n consoleLogger.debug(\n '[session:connection] ignored online while remote is not offline',\n {\n remote: state.getState('remote'),\n },\n );\n return;\n }\n\n // ONLINE - peer reconnected\n if (!state.canAction('remote', 'ONLINE')) {\n console.warn('[Offline] Cannot transition to ONLINE from current state');\n return;\n }\n\n // Transition to syncing state\n state.dispatch('remote', 'ONLINE', 'syncing');\n\n // Trigger sync to restore game state\n // sync handler will:\n // 1. Send SYNC_REQUEST\n // 2. Receive SYNC_STATE from peer\n // 3. Restore history and correct turn assignment\n bus.emit('SYNC_REQUEST', undefined, 'local');\n consoleLogger.debug('[session:connection] remote online, sync requested');\n};\n","import { CommandBus } from '../commandBus';\nimport { ready } from './ready';\nimport { start } from './start';\nimport { move } from './move';\nimport { request } from './request';\nimport { sync } from './sync';\nimport { undo } from './undo';\nimport { restart } from './restart';\nimport { offline } from './offLine';\n\nexport const registerHandlers = (bus: CommandBus) => {\n bus.register('READY', ready);\n bus.register('START', start);\n bus.register('MOVE', move);\n bus.register('UNDO', undo);\n bus.register('RESTART', restart);\n bus.register('SYNC_REQUEST', sync);\n bus.register('SYNC_STATE', sync);\n bus.register('OFFLINE', offline);\n bus.register('ONLINE', offline);\n bus.register('APPROVE', request);\n bus.register('REJECT', request);\n};\n","import type { CommandBus } from './commandBus';\n\nexport interface ISessionActions {\n ready(): void;\n start(): void;\n move(data: unknown): void;\n undo(): void;\n restart(): void;\n approve(): void;\n reject(): void;\n rejoin(sid: string): void;\n}\n\nexport class LocalActionsAPI implements ISessionActions {\n constructor(private bus: CommandBus) {}\n\n ready(): void {\n this.bus.emit('READY');\n }\n\n start(): void {\n this.bus.emit('START');\n }\n\n move(data: unknown): void {\n this.bus.emit('MOVE', data);\n }\n\n undo(): void {\n this.bus.emit('UNDO');\n }\n\n restart(): void {\n this.bus.emit('RESTART');\n }\n\n approve(): void {\n this.bus.emit('APPROVE');\n }\n\n reject(): void {\n this.bus.emit('REJECT');\n }\n\n rejoin(sid: string): void {\n this.bus.emit('REJOIN', { sid });\n }\n}\n\n","import { NetworkClient } from 'p2p-lockstep-kit-network';\nimport { CommandBus } from './commandBus';\nimport { State } from './state/state';\nimport { createNetClient } from './net';\nimport { initializeContext } from './context';\nimport { registerHandlers } from './handlers/busRegister.ts';\nimport {\n buildGameStateSnapshot,\n GameStateObserver,\n UINotificationAdapter,\n} from './observer';\nimport { LocalActionsAPI } from './actions';\n\n/**\n * Create a new game session with state management and networking\n * @param sid Session ID for rejoining (optional)\n * @param networkClient Custom network client (optional, creates default if not provided)\n * @returns Session manager with bus, state, observer, net, and send method\n *\n * @example\n * const session = createSession();\n * // UI automatically updates when state changes - no manual observer calls needed!\n * session.observer.subscribe(myUIObserver);\n * session.bus.emit('READY', undefined, 'local');\n * await session.net.connect(remotePeerId);\n */\nexport const createSession = (networkClient: NetworkClient, sid?: string) => {\n const bus = new CommandBus();\n const state = new State(null, null);\n const observer = new GameStateObserver();\n const net = createNetClient(networkClient, bus, null);\n\n // Connect State mutations to UI updates via adapter (plugin pattern)\n // This is the ONLY place where UI notifications are triggered\n const uiAdapter = new UINotificationAdapter(state, observer, () =>\n net.getIsConnected(),\n );\n state.subscribeStateObserver(uiAdapter);\n\n initializeContext(state, bus, net, sid);\n registerHandlers(bus);\n\n const actions = new LocalActionsAPI(bus);\n\n net.onConnectionChange((isConnected) => {\n observer.notifyConnectionChange(isConnected);\n observer.notifyStateChange(buildGameStateSnapshot(state, isConnected));\n });\n\n return {\n bus,\n state,\n observer,\n net,\n actions,\n send: net.send.bind(net),\n };\n};\n\nexport * from './observer';\nexport type { ISessionActions } from './actions';\n"],"mappings":"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","names":["move","localTarget","remoteTarget","validation","turn","winner","resumePlayer"]}
|
|
1
|
+
{"version":3,"sources":["../../utils/logger.ts","../../utils/serialization/json.ts","../../utils/protocol/session.ts","../../session/commandBus.ts","../../session/state/fsm.ts","../../session/observer/index.ts","../../session/state/state.ts","../../session/net.ts","../../session/context.ts","../../session/handlers/ready.ts","../../session/handlers/start.ts","../../session/handlers/move.ts","../../session/handlers/request.ts","../../session/handlers/sync.ts","../../session/handlers/undo.ts","../../session/handlers/restart.ts","../../session/handlers/offLine.ts","../../session/handlers/busRegister.ts","../../session/actions.ts","../../session/index.ts"],"sourcesContent":["export type Logger = {\n debug: (message: string, meta?: unknown) => void;\n info: (message: string, meta?: unknown) => void;\n warn: (message: string, meta?: unknown) => void;\n error: (message: string, meta?: unknown) => void;\n};\n\nconst logWith =\n (level: 'debug' | 'info' | 'warn' | 'error') =>\n (message: string, meta?: unknown) => {\n const write = level === 'debug' ? console.info : console[level];\n if (meta !== undefined) {\n // eslint-disable-next-line no-console\n write(message, meta);\n return;\n }\n // eslint-disable-next-line no-console\n write(message);\n };\n\nexport const consoleLogger: Logger = {\n debug: logWith('debug'),\n info: logWith('info'),\n warn: logWith('warn'),\n error: logWith('error'),\n};\n","export type Serialized = string;\n\nexport const encode = (value: unknown): Serialized => JSON.stringify(value);\n\nexport const decode = <T>(raw: Serialized): T => {\n if (typeof raw !== 'string') {\n throw new TypeError('decode expects a serialized string');\n }\n return JSON.parse(raw) as T;\n};\n\nexport const decodeSafe = <T>(\n raw: unknown,\n): { ok: true; value: T } | { ok: false; error: unknown } => {\n if (typeof raw !== 'string') {\n return {\n ok: false,\n error: new TypeError('decodeSafe expects a serialized string'),\n };\n }\n\n try {\n return { ok: true, value: JSON.parse(raw) as T };\n } catch (error) {\n return { ok: false, error };\n }\n};\n","import { decodeSafe } from '../serialization';\n\nexport type SessionMessageType =\n | 'READY'\n | 'START'\n | 'MOVE'\n | 'UNDO'\n | 'RESTART'\n | 'APPROVE'\n | 'REJECT'\n | 'REJOIN'\n | 'SYNC_REQUEST'\n | 'SYNC_STATE'\n | 'OFFLINE'\n | 'ONLINE'\n | 'GAME_OVER';\n\nexport type SessionMessage = {\n type: SessionMessageType;\n from?: string;\n seq?: number;\n sid?: string;\n turn?: number;\n stateHash?: string;\n payload?: any;\n};\n\nexport const parseSessionMessage = (\n data: unknown,\n): (Partial<SessionMessage> & { type?: string }) | null => {\n if (typeof data !== 'string') {\n if (!data || typeof data !== 'object') {\n return null;\n }\n return data as Partial<SessionMessage> & { type?: string };\n }\n\n const decoded = decodeSafe<Partial<SessionMessage> & { type?: string }>(\n data,\n );\n if (!decoded.ok || !decoded.value || typeof decoded.value !== 'object') {\n return null;\n }\n\n return decoded.value;\n};\n","import { consoleLogger, SessionMessage, SessionMessageType } from '../utils';\n\nexport type CommandOrigin = 'local' | 'remote';\nexport type BusMessageType =\n | SessionMessageType\n | 'OFFLINE'\n | 'ONLINE'\n | 'GAME_OVER';\nexport type BusMessage = Omit<SessionMessage, 'type'> & {\n type: BusMessageType;\n};\nexport type CommandListener = (message: BusMessage) => Promise<void> | void;\n\ntype HandlerMap = Partial<Record<BusMessageType, CommandListener>>;\n\nexport class CommandBus {\n private handlers: HandlerMap = {};\n private processingQueue: Promise<void> = Promise.resolve();\n\n public emit(\n type: BusMessageType,\n payload?: unknown,\n from: CommandOrigin = 'local',\n ): void {\n this.dispatch({ type, payload, from });\n }\n\n public register(type: BusMessageType, handler: CommandListener): void {\n this.handlers[type] = handler;\n consoleLogger.debug(`[session:bus] registered ${type}`);\n }\n\n public dispatch(message: BusMessage): void {\n this.processingQueue = this.processingQueue.then(async () => {\n consoleLogger.debug(`[session:bus] dispatch ${message.type}`, {\n from: message.from,\n payload: message.payload,\n turn: message.turn,\n sid: message.sid,\n });\n\n const handler = this.handlers[message.type];\n if (handler) {\n try {\n await handler(message);\n consoleLogger.debug(`[session:bus] handled ${message.type}`, {\n from: message.from,\n });\n } catch (err) {\n console.error(`[CommandBus] Error in ${message.type}:`, err);\n }\n return;\n }\n\n consoleLogger.debug(`[session:bus] no handler for ${message.type}`, {\n from: message.from,\n });\n });\n }\n}\n","export type SessionState =\n | 'idle'\n | 'ready'\n | 'could_start'\n | 'turn'\n | 'remote_turn'\n | 'approving'\n | 'waiting_approval'\n | 'syncing'\n | 'offline';\n\nexport type SessionEvent =\n | 'REMOTE_READY'\n | 'READY'\n | 'START'\n | 'REMOTE_START'\n | 'MOVE'\n | 'REMOTE_MOVE'\n | 'UNDO'\n | 'REMOTE_UNDO'\n | 'RESTART'\n | 'REMOTE_RESTART'\n | 'APPROVE'\n | 'REJECT'\n | 'GAME_OVER'\n | 'REJOIN'\n | 'SYNC'\n | 'SYNC_COMPLETE'\n | 'OFFLINE'\n | 'ONLINE';\n\nexport type Transition = {\n from: SessionState;\n event: SessionEvent;\n to: SessionState;\n};\n\nconst transitions: Transition[] = [\n // Lobby readiness\n { from: 'idle', event: 'READY', to: 'ready' },\n { from: 'ready', event: 'READY', to: 'idle' },\n { from: 'idle', event: 'REMOTE_READY', to: 'could_start' },\n { from: 'could_start', event: 'REMOTE_READY', to: 'idle' },\n { from: 'ready', event: 'REJECT', to: 'idle' },\n { from: 'could_start', event: 'REJECT', to: 'idle' },\n\n // Match start / turn assignment\n { from: 'ready', event: 'REMOTE_START', to: 'turn' },\n { from: 'ready', event: 'REMOTE_START', to: 'remote_turn' },\n { from: 'could_start', event: 'START', to: 'turn' },\n { from: 'could_start', event: 'START', to: 'remote_turn' },\n\n // Turn swapping after moves\n { from: 'turn', event: 'MOVE', to: 'remote_turn' },\n { from: 'remote_turn', event: 'REMOTE_MOVE', to: 'turn' },\n { from: 'turn', event: 'REJECT', to: 'turn' },\n { from: 'remote_turn', event: 'REJECT', to: 'remote_turn' },\n\n // Requests initiated by local player (undo/restart)\n { from: 'turn', event: 'UNDO', to: 'waiting_approval' },\n { from: 'remote_turn', event: 'UNDO', to: 'waiting_approval' },\n { from: 'turn', event: 'RESTART', to: 'waiting_approval' },\n { from: 'remote_turn', event: 'RESTART', to: 'waiting_approval' },\n\n // Requests coming from remote (we need to approve)\n { from: 'turn', event: 'REMOTE_UNDO', to: 'approving' },\n { from: 'remote_turn', event: 'REMOTE_UNDO', to: 'approving' },\n { from: 'turn', event: 'REMOTE_RESTART', to: 'approving' },\n { from: 'remote_turn', event: 'REMOTE_RESTART', to: 'approving' },\n\n // Approval outcomes when we were waiting for the peer.\n { from: 'waiting_approval', event: 'APPROVE', to: 'turn' },\n { from: 'waiting_approval', event: 'REJECT', to: 'turn' },\n { from: 'waiting_approval', event: 'REJECT', to: 'remote_turn' },\n\n // Approval outcomes when we were confirming the peer's request.\n // The target turn is explicit because resumeTurn decides who continues.\n { from: 'approving', event: 'APPROVE', to: 'remote_turn' },\n { from: 'approving', event: 'REJECT', to: 'remote_turn' },\n { from: 'approving', event: 'REJECT', to: 'turn' },\n\n // Game end resets back to lobby idle\n { from: 'turn', event: 'GAME_OVER', to: 'idle' },\n { from: 'remote_turn', event: 'GAME_OVER', to: 'idle' },\n\n // Rejoin/sync flows\n { from: 'turn', event: 'SYNC', to: 'syncing' },\n { from: 'remote_turn', event: 'SYNC', to: 'syncing' },\n { from: 'waiting_approval', event: 'SYNC', to: 'syncing' },\n { from: 'approving', event: 'SYNC', to: 'syncing' },\n { from: 'idle', event: 'SYNC', to: 'syncing' },\n { from: 'ready', event: 'SYNC', to: 'syncing' },\n { from: 'could_start', event: 'SYNC', to: 'syncing' },\n { from: 'syncing', event: 'SYNC_COMPLETE', to: 'turn' },\n { from: 'syncing', event: 'SYNC_COMPLETE', to: 'remote_turn' },\n\n // Connection state\n { from: 'idle', event: 'OFFLINE', to: 'offline' },\n { from: 'ready', event: 'OFFLINE', to: 'offline' },\n { from: 'could_start', event: 'OFFLINE', to: 'offline' },\n { from: 'turn', event: 'OFFLINE', to: 'offline' },\n { from: 'remote_turn', event: 'OFFLINE', to: 'offline' },\n { from: 'waiting_approval', event: 'OFFLINE', to: 'offline' },\n { from: 'approving', event: 'OFFLINE', to: 'offline' },\n { from: 'syncing', event: 'OFFLINE', to: 'offline' },\n { from: 'offline', event: 'ONLINE', to: 'syncing' },\n];\n// only receive 'to' from state. never receive from handlers.\nconst nextState = (\n state: SessionState,\n event: SessionEvent,\n to?: SessionState, // next turn or pending action\n): SessionState => {\n if (to) {\n if (\n !!transitions.find(\n (t) => t.from === state && t.event === event && t.to === to,\n )\n ) {\n return to;\n } else {\n return state;\n }\n } else {\n const hit = transitions.find((t) => t.from === state && t.event === event);\n return hit ? hit.to : state;\n }\n};\n\nconst hasNextState = (\n state: SessionState,\n action: SessionEvent,\n to?: SessionState,\n): boolean => {\n if (to) {\n return !!transitions.find(\n (t) => t.from === state && t.event === action && t.to === to,\n );\n }\n return !!transitions.find((t) => t.from === state && t.event === action);\n};\n\nexport class SessionFsm {\n private state: SessionState;\n\n constructor(state: SessionState = 'idle') {\n this.state = state;\n }\n\n public getState(): SessionState {\n return this.state;\n }\n\n public hasNextState(event: SessionEvent, to?: SessionState): boolean {\n return hasNextState(this.state, event, to);\n }\n\n public dispatch(action: SessionEvent, to?: SessionState) {\n this.state = nextState(this.state, action, to);\n }\n}\n","import type {\n PendingAction,\n PlayerLabel,\n TurnEntry,\n State,\n} from '../state/state';\nimport type { SessionState } from '../state/fsm';\nimport { consoleLogger } from '../../utils';\n\nexport interface GameStateSnapshot {\n localState: SessionState;\n remoteState: SessionState;\n turn: number;\n history: TurnEntry[];\n lastStart: PlayerLabel | null;\n pendingAction: PendingAction;\n connected: boolean;\n}\n\nexport interface GameEvent {\n type:\n | 'READY'\n | 'START'\n | 'MOVE'\n | 'GAME_OVER'\n | 'UNDO'\n | 'RESTART'\n | 'OFFLINE'\n | 'ONLINE'\n | 'SYNC'\n | 'ERROR';\n payload?: unknown;\n from?: 'local' | 'remote';\n timestamp?: number;\n}\n\nexport interface IGameObserver {\n onStateChange(snapshot: GameStateSnapshot): void;\n onGameEvent(event: GameEvent): void;\n onConnectionChange?(connected: boolean): void;\n onError?(error: { message: string; context?: unknown }): void;\n}\n\nexport interface IStateObserver {\n onStateChanged?(): void;\n onHistoryChanged?(): void;\n onGameReset?(): void;\n}\n\nexport interface IGamePlugin {\n validateMove(move: unknown, gameState: GameState): ValidationResult;\n checkWin(gameState: GameState, history: TurnEntry[]): PlayerLabel | null;\n initialize?(): void;\n cleanup?(): void;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n reason?: string;\n}\n\nexport interface GameState {\n history: TurnEntry[];\n localState: 'turn' | 'remote_turn' | string;\n remoteState: 'turn' | 'remote_turn' | string;\n turn: number;\n lastStart: PlayerLabel | null;\n}\n\nexport class DefaultGamePlugin implements IGamePlugin {\n validateMove(): ValidationResult {\n return { valid: true };\n }\n checkWin(): PlayerLabel | null {\n return null;\n }\n}\n\nexport class StateObserverManager {\n private observers: Set<IStateObserver> = new Set();\n\n subscribe(observer: IStateObserver): void {\n this.observers.add(observer);\n }\n\n unsubscribe(observer: IStateObserver): void {\n this.observers.delete(observer);\n }\n\n notifyStateChanged(): void {\n for (const observer of this.observers) {\n try {\n observer.onStateChanged?.();\n } catch (err) {\n console.error('[StateObserver]', err);\n }\n }\n }\n\n notifyHistoryChanged(): void {\n for (const observer of this.observers) {\n try {\n observer.onHistoryChanged?.();\n } catch (err) {\n console.error('[StateObserver]', err);\n }\n }\n }\n\n notifyGameReset(): void {\n for (const observer of this.observers) {\n try {\n observer.onGameReset?.();\n } catch (err) {\n console.error('[StateObserver]', err);\n }\n }\n }\n}\n\nexport class GameStateObserver {\n private observers: Set<IGameObserver> = new Set();\n private currentSnapshot: GameStateSnapshot | null = null;\n\n subscribe(observer: IGameObserver): () => void {\n this.observers.add(observer);\n return () => {\n this.observers.delete(observer);\n };\n }\n\n unsubscribe(observer: IGameObserver): void {\n this.observers.delete(observer);\n }\n\n notifyStateChange(snapshot: GameStateSnapshot): void {\n this.currentSnapshot = snapshot;\n for (const observer of this.observers) {\n try {\n observer.onStateChange(snapshot);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n notifyGameEvent(event: GameEvent): void {\n event.timestamp = Date.now();\n for (const observer of this.observers) {\n try {\n observer.onGameEvent(event);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n notifyConnectionChange(connected: boolean): void {\n for (const observer of this.observers) {\n try {\n observer.onConnectionChange?.(connected);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n notifyError(error: { message: string; context?: unknown }): void {\n for (const observer of this.observers) {\n try {\n observer.onError?.(error);\n } catch (err) {\n console.error('[GameStateObserver]', err);\n }\n }\n }\n\n getSnapshot(): GameStateSnapshot | null {\n return this.currentSnapshot;\n }\n\n getObserverCount(): number {\n return this.observers.size;\n }\n}\n\nexport function buildGameStateSnapshot(\n state: State,\n connected: boolean = false,\n): GameStateSnapshot {\n return {\n localState: state.getState('local'),\n remoteState: state.getState('remote'),\n turn: state.getTurnCount(),\n history: state.getHistory(),\n lastStart: state.getLastStart(),\n pendingAction: state.getPendingAction(),\n connected,\n };\n}\n\nexport class UINotificationAdapter implements IStateObserver {\n private lastNotificationTime = 0;\n private notificationThrottleMs = 0;\n\n constructor(\n private stateRef: State,\n private uiObserver: GameStateObserver,\n private getConnected: () => boolean = () => false,\n ) {}\n\n onStateChanged(): void {\n const now = Date.now();\n if (this.lastNotificationTime + this.notificationThrottleMs > now) return;\n this.lastNotificationTime = now;\n\n const snapshot = buildGameStateSnapshot(this.stateRef, this.getConnected());\n consoleLogger.debug('[session:observer] state snapshot', {\n local: snapshot.localState,\n remote: snapshot.remoteState,\n turn: snapshot.turn,\n history: snapshot.history.length,\n pending: snapshot.pendingAction,\n connected: snapshot.connected,\n });\n this.uiObserver.notifyStateChange(snapshot);\n }\n\n onHistoryChanged(): void {\n this.onStateChanged();\n }\n\n onGameReset(): void {\n this.onStateChanged();\n }\n\n emitEvent(event: Omit<GameEvent, 'timestamp'>): void {\n this.uiObserver.notifyGameEvent(event as GameEvent);\n }\n}\n","import { SessionEvent, SessionFsm, SessionState } from './fsm';\nimport type {\n IGamePlugin,\n GameState,\n ValidationResult,\n IStateObserver,\n} from '../observer';\nimport { DefaultGamePlugin, StateObserverManager } from '../observer';\nimport { consoleLogger } from '../../utils';\n\nexport type TurnEntry = {\n turn: number;\n player: 'local' | 'remote';\n move?: unknown;\n};\n\nexport type PlayerLabel = 'local' | 'remote';\nexport type PendingAction = 'undo' | 'restart' | null;\n\nexport class State {\n // will update map when multi-players (>=3)\n private local = new SessionFsm('idle');\n private remote = new SessionFsm('idle');\n // for compare remote is same people or not\n private readonly localId: string | null = null;\n private remoteId: string | null = null;\n // store all actions\n private readonly history: TurnEntry[] = [];\n // pending some state\n private pendingAction: PendingAction = null;\n private pendingUndoCount: 1 | 2 | null = null;\n private resumeTurn: PlayerLabel | null = null;\n private lastStart: PlayerLabel | null = null;\n\n // Game plugin for rule validation and win checking\n private gamePlugin: IGamePlugin = new DefaultGamePlugin();\n\n // Internal state observer for UI notifications\n private stateObserverManager = new StateObserverManager();\n\n constructor(id: string | null, remoteId: string | null) {\n if (id) {\n this.localId = id;\n }\n if (remoteId) {\n this.remoteId = remoteId;\n }\n consoleLogger.debug('[session:state] created', { localId: id, remoteId });\n }\n\n /**\n * Register an internal observer (like plugin pattern)\n * Use this to connect State mutations to UI updates\n */\n public subscribeStateObserver(observer: IStateObserver): void {\n this.stateObserverManager.subscribe(observer);\n }\n\n // ...existing code...\n\n public getId(): string | null {\n return this.localId;\n }\n\n public getremoteId(): string | null {\n return this.remoteId;\n }\n\n public setremoteId(id: string) {\n this.remoteId = id;\n consoleLogger.debug('[session:state] remote id set', { remoteId: id });\n }\n\n public getState(player: PlayerLabel): SessionState {\n return this.getPlayerFsm(player).getState();\n }\n\n public getTurnCount(): number {\n return this.history.length + 1;\n }\n\n public getHistory(): TurnEntry[] {\n return this.history.slice();\n }\n\n public replaceHistory(entries: TurnEntry[]): void {\n consoleLogger.debug('[session:history] replace', { count: entries.length });\n this.clearHistory();\n entries.forEach((entry) => {\n this.pushHistory({\n turn: entry.turn,\n player: entry.player,\n move: entry.move,\n });\n });\n }\n\n public clearHistory(): void {\n const count = this.history.length;\n this.history.splice(0, this.history.length);\n consoleLogger.debug('[session:history] clear', { count });\n this.notifyHistoryChanged();\n }\n\n public pushHistory(entry: TurnEntry): void {\n this.history.push(entry);\n consoleLogger.debug('[session:history] push', {\n turn: entry.turn,\n player: entry.player,\n move: entry.move,\n count: this.history.length,\n });\n this.notifyHistoryChanged();\n }\n\n public popHistory(): TurnEntry | null {\n const entry = this.history.pop() ?? null;\n if (entry) {\n consoleLogger.debug('[session:history] pop', {\n turn: entry.turn,\n player: entry.player,\n move: entry.move,\n count: this.history.length,\n });\n this.notifyHistoryChanged();\n }\n return entry;\n }\n\n public canAction(player: PlayerLabel, action: SessionEvent): boolean {\n return this.getPlayerFsm(player).hasNextState(action);\n }\n\n /**\n * Dispatch an action and automatically determine target state if unique\n * Only use explicit 'to' parameter for ambiguous transitions (APPROVE, REJECT, etc.)\n *\n * For most actions (READY, MOVE, START, etc.), there's only one valid transition,\n * so we automatically find and apply it.\n */\n public dispatch(\n player: PlayerLabel,\n action: SessionEvent,\n to?: SessionState,\n ): void {\n const before = this.getState(player);\n this.getPlayerFsm(player).dispatch(action, to);\n const after = this.getState(player);\n consoleLogger.debug(`[session:fsm] ${player} ${action}`, {\n from: before,\n to: after,\n requested: to,\n local: this.getState('local'),\n remote: this.getState('remote'),\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.notifyStateChanged();\n }\n\n public setPendingAction(action: PendingAction) {\n consoleLogger.debug('[session:state] pending action set', {\n from: this.pendingAction,\n to: action,\n });\n this.pendingAction = action;\n }\n\n public getPendingAction(): PendingAction {\n return this.pendingAction;\n }\n\n public setPendingUndoCount(count: 1 | 2 | null) {\n consoleLogger.debug('[session:state] pending undo count set', {\n from: this.pendingUndoCount,\n to: count,\n });\n this.pendingUndoCount = count;\n }\n\n public getPendingUndoCount(): 1 | 2 | null {\n return this.pendingUndoCount;\n }\n\n public setLastStart(player: PlayerLabel | null) {\n consoleLogger.debug('[session:state] last start set', {\n from: this.lastStart,\n to: player,\n });\n this.lastStart = player;\n }\n\n public getLastStart(): PlayerLabel | null {\n return this.lastStart;\n }\n\n public setResumeTurn(player: PlayerLabel | null) {\n consoleLogger.debug('[session:state] resume turn set', {\n from: this.resumeTurn,\n to: player,\n });\n this.resumeTurn = player;\n }\n\n public getResumeTurn(): PlayerLabel | null {\n return this.resumeTurn;\n }\n\n private getPlayerFsm(player: PlayerLabel): SessionFsm {\n return player === 'local' ? this.local : this.remote;\n }\n\n private notifyStateChanged(): void {\n consoleLogger.debug('[session:state] notify state changed', {\n local: this.getState('local'),\n remote: this.getState('remote'),\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.stateObserverManager.notifyStateChanged();\n }\n\n private notifyHistoryChanged(): void {\n consoleLogger.debug('[session:state] notify history changed', {\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.stateObserverManager.notifyHistoryChanged();\n }\n\n private notifyGameReset(): void {\n consoleLogger.debug('[session:state] notify game reset');\n this.stateObserverManager.notifyGameReset();\n }\n\n private dispatchPair(\n localAction: SessionEvent,\n localTo: SessionState,\n remoteAction: SessionEvent,\n remoteTo: SessionState,\n ): void {\n const before = {\n local: this.local.getState(),\n remote: this.remote.getState(),\n };\n this.local.dispatch(localAction, localTo);\n this.remote.dispatch(remoteAction, remoteTo);\n consoleLogger.debug('[session:fsm] pair dispatch', {\n before,\n after: {\n local: this.local.getState(),\n remote: this.remote.getState(),\n },\n localAction,\n localTo,\n remoteAction,\n remoteTo,\n turn: this.getTurnCount(),\n history: this.history.length,\n pending: this.pendingAction,\n });\n this.notifyStateChanged();\n }\n\n // ===== Helper Methods for Undo/Restart Request Handling =====\n\n /**\n * Save game state snapshot for undo/restart operations\n */\n private gameSnapshot: unknown = null;\n\n public saveGameSnapshot(snapshot: unknown): void {\n this.gameSnapshot = snapshot;\n consoleLogger.debug('[session:state] game snapshot saved', { snapshot });\n }\n\n public getGameSnapshot(): unknown {\n return this.gameSnapshot;\n }\n\n public clearGameSnapshot(): void {\n this.gameSnapshot = null;\n consoleLogger.debug('[session:state] game snapshot cleared');\n }\n\n /**\n * Check if there's a pending action (undo/restart)\n */\n public hasPendingAction(): boolean {\n return this.pendingAction !== null;\n }\n\n /**\n * Clear all pending states (called after approval/rejection)\n */\n public clearPendingStates(): void {\n consoleLogger.debug('[session:state] pending states cleared', {\n pending: this.pendingAction,\n pendingUndoCount: this.pendingUndoCount,\n resumeTurn: this.resumeTurn,\n });\n this.pendingAction = null;\n this.pendingUndoCount = null;\n this.resumeTurn = null;\n this.notifyStateChanged();\n }\n\n /**\n * Initialize undo request with undo count and current turn holder\n */\n public initializeUndoRequest(\n undoCount: 1 | 2,\n resumeTurn: PlayerLabel,\n ): void {\n this.pendingAction = 'undo';\n this.pendingUndoCount = undoCount;\n this.resumeTurn = resumeTurn;\n consoleLogger.debug('[session:state] undo request initialized', {\n undoCount,\n resumeTurn,\n });\n }\n\n /**\n * Initialize restart request with resume turn\n */\n public initializeRestartRequest(resumeTurn: PlayerLabel): void {\n this.pendingAction = 'restart';\n this.resumeTurn = resumeTurn;\n consoleLogger.debug('[session:state] restart request initialized', {\n resumeTurn,\n });\n }\n\n /**\n * Check if pending action is undo\n */\n public isPendingUndo(): boolean {\n return this.pendingAction === 'undo';\n }\n\n /**\n * Check if pending action is restart\n */\n public isPendingRestart(): boolean {\n return this.pendingAction === 'restart';\n }\n\n /**\n * Apply undo by popping history N times\n */\n public applyUndo(count: 1 | 2 = 1): void {\n consoleLogger.debug('[session:history] apply undo', { count });\n for (let i = 0; i < count; i++) {\n this.popHistory();\n }\n }\n\n /**\n * Reset game state to initial (for restart)\n */\n public resetGame(): void {\n consoleLogger.debug('[session:state] reset game', {\n local: this.getState('local'),\n remote: this.getState('remote'),\n history: this.history.length,\n lastStart: this.lastStart,\n pending: this.pendingAction,\n });\n this.clearHistory();\n this.local = new SessionFsm('idle');\n this.remote = new SessionFsm('idle');\n this.lastStart = null;\n this.pendingAction = null;\n this.pendingUndoCount = null;\n this.resumeTurn = null;\n this.notifyGameReset();\n this.notifyStateChanged();\n }\n\n /**\n * Save start player for rejoin flow\n */\n public recordStartPlayer(player: PlayerLabel): void {\n this.lastStart = player;\n consoleLogger.debug('[session:state] start player recorded', { player });\n }\n\n /**\n * Get move to undo from history\n */\n public getLastMove(): TurnEntry | null {\n return this.history.length > 0\n ? this.history[this.history.length - 1]\n : null;\n }\n\n // ===== Specialized FSM Dispatch Methods =====\n\n /**\n * Dispatch APPROVE action with automatic target state resolution\n * Multiple valid transitions exist - use state context to determine target\n */\n public dispatchApprove(): void {\n const localState = this.local.getState();\n if (localState === 'waiting_approval') {\n // Local requested the action; peer approved it.\n this.dispatchPair('APPROVE', 'turn', 'APPROVE', 'remote_turn');\n } else if (localState === 'approving') {\n // Peer requested the action; local approved it.\n this.dispatchPair('APPROVE', 'remote_turn', 'APPROVE', 'turn');\n }\n }\n\n /**\n * Dispatch REJECT action with automatic target state resolution\n * Multiple valid transitions exist - use resumeTurn to determine who continues\n */\n public dispatchReject(): void {\n const localState = this.local.getState();\n\n if (localState === 'waiting_approval' || localState === 'approving') {\n // resumeTurn tells us who should have turn after rejection\n const localTarget = this.resumeTurn === 'local' ? 'turn' : 'remote_turn';\n const remoteTarget = this.resumeTurn === 'local' ? 'remote_turn' : 'turn';\n this.dispatchPair('REJECT', localTarget, 'REJECT', remoteTarget);\n }\n }\n\n /**\n * Dispatch START action with automatic target state resolution\n * Determines who plays first based on starter parameter\n */\n public dispatchStart(firstPlayer: PlayerLabel): void {\n const before = {\n local: this.local.getState(),\n remote: this.remote.getState(),\n lastStart: this.lastStart,\n };\n if (firstPlayer === 'local') {\n this.local.dispatch('START', 'turn');\n this.remote.dispatch('START', 'remote_turn');\n this.lastStart = 'local';\n } else {\n this.local.dispatch('START', 'remote_turn');\n this.remote.dispatch('START', 'turn');\n this.lastStart = 'remote';\n }\n consoleLogger.debug('[session:fsm] start dispatch', {\n before,\n firstPlayer,\n after: {\n local: this.local.getState(),\n remote: this.remote.getState(),\n lastStart: this.lastStart,\n },\n });\n this.notifyStateChanged();\n }\n\n /**\n * Dispatch SYNC_COMPLETE with automatic target state resolution\n * Based on who should have the turn after sync\n */\n public dispatchSyncComplete(nextPlayer: PlayerLabel): void {\n if (nextPlayer === 'local') {\n this.dispatchPair(\n 'SYNC_COMPLETE',\n 'turn',\n 'SYNC_COMPLETE',\n 'remote_turn',\n );\n } else {\n this.dispatchPair(\n 'SYNC_COMPLETE',\n 'remote_turn',\n 'SYNC_COMPLETE',\n 'turn',\n );\n }\n this.resumeTurn = null;\n }\n\n // ===== Game Plugin Integration (Proxy Pattern) =====\n\n /**\n * Set the game plugin for rule validation and win checking\n * @param plugin Implementation of IGamePlugin\n */\n public setGamePlugin(plugin: IGamePlugin): void {\n this.gamePlugin = plugin;\n consoleLogger.debug('[session:plugin] game plugin set', {\n hasInitialize: Boolean(plugin.initialize),\n hasCleanup: Boolean(plugin.cleanup),\n });\n if (plugin.initialize) {\n plugin.initialize();\n }\n }\n\n /**\n * Get current game plugin\n */\n public getGamePlugin(): IGamePlugin {\n return this.gamePlugin;\n }\n\n /**\n * Validate a move using the game plugin\n * Called by move handler to check if move is legal\n * @param move The move data to validate\n * @returns Validation result with reason if invalid\n */\n public validateMove(move: unknown): ValidationResult {\n const gameState = this.buildGameState();\n const result = this.gamePlugin.validateMove(move, gameState);\n consoleLogger.debug('[session:plugin] validate move', {\n move,\n result,\n local: gameState.localState,\n remote: gameState.remoteState,\n turn: gameState.turn,\n history: gameState.history.length,\n });\n return result;\n }\n\n /**\n * Check if game has ended (someone won)\n * Called by move handler after move is applied\n * @returns Winner (local/remote) or null if game continues\n */\n public checkWin(): PlayerLabel | null {\n const gameState = this.buildGameState();\n const winner = this.gamePlugin.checkWin(gameState, this.getHistory());\n consoleLogger.debug('[session:plugin] check win', {\n winner,\n turn: gameState.turn,\n history: gameState.history.length,\n });\n return winner;\n }\n\n /**\n * Cleanup when game ends (for plugin to reset internal state)\n */\n public cleanupGame(): void {\n if (this.gamePlugin.cleanup) {\n this.gamePlugin.cleanup();\n }\n consoleLogger.debug('[session:plugin] cleanup game');\n }\n\n /**\n * Build game state for plugin\n * @private\n */\n private buildGameState(): GameState {\n return {\n history: this.getHistory(),\n localState: this.getState('local'),\n remoteState: this.getState('remote'),\n turn: this.getTurnCount(),\n lastStart: this.getLastStart(),\n };\n }\n}\n","import type { NetworkClient } from 'p2p-lockstep-kit-network';\nimport { parseSessionMessage, type SessionMessage } from '../utils';\nimport type { BusMessageType, CommandBus } from './commandBus';\n\n/**\n * Network client wrapper that bridges NetworkClient with CommandBus\n * Handles message encoding/decoding and connection state monitoring\n */\nexport class NetClient {\n private localPeerId: string | null;\n private remotePeerId: string | null;\n private isConnected: boolean = false;\n private connectionChangeListener: (isConnected: boolean) => void = () => {};\n private mediaStateListener: (active: boolean) => void = () => {};\n\n public constructor(\n private readonly client: NetworkClient,\n private readonly bus: CommandBus,\n peerId: string | null,\n ) {\n this.localPeerId = peerId ?? null;\n this.remotePeerId = null;\n\n // Forward incoming messages to the command bus\n this.client.onMessage((data) => {\n const message = parseSessionMessage(data);\n if (!message || typeof message !== 'object' || !message.type) {\n return;\n }\n this.bus.dispatch({\n ...(message as SessionMessage),\n type: message.type as BusMessageType,\n from: 'remote',\n });\n });\n\n // Monitor connection state and emit ONLINE/OFFLINE events\n this.client.onStateChange((state) => {\n const wasConnected = this.isConnected;\n this.isConnected = state === 'connected';\n\n // Notify listeners of connection state change\n this.connectionChangeListener(this.isConnected);\n\n // Only emit ONLINE/OFFLINE once when state changes\n if (this.isConnected && !wasConnected) {\n this.bus.emit('ONLINE', undefined, 'local');\n } else if (!this.isConnected && wasConnected) {\n this.bus.emit('OFFLINE', undefined, 'local');\n }\n });\n\n // Monitor remote media stream availability\n this.client.onRemoteStream((stream) => {\n const active =\n !!stream &&\n stream.getTracks().some((track) => track.readyState === 'live');\n this.mediaStateListener(active);\n });\n }\n\n /**\n * Send a message to the remote peer\n * Drops message if not connected and logs warning\n */\n public send(message: SessionMessage) {\n if (!this.isConnected) {\n console.warn(\n '[NetClient] Cannot send message: not connected',\n message.type,\n );\n return;\n }\n\n const enriched: SessionMessage = {\n ...message,\n from: message.from ?? this.localPeerId ?? '',\n };\n this.client.send(enriched);\n }\n\n /**\n * Update local and remote peer IDs\n */\n public setPeerIds(ids: { local?: string | null; remote?: string | null }) {\n if (ids.local !== undefined) {\n this.localPeerId = ids.local;\n }\n if (ids.remote !== undefined) {\n this.remotePeerId = ids.remote;\n }\n }\n\n /**\n * Get current peer IDs\n */\n public getPeerIds() {\n return { local: this.localPeerId, remote: this.remotePeerId };\n }\n\n /**\n * Check if currently connected to peer\n */\n public getIsConnected(): boolean {\n return this.isConnected;\n }\n\n /**\n * Monitor connection state changes\n * @param handler Called when connection state changes (true=connected, false=disconnected)\n */\n public onConnectionChange(handler: (isConnected: boolean) => void) {\n this.connectionChangeListener = handler;\n // Immediately call with current state if already have a state\n handler(this.isConnected);\n }\n\n /**\n * Monitor remote media stream state changes\n * @param handler Called when remote media becomes available or unavailable\n */\n public onMediaStateChange(handler: (active: boolean) => void) {\n this.mediaStateListener = handler;\n }\n}\n\nexport const createNetClient = (\n client: NetworkClient,\n bus: CommandBus,\n peerId: string | null,\n) => new NetClient(client, bus, peerId);\n","import type { State } from './state/state';\nimport type { CommandBus } from './commandBus';\nimport type { SessionMessage } from '../utils';\nimport { NetClient } from './net';\n\n/**\n * Global session context holder\n * Stores the current session's dependencies for handler access\n * Used by handlers to retrieve state, bus, and network client\n */\nclass SessionContext {\n private readonly state: State;\n private readonly bus: CommandBus;\n private readonly net: NetClient;\n private readonly sid?: string;\n\n constructor(state: State, bus: CommandBus, net: NetClient, sid?: string) {\n this.state = state;\n this.bus = bus;\n this.net = net;\n this.sid = sid;\n }\n\n getState() {\n return this.state;\n }\n\n getBus() {\n return this.bus;\n }\n\n getNet() {\n return this.net;\n }\n\n getSid() {\n return this.sid;\n }\n}\n\nlet instance: SessionContext | null = null;\n\n/**\n * Initialize the global session context\n * Must be called before handlers use context getters\n * @throws Error if context is accessed before initialization\n */\nexport const initializeContext = (\n state: State,\n bus: CommandBus,\n net: NetClient,\n sid?: string,\n) => {\n instance = new SessionContext(state, bus, net, sid);\n};\n\n/**\n * Reset the session context (for testing)\n * @internal Used by tests to clean up between test cases\n */\nexport const resetContext = () => {\n instance = null;\n};\n\n/**\n * Get the current session context\n * @throws Error if context has not been initialized\n * @internal Use getState(), getBus(), etc. instead\n */\nconst requireContext = () => {\n if (!instance) {\n throw new Error(\n '[SessionContext] Not initialized. Call initializeContext() first.',\n );\n }\n return instance;\n};\n\n/**\n * Get the current game state\n * @throws Error if context has not been initialized\n */\nexport const getState = () => requireContext().getState();\n\n/**\n * Get the command bus\n * @throws Error if context has not been initialized\n */\nexport const getBus = () => requireContext().getBus();\n\n/**\n * Get the network client\n * @throws Error if context has not been initialized\n * @internal Prefer using send() for sending messages\n */\nexport const getNet = () => requireContext().getNet();\n\n/**\n * Get the session ID\n * @throws Error if context has not been initialized\n */\nexport const getSid = () => requireContext().getSid();\n\n/**\n * Send a session message to the remote peer\n * @throws Error if context has not been initialized\n */\nexport const send = (message: SessionMessage) =>\n requireContext().getNet().send(message);\n\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getSid, getState, send } from '../context';\nimport { consoleLogger, type SessionMessage } from '../../utils';\n\n/**\n * Handle player ready status notification\n *\n * For most cases, there's only one valid FSM transition:\n * - READY: idle → ready (for initiator)\n * - REMOTE_READY: idle → could_start (for peer)\n *\n * The dispatch() method automatically finds the unique transition.\n */\nexport const ready: CommandListener = (command) => {\n const state = getState();\n const bus = getBus();\n const localSid = getSid();\n consoleLogger.debug('[session:ready] received', {\n from: command.from,\n sid: (command as any).sid,\n localSid,\n local: state.getState('local'),\n remote: state.getState('remote'),\n });\n\n if (command.from === 'local') {\n // Local player initiating READY\n if (!state.canAction('local', 'READY')) {\n console.warn('[Ready] Cannot dispatch READY from current state', {\n state: state.getState('local'),\n });\n return;\n }\n\n state.dispatch('local', 'READY');\n state.dispatch('remote', 'REMOTE_READY');\n\n const message: SessionMessage = {\n type: 'READY',\n sid: localSid,\n };\n send(message);\n consoleLogger.debug('[session:ready] local toggled', {\n local: state.getState('local'),\n remote: state.getState('remote'),\n });\n return;\n }\n\n // Remote player sent READY message\n const remoteSid = (command as any).sid;\n\n // Validate session ID\n if (!remoteSid || localSid !== remoteSid) {\n console.warn('[Ready] Session ID mismatch', {\n local: localSid,\n remote: remoteSid,\n });\n bus.emit('REJECT', { reason: 'sid-mismatch' }, 'local');\n return;\n }\n\n // Check if transition is valid\n if (!state.canAction('remote', 'READY')) {\n console.warn('[Ready] Cannot dispatch READY for remote peer', {\n state: state.getState('remote'),\n });\n return;\n }\n\n // Execute state transitions\n state.dispatch('remote', 'READY');\n state.dispatch('local', 'REMOTE_READY');\n consoleLogger.debug('[session:ready] remote toggled', {\n local: state.getState('local'),\n remote: state.getState('remote'),\n });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport type { PlayerLabel } from '../state/state';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Determine next starter (turn order rotation)\n * If no previous starter, randomly pick one\n * Otherwise, alternate between local and remote\n */\nconst getNextStarter = (lastStarter: PlayerLabel | null): PlayerLabel => {\n if (!lastStarter) {\n return Math.random() < 0.5 ? 'local' : 'remote';\n }\n return lastStarter === 'local' ? 'remote' : 'local';\n};\n\n/**\n * Handle game start request\n *\n * Determines who plays first and transitions both FSMs accordingly.\n */\nexport const start: CommandListener = (command) => {\n const state = getState();\n consoleLogger.debug('[session:start] received', {\n from: command.from,\n payload: command.payload,\n local: state.getState('local'),\n remote: state.getState('remote'),\n lastStart: state.getLastStart(),\n });\n\n if (command.from === 'local') {\n // Local player initiating START\n if (\n !state.canAction('local', 'START') ||\n !state.canAction('remote', 'REMOTE_START')\n ) {\n console.warn('[Start] Cannot START from current state', {\n localState: state.getState('local'),\n remoteState: state.getState('remote'),\n });\n return;\n }\n\n const nextStarter = getNextStarter(state.getLastStart());\n const localTarget = nextStarter === 'local' ? 'turn' : 'remote_turn';\n const remoteTarget = nextStarter === 'local' ? 'remote_turn' : 'turn';\n\n if (state.getHistory().length > 0) {\n state.clearHistory();\n consoleLogger.debug('[session:start] cleared previous match history');\n }\n state.setLastStart(nextStarter);\n state.dispatch('local', 'START', localTarget);\n state.dispatch('remote', 'REMOTE_START', remoteTarget);\n\n // Send message with starter info (encoded as 'sender'/'receiver')\n send({\n type: 'START',\n payload: { starter: nextStarter === 'local' ? 'sender' : 'receiver' },\n });\n consoleLogger.debug('[session:start] local started', { nextStarter });\n return;\n }\n\n // Remote player sent START message\n const starterInfo = (command as any).payload?.starter;\n\n if (!starterInfo) {\n console.warn('[Start] Invalid START message format', { payload: command });\n return;\n }\n\n // Check if transition is valid\n if (\n !state.canAction('local', 'REMOTE_START') ||\n !state.canAction('remote', 'START')\n ) {\n console.warn('[Start] Cannot START from current state', {\n localState: state.getState('local'),\n remoteState: state.getState('remote'),\n });\n return;\n }\n\n // Decode from the receiver's perspective: the sender is the remote peer.\n const starter = starterInfo === 'sender' ? 'remote' : 'local';\n const localTarget = starter === 'local' ? 'turn' : 'remote_turn';\n const remoteTarget = starter === 'local' ? 'remote_turn' : 'turn';\n\n if (state.getHistory().length > 0) {\n state.clearHistory();\n consoleLogger.debug('[session:start] cleared previous match history');\n }\n state.setLastStart(starter);\n state.dispatch('local', 'REMOTE_START', localTarget);\n state.dispatch('remote', 'START', remoteTarget);\n consoleLogger.debug('[session:start] remote started', { starter });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport { consoleLogger, type SessionMessage } from '../../utils';\n\n/**\n * Handle player move in game\n *\n * Flow:\n * 1. Validate move using game plugin (rule checking)\n * 2. Dispatch state transitions\n * 3. Record move in history\n * 4. Check win condition using game plugin\n * 5. Always send MOVE to peer, then let both peers check GAME_OVER locally\n *\n * The game plugin is injected via state.setGamePlugin(), allowing\n * different games to provide their own rule validation and win logic.\n */\nexport const move: CommandListener = (command) => {\n const state = getState();\n const movePayload = command.payload;\n\n consoleLogger.debug('[session:move] received', {\n from: command.from,\n payload: movePayload,\n local: state.getState('local'),\n remote: state.getState('remote'),\n turn: state.getTurnCount(),\n history: state.getHistory().length,\n });\n\n if (command.from === 'local') {\n // Local player making a move\n if (!state.canAction('local', 'MOVE')) {\n console.warn('[Move] Cannot MOVE from current state', {\n state: state.getState('local'),\n });\n return;\n }\n\n // ===== PROXY POINT 1: Validate move using game plugin =====\n const validation = state.validateMove(movePayload);\n if (!validation.valid) {\n console.warn('[Move] Move validation failed', {\n reason: validation.reason,\n move: movePayload,\n });\n // Could send REJECT to inform peer, but for now silently return\n return;\n }\n\n // Dispatch state transitions\n state.dispatch('local', 'MOVE');\n state.dispatch('remote', 'REMOTE_MOVE');\n\n // Record move in history\n const turn = state.getTurnCount();\n state.pushHistory({\n turn,\n player: 'local',\n move: movePayload,\n });\n\n const message: SessionMessage = {\n type: 'MOVE',\n turn,\n payload: movePayload,\n };\n send(message);\n consoleLogger.debug('[session:move] local move sent', {\n turn,\n payload: movePayload,\n });\n\n // ===== PROXY POINT 2: Check win condition using game plugin =====\n const winner = state.checkWin();\n if (winner) {\n // Game ended - someone won\n consoleLogger.debug('[session:move] game over detected', {\n winner,\n turn,\n });\n\n // Transition both FSMs back to idle (game ready to restart)\n state.dispatch('local', 'GAME_OVER');\n state.dispatch('remote', 'GAME_OVER');\n state.cleanupGame();\n consoleLogger.debug('[session:move] local game over applied', {\n winner,\n turn,\n });\n return;\n }\n\n return;\n }\n\n // Remote player made a move\n if (!state.canAction('remote', 'MOVE')) {\n console.warn('[Move] Cannot MOVE for remote player', {\n state: state.getState('remote'),\n });\n return;\n }\n\n // ===== PROXY POINT 1: Validate remote move =====\n const validation = state.validateMove(movePayload);\n if (!validation.valid) {\n console.warn('[Move] Remote move validation failed', {\n reason: validation.reason,\n move: movePayload,\n });\n return;\n }\n\n // Dispatch state transitions\n state.dispatch('remote', 'MOVE');\n state.dispatch('local', 'REMOTE_MOVE');\n\n // Record move in history\n const turn = state.getTurnCount();\n state.pushHistory({\n turn,\n player: 'remote',\n move: movePayload,\n });\n\n // ===== PROXY POINT 2: Check win condition =====\n const winner = state.checkWin();\n if (winner) {\n // Game ended - someone won\n consoleLogger.debug('[session:move] game over detected', { winner, turn });\n\n // Transition both FSMs back to idle (game ready to restart)\n state.dispatch('local', 'GAME_OVER');\n state.dispatch('remote', 'GAME_OVER');\n state.cleanupGame();\n consoleLogger.debug('[session:move] remote game over applied', {\n winner,\n turn,\n });\n return;\n }\n\n consoleLogger.debug('[session:move] remote move applied', {\n turn,\n payload: movePayload,\n });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\ntype RequestAction = 'undo' | 'restart';\ntype RequestPayload = {\n action?: string;\n reason?: string;\n};\n\nconst isRequestAction = (value: unknown): value is RequestAction =>\n value === 'undo' || value === 'restart';\n\n/**\n * Handle approval/rejection of pending requests (undo/restart)\n *\n * When local player approves/rejects a pending request:\n * - Apply undo/restart state changes (clear history, pop moves)\n * - Use dispatchApprove/dispatchReject for complex state transitions\n * - Notify peer of decision\n *\n * When remote player approves/rejects local's request:\n * - Similar flow but opposite state transitions\n */\nexport const request: CommandListener = (command) => {\n if (command.type !== 'APPROVE' && command.type !== 'REJECT') {\n return;\n }\n\n const state = getState();\n const payload = command.payload as RequestPayload | undefined;\n const pendingAction = state.getPendingAction();\n const action =\n pendingAction ??\n (command.from === 'local' &&\n command.type === 'REJECT' &&\n isRequestAction(payload?.action)\n ? payload.action\n : null);\n consoleLogger.debug('[session:request] received', {\n type: command.type,\n from: command.from,\n action,\n local: state.getState('local'),\n remote: state.getState('remote'),\n history: state.getHistory().length,\n });\n\n if (!action) {\n console.warn('[Request] No pending action', { commandType: command.type });\n return;\n }\n\n // Verify payload matches pending action\n if (payload?.action && payload.action !== action) {\n console.warn('[Request] Action mismatch', {\n pending: action,\n payload: payload.action,\n });\n return;\n }\n\n if (command.from === 'local') {\n if (!pendingAction && command.type === 'REJECT') {\n send({\n type: 'REJECT',\n payload: { action, reason: payload?.reason ?? 'rejected' },\n });\n consoleLogger.debug('[session:request] local auto rejected', {\n action,\n reason: payload?.reason,\n });\n return;\n }\n\n if (command.type === 'APPROVE') {\n // Local player approves the request\n if (!state.canAction('local', 'APPROVE')) {\n console.warn('[Request] Cannot APPROVE from current state');\n return;\n }\n\n // Use special method for complex APPROVE transition\n state.dispatchApprove();\n\n // Apply the approved action (undo or restart)\n if (action === 'undo') {\n state.applyUndo(state.getPendingUndoCount() ?? 1);\n } else if (action === 'restart') {\n state.resetGame();\n }\n\n send({ type: 'APPROVE', payload: { action } });\n state.clearPendingStates();\n consoleLogger.debug('[session:request] local approved', { action });\n return;\n }\n\n // REJECT from local\n if (!state.canAction('local', 'REJECT')) {\n console.warn('[Request] Cannot REJECT from current state');\n return;\n }\n\n // Use special method for complex REJECT transition\n state.dispatchReject();\n\n send({\n type: 'REJECT',\n payload: { action, reason: payload?.reason ?? 'rejected' },\n });\n state.clearPendingStates();\n consoleLogger.debug('[session:request] local rejected', { action });\n return;\n }\n\n // Remote player responded to local's request\n if (command.type === 'APPROVE') {\n // Remote player approves\n if (!state.canAction('local', 'APPROVE')) {\n console.warn(\n '[Request] Cannot APPROVE from current state (remote approved)',\n );\n return;\n }\n\n // Use special method for complex APPROVE transition\n state.dispatchApprove();\n\n // Apply the approved action (undo or restart)\n if (action === 'undo') {\n state.applyUndo(state.getPendingUndoCount() ?? 1);\n } else if (action === 'restart') {\n state.resetGame();\n }\n\n state.clearPendingStates();\n consoleLogger.debug('[session:request] remote approved', { action });\n return;\n }\n\n // REJECT from remote\n if (!state.canAction('local', 'REJECT')) {\n console.warn(\n '[Request] Cannot REJECT from current state (remote rejected)',\n );\n state.clearPendingStates();\n return;\n }\n\n // Use special method for complex REJECT transition\n state.dispatchReject();\n state.clearPendingStates();\n consoleLogger.debug('[session:request] remote rejected', { action });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport type { PlayerLabel, TurnEntry } from '../state/state';\nimport { consoleLogger } from '../../utils';\n\nconst fromPeerPerspective = (player: PlayerLabel): PlayerLabel =>\n player === 'local' ? 'remote' : 'local';\n\ntype SyncPayload = {\n history?: TurnEntry[];\n lastStart?: PlayerLabel | null;\n turn?: PlayerLabel;\n resumeTurn?: PlayerLabel | null;\n};\n\nconst getCurrentTurn = (): PlayerLabel => {\n const state = getState();\n return (\n state.getResumeTurn() ??\n (state.getState('local') === 'turn' ? 'local' : 'remote')\n );\n};\n\nconst buildSyncPayload = (): SyncPayload => {\n const state = getState();\n const turn = getCurrentTurn();\n\n return {\n history: state.getHistory(),\n lastStart: state.getLastStart(),\n turn,\n resumeTurn: state.getResumeTurn(),\n };\n};\n\nconst isInSyncRecovery = (): boolean => {\n const state = getState();\n return (\n state.getState('local') === 'syncing' ||\n state.getState('remote') === 'syncing' ||\n state.getState('remote') === 'offline' ||\n state.getResumeTurn() !== null\n );\n};\n\nconst enterSyncState = (): boolean => {\n const state = getState();\n\n if (state.getState('local') !== 'syncing') {\n if (!state.canAction('local', 'SYNC')) {\n consoleLogger.debug('[session:sync] local cannot enter sync', {\n local: state.getState('local'),\n });\n return false;\n }\n state.dispatch('local', 'SYNC', 'syncing');\n }\n\n if (state.getState('remote') === 'offline') {\n if (!state.canAction('remote', 'ONLINE')) {\n consoleLogger.debug('[session:sync] offline remote cannot enter sync', {\n remote: state.getState('remote'),\n });\n return false;\n }\n state.dispatch('remote', 'ONLINE', 'syncing');\n } else if (state.getState('remote') !== 'syncing') {\n if (!state.canAction('remote', 'SYNC')) {\n consoleLogger.debug('[session:sync] remote cannot enter sync', {\n remote: state.getState('remote'),\n });\n return false;\n }\n state.dispatch('remote', 'SYNC', 'syncing');\n }\n\n return (\n state.getState('local') === 'syncing' &&\n state.getState('remote') === 'syncing'\n );\n};\n\nconst restoreFromPayload = (\n payload: SyncPayload,\n mapPeerLabels: boolean,\n): void => {\n const state = getState();\n const mapPlayer = mapPeerLabels\n ? fromPeerPerspective\n : (player: PlayerLabel) => player;\n\n if (payload.history && payload.history.length > 0) {\n state.replaceHistory(\n payload.history.map((entry) => ({\n ...entry,\n player: mapPlayer(entry.player),\n })),\n );\n } else {\n state.clearHistory();\n }\n\n if (payload.lastStart) {\n state.setLastStart(mapPlayer(payload.lastStart));\n } else {\n state.setLastStart(null);\n }\n\n const nextPlayer = payload.resumeTurn\n ? mapPlayer(payload.resumeTurn)\n : payload.turn\n ? mapPlayer(payload.turn)\n : getCurrentTurn();\n\n consoleLogger.debug('[session:sync] state restored', {\n historyLength: state.getHistory().length,\n lastStart: state.getLastStart(),\n nextTurnPlayer: nextPlayer,\n mapped: mapPeerLabels,\n });\n\n if (!enterSyncState()) {\n return;\n }\n\n state.dispatchSyncComplete(nextPlayer);\n};\n\n/**\n * Handle game state synchronization after disconnect/reconnect\n *\n * SYNC_REQUEST: Initiator sends sync request, responder sends back complete game state\n * SYNC_STATE: Received game state, restore it, and transition both players to correct turn\n *\n * Synced data:\n * - history: All moves in order\n * - lastStart: Who started the last match (for turn rotation)\n * - turn: Current turn holder (to determine resume turn)\n * - resumeTurn: Who should have turn after sync (saved before disconnect)\n *\n * All player labels in SYNC_STATE are from the sender's perspective, so the\n * receiver maps local <-> remote before applying them.\n *\n * Flow:\n * 1. Local disconnects -> offline handler records resumeTurn\n * 2. Local reconnects -> online handler sends SYNC_REQUEST\n * 3. Remote responds with SYNC_STATE (history, lastStart, turn, resumeTurn)\n * 4. Local receives SYNC_STATE -> restores everything, calls dispatchSyncComplete\n * 5. Both FSMs now in correct 'turn'/'remote_turn' state\n */\nexport const sync: CommandListener = (command) => {\n const state = getState();\n consoleLogger.debug('[session:sync] received', {\n type: command.type,\n from: command.from,\n payload: command.payload,\n local: state.getState('local'),\n remote: state.getState('remote'),\n history: state.getHistory().length,\n resumeTurn: state.getResumeTurn(),\n });\n\n if (command.type === 'SYNC_REQUEST') {\n if (command.from === 'local') {\n // Local player initiated sync (after reconnection)\n if (\n state.getState('local') !== 'syncing' &&\n !state.canAction('local', 'SYNC')\n ) {\n console.warn('[Sync] Cannot SYNC from current state');\n return;\n }\n\n // Both transition to syncing state. Local may already be syncing when\n // OFFLINE abandoned a pending approval before ONLINE arrived.\n if (state.getState('local') !== 'syncing') {\n state.dispatch('local', 'SYNC', 'syncing');\n }\n if (state.getState('remote') !== 'syncing') {\n state.dispatch('remote', 'SYNC', 'syncing');\n }\n\n // Send request to peer (peer will respond with SYNC_STATE)\n send({ type: 'SYNC_REQUEST', from: '', payload: command.payload });\n consoleLogger.debug('[session:sync] request sent');\n return;\n }\n\n // Remote initiated sync. Send our current timeline first, then also close\n // our own FSM recovery path; the peer might be the only side requesting.\n const payload = buildSyncPayload();\n send({ type: 'SYNC_STATE', from: '', payload });\n consoleLogger.debug('[session:sync] state sent', payload);\n if (isInSyncRecovery()) {\n restoreFromPayload(payload, false);\n }\n return;\n }\n\n if (command.type !== 'SYNC_STATE') {\n return;\n }\n\n if (command.from === 'local') {\n const payload = buildSyncPayload();\n send({ type: 'SYNC_STATE', from: '', payload });\n consoleLogger.debug('[session:sync] state pushed', payload);\n if (isInSyncRecovery()) {\n restoreFromPayload(payload, false);\n }\n return;\n }\n\n restoreFromPayload((command.payload as SyncPayload) || {}, true);\n};\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Handle undo request from local or remote player\n *\n * Validates undo is legal (enough history, no pending action, valid state)\n * and initiates undo request with peer approval flow.\n *\n * Undo always rolls back the requester's last move:\n * - requester has just moved -> undo 1 ply\n * - requester has already received a reply -> undo 2 plies\n *\n * If the request is rejected, the game resumes from the pre-request turn.\n * Records pending undo state for approval/rejection handling by request handler.\n */\nexport const undo: CommandListener = (command) => {\n if (command.type !== 'UNDO') {\n return;\n }\n\n const state = getState();\n const bus = getBus();\n consoleLogger.debug('[session:undo] received', {\n from: command.from,\n local: state.getState('local'),\n remote: state.getState('remote'),\n pending: state.getPendingAction(),\n history: state.getHistory().length,\n });\n\n if (command.from === 'local') {\n // Validate no pending action\n if (state.hasPendingAction()) {\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'UNDO')) {\n console.warn('[Undo] Cannot UNDO from current state');\n return;\n }\n\n // If it is our turn, our last move is two plies back. If it is the\n // peer's turn, our last move is the latest ply.\n const localState = state.getState('local');\n const undoCount = localState === 'turn' ? 2 : 1;\n const rejectResumePlayer = localState === 'turn' ? 'local' : 'remote';\n\n // Validate history is long enough\n if (state.getHistory().length < undoCount) {\n console.warn('[Undo] Not enough history to undo', { count: undoCount });\n return;\n }\n\n // Store the pre-request turn for REJECT. APPROVE uses requester ownership\n // in dispatchApprove(), then applies the history rollback.\n state.initializeUndoRequest(undoCount as 1 | 2, rejectResumePlayer);\n\n // Transition to approval waiting state\n state.dispatch('local', 'UNDO');\n state.dispatch('remote', 'REMOTE_UNDO');\n\n send({ type: 'UNDO', payload: { count: undoCount } });\n consoleLogger.debug('[session:undo] local requested', { undoCount });\n return;\n }\n\n // Remote player requested undo\n if (state.hasPendingAction()) {\n bus.emit('REJECT', { action: 'undo', reason: 'busy' }, 'local');\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'REMOTE_UNDO')) {\n console.warn('[Undo] Cannot accept remote UNDO request');\n bus.emit('REJECT', { action: 'undo', reason: 'invalid_state' }, 'local');\n return;\n }\n\n // Extract undo count from payload\n const payload = command.payload as { count?: number } | undefined;\n const count = payload?.count === 2 ? 2 : 1;\n\n // Validate count value\n if (payload?.count && payload.count !== 1 && payload.count !== 2) {\n bus.emit('REJECT', { action: 'undo', reason: 'invalid' }, 'local');\n return;\n }\n\n // Validate history is long enough\n if (state.getHistory().length < count) {\n bus.emit('REJECT', { action: 'undo', reason: 'no_history' }, 'local');\n return;\n }\n\n // Store the pre-request turn for REJECT. APPROVE uses requester ownership\n // in dispatchApprove(), then applies the history rollback.\n const resumePlayer = state.getState('local') === 'turn' ? 'local' : 'remote';\n\n // Initialize pending undo state\n state.initializeUndoRequest(count as 1 | 2, resumePlayer);\n\n // Transition to approval waiting state (remote initiated, local approving)\n state.dispatch('local', 'REMOTE_UNDO');\n state.dispatch('remote', 'UNDO');\n consoleLogger.debug('[session:undo] remote requested', {\n count,\n resumePlayer,\n });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Handle restart request from local or remote player\n *\n * Validates restart is legal (no pending action, valid state)\n * and initiates restart request with peer approval flow.\n *\n * Restart clears all history but keeps player order for next match.\n */\nexport const restart: CommandListener = (command) => {\n if (command.type !== 'RESTART') {\n return;\n }\n\n const state = getState();\n const bus = getBus();\n consoleLogger.debug('[session:restart] received', {\n from: command.from,\n local: state.getState('local'),\n remote: state.getState('remote'),\n pending: state.getPendingAction(),\n history: state.getHistory().length,\n });\n\n if (command.from === 'local') {\n // Validate no pending action\n if (state.hasPendingAction()) {\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'RESTART')) {\n console.warn('[Restart] Cannot RESTART from current state');\n return;\n }\n\n // Determine who will go first in next match\n const resumePlayer =\n state.getState('local') === 'turn' ? 'local' : 'remote';\n\n // Initialize pending restart state\n state.initializeRestartRequest(resumePlayer);\n\n // Transition to approval waiting state\n state.dispatch('local', 'RESTART');\n state.dispatch('remote', 'REMOTE_RESTART');\n\n send({ type: 'RESTART' });\n consoleLogger.debug('[session:restart] local requested', { resumePlayer });\n return;\n }\n\n // Remote player requested restart\n if (state.hasPendingAction()) {\n bus.emit('REJECT', { action: 'restart', reason: 'busy' }, 'local');\n return;\n }\n\n // Validate state transitions\n if (!state.canAction('local', 'REMOTE_RESTART')) {\n console.warn('[Restart] Cannot accept remote RESTART request');\n bus.emit('REJECT', { action: 'restart', reason: 'invalid_state' }, 'local');\n return;\n }\n\n // Determine who will go first in next match\n const resumePlayer = state.getState('local') === 'turn' ? 'local' : 'remote';\n\n // Initialize pending restart state\n state.initializeRestartRequest(resumePlayer);\n\n // Transition to approval waiting state (remote initiated, local approving)\n state.dispatch('local', 'REMOTE_RESTART');\n state.dispatch('remote', 'RESTART');\n consoleLogger.debug('[session:restart] remote requested', { resumePlayer });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getBus, getState } from '../context';\nimport { consoleLogger } from '../../utils';\n\n/**\n * Handle connection state changes (OFFLINE/ONLINE)\n *\n * OFFLINE: Record current turn state before disconnecting\n * ONLINE: Trigger sync to restore game state after reconnect\n *\n * Flow:\n * 1. OFFLINE arrives -> save resumeTurn, transition to offline\n * 2. ONLINE arrives -> transition to syncing, emit SYNC_REQUEST\n * 3. sync handler takes over -> restore history and turn assignment\n */\nexport const offline: CommandListener = (command) => {\n if (command.type !== 'OFFLINE' && command.type !== 'ONLINE') {\n return;\n }\n\n const state = getState();\n const bus = getBus();\n consoleLogger.debug('[session:connection] received', {\n type: command.type,\n from: command.from,\n local: state.getState('local'),\n remote: state.getState('remote'),\n pending: state.getPendingAction(),\n resumeTurn: state.getResumeTurn(),\n });\n\n if (command.type === 'OFFLINE') {\n // A disconnect makes an in-flight approval unverifiable. Model this as\n // sync recovery: local waits in syncing, remote stays offline until ONLINE.\n if (state.hasPendingAction()) {\n const resumeTurn = state.getResumeTurn();\n\n if (state.canAction('local', 'SYNC')) {\n state.dispatch('local', 'SYNC', 'syncing');\n }\n state.clearPendingStates();\n state.setResumeTurn(resumeTurn);\n }\n\n // Peer disconnected - save current turn state for later recovery\n if (!state.canAction('remote', 'OFFLINE')) {\n console.warn('[Offline] Cannot transition to OFFLINE from current state');\n return;\n }\n\n // Record who had the turn before going offline\n const currentTurn =\n state.getResumeTurn() ??\n (state.getState('local') === 'turn' ? 'local' : 'remote');\n state.setResumeTurn(currentTurn);\n\n // Transition to offline state\n state.dispatch('remote', 'OFFLINE', 'offline');\n consoleLogger.debug('[session:connection] remote offline', { currentTurn });\n return;\n }\n\n // ONLINE only means \"reconnected\" if the session FSM was already offline.\n // Initial WebRTC connection is handled by the connection observer, not this\n // recovery path.\n if (state.getState('remote') !== 'offline') {\n consoleLogger.debug(\n '[session:connection] ignored online while remote is not offline',\n {\n remote: state.getState('remote'),\n },\n );\n return;\n }\n\n // ONLINE - peer reconnected\n if (!state.canAction('remote', 'ONLINE')) {\n console.warn('[Offline] Cannot transition to ONLINE from current state');\n return;\n }\n\n // Transition to syncing state\n state.dispatch('remote', 'ONLINE', 'syncing');\n\n // We kept the authoritative timeline while the peer was away, so push our\n // state to the reconnecting peer instead of asking it for a possibly empty one.\n bus.emit('SYNC_STATE', undefined, 'local');\n consoleLogger.debug('[session:connection] remote online, sync state pushed');\n};\n","import { CommandBus } from '../commandBus';\nimport { ready } from './ready';\nimport { start } from './start';\nimport { move } from './move';\nimport { request } from './request';\nimport { sync } from './sync';\nimport { undo } from './undo';\nimport { restart } from './restart';\nimport { offline } from './offLine';\n\nexport const registerHandlers = (bus: CommandBus) => {\n bus.register('READY', ready);\n bus.register('START', start);\n bus.register('MOVE', move);\n bus.register('UNDO', undo);\n bus.register('RESTART', restart);\n bus.register('SYNC_REQUEST', sync);\n bus.register('SYNC_STATE', sync);\n bus.register('OFFLINE', offline);\n bus.register('ONLINE', offline);\n bus.register('APPROVE', request);\n bus.register('REJECT', request);\n};\n","import type { CommandBus } from './commandBus';\n\nexport interface ISessionActions {\n ready(): void;\n start(): void;\n move(data: unknown): void;\n undo(): void;\n restart(): void;\n approve(): void;\n reject(): void;\n rejoin(sid: string): void;\n}\n\nexport class LocalActionsAPI implements ISessionActions {\n constructor(private bus: CommandBus) {}\n\n ready(): void {\n this.bus.emit('READY');\n }\n\n start(): void {\n this.bus.emit('START');\n }\n\n move(data: unknown): void {\n this.bus.emit('MOVE', data);\n }\n\n undo(): void {\n this.bus.emit('UNDO');\n }\n\n restart(): void {\n this.bus.emit('RESTART');\n }\n\n approve(): void {\n this.bus.emit('APPROVE');\n }\n\n reject(): void {\n this.bus.emit('REJECT');\n }\n\n rejoin(sid: string): void {\n this.bus.emit('REJOIN', { sid });\n }\n}\n\n","import { NetworkClient } from 'p2p-lockstep-kit-network';\nimport { CommandBus } from './commandBus';\nimport { State } from './state/state';\nimport { createNetClient } from './net';\nimport { initializeContext } from './context';\nimport { registerHandlers } from './handlers/busRegister.ts';\nimport {\n buildGameStateSnapshot,\n GameStateObserver,\n UINotificationAdapter,\n} from './observer';\nimport { LocalActionsAPI } from './actions';\n\n/**\n * Create a new game session with state management and networking\n * @param sid Session ID for rejoining (optional)\n * @param networkClient Custom network client (optional, creates default if not provided)\n * @returns Session manager with bus, state, observer, net, and send method\n *\n * @example\n * const session = createSession();\n * // UI automatically updates when state changes - no manual observer calls needed!\n * session.observer.subscribe(myUIObserver);\n * session.bus.emit('READY', undefined, 'local');\n * await session.net.connect(remotePeerId);\n */\nexport const createSession = (networkClient: NetworkClient, sid?: string) => {\n const bus = new CommandBus();\n const state = new State(null, null);\n const observer = new GameStateObserver();\n const net = createNetClient(networkClient, bus, null);\n\n // Connect State mutations to UI updates via adapter (plugin pattern)\n // This is the ONLY place where UI notifications are triggered\n const uiAdapter = new UINotificationAdapter(state, observer, () =>\n net.getIsConnected(),\n );\n state.subscribeStateObserver(uiAdapter);\n\n initializeContext(state, bus, net, sid);\n registerHandlers(bus);\n\n const actions = new LocalActionsAPI(bus);\n\n net.onConnectionChange((isConnected) => {\n observer.notifyConnectionChange(isConnected);\n observer.notifyStateChange(buildGameStateSnapshot(state, isConnected));\n });\n\n return {\n bus,\n state,\n observer,\n net,\n actions,\n send: net.send.bind(net),\n };\n};\n\nexport * from './observer';\nexport type { ISessionActions } from './actions';\nexport type { PendingAction, PlayerLabel, TurnEntry } from './state/state';\nexport type { SessionEvent, SessionState } from './state/fsm';\n"],"mappings":"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","names":["move","localTarget","remoteTarget","validation","turn","winner","resumePlayer"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "p2p-lockstep-kit-session",
|
|
3
3
|
"description": "Game session and lockstep engine for the p2p lockstep kit.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/session/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"p2p-lockstep-kit-network": "^0.1.
|
|
20
|
+
"p2p-lockstep-kit-network": "^0.1.3"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"prettier": "^3.3.1",
|
|
@@ -432,28 +432,9 @@ const oneMoveWinPlugin = {
|
|
|
432
432
|
await waitForBus();
|
|
433
433
|
|
|
434
434
|
assert.equal(session.state.getPendingAction(), null);
|
|
435
|
-
assert.equal(
|
|
436
|
-
assert.
|
|
437
|
-
assert.
|
|
438
|
-
|
|
439
|
-
client.inbound({
|
|
440
|
-
type: 'SYNC_STATE',
|
|
441
|
-
payload: {
|
|
442
|
-
history: [{ turn: 1, player: 'local', move: { step: 'peer-move' } }],
|
|
443
|
-
lastStart: 'local',
|
|
444
|
-
turn: 'local',
|
|
445
|
-
resumeTurn: 'local',
|
|
446
|
-
},
|
|
447
|
-
from: 'remote',
|
|
448
|
-
});
|
|
449
|
-
await waitForBus();
|
|
450
|
-
|
|
451
|
-
assert.equal(session.state.getPendingAction(), null);
|
|
452
|
-
assert.equal(session.state.getState('local'), 'remote_turn');
|
|
453
|
-
assert.equal(session.state.getState('remote'), 'turn');
|
|
454
|
-
assert.equal(session.state.getHistory().length, 1);
|
|
455
|
-
assert.equal(session.state.getHistory()[0].player, 'remote');
|
|
456
|
-
assert.equal(session.state.getLastStart(), 'remote');
|
|
435
|
+
assert.equal(client.sent.at(-1).type, 'SYNC_STATE');
|
|
436
|
+
assert.match(session.state.getState('local'), /^(turn|remote_turn)$/);
|
|
437
|
+
assert.match(session.state.getState('remote'), /^(turn|remote_turn)$/);
|
|
457
438
|
}
|
|
458
439
|
|
|
459
440
|
{
|
|
@@ -533,15 +514,20 @@ const oneMoveWinPlugin = {
|
|
|
533
514
|
await waitForBus();
|
|
534
515
|
|
|
535
516
|
assert.equal(session.state.getPendingAction(), null);
|
|
536
|
-
assert.equal(
|
|
537
|
-
assert.
|
|
538
|
-
assert.
|
|
517
|
+
assert.equal(client.sent.at(-1).type, 'SYNC_STATE');
|
|
518
|
+
assert.match(session.state.getState('local'), /^(turn|remote_turn)$/);
|
|
519
|
+
assert.match(session.state.getState('remote'), /^(turn|remote_turn)$/);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
{
|
|
523
|
+
const { client, session } = createConnectedSession();
|
|
524
|
+
await waitForBus();
|
|
539
525
|
|
|
540
526
|
client.inbound({
|
|
541
527
|
type: 'SYNC_STATE',
|
|
542
528
|
payload: {
|
|
543
|
-
history: [{ turn: 1, player: '
|
|
544
|
-
lastStart: '
|
|
529
|
+
history: [{ turn: 1, player: 'local', move: { step: 'peer-move' } }],
|
|
530
|
+
lastStart: 'local',
|
|
545
531
|
turn: 'remote',
|
|
546
532
|
resumeTurn: 'remote',
|
|
547
533
|
},
|
|
@@ -549,12 +535,11 @@ const oneMoveWinPlugin = {
|
|
|
549
535
|
});
|
|
550
536
|
await waitForBus();
|
|
551
537
|
|
|
552
|
-
assert.equal(session.state.getPendingAction(), null);
|
|
553
538
|
assert.equal(session.state.getState('local'), 'turn');
|
|
554
539
|
assert.equal(session.state.getState('remote'), 'remote_turn');
|
|
555
540
|
assert.equal(session.state.getHistory().length, 1);
|
|
556
|
-
assert.equal(session.state.getHistory()[0].player, '
|
|
557
|
-
assert.equal(session.state.getLastStart(), '
|
|
541
|
+
assert.equal(session.state.getHistory()[0].player, 'remote');
|
|
542
|
+
assert.equal(session.state.getLastStart(), 'remote');
|
|
558
543
|
}
|
|
559
544
|
|
|
560
545
|
{
|