@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,351 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * LightSource — thin wrapper over DirectedTunnel (T2, 2026-07-13,
5
+ * "the laser story — nearly free"; refactored I2, 2026-07-13, onto the
6
+ * I1-PROVEN tunnel semantics — the DSE-scale sugar-scale bore replica).
7
+ *
8
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
9
+ *
10
+ * A LightSource is a `DirectedTunnel` (never re-derived — REPLICA DISCIPLINE:
11
+ * zero LightSource-special tunnel behavior, the stock object unchanged) PLUS one
12
+ * of two source mechanisms populating its bore — mirroring the classic
13
+ * side-by-side `this.bulletHandle` (hand-placed) / `this.emitter` (auto-wired)
14
+ * duality:
15
+ *
16
+ * - `options.source = 'particleWalk'` (DEFAULT, I2 — the PRIMARY
17
+ * REFERENCE source) — one or more HAND-PLACED leaf frames in the bore (the
18
+ * hand-placed-projectile pattern, generalized to
19
+ * `options.particleCount` quanta — I1's own scale-sweep validated technique
20
+ * for building a stochastic ensemble from IDENTICAL start conditions:
21
+ * the scale sweep places its whole
22
+ * N-quantum population at ONE shared entrance pixel per seed and still
23
+ * measures genuine per-quantum variance at exit — each frame's OWN
24
+ * move-gate/wavelength-oscillator RNG draws diverge it from its siblings
25
+ * over time, Injection-time same-cell overlap does not merge them). Velocity
26
+ * derived from `orientation`+`speed` exactly like the emitter case,
27
+ * positioned at the bore's centreline just inside the breech (or the bore's
28
+ * geometric centre when `capNegX` is false — no breech to anchor to). No
29
+ * `QuantumEmitter`, no continuous firing — a SNAPSHOT placement (all
30
+ * `particleCount` quanta exist from tick 1), matching `particle-walk`-family
31
+ * experiments' own "hand-placed, not emitted" convention.
32
+ * - `options.source = 'emitter'` — the tunnel's own `options.emitter` (Tube's
33
+ * opt-in auto-wired `QuantumEmitter` at the breech, DirectedTunnel's
34
+ * `orientation`-derived `vx/vy`). Requires `capNegX` (defaults to `true` for
35
+ * THIS source type — a LightSource with no wall to emit from is meaningless).
36
+ *
37
+ * **I2 default flip (directed-tunnel composition model §3 row 2):
38
+ * `options.source` now DEFAULTS to `'particleWalk'`** (was `'emitter'` in T2) —
39
+ * "the PRIMARY reference source." The one production caller
40
+ * (the DSE-lab tunnels) always passes `source:'emitter'` explicitly, so this
41
+ * default flip is byte-identical for every existing caller in the tree; it only
42
+ * changes behavior for a hypothetical caller that OMITTED `source` entirely (none
43
+ * exist yet). The scale sweep's companion
44
+ * source-invariance harness (I2) quantifies particleWalk vs emitter exit-ensemble
45
+ * equivalence — the empirical justification for treating them as interchangeable
46
+ * defaults at all.
47
+ *
48
+ * **DSE-scale sugar defaults (I2, directed-tunnel composition model §2.3: "the
49
+ * Gun Chamber object set, scaled by sugar params — DSE example ~8px bore ×
50
+ * ~16px length"):** `options.tunnel.width`/`options.tunnel.length` now default to
51
+ * `8`/`16` when the caller omits them (previously REQUIRED — `DirectedTunnel`
52
+ * itself still throws on non-positive values, so a caller could not omit them
53
+ * before). `left`/`top`/`z` remain caller-required (position is always
54
+ * scene-specific) — DIRECTLY passed through, byte-identical.
55
+ *
56
+ * `options.polarization` ('random'|'positive'|'negative', default 'random') —
57
+ * lasers are light sources which are polarized and have smaller widths.
58
+ * Maps DIRECTLY onto the SAME charge vocabulary `PointSource`/
59
+ * `QuantumEmitter`'s own `chargeMode` already use — no new charge concept:
60
+ * 'random' -> null (charge draw per particle — the DEFAULT, matches every
61
+ * existing DSE/WI emitter's un-polarized behavior)
62
+ * 'positive' -> 0 ('POSITIVE'), 'negative' -> 1 ('NEGATIVE') (a FIXED charge —
63
+ * every emitted/placed quantum gets the SAME charge: a polarized beam).
64
+ *
65
+ * **`LightSource.laser(universe, options)`** — the Laser preset/factory (a Laser
66
+ * is a LightSource preset, not a separate class — report §4.4 point
67
+ * 3: "A thin Laser wrapper is legitimately separate... mirroring
68
+ * the fx-* factories layering thin convenience over
69
+ * Tube.js"). A STATIC FACTORY, not a subclass: pre-fills `polarization` (default
70
+ * `'positive'` — a FIXED, not random, charge: the report's own "collimated,
71
+ * phase-locked beam... every emitted quantum gets identical vx,vy" framing
72
+ * extends naturally to "and identical charge") and a NARROWER default `width`
73
+ * (report §4.3: "A tunnel + a directed emitter is a collimated, phase-locked
74
+ * beam with zero new physics" — narrowness is a geometry convenience, not a new
75
+ * mechanism), then constructs a plain `LightSource`. Every field is still
76
+ * caller-overridable (`LightSource.laser(universe, {width: 40, polarization:
77
+ * 'random', ...})` is a legal, narrower-but-unpolarized "laser").
78
+ *
79
+ * @param {Object} universe - Already-init'd universe (JS or WASM).
80
+ * @param {Object} options
81
+ * @param {'particleWalk'|'emitter'} [options.source='particleWalk'] - see above
82
+ * (I2: default flipped from 'emitter').
83
+ * @param {'random'|'positive'|'negative'} [options.polarization='random'] - see above.
84
+ * @param {number} [options.speed=1] - initial speed magnitude (both source types);
85
+ * direction is ALWAYS `options.tunnel.orientation` (see `DirectedTunnel`).
86
+ * @param {number} [options.particleCount=1] - `options.source==='particleWalk'`
87
+ * only: how many hand-placed quanta to snapshot-place (I2 generalization from
88
+ * T2's fixed "exactly one"; `1` is byte-identical to T2's behavior).
89
+ * @param {Object} [options.particle] - `options.source==='particleWalk'` only:
90
+ * `{wavelength, mass, level}` (all optional — `makeLeafFrame` defaults apply).
91
+ * @param {Object} [options.tunnel={}] - passed straight through to
92
+ * `DirectedTunnel`'s own constructor (`left,top,z,length,width,depth,
93
+ * orientation,behavior,resetLocalTime,redirectSpread,redirectSpeed,capNegX,
94
+ * capPosX,wallThickness,color,wallOpacity,sinkOpenEnds,collectCollided`) —
95
+ * `options.emitter`/`options.tunnel.emitter` is OWNED by this file (derived
96
+ * from `source`+`polarization`+`speed`), never set directly by the caller.
97
+ * `length`/`width` DEFAULT to 16/8 (I2 DSE-scale sugar default) when omitted.
98
+ *
99
+ * @property {Object} tunnel - the underlying `DirectedTunnel` instance (a STOCK
100
+ * instance — replica discipline; LightSource never subclasses or monkeypatches it).
101
+ * @property {Array<LargeObject>} walls - alias for `this.tunnel.walls`.
102
+ * @property {Object|null} emitter - alias for `this.tunnel.emitter` (`source:
103
+ * 'emitter'` only; `null` for `'particleWalk'`).
104
+ * @property {Array<number>} particleHandles - the hand-placed leaves' FrameIds
105
+ * (`source:'particleWalk'` only; `[]` for `'emitter'`). Length ===
106
+ * `options.particleCount`.
107
+ * @property {Array<number>} particleQids - `particleHandles`' quantum_ids
108
+ * (writable via `setFrameProperty`; `[]` for `'emitter'`).
109
+ * @property {number|null} particleHandle - DEPRECATED alias for
110
+ * `particleHandles[0]` (T2 single-quantum API, kept for byte-identical reads
111
+ * when `particleCount` is omitted/1); `null` for `'emitter'` or an empty count.
112
+ * @property {number|null} particleQid - DEPRECATED alias for `particleQids[0]`
113
+ * (see above).
114
+ * @property {Function} applyRedirect - alias for `this.tunnel.applyRedirect`,
115
+ * ALWAYS present (C1 fix)
116
+ * — prefer `this.tunnel.applyRedirectIfActive(tickData)` over calling this
117
+ * directly (self-gates on the tunnel's current `behavior`).
118
+ *
119
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
120
+ *
121
+ * LightSource adds ZERO new physics (canonical-bench SU1): it is a `DirectedTunnel`
122
+ * (itself a `Tube` — zero new physics, per Tube's own docstring) plus either
123
+ * `QuantumEmitter` (existing engine-agnostic sugar) or one-or-more hand-placed
124
+ * leaves (the SAME `makeLeafFrame`/`injectFrame`/`setFrameProperty` primitives
125
+ * every particle-walk/Gun-bullet experiment already uses, looped). The laser's
126
+ * "collimated, phase-locked beam" IS free (report §4.3): skipping the
127
+ * random-direction draw means every quantum gets IDENTICAL `vx,vy`, and a fresh
128
+ * emission is already phase-locked at `local_time=0` (established) — no
129
+ * new mechanism. Source-invariance (I2): particleWalk and emitter sourcing at IDENTICAL
130
+ * tunnel params produce statistically comparable exit-face ensembles.
131
+ */
132
+
133
+ import { DirectedTunnel } from './DirectedTunnel.js';
134
+ import * as SustainedSource from './sustainedSource.js';
135
+
136
+ // ── Imports ─────────────────────────────────────────────────────────
137
+
138
+ var VALID_SOURCES = ['emitter', 'particleWalk'];
139
+ var VALID_POLARIZATIONS = ['random', 'positive', 'negative'];
140
+
141
+ function _polarizationToCharge(polarization) {
142
+ if (polarization === 'positive') { return 0; }
143
+ if (polarization === 'negative') { return 1; }
144
+ return null; // 'random'
145
+ }
146
+
147
+ /**
148
+ * @param {Object} universe
149
+ * @param {Object} options - see module docstring above.
150
+ */
151
+ function LightSource(universe, options) {
152
+ options = options || {};
153
+
154
+ this.universe = universe;
155
+ this._schemaKind = 'LightSource';
156
+
157
+ // I2 default flip (directed-tunnel composition model §3 row 2):
158
+ // 'particleWalk' is now the DEFAULT (the PRIMARY reference
159
+ // source) — was 'emitter' in T2. The one production caller
160
+ // (the DSE-lab tunnels) always passes source:'emitter' explicitly, so
161
+ // this is byte-identical for every existing caller in the tree.
162
+ var source = options.source !== undefined ? options.source : 'particleWalk';
163
+ if (VALID_SOURCES.indexOf(source) === -1) {
164
+ throw new Error('[LightSource] unknown options.source "' + source + '". Expected: ' + VALID_SOURCES.join(' | '));
165
+ }
166
+ var polarization = options.polarization !== undefined ? options.polarization : 'random';
167
+ if (VALID_POLARIZATIONS.indexOf(polarization) === -1) {
168
+ throw new Error('[LightSource] unknown options.polarization "' + polarization + '". Expected: ' + VALID_POLARIZATIONS.join(' | '));
169
+ }
170
+ var speed = options.speed !== undefined ? options.speed : 1;
171
+ var particleCount = options.particleCount !== undefined ? options.particleCount : 1;
172
+ if (!(particleCount >= 1) || Math.floor(particleCount) !== particleCount) {
173
+ throw new Error('[LightSource] options.particleCount must be a positive integer (got ' + particleCount + ').');
174
+ }
175
+ this.source = source;
176
+ this.polarization = polarization;
177
+
178
+ var tunnelOpts = {};
179
+ var passthroughKeys = [
180
+ 'left', 'top', 'z', 'length', 'width', 'depth', 'orientation', 'behavior',
181
+ 'resetLocalTime', 'redirectSpread', 'redirectSpeed', 'capNegX', 'capPosX',
182
+ 'wallThickness', 'color', 'wallOpacity', 'sinkOpenEnds', 'collectCollided',
183
+ ];
184
+ var srcTunnelOpts = options.tunnel || {};
185
+ for (var i = 0; i < passthroughKeys.length; i++) {
186
+ var key = passthroughKeys[i];
187
+ if (srcTunnelOpts[key] !== undefined) { tunnelOpts[key] = srcTunnelOpts[key]; }
188
+ }
189
+ // I2 DSE-scale sugar defaults (directed-tunnel composition model §2.3):
190
+ // "the Gun Chamber object set, scaled by sugar params — DSE example ~8px
191
+ // bore x ~16px length". T2 required length/width (DirectedTunnel throws
192
+ // on omission); I2 defaults them here so a bare `new LightSource(universe,
193
+ // {left,top})` is legal at the composition's actual sugar scale.
194
+ if (tunnelOpts.length === undefined) { tunnelOpts.length = 16; }
195
+ if (tunnelOpts.width === undefined) { tunnelOpts.width = 8; }
196
+
197
+ var orientationDeg = tunnelOpts.orientation !== undefined ? tunnelOpts.orientation : 0;
198
+ var orientationRad = orientationDeg * Math.PI / 180;
199
+ var charge = _polarizationToCharge(polarization);
200
+
201
+ this.emitter = null;
202
+ this.particleHandles = [];
203
+ this.particleQids = [];
204
+ this.particleHandle = null; // deprecated T2 single-quantum alias (see docstring)
205
+ this.particleQid = null;
206
+
207
+ if (source === 'emitter') {
208
+ // A LightSource emitter needs a breech to emit from — default capNegX:true
209
+ // for THIS source type (byte-identical is not possible here since the
210
+ // caller is EXPLICITLY asking for an emitter; a LightSource with no wall to
211
+ // emit from is a caller error class, matched by DirectedTunnel/Tube's own
212
+ // FAIL-LOUD for options.emitter without capNegX).
213
+ if (tunnelOpts.capNegX === undefined) { tunnelOpts.capNegX = true; }
214
+ tunnelOpts.emitter = Object.assign({ speed: speed, charge: charge }, options.emitterSpec || {});
215
+ }
216
+
217
+ this.tunnel = new DirectedTunnel(universe, tunnelOpts);
218
+ this.walls = this.tunnel.walls;
219
+ this.sinks = this.tunnel.sinks;
220
+ this.innerRect = this.tunnel.innerRect;
221
+ this.largeObjects = this.tunnel.largeObjects;
222
+ this.applyRedirect = this.tunnel.applyRedirect;
223
+
224
+ this.sustainedSource = null;
225
+
226
+ if (source === 'emitter') {
227
+ this.emitter = this.tunnel.emitter;
228
+ } else {
229
+ // 'particleWalk' — one or more hand-placed leaf frames (the classic
230
+ // pattern, generalized I2), positioned at the bore's Y (and Z, when
231
+ // zContain) centreline, just inside the breech when capped (mirrors
232
+ // the emitter's own aperture placement), or at the bore's geometric
233
+ // X-centre when open on both ends (no breech to anchor to). ALL
234
+ // `particleCount` quanta share the SAME entrance pixel — the I1
235
+ // scale-sweep's own validated ensemble-building technique: a shared
236
+ // injection-time position does not merge same-tick frames, and each
237
+ // leaf's OWN move-gate/wavelength-oscillator RNG draws diverge it
238
+ // from its siblings over subsequent ticks — a genuine stochastic
239
+ // ensemble from an identical start, not degenerate.
240
+ //
241
+ // One source surface: this burst
242
+ // now routes through `sustainedSource.js`'s ONE `placeSourceQuanta`
243
+ // primitive — the SAME entrance-formula/velocity-draw/z-centring code
244
+ // `setupTunnelLab.js`'s and `setupLightSourceLab.js`'s own independently
245
+ // hand-rolled injectors used to duplicate. `velocityProfile:'beam'` +
246
+ // `entranceBias:'center'` reproduce this constructor's PRE-EXISTING
247
+ // formula EXACTLY (cos/sin(orientationRad)*speed, inner.top+inner.h/2) —
248
+ // byte-identical for every existing caller (DSE Lab, this file's own
249
+ // tests).
250
+ var particleSpec = options.particle || {};
251
+ var hasRealDepth = (tunnelOpts.depth !== undefined && tunnelOpts.depth !== null);
252
+ var burstSpec = SustainedSource.normalizeSourceSpec({
253
+ wavelength: particleSpec.wavelength !== undefined ? particleSpec.wavelength : null,
254
+ charge: charge,
255
+ velocityProfile: 'beam',
256
+ speed: speed,
257
+ orientationRad: orientationRad,
258
+ entranceBias: 'center',
259
+ level: particleSpec.level !== undefined ? particleSpec.level : null,
260
+ mass: particleSpec.mass,
261
+ });
262
+ var placed = SustainedSource.placeSourceQuanta(universe, this.tunnel.innerRect, !!tunnelOpts.capNegX, hasRealDepth, burstSpec, particleCount);
263
+ for (var pi = 0; pi < placed.length; pi++) {
264
+ this.particleHandles.push(placed[pi].handle);
265
+ this.particleQids.push(placed[pi].qid);
266
+ }
267
+ // Deprecated T2 single-quantum aliases — byte-identical reads for any
268
+ // caller written against the old ONE-quantum API when particleCount is
269
+ // omitted/1.
270
+ this.particleHandle = this.particleHandles.length ? this.particleHandles[0] : null;
271
+ this.particleQid = this.particleQids.length ? this.particleQids[0] : null;
272
+
273
+ // LightSource itself has the sustain capability through the SAME
274
+ // single authority: OPT-IN —
275
+ // `options.sustain` omitted (default) is byte-identical to every
276
+ // pre-existing caller (a genuine one-shot construction, DSE Lab's own
277
+ // usage untouched). When supplied (truthy — `true` for "use the SAME
278
+ // wavelength/charge/velocity convention as the burst above, just
279
+ // recurring", or a partial spec object to override individual
280
+ // fields), builds ONE recurring `sustainedSource` tick-fn replicating
281
+ // this burst's own entrance/velocity/charge convention forever —
282
+ // the SAME mechanism `setupTunnelLab.js`/`setupLightSourceLab.js` now
283
+ // both delegate to, rather than each hand-rolling their own copy
284
+ // (see `sustainedSource.js`'s module docstring).
285
+ if (options.sustain) {
286
+ var sustainOverrides = (options.sustain === true) ? {} : options.sustain;
287
+ var sustainSpec = Object.assign({}, burstSpec, sustainOverrides);
288
+ this.sustainedSource = SustainedSource.buildSustainedSource(
289
+ universe, this.tunnel.innerRect, !!tunnelOpts.capNegX, hasRealDepth, sustainSpec
290
+ );
291
+ }
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Tear down every disposable resource THIS LightSource registered — the
297
+ * SAME idiom `QuantumEmitter.prototype.dispose` and every setup factory's
298
+ * own `dispose()` already use (tag-then-splice by identity, idempotent).
299
+ * A `source:'particleWalk'` LightSource with no `options.sustain` (the
300
+ * default — every pre-existing caller) has nothing to dispose; calling this
301
+ * is always a safe no-op in that case. Callers building a page-level
302
+ * `setupResult.dispose()` should delegate here rather than re-deriving the
303
+ * teardown for `this.sustainedSource`/`this.emitter` a second time.
304
+ */
305
+ LightSource.prototype.dispose = function () {
306
+ if (this.sustainedSource) { this.sustainedSource.dispose(); }
307
+ if (this.emitter && typeof this.emitter.dispose === 'function') { this.emitter.dispose(); }
308
+ };
309
+
310
+ /**
311
+ * The Laser preset/factory (a Laser is a LightSource preset, not a
312
+ * separate class). Pre-fills a FIXED polarization (default 'positive' — a
313
+ * polarized beam, matching "lasers are ... polarized"), a NARROWER default
314
+ * width (report §4.3 — collimation/geometry convenience, zero new physics),
315
+ * and `source:'emitter'` (I2: a laser is "a collimated, phase-locked BEAM" —
316
+ * an ONGOING stream, not LightSource's own new I2 default of a single
317
+ * snapshot-placed particleWalk quantum; `laser()` keeps T2's original
318
+ * emitter-sourced behavior explicitly rather than silently inheriting
319
+ * LightSource's now-different bare default); every field is still
320
+ * caller-overridable (a `source:'particleWalk'` laser is legal, if unusual).
321
+ *
322
+ * **I2 re-baseline (width default 20 -> 4):** T2's "narrower" framing was
323
+ * relative to the THEN-current world where `width` had no LightSource
324
+ * default at all (callers always supplied one, historically ~40px) — 20px
325
+ * was narrower than that. I2 gave a bare LightSource its OWN DSE-scale
326
+ * default (`width:8`), which made the OLD laser default (20px) WIDER than
327
+ * the new baseline — breaking the "narrower" invariant this preset exists to
328
+ * express. Re-baselined to 4px (narrower than the new 8px DSE-scale
329
+ * default) so "a laser is narrower" stays TRUE, not just documented.
330
+ *
331
+ * @param {Object} universe
332
+ * @param {Object} [options] - SAME shape as `LightSource`'s own options; any
333
+ * field provided here overrides the laser defaults below.
334
+ * @returns {LightSource}
335
+ */
336
+ LightSource.laser = function (universe, options) {
337
+ options = options || {};
338
+ var tunnelDefaults = { width: 4 };
339
+ var tunnelOpts = Object.assign({}, tunnelDefaults, options.tunnel || {});
340
+ var laserOpts = Object.assign(
341
+ { polarization: 'positive', source: 'emitter' },
342
+ options,
343
+ { tunnel: tunnelOpts }
344
+ );
345
+ return new LightSource(universe, laserOpts);
346
+ };
347
+
348
+ // ── Export ───────────────────────────────────────────────────────────
349
+
350
+ export { LightSource };
351
+