@descix/frqtl-node 0.6.0-p0.2 → 0.6.0-p1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +7 -3
  3. package/dist/dist-manifest.json +54 -48
  4. package/dist/frqtl/assets/vendor/acorn.LICENSE +21 -0
  5. package/dist/frqtl/assets/vendor/acorn.PINNED.json +9 -0
  6. package/dist/frqtl/assets/vendor/acorn.mjs +6266 -0
  7. package/dist/frqtl/compat/renderer/lo-drawn-center.js +41 -0
  8. package/dist/frqtl/experiments/_shared/boot-failure.js +60 -0
  9. package/dist/frqtl/objects/FrqtlApp.js +109 -30
  10. package/dist/frqtl/objects/ObjectFactory.js +4 -5
  11. package/dist/frqtl/objects/RecipeContext.js +451 -9
  12. package/dist/frqtl/objects/instruments/hosted-eyes.js +68 -28
  13. package/dist/frqtl/objects/objects/QuantumEmitter.js +1 -1
  14. package/dist/frqtl/objects/schema/index.js +72 -0
  15. package/dist/frqtl/parts/DetectorWall.js +1 -1
  16. package/dist/frqtl/parts/FrqtlControlsPanel.js +4 -2
  17. package/dist/frqtl/parts/Wall.js +2 -0
  18. package/dist/frqtl/scenes/absorbing-wall-3d.js +18 -14
  19. package/dist/frqtl/scenes/absorbing-wall.js +18 -14
  20. package/dist/frqtl/scenes/atomic-model.js +3 -0
  21. package/dist/frqtl/scenes/big-bang.js +3 -0
  22. package/dist/frqtl/scenes/blackbody.js +14 -11
  23. package/dist/frqtl/scenes/circuit-sat.js +7 -4
  24. package/dist/frqtl/scenes/collision-matrix-3d.js +3 -0
  25. package/dist/frqtl/scenes/counting-wall-3d.js +19 -15
  26. package/dist/frqtl/scenes/counting-wall.js +19 -15
  27. package/dist/frqtl/scenes/double-slit.js +3 -0
  28. package/dist/frqtl/scenes/dse-one-emitter.js +64 -58
  29. package/dist/frqtl/scenes/dse-one-slit.js +59 -55
  30. package/dist/frqtl/scenes/dse-photoelectric.js +28 -24
  31. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +124 -99
  32. package/dist/frqtl/scenes/dse-two-emitters.js +64 -58
  33. package/dist/frqtl/scenes/dse-two-slits.js +69 -65
  34. package/dist/frqtl/scenes/empty.js +3 -0
  35. package/dist/frqtl/scenes/laser-buildup.js +23 -16
  36. package/dist/frqtl/scenes/mirror-3d.js +31 -27
  37. package/dist/frqtl/scenes/mirror-route.js +4 -1
  38. package/dist/frqtl/scenes/mirror.js +24 -20
  39. package/dist/frqtl/scenes/omni-3d.js +7 -4
  40. package/dist/frqtl/scenes/omni-source.js +7 -4
  41. package/dist/frqtl/scenes/self-interference.js +13 -10
  42. package/dist/frqtl/scenes/smoking-gun.js +15 -11
  43. package/dist/frqtl/scenes/straight-walk.js +7 -4
  44. package/dist/frqtl/scenes/sun.js +11 -8
  45. package/dist/frqtl/scenes/two-source.js +22 -17
  46. package/dist/frqtl/scenes/wave.js +10 -7
  47. package/dist/shim/bundle/manifest.js +1 -1
  48. package/dist/shim/engine/router.js +2 -2
  49. package/dist/shim/page-params/index.js +2 -2
  50. package/dist/shim/project/index.js +7 -5
  51. package/dist/shim/project/link.js +12 -5
  52. package/dist/shim/project/parts-module.js +67 -0
  53. package/dist/shim/project/parts-writer.js +453 -0
  54. package/dist/shim/project/specifiers.js +41 -13
  55. package/dist/shim/registry/walkthrough.js +9 -9
  56. package/dist/shim/render-state/apply.js +11 -14
  57. package/llms.txt +2 -2
  58. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung dse-two-slits).
11
11
  */
@@ -44,88 +44,92 @@
44
44
  // fail-loud realisability check is repeated verbatim so a future λ edit here
45
45
  // cannot silently drift from the pair it must match.
46
46
 
47
- import { SphereEmitter } from '@descix/frqtl-web/parts';
47
+ import { SphereEmitter, Tunnel, Wall } from '@descix/frqtl-web/parts';
48
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 = {
49
+ // Two Slits — reproduces dse-two-emitters' picture with the two point sources built
50
+ // as SLIT EXITS in a barrier instead of two free emitters. Barrier = Wall segments
51
+ // (destroy); each slit = a directed Tunnel with the exit gate collapsed + phase-reset
52
+ // (the dse-tunnel-rebuild composition) so the mouth is a phase-locked point source.
53
+ const CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
54
+ // ── THE ONLY LENGTH — IDENTICAL to the canonical-leg pair ───────────────
55
+ const lamPx = 320;
56
+ const lamArg = 3.2; // fullness 1/3.2 = 0.3125 -> yellow
57
+ const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
58
+ const wlScale = Math.floor(fwl / 4); // 100
59
+ // ── SLIT SEPARATION — IDENTICAL to dse-two-emitters' source separation ──
60
+ const legFrac = 1 / 4;
61
+ const D = Math.round(lamPx * legFrac); // 80 — the SAME leg, now measured from the
62
+ // slit EXIT plane, this apparatus's source plane
63
+ const sep = lamPx / 2; // 160 -> slit centres at y 120 and 280 — the
64
+ // EXACT y positions dse-two-emitters used
65
+ const cy = Math.round(H / 2); // 200
66
+ const slitCY1 = cy - sep / 2, slitCY2 = cy + sep / 2; // 120, 280
67
+ // ── THE BARRIER — one opaque plate, two slit apertures ──────────────────
68
+ const barrierW = Math.round(lamPx / 8); // 40 — plate thickness (dimensionless x lamPx,
69
+ // an independent dial from the transverse
70
+ // wavelength, same discipline as dse-tunnel-rebuild)
71
+ const slitH = Math.round(lamPx / 8); // 40 — slit aperture height (before liners)
72
+ const linerH = Math.round(lamPx / 64); // 5 — liner thickness (unjumpable: max step 2px/tick)
73
+ const bore = slitH - 2 * linerH; // 30 — the clear aperture between the liners
74
+ const srcX = Math.round(lamPx / 16); // 20 — the emitter, well behind the barrier
75
+ const barrierL = srcX + Math.round(lamPx / 2); // 180 — half a wavelength of travel room
76
+ const slit1Top = slitCY1 - slitH / 2, slit2Top = slitCY2 - slitH / 2; // 100, 260
77
+ const exitX = barrierL + barrierW; // 220 — the slit EXIT plane (barrier back face)
78
+ const detX = exitX + D; // 300 — SAME leg as dse-two-emitters, now
79
+ // measured from the slit exits
80
+ const detW = Math.round(lamPx / 64); // 5 — plate thickness (flux probe, sub-feature)
81
+ const sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
82
+
83
+ export const parts = {
84
+ source: [SphereEmitter, {
92
85
  preset: 'hemisphere', is2D: true, chargeMode: 'random', center: [srcX, cy],
93
86
  wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
94
87
  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);
88
+ }],
98
89
  // 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] });
90
+ wall1: [Wall, { rect: [barrierL, 0, barrierW, slit1Top],
91
+ collisionBehavior: 'destroy', color: [200, 60, 60] }],
92
+ wall2: [Wall, { rect: [barrierL, slit1Top + slitH, barrierW, slit2Top - (slit1Top + slitH)],
93
+ collisionBehavior: 'destroy', color: [150, 40, 40] }],
94
+ wall3: [Wall, { rect: [barrierL, slit2Top + slitH, barrierW, H - (slit2Top + slitH)],
95
+ collisionBehavior: 'destroy', color: [200, 60, 60] }],
105
96
  // The two slit channels — directed Tunnels, exit gate COLLAPSED + PHASE-RESET (the
106
97
  // dse-tunnel-rebuild composition): each channel's own mouth stands in for a point
107
98
  // source at the slit's own y, phase-locked — what a SphereEmitter would do at that pixel.
108
- exp.addObject('Tunnel', {
99
+ slit1_channel: [Tunnel, {
109
100
  left: barrierL, top: slit1Top + linerH, length: barrierW, bore: bore,
110
101
  wallThickness: linerH, axis: 'x', mode: 'directed',
111
102
  exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
112
- name: 'slit1_channel', color: [80, 140, 220]
113
- });
114
- exp.addObject('Tunnel', {
103
+ color: [80, 140, 220]
104
+ }],
105
+ slit2_channel: [Tunnel, {
115
106
  left: barrierL, top: slit2Top + linerH, length: barrierW, bore: bore,
116
107
  wallThickness: linerH, axis: 'x', mode: 'directed',
117
108
  exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
118
- name: 'slit2_channel', color: [80, 140, 220]
119
- });
109
+ color: [80, 140, 220]
110
+ }],
120
111
  // 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 });
112
+ far_sink: [Wall, { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
113
+ color: [120, 120, 120], alpha: 40 }],
123
114
  // ── THE PLATE — SAME instrument as the canonical-leg pair: a counting wall that does
124
115
  // 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] });
116
+ screen: [Wall, { rect: [detX, 0, detW, H],
117
+ preset: 'transparent-probe', markTag: 'plate', color: [120, 200, 120] }],
118
+ };
119
+
120
+ export default function buildExperiment(exp) {
121
+ if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
122
+ throw new Error('[dse-two-slits] lamPx ' + lamPx + ' is not realisable as lambda_eff: the ' +
123
+ 'engine can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
124
+ }
125
+ // ── THE LIGHT SOURCE — ONE emitter, behind the barrier, lighting BOTH slits ──
126
+ // preset 'hemisphere': 180 degrees of FORWARD emission, aimed at the barrier — the
127
+ // same walk-character (chargeMode/wavelength/fundamentalWaveLength) the slit exits must
128
+ // carry through so the exit's phase-locked point source is at the SAME λ_eff.
129
+ exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
130
+ const p = exp.declare(parts);
127
131
  exp.addObject('LiveChart', {
128
- source: screen, orientation: 90,
132
+ source: p.screen, orientation: 90,
129
133
  rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
130
134
  series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
131
135
  });
@@ -8,7 +8,10 @@
8
8
  * lib/shim/registry/scene-source.js::EMPTY_SCENE (not a walk-through rung).
9
9
  */
10
10
 
11
+ export const parts = {};
12
+
11
13
  export default function buildExperiment(exp) {
12
14
  // THE WORLD: a 600x400 2D canvas + universe on the dim-2 fundamental, λ multiplier 8.
13
15
  exp.createCanvas(600, 400, { dimension: 2, wavelengthScale: 8 });
16
+ exp.declare(parts);
14
17
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung r4b-laser-buildup).
11
11
  */
@@ -26,24 +26,31 @@
26
26
 
27
27
  import { SphereEmitter, Tunnel } from '@descix/frqtl-web/parts';
28
28
 
29
- export default function buildExperiment(exp) {
30
- // Laser Build-Up — gain source + resonant bore + output coupler.
31
- // Geometry and per-leaf creation conditions come from the ONE scene-spec authority,
32
- // Tunnel.pointExitSpec() (setupTunnelPointExit.sceneSpec), so this rung and the standalone page cannot drift.
33
- var W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
34
- var spec = Tunnel.pointExitSpec({
35
- canvasW: W, canvasH: H,
36
- tunnelLength: 240, // REBUILD-class: corridor geometry, re-authored not dragged
37
- wavelengthScale: 16, // eff lambda 256px — the self-interference golden's own regime
38
- exitRecenter: true, // output coupler collapses the mouth to a point
39
- exitPhaseReset: false // coherence OFF by default — turning it ON is the experiment
40
- });
29
+ // Laser Build-Up — gain source + resonant bore + output coupler.
30
+ // Geometry and per-leaf creation conditions come from the ONE scene-spec authority,
31
+ // Tunnel.pointExitSpec() (setupTunnelPointExit.sceneSpec), so this rung and the standalone page cannot drift.
32
+ const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
33
+ const SPEC = Tunnel.pointExitSpec({
34
+ canvasW: W, canvasH: H,
35
+ tunnelLength: 240, // REBUILD-class: corridor geometry, re-authored not dragged
36
+ wavelengthScale: 16, // eff lambda 256px — the self-interference golden's own regime
37
+ exitRecenter: true, // output coupler collapses the mouth to a point
38
+ exitPhaseReset: false // coherence OFF by default — turning it ON is the experiment
39
+ });
40
+ // The spec names its tunnel; in `parts` the key is the name.
41
+ const { name: _tunnelName, ...TUNNEL } = SPEC.tunnel;
42
+
43
+ export const parts = {
41
44
  // 1. THE GAIN SOURCE — a hemisphere emitter just inside the input mouth, so the whole
42
45
  // burst is aimed down the bore instead of half of it escaping backwards.
43
- exp.createCanvas(W, H, SphereEmitter.worldCfg(spec.emitter)); // THE WORLD: a 2D canvas + universe — first, then parts
44
- exp.addObject('SphereEmitter', spec.emitter);
46
+ emitter: [SphereEmitter, { ...SPEC.emitter }],
45
47
  // 2+3. THE BORE AND THE OUTPUT COUPLER — one directed Tunnel carrying the exit gate.
46
48
  // mode 'directed': every wall hit re-emits forward within 180 degrees of the axis.
47
49
  // exitRecenter / exitPhaseReset are the two coupler flags, both live in the Inspector.
48
- exp.addObject('Tunnel', spec.tunnel);
50
+ point_exit_tunnel: [Tunnel, { ...TUNNEL }],
51
+ };
52
+
53
+ export default function buildExperiment(exp) {
54
+ exp.createCanvas(W, H, SphereEmitter.worldCfg(parts.emitter[1])); // THE WORLD: a 2D canvas + universe — first, then parts
55
+ exp.declare(parts);
49
56
  }
@@ -5,40 +5,39 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung cl1a-mirror-3d).
11
11
  */
12
12
 
13
- import { SphereEmitter } from '@descix/frqtl-web/parts';
13
+ import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
14
14
 
15
- export default function buildExperiment(exp) {
16
- // Collision Lab 1A · 3D — a collimated LASER (particle-walk pins PURE +x in 2D AND
17
- // 3D) into a 3D mirror SLAB (depth → z-slab containing z=0), rendered as a box;
18
- // orbit to view. Same LASER tuning as the 2D 1A: single positive
19
- // charge, wavelength 1, wavelengthScale 1 (fundamentalWaveLength 4), tiny burst.
20
- // FACE-ON default: orientation 0 presents the full
21
- // drawn face to the beam → retro-reflection (beam straight back, recolored) — the
22
- // one mirror visual that reads correctly from EVERY camera azimuth. (A tilted
23
- // default's L-shaped 2th exit is correct optics but its mirror-flipped projection
24
- // reads as pass-through from presets that view the xy-plane from behind — measured:
25
- // zero leaves ever behind the face; the read was projection handedness, not physics.)
26
- // Rotate the orientation slider to steer: 45 -> straight up, 30 -> R(120), etc.
27
- var W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
28
- var cy = Math.round(H / 2);
29
- var SRC = {
15
+ // Collision Lab 1A · 3D — a collimated LASER (particle-walk pins PURE +x in 2D AND
16
+ // 3D) into a 3D mirror SLAB (depth → z-slab containing z=0), rendered as a box;
17
+ // orbit to view. Same LASER tuning as the 2D 1A: single positive
18
+ // charge, wavelength 1, wavelengthScale 1 (fundamentalWaveLength 4), tiny burst.
19
+ // FACE-ON default: orientation 0 presents the full
20
+ // drawn face to the beam → retro-reflection (beam straight back, recolored) — the
21
+ // one mirror visual that reads correctly from EVERY camera azimuth. (A tilted
22
+ // default's L-shaped 2th exit is correct optics but its mirror-flipped projection
23
+ // reads as pass-through from presets that view the xy-plane from behind — measured:
24
+ // zero leaves ever behind the face; the read was projection handedness, not physics.)
25
+ // Rotate the orientation slider to steer: 45 -> straight up, 30 -> R(120), etc.
26
+ const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
27
+ const cy = Math.round(H / 2);
28
+
29
+ export const parts = {
30
+ source: [SphereEmitter, {
30
31
  preset: 'particle-walk', is2D: false, center: [Math.round(W * 0.15), cy],
31
32
  chargeMode: 'positive', fundamentalDimension: 2, wavelength: 1,
32
33
  fundamentalWaveLength: 4, burstSize: 2, emitEveryTicks: 1
33
- };
34
- exp.createScene(W, H, 600, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 3D scene + universe, 600x400x600 — first, then parts
35
- exp.addObject('SphereEmitter', SRC);
36
- exp.addObject('Wall', {
34
+ }],
35
+ mirror: [Wall, {
37
36
  rect: [Math.round(W * 0.6), Math.round(H * 0.2), 20, Math.round(H * 0.6)],
38
37
  z: -80, depth: 160,
39
38
  collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: 0,
40
- name: 'mirror', color: [255, 120, 0], hooks: ['recolor']
41
- });
39
+ color: [255, 120, 0], hooks: ['recolor']
40
+ }],
42
41
  // SCENE Z-COLLIMATOR (without it the z lets things pass through instead of
43
42
  // reflecting): the beam's z-tail DIFFUSES over time (measured:
44
43
  // |z| at the wall trending 68 of the 80 rim within seconds), and any quantum
@@ -49,8 +48,13 @@ export default function buildExperiment(exp) {
49
48
  // dying at the canvas edge) — so the mirror provably faces EVERY alive quantum
50
49
  // and nothing can ever appear past the plate. 40px deep: un-jumpable (max z
51
50
  // step is ±2/tick).
52
- exp.addObject('Wall', { rect: [0, 0, W, H], z: 80, depth: 40,
53
- collisionBehavior: 'destroy', name: 'zClipFar', visible: false });
54
- exp.addObject('Wall', { rect: [0, 0, W, H], z: -120, depth: 40,
55
- collisionBehavior: 'destroy', name: 'zClipNear', visible: false });
51
+ zClipFar: [Wall, { rect: [0, 0, W, H], z: 80, depth: 40,
52
+ collisionBehavior: 'destroy', visible: false }],
53
+ zClipNear: [Wall, { rect: [0, 0, W, H], z: -120, depth: 40,
54
+ collisionBehavior: 'destroy', visible: false }],
55
+ };
56
+
57
+ export default function buildExperiment(exp) {
58
+ exp.createScene(W, H, 600, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 3D scene + universe, 600x400x600 — first, then parts
59
+ exp.declare(parts);
56
60
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung cl1d-mirror-route).
11
11
  */
@@ -37,6 +37,8 @@
37
37
  import { CameraControls, SphereEmitter } from '@descix/frqtl-web/parts';
38
38
  import * as FrqtlDisplay from '@descix/frqtl-web/compat/renderer/display-slot.js';
39
39
 
40
+ export const parts = {};
41
+
40
42
  export default function buildExperiment(exp) {
41
43
  // The booted display (the host's renderState.renderer) decides which world this mode makes.
42
44
  var IS3D = FrqtlDisplay.name() === 'three';
@@ -101,6 +103,7 @@ export default function buildExperiment(exp) {
101
103
  // THE WORLD — first, then parts: a 3D scene + universe (600x400x600) or a 2D canvas + universe.
102
104
  if (IS3D) { exp.createScene(W, H, WORLD_D, SphereEmitter.worldCfg(SRC)); }
103
105
  else { exp.createCanvas(W, H, SphereEmitter.worldCfg(SRC)); }
106
+ exp.declare(parts);
104
107
  exp.addObject('SphereEmitter', SRC);
105
108
  // The ten mirrors at the star vertices — each its own colour + the recolor tag. In 3D each is a
106
109
  // SLAB (z-slab −SD/2..SD/2); in 2D the bag is EMPTY, so the wall is the flat mirror.
@@ -5,34 +5,38 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung cl1a-mirror).
11
11
  */
12
12
 
13
- import { SphereEmitter } from '@descix/frqtl-web/parts';
13
+ import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
14
14
 
15
- export default function buildExperiment(exp) {
16
- // Collision Lab 1A (Mirror) — a collimated LASER aimed at a rotatable Bounce/Reflect
17
- // Wall. LASER TUNING: particle-walk pins PURE +x momentum (the
18
- // tightest constrained-angle the emitter supports — zero angular spread); a SINGLE
19
- // (positive) charge removes the random transverse braiding; wavelength 1 → the
20
- // minimal transverse wiggle; fundamentalWaveLength 4 → wavelengthScale 1 (the
21
- // 256px scale-spread REMOVED — that spray was the 'visually non-sensical' look);
22
- // a TINY burst (2/tick) keeps it a thin beam (frame-rate protection). Rotate the
23
- // mirror to steer the reflected beam: 45 -> straight-up exit, 30 -> R(120) exit.
24
- var W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
25
- var cy = Math.round(H / 2);
26
- var SRC = {
15
+ // Collision Lab 1A (Mirror) — a collimated LASER aimed at a rotatable Bounce/Reflect
16
+ // Wall. LASER TUNING: particle-walk pins PURE +x momentum (the
17
+ // tightest constrained-angle the emitter supports — zero angular spread); a SINGLE
18
+ // (positive) charge removes the random transverse braiding; wavelength 1 → the
19
+ // minimal transverse wiggle; fundamentalWaveLength 4 → wavelengthScale 1 (the
20
+ // 256px scale-spread REMOVED — that spray was the 'visually non-sensical' look);
21
+ // a TINY burst (2/tick) keeps it a thin beam (frame-rate protection). Rotate the
22
+ // mirror to steer the reflected beam: 45 -> straight-up exit, 30 -> R(120) exit.
23
+ const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
24
+ const cy = Math.round(H / 2);
25
+
26
+ export const parts = {
27
+ source: [SphereEmitter, {
27
28
  preset: 'particle-walk', is2D: true, center: [Math.round(W * 0.12), cy],
28
29
  orientationDeg: 0, chargeMode: 'positive', fundamentalDimension: 2, wavelength: 1,
29
30
  fundamentalWaveLength: 4, burstSize: 2, emitEveryTicks: 1
30
- };
31
- exp.createCanvas(W, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
32
- exp.addObject('SphereEmitter', SRC);
33
- exp.addObject('Wall', {
31
+ }],
32
+ mirror: [Wall, {
34
33
  rect: [Math.round(W * 0.6), Math.round(H * 0.2), 20, Math.round(H * 0.6)],
35
34
  collisionBehavior: 'bounce', bounceDirection: 'reflect',
36
- orientationDeg: 45, name: 'mirror', color: [255, 120, 0], hooks: ['recolor']
37
- });
35
+ orientationDeg: 45, color: [255, 120, 0], hooks: ['recolor']
36
+ }],
37
+ };
38
+
39
+ export default function buildExperiment(exp) {
40
+ exp.createCanvas(W, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
41
+ exp.declare(parts);
38
42
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung r7-3d).
11
11
  */
@@ -14,13 +14,16 @@
14
14
 
15
15
  import { CameraControls, SphereEmitter } from '@descix/frqtl-web/parts';
16
16
 
17
+ export const parts = {
18
+ source: [SphereEmitter, { preset: 'spherical', is2D: false }],
19
+ };
20
+
17
21
  export default function buildExperiment(exp) {
18
22
  // is2D:false opens the z-axis: the ball-rejected sampler draws a 3D BALL of
19
23
  // directions (bench S1). three renderer + perspective camera come from renderState.
20
24
  // CameraControls.attach = reusable FAIL-SOFT joystick sugar (no-op in 2D).
21
- var SRC = { preset: 'spherical', is2D: false };
22
- exp.createScene(600, 400, 600, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 3D scene + universe, 600x400x600 — first, then parts
23
- exp.addObject('SphereEmitter', SRC);
25
+ exp.createScene(600, 400, 600, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 3D scene + universe, 600x400x600 — first, then parts
26
+ exp.declare(parts);
24
27
  if (typeof document !== 'undefined' && exp.runner) {
25
28
  if (exp.runner.controlHost) { CameraControls.attach(exp.runner, exp.runner.controlHost); }
26
29
  }
@@ -5,18 +5,21 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung r3-omni-source).
11
11
  */
12
12
 
13
13
  import { SphereEmitter } from '@descix/frqtl-web/parts';
14
14
 
15
+ export const parts = {
16
+ source: [SphereEmitter, { preset: 'spherical', is2D: true }],
17
+ };
18
+
15
19
  export default function buildExperiment(exp) {
16
20
  // Rung 3 — full dispersion: the spherical preset draws (vx,vy) across the whole
17
21
  // ball; in 2D the ball-rejected sampler collapses to an even DISK of directions
18
22
  // (bench S5). One omni point source = the sun disk.
19
- var SRC = { preset: 'spherical', is2D: true };
20
- exp.createCanvas(600, 400, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
21
- exp.addObject('SphereEmitter', SRC);
23
+ exp.createCanvas(600, 400, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
24
+ exp.declare(parts);
22
25
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rungs r4-self-interference, r5-fountain-pool).
11
11
  */
@@ -19,6 +19,16 @@
19
19
 
20
20
  import { SphereEmitter } from '@descix/frqtl-web/parts';
21
21
 
22
+ export const parts = {
23
+ source: [SphereEmitter, {
24
+ is2D: true, preset: 'spherical',
25
+ emissionShell: 'surface', innerR: 16,
26
+ fundamentalDimension: 2, wavelength: 32,
27
+ chargeMode: 'random', fundamentalWaveLength: 64,
28
+ burstSize: 72, emitEveryTicks: 1
29
+ }],
30
+ };
31
+
22
32
  export default function buildExperiment(exp) {
23
33
  // The classic self-interference tuple (matches self-interference-preset.png):
24
34
  // dim 2 (capacity 16) + wavelength 32 -> clamped to 16 -> mass 1 (fullness 1/16);
@@ -27,13 +37,6 @@ export default function buildExperiment(exp) {
27
37
  // emissionShell 'surface' + innerR 16 -> RING launch (empty center, sharp bands);
28
38
  // burst 72/tick. Red is DERIVED from fullness 1/16 (ROYGBIV low end), not forced.
29
39
  // Pattern = radial BRAIDED BANDS (not concentric rings).
30
- var SRC = {
31
- is2D: true, preset: 'spherical',
32
- emissionShell: 'surface', innerR: 16,
33
- fundamentalDimension: 2, wavelength: 32,
34
- chargeMode: 'random', fundamentalWaveLength: 64,
35
- burstSize: 72, emitEveryTicks: 1
36
- };
37
- exp.createCanvas(600, 400, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
38
- exp.addObject('SphereEmitter', SRC);
40
+ exp.createCanvas(600, 400, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
41
+ exp.declare(parts);
39
42
  }
@@ -56,24 +56,27 @@
56
56
 
57
57
  import { CameraControls, CollisionTypes, LargeObject, SphereEmitter } from '@descix/frqtl-web/parts';
58
58
 
59
- export default function buildExperiment(exp) {
60
- var W = 600, H = 600, D = 600; // THE WORLD this scene states (its co-init call below)
61
- var BURST = 20; // quanta per tick at the muzzle, before the wind-down
62
- var BULLET_SIDE = 10; // the bullet cube's edge = the muzzle slit's height (px)
63
- var RAMP_START = 180, RAMP_END = 600; // the wind-down: full burst until RAMP_START, zero at RAMP_END
64
- var GUN_W = 150, GUN_H = 84; // the painted gun body (px); its right edge is the muzzle
59
+ const W = 600, H = 600, D = 600; // THE WORLD this scene states (its co-init call below)
60
+ const BURST = 20; // quanta per tick at the muzzle, before the wind-down
61
+ const BULLET_SIDE = 10; // the bullet cube's edge = the muzzle slit's height (px)
62
+ const MUZZLE_X = Math.floor(W / 2), MUZZLE_Y = Math.floor(H / 2);
65
63
 
66
- var MUZZLE_X = Math.floor(W / 2), MUZZLE_Y = Math.floor(H / 2);
67
- var SRC = {
64
+ export const parts = {
65
+ muzzle: [SphereEmitter, {
68
66
  preset: 'particle-walk', is2D: false, // pure +X momentum in 2D and 3D
69
67
  center: [MUZZLE_X, MUZZLE_Y, 0],
70
68
  emissionBox: { x: 0, y: BULLET_SIDE, z: 0 }, // the 1×10 slit
71
69
  chargeMode: 'negative', burstSize: BURST, emitEveryTicks: 1,
72
70
  fundamentalDimension: 0, fundamentalWaveLength: 64 // λ scale floor(64/4) = 16
73
- };
71
+ }],
72
+ };
73
+
74
+ export default function buildExperiment(exp) {
75
+ var RAMP_START = 180, RAMP_END = 600; // the wind-down: full burst until RAMP_START, zero at RAMP_END
76
+ var GUN_W = 150, GUN_H = 84; // the painted gun body (px); its right edge is the muzzle
74
77
 
75
78
  // ── WORLD — a 3D scene + universe W×H×D, fundamental layer 0, λ multiplier 16 ──
76
- exp.createScene(W, H, D, SphereEmitter.worldCfg(SRC));
79
+ exp.createScene(W, H, D, SphereEmitter.worldCfg(parts.muzzle[1]));
77
80
  var u = exp.universe;
78
81
  u.brownianMotion = true;
79
82
  u.withInterQuantumCollisions = true;
@@ -83,7 +86,8 @@ export default function buildExperiment(exp) {
83
86
  exp.runner.setRateLimits({ tpsLimit: 60 }); // pacing only: 60 ticks = 1 s of wall-clock
84
87
 
85
88
  // ── THE MUZZLE ──
86
- var muzzle = exp.addObject('SphereEmitter', SRC);
89
+ const p = exp.declare(parts);
90
+ var muzzle = p.muzzle;
87
91
 
88
92
  // ── THE WIND-DOWN — the burst falls linearly from BURST at RAMP_START to zero at RAMP_END ──
89
93
  // The burst setter's floor is one quantum per tick, so zero is the muzzle's cut, issued once.
@@ -5,18 +5,21 @@
5
5
  *
6
6
  * A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
7
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
8
+ * the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
9
9
  * objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
10
10
  * (rung r1-straight-walk).
11
11
  */
12
12
 
13
13
  import { SphereEmitter } from '@descix/frqtl-web/parts';
14
14
 
15
+ export const parts = {
16
+ source: [SphereEmitter, { preset: 'particle-walk', is2D: true }],
17
+ };
18
+
15
19
  export default function buildExperiment(exp) {
16
20
  // Rung 1 — deterministic +X drift (particle-walk preset pins vx=[R,R], vy=vz=0)
17
21
  // with the tiny always-armed transverse coin. Born AT the fundamental (dim 0)
18
22
  // so it is 'light' — steps every tick (fast straight drift).
19
- var SRC = { preset: 'particle-walk', is2D: true };
20
- exp.createCanvas(600, 400, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
21
- exp.addObject('SphereEmitter', SRC);
23
+ exp.createCanvas(600, 400, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
24
+ exp.declare(parts);
22
25
  }