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

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 (217) hide show
  1. package/CLAUDE.md +87 -0
  2. package/README.md +84 -2
  3. package/dist/dist-manifest.json +218 -0
  4. package/dist/eyes/index.js +109 -0
  5. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  6. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  7. package/dist/fat/pkg-node/package.json +12 -0
  8. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  9. package/dist/frqtl/compat/boot-node.js +34 -0
  10. package/dist/frqtl/compat/engine.js +127 -0
  11. package/dist/frqtl/compat/package-root.js +24 -0
  12. package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
  13. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  14. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  15. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  16. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  17. package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
  18. package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
  19. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  20. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  21. package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
  22. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  23. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  24. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  25. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  26. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  27. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  28. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  30. package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
  31. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  32. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  33. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  34. package/dist/frqtl/compat/vendor.js +196 -0
  35. package/dist/frqtl/node-entry.js +17 -0
  36. package/dist/frqtl/objects/FrqtlApp.js +2922 -0
  37. package/dist/frqtl/objects/ObjectFactory.js +297 -0
  38. package/dist/frqtl/objects/RecipeContext.js +322 -0
  39. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  40. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  41. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  42. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  43. package/dist/frqtl/objects/instruments/capture.js +711 -0
  44. package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
  45. package/dist/frqtl/objects/kinds-entry.js +14 -0
  46. package/dist/frqtl/objects/kinds.js +190 -0
  47. package/dist/frqtl/objects/objects/Box.js +273 -0
  48. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  49. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  50. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  51. package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
  52. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  53. package/dist/frqtl/objects/objects/LightSource.js +354 -0
  54. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  55. package/dist/frqtl/objects/objects/Tube.js +471 -0
  56. package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
  57. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  58. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  59. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  60. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  61. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  62. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  66. package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
  67. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  68. package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
  69. package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
  70. package/dist/frqtl/objects/objects/tagger.js +228 -0
  71. package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
  72. package/dist/frqtl/objects/schema/index.js +256 -0
  73. package/dist/frqtl/objects/schema/scopes.js +457 -0
  74. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  75. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  76. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  77. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  78. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  79. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  80. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  81. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  82. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
  83. package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  85. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  86. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  87. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  88. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  89. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  90. package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
  91. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  92. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  94. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
  95. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  96. package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
  97. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  98. package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
  99. package/dist/frqtl/objects/v1-replacements.js +30 -0
  100. package/dist/frqtl/package-core.js +23 -0
  101. package/dist/frqtl/parts/CameraControls.js +643 -0
  102. package/dist/frqtl/parts/CameraPath.js +575 -0
  103. package/dist/frqtl/parts/CollisionActions.js +35 -0
  104. package/dist/frqtl/parts/CollisionTypes.js +31 -0
  105. package/dist/frqtl/parts/DetectorWall.js +496 -0
  106. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  107. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  108. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  109. package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
  110. package/dist/frqtl/parts/LargeObject.js +406 -0
  111. package/dist/frqtl/parts/LightColor.js +255 -0
  112. package/dist/frqtl/parts/LiveChart.js +285 -0
  113. package/dist/frqtl/parts/LogicGate.js +720 -0
  114. package/dist/frqtl/parts/OvenBox.js +612 -0
  115. package/dist/frqtl/parts/PointSource.js +763 -0
  116. package/dist/frqtl/parts/Rectangle.js +200 -0
  117. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  118. package/dist/frqtl/parts/SlitScreen.js +628 -0
  119. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  120. package/dist/frqtl/parts/Trace.js +508 -0
  121. package/dist/frqtl/parts/Tunnel.js +1057 -0
  122. package/dist/frqtl/parts/Wall.js +1211 -0
  123. package/dist/frqtl/parts/index.js +33 -0
  124. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  126. package/dist/frqtl/scenes/atomic-model.js +88 -0
  127. package/dist/frqtl/scenes/big-bang.js +46 -0
  128. package/dist/frqtl/scenes/blackbody.js +81 -0
  129. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  130. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  131. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  132. package/dist/frqtl/scenes/counting-wall.js +38 -0
  133. package/dist/frqtl/scenes/double-slit.js +31 -0
  134. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  135. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  136. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  137. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
  138. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  139. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  140. package/dist/frqtl/scenes/empty.js +14 -0
  141. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  142. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  143. package/dist/frqtl/scenes/mirror-route.js +140 -0
  144. package/dist/frqtl/scenes/mirror.js +38 -0
  145. package/dist/frqtl/scenes/omni-3d.js +27 -0
  146. package/dist/frqtl/scenes/omni-source.js +22 -0
  147. package/dist/frqtl/scenes/self-interference.js +39 -0
  148. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  149. package/dist/frqtl/scenes/straight-walk.js +22 -0
  150. package/dist/frqtl/scenes/sun.js +39 -0
  151. package/dist/frqtl/scenes/two-source.js +37 -0
  152. package/dist/frqtl/scenes/wave.js +26 -0
  153. package/dist/shim/bundle/frqnb.js +112 -0
  154. package/dist/shim/bundle/index.js +121 -0
  155. package/dist/shim/bundle/indexHtml.js +107 -0
  156. package/dist/shim/bundle/manifest.js +95 -0
  157. package/dist/shim/bundle/zip.js +243 -0
  158. package/dist/shim/bus.js +67 -0
  159. package/dist/shim/controls/index.js +627 -0
  160. package/dist/shim/controls/registry.js +274 -0
  161. package/dist/shim/engine/artifact-shell.js +212 -0
  162. package/dist/shim/engine/boot-phases.js +44 -0
  163. package/dist/shim/engine/engine-stack-boot.js +59 -0
  164. package/dist/shim/engine/router.js +692 -0
  165. package/dist/shim/experiment-frame/index.js +296 -0
  166. package/dist/shim/expr/ast-schema.js +369 -0
  167. package/dist/shim/field-descriptor/index.js +326 -0
  168. package/dist/shim/frame-data/index.js +224 -0
  169. package/dist/shim/index.js +67 -0
  170. package/dist/shim/notebook/canvas-guard.js +82 -0
  171. package/dist/shim/notebook/catalog.js +35 -0
  172. package/dist/shim/notebook/format.js +736 -0
  173. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  174. package/dist/shim/notebook/kernel.js +672 -0
  175. package/dist/shim/notebook/knob.js +151 -0
  176. package/dist/shim/notebook/module-resolver.js +260 -0
  177. package/dist/shim/notebook/notebook-knobs.js +65 -0
  178. package/dist/shim/notebook/produced-bindings.js +67 -0
  179. package/dist/shim/notebook/realm-registry.js +121 -0
  180. package/dist/shim/notebook/realm-transport.js +73 -0
  181. package/dist/shim/notebook/setup-builder.js +510 -0
  182. package/dist/shim/notebook/store.js +159 -0
  183. package/dist/shim/notebook/vendor-importmap.js +134 -0
  184. package/dist/shim/notebook/view-builder.js +189 -0
  185. package/dist/shim/notebook/view-ctx.js +100 -0
  186. package/dist/shim/notebook/view-handle.js +202 -0
  187. package/dist/shim/notebook/warmup.js +102 -0
  188. package/dist/shim/package.json +4 -0
  189. package/dist/shim/page-params/index.js +214 -0
  190. package/dist/shim/project/identity.js +112 -0
  191. package/dist/shim/project/index.js +19 -0
  192. package/dist/shim/project/link.js +402 -0
  193. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  194. package/dist/shim/project/register-node.mjs +17 -0
  195. package/dist/shim/project/specifiers.js +106 -0
  196. package/dist/shim/registry/copy-gate.js +110 -0
  197. package/dist/shim/registry/exhibits.js +455 -0
  198. package/dist/shim/registry/experiments.js +215 -0
  199. package/dist/shim/registry/index.js +19 -0
  200. package/dist/shim/registry/scene-source.js +157 -0
  201. package/dist/shim/registry/walkthrough.js +807 -0
  202. package/dist/shim/render/capture-envelope.js +279 -0
  203. package/dist/shim/render/heat-colormap.js +92 -0
  204. package/dist/shim/render/render-stages.js +855 -0
  205. package/dist/shim/render/sun-look.js +33 -0
  206. package/dist/shim/render-state/apply.js +262 -0
  207. package/dist/shim/render-state/index.js +74 -0
  208. package/dist/shim/rpc/index.js +207 -0
  209. package/dist/shim/runtime/index.js +1023 -0
  210. package/dist/shim/storage/boot.js +69 -0
  211. package/dist/shim/storage/index.js +169 -0
  212. package/dist/shim/storage/local-storage-provider.js +230 -0
  213. package/dist/shim/storage/make-namespace-store.js +132 -0
  214. package/dist/shim/storage/modules.js +249 -0
  215. package/dist/shim/storage/projects.js +150 -0
  216. package/llms.txt +12 -0
  217. package/package.json +62 -4
@@ -0,0 +1,471 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * Tube — Engine-agnostic 3D containing tube (Dim-scaled walls; capped 0, 1, or 2 ends).
5
+ *
6
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
7
+ *
8
+ * A tube and a wire are the same object at different FRQTL Dims: the SAME
9
+ * primitive is a wire, a barrel, a car
10
+ * tunnel, or a wormhole — only the parameters and end-capping differ. Tube builds
11
+ * a rectangular Y/Z perimeter (top/bottom + near/far walls) running the length of
12
+ * its X (long) axis, with each X-end OPTIONALLY capped (a barrel = one capped end,
13
+ * one open; a wire/tunnel = both open; a sealed chamber = both capped).
14
+ *
15
+ * FRQTL-MAINT (2026-07-01, #61) — ALL SIX FACES, ONE PATH: every wall (TOP, BOTTOM,
16
+ * LEFT/RIGHT end-caps, and — when `zContain` — NEAR/FAR) is now a standard per-face
17
+ * `LargeObject` built with an explicit 6-element rect `[left, top, z, w, h, d]` (the
18
+ * landed `rect_to_z_capable_rectangle` wire contract, `egpt_wasm/src/lib.rs`, commit
19
+ * `1c349ab`) sized to the tube's TRUE z cross-section. `LargeObject`'s own constructor
20
+ * auto-compiles `collisionType`+`reposition` into an op-program for every face
21
+ * uniformly (`actionCompiler.compileActions`) — there is no manual op-program
22
+ * construction anywhere in this file. This RETIRES two prior workarounds (both
23
+ * deleted, not kept alongside — see the "RETIRED" comment blocks in
24
+ * `actionCompiler.js`): the literal-bounds `buildZContainmentOps` combined-program
25
+ * (one fake full-footprint LO faking a NEAR/FAR test) and the manual x/y-only
26
+ * `buildXYIntersectsTrigger` the thin end-caps needed (their z-slab used to be
27
+ * pinned to their own — deliberately thin — x-width). Both were needed only because
28
+ * every LO's z-slab used to be hardcoded to `[0, own-x-width]`; now that the wire
29
+ * contract carries real z/d, `self.near`/`self.far` (read by the `'near'`/`'far'`
30
+ * cases added to `actionCompiler.js`'s `buildEjectOps`/`buildVelocityFlipOp`) are
31
+ * meaningful for the first time, so NEAR/FAR (and the end-caps) work exactly like
32
+ * TOP/BOTTOM/LEFT/RIGHT always did.
33
+ *
34
+ * @param {Object} universe - The engine universe instance (JS or WASM), already `init()`'d.
35
+ * @param {Object} options - Configuration.
36
+ * @param {number} options.left - Interior min-X corner (pixels) — the tube's X origin
37
+ * (breech end when used as a barrel).
38
+ * @param {number} options.top - Interior min-Y corner (pixels).
39
+ * @param {number} [options.z=0] - Interior min-Z corner (pixels). Default 0 — the SAME
40
+ * non-negative corner convention as `left`/`top` (an arbitrary reference origin; any
41
+ * value — including negative — is valid now that z/d ride an explicit wire field
42
+ * instead of a hardcoded 0.0).
43
+ * @param {number} options.interiorWidth - Interior X extent (pixels) — the tube's
44
+ * LENGTH along its long axis (breech to muzzle for a barrel).
45
+ * @param {number} options.interiorHeight - Interior Y extent (pixels) — the bore's
46
+ * vertical cross-section.
47
+ * @param {number} options.interiorDepth - Interior Z extent (pixels) — the bore's
48
+ * lateral cross-section.
49
+ * @param {number} [options.wallThickness=10] - Exterior wall thickness (pixels). Every
50
+ * face extends outward from the interior by this amount on its own thin axis, AND
51
+ * into its neighboring faces' corners on the other two axes (matching the
52
+ * Box.js/OvenBox.js X/Y convention, now applied uniformly to Z too) so the six
53
+ * faces close every edge of the box with no corner gap.
54
+ * @param {boolean} [options.capNegX=false] - Build a wall capping the −X end (the
55
+ * "breech" when Tube is used as a barrel). Default false = open (a wire/tunnel).
56
+ * @param {boolean} [options.capPosX=false] - Build a wall capping the +X end (the
57
+ * "muzzle" when Tube is used as a barrel). Default false = open. **A barrel is
58
+ * `capNegX:true, capPosX:false`** — the muzzle is a bare ABSENCE of a wall, not a
59
+ * sensor (Q-L1-2).
60
+ * @param {string} [options.collisionType='BOUNCE'] - CollisionTypes value applied to
61
+ * every built face (a `CollisionTypes` constant) — TOP/BOTTOM/end-caps/NEAR/FAR alike.
62
+ * @param {Array<string>} [options.actions=[]] - Additional CollisionActions applied to
63
+ * every wall (stacks with collisionType; passed through to LargeObject).
64
+ * @param {Array|null} [options.observedDimensions=null] - Dimension layers the tube's
65
+ * walls observe. null = all dimensions (passed through to every LargeObject —
66
+ * Q4 "passes through the LO's params").
67
+ * @param {Array<number>} [options.color=[90,90,96]] - Wall RGB tint.
68
+ * @param {boolean} [options.zContain=true] - Build the NEAR/FAR z-perimeter walls.
69
+ * Set false to leave the z-axis open (e.g. a flat 2D-in-3D scene) — TOP/BOTTOM/
70
+ * end-caps then fall back to the legacy 4-element rect (z=0, depth=own-x-width;
71
+ * unaffected by this option, byte-identical to a z-open scene).
72
+ * @param {number} [options.wallOpacity=0.3] - Render opacity 0 (fully transparent) to
73
+ * 1 (fully opaque), applied to every wall (STANDARDIZED sugar-level knob, CEO
74
+ * 2026-07-02 — so contained quanta stay visible from outside views). Reuses the
75
+ * existing LO `alpha` schema field (0-255; see the Render shape block below) —
76
+ * NOT a new render hint. The white edge outline stays crisp at any opacity.
77
+ * @param {boolean} [options.collectCollided=false] - DIRECTED TUNNEL BUILD (2026-07-13):
78
+ * passed through as `LargeObject`'s own 10th
79
+ * ctor arg (`collectCollided`) for EVERY wall built by this Tube. When true, the
80
+ * engine accumulates each wall's collided frames into `tickData.objects[handle]
81
+ * .frames` every tick (zero physics change — a collection buffer, not a behavior).
82
+ * Default false — byte-identical for every existing Tube caller, none of which
83
+ * read per-wall collided
84
+ * frames. Needed by `DirectedTunnel`'s host-trig redirect (report §2 path (c)):
85
+ * the host tick-loop reads `tickData.objects[topWallHandle|bottomWallHandle]
86
+ * .frames` between ticks and writes back `vx`/`vy` via `setFrameProperty` — this
87
+ * flag is what makes those frames appear in the marshalled tick output at all.
88
+ * @param {Object|null} [options.emitter=null] - DIRECTED TUNNEL BUILD (2026-07-13):
89
+ * promotes the hand-rolled breech-emitter
90
+ * pattern into Tube's own constructor as an opt-in, auto-wired
91
+ * `QuantumEmitter` positioned just inside the `capNegX` breech wall, centred on the
92
+ * bore's Y (and, when `zContain`, Z) centreline. Requires `options.capNegX:true`
93
+ * (throws otherwise — FAIL-LOUD, not a silent no-op: an emitter with no breech to
94
+ * emit from is a caller error). Exposed as `this.emitter`. Fields (all optional,
95
+ * matching `QuantumEmitter`'s own ctor defaults exactly where omitted):
96
+ * - `vx`,`vy` (default: `speed`,`0` — a directed +X beam into the bore, mirroring
97
+ * a propellant emitter; `speed` below).
98
+ * - `speed` (default 1) - convenience: when `vx`/`vy` are BOTH omitted, the
99
+ * emitter fires at `(speed, 0)` (straight down the bore's local +X).
100
+ * - `burstSize` (default 1), `charge` (default null = random, back-compat
101
+ * 0/1/'POSITIVE'/'NEGATIVE' — matches `QuantumEmitter`'s own vocabulary),
102
+ * `randomDirection` (default false - a DIRECTED beam;
103
+ * true = an isotropic point-source INSIDE the bore, for a diffuse fill),
104
+ * `quantumLimit` (default Infinity), `delay` (default 0), `wrap` (default false).
105
+ * - `wavelength` (default null = engine default mass) - ws-dse-3d AC1: per-
106
+ * emitted-particle wavelength override (`QuantumEmitter`'s own new `opts.
107
+ * wavelength`, Engine Invariant #6). LIVE via `this.emitter.setWavelength()`.
108
+ * When `zContain` is armed, this Tube ALSO auto-centres the emitter's z
109
+ * (`opts.zMin`) on the bore's z-centreline and, when `randomDirection` is
110
+ * also true, makes the random-direction draw 3D-isotropic (`opts.
111
+ * zIsotropy`) — a DIRECTED emitter (`randomDirection:false`, the default)
112
+ * stays `vz=0` deliberately (collimation unaffected).
113
+ * - `sourceHeight` (default: half the bore height, the propellant-aperture
114
+ * convention) - the emission aperture's Y-extent, centred
115
+ * on the bore.
116
+ * Default `null` - byte-identical for every existing Tube caller (none pass
117
+ * `options.emitter`).
118
+ * @param {boolean} [options.sinkOpenEnds=false] - Build an ABSORB sink LO just beyond
119
+ * EACH open (uncapped) X-end — the reusable home for "the muzzle absorber-sink"
120
+ * (CEO 2026-07-02, L2.2): any open-ended contained-flow scene (a barrel firing gas
121
+ * out the muzzle, a wire/tunnel with through-traffic on both ends) wants a bounded
122
+ * population, not unbounded growth as frames exit and are never removed. Named
123
+ * generically (not `muzzleSink`) because Tube itself is generic (wire ≡ barrel ≡
124
+ * tunnel ≡ wormhole) — a barrel (`capNegX:true, capPosX:false`) gets exactly one
125
+ * sink, at the open +X (muzzle) end; a wire (both ends open) gets two. Reuses the
126
+ * EXISTING KILL/ABSORB precedent (`DetectorWall.js`: `CollisionTypes.NONE` +
127
+ * `[COUNT, ABSORB]` actions, compiled by `actionCompiler.js`'s standard path to
128
+ * `set frame.is_alive = 0` — NOT `mass=0`, per the theorist's L1 note) — no new
129
+ * op-program, no engine change. Each sink is invisible (`visible:false`) by
130
+ * default (a floating absorber box outside the bore reads as a rendering
131
+ * artifact, not a real object) but IS included in `this.largeObjects` (so the
132
+ * Layers panel / IDE tooling can still find and toggle it) and separately on
133
+ * `this.sinks` for direct introspection (e.g. reading each sink's `.count`).
134
+ *
135
+
136
+ * @property {Array<LargeObject>} walls - ALL built walls (TOP, BOTTOM, LEFT/RIGHT for
137
+ * any capped end, NEAR/FAR when `zContain`) — the caller's visible/render list.
138
+ * @property {Array<LargeObject>} sinks - The built open-end ABSORB sinks (when
139
+ * `sinkOpenEnds`), one per uncapped X-end. Empty array when `sinkOpenEnds` is false.
140
+ * @property {Object} innerRect - `{left, top, z, w, h, d}` — the interior box, for
141
+ * callers that need to place contents (a bullet, an emitter, preplaced quanta)
142
+ * inside the tube.
143
+ *
144
+ * Custom tick functions STACK on the tube's own (Q4): Tube registers no tick
145
+ * functions of its own — it only registers LargeObjects at construction time — so a
146
+ * caller's `universe.experimentTickFunctions.push(...)` composes freely alongside it.
147
+ *
148
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
149
+ *
150
+ * Tube adds ZERO new physics (canonical-bench SU1: sugar only WIRES the engine). Every
151
+ * face — X/Y perimeter AND (when `zContain`) the Z perimeter — is the existing
152
+ * OvenBox/Box eject+bounce op-program pattern (proven bit-identical to native
153
+ * WALL/BOUNCE, EP21-EP28) — `LargeObject`'s own constructor compiles
154
+ * `collisionType`+`reposition` to an op-program automatically for every face, including
155
+ * NEAR/FAR (see `lib/frqtl/objects/objects/actionCompiler.js` `compileActions` +
156
+ * its `'near'`/`'far'` reposition cases).
157
+ *
158
+ * OBSERVED (this workstream, pkg-node WASM, seed 42): a Dim-6 quantum fired at each of
159
+ * the tube's 6 possible faces (top/bottom/near/far/breech/muzzle, `capNegX:true
160
+ * capPosX:false`) bounces and stays contained on the 5 closed faces over 600+ ticks; a
161
+ * quantum fired at the open muzzle exits without any wall interaction — re-verified
162
+ * end-to-end against the landed z/depth wire contract (empirical re-proof, not assumed;
163
+ * see the delivery report for the exact run).
164
+ */
165
+
166
+ import { LargeObject } from '../../parts/LargeObject.js';
167
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
168
+ import { CollisionActions } from '../../parts/CollisionActions.js';
169
+ import { QuantumEmitter } from './QuantumEmitter.js';
170
+
171
+ // ── Imports ─────────────────────────────────────────────────────────
172
+
173
+ // ── Tube ────────────────────────────────────────────────────────────
174
+
175
+ /**
176
+ * @param {Object} universe - Already-init'd universe (JS or WASM).
177
+ * @param {Object} options - See module-level JSDoc above.
178
+ */
179
+ function Tube(universe, options) {
180
+ options = options || {};
181
+
182
+ this.universe = universe;
183
+ this._schemaKind = 'Tube';
184
+
185
+ var interiorWidth = options.interiorWidth;
186
+ var interiorHeight = options.interiorHeight;
187
+ var interiorDepth = options.interiorDepth;
188
+ if (!(interiorWidth > 0) || !(interiorHeight > 0) || !(interiorDepth > 0)) {
189
+ throw new Error(
190
+ '[Tube] interiorWidth/interiorHeight/interiorDepth must all be positive numbers.'
191
+ );
192
+ }
193
+
194
+ var wt = options.wallThickness !== undefined ? options.wallThickness : 10;
195
+
196
+ var innerLeft = options.left;
197
+ var innerTop = options.top;
198
+ // z defaults to 0 — the SAME non-negative corner convention as left/top (an
199
+ // arbitrary reference origin; FRQTL-MAINT 2026-07-01: any value is now valid,
200
+ // since z/d ride the explicit 6-element wire field rather than a hardcoded 0.0 —
201
+ // see the module docstring).
202
+ var innerZ = options.z !== undefined ? options.z : 0;
203
+ if (innerLeft === undefined || innerTop === undefined) {
204
+ throw new Error('[Tube] options.left and options.top are required.');
205
+ }
206
+
207
+ var capNegX = !!options.capNegX;
208
+ var capPosX = !!options.capPosX;
209
+
210
+ var ct = options.collisionType || CollisionTypes.BOUNCE;
211
+ var actions = options.actions || [];
212
+ var color = options.color || [90, 90, 96];
213
+ var observedDimensions = options.observedDimensions !== undefined ? options.observedDimensions : null;
214
+ var zContain = options.zContain !== undefined ? !!options.zContain : true;
215
+ // wallOpacity: the STANDARDIZED render knob (CEO 2026-07-02) — see the Render
216
+ // shape block below for the mechanism (reuses the existing LO `alpha` schema
217
+ // field, lib/frqtl/objects/schema/index.js:166 — NOT a new hint).
218
+ var wallOpacity = options.wallOpacity !== undefined ? options.wallOpacity : 0.3;
219
+ // sinkOpenEnds: the muzzle-absorber-sink home (CEO 2026-07-02, L2.2) — see the
220
+ // module docstring's param note. Default false — byte-identical to every
221
+ // existing Tube consumer that doesn't
222
+ // opt in.
223
+ var sinkOpenEnds = options.sinkOpenEnds !== undefined ? !!options.sinkOpenEnds : false;
224
+ // collectCollided: DIRECTED TUNNEL BUILD — see module docstring param note.
225
+ // Default false — byte-identical for every existing caller.
226
+ var collectCollided = options.collectCollided !== undefined ? !!options.collectCollided : false;
227
+
228
+ this.innerRect = {
229
+ left: innerLeft, top: innerTop, z: innerZ,
230
+ w: interiorWidth, h: interiorHeight, d: interiorDepth,
231
+ };
232
+ this.wallThickness = wt;
233
+
234
+ // ── ALL SIX FACES, ONE PATH (FRQTL-MAINT 2026-07-01, #61) ────────────────
235
+ // Every wall gets an explicit 6-element rect [left, top, z, w, h, d] (when
236
+ // zContain) sized to the tube's TRUE z cross-section — z spans
237
+ // [innerZ - wt, innerZ + interiorDepth + wt] (the bore plus a wt margin at each
238
+ // end, so all six faces close every corner, the same way the X-cap extension
239
+ // (extLeft/extRight) already closed the TOP/BOTTOM corners against the caps).
240
+ // LargeObject's own constructor auto-compiles collisionType+reposition into an
241
+ // op-program (actionCompiler.compileActions) — no manual program-building here,
242
+ // for ANY face.
243
+ var extLeft = capNegX ? wt : 0;
244
+ var extRight = capPosX ? wt : 0;
245
+ var zLo = innerZ - wt; // this wall's own near (−z) corner
246
+ var zD = interiorDepth + 2 * wt; // z span: the bore + a wt margin each end
247
+
248
+ // rect(left, top, w, h) → 6-element [left,top,z,w,h,d] when zContain (real,
249
+ // always-correct z-slab for this face); else the legacy 4-element form
250
+ // (back-compat: z=0/d=w, unaffected — matches a z-open scene exactly as before).
251
+ function faceRect(left, top, w, h) {
252
+ return zContain ? [left, top, zLo, w, h, zD] : [left, top, w, h];
253
+ }
254
+
255
+ this.walls = [];
256
+
257
+ // TOP wall (Y-min side) — pushes intersecting frames DOWN (+y) into the interior.
258
+ this.walls.push(new LargeObject(
259
+ universe,
260
+ faceRect(innerLeft - extLeft, innerTop - wt, interiorWidth + extLeft + extRight, wt),
261
+ ct, actions, 'TOP', 'bottom', observedDimensions, 1, color, collectCollided
262
+ ));
263
+
264
+ // BOTTOM wall (Y-max side) — pushes intersecting frames UP (-y) into the interior.
265
+ this.walls.push(new LargeObject(
266
+ universe,
267
+ faceRect(innerLeft - extLeft, innerTop + interiorHeight, interiorWidth + extLeft + extRight, wt),
268
+ ct, actions, 'BOTTOM', 'top', observedDimensions, 1, color, collectCollided
269
+ ));
270
+
271
+ // −X cap (breech, when requested) — pushes intersecting frames RIGHT (+x) inward.
272
+ // FRQTL-MAINT (2026-07-01): now a STANDARD per-face LO (was a manual
273
+ // collision_type:NONE + x/y-only-trigger program — see the RETIRED note in
274
+ // actionCompiler.js) — the explicit z/d above gives this thin-in-x wall a
275
+ // correct z-slab, so the native 3D Fn('intersects') trigger (LargeObject's
276
+ // normal auto-compile) now correctly catches a z-drifted frame too.
277
+ if (capNegX) {
278
+ this.walls.push(new LargeObject(
279
+ universe,
280
+ faceRect(innerLeft - wt, innerTop - wt, wt, interiorHeight + 2 * wt),
281
+ ct, actions, 'LEFT', 'right', observedDimensions, 1, color, collectCollided
282
+ ));
283
+ }
284
+
285
+ // +X cap (muzzle, when requested) — pushes intersecting frames LEFT (-x) inward.
286
+ // OMITTED by default (capPosX:false) — the open muzzle is a bare absence of a
287
+ // wall (Q-L1-2), not a sensor. Same standard construction as −X.
288
+ if (capPosX) {
289
+ this.walls.push(new LargeObject(
290
+ universe,
291
+ faceRect(innerLeft + interiorWidth, innerTop - wt, wt, interiorHeight + 2 * wt),
292
+ ct, actions, 'RIGHT', 'left', observedDimensions, 1, color, collectCollided
293
+ ));
294
+ }
295
+
296
+ // ── NEAR/FAR (Z faces) — FRQTL-MAINT (2026-07-01, #61): REAL per-face walls,
297
+ // unblocked by the landed z/depth wire contract. Previously structurally
298
+ // impossible (every LO's z-slab was pinned to the origin) and worked around with
299
+ // a manual literal-bounds combined program on one full-footprint LO
300
+ // (buildZContainmentOps, now RETIRED — see actionCompiler.js). NEAR sits at the
301
+ // bore's −z face (thin in z, full X/Y span, extended by wt into the X-cap and
302
+ // Y-perimeter corners exactly like TOP/BOTTOM/LEFT/RIGHT extend into EACH OTHER's
303
+ // corners); FAR mirrors it at the +z face. Both use the SAME standard
304
+ // auto-compile path (collisionType+reposition) as every other face — the
305
+ // 'near'/'far' reposition cases added to actionCompiler.js's buildEjectOps/
306
+ // buildVelocityFlipOp make self.near/self.far (now real, non-degenerate values)
307
+ // drive the eject/flip formulas exactly like self.left/self.right do for
308
+ // LEFT/RIGHT.
309
+ if (zContain) {
310
+ this.walls.push(new LargeObject(
311
+ universe,
312
+ [innerLeft - extLeft, innerTop - wt, zLo,
313
+ interiorWidth + extLeft + extRight, interiorHeight + 2 * wt, wt],
314
+ ct, actions, 'NEAR', 'far', observedDimensions, 1, color, collectCollided
315
+ ));
316
+ this.walls.push(new LargeObject(
317
+ universe,
318
+ [innerLeft - extLeft, innerTop - wt, innerZ + interiorDepth,
319
+ interiorWidth + extLeft + extRight, interiorHeight + 2 * wt, wt],
320
+ ct, actions, 'FAR', 'near', observedDimensions, 1, color, collectCollided
321
+ ));
322
+ }
323
+
324
+ // ── Open-end ABSORB sinks (CEO 2026-07-02, L2.2 — the muzzle-absorber-sink) ──
325
+ // ONE sink per uncapped X-end (never on a capped end — a closed face already
326
+ // contains everything; a sink there would be redundant and would fight the
327
+ // wall's own bounce). REUSES the existing KILL/ABSORB precedent verbatim
328
+ // (DetectorWall.js: CollisionTypes.NONE + [COUNT, ABSORB] actions, compiled by
329
+ // actionCompiler.js's standard compileActions path to `set frame.is_alive = 0`
330
+ // — NOT mass=0, per the theorist's L1 note) — zero new op-program, zero engine
331
+ // change. Positioned a small margin OUTSIDE the bore's open face (never
332
+ // overlapping the interior, so normal bore physics near the opening is
333
+ // unaffected) with the SAME z-slab (`faceRect`) every other face uses, so it
334
+ // catches an exiting frame regardless of its Y/Z position — mirrors the NEAR/
335
+ // FAR walls' "full-cross-section, thin-on-its-own-axis" shape.
336
+ this.sinks = [];
337
+ if (sinkOpenEnds) {
338
+ var sinkDepth = wt; // reuse wallThickness as the sink's own thin-axis extent
339
+ var sinkMargin = 2; // small gap so the sink never touches/overlaps the bore
340
+
341
+ if (!capNegX) {
342
+ // Sink just before the open −X (breech) end.
343
+ this.sinks.push(new LargeObject(
344
+ universe,
345
+ faceRect(innerLeft - sinkMargin - sinkDepth, innerTop - wt, sinkDepth, interiorHeight + 2 * wt),
346
+ CollisionTypes.NONE, [CollisionActions.COUNT, CollisionActions.ABSORB],
347
+ 'NEG_X_SINK', 'closest', observedDimensions, 1, color
348
+ ));
349
+ }
350
+ if (!capPosX) {
351
+ // Sink just beyond the open +X (muzzle) end.
352
+ this.sinks.push(new LargeObject(
353
+ universe,
354
+ faceRect(innerLeft + interiorWidth + sinkMargin, innerTop - wt, sinkDepth, interiorHeight + 2 * wt),
355
+ CollisionTypes.NONE, [CollisionActions.COUNT, CollisionActions.ABSORB],
356
+ 'POS_X_SINK', 'closest', observedDimensions, 1, color
357
+ ));
358
+ }
359
+ // Invisible by default — a floating absorber box outside the bore reads as
360
+ // a render artifact, not a real object. Physics (registerLargeObject) is
361
+ // unaffected by visibility (a renderer-only property) — the sink still
362
+ // absorbs whether or not it is drawn.
363
+ for (var _si = 0; _si < this.sinks.length; _si++) {
364
+ this.sinks[_si].visible = false;
365
+ }
366
+ }
367
+
368
+ // ── Breech emitter (DIRECTED TUNNEL BUILD, 2026-07-13, opt-in) ────────────
369
+ // Promotes the hand-rolled breech-emitter pattern into Tube's own
370
+ // constructor — see the module docstring's
371
+ // `options.emitter` param note for the full field list. FAIL-LOUD (not a
372
+ // silent no-op): an emitter needs a closed breech to emit FROM, so
373
+ // `options.emitter` without `capNegX:true` throws rather than building a
374
+ // directed emitter that immediately fires out an open end.
375
+ this.emitter = null;
376
+ if (options.emitter) {
377
+ if (!capNegX) {
378
+ throw new Error(
379
+ '[Tube] options.emitter requires options.capNegX:true (the emitter is ' +
380
+ 'positioned just inside the breech wall — there is no breech to emit from otherwise).'
381
+ );
382
+ }
383
+ var espec = options.emitter;
384
+ var espeed = espec.speed !== undefined ? espec.speed : 1;
385
+ var evx = espec.vx !== undefined ? espec.vx : espeed;
386
+ var evy = espec.vy !== undefined ? espec.vy : 0;
387
+ var eburst = espec.burstSize !== undefined ? espec.burstSize : 1;
388
+ var echarge = espec.charge !== undefined ? espec.charge : null;
389
+ var erandomDir = espec.randomDirection !== undefined ? !!espec.randomDirection : false;
390
+ var equantumLimit = espec.quantumLimit !== undefined ? espec.quantumLimit : Infinity;
391
+ var edelay = espec.delay !== undefined ? espec.delay : 0;
392
+ var ewrap = espec.wrap !== undefined ? !!espec.wrap : false;
393
+ // Aperture: centred on the bore's Y centreline, positioned just inside the
394
+ // breech wall (`breechEmitX = breechX + 2`). Height default is the
395
+ // `propellantSourceH` convention (half the bore height).
396
+ var esourceH = espec.sourceHeight !== undefined ? espec.sourceHeight : Math.max(1, Math.floor(interiorHeight / 2));
397
+ var eEmitX = innerLeft + 2;
398
+ var eCenterY = innerTop + interiorHeight / 2;
399
+ // ws-dse-3d AC1 (CEO drive #2 round-2 ruling, "the emitter source mode
400
+ // consumes the SAME normalized SOURCE_SPEC bag"): wavelength/z-centring/
401
+ // z-isotropy — derived from THIS Tube's own `zContain`/`innerZ`/
402
+ // `interiorDepth` (already in scope), never a caller burden. zContain
403
+ // false (every pre-existing Tube.options.emitter caller) -> eZMin stays
404
+ // null, eZIsotropy stays false -> byte-identical (QuantumEmitter's own
405
+ // opts defaults).
406
+ var ewavelength = espec.wavelength !== undefined ? espec.wavelength : null;
407
+ var eZMin = zContain ? (innerZ + interiorDepth / 2) : null; // bore's z-centreline
408
+ var eZDepth = zContain ? 0 : null; // pinned (single value), not a spread
409
+ var eZIsotropy = zContain; // only consumed when erandomDir is also true
410
+ this.emitter = new QuantumEmitter(
411
+ universe,
412
+ [[eEmitX, eCenterY - Math.floor(esourceH / 2), 1, esourceH]],
413
+ evx, evy, eburst, echarge,
414
+ true, // auto_fire
415
+ erandomDir,
416
+ equantumLimit,
417
+ edelay,
418
+ ewrap,
419
+ { wavelength: ewavelength, zMin: eZMin, zDepth: eZDepth, zIsotropy: eZIsotropy }
420
+ );
421
+ }
422
+
423
+ // ── Render shape (renderer-only hints; physics is unaffected) ────────────
424
+ // FRQTL-MAINT (2026-07-01, CEO-flagged): shape:'box' + depth wraps each wall LO
425
+ // with a real 3D BoxGeometry (Bullet.js:112-116 precedent) instead of the
426
+ // flat-plane default (_buildLoMesh, three-provider.js) — a zero-thickness plane
427
+ // reads as a thin 2D outline at oblique orbit angles, which is exactly the CEO's
428
+ // "walls draw as thin 2D outlines" finding. depth is read straight off the
429
+ // wall's OWN rect.d (set above by the 6-element rect) so the box's z-extent
430
+ // matches its true physical z-slab exactly; when zContain is false (legacy
431
+ // 4-element rect, no rect.d), depth stays undefined and _buildLoMesh's existing
432
+ // `lo.depth || lo.rect.w` fallback applies unchanged.
433
+ //
434
+ // wallOpacity (CEO 2026-07-02, translucent walls so contained quanta stay
435
+ // visible from outside): REUSES the existing `alpha` LO field — already a
436
+ // first-class, schema-declared render-only property (`lib/frqtl/objects/schema/
437
+ // index.js` name:'alpha', 0-255, widgetHint:'slider') that `_buildLoMesh`'s
438
+ // box-shape branch already reads for its material's `transparent`/`opacity`
439
+ // (`three-provider.js` ~line 520/557) — NOT a new hint, NOT a hardcoded material.
440
+ // `alpha` is 0-255 (matches `color`'s convention); `wallOpacity` is the
441
+ // standardized 0-1 sugar-level knob every caller passes, converted here once.
442
+ // The box mesh's white edge outline (`EdgesGeometry`+`LineBasicMaterial`,
443
+ // three-provider.js) is unconditional at its own fixed opacity regardless of the
444
+ // fill's alpha, so the duct silhouette stays crisp at any wallOpacity.
445
+ var wallAlpha = Math.round(255 * Math.max(0, Math.min(1, wallOpacity)));
446
+ for (var _wi = 0; _wi < this.walls.length; _wi++) {
447
+ this.walls[_wi].shape = 'box';
448
+ if (this.walls[_wi].rect.d !== undefined) {
449
+ this.walls[_wi].depth = this.walls[_wi].rect.d;
450
+ }
451
+ this.walls[_wi].alpha = wallAlpha;
452
+ }
453
+
454
+ // ── THE ONE UP-LINK on every wall and sink this Tube composed ─────────────
455
+ for (var _gi = 0; _gi < this.walls.length; _gi++) {
456
+ this.walls[_gi]._owner = this; // THE ONE UP-LINK
457
+ }
458
+ for (var _si2 = 0; _si2 < this.sinks.length; _si2++) {
459
+ this.sinks[_si2]._owner = this; // THE ONE UP-LINK
460
+ }
461
+
462
+ // ── Return-shape convenience ─────────────────────────────────────────────
463
+ // Sinks ARE included (so Layers-panel/IDE tooling can find + toggle them) —
464
+ // each is already visible:false, so the renderer draws nothing for it.
465
+ this.largeObjects = this.walls.concat(this.sinks);
466
+ }
467
+
468
+ // ── Export ───────────────────────────────────────────────────────────
469
+
470
+ export { Tube };
471
+