extends SceneTree
## Probe (user question 2026-10-03): while MOVING brushes, show the real brushes. Which
## costs less latency per step?
## A) live: translate_brush + DCLevel.rebuild() on every step;
## B) proxy: hide the brush + rebuild ONCE (lift), build a mesh from its faces, move
## that mesh by transform per step (no rebuild), unhide + translate + rebuild ONCE
## on drop.
## Measured headless on map_flat with N textured 1 m boxes (CPU side: mesh + collision
## build; GPU upload is not included). Run:
## HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/_probe_move_cost.gd
const SIZES := [50, 200, 800]
const STEPS := 20
var _frame := 0
var _map: Node
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)
return false
if _frame < 30:
return false
_run()
quit(0)
return true
func _box(lo: Vector3, hi: Vector3) -> PackedFloat32Array:
return PackedFloat32Array([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])
static func _ms(t0: int) -> float:
return (Time.get_ticks_usec() - t0) / 1000.0
static func _med(a: Array) -> float:
var s := a.duplicate()
s.sort()
return s[s.size() / 2]
## A mesh of one brush's faces (fan-triangulated windings). Material-less: this
## measures the cost of making the proxy, not how it looks.
func _proxy_mesh(level: Node, b: int) -> ArrayMesh:
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var planes: PackedFloat32Array = level.get_brush(b)["planes"]
for f in planes.size() / 4:
var w: PackedVector3Array = level.get_face_winding(b, f)
if w.size() < 3:
continue
var n := Vector3(planes[f * 4], planes[f * 4 + 1], planes[f * 4 + 2])
for i in range(1, w.size() - 1):
for p: Vector3 in [w[0], w[i], w[i + 1]]:
st.set_normal(n)
st.add_vertex(p)
return st.commit()
func _run() -> void:
var level: Node = _map.get_node("DCLevel")
var ft: Array = []
for i in 6:
ft.append({"tex": "kenney-prototype/dark_01", "mode": 1})
var made := 0
for n: int in SIZES:
while made < n:
var x := float(made % 30) * 1.5
var z := float(made / 30) * 1.5
level.add_brush(_box(Vector3(x, 0, z), Vector3(x + 1, 1, z + 1)), 1, PackedInt32Array(), 0,
false, "probe-%d" % made, false, "", ft)
made += 1
var t0 := Time.get_ticks_usec()
level.rebuild()
var base := _ms(t0)
var ids: Array = level.get_live_brush_ids()
var b: int = ids[ids.size() / 2]
# A) live rebuild per step
var a_steps: Array = []
for s in STEPS:
t0 = Time.get_ticks_usec()
level.translate_brush(b, Vector3(0.028, 0, 0), true)
level.rebuild()
a_steps.append(_ms(t0))
# B) proxy
t0 = Time.get_ticks_usec()
level.set_brush_hidden(b, true)
level.rebuild()
var lift := _ms(t0)
t0 = Time.get_ticks_usec()
var mi := MeshInstance3D.new()
mi.mesh = _proxy_mesh(level, b)
root.add_child(mi)
var build := _ms(t0)
var b_steps: Array = []
for s in STEPS:
t0 = Time.get_ticks_usec()
mi.position += Vector3(0.028, 0, 0)
b_steps.append(_ms(t0))
t0 = Time.get_ticks_usec()
level.set_brush_hidden(b, false)
level.translate_brush(b, Vector3(0.028 * STEPS, 0, 0), true)
level.rebuild()
var drop := _ms(t0)
mi.queue_free()
print("PROBE N=%4d tris=%6d | full rebuild %7.2f ms | A per step median %7.2f max %7.2f | B lift %7.2f proxy %6.3f per step %6.4f drop %7.2f" % [
n, level.get_triangle_count(), base, _med(a_steps), a_steps.max(), lift, build, _med(b_steps), drop])