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
+29 -12
View File
@@ -10,6 +10,7 @@ extends PopupPanel
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
const STICKMAN_RIG := preload("res://scripts/stickman_rig.gd")
const ACTION_RUNNER := preload("res://scripts/stickman_action_runner.gd")
signal edit_requested(index: int)
signal delete_requested(index: int)
@@ -65,14 +66,15 @@ func refresh() -> void:
_drag_index = -1
_drag_target = -1
var queue: Array[Dictionary] = []
var queue_view: Array[Dictionary] = []
if rig != null and is_instance_valid(rig):
queue = rig.get_queue()
queue_view = rig.get_queue_view()
_title_label.text = "Stickman: %s" % (String(rig.name) if rig != null and is_instance_valid(rig) else "?")
_empty_label.visible = queue.is_empty()
_empty_label.visible = queue_view.is_empty()
for i: int in queue.size():
_rows.append(_make_row(i, queue[i]))
for i: int in queue_view.size():
var entry: Dictionary = queue_view[i]
_rows.append(_make_row(i, entry.get("action", {}), int(entry.get("status", ACTION_RUNNER.QueueStatus.NOT_STARTED))))
func _build_ui() -> void:
@@ -129,7 +131,7 @@ func _build_ui() -> void:
min_size = Vector2i(460, 360)
func _make_row(index: int, action: Dictionary) -> PanelContainer:
func _make_row(index: int, action: Dictionary, status: int) -> PanelContainer:
var panel := PanelContainer.new()
panel.add_theme_stylebox_override("panel", _row_style(Color(0.0, 0.0, 0.0, 0.0)))
@@ -145,10 +147,15 @@ func _make_row(index: int, action: Dictionary) -> PanelContainer:
summary.size_flags_horizontal = Control.SIZE_EXPAND_FILL
summary.text = "%s %s" % [ACTION_REGISTRY.icon(String(action.get("type", ""))), ACTION_REGISTRY.summarize(action)]
row.add_child(summary)
var status_label := Label.new()
status_label.text = _status_label(status)
status_label.modulate = Color(1.0, 1.0, 1.0, 0.65) if status == ACTION_RUNNER.QueueStatus.COMPLETED or status == ACTION_RUNNER.QueueStatus.INTERRUPTED else Color.WHITE
row.add_child(status_label)
var edit := Button.new()
edit.text = "✎"
edit.tooltip_text = "Edit"
edit.disabled = status == ACTION_RUNNER.QueueStatus.COMPLETED or status == ACTION_RUNNER.QueueStatus.INTERRUPTED
edit.pressed.connect(func() -> void: edit_requested.emit(index))
row.add_child(edit)
@@ -161,6 +168,7 @@ func _make_row(index: int, action: Dictionary) -> PanelContainer:
var drag := Button.new()
drag.text = "≡"
drag.tooltip_text = "Drag to reorder"
drag.disabled = status == ACTION_RUNNER.QueueStatus.ACTIVE or status == ACTION_RUNNER.QueueStatus.COMPLETED or status == ACTION_RUNNER.QueueStatus.INTERRUPTED
drag.mouse_default_cursor_shape = Control.CURSOR_MOVE
drag.gui_input.connect(_on_drag_handle_gui_input.bind(index))
row.add_child(drag)
@@ -226,6 +234,20 @@ func _row_style(bg: Color) -> StyleBoxFlat:
return sb
func _status_label(status: int) -> String:
match status:
ACTION_RUNNER.QueueStatus.COMPLETED:
return "✓ Done"
ACTION_RUNNER.QueueStatus.INTERRUPTED:
return "↯ Interrupted"
ACTION_RUNNER.QueueStatus.ACTIVE:
return "▶ Active"
ACTION_RUNNER.QueueStatus.PENDING:
return "• Pending"
_:
return ""
func _on_drag_handle_gui_input(event: InputEvent, index: int) -> void:
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
if (event as InputEventMouseButton).pressed:
@@ -281,9 +303,4 @@ func _move_action(from: int, to: int) -> void:
var queue := rig.get_queue()
if from < 0 or from >= queue.size() or to < 0 or to >= queue.size():
return
var action: Dictionary = queue[from]
rig.remove_action(from)
var target := to
if to > from:
target -= 1
rig.insert_action(target, action)
rig.move_action(from, to)