@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,312 @@
1
+ /**
2
+ * History Manager
3
+ *
4
+ * Manages game move history with Immer-based immutable store.
5
+ * Tracks all move executions chronologically with player-aware visibility.
6
+ */
7
+
8
+ import { nanoid } from "nanoid";
9
+ import type { PlayerId } from "../types/branded";
10
+ import type {
11
+ FormattedHistoryEntry,
12
+ HistoryEntry,
13
+ HistoryQueryOptions,
14
+ MessageTemplateData,
15
+ VerbosityLevel,
16
+ VerbosityMessages,
17
+ } from "./types";
18
+
19
+ /**
20
+ * History Manager Options
21
+ *
22
+ * Configuration for HistoryManager initialization.
23
+ */
24
+ export type HistoryManagerOptions = {
25
+ /** Initial entries (for replay/restore) */
26
+ initialEntries?: HistoryEntry[];
27
+ };
28
+
29
+ /**
30
+ * History Manager
31
+ *
32
+ * In-memory store for game move history.
33
+ * Uses simple array storage (Immer patches handle immutability at engine level).
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const manager = new HistoryManager();
38
+ *
39
+ * // Add entry
40
+ * manager.addEntry({
41
+ * moveId: 'playCard',
42
+ * playerId: 'p1',
43
+ * params: { cardId: 'card-123' },
44
+ * timestamp: Date.now(),
45
+ * success: true,
46
+ * messages: {
47
+ * visibility: 'PUBLIC',
48
+ * messages: {
49
+ * casual: { key: 'moves.playCard', values: { card: 'Knight' } }
50
+ * }
51
+ * }
52
+ * });
53
+ *
54
+ * // Query history
55
+ * const entries = manager.query({ playerId: 'p1', verbosity: 'CASUAL' });
56
+ * ```
57
+ */
58
+ export class HistoryManager {
59
+ private entries: HistoryEntry[];
60
+
61
+ /**
62
+ * Create a new HistoryManager
63
+ *
64
+ * @param options - Configuration options
65
+ */
66
+ constructor(options: HistoryManagerOptions = {}) {
67
+ this.entries = options.initialEntries ?? [];
68
+ }
69
+
70
+ /**
71
+ * Add a new history entry
72
+ *
73
+ * @param entry - Entry data (without id - will be generated)
74
+ * @returns The created entry with generated id
75
+ */
76
+ addEntry(entry: Omit<HistoryEntry, "id">): HistoryEntry {
77
+ const fullEntry: HistoryEntry = {
78
+ id: nanoid(),
79
+ ...entry,
80
+ };
81
+
82
+ this.entries.push(fullEntry);
83
+ return fullEntry;
84
+ }
85
+
86
+ /**
87
+ * Query history entries with filtering and formatting
88
+ *
89
+ * @param options - Query options
90
+ * @returns Formatted entries matching the query
91
+ */
92
+ query(options: HistoryQueryOptions = {}): FormattedHistoryEntry[] {
93
+ const {
94
+ playerId,
95
+ verbosity = "CASUAL",
96
+ since,
97
+ moveId,
98
+ includeSuccess = true,
99
+ includeFailures = true,
100
+ } = options;
101
+
102
+ // Filter entries
103
+ let filtered = this.entries;
104
+
105
+ // Filter by timestamp
106
+ if (since !== undefined) {
107
+ filtered = filtered.filter((entry) => entry.timestamp > since);
108
+ }
109
+
110
+ // Filter by move ID
111
+ if (moveId !== undefined) {
112
+ filtered = filtered.filter((entry) => entry.moveId === moveId);
113
+ }
114
+
115
+ // Filter by success/failure
116
+ if (!includeSuccess) {
117
+ filtered = filtered.filter((entry) => !entry.success);
118
+ }
119
+ if (!includeFailures) {
120
+ filtered = filtered.filter((entry) => entry.success);
121
+ }
122
+
123
+ // Filter by player visibility and format
124
+ return filtered
125
+ .map((entry) => this.formatEntry(entry, playerId, verbosity))
126
+ .filter((entry): entry is FormattedHistoryEntry => entry !== null);
127
+ }
128
+
129
+ /**
130
+ * Get all entries (raw, no filtering)
131
+ *
132
+ * Used for debugging and serialization.
133
+ *
134
+ * @returns All history entries
135
+ */
136
+ getAllEntries(): readonly HistoryEntry[] {
137
+ return this.entries;
138
+ }
139
+
140
+ /**
141
+ * Clear all history entries
142
+ *
143
+ * Used for testing.
144
+ */
145
+ clear(): void {
146
+ this.entries = [];
147
+ }
148
+
149
+ /**
150
+ * Get the number of entries
151
+ *
152
+ * @returns Entry count
153
+ */
154
+ getCount(): number {
155
+ return this.entries.length;
156
+ }
157
+
158
+ /**
159
+ * Check if player can see this entry
160
+ *
161
+ * @param entry - History entry
162
+ * @param playerId - Player requesting access (undefined = see all)
163
+ * @returns True if player can see this entry
164
+ */
165
+ private canPlayerSeeEntry(
166
+ entry: HistoryEntry,
167
+ playerId: PlayerId | undefined,
168
+ ): boolean {
169
+ // If no player filter, show all entries
170
+ if (playerId === undefined) {
171
+ return true;
172
+ }
173
+
174
+ const { messages } = entry;
175
+
176
+ // PUBLIC entries are visible to all
177
+ if (messages.visibility === "PUBLIC") {
178
+ return true;
179
+ }
180
+
181
+ // PRIVATE entries are visible only to specified players
182
+ if (messages.visibility === "PRIVATE") {
183
+ return (
184
+ messages.visibleTo === undefined ||
185
+ messages.visibleTo.some((id) => String(id) === String(playerId))
186
+ );
187
+ }
188
+
189
+ // PLAYER_SPECIFIC entries are visible if player has a message
190
+ if (messages.visibility === "PLAYER_SPECIFIC") {
191
+ return String(playerId) in messages.messages;
192
+ }
193
+
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * Format an entry for display
199
+ *
200
+ * @param entry - Raw history entry
201
+ * @param playerId - Player requesting the entry (for filtering)
202
+ * @param verbosity - Verbosity level for message selection
203
+ * @returns Formatted entry or null if player can't see it
204
+ */
205
+ private formatEntry(
206
+ entry: HistoryEntry,
207
+ playerId: PlayerId | undefined,
208
+ verbosity: VerbosityLevel,
209
+ ): FormattedHistoryEntry | null {
210
+ // Check visibility
211
+ if (!this.canPlayerSeeEntry(entry, playerId)) {
212
+ return null;
213
+ }
214
+
215
+ // Get message for this player and verbosity
216
+ const message = this.getMessageForPlayer(entry, playerId, verbosity);
217
+ if (!message) {
218
+ return null;
219
+ }
220
+
221
+ // Build formatted entry
222
+ const formatted: FormattedHistoryEntry = {
223
+ id: entry.id,
224
+ moveId: entry.moveId,
225
+ playerId: entry.playerId,
226
+ timestamp: entry.timestamp,
227
+ turn: entry.turn,
228
+ phase: entry.phase,
229
+ segment: entry.segment,
230
+ message,
231
+ success: entry.success,
232
+ error: entry.error,
233
+ };
234
+
235
+ // Include additional details for DEVELOPER mode
236
+ if (verbosity === "DEVELOPER") {
237
+ formatted.metadata = entry.metadata;
238
+ formatted.params = entry.params;
239
+ }
240
+
241
+ return formatted;
242
+ }
243
+
244
+ /**
245
+ * Get the appropriate message for a player and verbosity level
246
+ *
247
+ * @param entry - History entry
248
+ * @param playerId - Player requesting the message
249
+ * @param verbosity - Verbosity level
250
+ * @returns Message template data or null if no message available
251
+ */
252
+ private getMessageForPlayer(
253
+ entry: HistoryEntry,
254
+ playerId: PlayerId | undefined,
255
+ verbosity: VerbosityLevel,
256
+ ): MessageTemplateData | null {
257
+ const { messages } = entry;
258
+
259
+ // Get verbosity-specific messages
260
+ let verbosityMessages: VerbosityMessages | undefined;
261
+
262
+ if (messages.visibility === "PUBLIC" || messages.visibility === "PRIVATE") {
263
+ verbosityMessages = messages.messages;
264
+ } else if (messages.visibility === "PLAYER_SPECIFIC") {
265
+ // Get player-specific messages
266
+ if (playerId !== undefined) {
267
+ verbosityMessages = messages.messages[String(playerId)];
268
+ } else {
269
+ // If no player specified, use first available
270
+ const firstPlayerId = Object.keys(messages.messages)[0];
271
+ if (firstPlayerId) {
272
+ verbosityMessages = messages.messages[firstPlayerId];
273
+ }
274
+ }
275
+ }
276
+
277
+ if (!verbosityMessages) {
278
+ return null;
279
+ }
280
+
281
+ // Select message based on verbosity level with fallback
282
+ return this.selectMessageByVerbosity(verbosityMessages, verbosity);
283
+ }
284
+
285
+ /**
286
+ * Select message by verbosity level with fallback
287
+ *
288
+ * Falls back to less detailed level if requested level not available:
289
+ * DEVELOPER -> ADVANCED -> CASUAL
290
+ *
291
+ * @param messages - Verbosity-specific messages
292
+ * @param verbosity - Requested verbosity level
293
+ * @returns Message template data or null if no messages available
294
+ */
295
+ private selectMessageByVerbosity(
296
+ messages: VerbosityMessages,
297
+ verbosity: VerbosityLevel,
298
+ ): MessageTemplateData | null {
299
+ switch (verbosity) {
300
+ case "DEVELOPER":
301
+ return (
302
+ messages.developer ?? messages.advanced ?? messages.casual ?? null
303
+ );
304
+ case "ADVANCED":
305
+ return messages.advanced ?? messages.casual ?? null;
306
+ case "CASUAL":
307
+ return messages.casual ?? null;
308
+ default:
309
+ return messages.casual ?? null;
310
+ }
311
+ }
312
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * History Operations
3
+ *
4
+ * Operations API exposed to move reducers via MoveContext.
5
+ * Allows moves to log custom history entries with player-specific visibility.
6
+ */
7
+
8
+ import type { PlayerId } from "../types/branded";
9
+ import type { HistoryManager } from "./history-manager";
10
+ import type { HistoryMessages } from "./types";
11
+
12
+ /**
13
+ * History Operations
14
+ *
15
+ * API for logging history entries from within move reducers.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // In a move reducer
20
+ * reducer: (draft, context) => {
21
+ * // Log a public message
22
+ * context.history.log({
23
+ * messages: {
24
+ * visibility: 'PUBLIC',
25
+ * messages: {
26
+ * casual: { key: 'moves.draw', values: { count: 5 } },
27
+ * advanced: { key: 'moves.draw.detailed', values: { cardIds: [...] } }
28
+ * }
29
+ * }
30
+ * });
31
+ *
32
+ * // Log a player-specific message (mulligan)
33
+ * context.history.log({
34
+ * messages: {
35
+ * visibility: 'PLAYER_SPECIFIC',
36
+ * messages: {
37
+ * [playerId]: {
38
+ * casual: { key: 'mulligan.self', values: { cards: [...] } }
39
+ * },
40
+ * [opponentId]: {
41
+ * casual: { key: 'mulligan.opponent', values: { count: 3 } }
42
+ * }
43
+ * }
44
+ * }
45
+ * });
46
+ * }
47
+ * ```
48
+ */
49
+ export type HistoryOperations = {
50
+ /**
51
+ * Log a custom history entry
52
+ *
53
+ * Creates a history entry with custom messages.
54
+ * Used by move reducers to add detailed logging beyond the automatic entry.
55
+ *
56
+ * Note: The engine automatically creates a base history entry for each move.
57
+ * Use this method to add additional context or player-specific details.
58
+ *
59
+ * @param input - Message templates and metadata
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // Log card draw with player-specific visibility
64
+ * context.history.log({
65
+ * messages: {
66
+ * visibility: 'PLAYER_SPECIFIC',
67
+ * messages: {
68
+ * [playerId]: {
69
+ * casual: { key: 'draw.self', values: { cards: ['Knight', 'Wizard'] } }
70
+ * },
71
+ * [opponentId]: {
72
+ * casual: { key: 'draw.opponent', values: { count: 2 } }
73
+ * }
74
+ * }
75
+ * }
76
+ * });
77
+ * ```
78
+ */
79
+ log(input: {
80
+ messages: HistoryMessages;
81
+ metadata?: Record<string, unknown>;
82
+ }): void;
83
+ };
84
+
85
+ /**
86
+ * Create History Operations
87
+ *
88
+ * Factory for creating HistoryOperations bound to a specific context.
89
+ *
90
+ * @param manager - History manager instance
91
+ * @param context - Move execution context
92
+ * @returns History operations API
93
+ */
94
+ export function createHistoryOperations(
95
+ manager: HistoryManager,
96
+ context: {
97
+ moveId: string;
98
+ playerId: string;
99
+ params: unknown;
100
+ timestamp: number;
101
+ turn?: number;
102
+ phase?: string;
103
+ segment?: string;
104
+ },
105
+ ): HistoryOperations {
106
+ return {
107
+ log(input) {
108
+ manager.addEntry({
109
+ moveId: context.moveId,
110
+ playerId: context.playerId as PlayerId,
111
+ params: context.params,
112
+ timestamp: context.timestamp,
113
+ turn: context.turn,
114
+ phase: context.phase,
115
+ segment: context.segment,
116
+ success: true,
117
+ messages: input.messages,
118
+ metadata: input.metadata,
119
+ });
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * History Module
3
+ *
4
+ * Game move history system with player-aware visibility and i18next localization.
5
+ */
6
+
7
+ export * from "./history-manager";
8
+ export * from "./history-operations";
9
+ export * from "./types";
@@ -0,0 +1,255 @@
1
+ /**
2
+ * History Types
3
+ *
4
+ * Type definitions for the game move history system.
5
+ * Provides structured move logging with player-aware visibility and i18next localization.
6
+ */
7
+
8
+ import type { PlayerId } from "../types/branded";
9
+
10
+ /**
11
+ * Verbosity Level
12
+ *
13
+ * Controls the level of detail in history messages:
14
+ * - CASUAL: Simple, narrative descriptions for casual players
15
+ * - ADVANCED: Technical details for competitive players
16
+ * - DEVELOPER: Full internal details for debugging
17
+ */
18
+ export type VerbosityLevel = "CASUAL" | "ADVANCED" | "DEVELOPER";
19
+
20
+ /**
21
+ * Visibility Level
22
+ *
23
+ * Controls who can see a history entry:
24
+ * - PUBLIC: Visible to all players
25
+ * - PRIVATE: Visible only to specific player(s)
26
+ * - PLAYER_SPECIFIC: Different details shown to different players
27
+ */
28
+ export type VisibilityLevel = "PUBLIC" | "PRIVATE" | "PLAYER_SPECIFIC";
29
+
30
+ /**
31
+ * Message Template Data
32
+ *
33
+ * i18next-compatible message template with interpolation values.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Template: "{{player}} drew {{count}} cards"
38
+ * {
39
+ * key: "moves.draw.success",
40
+ * values: { player: "Player 1", count: 5 }
41
+ * }
42
+ * ```
43
+ */
44
+ export type MessageTemplateData = {
45
+ /** i18next translation key */
46
+ key: string;
47
+
48
+ /** Interpolation values for the template */
49
+ values?: Record<string, unknown>;
50
+ };
51
+
52
+ /**
53
+ * Verbosity-Specific Messages
54
+ *
55
+ * Different message templates for different verbosity levels.
56
+ * All levels are optional; if not provided, falls back to less detailed level.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * {
61
+ * casual: { key: "moves.mulligan.casual", values: { count: 3 } },
62
+ * advanced: { key: "moves.mulligan.advanced", values: { cardIds: [...] } },
63
+ * developer: { key: "moves.mulligan.dev", values: { fullContext: {...} } }
64
+ * }
65
+ * ```
66
+ */
67
+ export type VerbosityMessages = {
68
+ /** Message for casual players (simple, narrative) */
69
+ casual?: MessageTemplateData;
70
+
71
+ /** Message for advanced players (technical details) */
72
+ advanced?: MessageTemplateData;
73
+
74
+ /** Message for developers (full internal details) */
75
+ developer?: MessageTemplateData;
76
+ };
77
+
78
+ /**
79
+ * Player-Specific Message Data
80
+ *
81
+ * Different message templates shown to different players.
82
+ * Used for moves with private information (e.g., opponent mulligans).
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * // Mulligan: owning player sees cards, opponent sees count only
87
+ * {
88
+ * player_one: {
89
+ * casual: { key: "mulligan.self", values: { cards: ["card1", "card2"] } }
90
+ * },
91
+ * player_two: {
92
+ * casual: { key: "mulligan.opponent", values: { count: 2 } }
93
+ * }
94
+ * }
95
+ * ```
96
+ */
97
+ export type PlayerSpecificMessages = Record<string, VerbosityMessages>;
98
+
99
+ /**
100
+ * History Entry Messages
101
+ *
102
+ * Message templates for a history entry.
103
+ * Can be public (same for all) or player-specific (different per player).
104
+ */
105
+ export type HistoryMessages =
106
+ | {
107
+ visibility: "PUBLIC" | "PRIVATE";
108
+ /** Messages at different verbosity levels */
109
+ messages: VerbosityMessages;
110
+ /** If PRIVATE, which player(s) can see this entry */
111
+ visibleTo?: PlayerId[];
112
+ }
113
+ | {
114
+ visibility: "PLAYER_SPECIFIC";
115
+ /** Different messages for different players */
116
+ messages: PlayerSpecificMessages;
117
+ };
118
+
119
+ /**
120
+ * History Entry
121
+ *
122
+ * A single entry in the game move history.
123
+ * Contains all information about a move execution including messages,
124
+ * visibility, and raw data for debugging.
125
+ */
126
+ export type HistoryEntry = {
127
+ /** Unique identifier for this entry */
128
+ id: string;
129
+
130
+ /** Move ID that was executed */
131
+ moveId: string;
132
+
133
+ /** Player who executed the move */
134
+ playerId: PlayerId;
135
+
136
+ /** Move-specific parameters */
137
+ params: unknown;
138
+
139
+ /** Timestamp when move was executed (milliseconds) */
140
+ timestamp: number;
141
+
142
+ /** Turn number when move was executed */
143
+ turn?: number;
144
+
145
+ /** Game phase when move was executed */
146
+ phase?: string;
147
+
148
+ /** Game segment when move was executed */
149
+ segment?: string;
150
+
151
+ /** Message templates for this entry */
152
+ messages: HistoryMessages;
153
+
154
+ /** Whether the move was successful */
155
+ success: boolean;
156
+
157
+ /** Error information if move failed */
158
+ error?: {
159
+ /** Error code */
160
+ code: string;
161
+ /** Error message */
162
+ message: string;
163
+ /** Additional error context */
164
+ context?: Record<string, unknown>;
165
+ };
166
+
167
+ /** Additional metadata for debugging */
168
+ metadata?: Record<string, unknown>;
169
+ };
170
+
171
+ /**
172
+ * Formatted History Entry
173
+ *
174
+ * A history entry with formatted message ready for display.
175
+ * Returned by getHistory() after filtering and formatting.
176
+ */
177
+ export type FormattedHistoryEntry = {
178
+ /** Unique identifier for this entry */
179
+ id: string;
180
+
181
+ /** Move ID that was executed */
182
+ moveId: string;
183
+
184
+ /** Player who executed the move */
185
+ playerId: PlayerId;
186
+
187
+ /** Timestamp when move was executed (milliseconds) */
188
+ timestamp: number;
189
+
190
+ /** Turn number when move was executed */
191
+ turn?: number;
192
+
193
+ /** Game phase when move was executed */
194
+ phase?: string;
195
+
196
+ /** Game segment when move was executed */
197
+ segment?: string;
198
+
199
+ /** Formatted message ready for display */
200
+ message: MessageTemplateData;
201
+
202
+ /** Whether the move was successful */
203
+ success: boolean;
204
+
205
+ /** Error information if move failed */
206
+ error?: {
207
+ code: string;
208
+ message: string;
209
+ context?: Record<string, unknown>;
210
+ };
211
+
212
+ /** Additional metadata (only included in DEVELOPER mode) */
213
+ metadata?: Record<string, unknown>;
214
+
215
+ /** Raw parameters (only included in DEVELOPER mode) */
216
+ params?: unknown;
217
+ };
218
+
219
+ /**
220
+ * History Query Options
221
+ *
222
+ * Options for querying game history.
223
+ */
224
+ export type HistoryQueryOptions = {
225
+ /** Filter to entries visible to this player (undefined = all entries) */
226
+ playerId?: PlayerId;
227
+
228
+ /** Verbosity level for message formatting */
229
+ verbosity?: VerbosityLevel;
230
+
231
+ /** Only return entries after this timestamp */
232
+ since?: number;
233
+
234
+ /** Only return entries for this move ID */
235
+ moveId?: string;
236
+
237
+ /** Include successful moves (default: true) */
238
+ includeSuccess?: boolean;
239
+
240
+ /** Include failed moves (default: true) */
241
+ includeFailures?: boolean;
242
+ };
243
+
244
+ /**
245
+ * Log Entry Input
246
+ *
247
+ * Input data for creating a history entry via context.history.log()
248
+ */
249
+ export type LogEntryInput = {
250
+ /** Message templates for this entry */
251
+ messages: HistoryMessages;
252
+
253
+ /** Additional metadata for debugging */
254
+ metadata?: Record<string, unknown>;
255
+ };