diff --git a/__pycache__/csv_preprocessor.cpython-312.pyc b/__pycache__/csv_preprocessor.cpython-312.pyc index 7d7e578..d82c258 100644 Binary files a/__pycache__/csv_preprocessor.cpython-312.pyc and b/__pycache__/csv_preprocessor.cpython-312.pyc differ diff --git a/csv_preprocessor.py b/csv_preprocessor.py index a8751eb..e1b204f 100644 --- a/csv_preprocessor.py +++ b/csv_preprocessor.py @@ -65,6 +65,10 @@ CLK_LP_LOW_MIN_NS = 300.0 HS_BURST_AMPLITUDE_MIN_MV = 40.0 # mV — below this, no real HS burst is present # Lowered from 50 mV: 48 mV capture (0001) was a false alarm; true flicker (0008) at 34 mV. +HS_BURST_AMPLITUDE_HIGH_MV = 70.0 # mV — above this with short LP-low → anomalous LP states +# Normal HS (dynamic video) = ~15–40 mV P95-P5/2 on LP probe (RC-filtered). +# LP-01/LP-10 states in the burst window (cap 0042 type) produce 130+ mV → flag these. + # Mode A minimum amplitude: LP-11-return edge artifacts produce near-zero amplitude in the # burst window (burst is pure LP-low DC between two LP-11 regions). Require ≥ this to # distinguish a genuine weak-HS attempt from a false rolling-std trigger on LP-11 return. @@ -752,6 +756,7 @@ class LPMetrics: # Flicker detection # A capture is flagged when the LP-low plateau is absent or shorter than # FLICKER_LP_LOW_MAX_NS. Normal captures show ~340 ns; flicker shows 0–50 ns. + hs_rolling_std_found: bool = False # rolling-std fired in HS window after LP-low ended flicker_suspect: bool = False warnings: list = field(default_factory=list) @@ -891,6 +896,9 @@ def analyze_lp_file(path: Path) -> "LPMetrics": n_hs_bursts = 0 hs_burst_dur_ns = None hs_amplitude_mv = None + hs_rolling_std_found = False + s_end = None + rstd = None if len(lp11_regions) >= 1: # Measure LP-11 → HS exit gap (LP-01 + LP-00 combined) using a rolling @@ -943,6 +951,17 @@ def analyze_lp_file(path: Path) -> "LPMetrics": float(np.percentile(burst_volts, 5))) / 2 * 1000, 1 ) + # Did rolling-std fire in the actual HS window (after LP-low ended)? + # With dynamic display content (video), genuine HS keeps rolling-std above + # threshold; absent HS does not. Used to gate Mode B/D false positives. + if lp_low_duration_ns is not None: + lp_low_end_idx = s_end + int((lp_low_duration_ns + 50.0) * 1e-9 / dt) + hs_check_end = min(lp_low_end_idx + int(1000e-9 / dt), len(rstd)) + if lp_low_end_idx < len(rstd): + hs_rolling_std_found = bool( + np.any(rstd[lp_low_end_idx:hs_check_end] >= HS_OSC_STD_V) + ) + # ── Warnings ───────────────────────────────────────────────────────── warnings = [] continuous_hs_clk = (not lp11_regions) and (channel == "clk") and (float(volts.max()) < LP11_HIGH_V) @@ -1027,16 +1046,27 @@ def analyze_lp_file(path: Path) -> "LPMetrics": # Mode A2: rolling-std never fired — HS absent or amplitude below HS_OSC_STD_V; # weak oscillations are misclassified as LP-low, masking the true HS failure or lp11_to_hs_ns is None - # Mode B: LP-low anomalously short + low amplitude = marginal HS launch - or _lp_low_short + # Mode B: LP-low anomalously short + low amplitude = marginal HS launch. + # Gated by hs_rolling_std_found: if HS actually launched (rolling-std fires + # after LP-low ends), LP-low being short is a timing anomaly but not a flicker. + or (_lp_low_short and not hs_rolling_std_found) # Mode D: LP-low normal, noise-spike lp11_to_hs (< 50 ns), low HS amplitude. - # Requires dynamic display content (video) during the test — with static/DC content - # the probe noise floor is 15–35 mV regardless of HS health, making this unreliable. + # Requires video/dynamic content. Gate: if rolling-std fires after LP-low ends, + # HS launched normally and the early lp11_to_hs was just the LP-low edge trigger. or (lp11_to_hs_ns is not None and lp11_to_hs_ns < LP_LOW_DUR_MIN_NS - and not _lp_low_short) + and not _lp_low_short + and not hs_rolling_std_found) ) ) + # Mode E: short LP-low with anomalously HIGH amplitude — LP-01/LP-10 states in the + # burst window (link failed to launch HS cleanly). Normal LP probe HS = 15–40 mV; + # LP-01/LP-10 DC levels produce 70+ mV. Confirmed: cap 0042 (lp_low=61 ns, amp=133 mV). + hs_burst_anomalous = ( + _lp_low_short + and hs_amplitude_mv is not None + and hs_amplitude_mv > HS_BURST_AMPLITUDE_HIGH_MV + ) # Mode C: no LP-11 at all → link silent (but exclude CLK which is always HS) link_silent = ( channel == "dat" @@ -1056,6 +1086,7 @@ def analyze_lp_file(path: Path) -> "LPMetrics": # positives when noise triggers gave lp_low < 50 ns with normal HS. lp_low_duration_ns is None or hs_burst_absent + or hs_burst_anomalous ) ) )