@descix/frqtl-node 0.6.0-p4.1 → 0.6.0
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 +2 -2
- package/README.md +5 -3
- package/dist/dist-manifest.json +58 -54
- package/dist/fat/pkg-node/package.json +2 -10
- package/dist/frqtl/compat/providers/wasm-engine.js +3 -4
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +1 -1
- package/dist/frqtl/objects/FrqtlApp.js +5 -5
- package/dist/frqtl/objects/RecipeContext.js +106 -1
- package/dist/frqtl/objects/constants/WallBehaviors.js +1 -1
- package/dist/frqtl/objects/instruments/capture.js +1 -24
- package/dist/frqtl/objects/instruments/hosted-eyes.js +17 -1
- package/dist/frqtl/objects/objects/makeControlsForObject.js +5 -5
- package/dist/frqtl/objects/record/Record.js +25 -5
- package/dist/frqtl/objects/schema/engine-scopes.js +2 -2
- package/dist/frqtl/objects/schema/scopes.js +2 -2
- package/dist/frqtl/objects/setup/_worldInit.js +1 -2
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +1 -2
- package/dist/frqtl/parts/CameraControls.js +8 -0
- package/dist/frqtl/parts/DetectorWall.js +1 -1
- package/dist/frqtl/parts/GraphingDetectorWall.js +7 -7
- package/dist/frqtl/parts/LargeObject.js +2 -2
- package/dist/frqtl/parts/LiveChart.js +2 -2
- package/dist/frqtl/parts/LogicGate.js +3 -3
- package/dist/frqtl/parts/OvenBox.js +1 -1
- package/dist/frqtl/parts/PointSource.js +1 -1
- package/dist/frqtl/parts/SlitLightSource.js +1 -1
- package/dist/frqtl/parts/SlitScreen.js +1 -1
- package/dist/frqtl/parts/SphereEmitter.js +12 -12
- package/dist/frqtl/parts/Trace.js +1 -1
- package/dist/frqtl/parts/Tunnel.js +11 -11
- package/dist/frqtl/parts/Wall.js +20 -10
- package/dist/frqtl/scenes/circuit-sat.js +1 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +32 -34
- package/dist/frqtl/scenes/dse-two-emitters.js +33 -34
- package/dist/frqtl/scenes/mirror-route.js +96 -79
- package/dist/shim/bundle/index.js +6 -0
- package/dist/shim/controls/index.js +1 -1
- package/dist/shim/engine/artifact-shell.js +1 -1
- package/dist/shim/engine/router.js +3 -3
- package/dist/shim/host/host-view.js +228 -0
- package/dist/shim/host/index.js +18 -0
- package/dist/shim/index.js +2 -4
- package/dist/shim/notebook/kernel.js +1 -1
- package/dist/shim/notebook/setup-builder.js +2 -2
- package/dist/shim/package.json +1 -0
- package/dist/shim/page-params/index.js +2 -30
- package/dist/shim/project/fork.js +293 -0
- package/dist/shim/project/identity.js +3 -1
- package/dist/shim/project/link.js +43 -3
- package/dist/shim/project/parts-writer.js +62 -5
- package/dist/shim/project/read-text.js +43 -0
- package/dist/shim/project/specifiers.js +104 -15
- package/dist/shim/registry/exhibits.js +15 -34
- package/dist/shim/registry/index.js +2 -2
- package/dist/shim/registry/scene-roster.js +35 -0
- package/dist/shim/registry/scene-source.js +6 -36
- package/dist/shim/registry/walkthrough.js +2 -3
- package/dist/shim/render-state/apply.js +66 -31
- package/dist/shim/render-state/index.js +6 -2
- package/dist/shim/runtime/index.js +4 -4
- package/llms.txt +4 -3
- package/package.json +19 -1
- package/dist/shim/registry/experiments.js +0 -215
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* - absent — the PACKAGE's: the dist layout anchored at the dist root this module ships in
|
|
27
27
|
* (`PACKAGE_ROOT_FROM_HERE`, dist/shim/registry/ → dist/). This is the `@descix/frqtl-web/registry`
|
|
28
28
|
* door's reader; in the source tree it names a file that is not there and rejects.
|
|
29
|
-
* The read is a
|
|
30
|
-
* node's fs
|
|
29
|
+
* The read is lib/shim/project/read-text.js readText (a capability split on the resolved URL: a `file:` URL
|
|
30
|
+
* through node's fs, anything else fetched).
|
|
31
31
|
*
|
|
32
32
|
* FAIL-LOUD: an unknown id, a row without a scenes/ path, an undeclared mode, a non-200 response, an
|
|
33
33
|
* unreadable file or an empty body REJECTS naming the scene and the URL. A blank scene is never a result.
|
|
@@ -41,6 +41,7 @@ import { WALKTHROUGH, walkthroughById, effectiveRenderState } from './walkthroug
|
|
|
41
41
|
import { engineRouter } from '../engine/router.js';
|
|
42
42
|
import { MANIFEST_FILE, manifestFor, serializeManifest } from '../project/manifest.js';
|
|
43
43
|
import { SKETCH_ENTRY } from '../project/link.js';
|
|
44
|
+
import { readText } from '../project/read-text.js';
|
|
44
45
|
|
|
45
46
|
/** The one path prefix a scene file lives under (the router's `scenes/` profile key). */
|
|
46
47
|
export var SCENES_PREFIX = 'scenes/';
|
|
@@ -96,36 +97,6 @@ function _packageRouter() {
|
|
|
96
97
|
return engineRouter({ mode: 'cdn', base: root });
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
function _readFileUrl(url, id) {
|
|
100
|
-
var proc = globalThis.process;
|
|
101
|
-
var getBuiltin = proc && proc.getBuiltinModule;
|
|
102
|
-
if (typeof getBuiltin !== 'function') {
|
|
103
|
-
throw new Error('[scene-source] scene "' + id + '" resolves to ' + url + ', a file: URL; reading ' +
|
|
104
|
-
'one needs node\'s process.getBuiltinModule (Node 20.16+ / 22.3+). In a page, serve the ' +
|
|
105
|
-
'package over http. (FAIL-LOUD.)');
|
|
106
|
-
}
|
|
107
|
-
var fs = getBuiltin('node:fs');
|
|
108
|
-
var fileURLToPath = getBuiltin('node:url').fileURLToPath;
|
|
109
|
-
return fs.promises.readFile(fileURLToPath(url), 'utf8').catch(function (e) {
|
|
110
|
-
throw new Error('[scene-source] scene "' + id + '" — reading ' + url + ' failed: ' +
|
|
111
|
-
(e && e.message) + '. (FAIL-LOUD.)');
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function _fetchText(url, id, fetchImpl) {
|
|
116
|
-
var f = fetchImpl || (typeof fetch === 'function' ? fetch : null);
|
|
117
|
-
if (!f) {
|
|
118
|
-
throw new Error('[scene-source] no fetch available to load ' + url + '.');
|
|
119
|
-
}
|
|
120
|
-
return Promise.resolve(f(url)).then(function (res) {
|
|
121
|
-
if (!res || !res.ok) {
|
|
122
|
-
throw new Error('[scene-source] scene "' + id + '" — GET ' + url + ' answered ' +
|
|
123
|
-
(res ? res.status : 'nothing') + '. (FAIL-LOUD.)');
|
|
124
|
-
}
|
|
125
|
-
return res.text();
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
100
|
/**
|
|
130
101
|
* A registry scene as the project folder a host boots, with the host's boot bag beside it.
|
|
131
102
|
* @param {string|{id:string, source:string}} scene — a rung id, or a registry row.
|
|
@@ -153,10 +124,9 @@ export function projectOf(scene, opts) {
|
|
|
153
124
|
'(lib/shim/engine/router.js) to resolve ' + rel + '. (FAIL-LOUD.)');
|
|
154
125
|
}
|
|
155
126
|
var url = router.resolve(rel);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return Promise.resolve(text).then(function (t) {
|
|
127
|
+
return readText(url, { fetch: opts.fetch }).catch(function (e) {
|
|
128
|
+
throw new Error('[scene-source] scene "' + row.id + '" — ' + ((e && e.message) || e) + '. (FAIL-LOUD.)');
|
|
129
|
+
}).then(function (t) {
|
|
160
130
|
if (typeof t !== 'string' || !t.trim()) {
|
|
161
131
|
throw new Error('[scene-source] scene "' + row.id + '" — ' + url +
|
|
162
132
|
' has an empty body. (FAIL-LOUD.)');
|
|
@@ -774,8 +774,7 @@ export function walkthroughById(id) {
|
|
|
774
774
|
});
|
|
775
775
|
|
|
776
776
|
// ── is3D normalization (E-W0a; mode-aware S3/W2) ─────────────────────────────
|
|
777
|
-
// STUDIO
|
|
778
|
-
// from the gallery registry (experiments.js `is3D`) or a walkthrough rung. A rung's
|
|
777
|
+
// STUDIO reads ONE `is3D` field for a selection. A rung's
|
|
779
778
|
// 3D-ness is ALREADY declared by its (possibly mode-merged) `renderState.renderer` —
|
|
780
779
|
// three IS the 3D renderer, the ONE display fact the load path threads — so `is3D` here is
|
|
781
780
|
// DERIVED through `effectiveRenderState` for the rung's DEFAULT mode, never a
|
|
@@ -787,7 +786,7 @@ WALKTHROUGH.forEach(function (rung) {
|
|
|
787
786
|
|
|
788
787
|
/**
|
|
789
788
|
* walkthroughIs3D(id, modeId) — the ONE mount-time 3D predicate for a walkthrough
|
|
790
|
-
* rung
|
|
789
|
+
* rung. Derived through
|
|
791
790
|
* `effectiveRenderState` (the single derivation point) for the given mode, or the
|
|
792
791
|
* rung's default mode when `modeId` is omitted. Returns false for an unknown id;
|
|
793
792
|
* THROWS (via effectiveRenderState) for a known id with an undeclared mode.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* that applies a renderState bag to a booted runner.
|
|
6
6
|
*
|
|
7
7
|
* A renderState bag (renderer/seed load-time selections + render rows + camera + backdrop
|
|
8
|
-
* visibility
|
|
8
|
+
* visibility) is replayed by every host: the GYM rung frame, STUDIO, the bundle's
|
|
9
9
|
* `bootRecipe`, the hosted eyes. They all come through `RenderState.apply` — directly (the bundle,
|
|
10
10
|
* which is its own page) or through the blank page's door `FrqtlPage.applyRenderState`
|
|
11
11
|
* (lib/frqtl/experiments/empty/index.html). A host never restates the procedure.
|
|
@@ -37,8 +37,13 @@
|
|
|
37
37
|
* census LO marked `backdrop === true`, or null. STUDIO's
|
|
38
38
|
* save captures its visibility through it, so capture and
|
|
39
39
|
* replay read one fact.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* RENDER_STATE_KEYS — THE key table of a bag, in the order the normalized bag is written;
|
|
41
|
+
* each row normalizes its own value.
|
|
42
|
+
* RENDER_STATE_VERSION — 1, the one version a bag may declare.
|
|
43
|
+
* normalizeSavedRenderState(raw) — a bag (or null: no boot bag) → the full bag off RENDER_STATE_KEYS.
|
|
44
|
+
* THROWS on a key outside the table (naming it and the table), on
|
|
45
|
+
* another version, on page controls (author code now), and on a
|
|
46
|
+
* value that is neither a plain object nor null.
|
|
42
47
|
* pickCameraReplay(saved) — pure decision: named preset over raw orbit.
|
|
43
48
|
* partitionRenderRows(rows, declared, known) — apply / retain / throw, before anything is written.
|
|
44
49
|
* renderRowsFromBag(bag) — a { key: value } render-option bag as ordered rows.
|
|
@@ -70,13 +75,42 @@ function pickCameraReplay(saved) {
|
|
|
70
75
|
return null;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
/** The one version a renderState bag may declare (an absent version is this one). */
|
|
79
|
+
var RENDER_STATE_VERSION = 1;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* THE key table of a renderState bag, in the order the normalized bag is written. Each row's `normalize(value)`
|
|
83
|
+
* takes the raw value (undefined when the key is absent) to the stored form; an absent or malformed value is the
|
|
84
|
+
* neutral one (null / []), which `apply` writes nothing for.
|
|
85
|
+
* @type {ReadonlyArray<{key: string, normalize: function(*): *}>}
|
|
86
|
+
*/
|
|
87
|
+
var RENDER_STATE_KEYS = Object.freeze([
|
|
88
|
+
{ key: 'version', normalize: function (v) {
|
|
89
|
+
if (v === undefined || v === RENDER_STATE_VERSION) { return RENDER_STATE_VERSION; }
|
|
90
|
+
throw new Error('[render-state] renderState.version ' + JSON.stringify(v) + ' — this owner reads version ' +
|
|
91
|
+
RENDER_STATE_VERSION + ' only. (FAIL-LOUD.)');
|
|
92
|
+
} },
|
|
93
|
+
{ key: 'renderer', normalize: function (v) { return (typeof v === 'string' && v.length > 0) ? v : null; } },
|
|
94
|
+
{ key: 'engine', normalize: function (v) { return (typeof v === 'string' && v.length > 0) ? v : null; } },
|
|
95
|
+
{ key: 'seed', normalize: function (v) { return (v !== undefined && v !== null && v !== '') ? String(v) : null; } },
|
|
96
|
+
{ key: 'backdropVisible', normalize: function (v) { return (typeof v === 'boolean') ? v : null; } },
|
|
97
|
+
{ key: 'camera', normalize: function (v) {
|
|
98
|
+
if (!_isPlainObject(v)) { return null; }
|
|
99
|
+
return { orbit: _isPlainObject(v.orbit) ? v.orbit : null, preset: (typeof v.preset === 'string') ? v.preset : null };
|
|
100
|
+
} },
|
|
101
|
+
{ key: 'render', normalize: function (v) {
|
|
102
|
+
return Array.isArray(v) ? v.filter(function (row) {
|
|
103
|
+
return _isPlainObject(row) && typeof row.key === 'string' && row.key.length > 0;
|
|
104
|
+
}) : [];
|
|
105
|
+
} }
|
|
106
|
+
].map(Object.freeze));
|
|
107
|
+
|
|
73
108
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* writes nothing for.
|
|
109
|
+
* The full renderState bag of `raw`, built off RENDER_STATE_KEYS: a bag (a host's capture, a registry rung's, a
|
|
110
|
+
* stored folder row's, an export's boot bag) or `null` (no boot bag — the one-file Import) → every key, in table
|
|
111
|
+
* order. An owner refuses what is not in its contract: a silent drop would be a silent fallback.
|
|
78
112
|
*
|
|
79
|
-
* @param {
|
|
113
|
+
* @param {object|null} raw
|
|
80
114
|
* @returns {{
|
|
81
115
|
* version: number,
|
|
82
116
|
* renderer: string|null,
|
|
@@ -86,34 +120,33 @@ function pickCameraReplay(saved) {
|
|
|
86
120
|
* camera: {orbit:object|null, preset:string|null}|null,
|
|
87
121
|
* render: {key:string, value:*}[]
|
|
88
122
|
* }}
|
|
89
|
-
* @throws {Error} a
|
|
90
|
-
*
|
|
123
|
+
* @throws {Error} a key outside RENDER_STATE_KEYS (named, beside the table); a version other than
|
|
124
|
+
* RENDER_STATE_VERSION; a bag carrying `pageControls` — page controls are declared in the sketch's own code, so a
|
|
125
|
+
* saved bag never carries them (migrate the project: move the controls into its sketch); and a value that is
|
|
126
|
+
* neither a plain object nor null (`undefined` included: a caller with no bag passes null).
|
|
91
127
|
*/
|
|
92
128
|
function normalizeSavedRenderState(raw) {
|
|
93
|
-
|
|
94
|
-
|
|
129
|
+
if (raw !== null && !_isPlainObject(raw)) {
|
|
130
|
+
throw new Error('[render-state] a renderState is a bag (a plain object) or null for none — got ' +
|
|
131
|
+
(raw === undefined ? 'undefined' : Array.isArray(raw) ? 'an array' : typeof raw) + '. (FAIL-LOUD.)');
|
|
132
|
+
}
|
|
133
|
+
var r = raw || {};
|
|
134
|
+
if (Object.prototype.hasOwnProperty.call(r, 'pageControls')) {
|
|
135
|
+
var rows = Array.isArray(r.pageControls) ? r.pageControls : [];
|
|
95
136
|
throw new Error('[render-state] this saved project carries page controls (renderState.pageControls: ' +
|
|
96
|
-
|
|
137
|
+
(rows.map(function (row) { return _isPlainObject(row) ? String(row.id) : '?'; }).join(', ') || 'an empty list') +
|
|
97
138
|
') — page controls are declared in the sketch\'s own code; move them into the sketch and save again. (FAIL-LOUD.)');
|
|
98
139
|
}
|
|
99
|
-
var
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
engine: (typeof r.engine === 'string' && r.engine.length > 0) ? r.engine : null,
|
|
110
|
-
seed: (r.seed !== undefined && r.seed !== null && r.seed !== '') ? String(r.seed) : null,
|
|
111
|
-
backdropVisible: (typeof r.backdropVisible === 'boolean') ? r.backdropVisible : null,
|
|
112
|
-
camera: camera,
|
|
113
|
-
render: Array.isArray(r.render) ? r.render.filter(function (row) {
|
|
114
|
-
return _isPlainObject(row) && typeof row.key === 'string' && row.key.length > 0;
|
|
115
|
-
}) : []
|
|
116
|
-
};
|
|
140
|
+
var known = RENDER_STATE_KEYS.map(function (row) { return row.key; });
|
|
141
|
+
Object.keys(r).forEach(function (k) {
|
|
142
|
+
if (known.indexOf(k) === -1) {
|
|
143
|
+
throw new Error('[render-state] renderState has no key "' + k + '" — its keys (RENDER_STATE_KEYS): ' +
|
|
144
|
+
known.join(', ') + '. (FAIL-LOUD.)');
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
var out = {};
|
|
148
|
+
RENDER_STATE_KEYS.forEach(function (row) { out[row.key] = row.normalize(r[row.key]); });
|
|
149
|
+
return out;
|
|
117
150
|
}
|
|
118
151
|
|
|
119
152
|
/**
|
|
@@ -252,6 +285,8 @@ function apply(runner, bag, env) {
|
|
|
252
285
|
export {
|
|
253
286
|
apply,
|
|
254
287
|
backdropOf,
|
|
288
|
+
RENDER_STATE_KEYS,
|
|
289
|
+
RENDER_STATE_VERSION,
|
|
255
290
|
normalizeSavedRenderState,
|
|
256
291
|
pickCameraReplay,
|
|
257
292
|
partitionRenderRows,
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
12
12
|
*
|
|
13
13
|
* RenderState — the owner object: RenderState.apply(runner, bag, env).
|
|
14
|
-
*
|
|
14
|
+
* RENDER_STATE_KEYS, RENDER_STATE_VERSION — the bag's key table and its one version.
|
|
15
|
+
* normalizeSavedRenderState(raw) — a bag or null → the full bag; THROWS on a key outside the table, another
|
|
16
|
+
* version, page controls, or a non-bag.
|
|
15
17
|
* pickCameraReplay(saved) — pure decision: named preset over raw orbit.
|
|
16
18
|
* partitionRenderRows(rows, declared, known)
|
|
17
19
|
* — resolve render rows against the TARGET renderer.
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
|
|
30
32
|
// The one owner of the renderState contract and its apply procedure.
|
|
31
33
|
import * as RenderState from './apply.js';
|
|
32
|
-
import { normalizeSavedRenderState, pickCameraReplay, partitionRenderRows, renderRowsFromBag } from './apply.js';
|
|
34
|
+
import { RENDER_STATE_KEYS, RENDER_STATE_VERSION, normalizeSavedRenderState, pickCameraReplay, partitionRenderRows, renderRowsFromBag } from './apply.js';
|
|
33
35
|
|
|
34
36
|
// ── applySavedUrlOverrides ───────────────────────────────────────────────────
|
|
35
37
|
|
|
@@ -65,6 +67,8 @@ function applySavedUrlOverrides(src, overrides) {
|
|
|
65
67
|
// ── Export ────────────────────────────────────────────────────────────────────
|
|
66
68
|
|
|
67
69
|
export {
|
|
70
|
+
RENDER_STATE_KEYS,
|
|
71
|
+
RENDER_STATE_VERSION,
|
|
68
72
|
RenderState,
|
|
69
73
|
normalizeSavedRenderState,
|
|
70
74
|
pickCameraReplay,
|
|
@@ -133,7 +133,7 @@ var _renderObj = {
|
|
|
133
133
|
* naming the global and the gate, rather than yielding undefined or null. A soft
|
|
134
134
|
* return is exactly what turns a boot-ordering defect into an opaque
|
|
135
135
|
* property-access TypeError one frame later ("Cannot read properties of null
|
|
136
|
-
* (reading 'build')"
|
|
136
|
+
* (reading 'build')"); the throw names what was unavailable instead.
|
|
137
137
|
* That covers the constructor members (`FrqtlApp`, `RecipeContext`) and every getter generated from
|
|
138
138
|
* the member table (CAPS_FRQTL_MEMBERS): there is no soft residual.
|
|
139
139
|
*
|
|
@@ -441,7 +441,7 @@ function _runMath(code, fileId, sourceUrl) {
|
|
|
441
441
|
* handle minted before the call is dead — a later use throws
|
|
442
442
|
* `arena:stale-handle`. For `js-reference` it is a no-op.
|
|
443
443
|
*
|
|
444
|
-
* THE SCOPE RULE
|
|
444
|
+
* THE SCOPE RULE: a math scope is one RUN, and the caller
|
|
445
445
|
* that knows what a run is owns the boundary. Two run units exist, each stated
|
|
446
446
|
* once: the `run()` lane's unit is one code buffer (it calls this in its own
|
|
447
447
|
* `finally`); a notebook's unit is one notebook run, so `NotebookKernel` calls
|
|
@@ -576,7 +576,7 @@ function _runMathModule(code, sdk, sourceUrl) {
|
|
|
576
576
|
* - _ensureMathSdk() (resolved SDK + backend select)
|
|
577
577
|
* - the stashed-console tee (the 7cc5520 lesson — observers never re-emit)
|
|
578
578
|
*
|
|
579
|
-
* It does NOT end the math scope: a cell is not a run
|
|
579
|
+
* It does NOT end the math scope: a cell is not a run. Its
|
|
580
580
|
* declared outputs are committed into the caller's binding store and must stay
|
|
581
581
|
* usable in later cells, so the scope boundary belongs to whoever owns that
|
|
582
582
|
* store — see `resetMathScope()`.
|
|
@@ -839,7 +839,7 @@ function runCell(spec) {
|
|
|
839
839
|
// NO math-scope reset here. A cell is not a run: the values this cell
|
|
840
840
|
// just returned are about to be committed into the caller's binding
|
|
841
841
|
// store, and draining the arena would make every one of them a stale
|
|
842
|
-
// handle before the next cell could read it
|
|
842
|
+
// handle before the next cell could read it. The
|
|
843
843
|
// scope boundary is the binding store's — see resetMathScope().
|
|
844
844
|
});
|
|
845
845
|
}).catch(function (e) {
|
package/llms.txt
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# @descix/frqtl-node
|
|
2
2
|
|
|
3
3
|
> The FRQTL physics engine (compiled Rust → WASM) for node: the same modules as @descix/frqtl-web, plus
|
|
4
|
-
> `bootNode` and the hosted eyes.
|
|
4
|
+
> `bootNode` and the hosted eyes. 0.6.0 on npm dist-tag `latest`. Node ≥ 22.12.
|
|
5
5
|
|
|
6
|
-
- Install: `npm install @descix/frqtl-node
|
|
6
|
+
- Install: `npm install @descix/frqtl-node`
|
|
7
7
|
- `import { bootNode, Wall } from '@descix/frqtl-node'` (require() works too); `await bootNode()` → the engine handle.
|
|
8
8
|
- `./eyes`: `openHostedEyes({ sceneRecord, sceneSource: { kind: 'project', files, entry }, pageUrl })`.
|
|
9
9
|
- `./project`: `linkProject` links a scene file against this package's own copy.
|
|
10
|
+
- `./host-view`: `declareHostView`, `HOST_VIEW_OUTCOMES`, `CONTRACT_VIOLATIONS`, `HOST_SDK_SCRIPT` — the same declarer as @descix/frqtl-web/host-view, pure, for a node test of a call site.
|
|
10
11
|
- `./register`: `node --import @descix/frqtl-node/register scene.mjs` resolves a scene's `@descix/frqtl-web/...` imports to this package.
|
|
11
|
-
- Full agent guide: https://cdn.jsdelivr.net/npm/@descix/frqtl-node@0.6.0
|
|
12
|
+
- Full agent guide: https://cdn.jsdelivr.net/npm/@descix/frqtl-node@0.6.0/CLAUDE.md
|
|
12
13
|
- Acceptance is visual; no CLI ships. No account, credits or tokens are needed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descix/frqtl-node",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "FRQTL for node \u2014 the discrete fractal physics engine (compiled Rust \u2192 WASM), its parts as ES modules, the node engine door (bootNode) and the hosted eyes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"./parts/*": "./dist/frqtl/parts/*",
|
|
14
14
|
"./project": "./dist/shim/project/index.js",
|
|
15
15
|
"./registry": "./dist/shim/registry/index.js",
|
|
16
|
+
"./host-view": "./dist/shim/host/index.js",
|
|
16
17
|
"./register": "./dist/shim/project/register-node.mjs",
|
|
17
18
|
"./shim/controls/registry.js": "./dist/shim/controls/registry.js",
|
|
18
19
|
"./shim/render/sun-look.js": "./dist/shim/render/sun-look.js",
|
|
@@ -21,6 +22,23 @@
|
|
|
21
22
|
"./objects/setup/_collisionMatrixLanes.js": "./dist/frqtl/objects/setup/_collisionMatrixLanes.js",
|
|
22
23
|
"./compat/engine.js": "./dist/frqtl/compat/engine.js",
|
|
23
24
|
"./kinds": "./dist/frqtl/objects/kinds-entry.js",
|
|
25
|
+
"./objects/objects/tagger.js": "./dist/frqtl/objects/objects/tagger.js",
|
|
26
|
+
"./objects/schema/index.js": "./dist/frqtl/objects/schema/index.js",
|
|
27
|
+
"./shim/render/heat-colormap.js": "./dist/shim/render/heat-colormap.js",
|
|
28
|
+
"./objects/schema/valueNormalizer.js": "./dist/frqtl/objects/schema/valueNormalizer.js",
|
|
29
|
+
"./objects/objects/Box.js": "./dist/frqtl/objects/objects/Box.js",
|
|
30
|
+
"./objects/objects/QuantumEmitter.js": "./dist/frqtl/objects/objects/QuantumEmitter.js",
|
|
31
|
+
"./shim/expr/ast-schema.js": "./dist/shim/expr/ast-schema.js",
|
|
32
|
+
"./objects/objects/actionCompiler.js": "./dist/frqtl/objects/objects/actionCompiler.js",
|
|
33
|
+
"./objects/v1-replacements.js": "./dist/frqtl/objects/v1-replacements.js",
|
|
34
|
+
"./objects/objects/slitLinerScatter.js": "./dist/frqtl/objects/objects/slitLinerScatter.js",
|
|
35
|
+
"./objects/setup/setupSphereEmitter3D.js": "./dist/frqtl/objects/setup/setupSphereEmitter3D.js",
|
|
36
|
+
"./objects/objects/directedRedirectProgram.js": "./dist/frqtl/objects/objects/directedRedirectProgram.js",
|
|
37
|
+
"./compat/renderer/lo-drawn-center.js": "./dist/frqtl/compat/renderer/lo-drawn-center.js",
|
|
38
|
+
"./objects/setup/setupTunnelPointExit.js": "./dist/frqtl/objects/setup/setupTunnelPointExit.js",
|
|
39
|
+
"./objects/constants/WallBehaviors.js": "./dist/frqtl/objects/constants/WallBehaviors.js",
|
|
40
|
+
"./objects/objects/absorbRadiation.js": "./dist/frqtl/objects/objects/absorbRadiation.js",
|
|
41
|
+
"./objects/geometry/SlabFace.js": "./dist/frqtl/objects/geometry/SlabFace.js",
|
|
24
42
|
"./dist-manifest.json": "./dist/dist-manifest.json",
|
|
25
43
|
"./package.json": "./package.json"
|
|
26
44
|
},
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* EXPERIMENTS — canonical experiment registry.
|
|
5
|
-
*
|
|
6
|
-
* Single ESM named export. Replaces the dual window.EXPERIMENTS + CommonJS export.
|
|
7
|
-
* Consumed by lib/features/ide via import; SimPane reads experiments from the prop.
|
|
8
|
-
*
|
|
9
|
-
* id : the directory name under frqtl/experiments/{id}/index.html
|
|
10
|
-
* title : human-readable label
|
|
11
|
-
* cat : category label
|
|
12
|
-
* jsOnly : true if the experiment requires JS-engine-only APIs
|
|
13
|
-
*
|
|
14
|
-
* Ordering (G1):
|
|
15
|
-
* Experiments listed in the exhibit manifest appear FIRST, in manifest order.
|
|
16
|
-
* Remaining experiments follow in their original declaration order.
|
|
17
|
-
* This ordering is consumed by SimPane's dropdown via the existing prop path
|
|
18
|
-
* (zero SimPane changes required).
|
|
19
|
-
*
|
|
20
|
-
* The manifest import is done lazily via a factory to avoid circular dependency
|
|
21
|
-
* (exhibits.js imports EXPERIMENTS from here; we cannot import exhibits.js at
|
|
22
|
-
* the top level of this file). The ordered list is computed once on first access.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* EXPERIMENT_TIERS — the tier vocabulary and what each tier means for PUBLIC
|
|
27
|
-
* SURFACING. This table is the contract; consumers never hardcode a tier string.
|
|
28
|
-
*
|
|
29
|
-
* - A row with NO `tier` is public (absence = public; there is deliberately no
|
|
30
|
-
* `tier:'public'` literal to maintain on every row).
|
|
31
|
-
* - A row whose tier resolves `public:false` is dropped from the public
|
|
32
|
-
* experiment choosers by `isPublicExperiment()` / `publicExperiments()`.
|
|
33
|
-
* - An UNKNOWN tier value FAILS LOUD (`isPublicExperiment` throws) — a typo'd
|
|
34
|
-
* tier must never silently publish a lab.
|
|
35
|
-
* - Tier gates the CURATED public chooser (STUDIO Templates). The GYM lists
|
|
36
|
-
* `internalExperiments()` instead — see that function's doc.
|
|
37
|
-
*
|
|
38
|
-
* Visibility only: a non-public tier NEVER affects loadability, deep-linking,
|
|
39
|
-
* ordering, `is3D`, or the label lookup. Those all read the full EXPERIMENTS list
|
|
40
|
-
* / `experimentById()`. `sdk/frqtl-web/build.js` ships `publicExperiments()` +
|
|
41
|
-
* the named infra ids: a dev-tier page is absent from the npm/CDN package, but
|
|
42
|
-
* present in the GYM (both the dev IDE and the shipped static deploy).
|
|
43
|
-
*
|
|
44
|
-
* ── The public set is the NEW-SUGAR content ONLY ─────────────────────────────
|
|
45
|
-
* EVERY row below is `tier:'dev'`: none of these pages has been rebuilt on the
|
|
46
|
-
* current sugar objects (SphereEmitter / v2 `Wall` / `Tunnel` / LiveChart /
|
|
47
|
-
* GraphingDetectorWall), so none is public and none ships. The PUBLIC gallery
|
|
48
|
-
* content is the WALK-THROUGH rung registry (`./walkthrough.js`), which is built
|
|
49
|
-
* entirely on those objects; `sdk/frqtl-web/src/index.html` is its dist surface.
|
|
50
|
-
* An unmigrated page FAILING LOUD in a dist context is the DESIRED signal — it is
|
|
51
|
-
* the list of what remains to migrate. A row returns to the public set by being
|
|
52
|
-
* REBUILT on the new objects and having its `tier` dropped — never by exception.
|
|
53
|
-
*
|
|
54
|
-
* @type {Readonly<Record<string, Readonly<{id:string, public:boolean, label:string}>>>}
|
|
55
|
-
*/
|
|
56
|
-
export const EXPERIMENT_TIERS = Object.freeze({
|
|
57
|
-
dev: Object.freeze({
|
|
58
|
-
id: 'dev',
|
|
59
|
-
public: false,
|
|
60
|
-
label: 'Developer isolation lab / demo',
|
|
61
|
-
}),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
/** @type {Array<{id:string, title:string, cat:string, jsOnly?:boolean, wasmOnly?:boolean, is3D?:boolean, tier?:string}>}
|
|
65
|
-
* jsOnly : requires JS-engine-only APIs (no WASM build) — filtered out of Compare.
|
|
66
|
-
* tier : a key of EXPERIMENT_TIERS (above). `tier:'dev'` marks a DEVELOPER-tier
|
|
67
|
-
* isolation lab / demo: fully registered and BOOTABLE by id (direct
|
|
68
|
-
* experiment-page URL + STUDIO deep link — the dev surface), HIDDEN from
|
|
69
|
-
* the PUBLIC experiment chooser (STUDIO Templates). It stays LISTED in
|
|
70
|
-
* the GYM (reads `internalExperiments()`), which is what driving these
|
|
71
|
-
* labs is for. Omit `tier` for a public experiment.
|
|
72
|
-
* wasmOnly: 3D experiment; the JS reference is a frozen 2D fixture, so Compare runs
|
|
73
|
-
* WASM-only (JS-reference pane disabled). See CompareView filter.
|
|
74
|
-
* is3D : the ONE mount-time 3D-render authority (E-W0a). TRUE iff
|
|
75
|
-
* the experiment page unconditionally boots a 3D display — its runner is
|
|
76
|
-
* `new FrqtlApp({ is3D: true, … })` on the three display — the ground-truth
|
|
77
|
-
* signal, NOT wasmOnly (orthogonal: collision-matrix is wasmOnly+2D). STUDIO
|
|
78
|
-
* reads THIS field pre-boot to light up the camera strip + MOVE/LOOK drive for a
|
|
79
|
-
* template-loaded 3D experiment. A gate test (experiment-is3d-agreement.test.mjs)
|
|
80
|
-
* asserts THIS field ↔ the page's runner `is3D: true` — a hand-typed drift is a
|
|
81
|
-
* CI failure (mirror kill). A page that switches 3D only via a `?renderer=three`
|
|
82
|
-
* reinit toggle (single-source-interference) is 2D-DEFAULT → is3D omitted/false. */
|
|
83
|
-
const _EXPERIMENTS_BASE = [
|
|
84
|
-
{ id: 'g1-lo-race', title: 'G1: LO races a quantum', cat: 'Engine Tests', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
85
|
-
{ id: 'g2-lo-sweep', title: 'G2: moving LO sweeps a field', cat: 'Engine Tests', wasmOnly: true, tier: 'dev' },
|
|
86
|
-
{ id: 'g3-lo-collide', title: 'G3: Collision Lab (box-of-balls)', cat: 'Engine Tests', wasmOnly: true, tier: 'dev' },
|
|
87
|
-
{ id: 'collision-matrix', title: 'Collision Matrix (density sweep)', cat: 'Engine Tests', wasmOnly: true, tier: 'dev' },
|
|
88
|
-
{ id: 'tunnel-lab', title: 'Tunnel Lab (DirectedTunnel isolation)', cat: 'Engine Tests', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
89
|
-
{ id: 'lightsource-lab', title: 'LightSource Lab (LightSource isolation)', cat: 'Engine Tests', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
90
|
-
{ id: 'mesh-demo', title: 'render.mesh demo (external glTF)', cat: 'Render Effects', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
91
|
-
{ id: 'cone-emitter-3d', title: 'Cone Emitter 3D (laser ↔ flashlight)', cat: 'Wave Mechanics', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
92
|
-
{ id: 'sphere-emitter-3d', title: 'Sphere Emitter 3D (sun / laser / drum)', cat: 'Wave Mechanics', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
93
|
-
{ id: 'single-source-interference', title: 'Single-Source Self-Interference (omni, 2D)', cat: 'Wave Mechanics', wasmOnly: true, tier: 'dev' },
|
|
94
|
-
{ id: 'tunnel-point-exit', title: 'Tunnel Point-Exit (collapsed mouth, 2D)', cat: 'Wave Mechanics', wasmOnly: true, tier: 'dev' },
|
|
95
|
-
{ id: 'fx-glow', title: 'FX: Glow', cat: 'Render Effects', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
96
|
-
{ id: 'fx-godrays', title: 'FX: God-rays', cat: 'Render Effects', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
97
|
-
{ id: 'fx-smoke', title: 'FX: Smoke', cat: 'Render Effects', wasmOnly: true, is3D: true, tier: 'dev' },
|
|
98
|
-
{ id: 'blackbody-substrate', title: 'Blackbody Substrate (L1+L2+L3+L4)', cat: 'Thermodynamics', tier: 'dev' },
|
|
99
|
-
{ id: 'circuit-basic', title: 'Circuit: Series Resistor', cat: 'Computing', tier: 'dev' },
|
|
100
|
-
{ id: 'trace-circuit', title: 'Trace: the wire part', cat: 'Computing', wasmOnly: true, tier: 'dev' },
|
|
101
|
-
{ id: 'oscillator', title: 'Quantum Oscillator', cat: 'Structure & Forces', tier: 'dev' }
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The flat experiment list, in declaration order. (The showcase roster — the front-page tiles — is
|
|
106
|
-
* scene files, lib/shim/registry/exhibits.js; no experiment page is a tile.)
|
|
107
|
-
*
|
|
108
|
-
* @type {Array<{id:string, title:string, cat:string, jsOnly?:boolean, wasmOnly?:boolean}>}
|
|
109
|
-
*/
|
|
110
|
-
export const EXPERIMENTS = _EXPERIMENTS_BASE;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* isPublicExperiment(entry) — THE tier-visibility predicate. Resolves an entry's
|
|
114
|
-
* `tier` against the EXPERIMENT_TIERS contract: no tier ⇒ public; a known tier ⇒
|
|
115
|
-
* that tier's `public` flag; an unknown tier ⇒ THROWS (fail loud — a typo must
|
|
116
|
-
* never silently publish a dev lab).
|
|
117
|
-
*
|
|
118
|
-
* This is the ONE place the tier→visibility rule lives. Any surface that lists
|
|
119
|
-
* experiments to the public FERRIES this predicate (or `publicExperiments()`
|
|
120
|
-
* below) — re-writing `e.tier !== 'dev'` in a consumer is the schema-mirror
|
|
121
|
-
* anti-pattern and will drift the moment a second non-public tier is added.
|
|
122
|
-
*
|
|
123
|
-
* @param {{id?:string, tier?:string}} entry — an EXPERIMENTS row.
|
|
124
|
-
* @returns {boolean} true if the entry belongs in a public experiment chooser.
|
|
125
|
-
* @throws {Error} if `entry.tier` is not a key of EXPERIMENT_TIERS.
|
|
126
|
-
*/
|
|
127
|
-
export function isPublicExperiment(entry) {
|
|
128
|
-
if (!entry || !entry.tier) { return true; }
|
|
129
|
-
var tier = EXPERIMENT_TIERS[entry.tier];
|
|
130
|
-
if (!tier) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
'[experiments] unknown tier "' + entry.tier + '" on experiment "' +
|
|
133
|
-
(entry.id || '?') + '". Known tiers: ' +
|
|
134
|
-
Object.keys(EXPERIMENT_TIERS).join(', ') + ' (omit `tier` for public).'
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
return tier.public === true;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* publicExperiments() — the EXPERIMENTS a PUBLIC chooser should list, filtered by
|
|
142
|
-
* `isPublicExperiment`. The single filtered view both public experiment-chooser
|
|
143
|
-
* surfaces (STUDIO Templates via ExperimentDropdown, the GYM/Compare select)
|
|
144
|
-
* consume, so the dev-tier hide is declared ONCE. Non-public experiments remain
|
|
145
|
-
* registered and bootable by id (direct experiment-page URL, STUDIO deep link) —
|
|
146
|
-
* they are only hidden from these curated lists.
|
|
147
|
-
*
|
|
148
|
-
* @returns {Array<{id:string, title:string, cat:string}>}
|
|
149
|
-
*/
|
|
150
|
-
export function publicExperiments() {
|
|
151
|
-
return EXPERIMENTS.filter(isPublicExperiment);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* internalExperiments() — the roster an INTERNAL-ONLY experiment chooser lists.
|
|
156
|
-
*
|
|
157
|
-
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
158
|
-
* Every registered row, tier-independent, in registry order. This is the peer of
|
|
159
|
-
* `publicExperiments()`, and the split is by SURFACE AUDIENCE, not by taste:
|
|
160
|
-
*
|
|
161
|
-
* - `publicExperiments()` — for a chooser a STRANGER can reach (STUDIO
|
|
162
|
-
* Templates). Tier gates it, because tier means "has this page been rebuilt
|
|
163
|
-
* on the current sugar and is it fit to publish".
|
|
164
|
-
* - `internalExperiments()` — for the GYM (`preset='compare'`), which drives
|
|
165
|
-
* dev-tier isolation labs directly by id and so needs the full roster
|
|
166
|
-
* regardless of tier.
|
|
167
|
-
*
|
|
168
|
-
* WHY THIS EXISTS (2026-07-28): with the whole legacy roster moved to `tier:'dev'`,
|
|
169
|
-
* `publicExperiments()` is legitimately EMPTY. A surface that lists it therefore
|
|
170
|
-
* offers nothing — which is correct for a public chooser (the public content set
|
|
171
|
-
* is the WALK-THROUGH registry) and wrong for the maintainer's driving
|
|
172
|
-
* instrument. Passing the audience question through the tier table was the
|
|
173
|
-
* conflation; naming the two rosters separately is the fix.
|
|
174
|
-
*
|
|
175
|
-
* A consumer picks the roster that matches ITS audience and derives everything
|
|
176
|
-
* else (its default selection included) from that ONE list — never a second
|
|
177
|
-
* hardcoded id.
|
|
178
|
-
*
|
|
179
|
-
* @returns {Array<{id:string, title:string, cat:string}>} registry rows BY REFERENCE.
|
|
180
|
-
*/
|
|
181
|
-
export function internalExperiments() {
|
|
182
|
-
return EXPERIMENTS.slice();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* experimentById(id) — THE id→entry resolution for the registry. Reads the FULL
|
|
187
|
-
* EXPERIMENTS list, tier-independent: a dev-tier experiment resolves exactly like
|
|
188
|
-
* a public one, which is what keeps it reachable by direct URL / deep link while
|
|
189
|
-
* hidden from the public choosers.
|
|
190
|
-
*
|
|
191
|
-
* @param {string} id — an experiment id (directory name under experiments/).
|
|
192
|
-
* @returns {{id:string, title:string, cat:string}|null} the entry, or null if unknown.
|
|
193
|
-
*/
|
|
194
|
-
export function experimentById(id) {
|
|
195
|
-
for (var i = 0; i < EXPERIMENTS.length; i++) {
|
|
196
|
-
if (EXPERIMENTS[i].id === id) { return EXPERIMENTS[i]; }
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* experimentIs3D(id) — the ONE mount-time 3D-render predicate for a gallery
|
|
203
|
-
* experiment (E-W0a). Reads the declarative `is3D` field off the registry entry
|
|
204
|
-
* (the single authority; see the `is3D` doc on `_EXPERIMENTS_BASE`). Returns
|
|
205
|
-
* false for an unknown id or a 2D-default experiment. STUDIO consults this pre-boot
|
|
206
|
-
* for a template-loaded experiment — the axis the walkthrough path derives from
|
|
207
|
-
* `renderState.renderer`. Never sniffs a page or a runner: 3D-ness is DATA here.
|
|
208
|
-
*
|
|
209
|
-
* @param {string} id — an experiment id (directory name under experiments/).
|
|
210
|
-
* @returns {boolean}
|
|
211
|
-
*/
|
|
212
|
-
export function experimentIs3D(id) {
|
|
213
|
-
var entry = experimentById(id);
|
|
214
|
-
return !!entry && entry.is3D === true;
|
|
215
|
-
}
|