@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,306 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupTunnelPointExit — the TUNNEL POINT-EXIT LAB (D3).
5
+ *
6
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
7
+ *
8
+ * Composes THREE already-committed pieces and adds no physics of its own (SU1):
9
+ * 1. a `SphereEmitter` on the `'hemisphere'` preset (D1), parked JUST INSIDE the
10
+ * corridor's input mouth and aimed down the bore, so the whole burst is fed into
11
+ * the tunnel instead of half of it flying back out of the entrance;
12
+ * 2. a `Tunnel` in `mode:'directed'` (D2) — each wall hit ejects the quantum back into
13
+ * the bore and re-emits it in a uniformly random direction within 180° of the axis;
14
+ * 3. that Tunnel's `exitCollapse` gate (D3) — every quantum crossing the output mouth is
15
+ * pinned to the bore mid-line, so the mouth becomes a one-pixel POINT SOURCE.
16
+ *
17
+ * The question the lab exists to answer VISUALLY: does a point source fed by tunnel
18
+ * transit produce the same semicircular banded self-interference as a fresh point emitter?
19
+ * Graded against the Single-Source Self-Interference experiment as the named exemplar.
20
+ *
21
+ * @param {Object} universe - an EGPTUniverse (not yet init'd; the emitter inits it).
22
+ * @param {Object} [options]
23
+ * @param {number} [options.tunnelLength=240] - corridor travel-axis extent, px. REBUILD-class
24
+ * (it is wall geometry): the page re-runs `runner.setup()` to change it — the documented
25
+ * restart-in-place idiom, not a live knob. Deliberately NOT faked as live.
26
+ * @param {number} [options.bore=40] - clear aperture between the corridor walls, px.
27
+ * @param {number} [options.wavelengthScale=16] - the universe's GLOBAL `wavelengthScale`.
28
+ * LIVE (`universe.wavelengthScale`, consumed fresh each tick). Leaves are born at
29
+ * dimension 2 (capacity 16) with a `wavelength:32` arg (mass 1), so capacity/mass = 16 and
30
+ * the EFFECTIVE per-leaf transverse wavelength is `16 × scale` px. 16 ⇒ eff λ 256, the
31
+ * exemplar's own golden.
32
+ * @param {boolean} [options.exitPhaseReset=false] - arm the exit gate's phase reset. LIVE.
33
+ * @param {number} [options.burstSize=6] @param {number} [options.emitEveryTicks=1]
34
+ * @param {boolean} [options.active=true]
35
+ *
36
+ * @returns {{ tunnel, emitterControl, largeObjects, pointExitControl, dispose }}
37
+ * `pointExitControl` is the live sugar handle (the canonical knob pattern):
38
+ * `setWavelengthScale/getWavelengthScale/getEffectiveWavelength`,
39
+ * `setExitPhaseReset/getExitPhaseReset`, `setActive`, `getTunnelLength`, `controls`.
40
+ *
41
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
42
+ *
43
+ * Zero new physics. The banding in the exemplar comes from equal-radius quanta sharing a
44
+ * spin phase: births at `local_time=0` are phase-locked, fundamental leaves step P=1 so AGE
45
+ * = RADIUS (bench M1), and the λ coin adds a transverse kick whose bias flips per spin
46
+ * quadrant — every ray snakes with period λ_eff and the ensemble reads as concentric bands.
47
+ * The exit collapse supplies the "one origin" half of that mechanism.
48
+ *
49
+ * It does NOT supply the phase half, and that is the whole point of the lab. Quanta arriving
50
+ * at the mouth have different transit histories, so their phases are spread (measured on this
51
+ * construction: within-same-crossing-tick `local_time` std ≈ 45 ticks). Against a spin period
52
+ * of λ_eff that spread is either negligible or dominant:
53
+ *
54
+ * | wavelengthScale | eff λ | spin period | expected picture, phase reset OFF |
55
+ * |---|---|---|---|
56
+ * | 16 | 256 | 256 ≫ 45 | ~90% phase-aligned for free — BANDED |
57
+ * | ≤ 8 | ≤ 128 | ≤ 128 | phase-flat (8-bin CV ≈ 0.09) — SMOOTH, un-banded |
58
+ *
59
+ * With the reset ON the mouth is phase-locked at any λ, so the fan should band in both
60
+ * regimes. Sweeping λ against the toggle IS the experiment — nothing here is tuned to make
61
+ * the two regimes agree.
62
+ *
63
+ * Runs the fast path (`withInterQuantumCollisions:false`, no brownian). That is a hard
64
+ * precondition of the collapse, enforced FAIL-LOUD inside `Tunnel`: with collisions ON the
65
+ * collapse pixel serializes one frame per tick and kills contended quanta, so the scene would
66
+ * measure contention rather than a point source.
67
+ *
68
+ * The emitter's start pixel carries ZERO jitter, matching the exemplar: a SphereEmitter burst
69
+ * with emission box 0 starts every quantum on the SAME pixel and co-location is inert on this
70
+ * path. A transverse random jitter is exactly what an earlier self-interference seed did
71
+ * before it was replaced for blurring the bands.
72
+ */
73
+
74
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
75
+ import { setupSphereEmitter3D } from './setupSphereEmitter3D.js';
76
+ import { kindNamed } from '../kinds.js';
77
+
78
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
79
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
80
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
81
+
82
+
83
+
84
+ // Leaves are born HERE (capacity = (2^2)² = 16) with a wavelength arg of 32 → mass 1, so
85
+ // capacity/mass = 16 and the global scale has real leverage. Both numbers are the
86
+ // Single-Source Self-Interference exemplar's own — parity of the per-leaf creation
87
+ // conditions is what makes the two pictures comparable at all.
88
+ var FUNDAMENTAL_DIMENSION = 2;
89
+ var LEAF_WAVELENGTH_ARG = 32;
90
+ var CAPACITY_OVER_MASS = 16; // (2^FUNDAMENTAL_DIMENSION)² / mass(=1)
91
+
92
+ /**
93
+ * THE ONE SCENE-SPEC AUTHORITY (D4). Pure function: options → the exact `SphereEmitter`
94
+ * and `Tunnel` construction configs for this scene, plus the derived geometry a readout
95
+ * wants. It draws nothing and touches no universe.
96
+ *
97
+ * It exists because this scene has TWO surfaces — the standalone page (which runs the
98
+ * factory below) and the Light Lab rung (which must be an `addObject` recipe so STUDIO can
99
+ * select and edit the objects). Without a shared authority those two would each hand-carry
100
+ * the same ten numbers and drift apart, which is the anti-pattern this repo keeps paying
101
+ * for. Both consume THIS; neither re-derives it.
102
+ *
103
+ * @param {Object} spec - `setupTunnelPointExit`'s own options (same names) PLUS the
104
+ * REQUIRED world size `canvasW`/`canvasH` — the caller ferries the size its co-init
105
+ * call declared (the rung: the sketch's own `W, H`). Absent THROWS; never a default.
106
+ * @returns {{emitter:Object, tunnel:Object, geom:Object}} the two addObject-ready configs.
107
+ */
108
+ function sceneSpec(spec) {
109
+ // sceneSpec is EXPORTED (the Light Lab rung consumes it so the rung and the
110
+ // standalone page cannot drift), and it reads the SAME bag the factory does plus
111
+ // the world size — so it takes the SAME gate, widened by exactly those two keys.
112
+ spec = _assertSetupOptions('setupTunnelPointExit.sceneSpec', spec, SCENE_SPEC_OPTIONS);
113
+ return _sceneGeometry(spec, _requireWorldRect('setupTunnelPointExit.sceneSpec canvasW/canvasH',
114
+ { w: spec.canvasW, h: spec.canvasH }));
115
+ }
116
+
117
+ // The geometry itself, from an options bag and the world rect. The factory calls this
118
+ // with `universe.universe_rect`, the rung (through sceneSpec) with its declared W, H.
119
+ function _sceneGeometry(o, world) {
120
+ var cw = world.w;
121
+ var ch = world.h;
122
+ var tunnelLength = o.tunnelLength !== undefined ? o.tunnelLength : 240;
123
+ var bore = o.bore !== undefined ? o.bore : 40;
124
+ var wallThickness = o.wallThickness !== undefined ? o.wallThickness : 10;
125
+ var wavelengthScale = o.wavelengthScale !== undefined ? o.wavelengthScale : 16;
126
+ var exitRecenter = (o.exitRecenter !== undefined) ? !!o.exitRecenter : true;
127
+ var exitPhaseReset = !!o.exitPhaseReset;
128
+ var burstSize = o.burstSize !== undefined ? o.burstSize : 6;
129
+ var emitEveryTicks = o.emitEveryTicks !== undefined ? o.emitEveryTicks : 1;
130
+
131
+ // The corridor sits in the LEFT portion so the exit mouth has open canvas to its right
132
+ // for the fan to develop — the fan is the observable, so it gets the room.
133
+ var left = Math.round(cw * 0.08);
134
+ var midY = Math.round(ch / 2);
135
+ var top = midY - Math.round(bore / 2);
136
+ var emitterX = left + Math.max(2, Math.round(wallThickness / 2)); // a few px inside the mouth
137
+
138
+ return {
139
+ emitter: {
140
+ preset: 'hemisphere', // D1 — forward 180°, so the whole burst enters the bore
141
+ is2D: true,
142
+ center: [emitterX, midY],
143
+ emissionBoxSide: 0, // a literal one-pixel source — no jitter
144
+ chargeMode: 'random', // braiding — the self-interference exemplar's setting
145
+ fundamentalDimension: FUNDAMENTAL_DIMENSION,
146
+ wavelength: LEAF_WAVELENGTH_ARG,
147
+ fundamentalWaveLength: 4 * wavelengthScale, // → waveLengthMultiplier = wavelengthScale
148
+ burstSize: burstSize,
149
+ emitEveryTicks: emitEveryTicks
150
+ },
151
+ tunnel: {
152
+ left: left, top: top, length: tunnelLength, bore: bore,
153
+ wallThickness: wallThickness, axis: 'x',
154
+ mode: 'directed', // D2 — 180° forward re-emission on every wall hit
155
+ exitCollapse: true, // D3 — the gate at the output mouth
156
+ exitRecenter: exitRecenter, // D4 — the point-source collapse (default ON)
157
+ exitPhaseReset: exitPhaseReset,
158
+ name: 'point_exit_tunnel', color: [90, 120, 200]
159
+ },
160
+ geom: {
161
+ left: left, top: top, midY: midY, bore: bore,
162
+ exitX: left + tunnelLength, tunnelLength: tunnelLength,
163
+ wavelengthScale: wavelengthScale,
164
+ effectiveWavelength: Math.floor(CAPACITY_OVER_MASS * wavelengthScale)
165
+ }
166
+ };
167
+ }
168
+ /**
169
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
170
+ * Exported as `setupTunnelPointExit.OPTIONS` so a composing caller consumes it instead of
171
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
172
+ * read-set by static trace and fails if this list drifts from the code.
173
+ */
174
+ var OPTIONS = [
175
+ 'active', 'bore', 'burstSize',
176
+ 'emitEveryTicks', 'exitPhaseReset', 'exitRecenter',
177
+ 'tunnelLength', 'wallThickness', 'wavelengthScale',
178
+ ];
179
+ // sceneSpec's vocabulary: the factory's own, plus the world size it cannot read off a
180
+ // universe (it touches none). Composed from OPTIONS, never retyped.
181
+ var SCENE_SPEC_OPTIONS = OPTIONS.concat(['canvasH', 'canvasW']);
182
+
183
+ function setupTunnelPointExit(universe, options) {
184
+ options = _assertSetupOptions('setupTunnelPointExit', options, OPTIONS);
185
+
186
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
187
+ var spec = _sceneGeometry(options, _requireWorldRect(
188
+ 'setupTunnelPointExit universe.universe_rect', universe.universe_rect));
189
+ var tunnelLength = spec.geom.tunnelLength;
190
+ var bore = spec.geom.bore;
191
+ var wavelengthScale = spec.geom.wavelengthScale;
192
+ var midY = spec.geom.midY;
193
+ var exitX = spec.geom.exitX;
194
+ var active = options.active !== undefined ? !!options.active : true;
195
+
196
+ var emitterFactory = setupSphereEmitter3D;
197
+ // The corridor part by its kind: Tunnel's own module imports this file (its
198
+ // `Tunnel.pointExitSpec` facade delegates to sceneSpec), so an import back would be a cycle.
199
+ var TunnelCls = kindNamed('Tunnel');
200
+
201
+ // ── (0) THE WORLD — this factory IS the sketch: init FIRST, then parts ──
202
+ // (fast path, 2D, derived from the emitter spec). Part construction ORDER is
203
+ // irrelevant once the world is up.
204
+ emitterFactory.initWorld(universe, spec.emitter);
205
+
206
+ // ── (1) The feeder: a hemisphere emitter JUST INSIDE the input mouth ──
207
+ var emitterResult = emitterFactory(universe, spec.emitter);
208
+ var emitterControl = emitterResult.sphereEmitterControl;
209
+ emitterControl.setActive(active);
210
+
211
+ // ── (2)+(3) The directed corridor WITH the point-exit gate ───────────
212
+ var tunnel = new TunnelCls(universe, spec.tunnel);
213
+
214
+ // The global λ multiplier is a LIVE universe property (consumed fresh each tick),
215
+ // the same knob the self-interference page drives.
216
+ universe.wavelengthScale = wavelengthScale;
217
+
218
+ var control = {
219
+ // λ — LIVE. Effective per-leaf transverse wavelength = (capacity/mass) × scale.
220
+ setWavelengthScale: function (v) {
221
+ var s = Math.max(1, Math.round(v));
222
+ wavelengthScale = s;
223
+ universe.wavelengthScale = s;
224
+ return s;
225
+ },
226
+ getWavelengthScale: function () { return wavelengthScale; },
227
+ getEffectiveWavelength: function () { return Math.floor(CAPACITY_OVER_MASS * wavelengthScale); },
228
+ // The two exit flags — both LIVE (each recompiles + pushes the gate's op-program).
229
+ setExitPhaseReset: function (on) { return tunnel.setExitPhaseReset(on); },
230
+ getExitPhaseReset: function () { return !!tunnel.exitPhaseReset; },
231
+ setExitRecenter: function (on) { return tunnel.setExitRecenter(on); },
232
+ getExitRecenter: function () { return !!tunnel.exitRecenter; },
233
+ // True when the gate currently carries at least one op. With BOTH flags off the
234
+ // gate is a registered but DISARMED collector (no program at all).
235
+ isExitGateArmed: function () { return tunnel.isExitGateArmed(); },
236
+ setActive: function (v) { emitterControl.setActive(!!v); },
237
+ // REBUILD-class — exposed as a getter so the page can show it and re-run setup()
238
+ // with a new value. There is no live setter BY DESIGN: the corridor length is wall
239
+ // geometry, and pretending otherwise would be a lie in the control surface.
240
+ getTunnelLength: function () { return tunnelLength; },
241
+ getBore: function () { return bore; },
242
+ getExitX: function () { return exitX; },
243
+ getMidY: function () { return midY; }
244
+ };
245
+
246
+ // The standardized descriptor array — declared ONCE and consumed by both the page's
247
+ // own sliders and any host control strip (the established `controls` idiom).
248
+ control.controls = [
249
+ {
250
+ id: 'wavelengthScale', label: 'λ scale (eff λ = 16 × scale)',
251
+ min: 1, max: 24, step: 1, value: wavelengthScale,
252
+ fmt: function (v) { return String(v) + ' (eff λ ' + Math.floor(CAPACITY_OVER_MASS * v) + 'px)'; },
253
+ get: function () { return control.getWavelengthScale(); },
254
+ onChange: function (v) { control.setWavelengthScale(v); }
255
+ },
256
+ {
257
+ id: 'exitRecenter', label: 'recenter at exit', kind: 'checkbox',
258
+ value: spec.tunnel.exitRecenter,
259
+ fmt: function (v) { return v ? 'on' : 'off'; },
260
+ get: function () { return control.getExitRecenter(); },
261
+ onChange: function (v) { control.setExitRecenter(v); }
262
+ },
263
+ {
264
+ id: 'exitPhaseReset', label: 'phase reset at exit', kind: 'checkbox',
265
+ value: spec.tunnel.exitPhaseReset,
266
+ fmt: function (v) { return v ? 'on' : 'off'; },
267
+ get: function () { return control.getExitPhaseReset(); },
268
+ onChange: function (v) { control.setExitPhaseReset(v); }
269
+ },
270
+ {
271
+ // REBUILD-class: the page re-runs setup() on change (restart-in-place).
272
+ id: 'tunnelLength', label: 'tunnel length (rebuild)',
273
+ min: 60, max: 420, step: 20, value: tunnelLength, rebuild: true,
274
+ fmt: function (v) { return String(v) + 'px'; },
275
+ get: function () { return control.getTunnelLength(); },
276
+ onChange: function () { /* the page owns the rebuild — see `rebuild:true` */ }
277
+ }
278
+ ];
279
+
280
+ return {
281
+ tunnel: tunnel,
282
+ emitterControl: emitterControl,
283
+ pointExitControl: control,
284
+ // FrqtlApp.setup() auto-wires this for rendering.
285
+ largeObjects: tunnel.largeObjects,
286
+ // CANONICAL TEARDOWN — the runner calls this before installing the next
287
+ // setupResult, so a rebuild (the length knob) never stacks emitters or walls.
288
+ dispose: function () {
289
+ if (emitterResult && typeof emitterResult.dispose === 'function') { emitterResult.dispose(); }
290
+ if (tunnel && typeof tunnel.dispose === 'function') { tunnel.dispose(); }
291
+ }
292
+ };
293
+ }
294
+
295
+ // ONE authority for the per-leaf creation constants the page/readouts quote, and for the
296
+ // SCENE SPEC both surfaces (this factory + the Light Lab rung recipe) consume.
297
+ setupTunnelPointExit.sceneSpec = sceneSpec;
298
+ setupTunnelPointExit.FUNDAMENTAL_DIMENSION = FUNDAMENTAL_DIMENSION;
299
+ setupTunnelPointExit.LEAF_WAVELENGTH_ARG = LEAF_WAVELENGTH_ARG;
300
+ setupTunnelPointExit.CAPACITY_OVER_MASS = CAPACITY_OVER_MASS;
301
+
302
+ setupTunnelPointExit.OPTIONS = OPTIONS;
303
+ setupTunnelPointExit.SCENE_SPEC_OPTIONS = SCENE_SPEC_OPTIONS;
304
+
305
+ export { setupTunnelPointExit };
306
+
@@ -0,0 +1,177 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupWaveInterference — Engine-agnostic wave interference experiment factory.
5
+ * Two PointSources separated by 2*wavelength, emitting radially.
6
+ *
7
+ * Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
8
+ */
9
+
10
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
11
+ import { PointSource } from '../../parts/PointSource.js';
12
+ import { LargeObject } from '../../parts/LargeObject.js';
13
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
14
+
15
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
16
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
17
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
18
+
19
+ // ── Imports ─────────────────────────────────────────────────────────
20
+
21
+ // ── Setup Factory ───────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Create a wave interference experiment with two radial point sources.
25
+ *
26
+ * Builds two {@link PointSource} emitters separated by `sourceSeparation`, each
27
+ * firing 1px/tick leaf Frames radially, plus two visual-only source circles.
28
+ * The fringe pattern is EMERGENT — no wave equation; it appears where the two
29
+ * particle streams overlap.
30
+ *
31
+ * Two independent "wavelength" knobs — keep them distinct:
32
+ * - `options.fundamentalWaveLength` → each emitter's `opts.wavelength` → particle
33
+ * MASS (`mass = capacity / wavelength`); 32 gives leaf mass ≈ 1 (clean fringes,
34
+ * matches WASM). It also sets the default source geometry below.
35
+ * - `options.wavelengthScale` → the universe-wide scale in
36
+ * `wavelength = floor((capacity/mass) × WAVELENGTH_CONSTANT)`. Raising it WIDENS
37
+ * the fringes (the live slider on the experiment page drives this).
38
+ *
39
+ * @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd.
40
+ * @param {Object} [options] - Configuration overrides.
41
+ * @param {number} [options.fundamentalDimension=2] - Fundamental (leaf) dimension layer.
42
+ * @param {number} [options.fundamentalWaveLength=32] - Per-emitter wavelength → particle
43
+ * mass; also the default for source separation (2×) and diameter (1×).
44
+ * @param {number} [options.wavelengthScale=8] - Universe-wide fringe-spacing scale
45
+ * (see above); higher = wider fringes.
46
+ * @param {number} [options.velocity=50000] - Default quantum-limit source (NOT particle
47
+ * speed; speed is fixed at 1px/tick by the Universal Speed Limit invariant).
48
+ * @param {number} [options.sourceSeparation] - Distance between the two sources (physics
49
+ * pixels; default 2×wavelength).
50
+ * @param {number} [options.sourceDiameter] - Source circle diameter (default = wavelength).
51
+ * @param {number} [options.quantumLimit] - Max quanta emitted per angle (default: velocity);
52
+ * the steady-state density ceiling.
53
+ * @param {number} [options.depth] - total z-extent for a 3D run. OMITTED
54
+ * (default) → today's 2D behavior, byte-identical (no `activeAxes`/z key touched at all).
55
+ * When provided: `universe.init()` gains `activeAxes:{x:true,y:true,z:true}` (mirrors
56
+ * `setupParticleWalk3D`) and BOTH PointSources emit with a uniform z-spread centered on
57
+ * z=0 (`zMin=-depth/2, zDepth=depth`) — every z-slice sees the same radial illumination,
58
+ * so the emergent fringe pattern is genuinely 3D rather than a dark volume around one
59
+ * lit plane (design §2.3/§4.4). This is the SAME emitter option `SlitLightSource`
60
+ * consumes — the z-spread lives in `PointSource`/
61
+ * `emitterProgram.js`, not here, precisely so both experiments share it.
62
+ * @returns {Object} `{ sources:{source1,source2}, source1Circle, source2Circle,
63
+ * fundamentalDimension, desiredWaveLength, largeObjects }`.
64
+ */
65
+ /**
66
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
67
+ * Exported as `setupWaveInterference.OPTIONS` so a composing caller consumes it instead of
68
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
69
+ * read-set by static trace and fails if this list drifts from the code.
70
+ */
71
+ var OPTIONS = [
72
+ 'depth', 'fundamentalDimension', 'fundamentalWaveLength', 'quantumLimit',
73
+ 'sourceDiameter', 'sourceSeparation', 'velocity', 'wavelengthScale',
74
+ ];
75
+
76
+ function setupWaveInterference(universe, options) {
77
+ options = _assertSetupOptions('setupWaveInterference', options, OPTIONS);
78
+
79
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
80
+ var canvas_rect = universe.universe_rect;
81
+ _requireWorldRect('setupWaveInterference universe.universe_rect', canvas_rect);
82
+
83
+ var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 2;
84
+ var desiredWaveLength = options.fundamentalWaveLength !== undefined ? options.fundamentalWaveLength : 32;
85
+ var wavelengthScale = options.wavelengthScale !== undefined ? options.wavelengthScale : 8;
86
+ // Opt-in 3D depth. undefined/null → 2D, byte-identical.
87
+ var depth = (options.depth !== undefined && options.depth !== null) ? options.depth : null;
88
+
89
+ // Init universe: ground-up, brownianMotion:false (the wave-walk). activeAxes is ONLY
90
+ // attached when a
91
+ // 3D depth was requested — omission keeps the exact init() call 2D callers already get.
92
+ var initOpts = { wavelengthScale: wavelengthScale, brownianMotion: false };
93
+ if (depth !== null) {
94
+ initOpts.activeAxes = { x: true, y: true, z: true };
95
+ }
96
+ universe.init(fundamentalDimension, initOpts);
97
+
98
+ // Centered uniform z-spread — the caller (this factory) decides centering;
99
+ // PointSource itself just forwards zMin/zDepth (see PointSource.js opts doc).
100
+ var zMin = depth !== null ? -depth / 2 : null;
101
+ var zDepth = depth !== null ? depth : null;
102
+
103
+ var light_source_center_y = Math.floor(canvas_rect.h / 2);
104
+ var light_source_center_x = Math.floor(canvas_rect.w / 2);
105
+
106
+ var sourceSeparation = options.sourceSeparation !== undefined ? options.sourceSeparation : 2 * desiredWaveLength;
107
+ var sourceOffset = sourceSeparation / 2;
108
+ var lightSourceDiameter = options.sourceDiameter !== undefined
109
+ ? options.sourceDiameter
110
+ : desiredWaveLength;
111
+
112
+ var velocity = options.velocity !== undefined ? options.velocity : 50000;
113
+ var quantumLimit = options.quantumLimit !== undefined ? options.quantumLimit : velocity;
114
+ var charge = null; // random +/- per particle
115
+
116
+ var center_x1 = light_source_center_x - sourceOffset;
117
+ var center_x2 = light_source_center_x + sourceOffset;
118
+ var sourceRadius = lightSourceDiameter / 2;
119
+
120
+ // Create two radial point sources
121
+ var pointSource1 = new PointSource(
122
+ universe,
123
+ center_x1, light_source_center_y,
124
+ lightSourceDiameter,
125
+ sourceRadius, // speed = radius (matches WASM adapter velocity pattern)
126
+ charge,
127
+ true, // auto_fire
128
+ quantumLimit, // quantum_limit per angle
129
+ 0, // delay
130
+ 5, // angle_increment = 5 degrees
131
+ { wavelength: desiredWaveLength, zMin: zMin, zDepth: zDepth } // JS leaf mass = capacity/wavelength → 1 (matches WASM; clean fringes)
132
+ );
133
+
134
+ var pointSource2 = new PointSource(
135
+ universe,
136
+ center_x2, light_source_center_y,
137
+ lightSourceDiameter,
138
+ sourceRadius,
139
+ charge,
140
+ true,
141
+ quantumLimit,
142
+ 0,
143
+ 5,
144
+ { wavelength: desiredWaveLength, zMin: zMin, zDepth: zDepth } // JS leaf mass = capacity/wavelength → 1 (matches WASM; clean fringes)
145
+ );
146
+
147
+ // Visual-only circles for the source bodies (black, no collision)
148
+ var source1Circle = new LargeObject(
149
+ universe,
150
+ [center_x1 - sourceRadius, light_source_center_y - sourceRadius, lightSourceDiameter, lightSourceDiameter],
151
+ CollisionTypes.NONE, [], 'source1', 'closest', null, 1, [0, 0, 0]
152
+ );
153
+ source1Circle.drawAsCircle = true;
154
+
155
+ var source2Circle = new LargeObject(
156
+ universe,
157
+ [center_x2 - sourceRadius, light_source_center_y - sourceRadius, lightSourceDiameter, lightSourceDiameter],
158
+ CollisionTypes.NONE, [], 'source2', 'closest', null, 1, [0, 0, 0]
159
+ );
160
+ source2Circle.drawAsCircle = true;
161
+
162
+ return {
163
+ sources: { source1: pointSource1, source2: pointSource2 },
164
+ source1Circle: source1Circle,
165
+ source2Circle: source2Circle,
166
+ fundamentalDimension: fundamentalDimension,
167
+ desiredWaveLength: desiredWaveLength,
168
+ largeObjects: [source1Circle, source2Circle]
169
+ };
170
+ }
171
+
172
+ // ── Export ───────────────────────────────────────────────────────────
173
+
174
+ setupWaveInterference.OPTIONS = OPTIONS;
175
+
176
+ export { setupWaveInterference };
177
+
@@ -0,0 +1,29 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * v1-replacements.js — the V1 → V2 port map: every removed V1 kind names its V2 composition, so an
5
+ * unknown-kind throw TELLS the author what to build instead of leaving them to guess. A LEAF (no
6
+ * imports): ObjectFactory reads it for its unknown-kind refusals, SlitLightSource for its own, and
7
+ * the sketch rule (lib/frqtl/scenes/test/_sketch-rule.mjs) reads the names from here.
8
+ */
9
+
10
+ export const V1_REPLACEMENTS = Object.freeze({
11
+ DetectorWall: "Wall{ collisionBehavior:'destroy', hooks:['count'] } (preset 'detector-screen')",
12
+ SlitScreen: "Wall{ collisionBehavior:'destroy' } slabs + Tunnel{ mode:'directed', exitCollapse }",
13
+ DSEWall: "Wall{ collisionBehavior:'destroy' } slabs + one Tunnel{ mode:'directed' } per passage",
14
+ SlitLightSource: "SphereEmitter{ preset:'laser' }",
15
+ QuantumEmitter: "SphereEmitter",
16
+ PointSource: "SphereEmitter{ preset:'drum' | 'spherical' }",
17
+ DirectedTunnel: "Tunnel{ mode:'directed' }",
18
+ LightSource: "SphereEmitter{ preset:'hemisphere' | 'laser' } + Tunnel",
19
+ Tube: "Tunnel (with Wall end caps)",
20
+ ChamberEmitter: "SphereEmitter + Tunnel (with Wall end caps)",
21
+ Gun: "Tunnel + SphereEmitter, composed at the sketch level",
22
+ Bullet: "Wall{ collisionBehavior:'bounce' } moved by a sketch tick-fn through setRect",
23
+ Box: "four Wall{ collisionBehavior:'block' } slabs",
24
+ OvenBox: "Wall{ collisionBehavior:'absorb' } + SphereEmitter{ emissionCap, gateCounter }",
25
+ CircuitGate: "LogicGate (the stock V2 gate: Wall{'destroy',['count']} input ammeters + Tunnel{'mirror'} channel + SphereEmitter rail + Wall{'destroy'} shunt valve)",
26
+ CircuitProbe: "Wall preset 'transparent-probe'",
27
+ LeafField: "(archived — no V2 successor)",
28
+ EgptFractal: "(archived — no V2 successor)"
29
+ });
@@ -0,0 +1,23 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * package-core.js — the members BOTH package entries carry beside the parts barrel and their own host
5
+ * door (frqtl/boot-entry.js for @descix/frqtl-web, frqtl/node-entry.js for @descix/frqtl-node): the
6
+ * engine accessors, the capture contracts and the renderer contract's validator. One module, so the two
7
+ * packages' `.` cannot disagree on them.
8
+ *
9
+ * engine, engineState the booted EngineHandle / the boot state (compat/engine.js)
10
+ * FrqtlCapture the render-pipeline tap (objects/instruments/capture.js)
11
+ * RenderStages, FieldBuilder the two contracts the instrument reads
12
+ * CaptureEnvelope the transport contract a recording is validated against
13
+ * validateRendererProvider, RENDERER_PROVIDER_REQUIRED, RENDERER_PROVIDER_OPTIONAL
14
+ * a BYO renderer's design aid (reports, never throws)
15
+ */
16
+
17
+ export { engine, engineState } from './compat/engine.js';
18
+ export { FrqtlCapture } from './objects/instruments/capture.js';
19
+ export * as RenderStages from '../shim/render/render-stages.js';
20
+ export { FieldBuilder } from './compat/renderer/FieldBuilder.js';
21
+ export * as CaptureEnvelope from '../shim/render/capture-envelope.js';
22
+ export { validateRendererProvider, RENDERER_PROVIDER_REQUIRED, RENDERER_PROVIDER_OPTIONAL }
23
+ from './compat/renderer/renderer-provider-contract.js';