···11+"use strict";
22+33+// Transmission line physics — pure functions, no DOM or canvas dependencies.
44+//
55+// Spatial convention: z ∈ [0, 1] maps to [0, ℓ].
66+// Time convention: tNorm is dimensionless time in units of τ_d (one-way delay).
77+// A wave launched at tNorm=launch reaches the far end at tNorm=launch+1.
88+//
99+// Future extension points:
1010+// • Rise time (RC/finite bandwidth): model.riseTimeTau > 0 means each wave front has a
1111+// smooth leading edge V = V_final * (1 − exp(−Δt / riseTimeTau)) rather than a hard step.
1212+// Δt for a rightward wave at position z is (tNorm − launch − z); for leftward, (tNorm − launch − (1−z)).
1313+// When implemented, totalSegmentsForWaves() in render.js would be replaced by a sampled
1414+// continuous V(z) function evaluated per-pixel.
1515+//
1616+// • Multi-segment lines: replace single Z0 with an array of segments [{Z0, length}].
1717+// Each impedance boundary generates both a reflected wave (Γ) and a transmitted wave (T = 1+Γ).
1818+// buildBounceSeries() would be replaced by a segment-aware lattice diagram solver.
1919+2020+const TLPhysics = (() => {
2121+ const BOUNCE_EPS = 1e-6;
2222+ const MAX_BOUNCES = 5000; // safety cap; ordinary use stays well under 50
2323+2424+ // ---- reflection coefficients and initial voltage step ----
2525+ // Returns { V1, gL, gS }
2626+ function computeWaveParams(model) {
2727+ const { Vg, Rg, Z0, RL } = model;
2828+ const V1 = Vg * Z0 / (Rg + Z0); // voltage of the first incident wave
2929+ const gL = (RL - Z0) / (RL + Z0); // reflection coefficient at load
3030+ const gS = (Rg - Z0) / (Rg + Z0); // reflection coefficient at source
3131+ return { V1, gL, gS };
3232+ }
3333+3434+ // ---- bounce series ----
3535+ // Builds the sequence of travelling waves produced by successive reflections.
3636+ // Returns {
3737+ // series: [{n, A, dir, launch}] — all generated waves in order
3838+ // srcEvents: [{t, dV}] — cumulative voltage steps at z=0 (VS)
3939+ // loadEvents: [{t, dV}] — cumulative voltage steps at z=ℓ (VL)
4040+ // gSEffective, tolPct, ampTol, tEnd
4141+ // }
4242+ function buildBounceSeries(model, waves) {
4343+ const gS = model.singleBounce ? 0 : waves.gS;
4444+ const tolPct = Number.isFinite(model.reflectTol) ? model.reflectTol : 1;
4545+ const ampTol = Math.max(BOUNCE_EPS, Math.abs(waves.V1) * (tolPct / 100));
4646+4747+ // wave n=1: first incident, rightward, launched at tNorm=0
4848+ const series = [{ n: 1, A: waves.V1, dir: +1, launch: 0 }];
4949+5050+ let A = waves.V1;
5151+ let launch = 1; // each successive wave launches one τ_d later
5252+ let reflectAtLoad = true; // V2 is the first reflection, from the load
5353+5454+ while (series.length < MAX_BOUNCES) {
5555+ const g = reflectAtLoad ? waves.gL : gS;
5656+ const nextA = A * g;
5757+ if (Math.abs(nextA) <= ampTol) break;
5858+ series.push({ n: series.length + 1, A: nextA, dir: reflectAtLoad ? -1 : +1, launch });
5959+ A = nextA;
6060+ launch++;
6161+ reflectAtLoad = !reflectAtLoad;
6262+ }
6363+6464+ // Node voltage histories (cumulative step events).
6565+ // VS: V source-side = sum of (1+Γ_S)·V_k for every left-going wave that arrives at source,
6666+ // plus V1 at t=0 (the initial step seen at z=0 when the switch closes).
6767+ // VL: V load-side = sum of (1+Γ_L)·V_k for every right-going wave that arrives at load.
6868+ const srcEvents = [{ t: 0, dV: waves.V1 }];
6969+ const loadEvents = [];
7070+ for (const w of series) {
7171+ const arrive = w.launch + 1;
7272+ if (w.dir > 0) {
7373+ loadEvents.push({ t: arrive, dV: (1 + waves.gL) * w.A });
7474+ } else {
7575+ srcEvents.push({ t: arrive, dV: (1 + gS) * w.A });
7676+ }
7777+ }
7878+7979+ return {
8080+ series,
8181+ srcEvents,
8282+ loadEvents,
8383+ gSEffective: gS,
8484+ tolPct,
8585+ ampTol,
8686+ tEnd: series.length > 0 ? series[series.length - 1].launch + 1 : 2,
8787+ };
8888+ }
8989+9090+ // ---- time query: node voltage ----
9191+ // Sum all voltage step events that have occurred by time tn.
9292+ function sumEventsAtTime(events, tn) {
9393+ const eps = 1e-9;
9494+ let v = 0;
9595+ for (const e of events) {
9696+ if (tn >= e.t - eps) v += e.dV;
9797+ }
9898+ return v;
9999+ }
100100+101101+ // ---- dynamic state at a given tNorm ----
102102+ // Returns the set of waves currently propagating and the node voltages.
103103+ // Each wave in launchedWaves gets two extra fields:
104104+ // u: time elapsed since launch (tNorm − launch)
105105+ // front: current position of the leading edge, ∈ [0,1]
106106+ function computeDynamicState(tn, bounce) {
107107+ const { clamp } = TLUtils;
108108+ const launchedWaves = [];
109109+ const activeWaves = []; // waves whose front is still in transit
110110+111111+ for (const w of bounce.series) {
112112+ const u = tn - w.launch;
113113+ if (u < 0) continue;
114114+ const front = w.dir > 0 ? clamp(u, 0, 1) : clamp(1 - u, 0, 1);
115115+ const ww = { ...w, u, front };
116116+ launchedWaves.push(ww);
117117+ if (u < 1) activeWaves.push(ww);
118118+ }
119119+120120+ return {
121121+ launchedWaves,
122122+ activeWaves,
123123+ VS: sumEventsAtTime(bounce.srcEvents, tn),
124124+ VL: sumEventsAtTime(bounce.loadEvents, tn),
125125+ };
126126+ }
127127+128128+ return {
129129+ BOUNCE_EPS,
130130+ MAX_BOUNCES,
131131+ computeWaveParams,
132132+ buildBounceSeries,
133133+ sumEventsAtTime,
134134+ computeDynamicState,
135135+ };
136136+})();