references/expert-card-patterns.md
# Expert patterns (load on demand)
> **MANDATORY** when implementing beyond Golden Path / Decision Trees. Do not paste into SKILL.md or scenes from memory.
## Architecture Overview
### 1. Card Data (Resource-based)
Godot Resources are perfect for card data.
```gdscript
# card_data.gd
extends Resource
class_name CardData
enum Type { ATTACK, SKILL, POWER }
enum Target { ENEMY, SELF, ALL_ENEMIES }
@export var id: String
@export var name: String
@export_multiline var description: String
@export var cost: int
@export var type: Type
@export var target_type: Target
@export var icon: Texture2D
@export var effect_script: Script # Custom logic per card
```
### 2. Deck Manager
Handles the piles: Draw Pile, Hand, Discard Pile, Exhaust Pile.
```gdscript
# deck_manager.gd
var draw_pile: Array[CardData] = []
var hand: Array[CardData] = []
var discard_pile: Array[CardData] = []
func draw_cards(amount: int) -> void:
for i in amount:
if draw_pile.is_empty():
reshuffle_discard()
if draw_pile.is_empty():
break # No cards left
var card = draw_pile.pop_back()
hand.append(card)
card_drawn.emit(card)
func reshuffle_discard() -> void:
draw_pile.append_array(discard_pile)
discard_pile.clear()
draw_pile.shuffle()
```
### 3. Card Visual (UI)
The interactive node representing a card in hand.
```gdscript
# card_ui.gd
extends Control
var card_data: CardData
var start_pos: Vector2
var is_dragging: bool = false
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
start_drag()
else:
end_drag()
func _process(delta: float) -> void:
if is_dragging:
global_position = get_global_mouse_position() - size / 2
else:
# Hover effect or return to hand position
pass
```
## Key Mechanics Implementation
### Effect Resolution (Command Pattern)
Decouple the "playing" of a card from its "effect".
```gdscript
func play_card(card: CardData, target: Node) -> void:
if current_energy < card.cost:
show_error("Not enough energy")
return
current_energy -= card.cost
# Execute effect
var effect = card.effect_script.new()
effect.execute(target)
move_to_discard(card)
```
### Hand Layout (Arching)
Cards in hand usually form an arc. Use a math formula (Bezier or Circle) to position them based on `index` and `total_cards`.
```gdscript
func update_hand_visuals() -> void:
var center_x = screen_width / 2
var radius = 1000.0
var angle_step = 5.0
for i in hand_visuals.size():
var card = hand_visuals[i]
var angle = deg_to_rad((i - hand_visuals.size() / 2.0) * angle_step)
var target_pos = Vector2(
center_x + sin(angle) * radius,
screen_height + cos(angle) * radius
)
card.target_rotation = angle
card.target_position = target_pos
```
## Godot-Specific Tips
* **MouseFilter**: Getting drag/drop to work with overlapping UI requires careful setup of `mouse_filter` (Pass vs Stop).
* **Z-Index**: Use `z_index` or `CanvasLayer` to ensure the dragged card is always on top of everything else.
* **Tweens**: Essential! Tween position, rotation, and scale for that "juicy" Hearthstone/Slay the Spire feel.
---
## 🚀 Elite Technical Implementations (Batch 09)
### 1. Holographic Foil (Shader Script)
Add visual rarity and "juice" to cards using a holographic shader. This script uses iridescence based on TIME and UV coordinates to create a shifting rainbow effect.
```glsl
shader_type canvas_item;
uniform float foil_speed : hint_range(0.1, 5.0) = 1.0;
uniform float foil_intensity : hint_range(0.0, 1.0) = 0.5;
void fragment() {
vec4 base_color = texture(TEXTURE, UV);
// Create shifting rainbow iridescence
vec3 holo_color = vec3(
0.5 + 0.5 * sin(TIME * foil_speed + UV.x * 10.0),
0.5 + 0.5 * sin(TIME * foil_speed + UV.y * 10.0 + 2.0),
0.5 + 0.5 * sin(TIME * foil_speed + (UV.x + UV.y) * 10.0 + 4.0)
);
// Blend with base texture alpha
COLOR = vec4(mix(base_color.rgb, holo_color, foil_intensity * base_color.a), base_color.a);
}
```
### 2. Card-History Logging (Action Tracking)
Track card actions (Played, Drawn, Discarded) for a history panel using a custom `Logger`. This intercepts messages tagged with `[CARD]` and routes them to a turn history buffer.
```gdscript
class_name CardHistoryLogger extends Logger
signal history_updated(entry: String)
var turn_history: Array[String] = []
func _log_message(message: String, error: bool) -> void:
if not error and message.begins_with("[CARD]"):
turn_history.append(message)
history_updated.emit(message)
# To register (in an Autoload):
# func _init(): OS.add_logger(CardHistoryLogger.new())
```
### 3. Hand-Limit Logic (Over-Draw Protection)
Encapsulate hand data and enforce a maximum size. Use signals to notify the UI when a card is successfully drawn or discarded due to being overdrawn.
```gdscript
class_name HandManager extends Node
signal card_drawn(card: Resource)
signal card_overdrawn(card: Resource)
@export var max_hand_size: int = 10
var _current_hand: Array[Resource] = []
func draw_card(new_card: Resource) -> void:
if _current_hand.size() >= max_hand_size:
# Hand is full; trigger overdraw
card_overdrawn.emit(new_card)
else:
_current_hand.append(new_card)
card_drawn.emit(new_card)
```
references/migration-notes.md
# Migration notes: godot-genre-card-game
Incremental upgrade for topics this skill covers. Apply **one hop**, stabilize/test, then next. Never skip hops.
If the project is **< 4.0**, follow [godot-version-migration](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-version-migration/SKILL.md) era bridges (legacy → 3→4) until 4.0, then these hops. Official 3→4: [Upgrading from Godot 3 to Godot 4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.html).
## 4.0 → 4.1
Official: [Upgrading to Godot 4.1](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.1.html)
- `SubViewportContainer.mouse_filter` must be STOP/PASS for input to reach SubViewports.
- Layered SubViewportContainers needing mouse input may need Area2D replacements.
- `CodeEdit.add_code_completion_option` gains `location`; Tree `edit_selected` gains `force_edit`.
## 4.1 → 4.2
Official: [Upgrading to Godot 4.2](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.2.html)
- `PopupMenu` shortcut helpers gain `allow_echo`; `clear` gains `free_submenus`.
- GraphEdit: `arrange_nodes_button_hidden` → `show_arrange_button`; snap props renamed; `get_zoom_hbox` → `get_menu_hbox`.
- GraphNode: large API move to `GraphElement`; connection query methods removed; `comment`/`show_close` removed.
## 4.2 → 4.3
Official: [Upgrading to Godot 4.3](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.3.html)
- If the genre uses TileMap, migrate to TileMapLayer nodes before relying on layer APIs.
- If the genre ships multiplayer, upgrade all peers to 4.3 together (SceneMultiplayer protocol).
- Default font outline color is black (was white).
- `auto_translate` deprecated for Node `auto_translate_mode` (inherit semantics).
- `AcceptDialog` register/remove helpers take LineEdit/Button specifically.
## 4.3 → 4.4
Official: [Upgrading to Godot 4.4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.4.html)
- `GraphEdit.connect_node` gains `keep_alive`; `frame_rect_changed` uses `Rect2`.
## 4.4 → 4.5
Official: [Upgrading to Godot 4.5](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.5.html)
- CanvasItem/Font/TextLine draw APIs gain optional `oversampling`.
- `TreeItem.add_button` gains `alt_text`.
- Resource deep-duplicate and UID export-file changes affect inventory/quest/economy Resource graphs.
## 4.5 → 4.6
Official: [Upgrading to Godot 4.6](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.6.html)
- Retune Environment glow/fog if the genre leans on bloom-heavy looks.
- `Control.grab_focus` / `has_focus` gain hide-focus options.
- `FileDialog.add_filter` gains `mime_type`; `SplitContainer.clamp_split_offset` gains `priority_index`.
- `EditorFileDialog` file APIs moved onto `FileDialog` base; `add_side_menu` removed.
- `PopupMenu.submenu_popup_delay` default 0.2 (was 0.3).
## 4.6 → 4.7
Official: [Upgrading to Godot 4.7](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.7.html)
- Confirm project stretch mode and AudioStreamPlayer area_mask after opening in 4.7.
- `Control.accessibility_live` uses `AccessibilityServer.AccessibilityLiveMode`.
- `TreeItem.select` gains `set_as_cursor`.
- `CanvasItem` no longer adds antialiasing feather that thickened lines — widen strokes if visuals relied on it.
- Re-validate Resource pipelines after packed-array setter and typed-return GDScript changes.
scripts/board_query_filter.gd
# board_query_filter.gd
# Using functional filtering to query card states
extends Node
# EXPERT NOTE: Array.filter() is highly efficient for
# logic like "Find all Taunt cards with health > 2".
func find_taunters(board_cards: Array[Node]) -> Array[Node]:
return board_cards.filter(func(card):
return card.get("is_taunt") == true and card.health > 2
)
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_array.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md — filter/Callable board queries without index bugs
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md — keyword flags (Taunt) consumed by filters
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/board_state_dictionary.gd
# board_state_dictionary.gd
# Tracking card positions via typed dictionaries
extends Node
# EXPERT NOTE: Dictionaries mapping Vector2i to CardData
# are better for board logic than 2D Godot node arrays.
var board: Dictionary = {} # Vector2i -> CardData
func place_card(coord: Vector2i, card: CardData):
if !board.has(coord):
board[coord] = card
print("Card placed at ", coord)
func get_card_at(coord: Vector2i) -> CardData:
return board.get(coord)
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html
# - https://docs.godotengine.org/en/stable/classes/class_array.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md — Dictionary[Vector2i] board models vs node order
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — store CardData refs, not Control nodes
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/card_data_resource.gd
# card_data_resource.gd
# Defining cards as lightweight data-driven Resources
class_name CardData extends Resource
# EXPERT NOTE: Resources allow designers to edit card stats
# in the Godot Inspector, saving them as .tres files.
@export var card_name: String = "Blank"
@export var mana_cost: int = 1
@export var attack: int = 0
@export var health: int = 1
# Setter with changed emission for reactive UI
func update_stats(new_atk, new_hp):
attack = new_atk
health = new_hp
emit_changed()
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html
# - https://docs.godotengine.org/en/stable/classes/class_resource.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — duplicate-before-mutate for match buffs
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — Resource.changed → reactive card faces
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/card_data.gd
# skills/genre-card-game/scripts/card_data.gd
extends Resource
## Card Data Resource (Expert Pattern)
## Data definition for cards. Can be created in Inspector.
class_name CardData
enum CardType { ATTACK, SKILL, POWER, CURSE }
enum TargetType { ENEMY, SELF, ALL_ENEMIES, NONE }
@export_group("Visuals")
@export var id: String
@export var name: String
@export_multiline var description: String
@export var icon: Texture2D
@export_group("Stats")
@export var cost: int = 1
@export var type: CardType = CardType.ATTACK
@export var target: TargetType = TargetType.ENEMY
@export var value: int = 0 # Generic value (Damage, Block amount)
@export_group("Behavior")
@export var script_logic: Script # Optional: Attach custom script for unique effects
func get_modified_cost(player_stats: Dictionary) -> int:
# Hook for cost reduction logic
return cost
## EXPERT USAGE:
## Right-click FileSystem -> Create New -> Resource -> CardData.
## Fill in fields. Load these Resources into DeckManager.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html
# - https://docs.godotengine.org/en/stable/classes/class_resource.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — .tres CardData authorship and emit_changed
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md — enums/exports for Inspector-safe definitions
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/card_drag_drop.gd
# card_drag_drop.gd
# Native Control node drag-and-drop implementation
extends Control
# EXPERT NOTE: Using Godot's built-in drag API ensures
# consistency and handles OS-level cursor and window events.
func _get_drag_data(_at_position: Vector2):
var preview = Label.new()
preview.text = name
set_drag_preview(preview)
return self # Pass card data or node to the drop target
func _can_drop_data(_pos: Vector2, _data):
return _data is Control # Basic validation
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_control.html
# - https://docs.godotengine.org/en/stable/tutorials/ui/custom_gui_controls.html
# - https://docs.godotengine.org/en/stable/tutorials/inputs/mouse_and_input_coordinates.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md — pointer vs shortcut/accessibility drag paths
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ui-containers/SKILL.md — mouse_filter / z_index while dragging
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/card_effect_resolution.gd
# skills/genre-card-game/scripts/card_effect_resolution.gd
extends Node
## Card Effect Resolution (Expert Pattern)
## Implements a LIFO Command stack for resolving card effects and nested reactions.
## Allows for complex chains, counter-play, and sequential animations.
class_name CardEffectResolution
signal effect_started(effect: CardEffect)
signal effect_finished(effect: CardEffect)
signal queue_empty
var effect_queue: Array[CardEffect] = []
var is_resolving: bool = false
# Inner class or external resource for Effect
class CardEffect:
var source_card: Resource
var target: Node
var type: String # DAMAGE, HEAL, DRAW
var value: int
func execute() -> void:
# Override this in subclasses
pass
func add_effect(effect: CardEffect) -> void:
effect_queue.append(effect)
if not is_resolving:
_resolve_next()
func _resolve_next() -> void:
if effect_queue.is_empty():
is_resolving = false
queue_empty.emit()
return
is_resolving = true
var effect = effect_queue.pop_back() # LIFO stack (reactions resolve last-in-first-out)
effect_started.emit(effect)
# Execute logic
await _execute_effect_logic(effect)
effect_finished.emit(effect)
# Recursive next
_resolve_next()
func _execute_effect_logic(effect: CardEffect) -> void:
# In a full system, this would call effect.execute()
# Here we simulate with a match or generic handler
print("Resolving Effect: %s on %s" % [effect.type, effect.target])
match effect.type:
"DAMAGE":
if effect.target.has_method("take_damage"):
effect.target.take_damage(effect.value)
"HEAL":
if effect.target.has_method("heal"):
effect.target.heal(effect.value)
# Fake animation delay
await get_tree().create_timer(0.5).timeout
## EXPERT USAGE:
## When playing a card, instantiate CardEffect and pass to add_effect().
## Listen to signals to block UI during resolution.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html
# - https://docs.godotengine.org/en/stable/classes/class_array.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — effect_started/finished UI locks
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md — keyword effects pushed as stack commands
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-combat-system/SKILL.md — damage/heal targets during resolve
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/card_history_logger.gd
# card_history_logger.gd
class_name CardHistoryLogger extends Logger
signal history_updated(entry: String)
var turn_history: Array[String] = []
func _log_message(message: String, error: bool) -> void:
if not error and message.begins_with("[CARD]"):
turn_history.append(message)
history_updated.emit(message)
# To register (in an Autoload):
# func _init(): OS.add_logger(CardHistoryLogger.new())
scripts/card_tween_manager.gd
# card_tween_manager.gd
# Managing fluent and interruptible card animations
extends Node
# EXPERT NOTE: Always assign Tweens to variables to allow
# kill() or parallel() management if board state changes fast.
func play_to_board(card: Control, target_pos: Vector2):
var tween = create_tween().set_trans(Tween.TRANS_QUART).set_ease(Tween.EASE_OUT)
tween.tween_property(card, "global_position", target_pos, 0.4)
# Interruptible: if card is destroyed, this tween stays clean.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_tween.html
# - https://docs.godotengine.org/en/stable/classes/class_control.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-tweening/SKILL.md — kill/parallel patterns for interruptible card juice
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ui-containers/SKILL.md — tween global_position vs layout containers
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/deck_builder_validator.gd
# deck_builder_validator.gd
# Enforcing rules during card collection management
extends Node
# EXPERT NOTE: Use for validating "Max 3 copies of a card"
# or "Total mana curve" constraints.
func is_deck_valid(deck: Array[CardData]) -> bool:
if deck.size() != 30: return false
var counts = {}
for card in deck:
counts[card.card_name] = counts.get(card.card_name, 0) + 1
if counts[card.card_name] > 2: return false # Duplicate limit
return true
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_array.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-inventory-system/SKILL.md — owned-card collection feeding builder rules
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md — validate mana curves against sim win-rates
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-economy-system/SKILL.md — pack/shop costs that constrain deck construction
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/deck_shuffle_bag.gd
# deck_shuffle_bag.gd
# Secure deck randomization using the "Shuffle Bag" pattern
extends Node
# EXPERT NOTE: Shuffle-bag logic prevents streaks of bad luck
# by ensuring a uniform distribution over the deck lifetime.
var deck: Array[CardData] = []
var rng := RandomNumberGenerator.new()
func _ready():
rng.randomize()
func shuffle_deck():
deck.shuffle() # Engine-level randomized shuffle
func draw_card() -> CardData:
return deck.pop_back() if !deck.is_empty() else null
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/math/random_number_generation.html
# - https://docs.godotengine.org/en/stable/classes/class_randomnumbergenerator.html
# - https://docs.godotengine.org/en/stable/classes/class_array.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md — measure streak risk vs shuffle-bag fairness
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md — typed Array pile ops (shuffle/pop_back)
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/hand_manager.gd
# hand_manager.gd
class_name HandManager extends Node
signal card_drawn(card: Resource)
signal card_overdrawn(card: Resource)
@export var max_hand_size: int = 10
var _current_hand: Array[Resource] = []
func draw_card(new_card: Resource) -> void:
if _current_hand.size() >= max_hand_size:
# Hand is full; trigger overdraw
card_overdrawn.emit(new_card)
else:
_current_hand.append(new_card)
card_drawn.emit(new_card)
scripts/match_state_resetter.gd
# match_state_resetter.gd
# Cleaning up temporary match buffs on resources
extends Node
# EXPERT NOTE: Implement a reset on resources to ensure
# match-only buffs don't persist in the .tres files.
func reset_card_collection(collection: Array[CardData]):
for card in collection:
# Custom logic to restore base values
card.update_stats(card.get("base_atk"), card.get("base_hp"))
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html
# - https://docs.godotengine.org/en/stable/classes/class_resource.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — reset/duplication so .tres stay pristine
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-save-load-systems/SKILL.md — persist collection, never in-match buffs
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/reactive_card_ui.gd
# reactive_card_ui.gd
# Automatically updating UI nodes via Resource listeners
extends Control
@export var data: CardData
@onready var label = $NameLabel
func _ready():
# EXPERT: React to data changes from ANY system
data.changed.connect(_update_ui)
_update_ui()
func _update_ui():
label.text = data.card_name
print("UI Refreshed for ", data.card_name)
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html
# - https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html
# - https://docs.godotengine.org/en/stable/classes/class_control.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — Resource.changed → label/stat refresh
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — bind UI to duplicated match instances
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
scripts/turn_state_machine.gd
# turn_state_machine.gd
# Handling rigid turn phases via match patterns
extends Node
# EXPERT NOTE: match statements are the first-class way
# to handle discrete turn-based game states.
enum Phase { DRAW, MAIN, COMBAT, END }
var current_phase: Phase = Phase.DRAW
func advance_phase():
match current_phase:
Phase.DRAW: current_phase = Phase.MAIN
Phase.MAIN: current_phase = Phase.COMBAT
Phase.COMBAT: current_phase = Phase.END
Phase.END: current_phase = Phase.DRAW
print("New Phase: ", Phase.keys()[current_phase])
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-turn-system/SKILL.md — shared Draw/Main/Combat/End phase ownership
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md — escalate when phases need nested FSMs
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — phase_changed events for UI locks
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-card-game/SKILL.md
# =============================================================================
SKILL.md
---
name: godot-genre-card-game
description: "Expert blueprint for digital card games (CCG/Deckbuilders) including card data structures (Resource-based), deck management (draw/discard/reshuffle), turn logic, hand layout (arcing), drag-and-drop UI, effect resolution (Command pattern), and visual polish (godot-tweening, shaders). Use for CCG, deckbuilders, or tactical card games. Trigger keywords: card_game, deck_manager, card_data, hand_layout, drag_drop_cards, effect_resolution, command_pattern, draw_pile, discard_pile."
---
## NEVER Do (Expert Anti-Patterns)
### Logic & Architecture
- NEVER hardcode card logic inside UI scripts; strictly encapsulate gameplay effects in **`Callable` objects** or **Command resources** pushed to a LIFO stack.
- NEVER perform board-state calculations (Power/Toughness) in `_process()`; strictly use **Signal-driven triggers** or a centralized `EffectStack` resolver.
- NEVER forget **LIFO Stack Resolution**; strictly use **`Array.push_back()`** and **`Array.pop_back()`** to resolve reactions from top-to-bottom.
### UX & Animation
- NEVER skip **Z-Index management** during drag-and-drop; strictly raise the card to the front on click to prevent it sliding under other cards.
- NEVER allow instant card "teleportation" between piles; strictly use **`create_tween()`** / **`tween_property`** chains (0.2s+) for pile moves.
- NEVER use `global_position` for cards in hand; strictly position them using a **`Curve2D`** layout with **`sample_baked()`** for smooth arcs.
### Deck & State Management
- NEVER forget to handle **Empty Deck** scenarios; strictly implement auto-reshuffle of the discard pile to prevent soft-locks.
- NEVER use floating point numbers for discrete card stats; strictly use `int` for Costs, Attack, and Health to avoid precision drift.
- NEVER use standard Control nodes for mass tokens/battlefields; strictly use **`_draw()` custom drawing** to bypass SceneTree overhead when rendering 100+ cards or map icons.
- NEVER rely on SceneTree order for hand logic; strictly manage logical order in an **Array** and update visuals via **`queue_redraw()`**.
- NEVER erase array elements during a standard `for` loop; strictly iterate in reverse or use `filter()` to avoid indexing errors.
- NEVER forget to provide parameterless constructors in `_init()`; otherwise, Resources will fail to load in the Inspector.
---
## 🛠 Expert Components (scripts/)
> **MANDATORY reads** before implementing the matching system:
> 1. [card_effect_resolution.gd](scripts/card_effect_resolution.gd) — LIFO effect stack (`push_back` / `pop_back`)
> 2. [card_tween_manager.gd](scripts/card_tween_manager.gd) — interruptible pile/hand Tweens (no teleports)
> 3. [card_data_resource.gd](scripts/card_data_resource.gd) — Inspector-safe Resource card definitions
### Original Expert Patterns
- [card_effect_resolution.gd](scripts/card_effect_resolution.gd) - LIFO stack resolver for nested triggers and counter-play.
### Modular Components
- [card_data_resource.gd](scripts/card_data_resource.gd) - Data-driven card definitions allowing Inspector-based design.
- [card_data.gd](scripts/card_data.gd) - Lightweight CardData Resource stub used by validators/tests.
- [deck_shuffle_bag.gd](scripts/deck_shuffle_bag.gd) - Secure randomization patterns for uniform card distribution.
- [turn_state_machine.gd](scripts/turn_state_machine.gd) - Managing rigid phases (Draw, Play, Combat) via state matching.
- [card_drag_drop.gd](scripts/card_drag_drop.gd) - Implementation of native `_get_drag_data()` for Control nodes.
- [board_query_filter.gd](scripts/board_query_filter.gd) - Functional `filter()` patterns for querying board metadata.
- [card_tween_manager.gd](scripts/card_tween_manager.gd) - Managing interruptible card juice and board transitions.
- [reactive_card_ui.gd](scripts/reactive_card_ui.gd) - Resource-signal driven UI for automatic visual state updates.
- [board_state_dictionary.gd](scripts/board_state_dictionary.gd) - Grid-based tracking (Vector2i) decoupled from Node order.
- [match_state_resetter.gd](scripts/match_state_resetter.gd) - Clean-up pattern for in-match temporary Resource modifications.
- [deck_builder_validator.gd](scripts/deck_builder_validator.gd) - Backend logic for deck-building constraints and mana curves.
---
## Core Loop
1. **Draw** → 2. **Evaluate** → 3. **Play** → 4. **Resolve (LIFO stack)** → 5. **Discard/End**
## Decision Trees
### Effect stack
| Need | Action |
|------|--------|
| Nested reactions / counters | **MANDATORY** [card_effect_resolution.gd](scripts/card_effect_resolution.gd) — **LIFO only** (`pop_back`) |
| FIFO sequential animations only | Rare; keep a separate queue — do **not** reuse the reaction stack |
### Hand & board
| Need | Action |
|------|--------|
| Arc hand layout | `Curve2D.sample_baked` + [card_tween_manager.gd](scripts/card_tween_manager.gd) |
| Drag targeting | [card_drag_drop.gd](scripts/card_drag_drop.gd) |
| Dense boards (100+ icons) | `_draw()` / [board_state_dictionary.gd](scripts/board_state_dictionary.gd) — not Control-per-token |
### Data
| Need | Action |
|------|--------|
| Card definitions | **MANDATORY** [card_data_resource.gd](scripts/card_data_resource.gd) (parameterless `_init`) |
| Deck RNG | [deck_shuffle_bag.gd](scripts/deck_shuffle_bag.gd) |
| Turn phases | [turn_state_machine.gd](scripts/turn_state_machine.gd) |
## Skill Chain
| Phase | Skills | Purpose |
|-------|--------|---------|
| 1. Data | `resources`, `custom-resources` | Card properties (Cost, Type, Effect) |
| 2. UI | `control-nodes`, `layout-containers` | Hand layout, tooltips |
| 3. Input | `drag-and-drop`, `state-machines` | Targeting, hovering |
| 4. Logic | `command-pattern`, `signals` | LIFO stack, turn phases |
| 5. Polish | `godot-tweening`, `shaders` | Draw juice, foils |
## Common Pitfalls
| Pitfall | Solution |
|---------|----------|
| FIFO `pop_front` on reaction stack | Use LIFO `pop_back` in [card_effect_resolution.gd](scripts/card_effect_resolution.gd) |
| Instant pile snaps | Tween via [card_tween_manager.gd](scripts/card_tween_manager.gd) |
| Float ATK/HP | Use `int` stats on Resources |
## Elite notes (optional polish)
- Holographic foil: UV scroll shader on card Control — not required for stack correctness.
- Card history: signal bus logger for replay/debug; keep off the hot resolve path.
## Expert knowledge (on demand)
> **LLM-ignorance rule:** If a general agent would not know it before reading, load the reference — never delete expert deltas.
- [expert-card-patterns.md](references/expert-card-patterns.md) — restored baseline pedagogy (architecture, WHY, implementation depth)
- [card_history_logger.gd](scripts/card_history_logger.gd)
- [hand_manager.gd](scripts/hand_manager.gd)
## Reference
> Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.
### Official Documentation
- [Resources](https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html) — CardData as `.tres` Resources so designers edit cost/stats without touching UI scripts.
- [GDScript exports](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html) — `@export` / `@export_group` patterns that drive Inspector-authored card definitions.
- [Control](https://docs.godotengine.org/en/stable/classes/class_control.html) — `_get_drag_data` / `_can_drop_data` / `_drop_data` and `set_drag_preview` for native card drag-and-drop.
- [Custom GUI controls](https://docs.godotengine.org/en/stable/tutorials/ui/custom_gui_controls.html) — building interactive card faces with `_gui_input`, `mouse_filter`, and `_draw` for dense boards.
- [Size and anchors](https://docs.godotengine.org/en/stable/tutorials/ui/size_and_anchors.html) — anchoring hand/board zones so layouts survive resolution changes.
- [Using Containers](https://docs.godotengine.org/en/stable/tutorials/ui/gui_containers.html) — when HBox/Grid help deck-builder grids vs manual hand arcing.
- [Tween](https://docs.godotengine.org/en/stable/classes/class_tween.html) — interruptible `create_tween()` / `tween_property` juice for draw, play, and discard moves.
- [Beziers, curves and paths](https://docs.godotengine.org/en/stable/tutorials/math/beziers_and_curves.html) — math behind arcing hand layouts instead of circular fudge factors.
- [Curve2D](https://docs.godotengine.org/en/stable/classes/class_curve2d.html) — `sample_baked()` for smooth non-circular fan positions and rotations.
- [Random number generation](https://docs.godotengine.org/en/stable/tutorials/math/random_number_generation.html) — seeded RNG and shuffle-bag fairness for draw piles.
- [Using signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html) — Resource `changed` and effect-stack signals that keep UI reactive without `_process` polling.
- [Your first 2D shader](https://docs.godotengine.org/en/stable/tutorials/shaders/your_first_shader/your_first_2d_shader.html) — canvas_item foil/rarity shaders on card faces.
### Related Skills
#### Prerequisites
- [godot-project-foundations](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-project-foundations/SKILL.md) — scene tree, Autoloads, and import basics before wiring DeckManager and card scenes.
- [godot-resource-data-patterns](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md) — `.tres` ownership, duplication, and `emit_changed` so match buffs never leak into authored card files.
- [godot-gdscript-mastery](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md) — typed Arrays, Callables/Commands, and `match` phases used by effect stacks and turn machines.
- [godot-ui-containers](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ui-containers/SKILL.md) — Control layout, anchors, and mouse_filter habits required for hand/board hit-testing.
#### Complements
- [godot-tweening](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-tweening/SKILL.md) — kill/parallel Tween patterns so rapid plays do not teleport or stack dead tweens.
- [godot-signal-architecture](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md) — bus/signal graphs for draw, play, resolve, and discard without UI owning game rules.
- [godot-input-handling](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md) — pointer vs action maps when cards share the viewport with keyboard shortcuts and accessibility drag.
- [godot-turn-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-turn-system/SKILL.md) — Draw/Main/Combat/End phase ownership that this skill's turn state machine plugs into.
- [godot-shaders-basics](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md) — canvas_item rarity foils and highlight materials beyond the skill's starter hologram snippet.
#### Downstream / consumers
- [godot-monte-carlo-balancer](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md) — simulate mulligans, mana curves, and win-rate vs cost/power so card stats stay fair under RNG.
- [godot-genre-roguelike](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-roguelike/SKILL.md) — run-based deckbuilders that consume CardData, shops, and per-act draft pools.
- [godot-inventory-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-inventory-system/SKILL.md) — collection/deck-builder UIs that persist owned cards separately from the in-match piles.
- [godot-ability-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md) — keyword/status effects (Poison, Taunt, Shield) resolved as reusable ability resources on the stack.
#### Master
- [godot-master](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-master/SKILL.md) — library router and mirrored module entry for cross-skill discovery.