@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,411 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DirectedTunnel — a directed-behavior convenience over Tube (T1, 2026-07-13,
|
|
5
|
+
* "EXTEND Tube, do not fork a sibling").
|
|
6
|
+
*
|
|
7
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* DirectedTunnel does NOT re-derive wall construction — it CONSTRUCTS a `Tube`
|
|
10
|
+
* (the ONE six-face-construction authority, #61 "ALL SIX FACES, ONE PATH") and
|
|
11
|
+
* then, post-construction, applies:
|
|
12
|
+
* 1. An opt-in `options.behavior` routed through `slitLinerScatter.js`'s
|
|
13
|
+
* composable authority (the SAME one-authority, no-fork rule the DSE liner
|
|
14
|
+
* already uses — report §4.4.4) onto the TOP/BOTTOM walls (the long,
|
|
15
|
+
* quantum-facing bounce surfaces a beam/scatter population actually
|
|
16
|
+
* interacts with repeatedly — report §3's own measured tunnel geometry).
|
|
17
|
+
* 2. When `behavior:'option1'`, registers the host-trig redirect
|
|
18
|
+
* (`directedRedirectProgram.js`) and exposes `this.applyRedirect(tickData)`
|
|
19
|
+
* for the CALLER to invoke once per tick (mirrors `DetectorWall.prototype.
|
|
20
|
+
* getCollisionHistogram(tickData)` — this is NOT a `universe.
|
|
21
|
+
* experimentTickFunctions` push; see `directedRedirectProgram.js`'s own
|
|
22
|
+
* docstring for why Phase-A timing is wrong for this mechanism).
|
|
23
|
+
*
|
|
24
|
+
* ── `options.behavior` — the two report-defined modes + the full composable set ──
|
|
25
|
+
*
|
|
26
|
+
* - `'option2'` (DEFAULT) — pure reflection, NATIVE `BOUNCE` (report §3's own
|
|
27
|
+
* mechanism; `REFLECT_*` is a no-op on the canonical engine — never used).
|
|
28
|
+
* This is Tube's OWN pre-existing default (`collisionType:'BOUNCE'`) —
|
|
29
|
+
* `behavior:'option2'` changes NOTHING; it exists so callers can name the
|
|
30
|
+
* report's mode explicitly rather than relying on Tube's implicit default.
|
|
31
|
+
* - `'option1'` — host-trig directed/isotropic redirect (report §2, path (c)):
|
|
32
|
+
* the TOP/BOTTOM walls are rebuilt with `collisionType:NONE, actions:[BLOCK]`
|
|
33
|
+
* (pure in-engine eject, ZERO velocity touch — `slitLinerScatter.js`'s
|
|
34
|
+
* `'ejectY'` behavior, chosen SPECIFICALLY because it has no velocity ops at
|
|
35
|
+
* all, so the host redirect below is the ONLY velocity write) via
|
|
36
|
+
* `SlitLinerScatter.applyLinerScatter`, and a `directedRedirectProgram.js`
|
|
37
|
+
* applier is built for `[topWall.handle, bottomWall.handle]` at `orientation`
|
|
38
|
+
* (see below) with `options.redirectSpread` (degrees, default 0 = fully
|
|
39
|
+
* directed) as the isotropic-spread sub-option. Requires `options.
|
|
40
|
+
* collectCollided` semantics — DirectedTunnel ALWAYS passes
|
|
41
|
+
* `collectCollided:true` to the underlying Tube when `behavior==='option1'`
|
|
42
|
+
* (needed for `tickData.objects[handle].frames` to exist at all; a no-op
|
|
43
|
+
* otherwise, so this is safe to always set for this one mode).
|
|
44
|
+
* - Any other string in `SlitLinerScatter.BEHAVIORS` (`'randomXY'|'symmetric'|
|
|
45
|
+
* 'bounce'|'ejectY'|'ejectYReflect'`) — routes DIRECTLY through the SAME
|
|
46
|
+
* composable authority (the "any liner-behavior composition" case, report
|
|
47
|
+
* §4.4.4) — gives a Tube's own walls the full DSE-liner scatter vocabulary,
|
|
48
|
+
* unifying the two composable surfaces. `options.resetLocalTime` (default
|
|
49
|
+
* false) is the SAME orthogonal axis `slitLinerScatter.js` already defines.
|
|
50
|
+
* - Unknown string — throws (FAIL-LOUD, never a silent no-op).
|
|
51
|
+
*
|
|
52
|
+
* ── `options.orientation` — v1 semantics (open; DOCUMENT LOUDLY) ──────────────────────
|
|
53
|
+
*
|
|
54
|
+
* `orientation` (degrees) is the direction the tunnel DIRECTS TOWARDS — it drives
|
|
55
|
+
* the DIRECTION machinery EXACTLY, at ANY angle, NEVER snapped:
|
|
56
|
+
* - the host-trig redirect's `angleRad` (`option1`'s `directedRedirectProgram`),
|
|
57
|
+
* - the auto-wired emitter's initial `(vx,vy) = (cos(orientation), sin(orientation))
|
|
58
|
+
* * speed` (when `options.emitter` is also supplied — see `this.emitter` below).
|
|
59
|
+
*
|
|
60
|
+
* The tunnel BODY, however, is a hard AABB (axis-aligned) construction — Tube's
|
|
61
|
+
* OWN structural convention is "long axis always X" (its own module docstring):
|
|
62
|
+
* `capNegX`/`capPosX` are the ONLY end-cap axis Tube can build; there is no
|
|
63
|
+
* `capNegY`/`capPosY`. **v1 LIMITATION, stated plainly:** the tunnel body is
|
|
64
|
+
* THEREFORE ALWAYS constructed X-long in v1 — `orientation`'s "snap to the
|
|
65
|
+
* nearest axis-aligned orientation" trivially resolves to X (the only axis Tube
|
|
66
|
+
* can cap) for EVERY input angle. A genuine Y/Z-long BODY (a real closed breech
|
|
67
|
+
* positioned at a Y or Z extreme) is NOT achievable without new Tube geometry
|
|
68
|
+
* (adding `capNegY`/`capPosY`) and is explicitly OUT OF SCOPE here — it
|
|
69
|
+
* is sugar-layer work, not engine work, but it is a materially larger addition
|
|
70
|
+
* than "opt-in default-off params," so it is deliberately deferred, not silently
|
|
71
|
+
* claimed. The DIRECTION (redirect angle / exit vector / emitter velocity) is the
|
|
72
|
+
* part that is genuinely free and exact at any angle — this is what "honors the
|
|
73
|
+
* exact angle" means in practice: a 30° tunnel has an X-long BODY (unaffected by
|
|
74
|
+
* the 90°-snap rule, since 30° would round toward 0° anyway) whose beam/redirect
|
|
75
|
+
* travels at EXACTLY 30°, not 0°.
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} universe - Already-init'd universe (JS or WASM).
|
|
78
|
+
* @param {Object} options
|
|
79
|
+
* @param {number} options.left, options.top, [options.z=0] - Interior origin corner
|
|
80
|
+
* (same convention as `Tube`).
|
|
81
|
+
* @param {number} options.length - Interior X extent (the tunnel's long/travel axis
|
|
82
|
+
* in v1 — see the orientation note above). Maps onto `Tube`'s `interiorWidth`.
|
|
83
|
+
* @param {number} options.width - Interior Y extent (bore cross-section). Maps onto
|
|
84
|
+
* `Tube`'s `interiorHeight`.
|
|
85
|
+
* @param {number} [options.depth] - Interior Z extent (3D cross-section). Omitted
|
|
86
|
+
* (default) -> `zContain:false` passed to `Tube` (a flat 2D-in-3D bore, no NEAR/
|
|
87
|
+
* FAR walls — byte-identical to a 2D scene); a positive value -> `zContain:true`,
|
|
88
|
+
* `depth` maps onto `Tube`'s `interiorDepth`.
|
|
89
|
+
* @param {number} [options.orientation=0] - Direction in DEGREES — see the note
|
|
90
|
+
* above. `0` = +X (Tube's native convention, unaffected).
|
|
91
|
+
* @param {'option1'|'option2'|'randomXY'|'symmetric'|'bounce'|'ejectY'|'ejectYReflect'}
|
|
92
|
+
* [options.behavior='option2'] - see the behavior note above.
|
|
93
|
+
* @param {boolean} [options.resetLocalTime=false] - orthogonal axis for the
|
|
94
|
+
* composable-behavior case (ignored for `'option1'`/`'option2'`).
|
|
95
|
+
* @param {number} [options.redirectSpread=0] - degrees; `'option1'`'s isotropic-
|
|
96
|
+
* spread sub-option (0 = fully directed).
|
|
97
|
+
* @param {number|null} [options.redirectSpeed=null] - `'option1'`'s redirected
|
|
98
|
+
* speed magnitude; `null` (default) preserves each frame's own incoming speed.
|
|
99
|
+
* @param {Object|null} [options.emitter=null] - passed straight through to `Tube`'s
|
|
100
|
+
* own `options.emitter` (requires `options.capNegX:true`), EXCEPT `vx`/`vy` are
|
|
101
|
+
* derived from `orientation`+`speed` when the caller omits them explicitly (a
|
|
102
|
+
* directed-tunnel emitter fires ALONG the tunnel's own direction by default).
|
|
103
|
+
* @param {...*} [options.*] - every other `Tube` option (`capNegX`, `capPosX`,
|
|
104
|
+
* `wallThickness`, `color`, `wallOpacity`, `sinkOpenEnds`, `collectCollided`,
|
|
105
|
+
* `observedDimensions`) passes straight through, UNCHANGED semantics.
|
|
106
|
+
*
|
|
107
|
+
* @property {Object} tube - the underlying `Tube` instance (walls/sinks/innerRect).
|
|
108
|
+
* @property {Array<LargeObject>} walls - alias for `this.tube.walls`.
|
|
109
|
+
* @property {Object|null} emitter - alias for `this.tube.emitter`.
|
|
110
|
+
* @property {Function} applyRedirect - ALWAYS present (C1 fix), regardless of the current `behavior`
|
|
111
|
+
* — a redirect applier is cheap to build and this lets a LIVE `updateBehavior`
|
|
112
|
+
* switch INTO 'option1' work without reconstruction. Prefer
|
|
113
|
+
* `applyRedirectIfActive(tickData)` (below) over calling this directly — it
|
|
114
|
+
* self-gates on `this.behavior === 'option1'` so a caller never needs its own
|
|
115
|
+
* `currentBehavior === 'option1'` check (the exact omission that caused C1).
|
|
116
|
+
* @property {string} behavior - the CURRENT behavior; kept live-accurate by
|
|
117
|
+
* `updateBehavior()` (never mutate directly).
|
|
118
|
+
*
|
|
119
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
120
|
+
*
|
|
121
|
+
* DirectedTunnel adds ZERO new physics (canonical-bench SU1): `option2` is Tube's
|
|
122
|
+
* pre-existing native BOUNCE; the composable-BEHAVIORS case is the SAME op-programs
|
|
123
|
+
* `slitLinerScatter.js` already proves byte-identical to the DSE liner's own
|
|
124
|
+
* construction; `option1`'s redirect is the MEASURED path (c) — zero (or
|
|
125
|
+
* bounded <=1px) extra latency vs a native mechanism, confirmed empirically
|
|
126
|
+
* (Test A/B).
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
import { Tube } from './Tube.js';
|
|
130
|
+
import * as SlitLinerScatter from './slitLinerScatter.js';
|
|
131
|
+
import * as DirectedRedirectProgram from './directedRedirectProgram.js';
|
|
132
|
+
import { CollisionTypes } from '../../parts/CollisionTypes.js';
|
|
133
|
+
|
|
134
|
+
// ── Imports ─────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
var OPTION1 = 'option1';
|
|
139
|
+
var OPTION2 = 'option2';
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {Object} universe
|
|
143
|
+
* @param {Object} options - see module docstring above.
|
|
144
|
+
*/
|
|
145
|
+
function DirectedTunnel(universe, options) {
|
|
146
|
+
options = options || {};
|
|
147
|
+
|
|
148
|
+
this.universe = universe;
|
|
149
|
+
this._schemaKind = 'DirectedTunnel';
|
|
150
|
+
|
|
151
|
+
var length = options.length;
|
|
152
|
+
var width = options.width;
|
|
153
|
+
if (!(length > 0) || !(width > 0)) {
|
|
154
|
+
throw new Error('[DirectedTunnel] options.length and options.width must both be positive numbers.');
|
|
155
|
+
}
|
|
156
|
+
var hasDepth = (options.depth !== undefined && options.depth !== null);
|
|
157
|
+
var depth = hasDepth ? options.depth : width; // placeholder — Tube requires interiorDepth > 0 even when zContain:false
|
|
158
|
+
var zContain = hasDepth;
|
|
159
|
+
// The SAME N1-fix-precedent
|
|
160
|
+
// gate `hasRealDepth` uses elsewhere in this file (polarSpreadRad) — stored
|
|
161
|
+
// for `_applyBehaviorToWalls`'s composable-behavior branch below, which
|
|
162
|
+
// threads it into `SlitLinerScatter.applyLinerScatter`'s new `hasRealDepth`
|
|
163
|
+
// opt-in (3D randomXY/symmetric scatter). zContain:false (every 2D tunnel,
|
|
164
|
+
// every pre-existing caller) -> stays false -> byte-identical.
|
|
165
|
+
this._hasRealDepth = zContain;
|
|
166
|
+
|
|
167
|
+
var orientationDeg = options.orientation !== undefined ? options.orientation : 0;
|
|
168
|
+
var orientationRad = orientationDeg * Math.PI / 180;
|
|
169
|
+
this.orientation = orientationDeg;
|
|
170
|
+
|
|
171
|
+
// v1 BODY SNAP (see module docstring "options.orientation" note): Tube can
|
|
172
|
+
// ONLY build end-caps on its own X axis, so the body is ALWAYS constructed
|
|
173
|
+
// X-long in v1 regardless of the requested orientation — the "snap to the
|
|
174
|
+
// nearest axis-aligned orientation" is trivially satisfied by this single
|
|
175
|
+
// fixed choice (documented loudly above; NOT silently narrower than implied).
|
|
176
|
+
this.bodyAxis = 'X';
|
|
177
|
+
|
|
178
|
+
var behavior = options.behavior !== undefined ? options.behavior : OPTION2;
|
|
179
|
+
var resetLocalTime = !!options.resetLocalTime;
|
|
180
|
+
var redirectSpreadDeg = options.redirectSpread !== undefined ? options.redirectSpread : 0;
|
|
181
|
+
var redirectSpeed = (options.redirectSpeed !== undefined) ? options.redirectSpeed : null;
|
|
182
|
+
|
|
183
|
+
var validComposable = SlitLinerScatter.BEHAVIORS.indexOf(behavior) !== -1;
|
|
184
|
+
if (behavior !== OPTION1 && behavior !== OPTION2 && !validComposable) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
'[DirectedTunnel] unknown behavior "' + behavior + '". Expected: option1 | option2 | ' +
|
|
187
|
+
SlitLinerScatter.BEHAVIORS.join(' | ')
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Emitter: derive vx/vy from orientation+speed when the caller omits them ──
|
|
192
|
+
var tubeEmitterOpt = null;
|
|
193
|
+
if (options.emitter) {
|
|
194
|
+
tubeEmitterOpt = {};
|
|
195
|
+
for (var k in options.emitter) { if (Object.prototype.hasOwnProperty.call(options.emitter, k)) { tubeEmitterOpt[k] = options.emitter[k]; } }
|
|
196
|
+
if (tubeEmitterOpt.vx === undefined && tubeEmitterOpt.vy === undefined) {
|
|
197
|
+
var espeed = tubeEmitterOpt.speed !== undefined ? tubeEmitterOpt.speed : 1;
|
|
198
|
+
tubeEmitterOpt.vx = Math.cos(orientationRad) * espeed;
|
|
199
|
+
tubeEmitterOpt.vy = Math.sin(orientationRad) * espeed;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Construct the underlying Tube (the ONE six-face-construction authority) ──
|
|
204
|
+
var tubeOpts = {
|
|
205
|
+
left: options.left, top: options.top, z: options.z,
|
|
206
|
+
interiorWidth: length, interiorHeight: width, interiorDepth: depth,
|
|
207
|
+
wallThickness: options.wallThickness,
|
|
208
|
+
capNegX: options.capNegX, capPosX: options.capPosX,
|
|
209
|
+
collisionType: options.collisionType, // Tube's own default (BOUNCE) applies unless option1 overrides TOP/BOTTOM below
|
|
210
|
+
actions: options.actions,
|
|
211
|
+
observedDimensions: options.observedDimensions,
|
|
212
|
+
color: options.color,
|
|
213
|
+
zContain: zContain,
|
|
214
|
+
wallOpacity: options.wallOpacity,
|
|
215
|
+
sinkOpenEnds: options.sinkOpenEnds,
|
|
216
|
+
// C1 FIX (2026-07-13): ALWAYS true, regardless of the INITIAL behavior —
|
|
217
|
+
// this.applyRedirect is now ALSO always built (below), so a LATER live
|
|
218
|
+
// switch INTO 'option1' (updateBehavior) needs tickData.objects[handle]
|
|
219
|
+
// .frames to already exist on TOP/BOTTOM; matches the DSE-lab tunnels'
|
|
220
|
+
// own "always collect" design for the SAME reason. Cheap (a collection
|
|
221
|
+
// buffer with no consumer is a no-op physics-wise) and DirectedTunnel has
|
|
222
|
+
// no OTHER existing callers to keep byte-identical against (T1-new).
|
|
223
|
+
collectCollided: true,
|
|
224
|
+
emitter: tubeEmitterOpt,
|
|
225
|
+
};
|
|
226
|
+
this.tube = new Tube(universe, tubeOpts);
|
|
227
|
+
this.walls = this.tube.walls;
|
|
228
|
+
this.sinks = this.tube.sinks;
|
|
229
|
+
this.innerRect = this.tube.innerRect;
|
|
230
|
+
this.emitter = this.tube.emitter;
|
|
231
|
+
this.largeObjects = this.tube.largeObjects;
|
|
232
|
+
|
|
233
|
+
this._topWall = this.walls.filter(function (w) { return w.name === 'TOP'; })[0];
|
|
234
|
+
this._bottomWall = this.walls.filter(function (w) { return w.name === 'BOTTOM'; })[0];
|
|
235
|
+
|
|
236
|
+
// C1 FIX: the host redirect applier is ALWAYS
|
|
237
|
+
// built (regardless of the INITIAL behavior) — mirrors the DSE-lab tunnels'
|
|
238
|
+
// OWN "always collectCollided:true + always an applyRedirect fallback" design
|
|
239
|
+
// (needed so a LIVE switch INTO option1 works with no reconstruction). Kept
|
|
240
|
+
// here too — not just at the Lab layer — so ANY consumer of DirectedTunnel
|
|
241
|
+
// gets the SAME guarantee "for free", not just the Lab's own hand-rolled path.
|
|
242
|
+
var wallHandles = [];
|
|
243
|
+
if (this._topWall) { wallHandles.push(this._topWall.handle); }
|
|
244
|
+
if (this._bottomWall) { wallHandles.push(this._bottomWall.handle); }
|
|
245
|
+
this.applyRedirect = DirectedRedirectProgram.buildRedirectApplier(universe, {
|
|
246
|
+
wallHandles: wallHandles,
|
|
247
|
+
angleRad: orientationRad,
|
|
248
|
+
spreadRad: redirectSpreadDeg * Math.PI / 180,
|
|
249
|
+
speed: redirectSpeed,
|
|
250
|
+
// 3D EXTENSION (§4 option (c)): when this tunnel
|
|
251
|
+
// has REAL z-containment armed (zContain, the SAME N1-fix-precedent
|
|
252
|
+
// gate `hasRealDepth` uses elsewhere — never innerRect.d, always a
|
|
253
|
+
// positive placeholder), mirror the azimuth spread's own magnitude
|
|
254
|
+
// onto the polar (z) axis too — a redirected population scatters on
|
|
255
|
+
// the unit SPHERE, not just the XY plane. zContain:false (every 2D
|
|
256
|
+
// tunnel, every pre-existing caller) -> polarSpreadRad stays exactly
|
|
257
|
+
// 0 -> byte-identical to the pre-3D 2-axis redirect formula.
|
|
258
|
+
polarSpreadRad: zContain ? (redirectSpreadDeg * Math.PI / 180) : 0,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// 'option2' at CONSTRUCTION time needs NO extra push — Tube's own native
|
|
262
|
+
// collisionType (default BOUNCE) already IS option2's target state (zero
|
|
263
|
+
// additional code path, matching the pre-C1-fix behavior exactly). Every
|
|
264
|
+
// OTHER initial behavior (option1 / a composable name) DOES need its
|
|
265
|
+
// construction-time push, same as before. `updateBehavior` (a LIVE switch,
|
|
266
|
+
// possibly INTO option2 from something else) always dispatches all 3 cases.
|
|
267
|
+
if (behavior !== OPTION2) { this._applyBehaviorToWalls(behavior, resetLocalTime); }
|
|
268
|
+
this.behavior = behavior;
|
|
269
|
+
this.resetLocalTime = resetLocalTime;
|
|
270
|
+
|
|
271
|
+
// ── THE ONE UP-LINK on every wall this corridor composed ──────────────────
|
|
272
|
+
for (var gi = 0; gi < this.walls.length; gi++) {
|
|
273
|
+
this.walls[gi]._owner = this; // THE ONE UP-LINK
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Apply ONE behavior to this tunnel's TOP/BOTTOM walls — the SAME three-
|
|
279
|
+
* mechanism dispatch the constructor uses (private; both the constructor and
|
|
280
|
+
* `updateBehavior` below call this ONE implementation, never a second copy).
|
|
281
|
+
* @param {string} behavior
|
|
282
|
+
* @param {boolean} resetLocalTime
|
|
283
|
+
*/
|
|
284
|
+
DirectedTunnel.prototype._applyBehaviorToWalls = function (behavior, resetLocalTime) {
|
|
285
|
+
var universe = this.universe;
|
|
286
|
+
var topWall = this._topWall, bottomWall = this._bottomWall;
|
|
287
|
+
if (behavior === OPTION1) {
|
|
288
|
+
// In-engine half: pure eject, ZERO velocity touch (ejectY has no velocity
|
|
289
|
+
// ops at all) — the host redirect (this.applyRedirect, ALWAYS present) is
|
|
290
|
+
// the ONLY velocity write once this mode is active.
|
|
291
|
+
if (topWall) { SlitLinerScatter.applyLinerScatter(topWall, universe, 'bottom', { behavior: 'ejectY', resetLocalTime: false }); }
|
|
292
|
+
if (bottomWall) { SlitLinerScatter.applyLinerScatter(bottomWall, universe, 'top', { behavior: 'ejectY', resetLocalTime: false }); }
|
|
293
|
+
} else if (behavior === OPTION2) {
|
|
294
|
+
// Native BOUNCE — LargeObject's OWN recompileActions (the S4 mechanism
|
|
295
|
+
// every collision_type/actions live edit already uses), NOT a
|
|
296
|
+
// slitLinerScatter push (native BOUNCE has no eject-prefix op-program
|
|
297
|
+
// shape to route through).
|
|
298
|
+
if (topWall) { topWall.recompileActions({ collisionType: 'BOUNCE', actions: [] }); }
|
|
299
|
+
if (bottomWall) { bottomWall.recompileActions({ collisionType: 'BOUNCE', actions: [] }); }
|
|
300
|
+
} else if (SlitLinerScatter.BEHAVIORS.indexOf(behavior) !== -1) {
|
|
301
|
+
// hasRealDepth threads THIS tunnel's own zContain (set
|
|
302
|
+
// at construction, never re-derived) into the composable scatter
|
|
303
|
+
// behaviors — a randomXY/symmetric switch on a real-depth tunnel now
|
|
304
|
+
// scatters all 3 axes, matching option2's (native BOUNCE) own
|
|
305
|
+
// already-correct NEAR/FAR handling.
|
|
306
|
+
if (topWall) { SlitLinerScatter.applyLinerScatter(topWall, universe, 'bottom', { behavior: behavior, resetLocalTime: resetLocalTime, hasRealDepth: this._hasRealDepth }); }
|
|
307
|
+
if (bottomWall) { SlitLinerScatter.applyLinerScatter(bottomWall, universe, 'top', { behavior: behavior, resetLocalTime: resetLocalTime, hasRealDepth: this._hasRealDepth }); }
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error(
|
|
310
|
+
'[DirectedTunnel] unknown behavior "' + behavior + '". Expected: option1 | option2 | ' +
|
|
311
|
+
SlitLinerScatter.BEHAVIORS.join(' | ')
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* LIVE behavior switch (C1 fix follow-through, 2026-07-13) — the STRUCTURAL
|
|
318
|
+
* home for "how does a DirectedTunnel switch its collision behavior live,
|
|
319
|
+
* across all 3 underlying mechanisms (option1 host-redirect / option2 native
|
|
320
|
+
* BOUNCE / a slitLinerScatter composable name)". Moved OFF the DSE Lab page
|
|
321
|
+
* (which used to hand-roll this by reaching into `tunnel.walls` directly) and
|
|
322
|
+
* ONTO DirectedTunnel itself so `this.behavior` NEVER goes stale after a live
|
|
323
|
+
* switch — the ONE thing that makes `applyRedirectIfActive` (below) safe to
|
|
324
|
+
* call unconditionally from ANY consumer's per-tick loop, not just the Lab's.
|
|
325
|
+
* @param {string} newBehavior
|
|
326
|
+
* @param {boolean} [resetLocalTime=false] - composable-BEHAVIORS-name case only.
|
|
327
|
+
*/
|
|
328
|
+
DirectedTunnel.prototype.updateBehavior = function (newBehavior, resetLocalTime) {
|
|
329
|
+
resetLocalTime = !!resetLocalTime;
|
|
330
|
+
this._applyBehaviorToWalls(newBehavior, resetLocalTime);
|
|
331
|
+
this.behavior = newBehavior;
|
|
332
|
+
this.resetLocalTime = resetLocalTime;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* C1 FIX — the redirect applier is a plain
|
|
337
|
+
* function the CALLER invokes once per tick with that tick's `tickData`
|
|
338
|
+
* (directedRedirectProgram.js's own docstring explains why this can never be
|
|
339
|
+
* a self-registering `experimentTickFunctions` push: it needs THIS tick's
|
|
340
|
+
* Phase-D `tickData.objects[handle].frames`, which does not exist until
|
|
341
|
+
* AFTER `doTick()` returns — i.e. AFTER `experimentTickFunctions` already
|
|
342
|
+
* ran). Since there is no generic "call me after every tick with tickData"
|
|
343
|
+
* registry a sugar object can self-register into (FrqtlApp exposes
|
|
344
|
+
* exactly ONE user-supplied `onTick` slot, matching the established
|
|
345
|
+
* `DetectorWall.prototype.getCollisionHistogram(tickData)` precedent — a
|
|
346
|
+
* caller-invoked method, not a hook), the caller MUST still call something,
|
|
347
|
+
* every tick. `applyRedirectIfActive` is that ONE call: it self-gates on
|
|
348
|
+
* `this.behavior` (kept live-accurate by `updateBehavior` above), so a
|
|
349
|
+
* consumer's onTick can call this UNCONDITIONALLY for every tunnel, every
|
|
350
|
+
* tick, regardless of which behavior is currently active — the exact
|
|
351
|
+
* "forgetting to gate on option1" mistake (this C1 bug) becomes structurally
|
|
352
|
+
* impossible to repeat.
|
|
353
|
+
* @param {Object} tickData - this tick's TickData (from `universe.doTick()`).
|
|
354
|
+
*/
|
|
355
|
+
DirectedTunnel.prototype.applyRedirectIfActive = function (tickData) {
|
|
356
|
+
if (this.behavior === OPTION1) { this.applyRedirect(tickData); }
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// BEHAVIORS: the canonical, complete list of legal `options.behavior` values —
|
|
360
|
+
// 'option1'/'option2' (the report's two modes) PLUS every slitLinerScatter
|
|
361
|
+
// composable name. ONE authority for any consumer (e.g. a control-panel select)
|
|
362
|
+
// that needs to enumerate the full set — never hand-typed (T3, 2026-07-13).
|
|
363
|
+
DirectedTunnel.BEHAVIORS = [OPTION1, OPTION2].concat(SlitLinerScatter.BEHAVIORS);
|
|
364
|
+
|
|
365
|
+
// BEHAVIOR_META (option1/option2 are non-descript names): a DISPLAY-ONLY
|
|
366
|
+
// label/description per canonical
|
|
367
|
+
// `options.behavior` VALUE — the wire value ('option1'/'option2'/composable
|
|
368
|
+
// name) is UNCHANGED (every existing caller — the tunnel/lightsource labs and
|
|
369
|
+
// every test — keeps passing/reading the
|
|
370
|
+
// SAME strings; renaming the wire value itself would ripple into the DSE Lab,
|
|
371
|
+
// which this drive explicitly scopes OUT — "a SEPARATE later loop"). ONE
|
|
372
|
+
// authority for any consumer building a control-panel select: read
|
|
373
|
+
// `BEHAVIOR_META[value].label` for the human-facing option text and
|
|
374
|
+
// `.description` for a tooltip — never hand-type either (tunnel-lab and
|
|
375
|
+
// lightsource-lab's own `<select id="ctrl-wallbehavior">` population loops
|
|
376
|
+
// consume this map).
|
|
377
|
+
DirectedTunnel.BEHAVIOR_META = {
|
|
378
|
+
option1: {
|
|
379
|
+
label: 'laserBeam',
|
|
380
|
+
description: 'Directed host-trig redirect — exits along the tunnel’s own direction, laser-like (collimated).'
|
|
381
|
+
},
|
|
382
|
+
option2: {
|
|
383
|
+
label: 'reflect',
|
|
384
|
+
description: 'Native BOUNCE — pure elastic ping-pong reflection off the walls (Tube’s own default).'
|
|
385
|
+
},
|
|
386
|
+
randomXY: {
|
|
387
|
+
label: 'randomXY',
|
|
388
|
+
description: 'Eject + vx=random() (always forward), vy symmetric (+ vz symmetric on a real-depth tunnel); charge redrawn.'
|
|
389
|
+
},
|
|
390
|
+
symmetric: {
|
|
391
|
+
label: 'symmetric',
|
|
392
|
+
description: 'Omnidirectional/unpolarized input — eject + vx AND vy both symmetric (+ vz symmetric on a real-depth tunnel; no forward bias); charge redrawn.'
|
|
393
|
+
},
|
|
394
|
+
bounce: {
|
|
395
|
+
label: 'bounce',
|
|
396
|
+
description: 'Eject + vy=-vy only (elastic reflection); no charge redraw.'
|
|
397
|
+
},
|
|
398
|
+
ejectY: {
|
|
399
|
+
label: 'ejectY',
|
|
400
|
+
description: 'Pure eject into the cavity, ZERO velocity writes; no charge redraw.'
|
|
401
|
+
},
|
|
402
|
+
ejectYReflect: {
|
|
403
|
+
label: 'ejectYReflect',
|
|
404
|
+
description: 'ejectY’s eject prefix PLUS vy=-vy; no charge redraw.'
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
export { DirectedTunnel };
|
|
411
|
+
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EgptFractal — JS-only fractal oscillator object.
|
|
5
|
+
*
|
|
6
|
+
* Creates a 3-level frame hierarchy: observer > oscillator > quantum.
|
|
7
|
+
* The quantum does a random walk inside the oscillator frame while compress()
|
|
8
|
+
* pulls it toward the oscillator center, creating a stable oscillation.
|
|
9
|
+
* The oscillator itself moves inside the observer frame, and the perceived
|
|
10
|
+
* motion from the observer's rest frame produces an emergent wave pattern.
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: This object uses devsdk-specific APIs (Frame.makeLeafFrame,
|
|
13
|
+
* dimension.addFrame, dimension.createFrameFromRect) and is NOT portable
|
|
14
|
+
* to the WASM engine. Mark experiments using this as `portable: false`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Rectangle } from '../../parts/Rectangle.js';
|
|
18
|
+
import { engine } from '../../compat/engine.js';
|
|
19
|
+
import { registerKind } from '../kinds.js';
|
|
20
|
+
|
|
21
|
+
// Frame and Charge are ENGINE values: read through engine() when a fractal is built,
|
|
22
|
+
// never at module evaluation (importing this file boots nothing).
|
|
23
|
+
|
|
24
|
+
// ── EgptFractal ─────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Object} universe - The JS engine universe instance (NOT WASM)
|
|
28
|
+
* @param {Object} canvasRect - Rectangle defining the canvas bounds
|
|
29
|
+
*/
|
|
30
|
+
function EgptFractal(universe, canvasRect) {
|
|
31
|
+
this.universe = universe;
|
|
32
|
+
this._schemaKind = 'EgptFractal';
|
|
33
|
+
this.canvas_rect = canvasRect;
|
|
34
|
+
|
|
35
|
+
var framedelta = 3;
|
|
36
|
+
|
|
37
|
+
this.quantum_dimension_number = 2;
|
|
38
|
+
|
|
39
|
+
// Observer dimension: highest power-of-2 exponent that fits canvas
|
|
40
|
+
var minSide = Math.min(canvasRect.w, canvasRect.h);
|
|
41
|
+
var observer_dimension_number = Math.floor(Math.log2(minSide));
|
|
42
|
+
|
|
43
|
+
// Init universe: frameGenMode:'groundUp' (the default); brownianMotion will be set in setupFractal.
|
|
44
|
+
// Brownian is left OFF here (the engine default for groundUp) so the observer
|
|
45
|
+
// exerts no brownian pull until setupFractal turns it on.
|
|
46
|
+
// noEscape:true confines the quantum to the oscillator (and the oscillator to the
|
|
47
|
+
// observer) so the fractal sustains a stable oscillation instead of leaking its
|
|
48
|
+
// quantum. Without it the quantum escapes the oscillator frame (~tick 55) and the
|
|
49
|
+
// hierarchy collapses to the bare observer — not an oscillator. (Lost when the shim
|
|
50
|
+
// contracts were cleaned up; restored 2026-06-23 with the §2a derived-mass fix.)
|
|
51
|
+
this.universe.init(this.quantum_dimension_number, { wavelengthScale: 1, noEscape: true });
|
|
52
|
+
|
|
53
|
+
// Create three dimension layers
|
|
54
|
+
this.quantum_dimension = this.universe.addDimension(this.quantum_dimension_number);
|
|
55
|
+
this.oscillator_dimension = this.universe.addDimension(this.quantum_dimension_number + framedelta);
|
|
56
|
+
this.observer_dimension = this.universe.addDimension(observer_dimension_number);
|
|
57
|
+
|
|
58
|
+
this.quanta = [];
|
|
59
|
+
this.oscillator = null;
|
|
60
|
+
this.observer_frame = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build the 3-level frame hierarchy and configure the universe.
|
|
65
|
+
* Returns an empty array (no large objects).
|
|
66
|
+
*/
|
|
67
|
+
EgptFractal.prototype.setupFractal = function () {
|
|
68
|
+
// Observer rect at canvas origin
|
|
69
|
+
var observer_rect = new Rectangle(
|
|
70
|
+
this.canvas_rect.x, this.canvas_rect.y,
|
|
71
|
+
this.observer_dimension.mb_w, this.observer_dimension.mb_h,
|
|
72
|
+
null, true
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Oscillator offset from observer center
|
|
76
|
+
var oscOffsetX = observer_rect.x - this.observer_dimension.mb_w / 4;
|
|
77
|
+
var oscOffsetY = observer_rect.y;
|
|
78
|
+
var oscillator_rect = new Rectangle(
|
|
79
|
+
oscOffsetX, oscOffsetY,
|
|
80
|
+
this.oscillator_dimension.mb_w, this.oscillator_dimension.mb_h,
|
|
81
|
+
null, true
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Quantum positioned on the lower Y axis of the oscillator
|
|
85
|
+
var pOffsetX = oscillator_rect.x;
|
|
86
|
+
var pOffsetY = oscillator_rect.y + this.oscillator_dimension.mb_h / 4;
|
|
87
|
+
var quantumRect = new Rectangle(
|
|
88
|
+
pOffsetX, pOffsetY,
|
|
89
|
+
this.quantum_dimension.mb_w, this.quantum_dimension.mb_h,
|
|
90
|
+
null, true
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
var eng = engine();
|
|
94
|
+
var quantumFrame = eng.Frame.makeLeafFrame(
|
|
95
|
+
quantumRect.left, quantumRect.top,
|
|
96
|
+
this.quantum_dimension_number, 1, 0, 0, false,
|
|
97
|
+
eng.Charge.POSITIVE, this.canvas_rect
|
|
98
|
+
);
|
|
99
|
+
quantumFrame.name = "quantum";
|
|
100
|
+
this.quanta.push(quantumFrame);
|
|
101
|
+
|
|
102
|
+
// Add quantum to quantum dimension
|
|
103
|
+
for (var i = 0; i < this.quanta.length; i++) {
|
|
104
|
+
this.quantum_dimension.addFrame(this.quanta[i]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The oscillator + observer render as OUTLINE boxes (the quantum renders filled). This is a
|
|
108
|
+
// pure user-space render choice carried in the frame's generic string_map ('render.outline'),
|
|
109
|
+
// NOT a typed engine flag — so both engines transport it identically (the engine never
|
|
110
|
+
// interprets it). The renderer deserializes string_map['render.outline'].
|
|
111
|
+
var OUTLINE_HINT = { 'render.outline': 'true' };
|
|
112
|
+
|
|
113
|
+
// Create oscillator frame containing the quantum
|
|
114
|
+
this.oscillator = this.oscillator_dimension.createFrameFromRect(
|
|
115
|
+
oscillator_rect, this.quanta, 0, 0, true, false, OUTLINE_HINT
|
|
116
|
+
);
|
|
117
|
+
this.oscillator.name = "oscillator";
|
|
118
|
+
|
|
119
|
+
// Create observer frame containing the oscillator
|
|
120
|
+
this.observer_frame = this.observer_dimension.createFrameFromRect(
|
|
121
|
+
observer_rect, [this.oscillator], 0, 0, true, true, OUTLINE_HINT
|
|
122
|
+
);
|
|
123
|
+
this.observer_frame.name = "observer";
|
|
124
|
+
|
|
125
|
+
// Configure universe
|
|
126
|
+
this.universe.leafQuantaWidth = this.quantum_dimension.mb_w;
|
|
127
|
+
this.universe.brownianMotion = true;
|
|
128
|
+
|
|
129
|
+
return [];
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ── The kind (objects/kinds.js): descriptor, construction defaults ──────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* EgptFractal — JS-only 3-level recursive frame hierarchy oscillator.
|
|
136
|
+
* Constructor: EgptFractal(universe, canvasRect)
|
|
137
|
+
* NOT portable to WASM; experiments must mark portable:false.
|
|
138
|
+
*/
|
|
139
|
+
var EgptFractalDescriptor = {
|
|
140
|
+
description: 'A 3-level recursive frame hierarchy that produces emergent wave oscillation. ' +
|
|
141
|
+
'Creates observer > oscillator > quantum dimension layers. The quantum does a random walk ' +
|
|
142
|
+
'inside the oscillator frame; compress() pulls it toward the oscillator center, creating ' +
|
|
143
|
+
'stable oscillation. The oscillator moves inside the observer frame, producing an emergent ' +
|
|
144
|
+
'wave pattern in the observer\'s rest frame. Demonstrates the Frame Axiom at three scales. ' +
|
|
145
|
+
'JS-only: uses Frame.makeLeafFrame and dimension APIs not yet implemented in WASM.',
|
|
146
|
+
fields: [
|
|
147
|
+
{
|
|
148
|
+
name: 'quantum_dimension_number',
|
|
149
|
+
type: 'readonly',
|
|
150
|
+
engineVisible: true,
|
|
151
|
+
description: 'Exponent for the quantum dimension scale (read-only; derived from canvasRect at construction).'
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
registerKind(EgptFractal, {
|
|
157
|
+
from: import.meta.url,
|
|
158
|
+
descriptor: EgptFractalDescriptor
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── Export ───────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
export { EgptFractal };
|
|
164
|
+
|