diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 201cb13..3fd1b48 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -1253,3 +1253,46 @@ ctx+0x188 writers to replace MULT placeholder with decomp value. 6e40+15060 thunks, then bigkernel exp2 in-place on band (line 805-832). ### Commits: 9ffe60e (IIR3 fwd-only + retune), 1a616e1 (bidirectional + decode). + +## ============ UPDATE 2026-08-21c: FULL STRUCTURAL CORPUS (first honest numbers) ============ +### Harness: scripts/corpus_structural.py (render48k x all 62 cases, bridge-compare mode) +Structural chain (48k/4096, LUT level-domain g=0.344 m=4.2, IIR3 bidi) vs bridge: + group n bridge struct delta + t1kq 7 0.226 0.768 +0.54 worse + t1k 11 1.801 2.114 +0.31 worse + al 6 0.638 0.986 +0.35 worse + res 11 0.628 0.437 -0.19 BETTER + dual 22 0.726 3.264 +2.54 MUCH worse + comb 5 10.149 6.116 -4.03 MUCH BETTER + TOTAL 62 1.594 2.286 +0.69 worse + +### KEY FINDINGS: +1. **comb: structural ALREADY beats bridge by 4 dB** (6.1 vs 10.1) WITHOUT acc + wiring — per-band structural min() + warp closes part of the multiband gap. + Plan's "combine closes comb" partially pre-empted by the chain itself. +2. **dual_*_500: constant -6.7 dB over-cut for ALL q** (q-independent!). + res[center] is q-independent (0.117) => systematic LEVEL offset at band + center on two-tone input, not shape. dual_*_2000 (off-center bin) BETTER + than bridge (-0.1..-0.4 vs +0.1..+2.3). At same xv, bridge-LUT cut ~-9.3 dB, + our exp2 path cuts ~-20 dB: real reduction SATURATES (~C_max~0.70 per + extracted LUT pts), exp2(-lvl) does not. +3. al level-sweep error is MONOTONE in level (+0.88@lv3 -> -1.82@lv24): our + level->cut curve too steep mid-range; but t1k (loudest) flips back to + under-cut (+1.8) => NON-monotonic overall, no monotone LUT fixes all groups. +4. gamma/MULT sweep (g in {0.15,0.22,0.30} x m in {4.2,5.0,5.8}, fresh builds, + 12-case subset incl. dual/res/comb): ALL points worse than current + g=0.344/m=4.2; gradient points BACK to current optimum. Blind param space + EXHAUSTED around this parametrization. + +### CONCLUSION (Step 1 status): criterion "structural >= bridge on single-band" +NOT met globally (regress on t1kq/t1k/al/dual; better on res). The missing +piece is the REAL reduction curve saturation — needs either: + (a) Step 7 BandConfig ctx+0x188 live capture (A/B/gamma per config), or + (b) re-examination of bigkernel INPUT: maybe level into exp2 carries an + additional transform (log-domain offset / per-band depth scalar + 0x540888 / offline 0x5e80+FUN_180323f20 ops) that saturates the cut. +Do NOT tune MULT further — it is calibrated; the CURVE SHAPE is wrong. + +### FFT-conv priority DOWNGRADED: NOTES:18c shows window 0x540658 tail is +near-flat (0.8->1.0 plateau at N/2>=2048), FIR-construction effect on mask +shape minimal for N=4096. Step 4 stays P3. diff --git a/scripts/corpus_structural.py b/scripts/corpus_structural.py new file mode 100644 index 0000000..010ef34 --- /dev/null +++ b/scripts/corpus_structural.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Structural-path full-corpus harness (render48k, 48000/4096 internal grid). + +Mirrors scripts/corpus.py but drives dsp/build/render48k (structural +FUN_180529fe0 chain on the internal grid) instead of framed_test (bridge). +Purpose: honest per-group comparison of the structural chain against the +committed bridge baseline (BITEXACT_PLAN step-1 criterion: structural must +not regress below bridge on single-band groups). + +Usage: + python3 scripts/corpus_structural.py [--out results.json] + python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json +""" +import json, sys + +sys.path.insert(0, 'scripts') +import corpus + +corpus.RB = '/home/m/re-tools/dsp/build/render48k' + +_orig_build_cases = corpus.build_cases + + +def build_cases_structural(): + cases = [] + for name, inp, args, ref, f in _orig_build_cases(): + # render48k parses only comma-form band args; join split dual args. + joined = [','.join(args)] if len(args) == 3 else args + cases.append((name, inp, joined, ref, f)) + return cases + + +corpus.build_cases = build_cases_structural + +if __name__ == '__main__': + a = sys.argv[1:] + if a and a[0] == '--vs-bridge': + base = json.load(open(a[1])) + res, _ = corpus.run_all() + bs, cs = corpus.group_stats(base), corpus.group_stats(res) + print(f'{"group":>12} {"n":>3} {"bridge":>8} {"struct":>8} {"d":>8} {"max":>7}') + for g in ['t1kq', 't1k', 'al', 'res', 'dual', 'comb', 'TOTAL']: + if g not in cs: + continue + b = bs.get(g, {'mean_abs': float('nan')})['mean_abs'] + s = cs[g] + print(f'{g:>12} {s["n"]:>3} {b:>8.3f} {s["mean_abs"]:>8.3f} ' + f'{s["mean_abs"] - b:>+8.3f} {s["max_abs"]:>7.3f}') + sys.exit(0) + sys.exit(corpus.main())