@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,216 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* orbit-bag.js — THE point-shape authority for the camera pose bag.
|
|
5
|
+
*
|
|
6
|
+
* Peer of `renderCapabilities-common.js` and `captureStill-common.js`: a shared contract-half
|
|
7
|
+
* BOTH providers import, so the pose vocabulary has ONE owner instead of one per provider.
|
|
8
|
+
* (`camera2d-geometry.js` and `providers/three-camera-geometry.js` are the per-projection MATH
|
|
9
|
+
* authorities and stay exactly that — this file does no geometry, only shape.)
|
|
10
|
+
*
|
|
11
|
+
* ── Why this file exists (V4 UX, fix 3) ──────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* The two providers each hand-validated `target` and disagreed, in two different wrong ways:
|
|
14
|
+
* the 2D handle threw `target.x must be a finite number` on the array spelling, while the 3D
|
|
15
|
+
* handle SILENTLY RESET a malformed target to `null` and re-centred the camera. The second is
|
|
16
|
+
* a fail-loud breach on its own (RENDERER_CONTRACT §1a.2: throw-or-apply, never
|
|
17
|
+
* clamp-and-continue), and the pair is a validation schema mirror — two modules hand-listing a
|
|
18
|
+
* shape the contract owns. So the shape moves HERE and both providers ferry through it.
|
|
19
|
+
*
|
|
20
|
+
* ── The vocabulary ───────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* A point-valued pose field accepts BOTH spellings, because a reader trained on p5 writes
|
|
23
|
+
* either one and both are unambiguous:
|
|
24
|
+
*
|
|
25
|
+
* `{x, y, z}` — the canonical OBJECT form (`z` optional ⇒ 0)
|
|
26
|
+
* `[x, y, z]` — the ARRAY form (`[x, y]` is accepted; `z` ⇒ 0)
|
|
27
|
+
* `null` / `undefined` — UNSET (the camera re-centres on the scene). This is a declared
|
|
28
|
+
* value, not a malformed one, exactly as it is in the schema accessors.
|
|
29
|
+
*
|
|
30
|
+
* OUTPUT is always the object form — `getOrbit()` is unchanged and a captured pose still
|
|
31
|
+
* round-trips value-exact through `setOrbit`. The array is an accepted INPUT spelling only:
|
|
32
|
+
* the same "an enum accepts a key or a value" precedent, applied to points. Anything else —
|
|
33
|
+
* a string, a 4-tuple, a bag with a NaN — THROWS one identical message under EVERY provider,
|
|
34
|
+
* naming both accepted spellings. Two providers disagreeing about a shape is the drift this
|
|
35
|
+
* owner exists to make impossible.
|
|
36
|
+
*
|
|
37
|
+
* A 2D handle PROJECTS the normalized point (it stores `{x, y}` and ignores `z`) — it does not
|
|
38
|
+
* re-validate, and it does not get its own opinion about the spelling.
|
|
39
|
+
*
|
|
40
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
41
|
+
*
|
|
42
|
+
* POINT_FIELDS — the pose keys that are point-valued (`target` today).
|
|
43
|
+
* A future point field joins this list and both providers
|
|
44
|
+
* accept both spellings for it for free.
|
|
45
|
+
* normalizePoint(value, fieldName) — throw-or-return `{x, y, z}` (or `null` for unset).
|
|
46
|
+
* normalizeOrbitPatch(patch) — a COPY of the patch with every point field normalized.
|
|
47
|
+
* Non-point keys are passed through untouched (the
|
|
48
|
+
* per-axis finite guards stay with the provider that
|
|
49
|
+
* declares those axes).
|
|
50
|
+
* ANGULAR_FIELDS — the pose keys that ROTATE the view (azimuth, elevation,
|
|
51
|
+
* roll). A camera that declares none of them is flat.
|
|
52
|
+
* hasAngularPose(cameraHandle) — THE 3D-ness predicate: does this camera handle declare an
|
|
53
|
+
* angular axis in its own `poseFields`? Every consumer that
|
|
54
|
+
* must branch on "is this scene 3D?" asks this — never handle
|
|
55
|
+
* presence (the 2D p5 camera is a real handle too) and never
|
|
56
|
+
* a renderer sniff. `null` (no pose control at all) is flat;
|
|
57
|
+
* a handle without a `poseFields` array breaks the camera
|
|
58
|
+
* contract and THROWS.
|
|
59
|
+
* makePoseNotice() — the POSE-CHANGE notice every camera handle publishes as
|
|
60
|
+
* `addPoseListener(fn) → unsubscribe`: `{ add, notify }`.
|
|
61
|
+
* A provider calls `notify(bag)` from the ONE place its pose
|
|
62
|
+
* is applied, so EVERY write reaches every listener (pads,
|
|
63
|
+
* camera strip, wheel, page-code `setOrbit`, a resize). A
|
|
64
|
+
* dependent that re-derived the notice per write site would
|
|
65
|
+
* go silently stale on the site it forgot.
|
|
66
|
+
*
|
|
67
|
+
* ── Layer 2: physics behaviour ───────────────────────────────────────────────
|
|
68
|
+
* ZERO physics. This is pose-input spelling at the output boundary: it changes which pixels
|
|
69
|
+
* you are shown, never a frame, a dimension or a tick.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/** The pose keys whose value is a POINT. Named once so no consumer re-lists them. */
|
|
74
|
+
var POINT_FIELDS = ['target'];
|
|
75
|
+
|
|
76
|
+
/** The pose keys that ROTATE the view. Named once: the 2D provider's setOrbit guard and the
|
|
77
|
+
* 3D-ness predicate both read it. */
|
|
78
|
+
var ANGULAR_FIELDS = ['azimuth', 'elevation', 'roll'];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* hasAngularPose(cameraHandle) — THE 3D-ness predicate, asked of the handle's own axis
|
|
82
|
+
* declaration (`poseFields[].name`), never of its presence.
|
|
83
|
+
*
|
|
84
|
+
* @param {object|null} cameraHandle — a `getCameraControl()` handle, or null (no pose control).
|
|
85
|
+
* @returns {boolean} true iff the handle declares at least one ANGULAR_FIELDS axis.
|
|
86
|
+
* @throws {Error} when a non-null handle publishes no `poseFields` array (camera contract breach).
|
|
87
|
+
*/
|
|
88
|
+
function hasAngularPose(cameraHandle) {
|
|
89
|
+
if (cameraHandle === null || cameraHandle === undefined) { return false; }
|
|
90
|
+
var fields = cameraHandle.poseFields;
|
|
91
|
+
if (!Array.isArray(fields)) {
|
|
92
|
+
throw new Error('camera pose: the camera handle publishes no `poseFields` array, so whether ' +
|
|
93
|
+
'it has angular axes cannot be read — every getCameraControl() handle declares its ' +
|
|
94
|
+
'axes (RENDERER_CONTRACT §1a.2). (FAIL-LOUD.)');
|
|
95
|
+
}
|
|
96
|
+
for (var i = 0; i < fields.length; i++) {
|
|
97
|
+
if (fields[i] && ANGULAR_FIELDS.indexOf(fields[i].name) !== -1) { return true; }
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** @private — the ONE message, so both providers are byte-identical on a bad input. */
|
|
103
|
+
function _reject(fieldName, value) {
|
|
104
|
+
throw new Error('camera pose: `' + fieldName + '` must be a POINT — either {x, y, z} or ' +
|
|
105
|
+
'[x, y, z] (z optional, defaults to 0), with finite numbers, or null to re-centre. ' +
|
|
106
|
+
'Got ' + _describe(value) + '. Throw-or-apply: a malformed point is never silently ' +
|
|
107
|
+
'reset and never silently clamped. (FAIL-LOUD.)');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @private — a short, honest rendering of whatever arrived. */
|
|
111
|
+
function _describe(v) {
|
|
112
|
+
if (Array.isArray(v)) { return 'an array of length ' + v.length + ' (' + v.join(', ') + ')'; }
|
|
113
|
+
if (v === null) { return 'null'; }
|
|
114
|
+
if (typeof v === 'object') {
|
|
115
|
+
var keys = [];
|
|
116
|
+
for (var k in v) { if (Object.prototype.hasOwnProperty.call(v, k)) { keys.push(k); } }
|
|
117
|
+
return 'an object {' + keys.join(', ') + '}';
|
|
118
|
+
}
|
|
119
|
+
return typeof v + ' ' + String(v);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @private */
|
|
123
|
+
function _finiteOrNull(v) {
|
|
124
|
+
return (typeof v === 'number' && isFinite(v)) ? v : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* normalizePoint(value, fieldName) — the ONE point-shape gate.
|
|
129
|
+
*
|
|
130
|
+
* @param {object|Array|null|undefined} value — `{x,y,z?}`, `[x,y,z?]`, or null/undefined.
|
|
131
|
+
* @param {string} fieldName — the pose key, for the message (e.g. 'target').
|
|
132
|
+
* @returns {{x:number,y:number,z:number}|null} the canonical object form, or null for unset.
|
|
133
|
+
* @throws {Error} one identical message under every provider on anything else.
|
|
134
|
+
*/
|
|
135
|
+
function normalizePoint(value, fieldName) {
|
|
136
|
+
if (value === null || value === undefined) { return null; }
|
|
137
|
+
var x, y, z;
|
|
138
|
+
if (Array.isArray(value)) {
|
|
139
|
+
// `[x, y]` and `[x, y, z]` only: a 1- or 4-tuple is a mistake, not a shorthand.
|
|
140
|
+
if (value.length !== 2 && value.length !== 3) { _reject(fieldName, value); }
|
|
141
|
+
x = _finiteOrNull(value[0]);
|
|
142
|
+
y = _finiteOrNull(value[1]);
|
|
143
|
+
z = (value.length === 3) ? _finiteOrNull(value[2]) : 0;
|
|
144
|
+
} else if (typeof value === 'object') {
|
|
145
|
+
x = _finiteOrNull(value.x);
|
|
146
|
+
y = _finiteOrNull(value.y);
|
|
147
|
+
z = (value.z === undefined || value.z === null) ? 0 : _finiteOrNull(value.z);
|
|
148
|
+
} else {
|
|
149
|
+
_reject(fieldName, value);
|
|
150
|
+
}
|
|
151
|
+
if (x === null || y === null || z === null) { _reject(fieldName, value); }
|
|
152
|
+
return { x: x, y: y, z: z };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* normalizeOrbitPatch(patch) — normalize every point-valued field of a setOrbit patch.
|
|
157
|
+
*
|
|
158
|
+
* A patch is PARTIAL by contract (only the keys it mentions are applied), so a point field is
|
|
159
|
+
* normalized only when the patch MENTIONS it — `'target' in patch` — which is what keeps an
|
|
160
|
+
* explicit `target:null` (re-centre) distinguishable from an unmentioned target.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} patch — a setOrbit spec.
|
|
163
|
+
* @returns {object} a COPY with point fields in canonical object form; non-point keys verbatim.
|
|
164
|
+
*/
|
|
165
|
+
function normalizeOrbitPatch(patch) {
|
|
166
|
+
if (!patch || typeof patch !== 'object') { return patch; }
|
|
167
|
+
var out = {}, k;
|
|
168
|
+
for (k in patch) {
|
|
169
|
+
if (Object.prototype.hasOwnProperty.call(patch, k)) { out[k] = patch[k]; }
|
|
170
|
+
}
|
|
171
|
+
for (var i = 0; i < POINT_FIELDS.length; i++) {
|
|
172
|
+
var f = POINT_FIELDS[i];
|
|
173
|
+
if (f in patch) { out[f] = normalizePoint(patch[f], f); }
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Export ────────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* makePoseNotice() → `{ add(fn) → unsubscribe, notify(bag) }` — the pose-change listener list a
|
|
182
|
+
* camera handle publishes as `addPoseListener`. Listeners are provider-scoped (every handle the
|
|
183
|
+
* provider returns shares one list), so a listener registered through one handle hears a write
|
|
184
|
+
* made through another. `notify` iterates a SNAPSHOT (a listener may unsubscribe itself) and lets
|
|
185
|
+
* a listener's throw propagate to the pose writer — a dependent that cannot follow the camera
|
|
186
|
+
* fails loud at the write, never silently. A non-function listener THROWS at `add`.
|
|
187
|
+
*/
|
|
188
|
+
function makePoseNotice() {
|
|
189
|
+
var listeners = [];
|
|
190
|
+
return {
|
|
191
|
+
add: function (fn) {
|
|
192
|
+
if (typeof fn !== 'function') {
|
|
193
|
+
throw new TypeError('[OrbitBag] addPoseListener(fn) needs a function, got ' +
|
|
194
|
+
String(fn) + '. (FAIL-LOUD.)');
|
|
195
|
+
}
|
|
196
|
+
if (listeners.indexOf(fn) === -1) { listeners.push(fn); }
|
|
197
|
+
return function () {
|
|
198
|
+
var i = listeners.indexOf(fn);
|
|
199
|
+
if (i !== -1) { listeners.splice(i, 1); }
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
notify: function (bag) {
|
|
203
|
+
var ls = listeners.slice();
|
|
204
|
+
for (var i = 0; i < ls.length; i++) { ls[i](bag); }
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {
|
|
210
|
+
makePoseNotice,
|
|
211
|
+
POINT_FIELDS,
|
|
212
|
+
ANGULAR_FIELDS,
|
|
213
|
+
normalizePoint,
|
|
214
|
+
normalizeOrbitPatch,
|
|
215
|
+
hasAngularPose
|
|
216
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* godrays-layer.js — the three provider's god-rays layer: WHAT the rays are computed from
|
|
5
|
+
* (the source rule), the light position, and the radial march + composite passes.
|
|
6
|
+
*
|
|
7
|
+
* God-rays are a render effect computed from the density of the LIGHT particles: by default
|
|
8
|
+
* (`godraySource: 'leaf'`) the frames of the fundamental (lowest) layer. A parent frame at a higher layer is drawn in the scene
|
|
9
|
+
* but is neither a ray source nor a ray shadow; an LO is not a frame, so it is no source
|
|
10
|
+
* either (the provider keeps it as a depth-only occluder). Every god-rays site — the
|
|
11
|
+
* provider's source pass (per-bucket / cube / clone visibility) and `lightUV` here — asks
|
|
12
|
+
* `isGodraySource`; none re-states the rule.
|
|
13
|
+
*
|
|
14
|
+
* What stays in `three-provider.js`: `_renderGodraySource`, which needs the provider's live
|
|
15
|
+
* scene objects, and the orchestration in `_renderGodrays`. This file is its own because the
|
|
16
|
+
* rule is pure and because `three-provider.js` sits at the per-file ceiling
|
|
17
|
+
* `verify-publish-surface.mjs` enforces, whose instruction is to split at a natural seam.
|
|
18
|
+
*
|
|
19
|
+
* An ES module: `three-provider.js` imports it; `providers/test/godrays-layer.test.mjs` drives it in node.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The world's fundamental layer as TickData states it: the lowest ALLOCATED layer in
|
|
26
|
+
* `tickData.dimensionData` (ENGINE_CONTRACT: the fundamental is derived as the lowest
|
|
27
|
+
* layer that exists; the engine collects one DimensionData per allocated layer). A
|
|
28
|
+
* renderer has no universe handle, so TickData is its one read. `null` for a world with
|
|
29
|
+
* no dimension and no frame; THROWS when frames arrive with no dimension to place them.
|
|
30
|
+
* @param {object} tickData
|
|
31
|
+
* @returns {number|null}
|
|
32
|
+
*/
|
|
33
|
+
function fundamentalLayerOf(tickData) {
|
|
34
|
+
var dd = tickData && tickData.dimensionData;
|
|
35
|
+
var lowest = null;
|
|
36
|
+
if (dd) {
|
|
37
|
+
for (var i = 0; i < dd.length; i++) {
|
|
38
|
+
var l = dd[i] && dd[i].layer;
|
|
39
|
+
if (typeof l === 'number' && (lowest === null || l < lowest)) { lowest = l; }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (lowest === null && tickData && tickData.frameData && tickData.frameData.length) {
|
|
43
|
+
throw new Error('GodraysLayer: tickData carries ' + tickData.frameData.length + ' frames but no ' +
|
|
44
|
+
'dimensionData[].layer, so the fundamental layer the god-rays are computed from cannot be named.');
|
|
45
|
+
}
|
|
46
|
+
return lowest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Is a frame at `layer` a god-rays source? Exactly the fundamental-layer frames.
|
|
51
|
+
* @param {number} layer — FrameData.layer
|
|
52
|
+
* @param {number|null} fundamental — fundamentalLayerOf(tickData)
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
function isGodraySource(layer, fundamental) {
|
|
56
|
+
return fundamental !== null && layer === fundamental;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The `godraySource` render option — the ONE owner of its values and default. A scene picks
|
|
61
|
+
* what the rays are computed from: 'leaf' (the fundamental-layer frames; LOs depth-only) or
|
|
62
|
+
* 'all' (the whole lit scene: every frame of every layer and every LO). The renderer reads
|
|
63
|
+
* it through the options bag; the engine interprets nothing.
|
|
64
|
+
*/
|
|
65
|
+
var GODRAY_SOURCES = ['leaf', 'all'];
|
|
66
|
+
var GODRAY_SOURCE_DEFAULT = 'leaf';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The effective source for a bag value: unset → the default; anything not declared THROWS
|
|
70
|
+
* naming every legal value.
|
|
71
|
+
* @param {*} value — opts.godraySource
|
|
72
|
+
* @returns {'leaf'|'all'}
|
|
73
|
+
*/
|
|
74
|
+
function resolveGodraySource(value) {
|
|
75
|
+
if (value === undefined || value === null) { return GODRAY_SOURCE_DEFAULT; }
|
|
76
|
+
if (GODRAY_SOURCES.indexOf(value) === -1) {
|
|
77
|
+
throw new Error('GodraysLayer: godraySource ' + JSON.stringify(value) + ' is not one of [' +
|
|
78
|
+
GODRAY_SOURCES.join(', ') + '] (default ' + JSON.stringify(GODRAY_SOURCE_DEFAULT) + ').');
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The frames the god-rays are computed from: the fundamental layer under 'leaf' (the
|
|
85
|
+
* default), every frame under 'all'.
|
|
86
|
+
* @param {object} tickData
|
|
87
|
+
* @param {*} [source] — opts.godraySource
|
|
88
|
+
* @returns {Array<object>}
|
|
89
|
+
*/
|
|
90
|
+
function godraySourceFrames(tickData, source) {
|
|
91
|
+
var fd = (tickData && tickData.frameData) || [];
|
|
92
|
+
if (resolveGodraySource(source) === 'all') { return fd.slice(); }
|
|
93
|
+
var fund = fundamentalLayerOf(tickData);
|
|
94
|
+
var out = [];
|
|
95
|
+
for (var i = 0; i < fd.length; i++) { if (isGodraySource(fd[i].layer, fund)) { out.push(fd[i]); } }
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// R4-B god-rays — radial light-scattering march (adapted from abberg's
|
|
100
|
+
// VolumetericLightShader / GPU Gems 3). For each pixel it steps TOWARD the
|
|
101
|
+
// projected light position, accumulating SOURCE brightness with exponential
|
|
102
|
+
// decay → bright shafts where the path crosses luminous quanta, ray-shadows
|
|
103
|
+
// where an occluder hid them. Output is PURE rays (starts at 0, no base scene
|
|
104
|
+
// term) so the composite adds them once over the unblurred full scene.
|
|
105
|
+
var GODRAY_BLUR_FRAG = [
|
|
106
|
+
'uniform sampler2D uScene;',
|
|
107
|
+
'uniform vec2 uLightPos;', // light position in [0,1] screen UV
|
|
108
|
+
'uniform float uDecay;',
|
|
109
|
+
'uniform float uDensity;',
|
|
110
|
+
'uniform float uWeight;',
|
|
111
|
+
'uniform float uExposure;',
|
|
112
|
+
'varying vec2 vTexCoord;',
|
|
113
|
+
'const int SAMPLES = 50;',
|
|
114
|
+
'void main() {',
|
|
115
|
+
' vec2 tc = vTexCoord;',
|
|
116
|
+
' vec2 delta = (tc - uLightPos) * (1.0 / float(SAMPLES)) * uDensity;',
|
|
117
|
+
' vec3 rays = vec3(0.0);',
|
|
118
|
+
' float decay = 1.0;',
|
|
119
|
+
' for (int i = 0; i < SAMPLES; i++) {',
|
|
120
|
+
' tc -= delta;',
|
|
121
|
+
' vec3 s = texture2D(uScene, tc).rgb;',
|
|
122
|
+
' rays += s * decay * uWeight;',
|
|
123
|
+
' decay *= uDecay;',
|
|
124
|
+
' }',
|
|
125
|
+
' gl_FragColor = vec4(rays * uExposure, 1.0);',
|
|
126
|
+
'}'
|
|
127
|
+
].join('\n');
|
|
128
|
+
|
|
129
|
+
// R4-B composite — unblurred scene + additive rays * intensity knob.
|
|
130
|
+
var GODRAY_COMP_FRAG = [
|
|
131
|
+
'uniform sampler2D uScene;',
|
|
132
|
+
'uniform sampler2D uRays;',
|
|
133
|
+
'uniform float uIntensity;',
|
|
134
|
+
'varying vec2 vTexCoord;',
|
|
135
|
+
'void main() {',
|
|
136
|
+
' vec3 scene = texture2D(uScene, vTexCoord).rgb;',
|
|
137
|
+
' vec3 rays = texture2D(uRays, vTexCoord).rgb;',
|
|
138
|
+
' gl_FragColor = vec4(scene + rays * uIntensity, 1.0);',
|
|
139
|
+
'}'
|
|
140
|
+
].join('\n');
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The live passes, created lazily by the provider on first activation.
|
|
144
|
+
* @param {object} THREE
|
|
145
|
+
* @param {string} quadVert — the provider's full-screen-quad vertex shader
|
|
146
|
+
* @param {number} w @param {number} h — the provider's physical size
|
|
147
|
+
* @returns {{rtSource:object, setSize:Function, lightUV:Function, march:Function}}
|
|
148
|
+
*/
|
|
149
|
+
function makeGodrayPasses(THREE, quadVert, w, h) {
|
|
150
|
+
var fxScene = new THREE.Scene();
|
|
151
|
+
var fxQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), null);
|
|
152
|
+
fxScene.add(fxQuad);
|
|
153
|
+
var vLight = new THREE.Vector3();
|
|
154
|
+
var blurMat = new THREE.ShaderMaterial({
|
|
155
|
+
uniforms: {
|
|
156
|
+
uScene: { value: null },
|
|
157
|
+
uLightPos: { value: new THREE.Vector2(0.5, 0.5) },
|
|
158
|
+
uDecay: { value: 0.95 },
|
|
159
|
+
uDensity: { value: 0.8 },
|
|
160
|
+
uWeight: { value: 0.4 },
|
|
161
|
+
uExposure: { value: 0.35 }
|
|
162
|
+
},
|
|
163
|
+
vertexShader: quadVert, fragmentShader: GODRAY_BLUR_FRAG, depthWrite: false, depthTest: false
|
|
164
|
+
});
|
|
165
|
+
var compMat = new THREE.ShaderMaterial({
|
|
166
|
+
uniforms: { uScene: { value: null }, uRays: { value: null }, uIntensity: { value: 1.0 } },
|
|
167
|
+
vertexShader: quadVert, fragmentShader: GODRAY_COMP_FRAG, depthWrite: false, depthTest: false
|
|
168
|
+
});
|
|
169
|
+
var passes = {
|
|
170
|
+
rtSource: new THREE.WebGLRenderTarget(w, h), // the SOURCE image the provider renders into
|
|
171
|
+
setSize: function (sw, sh) { passes.rtSource.setSize(sw, sh); },
|
|
172
|
+
// Light position in [0,1] screen UV: the fullness-weighted centroid of the SOURCE
|
|
173
|
+
// frames, projected through `camera` (2D ortho + 3D persp); screen centre when none.
|
|
174
|
+
lightUV: function (tickData, camera, source) {
|
|
175
|
+
var fd = godraySourceFrames(tickData, source);
|
|
176
|
+
var sx = 0, sy = 0, sz = 0, sw = 0;
|
|
177
|
+
for (var i = 0; i < fd.length; i++) {
|
|
178
|
+
var f = fd[i];
|
|
179
|
+
var wt = (f.fullness || 0) + 0.05; // floor so empty-fullness still weighs
|
|
180
|
+
sx += f.rect.x * wt; sy += f.rect.y * wt; sz += (f.z || 0) * wt; sw += wt;
|
|
181
|
+
}
|
|
182
|
+
if (sw <= 0) { return new THREE.Vector2(0.5, 0.5); }
|
|
183
|
+
vLight.set(sx / sw, sy / sw, sz / sw).project(camera); // → NDC [-1,1]
|
|
184
|
+
return new THREE.Vector2(vLight.x * 0.5 + 0.5, vLight.y * 0.5 + 0.5);
|
|
185
|
+
},
|
|
186
|
+
// Pass 2: march sourceRT → rtRays. Pass 3: sceneRT + rays × intensity → screen.
|
|
187
|
+
march: function (renderer, quadCamera, sceneRT, sourceRT, rtRays, lightUV, density, intensity) {
|
|
188
|
+
blurMat.uniforms.uScene.value = sourceRT.texture;
|
|
189
|
+
blurMat.uniforms.uLightPos.value.copy(lightUV);
|
|
190
|
+
if (typeof density === 'number') { blurMat.uniforms.uDensity.value = density; }
|
|
191
|
+
fxQuad.material = blurMat;
|
|
192
|
+
renderer.setRenderTarget(rtRays);
|
|
193
|
+
renderer.render(fxScene, quadCamera);
|
|
194
|
+
compMat.uniforms.uScene.value = sceneRT.texture;
|
|
195
|
+
compMat.uniforms.uRays.value = rtRays.texture;
|
|
196
|
+
compMat.uniforms.uIntensity.value = intensity;
|
|
197
|
+
fxQuad.material = compMat;
|
|
198
|
+
renderer.setRenderTarget(null);
|
|
199
|
+
renderer.render(fxScene, quadCamera);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
return passes;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export {
|
|
206
|
+
fundamentalLayerOf,
|
|
207
|
+
isGodraySource,
|
|
208
|
+
godraySourceFrames,
|
|
209
|
+
GODRAY_SOURCES,
|
|
210
|
+
GODRAY_SOURCE_DEFAULT,
|
|
211
|
+
resolveGodraySource,
|
|
212
|
+
makeGodrayPasses
|
|
213
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* providers/index.js — THE renderer registry: every built-in RendererProvider by its boot name.
|
|
5
|
+
*
|
|
6
|
+
* RENDERER_PROVIDERS[name] = { makeProvider(cfg) → provider, renderCapabilities() → descriptors }
|
|
7
|
+
*
|
|
8
|
+
* The boot module selects a provider by name from this table (`?renderer=` / `bootRealm({ renderer })`);
|
|
9
|
+
* `RenderCapabilitiesCommon.knownRenderCapabilityNames(RENDERER_PROVIDERS)` is the "known" set saved
|
|
10
|
+
* render rows are partitioned against. A BYO renderer is handed to the boot module as an instance; it
|
|
11
|
+
* never joins this table.
|
|
12
|
+
*
|
|
13
|
+
* Zero physics: a provider reads TickData and draws.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { makeP5Provider, _renderCapabilitiesP5 } from './p5-provider.js';
|
|
17
|
+
import { makeThreeProvider, _renderCapabilitiesThree } from './three-provider.js';
|
|
18
|
+
|
|
19
|
+
export const RENDERER_PROVIDERS = Object.freeze({
|
|
20
|
+
p5: Object.freeze({ makeProvider: makeP5Provider, renderCapabilities: _renderCapabilitiesP5 }),
|
|
21
|
+
three: Object.freeze({ makeProvider: makeThreeProvider, renderCapabilities: _renderCapabilitiesThree })
|
|
22
|
+
});
|