@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- package/package.json +62 -4
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupG3Collide — UNIT-TEST experiment G3: "two LO voxel bodies collide head-on".
|
|
5
|
+
*
|
|
6
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
7
|
+
* @param {Object} universe - WASM universe (this experiment is wasmOnly).
|
|
8
|
+
* @param {Object} [opts]
|
|
9
|
+
* @param {number} [opts.level=6] - Dimension of the bodies (2^level px voxel cube; 6 = 64px).
|
|
10
|
+
* @param {number} [opts.massRatio=1] - mass_B / mass_A. 1 = equal (clean velocity swap).
|
|
11
|
+
* @param {number} [opts.density=1] - Fullness of BOTH boxes: mass = capacity × density.
|
|
12
|
+
* 1.0 = full (boxes abut, no interpenetration); <1 = partial (overlap by empty fraction).
|
|
13
|
+
* @param {number} [opts.offset=0] - Transverse B offset in voxel units. 0 = head-on; ~1 = glancing.
|
|
14
|
+
* @returns {{ aHandle, aQid, bHandle, bQid, level, voxel, massRatio, density, offset,
|
|
15
|
+
* capacity, aMass, midY, collideControl }}
|
|
16
|
+
* `collideControl` is a LIVE control handle (Blackbody shape):
|
|
17
|
+
* - `setMassRatio(r)` — live: updates B's mass relative to A; no reload.
|
|
18
|
+
* - `setDensity(d)` — live: updates BOTH bodies' fullness (mass = capacity × d), then B scaled
|
|
19
|
+
* by current massRatio.
|
|
20
|
+
* - `setOffset(o)` — live: repositions B's y centre to midY + round(o × voxel).
|
|
21
|
+
* - `reset()` — re-seats BOTH bodies to launch positions (initial x, vx=±1, B.y = midY
|
|
22
|
+
* + round(offset × voxel)) so density/offset changes restart the collision
|
|
23
|
+
* honestly. Reads current live state (massRatio, density, offset) so the
|
|
24
|
+
* new collision respects the latest knob values.
|
|
25
|
+
* All setters write through the property accessors (setFrameProperty) against the stored
|
|
26
|
+
* quantum ids (aQid, bQid) — NEVER a page reload.
|
|
27
|
+
*
|
|
28
|
+
* ── Layer 2: physics behavior (what this PROVES) ─────────────────────────────
|
|
29
|
+
* Two LEAF FRAMES at the SAME non-fundamental Dimension (= same voxel size, so each
|
|
30
|
+
* stands in for the virtual children filling its voxel) are launched toward each other
|
|
31
|
+
* (A: vx=+1 from the left, B: vx=-1 from the right) with speed_prob=1 so they approach
|
|
32
|
+
* deterministically and the COLLISION is the whole story. When their voxel extents
|
|
33
|
+
* overlap, the engine's voxel_collision_pass exchanges the overlap-volume momentum
|
|
34
|
+
* (conserved): at full overlap with equal mass the result is a perfect velocity SWAP —
|
|
35
|
+
* both cubes reverse and separate (perfectly-elastic head-on bounce). With massRatio≠1
|
|
36
|
+
* the lighter body recoils faster than the heavier one plows forward. Fundamental
|
|
37
|
+
* "light" quanta are NOT involved — this pass acts only on virtual-children bodies, so
|
|
38
|
+
* the no-collision experiments (Particle Walk, Wave Interference) are untouched.
|
|
39
|
+
*
|
|
40
|
+
* Bodies wrap at canvas edges so after a bounce they re-approach and collide again —
|
|
41
|
+
* a continuous ping-pong demo.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
45
|
+
|
|
46
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
47
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
48
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
52
|
+
* Exported as `setupG3Collide.OPTIONS` so a composing caller consumes it instead of
|
|
53
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
54
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
55
|
+
*/
|
|
56
|
+
var OPTIONS = [
|
|
57
|
+
'density', 'level', 'massRatio', 'offset',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
function setupG3Collide(universe, opts) {
|
|
61
|
+
opts = _assertSetupOptions('setupG3Collide', opts, OPTIONS);
|
|
62
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
63
|
+
var rect = universe.universe_rect;
|
|
64
|
+
var world = _requireWorldRect('setupG3Collide universe.universe_rect', rect);
|
|
65
|
+
var cw = world.w, ch = world.h;
|
|
66
|
+
var level = (opts.level !== undefined) ? opts.level : 5; // 2^5 = 32px voxel body
|
|
67
|
+
// Initial knob values — held in the closure so live setters compose correctly.
|
|
68
|
+
var massRatio = (opts.massRatio !== undefined) ? opts.massRatio : 1.0;
|
|
69
|
+
// density ∈ (0,1] — fullness of BOTH boxes (mass = capacity·density). 1 = full (abut, no
|
|
70
|
+
// interpenetration); <1 = partial (the boxes interpenetrate by their empty fraction).
|
|
71
|
+
var density = (opts.density !== undefined) ? opts.density : 1.0;
|
|
72
|
+
// transverse offset (in voxel units): B's y relative to A. 0 = head-on; ~1 = glancing.
|
|
73
|
+
var offset = (opts.offset !== undefined) ? opts.offset : 0.0;
|
|
74
|
+
|
|
75
|
+
universe.init(0, { wavelengthScale: 1, brownianMotion: false });
|
|
76
|
+
// Enable the collision passes (default is false) — G3 needs voxel_collision_pass.
|
|
77
|
+
universe.withInterQuantumCollisions = true;
|
|
78
|
+
|
|
79
|
+
// Register the voxel dimension so the bodies are visible cubes/blobs.
|
|
80
|
+
universe.addDimension(level);
|
|
81
|
+
var voxel = Math.pow(2, level);
|
|
82
|
+
|
|
83
|
+
// Canonical launch x-positions (re-used by reset()).
|
|
84
|
+
var midY = Math.floor(ch * 0.5);
|
|
85
|
+
var ax0 = Math.floor(cw * 0.5) - 1.5 * voxel;
|
|
86
|
+
var bx0 = Math.floor(cw * 0.5) + 1.5 * voxel;
|
|
87
|
+
|
|
88
|
+
// Body A — warm, moving +x from the left. wrap=true so after the bounce it wraps and
|
|
89
|
+
// re-approaches (a continuous demo) instead of escaping a non-reflecting boundary.
|
|
90
|
+
var aHandle = universe.makeLeafFrame(ax0, midY, level, null, 1, 0, true, 0, [255, 140, 70]);
|
|
91
|
+
universe.injectFrame(aHandle);
|
|
92
|
+
// makeLeafFrame returns a FrameId; set/getFrameProperty key on the QUANTUM_ID.
|
|
93
|
+
var aQid = universe.getFrameQuantumId(aHandle);
|
|
94
|
+
|
|
95
|
+
// Body B — cool, moving -x from the right, offset transversely for glancing blows.
|
|
96
|
+
var bHandle = universe.makeLeafFrame(bx0, midY + Math.round(offset * voxel), level, null, -1, 0, true, 0, [70, 160, 255]);
|
|
97
|
+
universe.injectFrame(bHandle);
|
|
98
|
+
var bQid = universe.getFrameQuantumId(bHandle);
|
|
99
|
+
|
|
100
|
+
// Density: set BOTH boxes' fullness (mass = capacity·density). The capacity is the full
|
|
101
|
+
// mass makeLeafFrame gave A. Then mass ratio scales B on top (asymmetric collisions).
|
|
102
|
+
// capacity is treated as a constant after init; density and massRatio are live-writable.
|
|
103
|
+
var capacity = universe.getFrameProperty(aQid, 'mass'); // full body → mass == capacity
|
|
104
|
+
var aMass = capacity * density;
|
|
105
|
+
universe.setFrameProperty(aQid, 'mass', aMass);
|
|
106
|
+
universe.setFrameProperty(bQid, 'mass', aMass * massRatio);
|
|
107
|
+
|
|
108
|
+
// ── Live control handle (Blackbody shape) ──────────────────────────────
|
|
109
|
+
// Each setter writes through the property accessors against the stored quantum ids.
|
|
110
|
+
// No page reload — the same shape as the blackbody scene's temperature knob
|
|
111
|
+
// (lib/frqtl/scenes/blackbody.js: slider onChange → oven.setTemperature, live).
|
|
112
|
+
var collideControl = {
|
|
113
|
+
/**
|
|
114
|
+
* Update mass_B / mass_A live.
|
|
115
|
+
* @param {number} r - New mass ratio (mass_B relative to mass_A).
|
|
116
|
+
*/
|
|
117
|
+
setMassRatio: function (r) {
|
|
118
|
+
massRatio = r;
|
|
119
|
+
var curA = universe.getFrameProperty(aQid, 'mass');
|
|
120
|
+
universe.setFrameProperty(bQid, 'mass', curA * massRatio);
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Update the fullness (density) of BOTH bodies live.
|
|
125
|
+
* @param {number} d - Density in (0, 1]. mass = capacity × d; B then scaled by massRatio.
|
|
126
|
+
*/
|
|
127
|
+
setDensity: function (d) {
|
|
128
|
+
density = d;
|
|
129
|
+
var m = capacity * density;
|
|
130
|
+
universe.setFrameProperty(aQid, 'mass', m);
|
|
131
|
+
universe.setFrameProperty(bQid, 'mass', m * massRatio);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reposition B's y-centre live (transverse offset in voxel units).
|
|
136
|
+
* @param {number} o - Offset in voxel units. 0 = head-on; ~1 = glancing.
|
|
137
|
+
*/
|
|
138
|
+
setOffset: function (o) {
|
|
139
|
+
offset = o;
|
|
140
|
+
universe.setFrameProperty(bQid, 'y', midY + Math.round(o * voxel));
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Re-seat BOTH bodies to their canonical launch state so the collision restarts
|
|
145
|
+
* honestly with the current knob values. Reads the live massRatio / density / offset
|
|
146
|
+
* closure state so the new run respects the latest slider positions.
|
|
147
|
+
*/
|
|
148
|
+
reset: function () {
|
|
149
|
+
// Re-seat positions and velocities via property accessors.
|
|
150
|
+
universe.setFrameProperty(aQid, 'x', ax0);
|
|
151
|
+
universe.setFrameProperty(aQid, 'y', midY);
|
|
152
|
+
universe.setFrameProperty(aQid, 'vx', 1);
|
|
153
|
+
universe.setFrameProperty(bQid, 'x', bx0);
|
|
154
|
+
universe.setFrameProperty(bQid, 'y', midY + Math.round(offset * voxel));
|
|
155
|
+
universe.setFrameProperty(bQid, 'vx', -1);
|
|
156
|
+
// Re-apply current mass state (density × capacity, B scaled by massRatio).
|
|
157
|
+
var m = capacity * density;
|
|
158
|
+
universe.setFrameProperty(aQid, 'mass', m);
|
|
159
|
+
universe.setFrameProperty(bQid, 'mass', m * massRatio);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
getMassRatio: function () { return massRatio; },
|
|
163
|
+
getDensity: function () { return density; },
|
|
164
|
+
getOffset: function () { return offset; }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return { aHandle: aHandle, aQid: aQid, bHandle: bHandle, bQid: bQid,
|
|
168
|
+
level: level, voxel: voxel, massRatio: massRatio,
|
|
169
|
+
density: density, offset: offset,
|
|
170
|
+
capacity: capacity, aMass: aMass, midY: midY,
|
|
171
|
+
collideControl: collideControl };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setupG3Collide.OPTIONS = OPTIONS;
|
|
175
|
+
|
|
176
|
+
export { setupG3Collide };
|
|
177
|
+
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupLightSourceLab — LightSource ISOLATION (I2, directed-tunnel composition model
|
|
5
|
+
* §3 row 2): ONE `LightSource`, gun-chamber mechanics (fill / escape / dispersion) at a
|
|
6
|
+
* live-tunable scale — the visual half of the I2 source-invariance measurement
|
|
7
|
+
* (`docs/internal/physics-lab/directed-tunnel-scale-sweep/source-invariance.cjs`), same object,
|
|
8
|
+
* same DSE-scale defaults.
|
|
9
|
+
*
|
|
10
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} universe - WASM universe (not yet init'd — this factory calls init()).
|
|
13
|
+
* Same seeding convention as every other setup factory (no `opts.seed` — the
|
|
14
|
+
* seed is fixed by `FrqtlApp` before any factory runs).
|
|
15
|
+
* @param {Object} [opts]
|
|
16
|
+
* @param {number} [opts.bore=8] - Tunnel bore (`LightSource`'s own DSE-scale
|
|
17
|
+
* default). REINIT-CLASS.
|
|
18
|
+
* @param {number} [opts.length=16] - Tunnel length (`LightSource`'s own DSE-scale
|
|
19
|
+
* default). REINIT-CLASS.
|
|
20
|
+
* @param {'particleWalk'|'emitter'} [opts.source='particleWalk'] - `LightSource`'s
|
|
21
|
+
* own I2 default (the CEO's PRIMARY reference source). REINIT-CLASS (the two
|
|
22
|
+
* source mechanisms are fundamentally different constructions — see
|
|
23
|
+
* `LightSource.js`'s own docstring).
|
|
24
|
+
* @param {'random'|'positive'|'negative'} [opts.polarization='random'] - passed
|
|
25
|
+
* straight through to `LightSource`. REINIT-CLASS.
|
|
26
|
+
* @param {number} [opts.particleCount=20] - The INITIAL one-shot burst size
|
|
27
|
+
* hand-placed at construction (unchanged meaning/default — `LightSource`'s
|
|
28
|
+
* own construction-time placement, T2/I2 semantics untouched).
|
|
29
|
+
* @param {number} [opts.sourceDensity] - CEO drive #2 (2026-07-14 SUSTAIN fix
|
|
30
|
+
* — orchestrator own-look finding: "LightSource Lab decays to 0 by tick
|
|
31
|
+
* ~2000, particleWalk is a one-shot snapshot that never replenishes").
|
|
32
|
+
* Quanta per RECURRING emission event, for BOTH source modes — mirrors
|
|
33
|
+
* `setupTunnelLab.js`'s own `sourceDensity` exactly ("same treatment,
|
|
34
|
+
* layered"). Defaults to `particleCount` (the recurring bursts match the
|
|
35
|
+
* initial one's size unless told otherwise). `source:'particleWalk'` -> a
|
|
36
|
+
* NEW local host tick-fn (this factory, NOT `LightSource.js` — that class
|
|
37
|
+
* stays a one-shot construction for every OTHER caller, e.g. DSE Lab)
|
|
38
|
+
* re-places `sourceDensity` quanta at the SAME entrance pixel/velocity/
|
|
39
|
+
* charge convention `LightSource.js`'s own particleWalk uses (mirrored
|
|
40
|
+
* here, cited below). `source:'emitter'` -> `emitterSpec.burstSize`
|
|
41
|
+
* (quanta/tick from the underlying `QuantumEmitter`).
|
|
42
|
+
* @param {number} [opts.sourceRate=1] - Ticks between recurring emission
|
|
43
|
+
* events (mirrors `setupTunnelLab.js`'s own `sourceRate` exactly; `1` =
|
|
44
|
+
* every tick, the densest reachable cadence). `source:'particleWalk'` ->
|
|
45
|
+
* the local tick-fn's own cadence gate. `source:'emitter'` ->
|
|
46
|
+
* `emitterSpec.delay = max(0, sourceRate-1)` (`QuantumEmitter`'s own "ticks
|
|
47
|
+
* between bursts," 0-based — this mapping keeps `sourceRate=1` meaning
|
|
48
|
+
* "every tick" for BOTH source modes identically).
|
|
49
|
+
* @param {boolean} [opts.sourceActive=true] - Master on/off. LIVE
|
|
50
|
+
* (`setupResult.sourceState.active`) for `source:'particleWalk'` (gates the
|
|
51
|
+
* local recurring tick-fn). `source:'emitter'` does NOT currently wire this
|
|
52
|
+
* to the underlying `QuantumEmitter` (it auto-fires unconditionally once
|
|
53
|
+
* constructed, matching `Gun.js`'s own breech emitter — no live on/off
|
|
54
|
+
* toggle exists for ANY `Tube`/`DirectedTunnel` emitter today); `setupResult
|
|
55
|
+
* .sourceState.active` is still returned for a future caller that wants it.
|
|
56
|
+
*
|
|
57
|
+
* GUN-CHAMBER PARITY (ground-truthed `Gun.js`'s own breech
|
|
58
|
+
* `QuantumEmitter`, 2026-07-14): `auto_fire:true, quantum_limit:Infinity,
|
|
59
|
+
* delay:0` — an UNLIMITED, every-tick-firing emitter, exactly what
|
|
60
|
+
* `source:'emitter'` now does (`quantumLimit:Infinity` unconditionally,
|
|
61
|
+
* never `particleCount` — the OLD finite cap was the bug this fix removes).
|
|
62
|
+
* @param {string} [opts.wallBehavior='option2'] - `LightSource.tunnel`'s initial
|
|
63
|
+
* behavior (`DirectedTunnel.BEHAVIORS`). LIVE (`setupResult.tunnel.
|
|
64
|
+
* updateBehavior(newBehavior)` — the same stock `DirectedTunnel` accessor
|
|
65
|
+
* `tunnel-lab` uses; REPLICA DISCIPLINE means nothing LightSource-specific is
|
|
66
|
+
* needed here).
|
|
67
|
+
* @param {number} [opts.depth] - CEO drive #2 (2026-07-14) 3D toggle. OMITTED
|
|
68
|
+
* (default) -> 2D (byte-identical to every pre-existing caller). A positive
|
|
69
|
+
* value -> real z-containment (`activeAxes:{x,y,z}` on `universe.init()`,
|
|
70
|
+
* `LightSource.tunnel`'s own `depth`/`z=-depth/2` — `LightSource.js` ALREADY
|
|
71
|
+
* z-centres its `particleWalk` quanta whenever `tunnel.depth` is real, N1 fix
|
|
72
|
+
* precedent — zero new logic needed here beyond forwarding). Mirrors
|
|
73
|
+
* `setupTunnelLab.js`'s identical `opts.depth` recipe exactly (tunnel-lab and
|
|
74
|
+
* lightsource-lab STAY layered — same object, same 3D recipe). REINIT-CLASS.
|
|
75
|
+
*
|
|
76
|
+
* @returns {{ lightSource: LightSource, tunnel: DirectedTunnel, largeObjects: LargeObject[] }}
|
|
77
|
+
* `largeObjects` (= `lightSource.largeObjects` = `lightSource.tunnel.largeObjects`)
|
|
78
|
+
* is the canonical key `FrqtlApp.setup()` auto-wires for rendering.
|
|
79
|
+
*
|
|
80
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
81
|
+
*
|
|
82
|
+
* Zero new physics (canonical-bench SU1) — this factory only constructs a
|
|
83
|
+
* STOCK `LightSource` (itself zero new physics over `DirectedTunnel`/`Tube` — see
|
|
84
|
+
* those objects' own docstrings) with DSE-scale defaults, matching
|
|
85
|
+
* the directed-tunnel composition model §2.3's "the Gun Chamber object set, scaled
|
|
86
|
+
* by sugar params."`source:'particleWalk'`'s snapshot burst vs `source:'emitter'`'s
|
|
87
|
+
* continuous stream is the REAL, honest visual contrast between the two source
|
|
88
|
+
* mechanisms this page exists to demonstrate (I2's source-invariance oracle
|
|
89
|
+
* quantifies how close their downstream exit ensembles are — see that script's
|
|
90
|
+
* README section).
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
94
|
+
import { LightSource } from '../objects/LightSource.js';
|
|
95
|
+
import * as SustainedSource from '../objects/sustainedSource.js';
|
|
96
|
+
|
|
97
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
98
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
99
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
100
|
+
|
|
101
|
+
// ── Imports ─────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
var FUNDAMENTAL_DIMENSION = 2; // matches setupTunnelLab.js / setupDoubleSlit.js / LightSource.js's own construction path
|
|
104
|
+
|
|
105
|
+
// ws-dse-3d AC4 (CEO ruling, "MATCHED CONTROLS -> MATCHED PHYSICS"): the
|
|
106
|
+
// SAME MAX_USEFUL_WAVELENGTH clamp authority setupTunnelLab.js derives its
|
|
107
|
+
// own ceiling from (SustainedSource.capacityAtDimension) — at identical
|
|
108
|
+
// FUNDAMENTAL_DIMENSION=2, this is byte-identical to setupTunnelLab.
|
|
109
|
+
// MAX_USEFUL_WAVELENGTH (16), so a matched-wavelength setting on both labs
|
|
110
|
+
// produces the SAME mass/fullness/color.
|
|
111
|
+
var LIGHTSOURCE_LAB_CAPACITY = SustainedSource.capacityAtDimension(FUNDAMENTAL_DIMENSION);
|
|
112
|
+
/**
|
|
113
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
114
|
+
* Exported as `setupLightSourceLab.OPTIONS` so a composing caller consumes it instead of
|
|
115
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
116
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
117
|
+
*/
|
|
118
|
+
var OPTIONS = [
|
|
119
|
+
'bore', 'depth',
|
|
120
|
+
'length', 'particleCount', 'polarization', 'source',
|
|
121
|
+
'sourceActive', 'sourceDensity', 'sourceRate', 'wallBehavior',
|
|
122
|
+
'wavelength',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
function setupLightSourceLab(universe, opts) {
|
|
126
|
+
opts = _assertSetupOptions('setupLightSourceLab', opts, OPTIONS);
|
|
127
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
128
|
+
var canvasRect = universe.universe_rect;
|
|
129
|
+
var world = _requireWorldRect('setupLightSourceLab universe.universe_rect', canvasRect);
|
|
130
|
+
var cw = world.w, ch = world.h;
|
|
131
|
+
|
|
132
|
+
var bore = opts.bore !== undefined ? opts.bore : 8;
|
|
133
|
+
var length = opts.length !== undefined ? opts.length : 16;
|
|
134
|
+
var source = opts.source !== undefined ? opts.source : 'particleWalk';
|
|
135
|
+
var polarization = opts.polarization !== undefined ? opts.polarization : 'random';
|
|
136
|
+
var particleCount = opts.particleCount !== undefined ? opts.particleCount : 20;
|
|
137
|
+
var wallBehavior = opts.wallBehavior !== undefined ? opts.wallBehavior : 'option2';
|
|
138
|
+
var depth = (opts.depth !== undefined && opts.depth !== null) ? opts.depth : null;
|
|
139
|
+
// ws-dse-3d AC4 (CEO ruling, "lightsource-lab gains the SAME wavelength
|
|
140
|
+
// control tunnel-lab has, same default 8, same MAX_USEFUL_WAVELENGTH
|
|
141
|
+
// clamp authority"): threaded into the INITIAL one-shot burst
|
|
142
|
+
// (`lsOpts.particle.wavelength`, `source:'particleWalk'`, matching
|
|
143
|
+
// `LightSource.js`'s own `options.particle.wavelength` field) AND into
|
|
144
|
+
// the recurring sustain spec below — this is THE field whose omission
|
|
145
|
+
// on this factory's OLD injector produced the CEO's "different particle
|
|
146
|
+
// colors" report (tunnel-lab's mass=2/fullness=0.125/red vs this page's
|
|
147
|
+
// mass=16/fullness=1.0/violet at otherwise-identical settings).
|
|
148
|
+
// ws-dse-3d AC1 (round 2, CEO ruling: "lightsource-lab's λ slider must
|
|
149
|
+
// govern emitter mode too") CLOSES the prior round's deferral — `source:
|
|
150
|
+
// 'emitter'` now ALSO stamps `wavelength` (via `lsOpts.emitterSpec.
|
|
151
|
+
// wavelength` below, QuantumEmitter's new `opts.wavelength`), and is LIVE
|
|
152
|
+
// via the `sourceState.wavelength` accessor built further down.
|
|
153
|
+
var wavelength = opts.wavelength !== undefined ? opts.wavelength : 8;
|
|
154
|
+
|
|
155
|
+
// CEO drive #2 SUSTAIN fix (2026-07-14, orchestrator own-look finding):
|
|
156
|
+
// recurring emission params, ONE authority for BOTH source modes —
|
|
157
|
+
// mirrors setupTunnelLab.js's own sourceDensity/sourceRate/sourceActive
|
|
158
|
+
// exactly ("same treatment, layered"). Defaults to particleCount so the
|
|
159
|
+
// recurring bursts match the initial one's size unless told otherwise.
|
|
160
|
+
var sourceDensity = opts.sourceDensity !== undefined ? opts.sourceDensity : particleCount;
|
|
161
|
+
var sourceRate = opts.sourceRate !== undefined ? opts.sourceRate : 1;
|
|
162
|
+
var sourceActive = opts.sourceActive !== undefined ? !!opts.sourceActive : true;
|
|
163
|
+
|
|
164
|
+
var initOpts = {};
|
|
165
|
+
if (depth !== null) { initOpts.activeAxes = { x: true, y: true, z: true }; }
|
|
166
|
+
universe.init(FUNDAMENTAL_DIMENSION, initOpts);
|
|
167
|
+
|
|
168
|
+
var left = Math.round(cw * 0.1);
|
|
169
|
+
var top = Math.round(ch / 2 - bore / 2);
|
|
170
|
+
|
|
171
|
+
var tunnelOpts = {
|
|
172
|
+
left: left, top: top, length: length, width: bore,
|
|
173
|
+
capNegX: true, capPosX: false, // closed breech, open muzzle — Gun-Chamber convention
|
|
174
|
+
behavior: wallBehavior,
|
|
175
|
+
};
|
|
176
|
+
if (depth !== null) { tunnelOpts.depth = depth; tunnelOpts.z = -depth / 2; }
|
|
177
|
+
|
|
178
|
+
var lsOpts = {
|
|
179
|
+
source: source,
|
|
180
|
+
polarization: polarization,
|
|
181
|
+
tunnel: tunnelOpts,
|
|
182
|
+
};
|
|
183
|
+
if (source === 'particleWalk') {
|
|
184
|
+
lsOpts.particleCount = particleCount;
|
|
185
|
+
lsOpts.particle = { wavelength: wavelength };
|
|
186
|
+
// ws-dse-3d AC1 (CEO ruling, "LightSource itself should gain the
|
|
187
|
+
// sustain capability through the SAME single authority"): delegates
|
|
188
|
+
// the recurring re-placement to `LightSource`'s own opt-in
|
|
189
|
+
// `options.sustain` (-> `sustainedSource.js`'s ONE authority) —
|
|
190
|
+
// REPLACES this factory's own previously hand-rolled injector
|
|
191
|
+
// tick-fn (which "mirror[ed] setupTunnelLab.js's own injector idiom
|
|
192
|
+
// byte-for-byte"). `wavelength` here (density/rate/active too) feeds
|
|
193
|
+
// the recurring bursts; the initial one-shot burst above uses the
|
|
194
|
+
// SAME `wavelength` via `lsOpts.particle` — one source of truth,
|
|
195
|
+
// never two independently-typed values.
|
|
196
|
+
lsOpts.sustain = { density: sourceDensity, rate: sourceRate, active: sourceActive, wavelength: wavelength };
|
|
197
|
+
} else {
|
|
198
|
+
// GUN-CHAMBER PARITY (ground-truthed Gun.js's own breech
|
|
199
|
+
// QuantumEmitter, 2026-07-14): auto_fire:true, quantum_limit:
|
|
200
|
+
// Infinity, delay:0 — UNLIMITED. quantumLimit is now ALWAYS
|
|
201
|
+
// Infinity (never particleCount — that finite cap was the O5.2
|
|
202
|
+
// bug: the stream stopped admitting new quanta and the existing
|
|
203
|
+
// population eventually escaped/died with nothing to replace it).
|
|
204
|
+
// burstSize/delay are now driven by sourceDensity/sourceRate (the
|
|
205
|
+
// SAME rate-control mechanism 'particleWalk' mode uses above).
|
|
206
|
+
lsOpts.emitterSpec = {
|
|
207
|
+
burstSize: sourceDensity,
|
|
208
|
+
quantumLimit: Infinity,
|
|
209
|
+
randomDirection: false,
|
|
210
|
+
delay: Math.max(0, sourceRate - 1),
|
|
211
|
+
// ws-dse-3d AC1 (CEO drive #2 round-2 ruling, "lightsource-lab's λ
|
|
212
|
+
// slider must govern emitter mode too"): threads through
|
|
213
|
+
// LightSource.js's generic `options.emitterSpec` passthrough ->
|
|
214
|
+
// DirectedTunnel's generic `options.emitter` copy -> Tube.js's new
|
|
215
|
+
// `espec.wavelength` read -> QuantumEmitter's new `opts.wavelength`.
|
|
216
|
+
// Matches `lsOpts.particle.wavelength` above — ONE source of truth
|
|
217
|
+
// for BOTH source modes, never two independently-typed values.
|
|
218
|
+
wavelength: wavelength,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
var lightSource = new LightSource(universe, lsOpts);
|
|
223
|
+
|
|
224
|
+
// sourceState: for 'particleWalk', this IS `lightSource.sustainedSource
|
|
225
|
+
// .sourceState` (the SAME live-mutable normalized spec `sustainedSource
|
|
226
|
+
// .js` returns — a UI slider mutating `sourceState.wavelength`/`density`
|
|
227
|
+
// /`rate`/`active` takes effect on the NEXT recurring burst, no
|
|
228
|
+
// reinit). For 'emitter', a plain info object PLUS a `wavelength`
|
|
229
|
+
// ACCESSOR (ws-dse-3d AC1) that delegates to `QuantumEmitter.prototype.
|
|
230
|
+
// setWavelength` — so the page's wavelength slider
|
|
231
|
+
// (`runner.setupResult.sourceState.wavelength = v`, the SAME bare-
|
|
232
|
+
// assignment idiom used for BOTH modes) works identically whether the
|
|
233
|
+
// live source is a sustainedSource or this emitter, with zero page-level
|
|
234
|
+
// branching on source mode. burstSize/delay stay NOT live-wired
|
|
235
|
+
// (unchanged, out of THIS bite's scope).
|
|
236
|
+
var sourceState;
|
|
237
|
+
if (source === 'particleWalk' && lightSource.sustainedSource) {
|
|
238
|
+
sourceState = lightSource.sustainedSource.sourceState;
|
|
239
|
+
} else {
|
|
240
|
+
sourceState = { density: sourceDensity, rate: sourceRate, active: sourceActive };
|
|
241
|
+
if (source === 'emitter' && lightSource.emitter) {
|
|
242
|
+
Object.defineProperty(sourceState, 'wavelength', {
|
|
243
|
+
get: function () { return lightSource.emitter.wavelength; },
|
|
244
|
+
set: function (v) { lightSource.emitter.setWavelength(v); },
|
|
245
|
+
enumerable: true,
|
|
246
|
+
configurable: true,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
lightSource: lightSource,
|
|
253
|
+
tunnel: lightSource.tunnel,
|
|
254
|
+
sourceState: sourceState,
|
|
255
|
+
// FrqtlApp.setup() (FrqtlApp.js:528) auto-wires
|
|
256
|
+
// the render list; today the world's own census carries them by construction.
|
|
257
|
+
largeObjects: lightSource.largeObjects,
|
|
258
|
+
// CANONICAL TEARDOWN (CEO drive #2, 2026-07-14 — the reinit-
|
|
259
|
+
// compounding fix): delegates to `LightSource.prototype.dispose`
|
|
260
|
+
// (ws-dse-3d addition) — that ONE method already tears down BOTH
|
|
261
|
+
// `this.sustainedSource` (particleWalk+sustain) and `this.emitter`
|
|
262
|
+
// (emitter mode's own QuantumEmitter.dispose) — no bespoke
|
|
263
|
+
// teardownFns array needed here anymore.
|
|
264
|
+
dispose: function () { lightSource.dispose(); },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ONE authority for the achievable wavelength ceiling at this factory's
|
|
269
|
+
// FUNDAMENTAL_DIMENSION — ws-dse-3d AC4: byte-identical formula/value to
|
|
270
|
+
// `setupTunnelLab.MAX_USEFUL_WAVELENGTH` (both derive from
|
|
271
|
+
// `SustainedSource.capacityAtDimension`), so `lightsource-lab/index.html`
|
|
272
|
+
// reads THIS instead of hand-typing "16" — matched controls on both pages.
|
|
273
|
+
setupLightSourceLab.FUNDAMENTAL_DIMENSION = FUNDAMENTAL_DIMENSION;
|
|
274
|
+
setupLightSourceLab.CAPACITY = LIGHTSOURCE_LAB_CAPACITY;
|
|
275
|
+
setupLightSourceLab.MAX_USEFUL_WAVELENGTH = LIGHTSOURCE_LAB_CAPACITY;
|
|
276
|
+
|
|
277
|
+
setupLightSourceLab.OPTIONS = OPTIONS;
|
|
278
|
+
|
|
279
|
+
export { setupLightSourceLab };
|
|
280
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupOscillator — oscillator (EGPT Fractal) experiment factory.
|
|
5
|
+
*
|
|
6
|
+
* Builds a three-level frame hierarchy: observer > oscillator > quantum.
|
|
7
|
+
* The quantum does a random walk inside the oscillator while compress()
|
|
8
|
+
* pulls it toward the center, creating stable oscillation. The oscillator
|
|
9
|
+
* moves inside the observer, producing an emergent wave pattern.
|
|
10
|
+
*
|
|
11
|
+
* Uses the STANDARD JS-reference interface (Frame.makeLeafFrame, dimension.addFrame,
|
|
12
|
+
* dimension.createFrameFromRect via EgptFractal). The shim's boundary Dimension
|
|
13
|
+
* covers these: JS engine forwards natively; WASM engine logs+null for APIs not
|
|
14
|
+
* yet implemented in Rust and degrades gracefully.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
18
|
+
import { EgptFractal } from '../objects/EgptFractal.js';
|
|
19
|
+
|
|
20
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
21
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
22
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
23
|
+
|
|
24
|
+
// ── Imports ─────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
// ── Setup Factory ───────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create the oscillator (EGPT Fractal) experiment.
|
|
30
|
+
*
|
|
31
|
+
* The canvas bounds come from `universe.universe_rect` — the universe owns its rect.
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} universe - The engine universe instance (JS or WASM boundary object)
|
|
34
|
+
* @param {Object} options - Configuration
|
|
35
|
+
* @returns {Object} { fractal, quantum, oscillator, observer }
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
39
|
+
* Exported as `setupOscillator.OPTIONS` so a composing caller consumes it instead of
|
|
40
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
41
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
42
|
+
*/
|
|
43
|
+
var OPTIONS = [];
|
|
44
|
+
|
|
45
|
+
function setupOscillator(universe, options) {
|
|
46
|
+
options = _assertSetupOptions('setupOscillator', options, OPTIONS);
|
|
47
|
+
|
|
48
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
49
|
+
var canvas_rect = universe.universe_rect;
|
|
50
|
+
_requireWorldRect('setupOscillator universe.universe_rect', canvas_rect);
|
|
51
|
+
|
|
52
|
+
var fractal = new EgptFractal(universe, canvas_rect);
|
|
53
|
+
fractal.setupFractal();
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
fractal: fractal,
|
|
57
|
+
quantum: fractal.quanta[0],
|
|
58
|
+
oscillator: fractal.oscillator,
|
|
59
|
+
observer: fractal.observer_frame
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
setupOscillator.OPTIONS = OPTIONS;
|
|
66
|
+
|
|
67
|
+
export { setupOscillator };
|
|
68
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupParticleWalk — Engine-agnostic particle walk experiment factory.
|
|
5
|
+
* Emits particles rightward from canvas center with wave-walk behavior.
|
|
6
|
+
*
|
|
7
|
+
* Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
11
|
+
import { QuantumEmitter } from '../objects/QuantumEmitter.js';
|
|
12
|
+
|
|
13
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
14
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
15
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
16
|
+
|
|
17
|
+
// ── Imports ─────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
// ── Setup Factory ───────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a particle walk experiment.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd
|
|
25
|
+
* @param {Object} [options] - Configuration overrides
|
|
26
|
+
* @param {number} [options.fundamentalDimension=0] - Base dimension
|
|
27
|
+
* @param {number} [options.fundamentalWaveLength=64] - Desired wavelength
|
|
28
|
+
* @param {number} [options.velocity=1] - Emission velocity
|
|
29
|
+
* @returns {Object} { emitter, largeObjects }
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
33
|
+
* Exported as `setupParticleWalk.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', 'fundamentalWaveLength', 'velocity',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function setupParticleWalk(universe, options) {
|
|
42
|
+
options = _assertSetupOptions('setupParticleWalk', 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('setupParticleWalk universe.universe_rect', canvas_rect);
|
|
47
|
+
|
|
48
|
+
var fundamentalDimension = options.fundamentalDimension !== undefined ? options.fundamentalDimension : 0;
|
|
49
|
+
var desiredWaveLength = options.fundamentalWaveLength !== undefined ? options.fundamentalWaveLength : 64;
|
|
50
|
+
var velocity = options.velocity !== undefined ? options.velocity : 1;
|
|
51
|
+
|
|
52
|
+
var waveLengthMultiplier = Math.floor(desiredWaveLength / 4);
|
|
53
|
+
waveLengthMultiplier = Math.max(1, waveLengthMultiplier);
|
|
54
|
+
|
|
55
|
+
var vx = velocity;
|
|
56
|
+
var vy = 0;
|
|
57
|
+
|
|
58
|
+
// Init universe: ground-up mode, brownianMotion:false for the wave branch
|
|
59
|
+
universe.init(fundamentalDimension, { wavelengthScale: waveLengthMultiplier, brownianMotion: false });
|
|
60
|
+
universe.minMBFillRate = 0.2;
|
|
61
|
+
|
|
62
|
+
var light_source_center_y = Math.floor(canvas_rect.h / 2);
|
|
63
|
+
var light_source_center_x = Math.floor(canvas_rect.w / 2);
|
|
64
|
+
var light_source_height = 10;
|
|
65
|
+
|
|
66
|
+
// Create emitter at center, firing rightward
|
|
67
|
+
var sourceRect = [light_source_center_x, light_source_center_y, 1, light_source_height];
|
|
68
|
+
|
|
69
|
+
var emitter = new QuantumEmitter(
|
|
70
|
+
universe,
|
|
71
|
+
[sourceRect],
|
|
72
|
+
vx, vy,
|
|
73
|
+
20, // burst_size
|
|
74
|
+
1, // charge = NEGATIVE (1)
|
|
75
|
+
true, // auto_fire
|
|
76
|
+
false, // not random direction
|
|
77
|
+
Infinity, // quantum_limit
|
|
78
|
+
0, // delay
|
|
79
|
+
false // wrap = false (particles die at canvas boundary)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
emitter: emitter,
|
|
84
|
+
largeObjects: []
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
setupParticleWalk.OPTIONS = OPTIONS;
|
|
91
|
+
|
|
92
|
+
export { setupParticleWalk };
|
|
93
|
+
|