@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,286 @@
1
+ import type { CardDefinition } from "../cards/card-definition";
2
+ import type { CardInstance } from "../cards/card-instance";
3
+ import { matchesFilter } from "../filtering/filter-matching";
4
+ import type { CardRegistry } from "../operations/card-registry";
5
+ import type { PlayerId } from "../types";
6
+ import type { TargetDefinition } from "./target-definition";
7
+
8
+ /**
9
+ * Context for target validation
10
+ * Provides information needed to evaluate targeting restrictions
11
+ * @template TCustomState - The custom state type for CardInstance
12
+ */
13
+ export type TargetContext<TCustomState = unknown> = {
14
+ /** The card that is the source of the targeting (e.g., the spell being cast) */
15
+ sourceCard: CardInstance<TCustomState>;
16
+
17
+ /** The player controlling the targeting action */
18
+ controller: PlayerId;
19
+
20
+ /** Previously selected targets (for multi-target validation) */
21
+ previousTargets: CardInstance<TCustomState>[];
22
+ };
23
+
24
+ /**
25
+ * Result of target validation
26
+ */
27
+ export type ValidationResult = {
28
+ valid: boolean;
29
+ error?: string;
30
+ };
31
+
32
+ /**
33
+ * Check if a card is a legal target for a given target definition
34
+ * @template TCustomState - The custom state type for CardInstance
35
+ * @template TGameState - The game state type
36
+ * @param card - The potential target card
37
+ * @param targetDef - Target definition specifying requirements
38
+ * @param state - Game state for filter evaluation
39
+ * @param registry - Card definition registry
40
+ * @param context - Target context (source, controller, previous targets)
41
+ * @returns true if the card is a legal target
42
+ */
43
+ export function isLegalTarget<
44
+ TCustomState = unknown,
45
+ TGameState extends { cards: Record<string, CardInstance<TCustomState>> } = {
46
+ cards: Record<string, CardInstance<TCustomState>>;
47
+ },
48
+ >(
49
+ card: CardInstance<TCustomState>,
50
+ targetDef: TargetDefinition,
51
+ state: TGameState,
52
+ registry: CardRegistry<CardDefinition>,
53
+ context: TargetContext<TCustomState>,
54
+ ): boolean {
55
+ // Check if card matches the filter
56
+ if (!matchesFilter(card, targetDef.filter, state, registry)) {
57
+ return false;
58
+ }
59
+
60
+ // Check targeting restrictions
61
+ if (targetDef.restrictions) {
62
+ for (const restriction of targetDef.restrictions) {
63
+ if (restriction === "not-self") {
64
+ if (card.id === context.sourceCard.id) {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ if (restriction === "not-controller") {
70
+ if (card.controller === context.controller) {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ if (restriction === "not-owner") {
76
+ if (card.owner === context.controller) {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ if (restriction === "different-targets") {
82
+ if (context.previousTargets.some((target) => target.id === card.id)) {
83
+ return false;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ return true;
90
+ }
91
+
92
+ /**
93
+ * Get all legal targets for a target definition
94
+ * @template TCustomState - The custom state type for CardInstance
95
+ * @template TGameState - The game state type
96
+ * @param targetDef - Target definition
97
+ * @param state - Game state
98
+ * @param registry - Card definition registry
99
+ * @param context - Target context
100
+ * @returns Array of legal target cards
101
+ */
102
+ export function getLegalTargets<
103
+ TCustomState = unknown,
104
+ TGameState extends { cards: Record<string, CardInstance<TCustomState>> } = {
105
+ cards: Record<string, CardInstance<TCustomState>>;
106
+ },
107
+ >(
108
+ targetDef: TargetDefinition,
109
+ state: TGameState,
110
+ registry: CardRegistry<CardDefinition>,
111
+ context: TargetContext<TCustomState>,
112
+ ): CardInstance<TCustomState>[] {
113
+ const allCards = Object.values(state.cards);
114
+ return allCards.filter((card) =>
115
+ isLegalTarget(card, targetDef, state, registry, context),
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Validate a target selection against a target definition
121
+ * @template TCustomState - The custom state type for CardInstance
122
+ * @template TGameState - The game state type
123
+ * @param targets - Selected target cards
124
+ * @param targetDef - Target definition
125
+ * @param state - Game state
126
+ * @param registry - Card definition registry
127
+ * @param context - Target context (without previousTargets)
128
+ * @returns Validation result
129
+ */
130
+ export function validateTargetSelection<
131
+ TCustomState = unknown,
132
+ TGameState extends { cards: Record<string, CardInstance<TCustomState>> } = {
133
+ cards: Record<string, CardInstance<TCustomState>>;
134
+ },
135
+ >(
136
+ targets: CardInstance<TCustomState>[],
137
+ targetDef: TargetDefinition,
138
+ state: TGameState,
139
+ registry: CardRegistry<CardDefinition>,
140
+ context: Omit<TargetContext<TCustomState>, "previousTargets">,
141
+ ): ValidationResult {
142
+ // Validate count
143
+ if (typeof targetDef.count === "number") {
144
+ // Exact count required
145
+ if (targets.length !== targetDef.count) {
146
+ return {
147
+ valid: false,
148
+ error: `Expected ${targetDef.count} target(s), but got ${targets.length}`,
149
+ };
150
+ }
151
+ } else {
152
+ // Range count
153
+ const { min, max } = targetDef.count;
154
+ if (targets.length < min) {
155
+ return {
156
+ valid: false,
157
+ error: `Expected at least ${min} target(s), but got ${targets.length}`,
158
+ };
159
+ }
160
+ if (targets.length > max) {
161
+ return {
162
+ valid: false,
163
+ error: `Expected at most ${max} target(s), but got ${targets.length}`,
164
+ };
165
+ }
166
+ }
167
+
168
+ // Validate each target individually
169
+ for (let i = 0; i < targets.length; i++) {
170
+ const target = targets[i];
171
+ if (!target) {
172
+ return {
173
+ valid: false,
174
+ error: `Target at index ${i} is undefined`,
175
+ };
176
+ }
177
+
178
+ const previousTargets = targets.slice(0, i);
179
+
180
+ const fullContext: TargetContext<TCustomState> = {
181
+ ...context,
182
+ previousTargets,
183
+ };
184
+
185
+ if (!isLegalTarget(target, targetDef, state, registry, fullContext)) {
186
+ return {
187
+ valid: false,
188
+ error: `Target at index ${i} (${String(target.id)}) is not a legal target`,
189
+ };
190
+ }
191
+ }
192
+
193
+ return { valid: true };
194
+ }
195
+
196
+ /**
197
+ * Enumerate all valid target combinations for a target definition
198
+ * Useful for AI move generation
199
+ * @template TCustomState - The custom state type for CardInstance
200
+ * @template TGameState - The game state type
201
+ * @param targetDef - Target definition
202
+ * @param state - Game state
203
+ * @param registry - Card definition registry
204
+ * @param context - Target context
205
+ * @param maxCombinations - Maximum number of combinations to return
206
+ * @returns Array of target combinations (each combination is an array of cards)
207
+ */
208
+ export function enumerateTargetCombinations<
209
+ TCustomState = unknown,
210
+ TGameState extends { cards: Record<string, CardInstance<TCustomState>> } = {
211
+ cards: Record<string, CardInstance<TCustomState>>;
212
+ },
213
+ >(
214
+ targetDef: TargetDefinition,
215
+ state: TGameState,
216
+ registry: CardRegistry<CardDefinition>,
217
+ context: TargetContext<TCustomState>,
218
+ maxCombinations: number,
219
+ ): CardInstance<TCustomState>[][] {
220
+ const legalTargets = getLegalTargets(targetDef, state, registry, context);
221
+
222
+ // Determine count range
223
+ let minCount: number;
224
+ let maxCount: number;
225
+
226
+ if (typeof targetDef.count === "number") {
227
+ minCount = targetDef.count;
228
+ maxCount = targetDef.count;
229
+ } else {
230
+ minCount = targetDef.count.min;
231
+ maxCount = Math.min(targetDef.count.max, legalTargets.length);
232
+ }
233
+
234
+ const combinations: CardInstance<TCustomState>[][] = [];
235
+
236
+ // Helper function to generate combinations recursively
237
+ function generateCombinations(
238
+ start: number,
239
+ current: CardInstance<TCustomState>[],
240
+ targetCount: number,
241
+ ): void {
242
+ if (combinations.length >= maxCombinations) {
243
+ return;
244
+ }
245
+
246
+ if (current.length === targetCount) {
247
+ combinations.push([...current]);
248
+ return;
249
+ }
250
+
251
+ for (let i = start; i < legalTargets.length; i++) {
252
+ const candidate = legalTargets[i];
253
+ if (!candidate) {
254
+ continue;
255
+ }
256
+
257
+ // Check if this candidate is legal given previous selections
258
+ const updatedContext: TargetContext<TCustomState> = {
259
+ ...context,
260
+ previousTargets: current,
261
+ };
262
+
263
+ if (
264
+ isLegalTarget(candidate, targetDef, state, registry, updatedContext)
265
+ ) {
266
+ current.push(candidate);
267
+ generateCombinations(i + 1, current, targetCount);
268
+ current.pop();
269
+ }
270
+
271
+ if (combinations.length >= maxCombinations) {
272
+ break;
273
+ }
274
+ }
275
+ }
276
+
277
+ // Generate combinations for each valid count
278
+ for (let count = minCount; count <= maxCount; count++) {
279
+ generateCombinations(0, [], count);
280
+ if (combinations.length >= maxCombinations) {
281
+ break;
282
+ }
283
+ }
284
+
285
+ return combinations.slice(0, maxCombinations);
286
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Telemetry Events
3
+ *
4
+ * Event type definitions for the TCG Core telemetry system.
5
+ * All events are discriminated unions for type-safe handling.
6
+ */
7
+
8
+ import type { Patch } from "immer";
9
+ import type { PlayerId } from "../types";
10
+
11
+ /**
12
+ * Player Action Event
13
+ *
14
+ * Emitted whenever a player executes a move.
15
+ * Tracks move execution, parameters, result, and duration.
16
+ *
17
+ * Use cases:
18
+ * - Player behavior analysis
19
+ * - Move frequency tracking
20
+ * - Performance monitoring
21
+ * - Replay generation
22
+ */
23
+ export type PlayerActionEvent = {
24
+ type: "playerAction";
25
+ /** Move identifier */
26
+ moveId: string;
27
+ /** Player executing the move */
28
+ playerId: PlayerId;
29
+ /** Move parameters */
30
+ params: unknown;
31
+ /** Execution result (success/failure) */
32
+ result: "success" | "failure";
33
+ /** Error message (if failed) */
34
+ error?: string;
35
+ /** Error code (if failed) */
36
+ errorCode?: string;
37
+ /** Execution duration in milliseconds */
38
+ duration: number;
39
+ /** Event timestamp */
40
+ timestamp: number;
41
+ };
42
+
43
+ /**
44
+ * State Change Event
45
+ *
46
+ * Emitted after state mutations.
47
+ * Contains patches for incremental state sync and replay.
48
+ *
49
+ * Use cases:
50
+ * - Network synchronization
51
+ * - State replay/reconstruction
52
+ * - Debugging state issues
53
+ * - Audit trails
54
+ */
55
+ export type StateChangeEvent = {
56
+ type: "stateChange";
57
+ /** Forward patches (state mutations) */
58
+ patches: Patch[];
59
+ /** Inverse patches (for undo) */
60
+ inversePatches: Patch[];
61
+ /** Move that caused this change */
62
+ moveId?: string;
63
+ /** Optional before-state snapshot */
64
+ beforeSnapshot?: unknown;
65
+ /** Optional after-state snapshot */
66
+ afterSnapshot?: unknown;
67
+ /** Event timestamp */
68
+ timestamp: number;
69
+ };
70
+
71
+ /**
72
+ * Rule Evaluation Event
73
+ *
74
+ * Emitted during condition checks and rule evaluations.
75
+ * Tracks which rules fired, with what context, and the result.
76
+ *
77
+ * Use cases:
78
+ * - Debugging rule interactions
79
+ * - Understanding game decisions
80
+ * - AI training data
81
+ * - Balance analysis
82
+ */
83
+ export type RuleEvaluationEvent = {
84
+ type: "ruleEvaluation";
85
+ /** Rule or condition name */
86
+ ruleName: string;
87
+ /** Evaluation result (pass/fail) */
88
+ result: boolean;
89
+ /** Evaluation context */
90
+ context: Record<string, unknown>;
91
+ /** Evaluation duration in milliseconds */
92
+ duration?: number;
93
+ /** Event timestamp */
94
+ timestamp: number;
95
+ };
96
+
97
+ /**
98
+ * Flow Transition Event
99
+ *
100
+ * Emitted during game flow transitions (phases, turns, segments).
101
+ * Tracks progression through game structure.
102
+ *
103
+ * Use cases:
104
+ * - Game pacing analysis
105
+ * - Turn timing metrics
106
+ * - Flow debugging
107
+ * - UI synchronization
108
+ */
109
+ export type FlowTransitionEvent = {
110
+ type: "flowTransition";
111
+ /** Type of transition */
112
+ transitionType: "phase" | "segment" | "turn";
113
+ /** Source state (what we're leaving) */
114
+ from: string;
115
+ /** Destination state (where we're going) */
116
+ to: string;
117
+ /** Current turn number */
118
+ turn: number;
119
+ /** Event timestamp */
120
+ timestamp: number;
121
+ };
122
+
123
+ /**
124
+ * Engine Error Event
125
+ *
126
+ * Emitted when errors occur during engine execution.
127
+ * Captures full error context for debugging and monitoring.
128
+ *
129
+ * Use cases:
130
+ * - Error tracking and reporting
131
+ * - System health monitoring
132
+ * - Bug reproduction
133
+ * - Alert generation
134
+ */
135
+ export type EngineErrorEvent = {
136
+ type: "engineError";
137
+ /** Error message */
138
+ error: string;
139
+ /** Stack trace */
140
+ stack?: string;
141
+ /** Error context (move, player, etc.) */
142
+ context: Record<string, unknown>;
143
+ /** Move ID (if error during move execution) */
144
+ moveId?: string;
145
+ /** Player ID (if error during player action) */
146
+ playerId?: PlayerId;
147
+ /** Event timestamp */
148
+ timestamp: number;
149
+ };
150
+
151
+ /**
152
+ * Performance Event
153
+ *
154
+ * Emitted for performance-sensitive operations.
155
+ * Tracks execution time and resource usage.
156
+ *
157
+ * Use cases:
158
+ * - Performance profiling
159
+ * - Bottleneck identification
160
+ * - Optimization validation
161
+ * - Resource monitoring
162
+ */
163
+ export type PerformanceEvent = {
164
+ type: "performance";
165
+ /** Operation name */
166
+ operation: string;
167
+ /** Execution duration in milliseconds */
168
+ duration: number;
169
+ /** Operation metadata */
170
+ metadata?: Record<string, unknown>;
171
+ /** Event timestamp */
172
+ timestamp: number;
173
+ };
174
+
175
+ /**
176
+ * Telemetry Event Union
177
+ *
178
+ * Discriminated union of all telemetry event types.
179
+ * Use the `type` field for type narrowing.
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * function handleEvent(event: TelemetryEvent) {
184
+ * switch (event.type) {
185
+ * case 'playerAction':
186
+ * console.log(`Move: ${event.moveId}, Result: ${event.result}`);
187
+ * break;
188
+ * case 'flowTransition':
189
+ * console.log(`${event.from} -> ${event.to}`);
190
+ * break;
191
+ * // ... other cases
192
+ * }
193
+ * }
194
+ * ```
195
+ */
196
+ export type TelemetryEvent =
197
+ | PlayerActionEvent
198
+ | StateChangeEvent
199
+ | RuleEvaluationEvent
200
+ | FlowTransitionEvent
201
+ | EngineErrorEvent
202
+ | PerformanceEvent;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Telemetry Module
3
+ *
4
+ * Event-based telemetry system for TCG Core.
5
+ */
6
+
7
+ export type {
8
+ EngineErrorEvent,
9
+ FlowTransitionEvent,
10
+ PerformanceEvent,
11
+ PlayerActionEvent,
12
+ RuleEvaluationEvent,
13
+ StateChangeEvent,
14
+ TelemetryEvent,
15
+ } from "./events";
16
+ export { TelemetryManager } from "./telemetry-manager";
17
+ export type {
18
+ TelemetryHook,
19
+ TelemetryHooks,
20
+ TelemetryOptions,
21
+ } from "./types";
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Telemetry Manager
3
+ *
4
+ * Event-based telemetry system for TCG Core.
5
+ * Extends EventEmitter for flexible event subscription.
6
+ */
7
+
8
+ import { EventEmitter } from "node:events";
9
+ import type { TelemetryEvent } from "./events";
10
+ import type { TelemetryHooks, TelemetryOptions } from "./types";
11
+
12
+ /**
13
+ * Telemetry Manager
14
+ *
15
+ * Manages telemetry events and hooks for the engine.
16
+ * Provides dual API: EventEmitter style and callback hooks.
17
+ */
18
+ export class TelemetryManager extends EventEmitter {
19
+ private enabled: boolean;
20
+ private registeredHooks: TelemetryHooks;
21
+
22
+ constructor(options: TelemetryOptions) {
23
+ super();
24
+ this.enabled = options.enabled;
25
+ this.registeredHooks = options.hooks || {};
26
+
27
+ // Register initial hooks if provided
28
+ this.registerInitialHooks();
29
+ }
30
+
31
+ /**
32
+ * Register hooks provided at initialization
33
+ */
34
+ private registerInitialHooks(): void {
35
+ if (this.registeredHooks.onPlayerAction) {
36
+ this.on("playerAction", this.registeredHooks.onPlayerAction);
37
+ }
38
+ if (this.registeredHooks.onStateChange) {
39
+ this.on("stateChange", this.registeredHooks.onStateChange);
40
+ }
41
+ if (this.registeredHooks.onRuleEvaluation) {
42
+ this.on("ruleEvaluation", this.registeredHooks.onRuleEvaluation);
43
+ }
44
+ if (this.registeredHooks.onFlowTransition) {
45
+ this.on("flowTransition", this.registeredHooks.onFlowTransition);
46
+ }
47
+ if (this.registeredHooks.onEngineError) {
48
+ this.on("engineError", this.registeredHooks.onEngineError);
49
+ }
50
+ if (this.registeredHooks.onPerformance) {
51
+ this.on("onPerformance", this.registeredHooks.onPerformance);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Emit telemetry event
57
+ *
58
+ * Emits events via EventEmitter and invokes registered hooks.
59
+ * Does nothing if telemetry is disabled.
60
+ *
61
+ * @param event - Telemetry event to emit
62
+ * @returns True if emitted, false if disabled
63
+ */
64
+ emitEvent(event: TelemetryEvent): boolean {
65
+ if (!this.enabled) {
66
+ return false;
67
+ }
68
+
69
+ // Emit via EventEmitter (for runtime subscribers)
70
+ super.emit(event.type, event);
71
+
72
+ return true;
73
+ }
74
+
75
+ /**
76
+ * Register a single telemetry hook
77
+ *
78
+ * Allows external systems to subscribe to specific event types.
79
+ */
80
+ registerHook<K extends keyof TelemetryHooks>(
81
+ eventType: K,
82
+ handler: NonNullable<TelemetryHooks[K]>,
83
+ ): void {
84
+ this.registeredHooks[eventType] = handler;
85
+
86
+ // Register with EventEmitter
87
+ // Remove 'on' prefix for event name
88
+ const eventName = eventType.startsWith("on")
89
+ ? eventType.slice(2, 3).toLowerCase() + eventType.slice(3)
90
+ : eventType;
91
+
92
+ this.on(eventName, handler as (...args: unknown[]) => void);
93
+ }
94
+
95
+ /**
96
+ * Unregister a telemetry hook
97
+ */
98
+ unregisterHook<K extends keyof TelemetryHooks>(
99
+ eventType: K,
100
+ handler: NonNullable<TelemetryHooks[K]>,
101
+ ): void {
102
+ if (this.registeredHooks[eventType] === handler) {
103
+ delete this.registeredHooks[eventType];
104
+ }
105
+
106
+ // Unregister from EventEmitter
107
+ const eventName = eventType.startsWith("on")
108
+ ? eventType.slice(2, 3).toLowerCase() + eventType.slice(3)
109
+ : eventType;
110
+
111
+ this.off(eventName, handler as (...args: unknown[]) => void);
112
+ }
113
+
114
+ /**
115
+ * Set enabled state
116
+ */
117
+ setEnabled(enabled: boolean): void {
118
+ this.enabled = enabled;
119
+ }
120
+
121
+ /**
122
+ * Check if telemetry is enabled
123
+ */
124
+ isEnabled(): boolean {
125
+ return this.enabled;
126
+ }
127
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Telemetry Types
3
+ *
4
+ * Type definitions for the TCG Core telemetry system.
5
+ * Provides hooks for external analytics and monitoring systems.
6
+ */
7
+
8
+ import type {
9
+ EngineErrorEvent,
10
+ FlowTransitionEvent,
11
+ PerformanceEvent,
12
+ PlayerActionEvent,
13
+ RuleEvaluationEvent,
14
+ StateChangeEvent,
15
+ TelemetryEvent,
16
+ } from "./events";
17
+
18
+ /**
19
+ * Telemetry Hook
20
+ *
21
+ * Generic callback type for handling telemetry events.
22
+ * All hooks receive a single event parameter and return void.
23
+ */
24
+ export type TelemetryHook = (event: TelemetryEvent) => void;
25
+
26
+ /**
27
+ * Telemetry Hooks
28
+ *
29
+ * Object defining callbacks for specific event types.
30
+ * Each hook is optional and receives only events of its type.
31
+ */
32
+ export type TelemetryHooks = {
33
+ /** Called when a player executes a move */
34
+ onPlayerAction?: (event: PlayerActionEvent) => void;
35
+ /** Called when state changes occur */
36
+ onStateChange?: (event: StateChangeEvent) => void;
37
+ /** Called when rules are evaluated */
38
+ onRuleEvaluation?: (event: RuleEvaluationEvent) => void;
39
+ /** Called during flow transitions (phase/turn/segment) */
40
+ onFlowTransition?: (event: FlowTransitionEvent) => void;
41
+ /** Called when engine errors occur */
42
+ onEngineError?: (event: EngineErrorEvent) => void;
43
+ /** Called for performance metrics */
44
+ onPerformance?: (event: PerformanceEvent) => void;
45
+ };
46
+
47
+ /**
48
+ * Telemetry Options
49
+ *
50
+ * Configuration for TelemetryManager instances.
51
+ */
52
+ export type TelemetryOptions = {
53
+ /**
54
+ * Enable/disable telemetry
55
+ *
56
+ * When false, no events are emitted.
57
+ * Default: false
58
+ */
59
+ enabled: boolean;
60
+
61
+ /**
62
+ * Event hooks
63
+ *
64
+ * Optional callbacks for specific event types.
65
+ * Invoked synchronously when events are emitted.
66
+ */
67
+ hooks?: TelemetryHooks;
68
+ };