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 itsdispatchmethod. - Validates actions using
RuleValidator. - Processes valid actions, often delegating to specialized handlers (
ChainResolver,EffectExecutor). - Manages the core game loop (phases, turns).
- Emits
STATE_PATCHevents 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
EffectExecutorto 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
PromptManagerfor player interactions (targeting). - Uses
ActionRegistry,FilterRegistry,ConditionRegistryto execute declarative steps. - Provides an
EngineAPIfor 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
CardEffectLoaderto load definitions as needed.
CardEffectLoader(src/engine/cardEffects/CardEffectLoader.ts):- Loads card effect definitions (currently from JSON files generated from YAML) on demand.
- Uses
CardEffectIndexto 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
GameEngineto 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_PATCHevents 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 fromHandDisplayto aZone). - Action Dispatch: The UI component (
Zone.tsxorGameBoardUI.tsxviauseGameInterface) callsdispatchon theGameEnginewith anACTIVATE_EFFECTaction, includingplayerId,cardId, potentiallyzoneId(if activating to the field), andguid(passcode for effect lookup). - Engine Validation:
GameEngine.validateActionchecks basic rules (correct phase, player turn, etc.). If invalid, anACTION_REJECTEDevent is emitted. - Engine Processing:
GameEngine.processActionhandles theACTIVATE_EFFECTaction.- If activating from hand to field (e.g., a Spell), it may first queue/dispatch a
MOVE_CARDaction. - It calls
Actions.activateEffectto generate theEFFECT_ACTIVATEDdomain 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
ChainLinkusing details from theEFFECT_ACTIVATEDevent (cardId, playerId, effectId/guid, targets). - Adds the link to its internal
chainLinksarray. - Sets its status to
ChainBuilding. - Sets priority to the opponent.
- Emits a
CHAIN_LINK_ADDEDevent via theGameEngine.
- Creates the first
- Event Propagation: The
GameEngineemitsEFFECT_ACTIVATEDandCHAIN_LINK_ADDED. - Store Updates:
UIStorereceivesCHAIN_LINK_ADDEDand updates the visualChainStack. It might also trigger pulsation/highlight viaEFFECT_ACTIVATED.GameStorereceives the events but primarily updates its internalchainStatebased onSTATE_PATCHevents (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 byChainResolverstate changes or explicit calls) uses thePromptManagerto request a response from the player with priority. - Prompt Emission:
PromptManager.requestChainResponse(or similar) emits aPROMPT_PLAYER_ACTIONorCHAIN_RESPONSE_AVAILABLEevent via theGameEngine. - UI Prompt:
GameStorereceives the prompt event and updates its state (isWaiting,waitingPrompt,currentPrompt).PromptHandlercomponent 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.resolvePromptcallsPromptManager.resolvePrompt/resolveChainResponse, which fulfills the promise the engine was waiting on. - Engine Receives Response: The
GameEnginereceives the resolved response. - Dispatch Response Action: The engine dispatches a
RESPOND_TO_CHAINaction. - Process Response Action:
GameEngine.processActionhandlesRESPOND_TO_CHAIN.- If 'Pass': Calls
chainResolver.passPriority.- If the other player had already passed,
chainResolver.resolveChainis 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.addLinkwith the new card's details.- A new
CHAIN_LINK_ADDEDevent 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.resolveChainis called when both players consecutively pass priority. - Status Update:
ChainResolversets status toChainResolving, emitsCHAIN_RESOLVING(start) event. - Process Links (LIFO):
ChainResolver.processChainLinksstarts processing thechainLinksarray in reverse order. - For Each Link:
- Emit
CHAIN_RESOLVINGevent for the specific link number. - Create
EffectContextfor the link (includes cardId, playerId, targets, link number). - Call
effectExecutor.executeCardEffectwith the card identifier (GUID/passcode or ID) and context.
- Emit
- Execute Effect:
EffectExecutor.executeCardEffect:- Uses
CardEffectRegistry.getEffectsForCardto load the effect definition (usingCardEffectLoaderif needed). - Iterates through the
effectssteps defined in the loaded effect JSON/YAML.
- Uses
- Execute Step:
EffectExecutor.executeEffectStep:- Conditions: Checks step
conditionsusingConditionRegistry. If fail, skips step. - Targeting: If
targetingexists, callsEffectExecutor.executeTargeting.- Gets eligible targets using
FilterRegistry. - If
Manual, usesPromptManager.requestPromptto 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
actionsequentially usingActionRegistry.getAction.
- Conditions: Checks step
- Execute Action: The specific
ActionFunction(e.g.,moveCardAction,destroyCardsAction) is called withparams,context, andapi. - Engine API Interaction: The
ActionFunctionuses theEngineAPImethods (e.g.,api.requestMoveCard). - Engine Queues/Dispatches: The
EngineAPImethods 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
GameEnginereceives and processes these new actions (MOVE_CARD, etc.), generating domain events (CARD_MOVED, etc.) and correspondingSTATE_PATCHevents. These are emitted immediately. - Effect Step Completion: Control returns to
EffectExecutorafter all actions in a step complete. It moves to the next step or finishes the effect. - Link Resolution: Control returns to
ChainResolveraftereffectExecutor.executeCardEffectfinishes. - Effect Resolved Event:
ChainResolveremitsEFFECT_RESOLVEDvia theGameEngine. - Post-Resolution Handling:
GameEngine.handleEffectResolvedEventchecks if the resolved card (e.g., a Normal Spell) should be sent to the Graveyard and dispatches aMOVE_CARDaction after a delay if necessary. - Next Link:
ChainResolver.processChainLinkswaits briefly and proceeds to the next link (next highest number). - Chain Completion: After all links are processed,
ChainResolver.processChainLinksresolves. - Chain Resolved Event:
ChainResolveremitsCHAIN_RESOLVEDevent 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_MOVEDevent andSTATE_PATCHfor 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_ACTIVATEDevent. - Engine: Calls
chainResolver.startChain. - ChainResolver: Creates Chain Link 1 (Pot of Greed). Sets status
ChainBuilding. Priority to opponent. EmitsCHAIN_LINK_ADDEDvia 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 (
drawCardsaction, count 2). Executes the action. - Action (
drawCardsAction): Usesapi.requestDrawCard('player', 2). - EngineAPI: Dispatches
DRAW_CARDaction back to the engine. - Engine: Processes
DRAW_CARD. GeneratesCARD_DRAWNevent andSTATE_PATCHto 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_RESOLVEDvia Engine. - Engine: Handles
EFFECT_RESOLVED. Pot of Greed is a Normal Spell, so dispatchesMOVE_CARD(Field Zone -> Graveyard) after a delay. - ChainResolver:
processChainLinksfinishes. EmitsCHAIN_RESOLVEDvia Engine. Resets state toIdle. - Engine: (After delay) Processes
MOVE_CARDfor Pot of Greed -> Graveyard. GeneratesCARD_MOVEDandSTATE_PATCH. Emits both. - GameStore/UIStore: Receive events, update state, animate card going to GY.
- The system uses dynamic imports (
import()) managed byCardEffectLoader. CardEffectIndexmaps card identifiers (like passcodes '55144522') to the base filename of their JSON definition (e.g., 'PotOfGreed').- When
EffectExecutorneeds an effect, it asksCardEffectRegistry, which uses theCardEffectLoaderto 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
GameEngineattempts to preload effects for cards in the initial decks usingCardEffectRegistry.initializeCardEffectswhen theINITIALIZE_GAMEaction occurs.