@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,124 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { createTestEngine } from "../testing/test-engine-builder";
3
+ import { createTestPlayers } from "../testing/test-player-builder";
4
+ import { createMockRiftboundGame } from "./createMockRiftboundGame";
5
+
6
+ /**
7
+ * Riftbound Card Game - Engine Feature Tests
8
+ *
9
+ * Refactored to showcase:
10
+ * ✅ High-level zone utilities (createDeck, bulkMove, drawCards)
11
+ * ✅ Tracker system (hasDrawn)
12
+ * ✅ Standard moves (pass, concede)
13
+ * ✅ Flow context in phase hooks
14
+ * ✅ Massive simplification (10 fields → 4 fields)
15
+ */
16
+ describe("Riftbound Game - Refactored Engine Features", () => {
17
+ it("should initialize game with ONLY game-specific state", () => {
18
+ const gameDefinition = createMockRiftboundGame();
19
+ const players = createTestPlayers(2);
20
+ const engine = createTestEngine(gameDefinition, players);
21
+
22
+ const state = engine.getState();
23
+
24
+ // ✅ NEW: Only game-specific data
25
+ expect(state.victoryPoints).toBeDefined();
26
+ expect(state.battlefieldControl).toBeDefined();
27
+ expect(state.runePools).toBeDefined();
28
+ expect(state.conqueredThisTurn).toBeDefined();
29
+
30
+ // ✅ REMOVED: No manual tracking
31
+ // @ts-expect-error
32
+ expect(state.phase).toBeUndefined();
33
+ // @ts-expect-error
34
+ expect(state.setupStep).toBeUndefined();
35
+ // @ts-expect-error
36
+ expect(state.turn).toBeUndefined();
37
+ // @ts-expect-error
38
+ expect(state.activePlayer).toBeUndefined();
39
+ // @ts-expect-error
40
+ expect(state.hasDrawnThisTurn).toBeUndefined();
41
+ });
42
+
43
+ it("should have proper zone configuration", () => {
44
+ const gameDefinition = createMockRiftboundGame();
45
+ const zones = gameDefinition.zones;
46
+
47
+ // Verify Riftbound zones
48
+ expect(zones?.mainDeck).toBeDefined();
49
+ expect(zones?.hand).toBeDefined();
50
+ expect(zones?.runeDeck).toBeDefined();
51
+ expect(zones?.runePool).toBeDefined();
52
+ expect(zones?.legendZone).toBeDefined();
53
+ expect(zones?.championZone).toBeDefined();
54
+ expect(zones?.battlefield).toBeDefined();
55
+ expect(zones?.battlefieldRow).toBeDefined();
56
+ expect(zones?.gearArea).toBeDefined();
57
+ expect(zones?.discard).toBeDefined();
58
+
59
+ expect(zones?.mainDeck?.maxSize).toBe(40);
60
+ expect(zones?.runeDeck?.maxSize).toBe(12);
61
+ expect(zones?.legendZone?.maxSize).toBe(1);
62
+ expect(zones?.championZone?.maxSize).toBe(1);
63
+ expect(zones?.battlefieldRow?.maxSize).toBe(3);
64
+ });
65
+
66
+ it("should use high-level zone utilities", () => {
67
+ // ✅ NEW: zones.createDeck() for dual deck system
68
+ // ✅ NEW: zones.bulkMove() for rune channeling
69
+ // ✅ NEW: zones.drawCards() for card drawing
70
+
71
+ const gameDefinition = createMockRiftboundGame();
72
+ expect(gameDefinition.moves.initializeDecks).toBeDefined();
73
+ expect(gameDefinition.moves.channelRunes).toBeDefined();
74
+ expect(gameDefinition.moves.drawCard).toBeDefined();
75
+ expect(gameDefinition.moves.drawInitialHand).toBeDefined();
76
+ });
77
+
78
+ it("should configure tracker system", () => {
79
+ const gameDefinition = createMockRiftboundGame();
80
+
81
+ expect(gameDefinition.trackers).toBeDefined();
82
+ expect(gameDefinition.trackers?.perTurn).toContain("hasDrawn");
83
+ expect(gameDefinition.trackers?.perPlayer).toBe(true);
84
+ });
85
+
86
+ it("should use tracker system for draw limitation", () => {
87
+ const gameDefinition = createMockRiftboundGame();
88
+
89
+ const drawCard = gameDefinition.moves.drawCard;
90
+ expect(drawCard.condition).toBeDefined();
91
+
92
+ // Uses context.trackers.check("hasDrawn")
93
+ });
94
+
95
+ it("should use flow context in phase hooks", () => {
96
+ const gameDefinition = createMockRiftboundGame();
97
+ const flow = gameDefinition.flow;
98
+
99
+ // ending phase uses context.getCurrentPlayer()
100
+ expect(flow).toBeDefined();
101
+ if (!(flow && "turn" in flow)) {
102
+ throw new Error("Expected simplified flow definition with turn property");
103
+ }
104
+ expect(flow.turn.phases?.ending).toBeDefined();
105
+ });
106
+
107
+ it("should use standard moves", () => {
108
+ const gameDefinition = createMockRiftboundGame();
109
+
110
+ expect(gameDefinition.moves.pass).toBeDefined();
111
+ expect(gameDefinition.moves.concede).toBeDefined();
112
+ });
113
+
114
+ it("should demonstrate boilerplate reduction", () => {
115
+ // State fields: 10 → 4 (-60%)
116
+ // 593 lines → 440 lines (-26%)
117
+
118
+ const gameDefinition = createMockRiftboundGame();
119
+ const players = createTestPlayers(2);
120
+ const state = gameDefinition.setup(players);
121
+
122
+ expect(Object.keys(state).length).toBe(4);
123
+ });
124
+ });
@@ -0,0 +1,201 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { createPlayerId } from "../types";
3
+ import type {
4
+ ActionDefinition,
5
+ ActionInstance,
6
+ ActionMetadata,
7
+ ActionTiming,
8
+ } from "./action-definition";
9
+
10
+ describe("Action Definition Types", () => {
11
+ describe("ActionTiming", () => {
12
+ it("should define timing constraints with segments", () => {
13
+ const timing: ActionTiming = {
14
+ segments: ["gameplay"],
15
+ phases: ["mainPhase"],
16
+ };
17
+
18
+ expect(timing.segments).toEqual(["gameplay"]);
19
+ expect(timing.phases).toEqual(["mainPhase"]);
20
+ });
21
+
22
+ it("should support custom timing predicates", () => {
23
+ type GameState = { turnCount: number };
24
+ const timing: ActionTiming<GameState> = {
25
+ custom: (state) => state.turnCount > 5,
26
+ };
27
+
28
+ expect(timing.custom?.({ turnCount: 6 })).toBe(true);
29
+ expect(timing.custom?.({ turnCount: 3 })).toBe(false);
30
+ });
31
+
32
+ it("should support multiple phases and steps", () => {
33
+ const timing: ActionTiming = {
34
+ segments: ["gameplay"],
35
+ phases: ["mainPhase", "combatPhase"],
36
+ steps: ["attackStep", "blockStep"],
37
+ };
38
+
39
+ expect(timing.phases).toHaveLength(2);
40
+ expect(timing.steps).toHaveLength(2);
41
+ });
42
+ });
43
+
44
+ describe("ActionMetadata", () => {
45
+ it("should define metadata for categorization", () => {
46
+ const metadata: ActionMetadata = {
47
+ category: "card-play",
48
+ subcategory: "creature",
49
+ tags: ["costs-resources", "requires-target"],
50
+ priorityHint: 10,
51
+ };
52
+
53
+ expect(metadata.category).toBe("card-play");
54
+ expect(metadata.tags).toContain("costs-resources");
55
+ expect(metadata.priorityHint).toBe(10);
56
+ });
57
+
58
+ it("should support hidden actions", () => {
59
+ const metadata: ActionMetadata = {
60
+ hidden: true,
61
+ category: "internal",
62
+ };
63
+
64
+ expect(metadata.hidden).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe("ActionDefinition", () => {
69
+ it("should define minimal action with just id and name", () => {
70
+ const action: ActionDefinition = {
71
+ id: "pass",
72
+ name: "Pass Priority",
73
+ };
74
+
75
+ expect(action.id).toBe("pass");
76
+ expect(action.name).toBe("Pass Priority");
77
+ });
78
+
79
+ it("should define action with timing constraints", () => {
80
+ const action: ActionDefinition = {
81
+ id: "play-creature",
82
+ name: "Play Creature",
83
+ description: "Play a creature card from your hand",
84
+ timing: {
85
+ segments: ["gameplay"],
86
+ phases: ["mainPhase"],
87
+ },
88
+ };
89
+
90
+ expect(action.timing?.segments).toEqual(["gameplay"]);
91
+ expect(action.timing?.phases).toEqual(["mainPhase"]);
92
+ });
93
+
94
+ it("should define action with target requirements", () => {
95
+ const action: ActionDefinition = {
96
+ id: "lightning-bolt",
97
+ name: "Lightning Bolt",
98
+ targets: [
99
+ {
100
+ filter: { type: "creature" },
101
+ count: 1,
102
+ },
103
+ ],
104
+ };
105
+
106
+ expect(action.targets).toHaveLength(1);
107
+ expect(action.targets?.[0].count).toBe(1);
108
+ });
109
+
110
+ it("should define action with metadata", () => {
111
+ const action: ActionDefinition = {
112
+ id: "attack",
113
+ name: "Attack",
114
+ metadata: {
115
+ category: "combat",
116
+ tags: ["combat-action"],
117
+ priorityHint: 8,
118
+ },
119
+ };
120
+
121
+ expect(action.metadata?.category).toBe("combat");
122
+ expect(action.metadata?.priorityHint).toBe(8);
123
+ });
124
+
125
+ it("should support game-specific state types", () => {
126
+ type LorcanaState = { turnPhase: string; inkPool: number };
127
+
128
+ const action: ActionDefinition<LorcanaState> = {
129
+ id: "quest",
130
+ name: "Quest with Character",
131
+ timing: {
132
+ custom: (state) => state.turnPhase === "main" && state.inkPool >= 0,
133
+ },
134
+ };
135
+
136
+ expect(action.timing?.custom?.({ turnPhase: "main", inkPool: 3 })).toBe(
137
+ true,
138
+ );
139
+ expect(action.timing?.custom?.({ turnPhase: "draw", inkPool: 3 })).toBe(
140
+ false,
141
+ );
142
+ });
143
+ });
144
+
145
+ describe("ActionInstance", () => {
146
+ it("should create action instance without targets", () => {
147
+ const player1 = createPlayerId("player1");
148
+ const instance: ActionInstance = {
149
+ actionId: "pass",
150
+ playerId: player1,
151
+ };
152
+
153
+ expect(instance.actionId).toBe("pass");
154
+ expect(instance.playerId).toBe(player1);
155
+ });
156
+
157
+ it("should create action instance with targets", () => {
158
+ const player1 = createPlayerId("player1");
159
+ const instance: ActionInstance = {
160
+ actionId: "lightning-bolt",
161
+ playerId: player1,
162
+ targets: [["card1"]],
163
+ };
164
+
165
+ expect(instance.targets).toHaveLength(1);
166
+ expect(instance.targets?.[0]).toEqual(["card1"]);
167
+ });
168
+
169
+ it("should create action instance with custom parameters", () => {
170
+ const player1 = createPlayerId("player1");
171
+ const instance: ActionInstance = {
172
+ actionId: "choose-option",
173
+ playerId: player1,
174
+ params: {
175
+ optionIndex: 2,
176
+ cardId: "card1",
177
+ },
178
+ timestamp: Date.now(),
179
+ };
180
+
181
+ expect(instance.params?.optionIndex).toBe(2);
182
+ expect(instance.timestamp).toBeDefined();
183
+ });
184
+
185
+ it("should support multi-target actions", () => {
186
+ const player1 = createPlayerId("player1");
187
+ const instance: ActionInstance = {
188
+ actionId: "distribute-damage",
189
+ playerId: player1,
190
+ targets: [
191
+ ["creature1", "creature2"], // First target group
192
+ ["player1"], // Second target group
193
+ ],
194
+ };
195
+
196
+ expect(instance.targets).toHaveLength(2);
197
+ expect(instance.targets?.[0]).toHaveLength(2);
198
+ expect(instance.targets?.[1]).toHaveLength(1);
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,122 @@
1
+ import type { TargetDefinition } from "../targeting/target-definition";
2
+ import type { PlayerId } from "../types";
3
+
4
+ /**
5
+ * Action Timing Constraint
6
+ * Specifies when an action can be performed in terms of game flow.
7
+ *
8
+ * This is a thin layer over core-engine's phase/segment/step system,
9
+ * providing a way to validate timing without duplicating core-engine logic.
10
+ */
11
+ export type ActionTiming<TGameState = unknown> = {
12
+ /** Segments where this action is allowed (e.g., "setup", "gameplay") */
13
+ segments?: string[];
14
+
15
+ /** Phases where this action is allowed (e.g., "mainPhase", "combatPhase") */
16
+ phases?: string[];
17
+
18
+ /** Steps where this action is allowed (e.g., "drawStep", "playStep") */
19
+ steps?: string[];
20
+
21
+ /** Custom timing predicate for complex game-specific timing rules */
22
+ custom?: (state: TGameState) => boolean;
23
+ };
24
+
25
+ /**
26
+ * Action Metadata
27
+ * Provides categorization and UI/logging information for actions.
28
+ * Games can define their own category taxonomies.
29
+ */
30
+ export type ActionMetadata = {
31
+ /** Category for UI grouping (game-defined, e.g., "card-play", "combat", "special") */
32
+ category?: string;
33
+
34
+ /** Subcategory for finer-grained grouping */
35
+ subcategory?: string;
36
+
37
+ /** Tags for flexible categorization (e.g., ["instant-speed", "costs-resources"]) */
38
+ tags?: string[];
39
+
40
+ /** Priority hint for AI/automation (higher = more important) */
41
+ priorityHint?: number;
42
+
43
+ /** Whether this action is hidden from normal UI (for automatic/internal actions) */
44
+ hidden?: boolean;
45
+ };
46
+
47
+ /**
48
+ * Action Definition
49
+ *
50
+ * A minimal, game-agnostic definition of a player action.
51
+ * This complements core-engine's EnumerableMove by providing:
52
+ * - Timing validation (segments/phases/steps)
53
+ * - Metadata for UI/logging/categorization
54
+ * - Target specifications using @drmxrcy/tcg-core's targeting system
55
+ *
56
+ * The actual execution logic, game-specific constraints, and cost validation
57
+ * are handled by core-engine's EnumerableMove.getConstraints() and execute().
58
+ */
59
+ export type ActionDefinition<TGameState = unknown> = {
60
+ /** Unique identifier for this action */
61
+ id: string;
62
+
63
+ /** Human-readable name for UI display */
64
+ name: string;
65
+
66
+ /** Optional description for tooltips/help */
67
+ description?: string;
68
+
69
+ /** When this action can be performed */
70
+ timing?: ActionTiming<TGameState>;
71
+
72
+ /** Target requirements using @drmxrcy/tcg-core's targeting system */
73
+ targets?: TargetDefinition[];
74
+
75
+ /** Metadata for categorization and UI */
76
+ metadata?: ActionMetadata;
77
+ };
78
+
79
+ /**
80
+ * Action Instance
81
+ *
82
+ * Represents a specific action being performed by a player.
83
+ * This is the bridge between @drmxrcy/tcg-core's action definitions and
84
+ * core-engine's move execution system.
85
+ */
86
+ export type ActionInstance = {
87
+ /** The action being performed */
88
+ actionId: string;
89
+
90
+ /** Player performing the action */
91
+ playerId: PlayerId;
92
+
93
+ /** Selected targets (array of arrays for multi-target actions) */
94
+ targets?: string[][];
95
+
96
+ /** Additional action-specific parameters */
97
+ params?: Record<string, unknown>;
98
+
99
+ /** Timestamp when action was initiated */
100
+ timestamp?: number;
101
+ };
102
+
103
+ /**
104
+ * Action Validation Result
105
+ *
106
+ * Result of validating whether an action can be performed.
107
+ * Focused on timing and target validation - cost validation
108
+ * is handled by core-engine's constraint system.
109
+ */
110
+ export type ActionValidationResult = {
111
+ /** Whether the action is valid */
112
+ valid: boolean;
113
+
114
+ /** Human-readable error message if invalid */
115
+ error?: string;
116
+
117
+ /** Specific reason code for programmatic handling */
118
+ reason?: "timing" | "targets" | "precondition";
119
+
120
+ /** Invalid target indices (if reason is "targets") */
121
+ invalidTargets?: number[];
122
+ };