@descix/frqtl-node 0.6.0-p0.2 → 0.6.0-p1.1
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 +7 -3
- package/dist/dist-manifest.json +54 -48
- package/dist/frqtl/assets/vendor/acorn.LICENSE +21 -0
- package/dist/frqtl/assets/vendor/acorn.PINNED.json +9 -0
- package/dist/frqtl/assets/vendor/acorn.mjs +6266 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +41 -0
- package/dist/frqtl/experiments/_shared/boot-failure.js +60 -0
- package/dist/frqtl/objects/FrqtlApp.js +109 -30
- package/dist/frqtl/objects/ObjectFactory.js +4 -5
- package/dist/frqtl/objects/RecipeContext.js +451 -9
- package/dist/frqtl/objects/instruments/hosted-eyes.js +68 -28
- package/dist/frqtl/objects/objects/QuantumEmitter.js +1 -1
- package/dist/frqtl/objects/schema/index.js +72 -0
- package/dist/frqtl/parts/DetectorWall.js +1 -1
- package/dist/frqtl/parts/FrqtlControlsPanel.js +4 -2
- package/dist/frqtl/parts/Wall.js +2 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +18 -14
- package/dist/frqtl/scenes/absorbing-wall.js +18 -14
- package/dist/frqtl/scenes/atomic-model.js +3 -0
- package/dist/frqtl/scenes/big-bang.js +3 -0
- package/dist/frqtl/scenes/blackbody.js +14 -11
- package/dist/frqtl/scenes/circuit-sat.js +7 -4
- package/dist/frqtl/scenes/collision-matrix-3d.js +3 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +19 -15
- package/dist/frqtl/scenes/counting-wall.js +19 -15
- package/dist/frqtl/scenes/double-slit.js +3 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +64 -58
- package/dist/frqtl/scenes/dse-one-slit.js +59 -55
- package/dist/frqtl/scenes/dse-photoelectric.js +28 -24
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +124 -99
- package/dist/frqtl/scenes/dse-two-emitters.js +64 -58
- package/dist/frqtl/scenes/dse-two-slits.js +69 -65
- package/dist/frqtl/scenes/empty.js +3 -0
- package/dist/frqtl/scenes/laser-buildup.js +23 -16
- package/dist/frqtl/scenes/mirror-3d.js +31 -27
- package/dist/frqtl/scenes/mirror-route.js +4 -1
- package/dist/frqtl/scenes/mirror.js +24 -20
- package/dist/frqtl/scenes/omni-3d.js +7 -4
- package/dist/frqtl/scenes/omni-source.js +7 -4
- package/dist/frqtl/scenes/self-interference.js +13 -10
- package/dist/frqtl/scenes/smoking-gun.js +15 -11
- package/dist/frqtl/scenes/straight-walk.js +7 -4
- package/dist/frqtl/scenes/sun.js +11 -8
- package/dist/frqtl/scenes/two-source.js +22 -17
- package/dist/frqtl/scenes/wave.js +10 -7
- package/dist/shim/bundle/manifest.js +1 -1
- package/dist/shim/engine/router.js +2 -2
- package/dist/shim/page-params/index.js +2 -2
- package/dist/shim/project/index.js +7 -5
- package/dist/shim/project/link.js +12 -5
- package/dist/shim/project/parts-module.js +67 -0
- package/dist/shim/project/parts-writer.js +453 -0
- package/dist/shim/project/specifiers.js +41 -13
- package/dist/shim/registry/walkthrough.js +9 -9
- package/dist/shim/render-state/apply.js +11 -14
- package/llms.txt +2 -2
- package/package.json +1 -1
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
*
|
|
67
67
|
* A SceneRecord alone cannot open a host: it is identity plus reproduction pins, with no
|
|
68
68
|
* scene spec in it. So the API takes the PAIR — record + `{kind:'project', files, entry}`, the
|
|
69
|
-
* project whose entry's default export is `buildExperiment(exp)` (a one-string sketch, such as
|
|
70
|
-
*
|
|
69
|
+
* project whose entry's default export is `buildExperiment(exp)` (a one-string sketch, such as a
|
|
70
|
+
* shipped scene file, is `{ files: { 'sketch.js': text }, entry: 'sketch.js' }`). The
|
|
71
71
|
* page links it through its one door and stamps the project's identity (projectIdentity) as the
|
|
72
72
|
* record's `sourceSha256`; this process computes the same identity from the same files, and the
|
|
73
73
|
* open-time record check holds the two equal. A caller without one is REPLAY-grade
|
|
@@ -132,6 +132,7 @@ import { FrqtlCaptureHost } from './capture-host.js';
|
|
|
132
132
|
import { FrqtlVision } from '../../parts/FrqtlVision.js';
|
|
133
133
|
import { FrqtlCapture } from './capture.js';
|
|
134
134
|
import { projectIdentity, projectBytes } from '../../../shim/project/identity.js';
|
|
135
|
+
import { libPathOf } from '../../../shim/project/specifiers.js';
|
|
135
136
|
|
|
136
137
|
|
|
137
138
|
/** The contracts this instrument consumes, by static import (it is a node-only module). */
|
|
@@ -145,22 +146,29 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
/**
|
|
148
|
-
* The URL, AS THE PAGE SEES IT, of a module beside this file.
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
149
|
+
* The URL, AS THE PAGE SEES IT, of a module beside this file. MODULES sit at their
|
|
150
|
+
* lib-relative paths in lib/ and in every dist, so a module's lib path is its path from this
|
|
151
|
+
* tree's lib root. The PAGE does not: the dev mount serves it inside the lib tree, a dist
|
|
152
|
+
* re-anchors it (`<dist>/experiments/empty/`, the graph beside or under a build directory),
|
|
153
|
+
* so where the page sits says nothing about where its modules are. The page's own import map
|
|
154
|
+
* does — every row is its lib root plus a lib path (lib/shim/project/specifiers.js
|
|
155
|
+
* `importMapFor`, rendered for each placement) — so the lib root is asked of the page
|
|
156
|
+
* (`_IN_PAGE_LIB_ROOT` on `PAGE_ANCHOR`) and each module's lib path resolved against it.
|
|
157
|
+
* The in-page routines import the PAGE's module instances from these URLs — never a copy.
|
|
158
|
+
* @private
|
|
153
159
|
*/
|
|
154
|
-
var
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
var LIB_ROOT_DIR = fileURLToPath(new URL('../../../', import.meta.url));
|
|
161
|
+
var PAGE_ANCHOR_SPECIFIER = '@descix/frqtl-web/parts';
|
|
162
|
+
var PAGE_ANCHOR = { specifier: PAGE_ANCHOR_SPECIFIER, libPath: libPathOf(PAGE_ANCHOR_SPECIFIER) };
|
|
163
|
+
function _pageModuleUrl(pageLibRoot, moduleUrl) {
|
|
164
|
+
var rel = path.relative(LIB_ROOT_DIR, fileURLToPath(moduleUrl)).split(path.sep).join('/');
|
|
165
|
+
return new URL(rel, pageLibRoot).href;
|
|
158
166
|
}
|
|
159
|
-
/** The modules the capture-host KIND's in-page routines import, for
|
|
160
|
-
function _inPageModules(
|
|
167
|
+
/** The modules the capture-host KIND's in-page routines import, for a page whose lib root is `pageLibRoot`. @private */
|
|
168
|
+
function _inPageModules(pageLibRoot) {
|
|
161
169
|
return {
|
|
162
|
-
captureHost: _pageModuleUrl(
|
|
163
|
-
captureEnvelope: _pageModuleUrl(
|
|
170
|
+
captureHost: _pageModuleUrl(pageLibRoot, import.meta.resolve('./capture-host.js')),
|
|
171
|
+
captureEnvelope: _pageModuleUrl(pageLibRoot, import.meta.resolve('../../../shim/render/capture-envelope.js'))
|
|
164
172
|
};
|
|
165
173
|
}
|
|
166
174
|
|
|
@@ -362,11 +370,11 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
362
370
|
// ── The boot wait: every way it can fail is a NAMED refusal ─────────────────
|
|
363
371
|
|
|
364
372
|
/**
|
|
365
|
-
* Where a published dist
|
|
366
|
-
*
|
|
373
|
+
* Where a published dist serves the one page — under the dist's own URL, which may be an
|
|
374
|
+
* origin root or a sub-path, so it is named, not computed from `url`. @private
|
|
367
375
|
*/
|
|
368
|
-
function _distEmptyPage(
|
|
369
|
-
return
|
|
376
|
+
function _distEmptyPage() {
|
|
377
|
+
return '<dist>/experiments/empty/index.html';
|
|
370
378
|
}
|
|
371
379
|
|
|
372
380
|
/** D — the URL did not serve (an HTTP status other than 2xx, or a navigation error). @private */
|
|
@@ -374,7 +382,7 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
374
382
|
return new Error('[FrqtlHostedEyes.open] ' + url + ' did not serve: ' + what + '. That is ' +
|
|
375
383
|
'a deployment fact, not a scene problem — the hosted eyes boot ONLY the canonical empty ' +
|
|
376
384
|
'experiment page lib/frqtl/experiments/empty/index.html as your deployment serves it ' +
|
|
377
|
-
'(a published dist
|
|
385
|
+
'(a published dist serves it at ' + _distEmptyPage() + ', <dist> being the URL the dist is served at); point ' +
|
|
378
386
|
'`pageUrl` at it, or serve it. Nothing was booted. (FAIL-LOUD.)');
|
|
379
387
|
}
|
|
380
388
|
|
|
@@ -383,7 +391,7 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
383
391
|
return new Error('[FrqtlHostedEyes.open] ' + url + ' is not the canonical empty experiment ' +
|
|
384
392
|
'page: it loaded (HTTP ' + status + ') and defines no window.FrqtlPage. The hosted eyes ' +
|
|
385
393
|
'boot ONLY lib/frqtl/experiments/empty/index.html (in a published dist: ' +
|
|
386
|
-
_distEmptyPage(
|
|
394
|
+
_distEmptyPage() + ') and apply your scene into it. They never host an app page, the ' +
|
|
387
395
|
'IDE, or a page of your own: a page that boots and edits its own scene cannot be ' +
|
|
388
396
|
'regenerated from {sceneRecord, sceneSource}. To see your own page, capture IN it ' +
|
|
389
397
|
'through FrqtlVision (the in-page face). To drive it from node, use your own browser; ' +
|
|
@@ -558,6 +566,32 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
558
566
|
return (td && td.universeData) ? td.universeData.tick : null;
|
|
559
567
|
};
|
|
560
568
|
|
|
569
|
+
/**
|
|
570
|
+
* The page's lib root, resolved by the PAGE's import map: `anchor.specifier` resolves to the lib
|
|
571
|
+
* root plus `anchor.libPath`. A `blob:` module asks, because `import.meta.resolve` is the
|
|
572
|
+
* browser's own resolution against the document's map (the link site's blob modules resolve
|
|
573
|
+
* their imports the same way).
|
|
574
|
+
*/
|
|
575
|
+
var _IN_PAGE_LIB_ROOT = async function (anchor) {
|
|
576
|
+
var url = URL.createObjectURL(new Blob(['export default import.meta.resolve(' +
|
|
577
|
+
JSON.stringify(anchor.specifier) + ');'], { type: 'text/javascript' }));
|
|
578
|
+
var resolved;
|
|
579
|
+
try { resolved = (await import(url)).default; }
|
|
580
|
+
catch (e) {
|
|
581
|
+
throw new Error('[FrqtlHostedEyes/in-page] this page\'s import map does not resolve ' +
|
|
582
|
+
JSON.stringify(anchor.specifier) + ' (' + ((e && e.message) || String(e)) + ') — the canonical ' +
|
|
583
|
+
'empty page carries the specifier table\'s import map; nothing can locate its modules ' +
|
|
584
|
+
'without it. (FAIL-LOUD.)');
|
|
585
|
+
}
|
|
586
|
+
finally { URL.revokeObjectURL(url); }
|
|
587
|
+
if (typeof resolved !== 'string' || resolved.slice(-anchor.libPath.length) !== anchor.libPath) {
|
|
588
|
+
throw new Error('[FrqtlHostedEyes/in-page] ' + JSON.stringify(anchor.specifier) + ' resolved to ' +
|
|
589
|
+
JSON.stringify(resolved) + ', which does not end in its lib path ' + JSON.stringify(anchor.libPath) +
|
|
590
|
+
' — the page\'s import map is not the specifier table\'s rendering. (FAIL-LOUD.)');
|
|
591
|
+
}
|
|
592
|
+
return resolved.slice(0, resolved.length - anchor.libPath.length);
|
|
593
|
+
};
|
|
594
|
+
|
|
561
595
|
/** Point the ONE camera and redraw — pose is a still coordinate, set before capture. */
|
|
562
596
|
var _IN_PAGE_POSE = function (pose) {
|
|
563
597
|
var r = window.FrqtlPage.runner;
|
|
@@ -595,7 +629,8 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
595
629
|
* `setPose` after open overrides its camera. Omit it for a scene's PLAIN render.
|
|
596
630
|
* @param {string} opts.pageUrl — the canonical empty experiment page in YOUR deployment
|
|
597
631
|
* (dev server: `<origin>/frqtl/experiments/empty/index.html`; published dist:
|
|
598
|
-
* `<
|
|
632
|
+
* `<dist>/experiments/empty/index.html`, where `<dist>` is the dist's URL — an origin root
|
|
633
|
+
* or a sub-path). Required rather than guessed: the address of
|
|
599
634
|
* a deployment is not something a library may invent.
|
|
600
635
|
* @param {object} opts.browserType — a Playwright browser type (`chromium`). The
|
|
601
636
|
* `@descix/frqtl-node/eyes` entry supplies the packaged default; passing your own IS the
|
|
@@ -626,7 +661,7 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
626
661
|
throw new Error('[FrqtlHostedEyes.open] `pageUrl` is REQUIRED: the URL of the ' +
|
|
627
662
|
'CANONICAL empty experiment page (lib/frqtl/experiments/empty/index.html) in your ' +
|
|
628
663
|
'deployment — a dev server exposes it at its /frqtl mount, a published dist at ' +
|
|
629
|
-
'<
|
|
664
|
+
'<dist>/experiments/empty/index.html (<dist> = the URL the dist is served at). A library may not invent the address of ' +
|
|
630
665
|
'your deployment, and it must not ship a second copy of that page. (FAIL-LOUD.)');
|
|
631
666
|
}
|
|
632
667
|
if (!opts.browserType || typeof opts.browserType.launch !== 'function') {
|
|
@@ -641,7 +676,7 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
641
676
|
var sourceRef = (record.sourceRef === undefined || record.sourceRef === null)
|
|
642
677
|
? null : String(record.sourceRef);
|
|
643
678
|
|
|
644
|
-
var browser = null, page = null;
|
|
679
|
+
var browser = null, page = null, modules = null;
|
|
645
680
|
return Promise.resolve(opts.browserType.launch({
|
|
646
681
|
headless: true,
|
|
647
682
|
args: Array.isArray(opts.launchArgs) ? opts.launchArgs : _defaultLaunchArgs()
|
|
@@ -735,6 +770,11 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
735
770
|
_assertLanded(boot.renderState);
|
|
736
771
|
});
|
|
737
772
|
}).then(function () {
|
|
773
|
+
// Asked of the FINAL page (after any display reload), once: the in-page routines import
|
|
774
|
+
// its module instances from these URLs.
|
|
775
|
+
return page.evaluate(_IN_PAGE_LIB_ROOT, PAGE_ANCHOR);
|
|
776
|
+
}).then(function (pageLibRoot) {
|
|
777
|
+
modules = _inPageModules(pageLibRoot);
|
|
738
778
|
return page.evaluate(_IN_PAGE_SCENE);
|
|
739
779
|
}).then(function (pageScene) {
|
|
740
780
|
// The caller's `build`/`projection`, if any, were an EXPECTATION about what the
|
|
@@ -760,7 +800,7 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
760
800
|
'is closed. Open a new one; a capture surface that silently ' +
|
|
761
801
|
'resurrected a browser would leak processes by design. (FAIL-LOUD.)');
|
|
762
802
|
}
|
|
763
|
-
return page.evaluate(C.CH.IN_PAGE_CAPTURE, { stage: stage, modules:
|
|
803
|
+
return page.evaluate(C.CH.IN_PAGE_CAPTURE, { stage: stage, modules: modules })
|
|
764
804
|
.then(function (env) { return C.CH.decodeStillEnvelope(env); });
|
|
765
805
|
},
|
|
766
806
|
record: function (recordOpts) {
|
|
@@ -775,14 +815,14 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
775
815
|
var signal = recordOpts.signal || null;
|
|
776
816
|
var pageOpts = {};
|
|
777
817
|
for (var k in recordOpts) { if (k !== 'signal') { pageOpts[k] = recordOpts[k]; } }
|
|
778
|
-
var begun = page.evaluate(C.CH.IN_PAGE_RECORD_BEGIN, { id: id, opts: pageOpts, modules:
|
|
818
|
+
var begun = page.evaluate(C.CH.IN_PAGE_RECORD_BEGIN, { id: id, opts: pageOpts, modules: modules });
|
|
779
819
|
gate.opened = begun.then(null, function () {});
|
|
780
820
|
var onAbort = null;
|
|
781
821
|
var aborted = null;
|
|
782
822
|
if (signal) {
|
|
783
823
|
var sendAbort = function () {
|
|
784
824
|
return begun.then(function () {
|
|
785
|
-
return page.evaluate(C.CH.IN_PAGE_RECORD_ABORT, { id: id, modules:
|
|
825
|
+
return page.evaluate(C.CH.IN_PAGE_RECORD_ABORT, { id: id, modules: modules });
|
|
786
826
|
});
|
|
787
827
|
};
|
|
788
828
|
if (signal.aborted) { aborted = sendAbort(); }
|
|
@@ -793,7 +833,7 @@ import { projectIdentity, projectBytes } from '../../../shim/project/identity.js
|
|
|
793
833
|
}
|
|
794
834
|
var detach = function () { if (onAbort) { signal.removeEventListener('abort', onAbort); } };
|
|
795
835
|
return begun.then(function () {
|
|
796
|
-
return page.evaluate(C.CH.IN_PAGE_RECORD_END, { id: id, modules:
|
|
836
|
+
return page.evaluate(C.CH.IN_PAGE_RECORD_END, { id: id, modules: modules });
|
|
797
837
|
}).then(function (env) {
|
|
798
838
|
detach();
|
|
799
839
|
// A failed ABORT delivery is surfaced, never swallowed.
|
|
@@ -597,7 +597,7 @@ import { declaredCount } from '../ObjectFactory.js';
|
|
|
597
597
|
type: 'rect',
|
|
598
598
|
engineVisible: true,
|
|
599
599
|
description: 'Canonical bounding rect ' + ValueNormalizer.rectTupleText(4) + ' — repositions the single emission ' +
|
|
600
|
-
'source rect.
|
|
600
|
+
'source rect. Routed through ' +
|
|
601
601
|
'QuantumEmitter.setSourceRect() (mutates emState.sourceRects directly — the ' +
|
|
602
602
|
'RETIRED provider ABI updateEmitterSourceRects no longer exists post-G-EMIT-2). ' +
|
|
603
603
|
'The emitter moves its own marker.'
|
|
@@ -42,6 +42,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
42
42
|
import * as FrameDataSchema from '../../../shim/frame-data/index.js';
|
|
43
43
|
import * as FieldDescriptorSchema from '../../../shim/field-descriptor/index.js';
|
|
44
44
|
import * as ValueNormalizer from './valueNormalizer.js';
|
|
45
|
+
import * as LoDrawnCenter from '../../compat/renderer/lo-drawn-center.js';
|
|
45
46
|
import { SCOPE_DESCRIPTORS, shapeValuesMap } from './scopes.js';
|
|
46
47
|
import { kindOf, registeredKinds, placeableKinds } from '../kinds.js';
|
|
47
48
|
|
|
@@ -135,6 +136,18 @@ import { kindOf, registeredKinds, placeableKinds } from '../kinds.js';
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
/** A deep copy of arrays and plain objects; any other value (a primitive, a class instance) as it is. */
|
|
140
|
+
function _plainCopy(v) {
|
|
141
|
+
if (v === null || typeof v !== 'object') { return v; }
|
|
142
|
+
if (Array.isArray(v)) { return v.map(_plainCopy); }
|
|
143
|
+
if (ArrayBuffer.isView(v)) { return Array.from(v); }
|
|
144
|
+
var proto = Object.getPrototypeOf(v);
|
|
145
|
+
if (proto !== Object.prototype && proto !== null) { return v; }
|
|
146
|
+
var out = {};
|
|
147
|
+
Object.keys(v).forEach(function (k) { out[k] = _plainCopy(v[k]); });
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
138
151
|
var ObjectSchema = {
|
|
139
152
|
/**
|
|
140
153
|
* get(name) → the descriptor: a kind a module registered in THIS realm (objects/kinds.js),
|
|
@@ -212,6 +225,65 @@ import { kindOf, registeredKinds, placeableKinds } from '../kinds.js';
|
|
|
212
225
|
* @param {string} kind
|
|
213
226
|
* @returns {object}
|
|
214
227
|
*/
|
|
228
|
+
/**
|
|
229
|
+
* captureProps(obj, kind) → the plain props bag of a live object, read through its kind's descriptor:
|
|
230
|
+
* every declared field whose value is defined, deep-copied (arrays and plain objects; any other object
|
|
231
|
+
* is left as the value it is, so a writer refuses it by name); a `rect` field read as a {left, top, w, h}
|
|
232
|
+
* shape is captured in its write form: RECT_TUPLE[6] when it carries a z-extent (LoDrawnCenter.hasZ),
|
|
233
|
+
* else RECT_TUPLE[4]. A `writeOnly` field (a live reposition
|
|
234
|
+
* adapter — SphereEmitter.rect, Tunnel.rect) is skipped: the part's real position fields are captured.
|
|
235
|
+
* THROWS for a kind with no descriptor: there is no descriptor-less walk.
|
|
236
|
+
* Consumers: RecipeContext (the parts writer's baseline and its diff), the IDE's inspector reads.
|
|
237
|
+
* @param {object} obj — a live declared object
|
|
238
|
+
* @param {string} kind — its kind (runner.kindOf(obj))
|
|
239
|
+
* @returns {object}
|
|
240
|
+
*/
|
|
241
|
+
captureProps: function (obj, kind) {
|
|
242
|
+
var descriptor = _descriptor(kind);
|
|
243
|
+
if (!descriptor || !Array.isArray(descriptor.fields)) {
|
|
244
|
+
throw new Error('[ObjectSchema] captureProps(obj, "' + kind + '"): no descriptor for kind "' + kind +
|
|
245
|
+
'" in this realm — a capture reads the kind\'s declared fields, never the object\'s own keys. (FAIL-LOUD.)');
|
|
246
|
+
}
|
|
247
|
+
var props = {};
|
|
248
|
+
for (var i = 0; i < descriptor.fields.length; i++) {
|
|
249
|
+
var field = descriptor.fields[i];
|
|
250
|
+
if (field.writeOnly === true) { continue; }
|
|
251
|
+
var val = obj[field.name];
|
|
252
|
+
if (val === undefined) { continue; }
|
|
253
|
+
// A `rect` field READS as a {left, top, w, h} shape (a Rectangle) but is WRITTEN as the
|
|
254
|
+
// tuple whose order valueNormalizer.js::RECT_TUPLE owns: the capture is the write form.
|
|
255
|
+
// A rect with a z-extent (LoDrawnCenter.hasZ) is captured in the 6-number form, a flat one in the
|
|
256
|
+
// 4-number form, so a baseline and a live capture agree on form and a z move is a `rect` diff.
|
|
257
|
+
if (field.type === 'rect' && val && typeof val === 'object' && !Array.isArray(val)) {
|
|
258
|
+
var form = LoDrawnCenter.hasZ(val) ? 6 : 4;
|
|
259
|
+
var tuple = ValueNormalizer.RECT_TUPLE[form].map(function (k) {
|
|
260
|
+
return k === 'd' ? LoDrawnCenter.zExtent(val) : val[k];
|
|
261
|
+
});
|
|
262
|
+
if (tuple.every(function (n) { return typeof n === 'number'; })) {
|
|
263
|
+
props[field.name] = tuple;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
props[field.name] = _plainCopy(val);
|
|
268
|
+
}
|
|
269
|
+
return props;
|
|
270
|
+
},
|
|
271
|
+
/**
|
|
272
|
+
* derivedReads(kind) → [{ name, field, tuple, key }] — the kind's readonly reads that a write field owns
|
|
273
|
+
* (a descriptor row's `derivedFrom`: e.g. Wall `z` = RECT_TUPLE[6] key 'z' of `rect`). Data only: the
|
|
274
|
+
* write authority stays the named field. THROWS for a kind with no descriptor.
|
|
275
|
+
* @param {string} kind
|
|
276
|
+
* @returns {{name: string, field: string, tuple: number, key: string}[]}
|
|
277
|
+
*/
|
|
278
|
+
derivedReads: function (kind) {
|
|
279
|
+
var descriptor = _descriptor(kind);
|
|
280
|
+
if (!descriptor || !Array.isArray(descriptor.fields)) {
|
|
281
|
+
throw new Error('[ObjectSchema] derivedReads("' + kind + '"): no descriptor for kind "' + kind + '". (FAIL-LOUD.)');
|
|
282
|
+
}
|
|
283
|
+
return descriptor.fields.filter(function (f) { return !!f.derivedFrom; }).map(function (f) {
|
|
284
|
+
return { name: f.name, field: f.derivedFrom.field, tuple: f.derivedFrom.tuple, key: f.derivedFrom.key };
|
|
285
|
+
});
|
|
286
|
+
},
|
|
215
287
|
defaultPropsFor: function (kind) {
|
|
216
288
|
var row = kindOf(kind);
|
|
217
289
|
return (row && row.defaults) ? row.defaults : {};
|
|
@@ -283,7 +283,7 @@ import { registerKind } from '../objects/kinds.js';
|
|
|
283
283
|
if (tickData != null && tickData === this._lastTickData) {
|
|
284
284
|
throw new Error(
|
|
285
285
|
'[DetectorWall] getCollisionHistogram() called twice with the SAME TickData ' +
|
|
286
|
-
'object — each tick must be consumed exactly once
|
|
286
|
+
'object — each tick must be consumed exactly once, or its collisions are counted twice. ' +
|
|
287
287
|
'Call universe.doTick() again before re-consuming.'
|
|
288
288
|
);
|
|
289
289
|
}
|
|
@@ -173,8 +173,10 @@ function _dispatchControlsCall(registry, fn, args) {
|
|
|
173
173
|
hit.kind + '", not "action". (FAIL-LOUD.)');
|
|
174
174
|
}
|
|
175
175
|
var ctxA = registry._controlBindCtx || {};
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// `done` settles when the action has (a restart replays asynchronously: runner.restart() is a
|
|
177
|
+
// Promise for every scene); a failed replay REJECTS it, so a host that awaits it sees the error.
|
|
178
|
+
var ran = _ControlActionNode.resolveAction(hit.action, { runner: ctxA.runner }).run();
|
|
179
|
+
return { action: hit.action, done: Promise.resolve(ran) };
|
|
178
180
|
}
|
|
179
181
|
if (fn === 'setControlValue') {
|
|
180
182
|
var name2 = args[0], id = args[1], value = args[2];
|
package/dist/frqtl/parts/Wall.js
CHANGED
|
@@ -969,6 +969,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
969
969
|
{
|
|
970
970
|
name: 'depth',
|
|
971
971
|
type: 'readonly',
|
|
972
|
+
derivedFrom: { field: 'rect', tuple: 6, key: 'd' },
|
|
972
973
|
unit: 'px',
|
|
973
974
|
engineVisible: false,
|
|
974
975
|
description: 'The wall\'s z-slab depth (z-extent) in physics pixels — 0 for a flat 2D ' +
|
|
@@ -978,6 +979,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
978
979
|
{
|
|
979
980
|
name: 'z',
|
|
980
981
|
type: 'readonly',
|
|
982
|
+
derivedFrom: { field: 'rect', tuple: 6, key: 'z' },
|
|
981
983
|
unit: 'px',
|
|
982
984
|
engineVisible: false,
|
|
983
985
|
description: 'The wall\'s z placement (near-face … far-face CENTER) in physics pixels ' +
|
|
@@ -5,29 +5,33 @@
|
|
|
5
5
|
*
|
|
6
6
|
* A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
|
|
7
7
|
* (its co-init call, `exp.createCanvas` / `exp.createScene` — the one place its size is written),
|
|
8
|
-
* the parts (`exp.addObject`) and the render intent are all here; the sugar
|
|
8
|
+
* the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
|
|
9
9
|
* objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
|
|
10
10
|
* (rung cl1b-absorbing-wall-3d).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { SphereEmitter } from '@descix/frqtl-web/parts';
|
|
13
|
+
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// Collision Lab 1B · 3D — a volumetric (spherical) source into a 3D ABSORB wall
|
|
16
|
+
// slab (z-slab centered on 0 so the 3D spray collides); orbit the camera to view.
|
|
17
|
+
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
18
|
+
const cy = Math.round(H / 2);
|
|
19
|
+
|
|
20
|
+
export const parts = {
|
|
21
|
+
source: [SphereEmitter, {
|
|
21
22
|
preset: 'spherical', is2D: false, center: [Math.round(W * 0.15), cy],
|
|
22
23
|
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
23
24
|
burstSize: 24, emitEveryTicks: 1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exp.addObject('SphereEmitter', SRC);
|
|
27
|
-
exp.addObject('Wall', {
|
|
25
|
+
}],
|
|
26
|
+
absorber: [Wall, {
|
|
28
27
|
rect: [Math.round(W * 0.6), Math.round(H * 0.2), 20, Math.round(H * 0.6)],
|
|
29
28
|
z: -80, depth: 160,
|
|
30
29
|
collisionBehavior: 'absorb', capacity: 64,
|
|
31
|
-
|
|
32
|
-
}
|
|
30
|
+
color: [150, 90, 210]
|
|
31
|
+
}],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default function buildExperiment(exp) {
|
|
35
|
+
exp.createScene(W, H, 600, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 3D scene + universe, 600x400x600 — first, then parts
|
|
36
|
+
exp.declare(parts);
|
|
33
37
|
}
|
|
@@ -5,28 +5,32 @@
|
|
|
5
5
|
*
|
|
6
6
|
* A sketch file: the WHOLE scene as one ES module, `export default function buildExperiment(exp)`. The world
|
|
7
7
|
* (its co-init call, `exp.createCanvas` / `exp.createScene` — the one place its size is written),
|
|
8
|
-
* the parts (`exp.addObject`) and the render intent are all here; the sugar
|
|
8
|
+
* the parts (`export const parts` + `exp.declare`, or `exp.addObject`) and the render intent are all here; the sugar
|
|
9
9
|
* objects, renderer and engine are imports. Registered by lib/shim/registry/walkthrough.js
|
|
10
10
|
* (rung cl1b-absorbing-wall).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { SphereEmitter } from '@descix/frqtl-web/parts';
|
|
13
|
+
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// Collision Lab 1B (Absorbing Wall) — ABSORB v2 (Track A): the wall LO is backed by
|
|
16
|
+
// a REAL leaf frame; a host tick-fn tallies the killed masses into it + radiates.
|
|
17
|
+
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
18
|
+
const cy = Math.round(H / 2);
|
|
19
|
+
|
|
20
|
+
export const parts = {
|
|
21
|
+
source: [SphereEmitter, {
|
|
21
22
|
preset: 'spherical', is2D: true, center: [Math.round(W * 0.15), cy],
|
|
22
23
|
chargeMode: 'random', fundamentalDimension: 2, wavelength: 32,
|
|
23
24
|
burstSize: 24, emitEveryTicks: 1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exp.addObject('SphereEmitter', SRC);
|
|
27
|
-
exp.addObject('Wall', {
|
|
25
|
+
}],
|
|
26
|
+
absorber: [Wall, {
|
|
28
27
|
rect: [Math.round(W * 0.6), Math.round(H * 0.2), 20, Math.round(H * 0.6)],
|
|
29
28
|
collisionBehavior: 'absorb', capacity: 64,
|
|
30
|
-
|
|
31
|
-
}
|
|
29
|
+
color: [150, 90, 210]
|
|
30
|
+
}],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default function buildExperiment(exp) {
|
|
34
|
+
exp.createCanvas(W, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
35
|
+
exp.declare(parts);
|
|
32
36
|
}
|
|
@@ -24,6 +24,8 @@ import { Rectangle } from '@descix/frqtl-web/parts';
|
|
|
24
24
|
import * as OrbitBag from '@descix/frqtl-web/compat/renderer/orbit-bag.js';
|
|
25
25
|
import { engine } from '@descix/frqtl-web/compat/engine.js';
|
|
26
26
|
|
|
27
|
+
export const parts = {};
|
|
28
|
+
|
|
27
29
|
export default function buildExperiment(exp) {
|
|
28
30
|
var W = 600, H = 600; // THE WORLD this scene states (its co-init call below)
|
|
29
31
|
|
|
@@ -34,6 +36,7 @@ export default function buildExperiment(exp) {
|
|
|
34
36
|
|
|
35
37
|
// ── WORLD — a 2D canvas + universe: quark layer fundamental, λ multiplier 1, quarks confined ──
|
|
36
38
|
exp.createCanvas(W, H, { dimension: QUARK_LAYER, wavelengthScale: 1, bonding: true });
|
|
39
|
+
exp.declare(parts);
|
|
37
40
|
var u = exp.universe;
|
|
38
41
|
|
|
39
42
|
var quarkDim = u.addDimension(QUARK_LAYER);
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
import { PointSource } from '@descix/frqtl-web/parts';
|
|
20
20
|
|
|
21
|
+
export const parts = {};
|
|
22
|
+
|
|
21
23
|
export default function buildExperiment(exp) {
|
|
22
24
|
var W = 600, H = 600; // THE WORLD this scene states (its co-init call below)
|
|
23
25
|
|
|
@@ -29,6 +31,7 @@ export default function buildExperiment(exp) {
|
|
|
29
31
|
// iframeIntervalSeconds 1/60: parents are generated EVERY tick (the engine freezes the interval at
|
|
30
32
|
// init, so it rides the world cfg — at the default 1 s, Phase B ran every 60 ticks).
|
|
31
33
|
exp.createCanvas(W, H, { dimension: 0, wavelengthScale: 1, frames: true, iframeIntervalSeconds: 1 / 60 });
|
|
34
|
+
exp.declare(parts);
|
|
32
35
|
var u = exp.universe;
|
|
33
36
|
|
|
34
37
|
// ── THE SOURCE — at the centre, no initial momentum, random charge ──────
|
|
@@ -21,18 +21,27 @@
|
|
|
21
21
|
* the oven, the thermostat, the sink. Same calls, same order, same seed ⇒ the same RNG stream.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { CollisionActions, CollisionTypes, FrqtlControlOverlay, FrqtlControlsPanel, Rectangle } from '@descix/frqtl-web/parts';
|
|
24
|
+
import { CollisionActions, CollisionTypes, FrqtlControlOverlay, FrqtlControlsPanel, LargeObject, Rectangle } from '@descix/frqtl-web/parts';
|
|
25
25
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const W = 600, H = 400; // THE WORLD this scene states (its co-init call below)
|
|
28
|
+
const SINK_DEPTH = 30; // the absorbing strip at the right canvas edge (px)
|
|
29
|
+
|
|
30
|
+
export const parts = {
|
|
31
|
+
// ── THE SINK — the experiment boundary on the slit side ─────────────────
|
|
32
|
+
radiation_sink: [LargeObject, {
|
|
33
|
+
rect: { left: W - SINK_DEPTH, top: 0, w: SINK_DEPTH, h: H },
|
|
34
|
+
collision_type: CollisionTypes.NONE, actions: [CollisionActions.ABSORB],
|
|
35
|
+
reposition: 'closest', color: [0, 0, 0]
|
|
36
|
+
}],
|
|
37
|
+
};
|
|
29
38
|
|
|
39
|
+
export default function buildExperiment(exp) {
|
|
30
40
|
var TEMPERATURE = 1500; // target quanta inside the cavity (the knob below)
|
|
31
41
|
var QUANTUM_DIM = 3; // the layer leaves promote into: an 8 px box edge, 64 cells of capacity in 2D
|
|
32
42
|
var BOX_FRACTION = 1 / 3; // the cavity's outer box, as a fraction of the canvas
|
|
33
43
|
var WALL = 10; // wall thickness (px)
|
|
34
44
|
var SLIT_HEIGHT = 60; // the radiation slit in the right wall (px)
|
|
35
|
-
var SINK_DEPTH = 30; // the absorbing strip at the right canvas edge (px)
|
|
36
45
|
|
|
37
46
|
// ── WORLD — a 2D canvas + universe, fundamental layer 0 (1x1 leaves), λ multiplier 1 ──
|
|
38
47
|
exp.createCanvas(W, H, { dimension: 0, wavelengthScale: 1 });
|
|
@@ -57,13 +66,7 @@ export default function buildExperiment(exp) {
|
|
|
57
66
|
});
|
|
58
67
|
u.experimentTickFunctions[thermostatSlot] = oven.getTemperatureTickFunction();
|
|
59
68
|
|
|
60
|
-
//
|
|
61
|
-
exp.addObject('LargeObject', {
|
|
62
|
-
name: 'radiation_sink',
|
|
63
|
-
rect: { left: W - SINK_DEPTH, top: 0, w: SINK_DEPTH, h: H },
|
|
64
|
-
collision_type: CollisionTypes.NONE, actions: [CollisionActions.ABSORB],
|
|
65
|
-
reposition: 'closest', color: [0, 0, 0]
|
|
66
|
-
});
|
|
69
|
+
exp.declare(parts); // the sink (parts.radiation_sink)
|
|
67
70
|
|
|
68
71
|
// ── THE KNOB — temperature, live (the oven's own thermostat target) ─────
|
|
69
72
|
var registry = ControlRegistry.makeControlRegistry();
|
|
@@ -22,6 +22,8 @@
|
|
|
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
|
+
export const parts = {};
|
|
26
|
+
|
|
25
27
|
export default function buildExperiment(exp) {
|
|
26
28
|
var W = 760, H = 520; // THE WORLD this scene states (its co-init call below)
|
|
27
29
|
var LEFT = 60, TOP = 15; // the circuit's top-left
|
|
@@ -40,6 +42,7 @@ export default function buildExperiment(exp) {
|
|
|
40
42
|
|
|
41
43
|
// ── WORLD — a 2D canvas + universe; the emitter's own world (its worldCfg) ──
|
|
42
44
|
exp.createCanvas(W, H, SphereEmitter.worldCfg(emitterBag(null)));
|
|
45
|
+
exp.declare(parts);
|
|
43
46
|
var u = exp.universe;
|
|
44
47
|
|
|
45
48
|
var MIN = LogicGate.MINIMAL; // bore 3 / wall 3 / length 20
|
|
@@ -57,7 +60,7 @@ export default function buildExperiment(exp) {
|
|
|
57
60
|
{ input: 'A', gate: 'SUM', port: 'inPortA' }, { input: 'B', gate: 'SUM', port: 'inPortB' },
|
|
58
61
|
{ input: 'B', gate: 'CARRY', port: 'inPortA' }, { input: 'A', gate: 'CARRY', port: 'inPortB' }
|
|
59
62
|
];
|
|
60
|
-
var rails = { A: [], B: [] },
|
|
63
|
+
var rails = { A: [], B: [] }, traces = [];
|
|
61
64
|
LANES.forEach(function (row, i) {
|
|
62
65
|
var port = gates[row.gate][row.port], y = port[1];
|
|
63
66
|
var t = new Trace(u, { path: [[port[0] - LEAD, y], [port[0], y]], bore: MIN.bore,
|
|
@@ -66,7 +69,7 @@ export default function buildExperiment(exp) {
|
|
|
66
69
|
{ name: 'input' + row.input + i })); // parked inside the bore mouth
|
|
67
70
|
em.setActive(row.input === 'A' ? STATE.a : STATE.b);
|
|
68
71
|
rails[row.input].push(em);
|
|
69
|
-
|
|
72
|
+
traces.push(t);
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
// ── THE READOUTS — grounded traces off each gate's outlet: their current IS the answer ──
|
|
@@ -75,8 +78,8 @@ export default function buildExperiment(exp) {
|
|
|
75
78
|
bore: MIN.bore, wallThickness: MIN.wallThickness, ground: true, name: name, color: [60, 160, 220] });
|
|
76
79
|
}
|
|
77
80
|
var readouts = { SUM: readout(sum, 'SUM_out'), CARRY: readout(carry, 'CARRY_out') };
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
traces.push(readouts.SUM, readouts.CARRY);
|
|
82
|
+
traces.forEach(function (p) { p.attachToRunner(exp.runner); }); // every trace reads the tick stream (a declared gate subscribed at addObject)
|
|
80
83
|
|
|
81
84
|
function setInput(which, on) {
|
|
82
85
|
STATE[which] = !!on;
|
|
@@ -22,12 +22,15 @@ import { CameraControls, FrqtlControlOverlay, FrqtlControlsPanel } from '@descix
|
|
|
22
22
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
23
23
|
import { collisionMatrixLanes } from '@descix/frqtl-web/objects/setup/_collisionMatrixLanes.js';
|
|
24
24
|
|
|
25
|
+
export const parts = {};
|
|
26
|
+
|
|
25
27
|
export default function buildExperiment(exp) {
|
|
26
28
|
var W = 700, H = 700, D = 700; // THE WORLD this scene states (its co-init call below)
|
|
27
29
|
var LEVEL = 6; // the voxel layer: 64 px bodies, capacity 4096
|
|
28
30
|
|
|
29
31
|
// ── WORLD — a 3D scene + universe W×H×D, fundamental layer 0, λ multiplier 1 ──
|
|
30
32
|
exp.createScene(W, H, D, { dimension: 0, wavelengthScale: 1 });
|
|
33
|
+
exp.declare(parts);
|
|
31
34
|
var u = exp.universe;
|
|
32
35
|
u.withInterQuantumCollisions = true;
|
|
33
36
|
u.addDimension(LEVEL);
|