This document describes the architecture and workflow of the card effect system within the Yu-Gi-Oh! Web Duel Interface. The system is responsible for handling the activation, chaining, and resolution of card effects according to Yu-Gi-Oh! rules. It integrates declarative effect definitions (partially implemented via YAML/JSON) with the core game engine logic.
The system aims to manage:
- Activation: Determining when and if a card effect can be activated.
- Chaining: Building a sequence of effects when multiple effects are activated in response to each other.
- Resolution: Executing the effects in the correct (Last-In, First-Out) order.
- Targeting & Conditions: Handling player choices for targets and verifying activation conditions.
- State Updates: Ensuring the game state is correctly modified as effects resolve.
- Effect: A specific action or sequence of actions performed by a card (e.g., "Draw 2 cards", "Destroy 1 monster"). Effects are defined declaratively (partially via YAML/JSON).
- Trigger: An event or condition in the game that allows a card effect to be activated (e.g.,
onSummon
,onAttackDeclaration
,onActivation
). - Condition: A prerequisite that must be met for an effect to be activated or resolved (e.g.,
IsPhase: Main1
,HasCards: GraveyardPlayer
). - Targeting: The process of selecting specific cards, zones, or players that an effect will interact with. Can be
Manual
(player selection) orAutomatic
. - Action: A basic operation performed by an effect (e.g.,
MoveCard
,DestroyCards
,ModifyLifePoints
,DrawCards
). - Chain: A sequence of card effects activated in response to each other. Effects on the chain resolve in reverse order of activation (LIFO).
- Spell Speed: A rule determining which effects can respond to others on a chain (Spell Speed 1 < Spell Speed 2 < Spell Speed 3).
- Resolution: The process of executing an effect's actions after any chaining is complete.
GameEngine
(src/engine/GameEngine.ts
):- The central authority for game state and rules.
- Receives actions (like
ACTIVATE_EFFECT
) via itsdispatch
method. - Validates actions using
RuleValidator
. - Processes valid actions, often delegating to specialized handlers (
ChainResolver
,EffectExecutor
). - Manages the core game loop (phases, turns).
- Emits
STATE_PATCH
events reflecting authoritative state changes. - Emits other domain-specific events (e.g.,
ATTACK_TARGETS
,CHAIN_LINK_ADDED
,EFFECT_RESOLVED
).
ChainResolver
(src/engine/ChainResolver.ts
):- Manages the building and resolution of chains.
- Tracks chain links, player priority, and chain status (
Idle
,ChainBuilding
,WaitingForResponse
,ChainResolving
). - Calls the
EffectExecutor
to resolve each link in LIFO order. - Emits chain-related events (
CHAIN_LINK_ADDED
,CHAIN_RESOLVING
,CHAIN_RESOLVED
).
EffectExecutor
(src/engine/EffectExecutor.ts
):- Orchestrates the execution of a single card effect.
- Retrieves effect definitions via
CardEffectRegistry
. - Handles effect steps sequentially: condition checking, targeting, action execution.
- Uses
PromptManager
for player interactions (targeting). - Uses
ActionRegistry
,FilterRegistry
,ConditionRegistry
to execute declarative steps. - Provides an
EngineAPI
for actions to interact back with the engine (e.g., queueing state changes or dispatching further actions).
CardEffectRegistry
(src/engine/CardEffectRegistry.ts
):- Central point for retrieving effect definitions for a given card ID/GUID.
- Interfaces with
CardEffectLoader
to load definitions as needed.
CardEffectLoader
(src/engine/cardEffects/CardEffectLoader.ts
):- Loads card effect definitions (currently from JSON files generated from YAML) on demand.
- Uses
CardEffectIndex
to map card IDs to filenames. - Caches loaded effects.
EffectContext
(src/engine/effects/EffectContext.ts
):- Provides contextual information during an effect's execution (activating player, card, targets, chain link number, etc.).
- Holds references to the engine for state queries.
- Registries (
src/engine/effects/*Registry.ts
):ActionRegistry
: Contains functions implementing basic effect actions (MoveCard, DestroyCards, etc.).ConditionRegistry
: Contains functions implementing effect conditions.FilterRegistry
: Contains functions implementing target filters.
PromptManager
(src/engine/effects/PromptManager.ts
):- Handles requests for player input during effect resolution (e.g., selecting targets, choosing options).
- Interacts with the
GameEngine
to emit prompt events (PROMPT_PLAYER_ACTION
).
- UI Components (
src/components/
):GameBoardUI
,Zone
,HandDisplay
: Capture user interactions (clicks, drags) that trigger effect activations.PromptHandler
: Listens for prompt events and displays UI prompts.ChainStack
: Visualizes the current chain based on store updates.CardInfoPanel
: Displays details of hovered cards.
- Stores (
src/store/
):GameStore
: Maintains the primary game state for React components, updated mainly viaSTATE_PATCH
events from theGameEngine
. Handles resolving prompts initiated by the engine.UIStore
: Manages UI-specific state (highlights, animations, toasts). Processes domain events from the engine for visual feedback.
- User Interaction: The player interacts with a card (e.g., clicks "Activate" on a card in a
Zone
, drags a Spell fromHandDisplay
to aZone
). - Action Dispatch: The UI component (
Zone.tsx
orGameBoardUI.tsx
viauseGameInterface
) callsdispatch
on theGameEngine
with anACTIVATE_EFFECT
action, includingplayerId
,cardId
, potentiallyzoneId
(if activating to the field), andguid
(passcode for effect lookup). - Engine Validation:
GameEngine.validateAction
checks basic rules (correct phase, player turn, etc.). If invalid, anACTION_REJECTED
event is emitted. - Engine Processing:
GameEngine.processAction
handles theACTIVATE_EFFECT
action.- If activating from hand to field (e.g., a Spell), it may first queue/dispatch a
MOVE_CARD
action. - It calls
Actions.activateEffect
to generate theEFFECT_ACTIVATED
domain event. - Crucially, it initiates the chain process by calling
chainResolver.startChain
.
- If activating from hand to field (e.g., a Spell), it may first queue/dispatch a
- Chain Initiation:
ChainResolver.startChain
:- Creates the first
ChainLink
using details from theEFFECT_ACTIVATED
event (cardId, playerId, effectId/guid, targets). - Adds the link to its internal
chainLinks
array. - Sets its status to
ChainBuilding
. - Sets priority to the opponent.
- Emits a
CHAIN_LINK_ADDED
event via theGameEngine
.
- Creates the first
- Event Propagation: The
GameEngine
emitsEFFECT_ACTIVATED
andCHAIN_LINK_ADDED
. - Store Updates:
UIStore
receivesCHAIN_LINK_ADDED
and updates the visualChainStack
. It might also trigger pulsation/highlight viaEFFECT_ACTIVATED
.GameStore
receives the events but primarily updates its internalchainState
based onSTATE_PATCH
events (which the engine should also emit based on the chain state change, though this link isn't explicit in the provided code but is implied by the architecture).
- Priority & Response: The engine, guided by the
ChainResolver
, now needs to prompt the opponent (or the next player in priority) to respond. This leads to the Chain Building flow.
- Prompt for Response: After a chain link is added, the
GameEngine
(likely triggered byChainResolver
state changes or explicit calls) uses thePromptManager
to request a response from the player with priority. - Prompt Emission:
PromptManager.requestChainResponse
(or similar) emits aPROMPT_PLAYER_ACTION
orCHAIN_RESPONSE_AVAILABLE
event via theGameEngine
. - UI Prompt:
GameStore
receives the prompt event and updates its state (isWaiting
,waitingPrompt
,currentPrompt
).PromptHandler
component renders the prompt (e.g., "Respond with an effect?" Yes/No or card selection).
- Player Response: The user interacts with the prompt.
- Resolve Prompt: The UI calls
GameStore.resolvePrompt
, providing the user's choice ('Pass' or 'Activate' with card details). - Prompt Resolution:
GameStore.resolvePrompt
callsPromptManager.resolvePrompt
/resolveChainResponse
, which fulfills the promise the engine was waiting on. - Engine Receives Response: The
GameEngine
receives the resolved response. - Dispatch Response Action: The engine dispatches a
RESPOND_TO_CHAIN
action. - Process Response Action:
GameEngine.processAction
handlesRESPOND_TO_CHAIN
.- If 'Pass': Calls
chainResolver.passPriority
.- If the other player had already passed,
chainResolver.resolveChain
is called (Go to Resolution Flow). - If this is the first pass, priority shifts to the other player, and the process repeats from Step 1 for that player.
- If the other player had already passed,
- If 'Activate': Calls
chainResolver.addLink
with the new card's details.- A new
CHAIN_LINK_ADDED
event is emitted. - Priority shifts to the other player.
- The process repeats from Step 1 for the other player.
- A new
- If 'Pass': Calls
- Trigger Resolution:
ChainResolver.resolveChain
is called when both players consecutively pass priority. - Status Update:
ChainResolver
sets status toChainResolving
, emitsCHAIN_RESOLVING
(start) event. - Process Links (LIFO):
ChainResolver.processChainLinks
starts processing thechainLinks
array in reverse order. - For Each Link:
- Emit
CHAIN_RESOLVING
event for the specific link number. - Create
EffectContext
for the link (includes cardId, playerId, targets, link number). - Call
effectExecutor.executeCardEffect
with the card identifier (GUID/passcode or ID) and context.
- Emit
- Execute Effect:
EffectExecutor.executeCardEffect
:- Uses
CardEffectRegistry.getEffectsForCard
to load the effect definition (usingCardEffectLoader
if needed). - Iterates through the
effects
steps defined in the loaded effect JSON/YAML.
- Uses
- Execute Step:
EffectExecutor.executeEffectStep
:- Conditions: Checks step
conditions
usingConditionRegistry
. If fail, skips step. - Targeting: If
targeting
exists, callsEffectExecutor.executeTargeting
.- Gets eligible targets using
FilterRegistry
. - If
Manual
, usesPromptManager.requestPrompt
to ask the player to select targets. - Stores selected targets in the
EffectContext
. If targeting fails (e.g., not enough valid targets, player cancels), the effect step may fail.
- Gets eligible targets using
- Actions: Executes each
action
sequentially usingActionRegistry.getAction
.
- Conditions: Checks step
- Execute Action: The specific
ActionFunction
(e.g.,moveCardAction
,destroyCardsAction
) is called withparams
,context
, andapi
. - Engine API Interaction: The
ActionFunction
uses theEngineAPI
methods (e.g.,api.requestMoveCard
). - Engine Queues/Dispatches: The
EngineAPI
methods either queue state changes or, more likely based on the code, dispatch new actions (e.g.,MOVE_CARD
,MODIFY_LP
) back to theGameEngine
. - Inner Dispatch Loop: The
GameEngine
receives and processes these new actions (MOVE_CARD
, etc.), generating domain events (CARD_MOVED
, etc.) and correspondingSTATE_PATCH
events. These are emitted immediately. - Effect Step Completion: Control returns to
EffectExecutor
after all actions in a step complete. It moves to the next step or finishes the effect. - Link Resolution: Control returns to
ChainResolver
aftereffectExecutor.executeCardEffect
finishes. - Effect Resolved Event:
ChainResolver
emitsEFFECT_RESOLVED
via theGameEngine
. - Post-Resolution Handling:
GameEngine.handleEffectResolvedEvent
checks if the resolved card (e.g., a Normal Spell) should be sent to the Graveyard and dispatches aMOVE_CARD
action after a delay if necessary. - Next Link:
ChainResolver.processChainLinks
waits briefly and proceeds to the next link (next highest number). - Chain Completion: After all links are processed,
ChainResolver.processChainLinks
resolves. - Chain Resolved Event:
ChainResolver
emitsCHAIN_RESOLVED
event viaGameEngine
, resets its state toIdle
.
- Action: User clicks "Activate" on Pot of Greed in hand ->
dispatch({ type: 'ACTIVATE_EFFECT', payload: { cardId: 'pog-1', zoneId: 'player-spell-0', guid: '55144522' } })
- Engine: Validates action. Processes
ACTIVATE_EFFECT
. - Engine: Queues/Dispatches
MOVE_CARD
(Hand -> Field Zoneplayer-spell-0
). - Engine: Processes
MOVE_CARD
, generatesCARD_MOVED
event andSTATE_PATCH
for the move. Emits both. - GameStore/UIStore: Receive
CARD_MOVED
,STATE_PATCH
. UIStore triggers animation. GameStore updates state. Card appears face-up inplayer-spell-0
. - Engine: Calls
Actions.activateEffect
, generatesEFFECT_ACTIVATED
event. - Engine: Calls
chainResolver.startChain
. - ChainResolver: Creates Chain Link 1 (Pot of Greed). Sets status
ChainBuilding
. Priority to opponent. EmitsCHAIN_LINK_ADDED
via Engine. - Engine: Emits
EFFECT_ACTIVATED
,CHAIN_LINK_ADDED
. - Stores: UIStore updates ChainStack.
- Engine: Prompts opponent to respond (via
PromptManager
). EmitsPROMPT_PLAYER_ACTION
. - AI/Player: Opponent chooses 'Pass'.
- Engine:
dispatch({ type: 'RESPOND_TO_CHAIN', payload: { responseType: 'Pass', playerId: 'opponent' } })
. - Engine: Calls
chainResolver.passPriority
. Priority shifts to player. - Engine: Prompts player to respond. Emits
PROMPT_PLAYER_ACTION
. - UI/Player: Player chooses 'Pass'. UI calls
GameStore.resolvePrompt
. - Engine:
dispatch({ type: 'RESPOND_TO_CHAIN', payload: { responseType: 'Pass', playerId: 'player' } })
. - Engine: Calls
chainResolver.passPriority
. Both passed -> callschainResolver.resolveChain
. - ChainResolver: Sets status
ChainResolving
. EmitsCHAIN_RESOLVING
(start). StartsprocessChainLinks
. - ChainResolver: Processes Link 1 (Pot of Greed). Emits
CHAIN_RESOLVING
(link 1). CallseffectExecutor.executeCardEffect('55144522', context)
. - EffectExecutor: Loads Pot of Greed effect (
drawCards
action, count 2). Executes the action. - Action (
drawCardsAction
): Usesapi.requestDrawCard('player', 2)
. - EngineAPI: Dispatches
DRAW_CARD
action back to the engine. - Engine: Processes
DRAW_CARD
. GeneratesCARD_DRAWN
event andSTATE_PATCH
to update deck/hand. Emits both. - GameStore/UIStore: Receive
CARD_DRAWN
,STATE_PATCH
. Update state. UIStore may animate card draw. - EffectExecutor: Effect step completes.
- ChainResolver: Receives completion from executor. Emits
EFFECT_RESOLVED
via Engine. - Engine: Handles
EFFECT_RESOLVED
. Pot of Greed is a Normal Spell, so dispatchesMOVE_CARD
(Field Zone -> Graveyard) after a delay. - ChainResolver:
processChainLinks
finishes. EmitsCHAIN_RESOLVED
via Engine. Resets state toIdle
. - Engine: (After delay) Processes
MOVE_CARD
for Pot of Greed -> Graveyard. GeneratesCARD_MOVED
andSTATE_PATCH
. Emits both. - GameStore/UIStore: Receive events, update state, animate card going to GY.
- The system uses dynamic imports (
import()
) managed byCardEffectLoader
. CardEffectIndex
maps card identifiers (like passcodes '55144522') to the base filename of their JSON definition (e.g., 'PotOfGreed').- When
EffectExecutor
needs an effect, it asksCardEffectRegistry
, which uses theCardEffectLoader
to load the corresponding JSON file fromsrc/engine/cardEffects/json/
if not already cached. - (Implicitly) These JSON files are assumed to be generated/compiled from the YAML definitions during a build step, although the compiler itself is not fully implemented in the provided code.
- The
GameEngine
attempts to preload effects for cards in the initial decks usingCardEffectRegistry.initializeCardEffects
when theINITIALIZE_GAME
action occurs.