@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,100 @@
|
|
|
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 (CEO-ratified 2026-06-30): 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. See
|
|
20
|
+
* docs/internal/engine/RENDERER_CONTRACT.md §9.
|
|
21
|
+
*
|
|
22
|
+
* Priority chain (same shape as RenderShape.resolve):
|
|
23
|
+
* 1. render.mesh key in a FRAME's string_map (per-quantum override; highest priority)
|
|
24
|
+
* 2. `.mesh` field on a LARGE OBJECT (renderer-only LO field, parallel to lo.sprite /
|
|
25
|
+
* lo.renderHint — an LO carries render intent directly; it has no string_map bubble)
|
|
26
|
+
* 3. mesh in experiment render options (setRenderOption('mesh', id) default)
|
|
27
|
+
* 4. none (null) — object falls back to its sprite/cube/box/ball path
|
|
28
|
+
*
|
|
29
|
+
* Export contract (pure; no side effects, no three dependency):
|
|
30
|
+
* RenderMesh.resolve(frame, renderOpts) -> string | null (asset id, or null = no mesh)
|
|
31
|
+
* RenderMesh.hasMesh(frame, renderOpts) -> boolean
|
|
32
|
+
* RenderMesh.KEY -> 'render.mesh' (the frame-data-owned key name)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import * as FrameDataSchema from '../../../shim/frame-data/index.js';
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// The open-id render-intent key this resolver decodes. Asserted against the canonical
|
|
39
|
+
// owner (lib/shim/frame-data RENDER_INTENT_KEYS) so a contract drop FAILS LOUD rather
|
|
40
|
+
// than silently resolving nothing — same fail-loud discipline as RenderShape's ferry.
|
|
41
|
+
var KEY = 'render.mesh';
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
var _schema = FrameDataSchema;
|
|
45
|
+
if (_schema.RENDER_INTENT_KEYS.indexOf(KEY) === -1) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'RenderMesh: "' + KEY + '" is not a declared render-intent key in lib/shim/frame-data ' +
|
|
48
|
+
'RENDER_INTENT_KEYS — the open-id key set must be owned there.'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
// render.mesh is OPEN-ID: it must have NO RENDER_INTENT_VALUES entry (accept-any-string).
|
|
52
|
+
if (_schema.RENDER_INTENT_VALUES[KEY] !== undefined) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'RenderMesh: "' + KEY + '" must be OPEN-ID (no RENDER_INTENT_VALUES entry) — an asset id ' +
|
|
55
|
+
'is a free string validated at load time by the renderer, not an enum.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the external-mesh asset id for one renderable object, from the priority chain.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} obj - a FRAME (obj.data.string_map may carry 'render.mesh') OR a
|
|
63
|
+
* LARGE OBJECT (obj.mesh may carry the asset id directly).
|
|
64
|
+
* @param {object} renderOpts - FrqtlApp._renderOptions (the setRenderOption bag).
|
|
65
|
+
* @returns {string|null} the asset id, or null when no mesh is routed (sprite/cube/box path applies).
|
|
66
|
+
*/
|
|
67
|
+
function resolve(obj, renderOpts) {
|
|
68
|
+
var sm = obj && obj.data && obj.data.string_map;
|
|
69
|
+
if (sm && typeof sm[KEY] === 'string' && sm[KEY].length > 0) {
|
|
70
|
+
return sm[KEY];
|
|
71
|
+
}
|
|
72
|
+
// Per-LO renderer-only field. LOs have no frame-data bubble; they carry render intent
|
|
73
|
+
// directly on the object (like lo.sprite / lo.renderHint). This keeps RenderMesh the
|
|
74
|
+
// SINGLE resolver authority for the render.mesh intent across frames AND large objects.
|
|
75
|
+
if (obj && typeof obj.mesh === 'string' && obj.mesh.length > 0) {
|
|
76
|
+
return obj.mesh;
|
|
77
|
+
}
|
|
78
|
+
if (renderOpts && typeof renderOpts.mesh === 'string' && renderOpts.mesh.length > 0) {
|
|
79
|
+
return renderOpts.mesh;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* True when a frame routes to an external mesh (resolve returns a non-null asset id).
|
|
86
|
+
* @param {object} frame
|
|
87
|
+
* @param {object} renderOpts
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
function hasMesh(frame, renderOpts) {
|
|
91
|
+
return resolve(frame, renderOpts) !== null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var RenderMesh = {
|
|
95
|
+
KEY: KEY,
|
|
96
|
+
resolve: resolve,
|
|
97
|
+
hasMesh: hasMesh
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
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 (architect ruling 2026-06-30).
|
|
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
|
+
};
|