@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
package/dist/frqtl/parts/Wall.js
CHANGED
|
@@ -178,10 +178,20 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function _rectFromOpts(opts) {
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
181
|
+
// ONE spelling of the z-slab: a rect that already carries z/depth (the 6-element form, or an
|
|
182
|
+
// object rect with z/d) beside the z / d / depth props would be two statements of one fact,
|
|
183
|
+
// one of them silently ignored. Refused, naming both.
|
|
184
|
+
var slabProps = ['z', 'd', 'depth'].filter(function (k) { return opts[k] !== undefined; });
|
|
185
|
+
if (slabProps.length && opts.rect && (Array.isArray(opts.rect) ? opts.rect.length === 6
|
|
186
|
+
: (opts.rect.z !== undefined || opts.rect.d !== undefined))) {
|
|
187
|
+
throw new Error('[Wall] rect is already the z/depth form ' + ValueNormalizer.rectTupleText(6) +
|
|
188
|
+
' and the props also give ' + slabProps.join(', ') + ' — write the z-slab ONE way: the 6-element rect, ' +
|
|
189
|
+
'or a 4-element rect beside z/depth. (FAIL-LOUD.)');
|
|
190
|
+
}
|
|
191
|
+
// Normalize an OBJECT-bag rect ({left,top,w,h} / {left,top,z,w,h,d} — the shape a live LO's
|
|
192
|
+
// rect reads as) to the canonical ARRAY form FIRST, so the 3D checks below
|
|
193
|
+
// (_rectIs3D/_rectDepth are Array-based) see such a 3D wall as 3D. Mirrors LargeObject's
|
|
194
|
+
// own rectToArray.
|
|
185
195
|
if (opts.rect && !Array.isArray(opts.rect) && opts.rect.left !== undefined && opts.rect.w !== undefined) {
|
|
186
196
|
var ob = opts.rect;
|
|
187
197
|
var arr = (ob.z !== undefined || ob.d !== undefined)
|
|
@@ -382,7 +392,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
382
392
|
self._radiationTickFn = tickFn;
|
|
383
393
|
// The radiator OWNS the live emissivity (including the default when none was
|
|
384
394
|
// stated). Read the facade prop back off it rather than leaving it undefined —
|
|
385
|
-
// captureProps
|
|
395
|
+
// captureProps (the parts writer) and the Inspector read `wall.emissivity`, and a facade that
|
|
386
396
|
// says nothing while the radiator says 0.5 is a second, wrong answer.
|
|
387
397
|
self.emissivity = tickFn.emissivity();
|
|
388
398
|
if (universe && Array.isArray(universe.experimentTickFunctions)) {
|
|
@@ -399,10 +409,10 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
399
409
|
function Wall(universe, opts) {
|
|
400
410
|
opts = opts || {};
|
|
401
411
|
this.universe = universe;
|
|
402
|
-
this._schemaKind =
|
|
412
|
+
this._schemaKind = this.constructor.name;
|
|
403
413
|
|
|
404
|
-
// A `preset` expands to the v2 descriptor fields (construction sugar;
|
|
405
|
-
//
|
|
414
|
+
// A `preset` expands to the v2 descriptor fields (construction sugar; the parts
|
|
415
|
+
// writer captures the EXPANDED fields, never the preset name).
|
|
406
416
|
if (opts.preset) {
|
|
407
417
|
var expanded = expandWallPreset(opts.preset);
|
|
408
418
|
var merged = {}; Object.keys(opts).forEach(function (k) { if (k !== 'preset') { merged[k] = opts[k]; } });
|
|
@@ -437,7 +447,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
437
447
|
// ── Descriptor-field read surface (captureProps / Inspector) ─────────────
|
|
438
448
|
// captureProps(wall, WallDescriptor) reads wall[field.name] directly; these
|
|
439
449
|
// getters expose the LIVE inner-LO state so a dragged/edited wall Saves at its
|
|
440
|
-
// current state (the
|
|
450
|
+
// current state (what the parts writer captures). `rect` is the single
|
|
441
451
|
// captured position authority (the LO's own Rectangle, whose toJSON pins the
|
|
442
452
|
// {left,top,w,h} wire shape).
|
|
443
453
|
Object.defineProperty(Wall.prototype, 'rect', { get: function () { return this.lo ? this.lo.rect : undefined; } });
|
|
@@ -573,7 +583,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
573
583
|
*/
|
|
574
584
|
Wall.prototype.setV2Prop = function (prop, value) {
|
|
575
585
|
// EMISSIVITY — live radiating efficiency. The running radiator OWNS the value (one
|
|
576
|
-
// live copy); the facade prop is a ferried mirror for the Inspector
|
|
586
|
+
// live copy); the facade prop is a ferried mirror for the Inspector and capture read.
|
|
577
587
|
// Validation (fullness fraction in (0,1]) belongs to the owner and throws from there.
|
|
578
588
|
if (prop === 'emissivity') {
|
|
579
589
|
if (this.collisionBehavior !== 'absorb' || !this._radiationTickFn) {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { FrqtlControlOverlay, FrqtlControlsPanel, LogicGate, SphereEmitter, Trace } from '@descix/frqtl-web/parts';
|
|
23
23
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
24
24
|
|
|
25
|
+
// Code-declared on purpose: the emitter centres and rail traces derive from the gates' port geometry and RAIL (a knob rewrites it), and a Trace has no kind — a literal block would restate derived geometry by hand.
|
|
25
26
|
export const parts = {};
|
|
26
27
|
|
|
27
28
|
export default function buildExperiment(exp) {
|
|
@@ -67,9 +67,10 @@
|
|
|
67
67
|
// 2D ONLY, deliberately. There is no `modes` strip and no `exp.is3D` guard: these
|
|
68
68
|
// rungs declare one world each. A second dimensional variant would be a separate
|
|
69
69
|
// recipe declaration, not a mode.
|
|
70
|
-
// THE PAIR. `dse-two-emitters.js` is the SAME apparatus with
|
|
71
|
-
//
|
|
72
|
-
// fails if
|
|
70
|
+
// THE PAIR. `dse-two-emitters.js` is the SAME apparatus with TWO source entries. The two files differ ONLY in the
|
|
71
|
+
// title lines, this note, and their `source*` entries of `parts` (with the world call naming the first
|
|
72
|
+
// of them) — lib/frqtl/scenes/test/scene-files.test.mjs fails if anything else diverges, so the A/B the
|
|
73
|
+
// pair exists to make stays an A/B.
|
|
73
74
|
|
|
74
75
|
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
75
76
|
|
|
@@ -116,7 +117,34 @@ const detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
|
116
117
|
// the photoelectric experiment, and it saturates.
|
|
117
118
|
const detectorPreset = 'transparent-probe';
|
|
118
119
|
|
|
120
|
+
const lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
121
|
+
const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
122
|
+
const wlScale = Math.floor(fwl / 4); // 100
|
|
123
|
+
const cy = Math.round(H / 2); // 200 — the source plane's midline
|
|
124
|
+
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
125
|
+
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
126
|
+
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
127
|
+
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
128
|
+
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
129
|
+
// exactly where the one-source rung has no peak at all.
|
|
130
|
+
const sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
131
|
+
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
132
|
+
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
133
|
+
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
134
|
+
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
135
|
+
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
136
|
+
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
137
|
+
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
138
|
+
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
139
|
+
// forward component. One sink, not two.
|
|
140
|
+
const SRC = {
|
|
141
|
+
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
142
|
+
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
143
|
+
burstSize: 8, emitEveryTicks: 1
|
|
144
|
+
};
|
|
145
|
+
|
|
119
146
|
export const parts = {
|
|
147
|
+
source: [SphereEmitter, { ...SRC, center: [srcX, cy] }],
|
|
120
148
|
// FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
|
|
121
149
|
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
122
150
|
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
@@ -134,41 +162,11 @@ export const parts = {
|
|
|
134
162
|
};
|
|
135
163
|
|
|
136
164
|
export default function buildExperiment(exp) {
|
|
137
|
-
var NSRC = 1;
|
|
138
|
-
var lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
139
|
-
var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
140
|
-
var wlScale = Math.floor(fwl / 4); // 100
|
|
141
165
|
if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
|
|
142
166
|
throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
|
|
143
167
|
'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
144
168
|
}
|
|
145
|
-
|
|
146
|
-
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
147
|
-
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
148
|
-
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
149
|
-
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
150
|
-
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
151
|
-
// exactly where the one-source rung has no peak at all.
|
|
152
|
-
var sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
153
|
-
var srcYs = (NSRC === 2) ? [cy - sep / 2, cy + sep / 2] : [cy];
|
|
154
|
-
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
155
|
-
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
156
|
-
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
157
|
-
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
158
|
-
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
159
|
-
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
160
|
-
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
161
|
-
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
162
|
-
// forward component. One sink, not two.
|
|
163
|
-
var SRC = {
|
|
164
|
-
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
165
|
-
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
166
|
-
burstSize: 8, emitEveryTicks: 1
|
|
167
|
-
};
|
|
168
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
169
|
-
for (var si = 0; si < srcYs.length; si++) {
|
|
170
|
-
exp.addObject('SphereEmitter', Object.assign({}, SRC, { center: [srcX, Math.round(srcYs[si])] }));
|
|
171
|
-
}
|
|
169
|
+
exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
172
170
|
const p = exp.declare(parts);
|
|
173
171
|
// ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
|
|
174
172
|
// The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
|
|
@@ -67,9 +67,10 @@
|
|
|
67
67
|
// 2D ONLY, deliberately. There is no `modes` strip and no `exp.is3D` guard: these
|
|
68
68
|
// rungs declare one world each. A second dimensional variant would be a separate
|
|
69
69
|
// recipe declaration, not a mode.
|
|
70
|
-
// THE PAIR. `dse-one-emitter.js` is the SAME apparatus with
|
|
71
|
-
//
|
|
72
|
-
// fails if
|
|
70
|
+
// THE PAIR. `dse-one-emitter.js` is the SAME apparatus with ONE source entry. The two files differ ONLY in the
|
|
71
|
+
// title lines, this note, and their `source*` entries of `parts` (with the world call naming the first
|
|
72
|
+
// of them) — lib/frqtl/scenes/test/scene-files.test.mjs fails if anything else diverges, so the A/B the
|
|
73
|
+
// pair exists to make stays an A/B.
|
|
73
74
|
|
|
74
75
|
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
75
76
|
|
|
@@ -116,7 +117,35 @@ const detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
|
116
117
|
// the photoelectric experiment, and it saturates.
|
|
117
118
|
const detectorPreset = 'transparent-probe';
|
|
118
119
|
|
|
120
|
+
const lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
121
|
+
const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
122
|
+
const wlScale = Math.floor(fwl / 4); // 100
|
|
123
|
+
const cy = Math.round(H / 2); // 200 — the source plane's midline
|
|
124
|
+
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
125
|
+
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
126
|
+
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
127
|
+
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
128
|
+
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
129
|
+
// exactly where the one-source rung has no peak at all.
|
|
130
|
+
const sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
131
|
+
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
132
|
+
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
133
|
+
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
134
|
+
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
135
|
+
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
136
|
+
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
137
|
+
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
138
|
+
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
139
|
+
// forward component. One sink, not two.
|
|
140
|
+
const SRC = {
|
|
141
|
+
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
142
|
+
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
143
|
+
burstSize: 8, emitEveryTicks: 1
|
|
144
|
+
};
|
|
145
|
+
|
|
119
146
|
export const parts = {
|
|
147
|
+
source1: [SphereEmitter, { ...SRC, center: [srcX, Math.round(cy - sep / 2)] }],
|
|
148
|
+
source2: [SphereEmitter, { ...SRC, center: [srcX, Math.round(cy + sep / 2)] }],
|
|
120
149
|
// FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
|
|
121
150
|
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
122
151
|
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
@@ -134,41 +163,11 @@ export const parts = {
|
|
|
134
163
|
};
|
|
135
164
|
|
|
136
165
|
export default function buildExperiment(exp) {
|
|
137
|
-
var NSRC = 2;
|
|
138
|
-
var lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
139
|
-
var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
140
|
-
var wlScale = Math.floor(fwl / 4); // 100
|
|
141
166
|
if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
|
|
142
167
|
throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
|
|
143
168
|
'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
144
169
|
}
|
|
145
|
-
|
|
146
|
-
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
147
|
-
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
148
|
-
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
149
|
-
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
150
|
-
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
151
|
-
// exactly where the one-source rung has no peak at all.
|
|
152
|
-
var sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
153
|
-
var srcYs = (NSRC === 2) ? [cy - sep / 2, cy + sep / 2] : [cy];
|
|
154
|
-
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
155
|
-
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
156
|
-
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
157
|
-
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
158
|
-
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
159
|
-
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
160
|
-
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
161
|
-
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
162
|
-
// forward component. One sink, not two.
|
|
163
|
-
var SRC = {
|
|
164
|
-
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
165
|
-
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
166
|
-
burstSize: 8, emitEveryTicks: 1
|
|
167
|
-
};
|
|
168
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
169
|
-
for (var si = 0; si < srcYs.length; si++) {
|
|
170
|
-
exp.addObject('SphereEmitter', Object.assign({}, SRC, { center: [srcX, Math.round(srcYs[si])] }));
|
|
171
|
-
}
|
|
170
|
+
exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source1[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
172
171
|
const p = exp.declare(parts);
|
|
173
172
|
// ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
|
|
174
173
|
// The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
|
|
@@ -19,102 +19,119 @@
|
|
|
19
19
|
// captured descriptor DATA) keeps the population bounded so FPS stays healthy, Restart-safe.
|
|
20
20
|
//
|
|
21
21
|
// ONE MODE-AWARE SOURCE. The mode strip selects the RENDERER the host boots
|
|
22
|
-
// (renderState.renderer), and this scene reads it ONCE,
|
|
22
|
+
// (renderState.renderer), and this scene reads it ONCE, inside buildExperiment: `three` ⇒
|
|
23
23
|
// exp.createScene (a 600x400x600 world), else exp.createCanvas (600x400). The ring geometry,
|
|
24
|
-
// tilts, colours, laser and gate are the SAME in both modes (2D≡3D)
|
|
25
|
-
//
|
|
24
|
+
// tilts, colours, laser and gate are the SAME in both modes (2D≡3D). `parts` is written in the
|
|
25
|
+
// 3D default mode, and flat2D (below) derives the 2D block from it — the scene's own statement of
|
|
26
|
+
// two of its deltas:
|
|
26
27
|
// (a) every mirror is a 3D SLAB — a z-slab centred on the beam plane z=0, rendered as a plate;
|
|
27
28
|
// (b) the laser is VOLUMETRIC (is2D:false — it braids in y AND z; the slab depth catches the
|
|
28
|
-
// z-braid; its tilted xy faces reflect by the 2θ law, its near/far z-faces bounce vz)
|
|
29
|
+
// z-braid; its tilted xy faces reflect by the 2θ law, its near/far z-faces bounce vz).
|
|
30
|
+
// `IS3D` guards the rest, in code after exp.declare:
|
|
29
31
|
// (c) two invisible destroy z-collimators flush with the slab z-band;
|
|
30
|
-
// (d) the population counter carries a z-slab covering the WHOLE universe z-band
|
|
32
|
+
// (d) the population counter carries a z-slab covering the WHOLE universe z-band (the emitter's
|
|
33
|
+
// gateCounter names it, so it stays a code declaration);
|
|
31
34
|
// (e) MOVE/LOOK camera joysticks (the OMNI_3D pattern).
|
|
32
35
|
// The 3D mode's render intent — god-rays at the sun's settings, computed from the light
|
|
33
36
|
// particles only (godraySource 'leaf'), and the 'table-top' camera — rides the registry mode's
|
|
34
37
|
// renderState (lib/shim/registry/walkthrough.js), whose god-ray VALUES are the sun's, owned by
|
|
35
38
|
// lib/shim/render/sun-look.js.
|
|
36
39
|
|
|
37
|
-
import { CameraControls, SphereEmitter } from '@descix/frqtl-web/parts';
|
|
40
|
+
import { CameraControls, SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
38
41
|
import * as FrqtlDisplay from '@descix/frqtl-web/compat/renderer/display-slot.js';
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
const W = 600, H = 400, WORLD_D = 600; // THE WORLD this scene states (its co-init call below)
|
|
44
|
+
const f = Math.min(W, H) / 600;
|
|
45
|
+
// R 225·f: adjacent decagon vertices sit 2R·sin18° ≈ 0.618R apart vs slab
|
|
46
|
+
// height ~110·f, so the mirrors separate with clear gaps. Fit: R + rotated-slab half-diagonal
|
|
47
|
+
// (≈55·f) stays inside min(W,H)/2 with margin.
|
|
48
|
+
const CX = Math.round(W / 2), CY = Math.round(H / 2), R = Math.round(225 * f), HOP = 3, ROT = 12;
|
|
49
|
+
// CAP 6400 / burst 8 (brightness retune): a VERY BRIGHT braid. The cap is
|
|
50
|
+
// LIVE-controllable off the population counter (select 'population' in Layers → Inspector
|
|
51
|
+
// 'Population Gate').
|
|
52
|
+
const SW = Math.max(10, Math.round(14 * f)), SH = Math.max(70, Math.round(110 * f)), CAP = 6400;
|
|
53
|
+
// SD — the 3D slab z-depth: a 2:1 tile (twice the slab height, z-centred on the beam plane).
|
|
54
|
+
// The scene z-collimator clamps ALL quanta to this band, so every mirror provably faces every
|
|
55
|
+
// alive quantum.
|
|
56
|
+
const SD = 2 * SH;
|
|
57
|
+
// Each mirror a DIFFERENT colour = its identity (a red→magenta rainbow); the recolor hook
|
|
58
|
+
// stamps that colour onto every particle it reflects (string_map['render.color']).
|
|
59
|
+
const PAL = [[255,60,60],[255,140,0],[255,220,0],[150,230,40],[40,210,90],[0,210,190],[0,170,255],[70,90,255],[150,70,235],[235,50,190]];
|
|
60
|
+
// Hit-order vertices of the {10/3} star (m1..m10 named IN the order the beam visits them).
|
|
61
|
+
const order = []; for (var k = 0; k < 10; k++) { order.push((k * HOP) % 10); }
|
|
62
|
+
const D2R = Math.PI / 180;
|
|
63
|
+
const vpos = function (v) { var a = (v * 36 + ROT) * D2R; return [CX + R * Math.cos(a), CY + R * Math.sin(a)]; };
|
|
64
|
+
const pos = order.map(vpos);
|
|
65
|
+
const deg = function (a) { return ((a * 180 / Math.PI) % 360 + 360) % 360; };
|
|
66
|
+
const angOf = function (p, q) { return deg(Math.atan2(q[1] - p[1], q[0] - p[0])); };
|
|
67
|
+
// Per-mirror IN-PLANE orientation from the in/out chords: ori = (in+out−180)/2, branch chosen
|
|
68
|
+
// so the engine reflect law out = 180 + 2·ori − in reproduces the outgoing chord exactly.
|
|
69
|
+
// Orientation is z-axis rotation, so the 3D slab depth adds NO new angle.
|
|
70
|
+
const ORI = [];
|
|
71
|
+
for (var j = 0; j < 10; j++) {
|
|
72
|
+
var pr = pos[(j + 9) % 10], cu = pos[j], nx = pos[(j + 1) % 10];
|
|
73
|
+
var inA = angOf(pr, cu), outA = angOf(cu, nx);
|
|
74
|
+
var o = (((inA + outA - 180) / 2) % 360 + 360) % 360;
|
|
75
|
+
var op = ((180 + 2 * o - inA) % 360 + 360) % 360;
|
|
76
|
+
if (Math.abs(((op - outA + 540) % 360) - 180) > 0.5) { o = (o + 90) % 360; }
|
|
77
|
+
ORI.push(o);
|
|
78
|
+
}
|
|
79
|
+
// The NARROW STRAIGHT-WALK laser (the exemplar): the SAME emitter as Rung 1
|
|
80
|
+
// (Straight Walk) — preset 'particle-walk', every walk-character field INHERITED from the same
|
|
81
|
+
// factory defaults (chargeMode 'positive', dim 0 = 'light' P=1, fundamentalWaveLength 64 → the
|
|
82
|
+
// λ16 braided transverse ripple) — with ONLY the deliberate loop deltas: (a) a 2px source line
|
|
83
|
+
// (emissionBoxSide 2 vs r1's default 10), (b) seated on the m10→m1 leg JUST CLEAR of m10 (70·f
|
|
84
|
+
// past its vertex > the rotated-slab half-diagonal ≈55·f) and aimed along the leg (the longest
|
|
85
|
+
// clear throw: leg 1.618R ≈ 364·f), (c) a burst of 8/tick, and (d) the DECLARED emission gate.
|
|
86
|
+
const pS = pos[9], p0 = pos[0];
|
|
87
|
+
const dx = p0[0] - pS[0], dy = p0[1] - pS[1], L = Math.hypot(dx, dy), ux = dx / L, uy = dy / L;
|
|
88
|
+
const emit = [Math.round(pS[0] + 70 * f * ux), Math.round(pS[1] + 70 * f * uy)];
|
|
89
|
+
// EMISSION-CAP GATE as DECLARED DATA (the blackbody-thermostat pattern):
|
|
90
|
+
// emissionCap + gateCounter compile at CONSTRUCTION into a host tick-fn reading
|
|
91
|
+
// Wall.getLiveCount on the background counter named 'population' (placed below — the by-name
|
|
92
|
+
// reference resolves lazily on the first tick). The closed loop has NO sink, so the gate pauses
|
|
93
|
+
// emission at the cap and resumes below it: the population plateaus. Restart-safe: the parts
|
|
94
|
+
// writer keeps the two fields in the entry and the rebuilt emitter recompiles the gate (no free recipe tick-fn).
|
|
95
|
+
// is2D: false (the 3D default) opens activeAxes.z — the volumetric laser; flat2D writes it true.
|
|
96
|
+
// The parts, written in the 3D default mode (the mode strip's default): the laser, then the ten mirrors at the
|
|
97
|
+
// star vertices — each its own colour + the recolor tag, each a 3D SLAB (z-slab -SD/2..SD/2). 2D is derived from
|
|
98
|
+
// this block by flat2D below.
|
|
99
|
+
export const parts = {
|
|
100
|
+
source: [SphereEmitter, { preset: 'particle-walk', is2D: false, center: emit, orientationDeg: angOf(pS, p0), emissionBoxSide: 2, burstSize: 8, emissionCap: CAP, gateCounter: 'population' }],
|
|
101
|
+
m1: [Wall, { rect: [Math.round(pos[0][0] - SW / 2), Math.round(pos[0][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[0], color: PAL[0], hooks: ['recolor'] }],
|
|
102
|
+
m2: [Wall, { rect: [Math.round(pos[1][0] - SW / 2), Math.round(pos[1][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[1], color: PAL[1], hooks: ['recolor'] }],
|
|
103
|
+
m3: [Wall, { rect: [Math.round(pos[2][0] - SW / 2), Math.round(pos[2][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[2], color: PAL[2], hooks: ['recolor'] }],
|
|
104
|
+
m4: [Wall, { rect: [Math.round(pos[3][0] - SW / 2), Math.round(pos[3][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[3], color: PAL[3], hooks: ['recolor'] }],
|
|
105
|
+
m5: [Wall, { rect: [Math.round(pos[4][0] - SW / 2), Math.round(pos[4][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[4], color: PAL[4], hooks: ['recolor'] }],
|
|
106
|
+
m6: [Wall, { rect: [Math.round(pos[5][0] - SW / 2), Math.round(pos[5][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[5], color: PAL[5], hooks: ['recolor'] }],
|
|
107
|
+
m7: [Wall, { rect: [Math.round(pos[6][0] - SW / 2), Math.round(pos[6][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[6], color: PAL[6], hooks: ['recolor'] }],
|
|
108
|
+
m8: [Wall, { rect: [Math.round(pos[7][0] - SW / 2), Math.round(pos[7][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[7], color: PAL[7], hooks: ['recolor'] }],
|
|
109
|
+
m9: [Wall, { rect: [Math.round(pos[8][0] - SW / 2), Math.round(pos[8][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[8], color: PAL[8], hooks: ['recolor'] }],
|
|
110
|
+
m10: [Wall, { rect: [Math.round(pos[9][0] - SW / 2), Math.round(pos[9][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[9], color: PAL[9], hooks: ['recolor'] }],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** This scene's 2D delta, stated once: the same keys in the same order, each props copied with its z-slab
|
|
114
|
+
* (z, depth) dropped and is2D written true where it is spelled. Pure; adds no key. */
|
|
115
|
+
function flat2D(parts) {
|
|
116
|
+
const out = {};
|
|
117
|
+
for (const key of Object.keys(parts)) {
|
|
118
|
+
const props = Object.assign({}, parts[key][1]);
|
|
119
|
+
delete props.z;
|
|
120
|
+
delete props.depth;
|
|
121
|
+
if ('is2D' in props) { props.is2D = true; }
|
|
122
|
+
out[key] = [parts[key][0], props];
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
41
126
|
|
|
42
127
|
export default function buildExperiment(exp) {
|
|
43
128
|
// The booted display (the host's renderState.renderer) decides which world this mode makes.
|
|
44
129
|
var IS3D = FrqtlDisplay.name() === 'three';
|
|
45
|
-
var
|
|
46
|
-
var f = Math.min(W, H) / 600;
|
|
47
|
-
// R 225·f: adjacent decagon vertices sit 2R·sin18° ≈ 0.618R apart vs slab
|
|
48
|
-
// height ~110·f, so the mirrors separate with clear gaps. Fit: R + rotated-slab half-diagonal
|
|
49
|
-
// (≈55·f) stays inside min(W,H)/2 with margin.
|
|
50
|
-
var CX = Math.round(W / 2), CY = Math.round(H / 2), R = Math.round(225 * f), HOP = 3, ROT = 12;
|
|
51
|
-
// CAP 6400 / burst 8 (brightness retune): a VERY BRIGHT braid. The cap is
|
|
52
|
-
// LIVE-controllable off the population counter (select 'population' in Layers → Inspector
|
|
53
|
-
// 'Population Gate').
|
|
54
|
-
var SW = Math.max(10, Math.round(14 * f)), SH = Math.max(70, Math.round(110 * f)), CAP = 6400;
|
|
55
|
-
// SD — the 3D slab z-depth: a 2:1 tile (twice the slab height, z-centred on the beam plane).
|
|
56
|
-
// The scene z-collimator clamps ALL quanta to this band, so every mirror provably faces every
|
|
57
|
-
// alive quantum.
|
|
58
|
-
var SD = 2 * SH;
|
|
59
|
-
// Each mirror a DIFFERENT colour = its identity (a red→magenta rainbow); the recolor hook
|
|
60
|
-
// stamps that colour onto every particle it reflects (string_map['render.color']).
|
|
61
|
-
var PAL = [[255,60,60],[255,140,0],[255,220,0],[150,230,40],[40,210,90],[0,210,190],[0,170,255],[70,90,255],[150,70,235],[235,50,190]];
|
|
62
|
-
// Hit-order vertices of the {10/3} star (m1..m10 named IN the order the beam visits them).
|
|
63
|
-
var order = []; for (var k = 0; k < 10; k++) { order.push((k * HOP) % 10); }
|
|
64
|
-
var D2R = Math.PI / 180;
|
|
65
|
-
var vpos = function (v) { var a = (v * 36 + ROT) * D2R; return [CX + R * Math.cos(a), CY + R * Math.sin(a)]; };
|
|
66
|
-
var pos = order.map(vpos);
|
|
67
|
-
var deg = function (a) { return ((a * 180 / Math.PI) % 360 + 360) % 360; };
|
|
68
|
-
var angOf = function (p, q) { return deg(Math.atan2(q[1] - p[1], q[0] - p[0])); };
|
|
69
|
-
// Per-mirror IN-PLANE orientation from the in/out chords: ori = (in+out−180)/2, branch chosen
|
|
70
|
-
// so the engine reflect law out = 180 + 2·ori − in reproduces the outgoing chord exactly.
|
|
71
|
-
// Orientation is z-axis rotation, so the 3D slab depth adds NO new angle.
|
|
72
|
-
var ORI = [];
|
|
73
|
-
for (var j = 0; j < 10; j++) {
|
|
74
|
-
var pr = pos[(j + 9) % 10], cu = pos[j], nx = pos[(j + 1) % 10];
|
|
75
|
-
var inA = angOf(pr, cu), outA = angOf(cu, nx);
|
|
76
|
-
var o = (((inA + outA - 180) / 2) % 360 + 360) % 360;
|
|
77
|
-
var op = ((180 + 2 * o - inA) % 360 + 360) % 360;
|
|
78
|
-
if (Math.abs(((op - outA + 540) % 360) - 180) > 0.5) { o = (o + 90) % 360; }
|
|
79
|
-
ORI.push(o);
|
|
80
|
-
}
|
|
81
|
-
// The NARROW STRAIGHT-WALK laser (the exemplar): the SAME emitter as Rung 1
|
|
82
|
-
// (Straight Walk) — preset 'particle-walk', every walk-character field INHERITED from the same
|
|
83
|
-
// factory defaults (chargeMode 'positive', dim 0 = 'light' P=1, fundamentalWaveLength 64 → the
|
|
84
|
-
// λ16 braided transverse ripple) — with ONLY the deliberate loop deltas: (a) a 2px source line
|
|
85
|
-
// (emissionBoxSide 2 vs r1's default 10), (b) seated on the m10→m1 leg JUST CLEAR of m10 (70·f
|
|
86
|
-
// past its vertex > the rotated-slab half-diagonal ≈55·f) and aimed along the leg (the longest
|
|
87
|
-
// clear throw: leg 1.618R ≈ 364·f), (c) a burst of 8/tick, and (d) the DECLARED emission gate.
|
|
88
|
-
var pS = pos[9], p0 = pos[0];
|
|
89
|
-
var dx = p0[0] - pS[0], dy = p0[1] - pS[1], L = Math.hypot(dx, dy), ux = dx / L, uy = dy / L;
|
|
90
|
-
var emit = [Math.round(pS[0] + 70 * f * ux), Math.round(pS[1] + 70 * f * uy)];
|
|
91
|
-
// EMISSION-CAP GATE as DECLARED DATA (the blackbody-thermostat pattern):
|
|
92
|
-
// emissionCap + gateCounter compile at CONSTRUCTION into a host tick-fn reading
|
|
93
|
-
// Wall.getLiveCount on the background counter named 'population' (placed below — the by-name
|
|
94
|
-
// reference resolves lazily on the first tick). The closed loop has NO sink, so the gate pauses
|
|
95
|
-
// emission at the cap and resumes below it: the population plateaus. Restart-safe: codegen
|
|
96
|
-
// captures the two fields and the rebuilt emitter recompiles the gate (no free recipe tick-fn).
|
|
97
|
-
// is2D:!IS3D opens activeAxes.z in 3D — the volumetric laser.
|
|
98
|
-
var SRC = {
|
|
99
|
-
preset: 'particle-walk', is2D: !IS3D, center: emit, orientationDeg: angOf(pS, p0),
|
|
100
|
-
emissionBoxSide: 2, burstSize: 8,
|
|
101
|
-
emissionCap: CAP, gateCounter: 'population'
|
|
102
|
-
};
|
|
130
|
+
var P = IS3D ? parts : flat2D(parts);
|
|
103
131
|
// THE WORLD — first, then parts: a 3D scene + universe (600x400x600) or a 2D canvas + universe.
|
|
104
|
-
if (IS3D) { exp.createScene(W, H, WORLD_D, SphereEmitter.worldCfg(
|
|
105
|
-
else { exp.createCanvas(W, H, SphereEmitter.worldCfg(
|
|
106
|
-
exp.declare(
|
|
107
|
-
exp.addObject('SphereEmitter', SRC);
|
|
108
|
-
// The ten mirrors at the star vertices — each its own colour + the recolor tag. In 3D each is a
|
|
109
|
-
// SLAB (z-slab −SD/2..SD/2); in 2D the bag is EMPTY, so the wall is the flat mirror.
|
|
110
|
-
var ZSLAB = IS3D ? { z: -Math.round(SD / 2), depth: SD } : {};
|
|
111
|
-
for (var i = 0; i < 10; i++) {
|
|
112
|
-
var p = pos[i];
|
|
113
|
-
exp.addObject('Wall', Object.assign(
|
|
114
|
-
{ rect: [Math.round(p[0] - SW / 2), Math.round(p[1] - SH / 2), SW, SH] }, ZSLAB,
|
|
115
|
-
{ collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[i],
|
|
116
|
-
name: 'm' + (i + 1), color: PAL[i], hooks: ['recolor'] }));
|
|
117
|
-
}
|
|
132
|
+
if (IS3D) { exp.createScene(W, H, WORLD_D, SphereEmitter.worldCfg(P.source[1])); }
|
|
133
|
+
else { exp.createCanvas(W, H, SphereEmitter.worldCfg(P.source[1])); }
|
|
134
|
+
exp.declare(P);
|
|
118
135
|
if (IS3D) {
|
|
119
136
|
// SCENE Z-COLLIMATOR (without it the z lets things pass through instead of
|
|
120
137
|
// reflecting): the legs' z-braid diffuses lap over lap; any quantum beyond the mirrors'
|
|
@@ -81,6 +81,12 @@ function _fromZip(bytes) {
|
|
|
81
81
|
_fail('the archive carries ' + retired.join(' and ') + ': ' + RETIRED + '.');
|
|
82
82
|
}
|
|
83
83
|
if (names.indexOf(MANIFEST_FILE) === -1) {
|
|
84
|
+
var tops = names.map(function (n) { return n.split('/')[0]; }).filter(function (t, i, a) { return a.indexOf(t) === i; });
|
|
85
|
+
if (tops.length === 1 && names.indexOf(tops[0] + '/' + MANIFEST_FILE) !== -1 &&
|
|
86
|
+
names.every(function (n) { return n.indexOf(tops[0] + '/') === 0; })) {
|
|
87
|
+
_fail('the archive carries no ' + MANIFEST_FILE + ' at its root — the archive holds the folder `' + tops[0] + '/` rather ' +
|
|
88
|
+
'than its contents — zip the folder\'s CONTENTS (' + MANIFEST_FILE + ' at the root), as an Export writes it.');
|
|
89
|
+
}
|
|
84
90
|
_fail('the archive carries no ' + MANIFEST_FILE + ' at its root — a project archive is the folder zipped ' +
|
|
85
91
|
'(frqtl.json + sketch.js + …), as an Export writes it.');
|
|
86
92
|
}
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
// action — a MOMENTARY verb: {id,label,action} where `action` is a member of
|
|
102
102
|
// the closed ACTIONS vocabulary below. Stateless (no `value`, no
|
|
103
103
|
// read-back) and serializable by NAME, so a declared array of them
|
|
104
|
-
// round-trips through
|
|
104
|
+
// round-trips through the scene's text with no new machinery. Actions are
|
|
105
105
|
// deliberately NOT expressed as a `bind` — a bind is a property
|
|
106
106
|
// read/write pair, and `get()` of "restart" is meaningless.
|
|
107
107
|
//
|
|
@@ -164,7 +164,7 @@ export function renderArtifactShellHtml(opts) {
|
|
|
164
164
|
return (
|
|
165
165
|
'<!DOCTYPE html>\n' +
|
|
166
166
|
'<!-- Copyright 2023-2026 by Essam Abadir -->\n' +
|
|
167
|
-
'<!-- FRQTL — claude.ai Artifact host shell
|
|
167
|
+
'<!-- FRQTL — claude.ai Artifact host shell.\n' +
|
|
168
168
|
' Generated by lib/shim/engine/artifact-shell.js — DO NOT hand-edit.\n' +
|
|
169
169
|
' Paste this ENTIRE file into a claude.ai chat as an Artifact (type: HTML). -->\n' +
|
|
170
170
|
'<html lang="en">\n' +
|
|
@@ -148,7 +148,7 @@ var DIST_PROFILE = buildDistProfile('../..');
|
|
|
148
148
|
// lib/shim/engine/test/router.test.mjs asserts them against the two
|
|
149
149
|
// package.json#version fields so a forgotten bump fails `make verify`, not a
|
|
150
150
|
// silent stale-CDN-URL bug.
|
|
151
|
-
var CDN_SDK_VERSION = '0.6.0
|
|
151
|
+
var CDN_SDK_VERSION = '0.6.0'; // sdk/frqtl-web/package.json#version (the ONE version owner) — bound by router.test.mjs
|
|
152
152
|
var CDN_MATH_SDK_VERSION = '0.3.2'; // sdk/egpt-math-sdk/package.json#version — bound by router.test.mjs; bumped with each npm publish
|
|
153
153
|
|
|
154
154
|
/**
|
|
@@ -382,7 +382,7 @@ function documentSiteBuild() {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
// The static IDE bundle's entry document, beside its front page. The bundle ships THIS name and no
|
|
385
|
-
// index.html: a site's index.html is the publisher's front page (host-view.js::HOST_FRONT_PAGE),
|
|
385
|
+
// index.html: a site's index.html is the publisher's front page (lib/shim/host/host-view.js::HOST_FRONT_PAGE),
|
|
386
386
|
// which the IDE brand links home to.
|
|
387
387
|
var STATIC_APP_PAGE = 'app.html';
|
|
388
388
|
|
|
@@ -507,7 +507,7 @@ var _assertProfileParity = (function () {
|
|
|
507
507
|
* // CDN mode (static hosting — the artifact host shell has no server, no page anchor):
|
|
508
508
|
* const cdnRouter = engineRouter({ mode: 'cdn', engine: 'wasm' });
|
|
509
509
|
* console.log(cdnRouter.resolve('compat/engine.js'));
|
|
510
|
-
* // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.0
|
|
510
|
+
* // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.0/dist/frqtl/compat/engine.js'
|
|
511
511
|
*
|
|
512
512
|
* // CDN mode, self-hosted: a copy of dist/ served from the consumer's own origin:
|
|
513
513
|
* const selfHosted = engineRouter({ mode: 'cdn', engine: 'wasm', base: 'https://example.org/vendor/frqtl-web/dist' });
|