@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.
- package/CLAUDE.md +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- 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
|
+
}
|