@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CLAUDE.md +87 -0
  2. package/LICENSE.md +664 -0
  3. package/README.md +84 -2
  4. package/dist/dist-manifest.json +218 -0
  5. package/dist/eyes/index.js +109 -0
  6. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  7. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  8. package/dist/fat/pkg-node/package.json +12 -0
  9. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  10. package/dist/frqtl/compat/boot-node.js +34 -0
  11. package/dist/frqtl/compat/engine.js +127 -0
  12. package/dist/frqtl/compat/package-root.js +24 -0
  13. package/dist/frqtl/compat/providers/wasm-engine.js +2499 -0
  14. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  15. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  16. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  17. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  18. package/dist/frqtl/compat/renderer/RenderFamily.js +84 -0
  19. package/dist/frqtl/compat/renderer/RenderMesh.js +99 -0
  20. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  21. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  22. package/dist/frqtl/compat/renderer/captureStill-common.js +258 -0
  23. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  24. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  25. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  26. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  27. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  28. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  30. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  31. package/dist/frqtl/compat/renderer/providers/three-provider.js +2470 -0
  32. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  33. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  34. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  35. package/dist/frqtl/compat/vendor.js +196 -0
  36. package/dist/frqtl/node-entry.js +17 -0
  37. package/dist/frqtl/objects/FrqtlApp.js +2920 -0
  38. package/dist/frqtl/objects/ObjectFactory.js +296 -0
  39. package/dist/frqtl/objects/RecipeContext.js +322 -0
  40. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  41. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  42. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  43. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  44. package/dist/frqtl/objects/instruments/capture.js +710 -0
  45. package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -0
  46. package/dist/frqtl/objects/kinds-entry.js +14 -0
  47. package/dist/frqtl/objects/kinds.js +190 -0
  48. package/dist/frqtl/objects/objects/Box.js +273 -0
  49. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  50. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  51. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  52. package/dist/frqtl/objects/objects/DirectedTunnel.js +411 -0
  53. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  54. package/dist/frqtl/objects/objects/LightSource.js +351 -0
  55. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  56. package/dist/frqtl/objects/objects/Tube.js +471 -0
  57. package/dist/frqtl/objects/objects/absorbRadiation.js +309 -0
  58. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  59. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  60. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  61. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  62. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  66. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  67. package/dist/frqtl/objects/objects/makeControlsForObject.js +820 -0
  68. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  69. package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
  70. package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
  71. package/dist/frqtl/objects/objects/tagger.js +228 -0
  72. package/dist/frqtl/objects/schema/engine-scopes.js +468 -0
  73. package/dist/frqtl/objects/schema/index.js +256 -0
  74. package/dist/frqtl/objects/schema/scopes.js +457 -0
  75. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  76. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  77. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  78. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  79. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  80. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  81. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  82. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  83. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +250 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -0
  85. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  86. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  87. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  88. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  89. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  90. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  91. package/dist/frqtl/objects/setup/setupLightSourceLab.js +277 -0
  92. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  94. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  95. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1258 -0
  96. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  97. package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
  98. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  99. package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
  100. package/dist/frqtl/objects/v1-replacements.js +29 -0
  101. package/dist/frqtl/package-core.js +23 -0
  102. package/dist/frqtl/parts/CameraControls.js +643 -0
  103. package/dist/frqtl/parts/CameraPath.js +575 -0
  104. package/dist/frqtl/parts/CollisionActions.js +34 -0
  105. package/dist/frqtl/parts/CollisionTypes.js +30 -0
  106. package/dist/frqtl/parts/DetectorWall.js +496 -0
  107. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  108. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  109. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  110. package/dist/frqtl/parts/GraphingDetectorWall.js +489 -0
  111. package/dist/frqtl/parts/LargeObject.js +405 -0
  112. package/dist/frqtl/parts/LightColor.js +255 -0
  113. package/dist/frqtl/parts/LiveChart.js +285 -0
  114. package/dist/frqtl/parts/LogicGate.js +717 -0
  115. package/dist/frqtl/parts/OvenBox.js +611 -0
  116. package/dist/frqtl/parts/PointSource.js +762 -0
  117. package/dist/frqtl/parts/Rectangle.js +200 -0
  118. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  119. package/dist/frqtl/parts/SlitScreen.js +628 -0
  120. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  121. package/dist/frqtl/parts/Trace.js +508 -0
  122. package/dist/frqtl/parts/Tunnel.js +1057 -0
  123. package/dist/frqtl/parts/Wall.js +1211 -0
  124. package/dist/frqtl/parts/index.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  126. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  127. package/dist/frqtl/scenes/atomic-model.js +88 -0
  128. package/dist/frqtl/scenes/big-bang.js +46 -0
  129. package/dist/frqtl/scenes/blackbody.js +81 -0
  130. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  131. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  132. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  133. package/dist/frqtl/scenes/counting-wall.js +38 -0
  134. package/dist/frqtl/scenes/double-slit.js +31 -0
  135. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  136. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  137. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  138. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +243 -0
  139. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  140. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  141. package/dist/frqtl/scenes/empty.js +14 -0
  142. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  143. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  144. package/dist/frqtl/scenes/mirror-route.js +140 -0
  145. package/dist/frqtl/scenes/mirror.js +38 -0
  146. package/dist/frqtl/scenes/omni-3d.js +27 -0
  147. package/dist/frqtl/scenes/omni-source.js +22 -0
  148. package/dist/frqtl/scenes/self-interference.js +39 -0
  149. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  150. package/dist/frqtl/scenes/straight-walk.js +22 -0
  151. package/dist/frqtl/scenes/sun.js +39 -0
  152. package/dist/frqtl/scenes/two-source.js +37 -0
  153. package/dist/frqtl/scenes/wave.js +26 -0
  154. package/dist/shim/bundle/frqnb.js +112 -0
  155. package/dist/shim/bundle/index.js +121 -0
  156. package/dist/shim/bundle/indexHtml.js +107 -0
  157. package/dist/shim/bundle/manifest.js +95 -0
  158. package/dist/shim/bundle/zip.js +243 -0
  159. package/dist/shim/bus.js +67 -0
  160. package/dist/shim/controls/index.js +627 -0
  161. package/dist/shim/controls/registry.js +274 -0
  162. package/dist/shim/engine/artifact-shell.js +211 -0
  163. package/dist/shim/engine/boot-phases.js +44 -0
  164. package/dist/shim/engine/engine-stack-boot.js +59 -0
  165. package/dist/shim/engine/router.js +690 -0
  166. package/dist/shim/experiment-frame/index.js +296 -0
  167. package/dist/shim/expr/ast-schema.js +369 -0
  168. package/dist/shim/field-descriptor/index.js +325 -0
  169. package/dist/shim/frame-data/index.js +224 -0
  170. package/dist/shim/index.js +67 -0
  171. package/dist/shim/notebook/canvas-guard.js +82 -0
  172. package/dist/shim/notebook/catalog.js +35 -0
  173. package/dist/shim/notebook/format.js +736 -0
  174. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  175. package/dist/shim/notebook/kernel.js +671 -0
  176. package/dist/shim/notebook/knob.js +150 -0
  177. package/dist/shim/notebook/module-resolver.js +260 -0
  178. package/dist/shim/notebook/notebook-knobs.js +65 -0
  179. package/dist/shim/notebook/produced-bindings.js +67 -0
  180. package/dist/shim/notebook/realm-registry.js +121 -0
  181. package/dist/shim/notebook/realm-transport.js +73 -0
  182. package/dist/shim/notebook/setup-builder.js +507 -0
  183. package/dist/shim/notebook/store.js +159 -0
  184. package/dist/shim/notebook/vendor-importmap.js +134 -0
  185. package/dist/shim/notebook/view-builder.js +189 -0
  186. package/dist/shim/notebook/view-ctx.js +100 -0
  187. package/dist/shim/notebook/view-handle.js +202 -0
  188. package/dist/shim/notebook/warmup.js +102 -0
  189. package/dist/shim/package.json +4 -0
  190. package/dist/shim/page-params/index.js +213 -0
  191. package/dist/shim/project/identity.js +112 -0
  192. package/dist/shim/project/index.js +19 -0
  193. package/dist/shim/project/link.js +402 -0
  194. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  195. package/dist/shim/project/register-node.mjs +17 -0
  196. package/dist/shim/project/specifiers.js +106 -0
  197. package/dist/shim/registry/copy-gate.js +110 -0
  198. package/dist/shim/registry/exhibits.js +455 -0
  199. package/dist/shim/registry/experiments.js +215 -0
  200. package/dist/shim/registry/index.js +19 -0
  201. package/dist/shim/registry/scene-source.js +157 -0
  202. package/dist/shim/registry/walkthrough.js +804 -0
  203. package/dist/shim/render/capture-envelope.js +279 -0
  204. package/dist/shim/render/heat-colormap.js +92 -0
  205. package/dist/shim/render/render-stages.js +855 -0
  206. package/dist/shim/render/sun-look.js +33 -0
  207. package/dist/shim/render-state/apply.js +262 -0
  208. package/dist/shim/render-state/index.js +74 -0
  209. package/dist/shim/rpc/index.js +207 -0
  210. package/dist/shim/runtime/index.js +1023 -0
  211. package/dist/shim/storage/boot.js +69 -0
  212. package/dist/shim/storage/index.js +169 -0
  213. package/dist/shim/storage/local-storage-provider.js +230 -0
  214. package/dist/shim/storage/make-namespace-store.js +132 -0
  215. package/dist/shim/storage/modules.js +249 -0
  216. package/dist/shim/storage/projects.js +150 -0
  217. package/llms.txt +12 -0
  218. package/package.json +63 -4
@@ -0,0 +1,508 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * Trace — THE WIRE PART. A routed, terminated conductor assembled entirely from
5
+ * stock parts: mirror `Tunnel` runs + directed `Tunnel` corner junctions + a
6
+ * `Wall{destroy}` ground. It is the standard part gates and adders are built from.
7
+ *
8
+ * var t = new Trace(universe, { path: [[40,200],[240,200],[240,360]], bore: 3, ground: true });
9
+ * t.inPort; // [40,200] — where the source feeds it
10
+ * t.outPort; // [240,360] — where it delivers
11
+ * t.onTick(tickData); // once per tick (self-gating; no-op with no corners)
12
+ * t.getDelivered(); // quanta delivered to ground (calibrated)
13
+ *
14
+ * ── WHY THE NAME IS "TRACE" AND NOT "WIRE" ──────────────────────────────────
15
+ * Named LAST, from what it was measured to do. It routes on the CARDINAL grid
16
+ * only — straight x/y runs joined by right-angle junctions (Manhattan routing),
17
+ * because a diagonal corridor of tilted mirrors was measured NOT to conduct
18
+ * (G ≈ 0.04 at 15/30/45 deg vs 1.01 on-axis). Straight segments, right-angle
19
+ * corners and a fixed parallel pitch is exactly a PCB trace, so that is the name.
20
+ * It is the wire of this line; "trace" is just the honest word for the routing
21
+ * it actually supports.
22
+ *
23
+ * ── PURE COMPOSITION — zero engine, zero new physics (canonical-bench SU1) ────
24
+ * A Trace adds NO collision logic. Every piece is a part already on the floor:
25
+ * RUN → `Tunnel{ mode:'mirror' }` — two guarded-bounce mirror Walls = insulation
26
+ * CORNER → `Tunnel{ mode:'directed' }` — wall hits are RE-EMITTED down the outgoing
27
+ * leg instead of reflected back; + one mirror `Wall` capping the outer face
28
+ * GROUND → `Wall{ collisionBehavior:'destroy', hooks:['count'] }` — the sink + tally
29
+ * The source is NOT part of a Trace (a wire carries no battery); feed `inPort` with
30
+ * whatever emitter the experiment wants. If building a Trace ever seems to need engine
31
+ * or provider code, STOP: it is Tunnels + a Wall.
32
+ *
33
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
34
+ *
35
+ * `new Trace(universe, opts)` — builds + engine-registers the runs, junctions and
36
+ * ground. It does NOT add itself to a runner render list; the caller iterates
37
+ * `trace.largeObjects` (or `runner.setup()` auto-wires them).
38
+ *
39
+ * ORDERING PRECONDITION — `universe.init()` MUST already have run. A Trace registers
40
+ * LargeObjects in its constructor, and the engine throws ("EGPTUniverse.init() must be
41
+ * called before any simulation methods") on any registration against a fresh universe.
42
+ * An `FrqtlApp`'s universe is NOT init'd, so the SKETCH inits it FIRST and then
43
+ * constructs parts (world.init() FIRST → CONSTRUCT parts → WIRE → RUN) — see
44
+ * `setupTraceCircuit.js`, whose first act is the world step. Order among PARTS is
45
+ * irrelevant; only world-before-parts matters. This bit once already: the demo threw on
46
+ * every real page while its tests passed, because the test double had no ordering
47
+ * concept. `setup-trace-circuit.test.mjs` now enforces the precondition in its double.
48
+ * @param {Object} universe — an EGPTUniverse (JS or WASM compat shim).
49
+ * @param {Object} opts
50
+ * @param {Array<Array<number>>} opts.path — 2+ waypoints `[[x,y],…]` on the BORE
51
+ * CENTRE-LINE, px. Consecutive points must differ on exactly ONE axis (cardinal
52
+ * routing) — FAIL-LOUD otherwise, because a diagonal run does not conduct.
53
+ * @param {number} [opts.bore=3] — clear aperture, px. 3 is the recommended minimum
54
+ * (see Layer 2); the transport measurement is flat from bore 3 to bore 40.
55
+ * @param {number} [opts.wallThickness=6] — insulation thickness, px. This is the
56
+ * knob that actually sets the shrink floor — see Layer 2. Below 3px the mirror
57
+ * leaks; the required thickness RISES as the bore falls.
58
+ * @param {boolean} [opts.ground=false] — build the sink terminal at the outlet.
59
+ * @param {number} [opts.groundGap=4] — px between the outlet and the ground slab.
60
+ * @param {number} [opts.cornerSpread=5] — the directed junction's fan half-width in
61
+ * DEGREES. Tighter is better, with a shallow optimum at 5: measured corner
62
+ * throughput G = 0.94 (5), 0.94 (0), 0.92 (10), 0.88 (20), 0.76 (45), 0.59 (90).
63
+ * @param {Array} [opts.color=[90,120,200]] — RGB for the runs.
64
+ * @param {string} [opts.name='trace'] — identifier prefix.
65
+ *
66
+ * @property {Array<Tunnel>} segments — the mirror runs, in path order.
67
+ * @property {Array<Tunnel>} junctions — the directed corner cells, in path order.
68
+ * @property {Array<Wall>} caps — the mirror walls closing each corner's outer face.
69
+ * @property {Wall|null} ground — the sink terminal (`opts.ground`).
70
+ * @property {Array} inPort, outPort — `[x,y]` centre-line inlet / outlet.
71
+ * @property {Array<LargeObject>} largeObjects — every inner engine LO (render wiring).
72
+ *
73
+ * Methods:
74
+ * `onTick(tickData)` — the ONE per-tick call. Fans out to each junction's
75
+ * `applyRedirectIfActive` (the host half of directed mode) and samples the ground.
76
+ * SELF-GATING: a straight Trace with no corners and no ground is a no-op, so a
77
+ * caller never needs its own `if (hasCorners)` check.
78
+ * `attachToRunner(runner)` — subscribe `onTick` to the runner's per-tick fan-out.
79
+ * `getDelivered()` — quanta delivered to ground since construction, CALIBRATED.
80
+ * A destroy+count wall registers each arrival on TWO consecutive ticks (measured,
81
+ * deterministic), so the raw counter is halved here — one authority, so no caller
82
+ * has to know. Throws without a ground.
83
+ * `getCurrent()` — delivered per tick over the last sampled window.
84
+ * `dispose()` — tears down every sub-part (idempotent).
85
+ *
86
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
87
+ * ZERO physics of its own (SU1). Everything below is the emergent behaviour of the
88
+ * stock parts, MEASURED on this construction (WASM engine, 2D, fundamental dim 0,
89
+ * laser feed on the centre-line, steady-state current over a 250-tick window,
90
+ * conductance G = delivered/tick ÷ emitted/tick):
91
+ *
92
+ * CONDUCTION. A Trace run delivers essentially ALL the current it accepts:
93
+ * G = 1.00 ± 0.02 across seeds, against G = 0.29 ± 0.02 for the same source and
94
+ * ground with the run REMOVED. Leakage through the mirrors is 0.0 %. The mirrors
95
+ * are not "guiding" in a soft sense — nothing gets out.
96
+ *
97
+ * LENGTH IS FREE. Conduction is length-independent; the run is a lossless
98
+ * waveguide, so lengthening a Trace costs propagation DELAY (1 px/tick — bench M1)
99
+ * and nothing else. Optimise junctions, never run length.
100
+ *
101
+ * BORE. Transport is flat in bore: G = 1.01 / 1.00 / 1.01 / 1.00 / 1.00 at bore
102
+ * 40 / 24 / 16 / 8 / 4. Wider does not carry more current once the current is
103
+ * inside. (Wider does ACCEPT more at the mouth — that is a source-coupling
104
+ * property of the inlet, not of the conductor.)
105
+ *
106
+ * THE SHRINK FLOOR IS WALL THICKNESS, NOT BORE. Measured G by (bore, wallThickness):
107
+ *
108
+ * bore\wt | 3 6 10
109
+ * 3 | 1.010 1.002 0.996
110
+ * 2 | 0.203 1.001 1.001 ← 0.203 leaks 48 %
111
+ * 1 | 0.044 0.195 0.987 ← 0.987 leaks 0.1 %
112
+ *
113
+ * A bore-1 Trace conducts at G = 0.987 with zero leak PROVIDED the insulation is
114
+ * ≥ 10 px. The thinner the bore, the thicker the wall must be. A too-thin wall does
115
+ * not degrade — it goes transparent, and the fault looks like a broken conductor.
116
+ *
117
+ * PITCH (the reason it shrinks usefully). Lanes stacked one energised, the rest dark,
118
+ * measured lane-by-lane. Crosstalk only ever reaches the IMMEDIATELY adjacent lane —
119
+ * lane 2 and beyond measured EXACTLY 0.000 in every configuration tested.
120
+ *
121
+ * Each Trace owns its own insulation, so INDEPENDENT Traces stack at
122
+ * `bore + 2 x wallThickness` (`Trace.lanePitch()`), wall against wall:
123
+ *
124
+ * bore 3 + wall 3 → pitch 9 px, crosstalk 0.98 %
125
+ * bore 2 + wall 6 → pitch 14 px, crosstalk 0.00 %
126
+ * bore 12 + wall 6 → pitch 24 px, crosstalk 0.00 %
127
+ *
128
+ * DO NOT OVERLAP TWO TRACES' MIRRORS TO SAVE THE PITCH. Stacking Traces at
129
+ * `bore + wallThickness`, so each pair of neighbours' mirrors lands on exactly the
130
+ * same pixels, LEAKS BADLY — measured crosstalk 31.0 % (bore 3), 33.6 % (bore 2),
131
+ * 4.1 % (bore 12). Two coincident mirror walls are markedly WORSE than one: a
132
+ * SINGLE shared rail at that same 6 px pitch measures 4.1 % at bore 3, so the loss
133
+ * is caused by the duplication, not by the tightness. It fails silently — the
134
+ * energised lane still reads G = 0.98, so only the dark lane's meter shows it.
135
+ * FRQTL-MAINT(coincident-mirror-leak): design around it — never place two mirror
136
+ * Walls on the same pixels.
137
+ *
138
+ * A tighter bus is still available, but it needs a genuinely SHARED rail (one wall
139
+ * serving two bores), which is a different part from a row of independent Traces:
140
+ * a ladder of N+1 shared rails measured 4.1 % at 6 px pitch (bore 3 + wall 3),
141
+ * 1.2 % at 8 px (bore 2 + wall 6) and 0.03 % at 11 px (bore 1 + wall 10), with
142
+ * eight lanes fitting a 51 px band at G = 1.02 on the energised lane. That shared-rail
143
+ * bus is the natural NEXT part; a Trace deliberately does not pretend to be one.
144
+ *
145
+ * THE CORNER: TWO WORKING MECHANISMS, AND THE ONE THAT MEASURED BETTER.
146
+ * A specular mirror DOES turn a ballistic 1 px/tick current — that is exactly what
147
+ * `Wall{collisionBehavior:'bounce', bounceDirection:'reflect'}` compiles (the velocity
148
+ * component perpendicular to the face flips, out = 180 + 2*orientation - in), and the
149
+ * Collision-Lab rungs route with it: `cl1a-mirror` steers a laser off one tilted mirror,
150
+ * and `cl1d-mirror-route` loops a beam around a CLOSED ten-mirror {10/3} star forever.
151
+ * Both mechanisms were built here and measured on the SAME rig, bore 12, settled, 2 seeds,
152
+ * best configuration of each (51 mirror configurations swept):
153
+ *
154
+ * reflect-Wall mirror elbow → G = 0.738 +- 0.011
155
+ * directed-Tunnel junction → G = 0.951 +- 0.005
156
+ *
157
+ * The directed junction is what a Trace uses because it measured 1.3x better in a
158
+ * BORE-TO-BORE corner — NOT because a mirror cannot turn current. The gap (0.21) is ~20x
159
+ * the seed spread, so the choice is decided, but the REASON is geometric: a mirror
160
+ * reflects specularly, so it PRESERVES the beam's angular spread and every off-axis ray
161
+ * leaves off-axis and misses the narrow outgoing mouth; the directed junction RE-AIMS
162
+ * every hit into a tight fan down the outgoing leg, which is what a bore-to-bore corner
163
+ * needs. In OPEN SPACE — cl1d's star, where the next "mouth" is a slab many beam-widths
164
+ * wide — the mirror is the right part and a directed cell would be pointless. Pick by
165
+ * geometry, not by a blanket rule.
166
+ *
167
+ * Mirror-elbow tuning, if you build one anyway: the slab must be TALL/THIN (Wall infers
168
+ * the flip axis from the ASPECT — a wide/flat slab flips the wrong component) and the cell
169
+ * TIGHT. G = 0.73 (thickness 2), 0.67 (3), 0.68 (4), 0.34 (10), 0.17 (20); a cell much
170
+ * larger than the bore is worse (0.08), because a bigger room with the same small door
171
+ * loses more.
172
+ *
173
+ * Directed-junction tuning, by fan half-width: G = 0.951 (5 deg), 0.94 (0), 0.92 (10),
174
+ * 0.88 (20), 0.76 (45), 0.59 (90) — hence `cornerSpread: 5`.
175
+ *
176
+ * WHAT A CORNER ACTUALLY COSTS: DELAY AND STORED POPULATION, NOT CURRENT. Measured on the
177
+ * shipped Trace geometry, fully settled (2600 ticks), 2 seeds, spread +-0.011:
178
+ *
179
+ * route G in-flight population
180
+ * straight 0.997 4 035
181
+ * one corner 0.990 4 576
182
+ * two corners 0.978 5 731
183
+ *
184
+ * At steady state a corner gives back nearly everything: ~1 % per corner, which is only
185
+ * just above the seed spread. What visibly grows is the POPULATION HELD IN THE ROUTE — the
186
+ * junction cells store quanta while they find the exit. This is the same lesson as
187
+ * "length is free": the cost is transit time, not lost current. Read a corner too early
188
+ * and it looks far lossier than it is (the under-settled reads of this same geometry gave
189
+ * 0.94 / 0.87, and QA independently hit a 0-conductance read for the same reason) — always
190
+ * settle past propagation delay PLUS the averaging window before quoting a corner.
191
+ *
192
+ * CORNERS ARE BORE-LIMITED — ROUTE TURNS WIDE, RUN NARROW. Unlike a straight run
193
+ * (flat in bore), a turn's throughput collapses as the bore narrows, because the
194
+ * re-emitted current has to find the outgoing mouth and that acceptance is an
195
+ * aperture problem:
196
+ *
197
+ * bore 12, flare 12 → G = 0.951
198
+ * bore 3, flare 12 → G = 0.31-0.44 (unflared, cell = bore: 0.016 — a DEAD corner)
199
+ * bore 1, flare 12 → G = 0.000
200
+ *
201
+ * The same limit hits the mirror elbow, and harder — it has no re-aiming step at all, so
202
+ * at bore 3 it measured 0.047 where the directed junction managed 0.31-0.44.
203
+ *
204
+ * Hence `cornerSize` defaults to `max(bore, 12)`: a junction always flares to at
205
+ * least 12 px even when the runs are 1-3 px, which is the difference between a
206
+ * 19x-better corner and a dead one. The design rule that follows: put turns on wide
207
+ * bore and neck down to bore 1-3 only for the straight parallel stretches. A dense
208
+ * bus should be straight; the routing should happen where there is room.
209
+ *
210
+ * THE ONE SWITCH THAT CHANGES THE COMPONENT: `universe.withInterQuantumCollisions`.
211
+ * Off (the default here) the Trace is a lossless waveguide with no ampacity limit.
212
+ * On, it becomes a current-limited device — that is RESISTANCE, and it is a
213
+ * different component with a different datasheet.
214
+ */
215
+
216
+ import { Tunnel as _Tunnel } from './Tunnel.js';
217
+ import { Wall as _Wall } from './Wall.js';
218
+
219
+
220
+
221
+
222
+ /**
223
+ * A `destroy` wall carrying the `count` hook registers each arrival on TWO
224
+ * consecutive ticks (measured, deterministic: one quantum → 2 collected frames,
225
+ * on the tick the kill op fires and the tick after, before cleanup). Every
226
+ * delivered-count read divides by this ONE constant so no caller has to know.
227
+ * FRQTL-MAINT(destroy-count-double-registers) — calibrated, not litigated.
228
+ */
229
+ var GROUND_COUNTS_PER_ARRIVAL = 2;
230
+
231
+ /** Direction of travel from A to B as a compass unit vector + degrees. */
232
+ function _dir(a, b) {
233
+ var dx = b[0] - a[0], dy = b[1] - a[1];
234
+ if ((dx !== 0) === (dy !== 0)) {
235
+ throw new Error('[Trace] path leg ' + JSON.stringify(a) + '->' + JSON.stringify(b) +
236
+ ' is not cardinal. A Trace routes on the x/y grid only: a diagonal corridor was ' +
237
+ 'measured NOT to conduct (G ~ 0.04 vs 1.01 on-axis). Insert a waypoint. (FAIL-LOUD.)');
238
+ }
239
+ if (dx > 0) { return { ux: 1, uy: 0, deg: 0, axis: 'x' }; }
240
+ if (dx < 0) { return { ux: -1, uy: 0, deg: 180, axis: 'x' }; }
241
+ if (dy > 0) { return { ux: 0, uy: 1, deg: 90, axis: 'y' }; }
242
+ return { ux: 0, uy: -1, deg: 270, axis: 'y' };
243
+ }
244
+
245
+ function Trace(universe, opts) {
246
+ opts = opts || {};
247
+ this.universe = universe;
248
+ this._schemaKind = 'Trace';
249
+
250
+ var path = opts.path;
251
+ if (!Array.isArray(path) || path.length < 2) {
252
+ throw new Error('[Trace] opts.path must be an array of 2+ [x,y] waypoints. (FAIL-LOUD.)');
253
+ }
254
+ var bore = (opts.bore !== undefined && opts.bore !== null) ? opts.bore : 3;
255
+ var wallThickness = (opts.wallThickness !== undefined && opts.wallThickness !== null)
256
+ ? opts.wallThickness : 6;
257
+ if (!(bore > 0) || !(wallThickness > 0)) {
258
+ throw new Error('[Trace] bore and wallThickness must both be positive. (FAIL-LOUD.)');
259
+ }
260
+ var cornerSpread = (typeof opts.cornerSpread === 'number') ? opts.cornerSpread : 5;
261
+ var color = opts.color || [90, 120, 200];
262
+ var name = opts.name || 'trace';
263
+ var groundGap = (opts.groundGap !== undefined) ? opts.groundGap : 4;
264
+ // The junction is a SQUARE cell of this side, centred on the waypoint — a FLARE.
265
+ // Independent of the run bore, and that independence is load-bearing: a junction
266
+ // sized to a narrow bore does not turn the current at all (bore 3 with a 3px cell
267
+ // measured G = 0.016 — a dead corner; widening the cell along the outgoing leg
268
+ // alone did not help either, G = 0.014). A corner needs ROOM in the TRANSVERSE
269
+ // direction, so the cell flares out to `cornerSize` and the runs neck back down.
270
+ var cornerSize = (typeof opts.cornerSize === 'number') ? opts.cornerSize : Math.max(bore, 12);
271
+
272
+ this.path = path.map(function (p) { return [p[0], p[1]]; });
273
+ this.bore = bore;
274
+ this.wallThickness = wallThickness;
275
+ this.cornerSpread = cornerSpread;
276
+ this.cornerSize = cornerSize;
277
+ this.color = color;
278
+ this.name = name;
279
+
280
+ var TunnelCls = _Tunnel;
281
+ var WallCls = _Wall;
282
+
283
+ this.segments = [];
284
+ this.junctions = [];
285
+ this.caps = [];
286
+ this.ground = null;
287
+ this.largeObjects = [];
288
+ this.walls = [];
289
+
290
+ var half = bore / 2;
291
+ var i, d, dPrev;
292
+
293
+ // ── RUNS: one mirror Tunnel per leg, TRIMMED back by half a bore at every
294
+ // interior waypoint so the junction cell owns the corner square. ──────
295
+ for (i = 0; i < path.length - 1; i++) {
296
+ d = _dir(path[i], path[i + 1]);
297
+ // The junction owns a square of side `cornerSize` centred on the waypoint, so a
298
+ // leg stops (or starts) half a cell away from every interior waypoint.
299
+ var trim = cornerSize / 2;
300
+ var startTrim = (i === 0) ? 0 : trim; // leaving a corner
301
+ var endTrim = (i === path.length - 2) ? 0 : trim; // entering a corner
302
+ var ax = path[i][0] + d.ux * startTrim;
303
+ var ay = path[i][1] + d.uy * startTrim;
304
+ var bx = path[i + 1][0] - d.ux * endTrim;
305
+ var by = path[i + 1][1] - d.uy * endTrim;
306
+ // SIGNED along the direction of travel — an over-trimmed leg leaves a NEGATIVE
307
+ // remainder, and `Math.abs` would silently turn that into a short but "valid"
308
+ // run pointing the wrong way.
309
+ var runLen = (bx - ax) * d.ux + (by - ay) * d.uy;
310
+ if (runLen <= 0) {
311
+ throw new Error('[Trace] leg ' + i + ' is shorter than the corner cells it must join ' +
312
+ '(length ' + runLen + ' after trimming for bore ' + bore + '). Space the waypoints ' +
313
+ 'at least one bore apart. (FAIL-LOUD.)');
314
+ }
315
+ var seg = new TunnelCls(universe, (d.axis === 'x')
316
+ ? { left: Math.min(ax, bx), top: ay - half, length: runLen, bore: bore,
317
+ wallThickness: wallThickness, axis: 'x', mode: 'mirror',
318
+ name: name + '_run' + i, color: color }
319
+ : { left: ax - half, top: Math.min(ay, by), length: runLen, bore: bore,
320
+ wallThickness: wallThickness, axis: 'y', mode: 'mirror',
321
+ name: name + '_run' + i, color: color });
322
+ this.segments.push(seg);
323
+ }
324
+
325
+ // ── CORNERS: a short DIRECTED cell at each interior waypoint, aimed down the
326
+ // outgoing leg, plus one mirror cap closing the cell's outer face. ─────
327
+ for (i = 1; i < path.length - 1; i++) {
328
+ dPrev = _dir(path[i - 1], path[i]);
329
+ d = _dir(path[i], path[i + 1]);
330
+ if (d.axis === dPrev.axis) {
331
+ throw new Error('[Trace] waypoint ' + i + ' does not turn (both legs run on the ' +
332
+ d.axis + ' axis). Merge the collinear legs. (FAIL-LOUD.)');
333
+ }
334
+ var cx = path[i][0], cy = path[i][1];
335
+ // A square cell of side `cornerSize` centred on the waypoint. Its Tunnel runs
336
+ // along the OUTGOING axis, so its two directed walls face the INCOMING beam —
337
+ // the pair the current actually hits — and the re-emission aims every hit down
338
+ // the new leg.
339
+ var chalf = cornerSize / 2;
340
+ var cell = new TunnelCls(universe, {
341
+ left: cx - chalf, top: cy - chalf, length: cornerSize, bore: cornerSize,
342
+ wallThickness: wallThickness, axis: d.axis,
343
+ mode: 'directed', directedAngle: d.deg, redirectSpread: cornerSpread,
344
+ name: name + '_corner' + i, color: color
345
+ });
346
+ this.junctions.push(cell);
347
+
348
+ // Cap the face OPPOSITE the outlet (the -outgoing side) so nothing escapes
349
+ // backwards out of the junction. Prefix picks the reflect axis (Wall's own
350
+ // convention): a wall normal to y is TOP_, normal to x is LEFT_.
351
+ var span = cornerSize + 2 * wallThickness;
352
+ // Flush against the cell on the -outgoing side.
353
+ var capRect = (d.axis === 'y')
354
+ ? [cx - chalf - wallThickness, (d.uy > 0 ? cy - chalf - wallThickness : cy + chalf), span, wallThickness]
355
+ : [(d.ux > 0 ? cx - chalf - wallThickness : cx + chalf), cy - chalf - wallThickness, wallThickness, span];
356
+ this.caps.push(new WallCls(universe, {
357
+ rect: capRect,
358
+ collisionBehavior: 'bounce', bounceDirection: 'reflect',
359
+ name: (d.axis === 'y' ? 'TOP_' : 'LEFT_') + name + '_cap' + i,
360
+ color: color
361
+ }));
362
+ }
363
+
364
+ // ── GROUND: the sink terminal across the outlet. ─────────────────────────
365
+ this.inPort = [path[0][0], path[0][1]];
366
+ this.outPort = [path[path.length - 1][0], path[path.length - 1][1]];
367
+ var dOut = _dir(path[path.length - 2], path[path.length - 1]);
368
+ this.outDirection = dOut.deg;
369
+ if (opts.ground) {
370
+ var gspan = bore + 2 * wallThickness;
371
+ var gx = this.outPort[0] + dOut.ux * groundGap;
372
+ var gy = this.outPort[1] + dOut.uy * groundGap;
373
+ this.ground = new WallCls(universe, {
374
+ rect: (dOut.axis === 'x')
375
+ ? [dOut.ux > 0 ? gx : gx - 4, gy - gspan / 2, 4, gspan]
376
+ : [gx - gspan / 2, dOut.uy > 0 ? gy : gy - 4, gspan, 4],
377
+ collisionBehavior: 'destroy', hooks: ['count'],
378
+ name: name + '_ground', color: [60, 60, 255]
379
+ });
380
+ }
381
+
382
+ // ── Roll up every sub-part for render wiring. ────────────────────────────
383
+ var parts = [];
384
+ this.segments.forEach(function (s) { parts = parts.concat(s.walls); });
385
+ this.junctions.forEach(function (j) { parts = parts.concat(j.walls); });
386
+ parts = parts.concat(this.caps);
387
+ if (this.ground) { parts.push(this.ground); }
388
+ this.walls = parts;
389
+ var self = this;
390
+ parts.forEach(function (w) {
391
+ if (w && w.lo) {
392
+ self.largeObjects.push(w.lo);
393
+ }
394
+ });
395
+
396
+ // THE ONE UP-LINK: the Trace stamps exactly what IT composed — its run and corner
397
+ // Tunnels (each stamps its own plates), its caps and its ground.
398
+ this.segments.concat(this.junctions, this.caps, this.ground ? [this.ground] : []).forEach(function (p) {
399
+ if (p) { p._owner = self; }
400
+ });
401
+
402
+ this._rawDelivered = 0;
403
+ this._window = [];
404
+ this._unsub = null;
405
+ this._disposed = false;
406
+ }
407
+
408
+ /**
409
+ * THE ONE per-tick call. Self-gating: drives every directed junction's host-side
410
+ * redirect and samples the ground counter. A straight, ungrounded Trace is a no-op
411
+ * here, so a caller never needs its own `if (hasCorners)` guard — forgetting that
412
+ * guard is the exact bug `Tunnel.applyRedirectIfActive` exists to make impossible.
413
+ * @param {Object} tickData — this tick's TickData (from `universe.doTick()`).
414
+ */
415
+ Trace.prototype.onTick = function (tickData) {
416
+ for (var i = 0; i < this.junctions.length; i++) {
417
+ this.junctions[i].applyRedirectIfActive(tickData);
418
+ }
419
+ if (this.ground) {
420
+ var raw = this.ground.getCollisionHistogram(tickData).total;
421
+ this._window.push(raw);
422
+ if (this._window.length > 251) { this._window.shift(); }
423
+ this._rawDelivered = raw;
424
+ }
425
+ };
426
+
427
+ /** Subscribe `onTick` to a runner's per-tick fan-out. Idempotent. */
428
+ Trace.prototype.attachToRunner = function (runner) {
429
+ if (this._unsub || !runner || typeof runner.addTickListener !== 'function') { return this._unsub || null; }
430
+ var self = this;
431
+ this._unsub = runner.addTickListener(function (td) { self.onTick(td); });
432
+ return this._unsub;
433
+ };
434
+
435
+ /**
436
+ * Quanta delivered to ground since construction. CALIBRATED — see
437
+ * GROUND_COUNTS_PER_ARRIVAL. Throws on an ungrounded Trace rather than reporting 0.
438
+ */
439
+ Trace.prototype.getDelivered = function () {
440
+ if (!this.ground) {
441
+ throw new Error('[Trace.getDelivered] this Trace has no ground terminal ' +
442
+ '(construct with `ground:true`). (FAIL-LOUD.)');
443
+ }
444
+ return this._rawDelivered / GROUND_COUNTS_PER_ARRIVAL;
445
+ };
446
+
447
+ /** Delivered quanta per tick over the sampled window (0 until the window fills). */
448
+ Trace.prototype.getCurrent = function () {
449
+ if (this._window.length < 2) { return 0; }
450
+ var span = this._window.length - 1;
451
+ return ((this._window[span] - this._window[0]) / GROUND_COUNTS_PER_ARRIVAL) / span;
452
+ };
453
+
454
+ /** Full teardown; delegates to each sub-part's own dispose. Idempotent. */
455
+ Trace.prototype.dispose = function () {
456
+ if (this._disposed) { return; }
457
+ this._disposed = true;
458
+ if (this._unsub) { this._unsub(); this._unsub = null; }
459
+ var all = this.segments.concat(this.junctions);
460
+ all.forEach(function (p) { if (p && typeof p.dispose === 'function') { p.dispose(); } });
461
+ this.caps.forEach(function (w) { if (w && typeof w.dispose === 'function') { w.dispose(); } });
462
+ if (this.ground && typeof this.ground.dispose === 'function') { this.ground.dispose(); }
463
+ };
464
+
465
+ /**
466
+ * Centre-to-centre spacing for INDEPENDENT parallel Traces: each Trace carries its
467
+ * own two mirrors, so neighbours sit wall-against-wall. Do NOT tighten this to
468
+ * `bore + wallThickness` — that overlaps the two mirrors and leaks (see Layer 2).
469
+ * @param {number} bore @param {number} wallThickness @returns {number} px
470
+ */
471
+ Trace.lanePitch = function (bore, wallThickness) {
472
+ return bore + 2 * wallThickness;
473
+ };
474
+
475
+ /**
476
+ * The measured design rules, as DATA — so a layout tool (or a gate) can pick a
477
+ * geometry without re-running the bench. `pitch` is `Trace.lanePitch()` for
478
+ * independent Traces; `crosstalk` is the fraction of the energised lane's current
479
+ * that appears in the immediately adjacent lane at that pitch (lanes 2+ measured
480
+ * exactly zero in every configuration).
481
+ */
482
+ Trace.GEOMETRIES = [
483
+ { bore: 3, wallThickness: 3, pitch: 9, crosstalk: 0.0098, note: 'densest independent lane' },
484
+ { bore: 2, wallThickness: 6, pitch: 14, crosstalk: 0.0, note: 'narrow bore, thick insulation' },
485
+ { bore: 1, wallThickness: 10, pitch: 21, crosstalk: 0.0, note: 'smallest bore that conducts' },
486
+ { bore: 12, wallThickness: 6, pitch: 24, crosstalk: 0.0, note: 'roomy — the only bore that TURNS well' }
487
+ ];
488
+
489
+ /** Measured throughput of one right-angle junction, by `cornerSpread` (degrees). */
490
+ Trace.CORNER_THROUGHPUT = { 0: 0.935, 5: 0.951, 10: 0.920, 20: 0.881, 45: 0.760, 90: 0.590 };
491
+
492
+ /**
493
+ * The two corner MECHANISMS that were built and measured, best-of-sweep at bore 12, so a
494
+ * later part can pick with the numbers instead of re-deriving them. A specular
495
+ * `Wall{bounce/reflect}` elbow DOES turn the current (the Collision-Lab mirror rungs route
496
+ * with it); it simply measured lower in a bore-to-bore corner, where preserving the beam's
497
+ * angular spread is a liability rather than a feature.
498
+ */
499
+ Trace.CORNER_MECHANISMS = {
500
+ mirrorElbow: { G: 0.738, sd: 0.011, part: "Wall{collisionBehavior:'bounce', bounceDirection:'reflect'}" },
501
+ directedJunction: { G: 0.951, sd: 0.005, part: "Tunnel{mode:'directed'}" }
502
+ };
503
+
504
+ Trace.GROUND_COUNTS_PER_ARRIVAL = GROUND_COUNTS_PER_ARRIVAL;
505
+
506
+ // ── Export ───────────────────────────────────────────────────────────────
507
+ export { Trace };
508
+