@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.
- package/CLAUDE.md +87 -0
- package/LICENSE.md +664 -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 +2499 -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 +84 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +99 -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 +258 -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 +2470 -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 +2920 -0
- package/dist/frqtl/objects/ObjectFactory.js +296 -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 +710 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -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 +411 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +351 -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 +309 -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 +820 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +468 -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 +250 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -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 +277 -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 +1258 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
- package/dist/frqtl/objects/v1-replacements.js +29 -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 +34 -0
- package/dist/frqtl/parts/CollisionTypes.js +30 -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 +489 -0
- package/dist/frqtl/parts/LargeObject.js +405 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +717 -0
- package/dist/frqtl/parts/OvenBox.js +611 -0
- package/dist/frqtl/parts/PointSource.js +762 -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 +243 -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 +211 -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 +690 -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 +325 -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 +671 -0
- package/dist/shim/notebook/knob.js +150 -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 +507 -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 +213 -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 +804 -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 +63 -4
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module engineRouter
|
|
5
|
+
* @description
|
|
6
|
+
* The single source of truth for engine path routing.
|
|
7
|
+
*
|
|
8
|
+
* Four named profiles:
|
|
9
|
+
* - `DEV_CORE_PROFILE` — in-core dev mount (`lib/server` plugin serves `/frqtl`, `/fat`).
|
|
10
|
+
* - `DIST_PROFILE` — flat dist layout, same-origin RELATIVE paths (`sdk/frqtl-web/dist`;
|
|
11
|
+
* baked into the dist pages at build time by `sdk/frqtl-web/build.js`).
|
|
12
|
+
* - `CDN_PROFILE` — the SAME dist layout, resolved to ABSOLUTE jsdelivr URLs
|
|
13
|
+
* (`https://cdn.jsdelivr.net/npm/@descix/frqtl-web@<ver>/dist/...`) for static hosting
|
|
14
|
+
* with no server process at all. CDN_PROFILE is
|
|
15
|
+
* a DISTINCT profile object, not a DIST_PROFILE parameterization: DIST_PROFILE's
|
|
16
|
+
* relative bases are meaningless outside the exact dist/experiments/{id}/ page
|
|
17
|
+
* context they were computed for, whereas CDN_PROFILE's
|
|
18
|
+
* absolute bases resolve correctly from ANY host page (a claude.ai Artifact iframe has
|
|
19
|
+
* no such anchor point in the dist tree at all).
|
|
20
|
+
* - the static profile (`buildStaticProfile(graphBase, entryBase)`) — the SAME dist layout,
|
|
21
|
+
* its graph under the site's build-stamped `./pub/<build>` directory and its experiment
|
|
22
|
+
* entry pages at the fixed `./pub/experiments/`, RELATIVE TO THE DOCUMENT, with the math +
|
|
23
|
+
* notebook lanes co-served in the same tree (self-contained no-backend site served under
|
|
24
|
+
* ANY path prefix — the DeSciX platform serves it at /<env>/<app_id>/site/). Like
|
|
25
|
+
* CDN_PROFILE it is a DISTINCT profile (its math/notebook bases point SAME-ORIGIN).
|
|
26
|
+
*
|
|
27
|
+
* Profile parity is asserted at module load time: all four profiles MUST cover the same
|
|
28
|
+
* non-private path keys or an error is thrown (prevents dev/dist/cdn/static divergence).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* import { engineRouter } from 'lib/shim';
|
|
32
|
+
* const router = engineRouter({ mode: 'dev-core', engine: 'wasm' });
|
|
33
|
+
* // router.resolve(relPath)→ string (URL of a frqtl-relative path, e.g. 'experiments/_shared/boot.js')
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
// ── Path profiles ────────────────────────────────────────────────────────────
|
|
39
|
+
//
|
|
40
|
+
// Each profile maps a frqtl-relative prefix → URL prefix.
|
|
41
|
+
// The FIRST matching prefix wins (insertion order).
|
|
42
|
+
//
|
|
43
|
+
// DEV_CORE_PROFILE uses the lib/server plugin's in-process mounts:
|
|
44
|
+
// /frqtl → lib/frqtl /fat → lib/fat
|
|
45
|
+
//
|
|
46
|
+
// DIST_PROFILE uses the flat dist layout inside sdk/frqtl-web/dist/
|
|
47
|
+
// (mirrors what build.js:254-271 used to hand-synthesize into the html).
|
|
48
|
+
|
|
49
|
+
var DEV_CORE_PROFILE = {
|
|
50
|
+
// THE package root (lib/server/mounts.js serves lib/ as lib-identity siblings: /frqtl, /shim, /fat).
|
|
51
|
+
// Every game-layer path (LIB_IDENTITY_PREFIXES) resolves under it; this is the one game-layer fact.
|
|
52
|
+
'__frqtlRoot': '/',
|
|
53
|
+
// External-geometry fixtures (lib/frqtl/assets/models/) — asset DATA a page's own render.mesh call
|
|
54
|
+
// resolves; the dists place it outside the lib-identity tree (js/models/).
|
|
55
|
+
'assets/models/': '/frqtl/assets/models/',
|
|
56
|
+
// The experiment ENTRY pages (a general experiments/<id>/ path; experiments/_shared/ is lib identity).
|
|
57
|
+
'experiments/': '/frqtl/experiments/',
|
|
58
|
+
// M1: Math SDK served from SOURCE via vite's transform pipeline (dependency
|
|
59
|
+
// map rule 2: dev consumes source — a /pub_sdk dist route here is the
|
|
60
|
+
// "dev-core route pointing at a dist artifact" anti-pattern).
|
|
61
|
+
// /egpt-src/index.js is a virtual module resolved by fraqtlServe's
|
|
62
|
+
// resolveId/load hooks to sdk/egpt-math-sdk/src/browser/sdk-entry.js;
|
|
63
|
+
// '#egpt-math-canonical' resolves via the config() alias to lib/egpt source.
|
|
64
|
+
// MathView._deriveSdkBase: '/egpt-src/index.js' ends with '/index.js' ✓ —
|
|
65
|
+
// catalog loads from /egpt-src/editor/ (scoped static mounts in mounts.js).
|
|
66
|
+
'__mathSdkUrl': '/egpt-src/index.js',
|
|
67
|
+
// M4 fix: WASM math bridge served from SOURCE via the scoped /egpt-src/wasm
|
|
68
|
+
// static mount (mounts.js ← egpt-paths.js mathWasmSrcDir, the single path
|
|
69
|
+
// authority). Artifacts are the wasm-pack output of `npm run build:wasm-bridge`
|
|
70
|
+
// (sdk/egpt-math-sdk): glue module + binary. Empty strings here were the M4
|
|
71
|
+
// verification failure — no browser surface could ever register wasm-math.
|
|
72
|
+
// If the bridge isn't built the mount 404s and wasm-math SELECTION fails
|
|
73
|
+
// loud (Runtime._applyBackendSelection names the build command).
|
|
74
|
+
'__mathWasmUrl': '/egpt-src/wasm/egpt_math_sdk_wasm_bg.wasm',
|
|
75
|
+
'__mathWasmModuleUrl': '/egpt-src/wasm/egpt_math_sdk_wasm.js',
|
|
76
|
+
// M4: Math backend selection authority — the single source of truth for which
|
|
77
|
+
// MathBackendProvider id is selected at startup. Runtime.ensureMath().then
|
|
78
|
+
// calls MathBackendRegistry.select(mathBackend) if non-empty.
|
|
79
|
+
// Dev consumes source, so its default is 'js-reference' (registered by the dev
|
|
80
|
+
// math SDK entry). Every distribution profile defaults to 'wasm-math' — compute
|
|
81
|
+
// is WASM in every distribution, and js-reference is not registrable there.
|
|
82
|
+
// Normalisation: 'wasm' → 'wasm-math' (same alias as ?mathBackend=wasm URL param).
|
|
83
|
+
'__mathBackend': 'js-reference',
|
|
84
|
+
// Curated native notebooks (lib/notebooks/) — dev is the lib/server static
|
|
85
|
+
// mount at /notebooks (mounts.js). The notebook surfaces NEVER derive this
|
|
86
|
+
// base themselves; they receive router.notebookBase threaded in as a prop
|
|
87
|
+
// (same discipline as __mathSdkUrl / monacoBaseUrl). This is the single path
|
|
88
|
+
// authority for the notebook URL space — it closes the N3 carry that had
|
|
89
|
+
// presets.js hardcoding a '/notebooks/' literal (dev-only, 404s in dist).
|
|
90
|
+
'__notebookBase': '/notebooks',
|
|
91
|
+
// Placed vendor libs (lib/frqtl/assets/vendor/, gitignored artifacts) — the
|
|
92
|
+
// STABLE root-level /vendor alias mounts.js exposes (§2a.1).
|
|
93
|
+
// A realm cell's bare-specifier import (`import * as d3 from 'd3'`) resolves via
|
|
94
|
+
// a manifest-generated import-map whose base is THIS value (vendor-importmap.js).
|
|
95
|
+
// Single path authority for the vendor URL space — surfaces NEVER derive '/vendor'
|
|
96
|
+
// themselves; they read router.vendorBase (same discipline as __notebookBase).
|
|
97
|
+
'__vendorBase': '/vendor'
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// buildDistProfile(base) — the flat dist layout, every target RELATIVE to a page whose
|
|
101
|
+
// dist root is `base` ('../..' for a page at dist/experiments/{id}/ — DIST_PROFILE below;
|
|
102
|
+
// '../../<build>' for the static site's fixed experiment entry at pub/experiments/{id}/,
|
|
103
|
+
// whose graph sits under the build-stamped pub/<build>/). The math / notebook / vendor
|
|
104
|
+
// private keys sit one level ABOVE the dist root (the egpt-math-sdk dist co-served as a sibling).
|
|
105
|
+
function buildDistProfile(base) {
|
|
106
|
+
if (typeof base !== 'string' || !base || /\/$/.test(base)) {
|
|
107
|
+
throw new Error('[engineRouter] buildDistProfile(base) takes a relative dist-root path with no ' +
|
|
108
|
+
'trailing slash (e.g. "../.."), got ' + JSON.stringify(base) + '.');
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
// THE package root: the game layer and lib/shim sit at their lib-identity placement under the dist
|
|
112
|
+
// root (dist/frqtl/…, dist/shim/…, dist/fat/…), so every relative import resolves as in lib/.
|
|
113
|
+
'__frqtlRoot': base + '/',
|
|
114
|
+
// External-geometry fixtures — own dist subdir (js/models/).
|
|
115
|
+
'assets/models/': base + '/js/models/',
|
|
116
|
+
// The experiment entry pages: from dist/experiments/{id}/index.html, base + '/experiments/'.
|
|
117
|
+
'experiments/': base + '/experiments/',
|
|
118
|
+
// Math SDK path relative to dist/experiments/{id}/ (the page this '../..' base is for).
|
|
119
|
+
// sdk/frqtl-web/dist/experiments/{id}/ → ../../../egpt-math-sdk/index.js
|
|
120
|
+
'__mathSdkUrl': base + '/../egpt-math-sdk/index.js',
|
|
121
|
+
// M4 fix: dist-relative shape matching sdk/egpt-math-sdk/dist/wasm/ (built by
|
|
122
|
+
// scripts/build.mjs), same base as __mathSdkUrl (relative to dist/experiments/{id}/).
|
|
123
|
+
// wasm-loader normalizes relative URLs against document.baseURI in the browser
|
|
124
|
+
// so the dynamic import resolves page-relative, not bundle-relative.
|
|
125
|
+
'__mathWasmUrl': base + '/../egpt-math-sdk/wasm/egpt_math_sdk_wasm_bg.wasm',
|
|
126
|
+
'__mathWasmModuleUrl': base + '/../egpt-math-sdk/wasm/egpt_math_sdk_wasm.js',
|
|
127
|
+
// M4: Math backend default for dist — WASM (a distribution carries no JS math core).
|
|
128
|
+
'__mathBackend': 'wasm-math',
|
|
129
|
+
// Curated native notebooks relative to dist/experiments/{id}/ — same base shape as
|
|
130
|
+
// __mathSdkUrl (sdk/frqtl-web/dist/experiments/{id}/ → ../../../egpt-math-sdk/notebooks/).
|
|
131
|
+
// The sdk build copies lib/notebooks/ → dist/notebooks/ (build.mjs copyDirFromAbs).
|
|
132
|
+
'__notebookBase': base + '/../egpt-math-sdk/notebooks',
|
|
133
|
+
// Placed vendor libs in dist — same dist-relative shape as __notebookBase
|
|
134
|
+
// (sdk/frqtl-web/dist/experiments/{id}/ → ../../../egpt-math-sdk/vendor). The sdk build
|
|
135
|
+
// copies the manifest-listed vendor entries into the dist (2a-tail follow-up,
|
|
136
|
+
// §8.1); the LIVE consumer is dev (apps/ide).
|
|
137
|
+
'__vendorBase': base + '/../egpt-math-sdk/vendor'
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
var DIST_PROFILE = buildDistProfile('../..');
|
|
142
|
+
|
|
143
|
+
// ── CDN_PROFILE (jsdelivr static hosting) ───────────────────────────────────────
|
|
144
|
+
//
|
|
145
|
+
// Pinned-version constants — the "one constant" the packet's absolute jsdelivr
|
|
146
|
+
// bases resolve from. These are hardcoded (not read from package.json — this module runs in
|
|
147
|
+
// the browser, no fs access) and MUST be bumped alongside each npm publish;
|
|
148
|
+
// lib/shim/engine/test/router.test.mjs asserts them against the two
|
|
149
|
+
// package.json#version fields so a forgotten bump fails `make verify`, not a
|
|
150
|
+
// silent stale-CDN-URL bug.
|
|
151
|
+
var CDN_SDK_VERSION = '0.6.0-p0.2'; // sdk/frqtl-web/package.json#version (the ONE version owner) — bound by router.test.mjs
|
|
152
|
+
var CDN_MATH_SDK_VERSION = '0.3.2'; // sdk/egpt-math-sdk/package.json#version — bound by router.test.mjs; bumped with each npm publish
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The jsdelivr dist root of @descix/frqtl-web at `version` — the ONE spelling of the package's CDN URL.
|
|
156
|
+
* @param {string} version
|
|
157
|
+
* @returns {string} no trailing slash
|
|
158
|
+
*/
|
|
159
|
+
function cdnDistBaseFor(version) {
|
|
160
|
+
if (typeof version !== 'string' || !version) {
|
|
161
|
+
throw new Error('[engineRouter] cdnDistBaseFor(version): a published @descix/frqtl-web version (got ' + JSON.stringify(version) + ').');
|
|
162
|
+
}
|
|
163
|
+
return 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@' + version + '/dist';
|
|
164
|
+
}
|
|
165
|
+
var CDN_BASE = cdnDistBaseFor(CDN_SDK_VERSION);
|
|
166
|
+
var CDN_MATH_BASE = 'https://cdn.jsdelivr.net/npm/@descix/egpt-math-sdk@' + CDN_MATH_SDK_VERSION + '/dist';
|
|
167
|
+
|
|
168
|
+
// CDN_PROFILE mirrors DIST_PROFILE's internal dist-relative targets (both profiles
|
|
169
|
+
// resolve into the SAME published sdk/frqtl-web/dist/ tree — see build.js) but
|
|
170
|
+
// expresses each target as an absolute URL anchored at a base (CDN_BASE by
|
|
171
|
+
// default) instead of a page-relative '../../' navigation. A non-private key
|
|
172
|
+
// here MUST resolve to the identical dist sub-path DIST_PROFILE's matching key
|
|
173
|
+
// resolves to (both are the same physical file on disk, served two different
|
|
174
|
+
// ways) — the parity test also asserts this dist-subpath equivalence, not just
|
|
175
|
+
// key-name parity.
|
|
176
|
+
//
|
|
177
|
+
// buildCdnProfile(base) — a FACTORY over `base`, so one base re-anchors EVERY URL the router
|
|
178
|
+
// resolves (engine, renderer, game layer, vendor, shim), never just an entry module.
|
|
179
|
+
// `CDN_MATH_BASE` (the sibling egpt-math-sdk package) is deliberately not parameterized: it is a
|
|
180
|
+
// different published package.
|
|
181
|
+
//
|
|
182
|
+
// `base` anchors the dist layout at an absolute dist root. With no base it is CDN_BASE (the
|
|
183
|
+
// published jsdelivr copy). With one, it is the SELF-HOSTED form: any URL serving an
|
|
184
|
+
// UNMODIFIED copy of the package's dist/ tree —
|
|
185
|
+
// every file dist/dist-manifest.json lists, byte for byte, at its relative path. A hand-picked
|
|
186
|
+
// subset is not supported: the profile below addresses every subtree of the dist, and which files
|
|
187
|
+
// a scene reaches is the game layer's business, not the host's. Callers: bootScene/bootRecipe
|
|
188
|
+
// `opts.base` (a consumer's own site), the browser parity core and the dist's own pages (their own
|
|
189
|
+
// dist root, derived from import.meta), and the registry door's scene reader
|
|
190
|
+
// (lib/shim/registry/scene-source.js). `assertDistBase` is the shape gate. The math lane stays on CDN_MATH_BASE (see
|
|
191
|
+
// below), so a self-hosted base re-anchors this package only.
|
|
192
|
+
|
|
193
|
+
// The shape gate for a cdn-mode base: an ABSOLUTE URL (parsable with no document) with no
|
|
194
|
+
// trailing slash. A relative base would resolve against the document for <script> tags but
|
|
195
|
+
// against the importing MODULE for the engine's dynamic import(), so one base would name two
|
|
196
|
+
// places; a trailing slash doubles every joined path. Both are refused, naming the base.
|
|
197
|
+
function assertDistBase(base) {
|
|
198
|
+
var ok = typeof base === 'string' && base !== '' && !/\/$/.test(base);
|
|
199
|
+
if (ok) { try { new URL(base); } catch (_) { ok = false; } }
|
|
200
|
+
if (!ok) {
|
|
201
|
+
throw new Error('[engineRouter] mode:"cdn" opts.base must be the absolute URL of a copy of the package\'s ' +
|
|
202
|
+
'dist/ directory, with no trailing slash (e.g. "https://example.org/vendor/frqtl-web/dist"); got ' +
|
|
203
|
+
JSON.stringify(base) + '. From a page: new URL(\'./vendor/frqtl-web/dist\', location.href).href. (FAIL-LOUD.)');
|
|
204
|
+
}
|
|
205
|
+
return base;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function buildCdnProfile(base) {
|
|
209
|
+
assertDistBase(base);
|
|
210
|
+
return {
|
|
211
|
+
// THE package root (the same lib-identity dist tree as DIST_PROFILE, absolute at `base`).
|
|
212
|
+
'__frqtlRoot': base + '/',
|
|
213
|
+
'assets/models/': base + '/js/models/',
|
|
214
|
+
'experiments/': base + '/experiments/',
|
|
215
|
+
// Math SDK / math lane — INTENTIONALLY still anchored at CDN_MATH_BASE
|
|
216
|
+
// (the closed-over module constant), never `base` — see the factory
|
|
217
|
+
// docstring above: this is a different, sibling published package.
|
|
218
|
+
// Wired identically to DIST_PROFILE's shape. DEFERRED (packet §3b): not
|
|
219
|
+
// exercised by the double-slit showcase, but present so a future
|
|
220
|
+
// math-notebook artifact needs no new profile keys, only a version bump
|
|
221
|
+
// once egpt-math-sdk is published.
|
|
222
|
+
'__mathSdkUrl': CDN_MATH_BASE + '/index.js',
|
|
223
|
+
'__mathWasmUrl': CDN_MATH_BASE + '/wasm/egpt_math_sdk_wasm_bg.wasm',
|
|
224
|
+
'__mathWasmModuleUrl': CDN_MATH_BASE + '/wasm/egpt_math_sdk_wasm.js',
|
|
225
|
+
'__mathBackend': 'wasm-math',
|
|
226
|
+
'__notebookBase': CDN_MATH_BASE + '/notebooks',
|
|
227
|
+
// Vendor import-map root — frqtl-web's OWN dist/vendor/ (manifest-driven
|
|
228
|
+
// copy, build.js copyVendorManifestToDist()). Unlike DIST_PROFILE.__vendorBase
|
|
229
|
+
// (which points at the egpt-math-sdk SIBLING dist, co-served same-origin),
|
|
230
|
+
// a CDN host cannot assume a sibling package is co-published
|
|
231
|
+
// — frqtl-web must be self-sufficient for its own realm-import-map vendor
|
|
232
|
+
// libs (packet §3b). Uses `base` — THIS package's own dist/vendor/.
|
|
233
|
+
'__vendorBase': base + '/vendor'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// The default CDN profile — the SAME factory, called with the module constant.
|
|
238
|
+
// Every existing consumer (the parity assertion below, router.test.mjs,
|
|
239
|
+
// build.js's imports) gets a byte-identical object to before this refactor.
|
|
240
|
+
var CDN_PROFILE = buildCdnProfile(CDN_BASE);
|
|
241
|
+
|
|
242
|
+
// ── The static site profile (self-contained same-origin static host) ──────────────────────────────
|
|
243
|
+
//
|
|
244
|
+
// The React FraqtlIde deployed as a dumb-static, no-backend, WASM-only site (DeSciX
|
|
245
|
+
// CodeSite / any GCS-backed host — no fraqtlServe, no lib/server/server.js). The host serves the
|
|
246
|
+
// site under a path PREFIX it chooses (/<env>/<app_id>/site/ on the platform, /p/<app_id>/
|
|
247
|
+
// under `descix serve`), so every location is known only RELATIVE TO THE DOCUMENT.
|
|
248
|
+
//
|
|
249
|
+
// ENTRIES vs GRAPH. The site's HTML entries (the IDE document, pub/index.html, pub/sdk.html,
|
|
250
|
+
// pub/artifact/*.html, pub/experiments/{id}/index.html) sit at FIXED URLs — `*.html` is
|
|
251
|
+
// no-store on the platform (app-developer runbook §8.1). Everything those entries LOAD (the
|
|
252
|
+
// sdk payload: JS, WASM, CSS, JSON, notebooks) is the GRAPH and sits under the build-stamped
|
|
253
|
+
// directory STATIC_SDK_DIR/<build>/, so a browser holding a file from an earlier build never
|
|
254
|
+
// has it addressed by new HTML. `staticGraphDir(build)` is the ONE spelling of that directory.
|
|
255
|
+
// The build id is read off the document's own <html SITE_BUILD_ATTR> (written by the static
|
|
256
|
+
// build, apps/ide/vite.config.js), never guessed.
|
|
257
|
+
//
|
|
258
|
+
// - engineRouter({mode:'static'}) = buildStaticProfile(staticSdkBase(document.baseURI, build),
|
|
259
|
+
// staticEntryBase(document.baseURI)) — ABSOLUTE at runtime, because a relative URL handed to
|
|
260
|
+
// import(), an iframe inside an experiment page, or Monaco's loader would resolve against
|
|
261
|
+
// the wrong anchor (the importing module, not the document).
|
|
262
|
+
// - engineRouter({mode:'static', documentRelative:true, build}) = the same profile with
|
|
263
|
+
// DOCUMENT-RELATIVE values, for a BUILD-TIME consumer whose output the document resolves
|
|
264
|
+
// (the import map baked into index.html).
|
|
265
|
+
//
|
|
266
|
+
// THE DELIBERATE DIVERGENCE FROM CDN_PROFILE (design §A.3 / §E flag 4):
|
|
267
|
+
// CDN_PROFILE's math + notebook bases are hardwired CROSS-ORIGIN at CDN_MATH_BASE; the
|
|
268
|
+
// static-site semantic is "everything self-contained under ONE origin prefix": the math SDK
|
|
269
|
+
// is co-served at graphBase + '/egpt-math-sdk/...' and the curated notebooks ride the
|
|
270
|
+
// frqtl-web dist's OWN copy at graphBase + '/notebooks'. A DISTINCT profile here is the
|
|
271
|
+
// correct DRY call — the SAME justification that keeps CDN_PROFILE distinct from DIST_PROFILE.
|
|
272
|
+
//
|
|
273
|
+
// buildStaticProfile(graphBase, entryBase) — every GRAPH key anchors at `graphBase`; the
|
|
274
|
+
// general 'experiments/' key (the experiment ENTRY pages STUDIO/GYM frame) anchors at
|
|
275
|
+
// `entryBase`. Both are required: a host whose entries and graph share one directory passes
|
|
276
|
+
// the same base twice.
|
|
277
|
+
function buildStaticProfile(graphBase, entryBase) {
|
|
278
|
+
if (!graphBase || !entryBase) {
|
|
279
|
+
throw new Error('[engineRouter] buildStaticProfile(graphBase, entryBase) needs both bases (got ' +
|
|
280
|
+
JSON.stringify(graphBase) + ', ' + JSON.stringify(entryBase) + ') — the graph and the entry ' +
|
|
281
|
+
'pages are anchored separately; a co-served host passes the same base twice.');
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
// THE package root: the GRAPH (immutable per build) at its lib-identity placement.
|
|
285
|
+
'__frqtlRoot': graphBase + '/',
|
|
286
|
+
'assets/models/': graphBase + '/js/models/',
|
|
287
|
+
// The experiment pages are ENTRIES: resolve('experiments/{id}/index.html') is the fixed page,
|
|
288
|
+
// which itself points into the graph.
|
|
289
|
+
'experiments/': entryBase + '/experiments/',
|
|
290
|
+
// Math + notebook lanes anchored SAME-ORIGIN into the co-served payload —
|
|
291
|
+
// the deliberate divergence from CDN_PROFILE. See the factory docstring.
|
|
292
|
+
'__mathSdkUrl': graphBase + '/egpt-math-sdk/index.js',
|
|
293
|
+
'__mathWasmUrl': graphBase + '/egpt-math-sdk/wasm/egpt_math_sdk_wasm_bg.wasm',
|
|
294
|
+
'__mathWasmModuleUrl': graphBase + '/egpt-math-sdk/wasm/egpt_math_sdk_wasm.js',
|
|
295
|
+
'__mathBackend': 'wasm-math',
|
|
296
|
+
// Notebooks come from the frqtl-web dist's OWN copy (build.js copyDir
|
|
297
|
+
// lib/notebooks → dist/notebooks) — NOT under egpt-math-sdk (design §A.3/§B.1).
|
|
298
|
+
'__notebookBase': graphBase + '/notebooks',
|
|
299
|
+
// Vendor import-map root — frqtl-web's OWN dist/vendor/ (same as CDN, self-
|
|
300
|
+
// sufficient), just same-origin instead of jsdelivr.
|
|
301
|
+
'__vendorBase': graphBase + '/vendor'
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// The static site's sdk directory, relative to the site's document. The ONLY place it is
|
|
306
|
+
// spelled; the entries sit directly in it, the graph under its build-stamped child.
|
|
307
|
+
var STATIC_SDK_DIR = './pub';
|
|
308
|
+
|
|
309
|
+
// The <html> attribute carrying the static site's build id — the cross-repo contract
|
|
310
|
+
// (egpt-frqtl scripts/refresh-site.sh reads it). Every reader and writer names it through here.
|
|
311
|
+
var SITE_BUILD_ATTR = 'data-frqtl-site-build';
|
|
312
|
+
// A build id: the 8-hex-digit short sha of the commit the bundle was built from.
|
|
313
|
+
var SITE_BUILD_ID_RE = /^[0-9a-f]{8}$/;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The validated build id, or a thrown error naming what was supplied.
|
|
317
|
+
* @param {string} build
|
|
318
|
+
* @returns {string}
|
|
319
|
+
*/
|
|
320
|
+
function assertSiteBuildId(build) {
|
|
321
|
+
if (typeof build !== 'string' || !SITE_BUILD_ID_RE.test(build)) {
|
|
322
|
+
throw new Error('[engineRouter] the static site build id must match ' + SITE_BUILD_ID_RE +
|
|
323
|
+
' (the bundle\'s <html ' + SITE_BUILD_ATTR + '>), got ' + JSON.stringify(build) + '.');
|
|
324
|
+
}
|
|
325
|
+
return build;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* The build-stamped graph directory, relative to the document — './pub/<build>'. The ONE
|
|
330
|
+
* spelling of the stamped prefix.
|
|
331
|
+
* @param {string} build
|
|
332
|
+
* @returns {string}
|
|
333
|
+
*/
|
|
334
|
+
function staticGraphDir(build) {
|
|
335
|
+
return STATIC_SDK_DIR + '/' + assertSiteBuildId(build);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function _requireDocumentBase(documentBaseURI) {
|
|
339
|
+
if (!documentBaseURI) {
|
|
340
|
+
throw new Error('[engineRouter] mode:"static" resolves against the document — no document.baseURI here. ' +
|
|
341
|
+
'Outside a browser pass opts.documentBaseURI (the URL of the directory serving the static site).');
|
|
342
|
+
}
|
|
343
|
+
return documentBaseURI;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The static GRAPH's absolute base for a document: staticGraphDir(build) resolved against the
|
|
348
|
+
* document's base URI (e.g. 'https://h/prod/app/site/index.html?preset=author', 'ab12cd34' →
|
|
349
|
+
* 'https://h/prod/app/site/pub/ab12cd34'). FAILS LOUD without a base URI or a valid build id.
|
|
350
|
+
* @param {string} documentBaseURI
|
|
351
|
+
* @param {string} build
|
|
352
|
+
* @returns {string}
|
|
353
|
+
*/
|
|
354
|
+
function staticSdkBase(documentBaseURI, build) {
|
|
355
|
+
return new URL(staticGraphDir(build), _requireDocumentBase(documentBaseURI)).href;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The static ENTRY directory's absolute base for a document (e.g. 'https://h/prod/app/site/pub')
|
|
360
|
+
* — where the fixed HTML entries live.
|
|
361
|
+
* @param {string} documentBaseURI
|
|
362
|
+
* @returns {string}
|
|
363
|
+
*/
|
|
364
|
+
function staticEntryBase(documentBaseURI) {
|
|
365
|
+
return new URL(STATIC_SDK_DIR, _requireDocumentBase(documentBaseURI)).href;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The build id a static document carries on <html SITE_BUILD_ATTR>. THROWS when absent.
|
|
370
|
+
* @returns {string}
|
|
371
|
+
*/
|
|
372
|
+
function documentSiteBuild() {
|
|
373
|
+
var attr = (typeof document !== 'undefined' && document.documentElement &&
|
|
374
|
+
typeof document.documentElement.getAttribute === 'function')
|
|
375
|
+
? document.documentElement.getAttribute(SITE_BUILD_ATTR) : null;
|
|
376
|
+
if (!attr) {
|
|
377
|
+
throw new Error('[engineRouter] mode:"static": this document carries no build stamp (<html ' +
|
|
378
|
+
SITE_BUILD_ATTR + '>) — only the assembled bundle\'s pages can resolve the stamped graph ' +
|
|
379
|
+
'(make static-deploy).');
|
|
380
|
+
}
|
|
381
|
+
return assertSiteBuildId(attr);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The static IDE bundle's entry document, beside its front page. The bundle ships THIS name and no
|
|
385
|
+
// index.html: a site's index.html is the publisher's front page (host-view.js::HOST_FRONT_PAGE),
|
|
386
|
+
// which the IDE brand links home to.
|
|
387
|
+
var STATIC_APP_PAGE = 'app.html';
|
|
388
|
+
|
|
389
|
+
// The static IDE bundle's own chunk directory (vite assetsDir), relative to the document. Not
|
|
390
|
+
// `assets/`: the DeSciX platform routes `/assets/` on an app subdomain to app MEDIA, not the site.
|
|
391
|
+
var STATIC_APP_ASSETS_DIR = 'app-assets';
|
|
392
|
+
// The ONE stable-named (unhashed) static entry: the host-view declarer, which a publisher's
|
|
393
|
+
// front page beside the IDE loads by name. apps/ide/vite.config.js emits it here and the static
|
|
394
|
+
// URL gate asserts it is served (and that it imports nothing a release can delete).
|
|
395
|
+
var STATIC_HOST_VIEW_ENTRY = STATIC_APP_ASSETS_DIR + '/host-view.js';
|
|
396
|
+
|
|
397
|
+
// Parity assertion: all FOUR profiles must cover identical non-private keys.
|
|
398
|
+
// Private keys begin with '__'. The static profile is a factory; a sample instance is checked.
|
|
399
|
+
var _assertProfileParity = (function () {
|
|
400
|
+
var STATIC_SAMPLE = buildStaticProfile(staticGraphDir('00000000'), STATIC_SDK_DIR);
|
|
401
|
+
var devKeys = Object.keys(DEV_CORE_PROFILE).filter(function (k) { return k.indexOf('__') !== 0; }).sort();
|
|
402
|
+
var distKeys = Object.keys(DIST_PROFILE).filter(function (k) { return k.indexOf('__') !== 0; }).sort();
|
|
403
|
+
var cdnKeys = Object.keys(CDN_PROFILE).filter(function (k) { return k.indexOf('__') !== 0; }).sort();
|
|
404
|
+
var staticKeys = Object.keys(STATIC_SAMPLE).filter(function (k) { return k.indexOf('__') !== 0; }).sort();
|
|
405
|
+
if (devKeys.join(',') !== distKeys.join(',') ||
|
|
406
|
+
devKeys.join(',') !== cdnKeys.join(',') ||
|
|
407
|
+
devKeys.join(',') !== staticKeys.join(',')) {
|
|
408
|
+
throw new Error('[engineRouter] Profile parity FAIL: dev/dist/cdn/static profiles have different path keys.\n' +
|
|
409
|
+
'dev: ' + devKeys.join(', ') + '\n' +
|
|
410
|
+
'dist: ' + distKeys.join(', ') + '\n' +
|
|
411
|
+
'cdn: ' + cdnKeys.join(', ') + '\n' +
|
|
412
|
+
'static: ' + staticKeys.join(', '));
|
|
413
|
+
}
|
|
414
|
+
// The vendor base (__vendorBase) is a PRIVATE key but it MUST exist in ALL
|
|
415
|
+
// FOUR profiles (the single path authority for the /vendor URL space — every
|
|
416
|
+
// host resolves a realm import-map base through it). Assert it explicitly so a
|
|
417
|
+
// profile can never half-define it (§2a.4).
|
|
418
|
+
if (!DEV_CORE_PROFILE.__vendorBase || !DIST_PROFILE.__vendorBase ||
|
|
419
|
+
!CDN_PROFILE.__vendorBase || !STATIC_SAMPLE.__vendorBase) {
|
|
420
|
+
throw new Error('[engineRouter] Profile parity FAIL: __vendorBase must be defined in ALL FOUR profiles.');
|
|
421
|
+
}
|
|
422
|
+
return true;
|
|
423
|
+
})();
|
|
424
|
+
|
|
425
|
+
// ── engineRouter factory ─────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @typedef {object} EngineRouter
|
|
429
|
+
* @property {(relPath: string) => string} resolve
|
|
430
|
+
* Map a frqtl-relative path (e.g. `'experiments/_shared/boot.js'`) to an absolute URL
|
|
431
|
+
* according to the active profile. Returns `''` for paths omitted in dist (rule 5).
|
|
432
|
+
* @property {'wasm'} engine
|
|
433
|
+
* The physics engine — always 'wasm' (FRQTL is WASM-only). Consumers read this;
|
|
434
|
+
* never hardcode an engine id above the shim.
|
|
435
|
+
* @property {string} mathSdkUrl — egpt-math-sdk bundle URL.
|
|
436
|
+
* @property {string} mathWasmUrl — WASM math bridge binary URL.
|
|
437
|
+
* @property {string} mathWasmModuleUrl — WASM math bridge glue module URL.
|
|
438
|
+
* @property {string} mathBackend — active math backend id (`'js-reference'` | `'wasm-math'`).
|
|
439
|
+
* @property {string} notebookBase — curated-notebook URL base (dev: `/notebooks`).
|
|
440
|
+
* @property {string} vendorBase — placed-vendor URL base (dev: `/vendor`); the
|
|
441
|
+
* import-map base for realm bare-specifier imports (vendor-importmap.buildImportMap).
|
|
442
|
+
*/
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Create an engine router for the given mode and engine.
|
|
446
|
+
*
|
|
447
|
+
* @param {object} [opts]
|
|
448
|
+
* @param {'dev-core'|'dist'|'cdn'|'static'} [opts.mode='dev-core']
|
|
449
|
+
* `'dev-core'` uses the `lib/server` plugin mounts (`/frqtl`, `/fat`).
|
|
450
|
+
* `'dist'` uses the flat `sdk/frqtl-web/dist` layout, same-origin RELATIVE paths.
|
|
451
|
+
* `'cdn'` uses the SAME dist layout resolved to ABSOLUTE jsdelivr URLs (static
|
|
452
|
+
* hosting with no server).
|
|
453
|
+
* `'static'` uses the SAME dist layout resolved to ABSOLUTE URLs under the site's
|
|
454
|
+
* build-stamped `./pub/<build>` directory (experiment entries at `./pub/experiments/`),
|
|
455
|
+
* anchored at the document (`opts.documentBaseURI`, default `document.baseURI`), with the
|
|
456
|
+
* math + notebook lanes co-served in the same tree — so the site works under any path
|
|
457
|
+
* prefix. `opts.base` THROWS in this mode: the static base is derived, never supplied.
|
|
458
|
+
* @param {'wasm'} [opts.engine='wasm']
|
|
459
|
+
* Physics engine. FRQTL is WASM-only; an explicit `'js'` throws (archived).
|
|
460
|
+
* @param {string} [opts.mathSdkUrl]
|
|
461
|
+
* Override the egpt-math-sdk URL. Falls back to `window._egptMathSdkUrl` then
|
|
462
|
+
* the profile default.
|
|
463
|
+
* @param {string} [opts.mathWasmUrl]
|
|
464
|
+
* Override the WASM math bridge binary URL.
|
|
465
|
+
* @param {string} [opts.mathWasmModuleUrl]
|
|
466
|
+
* Override the WASM math bridge glue module URL.
|
|
467
|
+
* @param {string} [opts.mathBackend]
|
|
468
|
+
* Override the math backend id. `'wasm'` is normalized to `'wasm-math'`. Falls
|
|
469
|
+
* back to `?mathBackend=` URL param then the profile default (`__mathBackend`:
|
|
470
|
+
* `'js-reference'` for DEV_CORE_PROFILE, `'wasm-math'` for every distribution profile).
|
|
471
|
+
* @param {boolean} [opts.documentRelative]
|
|
472
|
+
* **`mode:'static'` ONLY.** Resolve to DOCUMENT-RELATIVE values (`./pub/<build>/...`)
|
|
473
|
+
* instead of anchoring at a document — for a build-time consumer whose output the document
|
|
474
|
+
* itself resolves (the static import map baked into index.html). Requires `opts.build`.
|
|
475
|
+
* Throws with any other mode, or together with `opts.documentBaseURI`.
|
|
476
|
+
* @param {string} [opts.build]
|
|
477
|
+
* **`mode:'static'` ONLY.** The site build id (`SITE_BUILD_ID_RE`). In a browser it is READ
|
|
478
|
+
* from the document's `<html SITE_BUILD_ATTR>` and must not be supplied; outside a browser
|
|
479
|
+
* (`documentBaseURI` / `documentRelative` — the build, the assembler, tests) it is required.
|
|
480
|
+
* @param {string} [opts.documentBaseURI]
|
|
481
|
+
* **`mode:'static'` ONLY.** The document URL the static site is served from (default
|
|
482
|
+
* `document.baseURI`). Supplied only outside a browser (the deploy assembler, tests) —
|
|
483
|
+
* e.g. `resolve('assets/vendor/monaco/vs')` with `'https://h/prod/app/site/'` and build
|
|
484
|
+
* `'ab12cd34'` → `'https://h/prod/app/site/pub/ab12cd34/frqtl/assets/vendor/monaco/vs'`.
|
|
485
|
+
* @param {string} [opts.base]
|
|
486
|
+
* **`mode:'cdn'` ONLY.** The absolute URL of the dist root every resolved URL is anchored at.
|
|
487
|
+
* Omitted: `CDN_BASE`, the published jsdelivr copy of this version. Supplied: the SELF-HOSTED
|
|
488
|
+
* form — a URL serving an UNMODIFIED copy of the package's `dist/` tree (every file
|
|
489
|
+
* `dist/dist-manifest.json` lists, byte for byte, at its relative path; a subset is
|
|
490
|
+
* unsupported). It must be absolute with no trailing slash, else this THROWS naming it
|
|
491
|
+
* (`assertDistBase`). The math lane (`mathSdkUrl`, `notebookBase`) is NOT re-anchored: it is
|
|
492
|
+
* the sibling package's CDN copy. Ignored for `mode:'dev-core'|'dist'`; throws for `'static'`.
|
|
493
|
+
* Consumer guide: docs/public/frqtl-sdk/COMPOSE_AND_CAPTURE.md, "Self-hosting the SDK".
|
|
494
|
+
* @returns {EngineRouter}
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* import { engineRouter } from 'lib/shim';
|
|
498
|
+
*
|
|
499
|
+
* // Dev mode (used by apps/ide during `vite dev`):
|
|
500
|
+
* const devRouter = engineRouter({ mode: 'dev-core', engine: 'wasm' });
|
|
501
|
+
* console.log(devRouter.mathSdkUrl); // '/egpt-src/index.js'
|
|
502
|
+
*
|
|
503
|
+
* // Dist mode (used by sdk/frqtl-web/build.js for the packaged shell):
|
|
504
|
+
* const distRouter = engineRouter({ mode: 'dist', engine: 'wasm' });
|
|
505
|
+
* console.log(distRouter.resolve('experiments/_shared/boot.js')); // '../../frqtl/experiments/_shared/boot.js'
|
|
506
|
+
*
|
|
507
|
+
* // CDN mode (static hosting — the artifact host shell has no server, no page anchor):
|
|
508
|
+
* const cdnRouter = engineRouter({ mode: 'cdn', engine: 'wasm' });
|
|
509
|
+
* console.log(cdnRouter.resolve('compat/engine.js'));
|
|
510
|
+
* // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.0-p0.2/dist/frqtl/compat/engine.js'
|
|
511
|
+
*
|
|
512
|
+
* // CDN mode, self-hosted: a copy of dist/ served from the consumer's own origin:
|
|
513
|
+
* const selfHosted = engineRouter({ mode: 'cdn', engine: 'wasm', base: 'https://example.org/vendor/frqtl-web/dist' });
|
|
514
|
+
* console.log(selfHosted.resolve('compat/engine.js'));
|
|
515
|
+
* // 'https://example.org/vendor/frqtl-web/dist/frqtl/compat/engine.js'
|
|
516
|
+
* console.log(selfHosted.distBase); // 'https://example.org/vendor/frqtl-web/dist'
|
|
517
|
+
*/
|
|
518
|
+
// The static profile for engineRouter's opts — the build id read from the document in a
|
|
519
|
+
// browser, supplied outside one; never both, never neither.
|
|
520
|
+
function _staticProfileFor(opts) {
|
|
521
|
+
var offDocument = !!(opts.documentRelative || opts.documentBaseURI);
|
|
522
|
+
if (offDocument && opts.build === undefined) {
|
|
523
|
+
throw new Error('[engineRouter] mode:"static" outside a browser needs opts.build — the site build id ' +
|
|
524
|
+
'the stamped graph directory is named by (scripts/site-build.mjs siteBuildId()).');
|
|
525
|
+
}
|
|
526
|
+
if (!offDocument && opts.build !== undefined) {
|
|
527
|
+
throw new Error('[engineRouter] mode:"static" in a browser reads the build from <html ' + SITE_BUILD_ATTR +
|
|
528
|
+
'>; remove opts.build:' + JSON.stringify(opts.build) + '.');
|
|
529
|
+
}
|
|
530
|
+
if (opts.documentRelative) {
|
|
531
|
+
return buildStaticProfile(staticGraphDir(opts.build), STATIC_SDK_DIR);
|
|
532
|
+
}
|
|
533
|
+
var doc = _requireDocumentBase(opts.documentBaseURI || (typeof document !== 'undefined' ? document.baseURI : ''));
|
|
534
|
+
var build = offDocument ? opts.build : documentSiteBuild();
|
|
535
|
+
return buildStaticProfile(staticSdkBase(doc, build), staticEntryBase(doc));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* The game layer's lib-identity prefixes (router-relative): every path under one resolves under the
|
|
540
|
+
* profile's package root (`__frqtlRoot`) at its lib path — 'shim/…' as itself, everything else under
|
|
541
|
+
* 'frqtl/'. The module graph needs nothing more: a module locates its imports relative to itself.
|
|
542
|
+
*/
|
|
543
|
+
var LIB_IDENTITY_PREFIXES = Object.freeze(['assets/vendor/', 'compat/', 'objects/', 'parts/', 'experiments/_shared/', 'scenes/', 'shim/']);
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Map a frqtl-relative path through a profile: a lib-identity path under the package root, else the
|
|
547
|
+
* FIRST matching public prefix (insertion order). Returns null when nothing matches — the caller decides
|
|
548
|
+
* what an unmapped path means.
|
|
549
|
+
* @param {object} profile
|
|
550
|
+
* @param {string} relPath
|
|
551
|
+
* @returns {string|null}
|
|
552
|
+
*/
|
|
553
|
+
export function resolveInProfile(profile, relPath) {
|
|
554
|
+
for (var j = 0; j < LIB_IDENTITY_PREFIXES.length; j++) {
|
|
555
|
+
if (relPath.indexOf(LIB_IDENTITY_PREFIXES[j]) === 0) {
|
|
556
|
+
return profile.__frqtlRoot + (relPath.indexOf('shim/') === 0 ? relPath : 'frqtl/' + relPath);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
var keys = Object.keys(profile).filter(function (k) { return k.indexOf('__') !== 0; });
|
|
560
|
+
for (var i = 0; i < keys.length; i++) {
|
|
561
|
+
var prefix = keys[i];
|
|
562
|
+
if (relPath === prefix || relPath.indexOf(prefix) === 0) {
|
|
563
|
+
return profile[prefix] + relPath.slice(prefix.length);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function engineRouter(opts) {
|
|
570
|
+
opts = opts || {};
|
|
571
|
+
var mode = opts.mode || 'dev-core';
|
|
572
|
+
var engine = opts.engine || 'wasm';
|
|
573
|
+
// FRQTL physics is WASM-only. An explicit 'js' request FAILS LOUD — the JS
|
|
574
|
+
// reference engine is archived, not a runtime backend.
|
|
575
|
+
if (engine === 'js') {
|
|
576
|
+
throw new Error('[engineRouter] JS reference engine archived — FRQTL is WASM-only. ' +
|
|
577
|
+
'Remove engine:"js"; the only FRQTL physics engine is the compiled Rust/WASM engine.');
|
|
578
|
+
}
|
|
579
|
+
if (mode === 'static' && opts.base) {
|
|
580
|
+
throw new Error('[engineRouter] mode:"static" takes no opts.base — the static base is derived from the ' +
|
|
581
|
+
'document (STATIC_SDK_DIR against document.baseURI). Remove base:' + JSON.stringify(opts.base) + '.');
|
|
582
|
+
}
|
|
583
|
+
if (opts.documentRelative && mode !== 'static') {
|
|
584
|
+
throw new Error('[engineRouter] opts.documentRelative applies only to mode:"static" (got mode:"' + mode + '").');
|
|
585
|
+
}
|
|
586
|
+
if (opts.documentRelative && opts.documentBaseURI) {
|
|
587
|
+
throw new Error('[engineRouter] opts.documentRelative and opts.documentBaseURI are exclusive — a ' +
|
|
588
|
+
'document-relative router is anchored at no document.');
|
|
589
|
+
}
|
|
590
|
+
if (opts.build !== undefined && mode !== 'static') {
|
|
591
|
+
throw new Error('[engineRouter] opts.build applies only to mode:"static" (got mode:"' + mode + '").');
|
|
592
|
+
}
|
|
593
|
+
var profile = mode === 'dist'
|
|
594
|
+
? DIST_PROFILE
|
|
595
|
+
: (mode === 'cdn'
|
|
596
|
+
? (opts.base !== undefined ? buildCdnProfile(opts.base) : CDN_PROFILE)
|
|
597
|
+
: (mode === 'static'
|
|
598
|
+
? _staticProfileFor(opts)
|
|
599
|
+
: DEV_CORE_PROFILE));
|
|
600
|
+
|
|
601
|
+
function resolve(relPath) {
|
|
602
|
+
// Check the __distOmit list first: files omitted in dist return '' (signal to skip).
|
|
603
|
+
var omitList = profile.__distOmit;
|
|
604
|
+
if (omitList && Array.isArray(omitList)) {
|
|
605
|
+
for (var oi = 0; oi < omitList.length; oi++) {
|
|
606
|
+
if (relPath === omitList[oi]) { return ''; }
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
var mapped = resolveInProfile(profile, relPath);
|
|
610
|
+
if (mapped !== null) { return mapped; }
|
|
611
|
+
// Fallback: the dev-core /frqtl mount serves lib/frqtl 1:1. Every other profile maps a
|
|
612
|
+
// FLATTENED or re-anchored tree, where a root-absolute '/frqtl/' guess is a silent 404
|
|
613
|
+
// — so an unmapped path there fails loud and names the profile key to add.
|
|
614
|
+
if (profile !== DEV_CORE_PROFILE) {
|
|
615
|
+
throw new Error('[engineRouter] mode:"' + mode + '" has no profile prefix for "' + relPath + '". ' +
|
|
616
|
+
'Add the prefix to every profile in lib/shim/engine/router.js (profile parity).');
|
|
617
|
+
}
|
|
618
|
+
return '/frqtl/' + relPath;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Resolve the math SDK URL:
|
|
622
|
+
// 1. Caller-supplied explicit override always wins.
|
|
623
|
+
// 2. window._egptMathSdkUrl (runtime injection used by dist shell).
|
|
624
|
+
// 3. Profile __mathSdkUrl entry (authoritative default per environment).
|
|
625
|
+
var mathSdkUrl = opts.mathSdkUrl
|
|
626
|
+
|| (typeof window !== 'undefined' && window._egptMathSdkUrl)
|
|
627
|
+
|| profile.__mathSdkUrl
|
|
628
|
+
|| '';
|
|
629
|
+
|
|
630
|
+
var mathWasmUrl = opts.mathWasmUrl
|
|
631
|
+
|| (typeof window !== 'undefined' && window._egptMathWasmUrl)
|
|
632
|
+
|| profile.__mathWasmUrl
|
|
633
|
+
|| '';
|
|
634
|
+
|
|
635
|
+
var mathWasmModuleUrl = opts.mathWasmModuleUrl
|
|
636
|
+
|| (typeof window !== 'undefined' && window._egptMathWasmModuleUrl)
|
|
637
|
+
|| profile.__mathWasmModuleUrl
|
|
638
|
+
|| '';
|
|
639
|
+
|
|
640
|
+
// Resolve the math backend id.
|
|
641
|
+
// URL param ?mathBackend=wasm|js-reference|wasm-math takes the lowest priority
|
|
642
|
+
// (can be overridden by the caller). 'wasm' is a friendly alias for 'wasm-math'.
|
|
643
|
+
// Profile __mathBackend is the environment default; caller opts.mathBackend wins.
|
|
644
|
+
var _urlMathBackend = '';
|
|
645
|
+
if (typeof window !== 'undefined' && typeof URLSearchParams !== 'undefined') {
|
|
646
|
+
try {
|
|
647
|
+
_urlMathBackend = new URLSearchParams(window.location.search).get('mathBackend') || '';
|
|
648
|
+
} catch (_) { /* ignore in non-browser */ }
|
|
649
|
+
}
|
|
650
|
+
var mathBackend = opts.mathBackend
|
|
651
|
+
|| _urlMathBackend
|
|
652
|
+
|| profile.__mathBackend;
|
|
653
|
+
// Normalise friendly alias: 'wasm' → 'wasm-math'.
|
|
654
|
+
if (mathBackend === 'wasm') { mathBackend = 'wasm-math'; }
|
|
655
|
+
|
|
656
|
+
// Notebook URL base (dev=/notebooks, dist=../../../egpt-math-sdk/notebooks).
|
|
657
|
+
// Single path authority for the curated-notebook URL space — surfaces never
|
|
658
|
+
// derive it; FraqtlIde threads it into the notebook browser/pane as a prop.
|
|
659
|
+
var notebookBase = profile.__notebookBase || '/notebooks';
|
|
660
|
+
|
|
661
|
+
// Vendor URL base (dev=/vendor, dist=../../../egpt-math-sdk/vendor). Single path
|
|
662
|
+
// authority for the placed-vendor URL space — the notebook surfaces NEVER derive
|
|
663
|
+
// '/vendor'; the dev serve-time splice and the static bake build the import-map base from
|
|
664
|
+
// THIS (vendor-importmap.buildImportMap(manifest, router.vendorBase)).
|
|
665
|
+
var vendorBase = profile.__vendorBase || '/vendor';
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
resolve: resolve,
|
|
669
|
+
// THE game-layer fact of this profile: the lib-identity package root (ends in '/').
|
|
670
|
+
frqtlRoot: function () { return profile.__frqtlRoot; },
|
|
671
|
+
// The physics engine — always 'wasm' (FRQTL is WASM-only). Consumers
|
|
672
|
+
// (e.g. the notebook realm-frqtl descriptor) READ this instead of hardcoding
|
|
673
|
+
// an engine or sniffing an audience flag (the dependency-map rule: never an
|
|
674
|
+
// `if (audience) engine='wasm'` branch above the shim).
|
|
675
|
+
engine: engine,
|
|
676
|
+
mathSdkUrl: mathSdkUrl,
|
|
677
|
+
mathWasmUrl: mathWasmUrl,
|
|
678
|
+
mathWasmModuleUrl: mathWasmModuleUrl,
|
|
679
|
+
mathBackend: mathBackend,
|
|
680
|
+
notebookBase: notebookBase,
|
|
681
|
+
vendorBase: vendorBase,
|
|
682
|
+
// mode:'cdn' only: the absolute dist root this router anchors at (opts.base, else
|
|
683
|
+
// CDN_BASE) — what a boot names when that root serves nothing. null in every other mode.
|
|
684
|
+
distBase: mode === 'cdn' ? (opts.base || CDN_BASE) : null
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
// Export profiles for the sdk build to consume.
|
|
690
|
+
export { LIB_IDENTITY_PREFIXES, DEV_CORE_PROFILE, DIST_PROFILE, CDN_PROFILE, STATIC_SDK_DIR, STATIC_APP_PAGE, STATIC_APP_ASSETS_DIR, STATIC_HOST_VIEW_ENTRY, SITE_BUILD_ATTR, SITE_BUILD_ID_RE, CDN_SDK_VERSION, CDN_MATH_SDK_VERSION, CDN_BASE, cdnDistBaseFor, CDN_MATH_BASE, buildCdnProfile, buildDistProfile, buildStaticProfile, staticGraphDir, staticSdkBase, staticEntryBase };
|