@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.
Files changed (64) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +4 -3
  3. package/dist/dist-manifest.json +26 -56
  4. package/dist/frqtl/experiments/_shared/empty-sketch.js +17 -0
  5. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +6 -6
  6. package/dist/frqtl/objects/setup/setupFxSmoke.js +2 -2
  7. package/dist/frqtl/objects/setup/setupG3Collide.js +1 -1
  8. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +2 -2
  9. package/dist/frqtl/objects/v1-replacements.js +1 -1
  10. package/dist/frqtl/parts/OvenBox.js +2 -2
  11. package/dist/frqtl/parts/SphereEmitter.js +1 -1
  12. package/dist/shim/engine/router.js +18 -6
  13. package/dist/shim/engine/site-assemble.mjs +268 -0
  14. package/dist/shim/engine/site-graph.js +42 -14
  15. package/dist/shim/host/device.js +1 -1
  16. package/dist/shim/index.js +3 -7
  17. package/dist/shim/notebook/catalog.js +2 -2
  18. package/dist/shim/notebook/format.js +1 -1
  19. package/dist/shim/notebook/knob.js +1 -1
  20. package/dist/shim/notebook/notebook-knobs.js +1 -1
  21. package/dist/shim/project/specifiers.js +8 -8
  22. package/dist/shim/registry/copy-gate.js +3 -3
  23. package/dist/shim/registry/index.js +9 -12
  24. package/dist/shim/registry/roster-link.js +142 -0
  25. package/dist/shim/registry/roster.js +595 -0
  26. package/dist/shim/registry/scene-source.js +80 -105
  27. package/dist/shim/registry/studio-landing.js +15 -7
  28. package/dist/shim/render/sun-look.js +1 -2
  29. package/llms.txt +4 -2
  30. package/package.json +1 -1
  31. package/dist/frqtl/scenes/absorbing-wall-3d.js +0 -37
  32. package/dist/frqtl/scenes/absorbing-wall.js +0 -36
  33. package/dist/frqtl/scenes/atomic-model.js +0 -91
  34. package/dist/frqtl/scenes/big-bang.js +0 -49
  35. package/dist/frqtl/scenes/blackbody.js +0 -84
  36. package/dist/frqtl/scenes/circuit-sat.js +0 -131
  37. package/dist/frqtl/scenes/collision-matrix-3d.js +0 -110
  38. package/dist/frqtl/scenes/counting-wall-3d.js +0 -40
  39. package/dist/frqtl/scenes/counting-wall.js +0 -42
  40. package/dist/frqtl/scenes/double-slit.js +0 -34
  41. package/dist/frqtl/scenes/dse-one-emitter.js +0 -200
  42. package/dist/frqtl/scenes/dse-one-slit.js +0 -118
  43. package/dist/frqtl/scenes/dse-photoelectric.js +0 -74
  44. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +0 -279
  45. package/dist/frqtl/scenes/dse-two-emitters.js +0 -201
  46. package/dist/frqtl/scenes/dse-two-slits.js +0 -136
  47. package/dist/frqtl/scenes/empty.js +0 -17
  48. package/dist/frqtl/scenes/laser-buildup.js +0 -56
  49. package/dist/frqtl/scenes/mirror-3d.js +0 -60
  50. package/dist/frqtl/scenes/mirror-route.js +0 -164
  51. package/dist/frqtl/scenes/mirror.js +0 -42
  52. package/dist/frqtl/scenes/omni-3d.js +0 -30
  53. package/dist/frqtl/scenes/omni-source.js +0 -25
  54. package/dist/frqtl/scenes/self-interference.js +0 -42
  55. package/dist/frqtl/scenes/smoking-gun.js +0 -126
  56. package/dist/frqtl/scenes/straight-walk.js +0 -25
  57. package/dist/frqtl/scenes/sun.js +0 -42
  58. package/dist/frqtl/scenes/two-source.js +0 -42
  59. package/dist/frqtl/scenes/wave.js +0 -29
  60. package/dist/shim/engine/artifact-shell.js +0 -211
  61. package/dist/shim/registry/exhibits.js +0 -436
  62. package/dist/shim/registry/scene-roster.js +0 -35
  63. package/dist/shim/registry/showcase-link.js +0 -61
  64. 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
- }