@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +87 -0
- package/LICENSE.md +664 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2499 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +84 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +99 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +258 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2470 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2920 -0
- package/dist/frqtl/objects/ObjectFactory.js +296 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +710 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +411 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +351 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +309 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +820 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +468 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +250 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +277 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1258 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
- package/dist/frqtl/objects/v1-replacements.js +29 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +34 -0
- package/dist/frqtl/parts/CollisionTypes.js +30 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +489 -0
- package/dist/frqtl/parts/LargeObject.js +405 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +717 -0
- package/dist/frqtl/parts/OvenBox.js +611 -0
- package/dist/frqtl/parts/PointSource.js +762 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +243 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +211 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +690 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +325 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +671 -0
- package/dist/shim/notebook/knob.js +150 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +507 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +213 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +804 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- package/package.json +63 -4
|
@@ -0,0 +1,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
|
+
};
|