Implement Phase 8: Rig Relocation and Component Modularization
- Moved the rig scene from `res://master_rig.tscn` to `res://scenes/stickman_rig.tscn`, preserving its UID. - Split the `StickmanRig` controller into three components: `StickmanLocomotion`, `StickmanRagdoll`, and `StickmanActionRunner`. - Updated the architecture to ensure `StickmanRig` acts as the public facade for the new components. - Created a new test suite `test_phase8_rig_modularization.gd` to verify the integrity of the relocated rig and its components. - Added checks for scene loading, component resolution, action queue delegation, and state authority.
This commit is contained in:
@@ -3,8 +3,9 @@ extends SceneTree
|
||||
## Focused deterministic regression checks for the Phase 6a terrain controller.
|
||||
## Runtime terrain/ramp integration remains covered by the existing stage suites.
|
||||
|
||||
const RIG_SCENE := preload("res://master_rig.tscn")
|
||||
const RIG_SCENE := preload("res://scenes/stickman_rig.tscn")
|
||||
const RIG := preload("res://scripts/stickman_rig.gd")
|
||||
const LOC := preload("res://scripts/stickman_locomotion.gd")
|
||||
|
||||
var _checks: int = 0
|
||||
var _failures: int = 0
|
||||
@@ -35,59 +36,62 @@ func _run() -> void:
|
||||
|
||||
|
||||
func _test_air_pose_and_momentum(rig: StickmanRig) -> void:
|
||||
var locomotion: StickmanLocomotion = rig.get_locomotion()
|
||||
var left_hand := rig._get_ik_marker("Left_Hand")
|
||||
var right_hand := rig._get_ik_marker("Right_Hand")
|
||||
var left_base := left_hand.position
|
||||
var right_base := right_hand.position
|
||||
rig._enter_airborne(300.0)
|
||||
_check(rig._terrain_motion == RIG.TerrainMotion.AIRBORNE, "enter_airborne selects AIRBORNE")
|
||||
_check(is_equal_approx(rig._terrain_air_velocity.x, 300.0), "takeoff keeps signed horizontal velocity")
|
||||
rig._terrain_air_velocity.y = RIG.AIR_POSE_FULL_SPEED
|
||||
rig._air_time = 0.0
|
||||
rig._apply_air_pose(1.0)
|
||||
_check(rig._air_pose_weight > 0.99 and rig._air_pose_weight <= 1.0, "air pose weight clamps at full fall speed")
|
||||
locomotion._enter_airborne(300.0)
|
||||
_check(locomotion.terrain_motion == LOC.TerrainMotion.AIRBORNE, "enter_airborne selects AIRBORNE")
|
||||
_check(is_equal_approx(locomotion.terrain_air_velocity.x, 300.0), "takeoff keeps signed horizontal velocity")
|
||||
locomotion.terrain_air_velocity.y = LOC.AIR_POSE_FULL_SPEED
|
||||
locomotion._air_time = 0.0
|
||||
locomotion._apply_air_pose(1.0)
|
||||
_check(locomotion.air_pose_weight > 0.99 and locomotion.air_pose_weight <= 1.0, "air pose weight clamps at full fall speed")
|
||||
_check(left_hand.position != left_base and right_hand.position != right_base, "air pose offsets both hands")
|
||||
rig._restore_air_pose()
|
||||
locomotion._restore_air_pose()
|
||||
_check(left_hand.position.is_equal_approx(left_base) and right_hand.position.is_equal_approx(right_base), "air pose restores exact captured markers")
|
||||
rig._terrain_motion = RIG.TerrainMotion.GROUNDED
|
||||
rig._enter_airborne(0.0)
|
||||
var forward_left_base: Vector2 = rig._air_pose_base["Left_Hand"]
|
||||
var forward_right_base: Vector2 = rig._air_pose_base["Right_Hand"]
|
||||
rig._terrain_air_velocity.y = RIG.AIR_HAND_FORWARD_START_SPEED + 20.0
|
||||
rig._air_time = 0.0
|
||||
rig._apply_air_pose(1.0)
|
||||
locomotion.terrain_motion = LOC.TerrainMotion.GROUNDED
|
||||
locomotion._enter_airborne(0.0)
|
||||
var forward_left_base: Vector2 = locomotion.air_pose_base["Left_Hand"]
|
||||
var forward_right_base: Vector2 = locomotion.air_pose_base["Right_Hand"]
|
||||
locomotion.terrain_air_velocity.y = LOC.AIR_HAND_FORWARD_START_SPEED + 20.0
|
||||
locomotion._air_time = 0.0
|
||||
locomotion._apply_air_pose(1.0)
|
||||
_check(left_hand.position.x > forward_left_base.x and right_hand.position.x > forward_right_base.x, "hands reach forward before lift engages")
|
||||
_check(is_equal_approx(left_hand.position.y, forward_left_base.y) and is_equal_approx(right_hand.position.y, forward_right_base.y), "early forward reach does not lift hands")
|
||||
rig._terrain_air_velocity.y = RIG.AIR_POSE_FULL_SPEED
|
||||
rig._apply_air_pose(1.0)
|
||||
locomotion.terrain_air_velocity.y = LOC.AIR_POSE_FULL_SPEED
|
||||
locomotion._apply_air_pose(1.0)
|
||||
_check(left_hand.position.y < forward_left_base.y and right_hand.position.y < forward_right_base.y, "hands lift after forward reach")
|
||||
rig._restore_air_pose()
|
||||
rig._terrain_air_velocity = Vector2(300.0, 0.0)
|
||||
rig._terrain_air_velocity.x *= exp(-RIG.AIR_DRAG_X * 0.1)
|
||||
_check(rig._terrain_air_velocity.x > 0.0 and rig._terrain_air_velocity.x < 300.0, "air drag preserves forward momentum while decaying it")
|
||||
locomotion._restore_air_pose()
|
||||
locomotion.terrain_air_velocity = Vector2(300.0, 0.0)
|
||||
locomotion.terrain_air_velocity.x *= exp(-LOC.AIR_DRAG_X * 0.1)
|
||||
_check(locomotion.terrain_air_velocity.x > 0.0 and locomotion.terrain_air_velocity.x < 300.0, "air drag preserves forward momentum while decaying it")
|
||||
|
||||
|
||||
func _test_grounded_reset_and_ragdoll_cleanup(rig: StickmanRig) -> void:
|
||||
rig._terrain_following_enabled = true
|
||||
rig._terrain_motion = RIG.TerrainMotion.GROUNDED
|
||||
rig._last_grounded_walk_velocity_x = 999.0
|
||||
var locomotion: StickmanLocomotion = rig.get_locomotion()
|
||||
locomotion.terrain_following_enabled = true
|
||||
locomotion.terrain_motion = LOC.TerrainMotion.GROUNDED
|
||||
locomotion._last_grounded_walk_velocity_x = 999.0
|
||||
rig._physics_process(0.0)
|
||||
_check(is_zero_approx(rig._last_grounded_walk_velocity_x), "grounded idle frame clears stale walk velocity")
|
||||
rig._enter_airborne(-240.0)
|
||||
rig._terrain_air_velocity.y = 640.0
|
||||
rig._apply_air_pose(0.1)
|
||||
_check(is_zero_approx(locomotion._last_grounded_walk_velocity_x), "grounded idle frame clears stale walk velocity")
|
||||
locomotion._enter_airborne(-240.0)
|
||||
locomotion.terrain_air_velocity.y = 640.0
|
||||
locomotion._apply_air_pose(0.1)
|
||||
rig.set_ragdoll(true)
|
||||
_check(rig.state == RIG.RigState.RAGDOLL, "direct ragdoll enters from ordinary airborne motion")
|
||||
_check(rig._terrain_motion == RIG.TerrainMotion.DISABLED and rig._air_pose_base.is_empty(), "direct ragdoll clears airborne terrain ownership")
|
||||
_check(rig._terrain_air_velocity.is_zero_approx() and is_zero_approx(rig._air_pose_weight), "direct ragdoll clears air velocity and pose weight")
|
||||
_check(locomotion.terrain_motion == LOC.TerrainMotion.DISABLED and locomotion.air_pose_base.is_empty(), "direct ragdoll clears airborne terrain ownership")
|
||||
_check(locomotion.terrain_air_velocity.is_zero_approx() and is_zero_approx(locomotion.air_pose_weight), "direct ragdoll clears air velocity and pose weight")
|
||||
rig.snap_to_standing()
|
||||
|
||||
|
||||
func _test_landing_timeline_and_barrier(rig: StickmanRig) -> void:
|
||||
var locomotion: StickmanLocomotion = rig.get_locomotion()
|
||||
var torso := rig._get_ik_marker("Torso")
|
||||
var left_leg := rig._get_ik_marker("Left_Leg")
|
||||
var right_leg := rig._get_ik_marker("Right_Leg")
|
||||
rig._enter_airborne(0.0)
|
||||
locomotion._enter_airborne(0.0)
|
||||
var base_torso := torso.position
|
||||
var left_hand := rig._get_ik_marker("Left_Hand")
|
||||
var right_hand := rig._get_ik_marker("Right_Hand")
|
||||
@@ -97,25 +101,25 @@ func _test_landing_timeline_and_barrier(rig: StickmanRig) -> void:
|
||||
"Left_Leg": {"surface": left_leg.global_position + Vector2(0.0, 1200.0), "surface_valid": true},
|
||||
"Right_Leg": {"surface": right_leg.global_position + Vector2(0.0, 1300.0), "surface_valid": true},
|
||||
}
|
||||
rig._air_fall_distance = RIG.HARD_LANDING_MIN_FALL_HEIGHT
|
||||
rig._begin_landing(0.0, {"world_pos": torso.global_position}, contacts)
|
||||
_check(is_equal_approx(rig._landing_compression, RIG.LANDING_COMPRESSION_MIN), "minimum hard fall height maps to minimum compression")
|
||||
_check(rig._is_action_queue_blocked(), "landing blocks primary queue")
|
||||
rig._update_landing_impact(RIG.LANDING_COMPRESS_DURATION)
|
||||
_check(is_equal_approx(torso.position.y, base_torso.y + RIG.LANDING_COMPRESSION_MIN), "impact reaches deepest compression at 0.08s")
|
||||
locomotion.air_fall_distance = LOC.HARD_LANDING_MIN_FALL_HEIGHT
|
||||
locomotion._begin_landing(0.0, {"world_pos": torso.global_position}, contacts)
|
||||
_check(is_equal_approx(locomotion.landing_compression, LOC.LANDING_COMPRESSION_MIN), "minimum hard fall height maps to minimum compression")
|
||||
_check(locomotion.is_action_blocked(), "landing blocks primary queue")
|
||||
locomotion._update_landing_impact(LOC.LANDING_COMPRESS_DURATION)
|
||||
_check(is_equal_approx(torso.position.y, base_torso.y + LOC.LANDING_COMPRESSION_MIN), "impact reaches deepest compression at 0.08s")
|
||||
_check(left_hand.position.y > left_hand_base.y and right_hand.position.y > right_hand_base.y, "hard landing sharply drives hands downward")
|
||||
_check(left_leg.global_position.y < torso.global_position.y + 500.0 and right_leg.global_position.y < torso.global_position.y + 500.0, "landing ignores stale pre-motion solved contacts")
|
||||
rig._update_landing_impact(RIG.LANDING_RECOVER_DURATION)
|
||||
_check(rig._terrain_motion == RIG.TerrainMotion.GROUNDED, "landing returns to GROUNDED after 0.30s")
|
||||
locomotion._update_landing_impact(LOC.LANDING_RECOVER_DURATION)
|
||||
_check(locomotion.terrain_motion == LOC.TerrainMotion.GROUNDED, "landing returns to GROUNDED after 0.30s")
|
||||
_check(torso.position.is_equal_approx(base_torso), "landing restores torso exactly")
|
||||
_check(left_hand.position.is_equal_approx(left_hand_base) and right_hand.position.is_equal_approx(right_hand_base), "landing restores hand bases exactly")
|
||||
_check(not rig._is_action_queue_blocked(), "queue barrier clears after landing")
|
||||
rig._enter_airborne(0.0)
|
||||
rig._air_fall_distance = RIG.HARD_LANDING_MAX_FALL_HEIGHT
|
||||
rig._begin_landing(0.0, {"world_pos": torso.global_position}, contacts)
|
||||
_check(is_equal_approx(rig._landing_compression, RIG.LANDING_COMPRESSION_MAX), "maximum hard fall height maps to maximum compression")
|
||||
rig._cancel_landing_impact(true)
|
||||
rig._restore_air_pose()
|
||||
_check(not locomotion.is_action_blocked(), "queue barrier clears after landing")
|
||||
locomotion._enter_airborne(0.0)
|
||||
locomotion.air_fall_distance = LOC.HARD_LANDING_MAX_FALL_HEIGHT
|
||||
locomotion._begin_landing(0.0, {"world_pos": torso.global_position}, contacts)
|
||||
_check(is_equal_approx(locomotion.landing_compression, LOC.LANDING_COMPRESSION_MAX), "maximum hard fall height maps to maximum compression")
|
||||
locomotion._cancel_landing_impact(true)
|
||||
locomotion._restore_air_pose()
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
|
||||
Reference in New Issue
Block a user