@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.
Files changed (218) hide show
  1. package/CLAUDE.md +87 -0
  2. package/LICENSE.md +664 -0
  3. package/README.md +84 -2
  4. package/dist/dist-manifest.json +218 -0
  5. package/dist/eyes/index.js +109 -0
  6. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  7. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  8. package/dist/fat/pkg-node/package.json +12 -0
  9. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  10. package/dist/frqtl/compat/boot-node.js +34 -0
  11. package/dist/frqtl/compat/engine.js +127 -0
  12. package/dist/frqtl/compat/package-root.js +24 -0
  13. package/dist/frqtl/compat/providers/wasm-engine.js +2499 -0
  14. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  15. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  16. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  17. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  18. package/dist/frqtl/compat/renderer/RenderFamily.js +84 -0
  19. package/dist/frqtl/compat/renderer/RenderMesh.js +99 -0
  20. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  21. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  22. package/dist/frqtl/compat/renderer/captureStill-common.js +258 -0
  23. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  24. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  25. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  26. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  27. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  28. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  30. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  31. package/dist/frqtl/compat/renderer/providers/three-provider.js +2470 -0
  32. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  33. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  34. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  35. package/dist/frqtl/compat/vendor.js +196 -0
  36. package/dist/frqtl/node-entry.js +17 -0
  37. package/dist/frqtl/objects/FrqtlApp.js +2920 -0
  38. package/dist/frqtl/objects/ObjectFactory.js +296 -0
  39. package/dist/frqtl/objects/RecipeContext.js +322 -0
  40. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  41. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  42. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  43. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  44. package/dist/frqtl/objects/instruments/capture.js +710 -0
  45. package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -0
  46. package/dist/frqtl/objects/kinds-entry.js +14 -0
  47. package/dist/frqtl/objects/kinds.js +190 -0
  48. package/dist/frqtl/objects/objects/Box.js +273 -0
  49. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  50. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  51. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  52. package/dist/frqtl/objects/objects/DirectedTunnel.js +411 -0
  53. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  54. package/dist/frqtl/objects/objects/LightSource.js +351 -0
  55. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  56. package/dist/frqtl/objects/objects/Tube.js +471 -0
  57. package/dist/frqtl/objects/objects/absorbRadiation.js +309 -0
  58. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  59. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  60. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  61. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  62. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  66. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  67. package/dist/frqtl/objects/objects/makeControlsForObject.js +820 -0
  68. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  69. package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
  70. package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
  71. package/dist/frqtl/objects/objects/tagger.js +228 -0
  72. package/dist/frqtl/objects/schema/engine-scopes.js +468 -0
  73. package/dist/frqtl/objects/schema/index.js +256 -0
  74. package/dist/frqtl/objects/schema/scopes.js +457 -0
  75. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  76. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  77. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  78. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  79. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  80. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  81. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  82. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  83. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +250 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -0
  85. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  86. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  87. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  88. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  89. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  90. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  91. package/dist/frqtl/objects/setup/setupLightSourceLab.js +277 -0
  92. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  94. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  95. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1258 -0
  96. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  97. package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
  98. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  99. package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
  100. package/dist/frqtl/objects/v1-replacements.js +29 -0
  101. package/dist/frqtl/package-core.js +23 -0
  102. package/dist/frqtl/parts/CameraControls.js +643 -0
  103. package/dist/frqtl/parts/CameraPath.js +575 -0
  104. package/dist/frqtl/parts/CollisionActions.js +34 -0
  105. package/dist/frqtl/parts/CollisionTypes.js +30 -0
  106. package/dist/frqtl/parts/DetectorWall.js +496 -0
  107. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  108. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  109. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  110. package/dist/frqtl/parts/GraphingDetectorWall.js +489 -0
  111. package/dist/frqtl/parts/LargeObject.js +405 -0
  112. package/dist/frqtl/parts/LightColor.js +255 -0
  113. package/dist/frqtl/parts/LiveChart.js +285 -0
  114. package/dist/frqtl/parts/LogicGate.js +717 -0
  115. package/dist/frqtl/parts/OvenBox.js +611 -0
  116. package/dist/frqtl/parts/PointSource.js +762 -0
  117. package/dist/frqtl/parts/Rectangle.js +200 -0
  118. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  119. package/dist/frqtl/parts/SlitScreen.js +628 -0
  120. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  121. package/dist/frqtl/parts/Trace.js +508 -0
  122. package/dist/frqtl/parts/Tunnel.js +1057 -0
  123. package/dist/frqtl/parts/Wall.js +1211 -0
  124. package/dist/frqtl/parts/index.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  126. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  127. package/dist/frqtl/scenes/atomic-model.js +88 -0
  128. package/dist/frqtl/scenes/big-bang.js +46 -0
  129. package/dist/frqtl/scenes/blackbody.js +81 -0
  130. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  131. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  132. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  133. package/dist/frqtl/scenes/counting-wall.js +38 -0
  134. package/dist/frqtl/scenes/double-slit.js +31 -0
  135. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  136. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  137. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  138. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +243 -0
  139. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  140. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  141. package/dist/frqtl/scenes/empty.js +14 -0
  142. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  143. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  144. package/dist/frqtl/scenes/mirror-route.js +140 -0
  145. package/dist/frqtl/scenes/mirror.js +38 -0
  146. package/dist/frqtl/scenes/omni-3d.js +27 -0
  147. package/dist/frqtl/scenes/omni-source.js +22 -0
  148. package/dist/frqtl/scenes/self-interference.js +39 -0
  149. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  150. package/dist/frqtl/scenes/straight-walk.js +22 -0
  151. package/dist/frqtl/scenes/sun.js +39 -0
  152. package/dist/frqtl/scenes/two-source.js +37 -0
  153. package/dist/frqtl/scenes/wave.js +26 -0
  154. package/dist/shim/bundle/frqnb.js +112 -0
  155. package/dist/shim/bundle/index.js +121 -0
  156. package/dist/shim/bundle/indexHtml.js +107 -0
  157. package/dist/shim/bundle/manifest.js +95 -0
  158. package/dist/shim/bundle/zip.js +243 -0
  159. package/dist/shim/bus.js +67 -0
  160. package/dist/shim/controls/index.js +627 -0
  161. package/dist/shim/controls/registry.js +274 -0
  162. package/dist/shim/engine/artifact-shell.js +211 -0
  163. package/dist/shim/engine/boot-phases.js +44 -0
  164. package/dist/shim/engine/engine-stack-boot.js +59 -0
  165. package/dist/shim/engine/router.js +690 -0
  166. package/dist/shim/experiment-frame/index.js +296 -0
  167. package/dist/shim/expr/ast-schema.js +369 -0
  168. package/dist/shim/field-descriptor/index.js +325 -0
  169. package/dist/shim/frame-data/index.js +224 -0
  170. package/dist/shim/index.js +67 -0
  171. package/dist/shim/notebook/canvas-guard.js +82 -0
  172. package/dist/shim/notebook/catalog.js +35 -0
  173. package/dist/shim/notebook/format.js +736 -0
  174. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  175. package/dist/shim/notebook/kernel.js +671 -0
  176. package/dist/shim/notebook/knob.js +150 -0
  177. package/dist/shim/notebook/module-resolver.js +260 -0
  178. package/dist/shim/notebook/notebook-knobs.js +65 -0
  179. package/dist/shim/notebook/produced-bindings.js +67 -0
  180. package/dist/shim/notebook/realm-registry.js +121 -0
  181. package/dist/shim/notebook/realm-transport.js +73 -0
  182. package/dist/shim/notebook/setup-builder.js +507 -0
  183. package/dist/shim/notebook/store.js +159 -0
  184. package/dist/shim/notebook/vendor-importmap.js +134 -0
  185. package/dist/shim/notebook/view-builder.js +189 -0
  186. package/dist/shim/notebook/view-ctx.js +100 -0
  187. package/dist/shim/notebook/view-handle.js +202 -0
  188. package/dist/shim/notebook/warmup.js +102 -0
  189. package/dist/shim/package.json +4 -0
  190. package/dist/shim/page-params/index.js +213 -0
  191. package/dist/shim/project/identity.js +112 -0
  192. package/dist/shim/project/index.js +19 -0
  193. package/dist/shim/project/link.js +402 -0
  194. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  195. package/dist/shim/project/register-node.mjs +17 -0
  196. package/dist/shim/project/specifiers.js +106 -0
  197. package/dist/shim/registry/copy-gate.js +110 -0
  198. package/dist/shim/registry/exhibits.js +455 -0
  199. package/dist/shim/registry/experiments.js +215 -0
  200. package/dist/shim/registry/index.js +19 -0
  201. package/dist/shim/registry/scene-source.js +157 -0
  202. package/dist/shim/registry/walkthrough.js +804 -0
  203. package/dist/shim/render/capture-envelope.js +279 -0
  204. package/dist/shim/render/heat-colormap.js +92 -0
  205. package/dist/shim/render/render-stages.js +855 -0
  206. package/dist/shim/render/sun-look.js +33 -0
  207. package/dist/shim/render-state/apply.js +262 -0
  208. package/dist/shim/render-state/index.js +74 -0
  209. package/dist/shim/rpc/index.js +207 -0
  210. package/dist/shim/runtime/index.js +1023 -0
  211. package/dist/shim/storage/boot.js +69 -0
  212. package/dist/shim/storage/index.js +169 -0
  213. package/dist/shim/storage/local-storage-provider.js +230 -0
  214. package/dist/shim/storage/make-namespace-store.js +132 -0
  215. package/dist/shim/storage/modules.js +249 -0
  216. package/dist/shim/storage/projects.js +150 -0
  217. package/llms.txt +12 -0
  218. package/package.json +63 -4
@@ -0,0 +1,99 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * RenderMesh — the ONE resolver for the `render.mesh` external-geometry intent.
5
+ *
6
+ * `render.mesh` is an OPEN-ID render-intent key (owned by lib/shim/frame-data
7
+ * RENDER_INTENT_KEYS; deliberately NO RENDER_INTENT_VALUES entry = accept-any-string).
8
+ * Its value is an external-geometry ASSET ID — e.g. an Objaverse glTF id or a served
9
+ * .glb/.gltf URL. It routes a frame/LO to a loaded mesh instead of a sprite/cube.
10
+ *
11
+ * Mirrors RenderShape: this file is the DECODE/RESOLVE authority (which asset id applies
12
+ * to a frame, via the priority chain). It does NOT load anything — asset loading + caching
13
+ * + FAIL-LOUD-on-failure live in the renderer provider (three GLTFLoader path). Keeping the
14
+ * resolver pure (no three import) means it runs in pkg-node/conformance unchanged.
15
+ *
16
+ * CANONICAL-STACK NOTE: WHICH mesh a frame maps to is a
17
+ * renderer/sugar/user-space CHOICE derived from FrameData (a frame's layer/mass/fullness,
18
+ * or an explicit per-frame `render.mesh` string_map key written by sugar). The engine emits
19
+ * primitives and carries `render.mesh` OPAQUE (I1) — it never interprets it.
20
+ *
21
+ * Priority chain (same shape as RenderShape.resolve):
22
+ * 1. render.mesh key in a FRAME's string_map (per-quantum override; highest priority)
23
+ * 2. `.mesh` field on a LARGE OBJECT (renderer-only LO field, parallel to lo.sprite /
24
+ * lo.renderHint — an LO carries render intent directly; it has no string_map bubble)
25
+ * 3. mesh in experiment render options (setRenderOption('mesh', id) default)
26
+ * 4. none (null) — object falls back to its sprite/cube/box/ball path
27
+ *
28
+ * Export contract (pure; no side effects, no three dependency):
29
+ * RenderMesh.resolve(frame, renderOpts) -> string | null (asset id, or null = no mesh)
30
+ * RenderMesh.hasMesh(frame, renderOpts) -> boolean
31
+ * RenderMesh.KEY -> 'render.mesh' (the frame-data-owned key name)
32
+ */
33
+
34
+ import * as FrameDataSchema from '../../../shim/frame-data/index.js';
35
+
36
+
37
+ // The open-id render-intent key this resolver decodes. Asserted against the canonical
38
+ // owner (lib/shim/frame-data RENDER_INTENT_KEYS) so a contract drop FAILS LOUD rather
39
+ // than silently resolving nothing — same fail-loud discipline as RenderShape's ferry.
40
+ var KEY = 'render.mesh';
41
+
42
+
43
+ var _schema = FrameDataSchema;
44
+ if (_schema.RENDER_INTENT_KEYS.indexOf(KEY) === -1) {
45
+ throw new Error(
46
+ 'RenderMesh: "' + KEY + '" is not a declared render-intent key in lib/shim/frame-data ' +
47
+ 'RENDER_INTENT_KEYS — the open-id key set must be owned there.'
48
+ );
49
+ }
50
+ // render.mesh is OPEN-ID: it must have NO RENDER_INTENT_VALUES entry (accept-any-string).
51
+ if (_schema.RENDER_INTENT_VALUES[KEY] !== undefined) {
52
+ throw new Error(
53
+ 'RenderMesh: "' + KEY + '" must be OPEN-ID (no RENDER_INTENT_VALUES entry) — an asset id ' +
54
+ 'is a free string validated at load time by the renderer, not an enum.'
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Resolve the external-mesh asset id for one renderable object, from the priority chain.
60
+ *
61
+ * @param {object} obj - a FRAME (obj.data.string_map may carry 'render.mesh') OR a
62
+ * LARGE OBJECT (obj.mesh may carry the asset id directly).
63
+ * @param {object} renderOpts - FrqtlApp._renderOptions (the setRenderOption bag).
64
+ * @returns {string|null} the asset id, or null when no mesh is routed (sprite/cube/box path applies).
65
+ */
66
+ function resolve(obj, renderOpts) {
67
+ var sm = obj && obj.data && obj.data.string_map;
68
+ if (sm && typeof sm[KEY] === 'string' && sm[KEY].length > 0) {
69
+ return sm[KEY];
70
+ }
71
+ // Per-LO renderer-only field. LOs have no frame-data bubble; they carry render intent
72
+ // directly on the object (like lo.sprite / lo.renderHint). This keeps RenderMesh the
73
+ // SINGLE resolver authority for the render.mesh intent across frames AND large objects.
74
+ if (obj && typeof obj.mesh === 'string' && obj.mesh.length > 0) {
75
+ return obj.mesh;
76
+ }
77
+ if (renderOpts && typeof renderOpts.mesh === 'string' && renderOpts.mesh.length > 0) {
78
+ return renderOpts.mesh;
79
+ }
80
+ return null;
81
+ }
82
+
83
+ /**
84
+ * True when a frame routes to an external mesh (resolve returns a non-null asset id).
85
+ * @param {object} frame
86
+ * @param {object} renderOpts
87
+ * @returns {boolean}
88
+ */
89
+ function hasMesh(frame, renderOpts) {
90
+ return resolve(frame, renderOpts) !== null;
91
+ }
92
+
93
+ var RenderMesh = {
94
+ KEY: KEY,
95
+ resolve: resolve,
96
+ hasMesh: hasMesh
97
+ };
98
+
99
+ export { RenderMesh };
@@ -0,0 +1,154 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * RenderShape — the ONE shape authority for all renderers.
5
+ *
6
+ * Resolves the per-frame render shape from the priority chain:
7
+ * 1. render.shape key in frame's string_map (per-frame override; highest priority)
8
+ * 2. shape in experiment render options (setRenderOption default; set by the experiment)
9
+ * 3. Renderer's own fallback (always 'ball')
10
+ *
11
+ * This is consumed by BOTH p5 and three providers — no per-provider shape logic.
12
+ *
13
+ * Why a single file: a render-color audit found that shape was
14
+ * driven by a construction-time global in three-provider
15
+ * and was absent entirely in p5-provider. That creates a provider divergence where
16
+ * the same experiment renders differently on each provider. RenderShape.js closes
17
+ * this by being the unique decoder both providers call.
18
+ *
19
+ * VALID SHAPES:
20
+ * 'ball' — round glowing point sprite (default; unchanged behavior for all existing
21
+ * experiments). In three: Gaussian glow in the fragment shader; in p5: ellipse.
22
+ * 'box' — filled square at the voxel size (1 Dim-0 = 1 screen px). In three: the
23
+ * existing Chebyshev-fill box shader branch (uShape=1); in p5: rect.
24
+ * 'circle' — outline circle (no fill). In three: same as ball but outline-only via
25
+ * discard inside radius; in p5: noFill+stroke ellipse.
26
+ * 'cube' — TRUE shaded 3D cube mesh (three.js InstancedMesh; 3D mode only). In three
27
+ * (when _is3D): an InstancedMesh of BoxGeometry with per-face brightness
28
+ * shading (front/top brighter than sides/back) so cube faces are visually
29
+ * distinct. In 2D or p5: silently falls back to 'box' (graceful degradation —
30
+ * the engine/physics are untouched; only the render path changes). The cube
31
+ * render path completely BYPASSES the Points mesh and GLSL shader; it uses a
32
+ * real THREE.Mesh so depth-sorting and face normals work correctly.
33
+ * 'sphere' — TRUE 3D instanced-sphere mesh (three.js InstancedMesh; 3D mode only) —
34
+ * SYMMETRIC with 'cube' (gated on isSphereMesh, exactly like isCubeMesh). In
35
+ * three (when _is3D): a per-instance-colored SphereGeometry InstancedMesh (the
36
+ * R6 "2D coloring extended to 3D" baseline) that applies render.color per
37
+ * instance, so render.shape and render.color stay INDEPENDENT (a forced-red
38
+ * sphere is a red sphere, not a point). In 2D or p5: degrades to the filled
39
+ * 'ball' disc — toUniform('sphere')=0 (ball glow) on the three Points path, and
40
+ * p5's default (else) branch draws it as the ball ellipse. No per-provider map:
41
+ * this file is the one authority; the fold-in retired the old shapeMesh3D flag.
42
+ *
43
+ * Export contract:
44
+ * RenderShape.resolve(frame, renderOpts) -> 'ball' | 'box' | 'circle' | 'cube' | 'sphere'
45
+ * RenderShape.toUniform(shape) -> number (three uShape value; cube/sphere→0 = ball
46
+ * because cube/sphere bypass the point-sprite shader)
47
+ * RenderShape.isCubeMesh(shape) -> boolean (true for 'cube' — instanced-mesh gate)
48
+ * RenderShape.isSphereMesh(shape) -> boolean (true for 'sphere' — instanced-mesh gate)
49
+ * RenderShape.VALID_SHAPES -> string[] (FERRIED from lib/shim/frame-data
50
+ * RENDER_INTENT_VALUES['render.shape'] — NOT a source here)
51
+ *
52
+ * Single-source: the shape VALUE set is owned by lib/shim/frame-data (the foundational
53
+ * cross-engine render-intent contract). RenderShape is the DECODE authority (resolve/
54
+ * isCubeMesh/toUniform) but re-exports the value list rather than re-listing it, so adding
55
+ * a shape is one edit at frame-data.
56
+ */
57
+
58
+ import * as FrameDataSchema from '../../../shim/frame-data/index.js';
59
+
60
+
61
+
62
+ var _schema = FrameDataSchema;
63
+ var VALID_SHAPES = _schema.RENDER_INTENT_VALUES['render.shape'];
64
+ if (!Array.isArray(VALID_SHAPES) || VALID_SHAPES.length === 0) {
65
+ throw new Error(
66
+ 'RenderShape: frame-data RENDER_INTENT_VALUES["render.shape"] is missing or empty — ' +
67
+ 'the shape value set must be owned by lib/shim/frame-data.'
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Resolve the shape for one frame, from the priority chain.
73
+ *
74
+ * @param {object} frame - FrameData object; frame.data.string_map may carry 'render.shape'.
75
+ * @param {object} renderOpts - FrqtlApp._renderOptions (the setRenderOption bag).
76
+ * @returns {string} a member of VALID_SHAPES (ferried from frame-data's
77
+ * RENDER_INTENT_VALUES['render.shape']: 'ball'|'box'|'circle'|'cube'|'sphere').
78
+ */
79
+ function resolve(frame, renderOpts) {
80
+ // 1. Per-frame override via string_map (highest priority).
81
+ var sm = frame && frame.data && frame.data.string_map;
82
+ if (sm && sm['render.shape'] && VALID_SHAPES.indexOf(sm['render.shape']) !== -1) {
83
+ return sm['render.shape'];
84
+ }
85
+ // 2. Experiment default via setRenderOption('shape', ...).
86
+ var optShape = renderOpts && renderOpts.shape;
87
+ if (optShape && VALID_SHAPES.indexOf(optShape) !== -1) {
88
+ return optShape;
89
+ }
90
+ // 3. Renderer default.
91
+ return 'ball';
92
+ }
93
+
94
+ /**
95
+ * Convert a resolved shape name to the three.js uShape uniform value.
96
+ *
97
+ * uShape is a float consumed by the QUANTA_FRAG shader (the Points mesh path):
98
+ * 0.0 = ball (round Gaussian glow — default)
99
+ * 1.0 = box (Chebyshev-fill solid square)
100
+ *
101
+ * 'circle' renders via the ball glow path in three (a full separate branch
102
+ * is a future enhancement — for now circle ≡ ball in three, and is
103
+ * distinct only in p5 where the stroke/noFill path is available without
104
+ * any shader change).
105
+ *
106
+ * 'cube' in 3D mode BYPASSES the Points mesh and GLSL shader entirely — the
107
+ * three-provider switches to an InstancedMesh of BoxGeometry instead. This
108
+ * function returns 0.0 for 'cube' as a safe sentinel; the caller must gate
109
+ * on RenderShape.isCubeMesh(shape) to know whether to use the instanced-mesh
110
+ * path vs the Points path. 'sphere' is the same: an instanced mesh in 3D
111
+ * (gate: isSphereMesh), 0.0 here.
112
+ *
113
+ * @param {string} shape - a member of VALID_SHAPES: 'ball' | 'box' | 'circle' | 'cube' | 'sphere'
114
+ * @returns {number} - 0.0 (ball/circle/cube/sphere) or 1.0 (box)
115
+ */
116
+ function toUniform(shape) {
117
+ return (shape === 'box') ? 1.0 : 0.0;
118
+ }
119
+
120
+ /**
121
+ * True when shape should be rendered as a 3D InstancedMesh cube (not a point sprite).
122
+ * The three-provider gates on this to switch between the Points path and the instanced-mesh path.
123
+ * In 2D or p5, 'cube' falls back to 'box' in resolve() before isCubeMesh is called.
124
+ *
125
+ * @param {string} shape - resolved shape name
126
+ * @returns {boolean}
127
+ */
128
+ function isCubeMesh(shape) {
129
+ return shape === 'cube';
130
+ }
131
+
132
+ /**
133
+ * True when shape should be rendered as a 3D InstancedMesh sphere (not a point sprite) —
134
+ * exactly parallel to isCubeMesh. The three-provider gates its sphere InstancedMesh path
135
+ * on `_is3D && isSphereMesh(resolvedShape)`, symmetric with the cube gate. In 2D or p5,
136
+ * 'sphere' degrades to the filled 'ball' disc (toUniform('sphere')=0 / p5's ball branch)
137
+ * before this matters.
138
+ *
139
+ * @param {string} shape - resolved shape name
140
+ * @returns {boolean}
141
+ */
142
+ function isSphereMesh(shape) {
143
+ return shape === 'sphere';
144
+ }
145
+
146
+ var RenderShape = {
147
+ VALID_SHAPES: VALID_SHAPES,
148
+ resolve: resolve,
149
+ toUniform: toUniform,
150
+ isCubeMesh: isCubeMesh,
151
+ isSphereMesh: isSphereMesh
152
+ };
153
+
154
+ export { RenderShape };
@@ -0,0 +1,183 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * camera2d-geometry.js — THE 2D camera map: the `affine2d` projection's one authority.
5
+ *
6
+ * Peer of `providers/three-camera-geometry.js` (the pinhole authority) and, like it, PURE and
7
+ * backend-free so it is unit-testable in plain Node with no canvas. It lives at
8
+ * `compat/renderer/` rather than under `providers/` because it is NOT p5-specific: it is the
9
+ * math of the `projection:{kind:'affine2d'}` mode, and three's own 2D orthographic mode
10
+ * reports that same kind. Naming it after one provider would be a lie the day the other uses it.
11
+ *
12
+ * ── Why 2D pan/zoom is a CAMERA concern, not a render option ─────────────────
13
+ *
14
+ * Three reasons, each decisive alone (ratified 2026-07-30):
15
+ *
16
+ * 1. **One write path / honest provenance.** Every still stamps `pose` from `cameraControl`.
17
+ * If 2D pan/zoom lived on the render surface (a `setRenderOption`, a view rect), a ZOOMED
18
+ * 2D still's `pose` would read `null` — a mislabeled training record, which is the corpus
19
+ * corruption channel reopened by the very feature meant to serve VLA.
20
+ * 2. **One mapping authority.** `screenToWorld` in 2D must change under pan/zoom. If the
21
+ * pan/zoom state lived anywhere but the camera authority, two authorities would feed one
22
+ * mapping — the split-basis failure the 3D camera consolidation just eliminated.
23
+ * 3. **One vocabulary.** "Look at point P at magnification Z" is the same sentence in 2D and
24
+ * 3D. In 3D it is already `target` + `distance`. Minting a parallel 2D vocabulary
25
+ * (`viewRect`, `panX`/`panY`/`zoom`) would fork the pose bag AND fork `CameraPath`.
26
+ *
27
+ * ── The degenerate orbit bag ─────────────────────────────────────────────────
28
+ *
29
+ * The 2D pose IS the orbit bag with the angular triple absent — NOT a new bag type:
30
+ *
31
+ * `target: {x, y}` — the physics-px point at the VIEW CENTRE; `null` = the canvas centre.
32
+ * **Pan IS target, in both modes — the same key.**
33
+ * `distance` — the VISIBLE PHYSICS-PX HEIGHT of the view; smaller = zoomed IN.
34
+ *
35
+ * `distance` is not a pun. In 3D at a fixed vertical FOV the visible height at the target
36
+ * plane is `2·distance·tan(fovY/2)`, so `distance` ALREADY means "how much world you see" in
37
+ * both modes — one observable, two projections. There is deliberately no `zoom` field: a
38
+ * second magnification vocabulary would fork the bag. "Zoom into that region" is
39
+ * `setOrbit({ target: regionCentre, distance: regionHeight })`.
40
+ *
41
+ * A consumer NEVER asks "is this 2D?". It asks which pose fields exist: the handle's
42
+ * `poseFields` declares exactly the axes it has, so a host hides the angular affordances and
43
+ * shows the rest by FIELD PRESENCE — the same capability idiom as `roll` and `move()`.
44
+ *
45
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
46
+ *
47
+ * POSE_FIELDS_2D(w, h) — the declared pose axes (distance, target). No azimuth /
48
+ * elevation / roll: this camera does not have them.
49
+ * PRESETS_2D(w, h) — the named framings, declared beside the axes.
50
+ * defaultPose2d(w, h) — `{distance: h, target: null}` = the whole canvas, 1:1.
51
+ * resolveView2d(pose, w, h) — `{k, cx, cy}`: scale factor and the physics-px point at
52
+ * the view centre. THE one place the transform is derived.
53
+ * screenToWorld2d(pose, w, h, sx, sy, sw, sh) — a point in canvas/buffer space → physics px.
54
+ *
55
+ * At the default pose `k === 1` and the map is the IDENTITY — so every existing 2D caller,
56
+ * drag, pick and capture is byte-unchanged until someone actually pans or zooms.
57
+ *
58
+ * ── Layer 2: physics behaviour ───────────────────────────────────────────────
59
+ * ZERO physics. A view transform at the output boundary: it changes which pixels you are
60
+ * shown, never a frame, a dimension or a tick. Zooming does not alter the simulation, which
61
+ * is exactly why it is safe as an observation instrument.
62
+ */
63
+
64
+ import * as FieldDescriptorSchema from '../../../shim/field-descriptor/index.js';
65
+
66
+
67
+
68
+ function _fail(msg) {
69
+ throw new Error('[camera2d-geometry] ' + msg + ' (FAIL-LOUD.)');
70
+ }
71
+
72
+
73
+ /**
74
+ * The DECLARED pose axes of a 2D camera — normalized field descriptors, the same shape the
75
+ * 3D camera publishes. A host renders whatever THIS camera declares; the absence of
76
+ * `azimuth`/`elevation`/`roll` is what hides the angular affordances, with no mode sniffing
77
+ * anywhere.
78
+ * @param {number} width @param {number} height — physics canvas dims
79
+ * @returns {Array<object>}
80
+ */
81
+ function POSE_FIELDS_2D(width, height) {
82
+ var nfd = FieldDescriptorSchema.normalizeFieldDescriptor;
83
+ return [
84
+ nfd({
85
+ name: 'distance', label: 'zoom', type: 'number', unit: 'px', default: height,
86
+ // Down to 8 px of visible height is a deep zoom (native pixel scale and beyond);
87
+ // up to 4x the canvas frames any scene with margin.
88
+ constraint: { min: 8, max: Math.max(height * 4, 32), integer: false },
89
+ widgetConfig: { step: Math.max(1, Math.round(height / 60)) },
90
+ description: 'The VISIBLE HEIGHT of the view in physics pixels — smaller = zoomed IN. ' +
91
+ 'Default = the full canvas height (1:1). The same observable as the 3D camera\'s ' +
92
+ 'distance: "how much world you see".'
93
+ }),
94
+ nfd({
95
+ name: 'target', label: 'centre', type: 'object', unit: 'px', default: null,
96
+ description: 'The physics-px point {x,y} at the view centre; null = the canvas centre. ' +
97
+ 'Pan IS target — the same key the 3D camera uses.'
98
+ })
99
+ ];
100
+ }
101
+
102
+ /**
103
+ * PRESETS_2D — the named framings this camera offers, DECLARED beside its axes.
104
+ *
105
+ * Lives here rather than inline in the provider for the same reason `POSE_FIELDS_2D` does:
106
+ * the 3D camera keeps its preset TABLE and its pose descriptors together in
107
+ * `three-camera-contract.js`, and splitting the 2D pair across two files would leave the
108
+ * provider holding declarative data. A 2D camera has exactly one framing — the whole canvas.
109
+ *
110
+ * @param {number} width @param {number} height — physics canvas dims
111
+ * @returns {Object<string,object>} preset name → the orbit-bag patch it applies
112
+ */
113
+ function PRESETS_2D(width, height) {
114
+ return { fit: { target: null, distance: height } };
115
+ }
116
+
117
+ /** `{distance: height, target: null}` — the whole canvas at 1:1. @returns {object} */
118
+ function defaultPose2d(width, height) {
119
+ return { distance: height, target: null };
120
+ }
121
+
122
+ /**
123
+ * resolveView2d(pose, width, height) → `{k, cx, cy}` — THE derivation of the 2D view.
124
+ *
125
+ * k — scale factor = `height / distance`. `k > 1` is zoomed in.
126
+ * cx, cy — the physics-px point held at the view centre (`target`, or the canvas centre).
127
+ *
128
+ * Every consumer (the provider's draw transform, `screenToWorld`, the offline
129
+ * `unprojectStill`) derives from THIS, so the picture and the mapping can never disagree.
130
+ *
131
+ * @param {object|null} pose — a (possibly partial) orbit bag
132
+ * @param {number} width @param {number} height — physics canvas dims
133
+ * @returns {{k:number, cx:number, cy:number}}
134
+ */
135
+ function resolveView2d(pose, width, height) {
136
+ if (!(width > 0 && height > 0)) { _fail('physics dims must be positive, got ' + width + 'x' + height); }
137
+ var d = (pose && typeof pose.distance === 'number' && isFinite(pose.distance) && pose.distance > 0)
138
+ ? pose.distance : height;
139
+ var t = (pose && pose.target && typeof pose.target.x === 'number' && typeof pose.target.y === 'number')
140
+ ? pose.target : null;
141
+ return {
142
+ k: height / d,
143
+ cx: t ? t.x : width / 2,
144
+ cy: t ? t.y : height / 2
145
+ };
146
+ }
147
+
148
+ /**
149
+ * screenToWorld2d — a point in CANVAS space → physics px, under the current 2D pose.
150
+ *
151
+ * `srcX/srcY` are in the coordinate space whose extent is `srcW/srcH`. That is deliberately
152
+ * general: the live provider passes CSS pixels and the canvas's CSS size, while the OFFLINE
153
+ * `unprojectStill` passes BUFFER pixels and the still's buffer dims. Same map, one
154
+ * implementation — which is what stops an offline measurement from drifting from the live
155
+ * drag/pick.
156
+ *
157
+ * @param {object|null} pose @param {number} width @param {number} height — physics dims
158
+ * @param {number} srcX @param {number} srcY @param {number} srcW @param {number} srcH
159
+ * @returns {{x:number, y:number}}
160
+ */
161
+ function screenToWorld2d(pose, width, height, srcX, srcY, srcW, srcH) {
162
+ var sw = (srcW > 0) ? srcW : width;
163
+ var sh = (srcH > 0) ? srcH : height;
164
+ var v = resolveView2d(pose, width, height);
165
+ // 1. source space → canvas-draw space (physics px on an unzoomed canvas)
166
+ var dx = srcX * (width / sw);
167
+ var dy = srcY * (height / sh);
168
+ // 2. invert the draw transform: translate(w/2,h/2) · scale(k) · translate(-cx,-cy)
169
+ return {
170
+ x: (dx - width / 2) / v.k + v.cx,
171
+ y: (dy - height / 2) / v.k + v.cy
172
+ };
173
+ }
174
+
175
+ // ── Export ────────────────────────────────────────────────────────────────────
176
+
177
+ export {
178
+ POSE_FIELDS_2D,
179
+ PRESETS_2D,
180
+ defaultPose2d,
181
+ resolveView2d,
182
+ screenToWorld2d
183
+ };