@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.
Files changed (217) hide show
  1. package/CLAUDE.md +87 -0
  2. package/README.md +84 -2
  3. package/dist/dist-manifest.json +218 -0
  4. package/dist/eyes/index.js +109 -0
  5. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  6. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  7. package/dist/fat/pkg-node/package.json +12 -0
  8. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  9. package/dist/frqtl/compat/boot-node.js +34 -0
  10. package/dist/frqtl/compat/engine.js +127 -0
  11. package/dist/frqtl/compat/package-root.js +24 -0
  12. package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
  13. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  14. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  15. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  16. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  17. package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
  18. package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
  19. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  20. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  21. package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
  22. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  23. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  24. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  25. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  26. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  27. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  28. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  30. package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
  31. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  32. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  33. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  34. package/dist/frqtl/compat/vendor.js +196 -0
  35. package/dist/frqtl/node-entry.js +17 -0
  36. package/dist/frqtl/objects/FrqtlApp.js +2922 -0
  37. package/dist/frqtl/objects/ObjectFactory.js +297 -0
  38. package/dist/frqtl/objects/RecipeContext.js +322 -0
  39. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  40. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  41. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  42. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  43. package/dist/frqtl/objects/instruments/capture.js +711 -0
  44. package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
  45. package/dist/frqtl/objects/kinds-entry.js +14 -0
  46. package/dist/frqtl/objects/kinds.js +190 -0
  47. package/dist/frqtl/objects/objects/Box.js +273 -0
  48. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  49. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  50. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  51. package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
  52. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  53. package/dist/frqtl/objects/objects/LightSource.js +354 -0
  54. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  55. package/dist/frqtl/objects/objects/Tube.js +471 -0
  56. package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
  57. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  58. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  59. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  60. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  61. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  62. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  66. package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
  67. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  68. package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
  69. package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
  70. package/dist/frqtl/objects/objects/tagger.js +228 -0
  71. package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
  72. package/dist/frqtl/objects/schema/index.js +256 -0
  73. package/dist/frqtl/objects/schema/scopes.js +457 -0
  74. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  75. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  76. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  77. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  78. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  79. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  80. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  81. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  82. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
  83. package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  85. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  86. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  87. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  88. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  89. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  90. package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
  91. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  92. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  94. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
  95. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  96. package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
  97. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  98. package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
  99. package/dist/frqtl/objects/v1-replacements.js +30 -0
  100. package/dist/frqtl/package-core.js +23 -0
  101. package/dist/frqtl/parts/CameraControls.js +643 -0
  102. package/dist/frqtl/parts/CameraPath.js +575 -0
  103. package/dist/frqtl/parts/CollisionActions.js +35 -0
  104. package/dist/frqtl/parts/CollisionTypes.js +31 -0
  105. package/dist/frqtl/parts/DetectorWall.js +496 -0
  106. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  107. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  108. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  109. package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
  110. package/dist/frqtl/parts/LargeObject.js +406 -0
  111. package/dist/frqtl/parts/LightColor.js +255 -0
  112. package/dist/frqtl/parts/LiveChart.js +285 -0
  113. package/dist/frqtl/parts/LogicGate.js +720 -0
  114. package/dist/frqtl/parts/OvenBox.js +612 -0
  115. package/dist/frqtl/parts/PointSource.js +763 -0
  116. package/dist/frqtl/parts/Rectangle.js +200 -0
  117. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  118. package/dist/frqtl/parts/SlitScreen.js +628 -0
  119. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  120. package/dist/frqtl/parts/Trace.js +508 -0
  121. package/dist/frqtl/parts/Tunnel.js +1057 -0
  122. package/dist/frqtl/parts/Wall.js +1211 -0
  123. package/dist/frqtl/parts/index.js +33 -0
  124. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  126. package/dist/frqtl/scenes/atomic-model.js +88 -0
  127. package/dist/frqtl/scenes/big-bang.js +46 -0
  128. package/dist/frqtl/scenes/blackbody.js +81 -0
  129. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  130. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  131. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  132. package/dist/frqtl/scenes/counting-wall.js +38 -0
  133. package/dist/frqtl/scenes/double-slit.js +31 -0
  134. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  135. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  136. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  137. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
  138. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  139. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  140. package/dist/frqtl/scenes/empty.js +14 -0
  141. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  142. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  143. package/dist/frqtl/scenes/mirror-route.js +140 -0
  144. package/dist/frqtl/scenes/mirror.js +38 -0
  145. package/dist/frqtl/scenes/omni-3d.js +27 -0
  146. package/dist/frqtl/scenes/omni-source.js +22 -0
  147. package/dist/frqtl/scenes/self-interference.js +39 -0
  148. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  149. package/dist/frqtl/scenes/straight-walk.js +22 -0
  150. package/dist/frqtl/scenes/sun.js +39 -0
  151. package/dist/frqtl/scenes/two-source.js +37 -0
  152. package/dist/frqtl/scenes/wave.js +26 -0
  153. package/dist/shim/bundle/frqnb.js +112 -0
  154. package/dist/shim/bundle/index.js +121 -0
  155. package/dist/shim/bundle/indexHtml.js +107 -0
  156. package/dist/shim/bundle/manifest.js +95 -0
  157. package/dist/shim/bundle/zip.js +243 -0
  158. package/dist/shim/bus.js +67 -0
  159. package/dist/shim/controls/index.js +627 -0
  160. package/dist/shim/controls/registry.js +274 -0
  161. package/dist/shim/engine/artifact-shell.js +212 -0
  162. package/dist/shim/engine/boot-phases.js +44 -0
  163. package/dist/shim/engine/engine-stack-boot.js +59 -0
  164. package/dist/shim/engine/router.js +692 -0
  165. package/dist/shim/experiment-frame/index.js +296 -0
  166. package/dist/shim/expr/ast-schema.js +369 -0
  167. package/dist/shim/field-descriptor/index.js +326 -0
  168. package/dist/shim/frame-data/index.js +224 -0
  169. package/dist/shim/index.js +67 -0
  170. package/dist/shim/notebook/canvas-guard.js +82 -0
  171. package/dist/shim/notebook/catalog.js +35 -0
  172. package/dist/shim/notebook/format.js +736 -0
  173. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  174. package/dist/shim/notebook/kernel.js +672 -0
  175. package/dist/shim/notebook/knob.js +151 -0
  176. package/dist/shim/notebook/module-resolver.js +260 -0
  177. package/dist/shim/notebook/notebook-knobs.js +65 -0
  178. package/dist/shim/notebook/produced-bindings.js +67 -0
  179. package/dist/shim/notebook/realm-registry.js +121 -0
  180. package/dist/shim/notebook/realm-transport.js +73 -0
  181. package/dist/shim/notebook/setup-builder.js +510 -0
  182. package/dist/shim/notebook/store.js +159 -0
  183. package/dist/shim/notebook/vendor-importmap.js +134 -0
  184. package/dist/shim/notebook/view-builder.js +189 -0
  185. package/dist/shim/notebook/view-ctx.js +100 -0
  186. package/dist/shim/notebook/view-handle.js +202 -0
  187. package/dist/shim/notebook/warmup.js +102 -0
  188. package/dist/shim/package.json +4 -0
  189. package/dist/shim/page-params/index.js +214 -0
  190. package/dist/shim/project/identity.js +112 -0
  191. package/dist/shim/project/index.js +19 -0
  192. package/dist/shim/project/link.js +402 -0
  193. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  194. package/dist/shim/project/register-node.mjs +17 -0
  195. package/dist/shim/project/specifiers.js +106 -0
  196. package/dist/shim/registry/copy-gate.js +110 -0
  197. package/dist/shim/registry/exhibits.js +455 -0
  198. package/dist/shim/registry/experiments.js +215 -0
  199. package/dist/shim/registry/index.js +19 -0
  200. package/dist/shim/registry/scene-source.js +157 -0
  201. package/dist/shim/registry/walkthrough.js +807 -0
  202. package/dist/shim/render/capture-envelope.js +279 -0
  203. package/dist/shim/render/heat-colormap.js +92 -0
  204. package/dist/shim/render/render-stages.js +855 -0
  205. package/dist/shim/render/sun-look.js +33 -0
  206. package/dist/shim/render-state/apply.js +262 -0
  207. package/dist/shim/render-state/index.js +74 -0
  208. package/dist/shim/rpc/index.js +207 -0
  209. package/dist/shim/runtime/index.js +1023 -0
  210. package/dist/shim/storage/boot.js +69 -0
  211. package/dist/shim/storage/index.js +169 -0
  212. package/dist/shim/storage/local-storage-provider.js +230 -0
  213. package/dist/shim/storage/make-namespace-store.js +132 -0
  214. package/dist/shim/storage/modules.js +249 -0
  215. package/dist/shim/storage/projects.js +150 -0
  216. package/llms.txt +12 -0
  217. 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
+ });