···1+"use strict";
2+3+// Transmission line physics — pure functions, no DOM or canvas dependencies.
4+//
5+// Spatial convention: z ∈ [0, 1] maps to [0, ℓ].
6+// Time convention: tNorm is dimensionless time in units of τ_d (one-way delay).
7+// A wave launched at tNorm=launch reaches the far end at tNorm=launch+1.
8+//
9+// Future extension points:
10+// • Rise time (RC/finite bandwidth): model.riseTimeTau > 0 means each wave front has a
11+// smooth leading edge V = V_final * (1 − exp(−Δt / riseTimeTau)) rather than a hard step.
12+// Δt for a rightward wave at position z is (tNorm − launch − z); for leftward, (tNorm − launch − (1−z)).
13+// When implemented, totalSegmentsForWaves() in render.js would be replaced by a sampled
14+// continuous V(z) function evaluated per-pixel.
15+//
16+// • Multi-segment lines: replace single Z0 with an array of segments [{Z0, length}].
17+// Each impedance boundary generates both a reflected wave (Γ) and a transmitted wave (T = 1+Γ).
18+// buildBounceSeries() would be replaced by a segment-aware lattice diagram solver.
19+20+const TLPhysics = (() => {
21+ const BOUNCE_EPS = 1e-6;
22+ const MAX_BOUNCES = 5000; // safety cap; ordinary use stays well under 50
23+24+ // ---- reflection coefficients and initial voltage step ----
25+ // Returns { V1, gL, gS }
26+ function computeWaveParams(model) {
27+ const { Vg, Rg, Z0, RL } = model;
28+ const V1 = Vg * Z0 / (Rg + Z0); // voltage of the first incident wave
29+ const gL = (RL - Z0) / (RL + Z0); // reflection coefficient at load
30+ const gS = (Rg - Z0) / (Rg + Z0); // reflection coefficient at source
31+ return { V1, gL, gS };
32+ }
33+34+ // ---- bounce series ----
35+ // Builds the sequence of travelling waves produced by successive reflections.
36+ // Returns {
37+ // series: [{n, A, dir, launch}] — all generated waves in order
38+ // srcEvents: [{t, dV}] — cumulative voltage steps at z=0 (VS)
39+ // loadEvents: [{t, dV}] — cumulative voltage steps at z=ℓ (VL)
40+ // gSEffective, tolPct, ampTol, tEnd
41+ // }
42+ function buildBounceSeries(model, waves) {
43+ const gS = model.singleBounce ? 0 : waves.gS;
44+ const tolPct = Number.isFinite(model.reflectTol) ? model.reflectTol : 1;
45+ const ampTol = Math.max(BOUNCE_EPS, Math.abs(waves.V1) * (tolPct / 100));
46+47+ // wave n=1: first incident, rightward, launched at tNorm=0
48+ const series = [{ n: 1, A: waves.V1, dir: +1, launch: 0 }];
49+50+ let A = waves.V1;
51+ let launch = 1; // each successive wave launches one τ_d later
52+ let reflectAtLoad = true; // V2 is the first reflection, from the load
53+54+ while (series.length < MAX_BOUNCES) {
55+ const g = reflectAtLoad ? waves.gL : gS;
56+ const nextA = A * g;
57+ if (Math.abs(nextA) <= ampTol) break;
58+ series.push({ n: series.length + 1, A: nextA, dir: reflectAtLoad ? -1 : +1, launch });
59+ A = nextA;
60+ launch++;
61+ reflectAtLoad = !reflectAtLoad;
62+ }
63+64+ // Node voltage histories (cumulative step events).
65+ // VS: V source-side = sum of (1+Γ_S)·V_k for every left-going wave that arrives at source,
66+ // plus V1 at t=0 (the initial step seen at z=0 when the switch closes).
67+ // VL: V load-side = sum of (1+Γ_L)·V_k for every right-going wave that arrives at load.
68+ const srcEvents = [{ t: 0, dV: waves.V1 }];
69+ const loadEvents = [];
70+ for (const w of series) {
71+ const arrive = w.launch + 1;
72+ if (w.dir > 0) {
73+ loadEvents.push({ t: arrive, dV: (1 + waves.gL) * w.A });
74+ } else {
75+ srcEvents.push({ t: arrive, dV: (1 + gS) * w.A });
76+ }
77+ }
78+79+ return {
80+ series,
81+ srcEvents,
82+ loadEvents,
83+ gSEffective: gS,
84+ tolPct,
85+ ampTol,
86+ tEnd: series.length > 0 ? series[series.length - 1].launch + 1 : 2,
87+ };
88+ }
89+90+ // ---- time query: node voltage ----
91+ // Sum all voltage step events that have occurred by time tn.
92+ function sumEventsAtTime(events, tn) {
93+ const eps = 1e-9;
94+ let v = 0;
95+ for (const e of events) {
96+ if (tn >= e.t - eps) v += e.dV;
97+ }
98+ return v;
99+ }
100+101+ // ---- dynamic state at a given tNorm ----
102+ // Returns the set of waves currently propagating and the node voltages.
103+ // Each wave in launchedWaves gets two extra fields:
104+ // u: time elapsed since launch (tNorm − launch)
105+ // front: current position of the leading edge, ∈ [0,1]
106+ function computeDynamicState(tn, bounce) {
107+ const { clamp } = TLUtils;
108+ const launchedWaves = [];
109+ const activeWaves = []; // waves whose front is still in transit
110+111+ for (const w of bounce.series) {
112+ const u = tn - w.launch;
113+ if (u < 0) continue;
114+ const front = w.dir > 0 ? clamp(u, 0, 1) : clamp(1 - u, 0, 1);
115+ const ww = { ...w, u, front };
116+ launchedWaves.push(ww);
117+ if (u < 1) activeWaves.push(ww);
118+ }
119+120+ return {
121+ launchedWaves,
122+ activeWaves,
123+ VS: sumEventsAtTime(bounce.srcEvents, tn),
124+ VL: sumEventsAtTime(bounce.loadEvents, tn),
125+ };
126+ }
127+128+ return {
129+ BOUNCE_EPS,
130+ MAX_BOUNCES,
131+ computeWaveParams,
132+ buildBounceSeries,
133+ sumEventsAtTime,
134+ computeDynamicState,
135+ };
136+})();