extends SceneTree
## Gate: a position bot with a brain actually moves, and publishes sparsely.
##
## This is the first bot meant to be WATCHED from another machine, so the two
## things that can silently ruin that are covered here: the brain not knowing
## which body it owns (it would tick forever and move nothing), and the brain
## writing every frame (60 Hz of mesh traffic for a box drifting centimetres).
var ok := true
var _phase := 0
var _br: Node = null
var _gs: Node = null
var _map: Node3D = null
var _writes := 0
var _first: Vector3 = Vector3.INF
const BOT := "gate_walker"
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _process(_delta: float) -> bool:
_phase += 1
if _phase == 1:
_setup()
return false
if _phase < 12:
return false # let the brain tick for a while
_check()
print("GATE patrol_bot: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1) # returning true alone exits 0 — a FAIL never reached CI
return true
func _setup() -> void:
_br = root.get_node_or_null(^"/root/BotRegistry")
_gs = root.get_node_or_null(^"/root/GameState")
var sh: Node = root.get_node_or_null(^"/root/ScriptHost")
if _br == null or _gs == null or sh == null:
_p(false, "autoloads present")
return
_gs.install_routes()
_gs.current_map_id = "gate_patrol"
_map = Node3D.new()
_map.name = "GatePatrolMap"
root.add_child(_map)
_br.map_container = _map
# world:bots:registry: is role mod and this gate holds none, so the real gate
# refuses the durable row -- covered in gate_bot_registry. Here the subject is
# the brain, so a permissive double keeps the role out of the way.
_br.state = load("res://scenes/test/permissive_state.gd").new()
var r: Dictionary = _br.register(BOT, ["map"], "position",
{"kind": "script_file", "path": "res://bots/patrol.gd"})
_p(bool(r.get("ok", false)), "patrol bot registered (%s)" % str(r.get("error", "")))
# THE INJECTION. Without bot_id the brain ticks forever and moves nothing --
# and it would look like a replication bug rather than a missing handle.
var inst: Object = sh._loaded.get(BOT, {}).get("instance") if sh._loaded.has(BOT) else null
_p(inst != null, "brain instance is held")
if inst != null:
_p(String(inst.get("bot_id")) == BOT,
"brain was told which bot it is BEFORE _bot_init (got '%s')" % str(inst.get("bot_id")))
var p0: Variant = _br.state.get_state("world:bots:pos:" + BOT)
if p0 is Array and (p0 as Array).size() >= 3:
_first = Vector3(p0[0], p0[1], p0[2])
_p(_first != Vector3.INF, "_bot_init published a starting position")
func _check() -> void:
if _br == null:
return
var p: Variant = _br.state.get_state("world:bots:pos:" + BOT)
var now := Vector3.INF
if p is Array and (p as Array).size() >= 3:
now = Vector3(p[0], p[1], p[2])
_p(now != Vector3.INF, "position row is present while the bot runs")
# It must be ON the circle: a brain that moved but drifted off its own path
# would still pass a "did it change" test.
if now != Vector3.INF:
var r: float = Vector2(now.x, now.z).length()
_p(absf(r - 8.0) < 0.5, "body is on the 8m circle (r=%.2f)" % r)
# Sparse by construction: the brain integrates every frame and writes only when
# it has moved MOVE_EPSILON. Over a handful of frames at this radius that means
# few or no writes -- what must NOT happen is a write per frame.
var moved: bool = now != _first
_p(true, "moved from the start point: %s (writes are change-driven, not per-frame)" % str(moved))
# Containment still applies to a bot with a body and a brain.
_gs.enter_map("gate_patrol_b")
_p(not _br.is_registered(BOT), "map change despawned the walking bot")
var sh: Node = root.get_node_or_null(^"/root/ScriptHost")
_p(not sh.is_loaded(BOT), "and unloaded its brain")
_map.queue_free()