extends SceneTree
## map_dc_world structure + teleporter round-trip gate — §5a.1.
##
## "$HYPER_TPS_GODOT" --headless --script scenes/test/dc_world_scene_gate.gd
##
## Checks the two things that make the world REACHABLE and LEAVABLE, because both
## fail silently:
##
## - a .tscn node of an unregistered type degrades to a plain Node rather than
## erroring (that is how 0.768.0 shipped an inert DCLevel), so "has a
## DCTerrain" has to be asserted by CLASS, not by name;
## - a teleporter whose target_map does not resolve is a prop you walk through.
##
## The scene is INSTANTIATED BUT NEVER ADDED TO THE TREE. _ready is what triggers
## the .MAP import, the rebuild and the terrain meshing; none of that belongs in a
## structural check, and dragging it into headless is what hyper-games #16 is
## about. So this asserts wiring only — that the pieces are present and point at
## each other. Whether the level RENDERS correctly inside the terrain is a thing
## you look at, not a thing this gate can claim.
const WORLD := "res://scenes/maps/map_dc_world.tscn"
const ENTRY := "res://scenes/maps/map_test.tscn"
var _failures: Array[String] = []
func _check(label: String, got: Variant, want: Variant) -> void:
var ok: bool = got == want
if not ok:
_failures.append("%s: got %s, want %s" % [label, str(got), str(want)])
print(" %-34s %-30s %s" % [label, str(got), "OK" if ok else "FAIL (want %s)" % str(want)])
func _find_child_of_class(n: Node, cls: String) -> Node:
for c in n.get_children():
if c.is_class(cls):
return c
return null
## Every teleporter in a scene, by target_map.
func _teleport_targets(n: Node) -> Dictionary:
var out := {}
for c in n.get_children():
if "target_map" in c and String(c.get("target_map")) != "":
out[String(c.get("target_map"))] = c.name
return out
func _initialize() -> void:
print("=== map_dc_world structure ===")
var ps: PackedScene = load(WORLD)
if ps == null:
print("FAIL — %s did not load" % WORLD)
quit(1)
return
var w: Node = ps.instantiate()
_check("root script is map_dc_world", w.get_script().resource_path,
"res://scenes/maps/map_dc_world.gd")
# BY CLASS, not by name — see the header.
var lvl := _find_child_of_class(w, "DCLevel")
var ter := _find_child_of_class(w, "DCTerrain")
_check("inherits a DCLevel", lvl != null, true)
_check("adds a DCTerrain", ter != null, true)
if lvl != null:
print(" DCLevel node '%s' class %s" % [lvl.name, lvl.get_class()])
if ter != null:
print(" DCTerrain node '%s' class %s" % [ter.name, ter.get_class()])
# The inherited pipeline: if these are gone, the subclass is not reusing
# map_dc_level at all and the duplicate this design exists to avoid is back.
_check("inherits TeleporterToHome", w.get_node_or_null("TeleporterToHome") != null, true)
print("\n=== round trip ===")
var from_world := _teleport_targets(w)
print(" world teleports to: %s" % str(from_world.keys()))
_check("world -> entry world", from_world.has(ENTRY), true)
var entry: Node = (load(ENTRY) as PackedScene).instantiate()
var from_entry := _teleport_targets(entry)
print(" entry teleports to: %s" % str(from_entry.keys()))
_check("entry world -> world", from_entry.has(WORLD), true)
# A target_map that does not resolve is a teleporter you walk through.
for target in from_entry.keys():
_check("entry target exists: %s" % String(target).get_file(),
ResourceLoader.exists(String(target)), true)
for target in from_world.keys():
_check("world target exists: %s" % String(target).get_file(),
ResourceLoader.exists(String(target)), true)
w.free()
entry.free()
print("\nRESULT: %s" % ("PASS" if _failures.is_empty() else "FAIL"))
for f in _failures:
print(" - %s" % f)
quit(0 if _failures.is_empty() else 1)