@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.
- package/README.md +882 -0
- package/package.json +58 -0
- package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
- package/src/__tests__/createMockAlphaClashGame.ts +462 -0
- package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
- package/src/__tests__/createMockGundamGame.ts +379 -0
- package/src/__tests__/createMockLorcanaGame.ts +328 -0
- package/src/__tests__/createMockOnePieceGame.ts +429 -0
- package/src/__tests__/createMockRiftboundGame.ts +462 -0
- package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
- package/src/__tests__/gundam-engine-definition.test.ts +110 -0
- package/src/__tests__/integration-complete-game.test.ts +508 -0
- package/src/__tests__/integration-network-sync.test.ts +469 -0
- package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
- package/src/__tests__/move-enumeration.test.ts +725 -0
- package/src/__tests__/multiplayer-engine.test.ts +555 -0
- package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
- package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
- package/src/actions/action-definition.test.ts +201 -0
- package/src/actions/action-definition.ts +122 -0
- package/src/actions/action-timing.test.ts +490 -0
- package/src/actions/action-timing.ts +257 -0
- package/src/cards/card-definition.test.ts +268 -0
- package/src/cards/card-definition.ts +27 -0
- package/src/cards/card-instance.test.ts +422 -0
- package/src/cards/card-instance.ts +49 -0
- package/src/cards/computed-properties.test.ts +530 -0
- package/src/cards/computed-properties.ts +84 -0
- package/src/cards/conditional-modifiers.test.ts +390 -0
- package/src/cards/modifiers.test.ts +286 -0
- package/src/cards/modifiers.ts +51 -0
- package/src/engine/MULTIPLAYER.md +425 -0
- package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
- package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
- package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
- package/src/engine/__tests__/rule-engine.test.ts +366 -0
- package/src/engine/index.ts +14 -0
- package/src/engine/multiplayer-engine.example.ts +571 -0
- package/src/engine/multiplayer-engine.ts +409 -0
- package/src/engine/rule-engine.test.ts +286 -0
- package/src/engine/rule-engine.ts +1539 -0
- package/src/engine/tracker-system.ts +172 -0
- package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
- package/src/filtering/card-filter.test.ts +230 -0
- package/src/filtering/card-filter.ts +91 -0
- package/src/filtering/card-query.test.ts +901 -0
- package/src/filtering/card-query.ts +273 -0
- package/src/filtering/filter-matching.test.ts +944 -0
- package/src/filtering/filter-matching.ts +315 -0
- package/src/flow/SERIALIZATION.md +428 -0
- package/src/flow/__tests__/flow-definition.test.ts +427 -0
- package/src/flow/__tests__/flow-manager.test.ts +756 -0
- package/src/flow/__tests__/flow-serialization.test.ts +565 -0
- package/src/flow/flow-definition.ts +453 -0
- package/src/flow/flow-manager.ts +1044 -0
- package/src/flow/index.ts +35 -0
- package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
- package/src/game-definition/__tests__/game-definition.test.ts +291 -0
- package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
- package/src/game-definition/game-definition.ts +261 -0
- package/src/game-definition/index.ts +28 -0
- package/src/game-definition/move-definitions.ts +188 -0
- package/src/game-definition/validation.ts +183 -0
- package/src/history/history-manager.test.ts +497 -0
- package/src/history/history-manager.ts +312 -0
- package/src/history/history-operations.ts +122 -0
- package/src/history/index.ts +9 -0
- package/src/history/types.ts +255 -0
- package/src/index.ts +32 -0
- package/src/logging/index.ts +27 -0
- package/src/logging/log-formatter.ts +187 -0
- package/src/logging/logger.ts +276 -0
- package/src/logging/types.ts +148 -0
- package/src/moves/create-move.test.ts +331 -0
- package/src/moves/create-move.ts +64 -0
- package/src/moves/move-enumeration.ts +228 -0
- package/src/moves/move-executor.test.ts +431 -0
- package/src/moves/move-executor.ts +195 -0
- package/src/moves/move-system.test.ts +380 -0
- package/src/moves/move-system.ts +463 -0
- package/src/moves/standard-moves.ts +231 -0
- package/src/operations/card-operations.test.ts +236 -0
- package/src/operations/card-operations.ts +116 -0
- package/src/operations/card-registry-impl.test.ts +251 -0
- package/src/operations/card-registry-impl.ts +70 -0
- package/src/operations/card-registry.test.ts +234 -0
- package/src/operations/card-registry.ts +106 -0
- package/src/operations/counter-operations.ts +152 -0
- package/src/operations/game-operations.test.ts +280 -0
- package/src/operations/game-operations.ts +140 -0
- package/src/operations/index.ts +24 -0
- package/src/operations/operations-impl.test.ts +354 -0
- package/src/operations/operations-impl.ts +468 -0
- package/src/operations/zone-operations.test.ts +295 -0
- package/src/operations/zone-operations.ts +223 -0
- package/src/rng/seeded-rng.test.ts +339 -0
- package/src/rng/seeded-rng.ts +123 -0
- package/src/targeting/index.ts +48 -0
- package/src/targeting/target-definition.test.ts +273 -0
- package/src/targeting/target-definition.ts +37 -0
- package/src/targeting/target-dsl.ts +279 -0
- package/src/targeting/target-resolver.ts +486 -0
- package/src/targeting/target-validation.test.ts +994 -0
- package/src/targeting/target-validation.ts +286 -0
- package/src/telemetry/events.ts +202 -0
- package/src/telemetry/index.ts +21 -0
- package/src/telemetry/telemetry-manager.ts +127 -0
- package/src/telemetry/types.ts +68 -0
- package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
- package/src/testing/index.ts +88 -0
- package/src/testing/test-assertions.test.ts +341 -0
- package/src/testing/test-assertions.ts +256 -0
- package/src/testing/test-card-factory.test.ts +228 -0
- package/src/testing/test-card-factory.ts +111 -0
- package/src/testing/test-context-factory.ts +187 -0
- package/src/testing/test-end-assertions.test.ts +262 -0
- package/src/testing/test-end-assertions.ts +95 -0
- package/src/testing/test-engine-builder.test.ts +389 -0
- package/src/testing/test-engine-builder.ts +46 -0
- package/src/testing/test-flow-assertions.test.ts +284 -0
- package/src/testing/test-flow-assertions.ts +115 -0
- package/src/testing/test-player-builder.test.ts +132 -0
- package/src/testing/test-player-builder.ts +46 -0
- package/src/testing/test-replay-assertions.test.ts +356 -0
- package/src/testing/test-replay-assertions.ts +164 -0
- package/src/testing/test-rng-helpers.test.ts +260 -0
- package/src/testing/test-rng-helpers.ts +190 -0
- package/src/testing/test-state-builder.test.ts +373 -0
- package/src/testing/test-state-builder.ts +99 -0
- package/src/testing/test-zone-factory.test.ts +295 -0
- package/src/testing/test-zone-factory.ts +224 -0
- package/src/types/branded-utils.ts +54 -0
- package/src/types/branded.test.ts +175 -0
- package/src/types/branded.ts +33 -0
- package/src/types/index.ts +8 -0
- package/src/types/state.test.ts +198 -0
- package/src/types/state.ts +154 -0
- package/src/validation/card-type-guards.test.ts +242 -0
- package/src/validation/card-type-guards.ts +179 -0
- package/src/validation/index.ts +40 -0
- package/src/validation/schema-builders.test.ts +403 -0
- package/src/validation/schema-builders.ts +345 -0
- package/src/validation/type-guard-builder.test.ts +216 -0
- package/src/validation/type-guard-builder.ts +109 -0
- package/src/validation/validator-builder.test.ts +375 -0
- package/src/validation/validator-builder.ts +273 -0
- package/src/zones/index.ts +28 -0
- package/src/zones/zone-factory.test.ts +183 -0
- package/src/zones/zone-factory.ts +44 -0
- package/src/zones/zone-operations.test.ts +800 -0
- package/src/zones/zone-operations.ts +306 -0
- package/src/zones/zone-state-helpers.test.ts +337 -0
- package/src/zones/zone-state-helpers.ts +128 -0
- package/src/zones/zone-visibility.test.ts +156 -0
- package/src/zones/zone-visibility.ts +36 -0
- package/src/zones/zone.test.ts +186 -0
- 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
|
+
};
|