OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_nav_follows_edits.gd

85 lines · gd ref:
extends Node
## GATE — THE BOT NAV GRAPH FOLLOWS LEVEL EDITS (#104 leftover, user 2026-10-05: "do
## the nav fix"). It was built once per map entry and never again, so bots routed
## through walls added later and around walls since deleted.
##
##   1. the load builds it ONCE (the load's own folds do not queue a second build)
##   2. a burst of 5 edits does not rebuild during the burst ...
##   3. ... and rebuilds exactly ONCE, NAV_REBUILD_DELAY after it
##   4. the rebuilt graph has the new geometry: a 12 x 12 m platform (4-5 m, 1 m thick:
##      thinner than a nav cell is not a floor the grid can see) adds standable cells and
##      its top joins the navigable set; deleting it removes them again
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_nav_follows_edits.tscn

var fails := 0
var checks := 0


func _ok(what: String, cond: bool, note: Variant = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-64s %s  %s" % [what, "OK" if cond else "**FAIL**", str(note)])


func _box(lo: Vector3, hi: Vector3) -> Array:
	return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]


func _ready() -> void:
	var gs: Node = get_tree().root.get_node("/root/GameState")
	gs.route("world:level:*", 1, "peer")
	var ks: Node = get_tree().root.get_node_or_null("/root/KeetService")
	if ks != null:
		ks.current_member_id = "gate-author"
	var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 6:
		await get_tree().process_frame
	await get_tree().create_timer(1.5).timeout          # past any load-queued rebuild
	await _run(map)
	if checks < 7:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE nav_follows_edits: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _standable(map: Node) -> int:
	var v: Node = map.get_nav_volume()
	return int((v.stats() as Dictionary).get("standable", -1)) if v != null else -1


func _run(map: Node3D) -> void:
	var persist: Node = map.get("_persist")
	_ok("1. the load built the nav graph once", int(map.get("nav_builds")) == 1, map.get("nav_builds"))
	var s0 := _standable(map)
	var b0: int = map.get("nav_builds")

	# A burst: a 12 x 12 platform at 4 m (made of 4 pieces) + one more small box.
	var made: Array = []
	for i in 4:
		var x0 := 20.0 + float(i % 2) * 6.0
		var z0 := 20.0 + float(i / 2) * 6.0
		made.append(persist.record("brush.create", {"planes": _box(Vector3(x0, 4, z0), Vector3(x0 + 6, 5, z0 + 6)),
			"proj": [], "material_id": 0, "level_id": 0, "sky": false}))
		persist.refold()
		await get_tree().create_timer(0.1).timeout
	made.append(persist.record("brush.create", {"planes": _box(Vector3(40, 0, 40), Vector3(41, 1, 41)),
		"proj": [], "material_id": 0, "level_id": 0, "sky": false}))
	persist.refold()
	_ok("2. no rebuild during the burst", int(map.get("nav_builds")) == b0, "%d builds" % (int(map.get("nav_builds")) - b0))
	await get_tree().create_timer(1.4).timeout
	_ok("3. exactly one rebuild after it", int(map.get("nav_builds")) == b0 + 1, "%d builds" % (int(map.get("nav_builds")) - b0))
	var s1 := _standable(map)
	_ok("4. the platform added standable cells", s1 > s0 + 50, "%d -> %d" % [s0, s1])
	var on_top: Dictionary = map.get_nav_volume().find_path(Vector3(0, 0.1, 0), Vector3(26, 5.1, 26))
	_ok("4. its top is on the navigable set", String(on_top.get("error", "")) != "goal is not on the navigable set", on_top.get("error", "found"))

	map.delete_brushes(made.map(func(d: String) -> int: return map.level.brush_by_key(d)))
	await get_tree().create_timer(1.4).timeout
	var s2 := _standable(map)
	_ok("4. deleting it rebuilt again", int(map.get("nav_builds")) == b0 + 2, "%d builds" % (int(map.get("nav_builds")) - b0))
	_ok("4. ... back to the original standable set", s2 == s0, "%d -> %d" % [s1, s2])