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

8.1 KiB

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

  • Step 1: Write the failing test

Create one test that asserts the six requested architecture artifacts exist and expose their intended behavior:

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()
  • Step 2: Run test to verify it fails

Run:

/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

  • 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).

  • Step 2: Register autoload

Add:

[autoload]
RhythmManager="*res://autoload/rhythm_manager.gd"
  • 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.

  • Step 4: Run rhythm tests

Run:

/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

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

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

  • Step 3: Run combo tests

Run:

/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

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

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

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

  • 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

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

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

  • 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

  • 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).

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

  • Step 1: Run full suite

Run:

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.

  • Step 2: Search for architecture residue

Run:

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.