@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,229 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupDoubleSlit_Laser — Laser-style double-slit experiment factory.
|
|
5
|
+
*
|
|
6
|
+
* Replaces the radial PointSource with a directional QuantumEmitter (like a
|
|
7
|
+
* circuit battery). Particles fire rightward with initial momentum; the
|
|
8
|
+
* engine's brownian motion + wave-walk creates the lateral displacement that
|
|
9
|
+
* produces interference.
|
|
10
|
+
*
|
|
11
|
+
* Slit geometry uses d/a ≈ 5 to match real-world HeNe laser teaching-lab
|
|
12
|
+
* proportions (slit separation / slit width ≈ 5).
|
|
13
|
+
*
|
|
14
|
+
* Includes single-photon mode: fires one particle at a time from a random
|
|
15
|
+
* spot on the laser aperture, demonstrating that individual particles still
|
|
16
|
+
* build up the interference pattern.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
20
|
+
import { SlitScreen } from '../../parts/SlitScreen.js';
|
|
21
|
+
import { QuantumEmitter } from '../objects/QuantumEmitter.js';
|
|
22
|
+
import { DetectorWall } from '../../parts/DetectorWall.js';
|
|
23
|
+
import { LargeObject } from '../../parts/LargeObject.js';
|
|
24
|
+
import { CollisionTypes } from '../../parts/CollisionTypes.js';
|
|
25
|
+
|
|
26
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
27
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
28
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
29
|
+
|
|
30
|
+
// ── Imports ─────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
// ── Defaults ────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
var DEFAULT_VOLTAGE = 2; // vx: rightward momentum
|
|
35
|
+
var DEFAULT_AMPERAGE = 8; // particles per tick
|
|
36
|
+
var DEFAULT_SLIT_HEIGHT = 20; // px — slit opening (a), gives d/a ≈ 5
|
|
37
|
+
var DEFAULT_GAP_HEIGHT = 80; // px — inter-slit wall, gives d = a + gap = 100
|
|
38
|
+
var SINGLE_PHOTON_DELAY = 8; // ticks between single-photon emissions
|
|
39
|
+
|
|
40
|
+
// ── Setup Factory ───────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a laser-style double-slit experiment.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd
|
|
46
|
+
* @param {Object} options - Configuration
|
|
47
|
+
* @param {number} [options.wavelength=64] - Wavelength for screen positioning
|
|
48
|
+
* @param {number} [options.fundamentalDimension=2] - Base dimension
|
|
49
|
+
* @param {number} [options.detectAtSlits=1] - Slit detection mode (0/1/2)
|
|
50
|
+
* @param {boolean} [options.absorbAtSlitWall=true] - Absorb vs block at walls
|
|
51
|
+
* @param {number} [options.wavelengthScale] - Override wavelength constant
|
|
52
|
+
* @param {number} [options.voltage=2] - Rightward momentum (vx)
|
|
53
|
+
* @param {number} [options.amperage=8] - Particles per tick
|
|
54
|
+
* @param {boolean} [options.singlePhoton=false] - Single-photon mode
|
|
55
|
+
* @param {number} [options.slitHeight=20] - Slit opening height (px)
|
|
56
|
+
* @param {number} [options.gapHeight=80] - Inter-slit wall height (px)
|
|
57
|
+
* @returns {Object} { slitScreen, emitter, detectorWall, startWall, interferenceParams, largeObjects, setSinglePhoton }
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
61
|
+
* Exported as `setupDoubleSlit_Laser.OPTIONS` so a composing caller consumes it instead of
|
|
62
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
63
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
64
|
+
*/
|
|
65
|
+
var OPTIONS = [
|
|
66
|
+
'absorbAtSlitWall', 'amperage', 'detectAtSlits',
|
|
67
|
+
'fundamentalDimension', 'gapHeight', 'singlePhoton', 'slitHeight',
|
|
68
|
+
'voltage', 'wavelength', 'wavelengthScale',
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
function setupDoubleSlit_Laser(universe, options) {
|
|
72
|
+
options = _assertSetupOptions('setupDoubleSlit_Laser', options, OPTIONS);
|
|
73
|
+
|
|
74
|
+
var wavelength = options.wavelength !== undefined ? options.wavelength : 64;
|
|
75
|
+
var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 2;
|
|
76
|
+
var detectAtSlits = options.detectAtSlits !== undefined ? options.detectAtSlits : 1;
|
|
77
|
+
var absorbAtSlitWall = options.absorbAtSlitWall !== undefined ? options.absorbAtSlitWall : true;
|
|
78
|
+
var canvasRect = universe.universe_rect;
|
|
79
|
+
// The world height, through the ONE world-rect normalizer (throws on a missing or
|
|
80
|
+
// non-positive dimension — a zero-high screen would count nothing, silently).
|
|
81
|
+
var canvasH = _requireWorldRect('setupDoubleSlit_Laser universe.universe_rect', canvasRect).h;
|
|
82
|
+
var voltage = options.voltage !== undefined ? options.voltage : DEFAULT_VOLTAGE;
|
|
83
|
+
var amperage = options.amperage !== undefined ? options.amperage : DEFAULT_AMPERAGE;
|
|
84
|
+
var singlePhoton = options.singlePhoton || false;
|
|
85
|
+
var slitHeight = options.slitHeight !== undefined ? options.slitHeight : DEFAULT_SLIT_HEIGHT;
|
|
86
|
+
var gapHeight = options.gapHeight !== undefined ? options.gapHeight : DEFAULT_GAP_HEIGHT;
|
|
87
|
+
|
|
88
|
+
// Wavelength constant: higher values create coarser wave-walk oscillation,
|
|
89
|
+
// producing wider, more detectable interference fringes. 32 gives stable
|
|
90
|
+
// 4/4 interference tests with ~4000 hits at 5×5000. Lower values (4-16)
|
|
91
|
+
// produce finer fringes that wash out with aggregated data.
|
|
92
|
+
var wavelengthScale = options.wavelengthScale !== undefined
|
|
93
|
+
? options.wavelengthScale
|
|
94
|
+
: 32;
|
|
95
|
+
|
|
96
|
+
universe.wavelengthScale = wavelengthScale;
|
|
97
|
+
|
|
98
|
+
// Init universe: ground-up mode with wave-walk (NOT brownian motion).
|
|
99
|
+
// brownianMotion:false disables brownian motion, enabling the wave-walk
|
|
100
|
+
// movement mode where particles move 1px along velocity + 1px perpendicular
|
|
101
|
+
// via the wavelength-driven coin-flip. This is what produces interference.
|
|
102
|
+
// (brownianMotion=true would enable pure random walk, which diffuses the pattern.)
|
|
103
|
+
universe.init(
|
|
104
|
+
fundamentalDimension,
|
|
105
|
+
{
|
|
106
|
+
wavelengthScale: wavelengthScale,
|
|
107
|
+
brownianMotion: false, // enables wave-walk mode
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// ── Slit Screen (d/a ≈ 5 geometry) ─────────────────────────────
|
|
112
|
+
var slitScreenLeft = wavelength * 1.5;
|
|
113
|
+
var slitScreen = new SlitScreen(
|
|
114
|
+
universe,
|
|
115
|
+
slitScreenLeft,
|
|
116
|
+
wavelength, // wall thickness
|
|
117
|
+
canvasH,
|
|
118
|
+
wavelength,
|
|
119
|
+
absorbAtSlitWall,
|
|
120
|
+
detectAtSlits,
|
|
121
|
+
slitHeight, // override: narrower slits
|
|
122
|
+
gapHeight // override: wider gap
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// ── Start Wall (absorb wall at left edge) ──────────────────────
|
|
126
|
+
var startWall = new LargeObject(
|
|
127
|
+
universe,
|
|
128
|
+
[0, 0, 10, canvasH],
|
|
129
|
+
CollisionTypes.ABSORB,
|
|
130
|
+
// RECOLOR removed: deleted in the engine-no-color cut (I1). Stale dead reference.
|
|
131
|
+
// KILL removed: was a Rust-only divergence, deleted from canonical action set.
|
|
132
|
+
// The ABSORB collision type already absorbs frames; no action needed here.
|
|
133
|
+
[],
|
|
134
|
+
'start_wall',
|
|
135
|
+
'closest',
|
|
136
|
+
null,
|
|
137
|
+
1,
|
|
138
|
+
[120, 120, 120]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// ── Laser Emitter ──────────────────────────────────────────────
|
|
142
|
+
// Two source rects, one aligned with each slit, positioned close to the
|
|
143
|
+
// slit screen (x = slitScreenLeft - 6). The wave-walk's coin-flip has a
|
|
144
|
+
// strong directional bias at high wavelengthScale; placing the source
|
|
145
|
+
// close minimizes pre-slit drift, keeping slit1/slit2 ratio near 1.0.
|
|
146
|
+
var laserX = slitScreenLeft - 6;
|
|
147
|
+
var sourceRect1 = [laserX, slitScreen.slit1_top, 4, slitScreen.slit_height];
|
|
148
|
+
var sourceRect2 = [laserX, slitScreen.slit2_top, 4, slitScreen.slit_height];
|
|
149
|
+
|
|
150
|
+
var effectiveBurst = singlePhoton ? 1 : amperage;
|
|
151
|
+
var effectiveDelay = singlePhoton ? SINGLE_PHOTON_DELAY : 0;
|
|
152
|
+
|
|
153
|
+
var emitter = new QuantumEmitter(
|
|
154
|
+
universe,
|
|
155
|
+
[sourceRect1, sourceRect2],
|
|
156
|
+
voltage, 0, // vx=rightward, vy=0
|
|
157
|
+
effectiveBurst,
|
|
158
|
+
null, // random charge
|
|
159
|
+
true, // auto_fire
|
|
160
|
+
false, // directional (not random)
|
|
161
|
+
Infinity, // unlimited quanta
|
|
162
|
+
effectiveDelay // delay between bursts
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// ── Detector Wall ──────────────────────────────────────────────
|
|
166
|
+
var detectorWallX = slitScreen.wall1.rect.left + slitScreen.wall1.rect.w + 3 * wavelength;
|
|
167
|
+
var detectorWall = new DetectorWall(
|
|
168
|
+
universe,
|
|
169
|
+
canvasRect,
|
|
170
|
+
detectorWallX
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// ── Interference parameters ────────────────────────────────────
|
|
174
|
+
var d_distance_between_slits = slitScreen.slit_height + slitScreen.slit_wall_gap_height;
|
|
175
|
+
var L_distance_slits_to_screen = detectorWall.x - (slitScreen.wall1.rect.left + slitScreen.wall1.rect.w);
|
|
176
|
+
|
|
177
|
+
// ── Collect all large objects for rendering ─────────────────────
|
|
178
|
+
var largeObjects = [];
|
|
179
|
+
for (var wi = 0; wi < slitScreen.walls.length; wi++) {
|
|
180
|
+
largeObjects.push(slitScreen.walls[wi]);
|
|
181
|
+
}
|
|
182
|
+
largeObjects.push(startWall);
|
|
183
|
+
largeObjects.push(detectorWall.detector_wall);
|
|
184
|
+
|
|
185
|
+
// ── Single-photon toggle ────────────────────────────────────────
|
|
186
|
+
var currentAmperage = amperage;
|
|
187
|
+
|
|
188
|
+
function setSinglePhoton(enabled) {
|
|
189
|
+
if (enabled) {
|
|
190
|
+
emitter.setBurstSize(1);
|
|
191
|
+
// Delay is set at registration; re-register or use setActive to pace
|
|
192
|
+
// For now, burst_size=1 is the primary control
|
|
193
|
+
} else {
|
|
194
|
+
emitter.setBurstSize(currentAmperage);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function setAmperage(newAmperage) {
|
|
199
|
+
currentAmperage = newAmperage;
|
|
200
|
+
emitter.setBurstSize(newAmperage);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function setVoltage(newVoltage) {
|
|
204
|
+
emitter.setVelocity(newVoltage, 0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
slitScreen: slitScreen,
|
|
209
|
+
emitter: emitter,
|
|
210
|
+
detectorWall: detectorWall,
|
|
211
|
+
startWall: startWall,
|
|
212
|
+
interferenceParams: {
|
|
213
|
+
d: d_distance_between_slits,
|
|
214
|
+
L: L_distance_slits_to_screen,
|
|
215
|
+
wavelength: wavelength
|
|
216
|
+
},
|
|
217
|
+
largeObjects: largeObjects,
|
|
218
|
+
setSinglePhoton: setSinglePhoton,
|
|
219
|
+
setAmperage: setAmperage,
|
|
220
|
+
setVoltage: setVoltage
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
setupDoubleSlit_Laser.OPTIONS = OPTIONS;
|
|
227
|
+
|
|
228
|
+
export { setupDoubleSlit_Laser };
|
|
229
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupEmpty — Engine-agnostic blank-universe factory.
|
|
5
|
+
*
|
|
6
|
+
* Initializes the universe (same init contract as every other setup factory) but
|
|
7
|
+
* creates NO objects and NO emitters. This is the base the AuthorView snapshot
|
|
8
|
+
* model loads: a saved project opens this page, the universe is init'd here, then
|
|
9
|
+
* AuthorView.applyDesign reconstructs the saved entities into it. Without the
|
|
10
|
+
* universe.init() call here, applyDesign would run against an uninitialized
|
|
11
|
+
* universe and silently restore nothing.
|
|
12
|
+
*
|
|
13
|
+
* Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
17
|
+
|
|
18
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
19
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
20
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize a blank universe with the standard dimension stack, no objects.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd
|
|
26
|
+
* @param {Object} [options]
|
|
27
|
+
* @param {number} [options.fundamentalDimension=2]
|
|
28
|
+
* @param {number} [options.wavelengthScale=8]
|
|
29
|
+
* @returns {Object} { largeObjects: [] }
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
33
|
+
* Exported as `setupEmpty.OPTIONS` so a composing caller consumes it instead of
|
|
34
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
35
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
36
|
+
*/
|
|
37
|
+
var OPTIONS = [
|
|
38
|
+
'fundamentalDimension', 'wavelengthScale',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function setupEmpty(universe, options) {
|
|
42
|
+
options = _assertSetupOptions('setupEmpty', options, OPTIONS);
|
|
43
|
+
|
|
44
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
45
|
+
var canvas_rect = universe.universe_rect;
|
|
46
|
+
_requireWorldRect('setupEmpty universe.universe_rect', canvas_rect);
|
|
47
|
+
var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 2;
|
|
48
|
+
var wavelengthScale = options.wavelengthScale !== undefined ? options.wavelengthScale : 8;
|
|
49
|
+
|
|
50
|
+
// Same init contract as setupWaveInterference: ground-up, brownianMotion:false.
|
|
51
|
+
universe.init(fundamentalDimension, { wavelengthScale: wavelengthScale, brownianMotion: false });
|
|
52
|
+
|
|
53
|
+
return { largeObjects: [] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setupEmpty.OPTIONS = OPTIONS;
|
|
57
|
+
|
|
58
|
+
export { setupEmpty };
|
|
59
|
+
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupFxSmoke — Emergent smoke experiment via voxel size-up ladder.
|
|
5
|
+
*
|
|
6
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} universe - The engine universe instance (JS or WASM compat shim),
|
|
9
|
+
* not yet init'd. `universe.init` is called internally.
|
|
10
|
+
* @param {Object} [options] - Configuration overrides.
|
|
11
|
+
* @param {number} [options.velocity=1]
|
|
12
|
+
* Rightward emission velocity (px/tick). Units: pixels/tick.
|
|
13
|
+
* @param {number} [options.fundamentalWaveLength=64]
|
|
14
|
+
* Desired wavelength for emitted quanta. Units: pixels.
|
|
15
|
+
* Drives `wavelengthScale = max(1, floor(λ/4))`.
|
|
16
|
+
* @param {number} [options.burstSize=20]
|
|
17
|
+
* Quanta emitted per tick from the beam source. Units: quanta/tick.
|
|
18
|
+
* @param {number} [options.billowDimensions=3]
|
|
19
|
+
* Number of extra aggregation dimensions to register (voxel size-up ladder).
|
|
20
|
+
* dim0 = 1 px (fundamental leaf), dim+1 = 4 px, dim+2 = 8 px, dim+3 = 16 px.
|
|
21
|
+
* Each dimension represents bigger puffs once quanta aggregate into parent frames.
|
|
22
|
+
* Range: 1–4. Higher values produce larger maximum puff sizes but require more
|
|
23
|
+
* collision events to reach the top level.
|
|
24
|
+
* @param {boolean} [options.brownianMotion=true]
|
|
25
|
+
* Enable Brownian (random) perturbation on each frame per tick. Keeps the beam
|
|
26
|
+
* from being perfectly laminar — essential for inter-quantum collision frequency.
|
|
27
|
+
* @param {number} [options.bulletSpeed=2]
|
|
28
|
+
* Speed of the moving Bullet occluder (px/tick). When > 0 a Bullet is placed at
|
|
29
|
+
* the beam centre and advances rightward; its BOUNCE collision compresses quanta
|
|
30
|
+
* in the wake, driving aggregation into bigger frames (the smoke billow effect).
|
|
31
|
+
* Set to 0 to disable the bullet (pure-beam smoke, no projectile).
|
|
32
|
+
* @param {string} [options.bulletCollision='BOUNCE']
|
|
33
|
+
* CollisionTypes value for the Bullet. BOUNCE deflects quanta; WALL stops them
|
|
34
|
+
* dead (sharper void). NONE makes the bullet a ghost (visual only, no physics).
|
|
35
|
+
*
|
|
36
|
+
* @returns {{
|
|
37
|
+
* emitter: QuantumEmitter,
|
|
38
|
+
* bullet: Bullet|null,
|
|
39
|
+
* largeObjects: LargeObject[]
|
|
40
|
+
* }}
|
|
41
|
+
* bullet is null when options.bulletSpeed === 0.
|
|
42
|
+
*
|
|
43
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
44
|
+
*
|
|
45
|
+
* The smoke billow is EMERGENT PHYSICS, not a sprite trick. It mirrors the
|
|
46
|
+
* Blackbody aggregation mechanic (lib/frqtl/scenes/blackbody.js):
|
|
47
|
+
*
|
|
48
|
+
* universe.withInterQuantumCollisions = true
|
|
49
|
+
* → colliding quanta PARENT into compound frames (merge/promote pipeline)
|
|
50
|
+
*
|
|
51
|
+
* addDimension(layer) for each size-up step
|
|
52
|
+
* → the engine's Phase B (STRUCTURE) builds higher-dimension frames from
|
|
53
|
+
* fundamental-quadtree collisions, growing compound frames with larger rect.w
|
|
54
|
+
* (2× per level: 1 px → 4 px → 8 px → 16 px)
|
|
55
|
+
*
|
|
56
|
+
* Bullet (BOUNCE) advancing rightward
|
|
57
|
+
* → compresses quanta in the beam's wake → higher local density
|
|
58
|
+
* → more inter-quantum collisions → faster aggregation up the size ladder
|
|
59
|
+
* → renderer reads each frame's rect.w and sizes the smoke puff accordingly
|
|
60
|
+
* → result: varied puff sizes, denser puffs in the wake → billowing smoke
|
|
61
|
+
*
|
|
62
|
+
* With no bullet (bulletSpeed=0), the beam still aggregates but more slowly —
|
|
63
|
+
* you get a smooth column of growing puffs rather than a directional billow.
|
|
64
|
+
*
|
|
65
|
+
* With bullet (bulletSpeed>0) the wakes produce large aggregated frames whose
|
|
66
|
+
* rect.w drives the renderer's `aSize` attribute (three-provider.js) → larger,
|
|
67
|
+
* softer puff sprites. Faster bullet = denser wake = bigger emergent frames.
|
|
68
|
+
*
|
|
69
|
+
* OBSERVED effect (design intent — verified in the realm):
|
|
70
|
+
* fx-smoke: at tick ~200 puffs range from 1 px (single leaf) to 8–16 px
|
|
71
|
+
* (aggregated compound frames). The bullet wake region shows higher density
|
|
72
|
+
* and larger puff sizes than the undisturbed beam. Adjusting the `puff` slider
|
|
73
|
+
* (render control, three-provider) scales the sprite size for each puff — the
|
|
74
|
+
* variance in sizes is physics, not the slider. Figure TBD (realm verification).
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
78
|
+
import { QuantumEmitter } from '../objects/QuantumEmitter.js';
|
|
79
|
+
import { Bullet } from '../objects/Bullet.js';
|
|
80
|
+
import { CollisionTypes } from '../../parts/CollisionTypes.js';
|
|
81
|
+
|
|
82
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
83
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
84
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
85
|
+
|
|
86
|
+
// ── Imports ─────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
// ── Voxel-ladder dimension levels ────────────────────────────────────────
|
|
89
|
+
//
|
|
90
|
+
// The JS reference engine uses integer layer numbers; addDimension(n) registers
|
|
91
|
+
// a new quadtree level at scale n. The WASM engine's add_dimension() mirrors this.
|
|
92
|
+
// Blackbody uses fundamental_dim=0 + quantum_dim=4 (one extra level).
|
|
93
|
+
//
|
|
94
|
+
// For the smoke ladder we want multiple extra levels so frames can aggregate into
|
|
95
|
+
// progressively larger puffs. The layer numbers determine the relative cell size:
|
|
96
|
+
// layer 0 = fundamental (1 px leaf quanta)
|
|
97
|
+
// layer 4 = first aggregation level (cell ≈ 4 px)
|
|
98
|
+
// layer 8 = second aggregation level (cell ≈ 8 px)
|
|
99
|
+
// layer 12 = third aggregation level (cell ≈ 16 px)
|
|
100
|
+
//
|
|
101
|
+
// These follow the same convention as Blackbody (quantum_dim=4).
|
|
102
|
+
var BILLOW_DIMS = [4, 8, 12]; // up to 3 extra levels; trimmed by billowDimensions option
|
|
103
|
+
|
|
104
|
+
// ── Setup factory ────────────────────────────────────────────────────────
|
|
105
|
+
/**
|
|
106
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
107
|
+
* Exported as `setupFxSmoke.OPTIONS` so a composing caller consumes it instead of
|
|
108
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
109
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
110
|
+
*/
|
|
111
|
+
var OPTIONS = [
|
|
112
|
+
'billowDimensions', 'brownianMotion', 'bulletCollision', 'bulletSpeed',
|
|
113
|
+
'burstSize', 'fundamentalWaveLength', 'velocity',
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
function setupFxSmoke(universe, options) {
|
|
117
|
+
options = _assertSetupOptions('setupFxSmoke', options, OPTIONS);
|
|
118
|
+
|
|
119
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
120
|
+
var canvas_rect = universe.universe_rect;
|
|
121
|
+
_requireWorldRect('setupFxSmoke universe.universe_rect', canvas_rect);
|
|
122
|
+
|
|
123
|
+
var desiredWaveLength = options.fundamentalWaveLength !== undefined ? options.fundamentalWaveLength : 64;
|
|
124
|
+
var velocity = options.velocity !== undefined ? options.velocity : 1;
|
|
125
|
+
var burstSize = options.burstSize !== undefined ? options.burstSize : 20;
|
|
126
|
+
var billowDimensions = options.billowDimensions !== undefined ? options.billowDimensions : 3;
|
|
127
|
+
var useBrownian = options.brownianMotion !== undefined ? options.brownianMotion : true;
|
|
128
|
+
var bulletSpeed = options.bulletSpeed !== undefined ? options.bulletSpeed : 2;
|
|
129
|
+
var bulletCollision = options.bulletCollision !== undefined ? options.bulletCollision : CollisionTypes.BOUNCE;
|
|
130
|
+
|
|
131
|
+
var waveLengthMultiplier = Math.max(1, Math.floor(desiredWaveLength / 4));
|
|
132
|
+
|
|
133
|
+
// ── Universe init ─────────────────────────────────────────────────────
|
|
134
|
+
// fundamental_dim=0 — leaf quanta are 1-px frames.
|
|
135
|
+
// withInterQuantumCollisions = true — inter-quantum collisions drive the
|
|
136
|
+
// merge/promote pipeline (as lib/frqtl/scenes/blackbody.js sets it).
|
|
137
|
+
universe.init(0, {
|
|
138
|
+
wavelengthScale: waveLengthMultiplier,
|
|
139
|
+
brownianMotion: false,
|
|
140
|
+
activeAxes: { x: true, y: true, z: true }
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Brownian motion keeps the beam from laminar flow → more collisions.
|
|
144
|
+
universe.brownianMotion = useBrownian;
|
|
145
|
+
|
|
146
|
+
// The Blackbody aggregation mechanic: colliding quanta parent up into
|
|
147
|
+
// compound frames that occupy progressively larger cells in the quadtree.
|
|
148
|
+
universe.withInterQuantumCollisions = true;
|
|
149
|
+
|
|
150
|
+
// Register the voxel size-up ladder — a subset of BILLOW_DIMS trimmed to
|
|
151
|
+
// billowDimensions entries (max 3 extra levels above fundamental dim=0).
|
|
152
|
+
var dimsToAdd = Math.max(1, Math.min(billowDimensions, BILLOW_DIMS.length));
|
|
153
|
+
for (var i = 0; i < dimsToAdd; i++) {
|
|
154
|
+
universe.addDimension(BILLOW_DIMS[i]);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Emitter ───────────────────────────────────────────────────────────
|
|
158
|
+
// Beam source at canvas centre, firing rightward (3D walk).
|
|
159
|
+
// The z-axis yo-yo kicks spread the beam into depth, giving the smoke
|
|
160
|
+
// a 3D volumetric appearance when viewed from an orbit angle.
|
|
161
|
+
var cx = Math.floor(canvas_rect.w / 2);
|
|
162
|
+
var cy = Math.floor(canvas_rect.h / 2);
|
|
163
|
+
var sourceHeight = 10;
|
|
164
|
+
|
|
165
|
+
var emitter = new QuantumEmitter(
|
|
166
|
+
universe,
|
|
167
|
+
[[cx, cy - Math.floor(sourceHeight / 2), 1, sourceHeight]],
|
|
168
|
+
velocity, 0,
|
|
169
|
+
burstSize,
|
|
170
|
+
1, // charge = NEGATIVE
|
|
171
|
+
true, // auto_fire
|
|
172
|
+
false, // not random_direction
|
|
173
|
+
Infinity, // quantum_limit
|
|
174
|
+
0, // delay
|
|
175
|
+
false // wrap = false
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// ── Bullet (optional) ─────────────────────────────────────────────────
|
|
179
|
+
// A small BOUNCE LO placed at the beam source, advancing rightward.
|
|
180
|
+
// Its moving wake compresses quanta → more inter-quantum collisions →
|
|
181
|
+
// aggregation into bigger frames → billowing smoke puffs (Effect C per design).
|
|
182
|
+
var bullet = null;
|
|
183
|
+
if (bulletSpeed > 0 && Bullet) {
|
|
184
|
+
bullet = new Bullet(universe, {
|
|
185
|
+
x: cx,
|
|
186
|
+
y: cy - 4,
|
|
187
|
+
w: 8,
|
|
188
|
+
h: 8,
|
|
189
|
+
speed: bulletSpeed,
|
|
190
|
+
collisionType: bulletCollision,
|
|
191
|
+
color: [40, 40, 40],
|
|
192
|
+
name: 'smoke_bullet',
|
|
193
|
+
loop: true
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
emitter: emitter,
|
|
199
|
+
bullet: bullet,
|
|
200
|
+
largeObjects: bullet ? [bullet.lo] : []
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Export ───────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
setupFxSmoke.OPTIONS = OPTIONS;
|
|
207
|
+
|
|
208
|
+
export { setupFxSmoke };
|
|
209
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupG1Race — UNIT-TEST experiment G1: "LO (a Frame) races a Dim-0 quantum".
|
|
5
|
+
*
|
|
6
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
7
|
+
* @param {Object} universe - Engine universe (WASM; this experiment is wasmOnly).
|
|
8
|
+
* @param {Object} [opts]
|
|
9
|
+
* @param {number} [opts.speedProb=0.5] - Initial speed probability for the "LO" frame.
|
|
10
|
+
* @returns {{ loHandle, loQid, quantumHandle, startX, speedProb }}
|
|
11
|
+
* loQid — the QUANTUM_ID of the LO frame (use with setFrameProperty — NOT loHandle).
|
|
12
|
+
*
|
|
13
|
+
* ── Layer 2: physics behavior (what this PROVES) ─────────────────────────────
|
|
14
|
+
* The realization of "an LO extends Frame": both racers are LEAF FRAMES moving
|
|
15
|
+
* rightward (vx=1), wrapping the canvas. The reference is a Dim-0 "light" quantum —
|
|
16
|
+
* a fundamental particle that ALWAYS steps (P=1, the speed limit). The "LO" is a
|
|
17
|
+
* larger Dim-2 leaf (4px voxel) that takes its along-momentum step with probability
|
|
18
|
+
* = its `speed_prob` (default 0.5) — so it falls behind the light at a rate set by
|
|
19
|
+
* that probability (E=MC²: a fuller/heavier frame's children have fewer free pixels,
|
|
20
|
+
* so more of a push converts to motion; here the user sets the probability directly).
|
|
21
|
+
* The orthogonal wavelength (transverse jitter) is unchanged — only the forward rate
|
|
22
|
+
* is gated. Sweeping speed_prob 1.0 → 0 sweeps the LO from light-speed to frozen.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
26
|
+
|
|
27
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
28
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
29
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
33
|
+
* Exported as `setupG1Race.OPTIONS` so a composing caller consumes it instead of
|
|
34
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
35
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
36
|
+
*/
|
|
37
|
+
var OPTIONS = [
|
|
38
|
+
'loLevel', 'speedProb',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function setupG1Race(universe, opts) {
|
|
42
|
+
opts = _assertSetupOptions('setupG1Race', opts, OPTIONS);
|
|
43
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
44
|
+
var rect = universe.universe_rect;
|
|
45
|
+
var ch = _requireWorldRect('setupG1Race universe.universe_rect', rect).h;
|
|
46
|
+
var startX = 30;
|
|
47
|
+
var speedProb = (opts.speedProb !== undefined) ? opts.speedProb : 0.5;
|
|
48
|
+
|
|
49
|
+
// 2D walk: a gentle transverse wavelength so the racers jitter sideways a little
|
|
50
|
+
// while the LONGITUDINAL race (the speed_prob gate) is the visible story.
|
|
51
|
+
universe.init(0, { wavelengthScale: 16, brownianMotion: false });
|
|
52
|
+
|
|
53
|
+
// Register Dim 6 so the "LO" can be a 64px voxel/frame there — a big body that
|
|
54
|
+
// renders clearly as a cube in 3D. The leaf still moves under the move pass; the
|
|
55
|
+
// gate applies. (mass fullness has a color interpretation via LightColor/Chroma.)
|
|
56
|
+
var loLevel = (opts.loLevel !== undefined) ? opts.loLevel : 6; // 2^6 = 64px voxel
|
|
57
|
+
universe.addDimension(loLevel);
|
|
58
|
+
|
|
59
|
+
// Reference: a Dim-0 LIGHT quantum (always P=1), blue, wrapping.
|
|
60
|
+
var quantumHandle = universe.makeLeafFrame(
|
|
61
|
+
startX, Math.floor(ch * 0.32), 0, null, 1, 0, true, 0, [120, 200, 255]);
|
|
62
|
+
universe.injectFrame(quantumHandle);
|
|
63
|
+
|
|
64
|
+
// The "LO": a Dim-6 leaf (64px voxel), gold, wrapping — moves with a speed probability.
|
|
65
|
+
var loHandle = universe.makeLeafFrame(
|
|
66
|
+
startX, Math.floor(ch * 0.62), loLevel, null, 1, 0, true, 0, [255, 180, 80]);
|
|
67
|
+
universe.injectFrame(loHandle);
|
|
68
|
+
|
|
69
|
+
// set/getFrameProperty key on the QUANTUM_ID, not the makeLeafFrame handle.
|
|
70
|
+
var loQid = universe.getFrameQuantumId(loHandle);
|
|
71
|
+
universe.setFrameProperty(loQid, 'speed_prob', speedProb);
|
|
72
|
+
|
|
73
|
+
return { loHandle: loHandle, loQid: loQid, quantumHandle: quantumHandle,
|
|
74
|
+
startX: startX, speedProb: speedProb };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setupG1Race.OPTIONS = OPTIONS;
|
|
78
|
+
|
|
79
|
+
export { setupG1Race };
|
|
80
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupG2Sweep — UNIT-TEST experiment G2: a MOVING LargeObject (LO extends Frame)
|
|
5
|
+
* sweeps a field of stationary targets and its collision trigger ABSORBS them.
|
|
6
|
+
*
|
|
7
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
8
|
+
* @param {Object} universe - WASM universe (this experiment is wasmOnly).
|
|
9
|
+
* @param {Object} [opts]
|
|
10
|
+
* @param {number} [opts.speedProb=0.6] - Speed probability of the LO's backing frame.
|
|
11
|
+
* @returns {{ lo, bodyHandle, bodyQid, targets:number, searchW:number }}
|
|
12
|
+
*
|
|
13
|
+
* ── Layer 2: physics behavior (what this PROVES) ─────────────────────────────
|
|
14
|
+
* G2 = "an LO extends Frame" by COMPOSITION. A `LargeObject` is linked to a BACKING
|
|
15
|
+
* FRAME (a moving leaf) via setLargeObjectFrame; the engine re-centers the LO's search
|
|
16
|
+
* rect on the frame every tick, so the LO MOVES via the normal move pass + speed_prob
|
|
17
|
+
* gate (G1). Its search rect (searchW) is LARGER than the 1px body — collisions fire
|
|
18
|
+
* over the rect, not the frame. The LO's `on:'collision'` op-program (ABSORB here)
|
|
19
|
+
* runs across its observed dimensions. So the LO both moves like a Frame AND triggers
|
|
20
|
+
* multi-dimensionally — with the backing frame excluded from its own trigger.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
24
|
+
import { LargeObject } from '../../parts/LargeObject.js';
|
|
25
|
+
import { CollisionTypes } from '../../parts/CollisionTypes.js';
|
|
26
|
+
|
|
27
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
28
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
29
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
33
|
+
* Exported as `setupG2Sweep.OPTIONS` so a composing caller consumes it instead of
|
|
34
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
35
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
36
|
+
*/
|
|
37
|
+
var OPTIONS = [
|
|
38
|
+
'speedProb',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function setupG2Sweep(universe, opts) {
|
|
42
|
+
opts = _assertSetupOptions('setupG2Sweep', opts, OPTIONS);
|
|
43
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
44
|
+
var rect = universe.universe_rect;
|
|
45
|
+
var world = _requireWorldRect('setupG2Sweep universe.universe_rect', rect);
|
|
46
|
+
var cw = world.w, ch = world.h;
|
|
47
|
+
var speedProb = (opts.speedProb !== undefined) ? opts.speedProb : 0.6;
|
|
48
|
+
|
|
49
|
+
universe.init(0, { wavelengthScale: 1, brownianMotion: false });
|
|
50
|
+
|
|
51
|
+
// ── Target field: a grid of STATIONARY quanta the LO will sweep through. ──
|
|
52
|
+
var targets = 0;
|
|
53
|
+
for (var gx = Math.floor(cw * 0.28); gx <= Math.floor(cw * 0.82); gx += 36) {
|
|
54
|
+
for (var gy = Math.floor(ch * 0.22); gy <= Math.floor(ch * 0.78); gy += 36) {
|
|
55
|
+
var th = universe.makeLeafFrame(gx, gy, 0, null, 0, 0, false, 0, [90, 150, 220]);
|
|
56
|
+
universe.injectFrame(th);
|
|
57
|
+
universe.setFrameProperty(universe.getFrameQuantumId(th), 'is_stationary', true);
|
|
58
|
+
targets++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── LO BODY: a leaf moving rightward (speed_prob gated), mid-height. ──
|
|
63
|
+
var bodyY = Math.floor(ch * 0.5);
|
|
64
|
+
var bodyHandle = universe.makeLeafFrame(20, bodyY, 0, null, 1, 0, false, 0, [255, 120, 80]);
|
|
65
|
+
universe.injectFrame(bodyHandle);
|
|
66
|
+
var bodyQid = universe.getFrameQuantumId(bodyHandle);
|
|
67
|
+
universe.setFrameProperty(bodyQid, 'speed_prob', speedProb);
|
|
68
|
+
|
|
69
|
+
// ── The LO: a TALL search rect (covers the field height) + ABSORB trigger. ──
|
|
70
|
+
// Its rect (searchW × searchH) is far larger than the 1px body — the collision
|
|
71
|
+
// fires over the rect. ABSORB kills any (non-body) frame inside it.
|
|
72
|
+
var searchW = 40, searchH = Math.floor(ch * 0.72);
|
|
73
|
+
var lo = new LargeObject(
|
|
74
|
+
universe,
|
|
75
|
+
[20 - searchW / 2, bodyY - searchH / 2, searchW, searchH],
|
|
76
|
+
CollisionTypes.NONE, ['ABSORB'],
|
|
77
|
+
'g2_sweeper', 'closest', null, 1, [255, 120, 80]
|
|
78
|
+
);
|
|
79
|
+
// Link the LO to its backing frame → it MOVES with the body each tick.
|
|
80
|
+
universe.setLargeObjectFrame(lo.handle, bodyHandle);
|
|
81
|
+
|
|
82
|
+
return { lo: lo, bodyHandle: bodyHandle, bodyQid: bodyQid,
|
|
83
|
+
targets: targets, searchW: searchW, searchH: searchH, bodyY: bodyY };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setupG2Sweep.OPTIONS = OPTIONS;
|
|
87
|
+
|
|
88
|
+
export { setupG2Sweep };
|
|
89
|
+
|