Phase 14 - Organized directories.

This commit is contained in:
2026-10-03 19:13:39 -04:00
parent 6666173c4f
commit d950fd9d9a
307 changed files with 1452 additions and 1110 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
extends SceneTree
const IO := preload("res://scripts/stage_scene_io.gd")
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const IO := preload("res://scripts/stage/stage_scene_io.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const FIXTURE_PATH := "user://cli_fixture.scene"
+120
View File
@@ -0,0 +1,120 @@
extends SceneTree
const CONTENT_PATHS := preload("res://scripts/shared/content_paths.gd")
const CLI_LAUNCHER := preload("res://scripts/tools/cli_scene_launcher.gd")
const STAGE_SCENE_IO := preload("res://scripts/stage/stage_scene_io.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const LEGACY_STAGE_PATH: String = "res://tests/.phase14_legacy_paths.scene"
const RESAVED_STAGE_PATH: String = "res://tests/.phase14_resaved_paths_resaved.scene"
var _checks: int = 0
var _failures: int = 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_path_mapping()
_test_moved_stage_file_paths()
await _test_legacy_stage_round_trip()
_cleanup()
print("RESULT: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
func _expect(condition: bool, message: String) -> void:
_checks += 1
if condition:
print("PASS: " + message)
else:
_failures += 1
push_error("Content path test failed: " + message)
func _test_path_mapping() -> void:
_expect(CONTENT_PATHS.canonicalize("res://stickmen/basic.stk") == "res://content/stickmen/basic.stk", "legacy stickman resource paths map into content")
_expect(CONTENT_PATHS.canonicalize("res://props/briefcase.prop") == "res://content/props/briefcase.prop", "legacy prop resource paths map into content")
_expect(CONTENT_PATHS.canonicalize("res://game_scenes/bend.scene") == "res://content/stages/bend.scene", "legacy stage resource paths map into content")
_expect(CONTENT_PATHS.canonicalize("res://unmanaged/asset.stk") == "res://unmanaged/asset.stk", "unknown resource paths pass through")
_expect(CONTENT_PATHS.canonicalize("user://stickmen/basic.stk") == "user://stickmen/basic.stk", "user-selected paths pass through")
_expect(CONTENT_PATHS.canonicalize("/tmp/external/stickmen/basic.stk") == "/tmp/external/stickmen/basic.stk", "external absolute paths pass through")
var project_root: String = ProjectSettings.globalize_path("res://").trim_suffix("/")
var old_absolute: String = project_root.path_join("stickmen/basic.stk")
var new_absolute: String = project_root.path_join("content/stickmen/basic.stk")
_expect(CONTENT_PATHS.canonicalize(old_absolute) == new_absolute, "legacy absolute paths inside the project migrate in place")
var cli_result: Dictionary = CLI_LAUNCHER.parse(["--scene", "res://game_scenes/bend.scene"])
_expect(String(cli_result.get("scene_path", "")) == "res://content/stages/bend.scene", "CLI stage paths migrate before playback")
func _test_moved_stage_file_paths() -> void:
var result: Dictionary = STAGE_SCENE_IO.read_file("res://game_scenes/bend.scene")
_expect(bool(result.get("ok", false)), "stage IO opens a stage through its previous project path")
func _test_legacy_stage_round_trip() -> void:
_cleanup()
var legacy_data: Dictionary = {
"version": STAGE_SCENE_IO.FILE_VERSION,
"scene_name": "Legacy Asset Paths",
"camera": {"position": {"x": 0.0, "y": -400.0}, "zoom": 0.5},
"objects": [
{
"file_id": "stickman_1",
"type": "stickman",
"position": {"x": 0.0, "y": 0.0},
"rotation": 0.0,
"stk_path": "res://stickmen/test.stk",
"action_queue": [],
},
{
"file_id": "prop_1",
"stage_object_id": "prop_1",
"type": "prop",
"position": {"x": 180.0, "y": 0.0},
"rotation": 0.0,
"asset_id": "res://props/briefcase.prop",
"scale": {"x": 1.0, "y": 1.0},
"material_preset": 1,
},
],
"event_rules": [],
"metadata": {},
}
_expect(bool(STAGE_SCENE_IO.write_file_safe(LEGACY_STAGE_PATH, legacy_data).get("ok", false)), "legacy stage fixture writes")
var stage: Node2D = STAGE_SCENE.instantiate() as Node2D
root.add_child(stage)
await process_frame
var loaded: bool = bool(stage.call("_load_scene_from_path", LEGACY_STAGE_PATH))
_expect(loaded, "legacy stage with moved stickman and prop assets loads")
var world: Node = stage.get("_world") as Node
_expect(world != null and world.get_child_count() == 2, "both moved assets are present after load")
var scene_data: Dictionary = stage.call("_collect_scene_data", false) as Dictionary
var objects: Array = scene_data.get("objects", [])
var saved_stickman_path: String = ""
var saved_prop_path: String = ""
for value: Variant in objects:
if not value is Dictionary:
continue
var entry: Dictionary = value as Dictionary
if String(entry.get("type", "")) == "stickman":
saved_stickman_path = String(entry.get("stk_path", ""))
elif String(entry.get("type", "")) == "prop":
saved_prop_path = String(entry.get("asset_id", ""))
_expect(saved_stickman_path == "res://content/stickmen/test.stk", "resaved stickman reference uses the canonical path")
_expect(saved_prop_path == "res://content/props/briefcase.prop", "resaved prop reference uses the canonical path")
_expect(bool(STAGE_SCENE_IO.write_file_safe(RESAVED_STAGE_PATH, scene_data).get("ok", false)), "canonical stage document resaves")
var reread: Dictionary = STAGE_SCENE_IO.read_file(RESAVED_STAGE_PATH)
_expect(bool(reread.get("ok", false)), "resaved stage document reads successfully")
var current_path: String = String(stage.get("_current_scene_path"))
_expect(current_path == LEGACY_STAGE_PATH, "unmanaged stage document path remains unchanged")
stage.queue_free()
await process_frame
func _cleanup() -> void:
for path: String in [LEGACY_STAGE_PATH, RESAVED_STAGE_PATH]:
if FileAccess.file_exists(path):
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
+1
View File
@@ -0,0 +1 @@
uid://biayrtuu3udms
+11 -11
View File
@@ -1,13 +1,13 @@
extends SceneTree
const PROP_LIBRARY := preload("res://scripts/prop_library.gd")
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const THUMBNAIL_CACHE := preload("res://scripts/thumbnails/thumbnail_cache.gd")
const PROP_FILE := preload("res://scripts/prop_file.gd")
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const MISSING_PROP_PLACEHOLDER := preload("res://scripts/missing_prop_placeholder.gd")
const PROP_LIBRARY := preload("res://scripts/props/prop_library.gd")
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
const THUMBNAIL_CACHE := preload("res://scripts/shared/thumbnails/thumbnail_cache.gd")
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const MISSING_PROP_PLACEHOLDER := preload("res://scripts/props/missing_prop_placeholder.gd")
const TEMP_PROP_PATH := "user://phase10_custom_integration.prop"
@@ -23,7 +23,7 @@ func _run() -> void:
_check(builtins.size() == 4, "built-in templates remain four entries")
var entries: Array[Dictionary] = PROP_LIBRARY.scan()
_check(entries.size() == 8, "scan returns built-ins and checked-in custom props")
var entry: Dictionary = PROP_LIBRARY.get_entry("res://props/briefcase.prop")
var entry: Dictionary = PROP_LIBRARY.get_entry("res://content/props/briefcase.prop")
_check(not entry.is_empty() and bool(entry.get("is_custom", false)), "custom path resolves without selector scan")
_check((entry.get("shapes", []) as Array).size() > 1, "custom entry preserves the full shape stack")
var hull: PackedVector2Array = entry.get("collision_hull", PackedVector2Array())
@@ -31,7 +31,7 @@ func _run() -> void:
var world: Node2D = Node2D.new()
root.add_child(world)
var spawner: StageSpawner = STAGE_SPAWNER.new(world)
var prop: PropBlock = spawner.spawn_prop_asset("res://props/briefcase.prop", Vector2(100, 50))
var prop: PropBlock = spawner.spawn_prop_asset("res://content/props/briefcase.prop", Vector2(100, 50))
_check(prop != null and prop.has_custom_shapes(), "spawner creates layered custom PropBlock")
if prop != null:
var visuals: Node2D = prop.get_node_or_null(NodePath("Visuals")) as Node2D
@@ -41,7 +41,7 @@ func _run() -> void:
var aabb: Rect2 = STAGE_SELECTION.get_world_aabb(prop)
_check(aabb.size.x > 0.0 and aabb.size.y > 0.0, "selection bounds include custom visuals")
var cache: ThumbnailCache = THUMBNAIL_CACHE.new()
_check(cache.prop_file_png("res://props/briefcase.prop") != cache.prop_file_png("user://briefcase.prop"), "thumbnail keys include complete canonical path")
_check(cache.prop_file_png("res://content/props/briefcase.prop") != cache.prop_file_png("user://briefcase.prop"), "thumbnail keys include complete canonical path")
world.queue_free()
await process_frame
await _test_authored_visual_values()
+5 -5
View File
@@ -1,10 +1,10 @@
extends SceneTree
const PROP_CANVAS := preload("res://scripts/prop_canvas.gd")
const PROP_ANCHORS := preload("res://scripts/prop_anchor_data.gd")
const STAGE_GIZMOS := preload("res://scripts/stage_gizmos.gd")
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const PROP_CANVAS := preload("res://scripts/editors/prop/prop_canvas.gd")
const PROP_ANCHORS := preload("res://scripts/props/prop_anchor_data.gd")
const STAGE_GIZMOS := preload("res://scripts/stage/stage_gizmos.gd")
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
var failures: int = 0
var checks: int = 0
+18 -18
View File
@@ -19,15 +19,15 @@
extends SceneTree
const STICKMAN_LIBRARY := preload("res://scripts/stickman_library.gd")
const PROP_LIBRARY := preload("res://scripts/prop_library.gd")
const THUMBNAIL_CACHE := preload("res://scripts/thumbnails/thumbnail_cache.gd")
const ASSET_SELECTOR := preload("res://scripts/asset_selector.gd")
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
const RIG := preload("res://scripts/stickman_rig.gd")
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const ASSET_SELECTOR_SCENE := preload("res://scenes/asset_selector.tscn")
const STICKMAN_LIBRARY := preload("res://scripts/characters/stickman/stickman_library.gd")
const PROP_LIBRARY := preload("res://scripts/props/prop_library.gd")
const THUMBNAIL_CACHE := preload("res://scripts/shared/thumbnails/thumbnail_cache.gd")
const ASSET_SELECTOR := preload("res://scripts/shared/ui/asset_selector.gd")
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
const RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const ASSET_SELECTOR_SCENE := preload("res://scenes/stage/asset_selector.tscn")
const TEST_DIR := "user://phase3b_test"
@@ -83,7 +83,7 @@ func _test_scan() -> void:
_check(String(_by_path(entries, "break.stk")["name"]) == "break",
"break.stk falls back to filename basename")
_check(not _by_path(entries, "basic.stk").is_empty(), "basic.stk entry has data")
var found: Dictionary = lib.find_by_path("res://stickmen/basic.stk")
var found: Dictionary = lib.find_by_path("res://content/stickmen/basic.stk")
_check(not found.is_empty() and String(found["name"]) == "Basic",
"find_by_path resolves basic.stk")
@@ -109,11 +109,11 @@ func _test_make_entry() -> void:
print("")
print("--- make_entry() ---")
var lib = STICKMAN_LIBRARY.new()
var entry := lib.make_entry("res://stickmen/basic.stk")
var entry := lib.make_entry("res://content/stickmen/basic.stk")
_check(not entry.is_empty(), "make_entry on valid path returns an entry")
_check(String(entry["name"]) == "Basic", "make_entry name == 'Basic'")
_check(not (entry["data"] as Dictionary).is_empty(), "make_entry carries parsed data")
_check(lib.make_entry("res://stickmen/does_not_exist.stk").is_empty(),
_check(lib.make_entry("res://content/stickmen/does_not_exist.stk").is_empty(),
"make_entry on missing path returns {}")
_ensure_test_dir()
_write_file(TEST_DIR + "/bad.stk", "nope nope")
@@ -153,7 +153,7 @@ func _test_spawner_registry() -> void:
_check(not ids.has("crate") and not ids.has("ball"),
"crate/ball registry entries removed")
_check(spawner.get_selected_prop_id() == "crate", "default selected_prop_id == 'crate'")
_check(spawner.get_selected_stickman_path() == "res://stickmen/test.stk",
_check(spawner.get_selected_stickman_path() == "res://content/stickmen/test.stk",
"default selected_stickman_path == test.stk")
world.queue_free()
@@ -183,7 +183,7 @@ func _test_spawn_stickman() -> void:
var world := Node2D.new()
root.add_child(world)
var spawner = STAGE_SPAWNER.new(world)
spawner.selected_stickman_path = "res://stickmen/basic.stk"
spawner.selected_stickman_path = "res://content/stickmen/basic.stk"
var rig: Node2D = spawner.spawn("stickman", Vector2.ZERO)
_check(rig != null, "stickman spawns from basic.stk")
_check(rig is RIG, "basic.stk spawns a StickmanRig")
@@ -194,8 +194,8 @@ func _test_thumbnail_cache() -> void:
print("")
print("--- ThumbnailCache key/png formatting ---")
var cache = THUMBNAIL_CACHE.new()
var mtime: int = FileAccess.get_modified_time("res://stickmen/test.stk")
var key := cache.stickman_key("res://stickmen/test.stk")
var mtime: int = FileAccess.get_modified_time("res://content/stickmen/test.stk")
var key := cache.stickman_key("res://content/stickmen/test.stk")
_check(key == "test_%d" % mtime, "stickman_key embeds basename + mtime (got '%s')" % key)
_check(cache.stickman_png(key) == "user://thumbnails/stickmen/" + key + ".png",
"stickman_png path formatting (got '%s')" % cache.stickman_png(key))
@@ -226,8 +226,8 @@ func _test_pagination() -> void:
func _test_scene_loads() -> void:
print("")
print("--- Scene load checks ---")
_check(load("res://scenes/sandbox_stage.tscn") != null, "sandbox_stage.tscn loads")
_check(load("res://scenes/asset_selector.tscn") != null, "asset_selector.tscn loads")
_check(load("res://scenes/stage/sandbox_stage.tscn") != null, "sandbox_stage.tscn loads")
_check(load("res://scenes/stage/asset_selector.tscn") != null, "asset_selector.tscn loads")
var selector: AssetSelector = ASSET_SELECTOR_SCENE.instantiate() as AssetSelector
_check(selector != null, "asset_selector.tscn instantiates as AssetSelector")
root.add_child(selector)
+4 -4
View File
@@ -55,8 +55,8 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const ASSET_SELECTOR_SCENE := preload("res://scenes/asset_selector.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const ASSET_SELECTOR_SCENE := preload("res://scenes/stage/asset_selector.tscn")
## Button stylebox states the old pre-highlight could have overridden on a cell.
const BUTTON_STYLEBOX_STATES: Array[String] = [
@@ -235,7 +235,7 @@ func _test_bug4_selector_prehighlight_removed() -> void:
_check(sel.get("_selected_id") == null, "member _selected_id removed")
# Source-presence: none of the deleted tokens may lurk in the script.
var file := FileAccess.open("res://scripts/asset_selector.gd", FileAccess.READ)
var file := FileAccess.open("res://scripts/shared/ui/asset_selector.gd", FileAccess.READ)
_check(file != null, "asset_selector.gd is readable")
if file != null:
var text := file.get_as_text()
@@ -271,7 +271,7 @@ func _test_bug4_stage_grid_no_prehighlight() -> void:
var stage := _new_stage()
# Selected file missing -> fall-back-to-first-entry still runs before open().
stage._spawner.selected_stickman_path = "res://stickmen/does_not_exist.stk"
stage._spawner.selected_stickman_path = "res://content/stickmen/does_not_exist.stk"
stage._open_selector("stickman")
_check(stage._selector_open, "stage selector opens from a palette toggle")
_check(stage._spawner.get_selected_stickman_path().ends_with("basic.stk"),
+1 -1
View File
@@ -38,7 +38,7 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
var _checks := 0
var _failures := 0
+13 -13
View File
@@ -31,15 +31,15 @@
extends SceneTree
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
const TRIGGER_REGISTRY := preload("res://scripts/trigger_registry.gd")
const QUEUE_PANEL_SCENE := preload("res://scenes/queue_panel.tscn")
const RULE_PANEL_SCENE := preload("res://scenes/rule_panel.tscn")
const ACTION_EDITOR_SCENE := preload("res://scenes/action_editor.tscn")
const RULE_EDITOR_SCENE := preload("res://scenes/rule_editor.tscn")
const WAYPOINT_CONTEXT := preload("res://scripts/waypoint_context.gd")
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
const TRIGGER_REGISTRY := preload("res://scripts/stage/trigger_registry.gd")
const QUEUE_PANEL_SCENE := preload("res://scenes/editors/stage/queue_panel.tscn")
const RULE_PANEL_SCENE := preload("res://scenes/editors/stage/rule_panel.tscn")
const ACTION_EDITOR_SCENE := preload("res://scenes/editors/stage/action_editor.tscn")
const RULE_EDITOR_SCENE := preload("res://scenes/editors/stage/rule_editor.tscn")
const WAYPOINT_CONTEXT := preload("res://scripts/stage/waypoint_context.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
var _checks := 0
var _failures := 0
@@ -207,10 +207,10 @@ func _test_rule_action_converters() -> void:
func _test_scene_shells() -> void:
print("")
print("--- Phase 3c scene shells load + _ready ---")
_check(load("res://scenes/queue_panel.tscn") != null, "queue_panel.tscn loads")
_check(load("res://scenes/rule_panel.tscn") != null, "rule_panel.tscn loads")
_check(load("res://scenes/action_editor.tscn") != null, "action_editor.tscn loads")
_check(load("res://scenes/rule_editor.tscn") != null, "rule_editor.tscn loads")
_check(load("res://scenes/editors/stage/queue_panel.tscn") != null, "queue_panel.tscn loads")
_check(load("res://scenes/editors/stage/rule_panel.tscn") != null, "rule_panel.tscn loads")
_check(load("res://scenes/editors/stage/action_editor.tscn") != null, "action_editor.tscn loads")
_check(load("res://scenes/editors/stage/rule_editor.tscn") != null, "rule_editor.tscn loads")
var qp: QueuePanel = QUEUE_PANEL_SCENE.instantiate()
root.add_child(qp)
+2 -2
View File
@@ -70,8 +70,8 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const RIG := preload("res://scripts/stickman_rig.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
## Physics frames per measurement block (head-mirror stability sample).
const SAMPLE_FRAMES := 50
+1 -1
View File
@@ -1,6 +1,6 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
var _checks := 0
var _failures := 0
+10 -10
View File
@@ -2,12 +2,12 @@ extends SceneTree
## Focused Phase 4 Final Bugs regression checks. Run with the project console
## executable and `--headless --script res://tests/test_phase4_final_bugs.gd`.
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
const GIZMOS := preload("res://scripts/stage_gizmos.gd")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const ACTION_EDITOR_SCENE := preload("res://scenes/action_editor.tscn")
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STICKMAN_FACTORY := preload("res://scripts/stickman_factory.gd")
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
const GIZMOS := preload("res://scripts/stage/stage_gizmos.gd")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
const ACTION_EDITOR_SCENE := preload("res://scenes/editors/stage/action_editor.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const STICKMAN_FACTORY := preload("res://scripts/characters/stickman/stickman_factory.gd")
var _checks: int = 0
var _failures: int = 0
@@ -312,15 +312,15 @@ func _test_stop_clears_head_reaction() -> void:
func _test_source_contracts() -> void:
var stage_source: String = FileAccess.get_file_as_string("res://scripts/sandbox_stage.gd")
var rig_source: String = FileAccess.get_file_as_string("res://scripts/stickman_rig.gd")
var ragdoll_source: String = FileAccess.get_file_as_string("res://scripts/stickman_ragdoll.gd")
var stage_source: String = FileAccess.get_file_as_string("res://scripts/editors/stage/sandbox_stage.gd")
var rig_source: String = FileAccess.get_file_as_string("res://scripts/characters/stickman/stickman_rig.gd")
var ragdoll_source: String = FileAccess.get_file_as_string("res://scripts/characters/stickman/stickman_ragdoll.gd")
_check(stage_source.contains("TERRAIN_DRAG_THRESHOLD_PX"), "terrain uses a drag threshold")
_check(stage_source.contains("PhysicsServer2D.body_set_state"), "frozen prop commits synchronize physics")
_check(stage_source.contains("transform_started.connect"), "gizmo start cancels deferred restoration")
_check(stage_source.contains("if current_mode != StageMode.PLAY:"), "collision callbacks are gated to PLAY mode")
_check(stage_source.contains("_speak_stop_current_check") and stage_source.contains("\"stop_current\": _speak_stop_current_check.button_pressed"), "legacy rule speak persists stop-current")
var runner_source: String = FileAccess.get_file_as_string("res://scripts/stickman_action_runner.gd")
var runner_source: String = FileAccess.get_file_as_string("res://scripts/characters/stickman/stickman_action_runner.gd")
_check(runner_source.contains("_primary_speech") and runner_source.contains("_overlay_speech"), "runner separates primary and overlay speech")
_check(rig_source.contains("_sync_speech_bubble_mirror"), "rig counter-mirrors LEFT speech")
_check(rig_source.contains("initialize_spawn_pose_from_skeleton"), "rig captures factory spawn pose")
+6 -6
View File
@@ -40,11 +40,11 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const TERRAIN_BLOCK := preload("res://scripts/terrain_block.gd")
const OVERLAY_SCRIPT := preload("res://scripts/stage_placement_overlay.gd")
const RIG := preload("res://scripts/stickman_rig.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
const TERRAIN_BLOCK := preload("res://scripts/terrain/terrain_block.gd")
const OVERLAY_SCRIPT := preload("res://scripts/stage/stage_placement_overlay.gd")
const RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
# The Ground registry template is authored 200x32 but StageSpawner sanitizes
# every terrain polygon onto the 16px TERRAIN_GRID_SIZE before placement, so the
@@ -297,7 +297,7 @@ func _test_bug2_overlay_clear() -> void:
# Source-presence check: clear_terrain_guide() and clear_action() must both
# call queue_redraw() so the erased line is removed (Godot 4.4 exposes no
# is_queued_for_redraw(), so this is the headless-observable regression).
var file := FileAccess.open("res://scripts/stage_placement_overlay.gd", FileAccess.READ)
var file := FileAccess.open("res://scripts/stage/stage_placement_overlay.gd", FileAccess.READ)
_check(file != null, "stage_placement_overlay.gd is readable")
if file != null:
var text := file.get_as_text()
+5 -5
View File
@@ -21,7 +21,7 @@
# * After Back the rule-builder state is IDLE/empty AND _action_popup is
# visible again (PopupMenu.visible IS observable headless).
#
# Bug 3 (head mirror jitter): scenes/stickman_rig.tscn's Head LookAt modification is a
# Bug 3 (head mirror jitter): scenes/characters/stickman/stickman_rig.tscn's Head LookAt modification is a
# full-range (-180..180), non-inverted, non-local-space constraint.
# * Constraint flags/band are asserted directly on the instantiated rig.
# * The Head marker is settled at the walk-left pose (-100,-614), then
@@ -47,9 +47,9 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const RIG := preload("res://scripts/stickman_rig.gd")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
# Physics frames / thresholds for the Bug 3 head-jitter measurement.
const WARMUP_FRAMES := 30
@@ -145,7 +145,7 @@ func _test_bug1_gizmo_draw_guard_source() -> void:
# Drawing output is not observable headless, so assert the guard that makes
# a deleted-but-still-referenced hover node disappear from the draw pass.
# This mirrors the source-presence checks used elsewhere in the repo.
var file := FileAccess.open("res://scripts/stage_gizmos.gd", FileAccess.READ)
var file := FileAccess.open("res://scripts/stage/stage_gizmos.gd", FileAccess.READ)
_check(file != null, "stage_gizmos.gd is readable")
if file != null:
var text := file.get_as_text()
+4 -4
View File
@@ -14,10 +14,10 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const TERRAIN_UTILS := preload("res://scripts/terrain_utils.gd")
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
const TRIGGER_AREA := preload("res://scripts/trigger_area.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const TERRAIN_UTILS := preload("res://scripts/terrain/terrain_utils.gd")
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
const TRIGGER_AREA := preload("res://scripts/stage/trigger_area.gd")
var _checks := 0
var _failures := 0
+4 -4
View File
@@ -12,10 +12,10 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
const TERRAIN_UTILS := preload("res://scripts/terrain_utils.gd")
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
const TERRAIN_UTILS := preload("res://scripts/terrain/terrain_utils.gd")
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
var _checks := 0
var _failures := 0
+3 -3
View File
@@ -8,7 +8,7 @@
# live mouse position on every hop, so the menus could land far away from
# the stickman whose action popup opened the session.
#
# Fix: scripts/sandbox_stage.gd now keeps session anchor state
# Fix: scripts/editors/stage/sandbox_stage.gd now keeps session anchor state
# `_popup_anchor: Rect2i` / `_popup_anchor_set: bool` plus helpers
# `_set_popup_anchor(rect)`, `_clear_popup_anchor()`, and
# `_popup_anchor_rect()` (self-records from `_mouse_popup_rect()` when
@@ -43,8 +43,8 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
var _checks := 0
var _failures := 0
+3 -3
View File
@@ -15,9 +15,9 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const TERRAIN_UTILS := preload("res://scripts/terrain_utils.gd")
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const TERRAIN_UTILS := preload("res://scripts/terrain/terrain_utils.gd")
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
var _checks := 0
var _failures := 0
+3 -3
View File
@@ -15,8 +15,8 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const TERRAIN_BLOCK := preload("res://scripts/terrain_block.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const TERRAIN_BLOCK := preload("res://scripts/terrain/terrain_block.gd")
var _checks := 0
var _failures := 0
@@ -93,7 +93,7 @@ func _run() -> void:
func _spawn_crate(world: Node2D, pos: Vector2) -> Node2D:
var crate = preload("res://scripts/prop_block.gd").new()
var crate = preload("res://scripts/props/prop_block.gd").new()
crate.name = "Crate"
crate.polygon_points = PackedVector2Array([
Vector2(-24, -24), Vector2(24, -24), Vector2(24, 24), Vector2(-24, 24),
+3 -3
View File
@@ -13,9 +13,9 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const RIG := preload("res://scripts/stickman_rig.gd")
const LOCOMOTION := preload("res://scripts/stickman_locomotion.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
const LOCOMOTION := preload("res://scripts/characters/stickman/stickman_locomotion.gd")
const SETTLE_FRAMES := 60
const MAX_FRAMES := 300
+3 -3
View File
@@ -4,9 +4,9 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STICKMAN_RIG := preload("res://scripts/stickman_rig.gd")
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const STICKMAN_RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
var _checks: int = 0
var _failures: int = 0
+5 -5
View File
@@ -1,8 +1,8 @@
extends SceneTree
const IO := preload("res://scripts/stage_scene_io.gd")
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const MISSING_PLACEHOLDER := preload("res://scripts/missing_stickman_placeholder.gd")
const IO := preload("res://scripts/stage/stage_scene_io.gd")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const MISSING_PLACEHOLDER := preload("res://scripts/characters/stickman/missing_stickman_placeholder.gd")
var _checks := 0
var _failures := 0
@@ -145,7 +145,7 @@ func _test_stage_round_trip_and_file_state() -> void:
area.size = Vector2(160.0, 80.0)
area.scale = Vector2(1.2, 0.8)
stage._finalize_placed_node(area)
var rig: StickmanRig = stage._spawner.spawn_stickman_asset("res://stickmen/test.stk", Vector2(-100.0, -385.0))
var rig: StickmanRig = stage._spawner.spawn_stickman_asset("res://content/stickmen/test.stk", Vector2(-100.0, -385.0))
rig.set_facing_profile(StickmanRig.FacingProfile.LEFT)
rig.queue_action({"type": "wait", "duration": 0.25})
stage._finalize_placed_node(rig)
@@ -180,7 +180,7 @@ func _test_stage_round_trip_and_file_state() -> void:
var loaded_area := stage._world.get_child(2) as TriggerArea
_expect(loaded_area != null and loaded_area.size == Vector2(160.0, 80.0) and loaded_area.scale.is_equal_approx(Vector2(1.2, 0.8)), "area size and scale survive")
var loaded_rig := stage._world.get_child(3) as StickmanRig
_expect(loaded_rig != null and loaded_rig.source_stk_path == "res://stickmen/test.stk" and loaded_rig.get_facing_profile() == StickmanRig.FacingProfile.LEFT, "stickman provenance and facing survive")
_expect(loaded_rig != null and loaded_rig.source_stk_path == "res://content/stickmen/test.stk" and loaded_rig.get_facing_profile() == StickmanRig.FacingProfile.LEFT, "stickman provenance and facing survive")
_expect(absf((loaded_rig.global_transform * Transform2D.FLIP_X).get_rotation()) < 0.01, "LEFT-facing rig loads with one mirror and canonical rotation")
_expect(loaded_rig.get_queue().size() == 1 and stage._event_rules.size() == 1, "queue and rule survive")
var loaded_trigger: Dictionary = stage._event_rules[0]["trigger"]
+3 -3
View File
@@ -3,9 +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://scenes/stickman_rig.tscn")
const RIG := preload("res://scripts/stickman_rig.gd")
const LOC := preload("res://scripts/stickman_locomotion.gd")
const RIG_SCENE := preload("res://scenes/characters/stickman/stickman_rig.tscn")
const RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
const LOC := preload("res://scripts/characters/stickman/stickman_locomotion.gd")
var _checks: int = 0
var _failures: int = 0
+2 -2
View File
@@ -1,7 +1,7 @@
extends SceneTree
const SPAWNER := preload("res://scripts/stage_spawner.gd")
const CARD := preload("res://scripts/ui/asset_card.gd")
const SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
const CARD := preload("res://scripts/shared/ui/asset_card.gd")
var _failures: int = 0
func _init() -> void:
+1 -1
View File
@@ -1,6 +1,6 @@
extends SceneTree
const THEME_SCRIPT := preload("res://scripts/sandbox_theme.gd")
const THEME_SCRIPT := preload("res://scripts/stage/sandbox_theme.gd")
var _failures: int = 0
func _init() -> void:
+1 -1
View File
@@ -1,6 +1,6 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
var _failures: int = 0
func _initialize() -> void:
+4 -4
View File
@@ -1,9 +1,9 @@
extends SceneTree
const COLOR_SWATCH := preload("res://scripts/ui/color_swatch_selector.gd")
const SIZE_TOGGLE := preload("res://scripts/ui/preset_size_toggle.gd")
const FONT_PICKER := preload("res://scripts/ui/font_preset_picker.gd")
const EMOJI_GRID := preload("res://scripts/ui/emoji_quick_grid.gd")
const COLOR_SWATCH := preload("res://scripts/shared/ui/color_swatch_selector.gd")
const SIZE_TOGGLE := preload("res://scripts/shared/ui/preset_size_toggle.gd")
const FONT_PICKER := preload("res://scripts/shared/ui/font_preset_picker.gd")
const EMOJI_GRID := preload("res://scripts/shared/ui/emoji_quick_grid.gd")
var _failures: int = 0
func _init() -> void:
+1 -1
View File
@@ -1,6 +1,6 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const BONE_PATHS: Array[String] = [
"Skeleton2D/Torso/Head",
"Skeleton2D/Torso/LeftUpperArm",
+1 -1
View File
@@ -1,6 +1,6 @@
extends SceneTree
const STAGE_SCENE := preload("res://scenes/sandbox_stage.tscn")
const STAGE_SCENE := preload("res://scenes/stage/sandbox_stage.tscn")
const WALK_PHASES: Array[float] = [0.0, 0.08, 0.16, 0.24, 0.32, 0.40, 0.48, 0.56, 0.64, 0.72]
const SETTLE_FRAMES := 30
+1 -1
View File
@@ -1,6 +1,6 @@
extends SceneTree
const RIG_SCENE_PATH := "res://scenes/stickman_rig.tscn"
const RIG_SCENE_PATH := "res://scenes/characters/stickman/stickman_rig.tscn"
const EXPECTED_UID := "uid://dr5ef3l2var2s"
var _checks := 0
+5 -5
View File
@@ -1,11 +1,11 @@
extends SceneTree
## Phase 9a editor-only regression: anchor schema, alignment math, and preview isolation.
const ANCHORS := preload("res://scripts/prop_anchor_data.gd")
const PROP_FILE := preload("res://scripts/prop_file.gd")
const PREVIEW_SCENE := preload("res://scenes/prop_attach_preview.tscn")
const EDITOR_SCENE := preload("res://scenes/prop_editor.tscn")
const OVERLAY := preload("res://scripts/prop_anchor_overlay.gd")
const ANCHORS := preload("res://scripts/props/prop_anchor_data.gd")
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
const PREVIEW_SCENE := preload("res://scenes/editors/prop/prop_attach_preview.tscn")
const EDITOR_SCENE := preload("res://scenes/editors/prop/prop_editor.tscn")
const OVERLAY := preload("res://scripts/props/prop_anchor_overlay.gd")
var _checks: int = 0
var _failures: int = 0
+3 -3
View File
@@ -1,9 +1,9 @@
extends SceneTree
## Phase 9b regression: canonical origin presentation, bounds, centering, and save threshold.
const ANCHORS := preload("res://scripts/prop_anchor_data.gd")
const CANVAS_SCRIPT := preload("res://scripts/prop_canvas.gd")
const EDITOR := preload("res://scripts/prop_editor.gd")
const ANCHORS := preload("res://scripts/props/prop_anchor_data.gd")
const CANVAS_SCRIPT := preload("res://scripts/editors/prop/prop_canvas.gd")
const EDITOR := preload("res://scripts/editors/prop/prop_editor.gd")
var _checks: int = 0
var _failures: int = 0
+5 -5
View File
@@ -1,9 +1,9 @@
extends SceneTree
const THEME := preload("res://scripts/prop_theme.gd")
const CANVAS_SCENE := preload("res://scenes/prop_editor.tscn")
const PREVIEW_SCENE := preload("res://scenes/prop_attach_preview.tscn")
const PROP_FILE := preload("res://scripts/prop_file.gd")
const THEME := preload("res://scripts/editors/prop/prop_theme.gd")
const CANVAS_SCENE := preload("res://scenes/editors/prop/prop_editor.tscn")
const PREVIEW_SCENE := preload("res://scenes/editors/prop/prop_attach_preview.tscn")
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
var _failed: int = 0
var _count: int = 0
@@ -92,7 +92,7 @@ func _run() -> void:
var main_split := editor.get("_main_layout") as HSplitContainer
var inspector := editor.get("_inspector_column") as PanelContainer
_check("inspector starts at minimum usable width", main_split != null and inspector != null and inspector.size.x >= inspector.get_combined_minimum_size().x)
var eye_data: Dictionary = PROP_FILE.load("res://props/eye.prop")
var eye_data: Dictionary = PROP_FILE.load("res://content/props/eye.prop")
_check("eye prop fixture is available", not eye_data.is_empty())
editor.call("_apply_json_data", eye_data)
editor.size = Vector2(1200.0, 600.0)
+1 -1
View File
@@ -6,7 +6,7 @@
extends SceneTree
const PROP_CANVAS := preload("res://scripts/prop_canvas.gd")
const PROP_CANVAS := preload("res://scripts/editors/prop/prop_canvas.gd")
var _checks: int = 0
var _failures: int = 0
+3 -3
View File
@@ -5,9 +5,9 @@
extends SceneTree
const PROP_FILE := preload("res://scripts/prop_file.gd")
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
const TERRAIN_UTILS := preload("res://scripts/terrain_utils.gd")
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
const TERRAIN_UTILS := preload("res://scripts/terrain/terrain_utils.gd")
var _checks: int = 0
var _failures: int = 0
+13 -13
View File
@@ -5,8 +5,8 @@
extends SceneTree
const PROP_EDITOR := preload("res://scripts/prop_editor.gd")
const PROP_FILE := preload("res://scripts/prop_file.gd")
const PROP_EDITOR := preload("res://scripts/editors/prop/prop_editor.gd")
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
var _checks: int = 0
var _failures: int = 0
@@ -43,10 +43,10 @@ func check(label: String, condition: bool) -> void:
func _test_window_title() -> void:
check("untitled no path no dirty", PROP_EDITOR.build_window_title("", "", false) == "Untitled")
check("name dirty", PROP_EDITOR.build_window_title("My Crate", "", true) == "My Crate *")
check("name path clean", PROP_EDITOR.build_window_title("My Crate", "res://props/mycrate.prop", false) == "My Crate — mycrate.prop")
check("untitled path dirty", PROP_EDITOR.build_window_title("", "res://props/mycrate.prop", true) == "Untitled — mycrate.prop *")
check("name path clean", PROP_EDITOR.build_window_title("My Crate", "res://content/props/mycrate.prop", false) == "My Crate — mycrate.prop")
check("untitled path dirty", PROP_EDITOR.build_window_title("", "res://content/props/mycrate.prop", true) == "Untitled — mycrate.prop *")
check("whitespace name falls back", PROP_EDITOR.build_window_title(" ", "", false) == "Untitled")
check("extension kept in title", PROP_EDITOR.build_window_title("X", "res://props/ball.prop", false).ends_with("ball.prop"))
check("extension kept in title", PROP_EDITOR.build_window_title("X", "res://content/props/ball.prop", false).ends_with("ball.prop"))
func _test_mode_button_text() -> void:
@@ -80,11 +80,11 @@ func _test_save_safe() -> void:
func _test_recent_list() -> void:
var list: Array = PROP_EDITOR.update_recent_list([], "res://props/a.prop", 5)
check("recent empty list inserts", list == ["res://props/a.prop"])
list = PROP_EDITOR.update_recent_list(["res://props/b.prop", "res://props/a.prop"], "res://props/a.prop", 5)
check("recent dedupe moves to front", list == ["res://props/a.prop", "res://props/b.prop"])
list = PROP_EDITOR.update_recent_list(["res://props/b.prop", "res://props/c.prop"], "res://props/a.prop", 5)
check("recent insert keeps most-recent-first", list == ["res://props/a.prop", "res://props/b.prop", "res://props/c.prop"])
list = PROP_EDITOR.update_recent_list(["res://props/b.prop", "res://props/c.prop", "res://props/d.prop", "res://props/e.prop", "res://props/f.prop"], "res://props/a.prop", 5)
check("recent trims to max", list.size() == 5 and list[0] == "res://props/a.prop" and list[4] == "res://props/e.prop")
var list: Array = PROP_EDITOR.update_recent_list([], "res://content/props/a.prop", 5)
check("recent empty list inserts", list == ["res://content/props/a.prop"])
list = PROP_EDITOR.update_recent_list(["res://content/props/b.prop", "res://content/props/a.prop"], "res://content/props/a.prop", 5)
check("recent dedupe moves to front", list == ["res://content/props/a.prop", "res://content/props/b.prop"])
list = PROP_EDITOR.update_recent_list(["res://content/props/b.prop", "res://content/props/c.prop"], "res://content/props/a.prop", 5)
check("recent insert keeps most-recent-first", list == ["res://content/props/a.prop", "res://content/props/b.prop", "res://content/props/c.prop"])
list = PROP_EDITOR.update_recent_list(["res://content/props/b.prop", "res://content/props/c.prop", "res://content/props/d.prop", "res://content/props/e.prop", "res://content/props/f.prop"], "res://content/props/a.prop", 5)
check("recent trims to max", list.size() == 5 and list[0] == "res://content/props/a.prop" and list[4] == "res://content/props/e.prop")
+2 -2
View File
@@ -5,8 +5,8 @@
extends SceneTree
const PROP_THEME := preload("res://scripts/prop_theme.gd")
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
const PROP_THEME := preload("res://scripts/editors/prop/prop_theme.gd")
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
var _checks: int = 0
var _failures: int = 0
+1 -1
View File
@@ -5,7 +5,7 @@
extends SceneTree
const THUMBNAIL_CACHE := preload("res://scripts/thumbnails/thumbnail_cache.gd")
const THUMBNAIL_CACHE := preload("res://scripts/shared/thumbnails/thumbnail_cache.gd")
var _checks: int = 0
var _failures: int = 0
+4 -4
View File
@@ -1,8 +1,8 @@
# test_text_baseline_fix.gd
# Headless regression test for two text-baseline drawing fixes:
# 1. scripts/stickman_speech_bubble.gd _draw() text baseline now includes
# 1. scripts/speech/stickman_speech_bubble.gd _draw() text baseline now includes
# font.get_ascent(FONT_SIZE) so glyphs sit inside the bubble rect.
# 2. scripts/stage_director_visuals.gd _draw_rule() rule-label baseline now
# 2. scripts/stage/stage_director_visuals.gd _draw_rule() rule-label baseline now
# includes font.get_ascent(font_size) so white text sits on the dark rect.
#
# The math is replicated against the real ThemeDB.fallback_font using each
@@ -17,8 +17,8 @@
extends SceneTree
const SpeechBubbleScript := preload("res://scripts/stickman_speech_bubble.gd")
const StageDirectorVisualsScript := preload("res://scripts/stage_director_visuals.gd")
const SpeechBubbleScript := preload("res://scripts/speech/stickman_speech_bubble.gd")
const StageDirectorVisualsScript := preload("res://scripts/stage/stage_director_visuals.gd")
# Inline constants from stage_director_visuals.gd's _draw_rule() (they are not
# named constants there). RULE_LABEL_FONT_SIZE_PX is read from the script.