OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_bot_marker.gd

149 lines · gd ref:
extends SceneTree
## Gate: a bot body says what it is, on sight.
##
## Every position body was the same grey box from the same _make_marker(id), so a
## room full of bots was a room full of identical boxes and there was no way to
## tell a map-bound bot from a session-bound one, or a bot with a running brain
## from an inert placeholder. The testbed exists to be LOOKED AT; markers that
## carry no information make it useless for the thing it is for.
##
## The mapping is chosen so that what you can see is what the test is about:
##
##   colour    CONTAINMENT — which lifetime this bot is bound to, i.e. exactly
##             what a map change is supposed to prove. Change map, the red ones
##             should go and the green ones should stay.
##   emission  A BRAIN IS RUNNING. Matte means brain=none, a placeholder body
##             nothing is driving. Glowing means something is ticking it, so a
##             glowing box that never moves is a real failure you can see.
##   shape     THE BODY KIND. One shape today because body=position is the only
##             visible body; derived rather than hardcoded so body=player does
##             not arrive looking identical.
##   label     the declaration in words, because colour alone cannot carry an id.
##
## ⚠ body=none bots have NO NODE, by design (spec/bot-testbed.md (e2)), so they
## are not here and cannot be. They are observable only through the state they
## write — beacon's state:bot:beacon:ticks. That is a real gap in the testbed and
## it is not this gate's job to hide it.

var ok := true
var _ran := false


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 bot_marker: %s" % ("PASS" if ok else "FAIL"))
	if not ok:
		quit(1)
	return true


func _mesh_of(marker: Node) -> MeshInstance3D:
	for c in marker.get_children():
		if c is MeshInstance3D:
			return c as MeshInstance3D
	return null


func _label_of(marker: Node) -> Label3D:
	for c in marker.get_children():
		if c is Label3D:
			return c as Label3D
	return null


func _mat_of(marker: Node) -> StandardMaterial3D:
	var m := _mesh_of(marker)
	if m == null:
		return null
	var mat: Variant = m.material_override
	if mat == null and m.mesh != null:
		mat = m.mesh.surface_get_material(0)
	return mat as StandardMaterial3D


func _run() -> void:
	var br: Node = root.get_node_or_null("BotRegistry")
	_p(br != null, "BotRegistry autoload present")
	if br == null:
		return
	# ⛔ ARITY IS CHECKED BEFORE CALLING. has_method() is true for the old
	# one-argument _make_marker, and calling it with four raises a SCRIPT ERROR
	# that aborts _run() where it stands — which on this gate's first run meant no
	# FAIL was recorded and it printed PASS against a registry that had none of
	# this. Same way gate_headless_map failed open. A gate that cannot fail is
	# worse than no gate.
	var argc := -1
	for m: Dictionary in br.get_method_list():
		if String(m.get("name", "")) == "_make_marker":
			argc = (m.get("args", []) as Array).size()
			break
	_p(argc >= 4, "_make_marker takes the declaration (id, bindings, body, brain) — got %d args" % argc)
	if argc < 4:
		return

	# The new signature carries the declaration. Called with it, a marker must
	# come back differentiated; the old one took only an id and could not.
	var m_map: Node = br._make_marker("b_map", ["map"], "position", "script_file")
	var m_room: Node = br._make_marker("b_room", ["room"], "position", "none")
	var m_sess: Node = br._make_marker("b_sess", ["session"], "position", "none")
	for pair in [[m_map, "map"], [m_room, "room"], [m_sess, "session"]]:
		_p(pair[0] != null, "%s-bound marker builds" % pair[1])
	if m_map == null or m_room == null or m_sess == null:
		return

	# ---- colour carries containment ----
	var c_map := _mat_of(m_map)
	var c_room := _mat_of(m_room)
	var c_sess := _mat_of(m_sess)
	_p(c_map != null and c_room != null and c_sess != null,
		"every marker carries a material (colour is settable at all)")
	if c_map == null or c_room == null or c_sess == null:
		return
	_p(c_map.albedo_color != c_room.albedo_color,
		"map and room bindings are different colours")
	_p(c_room.albedo_color != c_sess.albedo_color,
		"room and session bindings are different colours")
	_p(c_map.albedo_color != c_sess.albedo_color,
		"map and session bindings are different colours")

	# ---- emission says a brain is running ----
	_p(c_map.emission_enabled, "a bot WITH a brain emits light")
	_p(not c_room.emission_enabled, "a bot with brain=none does not")

	# SELF-CHECK: emission must follow the brain and not the binding, or the two
	# assertions above would both pass on a marker that simply lit map-bound bots.
	var m_room_brain: Node = br._make_marker("b_room2", ["room"], "position", "script_file")
	var c_room_brain := _mat_of(m_room_brain)
	_p(c_room_brain != null and c_room_brain.emission_enabled,
		"self-check: a ROOM-bound bot with a brain also emits, so emission tracks the brain")
	if c_room_brain != null:
		_p(c_room_brain.albedo_color == c_room.albedo_color,
			"self-check: and its colour still tracks the binding, not the brain")

	# ---- shape is derived from the body ----
	var mesh_map := _mesh_of(m_map)
	_p(mesh_map != null and mesh_map.mesh != null, "the body has a mesh")

	# ---- the label spells out the declaration ----
	var lab := _label_of(m_map)
	_p(lab != null, "the marker carries a Label3D")
	if lab != null:
		var t: String = lab.text
		_p(t.contains("b_map"), "the label names the bot (%s)" % t)
		_p(t.contains("map"), "and its binding")
		_p(t.contains("position"), "and its body")
		_p(t.contains("script_file"), "and its brain")

	for n in [m_map, m_room, m_sess, m_room_brain]:
		if n != null:
			n.free()