@descix/frqtl-node 0.6.0-p0.2 → 0.6.0-p4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/README.md +8 -4
- package/dist/dist-manifest.json +70 -62
- package/dist/eyes/index.js +5 -2
- package/dist/frqtl/assets/vendor/acorn.LICENSE +21 -0
- package/dist/frqtl/assets/vendor/acorn.PINNED.json +9 -0
- package/dist/frqtl/assets/vendor/acorn.mjs +6266 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +41 -0
- package/dist/frqtl/experiments/_shared/boot-failure.js +60 -0
- package/dist/frqtl/objects/FrqtlApp.js +136 -30
- package/dist/frqtl/objects/ObjectFactory.js +4 -5
- package/dist/frqtl/objects/RecipeContext.js +617 -9
- package/dist/frqtl/objects/instruments/hosted-eyes.js +268 -57
- package/dist/frqtl/objects/objects/QuantumEmitter.js +1 -1
- package/dist/frqtl/objects/record/Record.js +326 -0
- package/dist/frqtl/objects/record/open-record.js +22 -0
- package/dist/frqtl/objects/schema/index.js +72 -0
- package/dist/frqtl/parts/DetectorWall.js +1 -1
- package/dist/frqtl/parts/FrqtlControlsPanel.js +4 -2
- package/dist/frqtl/parts/FrqtlVision.js +21 -6
- package/dist/frqtl/parts/Wall.js +2 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +18 -14
- package/dist/frqtl/scenes/absorbing-wall.js +18 -14
- package/dist/frqtl/scenes/atomic-model.js +3 -0
- package/dist/frqtl/scenes/big-bang.js +3 -0
- package/dist/frqtl/scenes/blackbody.js +14 -11
- package/dist/frqtl/scenes/circuit-sat.js +7 -4
- package/dist/frqtl/scenes/collision-matrix-3d.js +3 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +19 -15
- package/dist/frqtl/scenes/counting-wall.js +19 -15
- package/dist/frqtl/scenes/double-slit.js +3 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +64 -58
- package/dist/frqtl/scenes/dse-one-slit.js +59 -55
- package/dist/frqtl/scenes/dse-photoelectric.js +28 -24
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +124 -99
- package/dist/frqtl/scenes/dse-two-emitters.js +64 -58
- package/dist/frqtl/scenes/dse-two-slits.js +69 -65
- package/dist/frqtl/scenes/empty.js +3 -0
- package/dist/frqtl/scenes/laser-buildup.js +23 -16
- package/dist/frqtl/scenes/mirror-3d.js +31 -27
- package/dist/frqtl/scenes/mirror-route.js +4 -1
- package/dist/frqtl/scenes/mirror.js +24 -20
- package/dist/frqtl/scenes/omni-3d.js +7 -4
- package/dist/frqtl/scenes/omni-source.js +7 -4
- package/dist/frqtl/scenes/self-interference.js +13 -10
- package/dist/frqtl/scenes/smoking-gun.js +15 -11
- package/dist/frqtl/scenes/straight-walk.js +7 -4
- package/dist/frqtl/scenes/sun.js +11 -8
- package/dist/frqtl/scenes/two-source.js +22 -17
- package/dist/frqtl/scenes/wave.js +10 -7
- package/dist/shim/bundle/index.js +112 -96
- package/dist/shim/bundle/indexHtml.js +66 -39
- package/dist/shim/engine/router.js +2 -2
- package/dist/shim/index.js +1 -1
- package/dist/shim/notebook/format.js +1 -1
- package/dist/shim/notebook/store.js +2 -3
- package/dist/shim/page-params/index.js +2 -2
- package/dist/shim/project/index.js +20 -5
- package/dist/shim/project/link.js +182 -37
- package/dist/shim/project/manifest.js +151 -0
- package/dist/shim/project/parts-module.js +67 -0
- package/dist/shim/project/parts-writer.js +457 -0
- package/dist/shim/project/record-format.js +345 -0
- package/dist/shim/project/specifiers.js +41 -13
- package/dist/shim/registry/index.js +1 -1
- package/dist/shim/registry/scene-source.js +44 -30
- package/dist/shim/registry/walkthrough.js +13 -12
- package/dist/shim/render-state/apply.js +11 -14
- package/dist/shim/storage/codec-chains.js +88 -0
- package/dist/shim/storage/folders.js +205 -0
- package/dist/shim/storage/index.js +64 -8
- package/dist/shim/storage/make-namespace-store.js +5 -5
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/dist/shim/bundle/frqnb.js +0 -112
- package/dist/shim/bundle/manifest.js +0 -95
- package/dist/shim/storage/modules.js +0 -249
- package/dist/shim/storage/projects.js +0 -150
|
@@ -24,6 +24,8 @@ import { Rectangle } from '@descix/frqtl-web/parts';
|
|
|
24
24
|
import * as OrbitBag from '@descix/frqtl-web/compat/renderer/orbit-bag.js';
|
|
25
25
|
import { engine } from '@descix/frqtl-web/compat/engine.js';
|
|
26
26
|
|
|
27
|
+
export const parts = {};
|
|
28
|
+
|
|
27
29
|
export default function buildExperiment(exp) {
|
|
28
30
|
var W = 600, H = 600; // THE WORLD this scene states (its co-init call below)
|
|
29
31
|
|
|
@@ -34,6 +36,7 @@ export default function buildExperiment(exp) {
|
|
|
34
36
|
|
|
35
37
|
// ── WORLD — a 2D canvas + universe: quark layer fundamental, λ multiplier 1, quarks confined ──
|
|
36
38
|
exp.createCanvas(W, H, { dimension: QUARK_LAYER, wavelengthScale: 1, bonding: true });
|
|
39
|
+
exp.declare(parts);
|
|
37
40
|
var u = exp.universe;
|
|
38
41
|
|
|
39
42
|
var quarkDim = u.addDimension(QUARK_LAYER);
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
import { PointSource } from '@descix/frqtl-web/parts';
|
|
20
20
|
|
|
21
|
+
export const parts = {};
|
|
22
|
+
|
|
21
23
|
export default function buildExperiment(exp) {
|
|
22
24
|
var W = 600, H = 600; // THE WORLD this scene states (its co-init call below)
|
|
23
25
|
|
|
@@ -29,6 +31,7 @@ export default function buildExperiment(exp) {
|
|
|
29
31
|
// iframeIntervalSeconds 1/60: parents are generated EVERY tick (the engine freezes the interval at
|
|
30
32
|
// init, so it rides the world cfg — at the default 1 s, Phase B ran every 60 ticks).
|
|
31
33
|
exp.createCanvas(W, H, { dimension: 0, wavelengthScale: 1, frames: true, iframeIntervalSeconds: 1 / 60 });
|
|
34
|
+
exp.declare(parts);
|
|
32
35
|
var u = exp.universe;
|
|
33
36
|
|
|
34
37
|
// ── THE SOURCE — at the centre, no initial momentum, random charge ──────
|
|
@@ -21,18 +21,27 @@
|
|
|
21
21
|
* the oven, the thermostat, the sink. Same calls, same order, same seed ⇒ the same RNG stream.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { CollisionActions, CollisionTypes, FrqtlControlOverlay, FrqtlControlsPanel, Rectangle } from '@descix/frqtl-web/parts';
|
|
24
|
+
import { CollisionActions, CollisionTypes, FrqtlControlOverlay, FrqtlControlsPanel, LargeObject, Rectangle } from '@descix/frqtl-web/parts';
|
|
25
25
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
28
|
+
const SINK_DEPTH = 30; // the absorbing strip at the right canvas edge (px)
|
|
29
|
+
|
|
30
|
+
export const parts = {
|
|
31
|
+
// ── THE SINK — the experiment boundary on the slit side ─────────────────
|
|
32
|
+
radiation_sink: [LargeObject, {
|
|
33
|
+
rect: { left: W - SINK_DEPTH, top: 0, w: SINK_DEPTH, h: H },
|
|
34
|
+
collision_type: CollisionTypes.NONE, actions: [CollisionActions.ABSORB],
|
|
35
|
+
reposition: 'closest', color: [0, 0, 0]
|
|
36
|
+
}],
|
|
37
|
+
};
|
|
29
38
|
|
|
39
|
+
export default function buildExperiment(exp) {
|
|
30
40
|
var TEMPERATURE = 1500; // target quanta inside the cavity (the knob below)
|
|
31
41
|
var QUANTUM_DIM = 3; // the layer leaves promote into: an 8 px box edge, 64 cells of capacity in 2D
|
|
32
42
|
var BOX_FRACTION = 1 / 3; // the cavity's outer box, as a fraction of the canvas
|
|
33
43
|
var WALL = 10; // wall thickness (px)
|
|
34
44
|
var SLIT_HEIGHT = 60; // the radiation slit in the right wall (px)
|
|
35
|
-
var SINK_DEPTH = 30; // the absorbing strip at the right canvas edge (px)
|
|
36
45
|
|
|
37
46
|
// ── WORLD — a 2D canvas + universe, fundamental layer 0 (1x1 leaves), λ multiplier 1 ──
|
|
38
47
|
exp.createCanvas(W, H, { dimension: 0, wavelengthScale: 1 });
|
|
@@ -57,13 +66,7 @@ export default function buildExperiment(exp) {
|
|
|
57
66
|
});
|
|
58
67
|
u.experimentTickFunctions[thermostatSlot] = oven.getTemperatureTickFunction();
|
|
59
68
|
|
|
60
|
-
//
|
|
61
|
-
exp.addObject('LargeObject', {
|
|
62
|
-
name: 'radiation_sink',
|
|
63
|
-
rect: { left: W - SINK_DEPTH, top: 0, w: SINK_DEPTH, h: H },
|
|
64
|
-
collision_type: CollisionTypes.NONE, actions: [CollisionActions.ABSORB],
|
|
65
|
-
reposition: 'closest', color: [0, 0, 0]
|
|
66
|
-
});
|
|
69
|
+
exp.declare(parts); // the sink (parts.radiation_sink)
|
|
67
70
|
|
|
68
71
|
// ── THE KNOB — temperature, live (the oven's own thermostat target) ─────
|
|
69
72
|
var registry = ControlRegistry.makeControlRegistry();
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
import { FrqtlControlOverlay, FrqtlControlsPanel, LogicGate, SphereEmitter, Trace } from '@descix/frqtl-web/parts';
|
|
23
23
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
24
24
|
|
|
25
|
+
export const parts = {};
|
|
26
|
+
|
|
25
27
|
export default function buildExperiment(exp) {
|
|
26
28
|
var W = 760, H = 520; // THE WORLD this scene states (its co-init call below)
|
|
27
29
|
var LEFT = 60, TOP = 15; // the circuit's top-left
|
|
@@ -40,6 +42,7 @@ export default function buildExperiment(exp) {
|
|
|
40
42
|
|
|
41
43
|
// ── WORLD — a 2D canvas + universe; the emitter's own world (its worldCfg) ──
|
|
42
44
|
exp.createCanvas(W, H, SphereEmitter.worldCfg(emitterBag(null)));
|
|
45
|
+
exp.declare(parts);
|
|
43
46
|
var u = exp.universe;
|
|
44
47
|
|
|
45
48
|
var MIN = LogicGate.MINIMAL; // bore 3 / wall 3 / length 20
|
|
@@ -57,7 +60,7 @@ export default function buildExperiment(exp) {
|
|
|
57
60
|
{ input: 'A', gate: 'SUM', port: 'inPortA' }, { input: 'B', gate: 'SUM', port: 'inPortB' },
|
|
58
61
|
{ input: 'B', gate: 'CARRY', port: 'inPortA' }, { input: 'A', gate: 'CARRY', port: 'inPortB' }
|
|
59
62
|
];
|
|
60
|
-
var rails = { A: [], B: [] },
|
|
63
|
+
var rails = { A: [], B: [] }, traces = [];
|
|
61
64
|
LANES.forEach(function (row, i) {
|
|
62
65
|
var port = gates[row.gate][row.port], y = port[1];
|
|
63
66
|
var t = new Trace(u, { path: [[port[0] - LEAD, y], [port[0], y]], bore: MIN.bore,
|
|
@@ -66,7 +69,7 @@ export default function buildExperiment(exp) {
|
|
|
66
69
|
{ name: 'input' + row.input + i })); // parked inside the bore mouth
|
|
67
70
|
em.setActive(row.input === 'A' ? STATE.a : STATE.b);
|
|
68
71
|
rails[row.input].push(em);
|
|
69
|
-
|
|
72
|
+
traces.push(t);
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
// ── THE READOUTS — grounded traces off each gate's outlet: their current IS the answer ──
|
|
@@ -75,8 +78,8 @@ export default function buildExperiment(exp) {
|
|
|
75
78
|
bore: MIN.bore, wallThickness: MIN.wallThickness, ground: true, name: name, color: [60, 160, 220] });
|
|
76
79
|
}
|
|
77
80
|
var readouts = { SUM: readout(sum, 'SUM_out'), CARRY: readout(carry, 'CARRY_out') };
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
traces.push(readouts.SUM, readouts.CARRY);
|
|
82
|
+
traces.forEach(function (p) { p.attachToRunner(exp.runner); }); // every trace reads the tick stream (a declared gate subscribed at addObject)
|
|
80
83
|
|
|
81
84
|
function setInput(which, on) {
|
|
82
85
|
STATE[which] = !!on;
|
|
@@ -22,12 +22,15 @@ import { CameraControls, FrqtlControlOverlay, FrqtlControlsPanel } from '@descix
|
|
|
22
22
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
23
23
|
import { collisionMatrixLanes } from '@descix/frqtl-web/objects/setup/_collisionMatrixLanes.js';
|
|
24
24
|
|
|
25
|
+
export const parts = {};
|
|
26
|
+
|
|
25
27
|
export default function buildExperiment(exp) {
|
|
26
28
|
var W = 700, H = 700, D = 700; // THE WORLD this scene states (its co-init call below)
|
|
27
29
|
var LEVEL = 6; // the voxel layer: 64 px bodies, capacity 4096
|
|
28
30
|
|
|
29
31
|
// ── WORLD — a 3D scene + universe W×H×D, fundamental layer 0, λ multiplier 1 ──
|
|
30
32
|
exp.createScene(W, H, D, { dimension: 0, wavelengthScale: 1 });
|
|
33
|
+
exp.declare(parts);
|
|
31
34
|
var u = exp.universe;
|
|
32
35
|
u.withInterQuantumCollisions = true;
|
|
33
36
|
u.addDimension(LEVEL);
|
|
@@ -5,31 +5,35 @@
|
|
|
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 cl1c-counting-wall-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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// Collision Lab 1C · 3D — a volumetric source into a 3D Detector Screen slab
|
|
16
|
+
// (destroy+count) + a LIVE in-scene chart, textured in three; orbit to view.
|
|
17
|
+
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
18
|
+
const cy = Math.round(H / 2);
|
|
19
|
+
|
|
20
|
+
export const parts = {
|
|
21
|
+
source: [SphereEmitter, {
|
|
21
22
|
preset: 'spherical', is2D: false, center: [Math.round(W * 0.15), cy],
|
|
22
23
|
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
23
24
|
burstSize: 24, emitEveryTicks: 1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exp.addObject('SphereEmitter', SRC);
|
|
27
|
-
var screen = exp.addObject('Wall', {
|
|
25
|
+
}],
|
|
26
|
+
screen: [Wall, {
|
|
28
27
|
rect: [Math.round(W * 0.6), 0, 20, H], z: -80, depth: 160,
|
|
29
|
-
preset: 'detector-screen',
|
|
30
|
-
}
|
|
28
|
+
preset: 'detector-screen', color: [120, 200, 120]
|
|
29
|
+
}],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default function buildExperiment(exp) {
|
|
33
|
+
exp.createScene(W, H, 600, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 3D scene + universe, 600x400x600 — first, then parts
|
|
34
|
+
const p = exp.declare(parts);
|
|
31
35
|
exp.addObject('LiveChart', {
|
|
32
|
-
source: screen,
|
|
36
|
+
source: p.screen,
|
|
33
37
|
rect: [Math.round(W * 0.66), Math.round(H * 0.08), Math.round(W * 0.28), Math.round(H * 0.5)],
|
|
34
38
|
series: 'bar', label: 'arrivals by y', refreshMs: 100, color: [24, 32, 46]
|
|
35
39
|
});
|
|
@@ -5,33 +5,37 @@
|
|
|
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 cl1c-counting-wall).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { SphereEmitter } from '@descix/frqtl-web/parts';
|
|
13
|
+
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// Collision Lab 1C (Counting Wall) — the Detector Screen preset (destroy + count),
|
|
16
|
+
// with a LIVE in-scene LiveChart of the running y-histogram placed beside it.
|
|
17
|
+
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
18
|
+
const cy = Math.round(H / 2);
|
|
19
|
+
|
|
20
|
+
export const parts = {
|
|
21
|
+
source: [SphereEmitter, {
|
|
21
22
|
preset: 'spherical', is2D: true, center: [Math.round(W * 0.15), cy],
|
|
22
23
|
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
23
24
|
burstSize: 24, emitEveryTicks: 1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exp.addObject('SphereEmitter', SRC);
|
|
27
|
-
var screen = exp.addObject('Wall', {
|
|
25
|
+
}],
|
|
26
|
+
screen: [Wall, {
|
|
28
27
|
rect: [Math.round(W * 0.6), 0, 20, H],
|
|
29
|
-
preset: 'detector-screen',
|
|
30
|
-
}
|
|
28
|
+
preset: 'detector-screen', color: [120, 200, 120]
|
|
29
|
+
}],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default function buildExperiment(exp) {
|
|
33
|
+
exp.createCanvas(W, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
34
|
+
const p = exp.declare(parts);
|
|
31
35
|
// The interactive counting demo: a live chart of the detector's histogram,
|
|
32
36
|
// placed to the RIGHT of the screen (past where the quanta are absorbed).
|
|
33
37
|
exp.addObject('LiveChart', {
|
|
34
|
-
source: screen,
|
|
38
|
+
source: p.screen,
|
|
35
39
|
rect: [Math.round(W * 0.66), Math.round(H * 0.08), Math.round(W * 0.28), Math.round(H * 0.5)],
|
|
36
40
|
series: 'bar', label: 'arrivals by y', refreshMs: 100, color: [24, 32, 46]
|
|
37
41
|
});
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
import { CollisionTypes, DetectorWall, LargeObject, SlitLightSource, SlitScreen } from '@descix/frqtl-web/parts';
|
|
19
19
|
|
|
20
|
+
export const parts = {};
|
|
21
|
+
|
|
20
22
|
export default function buildExperiment(exp) {
|
|
21
23
|
var S = 480, WAVELENGTH = 64; // THE WORLD this scene states (its co-init call below) and its one geometry knob
|
|
22
24
|
|
|
23
25
|
exp.createCanvas(S, S, { dimension: 2, wavelengthScale: Math.floor(WAVELENGTH / 16) }); // a 2D canvas + universe, S x S
|
|
26
|
+
exp.declare(parts);
|
|
24
27
|
var u = exp.universe;
|
|
25
28
|
|
|
26
29
|
var slits = new SlitScreen(u, WAVELENGTH * 1.5, WAVELENGTH, S, WAVELENGTH, true, 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-one-emitter).
|
|
11
11
|
*/
|
|
@@ -71,19 +71,70 @@
|
|
|
71
71
|
// the title lines and the `var NSRC` line — lib/frqtl/scenes/test/scene-files.test.mjs
|
|
72
72
|
// fails if any other line diverges, so the A/B the pair exists to make stays an A/B.
|
|
73
73
|
|
|
74
|
-
import { SphereEmitter } from '@descix/frqtl-web/parts';
|
|
74
|
+
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
75
|
+
|
|
76
|
+
const CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
77
|
+
// ── THE ONLY LENGTH ─────────────────────────────────────────────────────
|
|
78
|
+
// lamPx is lambda_eff in pixels. 320 is chosen for RESOLUTION, not for how many
|
|
79
|
+
// orders fit: the feature this rung exists to show (the minimum just inside the
|
|
80
|
+
// band and the peak riding outside it) is 0.025*lambda wide, which is 8 px at
|
|
81
|
+
// lambda 320 and 3 px at lambda 128. A stage that cannot separate them shows a
|
|
82
|
+
// smooth hump and leaves you unable to say whether the physics or the sampling
|
|
83
|
+
// was at fault. One well-resolved pair beats three mushy ones.
|
|
84
|
+
const lamPx = 320;
|
|
85
|
+
const sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
|
|
86
|
+
|
|
87
|
+
// ── THE DETECTOR LEG — ONE NAMED CONSTANT ───────────────────────────────
|
|
88
|
+
// D = legFrac * lambda, measured from the SOURCE (there is no barrier, so the
|
|
89
|
+
// emitter itself is the reference point of the ladder). legFrac = 1/4 is the
|
|
90
|
+
// leg the ring formula was written for; see the block comment above.
|
|
91
|
+
const legFrac = 1 / 4;
|
|
92
|
+
const D = Math.round(lamPx * legFrac); // 80
|
|
93
|
+
const srcX = Math.round(lamPx / 16); // 20 — launch room; the canvas edge is the back sink
|
|
94
|
+
const detX = srcX + D; // 100 — the plate's FRONT FACE
|
|
95
|
+
// PLATE THICKNESS IS AN OPTICAL CHOICE UNDER A FLUX DETECTOR, not trim. A flux
|
|
96
|
+
// probe counts every tick a quantum overlaps it, so it integrates the density
|
|
97
|
+
// ACROSS its thickness — and the ring crest is a CIRCLE about the source, not a
|
|
98
|
+
// plane. At the m=0 band the crest's slope is dy/dx = -D/y = -1, so a slab t px
|
|
99
|
+
// thick smears the band over ~t px of y. Against an 8 px feature a lambda/8 (40
|
|
100
|
+
// px) plate would guarantee mush. lambda/64 = 5 px keeps the smear sub-feature
|
|
101
|
+
// and is still unjumpable (a quantum steps at most 2 px/axis/tick). A 'hits'
|
|
102
|
+
// plate would not care — it counts at first contact, i.e. at the front face —
|
|
103
|
+
// which is exactly why the reference H1 runs could afford a lambda/8 slab.
|
|
104
|
+
const detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
105
|
+
// ── THE PLATE — A COUNTING WALL THAT DOES NOT ABSORB ────────────────────
|
|
106
|
+
// ONE NAMED CONSTANT, so the reading can be swapped in a single edit and the two
|
|
107
|
+
// instruments compared rather than argued about:
|
|
108
|
+
// 'transparent-probe' = collisionBehavior 'nothing' + mark/count. FLUX. The
|
|
109
|
+
// quantum is counted on every tick it overlaps the plate and then flies on,
|
|
110
|
+
// so the reading is the OCCUPANCY INTEGRAL at the plate plane — the density
|
|
111
|
+
// at that plane. This is the shipped choice.
|
|
112
|
+
// 'detector-screen' = destroy + count. HITS. Each quantum is counted once, at
|
|
113
|
+
// arrival, and removed: a transit census, the headcount a photographic plate
|
|
114
|
+
// would report. It is what the H1 reference runs used.
|
|
115
|
+
// NEITHER ABSORBS. 'absorb' stores the arriving mass and re-radiates it — that is
|
|
116
|
+
// the photoelectric experiment, and it saturates.
|
|
117
|
+
const detectorPreset = 'transparent-probe';
|
|
118
|
+
|
|
119
|
+
export const parts = {
|
|
120
|
+
// FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
|
|
121
|
+
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
122
|
+
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
123
|
+
// is where the population bound is STATED rather than inferred.
|
|
124
|
+
far_sink: [Wall, { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
|
|
125
|
+
color: [120, 120, 120], alpha: 40 }],
|
|
126
|
+
// markTag — the plate's PROVENANCE stamp. The mark hook fires ONLY when the
|
|
127
|
+
// collision trigger does (genuine overlap), whereas the engine buffers every
|
|
128
|
+
// frame inside the LO's mb_w-PADDED search rect whether it triggered or not. So a
|
|
129
|
+
// raw frame count off the buffer is a padded-occupancy reading and the
|
|
130
|
+
// mark-filtered count is the trigger-gated census: two different instruments, and
|
|
131
|
+
// an offline reader needs the tag to tell them apart.
|
|
132
|
+
screen: [Wall, { rect: [detX, 0, detW, H],
|
|
133
|
+
preset: detectorPreset, markTag: 'plate', color: [120, 200, 120] }],
|
|
134
|
+
};
|
|
75
135
|
|
|
76
136
|
export default function buildExperiment(exp) {
|
|
77
137
|
var NSRC = 1;
|
|
78
|
-
var CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
79
|
-
// ── THE ONLY LENGTH ─────────────────────────────────────────────────────
|
|
80
|
-
// lamPx is lambda_eff in pixels. 320 is chosen for RESOLUTION, not for how many
|
|
81
|
-
// orders fit: the feature this rung exists to show (the minimum just inside the
|
|
82
|
-
// band and the peak riding outside it) is 0.025*lambda wide, which is 8 px at
|
|
83
|
-
// lambda 320 and 3 px at lambda 128. A stage that cannot separate them shows a
|
|
84
|
-
// smooth hump and leaves you unable to say whether the physics or the sampling
|
|
85
|
-
// was at fault. One well-resolved pair beats three mushy ones.
|
|
86
|
-
var lamPx = 320;
|
|
87
138
|
var lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
88
139
|
var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
89
140
|
var wlScale = Math.floor(fwl / 4); // 100
|
|
@@ -91,26 +142,7 @@ export default function buildExperiment(exp) {
|
|
|
91
142
|
throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
|
|
92
143
|
'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
93
144
|
}
|
|
94
|
-
// ── THE DETECTOR LEG — ONE NAMED CONSTANT ───────────────────────────────
|
|
95
|
-
// D = legFrac * lambda, measured from the SOURCE (there is no barrier, so the
|
|
96
|
-
// emitter itself is the reference point of the ladder). legFrac = 1/4 is the
|
|
97
|
-
// leg the ring formula was written for; see the block comment above.
|
|
98
|
-
var legFrac = 1 / 4;
|
|
99
|
-
var D = Math.round(lamPx * legFrac); // 80
|
|
100
|
-
var srcX = Math.round(lamPx / 16); // 20 — launch room; the canvas edge is the back sink
|
|
101
145
|
var cy = Math.round(H / 2); // 200 — the source plane's midline
|
|
102
|
-
var detX = srcX + D; // 100 — the plate's FRONT FACE
|
|
103
|
-
// PLATE THICKNESS IS AN OPTICAL CHOICE UNDER A FLUX DETECTOR, not trim. A flux
|
|
104
|
-
// probe counts every tick a quantum overlaps it, so it integrates the density
|
|
105
|
-
// ACROSS its thickness — and the ring crest is a CIRCLE about the source, not a
|
|
106
|
-
// plane. At the m=0 band the crest's slope is dy/dx = -D/y = -1, so a slab t px
|
|
107
|
-
// thick smears the band over ~t px of y. Against an 8 px feature a lambda/8 (40
|
|
108
|
-
// px) plate would guarantee mush. lambda/64 = 5 px keeps the smear sub-feature
|
|
109
|
-
// and is still unjumpable (a quantum steps at most 2 px/axis/tick). A 'hits'
|
|
110
|
-
// plate would not care — it counts at first contact, i.e. at the front face —
|
|
111
|
-
// which is exactly why the reference H1 runs could afford a lambda/8 slab.
|
|
112
|
-
var detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
113
|
-
var sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
|
|
114
146
|
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
115
147
|
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
116
148
|
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
@@ -137,33 +169,7 @@ export default function buildExperiment(exp) {
|
|
|
137
169
|
for (var si = 0; si < srcYs.length; si++) {
|
|
138
170
|
exp.addObject('SphereEmitter', Object.assign({}, SRC, { center: [srcX, Math.round(srcYs[si])] }));
|
|
139
171
|
}
|
|
140
|
-
|
|
141
|
-
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
142
|
-
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
143
|
-
// is where the population bound is STATED rather than inferred.
|
|
144
|
-
exp.addObject('Wall', { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
|
|
145
|
-
name: 'far_sink', color: [120, 120, 120], alpha: 40 });
|
|
146
|
-
// ── THE PLATE — A COUNTING WALL THAT DOES NOT ABSORB ────────────────────
|
|
147
|
-
// ONE NAMED CONSTANT, so the reading can be swapped in a single edit and the two
|
|
148
|
-
// instruments compared rather than argued about:
|
|
149
|
-
// 'transparent-probe' = collisionBehavior 'nothing' + mark/count. FLUX. The
|
|
150
|
-
// quantum is counted on every tick it overlaps the plate and then flies on,
|
|
151
|
-
// so the reading is the OCCUPANCY INTEGRAL at the plate plane — the density
|
|
152
|
-
// at that plane. This is the shipped choice.
|
|
153
|
-
// 'detector-screen' = destroy + count. HITS. Each quantum is counted once, at
|
|
154
|
-
// arrival, and removed: a transit census, the headcount a photographic plate
|
|
155
|
-
// would report. It is what the H1 reference runs used.
|
|
156
|
-
// NEITHER ABSORBS. 'absorb' stores the arriving mass and re-radiates it — that is
|
|
157
|
-
// the photoelectric experiment, and it saturates.
|
|
158
|
-
var detectorPreset = 'transparent-probe';
|
|
159
|
-
// markTag — the plate's PROVENANCE stamp. The mark hook fires ONLY when the
|
|
160
|
-
// collision trigger does (genuine overlap), whereas the engine buffers every
|
|
161
|
-
// frame inside the LO's mb_w-PADDED search rect whether it triggered or not. So a
|
|
162
|
-
// raw frame count off the buffer is a padded-occupancy reading and the
|
|
163
|
-
// mark-filtered count is the trigger-gated census: two different instruments, and
|
|
164
|
-
// an offline reader needs the tag to tell them apart.
|
|
165
|
-
var screen = exp.addObject('Wall', { rect: [detX, 0, detW, H],
|
|
166
|
-
preset: detectorPreset, markTag: 'plate', name: 'screen', color: [120, 200, 120] });
|
|
172
|
+
const p = exp.declare(parts);
|
|
167
173
|
// ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
|
|
168
174
|
// The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
|
|
169
175
|
// are co-extensive on the bin axis at any canvas size, and it starts just past
|
|
@@ -189,7 +195,7 @@ export default function buildExperiment(exp) {
|
|
|
189
195
|
// profile's floor rises the curve compresses toward the left edge. Read positions off
|
|
190
196
|
// it, not magnitudes; magnitudes come from getCollisionHistogram().
|
|
191
197
|
exp.addObject('LiveChart', {
|
|
192
|
-
source: screen, orientation: 90,
|
|
198
|
+
source: p.screen, orientation: 90,
|
|
193
199
|
rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
|
|
194
200
|
series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
|
|
195
201
|
});
|
|
@@ -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-one-slit).
|
|
11
11
|
*/
|
|
@@ -36,78 +36,82 @@
|
|
|
36
36
|
// diffraction hump" expectation, which this engine's per-source ring mechanism
|
|
37
37
|
// does NOT predict — if that is what appears instead, it is a real null.)
|
|
38
38
|
|
|
39
|
-
import { SphereEmitter } from '@descix/frqtl-web/parts';
|
|
39
|
+
import { SphereEmitter, Tunnel, Wall } from '@descix/frqtl-web/parts';
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
var detW = Math.round(lamPx / 64); // 5 — plate thickness (flux probe, sub-feature)
|
|
76
|
-
var sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
|
|
77
|
-
// ── THE LIGHT SOURCE — ONE emitter, behind the barrier, lighting the one slit ───
|
|
78
|
-
var SRC = {
|
|
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, {
|
|
79
75
|
preset: 'hemisphere', is2D: true, chargeMode: 'random', center: [srcX, cy],
|
|
80
76
|
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
81
77
|
burstSize: 8, emitEveryTicks: 1
|
|
82
|
-
}
|
|
83
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
84
|
-
exp.addObject('SphereEmitter', SRC);
|
|
78
|
+
}],
|
|
85
79
|
// Barrier segments — opaque destroy sinks, leaving ONE slit gap (slit 1 only). Where
|
|
86
80
|
// dse-two-slits had wall2 (a short strip between the two slits) + a slit-2 Tunnel +
|
|
87
81
|
// wall3 (below slit 2), this rung has ONE solid wall from slit 1's bottom liner all
|
|
88
82
|
// the way to the far edge — slit 2's aperture is WALLED, not narrowed or moved.
|
|
89
|
-
|
|
90
|
-
collisionBehavior: 'destroy',
|
|
91
|
-
|
|
92
|
-
collisionBehavior: 'destroy',
|
|
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] }],
|
|
93
87
|
// The one surviving slit channel — directed Tunnel, exit gate COLLAPSED + PHASE-RESET
|
|
94
88
|
// (the dse-tunnel-rebuild composition): its own mouth stands in for a point source at
|
|
95
89
|
// y=120, phase-locked — what a SphereEmitter would do at that pixel.
|
|
96
|
-
|
|
90
|
+
slit1_channel: [Tunnel, {
|
|
97
91
|
left: barrierL, top: slit1Top + linerH, length: barrierW, bore: bore,
|
|
98
92
|
wallThickness: linerH, axis: 'x', mode: 'directed',
|
|
99
93
|
exitCollapse: true, exitRecenter: true, exitPhaseReset: true,
|
|
100
|
-
|
|
101
|
-
}
|
|
94
|
+
color: [80, 140, 220]
|
|
95
|
+
}],
|
|
102
96
|
// FAR-EDGE SINK — the same experiment-boundary sink the canonical-leg pair uses.
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
far_sink: [Wall, { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
|
|
98
|
+
color: [120, 120, 120], alpha: 40 }],
|
|
105
99
|
// ── THE PLATE — SAME instrument as dse-two-slits: a counting wall that does NOT
|
|
106
100
|
// absorb, at the SAME leg D past the new source plane (the one slit exit).
|
|
107
|
-
|
|
108
|
-
preset: 'transparent-probe', markTag: 'plate',
|
|
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);
|
|
109
113
|
exp.addObject('LiveChart', {
|
|
110
|
-
source: screen, orientation: 90,
|
|
114
|
+
source: p.screen, orientation: 90,
|
|
111
115
|
rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
|
|
112
116
|
series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
|
|
113
117
|
});
|