@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.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 +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- package/package.json +62 -4
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupConeEmitter3D — the smallest GYM-visible 3D surface for the CEO's
|
|
5
|
+
* concentric-ring cone-vs-cylinder construction (canonical-bench S2/S3,
|
|
6
|
+
* `docs/internal/physics-lab/canonical-bench/bench.cjs`), driven by ONE live SPREAD knob.
|
|
7
|
+
*
|
|
8
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} universe
|
|
11
|
+
* The engine universe instance (JS or WASM compat shim), not yet init'd.
|
|
12
|
+
* Obtained from `window.EGPTUniverse` via FrqtlApp.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} [options]
|
|
15
|
+
* @param {number} [options.fundamentalWaveLength=64] - Desired wavelength of
|
|
16
|
+
* emitted quanta (px) — same `wavelengthScale` derivation every other
|
|
17
|
+
* setup factory in this directory uses (`setupParticleWalk3D.js`,
|
|
18
|
+
* `setupSphereEmitter3D.js`).
|
|
19
|
+
* @param {number} [options.spread=0.6] - v_radial MAGNITUDE (px/tick) each ring
|
|
20
|
+
* leaf's (vx,vy) is scaled to, pointing along its own outward radial unit
|
|
21
|
+
* vector. `spread=0` ⇒ every leaf's (vx,vy)=(0,0) — the LITERAL S2
|
|
22
|
+
* construction (collimated cylinder/laser). `spread>0` ⇒ the S3 construction
|
|
23
|
+
* (diverging cone/flashlight); the emergent half-angle is `atan(spread/vz)`
|
|
24
|
+
* (bench S3 law, measured within ~0.3° across 5 vz values).
|
|
25
|
+
* @param {number} [options.vz=1] - Constant forward velocity (px/tick) every
|
|
26
|
+
* ring leaf is stamped with, along the engine's z axis (bench's S2/S3 axis
|
|
27
|
+
* convention — the CEO's literal "assuming z towards viewer" framing).
|
|
28
|
+
* @param {number} [options.ringCount=4] - Concentric rings per emission burst,
|
|
29
|
+
* radii evenly spaced from `ringRadius/ringCount` to `ringRadius`.
|
|
30
|
+
* @param {number} [options.ringRadius=40] - Outer ring radius (px).
|
|
31
|
+
* @param {number} [options.leavesPerRing=20] - Leaves placed per ring (evenly
|
|
32
|
+
* spaced in theta).
|
|
33
|
+
* @param {number} [options.emitEveryTicks=6] - Ticks between successive ring
|
|
34
|
+
* bursts (sustained emission — a host tick-fn, NOT a native emit-spec; see
|
|
35
|
+
* the Layer 2 note below). A shorter period gives a denser, more continuous
|
|
36
|
+
* beam at higher per-tick leaf-creation cost.
|
|
37
|
+
*
|
|
38
|
+
* @returns {{
|
|
39
|
+
* coneEmitterControl: {
|
|
40
|
+
* setSpread(v), getSpread(): number,
|
|
41
|
+
* setVz(v), getVz(): number,
|
|
42
|
+
* setActive(bool),
|
|
43
|
+
* setRingCount(v), setRingRadius(v),
|
|
44
|
+
* controls: Array<{id,label,min,max,step,value,fmt,onChange}>
|
|
45
|
+
* },
|
|
46
|
+
* largeObjects: []
|
|
47
|
+
* }}
|
|
48
|
+
* `coneEmitterControl` is a live sugar handle (the canonical knob pattern —
|
|
49
|
+
* the blackbody scene's temperature knob → `oven.setTemperature`, `g3-lo-collide`'s `ctl`): a page
|
|
50
|
+
* wires a slider's `input` event straight to `setSpread`/`setVz`, live, no
|
|
51
|
+
* reload. `coneEmitterControl.controls` is the SAME standardized
|
|
52
|
+
* `runner.experimentControls`-compatible array `SphereEmitter.controls`
|
|
53
|
+
* already established — declared ONCE, consumed by both the page's own
|
|
54
|
+
* sliders and the IDE GYM control strip.
|
|
55
|
+
*
|
|
56
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
57
|
+
*
|
|
58
|
+
* Zero new physics (canonical-bench SU1) — this is a pure composition of two
|
|
59
|
+
* already-goldened engine behaviors: M5 (velocity is conserved absent
|
|
60
|
+
* collision/attraction — a free leaf drifts in a dead-straight line) and S1
|
|
61
|
+
* (x, y, and z axes run the IDENTICAL update — 2D and 3D are the same physics
|
|
62
|
+
* with more axes). Every emitted leaf is placed on a ring in the x,y plane via
|
|
63
|
+
* `makeLeafFrame` + `injectFrame`, then stamped via `setFrameProperty` with a
|
|
64
|
+
* constant forward `vz` and a radial (vx,vy) scaled by the live `spread` knob
|
|
65
|
+
* — the SAME `leafV`/`ringLeaves` construction verified as canonical-bench
|
|
66
|
+
* `S2`/`S3` (re-applying vx/vy/vz AFTER inject is the documented safe idiom,
|
|
67
|
+
* `feedback_pkgnode_verification_script_gotchas.md` — omitting it silently
|
|
68
|
+
* no-ops the velocity write on some engine builds).
|
|
69
|
+
*
|
|
70
|
+
* `spread=0` reproduces S2 exactly: radial extent stays bounded near the
|
|
71
|
+
* ring's own starting radius while z travels the full 1px/tick speed limit
|
|
72
|
+
* (bench M1) — `atan(radial/z-travel)` SHRINKS toward 0 as z grows, i.e. a
|
|
73
|
+
* COLLIMATED CYLINDER (laser), not a cone. `spread>0` reproduces S3: radial
|
|
74
|
+
* extent now grows in lockstep with z-travel, and the half-angle converges to
|
|
75
|
+
* `atan(spread/vz)` — a genuine diverging CONE (flashlight). No engine change
|
|
76
|
+
* is implicated anywhere in this file — it is purely which velocity a
|
|
77
|
+
* scene-builder hands each leaf (see
|
|
78
|
+
* `.claude/agent-memory/frqtl-experiment-coder/reference_concentric_ring_cone_vs_cylinder_law.md`).
|
|
79
|
+
*
|
|
80
|
+
* Sustained emission is a HOST TICK-FN pushed to
|
|
81
|
+
* `universe.experimentTickFunctions` — the SAME framework hook
|
|
82
|
+
* Blackbody's thermostat already uses
|
|
83
|
+
* (architect guardrail R1: no new engine hook, no native emit-spec). Because
|
|
84
|
+
* each successive ring burst is younger (has traveled less z), a running scene
|
|
85
|
+
* shows a series of receding ring-shells at increasing z-depth — a solid-
|
|
86
|
+
* looking collimated tube (spread=0) or an opening flashlight cone (spread>0).
|
|
87
|
+
*
|
|
88
|
+
* Fast-path regime (task-specified): no `withInterQuantumCollisions`, no
|
|
89
|
+
* `brownianMotion`, no top-down auto-spawn ladder — `activeAxes`
|
|
90
|
+
* `{x:true,y:true,z:true}` only. Leaves drift forever once emitted (M5); there
|
|
91
|
+
* is no sink here, so `quantum_limit`-style population bounding is left to the
|
|
92
|
+
* page/GYM run duration, matching `particle-walk-3d`'s own precedent (no sink,
|
|
93
|
+
* `wrap:false`-equivalent — leaves simply exit `universe_rect` and stop
|
|
94
|
+
* appearing, per `reference_population_bounded_by_universe_edge_not_only_sink.md`).
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
98
|
+
|
|
99
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
100
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
101
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
105
|
+
* Exported as `setupConeEmitter3D.OPTIONS` so a composing caller consumes it instead of
|
|
106
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
107
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
108
|
+
*/
|
|
109
|
+
var OPTIONS = [
|
|
110
|
+
'emitEveryTicks', 'fundamentalWaveLength', 'leavesPerRing', 'ringCount',
|
|
111
|
+
'ringRadius', 'spread', 'vz',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
function setupConeEmitter3D(universe, options) {
|
|
115
|
+
options = _assertSetupOptions('setupConeEmitter3D', options, OPTIONS);
|
|
116
|
+
|
|
117
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
118
|
+
var canvas_rect = universe.universe_rect;
|
|
119
|
+
_requireWorldRect('setupConeEmitter3D universe.universe_rect', canvas_rect);
|
|
120
|
+
|
|
121
|
+
var desiredWaveLength = options.fundamentalWaveLength !== undefined
|
|
122
|
+
? options.fundamentalWaveLength : 64;
|
|
123
|
+
var waveLengthMultiplier = Math.max(1, Math.floor(desiredWaveLength / 4));
|
|
124
|
+
|
|
125
|
+
// ── Universe init: 3D, fast-path (no collisions/brownian/top-down frame-gen) ──
|
|
126
|
+
universe.init(0, {
|
|
127
|
+
wavelengthScale: waveLengthMultiplier,
|
|
128
|
+
brownianMotion: false,
|
|
129
|
+
activeAxes: { x: true, y: true, z: true }
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
var cx0 = Math.floor(canvas_rect.w / 2);
|
|
133
|
+
var cy0 = Math.floor(canvas_rect.h / 2);
|
|
134
|
+
var cz0 = 0;
|
|
135
|
+
|
|
136
|
+
// ── Live-mutable emission state (closed over by the tick-fn AND the
|
|
137
|
+
// returned control handle — the canonical live-knob pattern:
|
|
138
|
+
// the blackbody scene's knob calls OvenBox.setTemperature, which sets the
|
|
139
|
+
// target the thermostat tick-fn reads on its NEXT evaluation, never the engine
|
|
140
|
+
// directly). Values here are read fresh on every emitted ring, so a
|
|
141
|
+
// slider drag changes the NEXT burst's leaves — already-emitted
|
|
142
|
+
// leaves keep drifting on whatever velocity they were stamped with
|
|
143
|
+
// (M5: velocity is conserved — there is no retroactive rewrite). ──
|
|
144
|
+
var state = {
|
|
145
|
+
spread: options.spread !== undefined ? options.spread : 0.6,
|
|
146
|
+
vz: options.vz !== undefined ? options.vz : 1,
|
|
147
|
+
ringCount: options.ringCount !== undefined ? options.ringCount : 4,
|
|
148
|
+
ringRadius: options.ringRadius !== undefined ? options.ringRadius : 40,
|
|
149
|
+
leavesPerRing: options.leavesPerRing !== undefined ? options.leavesPerRing : 20,
|
|
150
|
+
active: true
|
|
151
|
+
};
|
|
152
|
+
var emitEveryTicks = options.emitEveryTicks !== undefined ? options.emitEveryTicks : 6;
|
|
153
|
+
|
|
154
|
+
// Mirrors bench.cjs's leafV()/ringLeaves() exactly (canonical-bench S2/S3) —
|
|
155
|
+
// re-applying vx/vy/vz AFTER injectFrame is the documented safe idiom.
|
|
156
|
+
function emitOneRing(radiusPx) {
|
|
157
|
+
var N = state.leavesPerRing;
|
|
158
|
+
for (var i = 0; i < N; i++) {
|
|
159
|
+
// HALF-STEP ANGULAR OFFSET (i + 0.5), not i: keep EVERY leaf off the
|
|
160
|
+
// exact cardinal axes. At the bare theta=2πi/N sampling, i=0 lands at
|
|
161
|
+
// theta=0 (vx=spread, vy=EXACTLY 0) and i=N/4,N/2,3N/4 land with a
|
|
162
|
+
// ~1e-16 float residual on their off-axis component — and the engine's
|
|
163
|
+
// move_frame classifies the propagation axis by a BIT-EXACT vx!=0.0 /
|
|
164
|
+
// vy!=0.0 test, so those degenerate leaves get NO transverse wave on the
|
|
165
|
+
// (bit-)zero axis and stay plane-confined, projecting as sharp bright
|
|
166
|
+
// axis-aligned LINES (CEO-flagged, 2026-07-16). The +0.5 offset gives
|
|
167
|
+
// every leaf a meaningfully-nonzero (vx,vy) pair so it fans into the cone
|
|
168
|
+
// VOLUME instead of a plane. |v_radial|=spread is unchanged by a pure
|
|
169
|
+
// angular rotation, so the S3 half-angle law atan(spread/vz) is preserved.
|
|
170
|
+
// Pure SUGAR fix (canonical-bench SU1) — zero engine change; the raw-engine
|
|
171
|
+
// S2/S3 goldens in bench.cjs keep their own 2πi/N sampling untouched.
|
|
172
|
+
var theta = (2 * Math.PI * (i + 0.5)) / N;
|
|
173
|
+
var lx = cx0 + radiusPx * Math.cos(theta);
|
|
174
|
+
var ly = cy0 + radiusPx * Math.sin(theta);
|
|
175
|
+
var vx = state.spread * Math.cos(theta);
|
|
176
|
+
var vy = state.spread * Math.sin(theta);
|
|
177
|
+
var h = universe.makeLeafFrame(lx, ly, 0, null, vx, vy, false, 0, null);
|
|
178
|
+
universe.injectFrame(h);
|
|
179
|
+
var q = universe.getFrameQuantumId(h);
|
|
180
|
+
universe.setFrameProperty(q, 'z', cz0);
|
|
181
|
+
universe.setFrameProperty(q, 'vz', state.vz);
|
|
182
|
+
universe.setFrameProperty(q, 'vx', vx);
|
|
183
|
+
universe.setFrameProperty(q, 'vy', vy);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function emitBurst() {
|
|
188
|
+
for (var r = 1; r <= state.ringCount; r++) {
|
|
189
|
+
emitOneRing(state.ringRadius * r / state.ringCount);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var _tick = 0;
|
|
194
|
+
var _tickFn = function () {
|
|
195
|
+
if (state.active && (_tick % emitEveryTicks === 0)) { emitBurst(); }
|
|
196
|
+
_tick++;
|
|
197
|
+
};
|
|
198
|
+
universe.experimentTickFunctions.push(_tickFn);
|
|
199
|
+
|
|
200
|
+
// ── Live sugar control handle (the canonical knob pattern) ──────────────
|
|
201
|
+
var control = {
|
|
202
|
+
setSpread: function (v) { state.spread = v; },
|
|
203
|
+
getSpread: function () { return state.spread; },
|
|
204
|
+
setVz: function (v) { state.vz = v; },
|
|
205
|
+
getVz: function () { return state.vz; },
|
|
206
|
+
setRingCount: function (v) { state.ringCount = Math.max(1, Math.round(v)); },
|
|
207
|
+
getRingCount: function () { return state.ringCount; },
|
|
208
|
+
setRingRadius: function (v) { state.ringRadius = v; },
|
|
209
|
+
getRingRadius: function () { return state.ringRadius; },
|
|
210
|
+
setActive: function (v) { state.active = !!v; },
|
|
211
|
+
dispose: function () {
|
|
212
|
+
var i = universe.experimentTickFunctions.indexOf(_tickFn);
|
|
213
|
+
if (i !== -1) { universe.experimentTickFunctions.splice(i, 1); }
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Standardized live knobs (runner.experimentControls-compatible — the SAME
|
|
218
|
+
// shape SphereEmitter.controls / g3-lo-collide's CONTROLS array use).
|
|
219
|
+
control.controls = [
|
|
220
|
+
{
|
|
221
|
+
id: 'spread', label: 'spread', min: 0, max: 2, step: 0.05, value: state.spread,
|
|
222
|
+
fmt: function (v) { return v.toFixed(2); },
|
|
223
|
+
onChange: function (v) { control.setSpread(v); }
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'vz', label: 'vz (forward)', min: 0.1, max: 4, step: 0.1, value: state.vz,
|
|
227
|
+
fmt: function (v) { return v.toFixed(1); },
|
|
228
|
+
onChange: function (v) { control.setVz(v); }
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'ringRadius', label: 'ring radius', min: 10, max: 120, step: 5, value: state.ringRadius,
|
|
232
|
+
fmt: function (v) { return String(v); },
|
|
233
|
+
onChange: function (v) { control.setRingRadius(v); }
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'ringCount', label: 'rings', min: 1, max: 10, step: 1, value: state.ringCount,
|
|
237
|
+
fmt: function (v) { return String(v); },
|
|
238
|
+
onChange: function (v) { control.setRingCount(v); }
|
|
239
|
+
}
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
coneEmitterControl: control,
|
|
244
|
+
largeObjects: []
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
setupConeEmitter3D.OPTIONS = OPTIONS;
|
|
249
|
+
|
|
250
|
+
export { setupConeEmitter3D };
|
|
251
|
+
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupDoubleSlit — Engine-agnostic double-slit experiment factory.
|
|
5
|
+
* Creates the complete DSE setup: slit screen, light source, detector wall.
|
|
6
|
+
*
|
|
7
|
+
* Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
11
|
+
import { SlitScreen } from '../../parts/SlitScreen.js';
|
|
12
|
+
import { SlitLightSource } from '../../parts/SlitLightSource.js';
|
|
13
|
+
import { DetectorWall } from '../../parts/DetectorWall.js';
|
|
14
|
+
import { LargeObject } from '../../parts/LargeObject.js';
|
|
15
|
+
import { CollisionTypes } from '../../parts/CollisionTypes.js';
|
|
16
|
+
|
|
17
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
18
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
19
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
20
|
+
|
|
21
|
+
// ── Imports ─────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
// ── Setup Factory ───────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a double-slit experiment.
|
|
27
|
+
*
|
|
28
|
+
* Assembles the complete DSE: a radial light source ({@link SlitLightSource}),
|
|
29
|
+
* a two-slit barrier ({@link SlitScreen}), and a counting detector wall
|
|
30
|
+
* ({@link DetectorWall}). There is no wave equation — each leaf Frame makes a
|
|
31
|
+
* local 1px/tick choice. The interference pattern at the detector wall is
|
|
32
|
+
* emergent from where the two particle streams overlap after passing through
|
|
33
|
+
* the slits.
|
|
34
|
+
*
|
|
35
|
+
* ## Physics you can drive
|
|
36
|
+
*
|
|
37
|
+
* **`options.wavelength`** is the single geometry master knob (default 64).
|
|
38
|
+
* It simultaneously scales every physical dimension of the experiment:
|
|
39
|
+
*
|
|
40
|
+
* | Derived quantity | Formula |
|
|
41
|
+
* |---|---|
|
|
42
|
+
* | Slit opening height | `floor(wavelength * 0.8)` px |
|
|
43
|
+
* | Inter-slit gap height | `floor(wavelength * 0.8)` px |
|
|
44
|
+
* | Slit screen left edge | `wavelength * 1.5` px from canvas left |
|
|
45
|
+
* | Detector wall x | `screenLeft + screenWidth + 3 * wavelength` px |
|
|
46
|
+
* | Universe `wavelengthScale` (derived) | `floor(wavelength / 16)` |
|
|
47
|
+
*
|
|
48
|
+
* Raising `wavelength` enlarges slits, widens the gap, moves the detector
|
|
49
|
+
* further right, and increases the universe oscillation period. The canonical
|
|
50
|
+
* operating point for a 600×600 canvas is `wavelength = 64`: at this value
|
|
51
|
+
* the complete geometry (source → screen → detector) fits within the canvas,
|
|
52
|
+
* both slits receive balanced illumination (slit1/slit2 hit ratio ≈ 1.0), and
|
|
53
|
+
* a clear multi-band interference pattern emerges on the detector histogram.
|
|
54
|
+
*
|
|
55
|
+
* Below 64: geometry shrinks; the slits become too small for balanced
|
|
56
|
+
* illumination and the pattern is suppressed. Above 64 on a 600×600 canvas:
|
|
57
|
+
* geometry exceeds the canvas and the detector wall falls off-screen.
|
|
58
|
+
*
|
|
59
|
+
* Observed sweep (engine = js, seed 42, tick 400):
|
|
60
|
+
*
|
|
61
|
+
* | `wavelength` | `wavelengthScale` | Active quanta | Slit1/Slit2 ratio | Field |
|
|
62
|
+
* |---|---|---|---|---|
|
|
63
|
+
* | 16 | 1 | ~556 | 0.04 | pattern compressed into left sixth of canvas |
|
|
64
|
+
* | 32 | 2 | ~2032 | 0.44 | moderate spread, unbalanced slit hits |
|
|
65
|
+
* | **64** (default) | **4** | ~3601 | **0.98** | full canvas, clear interference bands |
|
|
66
|
+
* | 128 | 8 | ~6029 | — | geometry exceeds canvas; detector not reached at tick 400 |
|
|
67
|
+
*
|
|
68
|
+
* Figure + screenshots: `docs/internal/physics-lab/double-slit-wavelength.md`.
|
|
69
|
+
*
|
|
70
|
+
* **Disambiguate from wave-interference `wavelengthScale`:** the DSE
|
|
71
|
+
* `wavelength` scales PHYSICAL GEOMETRY (moves walls). The wave-interference
|
|
72
|
+
* `wavelengthScale` in `setupWaveInterference` only scales the per-Frame
|
|
73
|
+
* oscillation period without moving anything. In the DSE, `wavelengthScale`
|
|
74
|
+
* is a derived quantity — it is set internally as `floor(wavelength / 16)` and
|
|
75
|
+
* is not a free input unless you pass `options.wavelengthScale` explicitly.
|
|
76
|
+
*
|
|
77
|
+
* **`options.detectAtSlits`** controls the measurement mode at the slit openings:
|
|
78
|
+
* - `0` — no detection (slits are transparent pass-through zones)
|
|
79
|
+
* - `1` (default) — detect + scatter (`RANDOM_XY` collision type) — the act of
|
|
80
|
+
* detection disturbs the particle's trajectory (canonical "which-slit" measurement)
|
|
81
|
+
* - `2` — detect + color only (no trajectory disturbance); used for visualization
|
|
82
|
+
*
|
|
83
|
+
* @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd.
|
|
84
|
+
* @param {Object} [options] - Configuration overrides.
|
|
85
|
+
* @param {number} [options.wavelength=64] - Geometry master knob: scales slit size,
|
|
86
|
+
* inter-slit gap, wall positions, detector distance, and derived
|
|
87
|
+
* `wavelengthScale`. See "Physics you can drive" above.
|
|
88
|
+
* @param {number} [options.fundamentalDimension=2] - Fundamental (leaf) dimension layer.
|
|
89
|
+
* @param {number} [options.detectAtSlits=1] - Slit detection mode: 0=none,
|
|
90
|
+
* 1=detect+scatter (RANDOM_XY), 2=detect+color only. Mode 1 is the canonical
|
|
91
|
+
* "which-slit" measurement.
|
|
92
|
+
* @param {boolean} [options.absorbAtSlitWall=true] - If true, wall segments absorb
|
|
93
|
+
* particles on contact (no reflection). If false, walls block (reflect) particles.
|
|
94
|
+
* @param {number} [options.wavelengthScale] - Override the derived universe
|
|
95
|
+
* `wavelengthScale` (normally `floor(wavelength/16)`). Use only when you want
|
|
96
|
+
* to decouple the oscillation scale from the geometry scale.
|
|
97
|
+
* @param {number} [options.angleIncrement=10] - Degrees between emission angles
|
|
98
|
+
* from the light source. Smaller = more rays = smoother illumination cone;
|
|
99
|
+
* larger = coarser, more visible discrete rays.
|
|
100
|
+
* @param {number} [options.depth] - WS-DSE-3D: total z-extent for a 3D run.
|
|
101
|
+
* OMITTED (default) → today's 2D behavior, byte-identical — no `activeAxes`
|
|
102
|
+
* key touched, every wall/screen/detector rect stays the legacy 4-element form,
|
|
103
|
+
* the light source's inner PointSource gets no z-draw at all. When provided:
|
|
104
|
+
* `universe.init()` gains `activeAxes:{x:true,y:true,z:true}` (mirrors
|
|
105
|
+
* `setupWaveInterference`'s `options.depth`, WS-DSE-3D Bite B0); `SlitScreen`
|
|
106
|
+
* becomes a FLOOR-TO-CEILING 3D wall (CEO geometry amendment, 2026-07-12):
|
|
107
|
+
* `wall1`/`wall2`/`wall3` span the full z-depth `[-depth/2,+depth/2]`, and
|
|
108
|
+
* each slit becomes a CUBIC HOLE (bounded in BOTH y and z, see
|
|
109
|
+
* `options.holeDepth`) tiled from a solid near/far z-side pair around the
|
|
110
|
+
* aperture — NOT a full-depth-in-z slot (supersedes the earlier Bite B1
|
|
111
|
+
* construction). The start-wall and detector wall get the SAME full-z-depth
|
|
112
|
+
* treatment (else z-spread quanta fly around/past them); `SlitLightSource`
|
|
113
|
+
* threads `zMin=-depth/2, zDepth=depth` into its inner PointSource — the
|
|
114
|
+
* IDENTICAL shared z-spread mechanism Bite B0 built for
|
|
115
|
+
* `setupWaveInterference` (`emitterProgram.js`'s opt-in `em.zMin`/`em.zDepth`),
|
|
116
|
+
* reused not forked. PHYSICS EXPECTATION (restated, CEO amendment): the
|
|
117
|
+
* detector pattern is genuinely 2D — y-interference fringes × a z
|
|
118
|
+
* aperture-diffraction envelope (concentrated near the hole's z-band, NOT
|
|
119
|
+
* uniform across all z-bands — the old "every z-band identical" self-check
|
|
120
|
+
* is superseded).
|
|
121
|
+
* @param {number} [options.holeDepth] - 3D only: z-extent of each cubic hole
|
|
122
|
+
* (centered on z=0). Default: the EFFECTIVE slit height (square-ish aperture
|
|
123
|
+
* at the default wavelength — see `SlitScreen`'s docstring for the exact
|
|
124
|
+
* value). Ignored when `options.depth` is omitted.
|
|
125
|
+
* @param {'current'|'noreset'|'symmetric'|'combined'|'bounce'|'ejectY'|'ejectYReflect'} [options.slitScatter='current']
|
|
126
|
+
* EXPERIMENT LOOP (2026-07-12, CEO physics briefing): selects the slit
|
|
127
|
+
* liner's edge-diffraction scatter behavior. `'current'` (default, OMIT to
|
|
128
|
+
* keep it) is today's exact compiled `RANDOM_XY` — byte-identical, the
|
|
129
|
+
* CEO-ratified 2D golden (seed42/wl64/detect1/t400 -> 103/63/40) depends on
|
|
130
|
+
* it. See `SlitScreen`'s `_buildLinerVelocityOps` docstring for
|
|
131
|
+
* `noreset`/`symmetric`/`combined`/`bounce`, and `_buildEjectYOps`'s
|
|
132
|
+
* docstring for `ejectY`/`ejectYReflect` (MATRIX EXTENSION, 2026-07-12 — the
|
|
133
|
+
* standard block+eject idiom other LOs use, Y-into-the-cavity). Works
|
|
134
|
+
* identically on the 2D and 3D (`options.depth`) construction paths.
|
|
135
|
+
* @param {number} [options.gapHeight] - DSE LAB (2026-07-13): inter-slit gap
|
|
136
|
+
* override — the "slit gap"/"slit-tunnel width" (the interference formula's
|
|
137
|
+
* `d`, read back via `interferenceParams.d`). OMITTED (default) -> SlitScreen's
|
|
138
|
+
* own default `floor(wavelength*0.8)`, byte-identical to every existing caller.
|
|
139
|
+
* REINIT-class knob: a live UI change must restart the experiment with a new
|
|
140
|
+
* value (no live LO rect-surgery path in v1).
|
|
141
|
+
* @returns {Object} `{ slitScreen, slitLightSource, detectorWall, startWall,
|
|
142
|
+
* interferenceParams: { d, L, wavelength, depth? } }` where `d` = inter-slit gap
|
|
143
|
+
* height, `L` = distance from slit screen face to detector wall, and `depth`
|
|
144
|
+
* is present ONLY when `options.depth` was set (3D run).
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* // Two-cell notebook pattern (declarative-canvas model):
|
|
148
|
+
* // module cell:
|
|
149
|
+
* export function setupDS(frqtl, universe, opts) {
|
|
150
|
+
* return setupDoubleSlit(universe, {
|
|
151
|
+
* wavelength: opts.wavelength ?? 64,
|
|
152
|
+
* detectAtSlits: opts.detectAtSlits ?? 1,
|
|
153
|
+
* canvasRect: universe.universe_rect
|
|
154
|
+
* });
|
|
155
|
+
* }
|
|
156
|
+
* // canvas cell: setup="<module>#setupDS" opts='{"wavelength":64}' stage="600x600" seed="42"
|
|
157
|
+
*/
|
|
158
|
+
/**
|
|
159
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
160
|
+
* Exported as `setupDoubleSlit.OPTIONS` so a composing caller consumes it instead of
|
|
161
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
162
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
163
|
+
*/
|
|
164
|
+
var OPTIONS = [
|
|
165
|
+
'absorbAtSlitWall', 'angleIncrement', 'depth',
|
|
166
|
+
'detectAtSlits', 'fundamentalDimension', 'gapHeight', 'holeDepth',
|
|
167
|
+
'slitScatter', 'wavelength', 'wavelengthScale',
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
function setupDoubleSlit(universe, options) {
|
|
171
|
+
options = _assertSetupOptions('setupDoubleSlit', options, OPTIONS);
|
|
172
|
+
|
|
173
|
+
var wavelength = options.wavelength !== undefined ? options.wavelength : 64;
|
|
174
|
+
var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 2;
|
|
175
|
+
var detectAtSlits = options.detectAtSlits !== undefined ? options.detectAtSlits : 1;
|
|
176
|
+
var absorbAtSlitWall = options.absorbAtSlitWall !== undefined ? options.absorbAtSlitWall : true;
|
|
177
|
+
var canvasRect = universe.universe_rect;
|
|
178
|
+
// The world height, through the ONE world-rect normalizer (throws on a missing or
|
|
179
|
+
// non-positive dimension — a zero-high screen would count nothing, silently).
|
|
180
|
+
var canvasH = _requireWorldRect('setupDoubleSlit universe.universe_rect', canvasRect).h;
|
|
181
|
+
var angleIncrement = options.angleIncrement !== undefined ? options.angleIncrement : 10;
|
|
182
|
+
// WS-DSE-3D: opt-in 3D depth. undefined/null → 2D, byte-identical.
|
|
183
|
+
var depth = (options.depth !== undefined && options.depth !== null) ? options.depth : null;
|
|
184
|
+
// WS-DSE-3D CEO geometry amendment: cubic-hole z-extent. undefined -> SlitScreen
|
|
185
|
+
// applies its own default (the effective slit_height). 2D-inert.
|
|
186
|
+
var holeDepth = (options.holeDepth !== undefined && options.holeDepth !== null) ? options.holeDepth : undefined;
|
|
187
|
+
// EXPERIMENT LOOP (2026-07-12): liner edge-diffraction scatter variant.
|
|
188
|
+
// 'current' (default/omitted) -> today's exact RANDOM_XY, byte-identical —
|
|
189
|
+
// the CEO-ratified 2D golden depends on this. Works identically in 2D and 3D.
|
|
190
|
+
var slitScatter = options.slitScatter !== undefined ? options.slitScatter : 'current';
|
|
191
|
+
// DSE LAB (2026-07-13): inter-slit gap override ("slit gap" / "slit-tunnel
|
|
192
|
+
// width" — the interference formula's `d`). undefined (default) -> SlitScreen's
|
|
193
|
+
// own default (floor(wavelength*0.8)), byte-identical to every existing caller.
|
|
194
|
+
// REINIT-class: a live change restarts the experiment with a new value; there
|
|
195
|
+
// is no live LO rect-surgery path (v1).
|
|
196
|
+
var gapHeight = (options.gapHeight !== undefined && options.gapHeight !== null) ? options.gapHeight : undefined;
|
|
197
|
+
|
|
198
|
+
// Wavelength constant: default to floor(wavelength / 16) matching original
|
|
199
|
+
var wavelengthScale = options.wavelengthScale !== undefined
|
|
200
|
+
? options.wavelengthScale
|
|
201
|
+
: Math.floor(wavelength / 16);
|
|
202
|
+
|
|
203
|
+
// Set wavelength constant on universe
|
|
204
|
+
universe.wavelengthScale = wavelengthScale;
|
|
205
|
+
|
|
206
|
+
// Init universe: ground-up mode, brownianMotion:false (the wave-walk). activeAxes is ONLY
|
|
207
|
+
// attached when a 3D depth was requested — omission keeps the exact init()
|
|
208
|
+
// call every existing 2D caller (incl. the settlement CLI/golden) gets.
|
|
209
|
+
var initOpts = {
|
|
210
|
+
wavelengthScale: wavelengthScale,
|
|
211
|
+
brownianMotion: false,
|
|
212
|
+
};
|
|
213
|
+
if (depth !== null) {
|
|
214
|
+
initOpts.activeAxes = { x: true, y: true, z: true };
|
|
215
|
+
}
|
|
216
|
+
universe.init(fundamentalDimension, initOpts);
|
|
217
|
+
|
|
218
|
+
// ── Slit Screen ──────────────────────────────────────────────────
|
|
219
|
+
var slitScreenLeft = wavelength * 1.5;
|
|
220
|
+
var slitScreen = new SlitScreen(
|
|
221
|
+
universe,
|
|
222
|
+
slitScreenLeft,
|
|
223
|
+
wavelength,
|
|
224
|
+
canvasH,
|
|
225
|
+
wavelength,
|
|
226
|
+
absorbAtSlitWall,
|
|
227
|
+
detectAtSlits,
|
|
228
|
+
undefined, // slitHeight override (none)
|
|
229
|
+
gapHeight, // DSE LAB: inter-slit gap override (undefined -> SlitScreen default)
|
|
230
|
+
depth, // WS-DSE-3D: null (default) -> legacy 4-elem rects
|
|
231
|
+
holeDepth, // WS-DSE-3D CEO geometry amendment: cubic-hole z-extent (2D-inert)
|
|
232
|
+
slitScatter // EXPERIMENT LOOP: 'current' (default) -> byte-identical liner behavior
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// ── Start Wall (absorb wall at left edge) ───────────────────────
|
|
236
|
+
// WS-DSE-3D Bite B1: full-z-depth 6-elem rect when depth is set (Tube.js
|
|
237
|
+
// precedent) — without it, z-spread quanta fly around a paper-thin (z=0,d=w)
|
|
238
|
+
// start wall. depth===null (default) -> legacy 4-elem rect, byte-identical.
|
|
239
|
+
var startWallRect = (depth !== null)
|
|
240
|
+
? [0, 0, -depth / 2, 10, canvasH, depth]
|
|
241
|
+
: [0, 0, 10, canvasH];
|
|
242
|
+
var startWall = new LargeObject(
|
|
243
|
+
universe,
|
|
244
|
+
startWallRect,
|
|
245
|
+
CollisionTypes.ABSORB,
|
|
246
|
+
// RECOLOR removed: deleted in the engine-no-color cut (I1). Stale dead reference.
|
|
247
|
+
// The ABSORB collision type already absorbs frames; no action needed here.
|
|
248
|
+
[],
|
|
249
|
+
'start_wall',
|
|
250
|
+
'closest',
|
|
251
|
+
null,
|
|
252
|
+
1,
|
|
253
|
+
[120, 120, 120]
|
|
254
|
+
);
|
|
255
|
+
// WS-DSE-3D fix-loop item 1 (render): the Tube.js precedent — a 6-element
|
|
256
|
+
// rect gives the ENGINE a real z-slab, but _buildLoMesh (three-provider.js)
|
|
257
|
+
// only builds a 3D BoxGeometry when the LO ALSO carries the render-only
|
|
258
|
+
// `shape='box'` + `depth` hint (see SlitScreen.js for the identical fix).
|
|
259
|
+
if (startWall.rect.d !== undefined) {
|
|
260
|
+
startWall.shape = 'box';
|
|
261
|
+
startWall.depth = startWall.rect.d;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── Light Source ─────────────────────────────────────────────────
|
|
265
|
+
var slitLightSource = new SlitLightSource(
|
|
266
|
+
universe,
|
|
267
|
+
slitScreen,
|
|
268
|
+
angleIncrement,
|
|
269
|
+
undefined, // quantum_limit (default)
|
|
270
|
+
undefined, // delay (default)
|
|
271
|
+
{ depth: depth } // WS-DSE-3D Bite B1: null (default) -> no z-draw at all
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// ── Detector Wall ────────────────────────────────────────────────
|
|
275
|
+
var detectorWallX = slitScreen.wall1.rect.left + slitScreen.wall1.rect.w + 3 * wavelength;
|
|
276
|
+
var detectorWall = new DetectorWall(
|
|
277
|
+
universe,
|
|
278
|
+
canvasRect,
|
|
279
|
+
detectorWallX,
|
|
280
|
+
undefined, undefined, undefined, undefined, undefined, undefined,
|
|
281
|
+
{ depth: depth } // WS-DSE-3D Bite B1: null (default) -> legacy 4-elem rect
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// ── Tick function ────────────────────────────────────────────────
|
|
285
|
+
var tickFunction = slitLightSource.getTickFunction();
|
|
286
|
+
universe.experimentTickFunctions.push(tickFunction);
|
|
287
|
+
|
|
288
|
+
// ── Interference parameters (for chart/analysis) ─────────────────
|
|
289
|
+
var d_distance_between_slits = slitScreen.slit_wall_gap_height;
|
|
290
|
+
var L_distance_slits_to_screen = detectorWall.x - (slitScreen.wall1.rect.left + slitScreen.wall1.rect.w);
|
|
291
|
+
|
|
292
|
+
// ── Collect all large objects for rendering ───────────────────────
|
|
293
|
+
var largeObjects = [];
|
|
294
|
+
// Slit screen walls + slit liners
|
|
295
|
+
for (var wi = 0; wi < slitScreen.walls.length; wi++) {
|
|
296
|
+
largeObjects.push(slitScreen.walls[wi]);
|
|
297
|
+
}
|
|
298
|
+
largeObjects.push(startWall);
|
|
299
|
+
largeObjects.push(detectorWall.detector_wall);
|
|
300
|
+
|
|
301
|
+
// WS-DSE-3D Bite B1 (ratified §2.1): depth key present ONLY on a 3D run —
|
|
302
|
+
// a 2D interferenceParams object has the EXACT SAME key set as pre-B1.
|
|
303
|
+
var interferenceParams = {
|
|
304
|
+
d: d_distance_between_slits,
|
|
305
|
+
L: L_distance_slits_to_screen,
|
|
306
|
+
wavelength: wavelength
|
|
307
|
+
};
|
|
308
|
+
if (depth !== null) {
|
|
309
|
+
interferenceParams.depth = depth;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
slitScreen: slitScreen,
|
|
314
|
+
slitLightSource: slitLightSource,
|
|
315
|
+
detectorWall: detectorWall,
|
|
316
|
+
startWall: startWall,
|
|
317
|
+
interferenceParams: interferenceParams,
|
|
318
|
+
largeObjects: largeObjects
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
setupDoubleSlit.OPTIONS = OPTIONS;
|
|
325
|
+
|
|
326
|
+
export { setupDoubleSlit };
|
|
327
|
+
|