p2p-lockstep-kit-session 0.1.11 → 0.1.13

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.
@@ -1,6 +1,6 @@
1
1
  import { NetworkClient } from 'p2p-lockstep-kit-network';
2
2
 
3
- type SessionMessageType = 'READY' | 'START' | 'MOVE' | 'UNDO' | 'RESTART' | 'APPROVE' | 'REJECT' | 'REJOIN' | 'SYNC_REQUEST' | 'SYNC_STATE' | 'OFFLINE' | 'ONLINE' | 'GAME_OVER';
3
+ type SessionMessageType = 'READY' | 'START' | 'MOVE' | 'UNDO' | 'RESTART' | 'REQUEST' | 'RESIGN' | 'APPROVE' | 'REJECT' | 'SYNC_REQUEST' | 'SYNC_STATE' | 'OFFLINE' | 'ONLINE' | 'GAME_OVER';
4
4
  type SessionMessage = {
5
5
  type: SessionMessageType;
6
6
  from?: string;
@@ -74,7 +74,7 @@ declare class NetClient {
74
74
  }
75
75
 
76
76
  type SessionState = 'idle' | 'ready' | 'could_start' | 'turn' | 'remote_turn' | 'approving' | 'waiting_approval' | 'syncing' | 'offline';
77
- type SessionEvent = 'REMOTE_READY' | 'READY' | 'START' | 'REMOTE_START' | 'MOVE' | 'REMOTE_MOVE' | 'UNDO' | 'REMOTE_UNDO' | 'RESTART' | 'REMOTE_RESTART' | 'APPROVE' | 'REJECT' | 'GAME_OVER' | 'REJOIN' | 'SYNC' | 'SYNC_COMPLETE' | 'OFFLINE' | 'ONLINE';
77
+ type SessionEvent = 'REMOTE_READY' | 'READY' | 'START' | 'REMOTE_START' | 'MOVE' | 'REMOTE_MOVE' | 'UNDO' | 'REMOTE_UNDO' | 'RESTART' | 'REMOTE_RESTART' | 'REQUEST' | 'REMOTE_REQUEST' | 'APPROVE' | 'REJECT' | 'GAME_OVER' | 'SYNC' | 'SYNC_COMPLETE' | 'OFFLINE' | 'ONLINE';
78
78
 
79
79
  interface GameStateSnapshot {
80
80
  localState: SessionState;
@@ -83,10 +83,11 @@ interface GameStateSnapshot {
83
83
  history: TurnEntry[];
84
84
  lastStart: PlayerLabel | null;
85
85
  pendingAction: PendingAction;
86
+ outcome: GameOutcome | null;
86
87
  connected: boolean;
87
88
  }
88
89
  interface GameEvent {
89
- type: 'READY' | 'START' | 'MOVE' | 'GAME_OVER' | 'UNDO' | 'RESTART' | 'OFFLINE' | 'ONLINE' | 'SYNC' | 'ERROR';
90
+ type: 'READY' | 'START' | 'MOVE' | 'GAME_OVER' | 'UNDO' | 'RESTART' | 'DRAW' | 'RESIGN' | 'OFFLINE' | 'ONLINE' | 'SYNC' | 'ERROR';
90
91
  payload?: unknown;
91
92
  from?: 'local' | 'remote';
92
93
  timestamp?: number;
@@ -169,7 +170,16 @@ type TurnEntry = {
169
170
  move?: unknown;
170
171
  };
171
172
  type PlayerLabel = 'local' | 'remote';
172
- type PendingAction = 'undo' | 'restart' | null;
173
+ type PendingActionId = 'undo' | 'restart' | 'draw';
174
+ type PendingAction = PendingActionId | null;
175
+ type GameOutcome = {
176
+ kind: 'win';
177
+ winner: PlayerLabel;
178
+ reason: 'rules' | 'resignation';
179
+ } | {
180
+ kind: 'draw';
181
+ reason: 'agreement' | 'mutual_resignation';
182
+ };
173
183
  declare class State {
174
184
  private local;
175
185
  private remote;
@@ -180,6 +190,7 @@ declare class State {
180
190
  private pendingUndoCount;
181
191
  private resumeTurn;
182
192
  private lastStart;
193
+ private outcome;
183
194
  private gamePlugin;
184
195
  private stateObserverManager;
185
196
  constructor(id: string | null, remoteId: string | null);
@@ -215,6 +226,8 @@ declare class State {
215
226
  getLastStart(): PlayerLabel | null;
216
227
  setResumeTurn(player: PlayerLabel | null): void;
217
228
  getResumeTurn(): PlayerLabel | null;
229
+ setOutcome(outcome: GameOutcome | null): void;
230
+ getOutcome(): GameOutcome | null;
218
231
  private getPlayerFsm;
219
232
  private notifyStateChanged;
220
233
  private notifyHistoryChanged;
@@ -243,6 +256,7 @@ declare class State {
243
256
  * Initialize restart request with resume turn
244
257
  */
245
258
  initializeRestartRequest(resumeTurn: PlayerLabel): void;
259
+ initializePendingRequest(action: Exclude<PendingActionId, 'undo' | 'restart'>, resumeTurn: PlayerLabel): void;
246
260
  /**
247
261
  * Check if pending action is undo
248
262
  */
@@ -260,7 +274,7 @@ declare class State {
260
274
  */
261
275
  resetGame(): void;
262
276
  /**
263
- * Save start player for rejoin flow
277
+ * Save start player for reconnect synchronization
264
278
  */
265
279
  recordStartPlayer(player: PlayerLabel): void;
266
280
  /**
@@ -309,6 +323,7 @@ declare class State {
309
323
  * @returns Winner (local/remote) or null if game continues
310
324
  */
311
325
  checkWin(): PlayerLabel | null;
326
+ completeGame(outcome: GameOutcome): void;
312
327
  /**
313
328
  * Cleanup when game ends (for plugin to reset internal state)
314
329
  */
@@ -326,9 +341,11 @@ interface ISessionActions {
326
341
  move(data: unknown): void;
327
342
  undo(): void;
328
343
  restart(): void;
344
+ request(action: PendingActionId, payload?: unknown): void;
345
+ offerDraw(): void;
346
+ resign(): void;
329
347
  approve(): void;
330
348
  reject(): void;
331
- rejoin(sid: string): void;
332
349
  }
333
350
  declare class LocalActionsAPI implements ISessionActions {
334
351
  private bus;
@@ -338,14 +355,16 @@ declare class LocalActionsAPI implements ISessionActions {
338
355
  move(data: unknown): void;
339
356
  undo(): void;
340
357
  restart(): void;
358
+ request(action: PendingActionId, payload?: unknown): void;
359
+ offerDraw(): void;
360
+ resign(): void;
341
361
  approve(): void;
342
362
  reject(): void;
343
- rejoin(sid: string): void;
344
363
  }
345
364
 
346
365
  /**
347
366
  * Create a new game session with state management and networking
348
- * @param sid Session ID for rejoining (optional)
367
+ * @param sid Protocol scope identifier shared by both peers (optional)
349
368
  * @param networkClient Custom network client (optional, creates default if not provided)
350
369
  * @returns Session manager with bus, state, observer, net, and send method
351
370
  *
@@ -365,4 +384,4 @@ declare const createSession: (networkClient: NetworkClient, sid?: string) => {
365
384
  send: (message: SessionMessage) => void;
366
385
  };
367
386
 
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 };
387
+ export { DefaultGamePlugin, type GameEvent, type GameOutcome, type GameState, GameStateObserver, type GameStateSnapshot, type IGameObserver, type IGamePlugin, type ISessionActions, type IStateObserver, type PendingAction, type PendingActionId, type PlayerLabel, type SessionEvent, type SessionState, StateObserverManager, type TurnEntry, UINotificationAdapter, type ValidationResult, buildGameStateSnapshot, createSession };
@@ -92,7 +92,7 @@ var CommandBus = class {
92
92
 
93
93
  // session/state/fsm.ts
94
94
  var transitions = [
95
- // Lobby readiness
95
+ // Pre-match readiness
96
96
  { from: "idle", event: "READY", to: "ready" },
97
97
  { from: "ready", event: "READY", to: "idle" },
98
98
  { from: "idle", event: "REMOTE_READY", to: "could_start" },
@@ -114,11 +114,15 @@ var transitions = [
114
114
  { from: "remote_turn", event: "UNDO", to: "waiting_approval" },
115
115
  { from: "turn", event: "RESTART", to: "waiting_approval" },
116
116
  { from: "remote_turn", event: "RESTART", to: "waiting_approval" },
117
+ { from: "turn", event: "REQUEST", to: "waiting_approval" },
118
+ { from: "remote_turn", event: "REQUEST", to: "waiting_approval" },
117
119
  // Requests coming from remote (we need to approve)
118
120
  { from: "turn", event: "REMOTE_UNDO", to: "approving" },
119
121
  { from: "remote_turn", event: "REMOTE_UNDO", to: "approving" },
120
122
  { from: "turn", event: "REMOTE_RESTART", to: "approving" },
121
123
  { from: "remote_turn", event: "REMOTE_RESTART", to: "approving" },
124
+ { from: "turn", event: "REMOTE_REQUEST", to: "approving" },
125
+ { from: "remote_turn", event: "REMOTE_REQUEST", to: "approving" },
122
126
  // Approval outcomes when we were waiting for the peer.
123
127
  { from: "waiting_approval", event: "APPROVE", to: "turn" },
124
128
  { from: "waiting_approval", event: "REJECT", to: "turn" },
@@ -128,9 +132,12 @@ var transitions = [
128
132
  { from: "approving", event: "APPROVE", to: "remote_turn" },
129
133
  { from: "approving", event: "REJECT", to: "remote_turn" },
130
134
  { from: "approving", event: "REJECT", to: "turn" },
131
- // Game end resets back to lobby idle
135
+ // Game end resets back to the pre-match idle state
132
136
  { from: "turn", event: "GAME_OVER", to: "idle" },
133
137
  { from: "remote_turn", event: "GAME_OVER", to: "idle" },
138
+ { from: "waiting_approval", event: "GAME_OVER", to: "idle" },
139
+ { from: "approving", event: "GAME_OVER", to: "idle" },
140
+ { from: "syncing", event: "GAME_OVER", to: "idle" },
134
141
  // Rejoin/sync flows
135
142
  { from: "turn", event: "SYNC", to: "syncing" },
136
143
  { from: "remote_turn", event: "SYNC", to: "syncing" },
@@ -304,6 +311,7 @@ function buildGameStateSnapshot(state, connected = false) {
304
311
  history: state.getHistory(),
305
312
  lastStart: state.getLastStart(),
306
313
  pendingAction: state.getPendingAction(),
314
+ outcome: state.getOutcome(),
307
315
  connected
308
316
  };
309
317
  }
@@ -357,11 +365,12 @@ var State = class {
357
365
  __publicField(this, "pendingUndoCount", null);
358
366
  __publicField(this, "resumeTurn", null);
359
367
  __publicField(this, "lastStart", null);
368
+ __publicField(this, "outcome", null);
360
369
  // Game plugin for rule validation and win checking
361
370
  __publicField(this, "gamePlugin", new DefaultGamePlugin());
362
371
  // Internal state observer for UI notifications
363
372
  __publicField(this, "stateObserverManager", new StateObserverManager());
364
- // ===== Helper Methods for Undo/Restart Request Handling =====
373
+ // ===== Helper Methods for Approval Request Handling =====
365
374
  /**
366
375
  * Save game state snapshot for undo/restart operations
367
376
  */
@@ -507,6 +516,17 @@ var State = class {
507
516
  getResumeTurn() {
508
517
  return this.resumeTurn;
509
518
  }
519
+ setOutcome(outcome) {
520
+ consoleLogger.debug("[session:state] outcome set", {
521
+ from: this.outcome,
522
+ to: outcome
523
+ });
524
+ this.outcome = outcome;
525
+ this.notifyStateChanged();
526
+ }
527
+ getOutcome() {
528
+ return this.outcome;
529
+ }
510
530
  getPlayerFsm(player) {
511
531
  return player === "local" ? this.local : this.remote;
512
532
  }
@@ -608,6 +628,14 @@ var State = class {
608
628
  resumeTurn
609
629
  });
610
630
  }
631
+ initializePendingRequest(action, resumeTurn) {
632
+ this.pendingAction = action;
633
+ this.resumeTurn = resumeTurn;
634
+ consoleLogger.debug("[session:state] approval request initialized", {
635
+ action,
636
+ resumeTurn
637
+ });
638
+ }
611
639
  /**
612
640
  * Check if pending action is undo
613
641
  */
@@ -644,6 +672,7 @@ var State = class {
644
672
  this.local = new SessionFsm("idle");
645
673
  this.remote = new SessionFsm("idle");
646
674
  this.lastStart = null;
675
+ this.outcome = null;
647
676
  this.pendingAction = null;
648
677
  this.pendingUndoCount = null;
649
678
  this.resumeTurn = null;
@@ -651,7 +680,7 @@ var State = class {
651
680
  this.notifyStateChanged();
652
681
  }
653
682
  /**
654
- * Save start player for rejoin flow
683
+ * Save start player for reconnect synchronization
655
684
  */
656
685
  recordStartPlayer(player) {
657
686
  this.lastStart = player;
@@ -795,6 +824,17 @@ var State = class {
795
824
  });
796
825
  return winner;
797
826
  }
827
+ completeGame(outcome) {
828
+ this.setOutcome(outcome);
829
+ if (this.canAction("local", "GAME_OVER")) {
830
+ this.dispatch("local", "GAME_OVER");
831
+ }
832
+ if (this.canAction("remote", "GAME_OVER")) {
833
+ this.dispatch("remote", "GAME_OVER");
834
+ }
835
+ this.clearPendingStates();
836
+ this.cleanupGame();
837
+ }
798
838
  /**
799
839
  * Cleanup when game ends (for plugin to reset internal state)
800
840
  */
@@ -1046,6 +1086,7 @@ var start = (command) => {
1046
1086
  state.clearHistory();
1047
1087
  consoleLogger.debug("[session:start] cleared previous match history");
1048
1088
  }
1089
+ state.setOutcome(null);
1049
1090
  state.setLastStart(nextStarter);
1050
1091
  state.dispatch("local", "START", localTarget2);
1051
1092
  state.dispatch("remote", "REMOTE_START", remoteTarget2);
@@ -1075,6 +1116,7 @@ var start = (command) => {
1075
1116
  state.clearHistory();
1076
1117
  consoleLogger.debug("[session:start] cleared previous match history");
1077
1118
  }
1119
+ state.setOutcome(null);
1078
1120
  state.setLastStart(starter);
1079
1121
  state.dispatch("local", "REMOTE_START", localTarget);
1080
1122
  state.dispatch("remote", "START", remoteTarget);
@@ -1132,9 +1174,7 @@ var move = (command) => {
1132
1174
  winner: winner2,
1133
1175
  turn: turn2
1134
1176
  });
1135
- state.dispatch("local", "GAME_OVER");
1136
- state.dispatch("remote", "GAME_OVER");
1137
- state.cleanupGame();
1177
+ state.completeGame({ kind: "win", winner: winner2, reason: "rules" });
1138
1178
  consoleLogger.debug("[session:move] local game over applied", {
1139
1179
  winner: winner2,
1140
1180
  turn: turn2
@@ -1168,9 +1208,7 @@ var move = (command) => {
1168
1208
  const winner = state.checkWin();
1169
1209
  if (winner) {
1170
1210
  consoleLogger.debug("[session:move] game over detected", { winner, turn });
1171
- state.dispatch("local", "GAME_OVER");
1172
- state.dispatch("remote", "GAME_OVER");
1173
- state.cleanupGame();
1211
+ state.completeGame({ kind: "win", winner, reason: "rules" });
1174
1212
  consoleLogger.debug("[session:move] remote game over applied", {
1175
1213
  winner,
1176
1214
  turn
@@ -1184,7 +1222,17 @@ var move = (command) => {
1184
1222
  };
1185
1223
 
1186
1224
  // session/handlers/request.ts
1187
- var isRequestAction = (value) => value === "undo" || value === "restart";
1225
+ var isRequestAction = (value) => value === "undo" || value === "restart" || value === "draw";
1226
+ var applyApprovedAction = (action) => {
1227
+ const state = getState();
1228
+ if (action === "undo") {
1229
+ state.applyUndo(state.getPendingUndoCount() ?? 1);
1230
+ } else if (action === "restart") {
1231
+ state.resetGame();
1232
+ } else {
1233
+ state.completeGame({ kind: "draw", reason: "agreement" });
1234
+ }
1235
+ };
1188
1236
  var request = (command) => {
1189
1237
  if (command.type !== "APPROVE" && command.type !== "REJECT") {
1190
1238
  return;
@@ -1230,11 +1278,7 @@ var request = (command) => {
1230
1278
  return;
1231
1279
  }
1232
1280
  state.dispatchApprove();
1233
- if (action === "undo") {
1234
- state.applyUndo(state.getPendingUndoCount() ?? 1);
1235
- } else if (action === "restart") {
1236
- state.resetGame();
1237
- }
1281
+ applyApprovedAction(action);
1238
1282
  send({ type: "APPROVE", payload: { action } });
1239
1283
  state.clearPendingStates();
1240
1284
  consoleLogger.debug("[session:request] local approved", { action });
@@ -1261,11 +1305,7 @@ var request = (command) => {
1261
1305
  return;
1262
1306
  }
1263
1307
  state.dispatchApprove();
1264
- if (action === "undo") {
1265
- state.applyUndo(state.getPendingUndoCount() ?? 1);
1266
- } else if (action === "restart") {
1267
- state.resetGame();
1268
- }
1308
+ applyApprovedAction(action);
1269
1309
  state.clearPendingStates();
1270
1310
  consoleLogger.debug("[session:request] remote approved", { action });
1271
1311
  return;
@@ -1295,7 +1335,8 @@ var buildSyncPayload = () => {
1295
1335
  history: state.getHistory(),
1296
1336
  lastStart: state.getLastStart(),
1297
1337
  turn,
1298
- resumeTurn: state.getResumeTurn()
1338
+ resumeTurn: state.getResumeTurn(),
1339
+ outcome: state.getOutcome()
1299
1340
  };
1300
1341
  };
1301
1342
  var isInSyncRecovery = () => {
@@ -1350,6 +1391,8 @@ var restoreFromPayload = (payload, mapPeerLabels) => {
1350
1391
  } else {
1351
1392
  state.setLastStart(null);
1352
1393
  }
1394
+ const mappedOutcome = payload.outcome ? payload.outcome.kind === "win" ? { ...payload.outcome, winner: mapPlayer(payload.outcome.winner) } : payload.outcome : null;
1395
+ state.setOutcome(mappedOutcome);
1353
1396
  const nextPlayer = payload.resumeTurn ? mapPlayer(payload.resumeTurn) : payload.turn ? mapPlayer(payload.turn) : getCurrentTurn();
1354
1397
  consoleLogger.debug("[session:sync] state restored", {
1355
1398
  historyLength: state.getHistory().length,
@@ -1360,7 +1403,12 @@ var restoreFromPayload = (payload, mapPeerLabels) => {
1360
1403
  if (!enterSyncState()) {
1361
1404
  return;
1362
1405
  }
1363
- state.dispatchSyncComplete(nextPlayer);
1406
+ if (mappedOutcome) {
1407
+ state.dispatch("local", "GAME_OVER");
1408
+ state.dispatch("remote", "GAME_OVER");
1409
+ } else {
1410
+ state.dispatchSyncComplete(nextPlayer);
1411
+ }
1364
1412
  };
1365
1413
  var sync = (command) => {
1366
1414
  const state = getState();
@@ -1551,10 +1599,10 @@ var offline = (command) => {
1551
1599
  console.warn("[Offline] Cannot transition to OFFLINE from current state");
1552
1600
  return;
1553
1601
  }
1554
- const currentTurn = state.getResumeTurn() ?? (state.getState("local") === "turn" ? "local" : "remote");
1555
- state.setResumeTurn(currentTurn);
1602
+ const currentTurn2 = state.getResumeTurn() ?? (state.getState("local") === "turn" ? "local" : "remote");
1603
+ state.setResumeTurn(currentTurn2);
1556
1604
  state.dispatch("remote", "OFFLINE", "offline");
1557
- consoleLogger.debug("[session:connection] remote offline", { currentTurn });
1605
+ consoleLogger.debug("[session:connection] remote offline", { currentTurn: currentTurn2 });
1558
1606
  return;
1559
1607
  }
1560
1608
  if (state.getState("remote") !== "offline") {
@@ -1575,6 +1623,85 @@ var offline = (command) => {
1575
1623
  consoleLogger.debug("[session:connection] remote online, sync state pushed");
1576
1624
  };
1577
1625
 
1626
+ // session/handlers/pendingRequest.ts
1627
+ var isGenericRequestAction = (value) => value === "draw";
1628
+ var currentTurn = () => getState().getState("local") === "turn" ? "local" : "remote";
1629
+ var pendingRequest = (command) => {
1630
+ if (command.type !== "REQUEST") return;
1631
+ const state = getState();
1632
+ const bus = getBus();
1633
+ const payload = command.payload;
1634
+ const action = payload?.action;
1635
+ if (!isGenericRequestAction(action)) {
1636
+ if (command.from === "remote") {
1637
+ send({
1638
+ type: "REJECT",
1639
+ payload: { action, reason: "unknown_action" }
1640
+ });
1641
+ }
1642
+ return;
1643
+ }
1644
+ if (state.getOutcome()) return;
1645
+ if (command.from === "local") {
1646
+ if (state.hasPendingAction() || !state.canAction("local", "REQUEST")) {
1647
+ return;
1648
+ }
1649
+ const resumeTurn2 = currentTurn();
1650
+ state.initializePendingRequest(action, resumeTurn2);
1651
+ state.dispatch("local", "REQUEST");
1652
+ state.dispatch("remote", "REMOTE_REQUEST");
1653
+ send({ type: "REQUEST", payload: { action, payload: payload?.payload } });
1654
+ consoleLogger.debug("[session:request] local requested", { action });
1655
+ return;
1656
+ }
1657
+ if (state.hasPendingAction()) {
1658
+ bus.emit("REJECT", { action, reason: "busy" }, "local");
1659
+ return;
1660
+ }
1661
+ if (!state.canAction("local", "REMOTE_REQUEST")) {
1662
+ bus.emit("REJECT", { action, reason: "invalid_state" }, "local");
1663
+ return;
1664
+ }
1665
+ const resumeTurn = currentTurn();
1666
+ state.initializePendingRequest(action, resumeTurn);
1667
+ state.dispatch("local", "REMOTE_REQUEST");
1668
+ state.dispatch("remote", "REQUEST");
1669
+ consoleLogger.debug("[session:request] remote requested", { action });
1670
+ };
1671
+
1672
+ // session/handlers/resign.ts
1673
+ var resign = (command) => {
1674
+ if (command.type !== "RESIGN") return;
1675
+ const state = getState();
1676
+ const previous = state.getOutcome();
1677
+ if (previous?.kind === "win" && previous.reason === "resignation") {
1678
+ state.setOutcome({ kind: "draw", reason: "mutual_resignation" });
1679
+ consoleLogger.debug("[session:resign] simultaneous resignation resolved");
1680
+ return;
1681
+ }
1682
+ if (previous || state.hasPendingAction()) return;
1683
+ const active = state.getState("local") === "turn" || state.getState("local") === "remote_turn";
1684
+ if (!active) return;
1685
+ if (command.from === "local") {
1686
+ send({ type: "RESIGN" });
1687
+ state.completeGame({
1688
+ kind: "win",
1689
+ winner: "remote",
1690
+ reason: "resignation"
1691
+ });
1692
+ } else {
1693
+ state.completeGame({
1694
+ kind: "win",
1695
+ winner: "local",
1696
+ reason: "resignation"
1697
+ });
1698
+ }
1699
+ consoleLogger.debug("[session:resign] game completed", {
1700
+ from: command.from,
1701
+ outcome: state.getOutcome()
1702
+ });
1703
+ };
1704
+
1578
1705
  // session/handlers/busRegister.ts
1579
1706
  var registerHandlers = (bus) => {
1580
1707
  bus.register("READY", ready);
@@ -1582,6 +1709,8 @@ var registerHandlers = (bus) => {
1582
1709
  bus.register("MOVE", move);
1583
1710
  bus.register("UNDO", undo);
1584
1711
  bus.register("RESTART", restart);
1712
+ bus.register("REQUEST", pendingRequest);
1713
+ bus.register("RESIGN", resign);
1585
1714
  bus.register("SYNC_REQUEST", sync);
1586
1715
  bus.register("SYNC_STATE", sync);
1587
1716
  bus.register("OFFLINE", offline);
@@ -1610,15 +1739,21 @@ var LocalActionsAPI = class {
1610
1739
  restart() {
1611
1740
  this.bus.emit("RESTART");
1612
1741
  }
1742
+ request(action, payload) {
1743
+ this.bus.emit("REQUEST", { action, payload });
1744
+ }
1745
+ offerDraw() {
1746
+ this.request("draw");
1747
+ }
1748
+ resign() {
1749
+ this.bus.emit("RESIGN");
1750
+ }
1613
1751
  approve() {
1614
1752
  this.bus.emit("APPROVE");
1615
1753
  }
1616
1754
  reject() {
1617
1755
  this.bus.emit("REJECT");
1618
1756
  }
1619
- rejoin(sid) {
1620
- this.bus.emit("REJOIN", { sid });
1621
- }
1622
1757
  };
1623
1758
 
1624
1759
  // session/index.ts
@@ -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 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"]}
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/pendingRequest.ts","../../session/handlers/resign.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 | 'REQUEST'\n | 'RESIGN'\n | 'APPROVE'\n | 'REJECT'\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 | 'REQUEST'\n | 'REMOTE_REQUEST'\n | 'APPROVE'\n | 'REJECT'\n | 'GAME_OVER'\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 // Pre-match 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 { from: 'turn', event: 'REQUEST', to: 'waiting_approval' },\n { from: 'remote_turn', event: 'REQUEST', 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 { from: 'turn', event: 'REMOTE_REQUEST', to: 'approving' },\n { from: 'remote_turn', event: 'REMOTE_REQUEST', 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 the pre-match idle state\n { from: 'turn', event: 'GAME_OVER', to: 'idle' },\n { from: 'remote_turn', event: 'GAME_OVER', to: 'idle' },\n { from: 'waiting_approval', event: 'GAME_OVER', to: 'idle' },\n { from: 'approving', event: 'GAME_OVER', to: 'idle' },\n { from: 'syncing', 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 GameOutcome,\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 outcome: GameOutcome | 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 | 'DRAW'\n | 'RESIGN'\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 outcome: state.getOutcome(),\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 PendingActionId = 'undo' | 'restart' | 'draw';\nexport type PendingAction = PendingActionId | null;\nexport type GameOutcome =\n | {\n kind: 'win';\n winner: PlayerLabel;\n reason: 'rules' | 'resignation';\n }\n | {\n kind: 'draw';\n reason: 'agreement' | 'mutual_resignation';\n };\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 private outcome: GameOutcome | 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 public setOutcome(outcome: GameOutcome | null): void {\n consoleLogger.debug('[session:state] outcome set', {\n from: this.outcome,\n to: outcome,\n });\n this.outcome = outcome;\n this.notifyStateChanged();\n }\n\n public getOutcome(): GameOutcome | null {\n return this.outcome;\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 Approval 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 public initializePendingRequest(\n action: Exclude<PendingActionId, 'undo' | 'restart'>,\n resumeTurn: PlayerLabel,\n ): void {\n this.pendingAction = action;\n this.resumeTurn = resumeTurn;\n consoleLogger.debug('[session:state] approval request initialized', {\n action,\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.outcome = 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 reconnect synchronization\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 public completeGame(outcome: GameOutcome): void {\n this.setOutcome(outcome);\n if (this.canAction('local', 'GAME_OVER')) {\n this.dispatch('local', 'GAME_OVER');\n }\n if (this.canAction('remote', 'GAME_OVER')) {\n this.dispatch('remote', 'GAME_OVER');\n }\n this.clearPendingStates();\n this.cleanupGame();\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.setOutcome(null);\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.setOutcome(null);\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 state.completeGame({ kind: 'win', winner, reason: 'rules' });\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 state.completeGame({ kind: 'win', winner, reason: 'rules' });\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' | 'draw';\ntype RequestPayload = {\n action?: string;\n reason?: string;\n};\n\nconst isRequestAction = (value: unknown): value is RequestAction =>\n value === 'undo' || value === 'restart' || value === 'draw';\n\nconst applyApprovedAction = (action: RequestAction): void => {\n const state = getState();\n if (action === 'undo') {\n state.applyUndo(state.getPendingUndoCount() ?? 1);\n } else if (action === 'restart') {\n state.resetGame();\n } else {\n state.completeGame({ kind: 'draw', reason: 'agreement' });\n }\n};\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 applyApprovedAction(action);\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 applyApprovedAction(action);\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 { GameOutcome, 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 outcome?: GameOutcome | 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 outcome: state.getOutcome(),\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 mappedOutcome: GameOutcome | null = payload.outcome\n ? payload.outcome.kind === 'win'\n ? { ...payload.outcome, winner: mapPlayer(payload.outcome.winner) }\n : payload.outcome\n : null;\n state.setOutcome(mappedOutcome);\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 if (mappedOutcome) {\n state.dispatch('local', 'GAME_OVER');\n state.dispatch('remote', 'GAME_OVER');\n } else {\n state.dispatchSyncComplete(nextPlayer);\n }\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 type { CommandListener } from '../commandBus';\nimport { getBus, getState, send } from '../context';\nimport type { PendingActionId, PlayerLabel } from '../state/state';\nimport { consoleLogger } from '../../utils';\n\ntype RequestPayload = { action?: unknown; payload?: unknown };\n\nconst isGenericRequestAction = (\n value: unknown,\n): value is Exclude<PendingActionId, 'undo' | 'restart'> => value === 'draw';\n\nconst currentTurn = (): PlayerLabel =>\n getState().getState('local') === 'turn' ? 'local' : 'remote';\n\nexport const pendingRequest: CommandListener = (command) => {\n if (command.type !== 'REQUEST') return;\n const state = getState();\n const bus = getBus();\n const payload = command.payload as RequestPayload | undefined;\n const action = payload?.action;\n\n if (!isGenericRequestAction(action)) {\n if (command.from === 'remote') {\n send({\n type: 'REJECT',\n payload: { action, reason: 'unknown_action' },\n });\n }\n return;\n }\n\n if (state.getOutcome()) return;\n\n if (command.from === 'local') {\n if (state.hasPendingAction() || !state.canAction('local', 'REQUEST')) {\n return;\n }\n const resumeTurn = currentTurn();\n state.initializePendingRequest(action, resumeTurn);\n state.dispatch('local', 'REQUEST');\n state.dispatch('remote', 'REMOTE_REQUEST');\n send({ type: 'REQUEST', payload: { action, payload: payload?.payload } });\n consoleLogger.debug('[session:request] local requested', { action });\n return;\n }\n\n if (state.hasPendingAction()) {\n bus.emit('REJECT', { action, reason: 'busy' }, 'local');\n return;\n }\n if (!state.canAction('local', 'REMOTE_REQUEST')) {\n bus.emit('REJECT', { action, reason: 'invalid_state' }, 'local');\n return;\n }\n\n const resumeTurn = currentTurn();\n state.initializePendingRequest(action, resumeTurn);\n state.dispatch('local', 'REMOTE_REQUEST');\n state.dispatch('remote', 'REQUEST');\n consoleLogger.debug('[session:request] remote requested', { action });\n};\n","import type { CommandListener } from '../commandBus';\nimport { getState, send } from '../context';\nimport { consoleLogger } from '../../utils';\n\nexport const resign: CommandListener = (command) => {\n if (command.type !== 'RESIGN') return;\n const state = getState();\n\n const previous = state.getOutcome();\n if (previous?.kind === 'win' && previous.reason === 'resignation') {\n state.setOutcome({ kind: 'draw', reason: 'mutual_resignation' });\n consoleLogger.debug('[session:resign] simultaneous resignation resolved');\n return;\n }\n if (previous || state.hasPendingAction()) return;\n\n const active =\n state.getState('local') === 'turn' ||\n state.getState('local') === 'remote_turn';\n if (!active) return;\n\n if (command.from === 'local') {\n send({ type: 'RESIGN' });\n state.completeGame({\n kind: 'win',\n winner: 'remote',\n reason: 'resignation',\n });\n } else {\n state.completeGame({\n kind: 'win',\n winner: 'local',\n reason: 'resignation',\n });\n }\n consoleLogger.debug('[session:resign] game completed', {\n from: command.from,\n outcome: state.getOutcome(),\n });\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';\nimport { pendingRequest } from './pendingRequest';\nimport { resign } from './resign';\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('REQUEST', pendingRequest);\n bus.register('RESIGN', resign);\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';\nimport type { PendingActionId } from './state/state';\n\nexport interface ISessionActions {\n ready(): void;\n start(): void;\n move(data: unknown): void;\n undo(): void;\n restart(): void;\n request(action: PendingActionId, payload?: unknown): void;\n offerDraw(): void;\n resign(): void;\n approve(): void;\n reject(): 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 request(action: PendingActionId, payload?: unknown): void {\n this.bus.emit('REQUEST', { action, payload });\n }\n\n offerDraw(): void {\n this.request('draw');\n }\n\n resign(): void {\n this.bus.emit('RESIGN');\n }\n\n approve(): void {\n this.bus.emit('APPROVE');\n }\n\n reject(): void {\n this.bus.emit('REJECT');\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 Protocol scope identifier shared by both peers (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 {\n GameOutcome,\n PendingAction,\n PendingActionId,\n PlayerLabel,\n TurnEntry,\n} from './state/state';\nexport type { SessionEvent, SessionState } from './state/fsm';\n"],"mappings":"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","names":["move","localTarget","remoteTarget","validation","turn","winner","resumePlayer","currentTurn","resumeTurn"]}
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.11",
4
+ "version": "0.1.13",
5
5
  "license": "GPL-3.0-only",
6
6
  "type": "module",
7
7
  "main": "./dist/session/index.js",
@@ -34,7 +34,7 @@ class BoundaryClient {
34
34
 
35
35
  const createConnectedSession = () => {
36
36
  const client = new BoundaryClient();
37
- const session = createSession(client, 'demo-room');
37
+ const session = createSession(client, 'demo-session');
38
38
 
39
39
  session.net.setPeerIds({ local: 'local', remote: 'remote' });
40
40
  client.connect();
@@ -44,7 +44,7 @@ const createConnectedSession = () => {
44
44
 
45
45
  const startGame = async () => {
46
46
  const runtime = createConnectedSession();
47
- runtime.client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
47
+ runtime.client.inbound({ type: 'READY', sid: 'demo-session', from: 'remote' });
48
48
  await waitForBus();
49
49
  runtime.session.actions.start();
50
50
  await waitForBus();
@@ -60,7 +60,7 @@ const startGame = async () => {
60
60
 
61
61
  const startGameWithFirstPlayer = async (firstPlayer) => {
62
62
  const runtime = createConnectedSession();
63
- runtime.client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
63
+ runtime.client.inbound({ type: 'READY', sid: 'demo-session', from: 'remote' });
64
64
  await waitForBus();
65
65
  runtime.session.state.setLastStart(
66
66
  firstPlayer === 'local' ? 'remote' : 'local',
@@ -99,7 +99,7 @@ const oneMoveWinPlugin = {
99
99
  const sent = client.sent.at(-1);
100
100
  assert.equal(typeof sent, 'object');
101
101
  assert.equal(sent.type, 'READY');
102
- assert.equal(sent.sid, 'demo-room');
102
+ assert.equal(sent.sid, 'demo-session');
103
103
  assert.equal(session.state.getState('local'), 'ready');
104
104
  assert.equal(session.state.getState('remote'), 'could_start');
105
105
  }
@@ -108,7 +108,7 @@ const oneMoveWinPlugin = {
108
108
  const { client, session } = createConnectedSession();
109
109
  await waitForBus();
110
110
 
111
- client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
111
+ client.inbound({ type: 'READY', sid: 'demo-session', from: 'remote' });
112
112
  await waitForBus();
113
113
 
114
114
  assert.equal(session.state.getState('local'), 'could_start');
@@ -120,7 +120,7 @@ const oneMoveWinPlugin = {
120
120
  await waitForBus();
121
121
 
122
122
  client.inbound(
123
- JSON.stringify({ type: 'READY', sid: 'demo-room', from: 'remote' }),
123
+ JSON.stringify({ type: 'READY', sid: 'demo-session', from: 'remote' }),
124
124
  );
125
125
  await waitForBus();
126
126
 
@@ -238,7 +238,7 @@ const oneMoveWinPlugin = {
238
238
  assert.equal(session.state.getState('local'), 'idle');
239
239
  assert.equal(session.state.getState('remote'), 'idle');
240
240
 
241
- client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
241
+ client.inbound({ type: 'READY', sid: 'demo-session', from: 'remote' });
242
242
  await waitForBus();
243
243
  session.actions.start();
244
244
  await waitForBus();
@@ -569,4 +569,115 @@ const oneMoveWinPlugin = {
569
569
  assert.equal(session.state.getHistory()[0].player, 'local');
570
570
  }
571
571
 
572
+ {
573
+ const { client, session } = await startGameWithFirstPlayer('local');
574
+ session.actions.move({ step: 'keep-this-move' });
575
+ await waitForBus();
576
+
577
+ session.actions.offerDraw();
578
+ await waitForBus();
579
+
580
+ const sent = client.sent.at(-1);
581
+ assert.equal(sent.type, 'REQUEST');
582
+ assert.equal(sent.payload.action, 'draw');
583
+ assert.equal(session.state.getPendingAction(), 'draw');
584
+ assert.equal(session.state.getState('local'), 'waiting_approval');
585
+ assert.equal(session.state.getState('remote'), 'approving');
586
+
587
+ client.inbound({
588
+ type: 'APPROVE',
589
+ payload: { action: 'draw' },
590
+ from: 'remote',
591
+ });
592
+ await waitForBus();
593
+
594
+ assert.deepEqual(session.state.getOutcome(), {
595
+ kind: 'draw',
596
+ reason: 'agreement',
597
+ });
598
+ assert.equal(session.state.getHistory().length, 1);
599
+ assert.equal(session.state.getState('local'), 'idle');
600
+ assert.equal(session.state.getState('remote'), 'idle');
601
+ assert.equal(session.observer.getSnapshot().outcome.kind, 'draw');
602
+ }
603
+
604
+ {
605
+ const { client, session } = await startGame();
606
+ const before = {
607
+ local: session.state.getState('local'),
608
+ remote: session.state.getState('remote'),
609
+ };
610
+
611
+ client.inbound({
612
+ type: 'REQUEST',
613
+ payload: { action: 'draw' },
614
+ from: 'remote',
615
+ });
616
+ await waitForBus();
617
+ assert.equal(session.state.getPendingAction(), 'draw');
618
+ assert.equal(session.state.getState('local'), 'approving');
619
+
620
+ session.actions.reject();
621
+ await waitForBus();
622
+ assert.equal(client.sent.at(-1).payload.action, 'draw');
623
+ assert.equal(session.state.getOutcome(), null);
624
+ assert.equal(session.state.getState('local'), before.local);
625
+ assert.equal(session.state.getState('remote'), before.remote);
626
+ }
627
+
628
+ {
629
+ const { client, session } = await startGame();
630
+ session.actions.resign();
631
+ await waitForBus();
632
+
633
+ assert.equal(client.sent.at(-1).type, 'RESIGN');
634
+ assert.deepEqual(session.state.getOutcome(), {
635
+ kind: 'win',
636
+ winner: 'remote',
637
+ reason: 'resignation',
638
+ });
639
+ assert.equal(session.state.getState('local'), 'idle');
640
+ assert.equal(session.state.getState('remote'), 'idle');
641
+ }
642
+
643
+ {
644
+ const { client, session } = await startGame();
645
+ client.inbound({ type: 'RESIGN', from: 'remote' });
646
+ await waitForBus();
647
+
648
+ assert.deepEqual(session.state.getOutcome(), {
649
+ kind: 'win',
650
+ winner: 'local',
651
+ reason: 'resignation',
652
+ });
653
+ }
654
+
655
+ {
656
+ const { client, session } = createConnectedSession();
657
+ await waitForBus();
658
+ client.inbound({
659
+ type: 'SYNC_STATE',
660
+ payload: {
661
+ history: [],
662
+ lastStart: 'local',
663
+ turn: 'local',
664
+ outcome: {
665
+ kind: 'win',
666
+ winner: 'local',
667
+ reason: 'resignation',
668
+ },
669
+ },
670
+ from: 'remote',
671
+ });
672
+ await waitForBus();
673
+
674
+ assert.deepEqual(session.state.getOutcome(), {
675
+ kind: 'win',
676
+ winner: 'remote',
677
+ reason: 'resignation',
678
+ });
679
+ assert.equal(session.state.getState('local'), 'idle');
680
+ assert.equal(session.state.getState('remote'), 'idle');
681
+ }
682
+
572
683
  console.log('serialization smoke passed');