Files
Fighting_Rthythm_game/docs/superpowers/plans/2026-07-02-rhythm-action-architecture-refactor.md
2026-07-02 09:47:52 -07:00

203 lines
8.1 KiB
Markdown

# Rhythm Action Architecture Refactor 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:** Refactor the project toward the architecture in `docs/架构方案.md`: global beat clock, four-slot combo window, ActionData resources, ActionResolver priority rules, Player execution components, and CombatManager formulas.
**Architecture:** Keep `EventBus` for decoupled broadcast, but add `RhythmManager` and `CombatManager` as first-class autoload services. Move domain logic out of `Player` into beat-aware components and data-driven resource files while preserving current gameplay tests.
**Tech Stack:** Godot 4.6 GDScript, `.tres` Resources, headless SceneTree tests.
---
### Task 1: Architecture Target Regression
**Files:**
- Create: `tests/test_rhythm_action_architecture.gd`
- [x] **Step 1: Write the failing test**
Create one test that asserts the six requested architecture artifacts exist and expose their intended behavior:
```gdscript
extends SceneTree
var failures: Array[String] = []
func _init() -> void:
_run.call_deferred()
func _run() -> void:
_check_autoloads()
_check_action_data()
_check_combo_window()
_check_action_resolver()
_check_player_components()
_check_combat_manager()
_finish()
```
- [x] **Step 2: Run test to verify it fails**
Run:
```bash
/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/wxm/code/project/Fighting_Rthythm_game -s res://tests/test_rhythm_action_architecture.gd
```
Expected: FAIL because `RhythmManager`, `CombatManager`, `ActionData`, `ActionResolver`, `ComboWindow`, `MotionExecutor`, and `BurstComponent` do not exist yet.
### Task 2: RhythmManager Autoload
**Files:**
- Create: `autoload/rhythm_manager.gd`
- Modify: `project.godot`
- Modify: `scenes/rhythm/rhythm_conductor.gd`
- Modify: `tests/test_rhythm_conductor.gd`
- [x] **Step 1: Implement `RhythmManager`**
Create an Autoload node with `beat_ticked`, `judgement_made`, and `action_judged` signals. It owns BPM, beat length, beat index, judgement scale, beat offset, fallback clock timing, and `judge_action(action_name)`.
- [x] **Step 2: Register autoload**
Add:
```ini
[autoload]
RhythmManager="*res://autoload/rhythm_manager.gd"
```
- [x] **Step 3: Preserve `RhythmConductor` compatibility**
Keep `scenes/rhythm/rhythm_conductor.gd` loadable for old tests by delegating equivalent judging math to the same model or leaving it as a scene adapter.
- [x] **Step 4: Run rhythm tests**
Run:
```bash
/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/wxm/code/project/Fighting_Rthythm_game -s res://tests/test_rhythm_conductor.gd
/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/wxm/code/project/Fighting_Rthythm_game -s res://tests/test_rhythm_action_architecture.gd
```
Expected: rhythm conductor still passes; architecture test advances past autoload checks.
### Task 3: Four-Slot ComboWindow
**Files:**
- Create: `scenes/components/combo_window.gd`
- Modify: `scenes/characters/player.tscn`
- Modify: `scenes/characters/player.gd`
- Modify: `tests/test_combo_window.gd`
- [x] **Step 1: Implement `ComboWindow`**
`ComboWindow` extends `Node`, stores four slots, records `StringName` inputs, keeps `&"Ø"` only as an explicit Miss placeholder, clears on miss/full/action clear, and emits `combo_updated`/`combo_cleared`. It must not auto-append `&"Ø"` just because a beat passed without input.
- [x] **Step 2: Replace Player node**
Rename or replace `ComboTracker` with `ComboWindow` in `player.tscn`. Keep compatibility methods `get_slots()`, `get_pattern()`, `get_contiguous_pattern()`, `queue_clear()`, `flush_pending_clear()` so existing tests can continue running while behavior becomes component-based.
- [x] **Step 3: Run combo tests**
Run:
```bash
/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/wxm/code/project/Fighting_Rthythm_game -s res://tests/test_combo_window.gd
/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/wxm/code/project/Fighting_Rthythm_game -s res://tests/test_player_combo_input.gd
```
Expected: existing combo behavior remains green, and the new architecture test confirms ComboWindow does not auto-append `&"Ø"` just because a beat passed without input.
### Task 4: ActionData and ActionResolver
**Files:**
- Create: `resources/action_data.gd`
- Create: `resources/actions/*.tres`
- Create: `scenes/combat/action_resolver.gd`
- Modify: `resources/skill_data.gd`
- Modify: `scenes/combat/input_resolver.gd`
- Modify: `scenes/characters/player.gd`
- Modify: `tests/test_combo_window.gd`
- Modify: `tests/test_architecture_refactor.gd`
- [x] **Step 1: Add full `ActionData`**
Create `ActionData extends Resource` with fields from `docs/架构方案.md`: `id`, `display_name`, `input_pattern`, `required_state`, `base_cost`, `damage_mult`, `move_mult_x`, `move_mult_y`, `action_beats`, `hit_type`, `range`, `target_type`, `armor_level`, `clear_window`, `can_chain`, `special`, plus compatibility fields `animation`, `energy_cost`, `energy_reward`, `spawns_projectile`, `projectile_scene`, and `displacement`.
- [x] **Step 2: Convert resources**
Create `resources/actions` equivalents for the current `resources/skills` files. After all tests and scripts use `ActionData`, remove the legacy `resources/skills` data.
- [x] **Step 3: Implement `ActionResolver`**
Load `resources/actions/*.tres`, expose `resolve(window, state_machine := null, context := {})`, `resolve_pattern(pattern, state_machine := null, context := {})`, `reload()`, and `clear_cache()`. Include an ordered rule pass for Space contexts before falling back to pattern matching.
- [x] **Step 4: Remove `InputResolver` after migration**
Move tests and runtime code to `ActionResolver` directly, then remove the legacy `InputResolver` adapter.
### Task 5: MotionExecutor and BurstComponent
**Files:**
- Create: `scenes/components/motion_executor.gd`
- Create: `scenes/components/burst_component.gd`
- Modify: `scenes/characters/player.tscn`
- Modify: `scenes/characters/player.gd`
- [x] **Step 1: Add `MotionExecutor`**
Expose `execute(action, direction, beat_time)` and `tick(delta)` to own action duration, lunge timing, and velocity output. Player calls it instead of hand-writing lunge windows.
- [x] **Step 2: Add `BurstComponent`**
Expose ready/active/cooldown state, beat-based duration, cost multiplier, damage multiplier, and judgement scale. It listens to `RhythmManager.beat_ticked` for duration and cooldown.
- [x] **Step 3: Move Player logic**
Replace direct charge/burst timing in `Player` with `BurstComponent`, and replace direct action displacement with `MotionExecutor`.
### Task 6: CombatManager
**Files:**
- Create: `autoload/combat_manager.gd`
- Modify: `project.godot`
- Modify: `scenes/components/damage_emitter.gd`
- Modify: `scenes/characters/player.gd`
- [x] **Step 1: Implement formulas**
`CombatManager` exposes `resolve_damage(base_attack, action, judgement, buffs := null, burst := null)`, `resolve_cost(action, burst := null)`, and `resolve_move(action, judgement, burst := null)`.
- [x] **Step 2: Wire damage and cost**
Player uses `CombatManager.resolve_cost` before action execution. `DamageEmitter` can accept an action context and asks `CombatManager.resolve_damage` rather than using scattered multipliers.
### Task 7: Verification and Gap Audit
**Files:**
- Modify: tests as needed to cover exact behavior.
- [x] **Step 1: Run full suite**
Run:
```bash
for test in tests/*.gd; do
/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/wxm/code/project/Fighting_Rthythm_game -s "res://$test" || exit 1
done
```
Expected: all tests exit `0`.
- [x] **Step 2: Search for architecture residue**
Run:
```bash
rg -n "Timer|charge_duration|attack_lunge|InputResolver|SkillData|ComboTracker|PlayerProjectile\\.new\\(|get_first_node_in_group|has_method|\\.call\\(" autoload resources scenes tests
```
Expected: any remaining hits are compatibility adapters, tests, or UI-only visual timing, not core gameplay architecture.