@descix/frqtl-node 0.6.1-p3.3 → 0.7.0-p0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -3
- package/README.md +4 -3
- package/dist/dist-manifest.json +26 -56
- package/dist/frqtl/experiments/_shared/empty-sketch.js +17 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +6 -6
- package/dist/frqtl/objects/setup/setupFxSmoke.js +2 -2
- package/dist/frqtl/objects/setup/setupG3Collide.js +1 -1
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +2 -2
- package/dist/frqtl/objects/v1-replacements.js +1 -1
- package/dist/frqtl/parts/OvenBox.js +2 -2
- package/dist/frqtl/parts/SphereEmitter.js +1 -1
- package/dist/shim/engine/router.js +18 -6
- package/dist/shim/engine/site-assemble.mjs +268 -0
- package/dist/shim/engine/site-graph.js +42 -14
- package/dist/shim/host/device.js +1 -1
- package/dist/shim/index.js +3 -7
- package/dist/shim/notebook/catalog.js +2 -2
- package/dist/shim/notebook/format.js +1 -1
- package/dist/shim/notebook/knob.js +1 -1
- package/dist/shim/notebook/notebook-knobs.js +1 -1
- package/dist/shim/project/specifiers.js +8 -8
- package/dist/shim/registry/copy-gate.js +3 -3
- package/dist/shim/registry/index.js +9 -12
- package/dist/shim/registry/roster-link.js +142 -0
- package/dist/shim/registry/roster.js +595 -0
- package/dist/shim/registry/scene-source.js +80 -105
- package/dist/shim/registry/studio-landing.js +15 -7
- package/dist/shim/render/sun-look.js +1 -2
- package/llms.txt +4 -2
- package/package.json +1 -1
- package/dist/frqtl/scenes/absorbing-wall-3d.js +0 -37
- package/dist/frqtl/scenes/absorbing-wall.js +0 -36
- package/dist/frqtl/scenes/atomic-model.js +0 -91
- package/dist/frqtl/scenes/big-bang.js +0 -49
- package/dist/frqtl/scenes/blackbody.js +0 -84
- package/dist/frqtl/scenes/circuit-sat.js +0 -131
- package/dist/frqtl/scenes/collision-matrix-3d.js +0 -110
- package/dist/frqtl/scenes/counting-wall-3d.js +0 -40
- package/dist/frqtl/scenes/counting-wall.js +0 -42
- package/dist/frqtl/scenes/double-slit.js +0 -34
- package/dist/frqtl/scenes/dse-one-emitter.js +0 -200
- package/dist/frqtl/scenes/dse-one-slit.js +0 -118
- package/dist/frqtl/scenes/dse-photoelectric.js +0 -74
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +0 -279
- package/dist/frqtl/scenes/dse-two-emitters.js +0 -201
- package/dist/frqtl/scenes/dse-two-slits.js +0 -136
- package/dist/frqtl/scenes/empty.js +0 -17
- package/dist/frqtl/scenes/laser-buildup.js +0 -56
- package/dist/frqtl/scenes/mirror-3d.js +0 -60
- package/dist/frqtl/scenes/mirror-route.js +0 -164
- package/dist/frqtl/scenes/mirror.js +0 -42
- package/dist/frqtl/scenes/omni-3d.js +0 -30
- package/dist/frqtl/scenes/omni-source.js +0 -25
- package/dist/frqtl/scenes/self-interference.js +0 -42
- package/dist/frqtl/scenes/smoking-gun.js +0 -126
- package/dist/frqtl/scenes/straight-walk.js +0 -25
- package/dist/frqtl/scenes/sun.js +0 -42
- package/dist/frqtl/scenes/two-source.js +0 -42
- package/dist/frqtl/scenes/wave.js +0 -29
- package/dist/shim/engine/artifact-shell.js +0 -211
- package/dist/shim/registry/exhibits.js +0 -436
- package/dist/shim/registry/scene-roster.js +0 -35
- package/dist/shim/registry/showcase-link.js +0 -61
- package/dist/shim/registry/walkthrough.js +0 -752
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SCENE — Circuit SAT (a half-adder)
|
|
5
|
-
*
|
|
6
|
-
* A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world, the two
|
|
7
|
-
* logic gates, the four rail traces with their input emitters, the two grounded readouts, the live
|
|
8
|
-
* input knobs and the framing are all here; the sugar (LogicGate, Trace, SphereEmitter), the
|
|
9
|
-
* renderer and the engine are imports. Registered by lib/shim/registry/walkthrough.js (rung
|
|
10
|
-
* circuit-sat).
|
|
11
|
-
*
|
|
12
|
-
* Physics (zero new — bench LO1/LO6/LO7 + the Trace wiring): input A and input B are laser
|
|
13
|
-
* emitters that drive quanta down rail traces into an XOR gate (SUM) and an AND gate (CARRY). A
|
|
14
|
-
* gate's valve opens only when its two input ammeters read the logic it embodies, so the circuit
|
|
15
|
-
* SETTLES onto the half-adder's answer: SUM = A xor B, CARRY = A and B, read as the current on each
|
|
16
|
-
* grounded readout. No search algorithm runs.
|
|
17
|
-
*
|
|
18
|
-
* Order (it seeds the run): world → SUM gate → CARRY gate → per lane plan row,
|
|
19
|
-
* the rail trace then its emitter (switched to its input) → the two readouts.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { FrqtlControlOverlay, FrqtlControlsPanel, LogicGate, SphereEmitter, Trace } from '@descix/frqtl-web/parts';
|
|
23
|
-
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
24
|
-
|
|
25
|
-
// Code-declared on purpose: the emitter centres and rail traces derive from the gates' port geometry and RAIL (a knob rewrites it), and a Trace has no kind — a literal block would restate derived geometry by hand.
|
|
26
|
-
export const parts = {};
|
|
27
|
-
|
|
28
|
-
export default function buildExperiment(exp) {
|
|
29
|
-
var W = 760, H = 520; // THE WORLD this scene states (its co-init call below)
|
|
30
|
-
var LEFT = 60, TOP = 15; // the circuit's top-left
|
|
31
|
-
var RAIL = 6; // rail voltage: quanta per tick per input emitter
|
|
32
|
-
var LEAD = 24, READOUT = 24; // input-rail and readout trace lengths
|
|
33
|
-
var INSET = 4; // each input emitter sits this far inside its rail's bore mouth
|
|
34
|
-
var STATE = { a: true, b: false }; // input A HIGH at start: SUM reads 1, CARRY reads 0
|
|
35
|
-
|
|
36
|
-
// The input emitter: a narrow 2D laser on the fundamental layer, random ± charge.
|
|
37
|
-
function emitterBag(center) {
|
|
38
|
-
var bag = { preset: 'laser', is2D: true, R: 24, orientationDeg: 0, emissionBoxSide: 0,
|
|
39
|
-
fundamentalDimension: 0, burstSize: RAIL, emitEveryTicks: 1, chargeMode: 'random' };
|
|
40
|
-
if (center) { bag.center = center; }
|
|
41
|
-
return bag;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ── WORLD — a 2D canvas + universe; the emitter's own world (its worldCfg) ──
|
|
45
|
-
exp.createCanvas(W, H, SphereEmitter.worldCfg(emitterBag(null)));
|
|
46
|
-
exp.declare(parts);
|
|
47
|
-
var u = exp.universe;
|
|
48
|
-
|
|
49
|
-
var MIN = LogicGate.MINIMAL; // bore 3 / wall 3 / length 20
|
|
50
|
-
var cell = LogicGate.cellSize(MIN.bore, MIN.wallThickness, MIN.length);
|
|
51
|
-
var gateOpts = { bore: MIN.bore, wallThickness: MIN.wallThickness, length: MIN.length, supplyVoltage: RAIL };
|
|
52
|
-
|
|
53
|
-
// ── THE GATES — SUM (XOR) above CARRY (AND) ─────────────────────────────
|
|
54
|
-
var sum = exp.addObject('LogicGate', Object.assign({ logic: 'XOR', left: LEFT, top: TOP, name: 'SUM', color: [200, 170, 70] }, gateOpts));
|
|
55
|
-
var carry = exp.addObject('LogicGate', Object.assign({ logic: 'AND', left: LEFT, top: TOP + cell.h, name: 'CARRY', color: [170, 200, 90] }, gateOpts));
|
|
56
|
-
var gates = { SUM: sum, CARRY: carry };
|
|
57
|
-
|
|
58
|
-
// ── THE INPUT RAILS — the lane plan: B and A are SWAPPED on CARRY (AND commutes; the swap buys
|
|
59
|
-
// a lane pitch of separation between the two inputs) ──────────────────────────────────────
|
|
60
|
-
var LANES = [
|
|
61
|
-
{ input: 'A', gate: 'SUM', port: 'inPortA' }, { input: 'B', gate: 'SUM', port: 'inPortB' },
|
|
62
|
-
{ input: 'B', gate: 'CARRY', port: 'inPortA' }, { input: 'A', gate: 'CARRY', port: 'inPortB' }
|
|
63
|
-
];
|
|
64
|
-
var rails = { A: [], B: [] }, traces = [];
|
|
65
|
-
LANES.forEach(function (row, i) {
|
|
66
|
-
var port = gates[row.gate][row.port], y = port[1];
|
|
67
|
-
var t = new Trace(u, { path: [[port[0] - LEAD, y], [port[0], y]], bore: MIN.bore,
|
|
68
|
-
wallThickness: MIN.wallThickness, name: 'rail' + row.input + i, color: [90, 120, 200] });
|
|
69
|
-
var em = exp.addObject('SphereEmitter', Object.assign(emitterBag([t.inPort[0] + INSET, y]),
|
|
70
|
-
{ name: 'input' + row.input + i })); // parked inside the bore mouth
|
|
71
|
-
em.setActive(row.input === 'A' ? STATE.a : STATE.b);
|
|
72
|
-
rails[row.input].push(em);
|
|
73
|
-
traces.push(t);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ── THE READOUTS — grounded traces off each gate's outlet: their current IS the answer ──
|
|
77
|
-
function readout(gate, name) {
|
|
78
|
-
return new Trace(u, { path: [[gate.outPort[0], gate.outPort[1]], [gate.outPort[0] + READOUT, gate.outPort[1]]],
|
|
79
|
-
bore: MIN.bore, wallThickness: MIN.wallThickness, ground: true, name: name, color: [60, 160, 220] });
|
|
80
|
-
}
|
|
81
|
-
var readouts = { SUM: readout(sum, 'SUM_out'), CARRY: readout(carry, 'CARRY_out') };
|
|
82
|
-
traces.push(readouts.SUM, readouts.CARRY);
|
|
83
|
-
traces.forEach(function (p) { p.attachToRunner(exp.runner); }); // every trace reads the tick stream (a declared gate subscribed at addObject)
|
|
84
|
-
|
|
85
|
-
function setInput(which, on) {
|
|
86
|
-
STATE[which] = !!on;
|
|
87
|
-
rails[which === 'a' ? 'A' : 'B'].forEach(function (em) { em.setActive(STATE[which]); });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ── THE CIRCUIT, PUBLISHED — the parts and the geometry a harness reads (the half-adder
|
|
91
|
-
// acceptance rig): each gate's own getState(), each readout's calibrated getDelivered(), the
|
|
92
|
-
// input switch, and the layout values its settle budget is computed from. ───────────────
|
|
93
|
-
exp.circuit = {
|
|
94
|
-
gates: gates, readouts: readouts,
|
|
95
|
-
rails: rails, lanes: LANES, setInput: setInput,
|
|
96
|
-
lead: LEAD, readout: READOUT, inset: INSET
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// ── THE VIEW — frame the 72x54 px circuit (render only: the 2D camera's distance IS the visible
|
|
100
|
-
// physics-px height) ─────────────────────────────────────────────────────────────────────
|
|
101
|
-
var fp = [LEFT - LEAD, TOP, LEAD + cell.w + READOUT + 4, cell.h * 2];
|
|
102
|
-
var cam = exp.runner.cameraControl;
|
|
103
|
-
if (cam && typeof cam.setOrbit === 'function') {
|
|
104
|
-
cam.setOrbit({ target: { x: fp[0] + fp[2] / 2, y: fp[1] + fp[3] / 2 },
|
|
105
|
-
distance: Math.max(fp[3] * 1.35, fp[2] * 1.35 * H / W) });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ── THE KNOBS (on the canvas): the two inputs and the rail ──────────────
|
|
109
|
-
var registry = ControlRegistry.makeControlRegistry();
|
|
110
|
-
registry.registerControls('circuit-sat', [
|
|
111
|
-
{ kind: 'scalar', id: 'inputA', label: 'input A', min: 0, max: 1, step: 1, value: STATE.a ? 1 : 0, widget: 'toggle',
|
|
112
|
-
fmt: function (v) { return v ? '1 (HIGH)' : '0 (LOW)'; }, onChange: function (v) { setInput('a', v); },
|
|
113
|
-
place: { x: 0.02, y: 0.80 } },
|
|
114
|
-
{ kind: 'scalar', id: 'inputB', label: 'input B', min: 0, max: 1, step: 1, value: STATE.b ? 1 : 0, widget: 'toggle',
|
|
115
|
-
fmt: function (v) { return v ? '1 (HIGH)' : '0 (LOW)'; }, onChange: function (v) { setInput('b', v); },
|
|
116
|
-
place: { x: 0.02, y: 0.88 } },
|
|
117
|
-
{ kind: 'scalar', id: 'rail', label: 'rail (burst/tick)', min: 1, max: 16, step: 1, value: RAIL,
|
|
118
|
-
fmt: function (v) { return String(v) + ' q/tick'; },
|
|
119
|
-
onChange: function (v) {
|
|
120
|
-
RAIL = Math.max(0, Math.round(v));
|
|
121
|
-
sum.setSupplyVoltage(RAIL); carry.setSupplyVoltage(RAIL);
|
|
122
|
-
rails.A.concat(rails.B).forEach(function (em) { em.setBurstSize(RAIL); });
|
|
123
|
-
},
|
|
124
|
-
place: { x: 0.30, y: 0.88 } }
|
|
125
|
-
]);
|
|
126
|
-
// Chrome (a page only): expose the knobs to a host frame, and mount the overlay through the
|
|
127
|
-
// runner's ONE door (runner.controlHost) — before a canvas is mounted (a notebook pane) there is
|
|
128
|
-
// none, so the knobs stay in the exposed registry only.
|
|
129
|
-
FrqtlControlsPanel.exposeRegistry(registry, { runner: exp.runner });
|
|
130
|
-
if (exp.runner.controlHost) { FrqtlControlOverlay.mount(exp.runner, { registry: registry, name: 'circuit-sat' }); }
|
|
131
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SCENE — 3D Collisions Lab
|
|
5
|
-
*
|
|
6
|
-
* A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The 3D world, the
|
|
7
|
-
* seven head-on lanes of equal-mass voxel pairs, their live knobs and the look are all here; the
|
|
8
|
-
* lane density table (`collisionMatrixLanes`), the renderer and the engine are imports.
|
|
9
|
-
* Registered by lib/shim/registry/walkthrough.js (rung collision-matrix-3d).
|
|
10
|
-
*
|
|
11
|
-
* Physics (zero new — bench C2, C3, C5, SU1): each lane fires two equal bodies at each other at
|
|
12
|
-
* speed_prob 1 along x; a lane's density (mass / capacity) sets how hard the bodies are — rigid
|
|
13
|
-
* bodies bounce cleanly, soft ones press into each other before parting. The lanes sit on a 3x3
|
|
14
|
-
* y/z grid, so the same contact happens at seven depths: collision is identical on any axis
|
|
15
|
-
* (2D ≡ 3D). The same engine that makes light makes mechanics — no second physics.
|
|
16
|
-
*
|
|
17
|
-
* Order (it seeds the run): world → collisions on → the voxel layer →
|
|
18
|
-
* per lane, body A then body B (engine-built, capacity read back once) → seat every body.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { CameraControls, FrqtlControlOverlay, FrqtlControlsPanel } from '@descix/frqtl-web/parts';
|
|
22
|
-
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
23
|
-
import { collisionMatrixLanes } from '@descix/frqtl-web/objects/setup/_collisionMatrixLanes.js';
|
|
24
|
-
|
|
25
|
-
export const parts = {};
|
|
26
|
-
|
|
27
|
-
export default function buildExperiment(exp) {
|
|
28
|
-
var W = 700, H = 700, D = 700; // THE WORLD this scene states (its co-init call below)
|
|
29
|
-
var LEVEL = 6; // the voxel layer: 64 px bodies, capacity 4096
|
|
30
|
-
|
|
31
|
-
// ── WORLD — a 3D scene + universe W×H×D, fundamental layer 0, λ multiplier 1 ──
|
|
32
|
-
exp.createScene(W, H, D, { dimension: 0, wavelengthScale: 1 });
|
|
33
|
-
exp.declare(parts);
|
|
34
|
-
var u = exp.universe;
|
|
35
|
-
u.withInterQuantumCollisions = true;
|
|
36
|
-
u.addDimension(LEVEL);
|
|
37
|
-
exp.runner.setRateLimits({ tpsLimit: 240 }); // pacing only: wall-clock, never the ticks
|
|
38
|
-
|
|
39
|
-
var lanesTable = collisionMatrixLanes; // the ONE lane density table (imported)
|
|
40
|
-
var voxel = Math.pow(2, LEVEL), half = voxel / 2;
|
|
41
|
-
var midX = Math.floor(W * 0.5);
|
|
42
|
-
var ax0 = midX - Math.floor(1.5 * voxel); // body A, left of centre
|
|
43
|
-
var bx0 = midX + Math.floor(1.5 * voxel); // body B, right of centre
|
|
44
|
-
var N = lanesTable.LANE_DENSITIES.length; // 7 lanes
|
|
45
|
-
var spacing = Math.round(H / 4);
|
|
46
|
-
var laneYs = [], laneZs = [];
|
|
47
|
-
for (var k = 0; k < N; k++) {
|
|
48
|
-
laneYs.push(Math.round(H / 2 + (Math.floor(k / 3) - 1) * spacing)); // rows 0,0,0,1,1,1,2
|
|
49
|
-
laneZs.push(((k % 3) - 1) * spacing); // cols 0,1,2,…
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// The live knobs: an offset (the bodies miss by that much) and an angle (vy against vx).
|
|
53
|
-
var offsetPx = 0, angleVy = 0;
|
|
54
|
-
function seat(qid, cx, cy, cz, dirX, mass) {
|
|
55
|
-
u.setFrameProperty(qid, 'x', cx);
|
|
56
|
-
u.setFrameProperty(qid, 'y', cy + dirX * offsetPx);
|
|
57
|
-
u.setFrameProperty(qid, 'z', cz);
|
|
58
|
-
u.setFrameProperty(qid, 'vx', dirX);
|
|
59
|
-
u.setFrameProperty(qid, 'vy', -dirX * angleVy);
|
|
60
|
-
u.setFrameProperty(qid, 'vz', 0);
|
|
61
|
-
u.setFrameProperty(qid, 'mass', mass);
|
|
62
|
-
u.setFrameProperty(qid, 'speed_prob', 1.0);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ── THE BODIES — A then B per lane; the capacity is read back from the engine, never assumed ──
|
|
66
|
-
var built = [], capacity = null;
|
|
67
|
-
for (var i = 0; i < N; i++) {
|
|
68
|
-
var a = u.makeLeafFrame(ax0 - half, laneYs[i] - half, LEVEL, null, 1, 0, true, 0, null);
|
|
69
|
-
u.injectFrame(a);
|
|
70
|
-
var aQid = u.getFrameQuantumId(a);
|
|
71
|
-
var b = u.makeLeafFrame(bx0 - half, laneYs[i] - half, LEVEL, null, -1, 0, true, 0, null);
|
|
72
|
-
u.injectFrame(b);
|
|
73
|
-
var bQid = u.getFrameQuantumId(b);
|
|
74
|
-
if (capacity === null) { capacity = u.getFrameProperty(aQid, 'mass'); }
|
|
75
|
-
built.push({ aQid: aQid, bQid: bQid });
|
|
76
|
-
}
|
|
77
|
-
var masses = lanesTable.laneMassesForCapacity(capacity);
|
|
78
|
-
var lanes = built.map(function (q, i) {
|
|
79
|
-
return { aQid: q.aQid, bQid: q.bQid, mass: masses[i], laneY: laneYs[i], laneZ: laneZs[i] };
|
|
80
|
-
});
|
|
81
|
-
function reseatAll() {
|
|
82
|
-
lanes.forEach(function (l) {
|
|
83
|
-
seat(l.aQid, ax0, l.laneY, l.laneZ, 1, l.mass);
|
|
84
|
-
seat(l.bQid, bx0, l.laneY, l.laneZ, -1, l.mass);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
reseatAll();
|
|
88
|
-
|
|
89
|
-
// ── THE LOOK — lit spheres, coloured by fullness (sparse red → dense violet) ──
|
|
90
|
-
exp.render({ shape: 'sphere', colorScheme: 'roygbiv' });
|
|
91
|
-
|
|
92
|
-
// ── THE KNOBS (on the canvas) + MOVE/LOOK pads ──
|
|
93
|
-
var registry = ControlRegistry.makeControlRegistry();
|
|
94
|
-
registry.registerControls('collision-matrix-3d', [
|
|
95
|
-
{ kind: 'scalar', id: 'offset', label: 'offset', min: 0, max: 48, step: 4, value: 0,
|
|
96
|
-
fmt: function (v) { return String(v); }, onChange: function (v) { offsetPx = v; reseatAll(); },
|
|
97
|
-
place: { x: 0.02, y: 0.03 } },
|
|
98
|
-
{ kind: 'scalar', id: 'angle', label: 'angle', min: 0, max: 1, step: 0.1, value: 0,
|
|
99
|
-
fmt: function (v) { return v.toFixed(1); }, onChange: function (v) { angleVy = v; reseatAll(); },
|
|
100
|
-
place: { x: 0.02, y: 0.12 } }
|
|
101
|
-
]);
|
|
102
|
-
// Chrome (a page only): expose the knobs to a host frame, and mount the overlay through the
|
|
103
|
-
// runner's ONE door (runner.controlHost) — before a canvas is mounted (a notebook pane) there is
|
|
104
|
-
// none, so the knobs stay in the exposed registry only.
|
|
105
|
-
FrqtlControlsPanel.exposeRegistry(registry, { runner: exp.runner });
|
|
106
|
-
if (exp.runner.controlHost) { FrqtlControlOverlay.mount(exp.runner, { registry: registry, name: 'collision-matrix-3d' }); }
|
|
107
|
-
if (typeof document !== 'undefined') {
|
|
108
|
-
if (exp.runner.controlHost) { CameraControls.attach(exp.runner, exp.runner.controlHost); }
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SCENE — 1C · Counting Wall (3D)
|
|
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 cl1c-counting-wall-3d).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
14
|
-
|
|
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, {
|
|
22
|
-
preset: 'spherical', is2D: false, center: [Math.round(W * 0.15), cy],
|
|
23
|
-
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
24
|
-
burstSize: 24, emitEveryTicks: 1
|
|
25
|
-
}],
|
|
26
|
-
screen: [Wall, {
|
|
27
|
-
rect: [Math.round(W * 0.6), 0, 20, H], z: -80, depth: 160,
|
|
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);
|
|
35
|
-
exp.addObject('LiveChart', {
|
|
36
|
-
source: p.screen,
|
|
37
|
-
rect: [Math.round(W * 0.66), Math.round(H * 0.08), Math.round(W * 0.28), Math.round(H * 0.5)],
|
|
38
|
-
series: 'bar', label: 'arrivals by y', refreshMs: 100, color: [24, 32, 46]
|
|
39
|
-
});
|
|
40
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SCENE — 1C · Counting Wall
|
|
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 cl1c-counting-wall).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
14
|
-
|
|
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, {
|
|
22
|
-
preset: 'spherical', is2D: true, center: [Math.round(W * 0.15), cy],
|
|
23
|
-
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
24
|
-
burstSize: 24, emitEveryTicks: 1
|
|
25
|
-
}],
|
|
26
|
-
screen: [Wall, {
|
|
27
|
-
rect: [Math.round(W * 0.6), 0, 20, H],
|
|
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);
|
|
35
|
-
// The interactive counting demo: a live chart of the detector's histogram,
|
|
36
|
-
// placed to the RIGHT of the screen (past where the quanta are absorbed).
|
|
37
|
-
exp.addObject('LiveChart', {
|
|
38
|
-
source: p.screen,
|
|
39
|
-
rect: [Math.round(W * 0.66), Math.round(H * 0.08), Math.round(W * 0.28), Math.round(H * 0.5)],
|
|
40
|
-
series: 'bar', label: 'arrivals by y', refreshMs: 100, color: [24, 32, 46]
|
|
41
|
-
});
|
|
42
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SCENE — Double slit (the SDK's artifact page)
|
|
5
|
-
*
|
|
6
|
-
* A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world (its
|
|
7
|
-
* co-init call, `exp.createCanvas` — the one place its size is written), the parts and their order
|
|
8
|
-
* are all here; the sugar (SlitScreen, SlitLightSource, DetectorWall, LargeObject), the renderer
|
|
9
|
-
* and the engine are imports. Shipped as `dist/artifact/double-slit.html`: the generator
|
|
10
|
-
* (lib/shim/engine/artifact-shell.js, ARTIFACT_SCENE) embeds THIS file's text verbatim.
|
|
11
|
-
*
|
|
12
|
-
* The parts are setupDoubleSlit's at its defaults (slit screen at 1.5 λ, an absorbing start wall,
|
|
13
|
-
* the slit light source at a 10° step, the detector 3 λ past the screen), so the seeded run is
|
|
14
|
-
* that factory's, tick for tick (lib/shim/engine/test/artifact-shell-sketch.test.mjs holds it on
|
|
15
|
-
* the WASM engine). Zero new physics.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { CollisionTypes, DetectorWall, LargeObject, SlitLightSource, SlitScreen } from '@descix/frqtl-web/parts';
|
|
19
|
-
|
|
20
|
-
export const parts = {};
|
|
21
|
-
|
|
22
|
-
export default function buildExperiment(exp) {
|
|
23
|
-
var S = 480, WAVELENGTH = 64; // THE WORLD this scene states (its co-init call below) and its one geometry knob
|
|
24
|
-
|
|
25
|
-
exp.createCanvas(S, S, { dimension: 2, wavelengthScale: Math.floor(WAVELENGTH / 16) }); // a 2D canvas + universe, S x S
|
|
26
|
-
exp.declare(parts);
|
|
27
|
-
var u = exp.universe;
|
|
28
|
-
|
|
29
|
-
var slits = new SlitScreen(u, WAVELENGTH * 1.5, WAVELENGTH, S, WAVELENGTH, true, 1);
|
|
30
|
-
new LargeObject(u, [0, 0, 10, S], CollisionTypes.ABSORB, [], 'start_wall', 'closest', null, 1, [120, 120, 120]);
|
|
31
|
-
var light = new SlitLightSource(u, slits, 10);
|
|
32
|
-
new DetectorWall(u, u.universe_rect, slits.wall1.rect.left + slits.wall1.rect.w + 3 * WAVELENGTH);
|
|
33
|
-
u.experimentTickFunctions.push(light.getTickFunction());
|
|
34
|
-
}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SCENE — One Emitter 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-emitter).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ── H1 AT THE CANONICAL DETECTOR LEG ─────────────────────────────────────────
|
|
14
|
-
//
|
|
15
|
-
// The competence baseline: the most basic thing, done right, seen with eyes. One
|
|
16
|
-
// emitter, then two, with the plate PRE-COMPUTED at the leg the ring formula was
|
|
17
|
-
// written for and the count-by-y chart registered to that plate. No barrier, no
|
|
18
|
-
// slits, no tunnels — nothing between the source and the plate, so a band on the
|
|
19
|
-
// chart can have come from nothing but the walk.
|
|
20
|
-
//
|
|
21
|
-
// WHY THESE EXIST BESIDE `dse-tunnel-rebuild`. That rung places its plate at
|
|
22
|
-
// D = 155 px against λ_eff = 128 px — 4.84× the reference leg — which pushes the
|
|
23
|
-
// low orders inside the leg where they never reach the plate at all, so only high-m
|
|
24
|
-
// rings can land. These two rungs put the plate ON the reference leg instead.
|
|
25
|
-
//
|
|
26
|
-
// THE GEOMETRY, and it is not re-derived here. λ is the ONLY length; every other
|
|
27
|
-
// length below is a dimensionless constant × λ (the `published/dse/scene.js`
|
|
28
|
-
// discipline). The ring ladder quoted in CLAUDE.md,
|
|
29
|
-
//
|
|
30
|
-
// r_m = (λ/4)·√((2m+1)² + 1) [slant, from the emission point]
|
|
31
|
-
//
|
|
32
|
-
// is the hypotenuse of a right triangle whose TRANSVERSE leg is ρ_m = (2m+1)·λ/4
|
|
33
|
-
// and whose AXIAL leg is λ/4 — the "+1" under the root IS the (λ/4)² axial term.
|
|
34
|
-
// Solve for the axial leg order by order and every order returns λ/4, so the leg is
|
|
35
|
-
// not fitted per-order: it is the one leg the formula was written for. At that leg
|
|
36
|
-
// the on-plate bands are ρ_m = (2m+1)·λ/4 from the source's own y, spacing λ/2.
|
|
37
|
-
//
|
|
38
|
-
// MEASURED, not assumed. `published/dse/h1/` ran exactly this apparatus (bare
|
|
39
|
-
// source, plate at D = λ/4 from the emitter, seed 1, detector 'hits') at λ = 160 /
|
|
40
|
-
// 320 / 640 and found a symmetric feature on the plate at |Δy| = λ/4 − 4 px — 36 /
|
|
41
|
-
// 76 / 156 px — holding that offset to the pixel across a 4× sweep while moving 120
|
|
42
|
-
// px in absolute terms. The raw histogram's shape is: a flat PLATEAU across the
|
|
43
|
-
// centre, a sharp MINIMUM just inside ±λ/4, and a PEAK riding immediately outside it
|
|
44
|
-
// at ±λ/4. At λ=640, counts by Δy/λ: −0.250 → 1107 | −0.225 → 306 | 0.000 → 1011 |
|
|
45
|
-
// +0.225 → 297 | +0.250 → 1125. At λ=320 the same shape, shallower. TWO points on
|
|
46
|
-
// the wall at ¼ λ_eff, with a plateau — not a hole — between them.
|
|
47
|
-
//
|
|
48
|
-
// AND THAT IS WHY λ IS 320 HERE. The dip-to-peak separation is 0.025λ: 16 px at
|
|
49
|
-
// λ=640, 8 px at λ=320, 4 px at λ=160 — which is why the λ=160 run reads as a broad
|
|
50
|
-
// hump with the feature only recoverable by a windowed search on a smoothed profile.
|
|
51
|
-
// Below ~λ=300 this stage cannot resolve the thing it exists to show. So this rung
|
|
52
|
-
// shows ONE well-resolved band pair per source rather than three mushy ones. The 4 px
|
|
53
|
-
// pedestal does NOT scale with λ (it is engine scale — a quantum steps ≤2 px/axis/
|
|
54
|
-
// tick) and no mechanism is offered for it here. The m ≥ 1 rungs of the ladder are
|
|
55
|
-
// NOT established: of the H1 runs' 8 predicted positions, 3 matched, and the m=1
|
|
56
|
-
// positions carried minima.
|
|
57
|
-
//
|
|
58
|
-
// THE LEG IS ONE NAMED CONSTANT (`legFrac`), and so is the plate's DETECTOR MODE —
|
|
59
|
-
// both so they can be moved deliberately from a one-line edit and the consequence
|
|
60
|
-
// re-derived, rather than being literals nobody chose.
|
|
61
|
-
//
|
|
62
|
-
// The beam is the `dse-tunnel-rebuild` beam and the H1 beam, unchanged: wavelength
|
|
63
|
-
// arg 3.2 → mass 5 / capacity 16 → fullness 0.3125, which LightColor renders yellow,
|
|
64
|
-
// and λ_eff = 3.2 × wavelengthScale. The leaves are emitted COLOURLESS; the colour
|
|
65
|
-
// is DERIVED from fullness, never forced.
|
|
66
|
-
//
|
|
67
|
-
// 2D ONLY, deliberately. There is no `modes` strip and no `exp.is3D` guard: these
|
|
68
|
-
// rungs declare one world each. A second dimensional variant would be a separate
|
|
69
|
-
// recipe declaration, not a mode.
|
|
70
|
-
// THE PAIR. `dse-two-emitters.js` is the SAME apparatus with TWO source entries. The two files differ ONLY in the
|
|
71
|
-
// title lines, this note, and their `source*` entries of `parts` (with the world call naming the first
|
|
72
|
-
// of them) — lib/frqtl/scenes/test/scene-files.test.mjs fails if anything else diverges, so the A/B the
|
|
73
|
-
// pair exists to make stays an A/B.
|
|
74
|
-
|
|
75
|
-
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
76
|
-
|
|
77
|
-
const CW = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
78
|
-
// ── THE ONLY LENGTH ─────────────────────────────────────────────────────
|
|
79
|
-
// lamPx is lambda_eff in pixels. 320 is chosen for RESOLUTION, not for how many
|
|
80
|
-
// orders fit: the feature this rung exists to show (the minimum just inside the
|
|
81
|
-
// band and the peak riding outside it) is 0.025*lambda wide, which is 8 px at
|
|
82
|
-
// lambda 320 and 3 px at lambda 128. A stage that cannot separate them shows a
|
|
83
|
-
// smooth hump and leaves you unable to say whether the physics or the sampling
|
|
84
|
-
// was at fault. One well-resolved pair beats three mushy ones.
|
|
85
|
-
const lamPx = 320;
|
|
86
|
-
const sinkW = Math.round(lamPx / 16); // 20 — far-edge sink thickness
|
|
87
|
-
|
|
88
|
-
// ── THE DETECTOR LEG — ONE NAMED CONSTANT ───────────────────────────────
|
|
89
|
-
// D = legFrac * lambda, measured from the SOURCE (there is no barrier, so the
|
|
90
|
-
// emitter itself is the reference point of the ladder). legFrac = 1/4 is the
|
|
91
|
-
// leg the ring formula was written for; see the block comment above.
|
|
92
|
-
const legFrac = 1 / 4;
|
|
93
|
-
const D = Math.round(lamPx * legFrac); // 80
|
|
94
|
-
const srcX = Math.round(lamPx / 16); // 20 — launch room; the canvas edge is the back sink
|
|
95
|
-
const detX = srcX + D; // 100 — the plate's FRONT FACE
|
|
96
|
-
// PLATE THICKNESS IS AN OPTICAL CHOICE UNDER A FLUX DETECTOR, not trim. A flux
|
|
97
|
-
// probe counts every tick a quantum overlaps it, so it integrates the density
|
|
98
|
-
// ACROSS its thickness — and the ring crest is a CIRCLE about the source, not a
|
|
99
|
-
// plane. At the m=0 band the crest's slope is dy/dx = -D/y = -1, so a slab t px
|
|
100
|
-
// thick smears the band over ~t px of y. Against an 8 px feature a lambda/8 (40
|
|
101
|
-
// px) plate would guarantee mush. lambda/64 = 5 px keeps the smear sub-feature
|
|
102
|
-
// and is still unjumpable (a quantum steps at most 2 px/axis/tick). A 'hits'
|
|
103
|
-
// plate would not care — it counts at first contact, i.e. at the front face —
|
|
104
|
-
// which is exactly why the reference H1 runs could afford a lambda/8 slab.
|
|
105
|
-
const detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
106
|
-
// ── THE PLATE — A COUNTING WALL THAT DOES NOT ABSORB ────────────────────
|
|
107
|
-
// ONE NAMED CONSTANT, so the reading can be swapped in a single edit and the two
|
|
108
|
-
// instruments compared rather than argued about:
|
|
109
|
-
// 'transparent-probe' = collisionBehavior 'nothing' + mark/count. FLUX. The
|
|
110
|
-
// quantum is counted on every tick it overlaps the plate and then flies on,
|
|
111
|
-
// so the reading is the OCCUPANCY INTEGRAL at the plate plane — the density
|
|
112
|
-
// at that plane. This is the shipped choice.
|
|
113
|
-
// 'detector-screen' = destroy + count. HITS. Each quantum is counted once, at
|
|
114
|
-
// arrival, and removed: a transit census, the headcount a photographic plate
|
|
115
|
-
// would report. It is what the H1 reference runs used.
|
|
116
|
-
// NEITHER ABSORBS. 'absorb' stores the arriving mass and re-radiates it — that is
|
|
117
|
-
// the photoelectric experiment, and it saturates.
|
|
118
|
-
const detectorPreset = 'transparent-probe';
|
|
119
|
-
|
|
120
|
-
const lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
121
|
-
const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
122
|
-
const wlScale = Math.floor(fwl / 4); // 100
|
|
123
|
-
const cy = Math.round(H / 2); // 200 — the source plane's midline
|
|
124
|
-
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
125
|
-
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
126
|
-
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
127
|
-
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
128
|
-
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
129
|
-
// exactly where the one-source rung has no peak at all.
|
|
130
|
-
const sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
131
|
-
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
132
|
-
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
133
|
-
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
134
|
-
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
135
|
-
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
136
|
-
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
137
|
-
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
138
|
-
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
139
|
-
// forward component. One sink, not two.
|
|
140
|
-
const SRC = {
|
|
141
|
-
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
142
|
-
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
143
|
-
burstSize: 8, emitEveryTicks: 1
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export const parts = {
|
|
147
|
-
source: [SphereEmitter, { ...SRC, center: [srcX, cy] }],
|
|
148
|
-
// FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
|
|
149
|
-
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
150
|
-
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
151
|
-
// is where the population bound is STATED rather than inferred.
|
|
152
|
-
far_sink: [Wall, { rect: [CW - sinkW, 0, sinkW, H], collisionBehavior: 'destroy',
|
|
153
|
-
color: [120, 120, 120], alpha: 40 }],
|
|
154
|
-
// markTag — the plate's PROVENANCE stamp. The mark hook fires ONLY when the
|
|
155
|
-
// collision trigger does (genuine overlap), whereas the engine buffers every
|
|
156
|
-
// frame inside the LO's mb_w-PADDED search rect whether it triggered or not. So a
|
|
157
|
-
// raw frame count off the buffer is a padded-occupancy reading and the
|
|
158
|
-
// mark-filtered count is the trigger-gated census: two different instruments, and
|
|
159
|
-
// an offline reader needs the tag to tell them apart.
|
|
160
|
-
screen: [Wall, { rect: [detX, 0, detW, H],
|
|
161
|
-
preset: detectorPreset, markTag: 'plate', color: [120, 200, 120] }],
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
export default function buildExperiment(exp) {
|
|
165
|
-
if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
|
|
166
|
-
throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
|
|
167
|
-
'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
168
|
-
}
|
|
169
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
170
|
-
const p = exp.declare(parts);
|
|
171
|
-
// ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
|
|
172
|
-
// The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
|
|
173
|
-
// are co-extensive on the bin axis at any canvas size, and it starts just past
|
|
174
|
-
// the plate's far face so it reads as sitting behind it. orientation 90 = bars
|
|
175
|
-
// stacked along y; the chart contract reverses the y scale when horizontal, so
|
|
176
|
-
// bin 0 (the SMALLEST y) sits at the TOP, matching the y-DOWN world.
|
|
177
|
-
// HONEST LIMIT (inherited from the shipped 2D block, and it is real): the bin
|
|
178
|
-
// axis is a CATEGORY axis over the bins that actually received hits, not a linear
|
|
179
|
-
// 0..H axis. Registration is exact in ORDER and DIRECTION always, and exact in
|
|
180
|
-
// absolute screen height only insofar as the hits span the wall. Here the plate
|
|
181
|
-
// is a full-height flux probe under a 180-degree source, so the occupied span
|
|
182
|
-
// fills the wall within a few hundred ticks and the axis is very nearly linear —
|
|
183
|
-
// but the ladder positions below must be read off the raw census, never measured
|
|
184
|
-
// with a ruler on the panel.
|
|
185
|
-
// SERIES 'line', NOT 'bar', AND THAT IS A MEASUREMENT NOT A TASTE. The bins are
|
|
186
|
-
// per-pixel y, so a full-height plate produces ~400 categories in a ~240 px plot:
|
|
187
|
-
// each bar is sub-pixel TALL, its 0.55 fill alpha is diluted by the coverage, and
|
|
188
|
-
// the profile collapses into a faint sliver hugging the axis. Measured identically
|
|
189
|
-
// on the shipped 1C Counting Wall rung, so it is the chart contract's behaviour at
|
|
190
|
-
// this bin count and not something these rungs introduced. A line joins the same 400
|
|
191
|
-
// points into one continuous curve, which reads as the profile it is. CAVEAT, stated
|
|
192
|
-
// because it bites: Chart.js does not begin a LINE value axis at zero, so once the
|
|
193
|
-
// profile's floor rises the curve compresses toward the left edge. Read positions off
|
|
194
|
-
// it, not magnitudes; magnitudes come from getCollisionHistogram().
|
|
195
|
-
exp.addObject('LiveChart', {
|
|
196
|
-
source: p.screen, orientation: 90,
|
|
197
|
-
rect: [detX + detW + 8, 0, Math.max(60, CW - (detX + detW + 8) - 8), H],
|
|
198
|
-
series: 'line', label: 'count by y', refreshMs: 100, color: [24, 32, 46], alpha: 225
|
|
199
|
-
});
|
|
200
|
-
}
|