OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps commits / 4bb4811

#120 L3 §14.8 step 7: measured cable fold cost (probe + doc)
_probe_cable_fold_time (a probe, not a gate; _probe_mod_fold_time's method): one 10 m cable 18 ms / 13 brushes; 7 strands 49-57 ms / 78; a 20-cable bundle 657-702 ms / 668; its 7-strand version 2977-3084 ms / 4008. Split of the bundle: ShapeFold.expand 316 ms (world_pieces 247, of which the 2 cm curve sampling 211, hull_of 14.5); the level's side ~386 ms (~0.6 ms a brush). Levers recorded in the doc, not done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4bb48113fe8b9ac195d4d29b68f13b9989a14fe6
by plankatron <fleeky@gmail.com> · 2026-10-19T16:11:12.000Z

Changed files (3) +162 -0

docs/node-tree-and-stacks.md +21 -0
……@@ -1055,6 +1055,27 @@ Every command in a menu and on a key (standing rule):
105510557. Measure: pieces and full-fold time for a 20-cable bundle and a 7-strand 10 m cable
10561056 (the §8 perf item: 500 stacked objects ≈ 1.7 s per edit).
10571057
1058**Step 7, measured 2026-10-19** (`scenes/test/_probe_cable_fold_time`, the
1059`_probe_mod_fold_time` method: full refold with the brush half forced, median of 3; three
1060runs on tabertron at load 0.3–2.3, ranges quoted):
1061
1062| room (map_dev_test +) | live brushes | full refold |
1063|---|---|---|
1064| nothing | 8 | 1.5–1.6 ms |
1065| one 10 m cable, defaults | 21 (+13) | 17.6–18.5 ms |
1066| that cable, 7 strands | 99 (+78) | 48.9–56.9 ms |
1067| a 20-cable bundle (slack 1 m) | 767 (+668) | 657–702 ms |
1068| the bundle, 7 strands each | 4775 (+4008) | 2977–3084 ms |
1069
1070A cable's curve is FOLLOWED (§11.2), so every curve edit in such a room pays its full
1071refold. Where the bundle's ~0.7 s goes (one run): `ShapeFold.expand` 316 ms, of which
1072`world_pieces` 247 ms — its fine `CurveMath.sample` (2 cm steps, GDScript) 211 ms,
1073`DCLevel.hull_of` 14.5 ms — and the level side (applying + rebuilding 668 brushes) the
1074other ~386 ms, about 0.6 ms a brush. Per brush, cables cost ~1 ms against the boxes' ~0.16
1075ms (§8: 1690 ms / 10508). Levers, NOT done: the 2 cm sampling (cache per curve `ver`, or a
1076coarser walk), piece count (strands multiply it; max_angle / max_len), and refolding only
1077the followers of an edited curve instead of the room.
1078
10581079**Status 2026-10-19:** step 1 done (`gate_cable_gen` S1–S8, 32/32). A closed cable's twist
10591080rounds to whole 2π / strands steps over the loop, so strand j closes onto strand j + m.
10601081**Seams, measured:** §11.1's "neighbours share ring k exactly" holds on a straight run
scenes/test/_probe_cable_fold_time.gd +135 -0
……@@ -0,0 +1,135 @@
1extends Node
2## PROBE (not a gate) — fold cost of the L3 cable extras (#120, docs/node-tree-and-stacks.md
3## §14.8 step 7, "measured, not asserted"), the same method as _probe_mod_fold_time: a FULL
4## refold (the brush half forced), median of 3, and the live brush count, after each stage:
5## base the map, no cables
6## cable + one straight-ish 10 m cable, the defaults (round 8, max_len 1 m)
7## strands that cable with 7 strands
8## bundle + a 20-cable bundle between two boxes 10 m apart (slack 1 m: they hang and bend)
9## bundle7 the bundle's cables with 7 strands each
10## A cable's curve edit re-folds IN FULL (§11.2: a followed curve), so each line is also what
11## one curve edit costs in that room.
12##
13## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . res://scenes/test/_probe_cable_fold_time.tscn
14
15const BundleCables := preload("res://scenes/maps/curves/bundle_cables.gd")
16const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")
17const ShapeFold := preload("res://scenes/maps/shapes/param/shape_fold.gd")
18
19var persist: Node
20var level: Node
21var map: Node3D
22
23
24func _ready() -> void:
25 var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
26 gs.route("world:level:*", 1, "peer")
27 var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
28 if keet != null:
29 keet.current_member_id = "probe"
30 get_tree().root.get_node("/root/GameMode").set_mode("build")
31 map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
32 add_child(map)
33 for i in 6:
34 await get_tree().process_frame
35 persist = map.get("_persist")
36 level = map.get("level")
37 var rows: Array = []
38 rows.append(["base", _time(), _n()])
39 # ---- one 10 m cable ----
40 var c := map.add_cable([CurveMath.point(Vector3(300, 2, 300), Vector3.ZERO), CurveMath.point(Vector3(305, 3, 302), Vector3.ZERO),
41 CurveMath.point(Vector3(310, 2, 300), Vector3.ZERO)], false, {}) as String
42 rows.append(["cable", _time(), _n()])
43 _set_params(c, {"strands": 7})
44 rows.append(["strands 7", _time(), _n()])
45 # ---- a 20-cable bundle: the facing sides of two boxes 10 m apart ----
46 var A := {"pts": PackedVector3Array([Vector3(400, 0, 300), Vector3(400, 0, 302), Vector3(400, 2, 302), Vector3(400, 2, 300)]), "n": Vector3.RIGHT}
47 var B := {"pts": PackedVector3Array([Vector3(410, 0, 300), Vector3(410, 2, 300), Vector3(410, 2, 302), Vector3(410, 0, 302)]), "n": Vector3.LEFT}
48 var dots: Array = map.cable_bundle([A, B], {"count": 20, "random": true, "seed": 1, "tension": 1.0,
49 "jitter": 0.3, "slack": 1.0}, {"radius": 0.03})
50 rows.append(["bundle 20", _time(), _n()])
51 _split(dots)
52 persist.begin_gesture("strands")
53 for d: String in dots:
54 persist.record("shape.set", {"target": d, "params": {"strands": 7}})
55 persist.end_gesture()
56 rows.append(["bundle 20 x 7 strands", _time(), _n()])
57 var prev := 0
58 for r: Array in rows:
59 print("PROBE %-24s full refold %8.1f ms %6d live brushes (+%d)" % [r[0], r[1], r[2], int(r[2]) - prev])
60 prev = int(r[2])
61 print("PROBE cables in the bundle: %d" % dots.size())
62 get_tree().quit(0)
63
64
65func _set_params(dot: String, p: Dictionary) -> void:
66 persist.begin_gesture("probe")
67 persist.record("shape.set", {"target": dot, "params": p})
68 persist.end_gesture()
69
70
71## WHERE THE BUNDLE'S FOLD TIME GOES, the generator alone (no level), median of 3:
72## world_pieces for the 20 cables; its fine CurveMath.sample alone; and DCLevel.hull_of alone
73## on the same rings (the hulls world_pieces makes, re-hulled from their corners).
74func _split(dots: Array) -> void:
75 var t: Object = ParamShapes.get_type("cable")
76 var curves: Dictionary = persist.curve_states()
77 var jobs: Array = []
78 for d: String in dots:
79 var p: Dictionary = t.resolve(persist.shape_state(d)["params"])
80 jobs.append([curves[String(p["curve"])], p])
81 var wp: Array = []
82 var smp: Array = []
83 var hl: Array = []
84 var pieces: Array = []
85 for rep in 3:
86 var t0 := Time.get_ticks_usec()
87 pieces = []
88 for j: Array in jobs:
89 pieces.append_array(t.world_pieces(j[0], j[1]))
90 wp.append(float(Time.get_ticks_usec() - t0) / 1000.0)
91 t0 = Time.get_ticks_usec()
92 for j2: Array in jobs:
93 var cv: Dictionary = j2[0]
94 var L := CurveMath.length(cv["points"], bool(cv["closed"]))
95 CurveMath.sample(cv["points"], bool(cv["closed"]), clampi(ceili(L / 0.02) + 1, 16, 4096))
96 smp.append(float(Time.get_ticks_usec() - t0) / 1000.0)
97 var pts_all: Array = []
98 for pc: Dictionary in pieces:
99 pts_all.append(FaceKit.corners(pc["planes"]))
100 t0 = Time.get_ticks_usec()
101 for cs: PackedVector3Array in pts_all:
102 DCLevel.hull_of(cs)
103 hl.append(float(Time.get_ticks_usec() - t0) / 1000.0)
104 wp.sort()
105 smp.sort()
106 hl.sort()
107 print("PROBE split (bundle 20, %d pieces): world_pieces %.1f ms | its fine sampling %.1f ms | hull_of alone %.1f ms"
108 % [pieces.size(), wp[1], smp[1], hl[1]])
109 # ShapeFold.expand on the room's shape records: the generator + resolve_states + the
110 # brush.create records. What a full refold spends beyond this is the level's side.
111 var shape_recs: Array = (persist.records(false) as Array).filter(func(r: Dictionary) -> bool: return ShapeFold.handles(String(r["kind"])))
112 var ex: Array = []
113 for rep2 in 3:
114 var t1 := Time.get_ticks_usec()
115 ShapeFold.expand(shape_recs, {}, curves)
116 ex.append(float(Time.get_ticks_usec() - t1) / 1000.0)
117 ex.sort()
118 print("PROBE split: ShapeFold.expand (whole room) %.1f ms" % ex[1])
119
120
121func _n() -> int:
122 return (level.get_live_brush_ids() as PackedInt32Array).size()
123
124
125## A full refold, median of 3 (as _probe_mod_fold_time).
126func _time() -> float:
127 var ts: Array = []
128 for i in 3:
129 persist.set("_brush_ver_folded", -1) # force the brush half
130 persist.set("_ix_tail_ok", false)
131 var t0 := Time.get_ticks_usec()
132 persist.refold()
133 ts.append(float(Time.get_ticks_usec() - t0) / 1000.0)
134 ts.sort()
135 return ts[1]
scenes/test/_probe_cable_fold_time.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_cable_fold_time.gd" id="1"]
4
5[node name="CableFoldProbe" type="Node"]
6script = ExtResource("1")