@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2
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/LICENSE.md +664 -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 +2499 -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 +84 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +99 -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 +258 -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 +2470 -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 +2920 -0
- package/dist/frqtl/objects/ObjectFactory.js +296 -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 +710 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -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 +411 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +351 -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 +309 -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 +820 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +468 -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 +250 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -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 +277 -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 +1258 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
- package/dist/frqtl/objects/v1-replacements.js +29 -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 +34 -0
- package/dist/frqtl/parts/CollisionTypes.js +30 -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 +489 -0
- package/dist/frqtl/parts/LargeObject.js +405 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +717 -0
- package/dist/frqtl/parts/OvenBox.js +611 -0
- package/dist/frqtl/parts/PointSource.js +762 -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 +243 -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 +211 -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 +690 -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 +325 -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 +671 -0
- package/dist/shim/notebook/knob.js +150 -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 +507 -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 +213 -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 +804 -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 +63 -4
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SlitScreen — Engine-agnostic double-slit barrier.
|
|
5
|
+
*
|
|
6
|
+
* 2D (depth omitted): a wall with two slits made of LargeObjects:
|
|
7
|
+
* - 3 wall segments (wall1 above slit1, wall2 between slits, wall3 below slit2)
|
|
8
|
+
* - 2 slit segments (slit1, slit2) with optional detection
|
|
9
|
+
* - 4 slit liners (top/bottom of each slit) for edge diffraction
|
|
10
|
+
*
|
|
11
|
+
* 3D (depth set — the geometry amendment, "floor to ceiling
|
|
12
|
+
* with cubic holes"): the screen spans the FULL universe cross-section — the SAME
|
|
13
|
+
* floor-to-ceiling y-extent as 2D (unchanged: wall1 starts at y=0, wall3 ends at
|
|
14
|
+
* canvasHeight) PLUS the full z-depth (the fix-loop item-1 render/collision fix) —
|
|
15
|
+
* but the two apertures are now CUBIC HOLES (bounded in BOTH y and z), not full-
|
|
16
|
+
* depth-in-z SLOTS. Built by TILING the wall around each hole: a wall1/wall2/wall3
|
|
17
|
+
* slab spans the full z-depth (no hole in those y-bands), and each hole's own
|
|
18
|
+
* y-band is split into THREE z-tiles — a solid "_near" side (z < holeZmin), the
|
|
19
|
+
* hole itself (z ∈ [holeZmin, holeZmax), still named `slit1`/`slit2` — detection
|
|
20
|
+
* identity preserved), and a solid "_far" side (z > holeZmax). AABB-only — this
|
|
21
|
+
* is exactly why round holes were rejected (§4.1) and cubic holes were ratified:
|
|
22
|
+
* a box aperture is just MORE 6-element rects, zero new collision machinery.
|
|
23
|
+
*
|
|
24
|
+
* Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { LargeObject } from './LargeObject.js';
|
|
28
|
+
import { CollisionTypes } from './CollisionTypes.js';
|
|
29
|
+
import { CollisionActions } from './CollisionActions.js';
|
|
30
|
+
import * as SlitLinerScatter from '../objects/objects/slitLinerScatter.js';
|
|
31
|
+
import { Wall } from './Wall.js';
|
|
32
|
+
import { registerKind } from '../objects/kinds.js';
|
|
33
|
+
import { ObjectSchema } from '../objects/schema/index.js';
|
|
34
|
+
|
|
35
|
+
// ── Imports (Node.js) or globals (browser) ──────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* DSE LAB / EXPERIMENT LOOP / MATRIX EXTENSION liner scatter — delegates
|
|
39
|
+
* ENTIRELY to `slitLinerScatter.js`, the ONE authority both this constructor
|
|
40
|
+
* (legacy `linerScatter` string, construction-time) AND the `dse-lab` page
|
|
41
|
+
* (LIVE, explicit `{behavior, resetLocalTime}`) push through. See that
|
|
42
|
+
* module's docstring for the full behavior/reset decomposition and the
|
|
43
|
+
* ground-truth physics finding behind the variants.
|
|
44
|
+
*/
|
|
45
|
+
function _applyLinerScatter(linerLO, variant, universe, intoCavityDir) {
|
|
46
|
+
SlitLinerScatter.applyLinerScatter(linerLO, universe, intoCavityDir, { slitScatter: variant });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── SlitScreen ────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Double-slit barrier with edge diffraction liners.
|
|
53
|
+
*
|
|
54
|
+
* 2D: creates five LargeObjects (three wall segments + two slit openings) plus
|
|
55
|
+
* four slit edge liners (9 total, `this.walls.length===9`). 3D (`depth` set):
|
|
56
|
+
* each slit opening becomes a CUBIC HOLE tiled from THREE z-pieces
|
|
57
|
+
* (near-side/hole/far-side), so the wall segment count grows to nine core
|
|
58
|
+
* pieces (wall1, slit1_near/slit1/slit1_far, wall2, slit2_near/slit2/slit2_far,
|
|
59
|
+
* wall3) plus the same four liners (13 total, `this.walls.length===13`) — see
|
|
60
|
+
* the module docstring above. The liners apply `RANDOM_XY` to particles at the
|
|
61
|
+
* very edge of each hole, which produces edge diffraction — a directional
|
|
62
|
+
* scatter that contributes to the emergent interference pattern.
|
|
63
|
+
*
|
|
64
|
+
* ## Physics you can drive
|
|
65
|
+
*
|
|
66
|
+
* **`wavelength`** drives the slit geometry. The default formulas are:
|
|
67
|
+
* ```
|
|
68
|
+
* slitHeight = floor(wavelength * 0.8) — height of each slit opening
|
|
69
|
+
* gapHeight = floor(wavelength * 0.8) — height of the wall between the two slits
|
|
70
|
+
* linerHeight = min(10, floor(slitHeight * 0.2)) — edge diffraction liner depth
|
|
71
|
+
* ```
|
|
72
|
+
* A larger `wavelength` gives taller slits and a wider inter-slit gap, allowing
|
|
73
|
+
* more particles through per tick and increasing the effective slit separation `d`
|
|
74
|
+
* in the interference formula. Pass explicit `slitHeight` / `gapHeight` to decouple
|
|
75
|
+
* the two dimensions independently (e.g. fixed slit size but varying gap).
|
|
76
|
+
*
|
|
77
|
+
* **`detectAtSlits`** sets the measurement mode at each slit opening:
|
|
78
|
+
* - `0` — no detection; slit objects are transparent (collision type `NONE`,
|
|
79
|
+
* action `MARK` only)
|
|
80
|
+
* - `1` — detect + scatter: collision type `RANDOM_XY` is applied, so the engine
|
|
81
|
+
* randomly deflects particles that pass through. This is the canonical
|
|
82
|
+
* "which-slit" measurement — detection disturbs the trajectory.
|
|
83
|
+
* - `2` — detect + color only: action `DETECT` is added and the slit objects are
|
|
84
|
+
* colored blue for visualization, but collision type stays `NONE` so trajectories
|
|
85
|
+
* are not disturbed.
|
|
86
|
+
*
|
|
87
|
+
* **`absorbAtSlitWall`**: when true the three wall segments silently absorb any
|
|
88
|
+
* particle that hits them (`ABSORB` action). When false the walls block and reflect
|
|
89
|
+
* particles (`BLOCK` action). Absorption is the default — it prevents reflected
|
|
90
|
+
* particles from re-entering the beam and contaminating the fringe signal.
|
|
91
|
+
*
|
|
92
|
+
* **`this.slit_wall_gap_height`** — the inter-slit gap `d` in the interference
|
|
93
|
+
* formula. Read this field on the returned `SlitScreen` instance to get the
|
|
94
|
+
* actual pixel distance between the two slit centers.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} universe - The engine universe instance (JS or WASM).
|
|
97
|
+
* @param {number} wallLeft - X position (left edge) of the slit screen (physics px).
|
|
98
|
+
* @param {number} wallWidth - Thickness of the slit screen (physics px).
|
|
99
|
+
* @param {number} canvasHeight - Full height of the simulation area (physics px).
|
|
100
|
+
* @param {number} wavelength - Geometry master knob: drives slit height, gap height,
|
|
101
|
+
* and liner depth (see formulas above).
|
|
102
|
+
* @param {boolean} absorbAtSlitWall - If true, wall segments absorb particles
|
|
103
|
+
* (ABSORB action); if false, they block/reflect (BLOCK action).
|
|
104
|
+
* @param {number} detectAtSlits - Measurement mode at slit openings:
|
|
105
|
+
* 0=none, 1=detect+scatter (RANDOM_XY), 2=detect+color only.
|
|
106
|
+
* @param {number} [slitHeight] - Override slit opening height
|
|
107
|
+
* (default: `floor(wavelength * 0.8)`).
|
|
108
|
+
* @param {number} [gapHeight] - Override inter-slit wall height
|
|
109
|
+
* (default: `floor(wavelength * 0.8)`).
|
|
110
|
+
* @param {number} [depth] - when provided, the screen becomes a 3D
|
|
111
|
+
* floor-to-ceiling wall (full z-depth `[-depth/2, +depth/2]`) tiled around two
|
|
112
|
+
* CUBIC HOLE apertures (the geometry amendment — supersedes the earlier
|
|
113
|
+
* full-depth-SLOT construction). `wall1`/`wall2`/`wall3` (no hole in
|
|
114
|
+
* their y-band) become 6-element rects spanning the FULL z-depth; `slit1`/
|
|
115
|
+
* `slit2` (still the detection identity) become 6-element rects bounded in
|
|
116
|
+
* BOTH y (unchanged from 2D) AND z (`holeDepth`, centered on z=0) — plus two
|
|
117
|
+
* new solid side tiles per hole (`slit1_near`/`slit1_far`, `slit2_near`/
|
|
118
|
+
* `slit2_far`) filling the rest of that y-band's z-span. OMITTED (default) →
|
|
119
|
+
* every rect stays the LEGACY 4-element `[left,top,w,h]` array, byte-identical
|
|
120
|
+
* to pre-B1/pre-amendment 2D behavior (no new code path executed at all).
|
|
121
|
+
* @param {number} [holeDepth] - 3D only: the z-extent of each cubic hole
|
|
122
|
+
* (centered on z=0, i.e. `[-holeDepth/2, +holeDepth/2)`). Default: the
|
|
123
|
+
* EFFECTIVE `slit_height` (respects a `slitHeight` override), so the default
|
|
124
|
+
* aperture is square-ish in the y×z plane (e.g. wavelength=64 → slit_height=51
|
|
125
|
+
* → a 51×51 hole); pass an explicit
|
|
126
|
+
* value to decouple the hole's z-extent from its y-extent. Ignored when
|
|
127
|
+
* `depth` is omitted.
|
|
128
|
+
* @param {'current'|'noreset'|'symmetric'|'combined'|'bounce'|'ejectY'|'ejectYReflect'} [linerScatter='current']
|
|
129
|
+
* Legacy compound scatter-variant string — resolved by `slitLinerScatter.js`'s
|
|
130
|
+
* `LEGACY_VARIANT_MAP` (the ONE authority for the behavior×resetLocalTime
|
|
131
|
+
* decomposition; see that module's docstring). `'current'` (default, OMIT to
|
|
132
|
+
* keep it) is the no-op sentinel — today's exact compiled `RANDOM_XY`,
|
|
133
|
+
* byte-identical, the ratified 2D golden depends on it. The other
|
|
134
|
+
* variants push a composed replacement op-program LIVE via
|
|
135
|
+
* `universe.updateObjectProperty` — the SAME mechanism the `dse-lab` page's
|
|
136
|
+
* LIVE `behavior`/`resetLocalTime` controls use (DSE LAB, 2026-07-13).
|
|
137
|
+
*/
|
|
138
|
+
function SlitScreen(universe, wallLeft, wallWidth, canvasHeight, wavelength, absorbAtSlitWall, detectAtSlits, slitHeight, gapHeight, depth, holeDepth, linerScatter) {
|
|
139
|
+
this.universe = universe;
|
|
140
|
+
this._schemaKind = 'SlitScreen';
|
|
141
|
+
this.wavelength = wavelength;
|
|
142
|
+
this.depth = (depth !== undefined && depth !== null) ? depth : null;
|
|
143
|
+
// 'current' (default/omitted) = today's exact liner behavior, byte-identical.
|
|
144
|
+
// See slitLinerScatter.js's LEGACY_VARIANT_MAP for the full decomposition.
|
|
145
|
+
this.linerScatter = linerScatter || 'current';
|
|
146
|
+
|
|
147
|
+
// ── Geometry (y-axis) — UNCHANGED by the cubic-hole amendment in EITHER
|
|
148
|
+
// mode: "floor to ceiling" was already satisfied here (wall1
|
|
149
|
+
// starts at y=0, wall3 ends at canvasHeight — confirmed by ground-truth,
|
|
150
|
+
// fix-loop item 1); only the APERTURE shape changes in 3D (§ below).
|
|
151
|
+
this.slit_height = (slitHeight !== undefined) ? slitHeight : Math.floor(wavelength * 0.8);
|
|
152
|
+
this.slit_wall_gap_height = (gapHeight !== undefined) ? gapHeight : Math.floor(wavelength * 0.8);
|
|
153
|
+
this.slit_group_height = this.slit_height + this.slit_wall_gap_height + this.slit_height;
|
|
154
|
+
this.slit_group_center_y = canvasHeight / 2;
|
|
155
|
+
this.slit1_top = this.slit_group_center_y - this.slit_group_height / 2;
|
|
156
|
+
this.slit_wall_gap_top = this.slit1_top + this.slit_height;
|
|
157
|
+
this.slit2_bottom = this.slit_group_center_y + this.slit_group_height / 2;
|
|
158
|
+
this.slit2_top = this.slit2_bottom - this.slit_height;
|
|
159
|
+
this.slit_group_left = wallLeft;
|
|
160
|
+
this.slit_width = wallWidth;
|
|
161
|
+
this.bottom_wall_height = canvasHeight - this.slit2_bottom;
|
|
162
|
+
|
|
163
|
+
// The geometry amendment: cubic-hole z-extent, centered
|
|
164
|
+
// on z=0. Default = the EFFECTIVE slit_height (square-ish aperture at the
|
|
165
|
+
// default wavelength=64 -> slit_height=51 -> a 51x51 hole) — overridable to
|
|
166
|
+
// decouple the hole's z-extent from its y-extent. 2D-inert (unused).
|
|
167
|
+
this.hole_depth = (holeDepth !== undefined && holeDepth !== null) ? holeDepth : this.slit_height;
|
|
168
|
+
|
|
169
|
+
// ── Collision config ─────────────────────────────────────────────
|
|
170
|
+
var wall_collision_actions = absorbAtSlitWall
|
|
171
|
+
? [CollisionActions.ABSORB]
|
|
172
|
+
: [CollisionActions.BLOCK];
|
|
173
|
+
var wall_collision_type = CollisionTypes.NONE;
|
|
174
|
+
|
|
175
|
+
// The three plain wall SEGMENTS (wall1/wall2/wall3) are built through the unified
|
|
176
|
+
// Wall primitive. `destroy`/`block` compile the BYTE-IDENTICAL op-programs these
|
|
177
|
+
// segments produced under the legacy `absorb`/`block` vocabulary (verified
|
|
178
|
+
// program-for-program), so the port moves ZERO physics. The SLITS (MARK/RANDOM_XY
|
|
179
|
+
// detectors) and LINERS (RANDOM_XY scatter) are NOT plain walls — they stay
|
|
180
|
+
// LargeObjects. `_wallLO(rect,name,color)` returns the Wall's inner engine LO so
|
|
181
|
+
// `this.walls` keeps holding LargeObjects (downstream — grouping, updateGapHeight,
|
|
182
|
+
// the runner auto-wire — is untouched).
|
|
183
|
+
var _segmentBehavior = absorbAtSlitWall ? 'destroy' : 'block';
|
|
184
|
+
var _wallUniverse = universe;
|
|
185
|
+
function _wallLO(rect, name, color) {
|
|
186
|
+
return new Wall(_wallUniverse, { rect: rect, collisionBehavior: _segmentBehavior,
|
|
187
|
+
name: name, color: color }).lo;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var detector_actions = [CollisionActions.MARK];
|
|
191
|
+
var detector_type = CollisionTypes.NONE;
|
|
192
|
+
var slit1_color = [0, 0, 0, 0];
|
|
193
|
+
var slit2_color = [0, 0, 0, 0];
|
|
194
|
+
|
|
195
|
+
if (detectAtSlits === 1) {
|
|
196
|
+
detector_actions.push(CollisionActions.DETECT);
|
|
197
|
+
detector_type = CollisionTypes.RANDOM_XY;
|
|
198
|
+
}
|
|
199
|
+
if (detectAtSlits === 2) {
|
|
200
|
+
detector_actions.push(CollisionActions.DETECT);
|
|
201
|
+
slit1_color = [0, 0, 255];
|
|
202
|
+
slit2_color = [0, 0, 255];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var liner_height = Math.min(10, Math.floor(this.slit_height * 0.2));
|
|
206
|
+
var _depth = this.depth;
|
|
207
|
+
|
|
208
|
+
if (_depth === null) {
|
|
209
|
+
// ═══ 2D — EXACT pre-amendment construction, byte-identical ═══
|
|
210
|
+
// (rect3 here is a plain pass-through; kept as a named local so this
|
|
211
|
+
// branch reads identically to the historical code it replaces.)
|
|
212
|
+
function rect3(left, top, w, h) { return [left, top, w, h]; }
|
|
213
|
+
|
|
214
|
+
// Wall above slit1
|
|
215
|
+
this.wall1 = _wallLO(
|
|
216
|
+
rect3(this.slit_group_left, 0, this.slit_width, this.slit1_top - 1),
|
|
217
|
+
'wall1', [255, 0, 0]
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Slit 1 (2D: a plain y-only slot, unchanged)
|
|
221
|
+
this.slit1 = new LargeObject(
|
|
222
|
+
universe,
|
|
223
|
+
rect3(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1),
|
|
224
|
+
detector_type, detector_actions, 'slit1',
|
|
225
|
+
'closest', null, 1, slit1_color
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Wall between slits
|
|
229
|
+
this.wall2 = _wallLO(
|
|
230
|
+
rect3(this.slit_group_left, this.slit_wall_gap_top, this.slit_width, this.slit_wall_gap_height - 1),
|
|
231
|
+
'wall2', [128, 0, 0]
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Slit 2
|
|
235
|
+
this.slit2 = new LargeObject(
|
|
236
|
+
universe,
|
|
237
|
+
rect3(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1),
|
|
238
|
+
detector_type, detector_actions, 'slit2',
|
|
239
|
+
'closest', null, 1, slit2_color
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Wall below slit2
|
|
243
|
+
this.wall3 = _wallLO(
|
|
244
|
+
rect3(this.slit_group_left, this.slit2_bottom, this.slit_width, this.bottom_wall_height - 1),
|
|
245
|
+
'wall3', [255, 0, 0]
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// ── Slit liners (edge diffraction) ───────────────────────────
|
|
249
|
+
var slit1_liner_top = new LargeObject(
|
|
250
|
+
universe,
|
|
251
|
+
rect3(this.slit_group_left, this.slit1_top, this.slit_width, liner_height),
|
|
252
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_top',
|
|
253
|
+
'closest', null, 1, [0, 0, 255]
|
|
254
|
+
);
|
|
255
|
+
_applyLinerScatter(slit1_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
|
|
256
|
+
|
|
257
|
+
var slit1_liner_bottom = new LargeObject(
|
|
258
|
+
universe,
|
|
259
|
+
rect3(this.slit_group_left, this.slit1_top + this.slit_height - liner_height, this.slit_width, liner_height),
|
|
260
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_bottom',
|
|
261
|
+
'closest', null, 1, [0, 0, 255]
|
|
262
|
+
);
|
|
263
|
+
_applyLinerScatter(slit1_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
|
|
264
|
+
|
|
265
|
+
var slit2_liner_top = new LargeObject(
|
|
266
|
+
universe,
|
|
267
|
+
rect3(this.slit_group_left, this.slit2_top, this.slit_width, liner_height),
|
|
268
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_top',
|
|
269
|
+
'closest', null, 1, [0, 0, 255]
|
|
270
|
+
);
|
|
271
|
+
_applyLinerScatter(slit2_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
|
|
272
|
+
|
|
273
|
+
var slit2_liner_bottom = new LargeObject(
|
|
274
|
+
universe,
|
|
275
|
+
rect3(this.slit_group_left, this.slit2_top + this.slit_height - liner_height, this.slit_width, liner_height),
|
|
276
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_bottom',
|
|
277
|
+
'closest', null, 1, [0, 0, 255]
|
|
278
|
+
);
|
|
279
|
+
_applyLinerScatter(slit2_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
|
|
280
|
+
|
|
281
|
+
this.walls = [
|
|
282
|
+
this.wall1, this.slit1, this.wall2, this.slit2, this.wall3,
|
|
283
|
+
slit1_liner_top, slit1_liner_bottom,
|
|
284
|
+
slit2_liner_top, slit2_liner_bottom
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
} else {
|
|
288
|
+
// ═══ 3D — CUBIC HOLES (the geometry amendment) ═══
|
|
289
|
+
// Tile the wall around each hole: wall1/wall2/wall3 (no hole in their
|
|
290
|
+
// y-band) span the FULL z-depth; each hole's own y-band splits into
|
|
291
|
+
// THREE z-tiles — a solid "_near" side (z < holeZmin), the hole itself
|
|
292
|
+
// (still named slit1/slit2 — detection identity preserved, now bounded
|
|
293
|
+
// in z too), and a solid "_far" side (z > holeZmax). Exact z-partition,
|
|
294
|
+
// no gaps/overlaps: near.z=[zLo,holeZlo), hole.z=[holeZlo,holeZhi),
|
|
295
|
+
// far.z=[holeZhi,zLo+depth) — verified by slit-family-depth.test.mjs.
|
|
296
|
+
var zLo = -_depth / 2; // full screen z-min
|
|
297
|
+
var holeZlo = -this.hole_depth / 2; // hole z-min (centered)
|
|
298
|
+
var holeZspan = this.hole_depth;
|
|
299
|
+
var farZlo = holeZlo + holeZspan; // = +hole_depth/2
|
|
300
|
+
var nearZspan = holeZlo - zLo; // z < holeZmin side-tile depth
|
|
301
|
+
var farZspan = (_depth / 2) - farZlo; // z > holeZmax side-tile depth
|
|
302
|
+
|
|
303
|
+
function zSlab(left, top, w, h) { return [left, top, zLo, w, h, _depth]; }
|
|
304
|
+
function zBand(left, top, w, h, zb, zs) { return [left, top, zb, w, h, zs]; }
|
|
305
|
+
|
|
306
|
+
// Wall above slit1 (no hole in this y-band -> full z-depth)
|
|
307
|
+
this.wall1 = _wallLO(
|
|
308
|
+
zSlab(this.slit_group_left, 0, this.slit_width, this.slit1_top - 1),
|
|
309
|
+
'wall1', [255, 0, 0]
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Hole 1's y-band, split into near/hole/far along z.
|
|
313
|
+
this.slit1_near = new LargeObject(
|
|
314
|
+
universe,
|
|
315
|
+
zBand(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1, zLo, nearZspan),
|
|
316
|
+
CollisionTypes.NONE, wall_collision_actions, 'slit1_near',
|
|
317
|
+
'closest', null, 1, [200, 60, 60]
|
|
318
|
+
);
|
|
319
|
+
this.slit1 = new LargeObject(
|
|
320
|
+
universe,
|
|
321
|
+
zBand(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1, holeZlo, holeZspan),
|
|
322
|
+
detector_type, detector_actions, 'slit1',
|
|
323
|
+
'closest', null, 1, slit1_color
|
|
324
|
+
);
|
|
325
|
+
this.slit1_far = new LargeObject(
|
|
326
|
+
universe,
|
|
327
|
+
zBand(this.slit_group_left, this.slit1_top, this.slit_width, this.slit_height - 1, farZlo, farZspan),
|
|
328
|
+
CollisionTypes.NONE, wall_collision_actions, 'slit1_far',
|
|
329
|
+
'closest', null, 1, [200, 60, 60]
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Wall between slits (no hole in this y-band -> full z-depth)
|
|
333
|
+
this.wall2 = _wallLO(
|
|
334
|
+
zSlab(this.slit_group_left, this.slit_wall_gap_top, this.slit_width, this.slit_wall_gap_height - 1),
|
|
335
|
+
'wall2', [128, 0, 0]
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Hole 2's y-band, split into near/hole/far along z.
|
|
339
|
+
this.slit2_near = new LargeObject(
|
|
340
|
+
universe,
|
|
341
|
+
zBand(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1, zLo, nearZspan),
|
|
342
|
+
CollisionTypes.NONE, wall_collision_actions, 'slit2_near',
|
|
343
|
+
'closest', null, 1, [200, 60, 60]
|
|
344
|
+
);
|
|
345
|
+
this.slit2 = new LargeObject(
|
|
346
|
+
universe,
|
|
347
|
+
zBand(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1, holeZlo, holeZspan),
|
|
348
|
+
detector_type, detector_actions, 'slit2',
|
|
349
|
+
'closest', null, 1, slit2_color
|
|
350
|
+
);
|
|
351
|
+
this.slit2_far = new LargeObject(
|
|
352
|
+
universe,
|
|
353
|
+
zBand(this.slit_group_left, this.slit2_top, this.slit_width, this.slit_height - 1, farZlo, farZspan),
|
|
354
|
+
CollisionTypes.NONE, wall_collision_actions, 'slit2_far',
|
|
355
|
+
'closest', null, 1, [200, 60, 60]
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Wall below slit2 (no hole in this y-band -> full z-depth)
|
|
359
|
+
this.wall3 = _wallLO(
|
|
360
|
+
zSlab(this.slit_group_left, this.slit2_bottom, this.slit_width, this.bottom_wall_height - 1),
|
|
361
|
+
'wall3', [255, 0, 0]
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// ── Slit liners (edge diffraction) — z-bounded to the hole's OWN
|
|
365
|
+
// z-range: edge diffraction only applies at the aperture, not at the
|
|
366
|
+
// solid side-tiles (there is no opening there to diffract around). ──
|
|
367
|
+
var slit1_liner_top = new LargeObject(
|
|
368
|
+
universe,
|
|
369
|
+
zBand(this.slit_group_left, this.slit1_top, this.slit_width, liner_height, holeZlo, holeZspan),
|
|
370
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_top',
|
|
371
|
+
'closest', null, 1, [0, 0, 255]
|
|
372
|
+
);
|
|
373
|
+
_applyLinerScatter(slit1_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
|
|
374
|
+
|
|
375
|
+
var slit1_liner_bottom = new LargeObject(
|
|
376
|
+
universe,
|
|
377
|
+
zBand(this.slit_group_left, this.slit1_top + this.slit_height - liner_height, this.slit_width, liner_height, holeZlo, holeZspan),
|
|
378
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit1_liner_bottom',
|
|
379
|
+
'closest', null, 1, [0, 0, 255]
|
|
380
|
+
);
|
|
381
|
+
_applyLinerScatter(slit1_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
|
|
382
|
+
|
|
383
|
+
var slit2_liner_top = new LargeObject(
|
|
384
|
+
universe,
|
|
385
|
+
zBand(this.slit_group_left, this.slit2_top, this.slit_width, liner_height, holeZlo, holeZspan),
|
|
386
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_top',
|
|
387
|
+
'closest', null, 1, [0, 0, 255]
|
|
388
|
+
);
|
|
389
|
+
_applyLinerScatter(slit2_liner_top, this.linerScatter, universe, 'bottom'); // "_top" liner -> into-cavity = bottom
|
|
390
|
+
|
|
391
|
+
var slit2_liner_bottom = new LargeObject(
|
|
392
|
+
universe,
|
|
393
|
+
zBand(this.slit_group_left, this.slit2_top + this.slit_height - liner_height, this.slit_width, liner_height, holeZlo, holeZspan),
|
|
394
|
+
CollisionTypes.RANDOM_XY, [CollisionActions.BLOCK], 'slit2_liner_bottom',
|
|
395
|
+
'closest', null, 1, [0, 0, 255]
|
|
396
|
+
);
|
|
397
|
+
_applyLinerScatter(slit2_liner_bottom, this.linerScatter, universe, 'top'); // "_bottom" liner -> into-cavity = top
|
|
398
|
+
|
|
399
|
+
this.walls = [
|
|
400
|
+
this.wall1,
|
|
401
|
+
this.slit1_near, this.slit1, this.slit1_far,
|
|
402
|
+
this.wall2,
|
|
403
|
+
this.slit2_near, this.slit2, this.slit2_far,
|
|
404
|
+
this.wall3,
|
|
405
|
+
slit1_liner_top, slit1_liner_bottom,
|
|
406
|
+
slit2_liner_top, slit2_liner_bottom
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ── Render shape (renderer-only hint; physics unaffected)
|
|
411
|
+
// The Tube.js precedent (FRQTL-MAINT 2026-07-01): a
|
|
412
|
+
// 6-element rect gives the ENGINE a real z-slab, but `_buildLoMesh`
|
|
413
|
+
// (three-provider.js) only wraps an LO in a 3D BoxGeometry when it
|
|
414
|
+
// ALSO carries the render-only `lo.shape='box'` + `lo.depth` hint —
|
|
415
|
+
// omitting it (as B1 did) left every wall a flat zero-thickness
|
|
416
|
+
// PlaneGeometry despite its correct 6-element engine rect. Only set
|
|
417
|
+
// when `depth` was actually requested (`rect.d` is only present then;
|
|
418
|
+
// a legacy 2D LO gets a 4-element rect with no `.d` and is untouched).
|
|
419
|
+
for (var _si = 0; _si < this.walls.length; _si++) {
|
|
420
|
+
if (this.walls[_si].rect.d !== undefined) {
|
|
421
|
+
this.walls[_si].shape = 'box';
|
|
422
|
+
this.walls[_si].depth = this.walls[_si].rect.d;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// THE ONE UP-LINK on every LO this screen composed (a drawn wall resolves to the screen).
|
|
427
|
+
for (var _wi = 0; _wi < this.walls.length; _wi++) {
|
|
428
|
+
this.walls[_wi]._owner = this; // THE ONE UP-LINK: the screen composed each LO
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Full teardown of what this screen composed: every wall, slit and liner LO leaves the
|
|
434
|
+
* world (dispose IS the removal). Idempotent (LargeObject.dispose guards itself).
|
|
435
|
+
*/
|
|
436
|
+
SlitScreen.prototype.dispose = function () {
|
|
437
|
+
for (var i = 0; i < this.walls.length; i++) { this.walls[i].dispose(); }
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* LIVE slit-gap update — recomputes
|
|
442
|
+
* every affected 2D wall/slit/liner rect (mirroring the constructor's OWN 2D
|
|
443
|
+
* geometry formulas, one authority, no schema-mirror) and pushes each via
|
|
444
|
+
* `runner.updateObjectProperty(lo, 'rect', [left,top,w,h])` — the SAME S3/S4
|
|
445
|
+
* live-drag accessor path `AuthorView.jsx`'s overlay-drag code already calls
|
|
446
|
+
* (mirrors/mutates the JS-side Rectangle wrapper AND forwards to the engine ABI;
|
|
447
|
+
* deliberately NOT a hand-rolled Rectangle-setter mirror, which would risk
|
|
448
|
+
* drifting from that canonical accessor's own setter-order discipline).
|
|
449
|
+
*
|
|
450
|
+
* Only `slit_wall_gap_height` changes; `slit_height`/`wavelength`/`slit_width`/
|
|
451
|
+
* `slit_group_left` and the group's vertical CENTER (`slit_group_center_y`,
|
|
452
|
+
* always `canvasHeight/2`) are invariants of a gap-only change — growing/
|
|
453
|
+
* shrinking the gap symmetrically re-centers `slit1_top`/`slit2_top` and
|
|
454
|
+
* reshapes `wall1`/`wall2`/`wall3`'s heights, exactly as the constructor would
|
|
455
|
+
* compute for that `gapHeight` from scratch.
|
|
456
|
+
*
|
|
457
|
+
* 3D (`this.depth !== null`) is NOT supported live — the cubic-hole z-tiling
|
|
458
|
+
* (the geometry amendment) adds ~13 LOs (near/far z-tiles per hole, wall1-3
|
|
459
|
+
* z-spanning tiles) whose z-partition math would need the identical live-update
|
|
460
|
+
* treatment; not built. Returns `false` so the caller falls
|
|
461
|
+
* back to a reinit reload — name this explicitly rather than silently no-op.
|
|
462
|
+
*
|
|
463
|
+
* @param {number} newGapHeight - the new `slit_wall_gap_height` (px).
|
|
464
|
+
* @param {Object} runner - an FrqtlApp exposing `updateObjectProperty(lo,prop,value)`.
|
|
465
|
+
* @returns {boolean} true if the live update was applied; false if 3D (caller must reinit).
|
|
466
|
+
*/
|
|
467
|
+
SlitScreen.prototype.updateGapHeight = function (newGapHeight, runner) {
|
|
468
|
+
if (this.depth !== null) {
|
|
469
|
+
return false; // 3D: structurally out of scope for a live update (see docstring above) — reinit.
|
|
470
|
+
}
|
|
471
|
+
if (!runner || typeof runner.updateObjectProperty !== 'function') {
|
|
472
|
+
throw new Error('[SlitScreen.updateGapHeight] requires a runner exposing updateObjectProperty(lo, prop, value).');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// slit_group_center_y = canvasHeight / 2 is a constructor invariant (never
|
|
476
|
+
// moves with gapHeight) — derive canvasHeight from it rather than storing a
|
|
477
|
+
// second field that could drift.
|
|
478
|
+
var canvasHeight = this.slit_group_center_y * 2;
|
|
479
|
+
|
|
480
|
+
this.slit_wall_gap_height = newGapHeight;
|
|
481
|
+
this.slit_group_height = this.slit_height + this.slit_wall_gap_height + this.slit_height;
|
|
482
|
+
this.slit1_top = this.slit_group_center_y - this.slit_group_height / 2;
|
|
483
|
+
this.slit_wall_gap_top = this.slit1_top + this.slit_height;
|
|
484
|
+
this.slit2_bottom = this.slit_group_center_y + this.slit_group_height / 2;
|
|
485
|
+
this.slit2_top = this.slit2_bottom - this.slit_height;
|
|
486
|
+
this.bottom_wall_height = canvasHeight - this.slit2_bottom;
|
|
487
|
+
|
|
488
|
+
var liner_height = Math.min(10, Math.floor(this.slit_height * 0.2));
|
|
489
|
+
var left = this.slit_group_left, w = this.slit_width;
|
|
490
|
+
|
|
491
|
+
function findWall(walls, name) {
|
|
492
|
+
for (var i = 0; i < walls.length; i++) { if (walls[i].name === name) return walls[i]; }
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
var wall1 = findWall(this.walls, 'wall1');
|
|
497
|
+
if (wall1) runner.updateObjectProperty(wall1, 'rect', [left, 0, w, this.slit1_top - 1]);
|
|
498
|
+
|
|
499
|
+
var slit1 = findWall(this.walls, 'slit1');
|
|
500
|
+
if (slit1) runner.updateObjectProperty(slit1, 'rect', [left, this.slit1_top, w, this.slit_height - 1]);
|
|
501
|
+
|
|
502
|
+
var wall2 = findWall(this.walls, 'wall2');
|
|
503
|
+
if (wall2) runner.updateObjectProperty(wall2, 'rect', [left, this.slit_wall_gap_top, w, this.slit_wall_gap_height - 1]);
|
|
504
|
+
|
|
505
|
+
var slit2 = findWall(this.walls, 'slit2');
|
|
506
|
+
if (slit2) runner.updateObjectProperty(slit2, 'rect', [left, this.slit2_top, w, this.slit_height - 1]);
|
|
507
|
+
|
|
508
|
+
var wall3 = findWall(this.walls, 'wall3');
|
|
509
|
+
if (wall3) runner.updateObjectProperty(wall3, 'rect', [left, this.slit2_bottom, w, this.bottom_wall_height - 1]);
|
|
510
|
+
|
|
511
|
+
var s1lt = findWall(this.walls, 'slit1_liner_top');
|
|
512
|
+
if (s1lt) runner.updateObjectProperty(s1lt, 'rect', [left, this.slit1_top, w, liner_height]);
|
|
513
|
+
|
|
514
|
+
var s1lb = findWall(this.walls, 'slit1_liner_bottom');
|
|
515
|
+
if (s1lb) runner.updateObjectProperty(s1lb, 'rect', [left, this.slit1_top + this.slit_height - liner_height, w, liner_height]);
|
|
516
|
+
|
|
517
|
+
var s2lt = findWall(this.walls, 'slit2_liner_top');
|
|
518
|
+
if (s2lt) runner.updateObjectProperty(s2lt, 'rect', [left, this.slit2_top, w, liner_height]);
|
|
519
|
+
|
|
520
|
+
var s2lb = findWall(this.walls, 'slit2_liner_bottom');
|
|
521
|
+
if (s2lb) runner.updateObjectProperty(s2lb, 'rect', [left, this.slit2_top + this.slit_height - liner_height, w, liner_height]);
|
|
522
|
+
|
|
523
|
+
return true;
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// ── The kind (objects/kinds.js): descriptor, construction defaults, builder ──────────
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* SlitScreen — Double-slit barrier (group: 3 wall segments + 2 slit segments + 4 liners = 9 sub-objects).
|
|
530
|
+
* Constructor: SlitScreen(universe, wallLeft, wallWidth, canvasHeight, wavelength,
|
|
531
|
+
* absorbAtSlitWall, detectAtSlits, slitHeight, gapHeight)
|
|
532
|
+
* Group wrapper: all 9 sub-LargeObjects are stamped with the ONE up-link `_owner` = <this>.
|
|
533
|
+
*/
|
|
534
|
+
var SlitScreenDescriptor = {
|
|
535
|
+
description: 'A double-slit barrier that produces quantum interference. ' +
|
|
536
|
+
'Creates 9 sub-LargeObjects: wall1 (above slit1), slit1, wall2 (between slits), ' +
|
|
537
|
+
'slit2, wall3 (below slit2), and 4 edge-diffraction liners (slit1_liner_top/bottom, ' +
|
|
538
|
+
'slit2_liner_top/bottom). The group is the single author-surface entity; ' +
|
|
539
|
+
'slit geometry is derived from the wavelength so the interference fringe spacing is ' +
|
|
540
|
+
'automatically correct for the particle wavelength.',
|
|
541
|
+
fields: [
|
|
542
|
+
{
|
|
543
|
+
name: 'slit_group_left',
|
|
544
|
+
type: 'number',
|
|
545
|
+
constraint: { integer: true },
|
|
546
|
+
engineVisible: true,
|
|
547
|
+
description: 'X position of the slit barrier left edge (physics pixels). Moving this repositions the entire slit assembly.'
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'slit_width',
|
|
551
|
+
type: 'number',
|
|
552
|
+
constraint: { min: 1, integer: true },
|
|
553
|
+
engineVisible: true,
|
|
554
|
+
unit: 'px',
|
|
555
|
+
description: 'Width/thickness of the slit screen (physics pixels). Thicker screens affect diffraction character.'
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: 'wavelength',
|
|
559
|
+
type: 'number',
|
|
560
|
+
constraint: { min: 4, integer: true },
|
|
561
|
+
engineVisible: true,
|
|
562
|
+
description: 'Wavelength driving slit and gap sizing. Default slit height = floor(wavelength * 0.8); inter-slit gap = same. Engine invariant 6: minimum wavelength is 4.'
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: 'slit_height',
|
|
566
|
+
type: 'number',
|
|
567
|
+
constraint: { min: 1, integer: true },
|
|
568
|
+
engineVisible: true,
|
|
569
|
+
description: 'Height of each slit opening (physics pixels). Narrower slits increase diffraction spread; wider slits reduce it.'
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'slit_wall_gap_height',
|
|
573
|
+
type: 'number',
|
|
574
|
+
constraint: { min: 1, integer: true },
|
|
575
|
+
engineVisible: true,
|
|
576
|
+
description: 'Height of the wall segment between the two slits (physics pixels). Larger gap → tighter interference fringes on the screen.'
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: 'absorbAtSlitWall',
|
|
580
|
+
type: 'boolean',
|
|
581
|
+
engineVisible: true,
|
|
582
|
+
description: 'If true, wall segments use ABSORB (particles disappear on contact). If false, walls use BLOCK. Absorbing walls eliminate back-scatter and produce cleaner interference patterns.'
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: 'detectAtSlits',
|
|
586
|
+
type: 'enum',
|
|
587
|
+
constraint: { values: { NONE: 0, DETECT_RANDOM_XY: 1, DETECT_MARK: 2 } },
|
|
588
|
+
engineVisible: true,
|
|
589
|
+
description: 'Slit detection mode: 0 = no detection (coherent, wave-like pattern); 1 = DETECT+RANDOM_XY (which-path measurement collapses the wavefunction); 2 = DETECT+MARK (which-path marker via string_map.detector_name).'
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
registerKind(SlitScreen, {
|
|
595
|
+
from: import.meta.url,
|
|
596
|
+
descriptor: SlitScreenDescriptor,
|
|
597
|
+
defaults: {
|
|
598
|
+
slit_group_left: 100,
|
|
599
|
+
slit_width: 8,
|
|
600
|
+
wavelength: 20,
|
|
601
|
+
slit_height: 16,
|
|
602
|
+
slit_wall_gap_height: 16,
|
|
603
|
+
absorbAtSlitWall: true,
|
|
604
|
+
detectAtSlits: 0
|
|
605
|
+
},
|
|
606
|
+
build: function (props, ctx, Class) {
|
|
607
|
+
var SS = Class; // the class that registered (a composed fork's own class)
|
|
608
|
+
var ph = ObjectSchema.worldRect(ctx.runner.canvas_rect, 'ObjectFactory create(\"SlitScreen\") runner.canvas_rect').h;
|
|
609
|
+
var screen = new SS(
|
|
610
|
+
ctx.runner.universe,
|
|
611
|
+
props.slit_group_left != null ? props.slit_group_left : Math.round(ctx.x),
|
|
612
|
+
props.slit_width || 8,
|
|
613
|
+
ph,
|
|
614
|
+
props.wavelength || 20,
|
|
615
|
+
props.absorbAtSlitWall != null ? props.absorbAtSlitWall : true,
|
|
616
|
+
props.detectAtSlits != null ? props.detectAtSlits : 0,
|
|
617
|
+
props.slit_height,
|
|
618
|
+
props.slit_wall_gap_height
|
|
619
|
+
);
|
|
620
|
+
screen._schemaKind = Class.name;
|
|
621
|
+
return screen;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
626
|
+
|
|
627
|
+
export { SlitScreen };
|
|
628
|
+
|