feat: Implement Live Director System for interactive simulation control

- Added waypoint context updates to handle action queue statuses in `waypoint_context.gd`.
- Introduced new scene file `manwalk.scene` with terrain and stickman setup.
- Created architecture summary for the Live Director System detailing core state transitions, physics mechanics, UI elements, and action execution.
- Specified class contracts and interfaces for the Live Director implementation in `phase_12_live_director.md`.
- Defined state transitions, input handling, and queue editing mechanics for the Live Director feature.
This commit is contained in:
2026-09-24 15:25:10 -04:00
parent 0a7f97cec7
commit 730a9a966b
19 changed files with 1633 additions and 168 deletions
+93 -2
View File
@@ -117,6 +117,9 @@ var _probe_bottom_y := 0.0
var _probe_exclude: Array[RID] = []
var _spawn_pose: Dictionary = {}
var _spawn_limb_rotations: Dictionary = {}
var _simulation_paused: bool = false
var _paused_body_states: Dictionary = {}
var _was_recovery_tween_running: bool = false
func configure(rig: StickmanRig, locomotion: StickmanLocomotion) -> void:
_rig = rig
@@ -150,9 +153,97 @@ func initialize(skeleton: Skeleton2D) -> void:
## straight knee cross its angular limit during the step; correcting here keeps
## that invalid intermediate pose from ever reaching the rendered frame.
func _process(_delta: float) -> void:
if _rig != null and _rig.state == StickmanRig.RigState.RAGDOLL:
if not _simulation_paused and _rig != null and _rig.state == StickmanRig.RigState.RAGDOLL:
_enforce_side_profile_leg_bends()
func pause_simulation() -> void:
if _simulation_paused:
return
_simulation_paused = true
_paused_body_states.clear()
_was_recovery_tween_running = _stand_up_tween != null and _stand_up_tween.is_valid() and _stand_up_tween.is_running()
if _was_recovery_tween_running:
_stand_up_tween.pause()
for body: RigidBody2D in _bodies.values():
if body == null or not is_instance_valid(body):
continue
var body_id: int = body.get_instance_id()
_paused_body_states[body_id] = {"body": body, "transform": body.global_transform, "linear_velocity": body.linear_velocity, "angular_velocity": body.angular_velocity, "sleeping": body.sleeping, "freeze": body.freeze, "freeze_mode": body.freeze_mode}
body.freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC
body.freeze = true
func resume_simulation() -> void:
if not _simulation_paused:
return
for body_id: int in _paused_body_states.keys():
var snapshot: Dictionary = _paused_body_states[body_id]
var body: RigidBody2D = snapshot.get("body") as RigidBody2D
if body == null or not is_instance_valid(body) or body.is_queued_for_deletion():
continue
body.freeze_mode = int(snapshot.get("freeze_mode", RigidBody2D.FREEZE_MODE_STATIC))
body.freeze = bool(snapshot.get("freeze", false))
body.linear_velocity = snapshot.get("linear_velocity", Vector2.ZERO)
body.angular_velocity = float(snapshot.get("angular_velocity", 0.0))
body.sleeping = bool(snapshot.get("sleeping", false))
_paused_body_states.clear()
_simulation_paused = false
if _was_recovery_tween_running and _stand_up_tween != null and _stand_up_tween.is_valid():
_stand_up_tween.play()
_was_recovery_tween_running = false
func clear_pause_snapshot() -> void:
_paused_body_states.clear()
_simulation_paused = false
_was_recovery_tween_running = false
func get_body_nodes() -> Array[RigidBody2D]:
var result: Array[RigidBody2D] = []
for body: RigidBody2D in _bodies.values():
if body != null and is_instance_valid(body):
result.append(body)
return result
func get_body_rids() -> Array[RID]:
var result: Array[RID] = []
for body: RigidBody2D in get_body_nodes():
var rid: RID = body.get_rid()
if rid.is_valid():
result.append(rid)
return result
func get_world_body_bounds() -> Rect2:
var bounds := Rect2()
var has_bounds: bool = false
for body: RigidBody2D in get_body_nodes():
var extent: float = float(body.get_meta("radius", LIMB_RADIUS)) + float(body.get_meta("half_height", 0.0))
var body_rect := Rect2(body.global_position - Vector2(extent, extent), Vector2(extent * 2.0, extent * 2.0))
if not has_bounds:
bounds = body_rect
has_bounds = true
else:
bounds = bounds.merge(body_rect)
return bounds if has_bounds else Rect2()
func interrupt_recovery_to_ragdoll() -> void:
if _rig == null or _rig.state != StickmanRig.RigState.RECOVERING:
return
if _stand_up_tween != null and _stand_up_tween.is_valid():
_stand_up_tween.kill()
_stand_up_tween = null
_stand_up_generation += 1
if enter_ragdoll():
state_transition_requested.emit(StickmanRig.RigState.RAGDOLL)
if _simulation_paused:
_simulation_paused = false
pause_simulation()
func _resolve_limb_bones() -> void:
_limb_bones.clear()
for joint: String in LIMB_BONE_PATHS:
@@ -186,7 +277,7 @@ func build() -> bool:
var parent: Node = _rig.get_parent()
if parent == null: parent = _rig.get_tree().current_scene
if parent == null: return false
_root = Node2D.new(); _root.name = CONTAINER_NAME; parent.add_child(_root); _bodies.clear()
_root = Node2D.new(); _root.name = CONTAINER_NAME; _root.set_meta("stickman_ragdoll_container", true); _root.add_to_group("stickman_ragdoll_container"); _root.set_meta("stickman_rig", _rig); parent.add_child(_root); _bodies.clear()
for entry: Dictionary in BODIES: _build_body(entry)
_stabilize_side_profile_leg_bends()
for entry: Dictionary in JOINTS: _build_joint(entry)