@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
|
@@ -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) {
|
|
@@ -67,9 +67,10 @@
|
|
|
67
67
|
// 2D ONLY, deliberately. There is no `modes` strip and no `exp.is3D` guard: these
|
|
68
68
|
// rungs declare one world each. A second dimensional variant would be a separate
|
|
69
69
|
// recipe declaration, not a mode.
|
|
70
|
-
// THE PAIR. `dse-two-emitters.js` is the SAME apparatus with
|
|
71
|
-
//
|
|
72
|
-
// fails if
|
|
70
|
+
// THE PAIR. `dse-two-emitters.js` is the SAME apparatus with TWO source entries. The two files differ ONLY in the
|
|
71
|
+
// title lines, this note, and their `source*` entries of `parts` (with the world call naming the first
|
|
72
|
+
// of them) — lib/frqtl/scenes/test/scene-files.test.mjs fails if anything else diverges, so the A/B the
|
|
73
|
+
// pair exists to make stays an A/B.
|
|
73
74
|
|
|
74
75
|
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
75
76
|
|
|
@@ -116,7 +117,34 @@ const detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
|
116
117
|
// the photoelectric experiment, and it saturates.
|
|
117
118
|
const detectorPreset = 'transparent-probe';
|
|
118
119
|
|
|
120
|
+
const lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
121
|
+
const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
122
|
+
const wlScale = Math.floor(fwl / 4); // 100
|
|
123
|
+
const cy = Math.round(H / 2); // 200 — the source plane's midline
|
|
124
|
+
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
125
|
+
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
126
|
+
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
127
|
+
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
128
|
+
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
129
|
+
// exactly where the one-source rung has no peak at all.
|
|
130
|
+
const sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
131
|
+
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
132
|
+
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
133
|
+
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
134
|
+
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
135
|
+
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
136
|
+
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
137
|
+
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
138
|
+
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
139
|
+
// forward component. One sink, not two.
|
|
140
|
+
const SRC = {
|
|
141
|
+
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
142
|
+
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
143
|
+
burstSize: 8, emitEveryTicks: 1
|
|
144
|
+
};
|
|
145
|
+
|
|
119
146
|
export const parts = {
|
|
147
|
+
source: [SphereEmitter, { ...SRC, center: [srcX, cy] }],
|
|
120
148
|
// FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
|
|
121
149
|
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
122
150
|
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
@@ -134,41 +162,11 @@ export const parts = {
|
|
|
134
162
|
};
|
|
135
163
|
|
|
136
164
|
export default function buildExperiment(exp) {
|
|
137
|
-
var NSRC = 1;
|
|
138
|
-
var lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
139
|
-
var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
140
|
-
var wlScale = Math.floor(fwl / 4); // 100
|
|
141
165
|
if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
|
|
142
166
|
throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
|
|
143
167
|
'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
144
168
|
}
|
|
145
|
-
|
|
146
|
-
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
147
|
-
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
148
|
-
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
149
|
-
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
150
|
-
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
151
|
-
// exactly where the one-source rung has no peak at all.
|
|
152
|
-
var sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
153
|
-
var srcYs = (NSRC === 2) ? [cy - sep / 2, cy + sep / 2] : [cy];
|
|
154
|
-
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
155
|
-
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
156
|
-
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
157
|
-
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
158
|
-
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
159
|
-
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
160
|
-
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
161
|
-
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
162
|
-
// forward component. One sink, not two.
|
|
163
|
-
var SRC = {
|
|
164
|
-
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
165
|
-
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
166
|
-
burstSize: 8, emitEveryTicks: 1
|
|
167
|
-
};
|
|
168
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
169
|
-
for (var si = 0; si < srcYs.length; si++) {
|
|
170
|
-
exp.addObject('SphereEmitter', Object.assign({}, SRC, { center: [srcX, Math.round(srcYs[si])] }));
|
|
171
|
-
}
|
|
169
|
+
exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
172
170
|
const p = exp.declare(parts);
|
|
173
171
|
// ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
|
|
174
172
|
// The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
|
|
@@ -67,9 +67,10 @@
|
|
|
67
67
|
// 2D ONLY, deliberately. There is no `modes` strip and no `exp.is3D` guard: these
|
|
68
68
|
// rungs declare one world each. A second dimensional variant would be a separate
|
|
69
69
|
// recipe declaration, not a mode.
|
|
70
|
-
// THE PAIR. `dse-one-emitter.js` is the SAME apparatus with
|
|
71
|
-
//
|
|
72
|
-
// fails if
|
|
70
|
+
// THE PAIR. `dse-one-emitter.js` is the SAME apparatus with ONE source entry. The two files differ ONLY in the
|
|
71
|
+
// title lines, this note, and their `source*` entries of `parts` (with the world call naming the first
|
|
72
|
+
// of them) — lib/frqtl/scenes/test/scene-files.test.mjs fails if anything else diverges, so the A/B the
|
|
73
|
+
// pair exists to make stays an A/B.
|
|
73
74
|
|
|
74
75
|
import { SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
75
76
|
|
|
@@ -116,7 +117,35 @@ const detW = Math.round(lamPx / 64); // 5 — plate thickness
|
|
|
116
117
|
// the photoelectric experiment, and it saturates.
|
|
117
118
|
const detectorPreset = 'transparent-probe';
|
|
118
119
|
|
|
120
|
+
const lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
121
|
+
const fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
122
|
+
const wlScale = Math.floor(fwl / 4); // 100
|
|
123
|
+
const cy = Math.round(H / 2); // 200 — the source plane's midline
|
|
124
|
+
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
125
|
+
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
126
|
+
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
127
|
+
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
128
|
+
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
129
|
+
// exactly where the one-source rung has no peak at all.
|
|
130
|
+
const sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
131
|
+
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
132
|
+
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
133
|
+
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
134
|
+
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
135
|
+
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
136
|
+
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
137
|
+
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
138
|
+
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
139
|
+
// forward component. One sink, not two.
|
|
140
|
+
const SRC = {
|
|
141
|
+
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
142
|
+
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
143
|
+
burstSize: 8, emitEveryTicks: 1
|
|
144
|
+
};
|
|
145
|
+
|
|
119
146
|
export const parts = {
|
|
147
|
+
source1: [SphereEmitter, { ...SRC, center: [srcX, Math.round(cy - sep / 2)] }],
|
|
148
|
+
source2: [SphereEmitter, { ...SRC, center: [srcX, Math.round(cy + sep / 2)] }],
|
|
120
149
|
// FAR-EDGE SINK. The plate below counts WITHOUT removing, so quanta fly on past
|
|
121
150
|
// it; this is the experiment-boundary sink downstream of the plate. It is not
|
|
122
151
|
// load-bearing (wrap is false, so the canvas edge would remove them anyway) — it
|
|
@@ -134,41 +163,11 @@ export const parts = {
|
|
|
134
163
|
};
|
|
135
164
|
|
|
136
165
|
export default function buildExperiment(exp) {
|
|
137
|
-
var NSRC = 2;
|
|
138
|
-
var lamArg = 3.2; // capacity/mass -> fullness 1/3.2 = 0.3125 (yellow)
|
|
139
|
-
var fwl = 4 * (lamPx / lamArg); // 400 -> wavelengthScale floor(400/4) = 100
|
|
140
|
-
var wlScale = Math.floor(fwl / 4); // 100
|
|
141
166
|
if (Math.abs(lamArg * wlScale - lamPx) > 1e-9) {
|
|
142
167
|
throw new Error('[dse-h1] lamPx ' + lamPx + ' is not realisable as lambda_eff: the engine ' +
|
|
143
168
|
'can only produce wavelengthArg x floor(fwl/4) = ' + (lamArg * wlScale) + '. (FAIL-LOUD.)');
|
|
144
169
|
}
|
|
145
|
-
|
|
146
|
-
// TWO-SOURCE SEPARATION. Two sources reinforce when their separation is a
|
|
147
|
-
// multiple of lambda/2, and the coupling falls as (lambda/d)^2, so lambda/2 is
|
|
148
|
-
// the strongest case. At this leg it also puts a band of EACH source on the
|
|
149
|
-
// midline y = H/2 (source 1's lower band and source 2's upper band coincide
|
|
150
|
-
// there) — the doubly-reinforced central peak, equidistant from both emitters,
|
|
151
|
-
// exactly where the one-source rung has no peak at all.
|
|
152
|
-
var sep = lamPx / 2; // 160 -> sources at y 120 and 280
|
|
153
|
-
var srcYs = (NSRC === 2) ? [cy - sep / 2, cy + sep / 2] : [cy];
|
|
154
|
-
// ── THE LIGHT SOURCE ────────────────────────────────────────────────────
|
|
155
|
-
// preset 'hemisphere' = 180 degrees of FORWARD (+X) emission: the SAME
|
|
156
|
-
// ball-rejected sampler as the omni source with the forward axis' range halved,
|
|
157
|
-
// so nothing about the walk changes, it is simply pointed at the plate. The
|
|
158
|
-
// default emission box is 0 for this preset, so the source is a single PIXEL —
|
|
159
|
-
// load-bearing, because any spread at birth blurs every band by that spread.
|
|
160
|
-
// NO START WALL: the backward population is empty by construction, and `wrap` is
|
|
161
|
-
// false, so the canvas edge kills whatever transverse wiggle out-runs its
|
|
162
|
-
// forward component. One sink, not two.
|
|
163
|
-
var SRC = {
|
|
164
|
-
preset: 'hemisphere', is2D: true, chargeMode: 'random',
|
|
165
|
-
wavelength: lamArg, fundamentalDimension: 2, fundamentalWaveLength: fwl,
|
|
166
|
-
burstSize: 8, emitEveryTicks: 1
|
|
167
|
-
};
|
|
168
|
-
exp.createCanvas(CW, H, SphereEmitter.worldCfg(SRC)); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
169
|
-
for (var si = 0; si < srcYs.length; si++) {
|
|
170
|
-
exp.addObject('SphereEmitter', Object.assign({}, SRC, { center: [srcX, Math.round(srcYs[si])] }));
|
|
171
|
-
}
|
|
170
|
+
exp.createCanvas(CW, H, SphereEmitter.worldCfg(parts.source1[1])); // THE WORLD: a 2D canvas + universe — first, then parts
|
|
172
171
|
const p = exp.declare(parts);
|
|
173
172
|
// ── THE INSTRUMENT — the live count-by-y histogram, REGISTERED TO THE PLATE ──
|
|
174
173
|
// The panel takes the plate's OWN rect y-origin (0) and height (H), so the two
|