@descix/frqtl-node 0.6.0-p1.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/README.md +6 -4
- package/dist/dist-manifest.json +71 -65
- package/dist/eyes/index.js +5 -2
- package/dist/fat/pkg-node/package.json +2 -10
- package/dist/frqtl/compat/providers/wasm-engine.js +3 -4
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +1 -1
- package/dist/frqtl/objects/FrqtlApp.js +33 -6
- package/dist/frqtl/objects/RecipeContext.js +284 -13
- package/dist/frqtl/objects/constants/WallBehaviors.js +1 -1
- package/dist/frqtl/objects/instruments/capture.js +1 -24
- package/dist/frqtl/objects/instruments/hosted-eyes.js +216 -29
- package/dist/frqtl/objects/objects/makeControlsForObject.js +5 -5
- package/dist/frqtl/objects/record/Record.js +346 -0
- package/dist/frqtl/objects/record/open-record.js +22 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +2 -2
- package/dist/frqtl/objects/schema/scopes.js +2 -2
- package/dist/frqtl/objects/setup/_worldInit.js +1 -2
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +1 -2
- package/dist/frqtl/parts/CameraControls.js +8 -0
- package/dist/frqtl/parts/DetectorWall.js +1 -1
- package/dist/frqtl/parts/FrqtlVision.js +21 -6
- package/dist/frqtl/parts/GraphingDetectorWall.js +7 -7
- package/dist/frqtl/parts/LargeObject.js +2 -2
- package/dist/frqtl/parts/LiveChart.js +2 -2
- package/dist/frqtl/parts/LogicGate.js +3 -3
- package/dist/frqtl/parts/OvenBox.js +1 -1
- package/dist/frqtl/parts/PointSource.js +1 -1
- package/dist/frqtl/parts/SlitLightSource.js +1 -1
- package/dist/frqtl/parts/SlitScreen.js +1 -1
- package/dist/frqtl/parts/SphereEmitter.js +12 -12
- package/dist/frqtl/parts/Trace.js +1 -1
- package/dist/frqtl/parts/Tunnel.js +11 -11
- package/dist/frqtl/parts/Wall.js +20 -10
- package/dist/frqtl/scenes/circuit-sat.js +1 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +32 -34
- package/dist/frqtl/scenes/dse-two-emitters.js +33 -34
- package/dist/frqtl/scenes/mirror-route.js +96 -79
- package/dist/shim/bundle/index.js +118 -96
- package/dist/shim/bundle/indexHtml.js +66 -39
- package/dist/shim/controls/index.js +1 -1
- package/dist/shim/engine/artifact-shell.js +1 -1
- package/dist/shim/engine/router.js +3 -3
- package/dist/shim/host/host-view.js +228 -0
- package/dist/shim/host/index.js +18 -0
- package/dist/shim/index.js +3 -5
- package/dist/shim/notebook/format.js +1 -1
- package/dist/shim/notebook/kernel.js +1 -1
- package/dist/shim/notebook/setup-builder.js +2 -2
- package/dist/shim/notebook/store.js +2 -3
- package/dist/shim/package.json +1 -0
- package/dist/shim/page-params/index.js +2 -30
- package/dist/shim/project/fork.js +293 -0
- package/dist/shim/project/identity.js +3 -1
- package/dist/shim/project/index.js +16 -3
- package/dist/shim/project/link.js +224 -46
- package/dist/shim/project/manifest.js +151 -0
- package/dist/shim/project/parts-writer.js +68 -7
- package/dist/shim/project/read-text.js +43 -0
- package/dist/shim/project/record-format.js +345 -0
- package/dist/shim/project/specifiers.js +104 -15
- package/dist/shim/registry/exhibits.js +15 -34
- package/dist/shim/registry/index.js +3 -3
- package/dist/shim/registry/scene-roster.js +35 -0
- package/dist/shim/registry/scene-source.js +48 -64
- package/dist/shim/registry/walkthrough.js +6 -6
- package/dist/shim/render-state/apply.js +66 -31
- package/dist/shim/render-state/index.js +6 -2
- package/dist/shim/runtime/index.js +4 -4
- package/dist/shim/storage/codec-chains.js +88 -0
- package/dist/shim/storage/folders.js +205 -0
- package/dist/shim/storage/index.js +64 -8
- package/dist/shim/storage/make-namespace-store.js +5 -5
- package/llms.txt +4 -3
- package/package.json +19 -1
- package/dist/shim/bundle/frqnb.js +0 -112
- package/dist/shim/bundle/manifest.js +0 -95
- package/dist/shim/registry/experiments.js +0 -215
- package/dist/shim/storage/modules.js +0 -249
- package/dist/shim/storage/projects.js +0 -150
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Record.js — THE live owner of a Record (the sketch-projects design §6): its change rows, its own clock and
|
|
5
|
+
* its clips. Opened by the facade (`const rec = await exp.startRecord()`, RecipeContext); serialized by the one
|
|
6
|
+
* writer, lib/shim/project/record-format.js serializeRecordFolder.
|
|
7
|
+
*
|
|
8
|
+
* rec.changes READ-ONLY: the rows so far, a new deep-frozen copy per read (open or closed).
|
|
9
|
+
* rec.tick READ-ONLY. The Record's OWN clock: 0 when it opens, +1 per world tick the runner
|
|
10
|
+
* delivers (runner.addTickListener, which survives a rebuild), so it climbs across
|
|
11
|
+
* every re-run inside the Record's world (setDeclared, restart) while the runner's
|
|
12
|
+
* own tick restarts at 0. Every row's tick and every clip span is on this clock.
|
|
13
|
+
* await rec.film({ seconds, fps, everyTicks, stage? })
|
|
14
|
+
* ceil(seconds / 10) back-to-back FrqtlVision recordings of equal frame count, each
|
|
15
|
+
* CLIP_SECONDS long; seconds below CLIP_SECONDS.min are refused. Resolves the clip
|
|
16
|
+
* spans it filmed. The host keeps the world ticking (its loop, or a driver).
|
|
17
|
+
* await rec.stop() → { files, record, clips: [{ file, firstTick, lastTick }] } | { empty: true }.
|
|
18
|
+
* REJECTS (the Record stays open): a change and no clip, a change after the last
|
|
19
|
+
* clip ends, a clip still being filmed, any fault recordFolderFaults finds in its
|
|
20
|
+
* own folder.
|
|
21
|
+
* rec.closed a Promise that settles once, when the Record closes: the stop() value plus
|
|
22
|
+
* `closedBy: 'stop'`, or — when the facade's world is re-authored (a build of
|
|
23
|
+
* text that is not the held text) — the folder of the world before, with
|
|
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.
|
|
28
|
+
* A refused close nobody READ (the getter records the read) makes the facade's
|
|
29
|
+
* next exp.startRecord() reject, naming it.
|
|
30
|
+
* err.record every rejection of stop() and of `closed` carries { changes, clips } — the
|
|
31
|
+
* Record's rows and clip spans — so a host can name them unheld instead of losing them.
|
|
32
|
+
*
|
|
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.
|
|
39
|
+
*
|
|
40
|
+
* WHERE ITS PARTS COME FROM — nothing here captures a second time:
|
|
41
|
+
* rows the writer's own edits at act end (RecipeContext.toSketch / setDeclared call `_row`); only what
|
|
42
|
+
* was WRITTEN makes a row;
|
|
43
|
+
* clips every FrqtlVision.record on this runner while the Record is open (FrqtlVision asks
|
|
44
|
+
* `openRecordOf(runner)` and hands the recording to `_clipFor`); a recording whose frames / fps
|
|
45
|
+
* falls outside CLIP_SECONDS is refused before it is encoded;
|
|
46
|
+
* parts parts.before / parts.after = linkProject(held text).module.parts at open / at close, each kind
|
|
47
|
+
* by the name the kinds registry resolves (kindNamed);
|
|
48
|
+
* project the held files at close, under project/.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { FrqtlVision } from '../../parts/FrqtlVision.js';
|
|
52
|
+
import { linkProject } from '../../../shim/project/link.js';
|
|
53
|
+
import { serializeRecordFolder, CLIP_SECONDS, BY, CHANGE_KEYS } from '../../../shim/project/record-format.js';
|
|
54
|
+
import { kindNamed } from '../kinds.js';
|
|
55
|
+
import { _setOpenRecord, openRecordOf } from './open-record.js';
|
|
56
|
+
|
|
57
|
+
function _fail(msg) {
|
|
58
|
+
throw new Error('[Record] ' + msg + ' (FAIL-LOUD.)');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function _bytesOfBase64(b64) {
|
|
62
|
+
var bin = atob(b64);
|
|
63
|
+
var out = new Uint8Array(bin.length);
|
|
64
|
+
for (var i = 0; i < bin.length; i++) { out[i] = bin.charCodeAt(i); }
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** linkProject(held).module.parts → { name: [registered kind name, props] } in key order. */
|
|
69
|
+
async function _partsOf(held) {
|
|
70
|
+
var linked = await linkProject(held);
|
|
71
|
+
var parts = linked.module && linked.module.parts;
|
|
72
|
+
if (!parts || typeof parts !== 'object') {
|
|
73
|
+
_fail('the held sketch exports no `parts` block — a Record writes parts.before/after from `export const parts`.');
|
|
74
|
+
}
|
|
75
|
+
var out = {};
|
|
76
|
+
Object.keys(parts).forEach(function (name) {
|
|
77
|
+
var e = parts[name];
|
|
78
|
+
var Kind = Array.isArray(e) ? e[0] : null;
|
|
79
|
+
if (typeof Kind !== 'function') { _fail('parts.' + name + ' carries no kind class.'); }
|
|
80
|
+
kindNamed(Kind.name); // the name the registry resolves (THROWS naming an unregistered kind)
|
|
81
|
+
out[name] = [Kind.name, e[1]];
|
|
82
|
+
});
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The held project's files, copied (text values). */
|
|
87
|
+
function _heldFiles(held) {
|
|
88
|
+
return Object.assign({}, held.files);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {object} exp — the RecipeContext that opens it (its runner and its held project)
|
|
93
|
+
* @private — construct through exp.startRecord()
|
|
94
|
+
*/
|
|
95
|
+
function Record(exp) {
|
|
96
|
+
var self = this;
|
|
97
|
+
this._exp = exp;
|
|
98
|
+
this._runner = exp.runner;
|
|
99
|
+
this._tick = 0;
|
|
100
|
+
this._rows = [];
|
|
101
|
+
this._clips = [];
|
|
102
|
+
this._inflight = 0;
|
|
103
|
+
this._open = true;
|
|
104
|
+
this._stopping = false;
|
|
105
|
+
this._filmArm = null;
|
|
106
|
+
this._before = null;
|
|
107
|
+
this._closedSettle = null;
|
|
108
|
+
this._closedRead = false;
|
|
109
|
+
this._refused = null;
|
|
110
|
+
this._closed = new Promise(function (resolve, reject) { self._closedSettle = { resolve: resolve, reject: reject }; });
|
|
111
|
+
// Observed here so node does not crash on an unhandled rejection; a refused close nobody read is LOUD at the next
|
|
112
|
+
// act instead (RecipeContext.startRecord refuses until `rec.closed` has been read).
|
|
113
|
+
this._closed.then(null, function () {});
|
|
114
|
+
this._settled = this._closed.then(function () {}, function () {});
|
|
115
|
+
this._onTick = function () { self._tick += 1; };
|
|
116
|
+
this._off = this._runner.addTickListener(this._onTick);
|
|
117
|
+
_setOpenRecord(this._runner, this);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Object.defineProperty(Record.prototype, 'tick', { get: function () { return this._tick; } });
|
|
121
|
+
/** A deep-frozen copy. */
|
|
122
|
+
function _frozen(v) {
|
|
123
|
+
if (v === null || typeof v !== 'object') { return v; }
|
|
124
|
+
var o = Array.isArray(v) ? v.map(_frozen) : Object.keys(v).reduce(function (a, k) { a[k] = _frozen(v[k]); return a; }, {});
|
|
125
|
+
return Object.freeze(o);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* rec.changes — the rows so far, a NEW deep-frozen copy per read (record.json's `changes` vocabulary), on an open or a
|
|
129
|
+
* closed Record. One act's rows: `const n = rec.changes.length; await exp.toSketch({ by }); rec.changes.slice(n)`.
|
|
130
|
+
*/
|
|
131
|
+
Object.defineProperty(Record.prototype, 'changes', { get: function () {
|
|
132
|
+
return _frozen(this._rows.map(function (r) { var o = {}; CHANGE_KEYS.forEach(function (k) { o[k] = r[k]; }); return o; }));
|
|
133
|
+
} });
|
|
134
|
+
/** rec.closed — reading it records that the host has heard how the Record closed. */
|
|
135
|
+
Object.defineProperty(Record.prototype, 'closed', { get: function () { this._closedRead = true; return this._closed; } });
|
|
136
|
+
|
|
137
|
+
/** Attach what the Record holds to a refusal — `err.record = { changes, clips }` — so a host can name it unheld. */
|
|
138
|
+
Record.prototype._carry = function (err) {
|
|
139
|
+
var e = (err instanceof Error) ? err : new Error(String(err));
|
|
140
|
+
e.record = {
|
|
141
|
+
changes: this._rows.map(function (r) { return JSON.parse(JSON.stringify(r)); }),
|
|
142
|
+
clips: this._clips.map(function (c) { return { firstTick: c.firstTick, lastTick: c.lastTick }; })
|
|
143
|
+
};
|
|
144
|
+
return e;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Read parts.before (the held text at open). @private */
|
|
148
|
+
Record.prototype._ready = async function (held) {
|
|
149
|
+
this._held = held;
|
|
150
|
+
this._before = await _partsOf(held);
|
|
151
|
+
return this;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Throws unless `by` is one of BY (while a Record is open, every act names who acted). */
|
|
155
|
+
Record.requireBy = function (by, where) {
|
|
156
|
+
if (BY.indexOf(by) === -1) {
|
|
157
|
+
throw new TypeError('[' + where + '] a Record is open: the act names who acted — { by: ' + BY.map(function (b) {
|
|
158
|
+
return JSON.stringify(b); }).join(' | ') + ' } (record-format.js BY); got ' + JSON.stringify(by) + '. (FAIL-LOUD.)');
|
|
159
|
+
}
|
|
160
|
+
return by;
|
|
161
|
+
};
|
|
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
|
+
|
|
174
|
+
/** One change row, at the Record's current tick, in the order acted. @private */
|
|
175
|
+
Record.prototype._row = function (row) {
|
|
176
|
+
this._admit();
|
|
177
|
+
this._rows.push({ tick: this._tick, name: row.name, op: row.op, by: row.by, from: row.from, to: row.to });
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The clip hook. FrqtlVision calls it with the normalized record() request, BEFORE it subscribes; it THROWS
|
|
182
|
+
* when the request cannot be a CLIP_SECONDS clip, and otherwise returns the recording's session:
|
|
183
|
+
* sample(i) called as frame i is sampled (inside the tick fan-out), reads this Record's tick
|
|
184
|
+
* check(frames) called before the encoder is finalized; THROWS outside CLIP_SECONDS
|
|
185
|
+
* end(envelope) the encoded clip joins the Record (ignored once the Record is closed)
|
|
186
|
+
* drop() the recording failed
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
Record.prototype._clipFor = function (req) {
|
|
190
|
+
var self = this;
|
|
191
|
+
var span = CLIP_SECONDS.min + '–' + CLIP_SECONDS.max + ' s';
|
|
192
|
+
var advice = 'film inside an open Record with rec.film({ seconds, fps, everyTicks }), or record ' + span +
|
|
193
|
+
' (frames / fps) per clip';
|
|
194
|
+
var planned = null;
|
|
195
|
+
if (req.stopAt.ticks !== undefined) { planned = Math.min(req.maxFrames, Math.floor(req.stopAt.ticks / req.everyTicks) + 1); }
|
|
196
|
+
var bound = planned === null ? req.maxFrames : planned;
|
|
197
|
+
var secs = function (n) { return n / req.fps; };
|
|
198
|
+
if ((planned !== null && (secs(planned) < CLIP_SECONDS.min || secs(planned) > CLIP_SECONDS.max)) ||
|
|
199
|
+
(planned === null && secs(bound) < CLIP_SECONDS.min)) {
|
|
200
|
+
_fail('a Record is open on this runner, so this recording is one of its clips, and a clip is ' + span +
|
|
201
|
+
' long: this one is ' + (planned !== null ? secs(planned) : 'at most ' + secs(bound)) + ' s (' + bound +
|
|
202
|
+
' frames at ' + req.fps + ' fps) — ' + advice + '.');
|
|
203
|
+
}
|
|
204
|
+
var arm = this._filmArm;
|
|
205
|
+
this._filmArm = null;
|
|
206
|
+
var ticks = [];
|
|
207
|
+
var done = false;
|
|
208
|
+
this._inflight += 1;
|
|
209
|
+
function settle() { if (!done) { done = true; self._inflight -= 1; } }
|
|
210
|
+
return {
|
|
211
|
+
sample: function (i) {
|
|
212
|
+
ticks[i] = self._tick;
|
|
213
|
+
if (arm && i === req.maxFrames - 1) { var next = arm.onLast; arm = null; next(); }
|
|
214
|
+
},
|
|
215
|
+
check: function (frames) {
|
|
216
|
+
if (secs(frames) < CLIP_SECONDS.min || secs(frames) > CLIP_SECONDS.max) {
|
|
217
|
+
settle();
|
|
218
|
+
_fail('a Record is open on this runner, so this recording is one of its clips, and a clip is ' + span +
|
|
219
|
+
' long: this one stopped at ' + secs(frames) + ' s (' + frames + ' frames at ' + req.fps + ' fps) — ' + advice + '.');
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
end: function (env) {
|
|
223
|
+
settle();
|
|
224
|
+
if (!self._open) { return; }
|
|
225
|
+
var n = env.track.frames.length;
|
|
226
|
+
self._clips.push({ bytes: _bytesOfBase64(env.encoded.bytes), firstTick: ticks[0], lastTick: ticks[n - 1] });
|
|
227
|
+
},
|
|
228
|
+
drop: function () { settle(); }
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* rec.film({ seconds, fps, everyTicks, stage? }) → Promise<[{ file?, firstTick, lastTick }]> — ceil(seconds/10)
|
|
234
|
+
* back-to-back segments of equal frame count. Each segment's recording is started INSIDE the previous one's last
|
|
235
|
+
* sampled tick (the tick fan-out dispatches a snapshot, so the next subscription fires from the next tick): the
|
|
236
|
+
* segments are contiguous on the world's clock whatever drives it. REFUSES: seconds below CLIP_SECONDS.min, a
|
|
237
|
+
* seconds × fps that does not split into equal whole-frame segments.
|
|
238
|
+
*/
|
|
239
|
+
Record.prototype.film = function (opts) {
|
|
240
|
+
var self = this;
|
|
241
|
+
try {
|
|
242
|
+
if (!this._open) { _fail('this Record is closed — open the next one (exp.startRecord()) to film.'); }
|
|
243
|
+
if (!opts || typeof opts !== 'object') { _fail('rec.film({ seconds, fps, everyTicks, stage? })'); }
|
|
244
|
+
Object.keys(opts).forEach(function (k) {
|
|
245
|
+
if (['seconds', 'fps', 'everyTicks', 'stage'].indexOf(k) === -1) { _fail('rec.film takes { seconds, fps, everyTicks, stage? }, not "' + k + '".'); }
|
|
246
|
+
});
|
|
247
|
+
var s = opts.seconds, fps = opts.fps, every = opts.everyTicks;
|
|
248
|
+
if (typeof s !== 'number' || !isFinite(s) || s < CLIP_SECONDS.min) {
|
|
249
|
+
_fail('rec.film: seconds is at least ' + CLIP_SECONDS.min + ' (CLIP_SECONDS), got ' + JSON.stringify(s) + '.');
|
|
250
|
+
}
|
|
251
|
+
if (!Number.isInteger(fps) || fps < 1) { _fail('rec.film: fps is a whole number ≥ 1 (the playback rate), got ' + JSON.stringify(fps) + '.'); }
|
|
252
|
+
if (!Number.isInteger(every) || every < 1) { _fail('rec.film: everyTicks is a whole number ≥ 1 (the capture cadence in ticks), got ' + JSON.stringify(every) + '.'); }
|
|
253
|
+
var n = Math.ceil(s / CLIP_SECONDS.max);
|
|
254
|
+
var total = s * fps;
|
|
255
|
+
if (!Number.isInteger(total) || total % n !== 0) {
|
|
256
|
+
_fail('rec.film: ' + s + ' s at ' + fps + ' fps is ' + total + ' frames, which does not split into ' + n +
|
|
257
|
+
' equal whole-frame segments of at most ' + CLIP_SECONDS.max + ' s — choose seconds × fps divisible by ' + n + '.');
|
|
258
|
+
}
|
|
259
|
+
var f = total / n;
|
|
260
|
+
var req = { everyTicks: every, fps: fps, maxFrames: f, stopAt: { ticks: (f - 1) * every } };
|
|
261
|
+
if (opts.stage !== undefined) { req.stage = opts.stage; }
|
|
262
|
+
var vision = FrqtlVision.attach(this._runner);
|
|
263
|
+
var first = this._clips.length;
|
|
264
|
+
// One deferred per segment: segment k+1 exists once segment k sampled its last frame.
|
|
265
|
+
var segs = [];
|
|
266
|
+
for (var k = 0; k < n; k++) {
|
|
267
|
+
(function () { var d = {}; d.promise = new Promise(function (res, rej) { d.resolve = res; d.reject = rej; }); segs.push(d); })();
|
|
268
|
+
}
|
|
269
|
+
var started = [];
|
|
270
|
+
var start = function (k) {
|
|
271
|
+
started[k] = true;
|
|
272
|
+
self._filmArm = (k < n - 1) ? { onLast: function () { start(k + 1); } } : null;
|
|
273
|
+
var p = vision.record(req);
|
|
274
|
+
segs[k].resolve(p);
|
|
275
|
+
p.then(function () {
|
|
276
|
+
if (k + 1 < n && !started[k + 1]) {
|
|
277
|
+
segs[k + 1].reject(new Error('[Record.film] segment ' + (k + 1) + ' of ' + n + ' ended without sampling its last frame, ' +
|
|
278
|
+
'so segment ' + (k + 2) + ' never started. (FAIL-LOUD.)'));
|
|
279
|
+
}
|
|
280
|
+
}, function (e) {
|
|
281
|
+
self._filmArm = null;
|
|
282
|
+
for (var j = k + 1; j < n; j++) { if (!started[j]) { started[j] = true; segs[j].reject(e); } }
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
start(0);
|
|
286
|
+
return Promise.all(segs.map(function (d) { return d.promise; })).then(function () {
|
|
287
|
+
return self._clips.slice(first).map(function (c) { return { firstTick: c.firstTick, lastTick: c.lastTick }; });
|
|
288
|
+
});
|
|
289
|
+
} catch (e) { return Promise.reject(e); }
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/** Serialize this Record's folder from `held` (the files it snapshots). @private */
|
|
293
|
+
Record.prototype._folder = async function (held) {
|
|
294
|
+
var after = await _partsOf(held);
|
|
295
|
+
return serializeRecordFolder({
|
|
296
|
+
changes: this._rows.slice(), clips: this._clips.slice(),
|
|
297
|
+
partsBefore: this._before, partsAfter: after, project: _heldFiles(held)
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/** Detach from the world (no row, tick or clip after this). @private */
|
|
302
|
+
Record.prototype._detach = function () {
|
|
303
|
+
this._open = false;
|
|
304
|
+
if (this._off) { this._off(); this._off = null; }
|
|
305
|
+
_setOpenRecord(this._runner, null);
|
|
306
|
+
this._exp._record = null;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/** rec.stop() — see the module docstring. */
|
|
310
|
+
Record.prototype.stop = async function () {
|
|
311
|
+
var self = this;
|
|
312
|
+
var refuse = function (msg) { try { _fail(msg); } catch (e) { throw self._carry(e); } };
|
|
313
|
+
if (!this._open) { refuse('this Record is already closed' + (this._closedBy ? ' (closedBy: ' + this._closedBy + ')' : '') + ' — read rec.closed.'); }
|
|
314
|
+
if (this._stopping) { refuse('stop() is already running.'); }
|
|
315
|
+
if (this._inflight) { refuse(this._inflight + ' clip(s) are still being filmed — await rec.film (or the record()) before stop().'); }
|
|
316
|
+
this._stopping = true;
|
|
317
|
+
var out;
|
|
318
|
+
try { out = await this._folder(this._exp._project); }
|
|
319
|
+
catch (e) { throw this._carry(e); }
|
|
320
|
+
finally { this._stopping = false; }
|
|
321
|
+
this._closedBy = 'stop';
|
|
322
|
+
this._detach();
|
|
323
|
+
this._closedSettle.resolve(Object.assign({}, out, { closedBy: 'stop', droppedClips: 0 }));
|
|
324
|
+
return out;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The facade re-authors its world: close with the folder of the world BEFORE (`held` = the held project before
|
|
329
|
+
* the build). Synchronous detach; the folder settles `closed`. @private
|
|
330
|
+
*/
|
|
331
|
+
Record.prototype._closeOnReauthor = function (held) {
|
|
332
|
+
var self = this;
|
|
333
|
+
var dropped = this._inflight; // clips still filming: detaching drops them (their end() finds the Record closed)
|
|
334
|
+
this._closedBy = 'reauthor';
|
|
335
|
+
this._detach();
|
|
336
|
+
this._folder(held).then(function (out) {
|
|
337
|
+
self._closedSettle.resolve(Object.assign({}, out, { closedBy: 'reauthor', droppedClips: dropped }));
|
|
338
|
+
}, function (e) {
|
|
339
|
+
self._refused = self._carry(e);
|
|
340
|
+
self._refused.closedBy = 'reauthor';
|
|
341
|
+
self._refused.droppedClips = dropped;
|
|
342
|
+
self._closedSettle.reject(self._refused);
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export { Record, openRecordOf };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* open-record.js — which Record is open on a runner: the ONE store FrqtlVision asks (its clip hook) and the
|
|
5
|
+
* Record writes. One open Record per runner; a second open is refused by the facade (RecipeContext.startRecord).
|
|
6
|
+
*
|
|
7
|
+
* openRecordOf(runner) → Record | null
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var _open = new WeakMap();
|
|
11
|
+
|
|
12
|
+
/** @param {object} runner @returns {object|null} the Record open on it */
|
|
13
|
+
function openRecordOf(runner) {
|
|
14
|
+
return (runner && _open.get(runner)) || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @private — Record.js only */
|
|
18
|
+
function _setOpenRecord(runner, rec) {
|
|
19
|
+
if (rec) { _open.set(runner, rec); } else { _open.delete(runner); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { openRecordOf, _setOpenRecord };
|
|
@@ -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).
|
|
@@ -183,6 +183,7 @@ import * as CaptureEnvelope from '../../shim/render/capture-envelope.js';
|
|
|
183
183
|
import { CameraPath } from './CameraPath.js';
|
|
184
184
|
import { FrqtlCaptureHost } from '../objects/instruments/capture-host.js';
|
|
185
185
|
import { vendorUrl } from '../compat/vendor.js';
|
|
186
|
+
import { openRecordOf } from '../objects/record/open-record.js';
|
|
186
187
|
|
|
187
188
|
function _rgba8Format(RS) { return RS.STAGE_FORMATS[RS.finalStage()]; }
|
|
188
189
|
|
|
@@ -574,16 +575,24 @@ function _record(runner, RS, opts) {
|
|
|
574
575
|
'localhost). The encoder is not optional — there is no second path');
|
|
575
576
|
}
|
|
576
577
|
|
|
578
|
+
// THE CLIP HOOK: while a Record is open on this runner, every recording is one of its clips. The Record
|
|
579
|
+
// refuses a request that cannot be a CLIP_SECONDS clip (before anything subscribes) and keeps the clip's
|
|
580
|
+
// ticks and bytes; this recorder holds no Record state of its own (objects/record/Record.js _clipFor).
|
|
581
|
+
var openRec = openRecordOf(runner);
|
|
582
|
+
var clip = openRec ? openRec._clipFor(req) : null;
|
|
583
|
+
|
|
577
584
|
// Everything that can THROW synchronously happens BEFORE the sampler subscribes: a
|
|
578
585
|
// CameraPath refused at attach (no pose control, an undeclared pose field) must not leave
|
|
579
586
|
// a subscribed sampler holding the clock behind a rejection nobody can reach.
|
|
580
587
|
var path = null;
|
|
581
588
|
if (req.stopAt.path) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
589
|
+
try {
|
|
590
|
+
path = CameraPath.attach(runner, {
|
|
591
|
+
waypoints: req.stopAt.path.waypoints,
|
|
592
|
+
loop: !!req.stopAt.path.loop,
|
|
593
|
+
waypointTimeoutMs: req.stopAt.path.waypointTimeoutMs
|
|
594
|
+
});
|
|
595
|
+
} catch (e) { if (clip) { clip.drop(); } throw e; }
|
|
587
596
|
}
|
|
588
597
|
var frames = [];
|
|
589
598
|
var inflight = [];
|
|
@@ -628,6 +637,7 @@ function _record(runner, RS, opts) {
|
|
|
628
637
|
_fail('record', 'the recording stopped before a single still was captured — nothing to ' +
|
|
629
638
|
'encode. Is the runner advancing ticks?');
|
|
630
639
|
}
|
|
640
|
+
if (clip) { clip.check(frames.length); }
|
|
631
641
|
return encodeChain;
|
|
632
642
|
}).then(function (enc) {
|
|
633
643
|
return enc.output.finalize().then(function () { return enc.output.getMimeType(); })
|
|
@@ -654,7 +664,11 @@ function _record(runner, RS, opts) {
|
|
|
654
664
|
truncated: truncated
|
|
655
665
|
});
|
|
656
666
|
});
|
|
657
|
-
}).then(
|
|
667
|
+
}).then(function (env) {
|
|
668
|
+
if (clip) { clip.end(env); }
|
|
669
|
+
settle.resolve(env);
|
|
670
|
+
}, function (e) {
|
|
671
|
+
if (clip) { clip.drop(); }
|
|
658
672
|
encoderReady.then(function (enc) { return enc.output.cancel(); }).then(null, function () {});
|
|
659
673
|
settle.reject(e);
|
|
660
674
|
});
|
|
@@ -670,6 +684,7 @@ function _record(runner, RS, opts) {
|
|
|
670
684
|
return true;
|
|
671
685
|
},
|
|
672
686
|
onStill: function (p, i) {
|
|
687
|
+
if (clip) { clip.sample(i); }
|
|
673
688
|
var handled = p.then(function (still) {
|
|
674
689
|
if (i === 0) {
|
|
675
690
|
scene = still.scene;
|
|
@@ -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;
|