OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_map_wiring.gd

106 lines · gd ref:
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)