@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +87 -0
- package/LICENSE.md +664 -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 +2499 -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 +84 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +99 -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 +258 -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 +2470 -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 +2920 -0
- package/dist/frqtl/objects/ObjectFactory.js +296 -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 +710 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -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 +411 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +351 -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 +309 -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 +820 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +468 -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 +250 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -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 +277 -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 +1258 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
- package/dist/frqtl/objects/v1-replacements.js +29 -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 +34 -0
- package/dist/frqtl/parts/CollisionTypes.js +30 -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 +489 -0
- package/dist/frqtl/parts/LargeObject.js +405 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +717 -0
- package/dist/frqtl/parts/OvenBox.js +611 -0
- package/dist/frqtl/parts/PointSource.js +762 -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 +243 -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 +211 -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 +690 -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 +325 -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 +671 -0
- package/dist/shim/notebook/knob.js +150 -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 +507 -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 +213 -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 +804 -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 +63 -4
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trace — THE WIRE PART. A routed, terminated conductor assembled entirely from
|
|
5
|
+
* stock parts: mirror `Tunnel` runs + directed `Tunnel` corner junctions + a
|
|
6
|
+
* `Wall{destroy}` ground. It is the standard part gates and adders are built from.
|
|
7
|
+
*
|
|
8
|
+
* var t = new Trace(universe, { path: [[40,200],[240,200],[240,360]], bore: 3, ground: true });
|
|
9
|
+
* t.inPort; // [40,200] — where the source feeds it
|
|
10
|
+
* t.outPort; // [240,360] — where it delivers
|
|
11
|
+
* t.onTick(tickData); // once per tick (self-gating; no-op with no corners)
|
|
12
|
+
* t.getDelivered(); // quanta delivered to ground (calibrated)
|
|
13
|
+
*
|
|
14
|
+
* ── WHY THE NAME IS "TRACE" AND NOT "WIRE" ──────────────────────────────────
|
|
15
|
+
* Named LAST, from what it was measured to do. It routes on the CARDINAL grid
|
|
16
|
+
* only — straight x/y runs joined by right-angle junctions (Manhattan routing),
|
|
17
|
+
* because a diagonal corridor of tilted mirrors was measured NOT to conduct
|
|
18
|
+
* (G ≈ 0.04 at 15/30/45 deg vs 1.01 on-axis). Straight segments, right-angle
|
|
19
|
+
* corners and a fixed parallel pitch is exactly a PCB trace, so that is the name.
|
|
20
|
+
* It is the wire of this line; "trace" is just the honest word for the routing
|
|
21
|
+
* it actually supports.
|
|
22
|
+
*
|
|
23
|
+
* ── PURE COMPOSITION — zero engine, zero new physics (canonical-bench SU1) ────
|
|
24
|
+
* A Trace adds NO collision logic. Every piece is a part already on the floor:
|
|
25
|
+
* RUN → `Tunnel{ mode:'mirror' }` — two guarded-bounce mirror Walls = insulation
|
|
26
|
+
* CORNER → `Tunnel{ mode:'directed' }` — wall hits are RE-EMITTED down the outgoing
|
|
27
|
+
* leg instead of reflected back; + one mirror `Wall` capping the outer face
|
|
28
|
+
* GROUND → `Wall{ collisionBehavior:'destroy', hooks:['count'] }` — the sink + tally
|
|
29
|
+
* The source is NOT part of a Trace (a wire carries no battery); feed `inPort` with
|
|
30
|
+
* whatever emitter the experiment wants. If building a Trace ever seems to need engine
|
|
31
|
+
* or provider code, STOP: it is Tunnels + a Wall.
|
|
32
|
+
*
|
|
33
|
+
* ── Layer 1: API contract ────────────────────────────────────────────────────
|
|
34
|
+
*
|
|
35
|
+
* `new Trace(universe, opts)` — builds + engine-registers the runs, junctions and
|
|
36
|
+
* ground. It does NOT add itself to a runner render list; the caller iterates
|
|
37
|
+
* `trace.largeObjects` (or `runner.setup()` auto-wires them).
|
|
38
|
+
*
|
|
39
|
+
* ORDERING PRECONDITION — `universe.init()` MUST already have run. A Trace registers
|
|
40
|
+
* LargeObjects in its constructor, and the engine throws ("EGPTUniverse.init() must be
|
|
41
|
+
* called before any simulation methods") on any registration against a fresh universe.
|
|
42
|
+
* An `FrqtlApp`'s universe is NOT init'd, so the SKETCH inits it FIRST and then
|
|
43
|
+
* constructs parts (world.init() FIRST → CONSTRUCT parts → WIRE → RUN) — see
|
|
44
|
+
* `setupTraceCircuit.js`, whose first act is the world step. Order among PARTS is
|
|
45
|
+
* irrelevant; only world-before-parts matters. This bit once already: the demo threw on
|
|
46
|
+
* every real page while its tests passed, because the test double had no ordering
|
|
47
|
+
* concept. `setup-trace-circuit.test.mjs` now enforces the precondition in its double.
|
|
48
|
+
* @param {Object} universe — an EGPTUniverse (JS or WASM compat shim).
|
|
49
|
+
* @param {Object} opts
|
|
50
|
+
* @param {Array<Array<number>>} opts.path — 2+ waypoints `[[x,y],…]` on the BORE
|
|
51
|
+
* CENTRE-LINE, px. Consecutive points must differ on exactly ONE axis (cardinal
|
|
52
|
+
* routing) — FAIL-LOUD otherwise, because a diagonal run does not conduct.
|
|
53
|
+
* @param {number} [opts.bore=3] — clear aperture, px. 3 is the recommended minimum
|
|
54
|
+
* (see Layer 2); the transport measurement is flat from bore 3 to bore 40.
|
|
55
|
+
* @param {number} [opts.wallThickness=6] — insulation thickness, px. This is the
|
|
56
|
+
* knob that actually sets the shrink floor — see Layer 2. Below 3px the mirror
|
|
57
|
+
* leaks; the required thickness RISES as the bore falls.
|
|
58
|
+
* @param {boolean} [opts.ground=false] — build the sink terminal at the outlet.
|
|
59
|
+
* @param {number} [opts.groundGap=4] — px between the outlet and the ground slab.
|
|
60
|
+
* @param {number} [opts.cornerSpread=5] — the directed junction's fan half-width in
|
|
61
|
+
* DEGREES. Tighter is better, with a shallow optimum at 5: measured corner
|
|
62
|
+
* throughput G = 0.94 (5), 0.94 (0), 0.92 (10), 0.88 (20), 0.76 (45), 0.59 (90).
|
|
63
|
+
* @param {Array} [opts.color=[90,120,200]] — RGB for the runs.
|
|
64
|
+
* @param {string} [opts.name='trace'] — identifier prefix.
|
|
65
|
+
*
|
|
66
|
+
* @property {Array<Tunnel>} segments — the mirror runs, in path order.
|
|
67
|
+
* @property {Array<Tunnel>} junctions — the directed corner cells, in path order.
|
|
68
|
+
* @property {Array<Wall>} caps — the mirror walls closing each corner's outer face.
|
|
69
|
+
* @property {Wall|null} ground — the sink terminal (`opts.ground`).
|
|
70
|
+
* @property {Array} inPort, outPort — `[x,y]` centre-line inlet / outlet.
|
|
71
|
+
* @property {Array<LargeObject>} largeObjects — every inner engine LO (render wiring).
|
|
72
|
+
*
|
|
73
|
+
* Methods:
|
|
74
|
+
* `onTick(tickData)` — the ONE per-tick call. Fans out to each junction's
|
|
75
|
+
* `applyRedirectIfActive` (the host half of directed mode) and samples the ground.
|
|
76
|
+
* SELF-GATING: a straight Trace with no corners and no ground is a no-op, so a
|
|
77
|
+
* caller never needs its own `if (hasCorners)` check.
|
|
78
|
+
* `attachToRunner(runner)` — subscribe `onTick` to the runner's per-tick fan-out.
|
|
79
|
+
* `getDelivered()` — quanta delivered to ground since construction, CALIBRATED.
|
|
80
|
+
* A destroy+count wall registers each arrival on TWO consecutive ticks (measured,
|
|
81
|
+
* deterministic), so the raw counter is halved here — one authority, so no caller
|
|
82
|
+
* has to know. Throws without a ground.
|
|
83
|
+
* `getCurrent()` — delivered per tick over the last sampled window.
|
|
84
|
+
* `dispose()` — tears down every sub-part (idempotent).
|
|
85
|
+
*
|
|
86
|
+
* ── Layer 2: physics behavior ────────────────────────────────────────────────
|
|
87
|
+
* ZERO physics of its own (SU1). Everything below is the emergent behaviour of the
|
|
88
|
+
* stock parts, MEASURED on this construction (WASM engine, 2D, fundamental dim 0,
|
|
89
|
+
* laser feed on the centre-line, steady-state current over a 250-tick window,
|
|
90
|
+
* conductance G = delivered/tick ÷ emitted/tick):
|
|
91
|
+
*
|
|
92
|
+
* CONDUCTION. A Trace run delivers essentially ALL the current it accepts:
|
|
93
|
+
* G = 1.00 ± 0.02 across seeds, against G = 0.29 ± 0.02 for the same source and
|
|
94
|
+
* ground with the run REMOVED. Leakage through the mirrors is 0.0 %. The mirrors
|
|
95
|
+
* are not "guiding" in a soft sense — nothing gets out.
|
|
96
|
+
*
|
|
97
|
+
* LENGTH IS FREE. Conduction is length-independent; the run is a lossless
|
|
98
|
+
* waveguide, so lengthening a Trace costs propagation DELAY (1 px/tick — bench M1)
|
|
99
|
+
* and nothing else. Optimise junctions, never run length.
|
|
100
|
+
*
|
|
101
|
+
* BORE. Transport is flat in bore: G = 1.01 / 1.00 / 1.01 / 1.00 / 1.00 at bore
|
|
102
|
+
* 40 / 24 / 16 / 8 / 4. Wider does not carry more current once the current is
|
|
103
|
+
* inside. (Wider does ACCEPT more at the mouth — that is a source-coupling
|
|
104
|
+
* property of the inlet, not of the conductor.)
|
|
105
|
+
*
|
|
106
|
+
* THE SHRINK FLOOR IS WALL THICKNESS, NOT BORE. Measured G by (bore, wallThickness):
|
|
107
|
+
*
|
|
108
|
+
* bore\wt | 3 6 10
|
|
109
|
+
* 3 | 1.010 1.002 0.996
|
|
110
|
+
* 2 | 0.203 1.001 1.001 ← 0.203 leaks 48 %
|
|
111
|
+
* 1 | 0.044 0.195 0.987 ← 0.987 leaks 0.1 %
|
|
112
|
+
*
|
|
113
|
+
* A bore-1 Trace conducts at G = 0.987 with zero leak PROVIDED the insulation is
|
|
114
|
+
* ≥ 10 px. The thinner the bore, the thicker the wall must be. A too-thin wall does
|
|
115
|
+
* not degrade — it goes transparent, and the fault looks like a broken conductor.
|
|
116
|
+
*
|
|
117
|
+
* PITCH (the reason it shrinks usefully). Lanes stacked one energised, the rest dark,
|
|
118
|
+
* measured lane-by-lane. Crosstalk only ever reaches the IMMEDIATELY adjacent lane —
|
|
119
|
+
* lane 2 and beyond measured EXACTLY 0.000 in every configuration tested.
|
|
120
|
+
*
|
|
121
|
+
* Each Trace owns its own insulation, so INDEPENDENT Traces stack at
|
|
122
|
+
* `bore + 2 x wallThickness` (`Trace.lanePitch()`), wall against wall:
|
|
123
|
+
*
|
|
124
|
+
* bore 3 + wall 3 → pitch 9 px, crosstalk 0.98 %
|
|
125
|
+
* bore 2 + wall 6 → pitch 14 px, crosstalk 0.00 %
|
|
126
|
+
* bore 12 + wall 6 → pitch 24 px, crosstalk 0.00 %
|
|
127
|
+
*
|
|
128
|
+
* DO NOT OVERLAP TWO TRACES' MIRRORS TO SAVE THE PITCH. Stacking Traces at
|
|
129
|
+
* `bore + wallThickness`, so each pair of neighbours' mirrors lands on exactly the
|
|
130
|
+
* same pixels, LEAKS BADLY — measured crosstalk 31.0 % (bore 3), 33.6 % (bore 2),
|
|
131
|
+
* 4.1 % (bore 12). Two coincident mirror walls are markedly WORSE than one: a
|
|
132
|
+
* SINGLE shared rail at that same 6 px pitch measures 4.1 % at bore 3, so the loss
|
|
133
|
+
* is caused by the duplication, not by the tightness. It fails silently — the
|
|
134
|
+
* energised lane still reads G = 0.98, so only the dark lane's meter shows it.
|
|
135
|
+
* FRQTL-MAINT(coincident-mirror-leak): design around it — never place two mirror
|
|
136
|
+
* Walls on the same pixels.
|
|
137
|
+
*
|
|
138
|
+
* A tighter bus is still available, but it needs a genuinely SHARED rail (one wall
|
|
139
|
+
* serving two bores), which is a different part from a row of independent Traces:
|
|
140
|
+
* a ladder of N+1 shared rails measured 4.1 % at 6 px pitch (bore 3 + wall 3),
|
|
141
|
+
* 1.2 % at 8 px (bore 2 + wall 6) and 0.03 % at 11 px (bore 1 + wall 10), with
|
|
142
|
+
* eight lanes fitting a 51 px band at G = 1.02 on the energised lane. That shared-rail
|
|
143
|
+
* bus is the natural NEXT part; a Trace deliberately does not pretend to be one.
|
|
144
|
+
*
|
|
145
|
+
* THE CORNER: TWO WORKING MECHANISMS, AND THE ONE THAT MEASURED BETTER.
|
|
146
|
+
* A specular mirror DOES turn a ballistic 1 px/tick current — that is exactly what
|
|
147
|
+
* `Wall{collisionBehavior:'bounce', bounceDirection:'reflect'}` compiles (the velocity
|
|
148
|
+
* component perpendicular to the face flips, out = 180 + 2*orientation - in), and the
|
|
149
|
+
* Collision-Lab rungs route with it: `cl1a-mirror` steers a laser off one tilted mirror,
|
|
150
|
+
* and `cl1d-mirror-route` loops a beam around a CLOSED ten-mirror {10/3} star forever.
|
|
151
|
+
* Both mechanisms were built here and measured on the SAME rig, bore 12, settled, 2 seeds,
|
|
152
|
+
* best configuration of each (51 mirror configurations swept):
|
|
153
|
+
*
|
|
154
|
+
* reflect-Wall mirror elbow → G = 0.738 +- 0.011
|
|
155
|
+
* directed-Tunnel junction → G = 0.951 +- 0.005
|
|
156
|
+
*
|
|
157
|
+
* The directed junction is what a Trace uses because it measured 1.3x better in a
|
|
158
|
+
* BORE-TO-BORE corner — NOT because a mirror cannot turn current. The gap (0.21) is ~20x
|
|
159
|
+
* the seed spread, so the choice is decided, but the REASON is geometric: a mirror
|
|
160
|
+
* reflects specularly, so it PRESERVES the beam's angular spread and every off-axis ray
|
|
161
|
+
* leaves off-axis and misses the narrow outgoing mouth; the directed junction RE-AIMS
|
|
162
|
+
* every hit into a tight fan down the outgoing leg, which is what a bore-to-bore corner
|
|
163
|
+
* needs. In OPEN SPACE — cl1d's star, where the next "mouth" is a slab many beam-widths
|
|
164
|
+
* wide — the mirror is the right part and a directed cell would be pointless. Pick by
|
|
165
|
+
* geometry, not by a blanket rule.
|
|
166
|
+
*
|
|
167
|
+
* Mirror-elbow tuning, if you build one anyway: the slab must be TALL/THIN (Wall infers
|
|
168
|
+
* the flip axis from the ASPECT — a wide/flat slab flips the wrong component) and the cell
|
|
169
|
+
* TIGHT. G = 0.73 (thickness 2), 0.67 (3), 0.68 (4), 0.34 (10), 0.17 (20); a cell much
|
|
170
|
+
* larger than the bore is worse (0.08), because a bigger room with the same small door
|
|
171
|
+
* loses more.
|
|
172
|
+
*
|
|
173
|
+
* Directed-junction tuning, by fan half-width: G = 0.951 (5 deg), 0.94 (0), 0.92 (10),
|
|
174
|
+
* 0.88 (20), 0.76 (45), 0.59 (90) — hence `cornerSpread: 5`.
|
|
175
|
+
*
|
|
176
|
+
* WHAT A CORNER ACTUALLY COSTS: DELAY AND STORED POPULATION, NOT CURRENT. Measured on the
|
|
177
|
+
* shipped Trace geometry, fully settled (2600 ticks), 2 seeds, spread +-0.011:
|
|
178
|
+
*
|
|
179
|
+
* route G in-flight population
|
|
180
|
+
* straight 0.997 4 035
|
|
181
|
+
* one corner 0.990 4 576
|
|
182
|
+
* two corners 0.978 5 731
|
|
183
|
+
*
|
|
184
|
+
* At steady state a corner gives back nearly everything: ~1 % per corner, which is only
|
|
185
|
+
* just above the seed spread. What visibly grows is the POPULATION HELD IN THE ROUTE — the
|
|
186
|
+
* junction cells store quanta while they find the exit. This is the same lesson as
|
|
187
|
+
* "length is free": the cost is transit time, not lost current. Read a corner too early
|
|
188
|
+
* and it looks far lossier than it is (the under-settled reads of this same geometry gave
|
|
189
|
+
* 0.94 / 0.87, and QA independently hit a 0-conductance read for the same reason) — always
|
|
190
|
+
* settle past propagation delay PLUS the averaging window before quoting a corner.
|
|
191
|
+
*
|
|
192
|
+
* CORNERS ARE BORE-LIMITED — ROUTE TURNS WIDE, RUN NARROW. Unlike a straight run
|
|
193
|
+
* (flat in bore), a turn's throughput collapses as the bore narrows, because the
|
|
194
|
+
* re-emitted current has to find the outgoing mouth and that acceptance is an
|
|
195
|
+
* aperture problem:
|
|
196
|
+
*
|
|
197
|
+
* bore 12, flare 12 → G = 0.951
|
|
198
|
+
* bore 3, flare 12 → G = 0.31-0.44 (unflared, cell = bore: 0.016 — a DEAD corner)
|
|
199
|
+
* bore 1, flare 12 → G = 0.000
|
|
200
|
+
*
|
|
201
|
+
* The same limit hits the mirror elbow, and harder — it has no re-aiming step at all, so
|
|
202
|
+
* at bore 3 it measured 0.047 where the directed junction managed 0.31-0.44.
|
|
203
|
+
*
|
|
204
|
+
* Hence `cornerSize` defaults to `max(bore, 12)`: a junction always flares to at
|
|
205
|
+
* least 12 px even when the runs are 1-3 px, which is the difference between a
|
|
206
|
+
* 19x-better corner and a dead one. The design rule that follows: put turns on wide
|
|
207
|
+
* bore and neck down to bore 1-3 only for the straight parallel stretches. A dense
|
|
208
|
+
* bus should be straight; the routing should happen where there is room.
|
|
209
|
+
*
|
|
210
|
+
* THE ONE SWITCH THAT CHANGES THE COMPONENT: `universe.withInterQuantumCollisions`.
|
|
211
|
+
* Off (the default here) the Trace is a lossless waveguide with no ampacity limit.
|
|
212
|
+
* On, it becomes a current-limited device — that is RESISTANCE, and it is a
|
|
213
|
+
* different component with a different datasheet.
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
import { Tunnel as _Tunnel } from './Tunnel.js';
|
|
217
|
+
import { Wall as _Wall } from './Wall.js';
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A `destroy` wall carrying the `count` hook registers each arrival on TWO
|
|
224
|
+
* consecutive ticks (measured, deterministic: one quantum → 2 collected frames,
|
|
225
|
+
* on the tick the kill op fires and the tick after, before cleanup). Every
|
|
226
|
+
* delivered-count read divides by this ONE constant so no caller has to know.
|
|
227
|
+
* FRQTL-MAINT(destroy-count-double-registers) — calibrated, not litigated.
|
|
228
|
+
*/
|
|
229
|
+
var GROUND_COUNTS_PER_ARRIVAL = 2;
|
|
230
|
+
|
|
231
|
+
/** Direction of travel from A to B as a compass unit vector + degrees. */
|
|
232
|
+
function _dir(a, b) {
|
|
233
|
+
var dx = b[0] - a[0], dy = b[1] - a[1];
|
|
234
|
+
if ((dx !== 0) === (dy !== 0)) {
|
|
235
|
+
throw new Error('[Trace] path leg ' + JSON.stringify(a) + '->' + JSON.stringify(b) +
|
|
236
|
+
' is not cardinal. A Trace routes on the x/y grid only: a diagonal corridor was ' +
|
|
237
|
+
'measured NOT to conduct (G ~ 0.04 vs 1.01 on-axis). Insert a waypoint. (FAIL-LOUD.)');
|
|
238
|
+
}
|
|
239
|
+
if (dx > 0) { return { ux: 1, uy: 0, deg: 0, axis: 'x' }; }
|
|
240
|
+
if (dx < 0) { return { ux: -1, uy: 0, deg: 180, axis: 'x' }; }
|
|
241
|
+
if (dy > 0) { return { ux: 0, uy: 1, deg: 90, axis: 'y' }; }
|
|
242
|
+
return { ux: 0, uy: -1, deg: 270, axis: 'y' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function Trace(universe, opts) {
|
|
246
|
+
opts = opts || {};
|
|
247
|
+
this.universe = universe;
|
|
248
|
+
this._schemaKind = 'Trace';
|
|
249
|
+
|
|
250
|
+
var path = opts.path;
|
|
251
|
+
if (!Array.isArray(path) || path.length < 2) {
|
|
252
|
+
throw new Error('[Trace] opts.path must be an array of 2+ [x,y] waypoints. (FAIL-LOUD.)');
|
|
253
|
+
}
|
|
254
|
+
var bore = (opts.bore !== undefined && opts.bore !== null) ? opts.bore : 3;
|
|
255
|
+
var wallThickness = (opts.wallThickness !== undefined && opts.wallThickness !== null)
|
|
256
|
+
? opts.wallThickness : 6;
|
|
257
|
+
if (!(bore > 0) || !(wallThickness > 0)) {
|
|
258
|
+
throw new Error('[Trace] bore and wallThickness must both be positive. (FAIL-LOUD.)');
|
|
259
|
+
}
|
|
260
|
+
var cornerSpread = (typeof opts.cornerSpread === 'number') ? opts.cornerSpread : 5;
|
|
261
|
+
var color = opts.color || [90, 120, 200];
|
|
262
|
+
var name = opts.name || 'trace';
|
|
263
|
+
var groundGap = (opts.groundGap !== undefined) ? opts.groundGap : 4;
|
|
264
|
+
// The junction is a SQUARE cell of this side, centred on the waypoint — a FLARE.
|
|
265
|
+
// Independent of the run bore, and that independence is load-bearing: a junction
|
|
266
|
+
// sized to a narrow bore does not turn the current at all (bore 3 with a 3px cell
|
|
267
|
+
// measured G = 0.016 — a dead corner; widening the cell along the outgoing leg
|
|
268
|
+
// alone did not help either, G = 0.014). A corner needs ROOM in the TRANSVERSE
|
|
269
|
+
// direction, so the cell flares out to `cornerSize` and the runs neck back down.
|
|
270
|
+
var cornerSize = (typeof opts.cornerSize === 'number') ? opts.cornerSize : Math.max(bore, 12);
|
|
271
|
+
|
|
272
|
+
this.path = path.map(function (p) { return [p[0], p[1]]; });
|
|
273
|
+
this.bore = bore;
|
|
274
|
+
this.wallThickness = wallThickness;
|
|
275
|
+
this.cornerSpread = cornerSpread;
|
|
276
|
+
this.cornerSize = cornerSize;
|
|
277
|
+
this.color = color;
|
|
278
|
+
this.name = name;
|
|
279
|
+
|
|
280
|
+
var TunnelCls = _Tunnel;
|
|
281
|
+
var WallCls = _Wall;
|
|
282
|
+
|
|
283
|
+
this.segments = [];
|
|
284
|
+
this.junctions = [];
|
|
285
|
+
this.caps = [];
|
|
286
|
+
this.ground = null;
|
|
287
|
+
this.largeObjects = [];
|
|
288
|
+
this.walls = [];
|
|
289
|
+
|
|
290
|
+
var half = bore / 2;
|
|
291
|
+
var i, d, dPrev;
|
|
292
|
+
|
|
293
|
+
// ── RUNS: one mirror Tunnel per leg, TRIMMED back by half a bore at every
|
|
294
|
+
// interior waypoint so the junction cell owns the corner square. ──────
|
|
295
|
+
for (i = 0; i < path.length - 1; i++) {
|
|
296
|
+
d = _dir(path[i], path[i + 1]);
|
|
297
|
+
// The junction owns a square of side `cornerSize` centred on the waypoint, so a
|
|
298
|
+
// leg stops (or starts) half a cell away from every interior waypoint.
|
|
299
|
+
var trim = cornerSize / 2;
|
|
300
|
+
var startTrim = (i === 0) ? 0 : trim; // leaving a corner
|
|
301
|
+
var endTrim = (i === path.length - 2) ? 0 : trim; // entering a corner
|
|
302
|
+
var ax = path[i][0] + d.ux * startTrim;
|
|
303
|
+
var ay = path[i][1] + d.uy * startTrim;
|
|
304
|
+
var bx = path[i + 1][0] - d.ux * endTrim;
|
|
305
|
+
var by = path[i + 1][1] - d.uy * endTrim;
|
|
306
|
+
// SIGNED along the direction of travel — an over-trimmed leg leaves a NEGATIVE
|
|
307
|
+
// remainder, and `Math.abs` would silently turn that into a short but "valid"
|
|
308
|
+
// run pointing the wrong way.
|
|
309
|
+
var runLen = (bx - ax) * d.ux + (by - ay) * d.uy;
|
|
310
|
+
if (runLen <= 0) {
|
|
311
|
+
throw new Error('[Trace] leg ' + i + ' is shorter than the corner cells it must join ' +
|
|
312
|
+
'(length ' + runLen + ' after trimming for bore ' + bore + '). Space the waypoints ' +
|
|
313
|
+
'at least one bore apart. (FAIL-LOUD.)');
|
|
314
|
+
}
|
|
315
|
+
var seg = new TunnelCls(universe, (d.axis === 'x')
|
|
316
|
+
? { left: Math.min(ax, bx), top: ay - half, length: runLen, bore: bore,
|
|
317
|
+
wallThickness: wallThickness, axis: 'x', mode: 'mirror',
|
|
318
|
+
name: name + '_run' + i, color: color }
|
|
319
|
+
: { left: ax - half, top: Math.min(ay, by), length: runLen, bore: bore,
|
|
320
|
+
wallThickness: wallThickness, axis: 'y', mode: 'mirror',
|
|
321
|
+
name: name + '_run' + i, color: color });
|
|
322
|
+
this.segments.push(seg);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── CORNERS: a short DIRECTED cell at each interior waypoint, aimed down the
|
|
326
|
+
// outgoing leg, plus one mirror cap closing the cell's outer face. ─────
|
|
327
|
+
for (i = 1; i < path.length - 1; i++) {
|
|
328
|
+
dPrev = _dir(path[i - 1], path[i]);
|
|
329
|
+
d = _dir(path[i], path[i + 1]);
|
|
330
|
+
if (d.axis === dPrev.axis) {
|
|
331
|
+
throw new Error('[Trace] waypoint ' + i + ' does not turn (both legs run on the ' +
|
|
332
|
+
d.axis + ' axis). Merge the collinear legs. (FAIL-LOUD.)');
|
|
333
|
+
}
|
|
334
|
+
var cx = path[i][0], cy = path[i][1];
|
|
335
|
+
// A square cell of side `cornerSize` centred on the waypoint. Its Tunnel runs
|
|
336
|
+
// along the OUTGOING axis, so its two directed walls face the INCOMING beam —
|
|
337
|
+
// the pair the current actually hits — and the re-emission aims every hit down
|
|
338
|
+
// the new leg.
|
|
339
|
+
var chalf = cornerSize / 2;
|
|
340
|
+
var cell = new TunnelCls(universe, {
|
|
341
|
+
left: cx - chalf, top: cy - chalf, length: cornerSize, bore: cornerSize,
|
|
342
|
+
wallThickness: wallThickness, axis: d.axis,
|
|
343
|
+
mode: 'directed', directedAngle: d.deg, redirectSpread: cornerSpread,
|
|
344
|
+
name: name + '_corner' + i, color: color
|
|
345
|
+
});
|
|
346
|
+
this.junctions.push(cell);
|
|
347
|
+
|
|
348
|
+
// Cap the face OPPOSITE the outlet (the -outgoing side) so nothing escapes
|
|
349
|
+
// backwards out of the junction. Prefix picks the reflect axis (Wall's own
|
|
350
|
+
// convention): a wall normal to y is TOP_, normal to x is LEFT_.
|
|
351
|
+
var span = cornerSize + 2 * wallThickness;
|
|
352
|
+
// Flush against the cell on the -outgoing side.
|
|
353
|
+
var capRect = (d.axis === 'y')
|
|
354
|
+
? [cx - chalf - wallThickness, (d.uy > 0 ? cy - chalf - wallThickness : cy + chalf), span, wallThickness]
|
|
355
|
+
: [(d.ux > 0 ? cx - chalf - wallThickness : cx + chalf), cy - chalf - wallThickness, wallThickness, span];
|
|
356
|
+
this.caps.push(new WallCls(universe, {
|
|
357
|
+
rect: capRect,
|
|
358
|
+
collisionBehavior: 'bounce', bounceDirection: 'reflect',
|
|
359
|
+
name: (d.axis === 'y' ? 'TOP_' : 'LEFT_') + name + '_cap' + i,
|
|
360
|
+
color: color
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── GROUND: the sink terminal across the outlet. ─────────────────────────
|
|
365
|
+
this.inPort = [path[0][0], path[0][1]];
|
|
366
|
+
this.outPort = [path[path.length - 1][0], path[path.length - 1][1]];
|
|
367
|
+
var dOut = _dir(path[path.length - 2], path[path.length - 1]);
|
|
368
|
+
this.outDirection = dOut.deg;
|
|
369
|
+
if (opts.ground) {
|
|
370
|
+
var gspan = bore + 2 * wallThickness;
|
|
371
|
+
var gx = this.outPort[0] + dOut.ux * groundGap;
|
|
372
|
+
var gy = this.outPort[1] + dOut.uy * groundGap;
|
|
373
|
+
this.ground = new WallCls(universe, {
|
|
374
|
+
rect: (dOut.axis === 'x')
|
|
375
|
+
? [dOut.ux > 0 ? gx : gx - 4, gy - gspan / 2, 4, gspan]
|
|
376
|
+
: [gx - gspan / 2, dOut.uy > 0 ? gy : gy - 4, gspan, 4],
|
|
377
|
+
collisionBehavior: 'destroy', hooks: ['count'],
|
|
378
|
+
name: name + '_ground', color: [60, 60, 255]
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ── Roll up every sub-part for render wiring. ────────────────────────────
|
|
383
|
+
var parts = [];
|
|
384
|
+
this.segments.forEach(function (s) { parts = parts.concat(s.walls); });
|
|
385
|
+
this.junctions.forEach(function (j) { parts = parts.concat(j.walls); });
|
|
386
|
+
parts = parts.concat(this.caps);
|
|
387
|
+
if (this.ground) { parts.push(this.ground); }
|
|
388
|
+
this.walls = parts;
|
|
389
|
+
var self = this;
|
|
390
|
+
parts.forEach(function (w) {
|
|
391
|
+
if (w && w.lo) {
|
|
392
|
+
self.largeObjects.push(w.lo);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// THE ONE UP-LINK: the Trace stamps exactly what IT composed — its run and corner
|
|
397
|
+
// Tunnels (each stamps its own plates), its caps and its ground.
|
|
398
|
+
this.segments.concat(this.junctions, this.caps, this.ground ? [this.ground] : []).forEach(function (p) {
|
|
399
|
+
if (p) { p._owner = self; }
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
this._rawDelivered = 0;
|
|
403
|
+
this._window = [];
|
|
404
|
+
this._unsub = null;
|
|
405
|
+
this._disposed = false;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* THE ONE per-tick call. Self-gating: drives every directed junction's host-side
|
|
410
|
+
* redirect and samples the ground counter. A straight, ungrounded Trace is a no-op
|
|
411
|
+
* here, so a caller never needs its own `if (hasCorners)` guard — forgetting that
|
|
412
|
+
* guard is the exact bug `Tunnel.applyRedirectIfActive` exists to make impossible.
|
|
413
|
+
* @param {Object} tickData — this tick's TickData (from `universe.doTick()`).
|
|
414
|
+
*/
|
|
415
|
+
Trace.prototype.onTick = function (tickData) {
|
|
416
|
+
for (var i = 0; i < this.junctions.length; i++) {
|
|
417
|
+
this.junctions[i].applyRedirectIfActive(tickData);
|
|
418
|
+
}
|
|
419
|
+
if (this.ground) {
|
|
420
|
+
var raw = this.ground.getCollisionHistogram(tickData).total;
|
|
421
|
+
this._window.push(raw);
|
|
422
|
+
if (this._window.length > 251) { this._window.shift(); }
|
|
423
|
+
this._rawDelivered = raw;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/** Subscribe `onTick` to a runner's per-tick fan-out. Idempotent. */
|
|
428
|
+
Trace.prototype.attachToRunner = function (runner) {
|
|
429
|
+
if (this._unsub || !runner || typeof runner.addTickListener !== 'function') { return this._unsub || null; }
|
|
430
|
+
var self = this;
|
|
431
|
+
this._unsub = runner.addTickListener(function (td) { self.onTick(td); });
|
|
432
|
+
return this._unsub;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Quanta delivered to ground since construction. CALIBRATED — see
|
|
437
|
+
* GROUND_COUNTS_PER_ARRIVAL. Throws on an ungrounded Trace rather than reporting 0.
|
|
438
|
+
*/
|
|
439
|
+
Trace.prototype.getDelivered = function () {
|
|
440
|
+
if (!this.ground) {
|
|
441
|
+
throw new Error('[Trace.getDelivered] this Trace has no ground terminal ' +
|
|
442
|
+
'(construct with `ground:true`). (FAIL-LOUD.)');
|
|
443
|
+
}
|
|
444
|
+
return this._rawDelivered / GROUND_COUNTS_PER_ARRIVAL;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
/** Delivered quanta per tick over the sampled window (0 until the window fills). */
|
|
448
|
+
Trace.prototype.getCurrent = function () {
|
|
449
|
+
if (this._window.length < 2) { return 0; }
|
|
450
|
+
var span = this._window.length - 1;
|
|
451
|
+
return ((this._window[span] - this._window[0]) / GROUND_COUNTS_PER_ARRIVAL) / span;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
/** Full teardown; delegates to each sub-part's own dispose. Idempotent. */
|
|
455
|
+
Trace.prototype.dispose = function () {
|
|
456
|
+
if (this._disposed) { return; }
|
|
457
|
+
this._disposed = true;
|
|
458
|
+
if (this._unsub) { this._unsub(); this._unsub = null; }
|
|
459
|
+
var all = this.segments.concat(this.junctions);
|
|
460
|
+
all.forEach(function (p) { if (p && typeof p.dispose === 'function') { p.dispose(); } });
|
|
461
|
+
this.caps.forEach(function (w) { if (w && typeof w.dispose === 'function') { w.dispose(); } });
|
|
462
|
+
if (this.ground && typeof this.ground.dispose === 'function') { this.ground.dispose(); }
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Centre-to-centre spacing for INDEPENDENT parallel Traces: each Trace carries its
|
|
467
|
+
* own two mirrors, so neighbours sit wall-against-wall. Do NOT tighten this to
|
|
468
|
+
* `bore + wallThickness` — that overlaps the two mirrors and leaks (see Layer 2).
|
|
469
|
+
* @param {number} bore @param {number} wallThickness @returns {number} px
|
|
470
|
+
*/
|
|
471
|
+
Trace.lanePitch = function (bore, wallThickness) {
|
|
472
|
+
return bore + 2 * wallThickness;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The measured design rules, as DATA — so a layout tool (or a gate) can pick a
|
|
477
|
+
* geometry without re-running the bench. `pitch` is `Trace.lanePitch()` for
|
|
478
|
+
* independent Traces; `crosstalk` is the fraction of the energised lane's current
|
|
479
|
+
* that appears in the immediately adjacent lane at that pitch (lanes 2+ measured
|
|
480
|
+
* exactly zero in every configuration).
|
|
481
|
+
*/
|
|
482
|
+
Trace.GEOMETRIES = [
|
|
483
|
+
{ bore: 3, wallThickness: 3, pitch: 9, crosstalk: 0.0098, note: 'densest independent lane' },
|
|
484
|
+
{ bore: 2, wallThickness: 6, pitch: 14, crosstalk: 0.0, note: 'narrow bore, thick insulation' },
|
|
485
|
+
{ bore: 1, wallThickness: 10, pitch: 21, crosstalk: 0.0, note: 'smallest bore that conducts' },
|
|
486
|
+
{ bore: 12, wallThickness: 6, pitch: 24, crosstalk: 0.0, note: 'roomy — the only bore that TURNS well' }
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
/** Measured throughput of one right-angle junction, by `cornerSpread` (degrees). */
|
|
490
|
+
Trace.CORNER_THROUGHPUT = { 0: 0.935, 5: 0.951, 10: 0.920, 20: 0.881, 45: 0.760, 90: 0.590 };
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The two corner MECHANISMS that were built and measured, best-of-sweep at bore 12, so a
|
|
494
|
+
* later part can pick with the numbers instead of re-deriving them. A specular
|
|
495
|
+
* `Wall{bounce/reflect}` elbow DOES turn the current (the Collision-Lab mirror rungs route
|
|
496
|
+
* with it); it simply measured lower in a bore-to-bore corner, where preserving the beam's
|
|
497
|
+
* angular spread is a liability rather than a feature.
|
|
498
|
+
*/
|
|
499
|
+
Trace.CORNER_MECHANISMS = {
|
|
500
|
+
mirrorElbow: { G: 0.738, sd: 0.011, part: "Wall{collisionBehavior:'bounce', bounceDirection:'reflect'}" },
|
|
501
|
+
directedJunction: { G: 0.951, sd: 0.005, part: "Tunnel{mode:'directed'}" }
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
Trace.GROUND_COUNTS_PER_ARRIVAL = GROUND_COUNTS_PER_ARRIVAL;
|
|
505
|
+
|
|
506
|
+
// ── Export ───────────────────────────────────────────────────────────────
|
|
507
|
+
export { Trace };
|
|
508
|
+
|