@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,412 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * DirectedTunnel — a directed-behavior convenience over Tube (T1, 2026-07-13,
5
+ * "EXTEND Tube, do not fork a sibling").
6
+ *
7
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
8
+ *
9
+ * DirectedTunnel does NOT re-derive wall construction — it CONSTRUCTS a `Tube`
10
+ * (the ONE six-face-construction authority, #61 "ALL SIX FACES, ONE PATH") and
11
+ * then, post-construction, applies:
12
+ * 1. An opt-in `options.behavior` routed through `slitLinerScatter.js`'s
13
+ * composable authority (the SAME one-authority, no-fork rule the DSE liner
14
+ * already uses — report §4.4.4) onto the TOP/BOTTOM walls (the long,
15
+ * quantum-facing bounce surfaces a beam/scatter population actually
16
+ * interacts with repeatedly — report §3's own measured tunnel geometry).
17
+ * 2. When `behavior:'option1'`, registers the host-trig redirect
18
+ * (`directedRedirectProgram.js`) and exposes `this.applyRedirect(tickData)`
19
+ * for the CALLER to invoke once per tick (mirrors `DetectorWall.prototype.
20
+ * getCollisionHistogram(tickData)` — this is NOT a `universe.
21
+ * experimentTickFunctions` push; see `directedRedirectProgram.js`'s own
22
+ * docstring for why Phase-A timing is wrong for this mechanism).
23
+ *
24
+ * ── `options.behavior` — the two report-defined modes + the full composable set ──
25
+ *
26
+ * - `'option2'` (DEFAULT) — pure reflection, NATIVE `BOUNCE` (report §3's own
27
+ * mechanism; `REFLECT_*` is a no-op on the canonical engine — never used).
28
+ * This is Tube's OWN pre-existing default (`collisionType:'BOUNCE'`) —
29
+ * `behavior:'option2'` changes NOTHING; it exists so callers can name the
30
+ * report's mode explicitly rather than relying on Tube's implicit default.
31
+ * - `'option1'` — host-trig directed/isotropic redirect (report §2, path (c)):
32
+ * the TOP/BOTTOM walls are rebuilt with `collisionType:NONE, actions:[BLOCK]`
33
+ * (pure in-engine eject, ZERO velocity touch — `slitLinerScatter.js`'s
34
+ * `'ejectY'` behavior, chosen SPECIFICALLY because it has no velocity ops at
35
+ * all, so the host redirect below is the ONLY velocity write) via
36
+ * `SlitLinerScatter.applyLinerScatter`, and a `directedRedirectProgram.js`
37
+ * applier is built for `[topWall.handle, bottomWall.handle]` at `orientation`
38
+ * (see below) with `options.redirectSpread` (degrees, default 0 = fully
39
+ * directed) as the isotropic-spread sub-option. Requires `options.
40
+ * collectCollided` semantics — DirectedTunnel ALWAYS passes
41
+ * `collectCollided:true` to the underlying Tube when `behavior==='option1'`
42
+ * (needed for `tickData.objects[handle].frames` to exist at all; a no-op
43
+ * otherwise, so this is safe to always set for this one mode).
44
+ * - Any other string in `SlitLinerScatter.BEHAVIORS` (`'randomXY'|'symmetric'|
45
+ * 'bounce'|'ejectY'|'ejectYReflect'`) — routes DIRECTLY through the SAME
46
+ * composable authority (the "any liner-behavior composition" case, report
47
+ * §4.4.4) — gives a Tube's own walls the full DSE-liner scatter vocabulary,
48
+ * unifying the two composable surfaces. `options.resetLocalTime` (default
49
+ * false) is the SAME orthogonal axis `slitLinerScatter.js` already defines.
50
+ * - Unknown string — throws (FAIL-LOUD, never a silent no-op).
51
+ *
52
+ * ── `options.orientation` — v1 semantics (CEO-pending, dispatched; DOCUMENT LOUDLY) ──
53
+ *
54
+ * `orientation` (degrees) is the direction the tunnel DIRECTS TOWARDS — it drives
55
+ * the DIRECTION machinery EXACTLY, at ANY angle, NEVER snapped:
56
+ * - the host-trig redirect's `angleRad` (`option1`'s `directedRedirectProgram`),
57
+ * - the auto-wired emitter's initial `(vx,vy) = (cos(orientation), sin(orientation))
58
+ * * speed` (when `options.emitter` is also supplied — see `this.emitter` below).
59
+ *
60
+ * The tunnel BODY, however, is a hard AABB (axis-aligned) construction — Tube's
61
+ * OWN structural convention is "long axis always X" (its own module docstring):
62
+ * `capNegX`/`capPosX` are the ONLY end-cap axis Tube can build; there is no
63
+ * `capNegY`/`capPosY`. **v1 LIMITATION, stated plainly:** the tunnel body is
64
+ * THEREFORE ALWAYS constructed X-long in v1 — `orientation`'s "snap to the
65
+ * nearest axis-aligned orientation" trivially resolves to X (the only axis Tube
66
+ * can cap) for EVERY input angle. A genuine Y/Z-long BODY (a real closed breech
67
+ * positioned at a Y or Z extreme) is NOT achievable without new Tube geometry
68
+ * (adding `capNegY`/`capPosY`) and is explicitly OUT OF SCOPE for this bite — it
69
+ * is sugar-layer work, not engine work, but it is a materially larger addition
70
+ * than "opt-in default-off params," so it is deliberately deferred, not silently
71
+ * claimed. The DIRECTION (redirect angle / exit vector / emitter velocity) is the
72
+ * part that is genuinely free and exact at any angle — this is what "honors the
73
+ * exact angle" means in practice: a 30° tunnel has an X-long BODY (unaffected by
74
+ * the 90°-snap rule, since 30° would round toward 0° anyway) whose beam/redirect
75
+ * travels at EXACTLY 30°, not 0°.
76
+ *
77
+ * @param {Object} universe - Already-init'd universe (JS or WASM).
78
+ * @param {Object} options
79
+ * @param {number} options.left, options.top, [options.z=0] - Interior origin corner
80
+ * (same convention as `Tube`).
81
+ * @param {number} options.length - Interior X extent (the tunnel's long/travel axis
82
+ * in v1 — see the orientation note above). Maps onto `Tube`'s `interiorWidth`.
83
+ * @param {number} options.width - Interior Y extent (bore cross-section). Maps onto
84
+ * `Tube`'s `interiorHeight`.
85
+ * @param {number} [options.depth] - Interior Z extent (3D cross-section). Omitted
86
+ * (default) -> `zContain:false` passed to `Tube` (a flat 2D-in-3D bore, no NEAR/
87
+ * FAR walls — byte-identical to a 2D scene); a positive value -> `zContain:true`,
88
+ * `depth` maps onto `Tube`'s `interiorDepth`.
89
+ * @param {number} [options.orientation=0] - Direction in DEGREES — see the note
90
+ * above. `0` = +X (Tube's native convention, unaffected).
91
+ * @param {'option1'|'option2'|'randomXY'|'symmetric'|'bounce'|'ejectY'|'ejectYReflect'}
92
+ * [options.behavior='option2'] - see the behavior note above.
93
+ * @param {boolean} [options.resetLocalTime=false] - orthogonal axis for the
94
+ * composable-behavior case (ignored for `'option1'`/`'option2'`).
95
+ * @param {number} [options.redirectSpread=0] - degrees; `'option1'`'s isotropic-
96
+ * spread sub-option (0 = fully directed).
97
+ * @param {number|null} [options.redirectSpeed=null] - `'option1'`'s redirected
98
+ * speed magnitude; `null` (default) preserves each frame's own incoming speed.
99
+ * @param {Object|null} [options.emitter=null] - passed straight through to `Tube`'s
100
+ * own `options.emitter` (requires `options.capNegX:true`), EXCEPT `vx`/`vy` are
101
+ * derived from `orientation`+`speed` when the caller omits them explicitly (a
102
+ * directed-tunnel emitter fires ALONG the tunnel's own direction by default).
103
+ * @param {...*} [options.*] - every other `Tube` option (`capNegX`, `capPosX`,
104
+ * `wallThickness`, `color`, `wallOpacity`, `sinkOpenEnds`, `collectCollided`,
105
+ * `observedDimensions`) passes straight through, UNCHANGED semantics.
106
+ *
107
+ * @property {Object} tube - the underlying `Tube` instance (walls/sinks/innerRect).
108
+ * @property {Array<LargeObject>} walls - alias for `this.tube.walls`.
109
+ * @property {Object|null} emitter - alias for `this.tube.emitter`.
110
+ * @property {Function} applyRedirect - ALWAYS present (C1 fix, 2026-07-13,
111
+ * ruling_directed_tunnel_lightsource.md), regardless of the current `behavior`
112
+ * — a redirect applier is cheap to build and this lets a LIVE `updateBehavior`
113
+ * switch INTO 'option1' work without reconstruction. Prefer
114
+ * `applyRedirectIfActive(tickData)` (below) over calling this directly — it
115
+ * self-gates on `this.behavior === 'option1'` so a caller never needs its own
116
+ * `currentBehavior === 'option1'` check (the exact omission that caused C1).
117
+ * @property {string} behavior - the CURRENT behavior; kept live-accurate by
118
+ * `updateBehavior()` (never mutate directly).
119
+ *
120
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
121
+ *
122
+ * DirectedTunnel adds ZERO new physics (canonical-bench SU1): `option2` is Tube's
123
+ * pre-existing native BOUNCE; the composable-BEHAVIORS case is the SAME op-programs
124
+ * `slitLinerScatter.js` already proves byte-identical to the DSE liner's own
125
+ * construction; `option1`'s redirect is the MEASURED path (c) — zero (or
126
+ * bounded <=1px) extra latency vs a native mechanism, confirmed empirically
127
+ * (Test A/B).
128
+ */
129
+
130
+ import { Tube } from './Tube.js';
131
+ import * as SlitLinerScatter from './slitLinerScatter.js';
132
+ import * as DirectedRedirectProgram from './directedRedirectProgram.js';
133
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
134
+
135
+ // ── Imports ─────────────────────────────────────────────────────────
136
+
137
+
138
+
139
+ var OPTION1 = 'option1';
140
+ var OPTION2 = 'option2';
141
+
142
+ /**
143
+ * @param {Object} universe
144
+ * @param {Object} options - see module docstring above.
145
+ */
146
+ function DirectedTunnel(universe, options) {
147
+ options = options || {};
148
+
149
+ this.universe = universe;
150
+ this._schemaKind = 'DirectedTunnel';
151
+
152
+ var length = options.length;
153
+ var width = options.width;
154
+ if (!(length > 0) || !(width > 0)) {
155
+ throw new Error('[DirectedTunnel] options.length and options.width must both be positive numbers.');
156
+ }
157
+ var hasDepth = (options.depth !== undefined && options.depth !== null);
158
+ var depth = hasDepth ? options.depth : width; // placeholder — Tube requires interiorDepth > 0 even when zContain:false
159
+ var zContain = hasDepth;
160
+ // ws-dse-3d AC2 (CEO drive #2 round-2 ruling): the SAME N1-fix-precedent
161
+ // gate `hasRealDepth` uses elsewhere in this file (polarSpreadRad) — stored
162
+ // for `_applyBehaviorToWalls`'s composable-behavior branch below, which
163
+ // threads it into `SlitLinerScatter.applyLinerScatter`'s new `hasRealDepth`
164
+ // opt-in (3D randomXY/symmetric scatter). zContain:false (every 2D tunnel,
165
+ // every pre-existing caller) -> stays false -> byte-identical.
166
+ this._hasRealDepth = zContain;
167
+
168
+ var orientationDeg = options.orientation !== undefined ? options.orientation : 0;
169
+ var orientationRad = orientationDeg * Math.PI / 180;
170
+ this.orientation = orientationDeg;
171
+
172
+ // v1 BODY SNAP (see module docstring "options.orientation" note): Tube can
173
+ // ONLY build end-caps on its own X axis, so the body is ALWAYS constructed
174
+ // X-long in v1 regardless of the requested orientation — the "snap to the
175
+ // nearest axis-aligned orientation" is trivially satisfied by this single
176
+ // fixed choice (documented loudly above; NOT silently narrower than implied).
177
+ this.bodyAxis = 'X';
178
+
179
+ var behavior = options.behavior !== undefined ? options.behavior : OPTION2;
180
+ var resetLocalTime = !!options.resetLocalTime;
181
+ var redirectSpreadDeg = options.redirectSpread !== undefined ? options.redirectSpread : 0;
182
+ var redirectSpeed = (options.redirectSpeed !== undefined) ? options.redirectSpeed : null;
183
+
184
+ var validComposable = SlitLinerScatter.BEHAVIORS.indexOf(behavior) !== -1;
185
+ if (behavior !== OPTION1 && behavior !== OPTION2 && !validComposable) {
186
+ throw new Error(
187
+ '[DirectedTunnel] unknown behavior "' + behavior + '". Expected: option1 | option2 | ' +
188
+ SlitLinerScatter.BEHAVIORS.join(' | ')
189
+ );
190
+ }
191
+
192
+ // ── Emitter: derive vx/vy from orientation+speed when the caller omits them ──
193
+ var tubeEmitterOpt = null;
194
+ if (options.emitter) {
195
+ tubeEmitterOpt = {};
196
+ for (var k in options.emitter) { if (Object.prototype.hasOwnProperty.call(options.emitter, k)) { tubeEmitterOpt[k] = options.emitter[k]; } }
197
+ if (tubeEmitterOpt.vx === undefined && tubeEmitterOpt.vy === undefined) {
198
+ var espeed = tubeEmitterOpt.speed !== undefined ? tubeEmitterOpt.speed : 1;
199
+ tubeEmitterOpt.vx = Math.cos(orientationRad) * espeed;
200
+ tubeEmitterOpt.vy = Math.sin(orientationRad) * espeed;
201
+ }
202
+ }
203
+
204
+ // ── Construct the underlying Tube (the ONE six-face-construction authority) ──
205
+ var tubeOpts = {
206
+ left: options.left, top: options.top, z: options.z,
207
+ interiorWidth: length, interiorHeight: width, interiorDepth: depth,
208
+ wallThickness: options.wallThickness,
209
+ capNegX: options.capNegX, capPosX: options.capPosX,
210
+ collisionType: options.collisionType, // Tube's own default (BOUNCE) applies unless option1 overrides TOP/BOTTOM below
211
+ actions: options.actions,
212
+ observedDimensions: options.observedDimensions,
213
+ color: options.color,
214
+ zContain: zContain,
215
+ wallOpacity: options.wallOpacity,
216
+ sinkOpenEnds: options.sinkOpenEnds,
217
+ // C1 FIX (2026-07-13): ALWAYS true, regardless of the INITIAL behavior —
218
+ // this.applyRedirect is now ALSO always built (below), so a LATER live
219
+ // switch INTO 'option1' (updateBehavior) needs tickData.objects[handle]
220
+ // .frames to already exist on TOP/BOTTOM; matches the DSE-lab tunnels'
221
+ // own "always collect" design for the SAME reason. Cheap (a collection
222
+ // buffer with no consumer is a no-op physics-wise) and DirectedTunnel has
223
+ // no OTHER existing callers to keep byte-identical against (T1-new).
224
+ collectCollided: true,
225
+ emitter: tubeEmitterOpt,
226
+ };
227
+ this.tube = new Tube(universe, tubeOpts);
228
+ this.walls = this.tube.walls;
229
+ this.sinks = this.tube.sinks;
230
+ this.innerRect = this.tube.innerRect;
231
+ this.emitter = this.tube.emitter;
232
+ this.largeObjects = this.tube.largeObjects;
233
+
234
+ this._topWall = this.walls.filter(function (w) { return w.name === 'TOP'; })[0];
235
+ this._bottomWall = this.walls.filter(function (w) { return w.name === 'BOTTOM'; })[0];
236
+
237
+ // C1 FIX (architect gate, 2026-07-13): the host redirect applier is ALWAYS
238
+ // built (regardless of the INITIAL behavior) — mirrors the DSE-lab tunnels'
239
+ // OWN "always collectCollided:true + always an applyRedirect fallback" design
240
+ // (needed so a LIVE switch INTO option1 works with no reconstruction). Kept
241
+ // here too — not just at the Lab layer — so ANY consumer of DirectedTunnel
242
+ // gets the SAME guarantee "for free", not just the Lab's own hand-rolled path.
243
+ var wallHandles = [];
244
+ if (this._topWall) { wallHandles.push(this._topWall.handle); }
245
+ if (this._bottomWall) { wallHandles.push(this._bottomWall.handle); }
246
+ this.applyRedirect = DirectedRedirectProgram.buildRedirectApplier(universe, {
247
+ wallHandles: wallHandles,
248
+ angleRad: orientationRad,
249
+ spreadRad: redirectSpreadDeg * Math.PI / 180,
250
+ speed: redirectSpeed,
251
+ // 3D EXTENSION (ws-dse-3d, theorist §4 option (c)): when this tunnel
252
+ // has REAL z-containment armed (zContain, the SAME N1-fix-precedent
253
+ // gate `hasRealDepth` uses elsewhere — never innerRect.d, always a
254
+ // positive placeholder), mirror the azimuth spread's own magnitude
255
+ // onto the polar (z) axis too — a redirected population scatters on
256
+ // the unit SPHERE, not just the XY plane. zContain:false (every 2D
257
+ // tunnel, every pre-existing caller) -> polarSpreadRad stays exactly
258
+ // 0 -> byte-identical to the pre-3D 2-axis redirect formula.
259
+ polarSpreadRad: zContain ? (redirectSpreadDeg * Math.PI / 180) : 0,
260
+ });
261
+
262
+ // 'option2' at CONSTRUCTION time needs NO extra push — Tube's own native
263
+ // collisionType (default BOUNCE) already IS option2's target state (zero
264
+ // additional code path, matching the pre-C1-fix behavior exactly). Every
265
+ // OTHER initial behavior (option1 / a composable name) DOES need its
266
+ // construction-time push, same as before. `updateBehavior` (a LIVE switch,
267
+ // possibly INTO option2 from something else) always dispatches all 3 cases.
268
+ if (behavior !== OPTION2) { this._applyBehaviorToWalls(behavior, resetLocalTime); }
269
+ this.behavior = behavior;
270
+ this.resetLocalTime = resetLocalTime;
271
+
272
+ // ── THE ONE UP-LINK on every wall this corridor composed ──────────────────
273
+ for (var gi = 0; gi < this.walls.length; gi++) {
274
+ this.walls[gi]._owner = this; // THE ONE UP-LINK
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Apply ONE behavior to this tunnel's TOP/BOTTOM walls — the SAME three-
280
+ * mechanism dispatch the constructor uses (private; both the constructor and
281
+ * `updateBehavior` below call this ONE implementation, never a second copy).
282
+ * @param {string} behavior
283
+ * @param {boolean} resetLocalTime
284
+ */
285
+ DirectedTunnel.prototype._applyBehaviorToWalls = function (behavior, resetLocalTime) {
286
+ var universe = this.universe;
287
+ var topWall = this._topWall, bottomWall = this._bottomWall;
288
+ if (behavior === OPTION1) {
289
+ // In-engine half: pure eject, ZERO velocity touch (ejectY has no velocity
290
+ // ops at all) — the host redirect (this.applyRedirect, ALWAYS present) is
291
+ // the ONLY velocity write once this mode is active.
292
+ if (topWall) { SlitLinerScatter.applyLinerScatter(topWall, universe, 'bottom', { behavior: 'ejectY', resetLocalTime: false }); }
293
+ if (bottomWall) { SlitLinerScatter.applyLinerScatter(bottomWall, universe, 'top', { behavior: 'ejectY', resetLocalTime: false }); }
294
+ } else if (behavior === OPTION2) {
295
+ // Native BOUNCE — LargeObject's OWN recompileActions (the S4 mechanism
296
+ // every collision_type/actions live edit already uses), NOT a
297
+ // slitLinerScatter push (native BOUNCE has no eject-prefix op-program
298
+ // shape to route through).
299
+ if (topWall) { topWall.recompileActions({ collisionType: 'BOUNCE', actions: [] }); }
300
+ if (bottomWall) { bottomWall.recompileActions({ collisionType: 'BOUNCE', actions: [] }); }
301
+ } else if (SlitLinerScatter.BEHAVIORS.indexOf(behavior) !== -1) {
302
+ // ws-dse-3d AC2: hasRealDepth threads THIS tunnel's own zContain (set
303
+ // at construction, never re-derived) into the composable scatter
304
+ // behaviors — a randomXY/symmetric switch on a real-depth tunnel now
305
+ // scatters all 3 axes, matching option2's (native BOUNCE) own
306
+ // already-correct NEAR/FAR handling.
307
+ if (topWall) { SlitLinerScatter.applyLinerScatter(topWall, universe, 'bottom', { behavior: behavior, resetLocalTime: resetLocalTime, hasRealDepth: this._hasRealDepth }); }
308
+ if (bottomWall) { SlitLinerScatter.applyLinerScatter(bottomWall, universe, 'top', { behavior: behavior, resetLocalTime: resetLocalTime, hasRealDepth: this._hasRealDepth }); }
309
+ } else {
310
+ throw new Error(
311
+ '[DirectedTunnel] unknown behavior "' + behavior + '". Expected: option1 | option2 | ' +
312
+ SlitLinerScatter.BEHAVIORS.join(' | ')
313
+ );
314
+ }
315
+ };
316
+
317
+ /**
318
+ * LIVE behavior switch (C1 fix follow-through, 2026-07-13) — the STRUCTURAL
319
+ * home for "how does a DirectedTunnel switch its collision behavior live,
320
+ * across all 3 underlying mechanisms (option1 host-redirect / option2 native
321
+ * BOUNCE / a slitLinerScatter composable name)". Moved OFF the DSE Lab page
322
+ * (which used to hand-roll this by reaching into `tunnel.walls` directly) and
323
+ * ONTO DirectedTunnel itself so `this.behavior` NEVER goes stale after a live
324
+ * switch — the ONE thing that makes `applyRedirectIfActive` (below) safe to
325
+ * call unconditionally from ANY consumer's per-tick loop, not just the Lab's.
326
+ * @param {string} newBehavior
327
+ * @param {boolean} [resetLocalTime=false] - composable-BEHAVIORS-name case only.
328
+ */
329
+ DirectedTunnel.prototype.updateBehavior = function (newBehavior, resetLocalTime) {
330
+ resetLocalTime = !!resetLocalTime;
331
+ this._applyBehaviorToWalls(newBehavior, resetLocalTime);
332
+ this.behavior = newBehavior;
333
+ this.resetLocalTime = resetLocalTime;
334
+ };
335
+
336
+ /**
337
+ * C1 FIX (architect gate, 2026-07-13) — the redirect applier is a plain
338
+ * function the CALLER invokes once per tick with that tick's `tickData`
339
+ * (directedRedirectProgram.js's own docstring explains why this can never be
340
+ * a self-registering `experimentTickFunctions` push: it needs THIS tick's
341
+ * Phase-D `tickData.objects[handle].frames`, which does not exist until
342
+ * AFTER `doTick()` returns — i.e. AFTER `experimentTickFunctions` already
343
+ * ran). Since there is no generic "call me after every tick with tickData"
344
+ * registry a sugar object can self-register into (FrqtlApp exposes
345
+ * exactly ONE user-supplied `onTick` slot, matching the established
346
+ * `DetectorWall.prototype.getCollisionHistogram(tickData)` precedent — a
347
+ * caller-invoked method, not a hook), the caller MUST still call something,
348
+ * every tick. `applyRedirectIfActive` is that ONE call: it self-gates on
349
+ * `this.behavior` (kept live-accurate by `updateBehavior` above), so a
350
+ * consumer's onTick can call this UNCONDITIONALLY for every tunnel, every
351
+ * tick, regardless of which behavior is currently active — the exact
352
+ * "forgetting to gate on option1" mistake (this C1 bug) becomes structurally
353
+ * impossible to repeat.
354
+ * @param {Object} tickData - this tick's TickData (from `universe.doTick()`).
355
+ */
356
+ DirectedTunnel.prototype.applyRedirectIfActive = function (tickData) {
357
+ if (this.behavior === OPTION1) { this.applyRedirect(tickData); }
358
+ };
359
+
360
+ // BEHAVIORS: the canonical, complete list of legal `options.behavior` values —
361
+ // 'option1'/'option2' (the report's two modes) PLUS every slitLinerScatter
362
+ // composable name. ONE authority for any consumer (e.g. a control-panel select)
363
+ // that needs to enumerate the full set — never hand-typed (T3, 2026-07-13).
364
+ DirectedTunnel.BEHAVIORS = [OPTION1, OPTION2].concat(SlitLinerScatter.BEHAVIORS);
365
+
366
+ // BEHAVIOR_META (CEO drive #2, 2026-07-14 — "option1/option2 are non-descript,
367
+ // I forgot what option1 does"): a DISPLAY-ONLY label/description per canonical
368
+ // `options.behavior` VALUE — the wire value ('option1'/'option2'/composable
369
+ // name) is UNCHANGED (every existing caller — the tunnel/lightsource labs and
370
+ // every test — keeps passing/reading the
371
+ // SAME strings; renaming the wire value itself would ripple into the DSE Lab,
372
+ // which this drive explicitly scopes OUT — "a SEPARATE later loop"). ONE
373
+ // authority for any consumer building a control-panel select: read
374
+ // `BEHAVIOR_META[value].label` for the human-facing option text and
375
+ // `.description` for a tooltip — never hand-type either (tunnel-lab and
376
+ // lightsource-lab's own `<select id="ctrl-wallbehavior">` population loops
377
+ // consume this map).
378
+ DirectedTunnel.BEHAVIOR_META = {
379
+ option1: {
380
+ label: 'laserBeam',
381
+ description: 'Directed host-trig redirect — exits along the tunnel’s own direction, laser-like (collimated).'
382
+ },
383
+ option2: {
384
+ label: 'reflect',
385
+ description: 'Native BOUNCE — pure elastic ping-pong reflection off the walls (Tube’s own default).'
386
+ },
387
+ randomXY: {
388
+ label: 'randomXY',
389
+ description: 'Eject + vx=random() (always forward), vy symmetric (+ vz symmetric on a real-depth tunnel); charge redrawn.'
390
+ },
391
+ symmetric: {
392
+ label: 'symmetric',
393
+ description: 'Omnidirectional/unpolarized input — eject + vx AND vy both symmetric (+ vz symmetric on a real-depth tunnel; no forward bias); charge redrawn.'
394
+ },
395
+ bounce: {
396
+ label: 'bounce',
397
+ description: 'Eject + vy=-vy only (elastic reflection); no charge redraw.'
398
+ },
399
+ ejectY: {
400
+ label: 'ejectY',
401
+ description: 'Pure eject into the cavity, ZERO velocity writes; no charge redraw.'
402
+ },
403
+ ejectYReflect: {
404
+ label: 'ejectYReflect',
405
+ description: 'ejectY’s eject prefix PLUS vy=-vy; no charge redraw.'
406
+ }
407
+ };
408
+
409
+ // ── Export ───────────────────────────────────────────────────────────
410
+
411
+ export { DirectedTunnel };
412
+
@@ -0,0 +1,164 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * EgptFractal — JS-only fractal oscillator object.
5
+ *
6
+ * Creates a 3-level frame hierarchy: observer > oscillator > quantum.
7
+ * The quantum does a random walk inside the oscillator frame while compress()
8
+ * pulls it toward the oscillator center, creating a stable oscillation.
9
+ * The oscillator itself moves inside the observer frame, and the perceived
10
+ * motion from the observer's rest frame produces an emergent wave pattern.
11
+ *
12
+ * IMPORTANT: This object uses devsdk-specific APIs (Frame.makeLeafFrame,
13
+ * dimension.addFrame, dimension.createFrameFromRect) and is NOT portable
14
+ * to the WASM engine. Mark experiments using this as `portable: false`.
15
+ */
16
+
17
+ import { Rectangle } from '../../parts/Rectangle.js';
18
+ import { engine } from '../../compat/engine.js';
19
+ import { registerKind } from '../kinds.js';
20
+
21
+ // Frame and Charge are ENGINE values: read through engine() when a fractal is built,
22
+ // never at module evaluation (importing this file boots nothing).
23
+
24
+ // ── EgptFractal ─────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * @param {Object} universe - The JS engine universe instance (NOT WASM)
28
+ * @param {Object} canvasRect - Rectangle defining the canvas bounds
29
+ */
30
+ function EgptFractal(universe, canvasRect) {
31
+ this.universe = universe;
32
+ this._schemaKind = 'EgptFractal';
33
+ this.canvas_rect = canvasRect;
34
+
35
+ var framedelta = 3;
36
+
37
+ this.quantum_dimension_number = 2;
38
+
39
+ // Observer dimension: highest power-of-2 exponent that fits canvas
40
+ var minSide = Math.min(canvasRect.w, canvasRect.h);
41
+ var observer_dimension_number = Math.floor(Math.log2(minSide));
42
+
43
+ // Init universe: frameGenMode:'groundUp' (the default); brownianMotion will be set in setupFractal.
44
+ // Brownian is left OFF here (the engine default for groundUp) so the observer
45
+ // exerts no brownian pull until setupFractal turns it on.
46
+ // noEscape:true confines the quantum to the oscillator (and the oscillator to the
47
+ // observer) so the fractal sustains a stable oscillation instead of leaking its
48
+ // quantum. Without it the quantum escapes the oscillator frame (~tick 55) and the
49
+ // hierarchy collapses to the bare observer — not an oscillator. (Lost when the shim
50
+ // contracts were cleaned up; restored 2026-06-23 with the §2a derived-mass fix.)
51
+ this.universe.init(this.quantum_dimension_number, { wavelengthScale: 1, noEscape: true });
52
+
53
+ // Create three dimension layers
54
+ this.quantum_dimension = this.universe.addDimension(this.quantum_dimension_number);
55
+ this.oscillator_dimension = this.universe.addDimension(this.quantum_dimension_number + framedelta);
56
+ this.observer_dimension = this.universe.addDimension(observer_dimension_number);
57
+
58
+ this.quanta = [];
59
+ this.oscillator = null;
60
+ this.observer_frame = null;
61
+ }
62
+
63
+ /**
64
+ * Build the 3-level frame hierarchy and configure the universe.
65
+ * Returns an empty array (no large objects).
66
+ */
67
+ EgptFractal.prototype.setupFractal = function () {
68
+ // Observer rect at canvas origin
69
+ var observer_rect = new Rectangle(
70
+ this.canvas_rect.x, this.canvas_rect.y,
71
+ this.observer_dimension.mb_w, this.observer_dimension.mb_h,
72
+ null, true
73
+ );
74
+
75
+ // Oscillator offset from observer center
76
+ var oscOffsetX = observer_rect.x - this.observer_dimension.mb_w / 4;
77
+ var oscOffsetY = observer_rect.y;
78
+ var oscillator_rect = new Rectangle(
79
+ oscOffsetX, oscOffsetY,
80
+ this.oscillator_dimension.mb_w, this.oscillator_dimension.mb_h,
81
+ null, true
82
+ );
83
+
84
+ // Quantum positioned on the lower Y axis of the oscillator
85
+ var pOffsetX = oscillator_rect.x;
86
+ var pOffsetY = oscillator_rect.y + this.oscillator_dimension.mb_h / 4;
87
+ var quantumRect = new Rectangle(
88
+ pOffsetX, pOffsetY,
89
+ this.quantum_dimension.mb_w, this.quantum_dimension.mb_h,
90
+ null, true
91
+ );
92
+
93
+ var eng = engine();
94
+ var quantumFrame = eng.Frame.makeLeafFrame(
95
+ quantumRect.left, quantumRect.top,
96
+ this.quantum_dimension_number, 1, 0, 0, false,
97
+ eng.Charge.POSITIVE, this.canvas_rect
98
+ );
99
+ quantumFrame.name = "quantum";
100
+ this.quanta.push(quantumFrame);
101
+
102
+ // Add quantum to quantum dimension
103
+ for (var i = 0; i < this.quanta.length; i++) {
104
+ this.quantum_dimension.addFrame(this.quanta[i]);
105
+ }
106
+
107
+ // The oscillator + observer render as OUTLINE boxes (the quantum renders filled). This is a
108
+ // pure user-space render choice carried in the frame's generic string_map ('render.outline'),
109
+ // NOT a typed engine flag — so both engines transport it identically (the engine never
110
+ // interprets it). The renderer deserializes string_map['render.outline'].
111
+ var OUTLINE_HINT = { 'render.outline': 'true' };
112
+
113
+ // Create oscillator frame containing the quantum
114
+ this.oscillator = this.oscillator_dimension.createFrameFromRect(
115
+ oscillator_rect, this.quanta, 0, 0, true, false, OUTLINE_HINT
116
+ );
117
+ this.oscillator.name = "oscillator";
118
+
119
+ // Create observer frame containing the oscillator
120
+ this.observer_frame = this.observer_dimension.createFrameFromRect(
121
+ observer_rect, [this.oscillator], 0, 0, true, true, OUTLINE_HINT
122
+ );
123
+ this.observer_frame.name = "observer";
124
+
125
+ // Configure universe
126
+ this.universe.leafQuantaWidth = this.quantum_dimension.mb_w;
127
+ this.universe.brownianMotion = true;
128
+
129
+ return [];
130
+ };
131
+
132
+ // ── The kind (objects/kinds.js): descriptor, construction defaults ──────────
133
+
134
+ /**
135
+ * EgptFractal — JS-only 3-level recursive frame hierarchy oscillator.
136
+ * Constructor: EgptFractal(universe, canvasRect)
137
+ * NOT portable to WASM; experiments must mark portable:false.
138
+ */
139
+ var EgptFractalDescriptor = {
140
+ description: 'A 3-level recursive frame hierarchy that produces emergent wave oscillation. ' +
141
+ 'Creates observer > oscillator > quantum dimension layers. The quantum does a random walk ' +
142
+ 'inside the oscillator frame; compress() pulls it toward the oscillator center, creating ' +
143
+ 'stable oscillation. The oscillator moves inside the observer frame, producing an emergent ' +
144
+ 'wave pattern in the observer\'s rest frame. Demonstrates the Frame Axiom at three scales. ' +
145
+ 'JS-only: uses Frame.makeLeafFrame and dimension APIs not yet implemented in WASM.',
146
+ fields: [
147
+ {
148
+ name: 'quantum_dimension_number',
149
+ type: 'readonly',
150
+ engineVisible: true,
151
+ description: 'Exponent for the quantum dimension scale (read-only; derived from canvasRect at construction).'
152
+ }
153
+ ]
154
+ };
155
+
156
+ registerKind(EgptFractal, {
157
+ from: import.meta.url,
158
+ descriptor: EgptFractalDescriptor
159
+ });
160
+
161
+ // ── Export ───────────────────────────────────────────────────────────
162
+
163
+ export { EgptFractal };
164
+