@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,1023 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * @module Runtime
5
+ * @description
6
+ * Runtime — CONTRACT-RUNTIME (frozen Phase 0).
7
+ *
8
+ * Named ESM export. A missing import is a hard FAIL-LOUD error.
9
+ * Replaces the ambient window.CommonRuntime global.
10
+ *
11
+ * THE UNIFIED RUNTIME OBJECT SURFACE.
12
+ *
13
+ * Exposes four parallel first-class objects to authored IDE code.
14
+ * All four run on the MAIN THREAD (no Worker in v1).
15
+ *
16
+ * Four first-class runtime objects:
17
+ * - `frqtl` — physics engine + game objects (the `caps.frqtl` sugar surface).
18
+ * - `math` — EGPT Math SDK object surface (lazy-initialized via ensureMath).
19
+ * - `console` — output sink object (routes through Bus console:write events).
20
+ * - `render` — graphics surface object (routes through Bus render:frame events).
21
+ *
22
+ * @example
23
+ * // Typical initialization inside FraqtlIde (lib/features/ide):
24
+ * import { Runtime, engineRouter } from 'lib/shim';
25
+ * const router = engineRouter({ mode: 'dev-core', engine: 'wasm' });
26
+ * await Runtime.init({
27
+ * engine: 'wasm',
28
+ * _routerMathSdkUrl: router.mathSdkUrl,
29
+ * _routerMathWasmUrl: router.mathWasmUrl,
30
+ * _routerMathWasmModuleUrl: router.mathWasmModuleUrl,
31
+ * _routerMathBackend: router.mathBackend,
32
+ * _router: router
33
+ * });
34
+ */
35
+
36
+ import { Bus } from '../bus.js';
37
+ import { MathBackendRegistry, MathBackendNotAvailableError } from '../../egpt/js/model/backend/MathBackendRegistry.js';
38
+ import { ensureEngineStack } from '../engine/engine-stack-boot.js';
39
+ import { validateAndMap as validateKnob } from '../notebook/knob.js';
40
+ import {
41
+ hasImport as _cellHasImport,
42
+ buildModuleClosure as buildNotebookModuleClosure,
43
+ compileEntryModuleSource as compileNotebookEntryModuleSource
44
+ } from '../notebook/module-resolver.js';
45
+
46
+ 'use strict';
47
+
48
+ /* ── Internal state ─────────────────────────────────────────────────── */
49
+ var _initialized = false;
50
+ // The realm's math sdk — set ONLY once its provider registered AND the requested
51
+ // backend was selected, so a cached sdk always runs the backend that was asked for.
52
+ var _mathSdk = null;
53
+ // The ONE math-SDK load of the realm. Concurrent callers (init's eager load racing a
54
+ // view's ensureMath) share it, so a realm builds exactly one sdk — and registers
55
+ // exactly one provider object per id (MathBackendRegistry.register throws on a
56
+ // second). A load that was ATTEMPTED and failed stays here, rejected: every later
57
+ // caller gets the same loud error (a reload clears it). Retrying a deterministic
58
+ // failure would build a second provider object and trip the identity guard with a
59
+ // misleading "two bundles" message.
60
+ var _mathSdkLoad = null;
61
+ var _mathSdkUrl = '';
62
+ var _mathWasmUrl = '';
63
+ var _mathWasmModuleUrl = '';
64
+ var _engine = 'js';
65
+ var _mathBackend = null; // M4: router-resolved backend id (the router profile owns the default)
66
+ // E2a: the engineRouter INSTANCE threaded in by Runtime.init (FraqtlIde passes
67
+ // `router`). frqtl.ensureEngine passes it straight to ensureEngineStack(router), which
68
+ // `.resolve`s the boot module and the realm's module instances (REALM_MODULES) — the runtime
69
+ // itself never imports a router or lib/frqtl/objects, it only carries the instance FraqtlIde built.
70
+ var _router = null;
71
+ // The realm's module instances, set when ensureEngine resolves (engine-stack-boot.js REALM_MODULES).
72
+ var _realm = null;
73
+
74
+ // Single-executor guard (P1).
75
+ // Runtime.init() may be called multiple times (StrictMode double-mount,
76
+ // remounts, preset switches). The Bus 'run:request' executor
77
+ // must be registered EXACTLY ONCE — extra registrations multiply execution count.
78
+ // _runRequestHandler stores the one handler reference so Bus.off() can remove a
79
+ // stale listener and Bus.on() re-registers only the current one (idempotent).
80
+ var _runRequestHandler = null;
81
+
82
+ /* ── console runtime object ─────────────────────────────────────────── */
83
+ var _consoleObj = {
84
+ log: function (level, text) {
85
+ Bus.emit('console:write', { level: level || 'info', text: String(text) });
86
+ },
87
+ print: function (text) {
88
+ Bus.emit('console:write', { level: 'info', text: String(text) });
89
+ },
90
+ warn: function (text) {
91
+ Bus.emit('console:write', { level: 'warn', text: String(text) });
92
+ },
93
+ error: function (text) {
94
+ Bus.emit('console:write', { level: 'error', text: String(text) });
95
+ },
96
+ clear: function () {
97
+ Bus.emit('console:clear', {});
98
+ }
99
+ };
100
+
101
+ /* ── render runtime object ──────────────────────────────────────────── */
102
+ var _renderObj = {
103
+ frame: function (universeOrTickData) {
104
+ Bus.emit('render:frame', {
105
+ universe: universeOrTickData,
106
+ tickData: universeOrTickData
107
+ });
108
+ },
109
+ start: function () {
110
+ Bus.emit('render:frame', { cmd: 'start' });
111
+ },
112
+ stop: function () {
113
+ Bus.emit('render:frame', { cmd: 'stop' });
114
+ }
115
+ };
116
+
117
+ /* ── frqtl runtime object ───────────────────────────────────────────────────
118
+ *
119
+ * The `frqtl` capability a builder cell sees (caps.frqtl). It exposes:
120
+ * - FrqtlApp / Universe — the engine entry points;
121
+ * - ensureEngine() — boot the engine + game layer so the SUGAR getters and
122
+ * FrqtlApp are live (the builder runs BEFORE the canvas cell that
123
+ * would otherwise boot the engine);
124
+ * - knob() — the single driving-knob authority;
125
+ * - SUGAR GETTERS (PointSource, LargeObject, CollisionTypes, …) — the library
126
+ * sugar objects, reached HERE so a user-code setup MODULE never imports
127
+ * `lib/frqtl/objects` (cross-arrow veto) and never reads window.* itself
128
+ * (B1; build-gate B). The module takes
129
+ * this object as a passed `frqtl` param and destructures the sugar from it.
130
+ *
131
+ * EVERY member that reads a game-layer global goes through `_sugar(name)` — the ONE
132
+ * reader — so reading one before ensureEngine() has booted the stack FAILS LOUD,
133
+ * naming the global and the gate, rather than yielding undefined or null. A soft
134
+ * return is exactly what turns a boot-ordering defect into an opaque
135
+ * property-access TypeError one frame later ("Cannot read properties of null
136
+ * (reading 'build')" — FRAQTL #73); the throw names what was unavailable instead.
137
+ * That covers the constructor members (`FrqtlApp`, `RecipeContext`) and every getter generated from
138
+ * the member table (CAPS_FRQTL_MEMBERS): there is no soft residual.
139
+ *
140
+ * The boot gate is `ensureEngine()` — the MANDATORY FAIL-LOUD entry point (it
141
+ * rejects if Runtime.init did not thread a router resolve). Every gallery builder
142
+ * cell `await`s it before touching ANY caps.frqtl member, so on the gallery paths
143
+ * nothing is read pre-boot.
144
+ */
145
+ // THE caps.frqtl member table, keyed by the EXPOSED name: what each member reads — [REALM_MODULES key,
146
+ // export name], or an ENGINE value read through the realm's engine accessor (compat/engine.js engine())
147
+ // at the call. Every row is exposed: the two constructor members (FrqtlApp, RecipeContext) are functions
148
+ // over their rows below; every other row is a getter generated from this table (no hand list).
149
+ var _SUGAR = Object.freeze({
150
+ FrqtlApp: ['app', 'FrqtlApp'], RecipeContext: ['recipe', 'RecipeContext'],
151
+ PointSource: ['parts', 'PointSource'], LargeObject: ['parts', 'LargeObject'], Wall: ['parts', 'Wall'],
152
+ SlitScreen: ['parts', 'SlitScreen'], SlitLightSource: ['parts', 'SlitLightSource'],
153
+ DetectorWall: ['parts', 'DetectorWall'], OvenBox: ['parts', 'OvenBox'], Rectangle: ['parts', 'Rectangle'],
154
+ CollisionTypes: ['parts', 'CollisionTypes'], CollisionActions: ['parts', 'CollisionActions'],
155
+ QuantumEmitter: ['QuantumEmitter', 'QuantumEmitter'], Box: ['Box', 'Box'],
156
+ CircuitGate: ['CircuitGate', 'CircuitGate'], CircuitProbe: ['CircuitProbe', 'CircuitProbe'],
157
+ Frame: ['engine', 'Frame'], Charge: ['engine', 'Charge'], Universe: ['engine', 'EGPTUniverse']
158
+ });
159
+ /** The caps.frqtl member table (exposed name → [REALM_MODULES key | 'engine', export name]) — exported so a
160
+ * conformance test drives off it (lib/shim/runtime/test/caps-frqtl-failloud.test.mjs). */
161
+ export var CAPS_FRQTL_MEMBERS = _SUGAR;
162
+ // The ONE reader behind caps.frqtl — the generated getters AND the constructor members (FrqtlApp,
163
+ // RecipeContext). Names the missing member and the gate; never returns undefined/null for a
164
+ // caller to trip over later.
165
+ function _sugar(name) {
166
+ var row = _SUGAR[name];
167
+ if (!row) { throw new Error('[frqtl] caps.frqtl has no member "' + name + '". (FAIL-LOUD.)'); }
168
+ if (!_realm) {
169
+ throw new Error(
170
+ '[frqtl] "' + name + '" is not available — the engine/game layer is not booted yet. A builder ' +
171
+ 'cell must `await frqtl.ensureEngine()` before it reads caps.frqtl (or before it calls a setup ' +
172
+ 'module that does). (FAIL-LOUD.)'
173
+ );
174
+ }
175
+ var v = row[0] === 'engine' ? _realm.engine.engine()[row[1]] : _realm[row[0]][row[1]];
176
+ if (v === undefined || v === null) {
177
+ throw new Error('[frqtl] the realm module for "' + name + '" exports no ' + row[1] + '. (FAIL-LOUD.)');
178
+ }
179
+ return v;
180
+ }
181
+
182
+ var _frqtlObj = {
183
+ FrqtlApp: function (opts) {
184
+ return new (_sugar('FrqtlApp'))(opts);
185
+ },
186
+ // FrqtlApp.carryFactoryStatics — the setup-builder FACTORY template wraps the module
187
+ // export in an arrow and carries its factory statics (FrqtlApp.FACTORY_STATICS) onto it,
188
+ // so runner.setup() sees what the bare factory declares. Owner: FrqtlApp.
189
+ carryFactoryStatics: function (wrapper, factory) {
190
+ return _sugar('FrqtlApp').carryFactoryStatics(wrapper, factory);
191
+ },
192
+ // The frozen recipe-facade (lib/frqtl/objects/RecipeContext.js) a saved
193
+ // project's `buildExperiment(exp)` recipe is evaluated against — exposed here so
194
+ // a notebook RECIPE-shape canvas cell (setup-builder.js `shape:'recipe'`) can
195
+ // build the SAME co-init + `addObject()` recipe the STUDIO blank page + the
196
+ // walkthrough rungs run, instead of the `(frqtl,universe,options)` setup-factory
197
+ // form. Read from the realm ensureEngine() booted; FAIL-LOUD through _sugar exactly like
198
+ // FrqtlApp above.
199
+ RecipeContext: function (opts) {
200
+ return new (_sugar('RecipeContext'))(opts);
201
+ },
202
+ // Boot the engine + game layer (so FrqtlApp + the sugar getters are
203
+ // live) WITHOUT importing GraphicsPane (upward-arrow veto) or lib/frqtl/objects
204
+ // (cross-arrow veto). Same canonical boot module GraphicsPane boots through,
205
+ // via the router threaded by Runtime.init. The builder cell awaits this before
206
+ // constructing the runner / calling its setup module. FAIL-LOUD if init never
207
+ // threaded a router. Idempotent + cached (ensureEngineStack).
208
+ ensureEngine: function () {
209
+ if (!_router) {
210
+ return Promise.reject(new Error(
211
+ '[frqtl] ensureEngine unavailable: Runtime.init did not thread a router (_router). (FAIL-LOUD.)'
212
+ ));
213
+ }
214
+ return ensureEngineStack(_router).then(function (realm) { _realm = realm; });
215
+ },
216
+ // E2c: validate + map a driving-knob assignment against the SINGLE knob
217
+ // authority (lib/shim/notebook/knob.js), returning the setup-factory option
218
+ // object to thread into runner.setup(setupFn, opts). The builder cell calls it
219
+ // so the knob stays VISIBLE/EDITABLE while still FAIL-LOUD on an unknown key /
220
+ // out-of-range value (delegates — no second validator). `spec` is the factory
221
+ // name (or comma-spec); `optsMap` is {param: value} ({} → {}). Synchronous —
222
+ // the builder cell composes it inline with frqtl.FrqtlApp.setup.
223
+ knob: function (spec, optsMap) {
224
+ return validateKnob(spec, optsMap || {});
225
+ }
226
+ };
227
+ // ── SUGAR GETTERS (B1) — the library sugar objects via caps.frqtl, one per table row ──────────────
228
+ // A user-code setup module receives this object and destructures what it needs. It NEVER imports
229
+ // lib/frqtl/objects and NEVER reads window.* directly.
230
+ Object.keys(_SUGAR).forEach(function (name) {
231
+ if (Object.prototype.hasOwnProperty.call(_frqtlObj, name)) { return; } // a constructor member, above
232
+ Object.defineProperty(_frqtlObj, name, { enumerable: true, get: function () { return _sugar(name); } });
233
+ });
234
+
235
+ /* ── init ───────────────────────────────────────────────────────────── */
236
+
237
+ /**
238
+ * Initialize the Runtime. Should be called once per mount (idempotent for the
239
+ * engine-ready listener; duplicates are removed via the P1 dedup guard).
240
+ *
241
+ * @param {object} [opts]
242
+ * @param {string} [opts.engine='js']
243
+ * Physics engine to use ('wasm'; 'js' is archived). Stored internally and forwarded to
244
+ * the engine boot (frqtl.ensureEngine).
245
+ * @param {string} [opts.mathSdkUrl]
246
+ * Explicit URL for the egpt-math-sdk bundle. Wins over all other sources.
247
+ * @param {string} [opts.mathWasmUrl]
248
+ * URL for the WASM math bridge binary (`.wasm` file). Used by wasm-math provider.
249
+ * @param {string} [opts.mathWasmModuleUrl]
250
+ * URL for the WASM math bridge glue module (`.js` file). Drives provider registration.
251
+ * @param {string} [opts._routerMathSdkUrl]
252
+ * Router-resolved math SDK URL (threaded by FraqtlIde; caller wins over this).
253
+ * @param {string} [opts._routerMathWasmUrl]
254
+ * Router-resolved WASM binary URL.
255
+ * @param {string} [opts._routerMathWasmModuleUrl]
256
+ * Router-resolved WASM glue module URL.
257
+ * @param {string} [opts._routerMathBackend]
258
+ * Router-resolved backend id ('js-reference' | 'wasm-math'). Normalized
259
+ * 'wasm' → 'wasm-math' internally.
260
+ * @param {import('../engine/router.js').EngineRouter} [opts._router]
261
+ * The engineRouter() INSTANCE itself, threaded so `frqtl.ensureEngine` boots the
262
+ * stack and reads the realm's module instances via the single path authority (`.resolve`)
263
+ * without importing the router directly.
264
+ * @returns {Promise<void>} Resolves when the runtime is wired (and the eagerly requested math
265
+ * backend selected). Rejects (FAIL-LOUD) on a math selection failure.
266
+ *
267
+ * @example
268
+ * // Standard initialization (FraqtlIde wires this):
269
+ * await Runtime.init({
270
+ * engine: 'wasm',
271
+ * _routerMathSdkUrl: router.mathSdkUrl,
272
+ * _routerMathWasmUrl: router.mathWasmUrl,
273
+ * _routerMathWasmModuleUrl: router.mathWasmModuleUrl,
274
+ * _routerMathBackend: router.mathBackend,
275
+ * _router: router
276
+ * });
277
+ */
278
+ function init(opts) {
279
+ opts = opts || {};
280
+ // Math SDK URL resolution (V2):
281
+ // 1. Caller-supplied explicit opt always wins.
282
+ // 2. window._egptMathSdkUrl (runtime injection used by dist shell).
283
+ // 3. Router profile __mathSdkUrl entry (the one path authority).
284
+ // The hardcoded default literal is gone — profile is the sole fallback.
285
+ _mathSdkUrl = opts.mathSdkUrl || (typeof window !== 'undefined' && window._egptMathSdkUrl) || (opts._routerMathSdkUrl || '');
286
+ _mathWasmUrl = opts.mathWasmUrl || (opts._routerMathWasmUrl || '');
287
+ _mathWasmModuleUrl = opts.mathWasmModuleUrl || (opts._routerMathWasmModuleUrl || '');
288
+ _engine = opts.engine || 'js';
289
+ // M4: router passes the requested backend id; normalise 'wasm'→'wasm-math'.
290
+ // No default here — the router profile (`__mathBackend`) is the one default
291
+ // authority. With no id at all, the registry's first registrant stays active.
292
+ var _requestedBackend = opts.mathBackend || opts._routerMathBackend || null;
293
+ if (_requestedBackend === 'wasm') { _requestedBackend = 'wasm-math'; }
294
+ _mathBackend = _requestedBackend;
295
+ // E2a: thread the engineRouter INSTANCE so frqtl.ensureEngine boots the engine
296
+ // stack via the one path authority — FraqtlIde passes the whole `router` object it built.
297
+ if (opts._router && typeof opts._router.resolve === 'function') { _router = opts._router; }
298
+
299
+ // M4 fix: a view's ensureMath() may have ALREADY created the SDK before init
300
+ // ran (child effects fire before parent effects — the ?preset=codec deep-link
301
+ // race). The cached-sdk early return in _ensureMathSdk means selection would
302
+ // otherwise never be applied for this init's backend. Apply it now; a failure
303
+ // rejects init's promise (FAIL-LOUD), never a silent js-reference fallback.
304
+ var _lateSelectionError = null;
305
+ var _eagerMath = null;
306
+ if (_mathSdk) {
307
+ try { _applyBackendSelection(_mathSdk); } catch (e) { _failMathRealm(e); _lateSelectionError = e; }
308
+ } else if (_mathBackend && _mathBackend !== 'js-reference') {
309
+ // M4 fix: eager-load the math SDK so the requested backend registers AND
310
+ // selects at startup (badges/bench chips derive from MathBackendRegistry, so
311
+ // they read correctly without a first Run). js-reference is excluded ONLY so
312
+ // a dev STUDIO session does not pull the JS reference core at boot; a
313
+ // js-reference request that this realm cannot serve still fails loud, at
314
+ // the first math use.
315
+ // FAIL-LOUD: init's promise settles only after this load, and REJECTS with
316
+ // its error (_applyBackendSelection's precise diagnosis, incl. the
317
+ // `npm run build:wasm-bridge` hint, also lands in the console pane) —
318
+ // never a console line alone, never a silent js-reference fallback.
319
+ _eagerMath = _ensureMathSdk();
320
+ }
321
+
322
+ // P1: dedup the run:request executor.
323
+ // If a previous handler is registered (e.g. StrictMode double-mount or a
324
+ // remount), remove it first so there is
325
+ // always EXACTLY ONE executor on this event — never two.
326
+ if (_runRequestHandler) { Bus.off('run:request', _runRequestHandler); }
327
+ _runRequestHandler = function (payload) {
328
+ run({ source: payload.source, code: payload.code, fileId: payload.fileId, sourceUrl: payload.sourceUrl || null });
329
+ };
330
+ Bus.on('run:request', _runRequestHandler);
331
+
332
+ // The physics engine is NOT booted here: it boots on demand, through the ONE boot module, when
333
+ // a surface needs it (GraphicsPane.boot(), frqtl.ensureEngine → ensureEngineStack). init settles
334
+ // once the math realm (when eagerly requested) is ready.
335
+ var _engineReady = _lateSelectionError ? Promise.reject(_lateSelectionError) : Promise.resolve();
336
+ _initialized = true;
337
+ if (!_eagerMath) { return _engineReady; }
338
+ return Promise.all([_engineReady, _eagerMath.catch(function (e) {
339
+ console.error('[Runtime] eager math SDK load for backend "' + _mathBackend + '" failed:', e);
340
+ throw e;
341
+ })]).then(function () {});
342
+ }
343
+
344
+ /* ── run ────────────────────────────────────────────────────────────── */
345
+
346
+ /**
347
+ * Fire-and-forget cell execution (the legacy code-editor run path). Emits
348
+ * `run:result` on the Bus when complete. NOT the notebook execution path — use
349
+ * `runCell` for value-returning, binding-aware execution.
350
+ *
351
+ * @param {object} [opts]
352
+ * @param {'frqtl'|'math'} [opts.source='frqtl'] — selects the execution lane.
353
+ * @param {string} [opts.code=''] — the cell body to evaluate.
354
+ * @param {string} [opts.fileId='_editor'] — display label in the console header.
355
+ * @param {string} [opts.sourceUrl] — absolute URL of the source file (math module path resolution).
356
+ * @returns {void}
357
+ */
358
+ function run(opts) {
359
+ opts = opts || {};
360
+ var source = opts.source || 'frqtl';
361
+ var code = opts.code || '';
362
+ var fileId = opts.fileId || '_editor';
363
+ var sourceUrl = opts.sourceUrl || null; // absolute URL of the source file (optional)
364
+
365
+ _consoleObj.clear();
366
+ _consoleObj.log('info', '--- run:' + source + ' (' + fileId + ') ---');
367
+
368
+ if (source === 'math') {
369
+ _runMath(code, fileId, sourceUrl);
370
+ } else {
371
+ _logBackendHeader();
372
+ _runFrqtl(code, fileId);
373
+ }
374
+ }
375
+
376
+ // P2: one derived backend header per run. Shows WHICH math
377
+ // backend executes — derived from MathBackendRegistry, never hardcoded — emitted
378
+ // ONCE per run:request execution so duplicate-execution bugs are immediately
379
+ // visible (N headers = N executions). The math lane emits it after ensureMath
380
+ // settles, so it names the backend the run actually uses, not the pre-load state.
381
+ function _logBackendHeader() {
382
+ var label;
383
+ try { label = MathBackendRegistry.active().id; } catch (_) { label = 'none'; }
384
+ _consoleObj.log('info', 'backend: ' + label);
385
+ }
386
+
387
+ function _runFrqtl(code, fileId) {
388
+ try {
389
+ var fn = new Function('frqtl', 'math', 'console', 'render', code); // eslint-disable-line no-new-func
390
+ fn(_frqtlObj, _mathSdk, _consoleObj, _renderObj);
391
+ Bus.emit('run:result', { source: 'frqtl', ok: true });
392
+ } catch (e) {
393
+ var msg = (e && e.message ? e.message : String(e));
394
+ _consoleObj.error(msg);
395
+ Bus.emit('run:result', { source: 'frqtl', ok: false, error: msg });
396
+ }
397
+ }
398
+
399
+ function _runMath(code, fileId, sourceUrl) {
400
+ _ensureMathSdk().then(function (sdk) {
401
+ _logBackendHeader();
402
+ // The math example suites are authored as ES modules with the contract
403
+ // `export async function run(sdk)` — the SAME contract the sdk editor's
404
+ // test-runner-worker executes. new Function cannot parse module syntax
405
+ // ("Unexpected token 'export'"), so module sources take the dynamic-import
406
+ // path; plain scripts keep the legacy function path.
407
+ if (_isModuleSource(code)) {
408
+ return _runMathModule(code, sdk, sourceUrl).then(function () {
409
+ Bus.emit('run:result', { source: 'math', ok: true });
410
+ }).catch(function (e) {
411
+ var msg = (e && e.message ? e.message : String(e));
412
+ _consoleObj.error(msg);
413
+ Bus.emit('run:result', { source: 'math', ok: false, error: msg });
414
+ });
415
+ }
416
+ try {
417
+ var fn = new Function('frqtl', 'math', 'console', 'render', code); // eslint-disable-line no-new-func
418
+ fn(_frqtlObj, sdk, _consoleObj, _renderObj);
419
+ Bus.emit('run:result', { source: 'math', ok: true });
420
+ } catch (e) {
421
+ var msg = (e && e.message ? e.message : String(e));
422
+ _consoleObj.error(msg);
423
+ Bus.emit('run:result', { source: 'math', ok: false, error: msg });
424
+ }
425
+ }, function (e) {
426
+ _logBackendHeader();
427
+ var msg = 'Math SDK init failed: ' + (e && e.message ? e.message : String(e));
428
+ _consoleObj.error(msg);
429
+ Bus.emit('run:result', { source: 'math', ok: false, error: msg });
430
+ }).finally(function () {
431
+ // THIS lane's run unit is the whole buffer: nothing the user can name
432
+ // outlives it, so the buffer IS the math scope and it ends here.
433
+ resetMathScope();
434
+ });
435
+ }
436
+
437
+ /**
438
+ * End the active math backend's value scope and begin a fresh one.
439
+ *
440
+ * For `wasm-math` this drains the WASM arena and increments its epoch, so EVERY
441
+ * handle minted before the call is dead — a later use throws
442
+ * `arena:stale-handle`. For `js-reference` it is a no-op.
443
+ *
444
+ * THE SCOPE RULE (FRAQTL issue #67): a math scope is one RUN, and the caller
445
+ * that knows what a run is owns the boundary. Two run units exist, each stated
446
+ * once: the `run()` lane's unit is one code buffer (it calls this in its own
447
+ * `finally`); a notebook's unit is one notebook run, so `NotebookKernel` calls
448
+ * this where its binding store's lifetime begins and ends. A CELL is NOT a run —
449
+ * a cell's declared outputs are committed into the kernel's binding store and
450
+ * must outlive the cell, which is why `runCell()` deliberately does not call it.
451
+ *
452
+ * Runtime is the SOLE owner of the `MathBackendRegistry.resetScope()` call above
453
+ * the shim (providers must never subscribe to Bus to reset themselves —
454
+ * inverted-arrow VETO); this method is the contract consumers call instead.
455
+ *
456
+ * @returns {void}
457
+ *
458
+ * @example
459
+ * // A host that owns its own run unit brackets it:
460
+ * Runtime.resetMathScope(); // fresh scope — prior values are now dead
461
+ * await Runtime.runCell({ ... }); // values minted here survive
462
+ * await Runtime.runCell({ ... }); // ...and are usable here
463
+ */
464
+ function resetMathScope() {
465
+ MathBackendRegistry.resetScope();
466
+ }
467
+
468
+ /* ── ES-module math execution (mirrors sdk editor test-runner-worker.js) ──
469
+ *
470
+ * The catalog suites import from '../../index.js' (and friends) relative to
471
+ * <sdk>/editor/examples/. A blob: module has no hierarchical base URL, so all
472
+ * specifiers are rewritten to ABSOLUTE URLs first — core/barrel imports map to
473
+ * the one sdk bundle (_mathSdkUrl, the engineRouter-resolved authority), node
474
+ * shims map to the sdk's editor/shims/, everything else resolves against the
475
+ * sdk's editor/ base.
476
+ *
477
+ * When sourceUrl is provided (e.g. for catalog entries fetched from theorems/),
478
+ * same-directory relative imports (./foo.js) resolve against the ENTRY's
479
+ * directory, not editorBase. This fixes TraceReport.js imports from theorems/.
480
+ *
481
+ * FAIL-LOUD: unresolvable specifiers throw at import time.
482
+ */
483
+
484
+ function _isModuleSource(code) {
485
+ return /^\s*(import|export)\s/m.test(code);
486
+ }
487
+
488
+ function _rewriteModuleSpecifiers(source, sdkUrl, editorBase, entryBase) {
489
+ return source.replace(/from\s+(['"])([^'"]+)\1/g, function (match, quote, specifier) {
490
+ if (/^(https?:|blob:|data:)/.test(specifier)) { return match; }
491
+ if (specifier === 'node:test') { return "from '" + new URL('./shims/node-test.js', editorBase).toString() + "'"; }
492
+ if (specifier === 'node:assert') { return "from '" + new URL('./shims/node-assert.js', editorBase).toString() + "'"; }
493
+ if (specifier === './EGPTTestSuite.js') { return "from '" + new URL('./shims/test-framework.js', editorBase).toString() + "'"; }
494
+ if (specifier.indexOf('/core/') !== -1 ||
495
+ specifier.indexOf('/stat/') !== -1 ||
496
+ specifier.indexOf('/spaces/log/') !== -1 ||
497
+ specifier.indexOf('/translators/') !== -1 ||
498
+ /\/index\.js$/.test(specifier)) {
499
+ return "from '" + sdkUrl + "'";
500
+ }
501
+ // Same-directory relative import (./foo.js): resolve against the entry's
502
+ // actual directory when available — NOT editorBase, which only works for
503
+ // entries inside editor/examples/ (not theorems/ or other siblings).
504
+ if (specifier.indexOf('./') === 0 && entryBase) {
505
+ return "from '" + new URL(specifier, entryBase).toString() + "'";
506
+ }
507
+ return "from '" + new URL(specifier, editorBase).toString() + "'";
508
+ });
509
+ }
510
+
511
+ function _runMathModule(code, sdk, sourceUrl) {
512
+ var sdkUrl = new URL(_mathSdkUrl, window.location.href).toString();
513
+ var editorBase = new URL(_mathSdkUrl.replace(/\/index\.js$/, '/editor/'), window.location.href).toString();
514
+ // Derive the entry's directory base for resolving same-dir imports (./foo.js).
515
+ // This lets theorems/ entries import siblings like ./TraceReport.js correctly
516
+ // instead of resolving against editorBase (which only covers editor/examples/).
517
+ var entryBase = sourceUrl ? new URL('./', new URL(sourceUrl, window.location.href)).toString() : null;
518
+ var src = _rewriteModuleSpecifiers(code, sdkUrl, editorBase, entryBase);
519
+ var blob = new Blob([src], { type: 'application/javascript' });
520
+ var url = URL.createObjectURL(blob);
521
+
522
+ // Tee console.log into the IDE console pane for the duration of the run —
523
+ // the suites log through console.log, exactly as the sdk worker captures it.
524
+ // Reentrancy guard: a console:write Bus handler (e.g. ConsolePane) may itself
525
+ // call console.log; without the guard that recurses tee→Bus→tee to overflow.
526
+ var origLog = console.log;
527
+ var _inTee = false;
528
+ console.log = function () {
529
+ if (_inTee) { origLog.apply(console, arguments); return; }
530
+ _inTee = true;
531
+ try {
532
+ var parts = Array.prototype.map.call(arguments, function (a) {
533
+ try { return (typeof a === 'object') ? JSON.stringify(a) : String(a); }
534
+ catch (e) { return String(a); }
535
+ });
536
+ _consoleObj.print(parts.join(' '));
537
+ } finally {
538
+ _inTee = false;
539
+ }
540
+ origLog.apply(console, arguments);
541
+ };
542
+
543
+ return import(/* @vite-ignore */ url).then(function (mod) {
544
+ if (typeof mod.run !== 'function') {
545
+ // Legacy suite: executed at import time (same convention the worker's
546
+ // injectLegacyRunExport covers). Import already ran its top level.
547
+ _consoleObj.print('(module executed on import — no run(sdk) export)');
548
+ return undefined;
549
+ }
550
+ return mod.run(sdk);
551
+ }).then(function (result) {
552
+ if (result !== undefined) {
553
+ _consoleObj.print(typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result));
554
+ }
555
+ }).finally(function () {
556
+ console.log = origLog;
557
+ URL.revokeObjectURL(url);
558
+ });
559
+ }
560
+
561
+ /* ── runCell — value-returning cell execution (notebook kernel) ──────────
562
+ *
563
+ * THE NOTEBOOK CONTRACT (UNIFIED_NOTEBOOK_DESIGN §3.2; O1 gate veto #1).
564
+ *
565
+ * Runtime.runCell({ lane, code, id, inputs, declaredOutputs })
566
+ * → Promise<{ outputs, ok, error }>
567
+ *
568
+ * This is a DIRECT ASYNC CALL — NOT routed through run()/_runRequestHandler,
569
+ * and it adds ZERO Bus listeners. It emits NO `cell:run`/`cell:result` events.
570
+ * That is THE constraint (binding veto #1) that keeps it clear of the
571
+ * The stacked-handler bug: the single `run:request` executor
572
+ * registered in init() is the only Bus execution path; runCell sits beside it
573
+ * as a plain callable the kernel invokes per cell.
574
+ *
575
+ * It REUSES the same machinery as the fire-and-forget lane (no duplication):
576
+ * - _ensureMathSdk() (resolved SDK + backend select)
577
+ * - the stashed-console tee (the 7cc5520 lesson — observers never re-emit)
578
+ *
579
+ * It does NOT end the math scope: a cell is not a run (FRAQTL issue #67). Its
580
+ * declared outputs are committed into the caller's binding store and must stay
581
+ * usable in later cells, so the scope boundary belongs to whoever owns that
582
+ * store — see `resetMathScope()`.
583
+ *
584
+ * Unlike the fire-and-forget lane, a cell body is wrapped as
585
+ * `async (caps, inputs) => { <body> }`
586
+ * so it can `await` and `return { … }` its declared outputs. The cell author
587
+ * writes plain statements + a `return { name: value, … }`; resolved `inputs`
588
+ * are injected as a named object and as `caps`.
589
+ *
590
+ * CELL-AS-MODULE (B2): when the cell body has a top-level
591
+ * `import`, it is instead compiled as a blob ES module whose relative `./x.js`
592
+ * specifiers resolve to the notebook's OWN user-code module cells via the IN-MEMORY
593
+ * blob closure (`spec.modules` = { name → source }, threaded by the kernel). The
594
+ * entry module exports `default async (caps, inputs) => {…}` so caps/inputs STAY
595
+ * INJECTED (they are NOT imports). The resolver NEVER fetches a relative sibling
596
+ * from a scoped HTTP mount (VETO-CLASS #1). Bare specifiers → host loader.
597
+ *
598
+ * Output contract (design §3.4 / §5.2, veto #4): the returned object's keys
599
+ * MUST be a SUPERSET of `declaredOutputs`. A declared output the cell did not
600
+ * return → FAIL-LOUD throw naming the cell + the missing key. We NEVER write
601
+ * `undefined` into a binding silently.
602
+ *
603
+ * lane === 'math' → caps = { math, console, display, inputs }
604
+ * lane === 'frqtl' → caps = { frqtl, render, math, console, display, inputs }
605
+ */
606
+
607
+ function _validateOutputs(id, returned, declaredOutputs) {
608
+ var outputs = {};
609
+ if (!declaredOutputs || !declaredOutputs.length) { return outputs; }
610
+ var bag = (returned && typeof returned === 'object') ? returned : {};
611
+ for (var i = 0; i < declaredOutputs.length; i++) {
612
+ var key = declaredOutputs[i];
613
+ if (!Object.prototype.hasOwnProperty.call(bag, key)) {
614
+ // FAIL-LOUD: a declared output was never returned. Naming cell+key.
615
+ throw new Error(
616
+ '[Runtime.runCell] cell "' + id + '" declares output "' + key +
617
+ '" but its returned object does not contain that key. ' +
618
+ 'A js cell must `return { ' + key + ': …, … }` for every declared out — ' +
619
+ 'never silently write undefined into a binding (FAIL-LOUD).'
620
+ );
621
+ }
622
+ outputs[key] = bag[key];
623
+ }
624
+ return outputs;
625
+ }
626
+
627
+ // Build the math/console/display + (frqtl) caps object for a cell execution.
628
+ // `display` is a structured-output sink that prints through the stashed console
629
+ // tee — the notebook pane reads console:write to populate the cell's output
630
+ // region. (Lightweight in A1; A2's pane may enrich the display target.)
631
+ function _cellCaps(lane, sdk, displayFn) {
632
+ var display = displayFn || function (value) {
633
+ try {
634
+ _consoleObj.print(typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value));
635
+ } catch (e) { _consoleObj.print(String(value)); }
636
+ };
637
+ if (lane === 'frqtl') {
638
+ return { frqtl: _frqtlObj, render: _renderObj, math: sdk, console: _consoleObj, display: display };
639
+ }
640
+ return { math: sdk, console: _consoleObj, display: display };
641
+ }
642
+
643
+ // Tee console.log/warn/error into the IDE console pane for the duration of a cell
644
+ // run. A notebook js cell's console.log/warn/error must all reach the cell's rich
645
+ // ConsolePane, each at its OWN level — log→info, warn→warn,
646
+ // error→error — so the ConsolePane's level filters/badges are meaningful.
647
+ //
648
+ // Reentrancy guard (SHARED across all three teed methods) mirrors _runMathModule:
649
+ // a console:write Bus handler (e.g. ConsolePane.log) may itself call a native
650
+ // console method via its module-stashed mirror; without the guard that recurses
651
+ // tee→Bus→handler→tee. The stash in console-output/code-editor binds the GENUINE
652
+ // natives at module load (before any tee), so their mirror never re-enters this
653
+ // tee — but a guard is still the belt-and-suspenders that makes a re-entry a
654
+ // straight passthrough. Returns a restore function the caller MUST call in finally.
655
+ function _installConsoleTee() {
656
+ var orig = { log: console.log, warn: console.warn, error: console.error };
657
+ var _inTee = false;
658
+ function _fmt(args) {
659
+ return Array.prototype.map.call(args, function (a) {
660
+ try { return (typeof a === 'object') ? JSON.stringify(a) : String(a); }
661
+ catch (e) { return String(a); }
662
+ }).join(' ');
663
+ }
664
+ function _tee(nativeName, sink) {
665
+ return function () {
666
+ if (_inTee) { orig[nativeName].apply(console, arguments); return; }
667
+ _inTee = true;
668
+ try { sink(_fmt(arguments)); } finally { _inTee = false; }
669
+ orig[nativeName].apply(console, arguments);
670
+ };
671
+ }
672
+ console.log = _tee('log', function (t) { _consoleObj.print(t); });
673
+ console.warn = _tee('warn', function (t) { _consoleObj.warn(t); });
674
+ console.error = _tee('error', function (t) { _consoleObj.error(t); });
675
+ return function restore() {
676
+ console.log = orig.log; console.warn = orig.warn; console.error = orig.error;
677
+ };
678
+ }
679
+
680
+ /* ── cell-as-module: the notebook-local in-memory blob resolver (B2) ──────────
681
+ *
682
+ * THE LOAD-BEARING CHANGE (build-gate C +
683
+ * VETO-CLASS constraint #1). When a cell body contains an `import`, it cannot run
684
+ * through `new Function` (module syntax is illegal there). It is compiled as a
685
+ * blob ES module instead. The PURE closure logic (path math, specifier rewrite,
686
+ * topological in-memory closure) lives host-neutral in
687
+ * `lib/shim/notebook/module-resolver.js` (no DOM/Runtime/engine deps — node-test-
688
+ * able, and it must NOT assume a pane exists). This function is only the BROWSER
689
+ * GLUE: it injects the blob-URL minter + dynamic-import. The resolver NEVER fetches
690
+ * a relative sibling from a scoped HTTP mount (VETO-CLASS #1 — the Phase-A 404 trap).
691
+ *
692
+ * `_runCellAsModule(code, id, modules, caps, inputs)` → Promise<returned>. The
693
+ * entry module exports `default async (caps, inputs) => {…}` so caps/inputs stay
694
+ * INJECTED (they are NOT imports); we invoke that default with the injected objects.
695
+ */
696
+ function _mintBlobUrl(source) {
697
+ var blob = new Blob([source], { type: 'application/javascript' });
698
+ return URL.createObjectURL(blob);
699
+ }
700
+
701
+ function _runCellAsModule(code, id, modules, caps, inputs) {
702
+ var closure = buildNotebookModuleClosure(modules, _mintBlobUrl);
703
+ // The entry (builder cell) lives at the NOTEBOOK ROOT — a single path segment,
704
+ // so its directory is root and `./setups/x.js` resolves to `setups/x.js` (the
705
+ // module-set key form). A multi-segment synthetic name would add a phantom dir.
706
+ var entryName = '__cell__-' + id + '.js';
707
+ var entrySrc = compileNotebookEntryModuleSource(code, entryName, closure.blobUrlByKey);
708
+ var entryUrl = _mintBlobUrl(entrySrc);
709
+ var allUrls = closure.created.concat([entryUrl]);
710
+ function _revokeAll() { for (var i = 0; i < allUrls.length; i++) { try { URL.revokeObjectURL(allUrls[i]); } catch (e) {} } }
711
+ return import(/* @vite-ignore */ entryUrl).then(function (mod) {
712
+ if (typeof mod.default !== 'function') {
713
+ throw new Error(
714
+ '[Runtime.runCell] cell-as-module: compiled entry for cell "' + id +
715
+ '" has no default export wrapper (internal). (FAIL-LOUD.)'
716
+ );
717
+ }
718
+ return mod.default(caps, inputs);
719
+ }).finally(_revokeAll);
720
+ }
721
+
722
+ /**
723
+ * @typedef {object} RunCellSpec
724
+ * @property {'math'|'frqtl'} [lane='math']
725
+ * Execution lane. `math` caps = `{ math, console, display, inputs }`;
726
+ * `frqtl` caps = `{ frqtl, render, math, console, display, inputs }`.
727
+ * @property {string} [code=''] — the cell body (plain statements + `return { … }`).
728
+ * @property {string} [id='_cell'] — stable cell identity (used in FAIL-LOUD messages).
729
+ * @property {Object<string,*>} [inputs={}] — resolved binding values keyed by name.
730
+ * @property {string[]} [declaredOutputs=[]]
731
+ * Names the cell MUST return. A missing key → FAIL-LOUD throw.
732
+ * @property {Object<string,string>} [modules={}]
733
+ * In-memory module set `{ name → source }` from `type="module"` cells. Used by
734
+ * the cell-as-module resolver when the cell body has a top-level `import`.
735
+ * @property {Function} [display] — structured-output sink (pane-supplied DOM writer).
736
+ * @property {boolean} [forceModulePath=false]
737
+ * Force the cell-as-module (blob) routing regardless of what the `hasImport`
738
+ * text-sniff would decide (C15).
739
+ * The synthesis authorities that KNOW their own template needs the module path
740
+ * (`setup-builder.js`'s synthesized builder, whose module reference is now a
741
+ * body-level DYNAMIC `import(...)` the line-start sniff cannot see) set this
742
+ * explicitly (`synth.usesModulePath`) rather than have the runtime re-derive
743
+ * the routing decision via regex on text it did not author. Widening the sniff
744
+ * itself (matching `import(` anywhere) was REJECTED — a coincidental `import(`
745
+ * substring inside an unrelated cell's string/comment would route it through
746
+ * the module-blob path, where `rewriteRelativeToSiblingBlobs` could spuriously
747
+ * FAIL-LOUD or, worse, silently rewrite text inside a string literal. Leave
748
+ * `false` for ordinary cells — `hasImport`'s existing sniff is unchanged and
749
+ * still correct for hand-authored module cells and the `view-builder.js`
750
+ * template (both use real static top-level imports).
751
+ */
752
+
753
+ /**
754
+ * @typedef {object} RunCellResult
755
+ * @property {Object<string,*>} outputs — resolved declared outputs (may be `{}`).
756
+ * @property {boolean} ok — `true` if the cell completed without error.
757
+ * @property {string|null} error — error message string if `ok` is `false`.
758
+ */
759
+
760
+ /**
761
+ * Value-returning cell execution (the notebook kernel path).
762
+ *
763
+ * A DIRECT ASYNC CALL — adds ZERO Bus listeners, emits NO `cell:*` events.
764
+ * Keeps `runCell` clear of the single-executor bug (`run:request` is the only Bus
765
+ * execution path; this sits beside it as a plain callable the kernel invokes).
766
+ *
767
+ * The cell body is wrapped as `async (caps, inputs) => { <body> }` so it can
768
+ * `await` and `return { … }` its declared outputs.
769
+ *
770
+ * Output contract: the returned object's keys MUST be a SUPERSET of `declaredOutputs`.
771
+ * A declared output the cell did not return → FAIL-LOUD throw naming the cell + key.
772
+ *
773
+ * @param {RunCellSpec} [spec]
774
+ * @returns {Promise<RunCellResult>}
775
+ *
776
+ * @example
777
+ * // A math lane cell that produces a histogram:
778
+ * const result = await Runtime.runCell({
779
+ * lane: 'math',
780
+ * code: 'const hist = math.EGPTStat.histogram([1,2,2,3]); return { hist };',
781
+ * id: 'c-stat-01',
782
+ * inputs: {},
783
+ * declaredOutputs: ['hist'],
784
+ * modules: {}
785
+ * });
786
+ * // result.ok === true; result.outputs.hist is the histogram object.
787
+ */
788
+ function runCell(spec) {
789
+ spec = spec || {};
790
+ var lane = spec.lane || 'math';
791
+ var code = spec.code || '';
792
+ var id = spec.id || '_cell';
793
+ var inputs = spec.inputs || {};
794
+ var declaredOutputs = spec.declaredOutputs || [];
795
+ var displayFn = spec.display || null;
796
+ // B2: the notebook's in-memory user-code module set { name → source } the
797
+ // kernel threads per run (fresh — never cached Runtime state). Drives the
798
+ // cell-as-module closure when the cell body imports a notebook-local module.
799
+ var modules = spec.modules || {};
800
+ // C15: an explicit signal from
801
+ // a synthesis authority that ALREADY KNOWS its template needs the module-blob
802
+ // path — bypasses the hasImport() text-sniff entirely for this call. See the
803
+ // RunCellSpec.forceModulePath doc above for why the sniff itself is NOT widened.
804
+ var forceModulePath = !!spec.forceModulePath;
805
+
806
+ // Resolve the SDK first (math lane needs it; frqtl lane gets it too for
807
+ // cross-lane parity — a frqtl cell may still call math). FAIL-LOUD if the
808
+ // math SDK is unavailable and required: _ensureMathSdk rejects, surfaced as
809
+ // { ok:false, error } below.
810
+ return _ensureMathSdk().then(function (sdk) {
811
+ var restoreTee = _installConsoleTee();
812
+ return Promise.resolve().then(function () {
813
+ var caps = _cellCaps(lane, sdk, displayFn);
814
+ // CELL-AS-MODULE (B2): a body with a top-level `import` cannot run via
815
+ // new Function (module syntax). Compile it as a blob ES module whose
816
+ // relative imports resolve to the notebook's OWN module cells via the
817
+ // IN-MEMORY blob closure (VETO-CLASS #1 — never an HTTP sibling fetch).
818
+ // caps/inputs stay INJECTED: the entry module exports
819
+ // `default async (caps, inputs) => {…}`, which _runCellAsModule invokes.
820
+ // forceModulePath (C15) short-circuits this to true WITHOUT sniffing —
821
+ // the authority declared it, never re-derived from text.
822
+ if (forceModulePath || _cellHasImport(code)) {
823
+ return _runCellAsModule(code, id, modules, caps, inputs);
824
+ }
825
+ // Fast path: no import → the existing new Function wrap (same trust
826
+ // model as the run() lane — the cell body IS user code, a Monaco buffer).
827
+ var bodySrc = 'return (async (caps, inputs) => {\n' + code + '\n})(caps, inputs);';
828
+ var factory = new Function('caps', 'inputs', bodySrc); // eslint-disable-line no-new-func
829
+ return factory(caps, inputs);
830
+ }).then(function (returned) {
831
+ var outputs = _validateOutputs(id, returned, declaredOutputs);
832
+ return { outputs: outputs, ok: true, error: null };
833
+ }).catch(function (e) {
834
+ var msg = (e && e.message ? e.message : String(e));
835
+ _consoleObj.error(msg);
836
+ return { outputs: {}, ok: false, error: msg };
837
+ }).finally(function () {
838
+ restoreTee();
839
+ // NO math-scope reset here. A cell is not a run: the values this cell
840
+ // just returned are about to be committed into the caller's binding
841
+ // store, and draining the arena would make every one of them a stale
842
+ // handle before the next cell could read it (FRAQTL issue #67). The
843
+ // scope boundary is the binding store's — see resetMathScope().
844
+ });
845
+ }).catch(function (e) {
846
+ // _ensureMathSdk rejection (math SDK unavailable). FAIL-LOUD as a
847
+ // structured error result — the kernel surfaces it on the cell.
848
+ var msg = 'Math SDK init failed: ' + (e && e.message ? e.message : String(e));
849
+ _consoleObj.error(msg);
850
+ return { outputs: {}, ok: false, error: msg };
851
+ });
852
+ }
853
+
854
+ function _ensureMathSdk(opts) {
855
+ opts = opts || {};
856
+ if (_mathSdk) { return Promise.resolve(_mathSdk); }
857
+ // A surface may pass its router-resolved mathSdkUrl explicitly. This closes a
858
+ // first-render race: a ?preset=codec deep link mounts CodecView, whose effect
859
+ // calls ensureMath BEFORE the parent FraqtlIde effect calls Runtime.init
860
+ // (child effects fire before parent effects in React). The value is the SAME
861
+ // single authority (FraqtlIde's engineRouter profile) threaded as a prop —
862
+ // never a second hardcoded literal. init()'s value wins once set.
863
+ if (!_mathSdkUrl && opts.mathSdkUrl) { _mathSdkUrl = opts.mathSdkUrl; }
864
+ // M4 fix: thread the wasm bridge URLs + backend through the SAME early-call
865
+ // seam. Without these, a deep-linked view's ensureMath would build the SDK
866
+ // with no wasm URLs (wasm-math never registered) and cache it — ?mathBackend=wasm
867
+ // would then fail even though the router profile is correct. Values come from
868
+ // the same single authority (the view's engineRouter props); init()'s win once set.
869
+ if (!_mathWasmUrl && opts.mathWasmUrl) { _mathWasmUrl = opts.mathWasmUrl; }
870
+ if (!_mathWasmModuleUrl && opts.mathWasmModuleUrl) { _mathWasmModuleUrl = opts.mathWasmModuleUrl; }
871
+ if (!_mathSdk && opts.mathBackend) {
872
+ var _b = opts.mathBackend;
873
+ if (_b === 'wasm') { _b = 'wasm-math'; }
874
+ _mathBackend = _b;
875
+ }
876
+ if (_mathSdkLoad) { return _mathSdkLoad; }
877
+ if (!_mathSdkUrl && (typeof window === 'undefined' || !window.createEgptMathSdk)) {
878
+ // V2/AM-C2-3 (R3 binding): FAIL-LOUD — a null SDK silently produces wrong results.
879
+ // The URL must come from the engineRouter profile (mathSdkUrl in DEV_CORE_PROFILE /
880
+ // DIST_PROFILE) — no hardcoded literal fallback exists. Code running with
881
+ // source:'math' against a null SDK is not a degraded experience — it is a broken run.
882
+ // Reject so the error surfaces in the console/run:result.
883
+ return Promise.reject(new Error(
884
+ '[Runtime] Math SDK unavailable: no mathSdkUrl resolved from engineRouter profile ' +
885
+ 'or window._egptMathSdkUrl, and no window.createEgptMathSdk. ' +
886
+ 'Ensure Runtime.init() is called with a router that defines __mathSdkUrl.'
887
+ ));
888
+ }
889
+ var built = (typeof window !== 'undefined' && window.createEgptMathSdk)
890
+ ? window.createEgptMathSdk({
891
+ wasmUrl: _mathWasmUrl || undefined,
892
+ wasmModuleUrl: _mathWasmModuleUrl || undefined
893
+ })
894
+ : import(_mathSdkUrl).then(function (mod) {
895
+ var factory = mod.createEgptMathSdk;
896
+ if (typeof factory !== 'function') { throw new Error('createEgptMathSdk not exported from ' + _mathSdkUrl); }
897
+ return factory({
898
+ wasmUrl: _mathWasmUrl || undefined,
899
+ wasmModuleUrl: _mathWasmModuleUrl || undefined
900
+ });
901
+ });
902
+ _mathSdkLoad = built.then(function (sdk) {
903
+ _registerSdkProvider(sdk);
904
+ _applyBackendSelection(sdk);
905
+ _mathSdk = sdk;
906
+ return sdk;
907
+ });
908
+ return _mathSdkLoad;
909
+ }
910
+
911
+ /* A selection that failed AFTER the sdk was cached (a later init requesting a
912
+ * backend this realm cannot select) poisons the realm the same way a failed load
913
+ * does: drop the cached sdk and pin the rejection, so no later caller resolves on
914
+ * the previously selected backend. */
915
+ function _failMathRealm(err) {
916
+ _mathSdk = null;
917
+ _mathSdkLoad = Promise.reject(err);
918
+ _mathSdkLoad.catch(function () {}); // observed by every later caller, not unhandled here
919
+ }
920
+
921
+ /* ── provider registration (static/dist twin-registry fix) ──────────────────
922
+ * Register the resolved SDK's OWN math-backend provider into the Runtime's
923
+ * MathBackendRegistry BEFORE selecting it.
924
+ *
925
+ * WHY (root cause of the static/public wasm-math failure): the WASM-only PUBLISH
926
+ * entry (sdk/egpt-math-sdk `dist-entry.js`, served at the site's
927
+ * `./pub/<build>/egpt-math-sdk/index.js` in the static build) registers its wasm provider ONLY into its private facade
928
+ * slot (`wasm-facade.js` `_setActiveWasmProvider`). To keep the JS math core OUT
929
+ * of the published bundle (the WASM-only publish rule) it deliberately
930
+ * NEVER imports `lib/egpt/js/model/backend/*`, so it CANNOT register into the registry
931
+ * this Runtime uses — and in a built app its bundled registry copy would be a
932
+ * DIFFERENT module instance across the dynamic-import boundary anyway. Result:
933
+ * `_applyBackendSelection` → `MathBackendRegistry.select('wasm-math')` threw
934
+ * "'wasm-math' is not registered", rejecting the eager load so the notebook pane
935
+ * never mounted. (The DEV source entry `sdk-entry.js` registers into the SAME
936
+ * shared source-instance itself, which is why dev never hit this — the dist IDE
937
+ * shell was dead until the static site exercised this path.)
938
+ *
939
+ * FIX: the sdk EXPOSES its provider (`sdk.mathBackendProvider`, a plain
940
+ * contract-conformant object); the Runtime — the SOLE owner of
941
+ * MathBackendRegistry above the shim — registers it here (owner-exposes-contract,
942
+ * consumer-registers). This makes select() succeed, the badge/header derive
943
+ * 'wasm-math' truthfully from the registry (truth-in-labeling, not a hardcoded
944
+ * label), and `resetScope()` reach the wasm arena.
945
+ *
946
+ * The DEV entry (`sdk-entry.js`) exposes its js-reference provider the same way.
947
+ * It shares this registry instance (the dev server), so the SAME object already
948
+ * self-registered through the reference barrel, and `register()` is a no-op for it.
949
+ *
950
+ * `register()` owns provider identity: the same object again is a no-op, a
951
+ * DIFFERENT object under a taken id throws (two provider bundles in one realm), and
952
+ * a malformed provider throws. Each surfaces as the ensureMath rejection.
953
+ */
954
+ function _registerSdkProvider(sdk) {
955
+ var provider = sdk && sdk.mathBackendProvider;
956
+ if (!provider || typeof provider !== 'object' || typeof provider.id !== 'string') { return; }
957
+ MathBackendRegistry.register(provider);
958
+ }
959
+
960
+ /* ── backend selection ──────────────────────────────────────────────── */
961
+ // M4: called once after the SDK resolves (the sdk's provider is registered by
962
+ // that point). FAIL-LOUD: if the requested backend isn't registered,
963
+ // MathBackendRegistry.select() throws MathBackendNotAvailableError. js-reference is
964
+ // a provider like any other — it exists only where a source graph registered it
965
+ // (the dev sdk entry), so requesting it from a distribution throws here.
966
+ function _applyBackendSelection(sdk) {
967
+ var id = _mathBackend;
968
+ if (!id) {
969
+ // No backend requested: the registry's first registrant stays active.
970
+ return;
971
+ }
972
+ try {
973
+ MathBackendRegistry.select(id);
974
+ console.log('[Runtime] Math backend selected: ' + id);
975
+ } catch (err) {
976
+ // FAIL-LOUD with a PRECISE diagnosis. A bridge that was named but did not load
977
+ // already rejected createEgptMathSdk (loadEgptMathWasm throws), so the one way
978
+ // wasm-math is missing here is that no bridge URL reached the sdk.
979
+ var detail = '';
980
+ if (id === 'wasm-math' && !_mathWasmModuleUrl) {
981
+ detail = ' No wasmModuleUrl was configured — the engineRouter profile ' +
982
+ '__mathWasmModuleUrl is empty or was not threaded into Runtime.';
983
+ }
984
+ var msg = '[Runtime] Math backend selection FAILED for id="' + id + '": ' +
985
+ (err && err.message ? err.message : String(err)) + '.' + detail +
986
+ ' Never falls back silently to js-reference (FAIL-LOUD).';
987
+ _consoleObj.error(msg);
988
+ // Re-throw so the calling .then chain rejects and the error surfaces. The
989
+ // registry's own error rides as `cause` (e.g. MathBackendNotAvailableError).
990
+ throw new Error(msg, { cause: err });
991
+ }
992
+ }
993
+
994
+ /* ── export ─────────────────────────────────────────────────────────── */
995
+
996
+ /**
997
+ * The singleton Runtime object.
998
+ *
999
+ * @type {object}
1000
+ * @property {typeof init} init — Initialize the runtime (once per mount).
1001
+ * @property {typeof run} run — Fire-and-forget lane execution (code-editor path).
1002
+ * @property {typeof runCell} runCell — Value-returning cell execution (notebook kernel path).
1003
+ * @property {typeof resetMathScope} resetMathScope — End the math value scope and begin a fresh
1004
+ * one. Called by whoever owns a run unit (the `run()` lane owns its own; `NotebookKernel` owns
1005
+ * the notebook's). See the method doc for the scope rule.
1006
+ * @property {Function} ensureMath — `() => Promise<sdk>`. Lazy-loads the math SDK.
1007
+ * CodecView and any surface needing the codec runtime calls this; the result is cached.
1008
+ * @property {object} frqtl — The `caps.frqtl` sugar object (see module doc).
1009
+ * @property {object|null} math — The cached math SDK object, or `null` if not yet loaded.
1010
+ * @property {object} console — The console sink object (`log/print/warn/error/clear`).
1011
+ * @property {object} render — The render surface object (`frame/start/stop`).
1012
+ */
1013
+ export const Runtime = {
1014
+ init: init,
1015
+ run: run,
1016
+ runCell: runCell,
1017
+ resetMathScope: resetMathScope,
1018
+ ensureMath: _ensureMathSdk,
1019
+ get frqtl() { return _frqtlObj; },
1020
+ get math() { return _mathSdk; },
1021
+ get console() { return _consoleObj; },
1022
+ get render() { return _renderObj; }
1023
+ };