@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,229 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupDoubleSlit_Laser — Laser-style double-slit experiment factory.
5
+ *
6
+ * Replaces the radial PointSource with a directional QuantumEmitter (like a
7
+ * circuit battery). Particles fire rightward with initial momentum; the
8
+ * engine's brownian motion + wave-walk creates the lateral displacement that
9
+ * produces interference.
10
+ *
11
+ * Slit geometry uses d/a ≈ 5 to match real-world HeNe laser teaching-lab
12
+ * proportions (slit separation / slit width ≈ 5).
13
+ *
14
+ * Includes single-photon mode: fires one particle at a time from a random
15
+ * spot on the laser aperture, demonstrating that individual particles still
16
+ * build up the interference pattern.
17
+ */
18
+
19
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
20
+ import { SlitScreen } from '../../parts/SlitScreen.js';
21
+ import { QuantumEmitter } from '../objects/QuantumEmitter.js';
22
+ import { DetectorWall } from '../../parts/DetectorWall.js';
23
+ import { LargeObject } from '../../parts/LargeObject.js';
24
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
25
+
26
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
27
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
28
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
29
+
30
+ // ── Imports ─────────────────────────────────────────────────────────
31
+
32
+ // ── Defaults ────────────────────────────────────────────────────────
33
+
34
+ var DEFAULT_VOLTAGE = 2; // vx: rightward momentum
35
+ var DEFAULT_AMPERAGE = 8; // particles per tick
36
+ var DEFAULT_SLIT_HEIGHT = 20; // px — slit opening (a), gives d/a ≈ 5
37
+ var DEFAULT_GAP_HEIGHT = 80; // px — inter-slit wall, gives d = a + gap = 100
38
+ var SINGLE_PHOTON_DELAY = 8; // ticks between single-photon emissions
39
+
40
+ // ── Setup Factory ───────────────────────────────────────────────────
41
+
42
+ /**
43
+ * Create a laser-style double-slit experiment.
44
+ *
45
+ * @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd
46
+ * @param {Object} options - Configuration
47
+ * @param {number} [options.wavelength=64] - Wavelength for screen positioning
48
+ * @param {number} [options.fundamentalDimension=2] - Base dimension
49
+ * @param {number} [options.detectAtSlits=1] - Slit detection mode (0/1/2)
50
+ * @param {boolean} [options.absorbAtSlitWall=true] - Absorb vs block at walls
51
+ * @param {number} [options.wavelengthScale] - Override wavelength constant
52
+ * @param {number} [options.voltage=2] - Rightward momentum (vx)
53
+ * @param {number} [options.amperage=8] - Particles per tick
54
+ * @param {boolean} [options.singlePhoton=false] - Single-photon mode
55
+ * @param {number} [options.slitHeight=20] - Slit opening height (px)
56
+ * @param {number} [options.gapHeight=80] - Inter-slit wall height (px)
57
+ * @returns {Object} { slitScreen, emitter, detectorWall, startWall, interferenceParams, largeObjects, setSinglePhoton }
58
+ */
59
+ /**
60
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
61
+ * Exported as `setupDoubleSlit_Laser.OPTIONS` so a composing caller consumes it instead of
62
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
63
+ * read-set by static trace and fails if this list drifts from the code.
64
+ */
65
+ var OPTIONS = [
66
+ 'absorbAtSlitWall', 'amperage', 'detectAtSlits',
67
+ 'fundamentalDimension', 'gapHeight', 'singlePhoton', 'slitHeight',
68
+ 'voltage', 'wavelength', 'wavelengthScale',
69
+ ];
70
+
71
+ function setupDoubleSlit_Laser(universe, options) {
72
+ options = _assertSetupOptions('setupDoubleSlit_Laser', options, OPTIONS);
73
+
74
+ var wavelength = options.wavelength !== undefined ? options.wavelength : 64;
75
+ var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 2;
76
+ var detectAtSlits = options.detectAtSlits !== undefined ? options.detectAtSlits : 1;
77
+ var absorbAtSlitWall = options.absorbAtSlitWall !== undefined ? options.absorbAtSlitWall : true;
78
+ var canvasRect = universe.universe_rect;
79
+ // The world height, through the ONE world-rect normalizer (throws on a missing or
80
+ // non-positive dimension — a zero-high screen would count nothing, silently).
81
+ var canvasH = _requireWorldRect('setupDoubleSlit_Laser universe.universe_rect', canvasRect).h;
82
+ var voltage = options.voltage !== undefined ? options.voltage : DEFAULT_VOLTAGE;
83
+ var amperage = options.amperage !== undefined ? options.amperage : DEFAULT_AMPERAGE;
84
+ var singlePhoton = options.singlePhoton || false;
85
+ var slitHeight = options.slitHeight !== undefined ? options.slitHeight : DEFAULT_SLIT_HEIGHT;
86
+ var gapHeight = options.gapHeight !== undefined ? options.gapHeight : DEFAULT_GAP_HEIGHT;
87
+
88
+ // Wavelength constant: higher values create coarser wave-walk oscillation,
89
+ // producing wider, more detectable interference fringes. 32 gives stable
90
+ // 4/4 interference tests with ~4000 hits at 5×5000. Lower values (4-16)
91
+ // produce finer fringes that wash out with aggregated data.
92
+ var wavelengthScale = options.wavelengthScale !== undefined
93
+ ? options.wavelengthScale
94
+ : 32;
95
+
96
+ universe.wavelengthScale = wavelengthScale;
97
+
98
+ // Init universe: ground-up mode with wave-walk (NOT brownian motion).
99
+ // brownianMotion:false disables brownian motion, enabling the wave-walk
100
+ // movement mode where particles move 1px along velocity + 1px perpendicular
101
+ // via the wavelength-driven coin-flip. This is what produces interference.
102
+ // (brownianMotion=true would enable pure random walk, which diffuses the pattern.)
103
+ universe.init(
104
+ fundamentalDimension,
105
+ {
106
+ wavelengthScale: wavelengthScale,
107
+ brownianMotion: false, // enables wave-walk mode
108
+ }
109
+ );
110
+
111
+ // ── Slit Screen (d/a ≈ 5 geometry) ─────────────────────────────
112
+ var slitScreenLeft = wavelength * 1.5;
113
+ var slitScreen = new SlitScreen(
114
+ universe,
115
+ slitScreenLeft,
116
+ wavelength, // wall thickness
117
+ canvasH,
118
+ wavelength,
119
+ absorbAtSlitWall,
120
+ detectAtSlits,
121
+ slitHeight, // override: narrower slits
122
+ gapHeight // override: wider gap
123
+ );
124
+
125
+ // ── Start Wall (absorb wall at left edge) ──────────────────────
126
+ var startWall = new LargeObject(
127
+ universe,
128
+ [0, 0, 10, canvasH],
129
+ CollisionTypes.ABSORB,
130
+ // RECOLOR removed: deleted in the engine-no-color cut (I1). Stale dead reference.
131
+ // KILL removed: was a Rust-only divergence, deleted from canonical action set.
132
+ // The ABSORB collision type already absorbs frames; no action needed here.
133
+ [],
134
+ 'start_wall',
135
+ 'closest',
136
+ null,
137
+ 1,
138
+ [120, 120, 120]
139
+ );
140
+
141
+ // ── Laser Emitter ──────────────────────────────────────────────
142
+ // Two source rects, one aligned with each slit, positioned close to the
143
+ // slit screen (x = slitScreenLeft - 6). The wave-walk's coin-flip has a
144
+ // strong directional bias at high wavelengthScale; placing the source
145
+ // close minimizes pre-slit drift, keeping slit1/slit2 ratio near 1.0.
146
+ var laserX = slitScreenLeft - 6;
147
+ var sourceRect1 = [laserX, slitScreen.slit1_top, 4, slitScreen.slit_height];
148
+ var sourceRect2 = [laserX, slitScreen.slit2_top, 4, slitScreen.slit_height];
149
+
150
+ var effectiveBurst = singlePhoton ? 1 : amperage;
151
+ var effectiveDelay = singlePhoton ? SINGLE_PHOTON_DELAY : 0;
152
+
153
+ var emitter = new QuantumEmitter(
154
+ universe,
155
+ [sourceRect1, sourceRect2],
156
+ voltage, 0, // vx=rightward, vy=0
157
+ effectiveBurst,
158
+ null, // random charge
159
+ true, // auto_fire
160
+ false, // directional (not random)
161
+ Infinity, // unlimited quanta
162
+ effectiveDelay // delay between bursts
163
+ );
164
+
165
+ // ── Detector Wall ──────────────────────────────────────────────
166
+ var detectorWallX = slitScreen.wall1.rect.left + slitScreen.wall1.rect.w + 3 * wavelength;
167
+ var detectorWall = new DetectorWall(
168
+ universe,
169
+ canvasRect,
170
+ detectorWallX
171
+ );
172
+
173
+ // ── Interference parameters ────────────────────────────────────
174
+ var d_distance_between_slits = slitScreen.slit_height + slitScreen.slit_wall_gap_height;
175
+ var L_distance_slits_to_screen = detectorWall.x - (slitScreen.wall1.rect.left + slitScreen.wall1.rect.w);
176
+
177
+ // ── Collect all large objects for rendering ─────────────────────
178
+ var largeObjects = [];
179
+ for (var wi = 0; wi < slitScreen.walls.length; wi++) {
180
+ largeObjects.push(slitScreen.walls[wi]);
181
+ }
182
+ largeObjects.push(startWall);
183
+ largeObjects.push(detectorWall.detector_wall);
184
+
185
+ // ── Single-photon toggle ────────────────────────────────────────
186
+ var currentAmperage = amperage;
187
+
188
+ function setSinglePhoton(enabled) {
189
+ if (enabled) {
190
+ emitter.setBurstSize(1);
191
+ // Delay is set at registration; re-register or use setActive to pace
192
+ // For now, burst_size=1 is the primary control
193
+ } else {
194
+ emitter.setBurstSize(currentAmperage);
195
+ }
196
+ }
197
+
198
+ function setAmperage(newAmperage) {
199
+ currentAmperage = newAmperage;
200
+ emitter.setBurstSize(newAmperage);
201
+ }
202
+
203
+ function setVoltage(newVoltage) {
204
+ emitter.setVelocity(newVoltage, 0);
205
+ }
206
+
207
+ return {
208
+ slitScreen: slitScreen,
209
+ emitter: emitter,
210
+ detectorWall: detectorWall,
211
+ startWall: startWall,
212
+ interferenceParams: {
213
+ d: d_distance_between_slits,
214
+ L: L_distance_slits_to_screen,
215
+ wavelength: wavelength
216
+ },
217
+ largeObjects: largeObjects,
218
+ setSinglePhoton: setSinglePhoton,
219
+ setAmperage: setAmperage,
220
+ setVoltage: setVoltage
221
+ };
222
+ }
223
+
224
+ // ── Export ───────────────────────────────────────────────────────────
225
+
226
+ setupDoubleSlit_Laser.OPTIONS = OPTIONS;
227
+
228
+ export { setupDoubleSlit_Laser };
229
+
@@ -0,0 +1,59 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupEmpty — Engine-agnostic blank-universe factory.
5
+ *
6
+ * Initializes the universe (same init contract as every other setup factory) but
7
+ * creates NO objects and NO emitters. This is the base the AuthorView snapshot
8
+ * model loads: a saved project opens this page, the universe is init'd here, then
9
+ * AuthorView.applyDesign reconstructs the saved entities into it. Without the
10
+ * universe.init() call here, applyDesign would run against an uninitialized
11
+ * universe and silently restore nothing.
12
+ *
13
+ * Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
14
+ */
15
+
16
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
17
+
18
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
19
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
20
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
21
+
22
+ /**
23
+ * Initialize a blank universe with the standard dimension stack, no objects.
24
+ *
25
+ * @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd
26
+ * @param {Object} [options]
27
+ * @param {number} [options.fundamentalDimension=2]
28
+ * @param {number} [options.wavelengthScale=8]
29
+ * @returns {Object} { largeObjects: [] }
30
+ */
31
+ /**
32
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
33
+ * Exported as `setupEmpty.OPTIONS` so a composing caller consumes it instead of
34
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
35
+ * read-set by static trace and fails if this list drifts from the code.
36
+ */
37
+ var OPTIONS = [
38
+ 'fundamentalDimension', 'wavelengthScale',
39
+ ];
40
+
41
+ function setupEmpty(universe, options) {
42
+ options = _assertSetupOptions('setupEmpty', options, OPTIONS);
43
+
44
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
45
+ var canvas_rect = universe.universe_rect;
46
+ _requireWorldRect('setupEmpty universe.universe_rect', canvas_rect);
47
+ var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 2;
48
+ var wavelengthScale = options.wavelengthScale !== undefined ? options.wavelengthScale : 8;
49
+
50
+ // Same init contract as setupWaveInterference: ground-up, brownianMotion:false.
51
+ universe.init(fundamentalDimension, { wavelengthScale: wavelengthScale, brownianMotion: false });
52
+
53
+ return { largeObjects: [] };
54
+ }
55
+
56
+ setupEmpty.OPTIONS = OPTIONS;
57
+
58
+ export { setupEmpty };
59
+
@@ -0,0 +1,209 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupFxSmoke — Emergent smoke experiment via voxel size-up ladder.
5
+ *
6
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
7
+ *
8
+ * @param {Object} universe - The engine universe instance (JS or WASM compat shim),
9
+ * not yet init'd. `universe.init` is called internally.
10
+ * @param {Object} [options] - Configuration overrides.
11
+ * @param {number} [options.velocity=1]
12
+ * Rightward emission velocity (px/tick). Units: pixels/tick.
13
+ * @param {number} [options.fundamentalWaveLength=64]
14
+ * Desired wavelength for emitted quanta. Units: pixels.
15
+ * Drives `wavelengthScale = max(1, floor(λ/4))`.
16
+ * @param {number} [options.burstSize=20]
17
+ * Quanta emitted per tick from the beam source. Units: quanta/tick.
18
+ * @param {number} [options.billowDimensions=3]
19
+ * Number of extra aggregation dimensions to register (voxel size-up ladder).
20
+ * dim0 = 1 px (fundamental leaf), dim+1 = 4 px, dim+2 = 8 px, dim+3 = 16 px.
21
+ * Each dimension represents bigger puffs once quanta aggregate into parent frames.
22
+ * Range: 1–4. Higher values produce larger maximum puff sizes but require more
23
+ * collision events to reach the top level.
24
+ * @param {boolean} [options.brownianMotion=true]
25
+ * Enable Brownian (random) perturbation on each frame per tick. Keeps the beam
26
+ * from being perfectly laminar — essential for inter-quantum collision frequency.
27
+ * @param {number} [options.bulletSpeed=2]
28
+ * Speed of the moving Bullet occluder (px/tick). When > 0 a Bullet is placed at
29
+ * the beam centre and advances rightward; its BOUNCE collision compresses quanta
30
+ * in the wake, driving aggregation into bigger frames (the smoke billow effect).
31
+ * Set to 0 to disable the bullet (pure-beam smoke, no projectile).
32
+ * @param {string} [options.bulletCollision='BOUNCE']
33
+ * CollisionTypes value for the Bullet. BOUNCE deflects quanta; WALL stops them
34
+ * dead (sharper void). NONE makes the bullet a ghost (visual only, no physics).
35
+ *
36
+ * @returns {{
37
+ * emitter: QuantumEmitter,
38
+ * bullet: Bullet|null,
39
+ * largeObjects: LargeObject[]
40
+ * }}
41
+ * bullet is null when options.bulletSpeed === 0.
42
+ *
43
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
44
+ *
45
+ * The smoke billow is EMERGENT PHYSICS, not a sprite trick. It mirrors the
46
+ * Blackbody aggregation mechanic (lib/frqtl/scenes/blackbody.js):
47
+ *
48
+ * universe.withInterQuantumCollisions = true
49
+ * → colliding quanta PARENT into compound frames (merge/promote pipeline)
50
+ *
51
+ * addDimension(layer) for each size-up step
52
+ * → the engine's Phase B (STRUCTURE) builds higher-dimension frames from
53
+ * fundamental-quadtree collisions, growing compound frames with larger rect.w
54
+ * (2× per level: 1 px → 4 px → 8 px → 16 px)
55
+ *
56
+ * Bullet (BOUNCE) advancing rightward
57
+ * → compresses quanta in the beam's wake → higher local density
58
+ * → more inter-quantum collisions → faster aggregation up the size ladder
59
+ * → renderer reads each frame's rect.w and sizes the smoke puff accordingly
60
+ * → result: varied puff sizes, denser puffs in the wake → billowing smoke
61
+ *
62
+ * With no bullet (bulletSpeed=0), the beam still aggregates but more slowly —
63
+ * you get a smooth column of growing puffs rather than a directional billow.
64
+ *
65
+ * With bullet (bulletSpeed>0) the wakes produce large aggregated frames whose
66
+ * rect.w drives the renderer's `aSize` attribute (three-provider.js) → larger,
67
+ * softer puff sprites. Faster bullet = denser wake = bigger emergent frames.
68
+ *
69
+ * OBSERVED effect (design intent — realm verification by orchestrator):
70
+ * fx-smoke: at tick ~200 puffs range from 1 px (single leaf) to 8–16 px
71
+ * (aggregated compound frames). The bullet wake region shows higher density
72
+ * and larger puff sizes than the undisturbed beam. Adjusting the `puff` slider
73
+ * (render control, three-provider) scales the sprite size for each puff — the
74
+ * variance in sizes is physics, not the slider. Figure TBD (realm verification).
75
+ */
76
+
77
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
78
+ import { QuantumEmitter } from '../objects/QuantumEmitter.js';
79
+ import { Bullet } from '../objects/Bullet.js';
80
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
81
+
82
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
83
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
84
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
85
+
86
+ // ── Imports ─────────────────────────────────────────────────────────────
87
+
88
+ // ── Voxel-ladder dimension levels ────────────────────────────────────────
89
+ //
90
+ // The JS reference engine uses integer layer numbers; addDimension(n) registers
91
+ // a new quadtree level at scale n. The WASM engine's add_dimension() mirrors this.
92
+ // Blackbody uses fundamental_dim=0 + quantum_dim=4 (one extra level).
93
+ //
94
+ // For the smoke ladder we want multiple extra levels so frames can aggregate into
95
+ // progressively larger puffs. The layer numbers determine the relative cell size:
96
+ // layer 0 = fundamental (1 px leaf quanta)
97
+ // layer 4 = first aggregation level (cell ≈ 4 px)
98
+ // layer 8 = second aggregation level (cell ≈ 8 px)
99
+ // layer 12 = third aggregation level (cell ≈ 16 px)
100
+ //
101
+ // These follow the same convention as Blackbody (quantum_dim=4).
102
+ var BILLOW_DIMS = [4, 8, 12]; // up to 3 extra levels; trimmed by billowDimensions option
103
+
104
+ // ── Setup factory ────────────────────────────────────────────────────────
105
+ /**
106
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
107
+ * Exported as `setupFxSmoke.OPTIONS` so a composing caller consumes it instead of
108
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
109
+ * read-set by static trace and fails if this list drifts from the code.
110
+ */
111
+ var OPTIONS = [
112
+ 'billowDimensions', 'brownianMotion', 'bulletCollision', 'bulletSpeed',
113
+ 'burstSize', 'fundamentalWaveLength', 'velocity',
114
+ ];
115
+
116
+ function setupFxSmoke(universe, options) {
117
+ options = _assertSetupOptions('setupFxSmoke', options, OPTIONS);
118
+
119
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
120
+ var canvas_rect = universe.universe_rect;
121
+ _requireWorldRect('setupFxSmoke universe.universe_rect', canvas_rect);
122
+
123
+ var desiredWaveLength = options.fundamentalWaveLength !== undefined ? options.fundamentalWaveLength : 64;
124
+ var velocity = options.velocity !== undefined ? options.velocity : 1;
125
+ var burstSize = options.burstSize !== undefined ? options.burstSize : 20;
126
+ var billowDimensions = options.billowDimensions !== undefined ? options.billowDimensions : 3;
127
+ var useBrownian = options.brownianMotion !== undefined ? options.brownianMotion : true;
128
+ var bulletSpeed = options.bulletSpeed !== undefined ? options.bulletSpeed : 2;
129
+ var bulletCollision = options.bulletCollision !== undefined ? options.bulletCollision : CollisionTypes.BOUNCE;
130
+
131
+ var waveLengthMultiplier = Math.max(1, Math.floor(desiredWaveLength / 4));
132
+
133
+ // ── Universe init ─────────────────────────────────────────────────────
134
+ // fundamental_dim=0 — leaf quanta are 1-px frames.
135
+ // withInterQuantumCollisions = true — inter-quantum collisions drive the
136
+ // merge/promote pipeline (as lib/frqtl/scenes/blackbody.js sets it).
137
+ universe.init(0, {
138
+ wavelengthScale: waveLengthMultiplier,
139
+ brownianMotion: false,
140
+ activeAxes: { x: true, y: true, z: true }
141
+ });
142
+
143
+ // Brownian motion keeps the beam from laminar flow → more collisions.
144
+ universe.brownianMotion = useBrownian;
145
+
146
+ // The Blackbody aggregation mechanic: colliding quanta parent up into
147
+ // compound frames that occupy progressively larger cells in the quadtree.
148
+ universe.withInterQuantumCollisions = true;
149
+
150
+ // Register the voxel size-up ladder — a subset of BILLOW_DIMS trimmed to
151
+ // billowDimensions entries (max 3 extra levels above fundamental dim=0).
152
+ var dimsToAdd = Math.max(1, Math.min(billowDimensions, BILLOW_DIMS.length));
153
+ for (var i = 0; i < dimsToAdd; i++) {
154
+ universe.addDimension(BILLOW_DIMS[i]);
155
+ }
156
+
157
+ // ── Emitter ───────────────────────────────────────────────────────────
158
+ // Beam source at canvas centre, firing rightward (3D walk).
159
+ // The z-axis yo-yo kicks spread the beam into depth, giving the smoke
160
+ // a 3D volumetric appearance when viewed from an orbit angle.
161
+ var cx = Math.floor(canvas_rect.w / 2);
162
+ var cy = Math.floor(canvas_rect.h / 2);
163
+ var sourceHeight = 10;
164
+
165
+ var emitter = new QuantumEmitter(
166
+ universe,
167
+ [[cx, cy - Math.floor(sourceHeight / 2), 1, sourceHeight]],
168
+ velocity, 0,
169
+ burstSize,
170
+ 1, // charge = NEGATIVE
171
+ true, // auto_fire
172
+ false, // not random_direction
173
+ Infinity, // quantum_limit
174
+ 0, // delay
175
+ false // wrap = false
176
+ );
177
+
178
+ // ── Bullet (optional) ─────────────────────────────────────────────────
179
+ // A small BOUNCE LO placed at the beam source, advancing rightward.
180
+ // Its moving wake compresses quanta → more inter-quantum collisions →
181
+ // aggregation into bigger frames → billowing smoke puffs (Effect C per design).
182
+ var bullet = null;
183
+ if (bulletSpeed > 0 && Bullet) {
184
+ bullet = new Bullet(universe, {
185
+ x: cx,
186
+ y: cy - 4,
187
+ w: 8,
188
+ h: 8,
189
+ speed: bulletSpeed,
190
+ collisionType: bulletCollision,
191
+ color: [40, 40, 40],
192
+ name: 'smoke_bullet',
193
+ loop: true
194
+ });
195
+ }
196
+
197
+ return {
198
+ emitter: emitter,
199
+ bullet: bullet,
200
+ largeObjects: bullet ? [bullet.lo] : []
201
+ };
202
+ }
203
+
204
+ // ── Export ───────────────────────────────────────────────────────────────
205
+
206
+ setupFxSmoke.OPTIONS = OPTIONS;
207
+
208
+ export { setupFxSmoke };
209
+
@@ -0,0 +1,80 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupG1Race — UNIT-TEST experiment G1: "LO (a Frame) races a Dim-0 quantum".
5
+ *
6
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
7
+ * @param {Object} universe - Engine universe (WASM; this experiment is wasmOnly).
8
+ * @param {Object} [opts]
9
+ * @param {number} [opts.speedProb=0.5] - Initial speed probability for the "LO" frame.
10
+ * @returns {{ loHandle, loQid, quantumHandle, startX, speedProb }}
11
+ * loQid — the QUANTUM_ID of the LO frame (use with setFrameProperty — NOT loHandle).
12
+ *
13
+ * ── Layer 2: physics behavior (what this PROVES) ─────────────────────────────
14
+ * The realization of "an LO extends Frame": both racers are LEAF FRAMES moving
15
+ * rightward (vx=1), wrapping the canvas. The reference is a Dim-0 "light" quantum —
16
+ * a fundamental particle that ALWAYS steps (P=1, the speed limit). The "LO" is a
17
+ * larger Dim-2 leaf (4px voxel) that takes its along-momentum step with probability
18
+ * = its `speed_prob` (default 0.5) — so it falls behind the light at a rate set by
19
+ * that probability (E=MC²: a fuller/heavier frame's children have fewer free pixels,
20
+ * so more of a push converts to motion; here the user sets the probability directly).
21
+ * The orthogonal wavelength (transverse jitter) is unchanged — only the forward rate
22
+ * is gated. Sweeping speed_prob 1.0 → 0 sweeps the LO from light-speed to frozen.
23
+ */
24
+
25
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
26
+
27
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
28
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
29
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
30
+
31
+ /**
32
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
33
+ * Exported as `setupG1Race.OPTIONS` so a composing caller consumes it instead of
34
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
35
+ * read-set by static trace and fails if this list drifts from the code.
36
+ */
37
+ var OPTIONS = [
38
+ 'loLevel', 'speedProb',
39
+ ];
40
+
41
+ function setupG1Race(universe, opts) {
42
+ opts = _assertSetupOptions('setupG1Race', opts, OPTIONS);
43
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
44
+ var rect = universe.universe_rect;
45
+ var ch = _requireWorldRect('setupG1Race universe.universe_rect', rect).h;
46
+ var startX = 30;
47
+ var speedProb = (opts.speedProb !== undefined) ? opts.speedProb : 0.5;
48
+
49
+ // 2D walk: a gentle transverse wavelength so the racers jitter sideways a little
50
+ // while the LONGITUDINAL race (the speed_prob gate) is the visible story.
51
+ universe.init(0, { wavelengthScale: 16, brownianMotion: false });
52
+
53
+ // Register Dim 6 so the "LO" can be a 64px voxel/frame there — a big body that
54
+ // renders clearly as a cube in 3D. The leaf still moves under the move pass; the
55
+ // gate applies. (mass fullness has a color interpretation via LightColor/Chroma.)
56
+ var loLevel = (opts.loLevel !== undefined) ? opts.loLevel : 6; // 2^6 = 64px voxel
57
+ universe.addDimension(loLevel);
58
+
59
+ // Reference: a Dim-0 LIGHT quantum (always P=1), blue, wrapping.
60
+ var quantumHandle = universe.makeLeafFrame(
61
+ startX, Math.floor(ch * 0.32), 0, null, 1, 0, true, 0, [120, 200, 255]);
62
+ universe.injectFrame(quantumHandle);
63
+
64
+ // The "LO": a Dim-6 leaf (64px voxel), gold, wrapping — moves with a speed probability.
65
+ var loHandle = universe.makeLeafFrame(
66
+ startX, Math.floor(ch * 0.62), loLevel, null, 1, 0, true, 0, [255, 180, 80]);
67
+ universe.injectFrame(loHandle);
68
+
69
+ // set/getFrameProperty key on the QUANTUM_ID, not the makeLeafFrame handle.
70
+ var loQid = universe.getFrameQuantumId(loHandle);
71
+ universe.setFrameProperty(loQid, 'speed_prob', speedProb);
72
+
73
+ return { loHandle: loHandle, loQid: loQid, quantumHandle: quantumHandle,
74
+ startX: startX, speedProb: speedProb };
75
+ }
76
+
77
+ setupG1Race.OPTIONS = OPTIONS;
78
+
79
+ export { setupG1Race };
80
+
@@ -0,0 +1,89 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupG2Sweep — UNIT-TEST experiment G2: a MOVING LargeObject (LO extends Frame)
5
+ * sweeps a field of stationary targets and its collision trigger ABSORBS them.
6
+ *
7
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
8
+ * @param {Object} universe - WASM universe (this experiment is wasmOnly).
9
+ * @param {Object} [opts]
10
+ * @param {number} [opts.speedProb=0.6] - Speed probability of the LO's backing frame.
11
+ * @returns {{ lo, bodyHandle, bodyQid, targets:number, searchW:number }}
12
+ *
13
+ * ── Layer 2: physics behavior (what this PROVES) ─────────────────────────────
14
+ * G2 = "an LO extends Frame" by COMPOSITION. A `LargeObject` is linked to a BACKING
15
+ * FRAME (a moving leaf) via setLargeObjectFrame; the engine re-centers the LO's search
16
+ * rect on the frame every tick, so the LO MOVES via the normal move pass + speed_prob
17
+ * gate (G1). Its search rect (searchW) is LARGER than the 1px body — collisions fire
18
+ * over the rect, not the frame. The LO's `on:'collision'` op-program (ABSORB here)
19
+ * runs across its observed dimensions. So the LO both moves like a Frame AND triggers
20
+ * multi-dimensionally — with the backing frame excluded from its own trigger.
21
+ */
22
+
23
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
24
+ import { LargeObject } from '../../parts/LargeObject.js';
25
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
26
+
27
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
28
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
29
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
30
+
31
+ /**
32
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
33
+ * Exported as `setupG2Sweep.OPTIONS` so a composing caller consumes it instead of
34
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
35
+ * read-set by static trace and fails if this list drifts from the code.
36
+ */
37
+ var OPTIONS = [
38
+ 'speedProb',
39
+ ];
40
+
41
+ function setupG2Sweep(universe, opts) {
42
+ opts = _assertSetupOptions('setupG2Sweep', opts, OPTIONS);
43
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
44
+ var rect = universe.universe_rect;
45
+ var world = _requireWorldRect('setupG2Sweep universe.universe_rect', rect);
46
+ var cw = world.w, ch = world.h;
47
+ var speedProb = (opts.speedProb !== undefined) ? opts.speedProb : 0.6;
48
+
49
+ universe.init(0, { wavelengthScale: 1, brownianMotion: false });
50
+
51
+ // ── Target field: a grid of STATIONARY quanta the LO will sweep through. ──
52
+ var targets = 0;
53
+ for (var gx = Math.floor(cw * 0.28); gx <= Math.floor(cw * 0.82); gx += 36) {
54
+ for (var gy = Math.floor(ch * 0.22); gy <= Math.floor(ch * 0.78); gy += 36) {
55
+ var th = universe.makeLeafFrame(gx, gy, 0, null, 0, 0, false, 0, [90, 150, 220]);
56
+ universe.injectFrame(th);
57
+ universe.setFrameProperty(universe.getFrameQuantumId(th), 'is_stationary', true);
58
+ targets++;
59
+ }
60
+ }
61
+
62
+ // ── LO BODY: a leaf moving rightward (speed_prob gated), mid-height. ──
63
+ var bodyY = Math.floor(ch * 0.5);
64
+ var bodyHandle = universe.makeLeafFrame(20, bodyY, 0, null, 1, 0, false, 0, [255, 120, 80]);
65
+ universe.injectFrame(bodyHandle);
66
+ var bodyQid = universe.getFrameQuantumId(bodyHandle);
67
+ universe.setFrameProperty(bodyQid, 'speed_prob', speedProb);
68
+
69
+ // ── The LO: a TALL search rect (covers the field height) + ABSORB trigger. ──
70
+ // Its rect (searchW × searchH) is far larger than the 1px body — the collision
71
+ // fires over the rect. ABSORB kills any (non-body) frame inside it.
72
+ var searchW = 40, searchH = Math.floor(ch * 0.72);
73
+ var lo = new LargeObject(
74
+ universe,
75
+ [20 - searchW / 2, bodyY - searchH / 2, searchW, searchH],
76
+ CollisionTypes.NONE, ['ABSORB'],
77
+ 'g2_sweeper', 'closest', null, 1, [255, 120, 80]
78
+ );
79
+ // Link the LO to its backing frame → it MOVES with the body each tick.
80
+ universe.setLargeObjectFrame(lo.handle, bodyHandle);
81
+
82
+ return { lo: lo, bodyHandle: bodyHandle, bodyQid: bodyQid,
83
+ targets: targets, searchW: searchW, searchH: searchH, bodyY: bodyY };
84
+ }
85
+
86
+ setupG2Sweep.OPTIONS = OPTIONS;
87
+
88
+ export { setupG2Sweep };
89
+