@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,244 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — Double Slit (Tunnel, symmetric)
5
+ *
6
+ * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
+ * (its co-init call, `exp.createCanvas` / `exp.createScene` — the one place its size is written),
8
+ * the parts (`exp.addObject`) and the render intent are all here; the sugar
9
+ * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
+ * (rung dse-tunnel-rebuild).
11
+ */
12
+
13
+ // The double slit built on the TUNNEL OBJECT at physical DSE proportions. The two
14
+ // slit channels are Tunnels — TWO parallel MIRROR walls (guarded bounce, zero leak) — so
15
+ // the slit reflects the beam; the barriers + screen are v2 Walls (destroy / detector-screen
16
+ // preset) so the scene round-trips through codegen on Restart. GEOMETRY: SYMMETRIC —
17
+ // emitter->front of the slit wall equals back of the slit wall->detector. The near-field
18
+ // result is a balanced, central-dominant three-lobe double-slit interference pattern (a
19
+ // stochastic near-field walk; the ray-optics λL/d Fraunhofer regime needs L >> d²/λ ≈ 580px,
20
+ // larger than a 600px canvas allows).
21
+ //
22
+ // ONE MODE-AWARE SOURCE (S3/W2, CEO-ratified 2026-07-26; CEO Q21 2026-10-12). The mode strip
23
+ // selects the RENDERER the host boots (renderState.renderer), and this scene reads it ONCE,
24
+ // before its world: `three` ⇒ exp.createScene (a 3D world), else exp.createCanvas (2D). `IS3D`
25
+ // then guards the dimensional deltas — there is NO second (`-3d`) source file:
26
+ // * the emitter opens the z-axis (is2D:!IS3D → activeAxes.z, a 3D ball of directions);
27
+ // * every barrier / tunnel / detector gains the SAME z-slab (z/depth → a 6-element rect),
28
+ // so the slit wall is a real SLOT-in-a-PLATE and what you SEE is what collides;
29
+ // * two invisible `destroy` z-collimators sit flush OUTSIDE the plate z-band (the
30
+ // cl1a-mirror-3d pattern — the universe z-edge moved inward, the sanctioned
31
+ // experiment-boundary sink), so no quantum can sail BESIDE a plate and read as
32
+ // pass-through, and every alive quantum provably faces the barrier;
33
+ // * 3D makes the detector a GraphingDetectorWall that paints its hits over its OWN
34
+ // face (y,z), spatially registered — the fringes read as BANDS on the plate, in the
35
+ // place the physics put them. In 2D the screen is a plain counting Wall.
36
+ // The 3D physics is the SAME engine physics (2D≡3D, bench H2a/H2b) — zero new physics.
37
+ //
38
+ // COLOUR: the beam is YELLOW because it IS a yellow-band beam — the leaves are emitted
39
+ // COLOURLESS and the renderer derives their colour from LightColor at their fullness
40
+ // (= mass/capacity = 1/wavelength). See the emitter block in the source for the two
41
+ // independent dials (band vs pixel wavelength) and why the fringe geometry is unchanged.
42
+
43
+ import { CameraControls, FrqtlControlOverlay, FrqtlControlsPanel, SphereEmitter } from '@descix/frqtl-web/parts';
44
+ import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
45
+ import * as FrqtlDisplay from '@descix/frqtl-web/compat/renderer/display-slot.js';
46
+ import * as SunLook from '@descix/frqtl-web/shim/render/sun-look.js';
47
+
48
+ export default function buildExperiment(exp) {
49
+ // Double Slit (Tunnel) — the two slit channels are MIRROR TUNNELS (guarded
50
+ // bounce, zero-leak); the detector sits at the SYMMETRIC distance (emitter->front ==
51
+ // back->detector). X placements are wl-derived (canvas-width-independent); Y is
52
+ // centered on H (groupCenterY = H/2).
53
+ // The booted display (the host's renderState.renderer) decides which world this mode makes.
54
+ var IS3D = FrqtlDisplay.name() === 'three';
55
+ var CW = 600, H = 400, WORLD_D = 600; // THE WORLD this scene states (its co-init call below)
56
+ // GEOMETRY and WAVELENGTH are two INDEPENDENT dials, and they are kept apart on
57
+ // purpose: `wl` sizes the PLATE (slit height, gap, wall placement) and nothing else,
58
+ // while `lamPx` is the transverse wavelength in pixels. Doubling lambda must NOT move
59
+ // the slit wall — if one variable drove both, the aperture would silently walk every
60
+ // time the beam was re-tuned and no two runs would be comparable.
61
+ var wl = 64, sh = Math.floor(wl * 0.8), gap = Math.floor(wl * 0.8); // 51, 51
62
+ var lamPx = 128; // the TRANSVERSE wavelength, in pixels
63
+ var groupH = sh + gap + sh; // 153
64
+ var gcY = H / 2; // 300 @ H=600
65
+ var s1Top = gcY - groupH / 2; // 223.5
66
+ var gapTop = s1Top + sh; // 274.5
67
+ var s2Bot = gcY + groupH / 2; // 376.5
68
+ var s2Top = s2Bot - sh; // 325.5
69
+ var srcX = 20; // source x (left, near the slit wall)
70
+ var L = wl * 1.5, W = wl; // slit-group left 96, width 64 (wall front..back = [96,160])
71
+ // THE DETECTOR LEG, DERIVED FROM THE RING LADDER (zero free parameters).
72
+ // A phase-locked POINT source lays down density rings at r_m = (lambda/4)*sqrt((2m+1)^2+1)
73
+ // measured from the emission point (guide row r3-radius-stall-shells;
74
+ // docs/internal/records/2026-07-30-dse-ring-campaign.md). Each slit exit below
75
+ // IS such a source, so the plate should be placed where
76
+ // a NAMED ring crosses it rather than at a symmetric figure. Put the MIDLINE on ring m=2
77
+ // of BOTH slits: with the exits at +/-halfSep from the midline, that needs a leg of
78
+ // D = sqrt(r_2^2 - halfSep^2) = sqrt(163.2^2 - 51^2) = 155
79
+ // so the two ring-2 circles intersect exactly on the axis and the centre is DOUBLY
80
+ // reinforced. Rings inside D (m=0 at 45.3, m=1 at 101.2) never reach the plate and
81
+ // contribute no band — a measured property of the ladder, not an assumption.
82
+ // This REPLACES the old symmetric-leg figure, whose own comment made it conditional on
83
+ // the slits becoming directed emitters; the exit gates below are that condition.
84
+ var ringR = function (m) { return (lamPx / 4) * Math.sqrt((2 * m + 1) * (2 * m + 1) + 1); };
85
+ var slitCY1 = s1Top + sh / 2, slitCY2 = s2Top + sh / 2; // 249, 351 — the bore midlines
86
+ var halfSep = gcY - slitCY1; // 51
87
+ var detW = 20; // the detector plate's own thickness
88
+ var D = Math.round(Math.sqrt(ringR(2) * ringR(2) - halfSep * halfSep)); // 155
89
+ var detX = (L + W) + D; // 315
90
+ var linerH = Math.min(10, Math.floor(sh * 0.2)), bore = sh - 2 * linerH; // 10, 31
91
+ // THE 3D DELTA, in ONE place: the plate z-band. Every barrier / tunnel liner / detector
92
+ // carries the SAME z-slab, so the slit wall is a genuine SLOT IN A PLATE standing in
93
+ // space (the slits are slots long in z — the 2D figure extruded). In 2D the bag is
94
+ // EMPTY, so every Object.assign below is byte-identical to the flat scene.
95
+ var zHalf = Math.round(groupH / 2); // 77 @ wl 64
96
+ var Z3 = IS3D ? { z: -zHalf, depth: 2 * zHalf } : {};
97
+ // THE BEAM'S COLOUR — its own frequency band, never a forced recolor. The emitter
98
+ // emits COLOURLESS leaves (no renderColor arg ⇒ no string_map['render.color']), so the
99
+ // renderer derives the colour from the ONE authority, LightColor, at the leaf's
100
+ // fullness. TWO INDEPENDENT dials produce it, both on the emitter:
101
+ // fullness = mass/capacity = 1 / wavelength -> the spectral BAND (LightColor)
102
+ // lambda_px = wavelength x wavelengthScale -> the transverse wave, in PIXELS
103
+ // (wavelengthScale = floor(fundamentalWaveLength / 4)). So the band is chosen by
104
+ // scaling fundamentalWaveLength INVERSELY with the wavelength arg — lambda_px (the
105
+ // fringe geometry) is untouched. 1/3.2 = 0.3125 sits in the YELLOW band; the rung's
106
+ // renderState declares the spectrally correct 'roygbiv' ramp, so the beam renders
107
+ // [255,243,0]. lambda_px = 3.2 x 40 = 128, INDEPENDENT of the 64-derived plate.
108
+ // THIS BEAM IS LIGHT, so the band is free: these leaves are born AT the universe's
109
+ // fundamental dimension (leafDimension == fundamentalDimension == 2), and a leaf at the
110
+ // fundamental ALWAYS steps, P=1, whatever its fullness (bench M1) — the fullness
111
+ // move-gate (bench M2) simply does not apply, and P=1 draws no RNG. Measured on the
112
+ // live detector: the fringe histogram is bin-for-bin the SAME as the (64, 16) tuple
113
+ // (t=30s, 16px profile: 11260 vs 11262 hits, every bin within 1, COM 201.7 both).
114
+ var lamArg = 3.2; // fullness 1/3.2 = 0.3125 -> yellow
115
+ var fwl = 4 * (lamPx / lamArg); // 160 -> wavelengthScale 40 -> lambda_px = 128
116
+ // The light source — the current SphereEmitter sugar (braided walk), placed at the
117
+ // original source pixel (srcX, gap-center = H/2).
118
+ // is2D:!IS3D opens activeAxes.z — the ball-rejected sampler then draws a 3D BALL of
119
+ // directions (bench S1/S5) instead of the 2D disk.
120
+ // preset 'hemisphere' = 180 degrees of FORWARD emission (D1): the SAME ball-rejected
121
+ // sampler with the forward axis' range halved to [0,R], i.e. the spherical distribution
122
+ // RESTRICTED to the forward half — same physics, pointed one way, so nothing about the
123
+ // walk changes. Forward is +X, which is where the slit wall stands (orientationDeg stays
124
+ // 0), so the half of the burst that used to fly LEFT into the back-scatter sink now goes
125
+ // toward the slits: the same burst size illuminates the aperture roughly twice as hard.
126
+ var SRC = {
127
+ preset: 'hemisphere', is2D: !IS3D, center: [srcX, Math.round(gcY)],
128
+ chargeMode: 'random', wavelength: lamArg, burstSize: 8, emitEveryTicks: 1,
129
+ fundamentalDimension: 2, fundamentalWaveLength: fwl
130
+ };
131
+ // THE WORLD — first, then parts: a 3D scene + universe (600x400x600) or a 2D canvas + universe.
132
+ if (IS3D) { exp.createScene(CW, H, WORLD_D, SphereEmitter.worldCfg(SRC)); }
133
+ else { exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); }
134
+ exp.addObject('SphereEmitter', SRC);
135
+ // Start wall — a plain sink (collisionBehavior:'destroy', [set is_alive=0]) at the
136
+ // left edge, catching back-scatter. It is TRANSPARENT (alpha 40 of 255): the default
137
+ // camera stands BEHIND the emitter, so an opaque slab here would be the first thing in
138
+ // frame and would hide the whole experiment. alpha is a DECLARED Wall field, so codegen
139
+ // captures it and Restart rebuilds the wall just as sheer. Physics is untouched — the
140
+ // sink still destroys every back-scattered quantum (render-only, SU1).
141
+ exp.addObject('Wall', Object.assign({ rect: [0, 0, 10, H] }, Z3, { collisionBehavior: 'destroy', name: 'start_wall', color: [120, 120, 120], alpha: 40 }));
142
+ // Barrier segments — opaque plain sinks (destroy) leaving the two slit gaps.
143
+ exp.addObject('Wall', Object.assign({ rect: [L, 0, W, s1Top - 1] }, Z3, { collisionBehavior: 'destroy', name: 'wall1', color: [200, 60, 60] }));
144
+ exp.addObject('Wall', Object.assign({ rect: [L, gapTop, W, gap - 1] }, Z3, { collisionBehavior: 'destroy', name: 'wall2', color: [150, 40, 40] }));
145
+ exp.addObject('Wall', Object.assign({ rect: [L, s2Bot, W, (H - s2Bot) - 1] }, Z3, { collisionBehavior: 'destroy', name: 'wall3', color: [200, 60, 60] }));
146
+ // The two slit channels as TUNNELS — walls land at the EXACT original liner positions
147
+ // ([L,s1Top,W,10] & [L,s1Top+sh-10,W,10]); the bore is the clear aperture between them.
148
+ // The Tunnel ferries z/depth onto BOTH walls, so a 3D channel is a real corridor.
149
+ // mode 'directed' (D2): the channel walls are no longer mirrors — a wall hit ejects the
150
+ // quantum back into the bore and RE-EMITS it in a uniformly random direction within 180°
151
+ // of the corridor axis (+x here, derived from axis:'x'). That is what makes the slit
152
+ // itself behave as a semi-circular emitter, which is the classical picture of what a
153
+ // slit does to a wavefront — and it supersedes the mirror mode FOR THIS RUNG: a
154
+ // collimating mirror channel preserves the incoming direction, so the two slits could
155
+ // only ever project two beams. Re-emission is what gives each slit its own angular
156
+ // spread to interfere with. Mirror mode stays available on the object (mode:'mirror',
157
+ // still the default) for the waveguide/light-pipe case it was built for.
158
+ // THE TUNNEL IS A CONFIGURED EMITTER (CEO 2026-07-30). The exit gate's flags are not
159
+ // plumbing details — they ARE the emitter vocabulary, one-to-one:
160
+ // exitRecenter ON + exitPhaseReset ON -> a POINT SOURCE (every quantum leaves the
161
+ // same pixel with the same phase) — what this rung uses, and what makes the ring
162
+ // ladder sharp: any spread at birth blurs every ring by exactly that spread.
163
+ // exitRecenter OFF + exitPhaseReset ON -> an EXTENDED LOCKED APERTURE, the
164
+ // plane-wave analog (phase-flat across a finite mouth).
165
+ // mode 'directed' spread -> the ANGULAR PRESET (here 180 deg about the
166
+ // corridor axis — the hemisphere preset, expressed at a slit).
167
+ // In 3D the recenter pins the TRANSVERSE axis only, so the slot stays extended in z:
168
+ // each slit is a LINE source and the bands extrude along z — the plate picture.
169
+ // Unifying this with the SphereEmitter preset vocabulary (one owner, ferried — never a
170
+ // second preset list) is a contract design flagged for architect + CEO ratification;
171
+ // this rung uses the existing flags and documents the mapping.
172
+ exp.addObject('Tunnel', Object.assign({ left: L, top: s1Top + linerH, length: W, bore: bore, wallThickness: linerH, axis: 'x' }, Z3, { mode: 'directed', exitCollapse: true, exitRecenter: true, exitPhaseReset: true, name: 'slit1_channel', color: [80, 140, 220] }));
173
+ exp.addObject('Tunnel', Object.assign({ left: L, top: s2Top + linerH, length: W, bore: bore, wallThickness: linerH, axis: 'x' }, Z3, { mode: 'directed', exitCollapse: true, exitRecenter: true, exitPhaseReset: true, name: 'slit2_channel', color: [80, 140, 220] }));
174
+ // The detector SCREEN — a NON-ABSORBING COUNTING wall ('transparent-probe':
175
+ // collisionBehavior 'nothing' + the count hook) in BOTH modes. This is the
176
+ // |psi|^2-faithful screen: an ABSORBING plate removes each quantum at first contact,
177
+ // so the reading becomes a first-arrival caustic that drifts with the leg D and lands
178
+ // at the WRONG radii; counting WITHOUT absorbing reproduces the ring ladder exactly,
179
+ // because the census at a plane IS the density at that plane. The far-edge sink above bounds the
180
+ // population that now flies past. The KIND follows the detector's dimensionality. In 2D it is a plain Wall.
181
+ // In 3D the detector is a PLATE, so it
182
+ // is a GraphingDetectorWall: it paints its OWN hit grid onto its own faces, spatially
183
+ // REGISTERED (a hot cell sits exactly where the collisions landed) — the wall is the
184
+ // axis, so there is no chart chrome in the scene. Both broad faces carry the map, so it
185
+ // never disappears as you orbit (the back reads as the through-the-window mirror).
186
+ // NOTE: a wall's collided-frame buffer may be drained by ONE consumer per tick — the
187
+ // heatmap painter IS that consumer in 3D.
188
+ // FAR-EDGE SINK. The screen below COUNTS WITHOUT ABSORBING, so quanta fly on past it;
189
+ // this destroy wall at the right edge is the experiment-boundary sink that keeps the
190
+ // population bounded (Invariant 5/7 — only an experiment boundary may destroy).
191
+ exp.addObject('Wall', Object.assign({ rect: [CW - 10, 0, 10, H] }, Z3, { collisionBehavior: 'destroy', name: 'far_sink', color: [120, 120, 120], alpha: 40 }));
192
+ exp.addObject(IS3D ? 'GraphingDetectorWall' : 'Wall', Object.assign({ rect: [detX, 0, detW, H] }, Z3, { preset: 'transparent-probe', name: 'screen', color: [120, 200, 120] }, IS3D ? { alpha: 220, binPx: 12 } : {}));
193
+ if (IS3D) {
194
+ // SCENE Z-COLLIMATORS — two INVISIBLE full-canvas destroy boundaries flush OUTSIDE
195
+ // the plate z-band (the cl1a-mirror-3d pattern): the emitted ball's z-tail diffuses,
196
+ // and any quantum beyond the plate band would sail BESIDE the barrier and read as
197
+ // pass-through. Clamping the scene to the plate band makes the barrier provably face
198
+ // EVERY alive quantum. 40px deep: un-jumpable (max z step is +/-2/tick).
199
+ exp.addObject('Wall', { rect: [0, 0, CW, H], z: zHalf, depth: 40,
200
+ collisionBehavior: 'destroy', name: 'zClipFar', visible: false });
201
+ exp.addObject('Wall', { rect: [0, 0, CW, H], z: -zHalf - 40, depth: 40,
202
+ collisionBehavior: 'destroy', name: 'zClipNear', visible: false });
203
+ // THE SWITCH (CEO 2026-10-13): god-rays computed from the light particles only (godraySource
204
+ // 'leaf'), at the sun's settings — the ONE owner of those numbers is lib/shim/render/sun-look.js
205
+ // (SunLook); this file types none of them. Default OFF (the first-contact picture is
206
+ // unchanged). OFF restores EXACTLY what was live before the last ON (a host row, a STUDIO
207
+ // slider), through the same declared door; a value the renderer does not declare THROWS there.
208
+ // The knob shows its last written value (a render write has no read-back). The picture changes
209
+ // at the next drawn tick, so in a paused (Design) view it shows once the scene runs.
210
+ var RAYS_ON = Object.assign({}, SunLook.SUN_GODRAYS, { godraySource: 'leaf' });
211
+ var raysPrior = null; // the three live values captured at ON; written back at OFF
212
+ var knobs = ControlRegistry.makeControlRegistry();
213
+ knobs.registerControls('dse-tunnel-rebuild', [
214
+ { kind: 'scalar', id: 'godrays', label: 'god rays', channel: 'render', widget: 'toggle',
215
+ min: 0, max: 1, step: 1, value: 0,
216
+ fmt: function (v) { return v ? 'on' : 'off'; },
217
+ onChange: function (v) {
218
+ if (v) {
219
+ raysPrior = {};
220
+ Object.keys(RAYS_ON).forEach(function (k) { raysPrior[k] = exp.runner.getRenderOption(k); });
221
+ exp.runner.setRenderOptions(RAYS_ON);
222
+ } else if (raysPrior) {
223
+ exp.runner.setRenderOptions(raysPrior);
224
+ raysPrior = null;
225
+ }
226
+ // OFF with nothing captured: the control is at its initial OFF and has written nothing — no write.
227
+ },
228
+ place: { x: 0.02, y: 0.04 } }
229
+ ]);
230
+ // Chrome (a page only): expose the knob to a host frame, and mount the overlay through the
231
+ // runner's ONE door (runner.controlHost) — before a canvas is mounted (a notebook pane) there is
232
+ // none, so the knob stays in the exposed registry only.
233
+ FrqtlControlsPanel.exposeRegistry(knobs, { runner: exp.runner });
234
+ if (exp.runner.controlHost) { FrqtlControlOverlay.mount(exp.runner, { registry: knobs, name: 'dse-tunnel-rebuild' }); }
235
+ // THE 3D INSTRUMENT IS THE DETECTOR ITSELF (the GraphingDetectorWall above): the
236
+ // hits are binned over the detector FACE (y,z) and painted where they landed, so the
237
+ // interference structure reads as BANDS on the plate, in the place the physics put
238
+ // them. No free-floating chart panel.
239
+ // MOVE/LOOK camera joysticks — the OMNI_3D pattern (FAIL-SOFT no-op in 2D).
240
+ if (typeof document !== 'undefined' && exp.runner) {
241
+ if (exp.runner.controlHost) { CameraControls.attach(exp.runner, exp.runner.controlHost); }
242
+ }
243
+ }
244
+ }
@@ -0,0 +1,196 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — Two Emitters at the Canonical Leg
5
+ *
6
+ * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
+ * (its co-init call, `exp.createCanvas` / `exp.createScene` — the one place its size is written),
8
+ * the parts (`exp.addObject`) and the render intent are all here; the sugar
9
+ * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
+ * (rung dse-two-emitters).
11
+ */
12
+
13
+ // ── H1 AT THE CANONICAL DETECTOR LEG (CEO 2026-08-03) ────────────────────────
14
+ //
15
+ // The competence baseline: the most basic thing, done right, seen with eyes. One
16
+ // emitter, then two, with the plate PRE-COMPUTED at the leg the ring formula was
17
+ // written for and the count-by-y chart registered to that plate. No barrier, no
18
+ // slits, no tunnels — nothing between the source and the plate, so a band on the
19
+ // chart can have come from nothing but the walk.
20
+ //
21
+ // WHY THESE EXIST BESIDE `dse-tunnel-rebuild`. That rung places its plate at
22
+ // D = 155 px against λ_eff = 128 px — 4.84× the reference leg — which pushes the
23
+ // low orders inside the leg where they never reach the plate at all, so only high-m
24
+ // rings can land. These two rungs put the plate ON the reference leg instead.
25
+ //
26
+ // THE GEOMETRY, and it is not re-derived here. λ is the ONLY length; every other
27
+ // length below is a dimensionless constant × λ (the `published/dse/scene.js`
28
+ // discipline). The ring ladder quoted in CLAUDE.md,
29
+ //
30
+ // r_m = (λ/4)·√((2m+1)² + 1) [slant, from the emission point]
31
+ //
32
+ // is the hypotenuse of a right triangle whose TRANSVERSE leg is ρ_m = (2m+1)·λ/4
33
+ // and whose AXIAL leg is λ/4 — the "+1" under the root IS the (λ/4)² axial term.
34
+ // Solve for the axial leg order by order and every order returns λ/4, so the leg is
35
+ // not fitted per-order: it is the one leg the formula was written for. At that leg
36
+ // the on-plate bands are ρ_m = (2m+1)·λ/4 from the source's own y, spacing λ/2.
37
+ //
38
+ // MEASURED, not assumed. `published/dse/h1/` ran exactly this apparatus (bare
39
+ // source, plate at D = λ/4 from the emitter, seed 1, detector 'hits') at λ = 160 /
40
+ // 320 / 640 and found a symmetric feature on the plate at |Δy| = λ/4 − 4 px — 36 /
41
+ // 76 / 156 px — holding that offset to the pixel across a 4× sweep while moving 120
42
+ // px in absolute terms. The raw histogram's shape is: a flat PLATEAU across the
43
+ // centre, a sharp MINIMUM just inside ±λ/4, and a PEAK riding immediately outside it
44
+ // at ±λ/4. At λ=640, counts by Δy/λ: −0.250 → 1107 | −0.225 → 306 | 0.000 → 1011 |
45
+ // +0.225 → 297 | +0.250 → 1125. At λ=320 the same shape, shallower. TWO points on
46
+ // the wall at ¼ λ_eff, with a plateau — not a hole — between them.
47
+ //
48
+ // AND THAT IS WHY λ IS 320 HERE. The dip-to-peak separation is 0.025λ: 16 px at
49
+ // λ=640, 8 px at λ=320, 4 px at λ=160 — which is why the λ=160 run reads as a broad
50
+ // hump with the feature only recoverable by a windowed search on a smoothed profile.
51
+ // Below ~λ=300 this stage cannot resolve the thing it exists to show. So this rung
52
+ // shows ONE well-resolved band pair per source rather than three mushy ones. The 4 px
53
+ // pedestal does NOT scale with λ (it is engine scale — a quantum steps ≤2 px/axis/
54
+ // tick) and no mechanism is offered for it here. The m ≥ 1 rungs of the ladder are
55
+ // NOT established: of the H1 runs' 8 predicted positions, 3 matched, and the m=1
56
+ // positions carried minima.
57
+ //
58
+ // THE LEG IS ONE NAMED CONSTANT (`legFrac`), and so is the plate's DETECTOR MODE —
59
+ // both so they can be moved deliberately from a one-line edit and the consequence
60
+ // re-derived, rather than being literals nobody chose.
61
+ //
62
+ // The beam is the `dse-tunnel-rebuild` beam and the H1 beam, unchanged: wavelength
63
+ // arg 3.2 → mass 5 / capacity 16 → fullness 0.3125, which LightColor renders yellow,
64
+ // and λ_eff = 3.2 × wavelengthScale. The leaves are emitted COLOURLESS; the colour
65
+ // is DERIVED from fullness, never forced.
66
+ //
67
+ // 2D ONLY, deliberately. There is no `modes` strip and no `exp.is3D` guard: these
68
+ // rungs declare one world each. A second dimensional variant would be a separate
69
+ // recipe declaration, not a mode.
70
+ // THE PAIR. `dse-one-emitter.js` is the SAME apparatus with NSRC = 1. The two files differ ONLY in
71
+ // the title lines and the `var NSRC` line — lib/frqtl/scenes/test/scene-files.test.mjs
72
+ // fails if any other line diverges, so the A/B the pair exists to make stays an A/B.
73
+
74
+ import { SphereEmitter } from '@descix/frqtl-web/parts';
75
+
76
+ export default function buildExperiment(exp) {
77
+ var NSRC = 2;
78
+ var CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
79
+ // ── THE ONLY LENGTH ─────────────────────────────────────────────────────
80
+ // lamPx is lambda_eff in pixels. 320 is chosen for RESOLUTION, not for how many
81
+ // orders fit: the feature this rung exists to show (the minimum just inside the
82
+ // band and the peak riding outside it) is 0.025*lambda wide, which is 8 px at
83
+ // lambda 320 and 3 px at lambda 128. A stage that cannot separate them shows a
84
+ // smooth hump and leaves you unable to say whether the physics or the sampling
85
+ // was at fault. One well-resolved pair beats three mushy ones.
86
+ var lamPx = 320;
87
+ var lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
88
+ var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
89
+ var wlScale = Math.floor(fwl / 4); // 100
90
+ if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
91
+ throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
92
+ 'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
93
+ }
94
+ // ── THE DETECTOR LEG — ONE NAMED CONSTANT ───────────────────────────────
95
+ // D = legFrac * lambda, measured from the SOURCE (there is no barrier, so the
96
+ // emitter itself is the reference point of the ladder). legFrac = 1/4 is the
97
+ // leg the ring formula was written for; see the block comment above.
98
+ var legFrac = 1 / 4;
99
+ var D = Math.round(lamPx * legFrac); // 80
100
+ var srcX = Math.round(lamPx / 16); // 20 — launch room; the canvas edge is the back sink
101
+ var cy = Math.round(H / 2); // 200 — the source plane's midline
102
+ var detX = srcX + D; // 100 — the plate's FRONT FACE
103
+ // PLATE THICKNESS IS AN OPTICAL CHOICE UNDER A FLUX DETECTOR, not trim. A flux
104
+ // probe counts every tick a quantum overlaps it, so it integrates the density
105
+ // ACROSS its thickness — and the ring crest is a CIRCLE about the source, not a
106
+ // plane. At the m=0 band the crest's slope is dy/dx = -D/y = -1, so a slab t px
107
+ // thick smears the band over ~t px of y. Against an 8 px feature a lambda/8 (40
108
+ // px) plate would guarantee mush. lambda/64 = 5 px keeps the smear sub-feature
109
+ // and is still unjumpable (a quantum steps at most 2 px/axis/tick). A 'hits'
110
+ // plate would not care — it counts at first contact, i.e. at the front face —
111
+ // which is exactly why the reference H1 runs could afford a lambda/8 slab.
112
+ var detW = Math.round(lamPx / 64); // 5 — plate thickness
113
+ var sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
114
+ // TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
115
+ // multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
116
+ // the strongest case. At this leg it also puts a band of EACH source on the
117
+ // midline y = H/2 (source 1's lower band and source 2's upper band coincide
118
+ // there) — the doubly-reinforced central peak, equidistant from both emitters,
119
+ // exactly where the one-source rung has no peak at all.
120
+ var sep = lamPx / 2; // 160 -> sources at y 120 and 280
121
+ var srcYs = (NSRC === 2) ? [cy - sep / 2, cy + sep / 2] : [cy];
122
+ // ── THE LIGHT SOURCE ────────────────────────────────────────────────────
123
+ // preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
124
+ // ball-rejected sampler as the omni source with the forward axis' range halved,
125
+ // so nothing about the walk changes, it is simply pointed at the plate. The
126
+ // default emission box is 0 for this preset, so the source is a single PIXEL —
127
+ // load-bearing, because any spread at birth blurs every band by that spread.
128
+ // NO START WALL: the backward population is empty by construction, and `wrap` is
129
+ // false, so the canvas edge kills whatever transverse wiggle out-runs its
130
+ // forward component. One sink, not two.
131
+ var SRC = {
132
+ preset: 'hemisphere', is2D: true, chargeMode: 'random',
133
+ wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
134
+ burstSize: 8, emitEveryTicks: 1
135
+ };
136
+ exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
137
+ for (var si = 0; si < srcYs.length; si++) {
138
+ exp.addObject('SphereEmitter', Object.assign({}, SRC, { center: [srcX, Math.round(srcYs[si])] }));
139
+ }
140
+ // FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
141
+ // it; this is the experiment-boundary sink downstream of the plate. It is not
142
+ // load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
143
+ // is where the population bound is STATED rather than inferred.
144
+ exp.addObject('Wall', { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
145
+ name: 'far_sink', color: [120, 120, 120], alpha: 40 });
146
+ // ── THE PLATE — A COUNTING WALL THAT DOES NOT ABSORB ────────────────────
147
+ // ONE NAMED CONSTANT, so the reading can be swapped in a single edit and the two
148
+ // instruments compared rather than argued about:
149
+ // 'transparent-probe' = collisionBehavior 'nothing' + mark/count. FLUX. The
150
+ // quantum is counted on every tick it overlaps the plate and then flies on,
151
+ // so the reading is the OCCUPANCY INTEGRAL at the plate plane — the density
152
+ // at that plane. This is the shipped choice.
153
+ // 'detector-screen' = destroy + count. HITS. Each quantum is counted once, at
154
+ // arrival, and removed: a transit census, the headcount a photographic plate
155
+ // would report. It is what the H1 reference runs used.
156
+ // NEITHER ABSORBS. 'absorb' stores the arriving mass and re-radiates it — that is
157
+ // the photoelectric experiment, and it saturates.
158
+ var detectorPreset = 'transparent-probe';
159
+ // markTag — the plate's PROVENANCE stamp. The mark hook fires ONLY when the
160
+ // collision trigger does (genuine overlap), whereas the engine buffers every
161
+ // frame inside the LO's mb_w-PADDED search rect whether it triggered or not. So a
162
+ // raw frame count off the buffer is a padded-occupancy reading and the
163
+ // mark-filtered count is the trigger-gated census: two different instruments, and
164
+ // an offline reader needs the tag to tell them apart.
165
+ var screen = exp.addObject('Wall', { rect: [detX, 0, detW, H],
166
+ preset: detectorPreset, markTag: 'plate', name: 'screen', color: [120, 200, 120] });
167
+ // ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
168
+ // The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
169
+ // are co-extensive on the bin axis at any canvas size, and it starts just past
170
+ // the plate's far face so it reads as sitting behind it. orientation 90 = bars
171
+ // stacked along y; the chart contract reverses the y scale when horizontal, so
172
+ // bin 0 (the SMALLEST y) sits at the TOP, matching the y-DOWN world.
173
+ // HONEST LIMIT (inherited from the shipped 2D block, and it is real): the bin
174
+ // axis is a CATEGORY axis over the bins that actually received hits, not a linear
175
+ // 0..H axis. Registration is exact in ORDER and DIRECTION always, and exact in
176
+ // absolute screen height only insofar as the hits span the wall. Here the plate
177
+ // is a full-height flux probe under a 180-degree source, so the occupied span
178
+ // fills the wall within a few hundred ticks and the axis is very nearly linear —
179
+ // but the ladder positions below must be read off the raw census, never measured
180
+ // with a ruler on the panel.
181
+ // SERIES 'line', NOT 'bar', AND THAT IS A MEASUREMENT NOT A TASTE. The bins are
182
+ // per-pixel y, so a full-height plate produces ~400 categories in a ~240 px plot:
183
+ // each bar is sub-pixel TALL, its 0.55 fill alpha is diluted by the coverage, and
184
+ // the profile collapses into a faint sliver hugging the axis. Measured identically
185
+ // on the shipped 1C Counting Wall rung, so it is the chart contract's behaviour at
186
+ // this bin count and not something these rungs introduced. A line joins the same 400
187
+ // points into one continuous curve, which reads as the profile it is. CAVEAT, stated
188
+ // because it bites: Chart.js does not begin a LINE value axis at zero, so once the
189
+ // profile's floor rises the curve compresses toward the left edge. Read positions off
190
+ // it, not magnitudes; magnitudes come from getCollisionHistogram().
191
+ exp.addObject('LiveChart', {
192
+ source: screen, orientation: 90,
193
+ rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
194
+ series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
195
+ });
196
+ }
@@ -0,0 +1,132 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — Two Slits at the Canonical Leg
5
+ *
6
+ * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
+ * (its co-init call, `exp.createCanvas` / `exp.createScene` — the one place its size is written),
8
+ * the parts (`exp.addObject`) and the render intent are all here; the sugar
9
+ * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
+ * (rung dse-two-slits).
11
+ */
12
+
13
+ // ── TWO SLITS AT THE CANONICAL LEG (frqtl-visual-bite, 2026-08-03) ───────────
14
+ //
15
+ // The SAME apparatus as `dse-two-emitters` — same λ, same separation, same leg,
16
+ // same detector, same chart — but the two point sources are now SLIT EXITS in
17
+ // an opaque barrier rather than two free emitters. ONE emitter sits behind the
18
+ // barrier and feeds BOTH slit channels; each channel is a `directed` Tunnel
19
+ // with the exit gate COLLAPSED + PHASE-RESET, exactly the composition
20
+ // `dse-tunnel-rebuild` already uses to make a slit's own mouth stand in for a
21
+ // point source ("THE TUNNEL IS A CONFIGURED EMITTER", CEO 2026-07-30, see that
22
+ // rung's source comment for the flag-by-flag mapping). Composed only: Wall +
23
+ // Tunnel, zero new parts, zero engine code.
24
+ //
25
+ // WHY THE NUMBERS ARE THE SAME AS `dse-two-emitters`. That rung places its two
26
+ // SOURCES at y = cy∓sep/2 (120, 280) and its plate at D = λ_eff/4 past the
27
+ // SOURCE plane (srcX). Here the slit CENTRES sit at the identical y = 120, 280
28
+ // (`sep` unchanged), and the plate sits at the identical D = λ_eff/4 — now
29
+ // measured past the SLIT EXIT plane (the barrier's back face), which is this
30
+ // apparatus's new "source plane". If the exit gate's point-source claim holds,
31
+ // the two apparatuses must draw the SAME picture: a central peak at y = 200
32
+ // (source 1's lower band and source 2's upper band coincide there) flanked by
33
+ // the outer bands at y = 40 and y = 360 — because nothing about the DERIVATION
34
+ // of those positions used "free emitter" anywhere; it only used "point source
35
+ // at (x0, y0), phase-locked, λ_eff". A slit exit under `exitPhaseReset` is
36
+ // exactly that.
37
+ //
38
+ // λ_eff, the leg, and the plate/instrument are the scenes/dse-one-emitter.js numbers,
39
+ // copied rather than referenced because this rung's body is shaped completely
40
+ // differently (a barrier + two Tunnels stand where two bare SphereEmitters
41
+ // stood) — there is no shared statement to factor without re-introducing the
42
+ // exact config-mirror risk the emitter pair's line-diff test guards elsewhere.
43
+ // The bare numbers (320 / 3.2 / 400 / 80 / 160) are the SAME numbers, and the
44
+ // fail-loud realisability check is repeated verbatim so a future λ edit here
45
+ // cannot silently drift from the pair it must match.
46
+
47
+ import { SphereEmitter } from '@descix/frqtl-web/parts';
48
+
49
+ export default function buildExperiment(exp) {
50
+ // Two Slits — reproduces dse-two-emitters' picture with the two point sources built
51
+ // as SLIT EXITS in a barrier instead of two free emitters. Barrier = Wall segments
52
+ // (destroy); each slit = a directed Tunnel with the exit gate collapsed + phase-reset
53
+ // (the dse-tunnel-rebuild composition) so the mouth is a phase-locked point source.
54
+ var CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
55
+ // ── THE ONLY LENGTH — IDENTICAL to the canonical-leg pair ───────────────
56
+ var lamPx = 320;
57
+ var lamArg = 3.2; // fullness 1/3.2 = 0.3125 -> yellow
58
+ var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
59
+ var wlScale = Math.floor(fwl / 4); // 100
60
+ if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
61
+ throw new Error('[dse-two-slits] lamPx ' + lamPx + ' is not realisable as lambda_eff: the ' +
62
+ 'engine can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
63
+ }
64
+ // ── SLIT SEPARATION — IDENTICAL to dse-two-emitters' source separation ──
65
+ var legFrac = 1 / 4;
66
+ var D = Math.round(lamPx * legFrac); // 80 — the SAME leg, now measured from the
67
+ // slit EXIT plane, this apparatus's source plane
68
+ var sep = lamPx / 2; // 160 -> slit centres at y 120 and 280 — the
69
+ // EXACT y positions dse-two-emitters used
70
+ var cy = Math.round(H / 2); // 200
71
+ var slitCY1 = cy - sep / 2, slitCY2 = cy + sep / 2; // 120, 280
72
+ // ── THE BARRIER — one opaque plate, two slit apertures ──────────────────
73
+ var barrierW = Math.round(lamPx / 8); // 40 — plate thickness (dimensionless x lamPx,
74
+ // an independent dial from the transverse
75
+ // wavelength, same discipline as dse-tunnel-rebuild)
76
+ var slitH = Math.round(lamPx / 8); // 40 — slit aperture height (before liners)
77
+ var linerH = Math.round(lamPx / 64); // 5 — liner thickness (unjumpable: max step 2px/tick)
78
+ var bore = slitH - 2 * linerH; // 30 — the clear aperture between the liners
79
+ var srcX = Math.round(lamPx / 16); // 20 — the emitter, well behind the barrier
80
+ var barrierL = srcX + Math.round(lamPx / 2); // 180 — half a wavelength of travel room
81
+ var slit1Top = slitCY1 - slitH / 2, slit2Top = slitCY2 - slitH / 2; // 100, 260
82
+ var exitX = barrierL + barrierW; // 220 — the slit EXIT plane (barrier back face)
83
+ var detX = exitX + D; // 300 — SAME leg as dse-two-emitters, now
84
+ // measured from the slit exits
85
+ var detW = Math.round(lamPx / 64); // 5 — plate thickness (flux probe, sub-feature)
86
+ var sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
87
+ // ── THE LIGHT SOURCE — ONE emitter, behind the barrier, lighting BOTH slits ──
88
+ // preset 'hemisphere': 180 degrees of FORWARD emission, aimed at the barrier — the
89
+ // same walk-character (chargeMode/wavelength/fundamentalWaveLength) the slit exits must
90
+ // carry through so the exit's phase-locked point source is at the SAME λ_eff.
91
+ var SRC = {
92
+ preset: 'hemisphere', is2D: true, chargeMode: 'random', center: [srcX, cy],
93
+ wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
94
+ burstSize: 8, emitEveryTicks: 1
95
+ };
96
+ exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
97
+ exp.addObject('SphereEmitter', SRC);
98
+ // Barrier segments — opaque destroy sinks, leaving the two slit gaps.
99
+ exp.addObject('Wall', { rect: [barrierL, 0, barrierW, slit1Top],
100
+ collisionBehavior: 'destroy', name: 'wall1', color: [200, 60, 60] });
101
+ exp.addObject('Wall', { rect: [barrierL, slit1Top + slitH, barrierW, slit2Top - (slit1Top + slitH)],
102
+ collisionBehavior: 'destroy', name: 'wall2', color: [150, 40, 40] });
103
+ exp.addObject('Wall', { rect: [barrierL, slit2Top + slitH, barrierW, H - (slit2Top + slitH)],
104
+ collisionBehavior: 'destroy', name: 'wall3', color: [200, 60, 60] });
105
+ // The two slit channels — directed Tunnels, exit gate COLLAPSED + PHASE-RESET (the
106
+ // dse-tunnel-rebuild composition): each channel's own mouth stands in for a point
107
+ // source at the slit's own y, phase-locked — what a SphereEmitter would do at that pixel.
108
+ exp.addObject('Tunnel', {
109
+ left: barrierL, top: slit1Top + linerH, length: barrierW, bore: bore,
110
+ wallThickness: linerH, axis: 'x', mode: 'directed',
111
+ exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
112
+ name: 'slit1_channel', color: [80, 140, 220]
113
+ });
114
+ exp.addObject('Tunnel', {
115
+ left: barrierL, top: slit2Top + linerH, length: barrierW, bore: bore,
116
+ wallThickness: linerH, axis: 'x', mode: 'directed',
117
+ exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
118
+ name: 'slit2_channel', color: [80, 140, 220]
119
+ });
120
+ // FAR-EDGE SINK — the same experiment-boundary sink the canonical-leg pair uses.
121
+ exp.addObject('Wall', { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
122
+ name: 'far_sink', color: [120, 120, 120], alpha: 40 });
123
+ // ── THE PLATE — SAME instrument as the canonical-leg pair: a counting wall that does
124
+ // NOT absorb, at the SAME leg D past the new source plane (the slit exits).
125
+ var screen = exp.addObject('Wall', { rect: [detX, 0, detW, H],
126
+ preset: 'transparent-probe', markTag: 'plate', name: 'screen', color: [120, 200, 120] });
127
+ exp.addObject('LiveChart', {
128
+ source: screen, orientation: 90,
129
+ rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
130
+ series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
131
+ });
132
+ }
@@ -0,0 +1,14 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — Empty Universe
5
+ *
6
+ * STUDIO's blank world, a scene like every other: it states its world in its first (and only)
7
+ * statement and constructs nothing — the palette authors into it. Named by
8
+ * lib/shim/registry/scene-source.js::EMPTY_SCENE (not a walk-through rung).
9
+ */
10
+
11
+ export default function buildExperiment(exp) {
12
+ // THE WORLD: a 600x400 2D canvas + universe on the dim-2 fundamental, λ multiplier 8.
13
+ exp.createCanvas(600, 400, { dimension: 2, wavelengthScale: 8 });
14
+ }