references/elite-combat-patterns.md
# Elite Combat Patterns (load on demand)
> **MANDATORY** for combat telemetry, authoritative multiplayer damage, combo buffers, and in-game hitbox visualization. Golden path remains DamageData → HealthComponent → Hitbox.
## Combo buffers
[combo_system.gd](../scripts/combo_system.gd) — windowed `StringName` buffer; finishers stay normal attacks gated by `combo_executed`. Wire input via [godot-input-handling](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md), not `_input` hit logic.
## Combat state gating
[combat_state.gd](../scripts/combat_state.gd) — `can_act` blocks attack/dodge overlap. Prefer [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md) when states multiply.
## Damage popups
[damage_popup.gd](../scripts/damage_popup.gd) — pool Labels in production; this script shows the tween + crit scale pattern.
## Combat telemetry
> **WHY batch JSON flushes:** Writing every hit to disk stalls combat. Batch ~10 events then flush to `user://combat_log.json`.
[combat_logger.gd](../scripts/combat_logger.gd)
## Authoritative networked damage
> **CAUTION:** Clients must never apply final damage locally in competitive multiplayer.
[networked_damage_manager.gd](../scripts/networked_damage_manager.gd) — `request_damage` → server validates → `client_confirm_hit`. Add lag-compensation / distance checks server-side.
## Hitbox visualization
[hitbox_visualizer.gd](../scripts/hitbox_visualizer.gd) — toggle `debug_collisions_hint`; color attack vs hurt volumes differently.
## Inline tutorials (moved — use scripts)
| Pattern | Script |
|---------|--------|
| DamageData payload | [damage_data.gd](../scripts/damage_data.gd) |
| Health + i-frames | [health_component.gd](../scripts/health_component.gd) |
| Area hit delivery | [hitbox_hurtbox.gd](../scripts/hitbox_hurtbox.gd) |
| AoE / hit-stop | [combat_system_patterns.gd](../scripts/combat_system_patterns.gd) |
| Abilities / cooldowns | [godot-ability-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md) |
## Critical hits
Roll crit on `DamageData` construction before `take_damage` — keep crit math out of UI.
```gdscript
func calculate_damage(base_damage: float, crit_chance: float = 0.1) -> DamageData:
var data := DamageData.new(base_damage)
if randf() < crit_chance:
data.is_critical = true
data.amount *= 2.0
return data
```
references/migration-notes.md
# Migration notes: godot-combat-system
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)
*No skill-relevant breaking changes for this hop.*
## 4.1 → 4.2
Official: [Upgrading to Godot 4.2](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.2.html)
*No skill-relevant breaking changes for this hop.*
## 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).
## 4.3 → 4.4
Official: [Upgrading to Godot 4.4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.4.html)
*No skill-relevant breaking changes for this hop.*
## 4.4 → 4.5
Official: [Upgrading to Godot 4.5](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.5.html)
- 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.
## 4.6 → 4.7
Official: [Upgrading to Godot 4.7](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.7.html)
- Re-validate Resource pipelines after packed-array setter and typed-return GDScript changes.
- Confirm project stretch mode and AudioStreamPlayer area_mask after opening in 4.7.
scripts/combat_logger.gd
# combat_logger.gd
class_name CombatLogger
extends Node
const LOG_FILE := "user://combat_log.json"
var _session_log: Array[Dictionary] = []
func log_damage_event(source: String, target: String, amount: int) -> void:
_session_log.append({"source": source, "target": target, "damage": amount})
if _session_log.size() >= 10:
_flush_to_disk()
func _flush_to_disk() -> void:
var file := FileAccess.open(LOG_FILE, FileAccess.WRITE)
if file:
file.store_string(JSON.stringify(_session_log))
file.close()
scripts/combat_state.gd
# combat_state.gd
class_name CombatState
extends Node
enum State { IDLE, ATTACKING, BLOCKING, DODGING, STUNNED }
var current_state: State = State.IDLE
var can_act: bool = true
func enter_attack_state() -> bool:
if not can_act:
return false
current_state = State.ATTACKING
can_act = false
return true
func enter_block_state() -> void:
current_state = State.BLOCKING
func enter_dodge_state() -> bool:
if not can_act:
return false
current_state = State.DODGING
can_act = false
return true
func exit_state() -> void:
current_state = State.IDLE
can_act = true
scripts/combat_system_patterns.gd
# combat_system_patterns.gd
extends Node
# 1. Safe Duck-Typing for Damage
# EXPERT NOTE: Safely test if a target can receive damage without needing to know its exact class.
func _on_hitbox_impact(target: Node) -> void:
if target.has_method(&"take_damage"):
var data := DamageData.new()
data.amount = 50.0
data.damage_types = DamageData.DamageType.PHYSICAL
target.call(&"take_damage", data)
# 2. Safe Type Casting
# EXPERT NOTE: Use the 'as' keyword. If the cast fails, it securely returns null instead of crashing.
func _on_area_body_entered(body: Node2D) -> void:
var player := body as CharacterBody2D
if player and player.has_method(&"die"):
player.call(&"die")
# 3. Decoupling UI via Signal Binding
# EXPERT NOTE: Connect specific combat data to the UI using Callables and bound arguments.
signal combat_log_requested(source: String, amount: int)
func setup_combat_listeners(entity: Node) -> void:
# Binds "Sword" and 100 to the signal every time it fires
entity.connect(&"on_hit", _log_damage.bind("Sword", 100))
func _log_damage(_src: String, _amt: int) -> void: pass
# 4. Custom Stat Resources
# EXPERT NOTE: Build data containers explicitly for the Godot Inspector to keep logic clean.
# class_name CombatStats extends Resource
# @export var max_health: int = 100
# @export var defense: int = 5
# 5. Exporting Enum Bit Flags
# EXPERT NOTE: Allow designers to set multiple elemental damage types seamlessly in the Inspector.
@export_flags("Physical", "Fire", "Ice", "Lightning", "Poison") var damage_types: int = 1 # DamageData.DamageType.PHYSICAL
# 6. Interruptible Hitstun Tweens
# EXPERT NOTE: Cache tweens to allow consecutive hits to safely override and restart animations.
var _hit_tween: Tween
func apply_hitstun_vfx(target: CanvasItem) -> void:
if _hit_tween: _hit_tween.kill() # Cancel previous if still running
_hit_tween = create_tween()
_hit_tween.tween_property(target, "modulate", Color.RED, 0.1)
_hit_tween.tween_property(target, "modulate", Color.WHITE, 0.1)
# 7. Nodeless AoE Shape Casting
# EXPERT NOTE: Bypass Area nodes for an instantaneous, C++ powered physics overlap check.
func check_explosion_at(pos: Vector3, radius: float) -> Array:
var query := PhysicsShapeQueryParameters3D.new()
var shape := SphereShape3D.new()
shape.radius = radius
query.shape_rid = shape.get_rid()
query.transform = Transform3D.IDENTITY.translated(pos)
# Perform direct space state check
return get_world_3d().direct_space_state.intersect_shape(query)
# 8. Unbinding Native Signal Variables
# EXPERT NOTE: Safely ignore default emitted arguments if the target method requires none.
func connect_attack_button(btn: Button) -> void:
# pressed normally suggests passing 0 args, but unbind(1) is useful if a signal sends data you don't want
btn.pressed.connect(_execute_swing.unbind(1))
func _execute_swing() -> void: pass
# 9. Disabling Hitboxes Safely
# EXPERT NOTE: Defer disabling collisions so the physics engine isn't disrupted mid-step.
func disable_hitbox(collider: CollisionShape2D) -> void:
collider.set_deferred(&"disabled", true)
# 10. Frame-Perfect Animation Syncing
# EXPERT NOTE: Override process modes for strict combat determinism (syncing with physics).
func setup_combat_animator(mixer: AnimationMixer) -> void:
mixer.callback_mode_process = AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_physicsshapequeryparameters3d.html — nodeless AoE intersect_shape
# - https://docs.godotengine.org/en/stable/classes/class_tween.html — kill/recreate hitstun VFX tweens
# - https://docs.godotengine.org/en/stable/classes/class_collisionshape2d.html — set_deferred disabled on hitboxes
# - https://docs.godotengine.org/en/stable/classes/class_animationmixer.html — ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-2d-physics/SKILL.md — space queries + deferred collision toggles
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — bind/unbind combat log Callables
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-animation-player/SKILL.md — physics-synced attack callbacks
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-combat-system/SKILL.md
# =============================================================================
scripts/combo_system.gd
# combo_system.gd
class_name ComboSystem
extends Node
signal combo_executed(combo_name: String)
@export var combo_window: float = 0.5
var combo_buffer: Array[StringName] = []
var last_input_time: float = 0.0
func register_input(action: StringName) -> void:
var current_time := Time.get_ticks_msec() / 1000.0
if current_time - last_input_time > combo_window:
combo_buffer.clear()
combo_buffer.append(action)
last_input_time = current_time
_check_combos()
func _check_combos() -> void:
if combo_buffer.size() >= 3:
var last_three := combo_buffer.slice(-3)
if last_three == [&"light", &"light", &"heavy"]:
_execute_combo(&"special_attack")
combo_buffer.clear()
func _execute_combo(combo_name: StringName) -> void:
combo_executed.emit(combo_name)
scripts/damage_data.gd
# skills/godot-combat-system/scripts/damage_data.gd
class_name DamageData
extends Resource
## Typed combat payload. Elemental types are bitflags — never raw strings.
enum DamageType {
PHYSICAL = 1,
FIRE = 2,
ICE = 4,
LIGHTNING = 8,
POISON = 16,
}
@export var amount: float = 10.0
@export var source: Node
@export_flags("Physical", "Fire", "Ice", "Lightning", "Poison")
var damage_types: int = DamageType.PHYSICAL
@export var knockback: Vector2 = Vector2.ZERO
@export var knockback_force_3d: float = 0.0
@export var hit_stun_time: float = 0.0
@export var is_critical: bool = false
@export var source_position: Vector3 = Vector3.ZERO
func has_type(flag: int) -> bool:
return (damage_types & flag) != 0
func with_amount(dmg: float, src: Node = null) -> DamageData:
var copy := duplicate(true) as DamageData
copy.amount = dmg
if src:
copy.source = src
return copy
scripts/damage_popup.gd
# damage_popup.gd
extends Label
func show_damage(amount: float, is_crit: bool = false) -> void:
text = str(int(amount))
if is_crit:
modulate = Color.RED
scale = Vector2(1.5, 1.5)
var tween := create_tween()
tween.set_parallel(true)
tween.tween_property(self, "position:y", position.y - 50, 1.0)
tween.tween_property(self, "modulate:a", 0.0, 1.0)
tween.finished.connect(queue_free)
scripts/health_component.gd
# skills/godot-combat-system/scripts/health_component.gd
extends Node
class_name HealthComponent
## Golden-path HP + invincibility frames. Wire Hurtbox → take_damage(DamageData).
signal health_changed(old_health: float, new_health: float)
signal died
signal healed(amount: float)
signal invincibility_started
signal invincibility_ended
@export var max_health: float = 100.0
@export var current_health: float = 100.0
@export var i_frame_duration: float = 0.25
@export var disable_hurt_shapes_on_death: bool = true
var _invincible: bool = false
var _i_frame_left: float = 0.0
func _physics_process(delta: float) -> void:
if _i_frame_left > 0.0:
_i_frame_left -= delta
if _i_frame_left <= 0.0:
_invincible = false
invincibility_ended.emit()
func take_damage(data: DamageData) -> void:
if data == null or _invincible or current_health <= 0.0:
return
var old := current_health
current_health = maxf(0.0, current_health - data.amount)
health_changed.emit(old, current_health)
if current_health <= 0.0:
_on_died()
return
_start_i_frames()
func heal(amount: float) -> void:
if amount <= 0.0 or current_health <= 0.0:
return
var old := current_health
current_health = minf(max_health, current_health + amount)
healed.emit(current_health - old)
health_changed.emit(old, current_health)
func _start_i_frames() -> void:
if i_frame_duration <= 0.0:
return
_invincible = true
_i_frame_left = i_frame_duration
invincibility_started.emit()
func _on_died() -> void:
died.emit()
if not disable_hurt_shapes_on_death:
return
var host := get_parent()
if host == null:
return
for child in host.find_children("*", "CollisionShape2D", true, false):
(child as CollisionShape2D).set_deferred("disabled", true)
for child in host.find_children("*", "CollisionShape3D", true, false):
(child as CollisionShape3D).set_deferred("disabled", true)
scripts/hitbox_component.gd
# skills/combat-system/scripts/hitbox_component.gd
extends Area3D
## Hitbox Component Expert Pattern
## Standardized damage delivery system working in tandem with HurtboxComponent.
class_name HitboxComponent
@export var damage := 10.0
@export var knockback_force := 5.0
@export var hit_stun_time := 0.2
@export_flags("Physical", "Fire", "Ice", "Lightning", "Poison")
var attack_types: int = 1 # DamageData.DamageType.PHYSICAL
# Optional: Team filtering (layer/mask is preferred, but this adds logic layer)
@export var team_index := 0
func _ready() -> void:
area_entered.connect(_on_area_entered)
monitorable = true
monitoring = true
func _on_area_entered(area: Area3D) -> void:
if area is HurtboxComponent:
if area.team_index != team_index: # Prevent friendly fire
var attack_data := DamageData.new()
attack_data.amount = damage
attack_data.knockback_force_3d = knockback_force
attack_data.hit_stun_time = hit_stun_time
attack_data.damage_types = attack_types
attack_data.source_position = global_position
attack_data.source = owner
area.receive_hit(attack_data)
## EXPERT USAGE:
## 1. Add HitboxComponent to Weapon/Projectile
## 2. Set Collision Layer to 'Hitbox'
## 3. Set Collision Mask to 'Hurtbox'
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_area3d.html — 3D HitboxComponent area_entered
# - https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html — layer/mask team filtering
# - https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html — AttackData as transferable hit payload
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition/SKILL.md — HitboxComponent on weapon/projectile scenes
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-rpg-stats/SKILL.md — elemental/team filters before receive_hit
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md — ability execute feeds AttackData fields
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-combat-system/SKILL.md
# =============================================================================
scripts/hitbox_hurtbox.gd
# skills/combat-system/code/hitbox_hurtbox.gd
extends Area2D
## Hitbox/Hurtbox Expert Pattern
## Component-based combat with Hit-Stop and Knockback support.
class_name Hitbox # Or Hurtbox, defined by usage
@export var damage: float = 10.0
@export var knockback_force: float = 200.0
@export var hit_stop_duration: float = 0.05 # Engine freeze time
func _on_area_entered(hurtbox: Area2D) -> void:
if hurtbox.has_method("take_damage"):
# 1. Calculate Knockback Vector
var source_pos = global_position
var target_pos = hurtbox.global_position
var kb_direction = (target_pos - source_pos).normalized()
# 2. Trigger Hit-Stop (Global Freeze)
_apply_hit_stop()
# 3. Transmit Data
hurtbox.take_damage(damage, kb_direction * knockback_force)
func _apply_hit_stop() -> void:
Engine.time_scale = 0.0
await get_tree().create_timer(hit_stop_duration, true, false, true).timeout
Engine.time_scale = 1.0
## EXPERT NOTE:
## Time-scale manipulation for hit-stop must use a SceneTreeTimer
## with 'ignore_time_scale' set to true, or the timer itself will freeze!
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/physics/using_area_2d.html — Area2D hit/hurt overlap pattern
# - https://docs.godotengine.org/en/stable/classes/class_area2d.html — area_entered damage delivery
# - https://docs.godotengine.org/en/stable/classes/class_scenetreetimer.html — hit-stop timer with ignore_time_scale
# - https://docs.godotengine.org/en/stable/classes/class_engine.html — Engine.time_scale for hit-stop
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-2d-physics/SKILL.md — layers/masks for hit vs hurt volumes
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — surface hit events without UI coupling
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md — validate hit-stop/i-frame TTK impact
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-combat-system/SKILL.md
# =============================================================================
scripts/hitbox_visualizer.gd
# hitbox_visualizer.gd
class_name HitboxVisualizer
extends Node
func toggle_debug_hitboxes() -> void:
get_tree().debug_collisions_hint = not get_tree().debug_collisions_hint
static func set_hitbox_color(shape: CollisionShape3D, is_attack: bool) -> void:
shape.debug_color = Color.RED if is_attack else Color.GREEN
scripts/networked_damage_manager.gd
# networked_damage_manager.gd
class_name NetworkedDamageManager
extends Node
func request_damage(target: Node, amount: int) -> void:
if multiplayer.has_multiplayer_peer():
rpc_id(1, &"server_validate_hit", target.get_path(), amount)
@rpc("any_peer", "call_remote", "reliable")
func server_validate_hit(target_path: NodePath, amount: int) -> void:
var sender_id := multiplayer.get_remote_sender_id()
var target_node := get_node_or_null(target_path)
if is_instance_valid(target_node) and target_node.has_method(&"take_damage"):
target_node.take_damage(amount)
rpc_id(sender_id, &"client_confirm_hit", target_path, amount)
@rpc("authority", "call_remote", "reliable")
func client_confirm_hit(target_path: NodePath, amount: int) -> void:
print_rich("[color=green]Hit confirmed by server.[/color]")
SKILL.md
---
name: godot-combat-system
description: "Expert patterns for combat systems including hitbox/hurtbox architecture, damage calculation (DamageData class), health components, combat state machines, combo systems, ability cooldowns, and damage popups. Use for action games, RPGs, or fighting games. Trigger keywords: Hitbox, Hurtbox, DamageData, HealthComponent, combat_state, combo_system, ability_cooldown, invincibility_frames, damage_popup."
---
## NEVER Do
- **NEVER use direct damage references (`target.health -= 10`)** — Bypass armor, resistances, and i-frames. Always `DamageData` + `HealthComponent.take_damage`.
- **NEVER forget invincibility frames (i-frames)** — Multi-hit shapes otherwise tick every physics frame. Apply a short invuln window after a successful hit.
- **NEVER keep hitboxes active permanently** — Enable/disable with AnimationPlayer tracks or timed code; permanent monitoring causes ghost hits.
- **NEVER use groups for physics-based hit filtering** — Prefer collision layers/masks (C++ filter). Groups are secondary logic, not the physics gate.
- **NEVER emit damage signals without a DamageData object** — Raw numbers lose type, source, knockback, and crit context.
- **NEVER use raw strings for elemental damage types** — Use `enum` / `@export_flags` bitfields. String `"physical"` violates this skill’s own contract.
- **NEVER use try/catch to validate targets** — GDScript has no exceptions. Use `has_method(&"take_damage")` / `is` checks.
- **NEVER hardcode hitstun with `OS.delay_msec()`** — Blocks the OS thread. Use tweens / `Engine.time_scale` + `ignore_time_scale` timers.
- **NEVER apply RigidBody impulses in `_process()`** — Use `_physics_process` / `_integrate_forces`.
- **NEVER couple UI lifebars inside the Player script** — Emit `health_changed`; HUD listens.
- **NEVER leave CollisionShapes active on dead entities** — `set_deferred("disabled", true)` on death.
- **NEVER scale CollisionShapes non-uniformly** — Scale the shape resource (`radius`, `size`), not the node transform unevenly.
- **NEVER use instanced Nodes for base combat stats** — Prefer `Resource` / `RefCounted` containers; `duplicate()` per instance.
- **NEVER use standard strings for high-frequency state names** — Prefer `StringName` (`&"attacking"`).
- **NEVER forget `duplicate()` on shared Resource stats** — Shared templates = shared health pools.
---
## Golden Path (MANDATORY)
1. **[damage_data.gd](scripts/damage_data.gd)** — typed `DamageData` Resource with `enum` / flags for damage types (no String elements).
2. **[health_component.gd](scripts/health_component.gd)** — `take_damage` + i-frame gate + `health_changed` / `died` signals.
3. **[hitbox_hurtbox.gd](scripts/hitbox_hurtbox.gd)** / **[hitbox_component.gd](scripts/hitbox_component.gd)** — Area hit delivery into hurtboxes.
4. **[combat_system_patterns.gd](scripts/combat_system_patterns.gd)** — duck-typing, hit-stop, nodeless AoE, frame sync.
**Do NOT** re-inline Hitbox/Health/Combo/Ability tutorials in scenes. Route abilities to [godot-ability-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md); compose components per [godot-composition](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition/SKILL.md); FSMs via [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md).
## Decision Tree
| Task | Load | Do NOT Load |
|------|------|-------------|
| Define damage payload | damage_data.gd | String `damage_type` fields |
| HP + i-frames | health_component.gd | Direct `health -= n` |
| Melee/projectile volumes | hitbox_hurtbox.gd / hitbox_component.gd | Permanent monitoring Areas |
| AoE / hit-stop / duck-type | combat_system_patterns.gd | Spawn temp Areas every tick |
| Ability cooldowns / skill bar | godot-ability-system | Inline AbilityManager novels here |
| Combo buffers | godot-input-handling + state machine | Embedding hit logic in `_input` |
## Damage Type Contract (aligned with NEVER)
```gdscript
# From damage_data.gd — prefer this shape everywhere
enum DamageType { PHYSICAL = 1, FIRE = 2, ICE = 4, LIGHTNING = 8, POISON = 16 }
@export_flags("Physical", "Fire", "Ice", "Lightning", "Poison")
var damage_types: int = DamageType.PHYSICAL
```
Hitboxes must pass `DamageData` (or equivalent AttackData built from the same flags), never `"Physical"` strings.
## Available Scripts
- [damage_data.gd](scripts/damage_data.gd) — **MANDATORY** DamageData Resource + type flags.
- [health_component.gd](scripts/health_component.gd) — **MANDATORY** Health + i-frames golden path.
- [hitbox_hurtbox.gd](scripts/hitbox_hurtbox.gd) — **MANDATORY** before Area combat wiring.
- [hitbox_component.gd](scripts/hitbox_component.gd) — 3D Area hitbox companion (flags-aligned).
- [combat_system_patterns.gd](scripts/combat_system_patterns.gd) — **MANDATORY** for AoE / hit-stop / duck-typing.
- [combo_system.gd](scripts/combo_system.gd) — windowed combo buffer (Do NOT Load if no combos).
- [combat_state.gd](scripts/combat_state.gd) — lightweight combat FSM gate.
- [damage_popup.gd](scripts/damage_popup.gd) — floating damage label tween (pool in production).
- [combat_logger.gd](scripts/combat_logger.gd) — batched combat telemetry JSON.
- [networked_damage_manager.gd](scripts/networked_damage_manager.gd) — server-validate damage RPC shell.
- [hitbox_visualizer.gd](scripts/hitbox_visualizer.gd) — toggle collision debug colors.
## Elite Deltas (keep short)
- **Combat telemetry:** batch JSON flushes via [combat_logger.gd](scripts/combat_logger.gd).
- **Authoritative damage:** [networked_damage_manager.gd](scripts/networked_damage_manager.gd) — clients request; server validates.
- **Hitbox debug:** [hitbox_visualizer.gd](scripts/hitbox_visualizer.gd) + `SceneTree.debug_collisions_hint`.
- **Combos / popups / FSM:** [combo_system.gd](scripts/combo_system.gd), [damage_popup.gd](scripts/damage_popup.gd), [combat_state.gd](scripts/combat_state.gd).
> **MANDATORY** for telemetry, networked hits, combos, and moved inline tutorials: [elite-combat-patterns.md](references/elite-combat-patterns.md). **Do NOT Load** for first DamageData + HealthComponent pass.
## Reference
> **Progressive disclosure:** Skim Official Documentation only for the APIs you are implementing (Areas, layers/masks, Resources, signals, timers, animation hit windows). Open Related Skills when wiring adjacent systems—do not preload the whole lattice.
### Official Documentation
- [Using Area2D](https://docs.godotengine.org/en/stable/tutorials/physics/using_area_2d.html) — Hitbox/hurtbox combat is Area overlap detection (`area_entered` / monitoring), not CharacterBody movement queries.
- [Physics introduction](https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html) — Prefer collision layers/masks for hit filtering; groups are slower and do not replace physics masks for high-frequency combat.
- [Area2D](https://docs.godotengine.org/en/stable/classes/class_area2d.html) — 2D hit volumes: `monitoring`/`monitorable`, `area_entered`, and layer/mask bits for team/faction filtering.
- [Area3D](https://docs.godotengine.org/en/stable/classes/class_area3d.html) — 3D `HitboxComponent` / hurtbox volumes use the same Area overlap model with 3D layers and shapes.
- [CollisionShape2D](https://docs.godotengine.org/en/stable/classes/class_collisionshape2d.html) — Enable/disable attack shapes with `set_deferred("disabled", …)` so the physics server is not mutated mid-step; never non-uniform-scale the node.
- [PhysicsShapeQueryParameters3D](https://docs.godotengine.org/en/stable/classes/class_physicsshapequeryparameters3d.html) — Nodeless AoE/explosions via `intersect_shape` on `PhysicsDirectSpaceState3D` without spawning temporary Area nodes.
- [AnimationPlayer](https://docs.godotengine.org/en/stable/classes/class_animationplayer.html) — Drive hitbox active windows from animation tracks (or method calls) so attacks are not permanently monitoring.
- [Resources](https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html) — Keep `DamageData` / combat stats as data (`Resource` / `RefCounted`), and `duplicate()` shared templates per instance so enemies do not share one health pool.
- [Using signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html) — Emit `health_changed` / `died` / damage events so HUD and VFX subscribe without coupling lifebars into the player script.
- [SceneTreeTimer](https://docs.godotengine.org/en/stable/classes/class_scenetreetimer.html) — Hit-stop after `Engine.time_scale = 0` must use `create_timer(..., ignore_time_scale=true)` or the thaw timer freezes with the world.
- [Tween](https://docs.godotengine.org/en/stable/classes/class_tween.html) — Interruptible hitstun/flash VFX: kill and recreate tweens on consecutive hits instead of stacking parallel flash animations.
- [High-level multiplayer](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html) — Authoritative damage: clients request hits; the server validates and confirms via `@rpc` before applying `take_damage`.
### Related Skills
#### Prerequisites
- [godot-2d-physics](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-2d-physics/SKILL.md) — Area layers/masks, `CollisionShape2D` deferred disable, and space queries are the physics substrate under hitbox/hurtbox filtering.
- [godot-signal-architecture](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md) — Damage, health, and death signals need clear ownership so combat components stay decoupled from UI and AI listeners.
- [godot-composition](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition/SKILL.md) — Prefer `HealthComponent` / `HitboxComponent` children over baking combat into a monolithic Character script.
- [godot-resource-data-patterns](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md) — `DamageData`, elemental flags, and combat stats belong in Resource/`RefCounted` data with safe `duplicate()` on spawn.
#### Complements
- [godot-ability-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md) — Abilities resolve into this skill’s damage/targeting pipeline; keep ability metadata separate from `DamageData`.
- [godot-rpg-stats](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-rpg-stats/SKILL.md) — Armor, resistances, crit chance, and modifier stacks feed `take_damage` before health is written.
- [godot-animation-player](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-animation-player/SKILL.md) — Attack animations own hitbox enable windows, cancel frames, and recovery locks for combos.
- [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md) — IDLE/ATTACKING/BLOCKING/STUNNED combat states belong in a character FSM that gates `can_act`, not ad-hoc bool soup.
- [godot-input-handling](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md) — Combo buffers and attack actions should call into combat/combo systems from the action map rather than embedding hit logic in input callbacks.
#### Downstream / consumers
- [godot-monte-carlo-balancer](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md) — After DamageData, i-frames, cooldowns, and crit curves are tunable, Monte Carlo sims prove DPS/TTK bands before shipping difficulty.
- [godot-multiplayer-networking](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-multiplayer-networking/SKILL.md) — Predicted hits, lag compensation, and authority checks build on the DamageData + server-validate RPC split.
- [godot-genre-action-rpg](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-action-rpg/SKILL.md) — Action-RPG combat loops assemble hitboxes, abilities, stats, and progression genre glue on top of this skill.
#### Master
- [godot-master](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-master/SKILL.md) — Library router and mirrored module entry; use when discovering peer skills or syncing shared script mirrors after Domain Skill edits.