@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
|
@@ -662,12 +662,21 @@ import { libPathOf } from '../../../shim/project/specifiers.js';
|
|
|
662
662
|
return { files: files, record: out.record, clips: out.clips };
|
|
663
663
|
};
|
|
664
664
|
|
|
665
|
-
/**
|
|
665
|
+
/** rec.changes (the page's own read: a deep-frozen copy per read) as plain JSON. */
|
|
666
|
+
var _IN_PAGE_RECORD_CHANGES = function () {
|
|
667
|
+
var p = window.__frqtlHostedRecord;
|
|
668
|
+
if (!p || !p.rec) { throw new Error('[FrqtlHostedEyes/in-page] no Record is open — eyes.record.start() first.'); }
|
|
669
|
+
return JSON.parse(JSON.stringify(p.rec.changes));
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
/** One act through the page's own doors: toSketch({by}), setDeclared(name, patch, {by}), fork(name, Kind, {by}),
|
|
673
|
+
* placeDeclaredAt(name, point). */
|
|
666
674
|
var _IN_PAGE_ACT = async function (args) {
|
|
667
675
|
var exp = window.FrqtlPage.experiment;
|
|
668
676
|
var r = window.FrqtlPage.runner;
|
|
669
677
|
if (args.door === 'toSketch') { return exp.toSketch(args.opts); }
|
|
670
678
|
if (args.door === 'setDeclared') { return exp.setDeclared(args.name, args.patch, args.opts); }
|
|
679
|
+
if (args.door === 'fork') { return exp.fork(args.name, args.Kind, args.opts); }
|
|
671
680
|
if (args.door === 'placeDeclaredAt') {
|
|
672
681
|
var d = r.declared.find(function (o) { return o.name === args.name; });
|
|
673
682
|
if (!d) { throw new Error('[FrqtlHostedEyes/in-page] no declared object is named ' + JSON.stringify(args.name) + '.'); }
|
|
@@ -1113,6 +1122,8 @@ import { libPathOf } from '../../../shim/project/specifiers.js';
|
|
|
1113
1122
|
* film(opts) rec.film(opts) → its clip spans. Drive the world while it films:
|
|
1114
1123
|
* `const f = eyes.record.film(o); await eyes.driveToTick(n); await f`
|
|
1115
1124
|
* stop() rec.stop() → { files: { path: string | Uint8Array }, record, clips } | { empty: true }
|
|
1125
|
+
* changes() rec.changes → the rows so far, plain JSON (the page's own read). REJECTS when no Record is
|
|
1126
|
+
* open (as stop() does). One act's rows: read the length before the act, slice after.
|
|
1116
1127
|
*/
|
|
1117
1128
|
record: {
|
|
1118
1129
|
start: function () {
|
|
@@ -1125,6 +1136,9 @@ import { libPathOf } from '../../../shim/project/specifiers.js';
|
|
|
1125
1136
|
},
|
|
1126
1137
|
stop: function () {
|
|
1127
1138
|
return page.evaluate(_IN_PAGE_RECORD_STOP, { modules: modules }).then(_rethrowRefused).then(_folderFromPage);
|
|
1139
|
+
},
|
|
1140
|
+
changes: function () {
|
|
1141
|
+
return page.evaluate(_IN_PAGE_RECORD_CHANGES);
|
|
1128
1142
|
}
|
|
1129
1143
|
},
|
|
1130
1144
|
|
|
@@ -1132,11 +1146,13 @@ import { libPathOf } from '../../../shim/project/specifiers.js';
|
|
|
1132
1146
|
* Acts through the page's own doors (the writer's rows come from these, never from node):
|
|
1133
1147
|
* toSketch({ by }) exp.toSketch
|
|
1134
1148
|
* setDeclared(name, patch, { by }) exp.setDeclared
|
|
1149
|
+
* fork(name, Kind, { by }) exp.fork → { fork, file, from, files } (the files as text)
|
|
1135
1150
|
* placeDeclaredAt(name, [x, y, z?]) runner.placeDeclaredAt on the declared object of that name
|
|
1136
1151
|
*/
|
|
1137
1152
|
act: {
|
|
1138
1153
|
toSketch: function (opts) { return page.evaluate(_IN_PAGE_ACT, { door: 'toSketch', opts: opts }); },
|
|
1139
1154
|
setDeclared: function (name, patch, opts) { return page.evaluate(_IN_PAGE_ACT, { door: 'setDeclared', name: name, patch: patch, opts: opts }); },
|
|
1155
|
+
fork: function (name, Kind, opts) { return page.evaluate(_IN_PAGE_ACT, { door: 'fork', name: name, Kind: Kind, opts: opts }); },
|
|
1140
1156
|
placeDeclaredAt: function (name, point) { return page.evaluate(_IN_PAGE_ACT, { door: 'placeDeclaredAt', name: name, point: point }); }
|
|
1141
1157
|
},
|
|
1142
1158
|
|
|
@@ -254,8 +254,8 @@ function _resolveBounds(field, value) {
|
|
|
254
254
|
|
|
255
255
|
function _label(field) {
|
|
256
256
|
// An optional display `label` overrides the field NAME for the
|
|
257
|
-
// control's shown label WITHOUT renaming the schema field (the
|
|
258
|
-
//
|
|
257
|
+
// control's shown label WITHOUT renaming the schema field (the capture key
|
|
258
|
+
// stays `field.name`). e.g. leafDimension → "Emit Dimension", the global
|
|
259
259
|
// shape capability → "Default Shape". Absent → the name, as before (byte-identical).
|
|
260
260
|
var base = (typeof field.label === 'string' && field.label) ? field.label : field.name;
|
|
261
261
|
return field.unit ? (base + ' (' + field.unit + ')') : base;
|
|
@@ -556,8 +556,8 @@ function _rectArrayHandler(field, env) {
|
|
|
556
556
|
value: r.slice(),
|
|
557
557
|
description: (field.description || '') +
|
|
558
558
|
' (DISPLAY ONLY — the bind contract {object,prop} has no array-index ' +
|
|
559
|
-
'addressing; a live per-entry write needs either bind.index
|
|
560
|
-
'extension
|
|
559
|
+
'addressing; a live per-entry write needs either bind.index or a shim-contract ' +
|
|
560
|
+
'extension (an onChange closure is not a bind). See ' +
|
|
561
561
|
'makeControlsForObject.js\'s module docstring, scope note #2.)'
|
|
562
562
|
});
|
|
563
563
|
}
|
|
@@ -730,7 +730,7 @@ function makeControlsForObject(wrapper, ctx, opts) {
|
|
|
730
730
|
// field-name list) marks an ENGINE INTERNAL that must never surface as a
|
|
731
731
|
// designer control/display (e.g. LargeObject.program's raw op-program AST,
|
|
732
732
|
// the derived op-program-scope geometry accessors). Skipped entirely here —
|
|
733
|
-
// no control, no display row. Still engineVisible + captured
|
|
733
|
+
// no control, no display row. Still engineVisible + captured by the parts writer.
|
|
734
734
|
if (field.internal === true) { continue; }
|
|
735
735
|
var handler = TYPE_DISPATCH[field.type];
|
|
736
736
|
if (!handler) {
|
|
@@ -22,12 +22,20 @@
|
|
|
22
22
|
* `closedBy: 'stop'`, or — when the facade's world is re-authored (a build of
|
|
23
23
|
* text that is not the held text) — the folder of the world before, with
|
|
24
24
|
* `closedBy: 'reauthor'` (rejected when that folder is refused, as stop() is).
|
|
25
|
+
* Both carry `droppedClips`: the clips still filming when the Record closed, which
|
|
26
|
+
* the close drops (0 on a stop, which refuses while a clip films); a refused
|
|
27
|
+
* re-author close carries it as err.droppedClips beside err.closedBy.
|
|
25
28
|
* A refused close nobody READ (the getter records the read) makes the facade's
|
|
26
29
|
* next exp.startRecord() reject, naming it.
|
|
27
30
|
* err.record every rejection of stop() and of `closed` carries { changes, clips } — the
|
|
28
31
|
* Record's rows and clip spans — so a host can name them unheld instead of losing them.
|
|
29
32
|
*
|
|
30
|
-
* `closedBy` and the clip spans are the host's (a lab's own `filmed` list); record.json never
|
|
33
|
+
* `closedBy`, `droppedClips` and the clip spans are the host's (a lab's own `filmed` list); record.json never
|
|
34
|
+
* carries them.
|
|
35
|
+
*
|
|
36
|
+
* An act (toSketch, setDeclared, fork) is ADMITTED only while the Record is open and not stopping (`_admit`, asked
|
|
37
|
+
* before the act changes the held text): an act during an async stop() is refused naming "stopping", and the
|
|
38
|
+
* held text is unchanged.
|
|
31
39
|
*
|
|
32
40
|
* WHERE ITS PARTS COME FROM — nothing here captures a second time:
|
|
33
41
|
* rows the writer's own edits at act end (RecipeContext.toSketch / setDeclared call `_row`); only what
|
|
@@ -152,10 +160,20 @@ Record.requireBy = function (by, where) {
|
|
|
152
160
|
return by;
|
|
153
161
|
};
|
|
154
162
|
|
|
163
|
+
/**
|
|
164
|
+
* THE admission check: an act may write into this Record only while it is open and not stopping. The facade asks
|
|
165
|
+
* it BEFORE an act changes the held text (toSketch, setDeclared, fork), and `_row` asks it again: one predicate.
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
Record.prototype._admit = function (where) {
|
|
169
|
+
var at = where ? where + ': ' : '';
|
|
170
|
+
if (!this._open) { _fail(at + 'this Record is closed — no row is written into it.'); }
|
|
171
|
+
if (this._stopping) { _fail(at + 'this Record is stopping — an act during stop() belongs to the next Record.'); }
|
|
172
|
+
};
|
|
173
|
+
|
|
155
174
|
/** One change row, at the Record's current tick, in the order acted. @private */
|
|
156
175
|
Record.prototype._row = function (row) {
|
|
157
|
-
|
|
158
|
-
if (this._stopping) { _fail('this Record is stopping — an act during stop() belongs to the next Record.'); }
|
|
176
|
+
this._admit();
|
|
159
177
|
this._rows.push({ tick: this._tick, name: row.name, op: row.op, by: row.by, from: row.from, to: row.to });
|
|
160
178
|
};
|
|
161
179
|
|
|
@@ -302,7 +320,7 @@ Record.prototype.stop = async function () {
|
|
|
302
320
|
finally { this._stopping = false; }
|
|
303
321
|
this._closedBy = 'stop';
|
|
304
322
|
this._detach();
|
|
305
|
-
this._closedSettle.resolve(Object.assign({}, out, { closedBy: 'stop' }));
|
|
323
|
+
this._closedSettle.resolve(Object.assign({}, out, { closedBy: 'stop', droppedClips: 0 }));
|
|
306
324
|
return out;
|
|
307
325
|
};
|
|
308
326
|
|
|
@@ -312,13 +330,15 @@ Record.prototype.stop = async function () {
|
|
|
312
330
|
*/
|
|
313
331
|
Record.prototype._closeOnReauthor = function (held) {
|
|
314
332
|
var self = this;
|
|
333
|
+
var dropped = this._inflight; // clips still filming: detaching drops them (their end() finds the Record closed)
|
|
315
334
|
this._closedBy = 'reauthor';
|
|
316
335
|
this._detach();
|
|
317
336
|
this._folder(held).then(function (out) {
|
|
318
|
-
self._closedSettle.resolve(Object.assign({}, out, { closedBy: 'reauthor' }));
|
|
337
|
+
self._closedSettle.resolve(Object.assign({}, out, { closedBy: 'reauthor', droppedClips: dropped }));
|
|
319
338
|
}, function (e) {
|
|
320
339
|
self._refused = self._carry(e);
|
|
321
340
|
self._refused.closedBy = 'reauthor';
|
|
341
|
+
self._refused.droppedClips = dropped;
|
|
322
342
|
self._closedSettle.reject(self._refused);
|
|
323
343
|
});
|
|
324
344
|
};
|
|
@@ -94,7 +94,7 @@ import * as FrameDataSchema from '../../../shim/frame-data/index.js';
|
|
|
94
94
|
engineVisible: true,
|
|
95
95
|
description: 'Frame center Z position (depth, physics pixels). ' +
|
|
96
96
|
'Default 0.0 when active_axes.z is false (2D mode). ' +
|
|
97
|
-
'
|
|
97
|
+
'Set by the 3D emitter; moved by the ' +
|
|
98
98
|
'independent z yo-yo coin when active_axes.z is true. ' +
|
|
99
99
|
'Cube edge = w (spec §11 — no separate d field; cubes only).'
|
|
100
100
|
},
|
|
@@ -322,7 +322,7 @@ import * as FrameDataSchema from '../../../shim/frame-data/index.js';
|
|
|
322
322
|
engineVisible: true,
|
|
323
323
|
description: 'Derived (scope-read): frame CENTER Z before this tick\'s move() ' +
|
|
324
324
|
'(physics pixels). Default 0.0 when active_axes.z is false. ' +
|
|
325
|
-
'
|
|
325
|
+
'Saved by Frame::move_frame alongside prev_x/prev_y. ' +
|
|
326
326
|
'Read-only — write THROWS (engine-owned per-tick snapshot).'
|
|
327
327
|
},
|
|
328
328
|
// ── Forbidden: engine-owned topology ──────────────────────────────
|
|
@@ -273,7 +273,7 @@ export function shapeValuesMap() {
|
|
|
273
273
|
// existing scene is byte-identical. A live edit (updateObjectProperty) sets the
|
|
274
274
|
// per-instance override that _visibleLargeObjects reads.
|
|
275
275
|
description: 'When this object\'s visual shell renders (render-only): design = only ' +
|
|
276
|
-
'while authoring (
|
|
276
|
+
'while authoring (the marker default); run = always visible (the "red light ' +
|
|
277
277
|
'bulb" opt-in); never = hidden. Generalizes the design-only marker suppression ' +
|
|
278
278
|
'at the single _visibleLargeObjects authority; engine collision is unaffected.'
|
|
279
279
|
},
|
|
@@ -322,7 +322,7 @@ export function shapeValuesMap() {
|
|
|
322
322
|
// designer control — the raw JSON must never render in the STUDIO Inspector.
|
|
323
323
|
// `internal:true` is the ONE authority for "hide from the designer surface"
|
|
324
324
|
// (consumed by makeControlsForObject + AuthorView's renderInspector); the
|
|
325
|
-
// program is still engineVisible + captured
|
|
325
|
+
// program is still engineVisible + captured (the parts writer's round trip).
|
|
326
326
|
internal: true,
|
|
327
327
|
description: 'Op-program attached to this LO (§10.3 SLICE 3). ' +
|
|
328
328
|
'Shape: { on: "collision"|"tick", ops: [{ set: "<handle>.<prop>", to: ExprAST }] }. ' +
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* The list is derived from what `initArgs()` below ACTUALLY reads. `interQuantumCollisions`
|
|
25
25
|
* is in it because it is still ACCEPTED (inert — it fed a removed positional slot) and
|
|
26
|
-
*
|
|
27
|
-
* would make every saved scene throw.
|
|
26
|
+
* saved scenes carry it: dropping it from the vocabulary would make them throw.
|
|
28
27
|
*
|
|
29
28
|
* @type {Array<{key:string, effect:string}>}
|
|
30
29
|
*/
|
|
@@ -34,8 +34,7 @@
|
|
|
34
34
|
* — as `lib/frqtl/scenes/blackbody.js` sets it. ENGINE physics; no new
|
|
35
35
|
* op-programs. Expected JS↔WASM divergence on parent-frame count (deferred §6).
|
|
36
36
|
*
|
|
37
|
-
* Set any flag to `false` only for progressive test isolation (L1/L2/L3 runs
|
|
38
|
-
* `experiments/blackbody-substrate/run.js`). The default (all true) runs the
|
|
37
|
+
* Set any flag to `false` only for progressive test isolation (L1/L2/L3 runs). The default (all true) runs the
|
|
39
38
|
* scene's flags (brownian on, inter-quantum collisions on).
|
|
40
39
|
*
|
|
41
40
|
* ## Thermostat control loop (as-intended §2.1, Dispatch C2)
|
|
@@ -177,6 +177,12 @@ import * as OrbitBag from '../compat/renderer/orbit-bag.js';
|
|
|
177
177
|
if (mountEl && mountEl.__frqtlCameraHandle && mountEl.__frqtlCameraHandle.mounted) {
|
|
178
178
|
return mountEl.__frqtlCameraHandle;
|
|
179
179
|
}
|
|
180
|
+
// ...and per RUNNER: one runner has one camera controller. A host that mounted the pads before the scene
|
|
181
|
+
// could (STUDIO: the sizeless page builds before its runner has a control host), then a re-run in the same
|
|
182
|
+
// document (a fork, setDeclared) that reaches the scene's own attach with another container, gets the live one.
|
|
183
|
+
if (runner.__frqtlCameraHandle && runner.__frqtlCameraHandle.mounted) {
|
|
184
|
+
return runner.__frqtlCameraHandle;
|
|
185
|
+
}
|
|
180
186
|
|
|
181
187
|
var doc = (mountEl && mountEl.ownerDocument) ? mountEl.ownerDocument
|
|
182
188
|
: (typeof document !== 'undefined' ? document : null);
|
|
@@ -581,6 +587,7 @@ import * as OrbitBag from '../compat/renderer/orbit-bag.js';
|
|
|
581
587
|
if (mountEl && mountEl.__frqtlCameraHandle === liveHandle) {
|
|
582
588
|
mountEl.__frqtlCameraHandle = null;
|
|
583
589
|
}
|
|
590
|
+
if (runner.__frqtlCameraHandle === liveHandle) { runner.__frqtlCameraHandle = null; }
|
|
584
591
|
if (rafId !== null) { cAF(rafId); rafId = null; }
|
|
585
592
|
// Force-release any in-flight joystick drag (detaches its doc listeners).
|
|
586
593
|
for (var i = 0; i < padCleanups.length; i++) {
|
|
@@ -617,6 +624,7 @@ import * as OrbitBag from '../compat/renderer/orbit-bag.js';
|
|
|
617
624
|
};
|
|
618
625
|
// F4: mark the container as owning this live instance (idempotency guard above).
|
|
619
626
|
if (mountEl) { mountEl.__frqtlCameraHandle = liveHandle; }
|
|
627
|
+
runner.__frqtlCameraHandle = liveHandle;
|
|
620
628
|
return liveHandle;
|
|
621
629
|
}
|
|
622
630
|
|
|
@@ -111,7 +111,7 @@ import { registerKind } from '../objects/kinds.js';
|
|
|
111
111
|
var depth = (opts.depth !== undefined && opts.depth !== null) ? opts.depth : null;
|
|
112
112
|
|
|
113
113
|
this.universe = universe;
|
|
114
|
-
this._schemaKind =
|
|
114
|
+
this._schemaKind = this.constructor.name;
|
|
115
115
|
|
|
116
116
|
// The world rect, through the ONE normalizer (a zero-high detector counts nothing
|
|
117
117
|
// and would report it as physics — so a missing dimension throws, never zeroes).
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* so every Wall option, live method and property accessor applies verbatim.
|
|
25
25
|
* When neither `preset` nor `collisionBehavior` is supplied it defaults to the
|
|
26
26
|
* `detector-screen` preset (destroy + count) — the counting authority. Extra
|
|
27
|
-
* options, all DECLARED descriptor fields (so
|
|
28
|
-
* rebuilds the heatmap wall from
|
|
27
|
+
* options, all DECLARED descriptor fields (so the parts writer captures them and the
|
|
28
|
+
* sketch rebuilds the heatmap wall from that state — the W2 declared-field rule):
|
|
29
29
|
*
|
|
30
30
|
* @param {number} [opts.binPx=12] — face bin size in physics pixels. Bins
|
|
31
31
|
* are anchored at the FACE ORIGIN (its top / near-z corner), so the map runs
|
|
@@ -105,7 +105,7 @@ import { registerKind, kindOf } from '../objects/kinds.js';
|
|
|
105
105
|
* painting of the only spatial information a 2D scene carries.
|
|
106
106
|
*
|
|
107
107
|
* @param {Array|Object} rect — the LO rect ([l,t,w,h] / [l,t,z,w,h,d] /
|
|
108
|
-
* {left,top,w,h[,z,d]} — the shape a
|
|
108
|
+
* {left,top,w,h[,z,d]} — the shape a live LO's rect reads as).
|
|
109
109
|
* @param {number} binPx
|
|
110
110
|
* @returns {{top:number,height:number,z0:number,depth:number,binPx:number,
|
|
111
111
|
* canvasW:number,canvasH:number}}
|
|
@@ -248,9 +248,9 @@ import { registerKind, kindOf } from '../objects/kinds.js';
|
|
|
248
248
|
opts = merged;
|
|
249
249
|
}
|
|
250
250
|
WallCls.call(this, universe, opts);
|
|
251
|
-
this._schemaKind =
|
|
251
|
+
this._schemaKind = this.constructor.name;
|
|
252
252
|
|
|
253
|
-
// Declared heatmap fields (own props →
|
|
253
|
+
// Declared heatmap fields (own props → the parts writer's captureProps).
|
|
254
254
|
this.binPx = _num(opts.binPx, 12);
|
|
255
255
|
this.refreshMs = _num(opts.refreshMs, 100);
|
|
256
256
|
this.heatBase = _num(opts.heatBase, 0.35);
|
|
@@ -399,8 +399,8 @@ import { registerKind, kindOf } from '../objects/kinds.js';
|
|
|
399
399
|
*
|
|
400
400
|
* FIELDS ARE FERRIED FROM WallDescriptor (never re-listed — the
|
|
401
401
|
* schema-mirror-drift discipline): this descriptor is Wall's field table plus
|
|
402
|
-
* the three heatmap fields. Every one is a DECLARED field, so
|
|
403
|
-
*
|
|
402
|
+
* the three heatmap fields. Every one is a DECLARED field, so the parts writer
|
|
403
|
+
* captures it and the sketch rebuilds the heatmap wall from that state.
|
|
404
404
|
*/
|
|
405
405
|
var WallDescriptor = kindOf(WallCls).descriptor;
|
|
406
406
|
var GraphingDetectorWallDescriptor = {
|
|
@@ -35,7 +35,7 @@ import { SCOPE_DESCRIPTORS } from '../objects/schema/scopes.js';
|
|
|
35
35
|
* Build a LargeObject `.rect` for the 2D (4-element) case: a REAL Rectangle instance
|
|
36
36
|
* (is_centered=false) so a center write (`.x`/`.y`) auto-recomputes left/top and vice
|
|
37
37
|
* versa (PR-1 — audit item 3). A non-enumerable `toJSON` is attached so JSON-serializing
|
|
38
|
-
* consumers (
|
|
38
|
+
* consumers (bundle export, save-state, any JSON copy) still produce the SAME plain
|
|
39
39
|
* `{left,top,w,h}` shape they always did — the Rectangle's own enumerable fields are
|
|
40
40
|
* `_left/_x/...`, which WOULD drift; `toJSON` pins the wire shape. (`left/top/w/h` are read
|
|
41
41
|
* via getters by every direct consumer, so those are unaffected.) 6-element z/d rects are
|
|
@@ -128,7 +128,7 @@ import { SCOPE_DESCRIPTORS } from '../objects/schema/scopes.js';
|
|
|
128
128
|
collectCollided = collectCollided || false;
|
|
129
129
|
|
|
130
130
|
this.universe = universe;
|
|
131
|
-
this._schemaKind =
|
|
131
|
+
this._schemaKind = this.constructor.name;
|
|
132
132
|
// rect is [left, top, w, h] (legacy) or [left, top, z, w, h, d] (FRQTL-MAINT
|
|
133
133
|
// 2026-07-01, explicit z/depth) — is_centered=false convention either way.
|
|
134
134
|
var ra = rectToArray(rect);
|
|
@@ -267,8 +267,8 @@ import { registerKind } from '../objects/kinds.js';
|
|
|
267
267
|
};
|
|
268
268
|
// Mirror the SERIALIZABLE chartSpec fields onto the LO under their DESCRIPTOR
|
|
269
269
|
// names (restart-bug fix, 2026-07-25): captureProps(lo, LiveChartDescriptor)
|
|
270
|
-
// reads lo[field.name], so without these mirrors a
|
|
271
|
-
//
|
|
270
|
+
// reads lo[field.name], so without these mirrors a captured chart (the parts writer's
|
|
271
|
+
// capture of a declared chart) would rebuild UNBOUND
|
|
272
272
|
// with default projector/label ("detector"). The name string — never the live
|
|
273
273
|
// sourceHandle — is the captured source (re-resolved here on rebuild).
|
|
274
274
|
lo.source = sourceName;
|
|
@@ -219,7 +219,7 @@ import { registerKind } from '../objects/kinds.js';
|
|
|
219
219
|
function LogicGate(universe, opts) {
|
|
220
220
|
opts = opts || {};
|
|
221
221
|
this.universe = universe;
|
|
222
|
-
this._schemaKind =
|
|
222
|
+
this._schemaKind = this.constructor.name;
|
|
223
223
|
|
|
224
224
|
var TunnelCls = _Tunnel;
|
|
225
225
|
var WallCls = _Wall;
|
|
@@ -705,8 +705,8 @@ import { registerKind } from '../objects/kinds.js';
|
|
|
705
705
|
var gate = new GateCls(ctx.runner.universe, opts);
|
|
706
706
|
gate._schemaKind = Class.name;
|
|
707
707
|
// The comparator is a HOST tick-fn: subscribe it to the runner's per-tick
|
|
708
|
-
// fan-out here, off the DECLARED fields, so the switching
|
|
709
|
-
//
|
|
708
|
+
// fan-out here, off the DECLARED fields, so the switching belongs to the declared
|
|
709
|
+
// object: every build of it (a sketch entry, a Restart) re-wires it. Idempotent.
|
|
710
710
|
if (typeof gate.attachToRunner === 'function') { gate.attachToRunner(ctx.runner); }
|
|
711
711
|
return gate;
|
|
712
712
|
}
|
|
@@ -199,7 +199,7 @@ import { registerKind } from '../objects/kinds.js';
|
|
|
199
199
|
options = options || {};
|
|
200
200
|
|
|
201
201
|
this.universe = universe;
|
|
202
|
-
this._schemaKind =
|
|
202
|
+
this._schemaKind = this.constructor.name;
|
|
203
203
|
this.name = options.name || 'oven'; // its declared name (the Tunnel/LogicGate idiom)
|
|
204
204
|
this.temperature = options.temperature || 300;
|
|
205
205
|
|
|
@@ -169,7 +169,7 @@ import { declaredCount } from '../objects/ObjectFactory.js';
|
|
|
169
169
|
var zDepth = opts.zDepth !== undefined ? opts.zDepth : null;
|
|
170
170
|
|
|
171
171
|
this.universe = universe;
|
|
172
|
-
this._schemaKind =
|
|
172
|
+
this._schemaKind = this.constructor.name;
|
|
173
173
|
this.x = x;
|
|
174
174
|
this.y = y;
|
|
175
175
|
this.diameter = diameter;
|
|
@@ -82,7 +82,7 @@ import { V1_REPLACEMENTS } from '../objects/v1-replacements.js';
|
|
|
82
82
|
var zDepth = (depth !== null) ? depth : null;
|
|
83
83
|
|
|
84
84
|
this.universe = universe;
|
|
85
|
-
this._schemaKind =
|
|
85
|
+
this._schemaKind = this.constructor.name;
|
|
86
86
|
|
|
87
87
|
// Light source diameter: hardcoded to 16 matching devsdk's pointSOURCESIZE.
|
|
88
88
|
// (wavelength * 0.25 * 2 = 32 would double the emission radius and velocity)
|
|
@@ -137,7 +137,7 @@ import { ObjectSchema } from '../objects/schema/index.js';
|
|
|
137
137
|
*/
|
|
138
138
|
function SlitScreen(universe, wallLeft, wallWidth, canvasHeight, wavelength, absorbAtSlitWall, detectAtSlits, slitHeight, gapHeight, depth, holeDepth, linerScatter) {
|
|
139
139
|
this.universe = universe;
|
|
140
|
-
this._schemaKind =
|
|
140
|
+
this._schemaKind = this.constructor.name;
|
|
141
141
|
this.wavelength = wavelength;
|
|
142
142
|
this.depth = (depth !== undefined && depth !== null) ? depth : null;
|
|
143
143
|
// 'current' (default/omitted) = today's exact liner behavior, byte-identical.
|
|
@@ -157,7 +157,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
157
157
|
* @param {Object} [opts]
|
|
158
158
|
*/
|
|
159
159
|
function SphereEmitter(position, opts) {
|
|
160
|
-
this._schemaKind =
|
|
160
|
+
this._schemaKind = this.constructor.name;
|
|
161
161
|
// First-arg overload: an array is a position; a plain object is opts
|
|
162
162
|
// (position defaults to canvas center → byte-identical factory default).
|
|
163
163
|
if (Array.isArray(position)) {
|
|
@@ -180,7 +180,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
180
180
|
// self-throttle target — the emitter deactivates when the live population
|
|
181
181
|
// measured by the counting Wall NAMED by `gateCounter` reaches the cap, and
|
|
182
182
|
// reactivates below it. Compiled at CONSTRUCTION into a host tick-fn (see
|
|
183
|
-
// _installEmissionGate in addTo) so the gate is
|
|
183
|
+
// _installEmissionGate in addTo) so the gate is captured by the parts writer and
|
|
184
184
|
// Restart-safe (the Restart-safe host-tick-fn rule — the gate is a
|
|
185
185
|
// construction side effect of captured descriptor fields, exactly like the
|
|
186
186
|
// ABSORB radiator, NEVER free recipe JS). Validation is throw-or-apply:
|
|
@@ -275,7 +275,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
275
275
|
* `fn(universe)` seam — the SAME seam as the emit tick-fn, the blackbody
|
|
276
276
|
* thermostat, and the ABSORB radiator). Each tick it:
|
|
277
277
|
* 1. LAZILY resolves the counting Wall by NAME on the FIRST tick (handles are
|
|
278
|
-
* registration-fresh and never survive
|
|
278
|
+
* registration-fresh and never survive a rebuild — the name is the reference;
|
|
279
279
|
* lazy resolution makes emitter/counter construction order irrelevant).
|
|
280
280
|
* Unresolved after the first tick → THROW (never a silent uncapped run).
|
|
281
281
|
* 2. Reads the live population through `counter.getLiveCount(universe._lastTickData)`
|
|
@@ -447,7 +447,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
447
447
|
// captureProps(sphereObj, SphereEmitterDescriptor) reads `sphereObj[field.name]`
|
|
448
448
|
// DIRECTLY for every descriptor field (a getter satisfies it). These expose the
|
|
449
449
|
// LIVE resolved value (from the factory's control) so a dragged/edited emitter
|
|
450
|
-
// Saves at its current state — the
|
|
450
|
+
// Saves at its current state — what the parts writer captures. Boot-level fields
|
|
451
451
|
// (fundamentalWaveLength) that the control doesn't surface read from the
|
|
452
452
|
// construction opts (they cannot change live). `rect` is a WRITE-ONLY reposition
|
|
453
453
|
// adapter: its getter is deliberately `undefined` so captureProps SKIPS it —
|
|
@@ -463,7 +463,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
463
463
|
// the ObjectFactory marker diameter + makeControlsForObject's rect-seed. captureProps no
|
|
464
464
|
// longer trips on this: the descriptor's rect field is `writeOnly:true`, so captureProps
|
|
465
465
|
// SKIPS it by the descriptor flag (not by the getter being undefined) — `center` stays the
|
|
466
|
-
// single captured position authority
|
|
466
|
+
// single captured position authority. The write path
|
|
467
467
|
// (rect → setCenter) is still the applyProp adapter (updateObjectProperty).
|
|
468
468
|
get: function () {
|
|
469
469
|
var c = (this._control && typeof this._control.getCenter === 'function')
|
|
@@ -487,7 +487,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
487
487
|
Object.defineProperty(SphereEmitter.prototype, 'is2D', _liveGetter(function (c) { return c.getIs2D(); }));
|
|
488
488
|
Object.defineProperty(SphereEmitter.prototype, 'orientationDeg', _liveGetter(function (c) { return c.getOrientation(); }));
|
|
489
489
|
Object.defineProperty(SphereEmitter.prototype, 'emissionBox', _liveGetter(function (c) { return c.getEmissionBox(); }));
|
|
490
|
-
// Declared emission-gate fields (captured
|
|
490
|
+
// Declared emission-gate fields (captured by the parts writer; undefined
|
|
491
491
|
// when uncapped → captureProps SKIPS them → byte-identical legacy scenes).
|
|
492
492
|
Object.defineProperty(SphereEmitter.prototype, 'emissionCap', {
|
|
493
493
|
get: function () { return this._emissionCap; }
|
|
@@ -585,7 +585,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
585
585
|
* builder composes a LargeObject-visual marker + the factory's host emit
|
|
586
586
|
* tick-fn (SU1 — the visual LO is NONE-collision, non-leaf, zero physics).
|
|
587
587
|
*
|
|
588
|
-
* This descriptor is the SINGLE field authority: captureProps (
|
|
588
|
+
* This descriptor is the SINGLE field authority: captureProps (the parts writer),
|
|
589
589
|
* makeControlsForObject (the Inspector), and FrqtlApp.updateObjectProperty
|
|
590
590
|
* (the live-edit write path) ALL drive off it — no hand-list anywhere.
|
|
591
591
|
*
|
|
@@ -600,7 +600,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
600
600
|
* round-trip; the Inspector just renders it read-only.
|
|
601
601
|
*
|
|
602
602
|
* FIELD SET is COMPLETE w.r.t. every scene-affecting setupSphereEmitter3D opt
|
|
603
|
-
* (a missing opt = lossy
|
|
603
|
+
* (a missing opt = a lossy capture). `emissionBoxSide` is subsumed by the
|
|
604
604
|
* captured derived `emissionBox`. There is no init flag: the SKETCH inits the
|
|
605
605
|
* world once (setupSphereEmitter3D.initWorld / the co-init call), so a second emitter on
|
|
606
606
|
* one universe (6A/6B/6C) is simply a second object.
|
|
@@ -635,7 +635,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
635
635
|
live: true,
|
|
636
636
|
// PR-1 item iii: the facade's `.rect` getter now returns a REAL rect DERIVED from
|
|
637
637
|
// the live center (so computeEntityBboxForDesign reads fresh — no stale bbox). To
|
|
638
|
-
// keep `center` the SINGLE captured position authority
|
|
638
|
+
// keep `center` the SINGLE captured position authority, this
|
|
639
639
|
// field is `writeOnly:true` → captureProps SKIPS it BY THE FLAG (not by the getter
|
|
640
640
|
// being undefined). The WRITE path is unchanged: a rect write maps rect-CENTER →
|
|
641
641
|
// the emission center (control.setCenter, via applyProp).
|
|
@@ -653,7 +653,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
653
653
|
engineVisible: false,
|
|
654
654
|
live: false,
|
|
655
655
|
description: 'Emission SOURCE POSITION in absolute canvas px as [x,y,z] (the captured ' +
|
|
656
|
-
'position authority —
|
|
656
|
+
'position authority — the parts writer writes this, not rect). Edited live via the rect ' +
|
|
657
657
|
'adapter / the visual marker drag; shown read-only here.'
|
|
658
658
|
},
|
|
659
659
|
{
|
|
@@ -698,7 +698,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
698
698
|
// pattern; circulation-emission-gate-contract §2 field shapes).
|
|
699
699
|
// Sensor = a placeable counting Wall (name in gateCounter); regulation =
|
|
700
700
|
// emitter-side sugar compiled at CONSTRUCTION into a host tick-fn (the
|
|
701
|
-
// Restart-safe host-tick-fn rule —
|
|
701
|
+
// Restart-safe host-tick-fn rule — the parts writer captures these two fields and
|
|
702
702
|
// the reconstructed emitter's constructor rebuilds the gate).
|
|
703
703
|
name: 'emissionCap',
|
|
704
704
|
type: 'number',
|
|
@@ -811,7 +811,7 @@ import { CollisionTypes } from './CollisionTypes.js';
|
|
|
811
811
|
constraint: { min: 0, integer: true },
|
|
812
812
|
engineVisible: false,
|
|
813
813
|
live: true,
|
|
814
|
-
// Field NAME stays `leafDimension` (
|
|
814
|
+
// Field NAME stays `leafDimension` (the capture key; a rename = round-trip
|
|
815
815
|
// break + a second name = schema mirror — Q-A2). The Inspector shows
|
|
816
816
|
// the display LABEL "Emit Dimension"; the write routes through applyProp →
|
|
817
817
|
// control.setLeafDimension (host-side addDimension + closure var flip; the NEXT
|
|
@@ -245,7 +245,7 @@ import { Wall as _Wall } from './Wall.js';
|
|
|
245
245
|
function Trace(universe, opts) {
|
|
246
246
|
opts = opts || {};
|
|
247
247
|
this.universe = universe;
|
|
248
|
-
this._schemaKind =
|
|
248
|
+
this._schemaKind = this.constructor.name;
|
|
249
249
|
|
|
250
250
|
var path = opts.path;
|
|
251
251
|
if (!Array.isArray(path) || path.length < 2) {
|
|
@@ -239,7 +239,7 @@ import { setupTunnelPointExit } from '../objects/setup/setupTunnelPointExit.js';
|
|
|
239
239
|
function Tunnel(universe, opts) {
|
|
240
240
|
opts = opts || {};
|
|
241
241
|
this.universe = universe;
|
|
242
|
-
this._schemaKind =
|
|
242
|
+
this._schemaKind = this.constructor.name;
|
|
243
243
|
|
|
244
244
|
var left = opts.left, top = opts.top;
|
|
245
245
|
var length = opts.length, bore = opts.bore;
|
|
@@ -307,7 +307,7 @@ import { setupTunnelPointExit } from '../objects/setup/setupTunnelPointExit.js';
|
|
|
307
307
|
'contention rather than the point source. (FAIL-LOUD.)');
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
// Stash the authored construction values (the
|
|
310
|
+
// Stash the authored construction values (what the parts writer captures —
|
|
311
311
|
// the descriptor fields read these back verbatim, Box precedent).
|
|
312
312
|
this.left = left; this.top = top; this.length = length; this.bore = bore;
|
|
313
313
|
this.wallThickness = wallThickness; this.axis = axis; this.color = color;
|
|
@@ -316,7 +316,7 @@ import { setupTunnelPointExit } from '../objects/setup/setupTunnelPointExit.js';
|
|
|
316
316
|
// knob, so the Inspector must render its selector on every tunnel, which needs a
|
|
317
317
|
// defined value to read. The three directed-only tunables follow the z/depth
|
|
318
318
|
// OPT-IN precedent instead — left undefined in mirror mode so captureProps skips
|
|
319
|
-
// them and a mirror tunnel's
|
|
319
|
+
// them and a mirror tunnel's captured entry gains exactly ONE key, not four.
|
|
320
320
|
this.mode = mode;
|
|
321
321
|
if (directed) {
|
|
322
322
|
this.directedAngle = directedAngleDeg;
|
|
@@ -331,9 +331,9 @@ import { setupTunnelPointExit } from '../objects/setup/setupTunnelPointExit.js';
|
|
|
331
331
|
this.exitPhaseReset = exitPhaseReset;
|
|
332
332
|
}
|
|
333
333
|
// z/depth are DECLARED descriptor fields (TunnelDescriptor) so a 3D corridor
|
|
334
|
-
// round-trips through
|
|
335
|
-
// `undefined` — captureProps skips undefined, so a 2D
|
|
336
|
-
//
|
|
334
|
+
// round-trips through the parts writer. A FLAT (2D) tunnel leaves them
|
|
335
|
+
// `undefined` — captureProps skips undefined, so a 2D capture carries
|
|
336
|
+
// no `"z":0,"depth":null` noise.
|
|
337
337
|
if (hasDepth) { this.z = z; this.depth = depth; }
|
|
338
338
|
|
|
339
339
|
// ── Mirror wall rects (two parallel plates a `bore` apart) ───────────
|
|
@@ -734,9 +734,9 @@ import { setupTunnelPointExit } from '../objects/setup/setupTunnelPointExit.js';
|
|
|
734
734
|
* Subscribe this tunnel's redirect to a runner's per-tick fan-out
|
|
735
735
|
* (`FrqtlApp.addTickListener` — the multi-subscriber peer of the page's single
|
|
736
736
|
* `onTick` slot, so it never steals it). Called by the ObjectFactory builder, which is
|
|
737
|
-
* what makes the directed mode
|
|
738
|
-
* descriptor field on the facade, so
|
|
739
|
-
*
|
|
737
|
+
* what makes the directed mode part of the declared object: the mode is a DECLARED
|
|
738
|
+
* descriptor field on the facade, so every build of it (a sketch entry, a Restart)
|
|
739
|
+
* re-wires itself.
|
|
740
740
|
*
|
|
741
741
|
* Idempotent, and a no-op for a mirror tunnel or a runner without the fan-out.
|
|
742
742
|
* @param {Object} runner - an FrqtlApp (needs `addTickListener`).
|
|
@@ -1045,8 +1045,8 @@ import { setupTunnelPointExit } from '../objects/setup/setupTunnelPointExit.js';
|
|
|
1045
1045
|
// mode:'directed' — subscribe the host redirect to the runner's per-tick fan-out.
|
|
1046
1046
|
// Self-gating + idempotent, so this is called unconditionally (a mirror tunnel
|
|
1047
1047
|
// no-ops). Wiring it HERE, off the DECLARED `mode` field, is what makes the
|
|
1048
|
-
// directed behavior
|
|
1049
|
-
//
|
|
1048
|
+
// directed behavior part of the declared object: every build of the facade
|
|
1049
|
+
// re-subscribes itself.
|
|
1050
1050
|
if (typeof tunnel.attachToRunner === 'function') { tunnel.attachToRunner(ctx.runner); }
|
|
1051
1051
|
return tunnel;
|
|
1052
1052
|
}
|