@descix/frqtl-node 0.6.0-p1.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 +6 -4
- package/dist/dist-manifest.json +71 -65
- package/dist/eyes/index.js +5 -2
- 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 +33 -6
- package/dist/frqtl/objects/RecipeContext.js +284 -13
- 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 +216 -29
- package/dist/frqtl/objects/objects/makeControlsForObject.js +5 -5
- package/dist/frqtl/objects/record/Record.js +346 -0
- package/dist/frqtl/objects/record/open-record.js +22 -0
- 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/FrqtlVision.js +21 -6
- 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 +118 -96
- package/dist/shim/bundle/indexHtml.js +66 -39
- 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 +3 -5
- package/dist/shim/notebook/format.js +1 -1
- package/dist/shim/notebook/kernel.js +1 -1
- package/dist/shim/notebook/setup-builder.js +2 -2
- package/dist/shim/notebook/store.js +2 -3
- 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/index.js +16 -3
- package/dist/shim/project/link.js +224 -46
- package/dist/shim/project/manifest.js +151 -0
- package/dist/shim/project/parts-writer.js +68 -7
- package/dist/shim/project/read-text.js +43 -0
- package/dist/shim/project/record-format.js +345 -0
- package/dist/shim/project/specifiers.js +104 -15
- package/dist/shim/registry/exhibits.js +15 -34
- package/dist/shim/registry/index.js +3 -3
- package/dist/shim/registry/scene-roster.js +35 -0
- package/dist/shim/registry/scene-source.js +48 -64
- package/dist/shim/registry/walkthrough.js +6 -6
- 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/dist/shim/storage/codec-chains.js +88 -0
- package/dist/shim/storage/folders.js +205 -0
- package/dist/shim/storage/index.js +64 -8
- package/dist/shim/storage/make-namespace-store.js +5 -5
- package/llms.txt +4 -3
- package/package.json +19 -1
- package/dist/shim/bundle/frqnb.js +0 -112
- package/dist/shim/bundle/manifest.js +0 -95
- package/dist/shim/registry/experiments.js +0 -215
- package/dist/shim/storage/modules.js +0 -249
- package/dist/shim/storage/projects.js +0 -150
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* specifiers.js — THE one table of `@descix/frqtl-web/...` specifiers a sketch may import.
|
|
5
5
|
*
|
|
6
|
-
* FRQTL_WEB_SPECIFIERS rows { specifier, libPath, why } — a row whose specifier ends in `/`
|
|
7
|
-
* is a PREFIX row (one part = one file under it)
|
|
6
|
+
* FRQTL_WEB_SPECIFIERS rows { specifier, libPath, why, tier } — a row whose specifier ends in `/`
|
|
7
|
+
* is a PREFIX row (one part = one file under it). `tier` is a key of SPECIFIER_TIERS:
|
|
8
|
+
* 'scene' (a sketch imports it) or 'part-internal' (a forked part file under parts/
|
|
9
|
+
* imports it, never a sketch: the parts' own internal imports, one row per file —
|
|
10
|
+
* the set equals what the registering parts import, held by fork.test.mjs G-CENSUS)
|
|
11
|
+
* SPECIFIER_TIERS { scene, 'part-internal' } — the tier vocabulary and who may import each
|
|
8
12
|
* libPathOf(specifier) → the lib-identity path it names; THROWS on an unknown specifier
|
|
13
|
+
* tierOf(specifier) → the row's tier; THROWS on an unknown specifier (libPathOf's rows)
|
|
14
|
+
* specifierOfLibPath(libPath) → the specifier naming that lib-identity file, or null: libPathOf's inverse over
|
|
15
|
+
* the same rows (a prefix row answers for each file directly under it)
|
|
9
16
|
* importMapFor(libRootUrl) → { imports } — the rows as an import map, every libPath appended to
|
|
10
17
|
* the URL of the lib-identity root AS SEEN FROM THE PAGE: an absolute
|
|
11
18
|
* URL or one starting with '/', './' or '../' (any other root THROWS —
|
|
@@ -20,32 +27,72 @@
|
|
|
20
27
|
*
|
|
21
28
|
* Inside lib/ the specifier is a SELF-REFERENCE — the package naming itself, the way node resolves a
|
|
22
29
|
* package importing itself by name — not a lib→sdk edge. It is legal ONLY in sketch content
|
|
23
|
-
* (lib/frqtl/scenes/**,
|
|
30
|
+
* (lib/frqtl/scenes/**, written sketches, sketch and notebook text); closure modules import relatively
|
|
24
31
|
* (scripts/verify-arrows.mjs holds the scope). Consumers ferry this table and never restate it:
|
|
25
32
|
* every page import map (importMapFor), the node resolve hook (node-specifier-hook.mjs) and the sdk
|
|
26
33
|
* build's module closure (libPathOf).
|
|
27
34
|
*/
|
|
28
35
|
|
|
29
|
-
/**
|
|
36
|
+
/** Who imports a row of each tier. */
|
|
37
|
+
export const SPECIFIER_TIERS = Object.freeze({
|
|
38
|
+
scene: Object.freeze({ id: 'scene', importedBy: 'any project file (a sketch imports it)' }),
|
|
39
|
+
'part-internal': Object.freeze({ id: 'part-internal', importedBy: 'a part file (parts/*.js) of a project — a forked part imports its parent part\'s internals; never a sketch' })
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** The published surface a project imports. Every row is a public exports entry of both packages (sdk/_build/exports-contract.mjs derives them from this table). */
|
|
30
43
|
export const FRQTL_WEB_SPECIFIERS = Object.freeze([
|
|
31
44
|
Object.freeze({ specifier: '@descix/frqtl-web/parts', libPath: 'frqtl/parts/index.js',
|
|
32
|
-
why: 'the parts barrel' }),
|
|
45
|
+
why: 'the parts barrel', tier: 'scene' }),
|
|
33
46
|
Object.freeze({ specifier: '@descix/frqtl-web/parts/', libPath: 'frqtl/parts/',
|
|
34
|
-
why: 'one part = one file' }),
|
|
47
|
+
why: 'one part = one file', tier: 'scene' }),
|
|
35
48
|
Object.freeze({ specifier: '@descix/frqtl-web/shim/controls/registry.js', libPath: 'shim/controls/registry.js',
|
|
36
|
-
why: 'knob registration (blackbody, circuit-sat, collision-matrix-3d, dse-tunnel-rebuild)' }),
|
|
49
|
+
why: 'knob registration (blackbody, circuit-sat, collision-matrix-3d, dse-tunnel-rebuild)', tier: 'scene' }),
|
|
37
50
|
Object.freeze({ specifier: '@descix/frqtl-web/shim/render/sun-look.js', libPath: 'shim/render/sun-look.js',
|
|
38
|
-
why: "the one owner of the sun's FX bag (dse-tunnel-rebuild)" }),
|
|
51
|
+
why: "the one owner of the sun's FX bag (dse-tunnel-rebuild)", tier: 'scene' }),
|
|
39
52
|
Object.freeze({ specifier: '@descix/frqtl-web/compat/renderer/display-slot.js', libPath: 'frqtl/compat/renderer/display-slot.js',
|
|
40
|
-
why: 'the booted display name before co-init (mirror-route, dse-tunnel-rebuild)' }),
|
|
53
|
+
why: 'the booted display name before co-init (mirror-route, dse-tunnel-rebuild)', tier: 'scene' }),
|
|
41
54
|
Object.freeze({ specifier: '@descix/frqtl-web/compat/renderer/orbit-bag.js', libPath: 'frqtl/compat/renderer/orbit-bag.js',
|
|
42
|
-
why: 'hasAngularPose, THE 3D-ness predicate (atomic-model)' }),
|
|
55
|
+
why: 'hasAngularPose, THE 3D-ness predicate (atomic-model)', tier: 'scene' }),
|
|
43
56
|
Object.freeze({ specifier: '@descix/frqtl-web/objects/setup/_collisionMatrixLanes.js', libPath: 'frqtl/objects/setup/_collisionMatrixLanes.js',
|
|
44
|
-
why: 'the one lane table (collision-matrix-3d)' }),
|
|
57
|
+
why: 'the one lane table (collision-matrix-3d)', tier: 'scene' }),
|
|
45
58
|
Object.freeze({ specifier: '@descix/frqtl-web/compat/engine.js', libPath: 'frqtl/compat/engine.js',
|
|
46
|
-
why: 'the engine accessor (atomic-model builds its frames by hand)' }),
|
|
59
|
+
why: 'the engine accessor (atomic-model builds its frames by hand)', tier: 'scene' }),
|
|
47
60
|
Object.freeze({ specifier: '@descix/frqtl-web/kinds', libPath: 'frqtl/objects/kinds-entry.js',
|
|
48
|
-
why: 'the kind registry: a composed fork registers its renamed class (registerKind, kindNamed, kindOf, registeredKinds)' })
|
|
61
|
+
why: 'the kind registry: a composed fork registers its renamed class (registerKind, kindNamed, kindOf, registeredKinds)', tier: 'scene' }),
|
|
62
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/tagger.js', libPath: 'frqtl/objects/objects/tagger.js',
|
|
63
|
+
why: 'imported by DetectorWall — a forked copy imports it', tier: 'part-internal' }),
|
|
64
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/schema/index.js', libPath: 'frqtl/objects/schema/index.js',
|
|
65
|
+
why: 'imported by DetectorWall, SlitScreen, Wall — a forked copy imports it', tier: 'part-internal' }),
|
|
66
|
+
Object.freeze({ specifier: '@descix/frqtl-web/shim/render/heat-colormap.js', libPath: 'shim/render/heat-colormap.js',
|
|
67
|
+
why: 'imported by GraphingDetectorWall — a forked copy imports it', tier: 'part-internal' }),
|
|
68
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/schema/valueNormalizer.js', libPath: 'frqtl/objects/schema/valueNormalizer.js',
|
|
69
|
+
why: 'imported by LiveChart, Wall — a forked copy imports it', tier: 'part-internal' }),
|
|
70
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/Box.js', libPath: 'frqtl/objects/objects/Box.js',
|
|
71
|
+
why: 'imported by OvenBox — a forked copy imports it', tier: 'part-internal' }),
|
|
72
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/QuantumEmitter.js', libPath: 'frqtl/objects/objects/QuantumEmitter.js',
|
|
73
|
+
why: 'imported by OvenBox — a forked copy imports it', tier: 'part-internal' }),
|
|
74
|
+
Object.freeze({ specifier: '@descix/frqtl-web/shim/expr/ast-schema.js', libPath: 'shim/expr/ast-schema.js',
|
|
75
|
+
why: 'imported by OvenBox, Tunnel — a forked copy imports it', tier: 'part-internal' }),
|
|
76
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/actionCompiler.js', libPath: 'frqtl/objects/objects/actionCompiler.js',
|
|
77
|
+
why: 'imported by OvenBox, Tunnel — a forked copy imports it', tier: 'part-internal' }),
|
|
78
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/v1-replacements.js', libPath: 'frqtl/objects/v1-replacements.js',
|
|
79
|
+
why: 'imported by SlitLightSource — a forked copy imports it', tier: 'part-internal' }),
|
|
80
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/slitLinerScatter.js', libPath: 'frqtl/objects/objects/slitLinerScatter.js',
|
|
81
|
+
why: 'imported by SlitScreen, Tunnel — a forked copy imports it', tier: 'part-internal' }),
|
|
82
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/setup/setupSphereEmitter3D.js', libPath: 'frqtl/objects/setup/setupSphereEmitter3D.js',
|
|
83
|
+
why: 'imported by SphereEmitter — a forked copy imports it', tier: 'part-internal' }),
|
|
84
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/directedRedirectProgram.js', libPath: 'frqtl/objects/objects/directedRedirectProgram.js',
|
|
85
|
+
why: 'imported by Tunnel — a forked copy imports it', tier: 'part-internal' }),
|
|
86
|
+
Object.freeze({ specifier: '@descix/frqtl-web/compat/renderer/lo-drawn-center.js', libPath: 'frqtl/compat/renderer/lo-drawn-center.js',
|
|
87
|
+
why: 'imported by Tunnel — a forked copy imports it', tier: 'part-internal' }),
|
|
88
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/setup/setupTunnelPointExit.js', libPath: 'frqtl/objects/setup/setupTunnelPointExit.js',
|
|
89
|
+
why: 'imported by Tunnel — a forked copy imports it', tier: 'part-internal' }),
|
|
90
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/constants/WallBehaviors.js', libPath: 'frqtl/objects/constants/WallBehaviors.js',
|
|
91
|
+
why: 'imported by Wall — a forked copy imports it', tier: 'part-internal' }),
|
|
92
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/objects/absorbRadiation.js', libPath: 'frqtl/objects/objects/absorbRadiation.js',
|
|
93
|
+
why: 'imported by Wall — a forked copy imports it', tier: 'part-internal' }),
|
|
94
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/geometry/SlabFace.js', libPath: 'frqtl/objects/geometry/SlabFace.js',
|
|
95
|
+
why: 'imported by Wall — a forked copy imports it', tier: 'part-internal' })
|
|
49
96
|
]);
|
|
50
97
|
|
|
51
98
|
/** The scene doors a host page imports — the package's `./boot` export; never a sketch specifier. */
|
|
@@ -56,9 +103,11 @@ export const FRQTL_WEB_BOOT = Object.freeze({ specifier: '@descix/frqtl-web/boot
|
|
|
56
103
|
export const FRQTL_WEB_HOST_SPECIFIERS = Object.freeze([
|
|
57
104
|
FRQTL_WEB_BOOT,
|
|
58
105
|
Object.freeze({ specifier: '@descix/frqtl-web/registry', libPath: 'shim/registry/index.js',
|
|
59
|
-
why: 'the walk-through rungs, the showcase roster, the
|
|
106
|
+
why: 'the walk-through rungs, the showcase roster, the scene list (sceneGroups), projectOf' }),
|
|
60
107
|
Object.freeze({ specifier: '@descix/frqtl-web/project', libPath: 'shim/project/index.js',
|
|
61
|
-
why: 'the specifier table, the link site, sketchProject and the project identity' })
|
|
108
|
+
why: 'the specifier table, the link site, sketchProject and the project identity' }),
|
|
109
|
+
Object.freeze({ specifier: '@descix/frqtl-web/host-view', libPath: 'shim/host/index.js',
|
|
110
|
+
why: 'the DeSciX host-view declarer: a page declares its own view' })
|
|
62
111
|
]);
|
|
63
112
|
|
|
64
113
|
/** The package a self-reference names. */
|
|
@@ -92,6 +141,46 @@ export function libPathOf(specifier) {
|
|
|
92
141
|
'. (FAIL-LOUD.)');
|
|
93
142
|
}
|
|
94
143
|
|
|
144
|
+
/** The row naming `specifier` (libPathOf's rule: an exact row first, else a prefix row with a file under it), or null. */
|
|
145
|
+
function _rowOf(specifier) {
|
|
146
|
+
for (const row of FRQTL_WEB_SPECIFIERS) {
|
|
147
|
+
if (!_isPrefix(row) && row.specifier === specifier) { return row; }
|
|
148
|
+
}
|
|
149
|
+
for (const row of FRQTL_WEB_SPECIFIERS) {
|
|
150
|
+
if (_isPrefix(row) && specifier.startsWith(row.specifier) && specifier.length > row.specifier.length) { return row; }
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} specifier — a `@descix/frqtl-web/...` specifier
|
|
157
|
+
* @returns {string} its row's tier (a key of SPECIFIER_TIERS); THROWS on a specifier no row names
|
|
158
|
+
*/
|
|
159
|
+
export function tierOf(specifier) {
|
|
160
|
+
libPathOf(specifier); // the one validation (and its refusal text)
|
|
161
|
+
return _rowOf(specifier).tier;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {string} libPath — a lib-identity path (e.g. 'frqtl/parts/Wall.js', 'frqtl/objects/schema/index.js')
|
|
166
|
+
* @returns {string|null} the specifier whose row names that file, or null when no row does
|
|
167
|
+
*/
|
|
168
|
+
export function specifierOfLibPath(libPath) {
|
|
169
|
+
if (typeof libPath !== 'string' || !libPath) {
|
|
170
|
+
throw new TypeError('[specifiers] specifierOfLibPath(libPath): a lib-identity path (got ' + JSON.stringify(libPath) + '). (FAIL-LOUD.)');
|
|
171
|
+
}
|
|
172
|
+
for (const row of FRQTL_WEB_SPECIFIERS) {
|
|
173
|
+
if (!_isPrefix(row) && row.libPath === libPath) { return row.specifier; }
|
|
174
|
+
}
|
|
175
|
+
for (const row of FRQTL_WEB_SPECIFIERS) {
|
|
176
|
+
if (_isPrefix(row) && libPath.startsWith(row.libPath)) {
|
|
177
|
+
const rest = libPath.slice(row.libPath.length);
|
|
178
|
+
if (rest && rest.indexOf('/') === -1) { return row.specifier + rest; }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
95
184
|
/** An import map resolves an address only if it is an absolute URL or starts with '/', './' or '../'. */
|
|
96
185
|
function _isMapAddress(url) {
|
|
97
186
|
if (/^(\/|\.\/|\.\.\/)/.test(url)) { return true; }
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* EXHIBITS is the ONE source for the front-page tiles, their GYM deep links and the scene card:
|
|
9
9
|
* nine rows in front-page order, each
|
|
10
10
|
* `{ kind, id, mode, title, tile, what, why, how, controls, still }`.
|
|
11
|
-
* - `kind` is 'rung' (a walk-through rung, lib/shim/registry/walkthrough.js)
|
|
12
|
-
*
|
|
13
|
-
* - `mode` is a rung's declared mode (null for a single-mode rung
|
|
11
|
+
* - `kind` is 'rung' (a walk-through rung, lib/shim/registry/walkthrough.js); `id` resolves there. A row of
|
|
12
|
+
* any other kind THROWS naming the rung form.
|
|
13
|
+
* - `mode` is a rung's declared mode (null for a single-mode rung).
|
|
14
14
|
* - `title` is the tile's and the card's title; `tile` is the published one-sentence tile line.
|
|
15
15
|
* - `what` (What you are seeing), `why` (Why it is impossible the usual way) and `how` (How FRQTL
|
|
16
16
|
* gets it) are the card's copy, CARD_PARTS, every one REQUIRED on every row.
|
|
@@ -47,14 +47,13 @@
|
|
|
47
47
|
* @module exhibits
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
|
-
import { EXPERIMENTS, experimentById, experimentIs3D } from './experiments.js';
|
|
51
50
|
import {
|
|
52
51
|
WALKTHROUGH, walkthroughById, walkthroughModes, defaultWalkthroughModeId, walkthroughIs3D, effectiveRenderState
|
|
53
52
|
} from './walkthrough.js';
|
|
54
53
|
import { assertCopyText, assertCopyParagraphs } from './copy-gate.js';
|
|
55
54
|
|
|
56
55
|
/** The scene kinds a roster row (and a player deep link) can name. */
|
|
57
|
-
export const EXHIBIT_KINDS = Object.freeze(['rung'
|
|
56
|
+
export const EXHIBIT_KINDS = Object.freeze(['rung']);
|
|
58
57
|
|
|
59
58
|
/** The copy parts a run-once CARD shows, in order; every roster row carries all of them. */
|
|
60
59
|
export const CARD_PARTS = Object.freeze(['what', 'why', 'how']);
|
|
@@ -287,15 +286,6 @@ function rungOrThrow(id) {
|
|
|
287
286
|
return rung;
|
|
288
287
|
}
|
|
289
288
|
|
|
290
|
-
function experimentOrThrow(id) {
|
|
291
|
-
const entry = experimentById(id);
|
|
292
|
-
if (!entry) {
|
|
293
|
-
throw new Error('[exhibits] unknown experiment "' + id + '" — declared experiments: [' +
|
|
294
|
-
EXPERIMENTS.map((e) => e.id).join(', ') + ']. Add it to lib/shim/registry/experiments.js first.');
|
|
295
|
-
}
|
|
296
|
-
return entry;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
289
|
/** A rung's effective mode: the declared one (validated), or null for a single-mode rung. */
|
|
300
290
|
function rungMode(rung, modeId) {
|
|
301
291
|
const eff = (modeId !== undefined && modeId !== null) ? modeId : defaultWalkthroughModeId(rung);
|
|
@@ -322,8 +312,8 @@ function assertNotPasted(part, where) {
|
|
|
322
312
|
}
|
|
323
313
|
|
|
324
314
|
/**
|
|
325
|
-
* assertRoster — the load-time validator: every row
|
|
326
|
-
*
|
|
315
|
+
* assertRoster — the load-time validator: every row is a rung whose id resolves in the walk-through
|
|
316
|
+
* registry, its mode is declared, no scene repeats, no unknown field, every
|
|
327
317
|
* row has a title, a tile line and every CARD_PARTS part, `controls` is non-empty when present, all
|
|
328
318
|
* copy passes the copy gate, and no part pastes the invitation or an intro paragraph. Exported so a
|
|
329
319
|
* test can show each refusal on a planted roster. @returns the normalized, frozen rows (with `order`).
|
|
@@ -337,17 +327,10 @@ export function assertRoster(rows) {
|
|
|
337
327
|
}
|
|
338
328
|
});
|
|
339
329
|
if (EXHIBIT_KINDS.indexOf(row.kind) === -1) {
|
|
340
|
-
throw new Error('[exhibits] row ' + order + ' kind ' + JSON.stringify(row.kind) + ' —
|
|
341
|
-
|
|
342
|
-
let mode = null;
|
|
343
|
-
if (row.kind === 'rung') {
|
|
344
|
-
mode = rungMode(rungOrThrow(row.id), row.mode);
|
|
345
|
-
} else {
|
|
346
|
-
experimentOrThrow(row.id);
|
|
347
|
-
if (row.mode !== undefined) {
|
|
348
|
-
throw new Error('[exhibits] experiment row "' + row.id + '" names a mode — only a rung has modes.');
|
|
349
|
-
}
|
|
330
|
+
throw new Error('[exhibits] row ' + order + ' kind ' + JSON.stringify(row.kind) + ' — an exhibit is a walk-through rung: ' +
|
|
331
|
+
'{ kind: \'rung\', id: <a lib/shim/registry/walkthrough.js id>, mode? } (EXHIBIT_KINDS: ' + EXHIBIT_KINDS.join(', ') + ').');
|
|
350
332
|
}
|
|
333
|
+
const mode = rungMode(rungOrThrow(row.id), row.mode);
|
|
351
334
|
if (row.title === undefined || row.title === null) {
|
|
352
335
|
throw new Error('[exhibits] row "' + row.id + '" has no title — every card is titled.');
|
|
353
336
|
}
|
|
@@ -380,15 +363,14 @@ export function exhibitsOfKind(kind) {
|
|
|
380
363
|
* The roster row for a scene, or null when the scene is not on the roster. A null is a DECLARED
|
|
381
364
|
* outcome the caller records (the player plays such a scene with no card: 'not-offered-no-roster-row').
|
|
382
365
|
* The scene itself must exist: an unknown kind, id or mode throws.
|
|
383
|
-
* @param {{kind: 'rung'
|
|
366
|
+
* @param {{kind: 'rung', id: string, modeId?: string|null}} scene
|
|
384
367
|
*/
|
|
385
368
|
export function exhibitFor(scene) {
|
|
386
369
|
const kind = scene && scene.kind;
|
|
387
370
|
if (EXHIBIT_KINDS.indexOf(kind) === -1) {
|
|
388
371
|
throw new Error('[exhibits] unknown scene kind ' + JSON.stringify(kind) + ' — EXHIBIT_KINDS: ' + EXHIBIT_KINDS.join(', ') + '.');
|
|
389
372
|
}
|
|
390
|
-
|
|
391
|
-
if (kind === 'rung') { mode = rungMode(rungOrThrow(scene.id), scene.modeId); } else { experimentOrThrow(scene.id); }
|
|
373
|
+
const mode = rungMode(rungOrThrow(scene.id), scene.modeId);
|
|
392
374
|
for (let i = 0; i < EXHIBITS.length; i++) {
|
|
393
375
|
const e = EXHIBITS[i];
|
|
394
376
|
if (e.kind === kind && e.id === scene.id && e.mode === mode) { return e; }
|
|
@@ -414,21 +396,20 @@ export const JOYSTICK_EXPLAINER = Object.freeze([
|
|
|
414
396
|
* sceneKey — the run-once seen-key of a scene: kind, id and, for a multi-mode rung, its EFFECTIVE
|
|
415
397
|
* mode (the one normalizer, rungMode); never a bare id, so it cannot collide with a tab key.
|
|
416
398
|
* Throws for an unknown kind, id or mode.
|
|
417
|
-
* @param {{kind: 'rung'
|
|
399
|
+
* @param {{kind: 'rung', id: string, modeId?: string|null}} scene
|
|
418
400
|
*/
|
|
419
401
|
export function sceneKey(scene) {
|
|
420
402
|
const kind = scene && scene.kind;
|
|
421
403
|
if (EXHIBIT_KINDS.indexOf(kind) === -1) {
|
|
422
404
|
throw new Error('[exhibits] unknown scene kind ' + JSON.stringify(kind) + ' — EXHIBIT_KINDS: ' + EXHIBIT_KINDS.join(', ') + '.');
|
|
423
405
|
}
|
|
424
|
-
|
|
425
|
-
if (kind === 'rung') { mode = rungMode(rungOrThrow(scene.id), scene.modeId); } else { experimentOrThrow(scene.id); }
|
|
406
|
+
const mode = rungMode(rungOrThrow(scene.id), scene.modeId);
|
|
426
407
|
return 'scene-' + kind + '-' + scene.id + (mode ? '-' + mode : '');
|
|
427
408
|
}
|
|
428
409
|
|
|
429
410
|
/**
|
|
430
411
|
* The run-once card of a ROSTER row: title, then `what`, `why`, `how`, `controls` when present, the
|
|
431
|
-
* JOYSTICK_EXPLAINER on a 3D scene (3D read from walkthroughIs3D
|
|
412
|
+
* JOYSTICK_EXPLAINER on a 3D scene (3D read from walkthroughIs3D), and
|
|
432
413
|
* SHOWCASE_INVITE last, by reference. Composed from the row and the showcase constants only: no
|
|
433
414
|
* registry prose ever. The bag's { seenKey, title, paragraphs } is what lib/components/run-once-card
|
|
434
415
|
* renders.
|
|
@@ -440,7 +421,7 @@ export function showcaseCard(row) {
|
|
|
440
421
|
throw new Error('[exhibits] showcaseCard takes a roster row (an EXHIBITS element, e.g. exhibitFor(scene)); got ' +
|
|
441
422
|
JSON.stringify(row && { kind: row.kind, id: row.id }) + '.');
|
|
442
423
|
}
|
|
443
|
-
const is3D =
|
|
424
|
+
const is3D = walkthroughIs3D(row.id, row.mode);
|
|
444
425
|
let paragraphs = CARD_PARTS.map((p) => Object.freeze([row[p]]));
|
|
445
426
|
if (row.controls !== null) { paragraphs = paragraphs.concat([Object.freeze([row.controls])]); }
|
|
446
427
|
if (is3D) { paragraphs = paragraphs.concat(JOYSTICK_EXPLAINER); }
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* walkthrough.js the walk-through rungs: walkthroughById, effectiveRenderState, modes
|
|
11
11
|
* exhibits.js the showcase roster
|
|
12
|
-
*
|
|
13
|
-
* scene-source.js
|
|
12
|
+
* scene-roster.js sceneGroups() — THE scene list both views show (the showcase, then the walk-through groups)
|
|
13
|
+
* scene-source.js projectOf(id, { mode }) — a rung as a project folder + its renderState (fetch in a page, file read in node)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
export * from './walkthrough.js';
|
|
17
17
|
export * from './exhibits.js';
|
|
18
|
-
export * from './
|
|
18
|
+
export * from './scene-roster.js';
|
|
19
19
|
export * from './scene-source.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scene-roster.js — THE scene list both views show (the GYM's select and STUDIO's dropdown): one owner, read by
|
|
5
|
+
* both, so the two lists are the same rows in the same order.
|
|
6
|
+
*
|
|
7
|
+
* SHOWCASE_GROUP the label of the showcase group ('Impossible things')
|
|
8
|
+
* sceneGroups() → [{ label, rows: [{ id, title }] }] — the showcase roster first (EXHIBITS, in tile order,
|
|
9
|
+
* each row its exhibit title), then every walk-through rung in WALKTHROUGH order under its
|
|
10
|
+
* lab group (walkthroughGroup), with NO rung removed from its own group: a showcase rung
|
|
11
|
+
* appears twice, and both rows name the same rung (a deep link selects it either way).
|
|
12
|
+
*
|
|
13
|
+
* A row is a scene id a host opens by ?walkthrough=<id>. A host keeps its own non-scene rows (STUDIO's blank world
|
|
14
|
+
* and saved projects) around these groups; the GYM's default scene is the first row.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EXHIBITS } from './exhibits.js';
|
|
18
|
+
import { WALKTHROUGH, walkthroughGroup } from './walkthrough.js';
|
|
19
|
+
|
|
20
|
+
/** The showcase group's label. */
|
|
21
|
+
export const SHOWCASE_GROUP = 'Impossible things';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @returns {Array<{label: string, rows: Array<{id: string, title: string}>}>} a new array on every call
|
|
25
|
+
*/
|
|
26
|
+
export function sceneGroups() {
|
|
27
|
+
var groups = [{ label: SHOWCASE_GROUP, rows: EXHIBITS.map(function (e) { return { id: e.id, title: e.title }; }) }];
|
|
28
|
+
WALKTHROUGH.forEach(function (r) {
|
|
29
|
+
var g = walkthroughGroup(r);
|
|
30
|
+
var last = groups[groups.length - 1];
|
|
31
|
+
if (groups.length === 1 || last.label !== g) { groups.push({ label: g, rows: [] }); last = groups[groups.length - 1]; }
|
|
32
|
+
last.rows.push({ id: r.id, title: r.title });
|
|
33
|
+
});
|
|
34
|
+
return groups;
|
|
35
|
+
}
|
|
@@ -1,38 +1,47 @@
|
|
|
1
1
|
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* scene-source.js — the ONE reader of a scene
|
|
4
|
+
* scene-source.js — the ONE reader of a registry scene as a PROJECT, in a page AND in node.
|
|
5
5
|
*
|
|
6
|
-
* A scene is ONE file, `lib/frqtl/scenes/<file>.js`: a
|
|
7
|
-
*
|
|
8
|
-
* its frqtl-relative PATH (`source: 'scenes/<file>.js'`), never its text. Every consumer
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* A scene is ONE sketch file, `lib/frqtl/scenes/<file>.js`: a module whose default export is
|
|
7
|
+
* `buildExperiment(exp)`, holding the whole scene (world, parts, render intent). A registry row carries
|
|
8
|
+
* its frqtl-relative PATH (`source: 'scenes/<file>.js'`), never its text. Every consumer that needs the
|
|
9
|
+
* scene (the GYM's rung frame, STUDIO, the hero, a package consumer of `@descix/frqtl-web/registry`) asks
|
|
10
|
+
* here, so the id → path → URL → project chain exists once.
|
|
11
11
|
*
|
|
12
|
-
* scenePath(rung)
|
|
13
|
-
*
|
|
12
|
+
* scenePath(rung) → 'scenes/<file>.js' (THROWS on any other shape)
|
|
13
|
+
* projectOf(scene, { router?, fetch?, mode? })
|
|
14
|
+
* → Promise<{ manifest, files, renderState }>
|
|
15
|
+
* files { 'frqtl.json': serializeManifest(manifestFor({ name: row.id, entry: 'sketch.js' })),
|
|
16
|
+
* 'sketch.js': <the scene file's text> } — the folder a host boots
|
|
17
|
+
* (bootProject(mount, { files, mint }, { renderState }), no entry needed)
|
|
18
|
+
* manifest that frqtl.json's normalized bag
|
|
19
|
+
* renderState effectiveRenderState(row, mode) — the registry's one derivation, ferried BESIDE the folder
|
|
20
|
+
* (boot facts never enter frqtl.json or the project identity)
|
|
14
21
|
*
|
|
15
|
-
* `scene` is a rung id (resolved by `walkthroughById`; an unknown id REJECTS naming the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
22
|
+
* `scene` is a rung id (resolved by `walkthroughById`; an unknown id REJECTS naming the declared ids) or a
|
|
23
|
+
* row (`{id, source}`, e.g. `EMPTY_SCENE`). The URL comes from a router, the one path authority
|
|
24
|
+
* (lib/shim/engine/router.js, `scenes/` profile key):
|
|
18
25
|
* - `opts.router` — the host's own (the IDE's engineRouter); a host always passes it.
|
|
19
|
-
* - absent — the PACKAGE's: the dist layout anchored at the dist root this module ships
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* read through node's fs (`process.getBuiltinModule`), anything else is fetched.
|
|
26
|
+
* - absent — the PACKAGE's: the dist layout anchored at the dist root this module ships in
|
|
27
|
+
* (`PACKAGE_ROOT_FROM_HERE`, dist/shim/registry/ → dist/). This is the `@descix/frqtl-web/registry`
|
|
28
|
+
* door's reader; in the source tree it names a file that is not there and rejects.
|
|
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).
|
|
25
31
|
*
|
|
26
|
-
* FAIL-LOUD: an unknown id, a row without a scenes/ path, a non-200 response, an
|
|
27
|
-
* file or an empty body REJECTS naming the scene and the URL. A blank scene is never a result.
|
|
32
|
+
* FAIL-LOUD: an unknown id, a row without a scenes/ path, an undeclared mode, a non-200 response, an
|
|
33
|
+
* unreadable file or an empty body REJECTS naming the scene and the URL. A blank scene is never a result.
|
|
28
34
|
*
|
|
29
35
|
* Zero physics: this moves text. The scene's physics is whatever its file composes.
|
|
30
36
|
*/
|
|
31
37
|
|
|
32
38
|
'use strict';
|
|
33
39
|
|
|
34
|
-
import { WALKTHROUGH, walkthroughById } from './walkthrough.js';
|
|
40
|
+
import { WALKTHROUGH, walkthroughById, effectiveRenderState } from './walkthrough.js';
|
|
35
41
|
import { engineRouter } from '../engine/router.js';
|
|
42
|
+
import { MANIFEST_FILE, manifestFor, serializeManifest } from '../project/manifest.js';
|
|
43
|
+
import { SKETCH_ENTRY } from '../project/link.js';
|
|
44
|
+
import { readText } from '../project/read-text.js';
|
|
36
45
|
|
|
37
46
|
/** The one path prefix a scene file lives under (the router's `scenes/` profile key). */
|
|
38
47
|
export var SCENES_PREFIX = 'scenes/';
|
|
@@ -47,11 +56,11 @@ var PACKAGE_ROOT_FROM_HERE = '../../';
|
|
|
47
56
|
|
|
48
57
|
/**
|
|
49
58
|
* The blank world — STUDIO's "Empty Universe" — is itself a scene file (the empty page states no
|
|
50
|
-
* world of its own). A row-shaped handle so it reads through the same `
|
|
59
|
+
* world of its own). A row-shaped handle so it reads through the same `projectOf`.
|
|
51
60
|
*/
|
|
52
61
|
export var EMPTY_SCENE = Object.freeze({ id: 'empty', source: 'scenes/empty.js' });
|
|
53
62
|
|
|
54
|
-
var OPT_KEYS = ['router', 'fetch'];
|
|
63
|
+
var OPT_KEYS = ['router', 'fetch', 'mode'];
|
|
55
64
|
|
|
56
65
|
/**
|
|
57
66
|
* @param {{id?:string, source?:string}} rung — a registry row.
|
|
@@ -77,7 +86,7 @@ function _rowOf(scene) {
|
|
|
77
86
|
return rung;
|
|
78
87
|
}
|
|
79
88
|
if (!scene || typeof scene !== 'object') {
|
|
80
|
-
throw new Error('[scene-source]
|
|
89
|
+
throw new Error('[scene-source] projectOf takes a rung id or a registry row, got ' +
|
|
81
90
|
JSON.stringify(scene) + '. (FAIL-LOUD.)');
|
|
82
91
|
}
|
|
83
92
|
return scene;
|
|
@@ -88,70 +97,45 @@ function _packageRouter() {
|
|
|
88
97
|
return engineRouter({ mode: 'cdn', base: root });
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
function _readFileUrl(url, id) {
|
|
92
|
-
var proc = globalThis.process;
|
|
93
|
-
var getBuiltin = proc && proc.getBuiltinModule;
|
|
94
|
-
if (typeof getBuiltin !== 'function') {
|
|
95
|
-
throw new Error('[scene-source] scene "' + id + '" resolves to ' + url + ', a file: URL; reading ' +
|
|
96
|
-
'one needs node\'s process.getBuiltinModule (Node 20.16+ / 22.3+). In a page, serve the ' +
|
|
97
|
-
'package over http. (FAIL-LOUD.)');
|
|
98
|
-
}
|
|
99
|
-
var fs = getBuiltin('node:fs');
|
|
100
|
-
var fileURLToPath = getBuiltin('node:url').fileURLToPath;
|
|
101
|
-
return fs.promises.readFile(fileURLToPath(url), 'utf8').catch(function (e) {
|
|
102
|
-
throw new Error('[scene-source] scene "' + id + '" — reading ' + url + ' failed: ' +
|
|
103
|
-
(e && e.message) + '. (FAIL-LOUD.)');
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function _fetchText(url, id, fetchImpl) {
|
|
108
|
-
var f = fetchImpl || (typeof fetch === 'function' ? fetch : null);
|
|
109
|
-
if (!f) {
|
|
110
|
-
throw new Error('[scene-source] no fetch available to load ' + url + '.');
|
|
111
|
-
}
|
|
112
|
-
return Promise.resolve(f(url)).then(function (res) {
|
|
113
|
-
if (!res || !res.ok) {
|
|
114
|
-
throw new Error('[scene-source] scene "' + id + '" — GET ' + url + ' answered ' +
|
|
115
|
-
(res ? res.status : 'nothing') + '. (FAIL-LOUD.)');
|
|
116
|
-
}
|
|
117
|
-
return res.text();
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
100
|
/**
|
|
122
|
-
*
|
|
101
|
+
* A registry scene as the project folder a host boots, with the host's boot bag beside it.
|
|
123
102
|
* @param {string|{id:string, source:string}} scene — a rung id, or a registry row.
|
|
124
|
-
* @param {{router?:{resolve:function(string):string}, fetch?:function}} [opts]
|
|
103
|
+
* @param {{router?:{resolve:function(string):string}, fetch?:function, mode?:string|null}} [opts]
|
|
125
104
|
* router — the host's engineRouter (absent: the package's own dist root);
|
|
126
|
-
* fetch — the fetch used for a non-file URL (defaults to the global; injectable for tests)
|
|
127
|
-
*
|
|
105
|
+
* fetch — the fetch used for a non-file URL (defaults to the global; injectable for tests);
|
|
106
|
+
* mode — a declared walk-through mode (absent: the rung's default) for the renderState.
|
|
107
|
+
* @returns {Promise<{manifest: object, files: Object<string,string>, renderState: object}>}
|
|
128
108
|
*/
|
|
129
|
-
export function
|
|
109
|
+
export function projectOf(scene, opts) {
|
|
130
110
|
return Promise.resolve().then(function () {
|
|
131
111
|
opts = opts || {};
|
|
132
112
|
Object.keys(opts).forEach(function (k) {
|
|
133
113
|
if (OPT_KEYS.indexOf(k) === -1) {
|
|
134
|
-
throw new Error('[scene-source]
|
|
114
|
+
throw new Error('[scene-source] projectOf has no option "' + k + '" — options: ' +
|
|
135
115
|
OPT_KEYS.join(', ') + '. (FAIL-LOUD.)');
|
|
136
116
|
}
|
|
137
117
|
});
|
|
138
118
|
var row = _rowOf(scene);
|
|
139
119
|
var rel = scenePath(row);
|
|
120
|
+
var renderState = effectiveRenderState(row, opts.mode); // an undeclared mode rejects before the read
|
|
140
121
|
var router = opts.router || _packageRouter();
|
|
141
122
|
if (typeof router.resolve !== 'function') {
|
|
142
123
|
throw new Error('[scene-source] opts.router has no resolve() — pass an engineRouter ' +
|
|
143
124
|
'(lib/shim/engine/router.js) to resolve ' + rel + '. (FAIL-LOUD.)');
|
|
144
125
|
}
|
|
145
126
|
var url = router.resolve(rel);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
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) {
|
|
150
130
|
if (typeof t !== 'string' || !t.trim()) {
|
|
151
131
|
throw new Error('[scene-source] scene "' + row.id + '" — ' + url +
|
|
152
132
|
' has an empty body. (FAIL-LOUD.)');
|
|
153
133
|
}
|
|
154
|
-
|
|
134
|
+
var manifest = manifestFor({ name: row.id, entry: SKETCH_ENTRY });
|
|
135
|
+
var files = {};
|
|
136
|
+
files[MANIFEST_FILE] = serializeManifest(manifest);
|
|
137
|
+
files[SKETCH_ENTRY] = t;
|
|
138
|
+
return { manifest: manifest, files: files, renderState: renderState };
|
|
155
139
|
});
|
|
156
140
|
});
|
|
157
141
|
}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*
|
|
12
12
|
* A rung row carries the PATH of its scene, never its text: each scene is ONE sketch
|
|
13
13
|
* file, `lib/frqtl/scenes/<file>.js` — a dual-mode `buildExperiment(exp)` module that
|
|
14
|
-
* holds the whole scene.
|
|
15
|
-
* (./scene-source.js:
|
|
16
|
-
* a non-200 or an empty body). Rungs
|
|
14
|
+
* holds the whole scene. A host reads it as a project through `projectOf(id | rung, opts)`
|
|
15
|
+
* (./scene-source.js: frqtl.json + sketch.js, with effectiveRenderState beside it; a fetch in a
|
|
16
|
+
* page, a file read in node; FAIL-LOUD on an unknown id, a non-200 or an empty body). Rungs
|
|
17
|
+
* that share one physics name the SAME file.
|
|
17
18
|
*
|
|
18
19
|
* ── Rung descriptor ──────────────────────────────────────────────────────────
|
|
19
20
|
* id : stable dropdown id (the `walkthrough:<id>` selection value)
|
|
@@ -773,8 +774,7 @@ export function walkthroughById(id) {
|
|
|
773
774
|
});
|
|
774
775
|
|
|
775
776
|
// ── is3D normalization (E-W0a; mode-aware S3/W2) ─────────────────────────────
|
|
776
|
-
// STUDIO
|
|
777
|
-
// 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
|
|
778
778
|
// 3D-ness is ALREADY declared by its (possibly mode-merged) `renderState.renderer` —
|
|
779
779
|
// three IS the 3D renderer, the ONE display fact the load path threads — so `is3D` here is
|
|
780
780
|
// DERIVED through `effectiveRenderState` for the rung's DEFAULT mode, never a
|
|
@@ -786,7 +786,7 @@ WALKTHROUGH.forEach(function (rung) {
|
|
|
786
786
|
|
|
787
787
|
/**
|
|
788
788
|
* walkthroughIs3D(id, modeId) — the ONE mount-time 3D predicate for a walkthrough
|
|
789
|
-
* rung
|
|
789
|
+
* rung. Derived through
|
|
790
790
|
* `effectiveRenderState` (the single derivation point) for the given mode, or the
|
|
791
791
|
* rung's default mode when `modeId` is omitted. Returns false for an unknown id;
|
|
792
792
|
* THROWS (via effectiveRenderState) for a known id with an undeclared mode.
|