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:
@@ -34,7 +34,7 @@
|
||||
#
|
||||
# Fix 2 (recovery re-anchor): _capture_ragdoll_pose() records the ragdoll
|
||||
# torso's world landing anchor -- _captured_landing_center (world center)
|
||||
# plus _captured_ground_y (center.y + RAGDOLL_TORSO_RADIUS, the ground
|
||||
# plus _captured_ground_y (center.y + StickmanRagdoll.TORSO_RADIUS, the ground
|
||||
# contact line) -- and _start_recovery() calls _reanchor_root_to_landing(),
|
||||
# which moves the rig root so the STANDING figure's FEET sit on the ground
|
||||
# at the landing X (feet = (_captured_landing_center.x, _captured_ground_y),
|
||||
@@ -544,8 +544,10 @@ func _test_recovery_reanchor() -> void:
|
||||
rig.state_changed.connect(func(s): state_events.append(int(s)))
|
||||
rig.set_ragdoll(true)
|
||||
_check(rig.is_in_ragdoll(), "set_ragdoll(true) puts the rig in RAGDOLL")
|
||||
_check(rig._ragdoll_bodies.size() == 10,
|
||||
"ragdoll builds 10 bodies (got %d)" % rig._ragdoll_bodies.size())
|
||||
var ragdoll: StickmanRagdoll = rig.get_node("RigComponents/Ragdoll") as StickmanRagdoll
|
||||
var ragdoll_bodies: Dictionary = ragdoll.get_bodies()
|
||||
_check(ragdoll_bodies.size() == 10,
|
||||
"ragdoll builds 10 bodies (got %d)" % ragdoll_bodies.size())
|
||||
var body_container := rig.get_node_or_null(NodePath("Body")) as Node2D
|
||||
var skeleton := rig.get_node_or_null(NodePath("Skeleton2D")) as Skeleton2D
|
||||
_check(body_container != null and not body_container.visible,
|
||||
@@ -553,7 +555,7 @@ func _test_recovery_reanchor() -> void:
|
||||
_check(skeleton != null and skeleton.modification_stack != null \
|
||||
and not skeleton.modification_stack.enabled,
|
||||
"IK stack is disabled during RAGDOLL")
|
||||
var torso: RigidBody2D = rig._ragdoll_bodies.get("torso") as RigidBody2D
|
||||
var torso: RigidBody2D = ragdoll_bodies.get("torso") as RigidBody2D
|
||||
_check(torso != null, "ragdoll torso body exists")
|
||||
if torso == null:
|
||||
await _free_stage(stage)
|
||||
@@ -563,13 +565,13 @@ func _test_recovery_reanchor() -> void:
|
||||
% float(torso.get_meta("half_height", 0.0)))
|
||||
|
||||
# Simulate "landed": freeze every body, then offset the whole pile.
|
||||
for key: String in rig._ragdoll_bodies:
|
||||
var b: RigidBody2D = rig._ragdoll_bodies[key] as RigidBody2D
|
||||
for key: String in ragdoll_bodies:
|
||||
var b: RigidBody2D = ragdoll_bodies[key] as RigidBody2D
|
||||
b.freeze = true
|
||||
b.freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC
|
||||
var torso_before: Vector2 = torso.global_position
|
||||
for key: String in rig._ragdoll_bodies:
|
||||
var b: RigidBody2D = rig._ragdoll_bodies[key] as RigidBody2D
|
||||
for key: String in ragdoll_bodies:
|
||||
var b: RigidBody2D = ragdoll_bodies[key] as RigidBody2D
|
||||
b.global_position += LANDING_DELTA
|
||||
for i in 3:
|
||||
await physics_frame
|
||||
@@ -582,10 +584,10 @@ func _test_recovery_reanchor() -> void:
|
||||
var spine_dir := Vector2.from_angle(torso.global_rotation)
|
||||
var hip_world: Vector2 = torso.global_position - spine_dir * half
|
||||
var landing_center: Vector2 = torso.global_position
|
||||
var ground_y: float = torso.global_position.y + float(RIG.RAGDOLL_TORSO_RADIUS)
|
||||
var ground_y: float = torso.global_position.y + StickmanRagdoll.TORSO_RADIUS
|
||||
var feet_world := Vector2(landing_center.x, ground_y)
|
||||
var expected_root: Vector2 = feet_world + (RIG.FOOT_OFFSET as Vector2)
|
||||
var stand_torso: Vector2 = (RIG.STAND_POSE["Torso"] as Dictionary).get("pos", Vector2.ZERO)
|
||||
var expected_root: Vector2 = feet_world + StickmanLocomotion.FOOT_OFFSET
|
||||
var stand_torso: Vector2 = (StickmanRagdoll.STAND_POSE["Torso"] as Dictionary).get("pos", Vector2.ZERO)
|
||||
var final_hip_world: Vector2 = expected_root + stand_torso
|
||||
var old_hip_anchor_root: Vector2 = hip_world - stand_torso
|
||||
_check(hip_world.distance_to(pre_root) > 400.0,
|
||||
@@ -598,12 +600,12 @@ func _test_recovery_reanchor() -> void:
|
||||
"request_recovery() enters RECOVERING (state %d)" % int(rig.state))
|
||||
_check(not state_events.is_empty() and state_events[-1] == int(rig.RigState.RECOVERING),
|
||||
"state_changed emitted RECOVERING (events %s)" % str(state_events))
|
||||
_check(rig._captured_landing_center.distance_to(landing_center) <= 0.5,
|
||||
_check(ragdoll._captured_landing_center.distance_to(landing_center) <= 0.5,
|
||||
"_captured_landing_center matches the simulated landing center (got %s)"
|
||||
% str(rig._captured_landing_center))
|
||||
_check(absf(rig._captured_ground_y - ground_y) <= 0.5,
|
||||
% str(ragdoll._captured_landing_center))
|
||||
_check(absf(ragdoll._captured_ground_y - ground_y) <= 0.5,
|
||||
"_captured_ground_y matches center.y + torso radius (got %.2f, expected %.2f)"
|
||||
% [rig._captured_ground_y, ground_y])
|
||||
% [ragdoll._captured_ground_y, ground_y])
|
||||
_check(rig.global_position.distance_to(expected_root) <= 0.5,
|
||||
"root re-anchored so the feet sit on the landing ground (got %s, expected %s)"
|
||||
% [str(rig.global_position), str(expected_root)])
|
||||
@@ -779,7 +781,9 @@ func _run_lying_recovery_case(label: String, torso_rotation: float) -> void:
|
||||
rig.state_changed.connect(func(s): state_events.append(int(s)))
|
||||
rig.set_ragdoll(true)
|
||||
_check(rig.is_in_ragdoll(), "set_ragdoll(true) puts the rig in RAGDOLL")
|
||||
var torso: RigidBody2D = rig._ragdoll_bodies.get("torso") as RigidBody2D
|
||||
var ragdoll: StickmanRagdoll = rig.get_node("RigComponents/Ragdoll") as StickmanRagdoll
|
||||
var ragdoll_bodies: Dictionary = ragdoll.get_bodies()
|
||||
var torso: RigidBody2D = ragdoll_bodies.get("torso") as RigidBody2D
|
||||
_check(torso != null, "ragdoll torso body exists")
|
||||
if torso == null:
|
||||
await _free_stage(stage)
|
||||
@@ -787,8 +791,8 @@ func _run_lying_recovery_case(label: String, torso_rotation: float) -> void:
|
||||
|
||||
# Freeze + teleport the whole pile, then rotate ONLY the torso body so its
|
||||
# spine lies ~horizontal (a true lying pose ~±90 deg from the standing -90).
|
||||
for key: String in rig._ragdoll_bodies:
|
||||
var b: RigidBody2D = rig._ragdoll_bodies[key] as RigidBody2D
|
||||
for key: String in ragdoll_bodies:
|
||||
var b: RigidBody2D = ragdoll_bodies[key] as RigidBody2D
|
||||
b.freeze = true
|
||||
b.freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC
|
||||
b.global_position += LANDING_DELTA
|
||||
@@ -804,13 +808,13 @@ func _run_lying_recovery_case(label: String, torso_rotation: float) -> void:
|
||||
var spine_dir := Vector2.from_angle(torso.global_rotation)
|
||||
var hip_world: Vector2 = torso.global_position - spine_dir * half
|
||||
var landing_center: Vector2 = torso.global_position
|
||||
var ground_y: float = torso.global_position.y + float(RIG.RAGDOLL_TORSO_RADIUS)
|
||||
var ground_y: float = torso.global_position.y + StickmanRagdoll.TORSO_RADIUS
|
||||
var feet_world := Vector2(landing_center.x, ground_y)
|
||||
var expected_root: Vector2 = feet_world + (RIG.FOOT_OFFSET as Vector2)
|
||||
var stand_torso: Vector2 = (RIG.STAND_POSE["Torso"] as Dictionary).get("pos", Vector2.ZERO)
|
||||
var expected_root: Vector2 = feet_world + StickmanLocomotion.FOOT_OFFSET
|
||||
var stand_torso: Vector2 = (StickmanRagdoll.STAND_POSE["Torso"] as Dictionary).get("pos", Vector2.ZERO)
|
||||
var final_hip_world: Vector2 = expected_root + stand_torso
|
||||
var old_hip_anchor_root: Vector2 = hip_world - stand_torso
|
||||
_check(absf(hip_world.y - ground_y) <= float(RIG.RAGDOLL_TORSO_RADIUS) + 1.0,
|
||||
_check(absf(hip_world.y - ground_y) <= StickmanRagdoll.TORSO_RADIUS + 1.0,
|
||||
"lying hip sits at the body's mid-height, near (not above) the ground line")
|
||||
_check(old_hip_anchor_root.distance_to(expected_root) > 300.0,
|
||||
"old hip-anchor root differs wildly from the new feet-anchor root (%.1f px)"
|
||||
@@ -820,12 +824,12 @@ func _run_lying_recovery_case(label: String, torso_rotation: float) -> void:
|
||||
rig.request_recovery()
|
||||
_check(not rig.is_in_ragdoll() and int(rig.state) == int(rig.RigState.RECOVERING),
|
||||
"request_recovery() enters RECOVERING (state %d)" % int(rig.state))
|
||||
_check(rig._captured_landing_center.distance_to(landing_center) <= 0.5,
|
||||
_check(ragdoll._captured_landing_center.distance_to(landing_center) <= 0.5,
|
||||
"_captured_landing_center matches the lying torso center (got %s)"
|
||||
% str(rig._captured_landing_center))
|
||||
_check(absf(rig._captured_ground_y - ground_y) <= 0.5,
|
||||
% str(ragdoll._captured_landing_center))
|
||||
_check(absf(ragdoll._captured_ground_y - ground_y) <= 0.5,
|
||||
"_captured_ground_y matches the lying ground-contact line (got %.2f, expected %.2f)"
|
||||
% [rig._captured_ground_y, ground_y])
|
||||
% [ragdoll._captured_ground_y, ground_y])
|
||||
_check(rig.global_position.distance_to(expected_root) <= 0.5,
|
||||
"root re-anchored so the standing feet sit on the lying ground (got %s, expected %s)"
|
||||
% [str(rig.global_position), str(expected_root)])
|
||||
|
||||
Reference in New Issue
Block a user