@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,89 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* controlAction.js — OBJECTS-SIDE execution for an `action` control descriptor
|
|
5
|
+
* (presentation pass Item 2, E-P2a/E-P2b). The sibling of
|
|
6
|
+
* `controlBind.js`, same home, same FAIL-LOUD style.
|
|
7
|
+
*
|
|
8
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* `resolveAction(action, ctx)` → `{ run() }`.
|
|
11
|
+
*
|
|
12
|
+
* Arrow rule (mirrors the ratified bind split EXACTLY): `lib/shim/controls`
|
|
13
|
+
* owns the NAME VOCABULARY (`ControlsSchema.ACTIONS` — the data shape);
|
|
14
|
+
* EXECUTION — mapping a name to a live `FrqtlApp` method and calling it
|
|
15
|
+
* — is OBJECTS-SIDE and lives here, beside the panel/overlay/sync sugar that
|
|
16
|
+
* consumes it. Because of this split NO experiment page re-implements pause /
|
|
17
|
+
* resume / restart plumbing: a page DECLARES `{kind:'action', action:'restart'}`
|
|
18
|
+
* and every host (the page's own button, the panel, the canvas overlay) runs the
|
|
19
|
+
* same one implementation.
|
|
20
|
+
*
|
|
21
|
+
* ctx: { runner } — the live FrqtlApp. Required.
|
|
22
|
+
*
|
|
23
|
+
* Every ACTIONS member maps to a method that exists on FrqtlApp:
|
|
24
|
+
*
|
|
25
|
+
* pause → runner.stop() resume → runner.start()
|
|
26
|
+
* toggle → runner.toggle() restart → runner.restart()
|
|
27
|
+
*
|
|
28
|
+
* FAIL-LOUD on an unknown action name, a missing runner, or a runner lacking the
|
|
29
|
+
* mapped method — never a silent no-op (a dead button is exactly the bug class
|
|
30
|
+
* this layer exists to prevent).
|
|
31
|
+
*
|
|
32
|
+
* ── Layer 2: physics behaviour ──────────────────────────────────────────────
|
|
33
|
+
*
|
|
34
|
+
* ZERO physics. These are RUN-LOOP verbs (start/stop the RAF loop, or replay the
|
|
35
|
+
* scene the runner retained — a setup factory or a sketch build, `retainSceneReplay`). None of them touches a frame, a property accessor or
|
|
36
|
+
* the engine RNG, so none can move a canonical-bench golden.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import * as _ControlsSchemaNode from '../../../shim/controls/index.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* ACTION_METHODS — the ONE action-name → runner-method map. Keyed by the SAME
|
|
43
|
+
* names `ControlsSchema.ACTIONS` blesses; a conformance test iterates ACTIONS and
|
|
44
|
+
* asserts every member has a row here (drift is a test failure, never a runtime
|
|
45
|
+
* surprise).
|
|
46
|
+
*/
|
|
47
|
+
var ACTION_METHODS = Object.freeze({
|
|
48
|
+
pause: 'stop',
|
|
49
|
+
resume: 'start',
|
|
50
|
+
toggle: 'toggle',
|
|
51
|
+
restart: 'restart'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* resolveAction(action, ctx) → { run() }.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} action — a member of ControlsSchema.ACTIONS.
|
|
58
|
+
* @param {{runner:object}} ctx
|
|
59
|
+
* @returns {{run: () => *}}
|
|
60
|
+
* @throws {Error} FAIL-LOUD — unknown action, missing ctx.runner, or a runner
|
|
61
|
+
* without the mapped method.
|
|
62
|
+
*/
|
|
63
|
+
function resolveAction(action, ctx) {
|
|
64
|
+
var ACTIONS = _ControlsSchemaNode.ACTIONS;
|
|
65
|
+
if (ACTIONS.indexOf(action) === -1) {
|
|
66
|
+
throw new Error('[controlAction] unknown action "' + String(action) + '" — must be one of [' +
|
|
67
|
+
ACTIONS.join(', ') + '] (the closed vocabulary owned by lib/shim/controls). (FAIL-LOUD.)');
|
|
68
|
+
}
|
|
69
|
+
var method = ACTION_METHODS[action];
|
|
70
|
+
var runner = ctx && ctx.runner;
|
|
71
|
+
if (!runner) {
|
|
72
|
+
throw new Error('[controlAction] resolveAction("' + action + '") requires ctx.runner (the live ' +
|
|
73
|
+
'FrqtlApp). (FAIL-LOUD — never a silently dead button.)');
|
|
74
|
+
}
|
|
75
|
+
if (typeof runner[method] !== 'function') {
|
|
76
|
+
throw new Error('[controlAction] resolveAction("' + action + '"): the runner has no "' + method +
|
|
77
|
+
'" method — the action vocabulary and the runner surface have drifted. (FAIL-LOUD.)');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
run: function () { return runner[method](); }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Export ────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
var ControlAction = { resolveAction: resolveAction, ACTION_METHODS: ACTION_METHODS };
|
|
87
|
+
|
|
88
|
+
export { ControlAction };
|
|
89
|
+
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* controlBind.js — OBJECTS-SIDE execution for a scalar control descriptor's
|
|
5
|
+
* `bind: {object, prop}` (gym-controls-as-sugar, FULL CONVERGENCE build,
|
|
6
|
+
* Stage i item 2).
|
|
7
|
+
*
|
|
8
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* `resolveBind(bind, ctx, channel)` → `{ set(value), get() }`.
|
|
11
|
+
*
|
|
12
|
+
* Arrow rule (§5 converged): `lib/shim/controls` owns the `bind` DATA SHAPE
|
|
13
|
+
* only (`{object, prop}`, both non-empty strings — `ControlsSchema.BIND_KEYS`).
|
|
14
|
+
* EXECUTION — resolving `bind.object` to a live handle, dispatching by
|
|
15
|
+
* `channel`/object-kind, and routing through the real property accessors — is
|
|
16
|
+
* OBJECTS-SIDE: it lives here, beside the panel/slider/sync sugar that
|
|
17
|
+
* consumes it, never inside the shim.
|
|
18
|
+
*
|
|
19
|
+
* `ctx` shape (all fields optional; the fields actually used depend on
|
|
20
|
+
* `channel` and the resolved object's `kind` — see the per-branch throws
|
|
21
|
+
* below for exactly what is required when):
|
|
22
|
+
* {
|
|
23
|
+
* registry: a makeControlRegistry() instance (lib/shim/controls/registry.js)
|
|
24
|
+
* — resolves `bind.object` → { instance, kind } via
|
|
25
|
+
* `getObject`/`getObjectKind`. Required for every
|
|
26
|
+
* non-render channel.
|
|
27
|
+
* universe: the live EGPTUniverse — `setFrameProperty`/`getFrameProperty`
|
|
28
|
+
* (Frame kind ONLY — see below). Required for the Frame channel.
|
|
29
|
+
* runner: the FrqtlApp. Required for the render channel
|
|
30
|
+
* (`runner.setRenderOption`/`getRenderOption` — the runner is the
|
|
31
|
+
* ONE render-option gate: it validates the key against the
|
|
32
|
+
* provider's declared capabilities, validates the value, and
|
|
33
|
+
* routes a `filter.<name>` toggle to its filter slots), for the LargeObject/backdrop
|
|
34
|
+
* channel (`runner.updateObjectProperty(wrapper, prop, value)` —
|
|
35
|
+
* the SAME uniform schema-driven accessor Studio's own Inspector
|
|
36
|
+
* calls, per root CLAUDE.md §Object architecture: "Writes go
|
|
37
|
+
* through the property accessors ... throw-or-apply, never
|
|
38
|
+
* silent-skip". S1 fix (design §2.3.1 / gap-census #3): this
|
|
39
|
+
* branch used to call `ctx.universe.updateObjectProperty(handle,
|
|
40
|
+
* kind, prop, value)` directly — the Universe-level WASM ABI,
|
|
41
|
+
* which schema-validates then SILENTLY NO-OPS every
|
|
42
|
+
* `engineVisible:false` prop (color/alpha/collision_type/
|
|
43
|
+
* actions/name) because the JS-wrapper mirror those props
|
|
44
|
+
* depend on lives in `FrqtlApp`, one layer above,
|
|
45
|
+
* which this bypassed. Routing through `ctx.runner` fixes it),
|
|
46
|
+
* AND for a `bind.object === 'universe'` bind
|
|
47
|
+
* (`runner.setUniverseProperty`/`getUniverseProperty` — S1 fix,
|
|
48
|
+
* design §2.3.2 / gap-census #10, the 4th resolveBind dispatch
|
|
49
|
+
* branch, see below).
|
|
50
|
+
* readSnapshot: optional `(handle, kind, prop) => *|undefined` — the
|
|
51
|
+
* LargeObject/backdrop READ path. There is no
|
|
52
|
+
* `getObjectProperty` accessor (`updateObjectProperty` is
|
|
53
|
+
* write-only); the only read authority for LO state is the
|
|
54
|
+
* LAST `TickData.objects[handle]` snapshot (§B2.2:
|
|
55
|
+
* `{handle, count, frames}` — `count` is the one scalar
|
|
56
|
+
* prop actually marshalled today). Callers that hold the
|
|
57
|
+
* last TickData (ControlSync, Stage ii) supply this;
|
|
58
|
+
* omitting it makes the LO `get()` a DEFINED SKIP
|
|
59
|
+
* (returns undefined) — never a fabricated read.
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* Registration precedent (`registry.js`'s own docstring/tests): a Frame-kind
|
|
63
|
+
* bind target registers the bare QUANTUM_ID (a number — what
|
|
64
|
+
* `setFrameProperty`/`getFrameProperty` key on, e.g. `lane.aQid` from
|
|
65
|
+
* `setupCollisionMatrix.js`); a LargeObject-kind bind target registers the
|
|
66
|
+
* live LO WRAPPER instance (has `.handle`, e.g. a `LargeObject`/`Tube`/
|
|
67
|
+
* `Tube` — `updateObjectProperty` and the TickData snapshot both key on
|
|
68
|
+
* `.handle`, not on the registry name).
|
|
69
|
+
*
|
|
70
|
+
* Two-stage validation (never a bind-local property list): Stage 1 is the
|
|
71
|
+
* shim's shape check (`bind.object`/`bind.prop` are non-empty strings) — DONE
|
|
72
|
+
* before this file ever runs. Stage 2 is the REAL accessor's OWN
|
|
73
|
+
* schema-driven prop validation (`setFrameProperty`/`updateObjectProperty`
|
|
74
|
+
* already consult `ObjectSchema.get(kind)` internally and throw on
|
|
75
|
+
* unknown/readonly props) — `resolveBind`'s `set()` gets this for free by
|
|
76
|
+
* calling the real accessor; it never re-implements a parallel prop list.
|
|
77
|
+
*
|
|
78
|
+
* Kind scope: `resolveBind` dispatches on FOUR things, in this order — render
|
|
79
|
+
* channel (no registry involved) → `bind.object === 'universe'` (reserved
|
|
80
|
+
* sentinel, no registry lookup — see the UNIVERSE branch below) → registry-
|
|
81
|
+
* resolved `kind === 'Frame'` → registry-resolved `kind === 'LargeObject' ||
|
|
82
|
+
* kind === 'backdrop'`. `resolveBind` FAILS LOUD for any OTHER registry-
|
|
83
|
+
* resolved kind (e.g. `Dimension`) rather than silently no-op through
|
|
84
|
+
* `updateObjectProperty`'s own WASM-ABI dispatch gap (`wasm-engine.js` — kinds
|
|
85
|
+
* outside `{LargeObject, backdrop}` pass schema validation then silently
|
|
86
|
+
* no-op there, a pre-existing, out-of-scope gap this file refuses to expose).
|
|
87
|
+
*
|
|
88
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
89
|
+
*
|
|
90
|
+
* This file adds ZERO physics — it is pure dispatch/routing glue between a
|
|
91
|
+
* declarative `{object, prop}` pair and the property-accessor substrate that
|
|
92
|
+
* already exists. A bind-driven slider produces the IDENTICAL simulation
|
|
93
|
+
* effect as calling the SAME accessor by hand (proven at the demo level —
|
|
94
|
+
* see the collision-matrix Rule-0 oracle for the concrete bind-only control).
|
|
95
|
+
*
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* resolveBind(bind, ctx, channel) → { set(value), get() }
|
|
101
|
+
*
|
|
102
|
+
* @param {{object:string, prop:string}} bind — already shape-validated by
|
|
103
|
+
* `lib/shim/controls` at descriptor-normalize time (Stage 1).
|
|
104
|
+
* @param {object} ctx — see file docstring.
|
|
105
|
+
* @param {'physics'|'render'} [channel='physics'] — the descriptor's own
|
|
106
|
+
* `channel` (defaults to the same DEFAULT_CHANNEL the shim applies).
|
|
107
|
+
* @returns {{set:(value:*)=>void, get:()=>*}}
|
|
108
|
+
* @throws {Error} FAIL-LOUD — malformed `bind`, an unregistered `bind.object`,
|
|
109
|
+
* a missing required `ctx` field for the resolved channel/kind, or a
|
|
110
|
+
* resolved object kind outside {Frame, LargeObject, backdrop}.
|
|
111
|
+
*/
|
|
112
|
+
function resolveBind(bind, ctx, channel) {
|
|
113
|
+
if (!bind || typeof bind !== 'object' || typeof bind.prop !== 'string' || !bind.prop ||
|
|
114
|
+
typeof bind.object !== 'string' || !bind.object) {
|
|
115
|
+
throw new Error('[controlBind] resolveBind requires bind = {object, prop} (both non-empty ' +
|
|
116
|
+
'strings) — got ' + JSON.stringify(bind) + '. (FAIL-LOUD — this should already have been ' +
|
|
117
|
+
'caught by lib/shim/controls at normalize time; resolveBind re-checks defensively, never ' +
|
|
118
|
+
'as the primary gate.)');
|
|
119
|
+
}
|
|
120
|
+
ctx = ctx || {};
|
|
121
|
+
var ch = channel || 'physics';
|
|
122
|
+
|
|
123
|
+
// ── RENDER CHANNEL ────────────────────────────────────────────────────────
|
|
124
|
+
// `bind.object` is carried for shape uniformity only (the shim's BIND_KEYS
|
|
125
|
+
// contract does not vary by channel); the render option/capability NAME is
|
|
126
|
+
// `bind.prop` (e.g. 'colorScheme', 'shape', 'godrays', 'filter.water'). The
|
|
127
|
+
// runner owns everything about the write — declared-key check, value check,
|
|
128
|
+
// and the `filter.<name>` routing — so this branch FERRIES it and adds none.
|
|
129
|
+
if (ch === 'render') {
|
|
130
|
+
return {
|
|
131
|
+
set: function (value) {
|
|
132
|
+
if (!ctx.runner) {
|
|
133
|
+
throw new Error('[controlBind] render-channel bind on prop "' + bind.prop +
|
|
134
|
+
'" requires ctx.runner (an FrqtlApp). (FAIL-LOUD.)');
|
|
135
|
+
}
|
|
136
|
+
ctx.runner.setRenderOption(bind.prop, value);
|
|
137
|
+
},
|
|
138
|
+
get: function () {
|
|
139
|
+
if (!ctx.runner) { return undefined; } // defined skip — no runner, no read authority
|
|
140
|
+
return ctx.runner.getRenderOption(bind.prop);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── UNIVERSE — a bare property on the live universe singleton (S1 fix, ─────
|
|
146
|
+
// design §2.3.2 / gap-census #10: Wave Interference's primary physics
|
|
147
|
+
// knob, wavelengthScale, is a UNIVERSE-level global, not a Frame/
|
|
148
|
+
// LargeObject prop — the 3-way {render, Frame, LargeObject/backdrop}
|
|
149
|
+
// dispatch below had no branch for it at all.
|
|
150
|
+
//
|
|
151
|
+
// `bind.object === 'universe'` is a RESERVED SENTINEL, checked BEFORE any
|
|
152
|
+
// registry lookup: the universe is a singleton (exactly one per experiment
|
|
153
|
+
// page), reached directly through ctx.runner — it is never named/registered
|
|
154
|
+
// via registry.registerObject (there is nothing to look up a "universe"
|
|
155
|
+
// object BY NAME for). Any OTHER string is resolved via the registry, same
|
|
156
|
+
// as every other kind.
|
|
157
|
+
if (bind.object === 'universe') {
|
|
158
|
+
return {
|
|
159
|
+
set: function (value) {
|
|
160
|
+
if (!ctx.runner || typeof ctx.runner.setUniverseProperty !== 'function') {
|
|
161
|
+
throw new Error('[controlBind] universe bind on "' + bind.prop +
|
|
162
|
+
'" requires ctx.runner (an FrqtlApp with setUniverseProperty). ' +
|
|
163
|
+
'(FAIL-LOUD.)');
|
|
164
|
+
}
|
|
165
|
+
// Validated accessor — throws on an unknown/readonly Universe field
|
|
166
|
+
// (ObjectSchema.get('Universe')) rather than a bare, unvalidated
|
|
167
|
+
// ctx.universe[bind.prop] = value (the bind-local-property-list
|
|
168
|
+
// anti-pattern this fix exists to close off).
|
|
169
|
+
ctx.runner.setUniverseProperty(bind.prop, value);
|
|
170
|
+
},
|
|
171
|
+
// Universe reads are LIVE — same discipline as the Frame branch (no
|
|
172
|
+
// tick/snapshot dependency, unlike the LargeObject/backdrop branch).
|
|
173
|
+
get: function () {
|
|
174
|
+
if (!ctx.runner || typeof ctx.runner.getUniverseProperty !== 'function') {
|
|
175
|
+
throw new Error('[controlBind] universe bind on "' + bind.prop +
|
|
176
|
+
'" requires ctx.runner (an FrqtlApp with getUniverseProperty). ' +
|
|
177
|
+
'(FAIL-LOUD.)');
|
|
178
|
+
}
|
|
179
|
+
return ctx.runner.getUniverseProperty(bind.prop);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── PHYSICS CHANNEL — resolve bind.object via the registry ──────────────
|
|
185
|
+
if (!ctx.registry || typeof ctx.registry.getObject !== 'function' ||
|
|
186
|
+
typeof ctx.registry.getObjectKind !== 'function') {
|
|
187
|
+
throw new Error('[controlBind] physics-channel bind on "' + bind.object + '.' + bind.prop +
|
|
188
|
+
'" requires ctx.registry (a makeControlRegistry() instance with getObject/getObjectKind). ' +
|
|
189
|
+
'(FAIL-LOUD.)');
|
|
190
|
+
}
|
|
191
|
+
var registered = ctx.registry.getObject(bind.object); // FAIL-LOUD on unknown name (registry's own guard)
|
|
192
|
+
var kind = ctx.registry.getObjectKind(bind.object);
|
|
193
|
+
|
|
194
|
+
if (kind === 'Frame') {
|
|
195
|
+
// Frame-kind registration precedent: the registered instance IS the
|
|
196
|
+
// quantum_id (a number) — what setFrameProperty/getFrameProperty key on.
|
|
197
|
+
var qid = registered;
|
|
198
|
+
return {
|
|
199
|
+
set: function (value) {
|
|
200
|
+
if (!ctx.universe || typeof ctx.universe.setFrameProperty !== 'function') {
|
|
201
|
+
throw new Error('[controlBind] Frame bind on "' + bind.object + '.' + bind.prop +
|
|
202
|
+
'" requires ctx.universe (setFrameProperty). (FAIL-LOUD.)');
|
|
203
|
+
}
|
|
204
|
+
ctx.universe.setFrameProperty(qid, bind.prop, value);
|
|
205
|
+
},
|
|
206
|
+
// Frame reads are LIVE — getFrameProperty has no tick dependency
|
|
207
|
+
// (unlike the LO snapshot path below).
|
|
208
|
+
get: function () {
|
|
209
|
+
if (!ctx.universe || typeof ctx.universe.getFrameProperty !== 'function') {
|
|
210
|
+
throw new Error('[controlBind] Frame bind on "' + bind.object + '.' + bind.prop +
|
|
211
|
+
'" requires ctx.universe (getFrameProperty). (FAIL-LOUD.)');
|
|
212
|
+
}
|
|
213
|
+
return ctx.universe.getFrameProperty(qid, bind.prop);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (kind === 'LargeObject' || kind === 'backdrop') {
|
|
219
|
+
// LargeObject-kind registration precedent: the registered instance is
|
|
220
|
+
// the live wrapper (`.handle` is the engine-side handle
|
|
221
|
+
// TickData.objects[] keys on). `wrapper` is what `set()` below needs
|
|
222
|
+
// (FrqtlApp.updateObjectProperty takes the WRAPPER, not a bare
|
|
223
|
+
// handle — it reads `lo._schemaKind`/`lo.rect`/etc. to decide the
|
|
224
|
+
// engineVisible:false render-only mirror vs the engine-visible
|
|
225
|
+
// universe-delegation path). `handle` (extracted for `get()`'s
|
|
226
|
+
// TickData-snapshot lookup, which DOES key on the bare handle) still
|
|
227
|
+
// accepts a bare-number registration for back-compat with a caller
|
|
228
|
+
// that registered a handle directly instead of a wrapper.
|
|
229
|
+
var wrapper = registered;
|
|
230
|
+
var handle = (registered && typeof registered === 'object' && registered.handle !== undefined)
|
|
231
|
+
? registered.handle
|
|
232
|
+
: registered;
|
|
233
|
+
return {
|
|
234
|
+
set: function (value) {
|
|
235
|
+
// S1 fix (design §2.3.1 / gap-census #3, HIGH RISK): route through
|
|
236
|
+
// ctx.runner.updateObjectProperty(wrapper, prop, value) — the SAME
|
|
237
|
+
// Runner-level accessor Studio's Inspector already calls
|
|
238
|
+
// (`AuthorView.jsx` commit()) — NEVER ctx.universe.updateObjectProperty
|
|
239
|
+
// directly. The Universe-level WASM ABI schema-validates then
|
|
240
|
+
// SILENTLY NO-OPS every engineVisible:false prop (color/alpha/
|
|
241
|
+
// collision_type/actions/name) because those props are mirrored
|
|
242
|
+
// onto the JS wrapper one layer above (FrqtlApp), which the
|
|
243
|
+
// old universe-direct call bypassed entirely — a bind-only control
|
|
244
|
+
// on those props "succeeded" (no throw) and did nothing.
|
|
245
|
+
if (!ctx.runner || typeof ctx.runner.updateObjectProperty !== 'function') {
|
|
246
|
+
throw new Error('[controlBind] LargeObject bind on "' + bind.object + '.' + bind.prop +
|
|
247
|
+
'" requires ctx.runner (an FrqtlApp with updateObjectProperty). (FAIL-LOUD.)');
|
|
248
|
+
}
|
|
249
|
+
// The SAME canonical accessor op-programs' compiled `program` field is
|
|
250
|
+
// itself installed through (actionCompiler.js) — schema-validated,
|
|
251
|
+
// throw-or-apply, never a parallel prop list. FrqtlApp's own
|
|
252
|
+
// schema lookup throws TypeError on an unknown/readonly prop for
|
|
253
|
+
// wrapper's kind — that throw propagates from here unmodified.
|
|
254
|
+
ctx.runner.updateObjectProperty(wrapper, bind.prop, value);
|
|
255
|
+
},
|
|
256
|
+
// No `getObjectProperty` accessor exists — the sole read authority is the
|
|
257
|
+
// LAST TickData.objects[handle] snapshot, supplied by the caller
|
|
258
|
+
// (ControlSync) via ctx.readSnapshot. Absent → a DEFINED SKIP (undefined),
|
|
259
|
+
// never a fabricated read (same discipline as the render channel).
|
|
260
|
+
get: function () {
|
|
261
|
+
if (typeof ctx.readSnapshot !== 'function') { return undefined; }
|
|
262
|
+
return ctx.readSnapshot(handle, kind, bind.prop);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
throw new Error('[controlBind] bind.object "' + bind.object + '" resolved to kind "' + kind +
|
|
268
|
+
'" — bind execution only supports {Frame, LargeObject, backdrop} (updateObjectProperty\'s ' +
|
|
269
|
+
'own WASM-ABI dispatch has no real branch for any other kind — it would validate then ' +
|
|
270
|
+
'silently no-op, which this file refuses to do). (FAIL-LOUD.)');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Export ────────────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
var ControlBind = { resolveBind: resolveBind };
|
|
276
|
+
|
|
277
|
+
export { ControlBind };
|
|
278
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* directedRedirectProgram.js — the "option1" host-trig redirect (DIRECTED TUNNEL
|
|
5
|
+
* BUILD, 2026-07-13).
|
|
6
|
+
*
|
|
7
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* The report's measured verdict (§2, path (c)): the ONLY way to give an in-tunnel
|
|
10
|
+
* collision the emitter's own true circular-isotropic trig ability is to keep
|
|
11
|
+
* EJECT in-engine (as every existing liner already does via BLOCK) and have the
|
|
12
|
+
* HOST tick-loop read `TickData.objects[handle].frames` between ticks and write
|
|
13
|
+
* back `vx`/`vy` via `setFrameProperty` — measured zero (or bounded ≤1px) extra
|
|
14
|
+
* latency vs a native mechanism, because collision detection is itself
|
|
15
|
+
* post-move within Phase C, so both an in-engine `set` and a host `setFrameProperty`
|
|
16
|
+
* can only affect motion starting the tick AFTER detection regardless of mechanism.
|
|
17
|
+
*
|
|
18
|
+
* This is a HOST TICK-FN COMPONENT in the SAME sense as `emitterProgram.js` — an
|
|
19
|
+
* engine-agnostic function composed entirely from generic primitives
|
|
20
|
+
* (`universe.random()`, `universe.setFrameProperty()`), NOT a native op-program (the
|
|
21
|
+
* op-vocabulary's `FN_NAMES` has no `cos`/`sin` — an established FN_NAMES gap,
|
|
22
|
+
* report §2(b)). UNLIKE `emitterProgram.js` (which registers itself onto
|
|
23
|
+
* `universe.experimentTickFunctions` and runs during Phase A, BEFORE that tick's own
|
|
24
|
+
* collision/collect), this component must run AFTER `doTick()` returns — it needs
|
|
25
|
+
* THIS tick's `TickData.objects[handle].frames`, which does not exist until Phase D
|
|
26
|
+
* completes. `buildRedirectApplier` therefore returns a plain function the CALLER
|
|
27
|
+
* (an experiment page's own `onTick`, or a setup factory's return value the caller
|
|
28
|
+
* invokes) calls ONCE per tick with that tick's `tickData` — mirroring how
|
|
29
|
+
* `DetectorWall.prototype.getCollisionHistogram(tickData)` is called.
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} universe - engine universe exposing random()/setFrameProperty().
|
|
32
|
+
* @param {Object} spec
|
|
33
|
+
* @param {Array<number>} spec.wallHandles - LargeObject handles whose collected
|
|
34
|
+
* frames (`tickData.objects[handle].frames`) get redirected each call. Each wall
|
|
35
|
+
* MUST have been constructed with `collectCollided:true` (Tube's own opt-in param)
|
|
36
|
+
* — otherwise `tickData.objects[handle].frames` is simply absent/empty and this
|
|
37
|
+
* applier is a silent (correct) no-op for that handle.
|
|
38
|
+
* @param {number} spec.angleRad - the directed redirect angle, RADIANS (the
|
|
39
|
+
* direction "the tunnel DIRECTS TOWARDS" — honored EXACTLY, never snapped; only
|
|
40
|
+
* the tunnel BODY snaps to an axis-aligned orientation — see DirectedTunnel.js).
|
|
41
|
+
* @param {number} [spec.spreadRad=0] - isotropic-spread sub-option (AZIMUTH,
|
|
42
|
+
* the XY-plane angle): each redirected frame's actual azimuth is
|
|
43
|
+
* `angleRad + (random()*2-1)*spreadRad` — a uniform draw in
|
|
44
|
+
* `[angleRad-spreadRad, angleRad+spreadRad]`. `0` (default) = fully directed,
|
|
45
|
+
* zero spread (every redirected frame gets the EXACT same angle) — byte-identical
|
|
46
|
+
* opt-out for a pure-directed beam.
|
|
47
|
+
* @param {number} [spec.polarSpreadRad=0] - 3D EXTENSION (§4
|
|
48
|
+
* option (c) — "extend the redirect to draw a direction on the unit SPHERE,
|
|
49
|
+
* azimuth + polar"): an independent isotropic-spread draw for the POLAR angle
|
|
50
|
+
* (elevation out of the XY plane), `phi = (random()*2-1)*polarSpreadRad`. `0`
|
|
51
|
+
* (default) = byte-identical to the pre-3D behavior — `phi` stays exactly `0`
|
|
52
|
+
* (`cos(0)=1, sin(0)=0` exactly in IEEE754), so `vx`/`vy` are UNCHANGED and NO
|
|
53
|
+
* `vz` `setFrameProperty` call happens at all (never even the RNG draw — a 2D
|
|
54
|
+
* tunnel's redirect never touches a 3rd axis). Non-zero (a caller with real
|
|
55
|
+
* z-containment armed) makes the redirected direction genuinely 3D — closes the
|
|
56
|
+
* "the laserBeam path only ever writes 2 axes" gap (DirectedTunnel's `option1`
|
|
57
|
+
* previously flattened every redirected quantum into the XY plane regardless of
|
|
58
|
+
* the tunnel's own z-containment).
|
|
59
|
+
* @param {number|null} [spec.speed=null] - redirected speed magnitude. `null`
|
|
60
|
+
* (default) preserves the frame's OWN incoming speed (`sqrt(vx^2+vy^2)`, falling
|
|
61
|
+
* back to 1 if the frame arrived with zero velocity) — a reflection-style
|
|
62
|
+
* redirect that changes DIRECTION only. A finite number forces every redirected
|
|
63
|
+
* frame to the SAME speed (a collimating/laser-style redirect).
|
|
64
|
+
* @returns {Function} `applyRedirect(tickData)` — call once per tick, AFTER
|
|
65
|
+
* `doTick()` returns that tick's `tickData`.
|
|
66
|
+
*/
|
|
67
|
+
'use strict';
|
|
68
|
+
|
|
69
|
+
function buildRedirectApplier(universe, spec) {
|
|
70
|
+
spec = spec || {};
|
|
71
|
+
var wallHandles = spec.wallHandles || [];
|
|
72
|
+
var angleRad = spec.angleRad;
|
|
73
|
+
if (typeof angleRad !== 'number' || !isFinite(angleRad)) {
|
|
74
|
+
throw new Error('[directedRedirectProgram] spec.angleRad must be a finite number (radians).');
|
|
75
|
+
}
|
|
76
|
+
var spreadRad = (spec.spreadRad !== undefined && spec.spreadRad !== null) ? spec.spreadRad : 0;
|
|
77
|
+
var polarSpreadRad = (spec.polarSpreadRad !== undefined && spec.polarSpreadRad !== null) ? spec.polarSpreadRad : 0;
|
|
78
|
+
var fixedSpeed = (spec.speed !== undefined) ? spec.speed : null;
|
|
79
|
+
|
|
80
|
+
return function applyRedirect(tickData) {
|
|
81
|
+
if (!tickData || !tickData.objects) { return; }
|
|
82
|
+
for (var w = 0; w < wallHandles.length; w++) {
|
|
83
|
+
var entry = tickData.objects[wallHandles[w]];
|
|
84
|
+
if (!entry || !Array.isArray(entry.frames)) { continue; }
|
|
85
|
+
var frames = entry.frames;
|
|
86
|
+
for (var i = 0; i < frames.length; i++) {
|
|
87
|
+
var fd = frames[i];
|
|
88
|
+
var theta = angleRad; // azimuth (XY-plane angle)
|
|
89
|
+
if (spreadRad) {
|
|
90
|
+
theta += (universe.random() * 2 - 1) * spreadRad; // draw — host trig, path (c)
|
|
91
|
+
}
|
|
92
|
+
var speed = (fixedSpeed !== null)
|
|
93
|
+
? fixedSpeed
|
|
94
|
+
: (Math.sqrt(fd.vx * fd.vx + fd.vy * fd.vy) || 1);
|
|
95
|
+
// 3D EXTENSION: phi (polar, elevation out of the XY
|
|
96
|
+
// plane) draws ONLY when polarSpreadRad is non-zero — otherwise
|
|
97
|
+
// phi stays exactly 0, making this byte-identical to the pre-3D
|
|
98
|
+
// 2-axis formula (cos(0)=1 -> vx=cos(theta)*speed,
|
|
99
|
+
// sin(0)=0 -> vz=0, never written).
|
|
100
|
+
var phi = 0;
|
|
101
|
+
if (polarSpreadRad) {
|
|
102
|
+
phi = (universe.random() * 2 - 1) * polarSpreadRad; // unit-sphere draw — host trig, path (c)
|
|
103
|
+
}
|
|
104
|
+
var vx = Math.cos(theta) * Math.cos(phi) * speed;
|
|
105
|
+
var vy = Math.sin(theta) * Math.cos(phi) * speed;
|
|
106
|
+
var vz = Math.sin(phi) * speed;
|
|
107
|
+
// fd.id is ALREADY the quantum_id (Rust FrameData's own `id` field —
|
|
108
|
+
// NOT a raw FrameId handle; unlike a fresh emission from makeLeafFrame,
|
|
109
|
+
// a COLLECTED tickData frame needs no getFrameQuantumId() conversion).
|
|
110
|
+
universe.setFrameProperty(fd.id, 'vx', vx);
|
|
111
|
+
universe.setFrameProperty(fd.id, 'vy', vy);
|
|
112
|
+
if (polarSpreadRad) { universe.setFrameProperty(fd.id, 'vz', vz); }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
export { buildRedirectApplier };
|
|
120
|
+
|