OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps commits / 2b1a31a

#127 probes: one-curve-edit stage + digest in _probe_fold_split; _probe_outliner_split
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2b1a31a22af1368789dc86588546b5750ca2b2f9
by plankatron <fleeky@gmail.com> · 2026-10-19T19:44:54.000Z

Changed files (3) +118 -0

scenes/test/_probe_fold_split.gd +35 -0
……@@ -204,3 +204,38 @@ func _split(case: String) -> void:
204204 pl2.append("%s %.1f" % [k3, _med(parts[k3])])
205205 print("PROBE %s: folded listener parts (median): %s" % [case, ", ".join(pl2)])
206206 print("PROBE %s: digest %s" % [case, h.finish().hex_encode().substr(0, 16)])
207 _edit_split(case, reps)
208
209
210## ONE CURVE EDITED — what a drag on a cable's point pays (map.set_curve_point: its record,
211## then the room's refold, listeners and all). The first curve's point 1 moves 5 cm up and
212## back, an ODD count of edits (2 x reps + 1), so the room ends 5 cm off where it began,
213## and its digest is the golden for a cache that must never answer for stale inputs.
214func _edit_split(case: String, reps: int) -> void:
215 var cs: Dictionary = map.curve_states()
216 if cs.is_empty():
217 return
218 var dots: Array = cs.keys()
219 dots.sort()
220 var cd: String = dots[0]
221 var ts := []
222 for e in reps * 2 + 1:
223 var q: Vector3 = (map.curve_states()[cd]["points"] as Array)[1]["p"]
224 var t0 := Time.get_ticks_usec()
225 map.set_curve_point(cd, 1, {"p": q + Vector3(0, 0.05 if e % 2 == 0 else -0.05, 0)})
226 ts.append(float(Time.get_ticks_usec() - t0) / 1000.0)
227 var blobs: Array = []
228 for b: int in (level.get_live_brush_ids() as PackedInt32Array):
229 var pl: PackedFloat32Array = level.get_brush(b)["planes"]
230 var bb := PackedByteArray()
231 for f in pl.size() / 4:
232 bb.append_array((level.get_face_winding(b, f) as PackedVector3Array).to_byte_array())
233 bb.append(255)
234 blobs.append(bb)
235 blobs.sort_custom(func(x: PackedByteArray, y: PackedByteArray) -> bool: return x.hex_encode() < y.hex_encode())
236 var h := HashingContext.new()
237 h.start(HashingContext.HASH_SHA256)
238 for bb2: PackedByteArray in blobs:
239 h.update(bb2)
240 print("PROBE %s: one curve point edited (set_curve_point, refold + listeners) median %.1f ms of %d, digest after %s"
241 % [case, _med(ts), ts.size(), h.finish().hex_encode().substr(0, 16)])
scenes/test/_probe_outliner_split.gd +77 -0
……@@ -0,0 +1,77 @@
1extends Node
2## PROBE (not a gate) — WHERE OutlinerPanel.refresh()'s TIME GOES (hyper-games #127: ~200 ms
3## of a 20-cable bundle's ~660 ms fold). The bundle20 room of _probe_fold_split, then each
4## stage of refresh() and of OutlinerModel.rows() the way they run, median of PROBE_REPS:
5## rows() WARM (the brush-row cache hit: an object edit) and COLD (cache dropped: what a
6## brush / shape fold pays), its five builders one by one, then the panel's own stages.
7##
8## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . res://scenes/test/_probe_outliner_split.tscn
9
10var map: Node3D
11
12
13func _ready() -> void:
14 var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
15 gs.route("world:level:*", 1, "peer")
16 var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
17 if keet != null:
18 keet.current_member_id = "probe"
19 get_tree().root.get_node("/root/GameMode").set_mode("build")
20 map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
21 add_child(map)
22 for i in 6:
23 await get_tree().process_frame
24 var n := int(OS.get_environment("PROBE_N")) if OS.get_environment("PROBE_N") != "" else 20
25 var A := {"pts": PackedVector3Array([Vector3(400, 0, 300), Vector3(400, 0, 302), Vector3(400, 2, 302), Vector3(400, 2, 300)]), "n": Vector3.RIGHT}
26 var B := {"pts": PackedVector3Array([Vector3(410, 0, 300), Vector3(410, 2, 300), Vector3(410, 2, 302), Vector3(410, 0, 302)]), "n": Vector3.LEFT}
27 map.cable_bundle([A, B], {"count": n, "random": true, "seed": 1, "tension": 1.0, "jitter": 0.3, "slack": 1.0},
28 {"radius": 0.03})
29 var ol: Node = map.get("_outliner")
30 if ol == null:
31 print("PROBE no outliner")
32 get_tree().quit(1)
33 return
34 var m: OutlinerModel = ol.get("model")
35 var reps := int(OS.get_environment("PROBE_REPS")) if OS.get_environment("PROBE_REPS") != "" else 5
36 var t := {}
37 for rep in reps:
38 _time(t, "refresh() warm", func() -> void: ol.refresh())
39 _time(t, "refresh() cold", _cold_refresh.bind(ol, m))
40 _time(t, "rows() warm", func() -> void: m.rows())
41 _time(t, "rows() cold", _cold_rows.bind(m))
42 _time(t, " _add_node_rows", func() -> void: m.call("_add_node_rows", []))
43 _time(t, " _add_object_rows", func() -> void: m.call("_add_object_rows", []))
44 _time(t, " _build_brush_rows", func() -> void: m.call("_build_brush_rows", []))
45 _time(t, " _add_collection_rows", func() -> void: m.call("_add_collection_rows", []))
46 var rs: Array = m.rows()
47 _time(t, " _resolve_hidden", func() -> void: m.call("_resolve_hidden", rs))
48 _time(t, " live_shapes", func() -> void: map.get("_persist").live_shapes())
49 _time(t, " collection_tree", func() -> void: map.get("_persist").collection_tree())
50 _time(t, "panel _on_map_selection", func() -> void: ol.call("_on_map_selection"))
51 _time(t, "panel _sync_position", func() -> void: ol.call("_sync_position"))
52 _time(t, "panel _update_status", func() -> void: ol.call("_update_status"))
53 var sel: Array = map.get("_sel_brushes")
54 print("PROBE outliner: %d rows, %d live brushes, %d selected brushes, sel_shape '%s', %d objects selected"
55 % [m.rows().size(), (map.get("level").get_live_brush_ids() as PackedInt32Array).size(), sel.size(),
56 String(map.get("_sel_shape")), (map.selected_objects() as Array).size()])
57 for k: String in t:
58 var a: Array = t[k]
59 a.sort()
60 print("PROBE outliner: %-28s %8.1f ms" % [k, a[a.size() / 2]])
61 get_tree().quit(0)
62
63
64func _time(t: Dictionary, k: String, f: Callable) -> void:
65 var t0 := Time.get_ticks_usec()
66 f.call()
67 (t.get_or_add(k, []) as Array).append(float(Time.get_ticks_usec() - t0) / 1000.0)
68
69
70func _cold_refresh(ol: Node, m: OutlinerModel) -> void:
71 m.set("_brush_rows_key", "")
72 ol.refresh()
73
74
75func _cold_rows(m: OutlinerModel) -> void:
76 m.set("_brush_rows_key", "")
77 m.rows()
scenes/test/_probe_outliner_split.tscn +6 -0
……@@ -0,0 +1,6 @@
1[gd_scene load_steps=2 format=3]
2
3[ext_resource type="Script" path="res://scenes/test/_probe_outliner_split.gd" id="1"]
4
5[node name="OutlinerSplitProbe" type="Node"]
6script = ExtResource("1")