extends SceneTree
## Every map loads, every teleporter points at a map that EXISTS, and no two
## teleporters in a map sit on top of each other.
##
## "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_map_wiring.gd
##
## WHY THIS GATE EXISTS. A teleporter's target_map is a STRING. A renamed or
## moved map does not break the build, does not break the scene, and does not
## error until somebody walks into the pad — at which point game.gd tries to
## change to a map that is not there. Nothing else in the suite reads these.
##
## The overlap check is the same shape: two pads close enough to share a body
## fire in whichever order the physics server happens to report, so the player
## ends up somewhere neither pad meant to send them. That is not hypothetical
## spacing pedantry — TeleporterToHome in a map has to be placed AWAY from its
## own arrival point or it re-fires the instant the player lands and bounces
## them straight back, which both map_flat.tscn and map_mountain.tscn record.
## Teleporter Area3D bodies are a few metres across; 6 m apart is touching.
const MIN_SEPARATION := 6.0
var _fail := 0
var _checks := 0
func _ck(name: String, ok: bool, detail: String = "") -> void:
_checks += 1
if not ok:
_fail += 1
print(" %s %s%s" % ["ok " if ok else "FAIL", name,
("" if detail == "" else " [" + detail + "]")])
func _initialize() -> void:
var dir := DirAccess.open("res://scenes/maps")
var maps: Array[String] = []
if dir != null:
for f in dir.get_files():
if f.ends_with(".tscn"):
maps.append("res://scenes/maps/" + f)
maps.sort()
_ck("found maps to check", maps.size() > 0, "%d" % maps.size())
for path in maps:
var ps: PackedScene = load(path)
if ps == null:
_ck("%s loads" % path.get_file(), false)
continue
var inst: Node = ps.instantiate()
if inst == null:
_ck("%s instantiates" % path.get_file(), false)
continue
var pads: Array[Node] = []
for c in inst.get_children():
var t = c.get("target_map")
if t == null:
continue
pads.append(c)
if str(t) != "" and not ResourceLoader.exists(str(t)):
_ck("%s: %s -> a map that exists" % [path.get_file(), c.name],
false, str(t))
# ⚠ ONE CHECK PER MAP, not one per pad: a map with no teleporters would
# otherwise contribute nothing and read as silence rather than as a pass.
var worst := INF
var worst_pair := ""
for i in pads.size():
for j in range(i + 1, pads.size()):
var d: float = (pads[i] as Node3D).position.distance_to(
(pads[j] as Node3D).position)
if d < worst:
worst = d
worst_pair = "%s / %s" % [pads[i].name, pads[j].name]
_ck("%s: %d pads, none overlapping" % [path.get_file(), pads.size()],
pads.size() < 2 or worst >= MIN_SEPARATION,
"closest %.2f m (%s)" % [worst, worst_pair] if pads.size() > 1
else "nothing to compare")
inst.free()
# ⛔ map_flat MUST NOT CARRY A TERRAIN. It did, and a second child answering
# nav_find_path with a DISJOINT graph is what left the bots inert and walking
# to the origin forever — _terrain() binds to the FIRST one that answers.
# The mountain lives in map_mountain now; this keeps it from drifting back.
var flat: Node = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
var terrains := 0
for c in flat.get_children():
if c.get_class() == "DCTerrain":
terrains += 1
_ck("map_flat carries no DCTerrain", terrains == 0, "%d found" % terrains)
flat.free()
# ...and the mountain map must carry exactly the one.
var mtn: Node = (load("res://scenes/maps/map_mountain.tscn") as PackedScene).instantiate()
var mt := 0
var spawns := 0
for c in mtn.get_children():
if c.get_class() == "DCTerrain":
mt += 1
if c.is_in_group("spawn_points"):
spawns += 1
_ck("map_mountain carries exactly one DCTerrain", mt == 1, "%d found" % mt)
_ck("map_mountain has spawn points", spawns > 0, "%d" % spawns)
mtn.free()
print("\n%d/%d checks passed" % [_checks - _fail, _checks])
quit(1 if _fail > 0 else 0)