···11+# Plan: Shunt Lumped Elements (caps branch)
22+33+## Goal
44+55+Extend the simulator to support arbitrary sequences of transmission line segments and
66+shunt lumped elements (R, L, C, short, open). The topology is a flat ordered list of
77+blocks — not a strict alternating pattern. Any arrangement is valid:
88+99+```
1010+[TL, TL, TL, R, C, TL, R, TL, C, TL, TL]
1111+```
1212+1313+Multiple T-lines in a row share a pure impedance-mismatch boundary (no shunt element).
1414+Multiple shunt elements in a row share the same electrical node (their admittances add).
1515+The sequence ends with a terminal condition (R / C / L / open / short) replacing the
1616+current single `RL` parameter.
1717+1818+Example circuit this covers (KiCad netlist pattern):
1919+2020+```spice
2121+Rs1 S A 40
2222+OT1 A GND B GND __T1
2323+C1 B GND 2p
2424+OT2 B GND C GND __T2
2525+```
2626+2727+Rendered as a block sequence: `[TL(T1), C(2p), TL(T2)]` with `Rg=40Ω`, terminal=open.
2828+2929+## Physics approach
3030+3131+Use the **Method of Characteristics with delay lines**. Each lossless T-line segment
3232+propagates V⁺ and V⁻ waves exactly (no numerical error in propagation). Reactive
3333+junction nodes are integrated one step at a time using the trapezoidal rule.
3434+3535+This is a strict extension of the current model: with only resistive elements, the
3636+simulation degenerates exactly to the existing bounce-series result.
3737+3838+### Time step
3939+4040+Choose Δt = min segment delay / `oversample` (default oversample = 4, user-adjustable).
4141+Each T-line segment must be an integer number of time steps long (round len/c to nearest
4242+integer ≥ 1 step, then adjust effective len to match).
4343+4444+### Propagation (per time step, per segment)
4545+4646+V⁺ and V⁻ are stored as ring buffers (one entry per time step of delay).
4747+Advancing one time step = rotating the ring buffer index. No arithmetic needed.
4848+4949+### Junction boundary conditions
5050+5151+At each internal node connecting segment i (left) and segment i+1 (right), with a shunt
5252+element to ground:
5353+5454+Let V = node voltage, Z_L = left segment Z0, Z_R = right segment Z0.
5555+Thevenin from left: V_th = V⁺_L_arriving * 2, Z_th = Z_L ∥ Z_R.
5656+5757+**Shunt R:** algebraic. V = (V⁺_L·2 + V⁺_R·2 - 0) · (Z_L∥Z_R∥R_sh) / ... simplifies
5858+ to the standard multi-port resistive formula. Identical to current Γ formula
5959+ when no shunt R is present (R_sh = ∞).
6060+6161+**Shunt C:** ODE dV/dt = (V_drive - V) / τ, τ = C · Z_eq, Z_eq = Z_L ∥ Z_R.
6262+ Trapezoidal step:
6363+ V[n] = (V[n-1] + Δt/τ · V_drive[n]) / (1 + Δt/τ)
6464+ (V_drive is the open-circuit node voltage from the incoming waves.)
6565+6666+**Shunt L:** dual of C. Current i_L is the state variable.
6767+ i_L[n] = (i_L[n-1] + Δt/Z_eq · V_drive[n]) / (1 + Δt·Z_eq/L) -- TBD,
6868+ derive carefully at implementation time.
6969+7070+**Short / Open:** degenerate cases of R=0 / R=∞. Handle as-is (Γ = -1 / +1).
7171+7272+After solving V at the node, the outgoing waves are:
7373+```
7474+V⁻_L_outgoing = V - V⁺_L_arriving (leftward, into segment i)
7575+V⁺_R_outgoing = V - V⁺_R_arriving (rightward, into segment i+1)
7676+```
7777+7878+### Source and load terminals
7979+8080+Source: same Thevenin model as before (Vg, Rg → V1, gS). Handled as a left terminal
8181+with no right segment (RL is the "shunt" to the right).
8282+8383+Load: same RL model as before. For reactive loads (C, L to ground at the far end),
8484+use the same ODE formulas as internal junction — just no right segment to feed.
8585+8686+## Model data structure changes
8787+8888+```js
8989+// Current
9090+model.segments = [{ Z0 }] // N entries
9191+model.Rg, model.RL // scalars
9292+9393+// New
9494+model.blocks = [
9595+ { type: 'tl', Z0, len }, // transmission line segment
9696+ { type: 'R', value }, // shunt resistor to ground (ohms)
9797+ { type: 'C', value }, // shunt capacitor to ground (farads)
9898+ { type: 'L', value }, // shunt inductor to ground (henries)
9999+ { type: 'short' }, // shunt short to ground (Γ = -1)
100100+ // 'open' not needed mid-circuit; it's a no-op (infinite impedance = nothing)
101101+]
102102+model.terminal = { type, value } // replaces model.RL; type: 'R'|'C'|'L'|'open'|'short'
103103+model.Rg // unchanged (series source resistance)
104104+model.Vg // unchanged
105105+```
106106+107107+The solver flattens `model.blocks` into an alternating node/segment graph:
108108+109109+- Consecutive shunt blocks (`R`, `C`, `L`, `short`) with no `tl` between them collapse
110110+ onto the same electrical node; their admittances are summed before solving the
111111+ boundary condition.
112112+- Consecutive `tl` blocks separated by no shunt block share a pure impedance-mismatch
113113+ boundary (implicitly no shunt element).
114114+115115+## New physics.js API
116116+117117+Replace `buildBounceSeries` with:
118118+119119+```js
120120+// Returns { tGrid, vPlus, vMinus, vNode }
121121+// tGrid: Float64Array of length nSteps (time values in τ_d units)
122122+// vPlus: [nSegs][nSteps × nStepsPerSeg] — V⁺ at each spatial sample, each time
123123+// vMinus: same for V⁻
124124+// vNode: [nNodes][nSteps] — voltage at each junction node over time
125125+//
126126+// nNodes = nSegs + 1 (source node, internal nodes, load node)
127127+function simulateTimeDomain(model, opts)
128128+// opts: { tEnd, oversample, nSpatialSamples }
129129+```
130130+131131+Keep existing helper functions (`riseShape`, `waveVoltageAt`, etc.) — they're still
132132+used for the source waveform shape.
133133+134134+Add a query function replacing `totalVoltageAt`:
135135+136136+```js
137137+// Returns V(z, tIdx) from the simulation grid (linear interpolation in z).
138138+function voltageAt(z, tIdx, simResult)
139139+```
140140+141141+`computeDynamicState` becomes a thin wrapper that samples the precomputed arrays
142142+at the current animation time.
143143+144144+## render.js changes
145145+146146+**Circuit diagram (`drawCircuit`):**
147147+- Draw N abutting T-line boxes (already works for multi-segment)
148148+- At each internal junction node, draw the shunt element symbol (C, L, R, short) below
149149+ the node point, going to the GND rail
150150+151151+**Plot (`drawPlot`):**
152152+- V(z,t) field: same as now, backed by `voltageAt(z, tIdx, sim)`
153153+- V⁺ / V⁻ component view: toggle to show forward/backward waves per segment (new)
154154+- Node voltage traces: show V_node(t) for each internal junction as time-domain overlays
155155+ (similar to current VS/VL traces)
156156+157157+## app.js / UI changes
158158+159159+- Junction controls: for each internal junction, a type selector (none / R / C / L /
160160+ short) and a value input (hidden when type is none or short)
161161+- Load control: extend existing RL input to support C / L / open / short types
162162+- `oversample` slider (hidden/advanced): controls simulation time resolution
163163+- On model change: re-run `simulateTimeDomain`, cache result, redraw
164164+165165+## Work order
166166+167167+1. **Physics core** — `simulateTimeDomain` with resistive-only junctions, validate
168168+ against existing `buildBounceSeries` output (existing SPICE tests still pass)
169169+2. **Resistive junction** — confirm multi-segment R-junction matches SPICE
170170+3. **Shunt C** — implement ODE step, add C-junction SPICE netlist for validation
171171+4. **Shunt L** — same
172172+5. **Model / UI** — add junction controls to app.js
173173+6. **Render** — update circuit diagram to show shunt elements; add V⁺/V⁻ view
174174+7. **Node voltage traces** — add per-junction V(t) overlay to plot
175175+176176+Steps 1–4 can be done and validated in `physics.test.js` before touching the UI.