@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,209 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * emitterProgram.js — the emitter as an engine-AGNOSTIC handle-program (sugar).
5
+ *
6
+ * G-EMIT-2: the emitter is NOT an engine object and NOT a per-engine provider method — it is a
7
+ * host tick-function (a "handle program") composed entirely from the generic engine primitives
8
+ * `universe.random()`, `universe.makeLeafFrame(...)`, and `universe.injectFrame(...)`. Both engines
9
+ * (WASM canonical + the JS reference) expose those primitives identically, so this ONE function is
10
+ * the single source of emit behavior — replacing the former dual per-engine `registerEmitter`
11
+ * implementations (compat/providers + the JS reference engine).
12
+ *
13
+ * DRAW-ORDER CONTRACT (golden-locked on WASM — do not reorder):
14
+ * per emitted leaf: random()→x, random()→y, [random()→angle if randomDirection],
15
+ * [random()→charge if charge is null], then makeLeafFrame(...) which on WASM
16
+ * draws ONE more internally (the ctor charge-mirror, always). The JS reference
17
+ * makeLeafFrame draws 0 there (charge is passed in) — engines may diverge
18
+ * (WASM is canonical); only the WASM goldens are asserted.
19
+ *
20
+ * OPT-IN 3D z-SPREAD: `em.zMin`/`em.zDepth` default to `null` — when
21
+ * both are absent, ZERO additional draws execute and this tick-fn is byte-identical to the
22
+ * contract above (every existing 2D caller — QuantumEmitter, PointSource — is unaffected).
23
+ * When a caller opts in (`em.zMin` a number), ONE additional `random()` draw executes AFTER
24
+ * `injectFrame` (uniformly distributing the emitted leaf across `[zMin, zMin+zDepth)`), then a
25
+ * `setFrameProperty(qid, 'z', z)` write — the canonical z-placement precedent
26
+ * (a THIRD setFrameProperty call, never inserted into the locked x/y/[angle]/[charge] sequence).
27
+ * `makeLeafFrame` has no z argument — no engine change, this composes existing primitives.
28
+ *
29
+ * OPT-IN 3D ISOTROPIC SCATTER: `em.zIsotropy`
30
+ * (default falsy) — when BOTH `em.randomDirection` AND `em.zIsotropy` are true, the
31
+ * random-direction draw becomes a full unit-SPHERE direction (azimuth + polar angle,
32
+ * TWO draws replacing the 2D branch's ONE) instead of a 2D-plane circle, and the drawn
33
+ * `vz` is written via a FOURTH `setFrameProperty` call (sharing the SAME qid lookup as
34
+ * the zMin branch above when both are armed). Every existing `randomDirection:true`
35
+ * caller has `zIsotropy` false/undefined and takes the unchanged 2D-circle branch —
36
+ * byte-identical, zero extra draws.
37
+ *
38
+ * The emState object is OWNED by the sugar (QuantumEmitter / PointSource). The sugar mutates it
39
+ * directly for active / velocity / burst-size / source-rect changes — there is no provider
40
+ * setEmitter* ABI. The tick-fn closes over emState so those mutations take effect live.
41
+ *
42
+ * DSE LAB (systematic controls): `em.everyNTicks` (opt-in,
43
+ * default null/undefined = fire every active tick, byte-identical to every existing
44
+ * caller) throttles firing to once every N ticks — a LIVE, per-tick-read cadence knob
45
+ * (mutate `em.everyNTicks` directly on the live emState any time; no re-init). This is
46
+ * DIFFERENT from `delay`/`remainingDelay`, which is a ONE-TIME initial wait before the
47
+ * FIRST burst (`remainingDelay` never resets after reaching 0 — ground-truthed by
48
+ * reading this file — so `delay` cannot serve as a repeating rate control). `fireOnce`
49
+ * (exported below) is the manual single-burst primitive for a "FIRE" button —
50
+ * refactored out of the tick-fn's burst loop (`_fireBurst`) so BOTH the automatic
51
+ * per-tick path and a manual trigger share the IDENTICAL draw-order/injection logic;
52
+ * zero duplication, zero change to the golden-locked draw order for the automatic path.
53
+ */
54
+
55
+ 'use strict';
56
+
57
+ /**
58
+ * Core burst-firing logic — shared by the automatic tick-fn AND `fireOnce` (the
59
+ * manual "FIRE" trigger). Extracted verbatim from the tick-fn (no logic change) so
60
+ * a manual single-burst call reuses the EXACT same draw-order/injection path as an
61
+ * automatic tick, rather than a second hand-rolled copy.
62
+ * @param {Object} universe
63
+ * @param {Object} em
64
+ */
65
+ function _fireBurst(universe, em) {
66
+ var burstLeft = em.burstSize;
67
+ for (var bi = 0; bi < burstLeft && em.remainingQuanta > 0; bi++) {
68
+ var rects = em.sourceRects;
69
+ for (var ri = 0; ri < rects.length && em.remainingQuanta > 0; ri++) {
70
+ var srcRect = rects[ri]; // [l, t, w, h]
71
+ var x = srcRect[0] + universe.random() * srcRect[2]; // draw 1
72
+ var y = srcRect[1] + universe.random() * srcRect[3]; // draw 2
73
+
74
+ var vx = em.vx;
75
+ var vy = em.vy;
76
+ var vzOut = null; // set only by the zIsotropy branch below
77
+ if (em.randomDirection) {
78
+ if (em.zIsotropy) {
79
+ // Velocity z-symmetry consistent with its
80
+ // profile: a real-depth-armed ISOTROPIC point source scatters
81
+ // on the unit SPHERE (azimuth + polar), not just the XY plane —
82
+ // mirrors directedRedirectProgram.js's own polarSpreadRad>0
83
+ // sphere draw. OPT-IN (em.zIsotropy defaults false) — zero extra
84
+ // draws for every pre-existing randomDirection:true caller, which
85
+ // takes the `else` 2D-circle branch unchanged (SAME single draw,
86
+ // SAME golden-locked position in the sequence).
87
+ var az = universe.random() * 2 * Math.PI; // draw 3a
88
+ var cosPolar = universe.random() * 2 - 1; // draw 3b
89
+ var sinPolar = Math.sqrt(Math.max(0, 1 - cosPolar * cosPolar));
90
+ var speed3 = Math.sqrt(vx * vx + vy * vy) || 1;
91
+ vx = Math.cos(az) * sinPolar * speed3;
92
+ vy = Math.sin(az) * sinPolar * speed3;
93
+ vzOut = cosPolar * speed3;
94
+ } else {
95
+ var angle = universe.random() * 2 * Math.PI; // draw 3
96
+ var speed = Math.sqrt(vx * vx + vy * vy) || 1;
97
+ vx = Math.cos(angle) * speed;
98
+ vy = Math.sin(angle) * speed;
99
+ }
100
+ }
101
+
102
+ var chargeInt;
103
+ if (em.charge === null || em.charge === undefined) {
104
+ chargeInt = (universe.random() < 0.5) ? 0 : 1; // draw 4
105
+ } else {
106
+ chargeInt = (em.charge === 'Positive' || em.charge === 'POSITIVE') ? 0 : 1;
107
+ }
108
+
109
+ // LEVEL (leaf scale) is a UNIVERSE property, NOT an emitter constant: a
110
+ // fundamental leaf is created at the universe's fundamental-dimension level
111
+ // (wave-interference / double-slit run at dimension 2 → 4×4 leaves, capacity 16
112
+ // → wavelength 16·WC). Pass null so makeLeafFrame derives it from the universe;
113
+ // a non-null em.fundamentalDim is an explicit override. (Hardcoding 0 here
114
+ // collapsed capacity 16→1 and wavelength 128→8 — the wave-richness regression.)
115
+ var level = (em.fundamentalDim != null) ? em.fundamentalDim : null;
116
+ // makeLeafFrame draws ONE more on WASM (ctor charge-mirror) → golden +1.
117
+ // R3-1: thread per-frame render.shape (10th) + render.outline (11th) EXACTLY as
118
+ // em.color is threaded (9th) — so QuantumEmitter/PointSource gain the same per-frame
119
+ // render reach SphereEmitter3D has. All three ride makeLeafFrame's WASM-side
120
+ // string_map merge stamp (0 RNG draws, golden preserved). Null default → no key →
121
+ // byte-identical for every pre-existing caller. WASM-ONLY (the frozen JS reference
122
+ // makeLeafFrame ignores the shape/outline positionals — a DOCUMENTED no-op, see the
123
+ // wasm-engine makeLeafFrame docstring + the render-intent-merge test; the realm is WASM).
124
+ var handle = universe.makeLeafFrame(
125
+ x, y, level,
126
+ (em.wavelength !== null && em.wavelength !== undefined) ? em.wavelength : 0,
127
+ vx, vy, !!em.wrap, chargeInt,
128
+ em.color || null,
129
+ (em.renderShape != null && em.renderShape !== '') ? em.renderShape : null,
130
+ (em.renderOutline != null) ? em.renderOutline : null
131
+ );
132
+ // liveQuantaCap refused the add: no leaf was born, so a finite quantumLimit is
133
+ // not spent on it (below the cap the handle is never null — byte-identical).
134
+ if (handle === null) { continue; }
135
+ universe.injectFrame(handle);
136
+
137
+ // OPT-IN 3D z-SPREAD (see module docstring). em.zMin is null for every
138
+ // existing 2D caller — this branch is dead code (0 extra draws) unless a
139
+ // caller explicitly opts in (PointSource `opts.zMin`/`opts.zDepth`).
140
+ // Shares the SAME qid lookup with the zIsotropy vz write
141
+ // below (never a second getFrameQuantumId call for the same handle).
142
+ var zMinArmed = (em.zMin !== null && em.zMin !== undefined);
143
+ if (zMinArmed || vzOut !== null) {
144
+ var qid = universe.getFrameQuantumId(handle);
145
+ if (zMinArmed) {
146
+ var zDepth = (em.zDepth !== null && em.zDepth !== undefined) ? em.zDepth : 0;
147
+ var z = em.zMin + (zDepth > 0 ? universe.random() * zDepth : 0); // extra draw, opt-in only
148
+ universe.setFrameProperty(qid, 'z', z);
149
+ }
150
+ if (vzOut !== null) {
151
+ universe.setFrameProperty(qid, 'vz', vzOut);
152
+ }
153
+ }
154
+
155
+ em.remainingQuanta--;
156
+ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Build a host tick-function that fires one emitter, drawing from the engine's single RNG and
162
+ * injecting leaves via the generic primitives. Push the returned fn onto
163
+ * `universe.experimentTickFunctions`.
164
+ *
165
+ * @param {Object} universe - engine universe exposing random()/makeLeafFrame()/injectFrame().
166
+ * @param {Object} em - emitter state: {
167
+ * sourceRects: Array<[l,t,w,h]>, vx, vy, burstSize, charge: (null|'POSITIVE'|'NEGATIVE'),
168
+ * randomDirection: bool, remainingQuanta: number, delay, remainingDelay,
169
+ * active: bool, tombstoned: bool, color: [r,g,b,a]|null, wrap: bool,
170
+ * wavelength: number|null, fundamentalDim: number, everyNTicks: number|null }
171
+ * @returns {Function} tick-fn (returns [] — frames are injected via injectFrame, not returned).
172
+ */
173
+ function buildEmitterTickFn(universe, em) {
174
+ return function () {
175
+ if (em.tombstoned || !em.active) return [];
176
+ if (em.remainingDelay > 0) { em.remainingDelay--; return []; }
177
+ if (em.remainingQuanta <= 0) return [];
178
+
179
+ // OPT-IN firing-rate cadence (DSE Lab): null/undefined/<=1 -> fire every
180
+ // active tick (byte-identical to pre-Lab behavior — this branch is dead
181
+ // code for every existing caller, which never sets em.everyNTicks).
182
+ if (em.everyNTicks !== null && em.everyNTicks !== undefined && em.everyNTicks > 1) {
183
+ em._rateCounter = (em._rateCounter || 0) + 1;
184
+ if (em._rateCounter % em.everyNTicks !== 0) return [];
185
+ }
186
+
187
+ _fireBurst(universe, em);
188
+ return [];
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Manual single-burst trigger (DSE Lab "FIRE" button, autofire off). Bypasses the
194
+ * `active`/`remainingDelay`/`everyNTicks` GATING (an explicit user action should
195
+ * always fire on request) but still respects `tombstoned`/`remainingQuanta` (a
196
+ * disposed or already-exhausted emitter cannot be forced to fire past its limit).
197
+ * Reuses the IDENTICAL `_fireBurst` draw-order/injection path the automatic tick-fn
198
+ * uses — no second implementation.
199
+ * @param {Object} universe
200
+ * @param {Object} em
201
+ */
202
+ function fireOnce(universe, em) {
203
+ if (em.tombstoned || em.remainingQuanta <= 0) return;
204
+ _fireBurst(universe, em);
205
+ }
206
+
207
+
208
+ export { buildEmitterTickFn, fireOnce };
209
+
@@ -0,0 +1,197 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * frqtl-control-slider.js — the SINGLE scalar-control widget
5
+ * (gym-controls-as-sugar, Layer B).
6
+ *
7
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
8
+ *
9
+ * `mount(hostEl, opts) → { dispose() }` renders ONE slider+label+value row
10
+ * (appended into `hostEl`, a parent container — multiple sliders may share
11
+ * one `hostEl`, each disposable independently) from a normalized `kind:
12
+ * 'scalar'` descriptor's DISPLAY shape (`{id,label,min,max,step,value,
13
+ * valueText}` — the SAME serializable shape `lib/shim/rpc`'s dispatch and
14
+ * `frqtl-controls-panel.js`'s `listControls`/`setControlValue` already
15
+ * produce). Dragging the slider calls back into the caller, which decides
16
+ * HOW the value gets applied:
17
+ *
18
+ * `opts.descriptor` — a LIVE, normalized descriptor object
19
+ * (`{id,label,min,max,step,value,fmt,onChange}`) in the SAME window.
20
+ * The STANDALONE in-app path: the widget mutates `descriptor.value` and
21
+ * calls `descriptor.onChange(value)` DIRECTLY — no transport, no RPC,
22
+ * the same-realm case.
23
+ *
24
+ * `opts.initial` + `opts.onInput` — the DECOUPLED path: `opts.initial` is
25
+ * the display-shape descriptor above; `opts.onInput(value)` is called on
26
+ * drag and may return a plain `{value,valueText}` OR a
27
+ * `Promise<{value,valueText}>` — the widget awaits either uniformly and
28
+ * refreshes the displayed value/text from whatever settles. This is the
29
+ * REMOTE path: the caller supplies an `onInput` that dispatches however
30
+ * it needs to (e.g. `frqtl-controls-panel.js` passes
31
+ * `function(v){ return handle.call('setControlValue',[name,d.id,v]); }`,
32
+ * reusing its OWN `lib/shim/rpc`-backed transport handle) — this widget
33
+ * itself knows NOTHING about registries, RPC, or transports; it is
34
+ * PURELY the rendering/wiring widget (the architectural point of this
35
+ * layer: "panel = collection host + transport plumbing; control lib =
36
+ * the widget").
37
+ *
38
+ * Exactly one of `opts.descriptor` OR (`opts.initial` + `opts.onInput`) is
39
+ * required — FAIL-LOUD otherwise.
40
+ *
41
+ * ── DRY discipline (the architectural point of Layer B) ──────────────────────
42
+ *
43
+ * Before this layer, `frqtl-controls-panel.js` built slider+label+value DOM
44
+ * internally (one implementation). This file is now THE ONE slider-rendering
45
+ * implementation; `frqtl-controls-panel.js` REFACTORED to call `mount()` here
46
+ * once per `kind:'scalar'` descriptor instead of building rows itself — see
47
+ * that file's own `renderRows` for the (now much smaller) call site. No
48
+ * behavior change to the panel's public API or its two demo placements.
49
+ *
50
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
51
+ *
52
+ * This file adds ZERO physics — pure UI plumbing over a caller-supplied
53
+ * descriptor/callback. Moving this slider produces the IDENTICAL simulation
54
+ * effect as moving any other consumer of the same declared-once control
55
+ * (proven by the Layer-B Rule-0 oracle: collision-matrix's `angle` control
56
+ * driven simultaneously by its own page slider, the panel's slider, and a
57
+ * standalone `frqtl-control-slider` instance — all three the SAME CONTROLS
58
+ * array entry).
59
+ */
60
+
61
+
62
+ /**
63
+ * mount(hostEl, opts) — render one slider+label+value row into `hostEl`.
64
+ *
65
+ * @param {HTMLElement} hostEl — parent container to append the row into.
66
+ * @param {object} opts
67
+ * @param {object} [opts.descriptor] — a LIVE, normalized descriptor
68
+ * `{id,label,min,max,step,value,fmt,onChange}` — the in-app direct path.
69
+ * @param {{id:string,label:string,min:number,max:number,step:number,
70
+ * value:number,valueText:string}} [opts.initial] — REQUIRED when
71
+ * `opts.descriptor` is absent: the initial serializable display state.
72
+ * @param {(value:number) => (*|Promise<{value:number,valueText:string}>)} [opts.onInput] —
73
+ * REQUIRED when `opts.descriptor` is absent: called with the new numeric
74
+ * value on drag; its (possibly-Promised) return refreshes the displayed
75
+ * value/text.
76
+ * @returns {{dispose: () => void, setValue: (value:number, valueText?:string) => void}}
77
+ * `setValue` (gym-controls-as-sugar FULL CONVERGENCE build, Stage ii item 5
78
+ * — the `ControlSync` reconciliation target) is DISPLAY-ONLY: it writes the
79
+ * slider's `.value` DOM property and the value label directly and NEVER
80
+ * fires `onInput`/`onChange` (setting an `<input>`'s `.value` property does
81
+ * not dispatch a synthetic `input` event — only genuine user interaction
82
+ * does; hard-tested via a spy in `frqtl-control-slider.test.mjs`). This is
83
+ * what makes N concurrent widgets reconcilable without an echo/feedback
84
+ * loop: `ControlSync` calls `setValue()` to REFLECT canonical state, which
85
+ * can never itself re-trigger a write.
86
+ * @throws {Error} FAIL-LOUD on a missing host element, or when neither
87
+ * `opts.descriptor` nor both of `opts.initial`/`opts.onInput` are supplied.
88
+ */
89
+ function mount(hostEl, opts) {
90
+ opts = opts || {};
91
+ if (!hostEl) {
92
+ throw new Error('[frqtl-control-slider] mount requires a host element. (FAIL-LOUD.)');
93
+ }
94
+
95
+ var initial, onInput;
96
+ if (opts.descriptor) {
97
+ var d = opts.descriptor;
98
+ if (!d.id || typeof d.min !== 'number' || typeof d.max !== 'number' || typeof d.step !== 'number') {
99
+ throw new Error('[frqtl-control-slider] mount({descriptor}) requires a normalized ' +
100
+ 'scalar descriptor with id/min/max/step. (FAIL-LOUD.)');
101
+ }
102
+ // A bind-only descriptor (no onChange) has NO write path this widget can
103
+ // execute — this file deliberately has zero dependency on registries/
104
+ // controlBind.js (Layer B: "the widget knows nothing about transports").
105
+ // Dragging it would otherwise silently do nothing (the exact class of
106
+ // bug the gym-controls-as-sugar FULL CONVERGENCE build fixed in the panel).
107
+ // Route a bind-only descriptor through frqtl-controls-panel.js instead
108
+ // (mode:'in-app'/'iframe', which DOES resolve bind execution), or mount
109
+ // this widget via the opts.initial/onInput decoupled path with onInput
110
+ // calling the panel's dispatch/RPC directly.
111
+ if (typeof d.onChange !== 'function' && d.bind) {
112
+ throw new Error('[frqtl-control-slider] mount({descriptor}) was given a bind-only ' +
113
+ 'descriptor (id "' + d.id + '", no onChange) — the standalone opts.descriptor path ' +
114
+ 'has no bind-execution context and cannot write it. Use frqtl-controls-panel.js\'s ' +
115
+ 'mount() instead, or mount this widget via opts.initial + opts.onInput with onInput ' +
116
+ 'calling the panel\'s dispatch/RPC. (FAIL-LOUD — never a silent no-op on drag.)');
117
+ }
118
+ initial = {
119
+ id: d.id, label: d.label, min: d.min, max: d.max, step: d.step, value: d.value,
120
+ valueText: (typeof d.fmt === 'function') ? d.fmt(d.value) : String(d.value)
121
+ };
122
+ onInput = function (v) {
123
+ d.value = v;
124
+ if (typeof d.onChange === 'function') { d.onChange(v); }
125
+ return { value: v, valueText: (typeof d.fmt === 'function') ? d.fmt(v) : String(v) };
126
+ };
127
+ } else {
128
+ if (!opts.initial || typeof opts.onInput !== 'function') {
129
+ throw new Error('[frqtl-control-slider] mount requires EITHER opts.descriptor OR ' +
130
+ 'BOTH opts.initial and opts.onInput. (FAIL-LOUD.)');
131
+ }
132
+ initial = opts.initial;
133
+ onInput = opts.onInput;
134
+ }
135
+
136
+ var row = document.createElement('div');
137
+ row.className = 'frqtl-control-slider-row';
138
+ row.style.cssText = 'display:flex;align-items:center;gap:8px;margin:4px 0;font:12px sans-serif;color:#ccc;';
139
+
140
+ var labelEl = document.createElement('span');
141
+ labelEl.textContent = initial.label + ':';
142
+ labelEl.style.cssText = 'min-width:56px;';
143
+
144
+ var slider = document.createElement('input');
145
+ slider.type = 'range';
146
+ slider.min = initial.min; slider.max = initial.max; slider.step = initial.step; slider.value = initial.value;
147
+ slider.setAttribute('data-frqtl-control-id', initial.id);
148
+
149
+ var valEl = document.createElement('span');
150
+ valEl.textContent = initial.valueText;
151
+ valEl.style.cssText = 'min-width:32px;text-align:right;color:#ffb450;';
152
+
153
+ slider.addEventListener('input', function () {
154
+ var v = parseFloat(slider.value);
155
+ Promise.resolve(onInput(v)).then(function (result) {
156
+ if (result && result.valueText !== undefined) { valEl.textContent = result.valueText; }
157
+ }).catch(function (e) {
158
+ valEl.textContent = 'ERR';
159
+ if (typeof console !== 'undefined' && console.error) {
160
+ console.error('[frqtl-control-slider] onInput failed:', e);
161
+ }
162
+ });
163
+ });
164
+
165
+ row.appendChild(labelEl);
166
+ row.appendChild(slider);
167
+ row.appendChild(valEl);
168
+ hostEl.appendChild(row);
169
+
170
+ // Fallback formatter for setValue()'s optional valueText: the descriptor
171
+ // path still has a live `d.fmt`; the decoupled (opts.initial/onInput) path
172
+ // has no formatter at all (only the ORIGINAL initial.valueText), so
173
+ // setValue() callers on that path should pass valueText explicitly —
174
+ // String(v) is the honest default, never a fabricated format.
175
+ var formatFallback = (opts.descriptor && typeof opts.descriptor.fmt === 'function')
176
+ ? opts.descriptor.fmt
177
+ : function (v) { return String(v); };
178
+
179
+ return {
180
+ dispose: function () {
181
+ if (row.parentNode) { row.parentNode.removeChild(row); }
182
+ },
183
+ // Display-only reconciliation write — see the @returns docstring above
184
+ // for the no-loop invariant this depends on.
185
+ setValue: function (value, valueText) {
186
+ slider.value = value;
187
+ valEl.textContent = (valueText !== undefined) ? valueText : formatFallback(value);
188
+ }
189
+ };
190
+ }
191
+
192
+ // ── Export ────────────────────────────────────────────────────────────────────
193
+
194
+ var FrqtlControlSlider = { mount: mount };
195
+
196
+ export { FrqtlControlSlider };
197
+