@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,628 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SlitScreen — Engine-agnostic double-slit barrier.
5
+ *
6
+ * 2D (depth omitted): a wall with two slits made of LargeObjects:
7
+ * - 3 wall segments (wall1 above slit1, wall2 between slits, wall3 below slit2)
8
+ * - 2 slit segments (slit1, slit2) with optional detection
9
+ * - 4 slit liners (top/bottom of each slit) for edge diffraction
10
+ *
11
+ * 3D (depth set — WS-DSE-3D CEO geometry amendment, 2026-07-12, "floor to ceiling
12
+ * with cubic holes"): the screen spans the FULL universe cross-section — the SAME
13
+ * floor-to-ceiling y-extent as 2D (unchanged: wall1 starts at y=0, wall3 ends at
14
+ * canvasHeight) PLUS the full z-depth (the fix-loop item-1 render/collision fix) —
15
+ * but the two apertures are now CUBIC HOLES (bounded in BOTH y and z), not full-
16
+ * depth-in-z SLOTS. Built by TILING the wall around each hole: a wall1/wall2/wall3
17
+ * slab spans the full z-depth (no hole in those y-bands), and each hole's own
18
+ * y-band is split into THREE z-tiles — a solid "_near" side (z < holeZmin), the
19
+ * hole itself (z ∈ [holeZmin, holeZmax), still named `slit1`/`slit2` — detection
20
+ * identity preserved), and a solid "_far" side (z > holeZmax). AABB-only — this
21
+ * is exactly why round holes were rejected (§4.1) and cubic holes were ratified:
22
+ * a box aperture is just MORE 6-element rects, zero new collision machinery.
23
+ *
24
+ * Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
25
+ */
26
+
27
+ import { LargeObject } from './LargeObject.js';
28
+ import { CollisionTypes } from './CollisionTypes.js';
29
+ import { CollisionActions } from './CollisionActions.js';
30
+ import * as SlitLinerScatter from '../objects/objects/slitLinerScatter.js';
31
+ import { Wall } from './Wall.js';
32
+ import { registerKind } from '../objects/kinds.js';
33
+ import { ObjectSchema } from '../objects/schema/index.js';
34
+
35
+ // ── Imports (Node.js) or globals (browser) ──────────────────────────
36
+
37
+ /**
38
+ * DSE LAB / EXPERIMENT LOOP / MATRIX EXTENSION liner scatter — delegates
39
+ * ENTIRELY to `slitLinerScatter.js`, the ONE authority both this constructor
40
+ * (legacy `linerScatter` string, construction-time) AND the `dse-lab` page
41
+ * (LIVE, explicit `{behavior, resetLocalTime}`) push through. See that
42
+ * module's docstring for the full behavior/reset decomposition and the
43
+ * ground-truth physics finding behind the variants.
44
+ */
45
+ function _applyLinerScatter(linerLO, variant, universe, intoCavityDir) {
46
+ SlitLinerScatter.applyLinerScatter(linerLO, universe, intoCavityDir, { slitScatter: variant });
47
+ }
48
+
49
+ // ── SlitScreen ────────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Double-slit barrier with edge diffraction liners.
53
+ *
54
+ * 2D: creates five LargeObjects (three wall segments + two slit openings) plus
55
+ * four slit edge liners (9 total, `this.walls.length===9`). 3D (`depth` set):
56
+ * each slit opening becomes a CUBIC HOLE tiled from THREE z-pieces
57
+ * (near-side/hole/far-side), so the wall segment count grows to nine core
58
+ * pieces (wall1, slit1_near/slit1/slit1_far, wall2, slit2_near/slit2/slit2_far,
59
+ * wall3) plus the same four liners (13 total, `this.walls.length===13`) — see
60
+ * the module docstring above. The liners apply `RANDOM_XY` to particles at the
61
+ * very edge of each hole, which produces edge diffraction — a directional
62
+ * scatter that contributes to the emergent interference pattern.
63
+ *
64
+ * ## Physics you can drive
65
+ *
66
+ * **`wavelength`** drives the slit geometry. The default formulas are:
67
+ * ```
68
+ * slitHeight = floor(wavelength * 0.8) — height of each slit opening
69
+ * gapHeight = floor(wavelength * 0.8) — height of the wall between the two slits
70
+ * linerHeight = min(10, floor(slitHeight * 0.2)) — edge diffraction liner depth
71
+ * ```
72
+ * A larger `wavelength` gives taller slits and a wider inter-slit gap, allowing
73
+ * more particles through per tick and increasing the effective slit separation `d`
74
+ * in the interference formula. Pass explicit `slitHeight` / `gapHeight` to decouple
75
+ * the two dimensions independently (e.g. fixed slit size but varying gap).
76
+ *
77
+ * **`detectAtSlits`** sets the measurement mode at each slit opening:
78
+ * - `0` — no detection; slit objects are transparent (collision type `NONE`,
79
+ * action `MARK` only)
80
+ * - `1` — detect + scatter: collision type `RANDOM_XY` is applied, so the engine
81
+ * randomly deflects particles that pass through. This is the canonical
82
+ * "which-slit" measurement — detection disturbs the trajectory.
83
+ * - `2` — detect + color only: action `DETECT` is added and the slit objects are
84
+ * colored blue for visualization, but collision type stays `NONE` so trajectories
85
+ * are not disturbed.
86
+ *
87
+ * **`absorbAtSlitWall`**: when true the three wall segments silently absorb any
88
+ * particle that hits them (`ABSORB` action). When false the walls block and reflect
89
+ * particles (`BLOCK` action). Absorption is the default — it prevents reflected
90
+ * particles from re-entering the beam and contaminating the fringe signal.
91
+ *
92
+ * **`this.slit_wall_gap_height`** — the inter-slit gap `d` in the interference
93
+ * formula. Read this field on the returned `SlitScreen` instance to get the
94
+ * actual pixel distance between the two slit centers.
95
+ *
96
+ * @param {Object} universe - The engine universe instance (JS or WASM).
97
+ * @param {number} wallLeft - X position (left edge) of the slit screen (physics px).
98
+ * @param {number} wallWidth - Thickness of the slit screen (physics px).
99
+ * @param {number} canvasHeight - Full height of the simulation area (physics px).
100
+ * @param {number} wavelength - Geometry master knob: drives slit height, gap height,
101
+ * and liner depth (see formulas above).
102
+ * @param {boolean} absorbAtSlitWall - If true, wall segments absorb particles
103
+ * (ABSORB action); if false, they block/reflect (BLOCK action).
104
+ * @param {number} detectAtSlits - Measurement mode at slit openings:
105
+ * 0=none, 1=detect+scatter (RANDOM_XY), 2=detect+color only.
106
+ * @param {number} [slitHeight] - Override slit opening height
107
+ * (default: `floor(wavelength * 0.8)`).
108
+ * @param {number} [gapHeight] - Override inter-slit wall height
109
+ * (default: `floor(wavelength * 0.8)`).
110
+ * @param {number} [depth] - WS-DSE-3D: when provided, the screen becomes a 3D
111
+ * floor-to-ceiling wall (full z-depth `[-depth/2, +depth/2]`) tiled around two
112
+ * CUBIC HOLE apertures (CEO geometry amendment, 2026-07-12 — supersedes the
113
+ * Bite B1 full-depth-SLOT construction). `wall1`/`wall2`/`wall3` (no hole in
114
+ * their y-band) become 6-element rects spanning the FULL z-depth; `slit1`/
115
+ * `slit2` (still the detection identity) become 6-element rects bounded in
116
+ * BOTH y (unchanged from 2D) AND z (`holeDepth`, centered on z=0) — plus two
117
+ * new solid side tiles per hole (`slit1_near`/`slit1_far`, `slit2_near`/
118
+ * `slit2_far`) filling the rest of that y-band's z-span. OMITTED (default) →
119
+ * every rect stays the LEGACY 4-element `[left,top,w,h]` array, byte-identical
120
+ * to pre-B1/pre-amendment 2D behavior (no new code path executed at all).
121
+ * @param {number} [holeDepth] - 3D only: the z-extent of each cubic hole
122
+ * (centered on z=0, i.e. `[-holeDepth/2, +holeDepth/2)`). Default: the
123
+ * EFFECTIVE `slit_height` (respects a `slitHeight` override), so the default
124
+ * aperture is square-ish in the y×z plane (e.g. wavelength=64 → slit_height=51
125
+ * → a 51×51 hole) — state-your-choice per the CEO's ask; pass an explicit
126
+ * value to decouple the hole's z-extent from its y-extent. Ignored when
127
+ * `depth` is omitted.
128
+ * @param {'current'|'noreset'|'symmetric'|'combined'|'bounce'|'ejectY'|'ejectYReflect'} [linerScatter='current']
129
+ * Legacy compound scatter-variant string — resolved by `slitLinerScatter.js`'s
130
+ * `LEGACY_VARIANT_MAP` (the ONE authority for the behavior×resetLocalTime
131
+ * decomposition; see that module's docstring). `'current'` (default, OMIT to
132
+ * keep it) is the no-op sentinel — today's exact compiled `RANDOM_XY`,
133
+ * byte-identical, the CEO-ratified 2D golden depends on it. The other
134
+ * variants push a composed replacement op-program LIVE via
135
+ * `universe.updateObjectProperty` — the SAME mechanism the `dse-lab` page's
136
+ * LIVE `behavior`/`resetLocalTime` controls use (DSE LAB, 2026-07-13).
137
+ */
138
+ function SlitScreen(universe, wallLeft, wallWidth, canvasHeight, wavelength, absorbAtSlitWall, detectAtSlits, slitHeight, gapHeight, depth, holeDepth, linerScatter) {
139
+ this.universe = universe;
140
+ this._schemaKind = 'SlitScreen';
141
+ this.wavelength = wavelength;
142
+ this.depth = (depth !== undefined && depth !== null) ? depth : null;
143
+ // 'current' (default/omitted) = today's exact liner behavior, byte-identical.
144
+ // See slitLinerScatter.js's LEGACY_VARIANT_MAP for the full decomposition.
145
+ this.linerScatter = linerScatter || 'current';
146
+
147
+ // ── Geometry (y-axis) — UNCHANGED by the cubic-hole amendment in EITHER
148
+ // mode: the CEO's "floor to ceiling" ask was already satisfied here (wall1
149
+ // starts at y=0, wall3 ends at canvasHeight — confirmed by ground-truth,
150
+ // fix-loop item 1); only the APERTURE shape changes in 3D (§ below).
151
+ this.slit_height = (slitHeight !== undefined) ? slitHeight : Math.floor(wavelength * 0.8);
152
+ this.slit_wall_gap_height = (gapHeight !== undefined) ? gapHeight : Math.floor(wavelength * 0.8);
153
+ this.slit_group_height = this.slit_height + this.slit_wall_gap_height + this.slit_height;
154
+ this.slit_group_center_y = canvasHeight / 2;
155
+ this.slit1_top = this.slit_group_center_y - this.slit_group_height / 2;
156
+ this.slit_wall_gap_top = this.slit1_top + this.slit_height;
157
+ this.slit2_bottom = this.slit_group_center_y + this.slit_group_height / 2;
158
+ this.slit2_top = this.slit2_bottom - this.slit_height;
159
+ this.slit_group_left = wallLeft;
160
+ this.slit_width = wallWidth;
161
+ this.bottom_wall_height = canvasHeight - this.slit2_bottom;
162
+
163
+ // WS-DSE-3D CEO geometry amendment (2026-07-12): cubic-hole z-extent, centered
164
+ // on z=0. Default = the EFFECTIVE slit_height (square-ish aperture at the
165
+ // default wavelength=64 -> slit_height=51 -> a 51x51 hole) — overridable to
166
+ // decouple the hole's z-extent from its y-extent. 2D-inert (unused).
167
+ this.hole_depth = (holeDepth !== undefined && holeDepth !== null) ? holeDepth : this.slit_height;
168
+
169
+ // ── Collision config ─────────────────────────────────────────────
170
+ var wall_collision_actions = absorbAtSlitWall
171
+ ? [CollisionActions.ABSORB]
172
+ : [CollisionActions.BLOCK];
173
+ var wall_collision_type = CollisionTypes.NONE;
174
+
175
+ // The three plain wall SEGMENTS (wall1/wall2/wall3) are built through the unified
176
+ // Wall primitive. `destroy`/`block` compile the BYTE-IDENTICAL op-programs these
177
+ // segments produced under the legacy `absorb`/`block` vocabulary (verified
178
+ // program-for-program), so the port moves ZERO physics. The SLITS (MARK/RANDOM_XY
179
+ // detectors) and LINERS (RANDOM_XY scatter) are NOT plain walls — they stay
180
+ // LargeObjects. `_wallLO(rect,name,color)` returns the Wall's inner engine LO so
181
+ // `this.walls` keeps holding LargeObjects (downstream — grouping, updateGapHeight,
182
+ // the runner auto-wire — is untouched).
183
+ var _segmentBehavior = absorbAtSlitWall ? 'destroy' : 'block';
184
+ var _wallUniverse = universe;
185
+ function _wallLO(rect, name, color) {
186
+ return new Wall(_wallUniverse, { rect: rect, collisionBehavior: _segmentBehavior,
187
+ name: name, color: color }).lo;
188
+ }
189
+
190
+ var detector_actions = [CollisionActions.MARK];
191
+ var detector_type = CollisionTypes.NONE;
192
+ var slit1_color = [0, 0, 0, 0];
193
+ var slit2_color = [0, 0, 0, 0];
194
+
195
+ if (detectAtSlits === 1) {
196
+ detector_actions.push(CollisionActions.DETECT);
197
+ detector_type = CollisionTypes.RANDOM_XY;
198
+ }
199
+ if (detectAtSlits === 2) {
200
+ detector_actions.push(CollisionActions.DETECT);
201
+ slit1_color = [0, 0, 255];
202
+ slit2_color = [0, 0, 255];
203
+ }
204
+
205
+ var liner_height = Math.min(10, Math.floor(this.slit_height * 0.2));
206
+ var _depth = this.depth;
207
+
208
+ if (_depth === null) {
209
+ // ═══ 2D — EXACT pre-amendment construction, byte-identical ═══
210
+ // (rect3 here is a plain pass-through; kept as a named local so this
211
+ // branch reads identically to the historical code it replaces.)
212
+ function rect3(left, top, w, h) { return [left, top, w, h]; }
213
+
214
+ // Wall above slit1
215
+ this.wall1 = _wallLO(
216
+ rect3(this.slit_group_left, 0, this.slit_width, this.slit1_top - 1),
217
+ 'wall1', [255, 0, 0]
218
+ );
219
+
220
+ // Slit 1 (2D: a plain y-only slot, unchanged)
221
+ this.slit1 = new LargeObject(
222
+ universe,
223
+ rect3(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1),
224
+ detector_type, detector_actions, 'slit1',
225
+ 'closest', null, 1, slit1_color
226
+ );
227
+
228
+ // Wall between slits
229
+ this.wall2 = _wallLO(
230
+ rect3(this.slit_group_left, this.slit_wall_gap_top, this.slit_width, this.slit_wall_gap_height - 1),
231
+ 'wall2', [128, 0, 0]
232
+ );
233
+
234
+ // Slit 2
235
+ this.slit2 = new LargeObject(
236
+ universe,
237
+ rect3(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1),
238
+ detector_type, detector_actions, 'slit2',
239
+ 'closest', null, 1, slit2_color
240
+ );
241
+
242
+ // Wall below slit2
243
+ this.wall3 = _wallLO(
244
+ rect3(this.slit_group_left, this.slit2_bottom, this.slit_width, this.bottom_wall_height - 1),
245
+ 'wall3', [255, 0, 0]
246
+ );
247
+
248
+ // ── Slit liners (edge diffraction) ───────────────────────────
249
+ var slit1_liner_top = new LargeObject(
250
+ universe,
251
+ rect3(this.slit_group_left, this.slit1_top, this.slit_width, liner_height),
252
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_top',
253
+ 'closest', null, 1, [0, 0, 255]
254
+ );
255
+ _applyLinerScatter(slit1_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
256
+
257
+ var slit1_liner_bottom = new LargeObject(
258
+ universe,
259
+ rect3(this.slit_group_left, this.slit1_top + this.slit_height - liner_height, this.slit_width, liner_height),
260
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_bottom',
261
+ 'closest', null, 1, [0, 0, 255]
262
+ );
263
+ _applyLinerScatter(slit1_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
264
+
265
+ var slit2_liner_top = new LargeObject(
266
+ universe,
267
+ rect3(this.slit_group_left, this.slit2_top, this.slit_width, liner_height),
268
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_top',
269
+ 'closest', null, 1, [0, 0, 255]
270
+ );
271
+ _applyLinerScatter(slit2_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
272
+
273
+ var slit2_liner_bottom = new LargeObject(
274
+ universe,
275
+ rect3(this.slit_group_left, this.slit2_top + this.slit_height - liner_height, this.slit_width, liner_height),
276
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_bottom',
277
+ 'closest', null, 1, [0, 0, 255]
278
+ );
279
+ _applyLinerScatter(slit2_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
280
+
281
+ this.walls = [
282
+ this.wall1, this.slit1, this.wall2, this.slit2, this.wall3,
283
+ slit1_liner_top, slit1_liner_bottom,
284
+ slit2_liner_top, slit2_liner_bottom
285
+ ];
286
+
287
+ } else {
288
+ // ═══ 3D — CUBIC HOLES (CEO geometry amendment, 2026-07-12) ═══
289
+ // Tile the wall around each hole: wall1/wall2/wall3 (no hole in their
290
+ // y-band) span the FULL z-depth; each hole's own y-band splits into
291
+ // THREE z-tiles — a solid "_near" side (z < holeZmin), the hole itself
292
+ // (still named slit1/slit2 — detection identity preserved, now bounded
293
+ // in z too), and a solid "_far" side (z > holeZmax). Exact z-partition,
294
+ // no gaps/overlaps: near.z=[zLo,holeZlo), hole.z=[holeZlo,holeZhi),
295
+ // far.z=[holeZhi,zLo+depth) — verified by slit-family-depth.test.mjs.
296
+ var zLo = -_depth / 2; // full screen z-min
297
+ var holeZlo = -this.hole_depth / 2; // hole z-min (centered)
298
+ var holeZspan = this.hole_depth;
299
+ var farZlo = holeZlo + holeZspan; // = +hole_depth/2
300
+ var nearZspan = holeZlo - zLo; // z < holeZmin side-tile depth
301
+ var farZspan = (_depth / 2) - farZlo; // z > holeZmax side-tile depth
302
+
303
+ function zSlab(left, top, w, h) { return [left, top, zLo, w, h, _depth]; }
304
+ function zBand(left, top, w, h, zb, zs) { return [left, top, zb, w, h, zs]; }
305
+
306
+ // Wall above slit1 (no hole in this y-band -> full z-depth)
307
+ this.wall1 = _wallLO(
308
+ zSlab(this.slit_group_left, 0, this.slit_width, this.slit1_top - 1),
309
+ 'wall1', [255, 0, 0]
310
+ );
311
+
312
+ // Hole 1's y-band, split into near/hole/far along z.
313
+ this.slit1_near = new LargeObject(
314
+ universe,
315
+ zBand(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1, zLo, nearZspan),
316
+ CollisionTypes.NONE, wall_collision_actions, 'slit1_near',
317
+ 'closest', null, 1, [200, 60, 60]
318
+ );
319
+ this.slit1 = new LargeObject(
320
+ universe,
321
+ zBand(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1, holeZlo, holeZspan),
322
+ detector_type, detector_actions, 'slit1',
323
+ 'closest', null, 1, slit1_color
324
+ );
325
+ this.slit1_far = new LargeObject(
326
+ universe,
327
+ zBand(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1, farZlo, farZspan),
328
+ CollisionTypes.NONE, wall_collision_actions, 'slit1_far',
329
+ 'closest', null, 1, [200, 60, 60]
330
+ );
331
+
332
+ // Wall between slits (no hole in this y-band -> full z-depth)
333
+ this.wall2 = _wallLO(
334
+ zSlab(this.slit_group_left, this.slit_wall_gap_top, this.slit_width, this.slit_wall_gap_height - 1),
335
+ 'wall2', [128, 0, 0]
336
+ );
337
+
338
+ // Hole 2's y-band, split into near/hole/far along z.
339
+ this.slit2_near = new LargeObject(
340
+ universe,
341
+ zBand(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1, zLo, nearZspan),
342
+ CollisionTypes.NONE, wall_collision_actions, 'slit2_near',
343
+ 'closest', null, 1, [200, 60, 60]
344
+ );
345
+ this.slit2 = new LargeObject(
346
+ universe,
347
+ zBand(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1, holeZlo, holeZspan),
348
+ detector_type, detector_actions, 'slit2',
349
+ 'closest', null, 1, slit2_color
350
+ );
351
+ this.slit2_far = new LargeObject(
352
+ universe,
353
+ zBand(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1, farZlo, farZspan),
354
+ CollisionTypes.NONE, wall_collision_actions, 'slit2_far',
355
+ 'closest', null, 1, [200, 60, 60]
356
+ );
357
+
358
+ // Wall below slit2 (no hole in this y-band -> full z-depth)
359
+ this.wall3 = _wallLO(
360
+ zSlab(this.slit_group_left, this.slit2_bottom, this.slit_width, this.bottom_wall_height - 1),
361
+ 'wall3', [255, 0, 0]
362
+ );
363
+
364
+ // ── Slit liners (edge diffraction) — z-bounded to the hole's OWN
365
+ // z-range: edge diffraction only applies at the aperture, not at the
366
+ // solid side-tiles (there is no opening there to diffract around). ──
367
+ var slit1_liner_top = new LargeObject(
368
+ universe,
369
+ zBand(this.slit_group_left, this.slit1_top, this.slit_width, liner_height, holeZlo, holeZspan),
370
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_top',
371
+ 'closest', null, 1, [0, 0, 255]
372
+ );
373
+ _applyLinerScatter(slit1_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
374
+
375
+ var slit1_liner_bottom = new LargeObject(
376
+ universe,
377
+ zBand(this.slit_group_left, this.slit1_top + this.slit_height - liner_height, this.slit_width, liner_height, holeZlo, holeZspan),
378
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_bottom',
379
+ 'closest', null, 1, [0, 0, 255]
380
+ );
381
+ _applyLinerScatter(slit1_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
382
+
383
+ var slit2_liner_top = new LargeObject(
384
+ universe,
385
+ zBand(this.slit_group_left, this.slit2_top, this.slit_width, liner_height, holeZlo, holeZspan),
386
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_top',
387
+ 'closest', null, 1, [0, 0, 255]
388
+ );
389
+ _applyLinerScatter(slit2_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
390
+
391
+ var slit2_liner_bottom = new LargeObject(
392
+ universe,
393
+ zBand(this.slit_group_left, this.slit2_top + this.slit_height - liner_height, this.slit_width, liner_height, holeZlo, holeZspan),
394
+ CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_bottom',
395
+ 'closest', null, 1, [0, 0, 255]
396
+ );
397
+ _applyLinerScatter(slit2_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
398
+
399
+ this.walls = [
400
+ this.wall1,
401
+ this.slit1_near, this.slit1, this.slit1_far,
402
+ this.wall2,
403
+ this.slit2_near, this.slit2, this.slit2_far,
404
+ this.wall3,
405
+ slit1_liner_top, slit1_liner_bottom,
406
+ slit2_liner_top, slit2_liner_bottom
407
+ ];
408
+ }
409
+
410
+ // ── Render shape (renderer-only hint; physics unaffected) — WS-DSE-3D
411
+ // fix-loop item 1, the Tube.js precedent (FRQTL-MAINT 2026-07-01): a
412
+ // 6-element rect gives the ENGINE a real z-slab, but `_buildLoMesh`
413
+ // (three-provider.js) only wraps an LO in a 3D BoxGeometry when it
414
+ // ALSO carries the render-only `lo.shape='box'` + `lo.depth` hint —
415
+ // omitting it (as B1 did) left every wall a flat zero-thickness
416
+ // PlaneGeometry despite its correct 6-element engine rect. Only set
417
+ // when `depth` was actually requested (`rect.d` is only present then;
418
+ // a legacy 2D LO gets a 4-element rect with no `.d` and is untouched).
419
+ for (var _si = 0; _si < this.walls.length; _si++) {
420
+ if (this.walls[_si].rect.d !== undefined) {
421
+ this.walls[_si].shape = 'box';
422
+ this.walls[_si].depth = this.walls[_si].rect.d;
423
+ }
424
+ }
425
+
426
+ // THE ONE UP-LINK on every LO this screen composed (a drawn wall resolves to the screen).
427
+ for (var _wi = 0; _wi < this.walls.length; _wi++) {
428
+ this.walls[_wi]._owner = this; // THE ONE UP-LINK: the screen composed each LO
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Full teardown of what this screen composed: every wall, slit and liner LO leaves the
434
+ * world (dispose IS the removal). Idempotent (LargeObject.dispose guards itself).
435
+ */
436
+ SlitScreen.prototype.dispose = function () {
437
+ for (var i = 0; i < this.walls.length; i++) { this.walls[i].dispose(); }
438
+ };
439
+
440
+ /**
441
+ * LAB DRIVE FIX BATCH item 3 (2026-07-13, CEO): LIVE slit-gap update — recomputes
442
+ * every affected 2D wall/slit/liner rect (mirroring the constructor's OWN 2D
443
+ * geometry formulas, one authority, no schema-mirror) and pushes each via
444
+ * `runner.updateObjectProperty(lo, 'rect', [left,top,w,h])` — the SAME S3/S4
445
+ * live-drag accessor path `AuthorView.jsx`'s overlay-drag code already calls
446
+ * (mirrors/mutates the JS-side Rectangle wrapper AND forwards to the engine ABI;
447
+ * deliberately NOT a hand-rolled Rectangle-setter mirror, which would risk
448
+ * drifting from that canonical accessor's own setter-order discipline).
449
+ *
450
+ * Only `slit_wall_gap_height` changes; `slit_height`/`wavelength`/`slit_width`/
451
+ * `slit_group_left` and the group's vertical CENTER (`slit_group_center_y`,
452
+ * always `canvasHeight/2`) are invariants of a gap-only change — growing/
453
+ * shrinking the gap symmetrically re-centers `slit1_top`/`slit2_top` and
454
+ * reshapes `wall1`/`wall2`/`wall3`'s heights, exactly as the constructor would
455
+ * compute for that `gapHeight` from scratch.
456
+ *
457
+ * 3D (`this.depth !== null`) is NOT supported live — the cubic-hole z-tiling
458
+ * (WS-DSE-3D CEO amendment) adds ~13 LOs (near/far z-tiles per hole, wall1-3
459
+ * z-spanning tiles) whose z-partition math would need the identical live-update
460
+ * treatment; out of scope for this bite. Returns `false` so the caller falls
461
+ * back to a reinit reload — name this explicitly rather than silently no-op.
462
+ *
463
+ * @param {number} newGapHeight - the new `slit_wall_gap_height` (px).
464
+ * @param {Object} runner - an FrqtlApp exposing `updateObjectProperty(lo,prop,value)`.
465
+ * @returns {boolean} true if the live update was applied; false if 3D (caller must reinit).
466
+ */
467
+ SlitScreen.prototype.updateGapHeight = function (newGapHeight, runner) {
468
+ if (this.depth !== null) {
469
+ return false; // 3D: structurally out of scope for a live update (see docstring above) — reinit.
470
+ }
471
+ if (!runner || typeof runner.updateObjectProperty !== 'function') {
472
+ throw new Error('[SlitScreen.updateGapHeight] requires a runner exposing updateObjectProperty(lo, prop, value).');
473
+ }
474
+
475
+ // slit_group_center_y = canvasHeight / 2 is a constructor invariant (never
476
+ // moves with gapHeight) — derive canvasHeight from it rather than storing a
477
+ // second field that could drift.
478
+ var canvasHeight = this.slit_group_center_y * 2;
479
+
480
+ this.slit_wall_gap_height = newGapHeight;
481
+ this.slit_group_height = this.slit_height + this.slit_wall_gap_height + this.slit_height;
482
+ this.slit1_top = this.slit_group_center_y - this.slit_group_height / 2;
483
+ this.slit_wall_gap_top = this.slit1_top + this.slit_height;
484
+ this.slit2_bottom = this.slit_group_center_y + this.slit_group_height / 2;
485
+ this.slit2_top = this.slit2_bottom - this.slit_height;
486
+ this.bottom_wall_height = canvasHeight - this.slit2_bottom;
487
+
488
+ var liner_height = Math.min(10, Math.floor(this.slit_height * 0.2));
489
+ var left = this.slit_group_left, w = this.slit_width;
490
+
491
+ function findWall(walls, name) {
492
+ for (var i = 0; i < walls.length; i++) { if (walls[i].name === name) return walls[i]; }
493
+ return null;
494
+ }
495
+
496
+ var wall1 = findWall(this.walls, 'wall1');
497
+ if (wall1) runner.updateObjectProperty(wall1, 'rect', [left, 0, w, this.slit1_top - 1]);
498
+
499
+ var slit1 = findWall(this.walls, 'slit1');
500
+ if (slit1) runner.updateObjectProperty(slit1, 'rect', [left, this.slit1_top, w, this.slit_height - 1]);
501
+
502
+ var wall2 = findWall(this.walls, 'wall2');
503
+ if (wall2) runner.updateObjectProperty(wall2, 'rect', [left, this.slit_wall_gap_top, w, this.slit_wall_gap_height - 1]);
504
+
505
+ var slit2 = findWall(this.walls, 'slit2');
506
+ if (slit2) runner.updateObjectProperty(slit2, 'rect', [left, this.slit2_top, w, this.slit_height - 1]);
507
+
508
+ var wall3 = findWall(this.walls, 'wall3');
509
+ if (wall3) runner.updateObjectProperty(wall3, 'rect', [left, this.slit2_bottom, w, this.bottom_wall_height - 1]);
510
+
511
+ var s1lt = findWall(this.walls, 'slit1_liner_top');
512
+ if (s1lt) runner.updateObjectProperty(s1lt, 'rect', [left, this.slit1_top, w, liner_height]);
513
+
514
+ var s1lb = findWall(this.walls, 'slit1_liner_bottom');
515
+ if (s1lb) runner.updateObjectProperty(s1lb, 'rect', [left, this.slit1_top + this.slit_height - liner_height, w, liner_height]);
516
+
517
+ var s2lt = findWall(this.walls, 'slit2_liner_top');
518
+ if (s2lt) runner.updateObjectProperty(s2lt, 'rect', [left, this.slit2_top, w, liner_height]);
519
+
520
+ var s2lb = findWall(this.walls, 'slit2_liner_bottom');
521
+ if (s2lb) runner.updateObjectProperty(s2lb, 'rect', [left, this.slit2_top + this.slit_height - liner_height, w, liner_height]);
522
+
523
+ return true;
524
+ };
525
+
526
+ // ── The kind (objects/kinds.js): descriptor, construction defaults, builder ──────────
527
+
528
+ /**
529
+ * SlitScreen — Double-slit barrier (group: 3 wall segments + 2 slit segments + 4 liners = 9 sub-objects).
530
+ * Constructor: SlitScreen(universe, wallLeft, wallWidth, canvasHeight, wavelength,
531
+ * absorbAtSlitWall, detectAtSlits, slitHeight, gapHeight)
532
+ * Group wrapper: all 9 sub-LargeObjects are stamped with the ONE up-link `_owner` = <this>.
533
+ */
534
+ var SlitScreenDescriptor = {
535
+ description: 'A double-slit barrier that produces quantum interference. ' +
536
+ 'Creates 9 sub-LargeObjects: wall1 (above slit1), slit1, wall2 (between slits), ' +
537
+ 'slit2, wall3 (below slit2), and 4 edge-diffraction liners (slit1_liner_top/bottom, ' +
538
+ 'slit2_liner_top/bottom). The group is the single author-surface entity; ' +
539
+ 'slit geometry is derived from the wavelength so the interference fringe spacing is ' +
540
+ 'automatically correct for the particle wavelength.',
541
+ fields: [
542
+ {
543
+ name: 'slit_group_left',
544
+ type: 'number',
545
+ constraint: { integer: true },
546
+ engineVisible: true,
547
+ description: 'X position of the slit barrier left edge (physics pixels). Moving this repositions the entire slit assembly.'
548
+ },
549
+ {
550
+ name: 'slit_width',
551
+ type: 'number',
552
+ constraint: { min: 1, integer: true },
553
+ engineVisible: true,
554
+ unit: 'px',
555
+ description: 'Width/thickness of the slit screen (physics pixels). Thicker screens affect diffraction character.'
556
+ },
557
+ {
558
+ name: 'wavelength',
559
+ type: 'number',
560
+ constraint: { min: 4, integer: true },
561
+ engineVisible: true,
562
+ description: 'Wavelength driving slit and gap sizing. Default slit height = floor(wavelength * 0.8); inter-slit gap = same. Engine invariant 6: minimum wavelength is 4.'
563
+ },
564
+ {
565
+ name: 'slit_height',
566
+ type: 'number',
567
+ constraint: { min: 1, integer: true },
568
+ engineVisible: true,
569
+ description: 'Height of each slit opening (physics pixels). Narrower slits increase diffraction spread; wider slits reduce it.'
570
+ },
571
+ {
572
+ name: 'slit_wall_gap_height',
573
+ type: 'number',
574
+ constraint: { min: 1, integer: true },
575
+ engineVisible: true,
576
+ description: 'Height of the wall segment between the two slits (physics pixels). Larger gap → tighter interference fringes on the screen.'
577
+ },
578
+ {
579
+ name: 'absorbAtSlitWall',
580
+ type: 'boolean',
581
+ engineVisible: true,
582
+ description: 'If true, wall segments use ABSORB (particles disappear on contact). If false, walls use BLOCK. Absorbing walls eliminate back-scatter and produce cleaner interference patterns.'
583
+ },
584
+ {
585
+ name: 'detectAtSlits',
586
+ type: 'enum',
587
+ constraint: { values: { NONE: 0, DETECT_RANDOM_XY: 1, DETECT_MARK: 2 } },
588
+ engineVisible: true,
589
+ description: 'Slit detection mode: 0 = no detection (coherent, wave-like pattern); 1 = DETECT+RANDOM_XY (which-path measurement collapses the wavefunction); 2 = DETECT+MARK (which-path marker via string_map.detector_name).'
590
+ }
591
+ ]
592
+ };
593
+
594
+ registerKind(SlitScreen, {
595
+ from: import.meta.url,
596
+ descriptor: SlitScreenDescriptor,
597
+ defaults: {
598
+ slit_group_left: 100,
599
+ slit_width: 8,
600
+ wavelength: 20,
601
+ slit_height: 16,
602
+ slit_wall_gap_height: 16,
603
+ absorbAtSlitWall: true,
604
+ detectAtSlits: 0
605
+ },
606
+ build: function (props, ctx, Class) {
607
+ var SS = Class; // the class that registered (a composed fork's own class)
608
+ var ph = ObjectSchema.worldRect(ctx.runner.canvas_rect, 'ObjectFactory create(\"SlitScreen\") runner.canvas_rect').h;
609
+ var screen = new SS(
610
+ ctx.runner.universe,
611
+ props.slit_group_left != null ? props.slit_group_left : Math.round(ctx.x),
612
+ props.slit_width || 8,
613
+ ph,
614
+ props.wavelength || 20,
615
+ props.absorbAtSlitWall != null ? props.absorbAtSlitWall : true,
616
+ props.detectAtSlits != null ? props.detectAtSlits : 0,
617
+ props.slit_height,
618
+ props.slit_wall_gap_height
619
+ );
620
+ screen._schemaKind = Class.name;
621
+ return screen;
622
+ }
623
+ });
624
+
625
+ // ── Export ───────────────────────────────────────────────────────────
626
+
627
+ export { SlitScreen };
628
+