extends Node
## PROBE (not a gate) — fold cost of the L3 cable extras (#120, docs/node-tree-and-stacks.md
## §14.8 step 7, "measured, not asserted"), the same method as _probe_mod_fold_time: a FULL
## refold (the brush half forced), median of 3, and the live brush count, after each stage:
## base the map, no cables
## cable + one straight-ish 10 m cable, the defaults (round 8, max_len 1 m)
## strands that cable with 7 strands
## bundle + a 20-cable bundle between two boxes 10 m apart (slack 1 m: they hang and bend)
## bundle7 the bundle's cables with 7 strands each
## A cable's curve edit re-folds IN FULL (§11.2: a followed curve), so each line is also what
## one curve edit costs in that room.
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . res://scenes/test/_probe_cable_fold_time.tscn
const BundleCables := preload("res://scenes/maps/curves/bundle_cables.gd")
const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")
const ShapeFold := preload("res://scenes/maps/shapes/param/shape_fold.gd")
var persist: Node
var level: Node
var map: Node3D
func _ready() -> void:
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer")
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "probe"
get_tree().root.get_node("/root/GameMode").set_mode("build")
map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
persist = map.get("_persist")
level = map.get("level")
var rows: Array = []
rows.append(["base", _time(), _n()])
# ---- one 10 m cable ----
var c := map.add_cable([CurveMath.point(Vector3(300, 2, 300), Vector3.ZERO), CurveMath.point(Vector3(305, 3, 302), Vector3.ZERO),
CurveMath.point(Vector3(310, 2, 300), Vector3.ZERO)], false, {}) as String
rows.append(["cable", _time(), _n()])
_set_params(c, {"strands": 7})
rows.append(["strands 7", _time(), _n()])
# ---- a 20-cable bundle: the facing sides of two boxes 10 m apart ----
var A := {"pts": PackedVector3Array([Vector3(400, 0, 300), Vector3(400, 0, 302), Vector3(400, 2, 302), Vector3(400, 2, 300)]), "n": Vector3.RIGHT}
var B := {"pts": PackedVector3Array([Vector3(410, 0, 300), Vector3(410, 2, 300), Vector3(410, 2, 302), Vector3(410, 0, 302)]), "n": Vector3.LEFT}
var dots: Array = map.cable_bundle([A, B], {"count": 20, "random": true, "seed": 1, "tension": 1.0,
"jitter": 0.3, "slack": 1.0}, {"radius": 0.03})
rows.append(["bundle 20", _time(), _n()])
_split(dots)
persist.begin_gesture("strands")
for d: String in dots:
persist.record("shape.set", {"target": d, "params": {"strands": 7}})
persist.end_gesture()
rows.append(["bundle 20 x 7 strands", _time(), _n()])
var prev := 0
for r: Array in rows:
print("PROBE %-24s full refold %8.1f ms %6d live brushes (+%d)" % [r[0], r[1], r[2], int(r[2]) - prev])
prev = int(r[2])
print("PROBE cables in the bundle: %d" % dots.size())
get_tree().quit(0)
func _set_params(dot: String, p: Dictionary) -> void:
persist.begin_gesture("probe")
persist.record("shape.set", {"target": dot, "params": p})
persist.end_gesture()
## WHERE THE BUNDLE'S FOLD TIME GOES, the generator alone (no level), median of 3:
## world_pieces for the 20 cables; its fine CurveMath.sample alone; and DCLevel.hull_of alone
## on the same rings (the hulls world_pieces makes, re-hulled from their corners).
func _split(dots: Array) -> void:
var t: Object = ParamShapes.get_type("cable")
var curves: Dictionary = persist.curve_states()
var jobs: Array = []
for d: String in dots:
var p: Dictionary = t.resolve(persist.shape_state(d)["params"])
jobs.append([curves[String(p["curve"])], p])
var wp: Array = []
var smp: Array = []
var hl: Array = []
var pieces: Array = []
for rep in 3:
var t0 := Time.get_ticks_usec()
pieces = []
for j: Array in jobs:
pieces.append_array(t.world_pieces(j[0], j[1]))
wp.append(float(Time.get_ticks_usec() - t0) / 1000.0)
t0 = Time.get_ticks_usec()
for j2: Array in jobs:
var cv: Dictionary = j2[0]
var L := CurveMath.length(cv["points"], bool(cv["closed"]))
CurveMath.sample(cv["points"], bool(cv["closed"]), clampi(ceili(L / 0.02) + 1, 16, 4096))
smp.append(float(Time.get_ticks_usec() - t0) / 1000.0)
var pts_all: Array = []
for pc: Dictionary in pieces:
pts_all.append(FaceKit.corners(pc["planes"]))
t0 = Time.get_ticks_usec()
for cs: PackedVector3Array in pts_all:
DCLevel.hull_of(cs)
hl.append(float(Time.get_ticks_usec() - t0) / 1000.0)
wp.sort()
smp.sort()
hl.sort()
print("PROBE split (bundle 20, %d pieces): world_pieces %.1f ms | its fine sampling %.1f ms | hull_of alone %.1f ms"
% [pieces.size(), wp[1], smp[1], hl[1]])
# ShapeFold.expand on the room's shape records: the generator + resolve_states + the
# brush.create records. What a full refold spends beyond this is the level's side.
var shape_recs: Array = (persist.records(false) as Array).filter(func(r: Dictionary) -> bool: return ShapeFold.handles(String(r["kind"])))
var ex: Array = []
for rep2 in 3:
var t1 := Time.get_ticks_usec()
ShapeFold.expand(shape_recs, {}, curves)
ex.append(float(Time.get_ticks_usec() - t1) / 1000.0)
ex.sort()
print("PROBE split: ShapeFold.expand (whole room) %.1f ms" % ex[1])
func _n() -> int:
return (level.get_live_brush_ids() as PackedInt32Array).size()
## A full refold, median of 3 (as _probe_mod_fold_time).
func _time() -> float:
var ts: Array = []
for i in 3:
persist.set("_brush_ver_folded", -1) # force the brush half
persist.set("_ix_tail_ok", false)
var t0 := Time.get_ticks_usec()
persist.refold()
ts.append(float(Time.get_ticks_usec() - t0) / 1000.0)
ts.sort()
return ts[1]