@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,209 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* emitterProgram.js — the emitter as an engine-AGNOSTIC handle-program (sugar).
|
|
5
|
+
*
|
|
6
|
+
* G-EMIT-2: the emitter is NOT an engine object and NOT a per-engine provider method — it is a
|
|
7
|
+
* host tick-function (a "handle program") composed entirely from the generic engine primitives
|
|
8
|
+
* `universe.random()`, `universe.makeLeafFrame(...)`, and `universe.injectFrame(...)`. Both engines
|
|
9
|
+
* (WASM canonical + the JS reference) expose those primitives identically, so this ONE function is
|
|
10
|
+
* the single source of emit behavior — replacing the former dual per-engine `registerEmitter`
|
|
11
|
+
* implementations (compat/providers + EGPTfrqtl.js).
|
|
12
|
+
*
|
|
13
|
+
* DRAW-ORDER CONTRACT (golden-locked on WASM — do not reorder):
|
|
14
|
+
* per emitted leaf: random()→x, random()→y, [random()→angle if randomDirection],
|
|
15
|
+
* [random()→charge if charge is null], then makeLeafFrame(...) which on WASM
|
|
16
|
+
* draws ONE more internally (the ctor charge-mirror, always). The JS reference
|
|
17
|
+
* makeLeafFrame draws 0 there (charge is passed in) — engines may diverge
|
|
18
|
+
* (WASM is canonical); only the WASM goldens are asserted.
|
|
19
|
+
*
|
|
20
|
+
* OPT-IN 3D z-SPREAD (WS-DSE-3D Bite B0): `em.zMin`/`em.zDepth` default to `null` — when
|
|
21
|
+
* both are absent, ZERO additional draws execute and this tick-fn is byte-identical to the
|
|
22
|
+
* contract above (every existing 2D caller — QuantumEmitter, PointSource — is unaffected).
|
|
23
|
+
* When a caller opts in (`em.zMin` a number), ONE additional `random()` draw executes AFTER
|
|
24
|
+
* `injectFrame` (uniformly distributing the emitted leaf across `[zMin, zMin+zDepth)`), then a
|
|
25
|
+
* `setFrameProperty(qid, 'z', z)` write — the canonical z-placement precedent
|
|
26
|
+
* (a THIRD setFrameProperty call, never inserted into the locked x/y/[angle]/[charge] sequence).
|
|
27
|
+
* `makeLeafFrame` has no z argument — no engine change, this composes existing primitives.
|
|
28
|
+
*
|
|
29
|
+
* OPT-IN 3D ISOTROPIC SCATTER (ws-dse-3d AC1, CEO drive #2 round-2 ruling): `em.zIsotropy`
|
|
30
|
+
* (default falsy) — when BOTH `em.randomDirection` AND `em.zIsotropy` are true, the
|
|
31
|
+
* random-direction draw becomes a full unit-SPHERE direction (azimuth + polar angle,
|
|
32
|
+
* TWO draws replacing the 2D branch's ONE) instead of a 2D-plane circle, and the drawn
|
|
33
|
+
* `vz` is written via a FOURTH `setFrameProperty` call (sharing the SAME qid lookup as
|
|
34
|
+
* the zMin branch above when both are armed). Every existing `randomDirection:true`
|
|
35
|
+
* caller has `zIsotropy` false/undefined and takes the unchanged 2D-circle branch —
|
|
36
|
+
* byte-identical, zero extra draws.
|
|
37
|
+
*
|
|
38
|
+
* The emState object is OWNED by the sugar (QuantumEmitter / PointSource). The sugar mutates it
|
|
39
|
+
* directly for active / velocity / burst-size / source-rect changes — there is no provider
|
|
40
|
+
* setEmitter* ABI. The tick-fn closes over emState so those mutations take effect live.
|
|
41
|
+
*
|
|
42
|
+
* DSE LAB (2026-07-13, CEO-chartered systematic controls): `em.everyNTicks` (opt-in,
|
|
43
|
+
* default null/undefined = fire every active tick, byte-identical to every existing
|
|
44
|
+
* caller) throttles firing to once every N ticks — a LIVE, per-tick-read cadence knob
|
|
45
|
+
* (mutate `em.everyNTicks` directly on the live emState any time; no re-init). This is
|
|
46
|
+
* DIFFERENT from `delay`/`remainingDelay`, which is a ONE-TIME initial wait before the
|
|
47
|
+
* FIRST burst (`remainingDelay` never resets after reaching 0 — ground-truthed by
|
|
48
|
+
* reading this file — so `delay` cannot serve as a repeating rate control). `fireOnce`
|
|
49
|
+
* (exported below) is the manual single-burst primitive for a "FIRE" button —
|
|
50
|
+
* refactored out of the tick-fn's burst loop (`_fireBurst`) so BOTH the automatic
|
|
51
|
+
* per-tick path and a manual trigger share the IDENTICAL draw-order/injection logic;
|
|
52
|
+
* zero duplication, zero change to the golden-locked draw order for the automatic path.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
'use strict';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Core burst-firing logic — shared by the automatic tick-fn AND `fireOnce` (the
|
|
59
|
+
* manual "FIRE" trigger). Extracted verbatim from the tick-fn (no logic change) so
|
|
60
|
+
* a manual single-burst call reuses the EXACT same draw-order/injection path as an
|
|
61
|
+
* automatic tick, rather than a second hand-rolled copy.
|
|
62
|
+
* @param {Object} universe
|
|
63
|
+
* @param {Object} em
|
|
64
|
+
*/
|
|
65
|
+
function _fireBurst(universe, em) {
|
|
66
|
+
var burstLeft = em.burstSize;
|
|
67
|
+
for (var bi = 0; bi < burstLeft && em.remainingQuanta > 0; bi++) {
|
|
68
|
+
var rects = em.sourceRects;
|
|
69
|
+
for (var ri = 0; ri < rects.length && em.remainingQuanta > 0; ri++) {
|
|
70
|
+
var srcRect = rects[ri]; // [l, t, w, h]
|
|
71
|
+
var x = srcRect[0] + universe.random() * srcRect[2]; // draw 1
|
|
72
|
+
var y = srcRect[1] + universe.random() * srcRect[3]; // draw 2
|
|
73
|
+
|
|
74
|
+
var vx = em.vx;
|
|
75
|
+
var vy = em.vy;
|
|
76
|
+
var vzOut = null; // ws-dse-3d AC1: set only by the zIsotropy branch below
|
|
77
|
+
if (em.randomDirection) {
|
|
78
|
+
if (em.zIsotropy) {
|
|
79
|
+
// ws-dse-3d AC1 ("velocity z-symmetry consistent with its
|
|
80
|
+
// profile"): a real-depth-armed ISOTROPIC point source scatters
|
|
81
|
+
// on the unit SPHERE (azimuth + polar), not just the XY plane —
|
|
82
|
+
// mirrors directedRedirectProgram.js's own polarSpreadRad>0
|
|
83
|
+
// sphere draw. OPT-IN (em.zIsotropy defaults false) — zero extra
|
|
84
|
+
// draws for every pre-existing randomDirection:true caller, which
|
|
85
|
+
// takes the `else` 2D-circle branch unchanged (SAME single draw,
|
|
86
|
+
// SAME golden-locked position in the sequence).
|
|
87
|
+
var az = universe.random() * 2 * Math.PI; // draw 3a
|
|
88
|
+
var cosPolar = universe.random() * 2 - 1; // draw 3b
|
|
89
|
+
var sinPolar = Math.sqrt(Math.max(0, 1 - cosPolar * cosPolar));
|
|
90
|
+
var speed3 = Math.sqrt(vx * vx + vy * vy) || 1;
|
|
91
|
+
vx = Math.cos(az) * sinPolar * speed3;
|
|
92
|
+
vy = Math.sin(az) * sinPolar * speed3;
|
|
93
|
+
vzOut = cosPolar * speed3;
|
|
94
|
+
} else {
|
|
95
|
+
var angle = universe.random() * 2 * Math.PI; // draw 3
|
|
96
|
+
var speed = Math.sqrt(vx * vx + vy * vy) || 1;
|
|
97
|
+
vx = Math.cos(angle) * speed;
|
|
98
|
+
vy = Math.sin(angle) * speed;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var chargeInt;
|
|
103
|
+
if (em.charge === null || em.charge === undefined) {
|
|
104
|
+
chargeInt = (universe.random() < 0.5) ? 0 : 1; // draw 4
|
|
105
|
+
} else {
|
|
106
|
+
chargeInt = (em.charge === 'Positive' || em.charge === 'POSITIVE') ? 0 : 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// LEVEL (leaf scale) is a UNIVERSE property, NOT an emitter constant: a
|
|
110
|
+
// fundamental leaf is created at the universe's fundamental-dimension level
|
|
111
|
+
// (wave-interference / double-slit run at dimension 2 → 4×4 leaves, capacity 16
|
|
112
|
+
// → wavelength 16·WC). Pass null so makeLeafFrame derives it from the universe;
|
|
113
|
+
// a non-null em.fundamentalDim is an explicit override. (Hardcoding 0 here
|
|
114
|
+
// collapsed capacity 16→1 and wavelength 128→8 — the wave-richness regression.)
|
|
115
|
+
var level = (em.fundamentalDim != null) ? em.fundamentalDim : null;
|
|
116
|
+
// makeLeafFrame draws ONE more on WASM (ctor charge-mirror) → golden +1.
|
|
117
|
+
// R3-1: thread per-frame render.shape (10th) + render.outline (11th) EXACTLY as
|
|
118
|
+
// em.color is threaded (9th) — so QuantumEmitter/PointSource gain the same per-frame
|
|
119
|
+
// render reach SphereEmitter3D has. All three ride makeLeafFrame's WASM-side
|
|
120
|
+
// string_map merge stamp (0 RNG draws, golden preserved). Null default → no key →
|
|
121
|
+
// byte-identical for every pre-existing caller. WASM-ONLY (the frozen JS reference
|
|
122
|
+
// makeLeafFrame ignores the shape/outline positionals — a DOCUMENTED no-op, see the
|
|
123
|
+
// wasm-engine makeLeafFrame docstring + the render-intent-merge test; the realm is WASM).
|
|
124
|
+
var handle = universe.makeLeafFrame(
|
|
125
|
+
x, y, level,
|
|
126
|
+
(em.wavelength !== null && em.wavelength !== undefined) ? em.wavelength : 0,
|
|
127
|
+
vx, vy, !!em.wrap, chargeInt,
|
|
128
|
+
em.color || null,
|
|
129
|
+
(em.renderShape != null && em.renderShape !== '') ? em.renderShape : null,
|
|
130
|
+
(em.renderOutline != null) ? em.renderOutline : null
|
|
131
|
+
);
|
|
132
|
+
// liveQuantaCap refused the add: no leaf was born, so a finite quantumLimit is
|
|
133
|
+
// not spent on it (below the cap the handle is never null — byte-identical).
|
|
134
|
+
if (handle === null) { continue; }
|
|
135
|
+
universe.injectFrame(handle);
|
|
136
|
+
|
|
137
|
+
// OPT-IN 3D z-SPREAD (see module docstring). em.zMin is null for every
|
|
138
|
+
// existing 2D caller — this branch is dead code (0 extra draws) unless a
|
|
139
|
+
// caller explicitly opts in (PointSource `opts.zMin`/`opts.zDepth`, WS-DSE-3D).
|
|
140
|
+
// ws-dse-3d AC1: shares the SAME qid lookup with the zIsotropy vz write
|
|
141
|
+
// below (never a second getFrameQuantumId call for the same handle).
|
|
142
|
+
var zMinArmed = (em.zMin !== null && em.zMin !== undefined);
|
|
143
|
+
if (zMinArmed || vzOut !== null) {
|
|
144
|
+
var qid = universe.getFrameQuantumId(handle);
|
|
145
|
+
if (zMinArmed) {
|
|
146
|
+
var zDepth = (em.zDepth !== null && em.zDepth !== undefined) ? em.zDepth : 0;
|
|
147
|
+
var z = em.zMin + (zDepth > 0 ? universe.random() * zDepth : 0); // extra draw, opt-in only
|
|
148
|
+
universe.setFrameProperty(qid, 'z', z);
|
|
149
|
+
}
|
|
150
|
+
if (vzOut !== null) {
|
|
151
|
+
universe.setFrameProperty(qid, 'vz', vzOut);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
em.remainingQuanta--;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build a host tick-function that fires one emitter, drawing from the engine's single RNG and
|
|
162
|
+
* injecting leaves via the generic primitives. Push the returned fn onto
|
|
163
|
+
* `universe.experimentTickFunctions`.
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} universe - engine universe exposing random()/makeLeafFrame()/injectFrame().
|
|
166
|
+
* @param {Object} em - emitter state: {
|
|
167
|
+
* sourceRects: Array<[l,t,w,h]>, vx, vy, burstSize, charge: (null|'POSITIVE'|'NEGATIVE'),
|
|
168
|
+
* randomDirection: bool, remainingQuanta: number, delay, remainingDelay,
|
|
169
|
+
* active: bool, tombstoned: bool, color: [r,g,b,a]|null, wrap: bool,
|
|
170
|
+
* wavelength: number|null, fundamentalDim: number, everyNTicks: number|null }
|
|
171
|
+
* @returns {Function} tick-fn (returns [] — frames are injected via injectFrame, not returned).
|
|
172
|
+
*/
|
|
173
|
+
function buildEmitterTickFn(universe, em) {
|
|
174
|
+
return function () {
|
|
175
|
+
if (em.tombstoned || !em.active) return [];
|
|
176
|
+
if (em.remainingDelay > 0) { em.remainingDelay--; return []; }
|
|
177
|
+
if (em.remainingQuanta <= 0) return [];
|
|
178
|
+
|
|
179
|
+
// OPT-IN firing-rate cadence (DSE Lab): null/undefined/<=1 -> fire every
|
|
180
|
+
// active tick (byte-identical to pre-Lab behavior — this branch is dead
|
|
181
|
+
// code for every existing caller, which never sets em.everyNTicks).
|
|
182
|
+
if (em.everyNTicks !== null && em.everyNTicks !== undefined && em.everyNTicks > 1) {
|
|
183
|
+
em._rateCounter = (em._rateCounter || 0) + 1;
|
|
184
|
+
if (em._rateCounter % em.everyNTicks !== 0) return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_fireBurst(universe, em);
|
|
188
|
+
return [];
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Manual single-burst trigger (DSE Lab "FIRE" button, autofire off). Bypasses the
|
|
194
|
+
* `active`/`remainingDelay`/`everyNTicks` GATING (an explicit user action should
|
|
195
|
+
* always fire on request) but still respects `tombstoned`/`remainingQuanta` (a
|
|
196
|
+
* disposed or already-exhausted emitter cannot be forced to fire past its limit).
|
|
197
|
+
* Reuses the IDENTICAL `_fireBurst` draw-order/injection path the automatic tick-fn
|
|
198
|
+
* uses — no second implementation.
|
|
199
|
+
* @param {Object} universe
|
|
200
|
+
* @param {Object} em
|
|
201
|
+
*/
|
|
202
|
+
function fireOnce(universe, em) {
|
|
203
|
+
if (em.tombstoned || em.remainingQuanta <= 0) return;
|
|
204
|
+
_fireBurst(universe, em);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
export { buildEmitterTickFn, fireOnce };
|
|
209
|
+
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* frqtl-control-slider.js — the SINGLE scalar-control widget
|
|
5
|
+
* (WS gym-controls-as-sugar Bite 3 Stage 2, Layer B).
|
|
6
|
+
*
|
|
7
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* `mount(hostEl, opts) → { dispose() }` renders ONE slider+label+value row
|
|
10
|
+
* (appended into `hostEl`, a parent container — multiple sliders may share
|
|
11
|
+
* one `hostEl`, each disposable independently) from a normalized `kind:
|
|
12
|
+
* 'scalar'` descriptor's DISPLAY shape (`{id,label,min,max,step,value,
|
|
13
|
+
* valueText}` — the SAME serializable shape `lib/shim/rpc`'s dispatch and
|
|
14
|
+
* `frqtl-controls-panel.js`'s `listControls`/`setControlValue` already
|
|
15
|
+
* produce). Dragging the slider calls back into the caller, which decides
|
|
16
|
+
* HOW the value gets applied:
|
|
17
|
+
*
|
|
18
|
+
* `opts.descriptor` — a LIVE, normalized descriptor object
|
|
19
|
+
* (`{id,label,min,max,step,value,fmt,onChange}`) in the SAME window.
|
|
20
|
+
* The STANDALONE in-app path: the widget mutates `descriptor.value` and
|
|
21
|
+
* calls `descriptor.onChange(value)` DIRECTLY — no transport, no RPC,
|
|
22
|
+
* the same-realm case.
|
|
23
|
+
*
|
|
24
|
+
* `opts.initial` + `opts.onInput` — the DECOUPLED path: `opts.initial` is
|
|
25
|
+
* the display-shape descriptor above; `opts.onInput(value)` is called on
|
|
26
|
+
* drag and may return a plain `{value,valueText}` OR a
|
|
27
|
+
* `Promise<{value,valueText}>` — the widget awaits either uniformly and
|
|
28
|
+
* refreshes the displayed value/text from whatever settles. This is the
|
|
29
|
+
* REMOTE path: the caller supplies an `onInput` that dispatches however
|
|
30
|
+
* it needs to (e.g. `frqtl-controls-panel.js` passes
|
|
31
|
+
* `function(v){ return handle.call('setControlValue',[name,d.id,v]); }`,
|
|
32
|
+
* reusing its OWN `lib/shim/rpc`-backed transport handle) — this widget
|
|
33
|
+
* itself knows NOTHING about registries, RPC, or transports; it is
|
|
34
|
+
* PURELY the rendering/wiring widget (the architectural point of this
|
|
35
|
+
* layer: "panel = collection host + transport plumbing; control lib =
|
|
36
|
+
* the widget").
|
|
37
|
+
*
|
|
38
|
+
* Exactly one of `opts.descriptor` OR (`opts.initial` + `opts.onInput`) is
|
|
39
|
+
* required — FAIL-LOUD otherwise.
|
|
40
|
+
*
|
|
41
|
+
* ── DRY discipline (the architectural point of Layer B) ──────────────────────
|
|
42
|
+
*
|
|
43
|
+
* Before this layer, `frqtl-controls-panel.js` built slider+label+value DOM
|
|
44
|
+
* internally (one implementation). This file is now THE ONE slider-rendering
|
|
45
|
+
* implementation; `frqtl-controls-panel.js` REFACTORED to call `mount()` here
|
|
46
|
+
* once per `kind:'scalar'` descriptor instead of building rows itself — see
|
|
47
|
+
* that file's own `renderRows` for the (now much smaller) call site. No
|
|
48
|
+
* behavior change to the panel's public API or its two demo placements.
|
|
49
|
+
*
|
|
50
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
51
|
+
*
|
|
52
|
+
* This file adds ZERO physics — pure UI plumbing over a caller-supplied
|
|
53
|
+
* descriptor/callback. Moving this slider produces the IDENTICAL simulation
|
|
54
|
+
* effect as moving any other consumer of the same declared-once control
|
|
55
|
+
* (proven by the Layer-B Rule-0 oracle: collision-matrix's `angle` control
|
|
56
|
+
* driven simultaneously by its own page slider, the panel's slider, and a
|
|
57
|
+
* standalone `frqtl-control-slider` instance — all three the SAME CONTROLS
|
|
58
|
+
* array entry).
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* mount(hostEl, opts) — render one slider+label+value row into `hostEl`.
|
|
64
|
+
*
|
|
65
|
+
* @param {HTMLElement} hostEl — parent container to append the row into.
|
|
66
|
+
* @param {object} opts
|
|
67
|
+
* @param {object} [opts.descriptor] — a LIVE, normalized descriptor
|
|
68
|
+
* `{id,label,min,max,step,value,fmt,onChange}` — the in-app direct path.
|
|
69
|
+
* @param {{id:string,label:string,min:number,max:number,step:number,
|
|
70
|
+
* value:number,valueText:string}} [opts.initial] — REQUIRED when
|
|
71
|
+
* `opts.descriptor` is absent: the initial serializable display state.
|
|
72
|
+
* @param {(value:number) => (*|Promise<{value:number,valueText:string}>)} [opts.onInput] —
|
|
73
|
+
* REQUIRED when `opts.descriptor` is absent: called with the new numeric
|
|
74
|
+
* value on drag; its (possibly-Promised) return refreshes the displayed
|
|
75
|
+
* value/text.
|
|
76
|
+
* @returns {{dispose: () => void, setValue: (value:number, valueText?:string) => void}}
|
|
77
|
+
* `setValue` (WS gym-controls-as-sugar FULL CONVERGENCE build, Stage ii item 5
|
|
78
|
+
* — the `ControlSync` reconciliation target) is DISPLAY-ONLY: it writes the
|
|
79
|
+
* slider's `.value` DOM property and the value label directly and NEVER
|
|
80
|
+
* fires `onInput`/`onChange` (setting an `<input>`'s `.value` property does
|
|
81
|
+
* not dispatch a synthetic `input` event — only genuine user interaction
|
|
82
|
+
* does; hard-tested via a spy in `frqtl-control-slider.test.mjs`). This is
|
|
83
|
+
* what makes N concurrent widgets reconcilable without an echo/feedback
|
|
84
|
+
* loop: `ControlSync` calls `setValue()` to REFLECT canonical state, which
|
|
85
|
+
* can never itself re-trigger a write.
|
|
86
|
+
* @throws {Error} FAIL-LOUD on a missing host element, or when neither
|
|
87
|
+
* `opts.descriptor` nor both of `opts.initial`/`opts.onInput` are supplied.
|
|
88
|
+
*/
|
|
89
|
+
function mount(hostEl, opts) {
|
|
90
|
+
opts = opts || {};
|
|
91
|
+
if (!hostEl) {
|
|
92
|
+
throw new Error('[frqtl-control-slider] mount requires a host element. (FAIL-LOUD.)');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var initial, onInput;
|
|
96
|
+
if (opts.descriptor) {
|
|
97
|
+
var d = opts.descriptor;
|
|
98
|
+
if (!d.id || typeof d.min !== 'number' || typeof d.max !== 'number' || typeof d.step !== 'number') {
|
|
99
|
+
throw new Error('[frqtl-control-slider] mount({descriptor}) requires a normalized ' +
|
|
100
|
+
'scalar descriptor with id/min/max/step. (FAIL-LOUD.)');
|
|
101
|
+
}
|
|
102
|
+
// A bind-only descriptor (no onChange) has NO write path this widget can
|
|
103
|
+
// execute — this file deliberately has zero dependency on registries/
|
|
104
|
+
// controlBind.js (Layer B: "the widget knows nothing about transports").
|
|
105
|
+
// Dragging it would otherwise silently do nothing (the exact class of
|
|
106
|
+
// bug WS gym-controls-as-sugar FULL CONVERGENCE fixed in the panel).
|
|
107
|
+
// Route a bind-only descriptor through frqtl-controls-panel.js instead
|
|
108
|
+
// (mode:'in-app'/'iframe', which DOES resolve bind execution), or mount
|
|
109
|
+
// this widget via the opts.initial/onInput decoupled path with onInput
|
|
110
|
+
// calling the panel's dispatch/RPC directly.
|
|
111
|
+
if (typeof d.onChange !== 'function' && d.bind) {
|
|
112
|
+
throw new Error('[frqtl-control-slider] mount({descriptor}) was given a bind-only ' +
|
|
113
|
+
'descriptor (id "' + d.id + '", no onChange) — the standalone opts.descriptor path ' +
|
|
114
|
+
'has no bind-execution context and cannot write it. Use frqtl-controls-panel.js\'s ' +
|
|
115
|
+
'mount() instead, or mount this widget via opts.initial + opts.onInput with onInput ' +
|
|
116
|
+
'calling the panel\'s dispatch/RPC. (FAIL-LOUD — never a silent no-op on drag.)');
|
|
117
|
+
}
|
|
118
|
+
initial = {
|
|
119
|
+
id: d.id, label: d.label, min: d.min, max: d.max, step: d.step, value: d.value,
|
|
120
|
+
valueText: (typeof d.fmt === 'function') ? d.fmt(d.value) : String(d.value)
|
|
121
|
+
};
|
|
122
|
+
onInput = function (v) {
|
|
123
|
+
d.value = v;
|
|
124
|
+
if (typeof d.onChange === 'function') { d.onChange(v); }
|
|
125
|
+
return { value: v, valueText: (typeof d.fmt === 'function') ? d.fmt(v) : String(v) };
|
|
126
|
+
};
|
|
127
|
+
} else {
|
|
128
|
+
if (!opts.initial || typeof opts.onInput !== 'function') {
|
|
129
|
+
throw new Error('[frqtl-control-slider] mount requires EITHER opts.descriptor OR ' +
|
|
130
|
+
'BOTH opts.initial and opts.onInput. (FAIL-LOUD.)');
|
|
131
|
+
}
|
|
132
|
+
initial = opts.initial;
|
|
133
|
+
onInput = opts.onInput;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var row = document.createElement('div');
|
|
137
|
+
row.className = 'frqtl-control-slider-row';
|
|
138
|
+
row.style.cssText = 'display:flex;align-items:center;gap:8px;margin:4px 0;font:12px sans-serif;color:#ccc;';
|
|
139
|
+
|
|
140
|
+
var labelEl = document.createElement('span');
|
|
141
|
+
labelEl.textContent = initial.label + ':';
|
|
142
|
+
labelEl.style.cssText = 'min-width:56px;';
|
|
143
|
+
|
|
144
|
+
var slider = document.createElement('input');
|
|
145
|
+
slider.type = 'range';
|
|
146
|
+
slider.min = initial.min; slider.max = initial.max; slider.step = initial.step; slider.value = initial.value;
|
|
147
|
+
slider.setAttribute('data-frqtl-control-id', initial.id);
|
|
148
|
+
|
|
149
|
+
var valEl = document.createElement('span');
|
|
150
|
+
valEl.textContent = initial.valueText;
|
|
151
|
+
valEl.style.cssText = 'min-width:32px;text-align:right;color:#ffb450;';
|
|
152
|
+
|
|
153
|
+
slider.addEventListener('input', function () {
|
|
154
|
+
var v = parseFloat(slider.value);
|
|
155
|
+
Promise.resolve(onInput(v)).then(function (result) {
|
|
156
|
+
if (result && result.valueText !== undefined) { valEl.textContent = result.valueText; }
|
|
157
|
+
}).catch(function (e) {
|
|
158
|
+
valEl.textContent = 'ERR';
|
|
159
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
160
|
+
console.error('[frqtl-control-slider] onInput failed:', e);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
row.appendChild(labelEl);
|
|
166
|
+
row.appendChild(slider);
|
|
167
|
+
row.appendChild(valEl);
|
|
168
|
+
hostEl.appendChild(row);
|
|
169
|
+
|
|
170
|
+
// Fallback formatter for setValue()'s optional valueText: the descriptor
|
|
171
|
+
// path still has a live `d.fmt`; the decoupled (opts.initial/onInput) path
|
|
172
|
+
// has no formatter at all (only the ORIGINAL initial.valueText), so
|
|
173
|
+
// setValue() callers on that path should pass valueText explicitly —
|
|
174
|
+
// String(v) is the honest default, never a fabricated format.
|
|
175
|
+
var formatFallback = (opts.descriptor && typeof opts.descriptor.fmt === 'function')
|
|
176
|
+
? opts.descriptor.fmt
|
|
177
|
+
: function (v) { return String(v); };
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
dispose: function () {
|
|
181
|
+
if (row.parentNode) { row.parentNode.removeChild(row); }
|
|
182
|
+
},
|
|
183
|
+
// Display-only reconciliation write — see the @returns docstring above
|
|
184
|
+
// for the no-loop invariant this depends on.
|
|
185
|
+
setValue: function (value, valueText) {
|
|
186
|
+
slider.value = value;
|
|
187
|
+
valEl.textContent = (valueText !== undefined) ? valueText : formatFallback(value);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Export ────────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
var FrqtlControlSlider = { mount: mount };
|
|
195
|
+
|
|
196
|
+
export { FrqtlControlSlider };
|
|
197
|
+
|