@descix/frqtl-node 0.6.0-p4.1 → 0.6.1-p1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/LICENSE.md +79 -63
- package/README.md +6 -3
- package/dist/dist-manifest.json +60 -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 +10 -17
- package/dist/shim/engine/site-graph.js +36 -0
- package/dist/shim/host/host-view.js +232 -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 +107 -16
- package/dist/shim/registry/exhibits.js +16 -35
- package/dist/shim/registry/index.js +5 -2
- package/dist/shim/registry/scene-roster.js +35 -0
- package/dist/shim/registry/scene-source.js +6 -36
- package/dist/shim/registry/showcase-link.js +61 -0
- 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 +21 -2
- package/dist/shim/registry/experiments.js +0 -215
|
@@ -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
|
}
|
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) {
|