Skip to content

Commit 8b1756d

Browse files
committed
chore: llms.txt agregado
1 parent 2f37db7 commit 8b1756d

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

llms.txt

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Card Combat Engine
2+
3+
A headless, domain-agnostic turn-based card combat engine for **Godot 4.6 /
4+
GDScript**. Handles the *logic* of a card battle — turn FSM, mana, draw,
5+
attack/defense/block, damage resolution, and pluggable AI — and exposes every
6+
game-specific rule as an injectable `Callable`.
7+
8+
> **Core idea:** the engine is a pure logic layer. It knows nothing about your
9+
> GDD, rarities, or abilities. You inject the specifics; it runs the match.
10+
11+
## Overview
12+
13+
Card Combat Engine fills the gap between Godot's presentation-layer card addons
14+
(drag, animate, layout) and a working rule set. It provides the *brain*, not the
15+
visuals.
16+
17+
**What it does:** turn FSM (`BEGIN → PREPARATION → MAIN → ATTACK → DEFENSE →
18+
RESOLVE → END`), mana ramp, hand/board/graveyard zones, creature combat,
19+
spell resolution, a structured event log, full serialization for save/resume and
20+
server-authoritative netcode.
21+
22+
**What it doesn't do:** rendering, UI, card loaders, rarities, named abilities,
23+
or game-specific balance. All of that is injected from your game layer.
24+
25+
**Determinism:** `CombatSession.setup(..., ai_seed)` seeds shuffles and the AI.
26+
Same seed + same starting decks = bit-for-bit identical combat — ideal for
27+
replays and authoritative networking.
28+
29+
**Topology:** supports 1v1, 2v2, and N-sided FFA via `setup_sides(sides, teams)`.
30+
31+
## Documentation
32+
33+
- [Addon README](addons/card_combat/README.md) — full class reference, all
34+
injection points, signal catalog, serialization protocol, AI contract, replay
35+
pattern, and observability guide.
36+
- [Integration Guide](addons/card_combat/docs/integration_guide.md) — 12-step
37+
walkthrough: minimum setup → AbilityLibrary → custom abilities → human input →
38+
custom AI → save/resume → multi-side → networking.
39+
- [Tutorial](addons/card_combat/docs/tutorial.md) — build *Tiny Clash* from zero:
40+
headless first, then keyword abilities, custom abilities, live signals, and a
41+
human-driven turn.
42+
- [Examples](addons/card_combat/examples/) — `demo.tscn` (DummyAI vs DummyAI,
43+
full log), `custom_ai.gd` (AI contract skeleton), `ability_demo.gd`
44+
(AbilityLibrary wiring).
45+
46+
## Architecture
47+
48+
Three ideas carry the whole engine:
49+
50+
1. **The engine is agnostic.** It understands decks, mana, a turn FSM,
51+
attacking, blocking and damage — nothing about your game's rarities,
52+
elements, or named abilities.
53+
2. **You inject the specifics.** Game rules enter through `Callable` hooks set
54+
on `CombatSession` before `setup()`, and through the opaque
55+
`CardData.metadata: Dictionary`. The engine never reads `metadata`; your code
56+
does.
57+
3. **You drive the FSM.** For AI-vs-AI call `auto_resolve()`. For a human player
58+
react to the `phase_changed` signal and call the action methods
59+
(`play_card`, `declare_attacker`, `declare_blocker`, …).
60+
61+
**`CombatSession`** is the single coordinator: FSM, mana, draw, damage
62+
resolution, event log, command log, and serialization all live there by design
63+
(extracted only when the extraction adds value — see the addon README for the
64+
reasoning).
65+
66+
**`CardData.play_kind`** controls engine behavior: `UNIT` (persists on board,
67+
combats), `EFFECT` (resolves spell effects, goes to graveyard), `PERSISTENT`
68+
(persists on board, triggers auras, does not combat). It is a dispatch enum,
69+
not a game taxonomy — game taxonomies (Weapon, Terrain, Rarity) belong in
70+
`metadata`.
71+
72+
## Key files
73+
74+
| File | Role |
75+
|------|------|
76+
| `addons/card_combat/combat_session.gd` | Main coordinator — FSM, actions, serialization |
77+
| `addons/card_combat/combat_deck.gd` | Card zones (hand / draw pile / board / graveyard / mana / extra) |
78+
| `addons/card_combat/card_data.gd` | Card core (id, cost, stats, `play_kind`, `metadata`) |
79+
| `addons/card_combat/card_instance.gd` | In-play card (health, modifiers, triggers) |
80+
| `addons/card_combat/combatant.gd` | Hero / participant (health, damage, heal, signals) |
81+
| `addons/card_combat/combat_ai.gd` | AI contract (5 methods every AI must implement) |
82+
| `addons/card_combat/dummy_ai.gd` | Reference AI (seeded random) |
83+
| `addons/card_combat/heuristic_ai.gd` | Stronger AI (greedy curve-filling, value trades) |
84+
| `addons/card_combat/spell_effect.gd` | Spell effect descriptor (DAMAGE/HEAL/BUFF/AOE/SUMMON/custom) |
85+
| `addons/card_combat/combat_config.gd` | Balance parameters (mana caps, hand/board limits) |
86+
| `addons/card_combat/combat_trigger_queue.gd` | Deferred trigger FIFO (QUEUED trigger mode) |
87+
| `addons/card_combat/hidden_card_stats.gd` | Declared vs hidden stats for bluffing |
88+
| `addons/card_combat/abilities/ability_library.gd` | Opt-in keyword ability handler |
89+
| `test/` | GUT 9.6 test suite — 381 cases across 20 files |
90+
| `addons/card_combat/examples/demo.tscn` | Runnable smoke-check demo |
91+
| `addons/card_combat/benchmark/combat_benchmark.gd` | Performance / leak measurement |
92+
93+
## Injection points
94+
95+
All hooks are `Callable` properties set on `CombatSession` **before** `setup()`.
96+
Every hook is optional — omit any you don't need.
97+
98+
| Property | Signature | Purpose |
99+
|----------|-----------|---------|
100+
| `ability_fn` | `(inst: CardInstance, trigger: StringName, ctx: Dictionary)` | Ability trigger dispatch (called by the engine for every card event) |
101+
| `damage_fn` | `(attacker: CardInstance, defender: CardInstance) -> int` | Combat damage formula override |
102+
| `exhaust_fn` | `(owner_id: int)` | Called when a side's draw pile empties (fatigue) |
103+
| `discard_fn` | `(card: CardInstance, owner_id: int)` | Called when a card is discarded |
104+
| `attack_restriction_fn` | `(attacker: CardInstance, enemies: Array) -> Array` | Filter valid attack targets (TAUNT redirect) |
105+
| `incoming_damage_fn` | `(inst: CardInstance, amount: int, source) -> int` | Pre-damage interception (armor, prevention) |
106+
| `cost_fn` | `(card: CardData, owner_id: int) -> int` | Dynamic card cost override |
107+
| `spell_power_fn` | `(owner_id: int) -> int` | Spell damage bonus |
108+
| `aura_fn` | `(session: CombatSession)` | Called to recompute continuous modifiers (auras) |
109+
110+
## AbilityLibrary keywords
111+
112+
`AbilityLibrary` is an **opt-in** module that provides 14 ready-made ability
113+
keywords. Wire it once and declare abilities in `CardData.metadata["keywords"]`:
114+
115+
```gdscript
116+
var session := CombatSession.new()
117+
var lib := AbilityLibrary.new(session)
118+
lib.wire_all() # sets ability_fn, attack_restriction_fn, incoming_damage_fn,
119+
# spell_power_fn, and aura_fn on the session
120+
```
121+
122+
| Keyword | Behavior | Metadata key (if tunable) |
123+
|---------|----------|---------------------------|
124+
| `CHARGE` | Can attack the turn it enters play | — |
125+
| `IMMUNITY` | Absorbs N hits completely | `immunity_hits` (default 1, -1 = infinite) |
126+
| `LIFESTEAL` | Combat damage heals the owner's hero | — |
127+
| `TAUNT` | Enemies must attack this creature while alive | — |
128+
| `THORNS` | Returns N damage to the attacker on hit | `thorns` (default 1) |
129+
| `STEALTH` | Cannot be attacked until it attacks | — |
130+
| `WINDFURY` | Can attack N times per turn | `windfury_attacks` (default 2) |
131+
| `FREEZE` | Freezes damaged creatures for N turns | `freeze_turns` (default 1) |
132+
| `ARMOR` | Reduces each incoming hit by N | `armor` (needs `incoming_damage_fn`) |
133+
| `BATTLECRY` | On play: deals N damage to a chosen target | `battlecry_damage` (default 1) |
134+
| `SPELLPOWER` | Adds N to the owner's spell damage | `spell_power` (needs `spell_power_fn`) |
135+
| `LORD` | Aura: boosts other friendly creatures +N/+M | `aura_attack`, `aura_health` (default 1) |
136+
| `OVERKILL` | Excess lethal damage transfers to the enemy hero | `overkill_factor` (default 1) |
137+
| `SPELLBURST` | Gains +N/+M permanent when owner casts a spell | `spellburst_attack`, `spellburst_health` |
138+
139+
## Quick start
140+
141+
```gdscript
142+
# Minimum 1v1 (AI vs AI, headless)
143+
var session := CombatSession.new()
144+
session.setup(hero0, deck0, hero1, deck1)
145+
session.start()
146+
session.auto_resolve()
147+
var result := session.get_result() # {winner_side, turn_number, hp}
148+
149+
# With AbilityLibrary
150+
var lib := AbilityLibrary.new(session)
151+
lib.wire_all()
152+
session.ability_fn = lib.handle # already set by wire_all; shown for clarity
153+
154+
# Human-driven turn (react to signals)
155+
session.phase_changed.connect(func(old, new): _on_phase(new))
156+
session.start()
157+
158+
func _on_phase(phase: int) -> void:
159+
if phase == CombatState.MAIN and session.current_side == PLAYER_SIDE:
160+
session.play_card(hand_card)
161+
session.end_main_phase()
162+
```
163+
164+
Full working example: `addons/card_combat/examples/demo.tscn`.
165+
166+
## Testing
167+
168+
```bash
169+
godot --headless -s addons/gut/gut_cmdln.gd -gdir=res://test -ginclude_subdirs -gexit
170+
```
171+
172+
381 test cases (GUT 9.6) across: FSM, mana ramp, serialization/resume,
173+
determinism, AI contract, all AbilityLibrary keywords, trigger queue (INLINE vs
174+
QUEUED), multi-sided topology, command replay, and leak detection.
175+
176+
CI runs three gates on every push: compile check, unit tests, and an ObjectDB
177+
leak gate.
178+
179+
## License
180+
181+
Dual-licensed:
182+
183+
- **AGPL v3.0** — free for open-source projects. Network use (server-side)
184+
requires releasing your product's source under the AGPL.
185+
- **Commercial** — closed-source and server-side use without AGPL obligations.
186+
Contact **islasjavieralf@gmail.com**.

0 commit comments

Comments
 (0)