OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_bot_spawn_spread.gd

118 lines · gd ref:
extends SceneTree
## Gate: two bots do not spawn inside each other.
##
## register() wrote [0.0, 0.0, 0.0] for every position body and added the node
## without touching its transform, so every body started at the container's
## origin. A bot with brain=none never moves, so the whole matrix stacked into
## one box at the origin and looked like a single bot.
##
## The offset is DERIVED FROM THE ID, not assigned, for two reasons:
##   - it needs no API change and no caller has to think about placement
##   - it must agree across peers. A remote peer builds the marker before any
##     position row arrives, so if the two sides picked different spots the body
##     would visibly jump the moment the first row landed.
##
## The hash is computed here rather than with GDScript's hash(), which is not
## promised to be stable across engine versions -- and "stable across peers"
## includes peers on different builds.

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


func _run() -> void:
	var br: Node = root.get_node_or_null("BotRegistry")
	_p(br != null, "BotRegistry autoload present")
	if br == null:
		return

	var argc := -1
	for m: Dictionary in br.get_method_list():
		if String(m.get("name", "")) == "_spawn_offset_for":
			argc = (m.get("args", []) as Array).size()
			break
	_p(argc >= 1, "BotRegistry exposes _spawn_offset_for(id) — got %d args" % argc)
	if argc < 1:
		return

	var ids := ["mx-pos-map", "mx-pos-room", "mx-pos-session", "a", "b", "beacon", "patrol"]
	var seen: Dictionary = {}
	var positions: Array[Vector3] = []
	for id: String in ids:
		var p: Vector3 = br._spawn_offset_for(id)
		positions.append(p)
		var key := "%.3f,%.3f,%.3f" % [p.x, p.y, p.z]
		_p(not seen.has(key), "%s gets its own spot (%s)" % [id, key])
		seen[key] = id

	# ---- deterministic: the same id must always land in the same place ----
	for id: String in ids:
		_p(br._spawn_offset_for(id) == br._spawn_offset_for(id),
			"%s is deterministic" % id)

	# ---- bounded and above the floor ----
	for i in positions.size():
		var p: Vector3 = positions[i]
		var flat := Vector2(p.x, p.z).length()
		_p(flat > 0.5, "%s is off the exact origin (%.2f)" % [ids[i], flat])
		_p(flat < 40.0, "%s is not flung across the map (%.2f)" % [ids[i], flat])
		_p(p.y > 0.0, "%s sits above the floor, not half-buried (y=%.2f)" % [ids[i], p.y])

	# ---- and far enough apart to be separate boxes, not a pile ----
	var min_sep := 1e9
	for i in positions.size():
		for j in range(i + 1, positions.size()):
			min_sep = min(min_sep, positions[i].distance_to(positions[j]))
	_p(min_sep > 0.6, "closest pair is further apart than a marker is wide (%.2f)" % min_sep)

	# ---- register() must actually USE it ----
	var fake_map := Node3D.new()
	fake_map.name = "GateMapSpread"
	root.add_child(fake_map)
	br.map_container = fake_map
	var real_state: Object = br.state
	br.state = load("res://scenes/test/permissive_state.gd").new()

	_p(bool(br.register("sp_a", ["map"], "position").get("ok", false)), "sp_a registers")
	_p(bool(br.register("sp_b", ["map"], "position").get("ok", false)), "sp_b registers")
	var pa: Variant = br.state.get_state("world:bots:pos:sp_a")
	var pb: Variant = br.state.get_state("world:bots:pos:sp_b")
	_p(pa is Array and pb is Array, "both wrote a position row")
	if pa is Array and pb is Array:
		_p(str(pa) != str(pb), "and the two rows are NOT the same point (%s vs %s)" % [str(pa), str(pb)])
		_p(str(pa) != "[0, 0, 0]", "sp_a did not spawn at the bare origin")

	var na: Node3D = fake_map.get_node_or_null(^"Bot_sp_a") as Node3D
	var nb: Node3D = fake_map.get_node_or_null(^"Bot_sp_b") as Node3D
	_p(na != null and nb != null, "both nodes exist")
	if na != null and nb != null:
		_p(na.position != nb.position, "and the NODES are apart too, not just the rows")
		_p(na.position != Vector3.ZERO, "the node is placed, not left at the container origin")

	# SELF-CHECK: a constant offset would satisfy "deterministic" and "above the
	# floor" and fail only the distinctness checks — so assert directly that the
	# function discriminates on its input.
	_p(br._spawn_offset_for("x") != br._spawn_offset_for("y"),
		"self-check: different ids give different offsets, so it reads the id")

	br.despawn("sp_a")
	br.despawn("sp_b")
	br.state = real_state