@descix/frqtl-node 0.6.0-p4.1 → 0.6.1-p1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CLAUDE.md +2 -2
  2. package/LICENSE.md +79 -63
  3. package/README.md +6 -3
  4. package/dist/dist-manifest.json +60 -54
  5. package/dist/fat/pkg-node/package.json +2 -10
  6. package/dist/frqtl/compat/providers/wasm-engine.js +3 -4
  7. package/dist/frqtl/compat/renderer/providers/p5-provider.js +1 -1
  8. package/dist/frqtl/objects/FrqtlApp.js +5 -5
  9. package/dist/frqtl/objects/RecipeContext.js +106 -1
  10. package/dist/frqtl/objects/constants/WallBehaviors.js +1 -1
  11. package/dist/frqtl/objects/instruments/capture.js +1 -24
  12. package/dist/frqtl/objects/instruments/hosted-eyes.js +17 -1
  13. package/dist/frqtl/objects/objects/makeControlsForObject.js +5 -5
  14. package/dist/frqtl/objects/record/Record.js +25 -5
  15. package/dist/frqtl/objects/schema/engine-scopes.js +2 -2
  16. package/dist/frqtl/objects/schema/scopes.js +2 -2
  17. package/dist/frqtl/objects/setup/_worldInit.js +1 -2
  18. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +1 -2
  19. package/dist/frqtl/parts/CameraControls.js +8 -0
  20. package/dist/frqtl/parts/DetectorWall.js +1 -1
  21. package/dist/frqtl/parts/GraphingDetectorWall.js +7 -7
  22. package/dist/frqtl/parts/LargeObject.js +2 -2
  23. package/dist/frqtl/parts/LiveChart.js +2 -2
  24. package/dist/frqtl/parts/LogicGate.js +3 -3
  25. package/dist/frqtl/parts/OvenBox.js +1 -1
  26. package/dist/frqtl/parts/PointSource.js +1 -1
  27. package/dist/frqtl/parts/SlitLightSource.js +1 -1
  28. package/dist/frqtl/parts/SlitScreen.js +1 -1
  29. package/dist/frqtl/parts/SphereEmitter.js +12 -12
  30. package/dist/frqtl/parts/Trace.js +1 -1
  31. package/dist/frqtl/parts/Tunnel.js +11 -11
  32. package/dist/frqtl/parts/Wall.js +20 -10
  33. package/dist/frqtl/scenes/circuit-sat.js +1 -0
  34. package/dist/frqtl/scenes/dse-one-emitter.js +32 -34
  35. package/dist/frqtl/scenes/dse-two-emitters.js +33 -34
  36. package/dist/frqtl/scenes/mirror-route.js +96 -79
  37. package/dist/shim/bundle/index.js +6 -0
  38. package/dist/shim/controls/index.js +1 -1
  39. package/dist/shim/engine/artifact-shell.js +1 -1
  40. package/dist/shim/engine/router.js +10 -17
  41. package/dist/shim/engine/site-graph.js +36 -0
  42. package/dist/shim/host/host-view.js +232 -0
  43. package/dist/shim/host/index.js +18 -0
  44. package/dist/shim/index.js +2 -4
  45. package/dist/shim/notebook/kernel.js +1 -1
  46. package/dist/shim/notebook/setup-builder.js +2 -2
  47. package/dist/shim/package.json +1 -0
  48. package/dist/shim/page-params/index.js +2 -30
  49. package/dist/shim/project/fork.js +293 -0
  50. package/dist/shim/project/identity.js +3 -1
  51. package/dist/shim/project/link.js +43 -3
  52. package/dist/shim/project/parts-writer.js +62 -5
  53. package/dist/shim/project/read-text.js +43 -0
  54. package/dist/shim/project/specifiers.js +107 -16
  55. package/dist/shim/registry/exhibits.js +16 -35
  56. package/dist/shim/registry/index.js +5 -2
  57. package/dist/shim/registry/scene-roster.js +35 -0
  58. package/dist/shim/registry/scene-source.js +6 -36
  59. package/dist/shim/registry/showcase-link.js +61 -0
  60. package/dist/shim/registry/walkthrough.js +2 -3
  61. package/dist/shim/render-state/apply.js +66 -31
  62. package/dist/shim/render-state/index.js +6 -2
  63. package/dist/shim/runtime/index.js +4 -4
  64. package/llms.txt +4 -3
  65. package/package.json +21 -2
  66. 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 CAPABILITY split on the resolved URL, never a host sniff: a `file:` URL is read through
30
- * node's fs (`process.getBuiltinModule`), anything else is fetched.
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
- var text = url.indexOf('file:') === 0
157
- ? _readFileUrl(url, row.id)
158
- : _fetchText(url, row.id, opts.fetch);
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.)');
@@ -0,0 +1,61 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * showcase-link.js — the ONE writer of a showcase tile's link: where a roster row opens on the site that serves
5
+ * the IDE. Every front page (the publisher sites, Gods World) composes its tile hrefs from these two names and types
6
+ * no query, page or origin of its own.
7
+ *
8
+ * showcaseHref(row) → 'app.html?preset=author&walkthrough=<id>[&mode=<mode>]', relative to the serving site's root:
9
+ * the IDE entry page (site-graph.js::STATIC_APP_PAGE) opened on STUDIO at the row's rung. `mode`
10
+ * is the row's EFFECTIVE mode (exhibits.js normalizes it: on a multi-mode rung the mode the row
11
+ * names, else the rung's default; null on a single-mode rung, which carries no `mode`), so the
12
+ * link opens the mode the tile's still shows.
13
+ * showcaseUrl(row) → the same link, absolute on SHOWCASE_SITE.origin: what a front page on ANY other origin uses.
14
+ * Whether a page links relative (it is the serving site) or absolute is the page's own fact.
15
+ * SHOWCASE_SITE → { app, origin } — the DeSciX app that serves the IDE and its public origin.
16
+ *
17
+ * `row` must be an element of the EXHIBITS array imported from the SAME module instance (the ./registry export):
18
+ * a copied, JSON-deserialized or second-copy row is refused, loudly.
19
+ *
20
+ * It writes no IDE state: the query is a link, read by the one parser (lib/features/ide/deep-link.js::parseDeepLink).
21
+ * The shim cannot import that parser (shim → features is illegal), so the binding is a round-trip conformance test
22
+ * in lib/features/ide/test/showcase-link-round-trip.test.mjs: for every EXHIBITS row the parsed link shows STUDIO,
23
+ * names the row's rung and mode, and is refused by nothing.
24
+ *
25
+ * Host-neutral ESM: it runs unchanged in node and in a page (the `./registry` export).
26
+ */
27
+
28
+ import { STATIC_APP_PAGE } from '../engine/site-graph.js';
29
+ import { EXHIBITS } from './exhibits.js';
30
+
31
+ /** The IDE surface a showcase tile opens: STUDIO (presets.js PRESETS.author; the round-trip test binds the two). */
32
+ const SHOWCASE_SURFACE = 'author';
33
+
34
+ /** The DeSciX app serving the IDE every showcase tile opens, and its public origin (no trailing slash). */
35
+ export const SHOWCASE_SITE = Object.freeze({ app: 'egpt-frqtl', origin: 'https://egpt-frqtl.descix.net' });
36
+
37
+ /**
38
+ * @param {object} row an element of EXHIBITS (exhibits.js)
39
+ * @returns {string} the tile's link, relative to the serving site's root
40
+ * @throws {Error} a row that is not on the roster
41
+ */
42
+ export function showcaseHref(row) {
43
+ if (EXHIBITS.indexOf(row) === -1) {
44
+ throw new Error('[showcase-link] showcaseHref(row): not a showcase roster row — pass an element of EXHIBITS ' +
45
+ '(lib/shim/registry/exhibits.js), got ' + JSON.stringify(row && { kind: row.kind, id: row.id }) + '. (FAIL-LOUD.)');
46
+ }
47
+ const params = new URLSearchParams();
48
+ params.set('preset', SHOWCASE_SURFACE);
49
+ params.set('walkthrough', row.id);
50
+ if (row.mode !== null) { params.set('mode', row.mode); }
51
+ return STATIC_APP_PAGE + '?' + params.toString();
52
+ }
53
+
54
+ /**
55
+ * @param {object} row an element of EXHIBITS (exhibits.js), from the same module instance
56
+ * @returns {string} the tile's link, absolute on SHOWCASE_SITE.origin
57
+ * @throws {Error} a row that is not on the roster
58
+ */
59
+ export function showcaseUrl(row) {
60
+ return new URL(showcaseHref(row), SHOWCASE_SITE.origin + '/').href;
61
+ }
@@ -774,8 +774,7 @@ export function walkthroughById(id) {
774
774
  });
775
775
 
776
776
  // ── is3D normalization (E-W0a; mode-aware S3/W2) ─────────────────────────────
777
- // STUDIO must read ONE `is3D` field for a selection regardless of whether it came
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, the peer of experiments.js `experimentIs3D`. Derived through
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 + page controls) is replayed by every host: the GYM rung frame, STUDIO, the bundle's
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
- * normalizeSavedRenderState(raw) — tolerant normalizer for a LOADED bag; THROWS only on a bag
41
- * that carries page controls (they are author code now).
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
- * Tolerant normalizer for a saved module's `renderState` field — a stored record may be
75
- * `undefined` (a pre-S6 legacy module), `null`, or a shape from another `version` — NEVER throws;
76
- * every missing/malformed field degrades to a NEUTRAL default (empty array / null), one `apply`
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 {*} raw
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 bag whose `pageControls` holds a row — page controls are declared in the sketch's own
90
- * code, so a saved bag never carries them; migrate the project (move the controls into its sketch).
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
- var r = _isPlainObject(raw) ? raw : {};
94
- if (Array.isArray(r.pageControls) && r.pageControls.length > 0) {
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
- r.pageControls.map(function (row) { return _isPlainObject(row) ? String(row.id) : '?'; }).join(', ') +
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 camera = null;
100
- if (_isPlainObject(r.camera)) {
101
- camera = {
102
- orbit: _isPlainObject(r.camera.orbit) ? r.camera.orbit : null,
103
- preset: (typeof r.camera.preset === 'string') ? r.camera.preset : null
104
- };
105
- }
106
- return {
107
- version: (typeof r.version === 'number') ? r.version : 1,
108
- renderer: (typeof r.renderer === 'string' && r.renderer.length > 0) ? r.renderer : null,
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
- * normalizeSavedRenderState(raw) — tolerant normalizer for a LOADED bag (never throws).
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')" — FRAQTL #73); the throw names what was unavailable instead.
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 (FRAQTL issue #67): a math scope is one RUN, and the caller
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 (FRAQTL issue #67). Its
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 (FRAQTL issue #67). The
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. Pre-release 0.6.0-p4.1 on npm dist-tag `next`. Node ≥ 22.12.
4
+ > `bootNode` and the hosted eyes. 0.6.1-p1.2 on npm dist-tag `next`. Node ≥ 22.12.
5
5
 
6
- - Install: `npm install @descix/frqtl-node@next`
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-p4.1/CLAUDE.md
12
+ - Full agent guide: https://cdn.jsdelivr.net/npm/@descix/frqtl-node@0.6.1-p1.2/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-p4.1",
3
+ "version": "0.6.1-p1.2",
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,8 @@
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",
17
+ "./site-graph": "./dist/shim/engine/site-graph.js",
16
18
  "./register": "./dist/shim/project/register-node.mjs",
17
19
  "./shim/controls/registry.js": "./dist/shim/controls/registry.js",
18
20
  "./shim/render/sun-look.js": "./dist/shim/render/sun-look.js",
@@ -21,10 +23,27 @@
21
23
  "./objects/setup/_collisionMatrixLanes.js": "./dist/frqtl/objects/setup/_collisionMatrixLanes.js",
22
24
  "./compat/engine.js": "./dist/frqtl/compat/engine.js",
23
25
  "./kinds": "./dist/frqtl/objects/kinds-entry.js",
26
+ "./objects/objects/tagger.js": "./dist/frqtl/objects/objects/tagger.js",
27
+ "./objects/schema/index.js": "./dist/frqtl/objects/schema/index.js",
28
+ "./shim/render/heat-colormap.js": "./dist/shim/render/heat-colormap.js",
29
+ "./objects/schema/valueNormalizer.js": "./dist/frqtl/objects/schema/valueNormalizer.js",
30
+ "./objects/objects/Box.js": "./dist/frqtl/objects/objects/Box.js",
31
+ "./objects/objects/QuantumEmitter.js": "./dist/frqtl/objects/objects/QuantumEmitter.js",
32
+ "./shim/expr/ast-schema.js": "./dist/shim/expr/ast-schema.js",
33
+ "./objects/objects/actionCompiler.js": "./dist/frqtl/objects/objects/actionCompiler.js",
34
+ "./objects/v1-replacements.js": "./dist/frqtl/objects/v1-replacements.js",
35
+ "./objects/objects/slitLinerScatter.js": "./dist/frqtl/objects/objects/slitLinerScatter.js",
36
+ "./objects/setup/setupSphereEmitter3D.js": "./dist/frqtl/objects/setup/setupSphereEmitter3D.js",
37
+ "./objects/objects/directedRedirectProgram.js": "./dist/frqtl/objects/objects/directedRedirectProgram.js",
38
+ "./compat/renderer/lo-drawn-center.js": "./dist/frqtl/compat/renderer/lo-drawn-center.js",
39
+ "./objects/setup/setupTunnelPointExit.js": "./dist/frqtl/objects/setup/setupTunnelPointExit.js",
40
+ "./objects/constants/WallBehaviors.js": "./dist/frqtl/objects/constants/WallBehaviors.js",
41
+ "./objects/objects/absorbRadiation.js": "./dist/frqtl/objects/objects/absorbRadiation.js",
42
+ "./objects/geometry/SlabFace.js": "./dist/frqtl/objects/geometry/SlabFace.js",
24
43
  "./dist-manifest.json": "./dist/dist-manifest.json",
25
44
  "./package.json": "./package.json"
26
45
  },
27
- "exportsNotes": "Every target is a file the build places at its lib-identity path (dist/frqtl/..., dist/shim/...), the same files @descix/frqtl-web ships (one source built twice; the [SHARED] gate holds every shared placement byte-identical). `.` is the parts barrel + bootNode + the shared package core (lib/frqtl/node-entry.js); `require()` of it works from CommonJS (require(esm): nothing awaits at top level). `./eyes` is the hosted eyes (openHostedEyes), node-only, the one surface that pulls playwright. `./parts`, `./parts/*`, `./project`, `./registry` and `./dist-manifest.json` are the same rows as @descix/frqtl-web's. The expected shape of this map is owned by sdk/_build/exports-contract.mjs.",
46
+ "exportsNotes": "The exports map is documented in README.md, section Exports.",
28
47
  "files": [
29
48
  "dist",
30
49
  "README.md",