@drmxrcy/tcg-core 0.0.0-202602060542

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.
Files changed (157) hide show
  1. package/README.md +882 -0
  2. package/package.json +58 -0
  3. package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
  4. package/src/__tests__/createMockAlphaClashGame.ts +462 -0
  5. package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
  6. package/src/__tests__/createMockGundamGame.ts +379 -0
  7. package/src/__tests__/createMockLorcanaGame.ts +328 -0
  8. package/src/__tests__/createMockOnePieceGame.ts +429 -0
  9. package/src/__tests__/createMockRiftboundGame.ts +462 -0
  10. package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
  11. package/src/__tests__/gundam-engine-definition.test.ts +110 -0
  12. package/src/__tests__/integration-complete-game.test.ts +508 -0
  13. package/src/__tests__/integration-network-sync.test.ts +469 -0
  14. package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
  15. package/src/__tests__/move-enumeration.test.ts +725 -0
  16. package/src/__tests__/multiplayer-engine.test.ts +555 -0
  17. package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
  18. package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
  19. package/src/actions/action-definition.test.ts +201 -0
  20. package/src/actions/action-definition.ts +122 -0
  21. package/src/actions/action-timing.test.ts +490 -0
  22. package/src/actions/action-timing.ts +257 -0
  23. package/src/cards/card-definition.test.ts +268 -0
  24. package/src/cards/card-definition.ts +27 -0
  25. package/src/cards/card-instance.test.ts +422 -0
  26. package/src/cards/card-instance.ts +49 -0
  27. package/src/cards/computed-properties.test.ts +530 -0
  28. package/src/cards/computed-properties.ts +84 -0
  29. package/src/cards/conditional-modifiers.test.ts +390 -0
  30. package/src/cards/modifiers.test.ts +286 -0
  31. package/src/cards/modifiers.ts +51 -0
  32. package/src/engine/MULTIPLAYER.md +425 -0
  33. package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
  34. package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
  35. package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
  36. package/src/engine/__tests__/rule-engine.test.ts +366 -0
  37. package/src/engine/index.ts +14 -0
  38. package/src/engine/multiplayer-engine.example.ts +571 -0
  39. package/src/engine/multiplayer-engine.ts +409 -0
  40. package/src/engine/rule-engine.test.ts +286 -0
  41. package/src/engine/rule-engine.ts +1539 -0
  42. package/src/engine/tracker-system.ts +172 -0
  43. package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
  44. package/src/filtering/card-filter.test.ts +230 -0
  45. package/src/filtering/card-filter.ts +91 -0
  46. package/src/filtering/card-query.test.ts +901 -0
  47. package/src/filtering/card-query.ts +273 -0
  48. package/src/filtering/filter-matching.test.ts +944 -0
  49. package/src/filtering/filter-matching.ts +315 -0
  50. package/src/flow/SERIALIZATION.md +428 -0
  51. package/src/flow/__tests__/flow-definition.test.ts +427 -0
  52. package/src/flow/__tests__/flow-manager.test.ts +756 -0
  53. package/src/flow/__tests__/flow-serialization.test.ts +565 -0
  54. package/src/flow/flow-definition.ts +453 -0
  55. package/src/flow/flow-manager.ts +1044 -0
  56. package/src/flow/index.ts +35 -0
  57. package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
  58. package/src/game-definition/__tests__/game-definition.test.ts +291 -0
  59. package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
  60. package/src/game-definition/game-definition.ts +261 -0
  61. package/src/game-definition/index.ts +28 -0
  62. package/src/game-definition/move-definitions.ts +188 -0
  63. package/src/game-definition/validation.ts +183 -0
  64. package/src/history/history-manager.test.ts +497 -0
  65. package/src/history/history-manager.ts +312 -0
  66. package/src/history/history-operations.ts +122 -0
  67. package/src/history/index.ts +9 -0
  68. package/src/history/types.ts +255 -0
  69. package/src/index.ts +32 -0
  70. package/src/logging/index.ts +27 -0
  71. package/src/logging/log-formatter.ts +187 -0
  72. package/src/logging/logger.ts +276 -0
  73. package/src/logging/types.ts +148 -0
  74. package/src/moves/create-move.test.ts +331 -0
  75. package/src/moves/create-move.ts +64 -0
  76. package/src/moves/move-enumeration.ts +228 -0
  77. package/src/moves/move-executor.test.ts +431 -0
  78. package/src/moves/move-executor.ts +195 -0
  79. package/src/moves/move-system.test.ts +380 -0
  80. package/src/moves/move-system.ts +463 -0
  81. package/src/moves/standard-moves.ts +231 -0
  82. package/src/operations/card-operations.test.ts +236 -0
  83. package/src/operations/card-operations.ts +116 -0
  84. package/src/operations/card-registry-impl.test.ts +251 -0
  85. package/src/operations/card-registry-impl.ts +70 -0
  86. package/src/operations/card-registry.test.ts +234 -0
  87. package/src/operations/card-registry.ts +106 -0
  88. package/src/operations/counter-operations.ts +152 -0
  89. package/src/operations/game-operations.test.ts +280 -0
  90. package/src/operations/game-operations.ts +140 -0
  91. package/src/operations/index.ts +24 -0
  92. package/src/operations/operations-impl.test.ts +354 -0
  93. package/src/operations/operations-impl.ts +468 -0
  94. package/src/operations/zone-operations.test.ts +295 -0
  95. package/src/operations/zone-operations.ts +223 -0
  96. package/src/rng/seeded-rng.test.ts +339 -0
  97. package/src/rng/seeded-rng.ts +123 -0
  98. package/src/targeting/index.ts +48 -0
  99. package/src/targeting/target-definition.test.ts +273 -0
  100. package/src/targeting/target-definition.ts +37 -0
  101. package/src/targeting/target-dsl.ts +279 -0
  102. package/src/targeting/target-resolver.ts +486 -0
  103. package/src/targeting/target-validation.test.ts +994 -0
  104. package/src/targeting/target-validation.ts +286 -0
  105. package/src/telemetry/events.ts +202 -0
  106. package/src/telemetry/index.ts +21 -0
  107. package/src/telemetry/telemetry-manager.ts +127 -0
  108. package/src/telemetry/types.ts +68 -0
  109. package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
  110. package/src/testing/index.ts +88 -0
  111. package/src/testing/test-assertions.test.ts +341 -0
  112. package/src/testing/test-assertions.ts +256 -0
  113. package/src/testing/test-card-factory.test.ts +228 -0
  114. package/src/testing/test-card-factory.ts +111 -0
  115. package/src/testing/test-context-factory.ts +187 -0
  116. package/src/testing/test-end-assertions.test.ts +262 -0
  117. package/src/testing/test-end-assertions.ts +95 -0
  118. package/src/testing/test-engine-builder.test.ts +389 -0
  119. package/src/testing/test-engine-builder.ts +46 -0
  120. package/src/testing/test-flow-assertions.test.ts +284 -0
  121. package/src/testing/test-flow-assertions.ts +115 -0
  122. package/src/testing/test-player-builder.test.ts +132 -0
  123. package/src/testing/test-player-builder.ts +46 -0
  124. package/src/testing/test-replay-assertions.test.ts +356 -0
  125. package/src/testing/test-replay-assertions.ts +164 -0
  126. package/src/testing/test-rng-helpers.test.ts +260 -0
  127. package/src/testing/test-rng-helpers.ts +190 -0
  128. package/src/testing/test-state-builder.test.ts +373 -0
  129. package/src/testing/test-state-builder.ts +99 -0
  130. package/src/testing/test-zone-factory.test.ts +295 -0
  131. package/src/testing/test-zone-factory.ts +224 -0
  132. package/src/types/branded-utils.ts +54 -0
  133. package/src/types/branded.test.ts +175 -0
  134. package/src/types/branded.ts +33 -0
  135. package/src/types/index.ts +8 -0
  136. package/src/types/state.test.ts +198 -0
  137. package/src/types/state.ts +154 -0
  138. package/src/validation/card-type-guards.test.ts +242 -0
  139. package/src/validation/card-type-guards.ts +179 -0
  140. package/src/validation/index.ts +40 -0
  141. package/src/validation/schema-builders.test.ts +403 -0
  142. package/src/validation/schema-builders.ts +345 -0
  143. package/src/validation/type-guard-builder.test.ts +216 -0
  144. package/src/validation/type-guard-builder.ts +109 -0
  145. package/src/validation/validator-builder.test.ts +375 -0
  146. package/src/validation/validator-builder.ts +273 -0
  147. package/src/zones/index.ts +28 -0
  148. package/src/zones/zone-factory.test.ts +183 -0
  149. package/src/zones/zone-factory.ts +44 -0
  150. package/src/zones/zone-operations.test.ts +800 -0
  151. package/src/zones/zone-operations.ts +306 -0
  152. package/src/zones/zone-state-helpers.test.ts +337 -0
  153. package/src/zones/zone-state-helpers.ts +128 -0
  154. package/src/zones/zone-visibility.test.ts +156 -0
  155. package/src/zones/zone-visibility.ts +36 -0
  156. package/src/zones/zone.test.ts +186 -0
  157. package/src/zones/zone.ts +66 -0
@@ -0,0 +1,428 @@
1
+ # Flow State Serialization
2
+
3
+ ## Overview
4
+
5
+ The FlowManager supports full serialization and deserialization of game state for persistence and replay functionality. This enables:
6
+
7
+ - **Game Saving**: Store complete game state to database
8
+ - **Game Loading**: Restore and continue from any saved point
9
+ - **Replay System**: Step through game history
10
+ - **Cross-session Play**: Players can resume games later
11
+
12
+ ## Core Concepts
13
+
14
+ ### Serializable State
15
+
16
+ All flow state is JSON-serializable:
17
+
18
+ ```typescript
19
+ type SerializedFlowState = {
20
+ currentPhase?: string;
21
+ currentSegment?: string;
22
+ turnNumber: number;
23
+ currentPlayer: string;
24
+ };
25
+ ```
26
+
27
+ ### Restoration Options
28
+
29
+ ```typescript
30
+ type FlowManagerOptions = {
31
+ /** Skip initialization hooks when restoring */
32
+ skipInitialization?: boolean;
33
+ /** Restore from serialized flow state */
34
+ restoreFrom?: SerializedFlowState;
35
+ };
36
+ ```
37
+
38
+ ## Usage Examples
39
+
40
+ ### Basic Save/Load Pattern
41
+
42
+ ```typescript
43
+ import { FlowManager, type FlowDefinition, type SerializedFlowState } from "@drmxrcy/tcg-core/flow";
44
+
45
+ // Define your game flow
46
+ const flow: FlowDefinition<GameState> = {
47
+ turn: {
48
+ phases: {
49
+ ready: { order: 0, next: "draw" },
50
+ draw: { order: 1, next: "main" },
51
+ main: { order: 2, next: "end" },
52
+ end: { order: 3, next: undefined },
53
+ },
54
+ },
55
+ };
56
+
57
+ // Play game
58
+ const manager = new FlowManager(flow, initialState);
59
+ manager.nextPhase(); // Progress game
60
+
61
+ // === Save to database ===
62
+ const saveData = {
63
+ gameState: manager.getGameState(),
64
+ flowState: manager.serializeFlowState(),
65
+ timestamp: Date.now(),
66
+ };
67
+
68
+ await db.games.insert(saveData);
69
+
70
+ // === Later: Load from database ===
71
+ const loaded = await db.games.findById(gameId);
72
+
73
+ // Restore exact state
74
+ const restored = new FlowManager(flow, loaded.gameState, {
75
+ restoreFrom: loaded.flowState,
76
+ });
77
+
78
+ // Continue playing from where you left off
79
+ restored.nextPhase();
80
+ ```
81
+
82
+ ### Database Schema Example
83
+
84
+ ```typescript
85
+ type SavedGame = {
86
+ id: string;
87
+ gameState: GameState; // Your game-specific state
88
+ flowState: SerializedFlowState; // Flow position
89
+ players: string[];
90
+ createdAt: Date;
91
+ updatedAt: Date;
92
+ };
93
+
94
+ async function saveGame(manager: FlowManager<GameState>): Promise<string> {
95
+ const saveData: SavedGame = {
96
+ id: generateId(),
97
+ gameState: manager.getGameState(),
98
+ flowState: manager.serializeFlowState(),
99
+ players: /* extract from state */,
100
+ createdAt: new Date(),
101
+ updatedAt: new Date(),
102
+ };
103
+
104
+ await db.collection('games').insertOne(saveData);
105
+ return saveData.id;
106
+ }
107
+
108
+ async function loadGame(gameId: string): Promise<FlowManager<GameState>> {
109
+ const saved = await db.collection('games').findOne({ id: gameId });
110
+
111
+ if (!saved) throw new Error('Game not found');
112
+
113
+ return new FlowManager(gameFlow, saved.gameState, {
114
+ restoreFrom: saved.flowState,
115
+ });
116
+ }
117
+ ```
118
+
119
+ ### Replay System
120
+
121
+ ```typescript
122
+ type GameSnapshot = {
123
+ gameState: GameState;
124
+ flowState: SerializedFlowState;
125
+ moveNumber: number;
126
+ };
127
+
128
+ class ReplaySystem {
129
+ private snapshots: GameSnapshot[] = [];
130
+
131
+ // Record snapshot after each move
132
+ recordSnapshot(manager: FlowManager<GameState>) {
133
+ this.snapshots.push({
134
+ gameState: manager.getGameState(),
135
+ flowState: manager.serializeFlowState(),
136
+ moveNumber: this.snapshots.length,
137
+ });
138
+ }
139
+
140
+ // Jump to specific move
141
+ jumpToMove(moveNumber: number): FlowManager<GameState> {
142
+ const snapshot = this.snapshots[moveNumber];
143
+ if (!snapshot) throw new Error('Snapshot not found');
144
+
145
+ return new FlowManager(gameFlow, snapshot.gameState, {
146
+ restoreFrom: snapshot.flowState,
147
+ });
148
+ }
149
+
150
+ // Step forward/backward
151
+ stepForward(current: number) { return this.jumpToMove(current + 1); }
152
+ stepBackward(current: number) { return this.jumpToMove(current - 1); }
153
+ }
154
+ ```
155
+
156
+ ### Network Synchronization
157
+
158
+ ```typescript
159
+ // Server: Send snapshot to clients
160
+ socket.on('requestGameState', async (gameId) => {
161
+ const manager = activeGames.get(gameId);
162
+
163
+ socket.emit('gameState', {
164
+ game: manager.getGameState(),
165
+ flow: manager.serializeFlowState(),
166
+ });
167
+ });
168
+
169
+ // Client: Receive and restore
170
+ socket.on('gameState', (snapshot) => {
171
+ const manager = new FlowManager(gameFlow, snapshot.game, {
172
+ restoreFrom: snapshot.flow,
173
+ });
174
+
175
+ // Client now has exact game state
176
+ renderGame(manager);
177
+ });
178
+ ```
179
+
180
+ ### Spectator Mode
181
+
182
+ ```typescript
183
+ async function createSpectatorView(gameId: string): Promise<FlowManager<GameState>> {
184
+ const liveGame = await db.games.findById(gameId);
185
+
186
+ // Spectators get read-only copy at current state
187
+ return new FlowManager(gameFlow, liveGame.gameState, {
188
+ restoreFrom: liveGame.flowState,
189
+ });
190
+ }
191
+ ```
192
+
193
+ ### Auto-save Every N Turns
194
+
195
+ ```typescript
196
+ class AutoSaveManager {
197
+ private saveInterval = 5; // Save every 5 turns
198
+
199
+ async checkAndSave(manager: FlowManager<GameState>, gameId: string) {
200
+ const flowState = manager.serializeFlowState();
201
+
202
+ if (flowState.turnNumber % this.saveInterval === 0) {
203
+ await this.saveGame(gameId, manager);
204
+ }
205
+ }
206
+
207
+ private async saveGame(gameId: string, manager: FlowManager<GameState>) {
208
+ await db.games.update(gameId, {
209
+ gameState: manager.getGameState(),
210
+ flowState: manager.serializeFlowState(),
211
+ updatedAt: new Date(),
212
+ });
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Important Considerations
218
+
219
+ ### 1. Hooks Are Not Re-executed on Restore
220
+
221
+ When restoring from serialized state, lifecycle hooks (`onBegin`, `onEnd`) are **not** executed. This is intentional because:
222
+
223
+ - The hooks already executed in the original game session
224
+ - Their effects are already in the game state
225
+ - Re-executing would duplicate side effects
226
+
227
+ ```typescript
228
+ // Original game - hooks execute
229
+ const original = new FlowManager(flow, state);
230
+ // onBegin hooks run, modify state
231
+
232
+ // Save
233
+ const saved = {
234
+ game: original.getGameState(), // Contains hook effects
235
+ flow: original.serializeFlowState(),
236
+ };
237
+
238
+ // Restore - hooks DON'T re-execute
239
+ const restored = new FlowManager(flow, saved.game, {
240
+ restoreFrom: saved.flow, // Skips initialization
241
+ });
242
+ // State already contains all hook effects
243
+ ```
244
+
245
+ ### 2. Phase/Segment Position Preserved
246
+
247
+ The exact flow position is maintained:
248
+
249
+ ```typescript
250
+ // Original at main phase, damage segment
251
+ manager.getCurrentPhase(); // "main"
252
+ manager.getCurrentSegment(); // "damage"
253
+
254
+ // After save/restore
255
+ restored.getCurrentPhase(); // "main"
256
+ restored.getCurrentSegment(); // "damage"
257
+
258
+ // Can continue exactly where left off
259
+ restored.nextSegment();
260
+ ```
261
+
262
+ ### 3. All Game State Must Be Serializable
263
+
264
+ Ensure your game state only contains JSON-serializable data:
265
+
266
+ ✅ **Serializable**:
267
+ - Primitives (string, number, boolean)
268
+ - Plain objects
269
+ - Arrays
270
+ - null
271
+
272
+ ❌ **Not Serializable**:
273
+ - Functions
274
+ - Dates (convert to timestamp)
275
+ - Maps/Sets (convert to arrays)
276
+ - Class instances (use plain objects)
277
+
278
+ ```typescript
279
+ // ❌ Bad
280
+ type BadGameState = {
281
+ createdAt: Date; // Loses type on serialize
282
+ players: Map<string, Player>; // Not serializable
283
+ };
284
+
285
+ // ✅ Good
286
+ type GoodGameState = {
287
+ createdAt: number; // Unix timestamp
288
+ players: Record<string, Player>; // Plain object
289
+ };
290
+ ```
291
+
292
+ ### 4. Flow Definition Must Match
293
+
294
+ The same `FlowDefinition` must be used when restoring:
295
+
296
+ ```typescript
297
+ // ❌ Won't work correctly
298
+ const v1Flow = { /* old definition */ };
299
+ const v2Flow = { /* updated definition */ };
300
+
301
+ const manager = new FlowManager(v1Flow, state);
302
+ const saved = manager.serializeFlowState();
303
+
304
+ // Phase names might not exist in v2
305
+ const restored = new FlowManager(v2Flow, state, {
306
+ restoreFrom: saved, // Mismatch!
307
+ });
308
+
309
+ // ✅ Use matching definition
310
+ const restored = new FlowManager(v1Flow, state, {
311
+ restoreFrom: saved,
312
+ });
313
+ ```
314
+
315
+ ## Best Practices
316
+
317
+ ### 1. Version Your Saves
318
+
319
+ ```typescript
320
+ type VersionedSave = {
321
+ version: number;
322
+ gameState: GameState;
323
+ flowState: SerializedFlowState;
324
+ };
325
+
326
+ async function saveGameWithVersion(manager: FlowManager<GameState>) {
327
+ const save: VersionedSave = {
328
+ version: CURRENT_GAME_VERSION,
329
+ gameState: manager.getGameState(),
330
+ flowState: manager.serializeFlowState(),
331
+ };
332
+
333
+ await db.games.insert(save);
334
+ }
335
+ ```
336
+
337
+ ### 2. Validate Before Restore
338
+
339
+ ```typescript
340
+ function validateSavedGame(save: any): save is SavedGame {
341
+ return (
342
+ typeof save.gameState === 'object' &&
343
+ typeof save.flowState === 'object' &&
344
+ typeof save.flowState.turnNumber === 'number'
345
+ );
346
+ }
347
+
348
+ async function safeLoadGame(gameId: string) {
349
+ const saved = await db.games.findById(gameId);
350
+
351
+ if (!validateSavedGame(saved)) {
352
+ throw new Error('Invalid save data');
353
+ }
354
+
355
+ return new FlowManager(gameFlow, saved.gameState, {
356
+ restoreFrom: saved.flowState,
357
+ });
358
+ }
359
+ ```
360
+
361
+ ### 3. Compress Large States
362
+
363
+ ```typescript
364
+ import { compress, decompress } from 'lz-string';
365
+
366
+ async function saveCompressed(manager: FlowManager<GameState>) {
367
+ const data = {
368
+ game: manager.getGameState(),
369
+ flow: manager.serializeFlowState(),
370
+ };
371
+
372
+ const compressed = compress(JSON.stringify(data));
373
+ await db.games.insert({ id, data: compressed });
374
+ }
375
+
376
+ async function loadCompressed(gameId: string) {
377
+ const saved = await db.games.findById(gameId);
378
+ const data = JSON.parse(decompress(saved.data));
379
+
380
+ return new FlowManager(gameFlow, data.game, {
381
+ restoreFrom: data.flow,
382
+ });
383
+ }
384
+ ```
385
+
386
+ ## Testing Serialization
387
+
388
+ ```typescript
389
+ import { describe, it, expect } from 'bun:test';
390
+
391
+ describe('Game Serialization', () => {
392
+ it('should survive round-trip serialization', () => {
393
+ const original = new FlowManager(flow, initialState);
394
+ original.nextPhase();
395
+ original.nextPhase();
396
+
397
+ // Serialize
398
+ const serialized = JSON.stringify({
399
+ game: original.getGameState(),
400
+ flow: original.serializeFlowState(),
401
+ });
402
+
403
+ // Deserialize
404
+ const data = JSON.parse(serialized);
405
+ const restored = new FlowManager(flow, data.game, {
406
+ restoreFrom: data.flow,
407
+ });
408
+
409
+ // Verify exact match
410
+ expect(restored.getGameState()).toEqual(original.getGameState());
411
+ expect(restored.getCurrentPhase()).toBe(original.getCurrentPhase());
412
+ expect(restored.getCurrentSegment()).toBe(original.getCurrentSegment());
413
+ });
414
+ });
415
+ ```
416
+
417
+ ## Performance Considerations
418
+
419
+ - **Serialization**: O(n) where n is state size
420
+ - **Deserialization**: O(n) for parsing + O(1) for flow restoration
421
+ - **Memory**: Keep snapshots minimal (delta compression for large histories)
422
+ - **Database**: Index on `gameId`, `updatedAt` for fast lookups
423
+
424
+ ## See Also
425
+
426
+ - [Flow Definition Guide](./flow-definition.ts)
427
+ - [Flow Manager API](./flow-manager.ts)
428
+ - [Serialization Tests](./flow/__tests__/flow-serialization.test.ts)