# Combat System Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Create a stateless combat system that produces pre-combat proposals and resolves attacks between two units.

**Architecture:** A `CombatProposal` resource holds snapshot stats for attacker and defender. A `CombatSystem` node exposes `create_proposal()` to build proposals and `apply_proposal()` to resolve combat, modifying unit HP directly. The system is added to the strategy phase scene tree.

**Tech Stack:** Godot 4.6, GDScript

---

## File Structure

| File | Responsibility |
|------|---------------|
| `resources/resource_definitions/combat_proposal.gd` | CombatProposal resource with CombatantStats inner class — holds unit references and snapshot stats |
| `nodes/combat_system.gd` | CombatSystem node — `create_proposal()` and `apply_proposal()` methods |
| `scenes/strategy_phase.tscn` | Scene tree — add CombatSystem node |

---

### Task 1: Create the CombatProposal resource

**Files:**
- Create: `resources/resource_definitions/combat_proposal.gd`

- [ ] **Step 1: Create the CombatantStats inner class and CombatProposal resource**

```gdscript
# resources/resource_definitions/combat_proposal.gd
class_name CombatProposal extends Resource

class CombatantStats:
	var unit: Unit
	var hp: int
	var sp: int
	var hit: int
	var atk: int
	var def: int
	var spd: int

var attacker: CombatantStats
var defender: CombatantStats
```

- [ ] **Step 2: Verify the file loads without errors**

Open the Godot editor and check the Output panel for parse errors. Confirm `CombatProposal` appears as a recognized class by typing it in any script's autocomplete.

---

### Task 2: Create the CombatSystem node

**Files:**
- Create: `nodes/combat_system.gd`
- Reference: `resources/resource_definitions/combat_proposal.gd`
- Reference: `resources/resource_definitions/unit_stats.gd`
- Reference: `nodes/unit.gd`

- [ ] **Step 1: Create combat_system.gd with create_proposal**

```gdscript
# nodes/combat_system.gd
class_name CombatSystem extends Node

func create_proposal(attacker: Unit, defender: Unit) -> CombatProposal:
	var proposal := CombatProposal.new()

	proposal.attacker = _snapshot(attacker, defender)
	proposal.defender = _snapshot(defender, attacker)

	return proposal

func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats:
	var stats := CombatProposal.CombatantStats.new()
	stats.unit = unit
	stats.hp = unit.current_stats.current_hp
	stats.sp = unit.current_stats.current_sp
	stats.hit = unit.current_stats.hit - opponent.current_stats.eva
	stats.atk = unit.current_stats.phys_atk
	stats.def = unit.current_stats.phys_def
	stats.spd = unit.current_stats.spd
	return stats
```

- [ ] **Step 2: Add apply_proposal method**

Append to `nodes/combat_system.gd`:

```gdscript
func apply_proposal(proposal: CombatProposal) -> void:
	var atk_stats := proposal.attacker
	var def_stats := proposal.defender
	var atk_unit := atk_stats.unit
	var def_unit := def_stats.unit

	# Attacker strikes
	var atk_roll := randi_range(1, 100)
	if atk_roll <= atk_stats.hit:
		var damage := maxi(atk_stats.atk - def_stats.def, 0)
		def_unit.current_stats.current_hp -= damage

	# Counterattack if defender survives
	if def_unit.current_stats.current_hp > 0:
		var def_roll := randi_range(1, 100)
		if def_roll <= def_stats.hit:
			var damage := maxi(def_stats.atk - atk_stats.def, 0)
			atk_unit.current_stats.current_hp -= damage
```

- [ ] **Step 3: Verify the file loads without errors**

Open the Godot editor and check the Output panel for parse errors on `combat_system.gd`.

---

### Task 3: Add CombatSystem to the strategy phase scene

**Files:**
- Modify: `scenes/strategy_phase.tscn`

- [ ] **Step 1: Add CombatSystem node and script reference to the scene**

Add a new `ext_resource` entry for the combat_system.gd script and a new node to the scene file. Append after the existing ext_resources and add the node as a child of the root `CombatTest` node:

New ext_resource (use the next available id, `6_xxxxx` — the exact suffix will be assigned):
```
[ext_resource type="Script" path="res://nodes/combat_system.gd" id="6_combat"]
```

New node (add after the PlayerController node block):
```
[node name="CombatSystem" type="Node" parent="."]
script = ExtResource("6_combat")
```

The resulting scene tree order:
```
CombatTest (Node2D)
  ├─ CombatUI
  ├─ CombatMap
  ├─ PlayerController
  ├─ CombatSystem       <-- new
  ├─ Camera2D
  └─ AudioStreamPlayer
```

- [ ] **Step 2: Verify in the Godot editor**

Open `scenes/strategy_phase.tscn` in the editor. Confirm:
- CombatSystem node appears in the scene tree
- No errors in the Output panel
- The scene runs without crashes

---

### Task 4: Manual integration verification

- [ ] **Step 1: Verify create_proposal works**

Temporarily add the following to `CombatSystem._ready()` to test proposal creation (remove after verification):

```gdscript
func _ready() -> void:
	# Temporary test — remove after verification
	var units := get_tree().get_nodes_in_group("units")
	if units.size() >= 2:
		var proposal := create_proposal(units[0], units[1])
		print("Proposal created:")
		print("  Attacker: ", proposal.attacker.unit.current_info.name,
			" HP=", proposal.attacker.hp,
			" ATK=", proposal.attacker.atk,
			" DEF=", proposal.attacker.def,
			" HIT=", proposal.attacker.hit)
		print("  Defender: ", proposal.defender.unit.current_info.name,
			" HP=", proposal.defender.hp,
			" ATK=", proposal.defender.atk,
			" DEF=", proposal.defender.def,
			" HIT=", proposal.defender.hit)
```

Run the scene with at least 2 units deployed. Check the Output panel for correct stat snapshots.

- [ ] **Step 2: Verify apply_proposal works**

Extend the temporary `_ready()` to also apply the proposal:

```gdscript
		print("Before combat: Attacker HP=", proposal.attacker.unit.current_stats.current_hp,
			" Defender HP=", proposal.defender.unit.current_stats.current_hp)
		apply_proposal(proposal)
		print("After combat: Attacker HP=", proposal.attacker.unit.current_stats.current_hp,
			" Defender HP=", proposal.defender.unit.current_stats.current_hp)
```

Run multiple times (results will vary due to hit rolls). Confirm:
- Defender HP decreases when attacker hits
- Attacker HP decreases when defender counterattacks
- HP never increases (no negative damage)
- If defender dies (HP <= 0), no counterattack occurs

- [ ] **Step 3: Remove temporary test code**

Delete the entire `_ready()` method from `combat_system.gd`.
