extends SceneTree
## GATE — the STARTING MAP (map_test, a map_dc_level) gives bots navigation.
## (Run on map_dev_test since 2026-10-06: map_test became the minimal start map with no
## walls; map_dev_test is the old start map, the same map_dc_level nav source.)
##
## User 2026-10-04: bots walk over / through obstacles on the starting map. Cause:
## map_test had NO nav source at all (DCLevel is a plain Node3D; only map_flat built
## a DCNavVolume), so the A* brain logged "no nav source" and stood still, and the
## other brains moved without one. Asserts, on the real map scene:
## N1 the map has a child answering nav_find_path
## N2 a route from one side of Wall1 to the other goes AROUND it: it exists, and
## no segment crosses the wall's footprint
## N3 how long the graph takes to build (printed; the budget is set from it)
##
## Run: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_level_nav.gd
var ok := true
var _frames := 0
var _map: Node = null
## Bots find the world through /root/Game.current_map (see gate_nav_end_to_end).
class StubGame extends Node3D:
var current_map: Node = null
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _initialize() -> void:
var game := StubGame.new()
game.name = "Game"
root.add_child(game)
_map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
game.add_child(_map)
game.current_map = _map
func _process(_dt: float) -> bool:
_frames += 1
if _frames < 30: # deferred level import + deferred nav build
return false
_run()
print("GATE level_nav: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return true
func _nav() -> Node:
for c in _map.get_children():
if c.has_method("nav_find_path"):
return c
return null
## Does the 2D segment a-b (x, z) pass through the axis-aligned rectangle?
static func _segment_hits_rect(a: Vector2, b: Vector2, lo: Vector2, hi: Vector2) -> bool:
var steps := 64
for i in steps + 1:
var p := a.lerp(b, float(i) / steps)
if p.x > lo.x and p.x < hi.x and p.y > lo.y and p.y < hi.y:
return true
return false
func _run() -> void:
var nav := _nav()
_p(nav != null, "N1 map_test has a child answering nav_find_path")
if nav == null:
return
# N3 — a fresh build, timed.
if _map.has_method("nav_rebuild"):
var t0 := Time.get_ticks_usec()
var chunks: int = _map.nav_rebuild()
print("N3 nav_rebuild: %d chunks in %.1f ms" % [chunks, (Time.get_ticks_usec() - t0) / 1000.0])
# N2 — around Wall1 (its collider, read from the scene, not assumed). Read from a
# FRESH, unbaked instance of map_test.tscn: since 2026-10-05 the live map bakes its
# scene boxes into base brushes and removes the nodes (map_dc_level
# _bake_scene_boxes), so Wall1/Shape no longer exists under _map.
var src: Node = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
var wb := src.get_node_or_null(^"Wall1") as Node3D
var gb := src.get_node_or_null(^"Ground") as Node3D
var wall := src.get_node_or_null(^"Wall1/Shape") as CollisionShape3D
var ground := src.get_node_or_null(^"Ground/GroundShape") as CollisionShape3D
_p(wall != null and ground != null, "N2 the scene has Wall1 and the ground")
if wall == null or ground == null:
src.free()
return
var root_xf: Transform3D = (_map as Node3D).global_transform
var wc := (root_xf * wb.transform * wall.transform).origin
var wh := (wall.shape as BoxShape3D).size * 0.5
var gy := (root_xf * gb.transform * ground.transform).origin.y + (ground.shape as BoxShape3D).size.y * 0.5
src.free()
# Either side of the wall's thin axis, at its middle, on the ground.
var thin := 0 if wh.x < wh.z else 2
var off := Vector3.ZERO
off[thin] = wh[thin] + 2.0
var from := Vector3(wc.x, gy + 0.1, wc.z) - off
var to := Vector3(wc.x, gy + 0.1, wc.z) + off
var r: Dictionary = nav.nav_find_path(from, to)
_p(bool(r.get("found", false)), "N2 a route exists from %s to %s (%s)" % [from, to, r.get("error", "")])
if not bool(r.get("found", false)):
return
var pts: Array = [from]
for w in (r.get("waypoints", PackedVector3Array()) as PackedVector3Array):
pts.append(w)
pts.append(to)
var lo := Vector2(wc.x - wh.x, wc.z - wh.z)
var hi := Vector2(wc.x + wh.x, wc.z + wh.z)
var crossed := 0
for i in pts.size() - 1:
var a: Vector3 = pts[i]
var b: Vector3 = pts[i + 1]
if _segment_hits_rect(Vector2(a.x, a.z), Vector2(b.x, b.z), lo, hi):
crossed += 1
_p(crossed == 0, "N2 no segment of the %d-point route crosses Wall1's footprint (%d do)" % [pts.size(), crossed])