cosh(z)**2 overflows past |z|~355, which happens routinely once solve_ivp
evaluates the sensor event far from x_sensor over a wide time budget --
correct result (bump->0) but with a RuntimeWarning on every call, which
would flood stderr across a million-run sweep. Clamp the argument to
±20 (already ~1e-17, physically indistinguishable from further growth)
before computing cosh. Caught by running a real 500-run sweep through
Docker as part of Stage 8 verification, not by unit tests alone -- added
a regression test asserting no warning and a finite result far from the
sensor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- physics/sensors.py: optical/Hall (velocity-independent) and inductive
(velocity-scaled, sech^2 spatial sensitivity) trigger events for solve_ivp
- sim/stage.py: flight-to-trigger -> fire delay -> discharge -> energy
accounting, returning StageResult(feasible=False, reason=...) instead of
raising when a sensor never fires or discharge never commutates
- Found and fixed a real bug caught by the energy-conservation test: the
saturation clamp was applied to the mechanical force but not the
electrical back-EMF term, silently breaking energy balance by ~15%.
Removed the dynamic clamp (documented as a deferred nonlinear-L(x,I)
limitation) and kept saturation as a diagnostic-only warning
(StageResult.saturation_warning) so numbers stay honest rather than
quietly wrong. Balance error is now ~0.02%.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>