@descix/frqtl-node 0.6.1-p3.4 → 0.7.0-p0
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 +3 -3
- package/README.md +4 -3
- package/dist/dist-manifest.json +26 -56
- package/dist/frqtl/experiments/_shared/empty-sketch.js +17 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +6 -6
- package/dist/frqtl/objects/setup/setupFxSmoke.js +2 -2
- package/dist/frqtl/objects/setup/setupG3Collide.js +1 -1
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +2 -2
- package/dist/frqtl/objects/v1-replacements.js +1 -1
- package/dist/frqtl/parts/OvenBox.js +2 -2
- package/dist/frqtl/parts/SphereEmitter.js +1 -1
- package/dist/shim/engine/router.js +18 -6
- package/dist/shim/engine/site-assemble.mjs +268 -0
- package/dist/shim/engine/site-graph.js +42 -14
- package/dist/shim/host/device.js +1 -1
- package/dist/shim/index.js +3 -7
- package/dist/shim/notebook/catalog.js +2 -2
- package/dist/shim/notebook/format.js +1 -1
- package/dist/shim/notebook/knob.js +1 -1
- package/dist/shim/notebook/notebook-knobs.js +1 -1
- package/dist/shim/project/specifiers.js +8 -8
- package/dist/shim/registry/copy-gate.js +3 -3
- package/dist/shim/registry/index.js +9 -12
- package/dist/shim/registry/roster-link.js +142 -0
- package/dist/shim/registry/roster.js +595 -0
- package/dist/shim/registry/scene-source.js +80 -105
- package/dist/shim/registry/studio-landing.js +15 -7
- package/dist/shim/render/sun-look.js +1 -2
- package/llms.txt +4 -2
- package/package.json +1 -1
- package/dist/frqtl/scenes/absorbing-wall-3d.js +0 -37
- package/dist/frqtl/scenes/absorbing-wall.js +0 -36
- package/dist/frqtl/scenes/atomic-model.js +0 -91
- package/dist/frqtl/scenes/big-bang.js +0 -49
- package/dist/frqtl/scenes/blackbody.js +0 -84
- package/dist/frqtl/scenes/circuit-sat.js +0 -131
- package/dist/frqtl/scenes/collision-matrix-3d.js +0 -110
- package/dist/frqtl/scenes/counting-wall-3d.js +0 -40
- package/dist/frqtl/scenes/counting-wall.js +0 -42
- package/dist/frqtl/scenes/double-slit.js +0 -34
- package/dist/frqtl/scenes/dse-one-emitter.js +0 -200
- package/dist/frqtl/scenes/dse-one-slit.js +0 -118
- package/dist/frqtl/scenes/dse-photoelectric.js +0 -74
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +0 -279
- package/dist/frqtl/scenes/dse-two-emitters.js +0 -201
- package/dist/frqtl/scenes/dse-two-slits.js +0 -136
- package/dist/frqtl/scenes/empty.js +0 -17
- package/dist/frqtl/scenes/laser-buildup.js +0 -56
- package/dist/frqtl/scenes/mirror-3d.js +0 -60
- package/dist/frqtl/scenes/mirror-route.js +0 -164
- package/dist/frqtl/scenes/mirror.js +0 -42
- package/dist/frqtl/scenes/omni-3d.js +0 -30
- package/dist/frqtl/scenes/omni-source.js +0 -25
- package/dist/frqtl/scenes/self-interference.js +0 -42
- package/dist/frqtl/scenes/smoking-gun.js +0 -126
- package/dist/frqtl/scenes/straight-walk.js +0 -25
- package/dist/frqtl/scenes/sun.js +0 -42
- package/dist/frqtl/scenes/two-source.js +0 -42
- package/dist/frqtl/scenes/wave.js +0 -29
- package/dist/shim/engine/artifact-shell.js +0 -211
- package/dist/shim/registry/exhibits.js +0 -436
- package/dist/shim/registry/scene-roster.js +0 -35
- package/dist/shim/registry/showcase-link.js +0 -61
- package/dist/shim/registry/walkthrough.js +0 -752
|
@@ -1,118 +0,0 @@
|
|
|
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 (`export const parts` + `exp.declare`, or `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 ────────────────────────────────────────────
|
|
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 scene 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, Tunnel, Wall } from '@descix/frqtl-web/parts';
|
|
40
|
-
|
|
41
|
-
// One Slit -- the SAME apparatus as dse-two-slits with slit 2 WALLED OFF. Barrier =
|
|
42
|
-
// Wall segments (destroy); the surviving slit 1 = a directed Tunnel with the exit
|
|
43
|
-
// gate collapsed + phase-reset (identical to dse-two-slits' slit1_channel).
|
|
44
|
-
const CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
45
|
-
// ── THE ONLY LENGTH — IDENTICAL to the canonical-leg pair and dse-two-slits ─────
|
|
46
|
-
const lamPx = 320;
|
|
47
|
-
const lamArg = 3.2; // fullness 1/3.2 = 0.3125 -> yellow
|
|
48
|
-
const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
49
|
-
const wlScale = Math.floor(fwl / 4); // 100
|
|
50
|
-
// ── SLIT 1's POSITION — IDENTICAL to dse-two-slits' slit-1 aperture ─────────────
|
|
51
|
-
// sep is kept only to DERIVE slitCY1 at the exact same y dse-two-slits used (120) —
|
|
52
|
-
// there is no slit 2 in this rung, so it is never used to place a second aperture.
|
|
53
|
-
const legFrac = 1 / 4;
|
|
54
|
-
const D = Math.round(lamPx * legFrac); // 80 — the SAME leg, measured from the
|
|
55
|
-
// (single) slit EXIT plane
|
|
56
|
-
const sep = lamPx / 2; // 160 — dse-two-slits' source separation,
|
|
57
|
-
// reused ONLY to derive slit 1's y below
|
|
58
|
-
const cy = Math.round(H / 2); // 200
|
|
59
|
-
const slitCY1 = cy - sep / 2; // 120 — IDENTICAL y to dse-two-slits' slit 1
|
|
60
|
-
// ── THE BARRIER — one opaque plate, ONE slit aperture (slit 2 walled off) ───────
|
|
61
|
-
const barrierW = Math.round(lamPx / 8); // 40 — plate thickness
|
|
62
|
-
const slitH = Math.round(lamPx / 8); // 40 — slit aperture height (before liners)
|
|
63
|
-
const linerH = Math.round(lamPx / 64); // 5 — liner thickness (unjumpable: max step 2px/tick)
|
|
64
|
-
const bore = slitH - 2 * linerH; // 30 — the clear aperture between the liners
|
|
65
|
-
const srcX = Math.round(lamPx / 16); // 20 — the emitter, well behind the barrier
|
|
66
|
-
const barrierL = srcX + Math.round(lamPx / 2); // 180 — half a wavelength of travel room
|
|
67
|
-
const slit1Top = slitCY1 - slitH / 2; // 100 -- slit 1 spans y 100..140
|
|
68
|
-
const exitX = barrierL + barrierW; // 220 — the slit EXIT plane (barrier back face)
|
|
69
|
-
const detX = exitX + D; // 300 — SAME leg as dse-two-slits
|
|
70
|
-
const detW = Math.round(lamPx / 64); // 5 — plate thickness (flux probe, sub-feature)
|
|
71
|
-
const sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
|
|
72
|
-
|
|
73
|
-
export const parts = {
|
|
74
|
-
source: [SphereEmitter, {
|
|
75
|
-
preset: 'hemisphere', is2D: true, chargeMode: 'random', center: [srcX, cy],
|
|
76
|
-
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
77
|
-
burstSize: 8, emitEveryTicks: 1
|
|
78
|
-
}],
|
|
79
|
-
// Barrier segments — opaque destroy sinks, leaving ONE slit gap (slit 1 only). Where
|
|
80
|
-
// dse-two-slits had wall2 (a short strip between the two slits) + a slit-2 Tunnel +
|
|
81
|
-
// wall3 (below slit 2), this rung has ONE solid wall from slit 1's bottom liner all
|
|
82
|
-
// the way to the far edge — slit 2's aperture is WALLED, not narrowed or moved.
|
|
83
|
-
wall1: [Wall, { rect: [barrierL, 0, barrierW, slit1Top],
|
|
84
|
-
collisionBehavior: 'destroy', color: [200, 60, 60] }],
|
|
85
|
-
wall2_blocked_slit2: [Wall, { rect: [barrierL, slit1Top + slitH, barrierW, H - (slit1Top + slitH)],
|
|
86
|
-
collisionBehavior: 'destroy', color: [150, 40, 40] }],
|
|
87
|
-
// The one surviving slit channel — directed Tunnel, exit gate COLLAPSED + PHASE-RESET
|
|
88
|
-
// (the dse-tunnel-rebuild composition): its own mouth stands in for a point source at
|
|
89
|
-
// y=120, phase-locked — what a SphereEmitter would do at that pixel.
|
|
90
|
-
slit1_channel: [Tunnel, {
|
|
91
|
-
left: barrierL, top: slit1Top + linerH, length: barrierW, bore: bore,
|
|
92
|
-
wallThickness: linerH, axis: 'x', mode: 'directed',
|
|
93
|
-
exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
|
|
94
|
-
color: [80, 140, 220]
|
|
95
|
-
}],
|
|
96
|
-
// FAR-EDGE SINK — the same experiment-boundary sink the canonical-leg pair uses.
|
|
97
|
-
far_sink: [Wall, { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
|
|
98
|
-
color: [120, 120, 120], alpha: 40 }],
|
|
99
|
-
// ── THE PLATE — SAME instrument as dse-two-slits: a counting wall that does NOT
|
|
100
|
-
// absorb, at the SAME leg D past the new source plane (the one slit exit).
|
|
101
|
-
screen: [Wall, { rect: [detX, 0, detW, H],
|
|
102
|
-
preset: 'transparent-probe', markTag: 'plate', color: [120, 200, 120] }],
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export default function buildExperiment(exp) {
|
|
106
|
-
if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
|
|
107
|
-
throw new Error('[dse-one-slit] lamPx ' + lamPx + ' is not realisable as lambda_eff: the ' +
|
|
108
|
-
'engine can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
109
|
-
}
|
|
110
|
-
// ── THE LIGHT SOURCE — ONE emitter, behind the barrier, lighting the one slit ───
|
|
111
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
112
|
-
const p = exp.declare(parts);
|
|
113
|
-
exp.addObject('LiveChart', {
|
|
114
|
-
source: p.screen, orientation: 90,
|
|
115
|
-
rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
|
|
116
|
-
series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
|
|
117
|
-
});
|
|
118
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
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 (`export const parts` + `exp.declare`, or `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
|
|
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 (the CURRENT absorb/radiate
|
|
19
|
-
// 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, Wall } from '@descix/frqtl-web/parts';
|
|
28
|
-
|
|
29
|
-
// 2B (Photoelectric) — light stream → ABSORB wall (charges + radiates) → a Detector
|
|
30
|
-
// Screen BEHIND counting the radiated photoelectrons. Two live charts: the wall's
|
|
31
|
-
// charge ledger (absorberStats) and the rear detector's arrivals.
|
|
32
|
-
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
33
|
-
const cy = Math.round(H / 2);
|
|
34
|
-
|
|
35
|
-
export const parts = {
|
|
36
|
-
source: [SphereEmitter, {
|
|
37
|
-
preset: 'particle-walk', is2D: true, center: [Math.round(W * 0.1), cy],
|
|
38
|
-
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
39
|
-
burstSize: 1, emitEveryTicks: 2
|
|
40
|
-
}],
|
|
41
|
-
// The absorbing wall (ABSORB v2): charges up to capacity, then radiates lower-dim
|
|
42
|
-
// leaves out its back (mass conserved) — the photocathode.
|
|
43
|
-
absorber: [Wall, {
|
|
44
|
-
rect: [Math.round(W * 0.45), Math.round(H * 0.25), 20, Math.round(H * 0.5)],
|
|
45
|
-
collisionBehavior: 'absorb', capacity: 64, color: [150, 90, 210]
|
|
46
|
-
}],
|
|
47
|
-
// The photoelectron detector: a Detector Screen (destroy + count) BEHIND the absorber,
|
|
48
|
-
// counting the radiated quanta that emerge past it.
|
|
49
|
-
photodetector: [Wall, {
|
|
50
|
-
rect: [Math.round(W * 0.72), Math.round(H * 0.2), 20, Math.round(H * 0.6)],
|
|
51
|
-
preset: 'detector-screen', color: [120, 200, 120]
|
|
52
|
-
}],
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export default function buildExperiment(exp) {
|
|
56
|
-
// The incoming light stream: a thin +x beam of oppositely-charged dim-2 leaves. A
|
|
57
|
-
// SPARSE flux (1 leaf / 2 ticks) keeps the slow absorb/radiate drip radiation-dominated
|
|
58
|
-
// (a dense beam saturates the wall, which then leaks past it — the deferred emergent-
|
|
59
|
-
// probability threshold would gate that; see 2B prose).
|
|
60
|
-
exp.createCanvas(W, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
61
|
-
const p = exp.declare(parts);
|
|
62
|
-
// The wall's charge ledger (stored / absorbed / radiated) — the absorb-stats series.
|
|
63
|
-
exp.addObject('LiveChart', {
|
|
64
|
-
source: p.absorber, projector: 'absorberStats',
|
|
65
|
-
rect: [Math.round(W * 0.04), Math.round(H * 0.03), Math.round(W * 0.32), Math.round(H * 0.28)],
|
|
66
|
-
series: 'bar', label: 'wall charge (stored/absorbed/radiated)', refreshMs: 100, color: [30, 22, 44], alpha: 230
|
|
67
|
-
});
|
|
68
|
-
// The photoelectron count beyond the wall — the rear detector's live arrivals.
|
|
69
|
-
exp.addObject('LiveChart', {
|
|
70
|
-
source: p.photodetector,
|
|
71
|
-
rect: [Math.round(W * 0.63), Math.round(H * 0.03), Math.round(W * 0.32), Math.round(H * 0.28)],
|
|
72
|
-
series: 'bar', label: 'photoelectrons counted', refreshMs: 100, color: [22, 40, 30], alpha: 230
|
|
73
|
-
});
|
|
74
|
-
}
|
|
@@ -1,279 +0,0 @@
|
|
|
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 (`export const parts` + `exp.declare`, or `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), declared in `parts` so a hand edit is a code edit. 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). 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).
|
|
25
|
-
// `parts` is written in the DEFAULT mode, 3D; in 2D the scene declares flat2D(parts), the
|
|
26
|
-
// scene's own statement of its 2D delta — there is NO second (`-3d`) source file:
|
|
27
|
-
// * the emitter opens the z-axis (is2D:!IS3D → activeAxes.z, a 3D ball of directions);
|
|
28
|
-
// * every barrier / tunnel / detector gains the SAME z-slab (z/depth → a 6-element rect),
|
|
29
|
-
// so the slit wall is a real SLOT-in-a-PLATE and what you SEE is what collides;
|
|
30
|
-
// * two invisible `destroy` z-collimators sit flush OUTSIDE the plate z-band (the
|
|
31
|
-
// cl1a-mirror-3d pattern — the universe z-edge moved inward, the sanctioned
|
|
32
|
-
// experiment-boundary sink), so no quantum can sail BESIDE a plate and read as
|
|
33
|
-
// pass-through, and every alive quantum provably faces the barrier;
|
|
34
|
-
// * 3D makes the detector a GraphingDetectorWall that paints its hits over its OWN
|
|
35
|
-
// face (y,z), spatially registered — the fringes read as BANDS on the plate, in the
|
|
36
|
-
// place the physics put them. In 2D the screen is a plain counting Wall.
|
|
37
|
-
// The 3D physics is the SAME engine physics (2D≡3D, bench H2a/H2b) — zero new physics.
|
|
38
|
-
//
|
|
39
|
-
// COLOUR: the beam is YELLOW because it IS a yellow-band beam — the leaves are emitted
|
|
40
|
-
// COLOURLESS and the renderer derives their colour from LightColor at their fullness
|
|
41
|
-
// (= mass/capacity = 1/wavelength). See the emitter block in the source for the two
|
|
42
|
-
// independent dials (band vs pixel wavelength) and why the fringe geometry is unchanged.
|
|
43
|
-
|
|
44
|
-
import { CameraControls, FrqtlControlOverlay, FrqtlControlsPanel, GraphingDetectorWall, SphereEmitter, Tunnel, Wall } from '@descix/frqtl-web/parts';
|
|
45
|
-
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
46
|
-
import * as FrqtlDisplay from '@descix/frqtl-web/compat/renderer/display-slot.js';
|
|
47
|
-
|
|
48
|
-
const CW = 600, H = 400, WORLD_D = 600; // THE WORLD this scene states (its co-init call below)
|
|
49
|
-
// GEOMETRY and WAVELENGTH are two INDEPENDENT dials, and they are kept apart on
|
|
50
|
-
// purpose: `wl` sizes the PLATE (slit height, gap, wall placement) and nothing else,
|
|
51
|
-
// while `lamPx` is the transverse wavelength in pixels. Doubling lambda must NOT move
|
|
52
|
-
// the slit wall — if one variable drove both, the aperture would silently walk every
|
|
53
|
-
// time the beam was re-tuned and no two runs would be comparable.
|
|
54
|
-
const wl = 64, sh = Math.floor(wl * 0.8), gap = Math.floor(wl * 0.8); // 51, 51
|
|
55
|
-
const lamPx = 128; // the TRANSVERSE wavelength, in pixels
|
|
56
|
-
const groupH = sh + gap + sh; // 153
|
|
57
|
-
const gcY = H / 2; // 300 @ H=600
|
|
58
|
-
const s1Top = gcY - groupH / 2; // 223.5
|
|
59
|
-
const gapTop = s1Top + sh; // 274.5
|
|
60
|
-
const s2Bot = gcY + groupH / 2; // 376.5
|
|
61
|
-
const s2Top = s2Bot - sh; // 325.5
|
|
62
|
-
const srcX = 20; // source x (left, near the slit wall)
|
|
63
|
-
const L = wl * 1.5, W = wl; // slit-group left 96, width 64 (wall front..back = [96,160])
|
|
64
|
-
// THE DETECTOR LEG, DERIVED FROM THE RING LADDER (zero free parameters).
|
|
65
|
-
// A phase-locked POINT source lays down density rings at r_m = (lambda/4)*sqrt((2m+1)^2+1)
|
|
66
|
-
// measured from the emission point (guide row r3-radius-stall-shells). Each slit exit below
|
|
67
|
-
// IS such a source, so the plate should be placed where
|
|
68
|
-
// a NAMED ring crosses it rather than at a symmetric figure. Put the MIDLINE on ring m=2
|
|
69
|
-
// of BOTH slits: with the exits at +/-halfSep from the midline, that needs a leg of
|
|
70
|
-
// D = sqrt(r_2^2 - halfSep^2) = sqrt(163.2^2 - 51^2) = 155
|
|
71
|
-
// so the two ring-2 circles intersect exactly on the axis and the centre is DOUBLY
|
|
72
|
-
// reinforced. Rings inside D (m=0 at 45.3, m=1 at 101.2) never reach the plate and
|
|
73
|
-
// contribute no band — a measured property of the ladder, not an assumption.
|
|
74
|
-
// This REPLACES the old symmetric-leg figure, whose own comment made it conditional on
|
|
75
|
-
// the slits becoming directed emitters; the exit gates below are that condition.
|
|
76
|
-
const ringR = function (m) { return (lamPx / 4) * Math.sqrt((2 * m + 1) * (2 * m + 1) + 1); };
|
|
77
|
-
const slitCY1 = s1Top + sh / 2, slitCY2 = s2Top + sh / 2; // 249, 351 — the bore midlines
|
|
78
|
-
const halfSep = gcY - slitCY1; // 51
|
|
79
|
-
const detW = 20; // the detector plate's own thickness
|
|
80
|
-
const D = Math.round(Math.sqrt(ringR(2) * ringR(2) - halfSep * halfSep)); // 155
|
|
81
|
-
const detX = (L + W) + D; // 315
|
|
82
|
-
const linerH = Math.min(10, Math.floor(sh * 0.2)), bore = sh - 2 * linerH; // 10, 31
|
|
83
|
-
// THE 3D DELTA: the plate z-band. Every barrier / tunnel liner / detector in `parts`
|
|
84
|
-
// carries the SAME z-slab (z: -zHalf, depth: 2 * zHalf), so the slit wall is a genuine SLOT
|
|
85
|
-
// IN A PLATE standing in space (the slits are slots long in z — the 2D figure extruded).
|
|
86
|
-
// flat2D (below) deletes the slab, so the 2D scene is the flat figure.
|
|
87
|
-
const zHalf = Math.round(groupH / 2); // 77 @ wl 64
|
|
88
|
-
// THE BEAM'S COLOUR — its own frequency band, never a forced recolor. The emitter
|
|
89
|
-
// emits COLOURLESS leaves (no renderColor arg ⇒ no string_map['render.color']), so the
|
|
90
|
-
// renderer derives the colour from the ONE authority, LightColor, at the leaf's
|
|
91
|
-
// fullness. TWO INDEPENDENT dials produce it, both on the emitter:
|
|
92
|
-
// fullness = mass/capacity = 1 / wavelength -> the spectral BAND (LightColor)
|
|
93
|
-
// lambda_px = wavelength x wavelengthScale -> the transverse wave, in PIXELS
|
|
94
|
-
// (wavelengthScale = floor(fundamentalWaveLength / 4)). So the band is chosen by
|
|
95
|
-
// scaling fundamentalWaveLength INVERSELY with the wavelength arg — lambda_px (the
|
|
96
|
-
// fringe geometry) is untouched. 1/3.2 = 0.3125 sits in the YELLOW band; the rung's
|
|
97
|
-
// renderState declares the spectrally correct 'roygbiv' ramp, so the beam renders
|
|
98
|
-
// [255,243,0]. lambda_px = 3.2 x 40 = 128, INDEPENDENT of the 64-derived plate.
|
|
99
|
-
// THIS BEAM IS LIGHT, so the band is free: these leaves are born AT the universe's
|
|
100
|
-
// fundamental dimension (leafDimension == fundamentalDimension == 2), and a leaf at the
|
|
101
|
-
// fundamental ALWAYS steps, P=1, whatever its fullness (bench M1) — the fullness
|
|
102
|
-
// move-gate (bench M2) simply does not apply, and P=1 draws no RNG. Measured on the
|
|
103
|
-
// live detector: the fringe histogram is bin-for-bin the SAME as the (64, 16) tuple
|
|
104
|
-
// (t=30s, 16px profile: 11260 vs 11262 hits, every bin within 1, COM 201.7 both).
|
|
105
|
-
const lamArg = 3.2; // fullness 1/3.2 = 0.3125 -> yellow
|
|
106
|
-
const fwl = 4 * (lamPx / lamArg); // 160 -> wavelengthScale 40 -> lambda_px = 128
|
|
107
|
-
|
|
108
|
-
// The light source — the current SphereEmitter sugar (braided walk), placed at the
|
|
109
|
-
// original source pixel (srcX, gap-center = H/2).
|
|
110
|
-
// is2D:!IS3D opens activeAxes.z — the ball-rejected sampler then draws a 3D BALL of
|
|
111
|
-
// directions (bench S1/S5) instead of the 2D disk.
|
|
112
|
-
// preset 'hemisphere' = 180 degrees of FORWARD emission (D1): the SAME ball-rejected
|
|
113
|
-
// sampler with the forward axis' range halved to [0,R], i.e. the spherical distribution
|
|
114
|
-
// RESTRICTED to the forward half — same physics, pointed one way, so nothing about the
|
|
115
|
-
// walk changes. Forward is +X, which is where the slit wall stands (orientationDeg stays
|
|
116
|
-
// 0), so the half of the burst that used to fly LEFT into the back-scatter sink now goes
|
|
117
|
-
// toward the slits: the same burst size illuminates the aperture roughly twice as hard.
|
|
118
|
-
export const parts = {
|
|
119
|
-
source: [SphereEmitter, {
|
|
120
|
-
preset: 'hemisphere', is2D: false, center: [srcX, Math.round(gcY)],
|
|
121
|
-
chargeMode: 'random', wavelength: lamArg, burstSize: 8, emitEveryTicks: 1,
|
|
122
|
-
fundamentalDimension: 2, fundamentalWaveLength: fwl
|
|
123
|
-
}],
|
|
124
|
-
// Start wall — a plain sink (collisionBehavior:'destroy', [set is_alive=0]) at the
|
|
125
|
-
// left edge, catching back-scatter. It is TRANSPARENT (alpha 40 of 255): the default
|
|
126
|
-
// camera stands BEHIND the emitter, so an opaque slab here would be the first thing in
|
|
127
|
-
// frame and would hide the whole experiment. alpha is a DECLARED Wall field, so the parts block
|
|
128
|
-
// carries it and Restart rebuilds the wall just as sheer. Physics is untouched — the
|
|
129
|
-
// sink still destroys every back-scattered quantum (render-only, SU1).
|
|
130
|
-
start_wall: [Wall, { rect: [0, 0, 10, H], z: -zHalf, depth: 2 * zHalf, collisionBehavior: 'destroy', color: [120, 120, 120], alpha: 40 }],
|
|
131
|
-
// Barrier segments — opaque plain sinks (destroy) leaving the two slit gaps.
|
|
132
|
-
wall1: [Wall, { rect: [L, 0, W, s1Top - 1], z: -zHalf, depth: 2 * zHalf, collisionBehavior: 'destroy', color: [200, 60, 60] }],
|
|
133
|
-
wall2: [Wall, { rect: [L, gapTop, W, gap - 1], z: -zHalf, depth: 2 * zHalf, collisionBehavior: 'destroy', color: [150, 40, 40] }],
|
|
134
|
-
wall3: [Wall, { rect: [L, s2Bot, W, (H - s2Bot) - 1], z: -zHalf, depth: 2 * zHalf, collisionBehavior: 'destroy', color: [200, 60, 60] }],
|
|
135
|
-
// The two slit channels as TUNNELS — walls land at the EXACT original liner positions
|
|
136
|
-
// ([L,s1Top,W,10] & [L,s1Top+sh-10,W,10]); the bore is the clear aperture between them.
|
|
137
|
-
// The Tunnel ferries z/depth onto BOTH walls, so a 3D channel is a real corridor.
|
|
138
|
-
// mode 'directed' (D2): the channel walls are no longer mirrors — a wall hit ejects the
|
|
139
|
-
// quantum back into the bore and RE-EMITS it in a uniformly random direction within 180°
|
|
140
|
-
// of the corridor axis (+x here, derived from axis:'x'). That is what makes the slit
|
|
141
|
-
// itself behave as a semi-circular emitter, which is the classical picture of what a
|
|
142
|
-
// slit does to a wavefront — and it supersedes the mirror mode FOR THIS RUNG: a
|
|
143
|
-
// collimating mirror channel preserves the incoming direction, so the two slits could
|
|
144
|
-
// only ever project two beams. Re-emission is what gives each slit its own angular
|
|
145
|
-
// spread to interfere with. Mirror mode stays available on the object (mode:'mirror',
|
|
146
|
-
// still the default) for the waveguide/light-pipe case it was built for.
|
|
147
|
-
// THE TUNNEL IS A CONFIGURED EMITTER. The exit gate's flags are not
|
|
148
|
-
// plumbing details — they ARE the emitter vocabulary, one-to-one:
|
|
149
|
-
// exitRecenter ON + exitPhaseReset ON -> a POINT SOURCE (every quantum leaves the
|
|
150
|
-
// same pixel with the same phase) — what this rung uses, and what makes the ring
|
|
151
|
-
// ladder sharp: any spread at birth blurs every ring by exactly that spread.
|
|
152
|
-
// exitRecenter OFF + exitPhaseReset ON -> an EXTENDED LOCKED APERTURE, the
|
|
153
|
-
// plane-wave analog (phase-flat across a finite mouth).
|
|
154
|
-
// mode 'directed' spread -> the ANGULAR PRESET (here 180 deg about the
|
|
155
|
-
// corridor axis — the hemisphere preset, expressed at a slit).
|
|
156
|
-
// In 3D the recenter pins the TRANSVERSE axis only, so the slot stays extended in z:
|
|
157
|
-
// each slit is a LINE source and the bands extrude along z — the plate picture.
|
|
158
|
-
// Unifying this with the SphereEmitter preset vocabulary (one owner, ferried — never a
|
|
159
|
-
// second preset list) is an open contract design;
|
|
160
|
-
// this rung uses the existing flags and documents the mapping.
|
|
161
|
-
slit1_channel: [Tunnel, { left: L, top: s1Top + linerH, length: W, bore: bore, wallThickness: linerH, axis: 'x', z: -zHalf, depth: 2 * zHalf, mode: 'directed', exitCollapse: true, exitRecenter: true, exitPhaseReset: true, color: [80, 140, 220] }],
|
|
162
|
-
slit2_channel: [Tunnel, { left: L, top: s2Top + linerH, length: W, bore: bore, wallThickness: linerH, axis: 'x', z: -zHalf, depth: 2 * zHalf, mode: 'directed', exitCollapse: true, exitRecenter: true, exitPhaseReset: true, color: [80, 140, 220] }],
|
|
163
|
-
// The detector SCREEN — a NON-ABSORBING COUNTING wall ('transparent-probe':
|
|
164
|
-
// collisionBehavior 'nothing' + the count hook) in BOTH modes. This is the
|
|
165
|
-
// |psi|^2-faithful screen: an ABSORBING plate removes each quantum at first contact,
|
|
166
|
-
// so the reading becomes a first-arrival caustic that drifts with the leg D and lands
|
|
167
|
-
// at the WRONG radii; counting WITHOUT absorbing reproduces the ring ladder exactly,
|
|
168
|
-
// because the census at a plane IS the density at that plane. The far-edge sink above bounds the
|
|
169
|
-
// population that now flies past. The KIND follows the detector's dimensionality. In 2D it is a plain Wall.
|
|
170
|
-
// In 3D the detector is a PLATE, so it
|
|
171
|
-
// is a GraphingDetectorWall: it paints its OWN hit grid onto its own faces, spatially
|
|
172
|
-
// REGISTERED (a hot cell sits exactly where the collisions landed) — the wall is the
|
|
173
|
-
// axis, so there is no chart chrome in the scene. Both broad faces carry the map, so it
|
|
174
|
-
// never disappears as you orbit (the back reads as the through-the-window mirror).
|
|
175
|
-
// NOTE: a wall's collided-frame buffer may be drained by ONE consumer per tick — the
|
|
176
|
-
// heatmap painter IS that consumer in 3D.
|
|
177
|
-
// FAR-EDGE SINK. The screen below COUNTS WITHOUT ABSORBING, so quanta fly on past it;
|
|
178
|
-
// this destroy wall at the right edge is the experiment-boundary sink that keeps the
|
|
179
|
-
// population bounded (Invariant 5/7 — only an experiment boundary may destroy).
|
|
180
|
-
far_sink: [Wall, { rect: [CW - 10, 0, 10, H], z: -zHalf, depth: 2 * zHalf, collisionBehavior: 'destroy', color: [120, 120, 120], alpha: 40 }],
|
|
181
|
-
screen: [GraphingDetectorWall, { rect: [detX, 0, detW, H], z: -zHalf, depth: 2 * zHalf, preset: 'transparent-probe', color: [120, 200, 120], alpha: 220, binPx: 12 }],
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* flat2D(parts) — THE 2D MODE of this scene, derived from its 3D `parts`: the same keys in the same order,
|
|
186
|
-
* each entry's props copied with the z-slab (z, depth) deleted, is2D set true (by assignment, so the key
|
|
187
|
-
* keeps its position), and the GraphingDetectorWall plate a plain counting Wall without its paint
|
|
188
|
-
* (alpha, binPx). Pure: it reads only its argument. A hand-added entry flattens by the same rule.
|
|
189
|
-
*/
|
|
190
|
-
function flat2D(parts) {
|
|
191
|
-
var out = {};
|
|
192
|
-
Object.keys(parts).forEach(function (key) {
|
|
193
|
-
var K = parts[key][0];
|
|
194
|
-
var props = Object.assign({}, parts[key][1]);
|
|
195
|
-
delete props.z;
|
|
196
|
-
delete props.depth;
|
|
197
|
-
if ('is2D' in props) { props.is2D = true; }
|
|
198
|
-
if (K === GraphingDetectorWall) { K = Wall; delete props.alpha; delete props.binPx; }
|
|
199
|
-
out[key] = [K, props];
|
|
200
|
-
});
|
|
201
|
-
return out;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export default function buildExperiment(exp) {
|
|
205
|
-
// Double Slit (Tunnel) — the two slit channels are MIRROR TUNNELS (guarded
|
|
206
|
-
// bounce, zero-leak); the detector sits at the SYMMETRIC distance (emitter->front ==
|
|
207
|
-
// back->detector). X placements are wl-derived (canvas-width-independent); Y is
|
|
208
|
-
// centered on H (groupCenterY = H/2).
|
|
209
|
-
// The booted display (the host's renderState.renderer) decides which world this mode makes.
|
|
210
|
-
var IS3D = FrqtlDisplay.name() === 'three';
|
|
211
|
-
var P = IS3D ? parts : flat2D(parts);
|
|
212
|
-
// THE WORLD — first, then parts: a 3D scene + universe (600x400x600) or a 2D canvas + universe.
|
|
213
|
-
if (IS3D) { exp.createScene(CW, H, WORLD_D, SphereEmitter.worldCfg(P.source[1])); }
|
|
214
|
-
else { exp.createCanvas(CW, H, SphereEmitter.worldCfg(P.source[1])); }
|
|
215
|
-
exp.declare(P);
|
|
216
|
-
if (IS3D) {
|
|
217
|
-
// SCENE Z-COLLIMATORS — two INVISIBLE full-canvas destroy boundaries flush OUTSIDE
|
|
218
|
-
// the plate z-band (the cl1a-mirror-3d pattern): the emitted ball's z-tail diffuses,
|
|
219
|
-
// and any quantum beyond the plate band would sail BESIDE the barrier and read as
|
|
220
|
-
// pass-through. Clamping the scene to the plate band makes the barrier provably face
|
|
221
|
-
// EVERY alive quantum. 40px deep: un-jumpable (max z step is +/-2/tick).
|
|
222
|
-
exp.addObject('Wall', { rect: [0, 0, CW, H], z: zHalf, depth: 40,
|
|
223
|
-
collisionBehavior: 'destroy', name: 'zClipFar', visible: false });
|
|
224
|
-
exp.addObject('Wall', { rect: [0, 0, CW, H], z: -zHalf - 40, depth: 40,
|
|
225
|
-
collisionBehavior: 'destroy', name: 'zClipNear', visible: false });
|
|
226
|
-
// PACING (wall-clock only, never the ticks): 240 ticks/s, four times the 1:1 lock on a
|
|
227
|
-
// 60 Hz display, so the interference bands build up four times sooner in real time.
|
|
228
|
-
exp.runner.setRateLimits({ tpsLimit: 240 });
|
|
229
|
-
// THE SWITCH: this scene's god-ray look, computed from the light particles only (godraySource
|
|
230
|
-
// 'leaf') with the 'field' pass (each pixel marches along the local direction of travel), and the
|
|
231
|
-
// per-layer glow under it. The numbers are this scene's own, set by eye against a reference
|
|
232
|
-
// STUDIO picture (godrays 2.2 of [0, 3], godrayDensity 1.5 = the top of [0.2, 1.5],
|
|
233
|
-
// glowIntensity 0.66 of [0, 3]); the renderer declares the keys and their ranges and refuses any
|
|
234
|
-
// other. Default ON: the scene writes ON once at build, through the same function the knob calls.
|
|
235
|
-
// OFF restores EXACTLY what was live before the last ON (a host row, a STUDIO slider), through
|
|
236
|
-
// the same declared door. The knob shows its last written value (a render write has no
|
|
237
|
-
// read-back). The picture changes at the next drawn tick, so in a paused (Design) view it shows
|
|
238
|
-
// once the scene runs.
|
|
239
|
-
var RAYS_ON = { godrays: 2.2, godrayDensity: 1.5, godrayMode: 'field', godraySource: 'leaf', glowIntensity: 0.66 };
|
|
240
|
-
var RAYS_DEFAULT = 1;
|
|
241
|
-
var raysPrior = null; // the live values of RAYS_ON's keys captured at ON; written back at OFF
|
|
242
|
-
function setRays(v) {
|
|
243
|
-
if (v) {
|
|
244
|
-
// Capture only on the OFF -> ON edge: an ON pressed while already ON keeps the pre-ON state.
|
|
245
|
-
if (!raysPrior) {
|
|
246
|
-
raysPrior = {};
|
|
247
|
-
Object.keys(RAYS_ON).forEach(function (k) { raysPrior[k] = exp.runner.getRenderOption(k); });
|
|
248
|
-
}
|
|
249
|
-
exp.runner.setRenderOptions(RAYS_ON);
|
|
250
|
-
} else if (raysPrior) {
|
|
251
|
-
exp.runner.setRenderOptions(raysPrior);
|
|
252
|
-
raysPrior = null;
|
|
253
|
-
}
|
|
254
|
-
// OFF with nothing captured: nothing was written by this switch — no write.
|
|
255
|
-
}
|
|
256
|
-
var knobs = ControlRegistry.makeControlRegistry();
|
|
257
|
-
knobs.registerControls('dse-tunnel-rebuild', [
|
|
258
|
-
{ kind: 'scalar', id: 'godrays', label: 'god rays', channel: 'render', widget: 'toggle',
|
|
259
|
-
min: 0, max: 1, step: 1, value: RAYS_DEFAULT,
|
|
260
|
-
fmt: function (v) { return v ? 'on' : 'off'; },
|
|
261
|
-
onChange: setRays,
|
|
262
|
-
place: { x: 0.02, y: 0.04 } }
|
|
263
|
-
]);
|
|
264
|
-
setRays(RAYS_DEFAULT);
|
|
265
|
-
// Chrome (a page only): expose the knob to a host frame, and mount the overlay through the
|
|
266
|
-
// runner's ONE door (runner.controlHost) — before a canvas is mounted (a notebook pane) there is
|
|
267
|
-
// none, so the knob stays in the exposed registry only.
|
|
268
|
-
FrqtlControlsPanel.exposeRegistry(knobs, { runner: exp.runner });
|
|
269
|
-
if (exp.runner.controlHost) { FrqtlControlOverlay.mount(exp.runner, { registry: knobs, name: 'dse-tunnel-rebuild' }); }
|
|
270
|
-
// THE 3D INSTRUMENT IS THE DETECTOR ITSELF (the GraphingDetectorWall above): the
|
|
271
|
-
// hits are binned over the detector FACE (y,z) and painted where they landed, so the
|
|
272
|
-
// interference structure reads as BANDS on the plate, in the place the physics put
|
|
273
|
-
// them. No free-floating chart panel.
|
|
274
|
-
// MOVE/LOOK camera joysticks — the OMNI_3D pattern (FAIL-SOFT no-op in 2D).
|
|
275
|
-
if (typeof document !== 'undefined' && exp.runner) {
|
|
276
|
-
if (exp.runner.controlHost) { CameraControls.attach(exp.runner, exp.runner.controlHost); }
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|