@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2

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