OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_bot_tiers.gd

153 lines · gd ref:
extends SceneTree
## GATE — bot bodies get SIMULATION TIERS + SLEEP (creatures → bots, #114 phase 1).
## Creatures and bots are one concept (user 2026-10-13); the tiers were the creature
## spawner's alone, so a placed bot's body ticked every physics frame however far away.
##   T1  a placed bot's body is scheduled by BotRegistry (it stops ticking itself)
##   T2  near an observer it is NEAR and ticks
##   T3  with every observer far away it SLEEPS: zero ticks
##   T4  back near, it ticks again
##   T5  a body rebuilt (body kind changed) swaps schedulers cleanly: old out, new in
##   T6  deleting the bot takes its body out of the scheduler
##
## Run: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_bot_tiers.gd

const HOME := Vector3(4, 0.5, 4)
const FAR := Vector3(4000, 0.5, 4000)

var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _reg: Node
var _dot := ""
var _obs: Node3D
var _step := 0
var _wait := 0
var _ticks0 := 0
var _old_body: Node3D


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


func _body() -> Node3D:
	var n: Node = _map.object_node(_dot)
	return n.body() if n != null and n.has_method("body") else null


func _sched() -> CreatureScheduler:
	return _reg.scheduler()


## Untyped: a freed body must be askable too (the scheduler must not hold it).
func _held(b: Variant) -> bool:
	return b != null and (_sched().get("_e") as Dictionary).has(b)


func _ticks(b: Node3D) -> int:
	return int(_sched().tick_count.get(b, 0))


## Every observer to `at`: the stand-in player and, if the map has one, the camera.
func _look_from(at: Vector3) -> void:
	_obs.global_position = at
	var cam := root.get_viewport().get_camera_3d()
	if cam != null:
		cam.global_position = at


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		_obs = Node3D.new()
		_obs.name = "GateObserver"
		root.add_child(_obs)
		_obs.add_to_group("player")
		return false
	if _frame < 30:
		return false
	# PHYSICS frames, not process frames: headless, process frames run far faster than
	# the 60 Hz physics tick the scheduler (and its 0.5 s retier) runs on.
	if Engine.get_physics_frames() < _wait:
		return false
	_step += 1
	match _step:
		1: _t1()
		2: _t2()
		3: _t3_start()
		4: _t3_check()
		5: _t4()
		6: _t5()
		7: _t6()
		8: _t6_check()
		_:
			if checks < 11:          # a gate that died early must not read as a pass
				ok = false
				print("FAIL: only %d checks ran -- the gate died before its assertions" % checks)
			print("\n%d checks — %s" % [checks, "PASS" if ok else "FAIL"])
			quit(0 if ok else 1)
			return true
	_wait = Engine.get_physics_frames() + 45     # > RETIER_INTERVAL (0.5 s) at 60 Hz: a retier has run
	return false


func _t1() -> void:
	_reg = root.get_node("/root/BotRegistry")
	_look_from(HOME)
	_dot = _map.create_object("bot", Transform3D(Basis(), HOME))
	_map.set_object_props(_dot, {"brain": "idle", "body": "crawler"})
	var b := _body()
	_p(b is GroundCrawler, "T1 the bot has a crawler body", b)
	_p(_held(b), "T1 BotRegistry schedules it")
	_p(b != null and bool(b.get("scheduled")), "T1 and it no longer ticks itself")


func _t2() -> void:
	var b := _body()
	_p(_sched().tier_of(b) == CreatureScheduler.Tier.NEAR, "T2 near an observer it is NEAR", _sched().tier_of(b))
	_p(_ticks(b) > 0, "T2 and ticks", _ticks(b))


func _t3_start() -> void:
	_look_from(FAR)


func _t3_check() -> void:
	var b := _body()
	_p(_sched().tier_of(b) == CreatureScheduler.Tier.SLEEP, "T3 every observer far away: it SLEEPS",
		_sched().tier_of(b))
	_ticks0 = _ticks(b)


func _t4() -> void:
	var b := _body()
	_p(_ticks(b) == _ticks0, "T3 zero ticks while asleep", [_ticks0, _ticks(b)])
	_look_from(HOME)
	_ticks0 = _ticks(b)


func _t5() -> void:
	var b := _body()
	_p(_sched().tier_of(b) == CreatureScheduler.Tier.NEAR and _ticks(b) > _ticks0, "T4 back near: it ticks again",
		[_sched().tier_of(b), _ticks0, _ticks(b)])
	_old_body = b
	_map.set_object_props(_dot, {"body": "hopper"})
	var nb := _body()
	_p(nb is Hopper and _held(nb), "T5 a rebuilt body (hopper) is scheduled", nb)
	_p(not _held(_old_body), "T5 and the old crawler is out of the scheduler")


func _t6() -> void:
	_old_body = _body()
	_map.delete_objects([_dot])


func _t6_check() -> void:
	_p(not _held(_old_body), "T6 deleting the bot takes its body out of the scheduler")