@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
|
@@ -74,7 +74,12 @@ import { ObjectSchema } from './schema/index.js';
|
|
|
74
74
|
import { kindOf } from './kinds.js';
|
|
75
75
|
import { linkProject } from '../../shim/project/link.js';
|
|
76
76
|
import { patchParts, PARTS_EXPORT, entryKeys, entryLiteral } from '../../shim/project/parts-writer.js';
|
|
77
|
-
import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
77
|
+
import { moduleOfKind, PARTS_SPECIFIER } from '../../shim/project/parts-module.js';
|
|
78
|
+
import { forkPartText, FORK_REFUSED } from '../../shim/project/fork.js';
|
|
79
|
+
import { readText } from '../../shim/project/read-text.js';
|
|
80
|
+
import { packageRoot } from '../compat/package-root.js';
|
|
81
|
+
import { MANIFEST_FILE } from '../../shim/project/manifest.js';
|
|
82
|
+
import { Record, openRecordOf } from './record/Record.js';
|
|
78
83
|
|
|
79
84
|
/** The world-init contract (objects/setup/_worldInit.js). */
|
|
80
85
|
function _worldInit() {
|
|
@@ -123,6 +128,8 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
123
128
|
this._parts = new Map(); // declared object → { key, baseline } — the objects in `parts`
|
|
124
129
|
this._code = new Map(); // declared object → baseline — the code declarations at build end
|
|
125
130
|
this._declaredOnce = false;
|
|
131
|
+
this._record = null; // the open Record (exp.startRecord), or null
|
|
132
|
+
this._reauthored = null; // the Record the last re-authoring closed (startRecord checks its close was heard)
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
function _requireWorld(self) {
|
|
@@ -195,6 +202,7 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
195
202
|
}
|
|
196
203
|
// A (re-)inited world has no declared objects; the recipe re-declares what it builds.
|
|
197
204
|
self._runner.clearDeclared();
|
|
205
|
+
self._runner.beginWorld(); // a (re-)inited world has not stepped
|
|
198
206
|
W.applyPreInit(cfg, u); // pre-init world keys (iframeIntervalSeconds) — the engine reads them at init
|
|
199
207
|
u.init.apply(u, args);
|
|
200
208
|
self._world = Object.freeze({ renderer: renderer, w: w, h: h, d: renderer === 'three' ? d : null,
|
|
@@ -300,7 +308,7 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
300
308
|
}
|
|
301
309
|
var obj = self.addObject(Kind, Object.assign({ name: key }, _plain(props)));
|
|
302
310
|
self._runner._adoptIntoParts(obj);
|
|
303
|
-
self._parts.set(obj, { key: key, baseline: _capture(self, obj) });
|
|
311
|
+
self._parts.set(obj, { key: key, baseline: _capture(self, obj), kind: self._runner.kindOf(obj) });
|
|
304
312
|
out[key] = obj;
|
|
305
313
|
});
|
|
306
314
|
return Object.freeze(out);
|
|
@@ -366,6 +374,22 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
366
374
|
return props;
|
|
367
375
|
}
|
|
368
376
|
|
|
377
|
+
/**
|
|
378
|
+
* Spell a touched `center` in the entry's OWN arity: two numbers when the entry
|
|
379
|
+
* spells two (a literal, or an array expression — entryLiteral's `length`) and the live z is 0; otherwise the
|
|
380
|
+
* live [x, y, z] (an entry spelled three, a 3D hand off z 0, an entry whose center is not an array). The
|
|
381
|
+
* read-back (_verifyWritten) guards every case. Mutates and returns `props`.
|
|
382
|
+
*/
|
|
383
|
+
function _spellCenter(text, name, props) {
|
|
384
|
+
var v = props.center;
|
|
385
|
+
if (!Array.isArray(v) || v.length !== 3 || v[2] !== 0) { return props; }
|
|
386
|
+
if (entryKeys(text, name).indexOf('center') === -1) { return props; }
|
|
387
|
+
var r = entryLiteral(text, name, 'center');
|
|
388
|
+
var n = r.literal ? (Array.isArray(r.value) ? r.value.length : null) : r.length;
|
|
389
|
+
if (n === 2) { props.center = [v[0], v[1]]; }
|
|
390
|
+
return props;
|
|
391
|
+
}
|
|
392
|
+
|
|
369
393
|
/**
|
|
370
394
|
* The post-patch check: what the patched text now declares for entry `name`, read back (entryLiteral) for every
|
|
371
395
|
* written key and folded with the baseline for every unwritten one, must equal the live capture — `rect` in its
|
|
@@ -379,6 +403,8 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
379
403
|
Object.keys(written).forEach(function (k) {
|
|
380
404
|
var r = entryLiteral(text, name, k);
|
|
381
405
|
decl[k] = r.literal ? r.value : undefined;
|
|
406
|
+
// A two-number center declares z 0 (the reading placeDeclaredAt gives an omitted z).
|
|
407
|
+
if (k === 'center' && Array.isArray(decl[k]) && decl[k].length === 2) { decl[k] = decl[k].concat([0]); }
|
|
382
408
|
});
|
|
383
409
|
function refuse(k, declared, liveV) {
|
|
384
410
|
refused.push({ op: 'set', name: name, key: k, reason: 'the written text declares ' + _JSON(declared) + ' but the live object is ' +
|
|
@@ -436,9 +462,11 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
436
462
|
* changes patchParts' changes (a `literalized` set names the expression it replaced)
|
|
437
463
|
* refused every hand not written, { op, name, key?, reason }
|
|
438
464
|
*/
|
|
439
|
-
RecipeContext.prototype.toSketch = async function () {
|
|
465
|
+
RecipeContext.prototype.toSketch = async function (opts) {
|
|
440
466
|
_requireWorld(this);
|
|
441
467
|
_requireHeld(this, 'toSketch');
|
|
468
|
+
var by = this._record ? Record.requireBy(opts && opts.by, 'RecipeContext.toSketch') : null;
|
|
469
|
+
if (this._record) { this._record._admit('RecipeContext.toSketch'); }
|
|
442
470
|
var self = this;
|
|
443
471
|
var runner = this._runner;
|
|
444
472
|
var declared = runner.declared;
|
|
@@ -447,7 +475,7 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
447
475
|
var adopted = [];
|
|
448
476
|
var hands = runner._takeHandTouched();
|
|
449
477
|
this._parts.forEach(function (row, obj) {
|
|
450
|
-
if (declared.indexOf(obj) === -1) { edits.push({ op: 'remove', name: row.key }); return; }
|
|
478
|
+
if (declared.indexOf(obj) === -1) { edits.push({ op: 'remove', name: row.key, kind: row.kind, baseline: row.baseline }); return; }
|
|
451
479
|
if (!hands.has(obj)) { return; }
|
|
452
480
|
var kind = runner.kindOf(obj);
|
|
453
481
|
var t = _touched(kind, row.baseline, _capture(self, obj));
|
|
@@ -459,8 +487,10 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
459
487
|
refused.push({ op: 'set', name: row.key, key: k, reason: 'the live object no longer reports "' + k + '" — there is no value to write.' });
|
|
460
488
|
});
|
|
461
489
|
if (Object.keys(t.props).length) {
|
|
490
|
+
var raw = _plain(t.props); // the capture form, before the entry's spelling (the Record's row values)
|
|
462
491
|
_spell(self._project.files[self._project.entry], row.key, kind, t.props, row.baseline);
|
|
463
|
-
|
|
492
|
+
_spellCenter(self._project.files[self._project.entry], row.key, t.props);
|
|
493
|
+
if (Object.keys(t.props).length) { edits.push({ op: 'set', name: row.key, props: t.props, obj: obj, kind: kind, baseline: row.baseline, raw: raw }); }
|
|
464
494
|
}
|
|
465
495
|
});
|
|
466
496
|
this._code.forEach(function (baseline, obj) {
|
|
@@ -487,7 +517,9 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
487
517
|
});
|
|
488
518
|
var entry = this._project.entry;
|
|
489
519
|
var r = patchParts(this._project.files[entry], edits.map(function (e) {
|
|
490
|
-
|
|
520
|
+
if (e.op === 'set') { return { op: 'set', name: e.name, props: e.props }; }
|
|
521
|
+
if (e.op === 'remove') { return { op: 'remove', name: e.name }; }
|
|
522
|
+
return { op: 'add', name: e.name, Kind: e.Kind, module: e.module, props: e.props };
|
|
491
523
|
}), { entry: entry });
|
|
492
524
|
// Never decide it succeeded without asking: read the patched text back against each moved object.
|
|
493
525
|
edits.forEach(function (e) {
|
|
@@ -511,11 +543,76 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
511
543
|
var key = obj.name || runner.kindOf(obj);
|
|
512
544
|
if (refusedAdd[key]) { return; }
|
|
513
545
|
runner._adoptIntoParts(obj);
|
|
514
|
-
self._parts.set(obj, { key: key, baseline: _capture(self, obj) });
|
|
546
|
+
self._parts.set(obj, { key: key, baseline: _capture(self, obj), kind: runner.kindOf(obj) });
|
|
515
547
|
});
|
|
516
|
-
|
|
548
|
+
var allRefused = refused.concat(r.refused);
|
|
549
|
+
if (this._record) { _recordRows(this, edits, allRefused, by); }
|
|
550
|
+
return { files: Object.assign({}, files), changes: r.changes, refused: allRefused };
|
|
517
551
|
};
|
|
518
552
|
|
|
553
|
+
/**
|
|
554
|
+
* The Record's rows for one toSketch, from the writer's own edits as WRITTEN (a refused edit, or a refused key
|
|
555
|
+
* of one, makes no row), in edit order (R3). A `set` whose touched keys include the part's position authority
|
|
556
|
+
* (runner._placementAuthority: `center`, or `rect` with its derivedFrom reads) is a `move` with that ONE key in
|
|
557
|
+
* its capture form; its other touched keys are one `setDeclared` row. `add` → [kind, props as written];
|
|
558
|
+
* `remove` → [kind, the baseline's writable props].
|
|
559
|
+
*/
|
|
560
|
+
function _recordRows(self, edits, refused, by) {
|
|
561
|
+
var runner = self._runner;
|
|
562
|
+
var rec = self._record;
|
|
563
|
+
function refusedKeys(name, op) {
|
|
564
|
+
var keys = {};
|
|
565
|
+
var all = false;
|
|
566
|
+
refused.forEach(function (x) {
|
|
567
|
+
if (x.name !== name || x.op !== op) { return; }
|
|
568
|
+
if (x.key === undefined) { all = true; } else { keys[x.key] = true; }
|
|
569
|
+
});
|
|
570
|
+
return all ? null : keys;
|
|
571
|
+
}
|
|
572
|
+
function writableProps(kind, bag) {
|
|
573
|
+
var w = _writable(kind);
|
|
574
|
+
var out = {};
|
|
575
|
+
Object.keys(bag).forEach(function (k) { if (w[k] && k !== 'name') { out[k] = _plain(bag[k]); } });
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
edits.forEach(function (e) {
|
|
579
|
+
if (e.op === 'add') {
|
|
580
|
+
if (refusedKeys(e.name, 'add') === null) { return; }
|
|
581
|
+
rec._row({ name: e.name, op: 'add', by: by, from: null, to: [e.Kind, _plain(e.props)] });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (e.op === 'remove') {
|
|
585
|
+
if (refusedKeys(e.name, 'remove') === null) { return; }
|
|
586
|
+
rec._row({ name: e.name, op: 'remove', by: by, from: [e.kind, writableProps(e.kind, e.baseline)], to: null });
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
var bad = refusedKeys(e.name, 'set');
|
|
590
|
+
if (bad === null) { return; }
|
|
591
|
+
var raw = e.raw;
|
|
592
|
+
var auth = (raw.center !== undefined || raw.rect !== undefined) ? runner._placementAuthority(e.obj) : null;
|
|
593
|
+
var authReads = {};
|
|
594
|
+
if (auth) {
|
|
595
|
+
authReads[auth] = true;
|
|
596
|
+
ObjectSchema.derivedReads(e.kind).forEach(function (d) { if (d.field === auth) { authReads[d.name] = true; } });
|
|
597
|
+
}
|
|
598
|
+
var moveRefused = Object.keys(bad).some(function (k) { return authReads[k]; });
|
|
599
|
+
if (auth && raw[auth] !== undefined && !moveRefused) {
|
|
600
|
+
var from = {}, to = {};
|
|
601
|
+
from[auth] = _plain(e.baseline[auth]);
|
|
602
|
+
to[auth] = _plain(raw[auth]);
|
|
603
|
+
rec._row({ name: e.name, op: 'move', by: by, from: from, to: to });
|
|
604
|
+
}
|
|
605
|
+
var pf = {}, pt = {}, any = false;
|
|
606
|
+
Object.keys(raw).forEach(function (k) {
|
|
607
|
+
if (k === auth || bad[k]) { return; }
|
|
608
|
+
pf[k] = e.baseline[k] === undefined ? null : _plain(e.baseline[k]);
|
|
609
|
+
pt[k] = _plain(raw[k]);
|
|
610
|
+
any = true;
|
|
611
|
+
});
|
|
612
|
+
if (any) { rec._row({ name: e.name, op: 'setDeclared', by: by, from: pf, to: pt }); }
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
519
616
|
/** The names of declared objects a hand marked whose live capture differs from their baseline (not yet written). */
|
|
520
617
|
function _unwrittenHands(self) {
|
|
521
618
|
var runner = self._runner;
|
|
@@ -553,11 +650,15 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
553
650
|
* setOrbit) are kept; `kept` lists 'cameraPose' only when a pose was captured and re-applied. A `rect` /
|
|
554
651
|
* `center` patch sets where the entry STARTS. The loop's run state is kept. REFUSES (throws) while a hand
|
|
555
652
|
* made since the last toSketch is unwritten (write it first: await exp.toSketch()), and a `name` patch (the
|
|
556
|
-
* key is the name).
|
|
653
|
+
* key is the name). `from` and `to` are the patched keys' capture (ObjectSchema.captureProps) before and after the
|
|
654
|
+
* re-run — one form on both sides (a z-slab wall's rect is 6 numbers even when the patch typed 4); the patch as
|
|
655
|
+
* typed is in `files`. With a Record open, its `setDeclared` row carries the same `from`/`to`, written after the re-run.
|
|
557
656
|
*/
|
|
558
|
-
RecipeContext.prototype.setDeclared = async function (name, patch) {
|
|
657
|
+
RecipeContext.prototype.setDeclared = async function (name, patch, opts) {
|
|
559
658
|
_requireWorld(this);
|
|
560
659
|
_requireHeld(this, 'setDeclared');
|
|
660
|
+
var by = this._record ? Record.requireBy(opts && opts.by, 'RecipeContext.setDeclared') : null;
|
|
661
|
+
if (this._record) { this._record._admit('RecipeContext.setDeclared'); }
|
|
561
662
|
var runner = this._runner;
|
|
562
663
|
var obj = null;
|
|
563
664
|
this._parts.forEach(function (row, o) { if (row.key === name) { obj = o; } });
|
|
@@ -615,7 +716,117 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
615
716
|
runner.cameraControl.setOrbit(pose);
|
|
616
717
|
kept.push('cameraPose');
|
|
617
718
|
}
|
|
618
|
-
|
|
719
|
+
// `to` is what the re-run declared, in the capture form `from` is in (the patch as typed is in the text).
|
|
720
|
+
var rebuilt = null;
|
|
721
|
+
this._parts.forEach(function (row, o) { if (row.key === name) { rebuilt = o; } });
|
|
722
|
+
if (!rebuilt) {
|
|
723
|
+
throw new Error('[RecipeContext.setDeclared] the re-run declared no "' + name + '" — the patched text dropped the entry. (FAIL-LOUD.)');
|
|
724
|
+
}
|
|
725
|
+
var after = _capture(this, rebuilt);
|
|
726
|
+
var to = {};
|
|
727
|
+
Object.keys(patch).forEach(function (k) { to[k] = after[k] === undefined ? null : _plain(after[k]); });
|
|
728
|
+
if (this._record) { this._record._row({ name: name, op: 'setDeclared', by: by, from: _plain(from), to: _plain(to) }); }
|
|
729
|
+
return { set: name, from: from, to: to, tick: 0, kept: kept, files: Object.assign({}, files) };
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* exp.fork(name, NewKind[, { by }]) → Promise<{ fork, file, from, files }> — copy the part file of `inParts` object
|
|
734
|
+
* `name`'s kind into the project as `parts/<NewKind>.js` (lib/shim/project/fork.js forkPartText: the class renamed,
|
|
735
|
+
* its imports rewritten to package specifiers, every other byte the source's), point the entry at the fork (the
|
|
736
|
+
* parts writer's `kind` edit: the Kind identifier and one import line), and re-run the scene IN THIS DOCUMENT
|
|
737
|
+
* through the one restart door, as setDeclared does: the seed and the camera pose are kept, the loop's run state
|
|
738
|
+
* is kept. The source text is the exact bytes the kind's `from` names: a package part's file (read through
|
|
739
|
+
* read-text.js readText), or a project fork's held file. The held project is updated BEFORE the re-run, so an open
|
|
740
|
+
* Record stays open; with one open, `by` is required and its `fork` row ({ from: <kind>, to: { kind, file } }) is
|
|
741
|
+
* written after the re-run. REFUSES (throws, the held text unchanged): a name that is not an `inParts` object, a
|
|
742
|
+
* hand made since the last toSketch that is unwritten, a kind with no file a fork can copy, every forkPartText
|
|
743
|
+
* refusal, a writer refusal, and a re-run whose entry is not the fork.
|
|
744
|
+
*/
|
|
745
|
+
/**
|
|
746
|
+
* The fork's plan for `inParts` object `name`, before any read: { obj, from, row, module }. THROWS the refusal
|
|
747
|
+
* fork() and forkRefusal() both name: not an `inParts` object, an unwritten hand, a kind FORK_REFUSED names
|
|
748
|
+
* (lib/shim/project/fork.js, the census reason), a kind with no part file a fork can copy.
|
|
749
|
+
*/
|
|
750
|
+
function _forkPlan(self, name) {
|
|
751
|
+
var runner = self._runner;
|
|
752
|
+
var obj = null;
|
|
753
|
+
self._parts.forEach(function (row, o) { if (row.key === name) { obj = o; } });
|
|
754
|
+
if (!obj || runner.declared.indexOf(obj) === -1) {
|
|
755
|
+
throw new Error('[RecipeContext.fork] "' + name + '" is not an object of this sketch\'s `parts` block' +
|
|
756
|
+
(self._code.size ? ' (code declarations: ' + Array.from(self._code.keys()).map(function (o) { return o.name; }).join(', ') +
|
|
757
|
+
' — ' + OUTSIDE_PARTS + ')' : '') + '. (FAIL-LOUD.)');
|
|
758
|
+
}
|
|
759
|
+
var unwritten = _unwrittenHands(self);
|
|
760
|
+
if (unwritten.length) {
|
|
761
|
+
throw new Error('[RecipeContext.fork] "' + unwritten.join('", "') + '" carries a hand the sketch text does ' +
|
|
762
|
+
'not hold yet — write the hand first: await exp.toSketch(). (FAIL-LOUD.)');
|
|
763
|
+
}
|
|
764
|
+
var from = runner.kindOf(obj);
|
|
765
|
+
if (Object.prototype.hasOwnProperty.call(FORK_REFUSED, from)) {
|
|
766
|
+
throw new Error('[RecipeContext.fork] ' + from + ' (the kind of "' + name + '") is not forkable — ' + FORK_REFUSED[from] + '. (FAIL-LOUD.)');
|
|
767
|
+
}
|
|
768
|
+
var row = kindOf(from);
|
|
769
|
+
var module = row ? moduleOfKind(row.from, self._urls) : null;
|
|
770
|
+
if (module !== PARTS_SPECIFIER && !(module && Object.prototype.hasOwnProperty.call(self._project.files, module))) {
|
|
771
|
+
throw new Error('[RecipeContext.fork] ' + from + ' (the kind of "' + name + '") has no part file a fork can copy — a fork ' +
|
|
772
|
+
'copies a part of @descix/frqtl-web/parts or a part file of this project. (FAIL-LOUD.)');
|
|
773
|
+
}
|
|
774
|
+
return { obj: obj, from: from, row: row, module: module };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* exp.forkRefusal(name) → null when exp.fork(name, …) may run, else the refusal it would throw (one sentence). A
|
|
779
|
+
* host's "Fork into project" control reads it for its disabled state and title; fork() runs the same plan.
|
|
780
|
+
*/
|
|
781
|
+
RecipeContext.prototype.forkRefusal = function (name) {
|
|
782
|
+
_requireWorld(this);
|
|
783
|
+
_requireHeld(this, 'forkRefusal');
|
|
784
|
+
try { _forkPlan(this, name); return null; }
|
|
785
|
+
catch (err) { return String(err && err.message).replace(/^\[RecipeContext\.fork\] /, '').replace(/ \(FAIL-LOUD\.\)$/, ''); }
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
RecipeContext.prototype.fork = async function (name, NewKind, opts) {
|
|
789
|
+
_requireWorld(this);
|
|
790
|
+
_requireHeld(this, 'fork');
|
|
791
|
+
var by = this._record ? Record.requireBy(opts && opts.by, 'RecipeContext.fork') : null;
|
|
792
|
+
if (this._record) { this._record._admit('RecipeContext.fork'); }
|
|
793
|
+
var runner = this._runner;
|
|
794
|
+
var plan = _forkPlan(this, name);
|
|
795
|
+
var from = plan.from, row = plan.row, module = plan.module;
|
|
796
|
+
var text, libPath;
|
|
797
|
+
if (module === PARTS_SPECIFIER) {
|
|
798
|
+
libPath = row.from.slice(packageRoot().length);
|
|
799
|
+
try { text = await readText(row.from); }
|
|
800
|
+
catch (err) { throw new Error('[RecipeContext.fork] the part file of ' + from + ': ' + ((err && err.message) || err) + '. (FAIL-LOUD.)'); }
|
|
801
|
+
} else {
|
|
802
|
+
libPath = module;
|
|
803
|
+
text = this._project.files[module];
|
|
804
|
+
}
|
|
805
|
+
var forked = forkPartText(text, { from: from, to: NewKind, libPath: libPath, files: this._project.files });
|
|
806
|
+
var file = 'parts/' + NewKind + '.js';
|
|
807
|
+
var entry = this._project.entry;
|
|
808
|
+
var r = patchParts(this._project.files[entry], [{ op: 'kind', name: name, Kind: NewKind, module: file }], { entry: entry });
|
|
809
|
+
if (r.refused.length) {
|
|
810
|
+
throw new Error('[RecipeContext.fork] the sketch text refused the fork: ' +
|
|
811
|
+
r.refused.map(function (x) { return x.reason; }).join('; ') + ' (FAIL-LOUD.)');
|
|
812
|
+
}
|
|
813
|
+
var files = Object.assign({}, this._project.files);
|
|
814
|
+
files[file] = forked.text;
|
|
815
|
+
files[entry] = r.text;
|
|
816
|
+
this._project = Object.assign({}, this._project, { files: files });
|
|
817
|
+
var pose = _cameraPose(runner);
|
|
818
|
+
var wasRunning = !!runner._isLooping;
|
|
819
|
+
await runner.restart();
|
|
820
|
+
if (!wasRunning) { runner.stop(); }
|
|
821
|
+
if (pose && runner.cameraControl && typeof runner.cameraControl.setOrbit === 'function') { runner.cameraControl.setOrbit(pose); }
|
|
822
|
+
var rebuilt = null;
|
|
823
|
+
this._parts.forEach(function (row2, o) { if (row2.key === name) { rebuilt = o; } });
|
|
824
|
+
if (!rebuilt || runner.kindOf(rebuilt) !== NewKind) {
|
|
825
|
+
throw new Error('[RecipeContext.fork] the re-run declared "' + name + '" as ' + (rebuilt ? runner.kindOf(rebuilt) : 'nothing') +
|
|
826
|
+
', not the fork ' + NewKind + '. (FAIL-LOUD.)');
|
|
827
|
+
}
|
|
828
|
+
if (this._record) { this._record._row({ name: name, op: 'fork', by: by, from: from, to: { kind: NewKind, file: file } }); }
|
|
829
|
+
return { fork: NewKind, file: file, from: from, files: Object.assign({}, files) };
|
|
619
830
|
};
|
|
620
831
|
|
|
621
832
|
RecipeContext.prototype.addTickFunction = function (fn) {
|
|
@@ -683,8 +894,8 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
683
894
|
var linked = null;
|
|
684
895
|
if (sketch && typeof sketch === 'object' && typeof sketch.buildExperiment === 'function') {
|
|
685
896
|
if (!sketch.project || typeof sketch.project !== 'object' || !sketch.project.files || typeof sketch.project.entry !== 'string') {
|
|
686
|
-
throw new TypeError('[RecipeContext.build] build(linked): linked is linkProject\'s result
|
|
687
|
-
'
|
|
897
|
+
throw new TypeError('[RecipeContext.build] build(linked): linked is linkProject\'s result (its contract: ' +
|
|
898
|
+
'lib/shim/project/link.js). (FAIL-LOUD.)');
|
|
688
899
|
}
|
|
689
900
|
linked = sketch;
|
|
690
901
|
sketch = linked.buildExperiment;
|
|
@@ -693,6 +904,12 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
693
904
|
'linkProject\'s result (got ' + typeof sketch + '). (FAIL-LOUD.)');
|
|
694
905
|
}
|
|
695
906
|
if (typeof sketch === 'function') {
|
|
907
|
+
// ONE Record per booted world: a build of text that is not the held text re-authors it, and closes the
|
|
908
|
+
// open Record with the world before (a rebuild of the same text — restart, setDeclared's re-run — does not).
|
|
909
|
+
if (this._record && !(linked && this._project && _sameFiles(linked.project.files, this._project.files))) {
|
|
910
|
+
this._reauthored = this._record;
|
|
911
|
+
this._record._closeOnReauthor(this._project);
|
|
912
|
+
}
|
|
696
913
|
this._buildFn = sketch;
|
|
697
914
|
this._project = linked ? linked.project : null;
|
|
698
915
|
this._urls = linked ? (linked.urls || null) : null;
|
|
@@ -719,6 +936,60 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
|
|
|
719
936
|
return this;
|
|
720
937
|
};
|
|
721
938
|
|
|
939
|
+
/** Two file maps hold the same paths with byte-equal text. */
|
|
940
|
+
function _sameFiles(a, b) {
|
|
941
|
+
var ka = Object.keys(a), kb = Object.keys(b);
|
|
942
|
+
if (ka.length !== kb.length) { return false; }
|
|
943
|
+
return ka.every(function (k) { return Object.prototype.hasOwnProperty.call(b, k) && a[k] === b[k]; });
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* exp.startRecord() → Promise<Record> — open a Record (lib/frqtl/objects/record/Record.js) on this facade's world.
|
|
948
|
+
* REFUSES (rejects): a second open Record (one per facade, one per runner); a held project with no frqtl.json
|
|
949
|
+
* (a Record snapshots a folder); a hand the sketch text does not hold yet (as setDeclared). While it is open,
|
|
950
|
+
* toSketch({ by }) and setDeclared(name, patch, { by }) name who acted.
|
|
951
|
+
*/
|
|
952
|
+
RecipeContext.prototype.startRecord = async function () {
|
|
953
|
+
_requireWorld(this);
|
|
954
|
+
_requireHeld(this, 'startRecord');
|
|
955
|
+
if (this._record) {
|
|
956
|
+
throw new Error('[RecipeContext.startRecord] a Record is already open on this facade (one per booted world) — ' +
|
|
957
|
+
'await rec.stop() first. (FAIL-LOUD.)');
|
|
958
|
+
}
|
|
959
|
+
// A re-authoring closed the previous Record; if that close was REFUSED and nobody read it, its rows would be lost
|
|
960
|
+
// without a word: refuse until the host reads `rec.closed` (the rejection carries err.record).
|
|
961
|
+
var last = this._reauthored;
|
|
962
|
+
if (last) {
|
|
963
|
+
await last._settled;
|
|
964
|
+
if (last._refused && !last._closedRead) {
|
|
965
|
+
var err = new Error('[RecipeContext.startRecord] the previous Record of this facade was closed by a re-authoring and ' +
|
|
966
|
+
'REFUSED (' + last._refused.message + ') — nobody read rec.closed, so its ' + last._refused.record.changes.length +
|
|
967
|
+
' row(s) would be lost: read rec.closed (its rejection carries err.record = { changes, clips }) first. (FAIL-LOUD.)');
|
|
968
|
+
err.record = last._refused.record;
|
|
969
|
+
throw err;
|
|
970
|
+
}
|
|
971
|
+
this._reauthored = null;
|
|
972
|
+
}
|
|
973
|
+
if (openRecordOf(this._runner)) {
|
|
974
|
+
throw new Error('[RecipeContext.startRecord] another facade holds an open Record on this runner. (FAIL-LOUD.)');
|
|
975
|
+
}
|
|
976
|
+
if (!Object.prototype.hasOwnProperty.call(this._project.files, MANIFEST_FILE)) {
|
|
977
|
+
throw new Error('[RecipeContext.startRecord] a Record snapshots a folder; boot it as one — the held project carries ' +
|
|
978
|
+
'no ' + MANIFEST_FILE + ': add files[\'' + MANIFEST_FILE + '\'] = serializeManifest(manifestFor({ name, entry: SKETCH_ENTRY })) ' +
|
|
979
|
+
'(all from ./project) to the files you boot. (FAIL-LOUD.)');
|
|
980
|
+
}
|
|
981
|
+
var unwritten = _unwrittenHands(this);
|
|
982
|
+
if (unwritten.length) {
|
|
983
|
+
throw new Error('[RecipeContext.startRecord] "' + unwritten.join('", "') + '" carries a hand the sketch text does ' +
|
|
984
|
+
'not hold yet — write the hand first: await exp.toSketch(). (FAIL-LOUD.)');
|
|
985
|
+
}
|
|
986
|
+
var rec = new Record(this);
|
|
987
|
+
this._record = rec;
|
|
988
|
+
try { await rec._ready(this._project); }
|
|
989
|
+
catch (e) { rec._detach(); throw e; }
|
|
990
|
+
return rec;
|
|
991
|
+
};
|
|
992
|
+
|
|
722
993
|
RecipeContext.prototype.run = function (buildFn) {
|
|
723
994
|
if (buildFn && typeof buildFn === 'function' && !this._built) { this.build(buildFn); }
|
|
724
995
|
_requireWorld(this);
|
|
@@ -128,7 +128,7 @@ import { compileWallV2 } from '../objects/actionCompiler.js';
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// ── Presets (B6) — pure CONSTRUCTION sugar expanding to the v2 descriptor fields.
|
|
131
|
-
// The
|
|
131
|
+
// The parts writer captures the EXPANDED fields (never the preset name).
|
|
132
132
|
//
|
|
133
133
|
// THE NAMING RULE: a preset's NAME MUST ASSERT ITS BEHAVIOR.
|
|
134
134
|
// `collisionBehavior:'nothing'` (pass-through) belongs ONLY on a preset whose name SAYS
|
|
@@ -71,29 +71,6 @@ import { loadedWasmSha256 } from '../../compat/providers/wasm-load-browser.js';
|
|
|
71
71
|
import * as FrqtlDisplay from '../../compat/renderer/display-slot.js';
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
/**
|
|
75
|
-
* The experiment identity the page ALREADY carries, ferried — not a second authority.
|
|
76
|
-
*
|
|
77
|
-
* The experiments registry DEFINES an experiment id as "the directory name under
|
|
78
|
-
* experiments/" (`experimentById`), and a canonical page is served from
|
|
79
|
-
* `…/experiments/<id>/index.html`. So the path segment IS the registry id, and reading it
|
|
80
|
-
* ferries the existing identity rather than minting a parallel one. The registry module
|
|
81
|
-
* is a registry, not a page fact, which is why the id is taken from the URL the registry's
|
|
82
|
-
* own ids are defined by.
|
|
83
|
-
*
|
|
84
|
-
* Fails SOFT to null (no DOM, a notebook realm, an ad-hoc page): `sourceRef: null` is the
|
|
85
|
-
* honest "this scene has no authored identity" value, and the corpus row then carries the
|
|
86
|
-
* code itself. A caller with a better identity overrides via `setSourceRef()`.
|
|
87
|
-
* @private
|
|
88
|
-
*/
|
|
89
|
-
function _defaultSourceRef() {
|
|
90
|
-
try {
|
|
91
|
-
if (typeof location === 'undefined' || !location) { return null; }
|
|
92
|
-
var m = String(location.pathname).match(/\/experiments\/([^/]+)\//);
|
|
93
|
-
return (m && m[1]) ? m[1] : null;
|
|
94
|
-
} catch (e) { return null; }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
74
|
/**
|
|
98
75
|
* The RASTER PATH pin — which GL backend this browser actually rasters with.
|
|
99
76
|
*
|
|
@@ -207,7 +184,7 @@ import * as FrqtlDisplay from '../../compat/renderer/display-slot.js';
|
|
|
207
184
|
// harness via setSourceRef(). null is HONEST (an ad-hoc universe); the corpus row then
|
|
208
185
|
// carries the code itself. Never per-capture caller-supplied provenance.
|
|
209
186
|
this._sourceRef = (identity.sourceRef === undefined || identity.sourceRef === null)
|
|
210
|
-
?
|
|
187
|
+
? null : String(identity.sourceRef);
|
|
211
188
|
// The code identity (setSourceSha256) — null until a door that linked a project registers it.
|
|
212
189
|
this._sourceSha256 = null;
|
|
213
190
|
// Memo for the content-addressed SceneRecord — see sceneRecord().
|