feat(stage): complete phase 20b action plans

This commit is contained in:
2026-11-04 01:19:57 -05:00
parent 87a33b0abc
commit a9c0886348
70 changed files with 14234 additions and 469 deletions
@@ -0,0 +1,102 @@
extends SceneTree
const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
const STAGE_DIALOG_STYLER := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
const CAPTURE_DIR := "res://tests/phase20b_captures/pickup_action_marker"
var _stage: SandboxStage
var _failures: int = 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
root.size = Vector2i(1280, 720)
root.gui_embed_subwindows = true
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(CAPTURE_DIR))
_stage = SANDBOX_STAGE.instantiate() as SandboxStage
root.add_child(_stage)
await _wait_frames(10)
var recovery: ConfirmationDialog = _stage.find_child("AutosaveRecoveryDialog", true, false) as ConfirmationDialog
if recovery != null:
STAGE_DIALOG_STYLER.hide(recovery)
_stage.call("_on_grid_toggled", false)
var spawner: StageSpawner = _stage.get("_spawner") as StageSpawner
var world: Node2D = _stage.get_node("World") as Node2D
var actor: StickmanRig = spawner.spawn_stickman_asset("res://content/stickmen/break.stk", Vector2(-160.0, -190.0))
var prop: PropBlock = spawner.spawn_prop_asset("crate", Vector2(150.0, -10.0))
_check(actor != null and prop != null, "production Stage fixture creates a character and pickup target")
if actor == null or prop == null:
await _finish()
return
_stage.call("_finalize_placed_node", actor)
_stage.call("_finalize_placed_node", prop)
var identity: StageObjectIdentity = _stage.get("_object_identity") as StageObjectIdentity
var rename_result: Dictionary = identity.rename(prop, "Brown Suitcase", world)
_check(bool(rename_result.get("ok", false)), "pickup target receives its friendly stage name")
var pickup_action: Dictionary = {
"action_id": "pickup-marker-step-one",
"type": "pick_up_prop",
"target_prop_id": prop.stage_object_id,
"hand": "auto",
}
_check(actor.set_action_plan([{"actions": [pickup_action]}]), "Step 1 receives a real Pick Up Prop action")
_stage.call("set_mode", SandboxStage.StageMode.DIRECT)
var selection: StageSelection = _stage.get("_selection") as StageSelection
selection.select_only(actor)
_stage.call("_refresh_character_settings_card")
await _wait_frames(8)
var visuals: StageDirectorVisuals = _stage.get("_director_visuals") as StageDirectorVisuals
var projection: Dictionary = {}
for entry: Dictionary in visuals._plan_projection(actor):
if String(entry.get("action_id", "")) == String(pickup_action["action_id"]):
projection = entry
break
_check(String(projection.get("kind", "")) == "other" and String((projection.get("action", {}) as Dictionary).get("type", "")) == "pick_up_prop", "pickup action reaches the production Stage overlay projection")
var canvas_transform: Transform2D = _stage.get_viewport().get_canvas_transform()
var prop_world_bounds: Rect2 = STAGE_SELECTION.get_world_aabb(prop)
var prop_screen_bounds: Rect2 = visuals._world_rect_to_screen_rect(prop_world_bounds, canvas_transform)
var badge_size: Vector2 = visuals._text_action_badge_screen_size("Pick up", visuals._zoom())
var viewport_size: Vector2 = root.size
var safe_rect: Rect2 = Rect2(Vector2(8.0, 72.0), Vector2(viewport_size.x - 16.0, viewport_size.y - 112.0))
var actor_name_rect: Rect2 = visuals._actor_name_badge_screen_rect(actor, visuals._zoom())
var badge_rect: Rect2 = visuals._pickup_badge_screen_rect(prop_screen_bounds, badge_size, safe_rect, actor_name_rect)
_check(safe_rect.encloses(badge_rect), "Pick up pill stays inside the visible Stage safe area")
_check(not badge_rect.intersects(prop_screen_bounds), "Pick up pill sits outside the target prop bounds")
if _can_capture_image():
await RenderingServer.frame_post_draw
var image: Image = root.get_texture().get_image()
var path: String = CAPTURE_DIR.path_join("pickup_step_one_pill_over_prop.png")
var save_error: Error = image.save_png(ProjectSettings.globalize_path(path)) if image != null and not image.is_empty() else ERR_CANT_CREATE
_check(save_error == OK, "focused Godot run saves the rendered pickup action marker PNG")
print("PICKUP_ACTION_MARKER_CAPTURE path=%s size=%s" % [path, str(image.get_size()) if image != null else "missing"])
print("PHASE20B_PICKUP_ACTION_MARKER_RESULT failures=%d" % _failures)
await _finish()
quit(1 if _failures > 0 else 0)
func _wait_frames(count: int) -> void:
for _index: int in count:
await process_frame
func _can_capture_image() -> bool:
var display_name: String = DisplayServer.get_name().to_lower()
return not OS.has_feature("headless") and display_name != "headless" and display_name != "dummy"
func _check(condition: bool, message: String) -> void:
if condition:
print("PASS: %s" % message)
else:
_failures += 1
push_error("FAIL: " + message)
func _finish() -> void:
if _stage != null and is_instance_valid(_stage):
_stage.queue_free()
await process_frame