@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,134 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wasm-load-browser.js — the BROWSER loader for the WASM engine (an ES module).
|
|
5
|
+
*
|
|
6
|
+
* Imports the wasm-pack --target web glue located BESIDE this tree —
|
|
7
|
+
* `new URL('../../../fat/pkg-web/egpt_wasm.js', import.meta.url)` — and returns the resolved
|
|
8
|
+
* WasmUniverse constructor so wasm-engine.js stays env-agnostic. The boot door hands this
|
|
9
|
+
* loader to `bootEngine` (compat/engine.js); nothing here publishes a global or a ready promise.
|
|
10
|
+
*
|
|
11
|
+
* A host serves the engine at its lib/-relative placement (frqtl/compat/… beside fat/pkg-web/…),
|
|
12
|
+
* so the glue's location is a fact of this file's own URL — there is no override global and no
|
|
13
|
+
* probe list (a probe list is a silent search). `load(glueUrl)` takes an explicit URL only for a
|
|
14
|
+
* test that must cache-bust the glue.
|
|
15
|
+
*
|
|
16
|
+
* Artifact-safe init (ws-artifact-showcase Bite A5):
|
|
17
|
+
* We NEVER call the wasm-pack glue's `default()`/`__wbg_init()` bare (no
|
|
18
|
+
* args) — bare, it builds its own `new URL('egpt_wasm_bg.wasm', import.meta.url)`
|
|
19
|
+
* and does `fetch(urlObject)` internally, which throws `DataCloneError`
|
|
20
|
+
* inside a claude.ai Artifact iframe (its `window.fetch` is proxied to the
|
|
21
|
+
* parent via `postMessage`, and a URL object is not structured-clonable).
|
|
22
|
+
* Instead we self-fetch the .wasm bytes with a plain STRING url (strings
|
|
23
|
+
* clone fine) and hand the glue raw bytes — see the inline comment at the
|
|
24
|
+
* `wasmModule.default(...)` call site for the full trace. Regression test:
|
|
25
|
+
* wasm-load-browser.test.mjs.
|
|
26
|
+
*
|
|
27
|
+
* Package content identity:
|
|
28
|
+
* The self-fetched bytes ARE the executing binary, so this is where its identity is
|
|
29
|
+
* taken: `loadedWasmSha256()` is the sha256 hex of the `.wasm` instantiated — the same
|
|
30
|
+
* definition `published/<id>/PINNED.json` records as `engine.wasmSha256`. The capture
|
|
31
|
+
* instrument pins it as `build.wasmSha256`; the serving URL is never identity (the same
|
|
32
|
+
* bytes from two origins regenerate one scene). Gate: scene-identity-origin.test.mjs.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** The glue's URL: a fact of this module's own placement. */
|
|
36
|
+
export const GLUE_URL = new URL('../../../fat/pkg-web/egpt_wasm.js', import.meta.url).href;
|
|
37
|
+
|
|
38
|
+
/** sha256 hex of the last binary this loader instantiated; null before any load. */
|
|
39
|
+
let _loadedWasmSha256 = null;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* sha256 hex of `bytes` via `crypto.subtle`, or null where the page has none (a
|
|
43
|
+
* non-secure origin). Null there cannot mint a false identity: `RenderStages.sceneHash`
|
|
44
|
+
* refuses to hash without the same `crypto.subtle`, so no scene id exists in that page.
|
|
45
|
+
*/
|
|
46
|
+
async function _sha256Hex(bytes) {
|
|
47
|
+
const subtle = (typeof crypto !== 'undefined') ? crypto.subtle : null;
|
|
48
|
+
if (!subtle || typeof subtle.digest !== 'function') { return null; }
|
|
49
|
+
const view = new Uint8Array(await subtle.digest('SHA-256', bytes));
|
|
50
|
+
let hex = '';
|
|
51
|
+
for (let i = 0; i < view.length; i++) {
|
|
52
|
+
hex += (view[i] < 16 ? '0' : '') + view[i].toString(16);
|
|
53
|
+
}
|
|
54
|
+
return hex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @returns {string|null} sha256 hex of the instantiated `.wasm`, or null. */
|
|
58
|
+
export function loadedWasmSha256() {
|
|
59
|
+
return _loadedWasmSha256;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load the WASM web bundle and return the WasmUniverse constructor.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} [glueUrl] - the glue URL (default GLUE_URL); a test passes a cache-busted one.
|
|
66
|
+
* @returns {Promise<{WasmUniverse: Function}>}
|
|
67
|
+
*/
|
|
68
|
+
export async function load(glueUrl) {
|
|
69
|
+
const pkgUrl = glueUrl || GLUE_URL;
|
|
70
|
+
let wasmModule;
|
|
71
|
+
try {
|
|
72
|
+
wasmModule = await import(pkgUrl);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'[wasm-load-browser] the WASM package glue did not load from ' + pkgUrl + ' (' +
|
|
76
|
+
(e && e.message ? e.message : String(e)) + ').\n' +
|
|
77
|
+
'Build it with:\n' +
|
|
78
|
+
' wasm-pack build fat/crates/egpt_wasm --target web --out-dir fat/pkg-web'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// wasm-pack --target web generates an init() default export that,
|
|
83
|
+
// called bare, builds its OWN `new URL('egpt_wasm_bg.wasm', import.meta.url)`
|
|
84
|
+
// and does `fetch(thatUrlObject)` internally (verified against
|
|
85
|
+
// fat/pkg-web/egpt_wasm.js's own __wbg_init/__wbg_load source).
|
|
86
|
+
//
|
|
87
|
+
// ARTIFACT-SAFE INIT (ws-artifact-showcase Bite A5): a claude.ai
|
|
88
|
+
// Artifact runs inside a sandboxed iframe whose `window.fetch` is not
|
|
89
|
+
// a direct network fetch — it is PROXIED to the parent frame via
|
|
90
|
+
// `postMessage`, and every postMessage argument must be structured-
|
|
91
|
+
// clonable (the HTML Structured Clone Algorithm). A `URL` object is
|
|
92
|
+
// NOT clonable (confirmed identical via the platform's own global
|
|
93
|
+
// `structuredClone()` — throws `DataCloneError`), so the glue's bare
|
|
94
|
+
// `fetch(urlObject)` call throws inside an Artifact — this is the
|
|
95
|
+
// EXACT defect from the real claude.ai paste (`DataCloneError:
|
|
96
|
+
// Failed to execute 'postMessage' on 'Window': URL object could not
|
|
97
|
+
// be cloned` at `window.fetch (about:srcdoc:109)` <- `Module.__wbg_init`
|
|
98
|
+
// <- `load` <- the ESM entry's boot (lib/frqtl/experiments/_shared/doors.js)).
|
|
99
|
+
//
|
|
100
|
+
// Fix: we fetch the .wasm bytes OURSELVES with a plain STRING url
|
|
101
|
+
// (strings clone fine everywhere, including through a postMessage
|
|
102
|
+
// proxy) and hand the glue raw bytes instead. wasm-bindgen's
|
|
103
|
+
// `__wbg_init()` takes a BufferSource straight to
|
|
104
|
+
// `WebAssembly.instantiate(bytes, imports)` — it never touches
|
|
105
|
+
// `fetch()` again once it has bytes in hand. We pass the bytes via
|
|
106
|
+
// the glue's non-deprecated `{module_or_path: bytes}` calling
|
|
107
|
+
// convention (a bare positional BufferSource also works, but trips
|
|
108
|
+
// the glue's own "using deprecated parameters" console.warn).
|
|
109
|
+
let wasmSha256 = null; // no self-fetched bytes (no init export) ⇒ honestly unknown
|
|
110
|
+
if (typeof wasmModule.default === 'function') {
|
|
111
|
+
const wasmBgUrl = new URL('egpt_wasm_bg.wasm', pkgUrl).href;
|
|
112
|
+
const wasmResponse = await fetch(String(wasmBgUrl));
|
|
113
|
+
if (!wasmResponse.ok) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
'[wasm-load-browser] failed to fetch WASM binary: ' +
|
|
116
|
+
wasmResponse.status + ' ' + wasmResponse.statusText + ' (' + wasmBgUrl + ')'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const wasmBytes = await wasmResponse.arrayBuffer();
|
|
120
|
+
wasmSha256 = await _sha256Hex(wasmBytes);
|
|
121
|
+
await wasmModule.default({ module_or_path: wasmBytes });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const WasmUniverse = wasmModule.WasmUniverse;
|
|
125
|
+
if (!WasmUniverse) {
|
|
126
|
+
throw new Error('[wasm-load-browser] WasmUniverse not found in loaded WASM module.');
|
|
127
|
+
}
|
|
128
|
+
_loadedWasmSha256 = wasmSha256;
|
|
129
|
+
|
|
130
|
+
return { WasmUniverse: WasmUniverse };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The loader the browser door hands to bootEngine. */
|
|
134
|
+
export const browserWasmLoader = Object.freeze({ env: 'browser', load: function () { return load(); } });
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wasm-load-node.js — the NODE loader for the WASM engine (an ES module).
|
|
5
|
+
*
|
|
6
|
+
* Resolves the wasm-pack --target nodejs artifact beside this tree
|
|
7
|
+
* (`new URL('../../../fat/pkg-node/egpt_wasm.js', import.meta.url)`) and returns the
|
|
8
|
+
* WasmUniverse constructor so wasm-engine.js stays env-agnostic. The node door hands this
|
|
9
|
+
* loader to `bootEngine` (compat/engine.js).
|
|
10
|
+
*
|
|
11
|
+
* The nodejs pkg is CommonJS and SYNCHRONOUS: it reads the .wasm via readFileSync and
|
|
12
|
+
* instantiates during require(), so it is loaded through `createRequire(import.meta.url)`.
|
|
13
|
+
* There is NO async init() call. An absent artifact (fat/pkg-node not built) REJECTS naming
|
|
14
|
+
* the build command — never a silent undefined. This file installs no global.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
/** The nodejs glue's path: a fact of this module's own placement. */
|
|
23
|
+
export const NODE_PKG_PATH = fileURLToPath(new URL('../../../fat/pkg-node/egpt_wasm.js', import.meta.url));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load the WASM universe constructor for node.
|
|
27
|
+
*
|
|
28
|
+
* @returns {Promise<{WasmUniverse: Function}>}
|
|
29
|
+
*/
|
|
30
|
+
export function load() {
|
|
31
|
+
return new Promise(function (resolve, reject) {
|
|
32
|
+
let wasmMod;
|
|
33
|
+
try {
|
|
34
|
+
wasmMod = require(NODE_PKG_PATH);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (e && e.code === 'MODULE_NOT_FOUND') {
|
|
37
|
+
reject(new Error(
|
|
38
|
+
'[wasm-load-node] WASM node artifact not found at: ' + NODE_PKG_PATH + '\n' +
|
|
39
|
+
'Build it with:\n' +
|
|
40
|
+
' wasm-pack build fat/crates/egpt_wasm --target nodejs --out-dir fat/pkg-node\n' +
|
|
41
|
+
'(from the FRAQTL repo root; `make wasm-node`)'
|
|
42
|
+
));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
reject(new Error('[wasm-load-node] Failed to load WASM node module: ' + (e && e.message)));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const WasmUniverse = wasmMod.WasmUniverse;
|
|
49
|
+
if (!WasmUniverse) {
|
|
50
|
+
reject(new Error('[wasm-load-node] WasmUniverse not found in module at ' + NODE_PKG_PATH +
|
|
51
|
+
'. Ensure wasm-pack built with --target nodejs.'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
resolve({ WasmUniverse: WasmUniverse });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The loader the node door hands to bootEngine. */
|
|
59
|
+
export const nodeWasmLoader = Object.freeze({ env: 'node', load: load });
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FieldBuilder — shared smooth heightfield builder.
|
|
5
|
+
*
|
|
6
|
+
* The ONE field-build source for all renderer providers. Both p5 and three
|
|
7
|
+
* providers call FieldBuilder.buildHeightField(frameData, opts) to produce
|
|
8
|
+
* the same smooth Uint8Array heightfield. No per-provider splat loop.
|
|
9
|
+
*
|
|
10
|
+
* Algorithm: soft-splat with quadratic falloff kernel + optional separable blur.
|
|
11
|
+
* Each particle deposits its fullness into a (2R+1)^2 neighborhood with weight
|
|
12
|
+
* w(dx,dy) = max(0, 1 - (dx^2+dy^2)/R^2)
|
|
13
|
+
* Accumulated values are scaled by FIELD_GAIN then clamped to [0,255].
|
|
14
|
+
* An optional 3-tap separable blur pass smooths the result further.
|
|
15
|
+
*
|
|
16
|
+
* Coordinate convention: field[py * w + px], physics-pixel space (y-down, top-left).
|
|
17
|
+
* Matches the vTexCoord flip in the providers' texture uploads.
|
|
18
|
+
*
|
|
19
|
+
* Export:
|
|
20
|
+
* FieldBuilder.buildHeightField(frameData, opts) -> Uint8Array(w * h)
|
|
21
|
+
* FieldBuilder.FIELD_OPTS — shared default options constant
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { requireProviderSize } from './renderer-provider-contract.js';
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// Shared constants — both providers use THESE values so they cannot drift.
|
|
28
|
+
var FIELD_OPTS = {
|
|
29
|
+
w: 300, // field texel resolution — a render choice, independent of the world rect
|
|
30
|
+
h: 200, // (canvasW/canvasH map physics px onto it per call)
|
|
31
|
+
radius: 2, // splat radius in texels
|
|
32
|
+
gain: 4.0, // scale factor: typical fullness × gain ≈ mid-range accumulation
|
|
33
|
+
blur: true // run a 3-tap separable blur pass after the splat
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a smooth single-channel heightfield from frameData.
|
|
39
|
+
*
|
|
40
|
+
* @param {Array<{rect:{x,y}, fullness:number}>} frameData
|
|
41
|
+
* @param {{w:number, h:number, radius:number, gain:number, blur:boolean,
|
|
42
|
+
* canvasW:number, canvasH:number}} opts
|
|
43
|
+
* `canvasW`/`canvasH` are REQUIRED: the physics rect the frameData coordinates live in
|
|
44
|
+
* (the runner's canvas_rect). Callers spread FIELD_OPTS and add them.
|
|
45
|
+
* @returns {Uint8Array} length = opts.w * opts.h, values 0-255
|
|
46
|
+
* @throws {Error} when canvasW/canvasH are absent or not positive finite numbers.
|
|
47
|
+
*/
|
|
48
|
+
function buildHeightField(frameData, opts) {
|
|
49
|
+
requireProviderSize('FieldBuilder.buildHeightField(frameData, Object.assign({}, FIELD_OPTS, ' +
|
|
50
|
+
'{ canvasW, canvasH })) opts.canvasW/canvasH', opts && opts.canvasW, opts && opts.canvasH);
|
|
51
|
+
var canvasW = opts.canvasW;
|
|
52
|
+
var canvasH = opts.canvasH;
|
|
53
|
+
var fw = opts.w || FIELD_OPTS.w;
|
|
54
|
+
var fh = opts.h || FIELD_OPTS.h;
|
|
55
|
+
var R = opts.radius !== undefined ? opts.radius : FIELD_OPTS.radius;
|
|
56
|
+
var gain = opts.gain !== undefined ? opts.gain : FIELD_OPTS.gain;
|
|
57
|
+
var doBlur = opts.blur !== undefined ? opts.blur : FIELD_OPTS.blur;
|
|
58
|
+
|
|
59
|
+
var R2 = R * R;
|
|
60
|
+
// Use a Float32 accumulation buffer then quantize at the end
|
|
61
|
+
var accum = new Float32Array(fw * fh);
|
|
62
|
+
|
|
63
|
+
if (frameData) {
|
|
64
|
+
// Map physics-pixel coords → field-texel coords by (fw/canvasW, fh/canvasH).
|
|
65
|
+
var scaleX = fw / canvasW;
|
|
66
|
+
var scaleY = fh / canvasH;
|
|
67
|
+
|
|
68
|
+
for (var i = 0; i < frameData.length; i++) {
|
|
69
|
+
var f = frameData[i];
|
|
70
|
+
var fx = f.rect.x * scaleX;
|
|
71
|
+
var fy = f.rect.y * scaleY;
|
|
72
|
+
var px = Math.round(fx) | 0;
|
|
73
|
+
var py = Math.round(fy) | 0;
|
|
74
|
+
var v = f.fullness || 0;
|
|
75
|
+
if (v <= 0) continue;
|
|
76
|
+
|
|
77
|
+
// Splat into (2R+1)^2 neighborhood
|
|
78
|
+
var x0 = Math.max(0, px - R);
|
|
79
|
+
var x1 = Math.min(fw - 1, px + R);
|
|
80
|
+
var y0 = Math.max(0, py - R);
|
|
81
|
+
var y1 = Math.min(fh - 1, py + R);
|
|
82
|
+
|
|
83
|
+
for (var sy = y0; sy <= y1; sy++) {
|
|
84
|
+
var dy = sy - py;
|
|
85
|
+
for (var sx = x0; sx <= x1; sx++) {
|
|
86
|
+
var dx = sx - px;
|
|
87
|
+
var d2 = dx * dx + dy * dy;
|
|
88
|
+
if (d2 > R2) continue;
|
|
89
|
+
var w = 1.0 - d2 / R2; // quadratic falloff
|
|
90
|
+
accum[sy * fw + sx] += v * w;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Quantize (scale by gain, clamp to [0,255])
|
|
97
|
+
var out = new Uint8Array(fw * fh);
|
|
98
|
+
for (var k = 0; k < accum.length; k++) {
|
|
99
|
+
var val = Math.round(accum[k] * gain * 255);
|
|
100
|
+
out[k] = val > 255 ? 255 : val;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Optional 3-tap separable Gaussian blur [0.25, 0.5, 0.25]
|
|
104
|
+
if (doBlur && fw > 2 && fh > 2) {
|
|
105
|
+
var tmp = new Uint8Array(fw * fh);
|
|
106
|
+
var row, col, idx, a, b, c;
|
|
107
|
+
|
|
108
|
+
// Horizontal pass
|
|
109
|
+
for (row = 0; row < fh; row++) {
|
|
110
|
+
var rowBase = row * fw;
|
|
111
|
+
tmp[rowBase] = out[rowBase];
|
|
112
|
+
tmp[rowBase + fw - 1] = out[rowBase + fw - 1];
|
|
113
|
+
for (col = 1; col < fw - 1; col++) {
|
|
114
|
+
idx = rowBase + col;
|
|
115
|
+
a = out[idx - 1];
|
|
116
|
+
b = out[idx];
|
|
117
|
+
c = out[idx + 1];
|
|
118
|
+
tmp[idx] = (a + 2 * b + c + 2) >> 2;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Vertical pass (reads tmp, writes out)
|
|
123
|
+
for (col = 0; col < fw; col++) {
|
|
124
|
+
out[col] = tmp[col];
|
|
125
|
+
out[(fh - 1) * fw + col] = tmp[(fh - 1) * fw + col];
|
|
126
|
+
}
|
|
127
|
+
for (row = 1; row < fh - 1; row++) {
|
|
128
|
+
for (col = 0; col < fw; col++) {
|
|
129
|
+
a = tmp[(row - 1) * fw + col];
|
|
130
|
+
b = tmp[row * fw + col];
|
|
131
|
+
c = tmp[(row + 1) * fw + col];
|
|
132
|
+
out[row * fw + col] = (a + 2 * b + c + 2) >> 2;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var FieldBuilder = {
|
|
141
|
+
FIELD_OPTS: FIELD_OPTS,
|
|
142
|
+
buildHeightField: buildHeightField
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { FieldBuilder };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters — built-in post-process filter specs for the RendererProvider shim.
|
|
5
|
+
*
|
|
6
|
+
* A filterSpec is a backend-keyed object:
|
|
7
|
+
* {
|
|
8
|
+
* name: string, // unique ID used by filter()/removeFilter()
|
|
9
|
+
* glsl: string, // GLSL 1.0 WebGL2 fragment body (works both backends)
|
|
10
|
+
* strands: function(p5), // optional: p5.strands JS body for WebGPU
|
|
11
|
+
* tsl: function(THREE), // optional: TSL node graph for three.js WebGPU
|
|
12
|
+
* uniforms: string[] // uniform names this filter uses
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* The glsl field is the day-one portable body — works on the WebGL2 path for
|
|
16
|
+
* BOTH backends (p5 provider offscreen canvas; three ShaderMaterial quad).
|
|
17
|
+
* strands/tsl fields are ADDITIVE for the WebGPU path and are NOT required.
|
|
18
|
+
*
|
|
19
|
+
* Standard uniforms always available to every filter:
|
|
20
|
+
* uResolution vec2 — canvas size in pixels
|
|
21
|
+
* uTime float — simulation seconds at the tick being rendered
|
|
22
|
+
* (tick / FILTER_TICKS_PER_SECOND, 60 today; owner:
|
|
23
|
+
* renderer-provider-contract.js `filterTime`) — a paused
|
|
24
|
+
* scene's filters hold still
|
|
25
|
+
* uField sampler2D — R8 occupancy/height texture (from doTick() frameData)
|
|
26
|
+
* uScene sampler2D — rendered particle layer (the frame before this pass)
|
|
27
|
+
*
|
|
28
|
+
* Coordinate convention: vTexCoord is [0..1] with origin at bottom-left (WebGL
|
|
29
|
+
* default for textures). The water heightfield uses this directly.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* renderer.filter(Filters.water);
|
|
33
|
+
* renderer.filter(Filters.bloom);
|
|
34
|
+
* renderer.removeFilter('water');
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// ── Filters.water ────────────────────────────────────────────────────────
|
|
39
|
+
//
|
|
40
|
+
// WebGL2 water-surface post-process (§8 from WS_RENDERER_SHIM_DESIGN.md).
|
|
41
|
+
// uField = occupancy/height from the CA wave; uScene = rendered particle layer.
|
|
42
|
+
// Computes surface normal via finite differences of the heightfield, then:
|
|
43
|
+
// - Refracts the scene lookup by the surface slope
|
|
44
|
+
// - Adds a specular highlight from a fixed light
|
|
45
|
+
// - Adds fake caustics (bright at high curvature)
|
|
46
|
+
// - Applies a shallow-water tint
|
|
47
|
+
|
|
48
|
+
var WATER_GLSL = [
|
|
49
|
+
'precision highp float;',
|
|
50
|
+
'',
|
|
51
|
+
'uniform sampler2D uField;',
|
|
52
|
+
'uniform sampler2D uScene;',
|
|
53
|
+
'uniform vec2 uResolution;',
|
|
54
|
+
'uniform float uTime;',
|
|
55
|
+
'',
|
|
56
|
+
'varying vec2 vTexCoord;',
|
|
57
|
+
'',
|
|
58
|
+
'// Sample height with a small ripple that advances with simulation time (uTime).',
|
|
59
|
+
'float height(vec2 uv) {',
|
|
60
|
+
' float h = texture2D(uField, uv).r;',
|
|
61
|
+
' h += 0.03 * sin(uv.x * 60.0 + uTime * 1.5)',
|
|
62
|
+
' + 0.03 * sin(uv.y * 48.0 - uTime * 1.1);',
|
|
63
|
+
' return h;',
|
|
64
|
+
'}',
|
|
65
|
+
'',
|
|
66
|
+
'void main() {',
|
|
67
|
+
' vec2 uv = vTexCoord;',
|
|
68
|
+
' vec2 texel = 1.0 / uResolution;',
|
|
69
|
+
'',
|
|
70
|
+
' // Surface normal from finite differences (Sobel-ish).',
|
|
71
|
+
' float hL = height(uv - vec2(texel.x, 0.0));',
|
|
72
|
+
' float hR = height(uv + vec2(texel.x, 0.0));',
|
|
73
|
+
' float hD = height(uv - vec2(0.0, texel.y));',
|
|
74
|
+
' float hU = height(uv + vec2(0.0, texel.y));',
|
|
75
|
+
' vec3 n = normalize(vec3(hL - hR, hD - hU, 0.18));',
|
|
76
|
+
'',
|
|
77
|
+
' // Refraction: offset scene lookup by surface slope (Snell-ish cheap approx).',
|
|
78
|
+
' float refractAmt = 0.025;',
|
|
79
|
+
' vec2 refracted = uv + n.xy * refractAmt;',
|
|
80
|
+
' vec3 base = texture2D(uScene, refracted).rgb;',
|
|
81
|
+
'',
|
|
82
|
+
' // Specular highlight from a fixed directional light.',
|
|
83
|
+
' vec3 lightDir = normalize(vec3(0.4, 0.7, 0.6));',
|
|
84
|
+
' float spec = pow(max(dot(n, lightDir), 0.0), 32.0);',
|
|
85
|
+
'',
|
|
86
|
+
' // Fake caustics: bright where the surface focuses (high curvature).',
|
|
87
|
+
' float curvature = abs((hL + hR + hU + hD) * 0.25 - height(uv));',
|
|
88
|
+
' float caustic = smoothstep(0.02, 0.12, curvature);',
|
|
89
|
+
'',
|
|
90
|
+
' // Shallow-water tint deepening with height; add specular + caustics.',
|
|
91
|
+
' vec3 waterTint = mix(',
|
|
92
|
+
' vec3(0.02, 0.10, 0.16),',
|
|
93
|
+
' vec3(0.10, 0.34, 0.42),',
|
|
94
|
+
' height(uv)',
|
|
95
|
+
' );',
|
|
96
|
+
' vec3 color = base * 0.65 + waterTint * 0.55',
|
|
97
|
+
' + spec * vec3(0.9, 0.97, 1.0)',
|
|
98
|
+
' + caustic * vec3(0.45, 0.85, 0.95);',
|
|
99
|
+
'',
|
|
100
|
+
' gl_FragColor = vec4(color, 1.0);',
|
|
101
|
+
'}'
|
|
102
|
+
].join('\n');
|
|
103
|
+
|
|
104
|
+
// ── Filters.bloom ────────────────────────────────────────────────────────
|
|
105
|
+
//
|
|
106
|
+
// Simple single-pass bloom / glow: bright areas bleed softly into neighbours.
|
|
107
|
+
// Uses a 9-tap box blur on pixels above a luminance threshold.
|
|
108
|
+
|
|
109
|
+
var BLOOM_GLSL = [
|
|
110
|
+
'precision highp float;',
|
|
111
|
+
'',
|
|
112
|
+
'uniform sampler2D uScene;',
|
|
113
|
+
'uniform vec2 uResolution;',
|
|
114
|
+
'uniform float uTime;',
|
|
115
|
+
'',
|
|
116
|
+
'varying vec2 vTexCoord;',
|
|
117
|
+
'',
|
|
118
|
+
'float luma(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }',
|
|
119
|
+
'',
|
|
120
|
+
'void main() {',
|
|
121
|
+
' vec2 uv = vTexCoord;',
|
|
122
|
+
' vec2 texel = 1.0 / uResolution;',
|
|
123
|
+
'',
|
|
124
|
+
' vec3 base = texture2D(uScene, uv).rgb;',
|
|
125
|
+
'',
|
|
126
|
+
' // 9-tap box blur of bright regions only',
|
|
127
|
+
' vec3 bloom = vec3(0.0);',
|
|
128
|
+
' float wSum = 0.0;',
|
|
129
|
+
' for (int dx = -2; dx <= 2; dx++) {',
|
|
130
|
+
' for (int dy = -2; dy <= 2; dy++) {',
|
|
131
|
+
' vec2 off = vec2(float(dx), float(dy)) * texel * 2.0;',
|
|
132
|
+
' vec3 s = texture2D(uScene, uv + off).rgb;',
|
|
133
|
+
' float l = luma(s);',
|
|
134
|
+
' float w = step(0.5, l); // only bright pixels contribute',
|
|
135
|
+
' bloom += s * w;',
|
|
136
|
+
' wSum += w;',
|
|
137
|
+
' }',
|
|
138
|
+
' }',
|
|
139
|
+
' if (wSum > 0.0) bloom /= wSum;',
|
|
140
|
+
'',
|
|
141
|
+
' // Additive blend: base + soft bloom',
|
|
142
|
+
' vec3 color = base + bloom * 0.35;',
|
|
143
|
+
' gl_FragColor = vec4(color, 1.0);',
|
|
144
|
+
'}'
|
|
145
|
+
].join('\n');
|
|
146
|
+
|
|
147
|
+
// ── Filters catalog ────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
var Filters = {
|
|
150
|
+
water: {
|
|
151
|
+
name: 'water',
|
|
152
|
+
glsl: WATER_GLSL,
|
|
153
|
+
uniforms: ['uField', 'uScene', 'uResolution', 'uTime']
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
bloom: {
|
|
157
|
+
name: 'bloom',
|
|
158
|
+
glsl: BLOOM_GLSL,
|
|
159
|
+
uniforms: ['uScene', 'uResolution', 'uTime']
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export { Filters };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RenderFamily — the ONE per-frame render-FAMILY decode authority for the 3D provider.
|
|
5
|
+
*
|
|
6
|
+
* WHY (Red-Team R2-4 §6; contract: docs/internal/engine/RENDERER_CONTRACT.md,
|
|
7
|
+
* §per-frame render-intent resolution): the three.js tick loop used to
|
|
8
|
+
* decide the render path (mesh vs cube vs sphere vs point-sprite) ONCE per tick from a
|
|
9
|
+
* `RenderShape.resolve(null, opts)` / `RenderMesh.resolve(null, opts)` call — frame hard-coded
|
|
10
|
+
* `null`, so a correctly-stamped PER-FRAME `render.shape` / `render.mesh` was silently ignored
|
|
11
|
+
* (the bug class the resolver-only unit tests structurally cannot see: a resolver is 100%
|
|
12
|
+
* correct while its ONLY caller feeds it null). This file lifts that routing DECISION into a
|
|
13
|
+
* PURE, Node-testable decode function with a SINGLE caller — mirroring RenderShape.js
|
|
14
|
+
* (RenderShape.resolve), the existing precedent for "extract the tick-loop routing
|
|
15
|
+
* decision into a pure decode fn." A future call-site cannot re-pass null silently because this
|
|
16
|
+
* function has NO null-frame code path — it composes the two authorities per-frame.
|
|
17
|
+
*
|
|
18
|
+
* It COMPOSES the two existing decode authorities (never re-maps): RenderMesh first (a mesh stamp
|
|
19
|
+
* outranks a shape), then RenderShape. It adds NO shader, NO GLSL attribute, NO new vocab — the
|
|
20
|
+
* three-provider folds the returned family into its existing per-(dim,family) bucket key.
|
|
21
|
+
*
|
|
22
|
+
* FAMILY (what THREE object draws the frame):
|
|
23
|
+
* 'mesh' (+assetId) → a clone of a loaded glTF (RenderMesh.resolve truthy; 3D-only)
|
|
24
|
+
* 'cube' → a lit BoxGeometry InstancedMesh (RenderShape 'cube'; 3D-only)
|
|
25
|
+
* 'sphere' → a lit SphereGeometry InstancedMesh (RenderShape 'sphere'; 3D-only)
|
|
26
|
+
* 'pts' (+uShapeVariant 0|1) → the Points shader sprite (ball/circle→0, box→1; any 2D shape)
|
|
27
|
+
*
|
|
28
|
+
* 3D gate: mesh/cube/sphere are 3D-only families (they are lit meshes). When `is3D` is false
|
|
29
|
+
* (2D three, or p5 never calls here) they DEGRADE to the 'pts' family exactly as RenderShape
|
|
30
|
+
* already degrades cube/sphere to the ball/box sprite — no separate 2D branch needed.
|
|
31
|
+
*
|
|
32
|
+
* Export contract (PURE — no THREE, no side effects; runs unchanged in pkg-node/conformance):
|
|
33
|
+
* RenderFamily.resolveRenderFamily(frame, renderOpts, is3D)
|
|
34
|
+
* -> { family:'mesh'|'cube'|'sphere'|'pts', key:string, assetId?:string, uShapeVariant?:number }
|
|
35
|
+
* RenderFamily.familyKey(res) -> string (the bucket-key token: 'mesh:<id>'|'cube'|'sphere'|'pts:<v>')
|
|
36
|
+
*
|
|
37
|
+
* Single-source: RenderShape / RenderMesh remain the shape/mesh decode authorities; this file
|
|
38
|
+
* only ORDERS them into one family verdict. It re-uses their outputs by reference — it never
|
|
39
|
+
* re-lists shape names or re-reads string_map keys itself.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { RenderShape } from './RenderShape.js';
|
|
43
|
+
import { RenderMesh } from './RenderMesh.js';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the render FAMILY of one frame, composing RenderMesh (highest) then RenderShape.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} frame - FrameData; frame.data.string_map may carry render.mesh/render.shape.
|
|
49
|
+
* @param {object} renderOpts - the setRenderOption bag (opts.mesh / opts.shape scene defaults).
|
|
50
|
+
* @param {boolean} is3D - whether the provider is in 3D mode (mesh/cube/sphere families
|
|
51
|
+
* only exist in 3D; 2D degrades every one to the 'pts' sprite).
|
|
52
|
+
* @returns {{family:string, key:string, assetId?:string, uShapeVariant?:number}}
|
|
53
|
+
*/
|
|
54
|
+
function resolveRenderFamily(frame, renderOpts, is3D) {
|
|
55
|
+
// 1. Mesh outranks shape (a per-frame render.mesh stamp routes to a glTF clone). 3D-only.
|
|
56
|
+
if (is3D) {
|
|
57
|
+
var assetId = RenderMesh.resolve(frame, renderOpts);
|
|
58
|
+
if (assetId) {
|
|
59
|
+
return { family: 'mesh', assetId: assetId, key: 'mesh:' + assetId };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 2. Shape family. cube/sphere are lit InstancedMeshes (3D-only); everything else is the
|
|
63
|
+
// Points sprite whose ball-vs-box is the per-bucket uShape uniform.
|
|
64
|
+
var shape = RenderShape.resolve(frame, renderOpts);
|
|
65
|
+
if (is3D && RenderShape.isCubeMesh(shape)) {
|
|
66
|
+
return { family: 'cube', key: 'cube' };
|
|
67
|
+
}
|
|
68
|
+
if (is3D && RenderShape.isSphereMesh(shape)) {
|
|
69
|
+
return { family: 'sphere', key: 'sphere' };
|
|
70
|
+
}
|
|
71
|
+
var uShapeVariant = RenderShape.toUniform(shape); // 0 = ball/circle/(degraded cube/sphere), 1 = box
|
|
72
|
+
return { family: 'pts', uShapeVariant: uShapeVariant, key: 'pts:' + uShapeVariant };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The bucket-key token for a resolved family (already precomputed on `.key`). */
|
|
76
|
+
function familyKey(res) {
|
|
77
|
+
return res && res.key ? res.key : 'pts:0';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var RenderFamily = {
|
|
81
|
+
resolveRenderFamily: resolveRenderFamily,
|
|
82
|
+
familyKey: familyKey
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { RenderFamily };
|