@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.1
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 +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- package/package.json +62 -4
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupTraceCircuit — the smallest complete circuit on this floor: SOURCE → TRACE → GROUND.
|
|
5
|
+
*
|
|
6
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
7
|
+
*
|
|
8
|
+
* Composes THREE already-committed pieces and adds no physics of its own (SU1):
|
|
9
|
+
* 1. a `SphereEmitter` on the `'laser'` preset — the VOLTAGE source, parked just
|
|
10
|
+
* inside the Trace's inlet and aimed down the first leg, so the whole burst is
|
|
11
|
+
* fed into the bore instead of sprayed at the inlet walls;
|
|
12
|
+
* 2. a `Trace` — the WIRE (mirror `Tunnel` runs + directed `Tunnel` corners);
|
|
13
|
+
* 3. that Trace's own `ground:true` terminal — the SINK, which also tallies delivery.
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} universe - a constructed, NOT-yet-init'd EGPTUniverse. This factory
|
|
16
|
+
* IS the sketch: it inits the world FIRST and then constructs parts (world.init()
|
|
17
|
+
* FIRST → CONSTRUCT parts → WIRE → RUN), so part construction order is irrelevant.
|
|
18
|
+
* Runs the FAST PATH
|
|
19
|
+
* (`withInterQuantumCollisions:false`): that is the lossless-waveguide regime the
|
|
20
|
+
* Trace datasheet is quoted in. Turning inter-quantum collisions ON is RESISTANCE and
|
|
21
|
+
* makes this a different, current-limited component.
|
|
22
|
+
* @param {Object} [options]
|
|
23
|
+
* @param {Array<Array<number>>} [options.path] - the Trace route (cardinal waypoints).
|
|
24
|
+
* Default: a straight 400px run across the middle of the canvas.
|
|
25
|
+
* @param {number} [options.bore=12] @param {number} [options.wallThickness=6]
|
|
26
|
+
* @param {number} [options.voltage=6] - emitter burst per tick. LIVE. Measured Ohmic:
|
|
27
|
+
* delivered current is a straight multiple of this with no knee (see Layer 2).
|
|
28
|
+
* @param {boolean} [options.active=true]
|
|
29
|
+
* @param {number} [options.lanes=1] - build N PARALLEL Traces at the published pitch,
|
|
30
|
+
* sharing rails, with only lane 0 energised. This is the miniaturization demo: the
|
|
31
|
+
* dark lanes are the crosstalk measurement.
|
|
32
|
+
*
|
|
33
|
+
* @returns {{ trace, traces, emitterControl, traceControl, largeObjects, onTick, dispose }}
|
|
34
|
+
* `traceControl` is the live sugar handle (the canonical knob pattern):
|
|
35
|
+
* `setVoltage/getVoltage`, `setActive`, `getDelivered`, `getCurrent`,
|
|
36
|
+
* `getConductance`, `getCrosstalk`, `controls`.
|
|
37
|
+
*
|
|
38
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
39
|
+
* Zero new physics. What the two knobs do to the running scene:
|
|
40
|
+
*
|
|
41
|
+
* `voltage` (emitter burst/tick) — the current is LINEAR in it with no ceiling in
|
|
42
|
+
* this regime: the Trace is a lossless waveguide, so whatever the source pushes in
|
|
43
|
+
* arrives. Conductance (delivered/tick ÷ emitted/tick) measured 1.00 ± 0.02 on a
|
|
44
|
+
* straight run against 0.29 for the same source and ground with the Trace REMOVED.
|
|
45
|
+
*
|
|
46
|
+
* `lanes` — stacks independent parallel Traces wall-against-wall at
|
|
47
|
+
* `Trace.lanePitch(bore, wallThickness)`. Only lane 0 is fed; the readout on the dark
|
|
48
|
+
* lanes IS the crosstalk measurement — 0.98 % into the immediately adjacent lane at
|
|
49
|
+
* bore 3 / wall 3 (9px pitch), and EXACTLY zero into every lane beyond it. This is the
|
|
50
|
+
* picture that shows the part shrinks: four 3px-bore lanes inside a 36px band with the
|
|
51
|
+
* energised lane still at G = 0.98.
|
|
52
|
+
*
|
|
53
|
+
* The full datasheet — bore, length, wall-thickness floor, corner throughput — lives in
|
|
54
|
+
* `Trace.js`'s Layer 2. It is quoted there once and not restated here.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
58
|
+
import { Trace } from '../../parts/Trace.js';
|
|
59
|
+
import { setupSphereEmitter3D } from './setupSphereEmitter3D.js';
|
|
60
|
+
|
|
61
|
+
/** Degrees → the emitter yaw that aims a burst down the Trace's first leg. */
|
|
62
|
+
function _aimDeg(path) {
|
|
63
|
+
var dx = path[1][0] - path[0][0], dy = path[1][1] - path[0][1];
|
|
64
|
+
if (dx > 0) { return 0; }
|
|
65
|
+
if (dx < 0) { return 180; }
|
|
66
|
+
return dy > 0 ? 90 : 270;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
70
|
+
* Exported as `setupTraceCircuit.OPTIONS` so a composing caller consumes it instead of
|
|
71
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
72
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
73
|
+
*/
|
|
74
|
+
var OPTIONS = [
|
|
75
|
+
'active', 'bore', 'lanes', 'path',
|
|
76
|
+
'runner', 'voltage', 'wallThickness',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
function setupTraceCircuit(universe, options) {
|
|
80
|
+
options = _assertSetupOptions('setupTraceCircuit', options, OPTIONS);
|
|
81
|
+
var TraceCls = Trace;
|
|
82
|
+
var emitterFactory = setupSphereEmitter3D;
|
|
83
|
+
|
|
84
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
85
|
+
var world = _requireWorldRect('setupTraceCircuit universe.universe_rect', universe.universe_rect);
|
|
86
|
+
var W = world.w, H = world.h;
|
|
87
|
+
var bore = options.bore !== undefined ? options.bore : 12;
|
|
88
|
+
var wallThickness = options.wallThickness !== undefined ? options.wallThickness : 6;
|
|
89
|
+
var voltage = options.voltage !== undefined ? options.voltage : 6;
|
|
90
|
+
var lanes = Math.max(1, options.lanes !== undefined ? options.lanes : 1);
|
|
91
|
+
var active = options.active !== undefined ? !!options.active : true;
|
|
92
|
+
|
|
93
|
+
// Independent Traces stack wall-against-wall. NOT bore+wallThickness: that would
|
|
94
|
+
// put each pair of neighbours' mirrors on the SAME pixels, which measured 31 %
|
|
95
|
+
// crosstalk while the energised lane still read G = 0.98 (a silent failure).
|
|
96
|
+
var pitch = TraceCls.lanePitch(bore, wallThickness);
|
|
97
|
+
var midY = Math.round(H / 2) - Math.round((lanes - 1) * pitch / 2);
|
|
98
|
+
var basePath = options.path || [[40, midY], [Math.max(140, W - 60), midY]];
|
|
99
|
+
|
|
100
|
+
// ══ STEP 1 — THE WORLD ══════════════════════════════════════════════
|
|
101
|
+
// This factory IS the sketch: it inits the world FIRST, before any part.
|
|
102
|
+
// Everything below is part construction, and construction ORDER among parts
|
|
103
|
+
// no longer matters (the old "source first, walls second" idiom existed only
|
|
104
|
+
// because the emitter factory owned init; it does not any more).
|
|
105
|
+
var aim = _aimDeg(basePath);
|
|
106
|
+
var inPort = basePath[0];
|
|
107
|
+
var inset = 4;
|
|
108
|
+
var emitterOpts = {
|
|
109
|
+
preset: 'laser', is2D: true, R: 24,
|
|
110
|
+
center: [
|
|
111
|
+
inPort[0] + (aim === 0 ? inset : aim === 180 ? -inset : 0),
|
|
112
|
+
inPort[1] + (aim === 90 ? inset : aim === 270 ? -inset : 0)
|
|
113
|
+
],
|
|
114
|
+
orientationDeg: aim,
|
|
115
|
+
emissionBoxSide: 0, fundamentalDimension: 0,
|
|
116
|
+
burstSize: voltage, emitEveryTicks: 1,
|
|
117
|
+
chargeMode: 'random'
|
|
118
|
+
};
|
|
119
|
+
emitterFactory.initWorld(universe, emitterOpts);
|
|
120
|
+
|
|
121
|
+
// ══ STEP 2 — THE PARTS ══════════════════════════════════════════════
|
|
122
|
+
var emitterResult = emitterFactory(universe, emitterOpts);
|
|
123
|
+
var emitterControl = emitterResult.sphereEmitterControl;
|
|
124
|
+
emitterControl.setActive(active);
|
|
125
|
+
|
|
126
|
+
// Lane 0 carries the authored route; extra lanes are that route offset by the
|
|
127
|
+
// pitch, so exactly one wall of separation sits between neighbours (the measured
|
|
128
|
+
// stacking geometry).
|
|
129
|
+
var traces = [];
|
|
130
|
+
for (var i = 0; i < lanes; i++) {
|
|
131
|
+
var lanePath = basePath.map(function (p) { return [p[0], p[1] + i * pitch]; });
|
|
132
|
+
traces.push(new TraceCls(universe, {
|
|
133
|
+
path: lanePath, bore: bore, wallThickness: wallThickness,
|
|
134
|
+
ground: true, name: 'trace' + i,
|
|
135
|
+
color: i === 0 ? [90, 120, 200] : [70, 80, 110]
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
var trace = traces[0];
|
|
139
|
+
|
|
140
|
+
var control = {
|
|
141
|
+
setVoltage: function (v) {
|
|
142
|
+
voltage = Math.max(0, Math.round(v));
|
|
143
|
+
emitterControl.setBurstSize(voltage);
|
|
144
|
+
return voltage;
|
|
145
|
+
},
|
|
146
|
+
getVoltage: function () { return voltage; },
|
|
147
|
+
setActive: function (v) { emitterControl.setActive(!!v); },
|
|
148
|
+
getDelivered: function () { return trace.getDelivered(); },
|
|
149
|
+
getCurrent: function () { return trace.getCurrent(); },
|
|
150
|
+
/** delivered/tick ÷ emitted/tick — 1.0 is a perfect conductor. */
|
|
151
|
+
getConductance: function () { return voltage ? trace.getCurrent() / voltage : 0; },
|
|
152
|
+
/** Fraction of lane 0's current appearing in the dark lanes (0 with lanes=1). */
|
|
153
|
+
getCrosstalk: function () {
|
|
154
|
+
if (traces.length < 2) { return 0; }
|
|
155
|
+
var lead = trace.getCurrent();
|
|
156
|
+
if (!lead) { return 0; }
|
|
157
|
+
var dark = 0;
|
|
158
|
+
for (var k = 1; k < traces.length; k++) { dark += traces[k].getCurrent(); }
|
|
159
|
+
return dark / lead;
|
|
160
|
+
},
|
|
161
|
+
getPitch: function () { return pitch; },
|
|
162
|
+
getLanes: function () { return traces.length; }
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// The standardized descriptor array — declared ONCE, consumed by both the page's
|
|
166
|
+
// own sliders and any host control strip (the established `controls` idiom).
|
|
167
|
+
control.controls = [
|
|
168
|
+
{
|
|
169
|
+
id: 'voltage', label: 'voltage (burst/tick)',
|
|
170
|
+
min: 0, max: 32, step: 1, value: voltage,
|
|
171
|
+
fmt: function (v) { return String(v) + ' q/tick'; },
|
|
172
|
+
get: function () { return control.getVoltage(); },
|
|
173
|
+
onChange: function (v) { control.setVoltage(v); }
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
// REBUILD-class: bore is wall geometry, so the page re-runs setup().
|
|
177
|
+
id: 'bore', label: 'bore (rebuild)',
|
|
178
|
+
min: 1, max: 40, step: 1, value: bore, rebuild: true,
|
|
179
|
+
fmt: function (v) { return String(v) + 'px'; },
|
|
180
|
+
get: function () { return bore; },
|
|
181
|
+
onChange: function () { /* the page owns the rebuild — see `rebuild:true` */ }
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'lanes', label: 'parallel lanes (rebuild)',
|
|
185
|
+
min: 1, max: 8, step: 1, value: lanes, rebuild: true,
|
|
186
|
+
fmt: function (v) { return String(v) + ' @ ' + pitch + 'px pitch'; },
|
|
187
|
+
get: function () { return control.getLanes(); },
|
|
188
|
+
onChange: function () { /* rebuild */ }
|
|
189
|
+
}
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
var result = {
|
|
193
|
+
trace: trace,
|
|
194
|
+
traces: traces,
|
|
195
|
+
emitterControl: emitterControl,
|
|
196
|
+
traceControl: control,
|
|
197
|
+
// FrqtlApp.setup() auto-wires this for rendering.
|
|
198
|
+
largeObjects: traces.reduce(function (acc, t) { return acc.concat(t.largeObjects); }, []),
|
|
199
|
+
/**
|
|
200
|
+
* The ONE per-tick call — drives every lane's junctions + ground sampling.
|
|
201
|
+
* `attachToRunner` below wires it for you; this stays public for a harness
|
|
202
|
+
* that ticks the universe directly with no runner.
|
|
203
|
+
*/
|
|
204
|
+
onTick: function (tickData) {
|
|
205
|
+
for (var k = 0; k < traces.length; k++) { traces[k].onTick(tickData); }
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* Subscribe every lane to the runner's per-tick fan-out. REQUIRED: nothing in
|
|
209
|
+
* `FrqtlApp` calls a setupResult's `onTick`, so without this the corner
|
|
210
|
+
* junctions never redirect and `getDelivered()`/`getCurrent()` read 0 forever —
|
|
211
|
+
* which looks exactly like "the wire doesn't conduct" (QA hit precisely that).
|
|
212
|
+
* Uses `addTickListener`, the multi-subscriber peer of the page's single `onTick`
|
|
213
|
+
* slot, so it never steals the page's own hook. Idempotent per lane.
|
|
214
|
+
* @param {Object} runner - an FrqtlApp.
|
|
215
|
+
*/
|
|
216
|
+
attachToRunner: function (runner) {
|
|
217
|
+
for (var k = 0; k < traces.length; k++) { traces[k].attachToRunner(runner); }
|
|
218
|
+
return result;
|
|
219
|
+
},
|
|
220
|
+
// CANONICAL TEARDOWN — the runner calls this before installing the next
|
|
221
|
+
// setupResult, so a rebuild never stacks emitters or walls. Each Trace's own
|
|
222
|
+
// dispose drops its tick subscription too.
|
|
223
|
+
dispose: function () {
|
|
224
|
+
if (emitterResult && typeof emitterResult.dispose === 'function') { emitterResult.dispose(); }
|
|
225
|
+
traces.forEach(function (t) { t.dispose(); });
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Auto-wire when the caller hands us the runner (the page idiom below). Optional:
|
|
230
|
+
// a caller without a runner calls `result.attachToRunner(runner)` after setup, or
|
|
231
|
+
// drives `result.onTick(tickData)` itself.
|
|
232
|
+
if (options.runner) { result.attachToRunner(options.runner); }
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
setupTraceCircuit.OPTIONS = OPTIONS;
|
|
237
|
+
|
|
238
|
+
export { setupTraceCircuit };
|
|
239
|
+
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setupTunnelLab — DirectedTunnel ISOLATION (I1, the directed-tunnel composition model
|
|
5
|
+
* §3): ONE `DirectedTunnel`, gun-chamber mechanics (fill / escape / dispersion) at a
|
|
6
|
+
* live-tunable scale — the visual half of the scale-sweep measurement
|
|
7
|
+
* (`docs/internal/physics-lab/directed-tunnel-scale-sweep/`), same methodology, same source shape.
|
|
8
|
+
*
|
|
9
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} universe - WASM universe (not yet init'd — this factory calls init()).
|
|
12
|
+
* NOTE ON DETERMINISTIC SEEDING: same convention as every other setup factory
|
|
13
|
+
* (`setupGunChamber.js`) — `universe` arrives ALREADY
|
|
14
|
+
* CONSTRUCTED with its seed fixed by `FrqtlApp`; there is no `opts.seed` here.
|
|
15
|
+
* @param {Object} [opts]
|
|
16
|
+
* @param {number} [opts.bore=40] - Tunnel bore (interior height/cross-section), pixels.
|
|
17
|
+
* REINIT-CLASS (changing it needs a fresh `runner.setup()` — matches the sweep's own
|
|
18
|
+
* per-cell tunnel construction; the tunnel geometry is not a live-resizable rect).
|
|
19
|
+
* @param {number} [opts.length=240] - Tunnel length (interior width), pixels. REINIT-CLASS.
|
|
20
|
+
* @param {number} [opts.wavelength=64] - Per-frame wavelength override for EVERY hand-
|
|
21
|
+
* placed source quantum (the SAME `makeLeafFrame` per-frame override the sweep uses —
|
|
22
|
+
* `QuantumEmitter` has no wavelength field, which is exactly why this factory hand-
|
|
23
|
+
* places quanta via a local host tick-fn instead of `DirectedTunnel`'s `options.emitter`
|
|
24
|
+
* — see the Layer 2 note below). LIVE (`setupResult.sourceState.wavelength`).
|
|
25
|
+
* **CEILING (CEO drive #2, 2026-07-14 — "the wavelength slider has no effect"
|
|
26
|
+
* root cause):** Engine Invariant #6 is `mass = max(1, capacity/wavelength)` —
|
|
27
|
+
* at this factory's `FUNDAMENTAL_DIMENSION=2`, `capacity = (2^2)^2 = 16`
|
|
28
|
+
* (`setupTunnelLab.CAPACITY`, exported below), so ANY `wavelength >= capacity`
|
|
29
|
+
* (16) saturates `mass` at its floor (1) and therefore ALSO saturates the
|
|
30
|
+
* DERIVED wavelength (`capacity/mass`) at exactly `capacity` (16) — `wavelength:
|
|
31
|
+
* 64`, `128`, or `1000000` are ALL physically IDENTICAL to `wavelength: 16`
|
|
32
|
+
* (same mass, same transverse-oscillation period). This was NOT a collision-
|
|
33
|
+
* reset bug (verified: `mass` survives every wall behavior — option1/option2/
|
|
34
|
+
* composable — unchanged across ticks, pkg-node probe) — it is a genuine
|
|
35
|
+
* ceiling in the formula. Only `wavelength` values in `(0, capacity]` (i.e.
|
|
36
|
+
* `(0, 16]` here) produce DISTINCT mass/oscillation-period values; `tunnel-lab
|
|
37
|
+
* /index.html`'s own wavelength slider is clamped to `setupTunnelLab.
|
|
38
|
+
* MAX_USEFUL_WAVELENGTH` (= `CAPACITY`) for exactly this reason — one
|
|
39
|
+
* authority, no hand-typed "16" duplicate in the page.
|
|
40
|
+
* @param {string} [opts.wallBehavior='option2'] - Initial `DirectedTunnel.behavior`
|
|
41
|
+
* (`DirectedTunnel.BEHAVIORS` — option1 | option2 | any `SlitLinerScatter` composable
|
|
42
|
+
* name). LIVE — `setupResult.tunnel.updateBehavior(newBehavior)`.
|
|
43
|
+
* @param {number} [opts.sourceDensity=5] - Quanta hand-placed per burst. LIVE
|
|
44
|
+
* (`setupResult.sourceState.density`). NOTE: this is the FACTORY's own
|
|
45
|
+
* omitted-opt default (unchanged, for any headless caller) — `tunnel-lab
|
|
46
|
+
* /index.html`'s own UI default is 10 (CEO drive #2, 2026-07-14
|
|
47
|
+
* population-density fix: the page ALWAYS passes an explicit value read
|
|
48
|
+
* from its own `<input>` default, so this factory default is never
|
|
49
|
+
* actually reached by the page — see that file's own docstring for the
|
|
50
|
+
* pkg-node-measured TPS/alive-count calibration against `setupGunChamber
|
|
51
|
+
* .js`'s own 20/tick emission rate).
|
|
52
|
+
* @param {number} [opts.sourceRate=10] - Ticks between bursts (an `everyNTicks`-style
|
|
53
|
+
* cadence, mirroring `emitterProgram.js`'s own live cadence knob). LIVE
|
|
54
|
+
* (`setupResult.sourceState.rate`). NOTE: same as `sourceDensity` above —
|
|
55
|
+
* the factory default (unchanged) vs. the page's own UI default (1) diverge
|
|
56
|
+
* for the same reason.
|
|
57
|
+
* @param {boolean} [opts.sourceActive=true] - Master on/off for the burst source. LIVE
|
|
58
|
+
* (`setupResult.sourceState.active`).
|
|
59
|
+
* @param {number} [opts.depth] - CEO drive #2 (2026-07-14) 3D toggle. OMITTED
|
|
60
|
+
* (default) -> 2D (byte-identical to every pre-existing caller). A positive
|
|
61
|
+
* value -> real z-containment (`activeAxes:{x,y,z}` on `universe.init()`,
|
|
62
|
+
* `DirectedTunnel`'s own `depth`/`z=-depth/2`, hand-placed quanta z-centred)
|
|
63
|
+
* — the standard `options.depth` recipe. REINIT-CLASS
|
|
64
|
+
* (only meaningful at construction, like bore/length).
|
|
65
|
+
* @param {'spray'|'beam'} [opts.velocityProfile='spray'] - ws-dse-3d AC3 (CEO
|
|
66
|
+
* drive #2 round-2 ruling, "tunnel-lab gains the source-profile selection off
|
|
67
|
+
* the shared surface"): the SAME `SustainedSource.SOURCE_SPEC_FIELDS.
|
|
68
|
+
* velocityProfile` axis `LightSource`'s own `particleWalk` burst always uses
|
|
69
|
+
* (`'beam'`, hardcoded there) — exposed here so tunnel-lab can be set to
|
|
70
|
+
* `'beam'` and produce a directly comparable collimated source to
|
|
71
|
+
* lightsource-lab's default particleWalk mode at matched bore/length/
|
|
72
|
+
* wavelength/wallBehavior ("same settings -> same visuals"). Default `'spray'`
|
|
73
|
+
* — byte-identical to every pre-existing caller (this factory's own original
|
|
74
|
+
* hardcoded biased-fan shape, unchanged). LIVE (`setupResult.sourceState.
|
|
75
|
+
* velocityProfile` — `placeSourceQuanta` reads it fresh every burst, no reinit).
|
|
76
|
+
*
|
|
77
|
+
* @returns {{
|
|
78
|
+
* tunnel: DirectedTunnel, sourceState: {density:number, rate:number, wavelength:number,
|
|
79
|
+
* active:boolean}, largeObjects: LargeObject[]
|
|
80
|
+
* }}
|
|
81
|
+
* `largeObjects` (= `tunnel.largeObjects`) is the canonical key `FrqtlApp.setup()`
|
|
82
|
+
* auto-wires for rendering (matches every other setup factory).
|
|
83
|
+
*
|
|
84
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
85
|
+
*
|
|
86
|
+
* **Zero new physics (canonical-bench SU1)** — this factory only WIRES existing
|
|
87
|
+
* primitives: `DirectedTunnel` (itself zero new physics over `Tube`), and a burst source
|
|
88
|
+
* built from the SAME `makeLeafFrame`+`injectFrame`+`universe.random()` primitives the
|
|
89
|
+
* scale-sweep script and every hand-placed-quantum experiment already use. The source is
|
|
90
|
+
* a LOCAL host tick-fn (NOT `QuantumEmitter`) because `QuantumEmitter`'s constructor has
|
|
91
|
+
* no wavelength field — the isolation lab needs the SAME per-frame wavelength override
|
|
92
|
+
* the scale sweep uses so the page visually demonstrates EXACTLY what the sweep
|
|
93
|
+
* measured, not a physically-different (unmodifiable-wavelength) source. Pushed onto
|
|
94
|
+
* `universe.experimentTickFunctions` (Phase A, matching `emitterProgram.js`'s own
|
|
95
|
+
* registration point) — legitimate here because, unlike `DirectedTunnel`'s `option1`
|
|
96
|
+
* redirect (which needs Phase-D `tickData`), injection is a pre-tick primitive with no
|
|
97
|
+
* such timing constraint.
|
|
98
|
+
*
|
|
99
|
+
* **Gun-chamber convention**: `capNegX:true, capPosX:false` — a CLOSED breech (source
|
|
100
|
+
* fires INTO the bore, quanta bounce/fill) and an OPEN muzzle (quanta escape, then
|
|
101
|
+
* disperse freely with no walls) — the CEO's own Gun-Chamber model ("particles ping-pong
|
|
102
|
+
* in a tube, escape the open end, spread comes from dispersion AFTER escape").
|
|
103
|
+
*
|
|
104
|
+
* **Fundamental dimension = 2** (matches `setupDoubleSlit.js`'s default AND the scale-
|
|
105
|
+
* sweep's own convention — see `docs/internal/physics-lab/directed-tunnel-scale-sweep/README.md`'s
|
|
106
|
+
* "level 0 pins wavelength to a no-op" note): a level-0 leaf has `capacity=1`, so
|
|
107
|
+
* `mass=max(1,capacity/wavelength)` is PINNED at 1 for any `wavelength>=2` — the
|
|
108
|
+
* wavelength slider would be a silent no-op at level 0.
|
|
109
|
+
*
|
|
110
|
+
* **Biased entrance (bunched near the TOP wall)** — the SAME shape the scale sweep uses,
|
|
111
|
+
* because a symmetric/spread entrance is already balanced and shows no washout effect at
|
|
112
|
+
* all (the scale sweep's own finding); bunching is what makes
|
|
113
|
+
* "uniformizes vs transmits unmodified" visually OBSERVABLE as the population fills the
|
|
114
|
+
* bore over time.
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
|
|
118
|
+
import { DirectedTunnel } from '../objects/DirectedTunnel.js';
|
|
119
|
+
import * as SustainedSource from '../objects/sustainedSource.js';
|
|
120
|
+
|
|
121
|
+
// The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
|
|
122
|
+
// LAZILY in the browser: the game layer defines window.assertSetupOptions before any
|
|
123
|
+
// page script runs, but reading it at eval time would bake in a load-order dependency.
|
|
124
|
+
|
|
125
|
+
// ── Imports ─────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
var FUNDAMENTAL_DIMENSION = 2; // matches setupDoubleSlit.js + the scale-sweep script
|
|
128
|
+
|
|
129
|
+
// CEO drive #2 (2026-07-14) — the ONE authority for "what's the largest
|
|
130
|
+
// wavelength value that still produces a DISTINCT mass from any larger one"
|
|
131
|
+
// at this factory's FUNDAMENTAL_DIMENSION. Engine Invariant #6:
|
|
132
|
+
// `mass = max(1, capacity/wavelength)` — the floor (mass=1) is reached
|
|
133
|
+
// exactly at `wavelength === capacity`; every wavelength beyond that is
|
|
134
|
+
// indistinguishable. `tunnel-lab/index.html` reads this (never a hand-typed
|
|
135
|
+
// "16" literal) to clamp its wavelength slider's max to the achievable range.
|
|
136
|
+
// ws-dse-3d: derived from `SustainedSource.capacityAtDimension` — the SAME
|
|
137
|
+
// authority `setupLightSourceLab.js` now also derives its own
|
|
138
|
+
// MAX_USEFUL_WAVELENGTH from (AC4: "same MAX_USEFUL_WAVELENGTH clamp
|
|
139
|
+
// authority" for both labs).
|
|
140
|
+
var TUNNEL_LAB_CAPACITY = SustainedSource.capacityAtDimension(FUNDAMENTAL_DIMENSION); // 16 at FUNDAMENTAL_DIMENSION=2
|
|
141
|
+
/**
|
|
142
|
+
* OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
|
|
143
|
+
* Exported as `setupTunnelLab.OPTIONS` so a composing caller consumes it instead of
|
|
144
|
+
* retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
|
|
145
|
+
* read-set by static trace and fails if this list drifts from the code.
|
|
146
|
+
*/
|
|
147
|
+
var OPTIONS = [
|
|
148
|
+
'bore', 'depth',
|
|
149
|
+
'length', 'sourceActive', 'sourceDensity', 'sourceRate',
|
|
150
|
+
'velocityProfile', 'wallBehavior', 'wavelength',
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
function setupTunnelLab(universe, opts) {
|
|
154
|
+
opts = _assertSetupOptions('setupTunnelLab', opts, OPTIONS);
|
|
155
|
+
// The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
|
|
156
|
+
var canvasRect = universe.universe_rect;
|
|
157
|
+
var world = _requireWorldRect('setupTunnelLab universe.universe_rect', canvasRect);
|
|
158
|
+
var cw = world.w, ch = world.h;
|
|
159
|
+
|
|
160
|
+
var bore = opts.bore !== undefined ? opts.bore : 40;
|
|
161
|
+
var length = opts.length !== undefined ? opts.length : 240;
|
|
162
|
+
var wavelength = opts.wavelength !== undefined ? opts.wavelength : 64;
|
|
163
|
+
var wallBehavior = opts.wallBehavior !== undefined ? opts.wallBehavior : 'option2';
|
|
164
|
+
var sourceDensity = opts.sourceDensity !== undefined ? opts.sourceDensity : 5;
|
|
165
|
+
var sourceRate = opts.sourceRate !== undefined ? opts.sourceRate : 10;
|
|
166
|
+
var sourceActive = opts.sourceActive !== undefined ? !!opts.sourceActive : true;
|
|
167
|
+
// CEO drive #2 item 1 (2026-07-14) — OPT-IN 3D, OMISSION-preserves-2D
|
|
168
|
+
// (the "before adding a creation-method parameter" discipline): mirrors
|
|
169
|
+
// the standard `options.depth` recipe — omitted/null ->
|
|
170
|
+
// 2D (zContain:false, byte-identical to every existing caller); a
|
|
171
|
+
// positive value -> real z-containment (Tube/DirectedTunnel heritage —
|
|
172
|
+
// `options.depth` already existed on DirectedTunnel, T1; this factory
|
|
173
|
+
// simply now forwards it + arms activeAxes).
|
|
174
|
+
var depth = (opts.depth !== undefined && opts.depth !== null) ? opts.depth : null;
|
|
175
|
+
// ws-dse-3d AC3 — default 'spray' is byte-identical to every pre-existing
|
|
176
|
+
// caller (this factory's own original hardcoded value below).
|
|
177
|
+
var velocityProfile = opts.velocityProfile !== undefined ? opts.velocityProfile : 'spray';
|
|
178
|
+
|
|
179
|
+
var initOpts = {};
|
|
180
|
+
if (depth !== null) { initOpts.activeAxes = { x: true, y: true, z: true }; }
|
|
181
|
+
universe.init(FUNDAMENTAL_DIMENSION, initOpts);
|
|
182
|
+
|
|
183
|
+
var left = Math.round(cw * 0.1);
|
|
184
|
+
var top = Math.round(ch / 2 - bore / 2);
|
|
185
|
+
|
|
186
|
+
var tunnelOpts = {
|
|
187
|
+
left: left, top: top, length: length, width: bore,
|
|
188
|
+
capNegX: true, capPosX: false, // closed breech, open muzzle — Gun-Chamber convention
|
|
189
|
+
behavior: wallBehavior,
|
|
190
|
+
};
|
|
191
|
+
if (depth !== null) { tunnelOpts.depth = depth; tunnelOpts.z = -depth / 2; }
|
|
192
|
+
var tunnel = new DirectedTunnel(universe, tunnelOpts);
|
|
193
|
+
|
|
194
|
+
// ws-dse-3d (CEO ruling 2026-07-14, "one source surface"): the recurring
|
|
195
|
+
// burst injector now routes through `sustainedSource.js`'s ONE
|
|
196
|
+
// `buildSustainedSource` authority — the SAME surface
|
|
197
|
+
// `setupLightSourceLab.js` (via `LightSource`'s own opt-in
|
|
198
|
+
// `options.sustain`) now also consumes. `velocityProfile:'spray'` +
|
|
199
|
+
// `entranceBias:'top'` reproduce this factory's PRE-EXISTING biased-
|
|
200
|
+
// random-fan shape EXACTLY (the "uniformization visible" demo shape —
|
|
201
|
+
// see the module docstring's "Biased entrance" note); `zIsotropy:true`
|
|
202
|
+
// (the default) is the 3D-SYMMETRIC EMISSION fix (ws-dse-3d, theorist §4
|
|
203
|
+
// option (a)) — when `depth` is armed, every burst now ALSO draws a `vz`
|
|
204
|
+
// symmetric to the `vy` spread, instead of pinning z-momentum at zero
|
|
205
|
+
// forever (the tester-measured z/y=0.23 flatness root cause).
|
|
206
|
+
var src = SustainedSource.buildSustainedSource(universe, tunnel.innerRect, true /* capNegX (Gun-Chamber closed breech) */, depth !== null, {
|
|
207
|
+
wavelength: wavelength,
|
|
208
|
+
velocityProfile: velocityProfile,
|
|
209
|
+
entranceBias: 'top',
|
|
210
|
+
zIsotropy: true,
|
|
211
|
+
density: sourceDensity,
|
|
212
|
+
rate: sourceRate,
|
|
213
|
+
active: sourceActive,
|
|
214
|
+
});
|
|
215
|
+
var sourceState = src.sourceState;
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
tunnel: tunnel,
|
|
219
|
+
sourceState: sourceState,
|
|
220
|
+
// FrqtlApp.setup() (FrqtlApp.js:528) auto-wires
|
|
221
|
+
// the render list; today the world's own census carries them by construction.
|
|
222
|
+
largeObjects: tunnel.largeObjects,
|
|
223
|
+
// CANONICAL TEARDOWN (CEO drive #2, 2026-07-14 — the reinit-
|
|
224
|
+
// compounding fix): `FrqtlApp.prototype.setup()` calls THIS
|
|
225
|
+
// automatically before installing the NEXT setupResult. Delegates to
|
|
226
|
+
// `src.dispose()` (the ONE `sustainedSource.js` teardown mechanism)
|
|
227
|
+
// rather than re-deriving the tag-then-splice idiom a second time.
|
|
228
|
+
dispose: src.dispose,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ONE authority for the achievable wavelength ceiling at this factory's
|
|
233
|
+
// FUNDAMENTAL_DIMENSION (see the TUNNEL_LAB_CAPACITY comment above) — the
|
|
234
|
+
// page reads this instead of hand-typing "16".
|
|
235
|
+
setupTunnelLab.FUNDAMENTAL_DIMENSION = FUNDAMENTAL_DIMENSION;
|
|
236
|
+
setupTunnelLab.CAPACITY = TUNNEL_LAB_CAPACITY;
|
|
237
|
+
setupTunnelLab.MAX_USEFUL_WAVELENGTH = TUNNEL_LAB_CAPACITY;
|
|
238
|
+
|
|
239
|
+
setupTunnelLab.OPTIONS = OPTIONS;
|
|
240
|
+
|
|
241
|
+
export { setupTunnelLab };
|
|
242
|
+
|