@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,497 @@
1
+ import { beforeEach, describe, expect, it } from "bun:test";
2
+ import { createPlayerId } from "../types/branded-utils";
3
+ import { HistoryManager } from "./history-manager";
4
+ import type { HistoryEntry, VerbosityLevel } from "./types";
5
+
6
+ describe("HistoryManager", () => {
7
+ let manager: HistoryManager;
8
+ const PLAYER_ONE = createPlayerId("player_one");
9
+ const PLAYER_TWO = createPlayerId("player_two");
10
+
11
+ beforeEach(() => {
12
+ manager = new HistoryManager();
13
+ });
14
+
15
+ describe("Basic Operations", () => {
16
+ it("should start with empty history", () => {
17
+ expect(manager.getCount()).toBe(0);
18
+ expect(manager.getAllEntries()).toEqual([]);
19
+ });
20
+
21
+ it("should add entries with generated IDs", () => {
22
+ const entry = manager.addEntry({
23
+ moveId: "testMove",
24
+ playerId: PLAYER_ONE,
25
+ params: { test: true },
26
+ timestamp: Date.now(),
27
+ success: true,
28
+ messages: {
29
+ visibility: "PUBLIC",
30
+ messages: {
31
+ casual: { key: "test.message" },
32
+ },
33
+ },
34
+ });
35
+
36
+ expect(entry.id).toBeDefined();
37
+ expect(typeof entry.id).toBe("string");
38
+ expect(manager.getCount()).toBe(1);
39
+ });
40
+
41
+ it("should retrieve all entries", () => {
42
+ manager.addEntry({
43
+ moveId: "move1",
44
+ playerId: PLAYER_ONE,
45
+ params: {},
46
+ timestamp: Date.now(),
47
+ success: true,
48
+ messages: {
49
+ visibility: "PUBLIC",
50
+ messages: { casual: { key: "msg1" } },
51
+ },
52
+ });
53
+
54
+ manager.addEntry({
55
+ moveId: "move2",
56
+ playerId: PLAYER_TWO,
57
+ params: {},
58
+ timestamp: Date.now(),
59
+ success: true,
60
+ messages: {
61
+ visibility: "PUBLIC",
62
+ messages: { casual: { key: "msg2" } },
63
+ },
64
+ });
65
+
66
+ const entries = manager.getAllEntries();
67
+ expect(entries.length).toBe(2);
68
+ expect(entries[0]?.moveId).toBe("move1");
69
+ expect(entries[1]?.moveId).toBe("move2");
70
+ });
71
+
72
+ it("should clear all entries", () => {
73
+ manager.addEntry({
74
+ moveId: "move1",
75
+ playerId: PLAYER_ONE,
76
+ params: {},
77
+ timestamp: Date.now(),
78
+ success: true,
79
+ messages: {
80
+ visibility: "PUBLIC",
81
+ messages: { casual: { key: "msg1" } },
82
+ },
83
+ });
84
+
85
+ expect(manager.getCount()).toBe(1);
86
+ manager.clear();
87
+ expect(manager.getCount()).toBe(0);
88
+ });
89
+ });
90
+
91
+ describe("Public Visibility", () => {
92
+ it("should show public entries to all players", () => {
93
+ const timestamp = Date.now();
94
+ manager.addEntry({
95
+ moveId: "publicMove",
96
+ playerId: PLAYER_ONE,
97
+ params: {},
98
+ timestamp,
99
+ success: true,
100
+ messages: {
101
+ visibility: "PUBLIC",
102
+ messages: {
103
+ casual: { key: "public.message", values: { player: "One" } },
104
+ },
105
+ },
106
+ });
107
+
108
+ const p1History = manager.query({ playerId: PLAYER_ONE });
109
+ const p2History = manager.query({ playerId: PLAYER_TWO });
110
+ const allHistory = manager.query();
111
+
112
+ expect(p1History.length).toBe(1);
113
+ expect(p2History.length).toBe(1);
114
+ expect(allHistory.length).toBe(1);
115
+
116
+ expect(p1History[0]?.message.key).toBe("public.message");
117
+ expect(p2History[0]?.message.key).toBe("public.message");
118
+ });
119
+ });
120
+
121
+ describe("Private Visibility", () => {
122
+ it("should show private entries only to specified player", () => {
123
+ manager.addEntry({
124
+ moveId: "privateMove",
125
+ playerId: PLAYER_ONE,
126
+ params: {},
127
+ timestamp: Date.now(),
128
+ success: true,
129
+ messages: {
130
+ visibility: "PRIVATE",
131
+ visibleTo: [PLAYER_ONE],
132
+ messages: {
133
+ casual: { key: "private.message" },
134
+ },
135
+ },
136
+ });
137
+
138
+ const p1History = manager.query({ playerId: PLAYER_ONE });
139
+ const p2History = manager.query({ playerId: PLAYER_TWO });
140
+
141
+ expect(p1History.length).toBe(1);
142
+ expect(p2History.length).toBe(0);
143
+ });
144
+
145
+ it("should show private entries to multiple specified players", () => {
146
+ manager.addEntry({
147
+ moveId: "privateMove",
148
+ playerId: PLAYER_ONE,
149
+ params: {},
150
+ timestamp: Date.now(),
151
+ success: true,
152
+ messages: {
153
+ visibility: "PRIVATE",
154
+ visibleTo: [PLAYER_ONE, PLAYER_TWO],
155
+ messages: {
156
+ casual: { key: "private.message" },
157
+ },
158
+ },
159
+ });
160
+
161
+ const p1History = manager.query({ playerId: PLAYER_ONE });
162
+ const p2History = manager.query({ playerId: PLAYER_TWO });
163
+
164
+ expect(p1History.length).toBe(1);
165
+ expect(p2History.length).toBe(1);
166
+ });
167
+ });
168
+
169
+ describe("Player-Specific Messages", () => {
170
+ it("should show different messages to different players (mulligan example)", () => {
171
+ const timestamp = Date.now();
172
+ const cardsDrawn = ["Knight", "Wizard", "Dragon"];
173
+
174
+ manager.addEntry({
175
+ moveId: "mulligan",
176
+ playerId: PLAYER_ONE,
177
+ params: { cardIds: ["card1", "card2", "card3"] },
178
+ timestamp,
179
+ turn: 1,
180
+ phase: "mulligan",
181
+ success: true,
182
+ messages: {
183
+ visibility: "PLAYER_SPECIFIC",
184
+ messages: {
185
+ [PLAYER_ONE]: {
186
+ casual: {
187
+ key: "mulligan.self",
188
+ values: { cards: cardsDrawn, count: 3 },
189
+ },
190
+ advanced: {
191
+ key: "mulligan.self.detailed",
192
+ values: {
193
+ cardIds: ["card1", "card2", "card3"],
194
+ cards: cardsDrawn,
195
+ },
196
+ },
197
+ },
198
+ [PLAYER_TWO]: {
199
+ casual: {
200
+ key: "mulligan.opponent",
201
+ values: { count: 3 },
202
+ },
203
+ advanced: {
204
+ key: "mulligan.opponent.detailed",
205
+ values: { count: 3, playerId: PLAYER_ONE },
206
+ },
207
+ },
208
+ },
209
+ },
210
+ });
211
+
212
+ // Player one sees their cards
213
+ const p1History = manager.query({
214
+ playerId: PLAYER_ONE,
215
+ verbosity: "CASUAL",
216
+ });
217
+ expect(p1History.length).toBe(1);
218
+ expect(p1History[0]?.message.key).toBe("mulligan.self");
219
+ expect(p1History[0]?.message.values?.cards).toEqual(cardsDrawn);
220
+
221
+ // Player two only sees count
222
+ const p2History = manager.query({
223
+ playerId: PLAYER_TWO,
224
+ verbosity: "CASUAL",
225
+ });
226
+ expect(p2History.length).toBe(1);
227
+ expect(p2History[0]?.message.key).toBe("mulligan.opponent");
228
+ expect(p2History[0]?.message.values?.count).toBe(3);
229
+ expect(p2History[0]?.message.values?.cards).toBeUndefined();
230
+ });
231
+ });
232
+
233
+ describe("Verbosity Levels", () => {
234
+ beforeEach(() => {
235
+ manager.addEntry({
236
+ moveId: "drawCards",
237
+ playerId: PLAYER_ONE,
238
+ params: { count: 5 },
239
+ timestamp: Date.now(),
240
+ success: true,
241
+ messages: {
242
+ visibility: "PUBLIC",
243
+ messages: {
244
+ casual: {
245
+ key: "draw.casual",
246
+ values: { player: "One", count: 5 },
247
+ },
248
+ advanced: {
249
+ key: "draw.advanced",
250
+ values: {
251
+ player: "One",
252
+ count: 5,
253
+ cardIds: ["c1", "c2", "c3", "c4", "c5"],
254
+ },
255
+ },
256
+ developer: {
257
+ key: "draw.developer",
258
+ values: {
259
+ playerId: PLAYER_ONE,
260
+ params: { count: 5 },
261
+ fullContext: {},
262
+ },
263
+ },
264
+ },
265
+ },
266
+ });
267
+ });
268
+
269
+ it("should return CASUAL message at CASUAL verbosity", () => {
270
+ const history = manager.query({ verbosity: "CASUAL" });
271
+ expect(history[0]?.message.key).toBe("draw.casual");
272
+ });
273
+
274
+ it("should return ADVANCED message at ADVANCED verbosity", () => {
275
+ const history = manager.query({ verbosity: "ADVANCED" });
276
+ expect(history[0]?.message.key).toBe("draw.advanced");
277
+ });
278
+
279
+ it("should return DEVELOPER message at DEVELOPER verbosity", () => {
280
+ const history = manager.query({ verbosity: "DEVELOPER" });
281
+ expect(history[0]?.message.key).toBe("draw.developer");
282
+ });
283
+
284
+ it("should fallback to CASUAL if ADVANCED not available", () => {
285
+ manager.clear();
286
+ manager.addEntry({
287
+ moveId: "simpleMove",
288
+ playerId: PLAYER_ONE,
289
+ params: {},
290
+ timestamp: Date.now(),
291
+ success: true,
292
+ messages: {
293
+ visibility: "PUBLIC",
294
+ messages: {
295
+ casual: { key: "simple.casual" },
296
+ // No advanced or developer
297
+ },
298
+ },
299
+ });
300
+
301
+ const advancedHistory = manager.query({ verbosity: "ADVANCED" });
302
+ const developerHistory = manager.query({ verbosity: "DEVELOPER" });
303
+
304
+ expect(advancedHistory[0]?.message.key).toBe("simple.casual");
305
+ expect(developerHistory[0]?.message.key).toBe("simple.casual");
306
+ });
307
+
308
+ it("should fallback through verbosity chain", () => {
309
+ manager.clear();
310
+ manager.addEntry({
311
+ moveId: "partialMove",
312
+ playerId: PLAYER_ONE,
313
+ params: {},
314
+ timestamp: Date.now(),
315
+ success: true,
316
+ messages: {
317
+ visibility: "PUBLIC",
318
+ messages: {
319
+ casual: { key: "partial.casual" },
320
+ developer: { key: "partial.developer" },
321
+ // No advanced
322
+ },
323
+ },
324
+ });
325
+
326
+ const advancedHistory = manager.query({ verbosity: "ADVANCED" });
327
+ const developerHistory = manager.query({ verbosity: "DEVELOPER" });
328
+
329
+ expect(advancedHistory[0]?.message.key).toBe("partial.casual");
330
+ expect(developerHistory[0]?.message.key).toBe("partial.developer");
331
+ });
332
+ });
333
+
334
+ describe("Query Filtering", () => {
335
+ beforeEach(() => {
336
+ const baseTime = Date.now();
337
+
338
+ manager.addEntry({
339
+ moveId: "move1",
340
+ playerId: PLAYER_ONE,
341
+ params: {},
342
+ timestamp: baseTime,
343
+ success: true,
344
+ messages: {
345
+ visibility: "PUBLIC",
346
+ messages: { casual: { key: "msg1" } },
347
+ },
348
+ });
349
+
350
+ manager.addEntry({
351
+ moveId: "move2",
352
+ playerId: PLAYER_TWO,
353
+ params: {},
354
+ timestamp: baseTime + 1000,
355
+ success: false,
356
+ error: { code: "ERROR", message: "Failed" },
357
+ messages: {
358
+ visibility: "PUBLIC",
359
+ messages: { casual: { key: "msg2" } },
360
+ },
361
+ });
362
+
363
+ manager.addEntry({
364
+ moveId: "move3",
365
+ playerId: PLAYER_ONE,
366
+ params: {},
367
+ timestamp: baseTime + 2000,
368
+ success: true,
369
+ messages: {
370
+ visibility: "PUBLIC",
371
+ messages: { casual: { key: "msg3" } },
372
+ },
373
+ });
374
+ });
375
+
376
+ it("should filter by timestamp", () => {
377
+ const baseTime = Date.now();
378
+ const history = manager.query({ since: baseTime + 1500 });
379
+
380
+ expect(history.length).toBe(1);
381
+ expect(history[0]?.moveId).toBe("move3");
382
+ });
383
+
384
+ it("should filter by move ID", () => {
385
+ const history = manager.query({ moveId: "move2" });
386
+
387
+ expect(history.length).toBe(1);
388
+ expect(history[0]?.moveId).toBe("move2");
389
+ });
390
+
391
+ it("should filter by success", () => {
392
+ const successHistory = manager.query({ includeFailures: false });
393
+ const failureHistory = manager.query({ includeSuccess: false });
394
+
395
+ expect(successHistory.length).toBe(2);
396
+ expect(failureHistory.length).toBe(1);
397
+ expect(failureHistory[0]?.success).toBe(false);
398
+ });
399
+ });
400
+
401
+ describe("Failed Moves", () => {
402
+ it("should include error information in failed move entries", () => {
403
+ manager.addEntry({
404
+ moveId: "failedMove",
405
+ playerId: PLAYER_ONE,
406
+ params: {},
407
+ timestamp: Date.now(),
408
+ success: false,
409
+ error: {
410
+ code: "INVALID_TARGET",
411
+ message: "Target no longer exists",
412
+ context: { targetId: "card-123" },
413
+ },
414
+ messages: {
415
+ visibility: "PUBLIC",
416
+ messages: {
417
+ casual: {
418
+ key: "move.failed",
419
+ values: { reason: "Invalid target" },
420
+ },
421
+ advanced: {
422
+ key: "move.failed.technical",
423
+ values: {
424
+ errorCode: "INVALID_TARGET",
425
+ targetId: "card-123",
426
+ },
427
+ },
428
+ },
429
+ },
430
+ });
431
+
432
+ const casualHistory = manager.query({ verbosity: "CASUAL" });
433
+ const advancedHistory = manager.query({ verbosity: "ADVANCED" });
434
+
435
+ expect(casualHistory[0]?.success).toBe(false);
436
+ expect(casualHistory[0]?.error?.code).toBe("INVALID_TARGET");
437
+ expect(casualHistory[0]?.message.key).toBe("move.failed");
438
+
439
+ expect(advancedHistory[0]?.message.key).toBe("move.failed.technical");
440
+ });
441
+ });
442
+
443
+ describe("Metadata", () => {
444
+ it("should include metadata in DEVELOPER mode only", () => {
445
+ manager.addEntry({
446
+ moveId: "metadataMove",
447
+ playerId: PLAYER_ONE,
448
+ params: { test: true },
449
+ timestamp: Date.now(),
450
+ success: true,
451
+ metadata: { debug: "info", internal: "data" },
452
+ messages: {
453
+ visibility: "PUBLIC",
454
+ messages: {
455
+ casual: { key: "move.msg" },
456
+ developer: { key: "move.dev" },
457
+ },
458
+ },
459
+ });
460
+
461
+ const casualHistory = manager.query({ verbosity: "CASUAL" });
462
+ const developerHistory = manager.query({ verbosity: "DEVELOPER" });
463
+
464
+ expect(casualHistory[0]?.metadata).toBeUndefined();
465
+ expect(casualHistory[0]?.params).toBeUndefined();
466
+
467
+ expect(developerHistory[0]?.metadata).toBeDefined();
468
+ expect(developerHistory[0]?.metadata?.debug).toBe("info");
469
+ expect(developerHistory[0]?.params).toBeDefined();
470
+ });
471
+ });
472
+
473
+ describe("Turn and Phase Information", () => {
474
+ it("should include turn and phase information", () => {
475
+ manager.addEntry({
476
+ moveId: "phaseMove",
477
+ playerId: PLAYER_ONE,
478
+ params: {},
479
+ timestamp: Date.now(),
480
+ turn: 3,
481
+ phase: "main",
482
+ segment: "step1",
483
+ success: true,
484
+ messages: {
485
+ visibility: "PUBLIC",
486
+ messages: { casual: { key: "move.msg" } },
487
+ },
488
+ });
489
+
490
+ const history = manager.query();
491
+
492
+ expect(history[0]?.turn).toBe(3);
493
+ expect(history[0]?.phase).toBe("main");
494
+ expect(history[0]?.segment).toBe("step1");
495
+ });
496
+ });
497
+ });