OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_headless_map.gd

114 lines · gd ref:
extends SceneTree
## Gate: a headless peer's map is a DECLARATION, and the state namespace matches
## the map that is actually loaded.
##
## THE BUG THIS EXISTS FOR, two of them in six lines. game.gd's `_ready` had:
##
##     if DisplayServer.get_name() == "headless":
##         for child in maps_node.get_children(): child.queue_free()
##         var server_map := load("res://scenes/maps/map_server.tscn")
##
## 1. The map is hardcoded. spec/bot-testbed.md (e1) lists "a map argument for
##    headless, so a peer-bot's map is a declaration rather than a hardcoded
##    branch" as an obligation of the containment work, and it was never built.
##    A peer-bot reaches map_flat only if a map change happens to replicate to
##    it, which is the containment question from the other side -- the process
##    that runs the bots cannot say which world it is in.
##
## 2. current_map_path was NOT updated. It still held _initial_map_path()
##    (map_test, or HYPER_TPS_MAP), while map_server was the scene in the tree.
##    Twelve lines later `GameState.current_map_id =
##    map_id_from_path(current_map_path)` seeds the per-map state namespace from
##    it -- so every map-scoped key on a headless peer, world:bots: and
##    world:creatures: included, was namespaced to a map that was not loaded.
##    Bot containment is map-scoped, so this lands squarely on the peer-bot.
##
## HYPER_TPS_MAP is reused rather than a new variable invented: it is already the
## declaration for the graphical path, and two ways to say the same thing is how
## the headless and graphical branches drift apart again.

var ok := true
var _ran := false

const HEADLESS_DEFAULT := "res://scenes/maps/map_server.tscn"
const A_REAL_MAP := "res://scenes/maps/map_flat.tscn"


func _p(c: bool, m: String) -> void:
	print(("ok   " if c else "FAIL: ") + m)
	if not c:
		ok = false


func _process(_delta: float) -> bool:
	if _ran:
		return true
	_ran = true
	_run()
	print("GATE headless_map: %s" % ("PASS" if ok else "FAIL"))
	if not ok:
		quit(1)
	return true


func _run() -> void:
	var gs: GDScript = load("res://scenes/game/game.gd")
	_p(gs != null, "game.gd loads")
	if gs == null:
		return

	_p(ResourceLoader.exists(HEADLESS_DEFAULT), "map_server.tscn exists")
	_p(ResourceLoader.exists(A_REAL_MAP), "map_flat.tscn exists")

	var had := OS.has_environment("HYPER_TPS_MAP")
	var prior := OS.get_environment("HYPER_TPS_MAP") if had else ""

	# Presence is checked BEFORE calling. A .call() for a function that does not
	# exist raises a SCRIPT ERROR that aborts _run() where it stands -- which on
	# the first run of this gate meant no FAIL was ever recorded and it printed
	# PASS against a game.gd that had none of this. A gate that cannot fail is
	# worse than no gate.
	var declared_ok: bool = gs.has_method("_headless_map_path")
	_p(declared_ok, "game.gd exposes _headless_map_path()")
	if not declared_ok:
		return

	# ---- undeclared: the server map, i.e. today's behaviour preserved ----
	OS.set_environment("HYPER_TPS_MAP", "")
	var undeclared: Variant = gs.call("_headless_map_path")
	_p(String(undeclared) == HEADLESS_DEFAULT,
		"undeclared headless map is map_server (got %s)" % undeclared)

	# ---- declared: the declaration wins ----
	OS.set_environment("HYPER_TPS_MAP", A_REAL_MAP)
	var declared: String = String(gs.call("_headless_map_path"))
	_p(declared == A_REAL_MAP,
		"HYPER_TPS_MAP declares the headless map (got %s)" % declared)

	# ---- declared but nonexistent: fall back, never load nothing ----
	OS.set_environment("HYPER_TPS_MAP", "res://scenes/maps/does_not_exist.tscn")
	var bogus: String = String(gs.call("_headless_map_path"))
	_p(bogus == HEADLESS_DEFAULT,
		"a nonexistent declaration falls back to map_server (got %s)" % bogus)

	if had:
		OS.set_environment("HYPER_TPS_MAP", prior)
	else:
		OS.set_environment("HYPER_TPS_MAP", "")

	# ---- the namespace must follow the loaded map ----
	var src: String = FileAccess.get_file_as_string("res://scenes/game/game.gd")
	var idx: int = src.find('DisplayServer.get_name() == "headless"')
	_p(idx >= 0, "the headless branch is still findable")
	if idx < 0:
		return
	var branch: String = src.substr(idx, 900)
	_p(branch.contains("current_map_path"),
		"the headless branch assigns current_map_path -- or the state namespace " +
		"names a map that is not loaded")

	# SELF-CHECK: the pre-fix branch must be detected as missing that assignment.
	var old: String = 'if DisplayServer.get_name() == "headless":\n\t\tfor child in maps_node.get_children():\n\t\t\tchild.queue_free()\n\t\tvar server_map := load("res://scenes/maps/map_server.tscn")\n'
	_p(not old.contains("current_map_path"),
		"self-check: the pre-fix headless branch is detected as not assigning it")