# 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.