@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,354 @@
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 CEO's 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
+ * `docs/internal/physics-lab/directed-tunnel-scale-sweep/sweep.cjs` 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 (CEO ruling, 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). `docs/internal/physics-lab/directed-tunnel-scale-sweep/README.md`'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 this bite). `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 just light sources which are polarized and have smaller widths"
58
+ * (CEO). 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 (CEO:
66
+ * "a Laser 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, 2026-07-13, ruling_directed_tunnel_lightsource.md)
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` (theorist-established) — no
129
+ * new mechanism. Source-invariance (I2, `docs/internal/physics-lab/directed-tunnel-scale-
130
+ * sweep/source-invariance.cjs`): particleWalk and emitter sourcing at IDENTICAL
131
+ * tunnel params produce statistically comparable exit-face ensembles (see that
132
+ * script's README section for the measured numbers and the metric used).
133
+ */
134
+
135
+ import { DirectedTunnel } from './DirectedTunnel.js';
136
+ import * as SustainedSource from './sustainedSource.js';
137
+
138
+ // ── Imports ─────────────────────────────────────────────────────────
139
+
140
+ var VALID_SOURCES = ['emitter', 'particleWalk'];
141
+ var VALID_POLARIZATIONS = ['random', 'positive', 'negative'];
142
+
143
+ function _polarizationToCharge(polarization) {
144
+ if (polarization === 'positive') { return 0; }
145
+ if (polarization === 'negative') { return 1; }
146
+ return null; // 'random'
147
+ }
148
+
149
+ /**
150
+ * @param {Object} universe
151
+ * @param {Object} options - see module docstring above.
152
+ */
153
+ function LightSource(universe, options) {
154
+ options = options || {};
155
+
156
+ this.universe = universe;
157
+ this._schemaKind = 'LightSource';
158
+
159
+ // I2 default flip (directed-tunnel composition model §3 row 2, CEO
160
+ // ruling): 'particleWalk' is now the DEFAULT ("the PRIMARY reference
161
+ // source") — was 'emitter' in T2. The one production caller
162
+ // (the DSE-lab tunnels) always passes source:'emitter' explicitly, so
163
+ // this is byte-identical for every existing caller in the tree.
164
+ var source = options.source !== undefined ? options.source : 'particleWalk';
165
+ if (VALID_SOURCES.indexOf(source) === -1) {
166
+ throw new Error('[LightSource] unknown options.source "' + source + '". Expected: ' + VALID_SOURCES.join(' | '));
167
+ }
168
+ var polarization = options.polarization !== undefined ? options.polarization : 'random';
169
+ if (VALID_POLARIZATIONS.indexOf(polarization) === -1) {
170
+ throw new Error('[LightSource] unknown options.polarization "' + polarization + '". Expected: ' + VALID_POLARIZATIONS.join(' | '));
171
+ }
172
+ var speed = options.speed !== undefined ? options.speed : 1;
173
+ var particleCount = options.particleCount !== undefined ? options.particleCount : 1;
174
+ if (!(particleCount >= 1) || Math.floor(particleCount) !== particleCount) {
175
+ throw new Error('[LightSource] options.particleCount must be a positive integer (got ' + particleCount + ').');
176
+ }
177
+ this.source = source;
178
+ this.polarization = polarization;
179
+
180
+ var tunnelOpts = {};
181
+ var passthroughKeys = [
182
+ 'left', 'top', 'z', 'length', 'width', 'depth', 'orientation', 'behavior',
183
+ 'resetLocalTime', 'redirectSpread', 'redirectSpeed', 'capNegX', 'capPosX',
184
+ 'wallThickness', 'color', 'wallOpacity', 'sinkOpenEnds', 'collectCollided',
185
+ ];
186
+ var srcTunnelOpts = options.tunnel || {};
187
+ for (var i = 0; i < passthroughKeys.length; i++) {
188
+ var key = passthroughKeys[i];
189
+ if (srcTunnelOpts[key] !== undefined) { tunnelOpts[key] = srcTunnelOpts[key]; }
190
+ }
191
+ // I2 DSE-scale sugar defaults (directed-tunnel composition model §2.3):
192
+ // "the Gun Chamber object set, scaled by sugar params — DSE example ~8px
193
+ // bore x ~16px length". T2 required length/width (DirectedTunnel throws
194
+ // on omission); I2 defaults them here so a bare `new LightSource(universe,
195
+ // {left,top})` is legal at the composition's actual sugar scale.
196
+ if (tunnelOpts.length === undefined) { tunnelOpts.length = 16; }
197
+ if (tunnelOpts.width === undefined) { tunnelOpts.width = 8; }
198
+
199
+ var orientationDeg = tunnelOpts.orientation !== undefined ? tunnelOpts.orientation : 0;
200
+ var orientationRad = orientationDeg * Math.PI / 180;
201
+ var charge = _polarizationToCharge(polarization);
202
+
203
+ this.emitter = null;
204
+ this.particleHandles = [];
205
+ this.particleQids = [];
206
+ this.particleHandle = null; // deprecated T2 single-quantum alias (see docstring)
207
+ this.particleQid = null;
208
+
209
+ if (source === 'emitter') {
210
+ // A LightSource emitter needs a breech to emit from — default capNegX:true
211
+ // for THIS source type (byte-identical is not possible here since the
212
+ // caller is EXPLICITLY asking for an emitter; a LightSource with no wall to
213
+ // emit from is a caller error class, matched by DirectedTunnel/Tube's own
214
+ // FAIL-LOUD for options.emitter without capNegX).
215
+ if (tunnelOpts.capNegX === undefined) { tunnelOpts.capNegX = true; }
216
+ tunnelOpts.emitter = Object.assign({ speed: speed, charge: charge }, options.emitterSpec || {});
217
+ }
218
+
219
+ this.tunnel = new DirectedTunnel(universe, tunnelOpts);
220
+ this.walls = this.tunnel.walls;
221
+ this.sinks = this.tunnel.sinks;
222
+ this.innerRect = this.tunnel.innerRect;
223
+ this.largeObjects = this.tunnel.largeObjects;
224
+ this.applyRedirect = this.tunnel.applyRedirect;
225
+
226
+ this.sustainedSource = null;
227
+
228
+ if (source === 'emitter') {
229
+ this.emitter = this.tunnel.emitter;
230
+ } else {
231
+ // 'particleWalk' — one or more hand-placed leaf frames (the classic
232
+ // pattern, generalized I2), positioned at the bore's Y (and Z, when
233
+ // zContain) centreline, just inside the breech when capped (mirrors
234
+ // the emitter's own aperture placement), or at the bore's geometric
235
+ // X-centre when open on both ends (no breech to anchor to). ALL
236
+ // `particleCount` quanta share the SAME entrance pixel — the I1
237
+ // scale-sweep's own validated ensemble-building technique
238
+ // (docs/internal/physics-lab/directed-tunnel-scale-sweep/sweep.cjs): a shared
239
+ // injection-time position does not merge same-tick frames, and each
240
+ // leaf's OWN move-gate/wavelength-oscillator RNG draws diverge it
241
+ // from its siblings over subsequent ticks — a genuine stochastic
242
+ // ensemble from an identical start, not degenerate.
243
+ //
244
+ // ws-dse-3d (CEO ruling 2026-07-14, "one source surface"): this burst
245
+ // now routes through `sustainedSource.js`'s ONE `placeSourceQuanta`
246
+ // primitive — the SAME entrance-formula/velocity-draw/z-centring code
247
+ // `setupTunnelLab.js`'s and `setupLightSourceLab.js`'s own independently
248
+ // hand-rolled injectors used to duplicate. `velocityProfile:'beam'` +
249
+ // `entranceBias:'center'` reproduce this constructor's PRE-EXISTING
250
+ // formula EXACTLY (cos/sin(orientationRad)*speed, inner.top+inner.h/2) —
251
+ // byte-identical for every existing caller (DSE Lab, this file's own
252
+ // tests).
253
+ var particleSpec = options.particle || {};
254
+ var hasRealDepth = (tunnelOpts.depth !== undefined && tunnelOpts.depth !== null);
255
+ var burstSpec = SustainedSource.normalizeSourceSpec({
256
+ wavelength: particleSpec.wavelength !== undefined ? particleSpec.wavelength : null,
257
+ charge: charge,
258
+ velocityProfile: 'beam',
259
+ speed: speed,
260
+ orientationRad: orientationRad,
261
+ entranceBias: 'center',
262
+ level: particleSpec.level !== undefined ? particleSpec.level : null,
263
+ mass: particleSpec.mass,
264
+ });
265
+ var placed = SustainedSource.placeSourceQuanta(universe, this.tunnel.innerRect, !!tunnelOpts.capNegX, hasRealDepth, burstSpec, particleCount);
266
+ for (var pi = 0; pi < placed.length; pi++) {
267
+ this.particleHandles.push(placed[pi].handle);
268
+ this.particleQids.push(placed[pi].qid);
269
+ }
270
+ // Deprecated T2 single-quantum aliases — byte-identical reads for any
271
+ // caller written against the old ONE-quantum API when particleCount is
272
+ // omitted/1.
273
+ this.particleHandle = this.particleHandles.length ? this.particleHandles[0] : null;
274
+ this.particleQid = this.particleQids.length ? this.particleQids[0] : null;
275
+
276
+ // ws-dse-3d AC1 (CEO ruling, "LightSource itself should gain the
277
+ // sustain capability through the SAME single authority"): OPT-IN —
278
+ // `options.sustain` omitted (default) is byte-identical to every
279
+ // pre-existing caller (a genuine one-shot construction, DSE Lab's own
280
+ // usage untouched). When supplied (truthy — `true` for "use the SAME
281
+ // wavelength/charge/velocity convention as the burst above, just
282
+ // recurring", or a partial spec object to override individual
283
+ // fields), builds ONE recurring `sustainedSource` tick-fn replicating
284
+ // this burst's own entrance/velocity/charge convention forever —
285
+ // the SAME mechanism `setupTunnelLab.js`/`setupLightSourceLab.js` now
286
+ // both delegate to, rather than each hand-rolling their own copy
287
+ // (see `sustainedSource.js`'s module docstring).
288
+ if (options.sustain) {
289
+ var sustainOverrides = (options.sustain === true) ? {} : options.sustain;
290
+ var sustainSpec = Object.assign({}, burstSpec, sustainOverrides);
291
+ this.sustainedSource = SustainedSource.buildSustainedSource(
292
+ universe, this.tunnel.innerRect, !!tunnelOpts.capNegX, hasRealDepth, sustainSpec
293
+ );
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Tear down every disposable resource THIS LightSource registered — the
300
+ * SAME idiom `QuantumEmitter.prototype.dispose` and every setup factory's
301
+ * own `dispose()` already use (tag-then-splice by identity, idempotent).
302
+ * A `source:'particleWalk'` LightSource with no `options.sustain` (the
303
+ * default — every pre-existing caller) has nothing to dispose; calling this
304
+ * is always a safe no-op in that case. Callers building a page-level
305
+ * `setupResult.dispose()` should delegate here rather than re-deriving the
306
+ * teardown for `this.sustainedSource`/`this.emitter` a second time.
307
+ */
308
+ LightSource.prototype.dispose = function () {
309
+ if (this.sustainedSource) { this.sustainedSource.dispose(); }
310
+ if (this.emitter && typeof this.emitter.dispose === 'function') { this.emitter.dispose(); }
311
+ };
312
+
313
+ /**
314
+ * The Laser preset/factory (CEO: "a Laser is a LightSource preset ... not a
315
+ * separate class"). Pre-fills a FIXED polarization (default 'positive' — a
316
+ * polarized beam, matching "lasers are ... polarized"), a NARROWER default
317
+ * width (report §4.3 — collimation/geometry convenience, zero new physics),
318
+ * and `source:'emitter'` (I2: a laser is "a collimated, phase-locked BEAM" —
319
+ * an ONGOING stream, not LightSource's own new I2 default of a single
320
+ * snapshot-placed particleWalk quantum; `laser()` keeps T2's original
321
+ * emitter-sourced behavior explicitly rather than silently inheriting
322
+ * LightSource's now-different bare default); every field is still
323
+ * caller-overridable (a `source:'particleWalk'` laser is legal, if unusual).
324
+ *
325
+ * **I2 re-baseline (width default 20 -> 4):** T2's "narrower" framing was
326
+ * relative to the THEN-current world where `width` had no LightSource
327
+ * default at all (callers always supplied one, historically ~40px) — 20px
328
+ * was narrower than that. I2 gave a bare LightSource its OWN DSE-scale
329
+ * default (`width:8`), which made the OLD laser default (20px) WIDER than
330
+ * the new baseline — breaking the "narrower" invariant this preset exists to
331
+ * express. Re-baselined to 4px (narrower than the new 8px DSE-scale
332
+ * default) so "a laser is narrower" stays TRUE, not just documented.
333
+ *
334
+ * @param {Object} universe
335
+ * @param {Object} [options] - SAME shape as `LightSource`'s own options; any
336
+ * field provided here overrides the laser defaults below.
337
+ * @returns {LightSource}
338
+ */
339
+ LightSource.laser = function (universe, options) {
340
+ options = options || {};
341
+ var tunnelDefaults = { width: 4 };
342
+ var tunnelOpts = Object.assign({}, tunnelDefaults, options.tunnel || {});
343
+ var laserOpts = Object.assign(
344
+ { polarization: 'positive', source: 'emitter' },
345
+ options,
346
+ { tunnel: tunnelOpts }
347
+ );
348
+ return new LightSource(universe, laserOpts);
349
+ };
350
+
351
+ // ── Export ───────────────────────────────────────────────────────────
352
+
353
+ export { LightSource };
354
+