@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,31 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — Double slit (the SDK's artifact page)
5
+ *
6
+ * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world (its
7
+ * co-init call, `exp.createCanvas` — the one place its size is written), the parts and their order
8
+ * are all here; the sugar (SlitScreen, SlitLightSource, DetectorWall, LargeObject), the renderer
9
+ * and the engine are imports. Shipped as `dist/artifact/double-slit.html`: the generator
10
+ * (lib/shim/engine/artifact-shell.js, ARTIFACT_SCENE) embeds THIS file's text verbatim.
11
+ *
12
+ * The parts are setupDoubleSlit's at its defaults (slit screen at 1.5 λ, an absorbing start wall,
13
+ * the slit light source at a 10° step, the detector 3 λ past the screen), so the seeded run is
14
+ * that factory's, tick for tick (lib/shim/engine/test/artifact-shell-sketch.test.mjs holds it on
15
+ * the WASM engine). Zero new physics.
16
+ */
17
+
18
+ import { CollisionTypes, DetectorWall, LargeObject, SlitLightSource, SlitScreen } from '@descix/frqtl-web/parts';
19
+
20
+ export default function buildExperiment(exp) {
21
+ var S = 480, WAVELENGTH = 64; // THE WORLD this scene states (its co-init call below) and its one geometry knob
22
+
23
+ exp.createCanvas(S, S, { dimension: 2, wavelengthScale: Math.floor(WAVELENGTH / 16) }); // a 2D canvas + universe, S x S
24
+ var u = exp.universe;
25
+
26
+ var slits = new SlitScreen(u, WAVELENGTH * 1.5, WAVELENGTH, S, WAVELENGTH, true, 1);
27
+ new LargeObject(u, [0, 0, 10, S], CollisionTypes.ABSORB, [], 'start_wall', 'closest', null, 1, [120, 120, 120]);
28
+ var light = new SlitLightSource(u, slits, 10);
29
+ new DetectorWall(u, u.universe_rect, slits.wall1.rect.left + slits.wall1.rect.w + 3 * WAVELENGTH);
30
+ u.experimentTickFunctions.push(light.getTickFunction());
31
+ }
@@ -0,0 +1,196 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — One Emitter 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-one-emitter).
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-two-emitters.js` is the SAME apparatus with NSRC = 2. 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 = 1;
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,114 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — One Slit 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-one-slit).
11
+ */
12
+
13
+ // ── ONE SLIT AT THE CANONICAL LEG (frqtl-visual-bite, 2026-08-04) ────────────
14
+ //
15
+ // The companion arm to `dse-two-slits`: the IDENTICAL apparatus (same emitter, same
16
+ // barrier x-position, same slit-1 aperture at y=120, same leg D, same plate, same
17
+ // chart) with slit 2 (the y=280 aperture) WALLED OFF — a single opaque barrier
18
+ // segment stands where `wall2` + the slit-2 `Tunnel` + `wall3` stood in
19
+ // `dse-two-slits`. Nothing else moves: this rung and `dse-two-slits` differ by
20
+ // exactly one declared thing — how many apertures the barrier has.
21
+ //
22
+ // PRE-FLIGHT PREDICTION (recorded before capture; see the ring ladder in
23
+ // the header comment of scenes/dse-one-emitter.js, and `dse-one-emitter`'s use of the same
24
+ // ladder off-centre here): a slit's exit is a phase-locked point source at its own
25
+ // y (the `dse-tunnel-rebuild` composition), so with ONLY slit 1 open the surviving
26
+ // population is governed by ONE point source at y=120, D=lambda_eff/4=80 past the
27
+ // exit plane. Its own ladder (y0 +/- lambda/4, +/- 3*lambda/4, ...) places bands at
28
+ // y = 120-80=40, 120+80=200, and (the m=1 term) 120+240=360 -- the m=1 LOWER term
29
+ // (120-240=-120) falls off the 400px canvas and does not exist.
30
+ // PREDICTED: three bands at y=40, y=200, y=360; a dip/minimum on either side of
31
+ // each; NO claim about height relative to `dse-two-slits` — degree of
32
+ // reinforcement is a different question this bite does not ask. The claim under
33
+ // test is PRESENCE AND POSITION: does structure appear at all with one aperture,
34
+ // and does it land on this specific off-centre ladder rather than smearing into
35
+ // a single broad structureless lobe centred on y=120 (the naive "single-slit
36
+ // diffraction hump" expectation, which this engine's per-source ring mechanism
37
+ // does NOT predict — if that is what appears instead, it is a real null.)
38
+
39
+ import { SphereEmitter } from '@descix/frqtl-web/parts';
40
+
41
+ export default function buildExperiment(exp) {
42
+ // One Slit -- the SAME apparatus as dse-two-slits with slit 2 WALLED OFF. Barrier =
43
+ // Wall segments (destroy); the surviving slit 1 = a directed Tunnel with the exit
44
+ // gate collapsed + phase-reset (identical to dse-two-slits' slit1_channel).
45
+ var CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
46
+ // ── THE ONLY LENGTH — IDENTICAL to the canonical-leg pair and dse-two-slits ─────
47
+ var lamPx = 320;
48
+ var lamArg = 3.2; // fullness 1/3.2 = 0.3125 -> yellow
49
+ var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
50
+ var wlScale = Math.floor(fwl / 4); // 100
51
+ if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
52
+ throw new Error('[dse-one-slit] lamPx ' + lamPx + ' is not realisable as lambda_eff: the ' +
53
+ 'engine can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
54
+ }
55
+ // ── SLIT 1's POSITION — IDENTICAL to dse-two-slits' slit-1 aperture ─────────────
56
+ // sep is kept only to DERIVE slitCY1 at the exact same y dse-two-slits used (120) —
57
+ // there is no slit 2 in this rung, so it is never used to place a second aperture.
58
+ var legFrac = 1 / 4;
59
+ var D = Math.round(lamPx * legFrac); // 80 — the SAME leg, measured from the
60
+ // (single) slit EXIT plane
61
+ var sep = lamPx / 2; // 160 — dse-two-slits' source separation,
62
+ // reused ONLY to derive slit 1's y below
63
+ var cy = Math.round(H / 2); // 200
64
+ var slitCY1 = cy - sep / 2; // 120 — IDENTICAL y to dse-two-slits' slit 1
65
+ // ── THE BARRIER — one opaque plate, ONE slit aperture (slit 2 walled off) ───────
66
+ var barrierW = Math.round(lamPx / 8); // 40 — plate thickness
67
+ var slitH = Math.round(lamPx / 8); // 40 — slit aperture height (before liners)
68
+ var linerH = Math.round(lamPx / 64); // 5 — liner thickness (unjumpable: max step 2px/tick)
69
+ var bore = slitH - 2 * linerH; // 30 — the clear aperture between the liners
70
+ var srcX = Math.round(lamPx / 16); // 20 — the emitter, well behind the barrier
71
+ var barrierL = srcX + Math.round(lamPx / 2); // 180 — half a wavelength of travel room
72
+ var slit1Top = slitCY1 - slitH / 2; // 100 -- slit 1 spans y 100..140
73
+ var exitX = barrierL + barrierW; // 220 — the slit EXIT plane (barrier back face)
74
+ var detX = exitX + D; // 300 — SAME leg as dse-two-slits
75
+ var detW = Math.round(lamPx / 64); // 5 — plate thickness (flux probe, sub-feature)
76
+ var sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
77
+ // ── THE LIGHT SOURCE — ONE emitter, behind the barrier, lighting the one slit ───
78
+ var SRC = {
79
+ preset: 'hemisphere', is2D: true, chargeMode: 'random', center: [srcX, cy],
80
+ wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
81
+ burstSize: 8, emitEveryTicks: 1
82
+ };
83
+ exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
84
+ exp.addObject('SphereEmitter', SRC);
85
+ // Barrier segments — opaque destroy sinks, leaving ONE slit gap (slit 1 only). Where
86
+ // dse-two-slits had wall2 (a short strip between the two slits) + a slit-2 Tunnel +
87
+ // wall3 (below slit 2), this rung has ONE solid wall from slit 1's bottom liner all
88
+ // the way to the far edge — slit 2's aperture is WALLED, not narrowed or moved.
89
+ exp.addObject('Wall', { rect: [barrierL, 0, barrierW, slit1Top],
90
+ collisionBehavior: 'destroy', name: 'wall1', color: [200, 60, 60] });
91
+ exp.addObject('Wall', { rect: [barrierL, slit1Top + slitH, barrierW, H - (slit1Top + slitH)],
92
+ collisionBehavior: 'destroy', name: 'wall2_blocked_slit2', color: [150, 40, 40] });
93
+ // The one surviving slit channel — directed Tunnel, exit gate COLLAPSED + PHASE-RESET
94
+ // (the dse-tunnel-rebuild composition): its own mouth stands in for a point source at
95
+ // y=120, phase-locked — what a SphereEmitter would do at that pixel.
96
+ exp.addObject('Tunnel', {
97
+ left: barrierL, top: slit1Top + linerH, length: barrierW, bore: bore,
98
+ wallThickness: linerH, axis: 'x', mode: 'directed',
99
+ exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
100
+ name: 'slit1_channel', color: [80, 140, 220]
101
+ });
102
+ // FAR-EDGE SINK — the same experiment-boundary sink the canonical-leg pair uses.
103
+ exp.addObject('Wall', { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
104
+ name: 'far_sink', color: [120, 120, 120], alpha: 40 });
105
+ // ── THE PLATE — SAME instrument as dse-two-slits: a counting wall that does NOT
106
+ // absorb, at the SAME leg D past the new source plane (the one slit exit).
107
+ var screen = exp.addObject('Wall', { rect: [detX, 0, detW, H],
108
+ preset: 'transparent-probe', markTag: 'plate', name: 'screen', color: [120, 200, 120] });
109
+ exp.addObject('LiveChart', {
110
+ source: screen, orientation: 90,
111
+ rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
112
+ series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
113
+ });
114
+ }
@@ -0,0 +1,70 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * SCENE — Photoelectric
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-photoelectric).
11
+ */
12
+
13
+ // ── DSE Lab (CEO 2026-07-24, DSE-rebuild-before-republish; consolidated 2026-07-26)
14
+ // — the photoelectric experiment + THE double-slit, built on the full new
15
+ // component set (SphereEmitter chargeMode random, v2 Walls + presets, in-scene
16
+ // LiveChart). The single canonical double-slit is 2C (on the Tunnel object); the
17
+ // old Light-Lab r9 and DSE 2A double-slit rungs were retired.
18
+ // 2B — the photoelectric effect on the new component set (CEO ruling 2026-07-24:
19
+ // "leave as is now" = the CURRENT absorb/radiate drip, NO workFunction threshold).
20
+ // An incoming light stream charges an ABSORBING Wall (ABSORB v2 real backing-frame mass:
21
+ // fills to capacity, then per-tick radiates lower-dimension leaves out its back with
22
+ // p=1/capacity, mass conserved). A LiveChart on the absorber's absorb-stats series shows
23
+ // stored/absorbed/radiated as the wall charges; a second Detector Screen BEHIND the wall
24
+ // counts the RADIATED quanta (the photoelectron detector) — and its own live arrivals chart
25
+ // shows them being counted. SU1 — the LO5 absorb/radiate golden, composed, zero new physics.
26
+
27
+ import { SphereEmitter } from '@descix/frqtl-web/parts';
28
+
29
+ export default function buildExperiment(exp) {
30
+ // 2B (Photoelectric) — light stream → ABSORB wall (charges + radiates) → a Detector
31
+ // Screen BEHIND counting the radiated photoelectrons. Two live charts: the wall's
32
+ // charge ledger (absorberStats) and the rear detector's arrivals.
33
+ var W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
34
+ var cy = Math.round(H / 2);
35
+ // The incoming light stream: a thin +x beam of oppositely-charged dim-2 leaves. A
36
+ // SPARSE flux (1 leaf / 2 ticks) keeps the slow absorb/radiate drip radiation-dominated
37
+ // (a dense beam saturates the wall, which then leaks past it — the deferred emergent-
38
+ // probability threshold would gate that; see 2B prose).
39
+ var SRC = {
40
+ preset: 'particle-walk', is2D: true, center: [Math.round(W * 0.1), cy],
41
+ chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
42
+ burstSize: 1, emitEveryTicks: 2
43
+ };
44
+ exp.createCanvas(W, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
45
+ exp.addObject('SphereEmitter', SRC);
46
+ // The absorbing wall (ABSORB v2): charges up to capacity, then radiates lower-dim
47
+ // leaves out its back (mass conserved) — the photocathode.
48
+ var absorber = exp.addObject('Wall', {
49
+ rect: [Math.round(W * 0.45), Math.round(H * 0.25), 20, Math.round(H * 0.5)],
50
+ collisionBehavior: 'absorb', capacity: 64, name: 'absorber', color: [150, 90, 210]
51
+ });
52
+ // The photoelectron detector: a Detector Screen (destroy + count) BEHIND the absorber,
53
+ // counting the radiated quanta that emerge past it.
54
+ var photodetector = exp.addObject('Wall', {
55
+ rect: [Math.round(W * 0.72), Math.round(H * 0.2), 20, Math.round(H * 0.6)],
56
+ preset: 'detector-screen', name: 'photodetector', color: [120, 200, 120]
57
+ });
58
+ // The wall's charge ledger (stored / absorbed / radiated) — the absorb-stats series.
59
+ exp.addObject('LiveChart', {
60
+ source: absorber, projector: 'absorberStats',
61
+ rect: [Math.round(W * 0.04), Math.round(H * 0.03), Math.round(W * 0.32), Math.round(H * 0.28)],
62
+ series: 'bar', label: 'wall charge (stored/absorbed/radiated)', refreshMs: 100, color: [30, 22, 44], alpha: 230
63
+ });
64
+ // The photoelectron count beyond the wall — the rear detector's live arrivals.
65
+ exp.addObject('LiveChart', {
66
+ source: photodetector,
67
+ rect: [Math.round(W * 0.63), Math.round(H * 0.03), Math.round(W * 0.32), Math.round(H * 0.28)],
68
+ series: 'bar', label: 'photoelectrons counted', refreshMs: 100, color: [22, 40, 30], alpha: 230
69
+ });
70
+ }