Files
soothe2-re/BITEXACT_PLAN.md
T

11 KiB
Raw Blame History

BIT-EXACT PLAN — путь от dB-приближения к побайтовой парности

Статус: НЕ bit-exact. Мы на уровне честной dB-параллели (полный корпус mean 1.594 dB, comb 10 dB). Bit-exact ДОСТИЖИМ (F0 gate: плагин байт-детерминирован), но модель ещё не воспроизводит реальный DSP-путь. Этот документ — план, как туда дойти, и точка отсчёта для любой будущей сессии. Читать вместе с AGENTS.md и handoff/NOTES_LEVEL.md.

Цель фазы C (bit-exact): воспроизвести FramedDetector (= fn FUN_180529fe0 mono-path) настолько точно, что verify_bit_exact.py даёт побайтовое совпадение на рендерах (t1kq_*, dual_*, comb_*). Ниже — конкретный порядок, что и зачем.


0. Текущее состояние (честная метрика, 24-bit, trimmed, 62 случая)

| Группа | mean|err| (dB) | max | Уровень | |--------|----------|-----|---------| | t1kq (fc-scan) | 0.226 | 0.500 | ✓ ближе всего | | t1k (loud 0 dBFS) | 1.801 | 2.227 | ! уровневая | | al (level sweep) | 0.638 | 1.596 | ! уровневая | | res | 0.628 | 1.535 | ! | | dual (q-sweep) | 0.726 | 2.252 | ! | | comb (4-band) | 10.149 | 14.752 | ✗ сломан | | TOTAL | 1.594 | 14.752 | |

Корень проблемы: активный канон — ЭМПИРИЧЕСКАЯ bridge-модель C = G·LUT(xv) + W·warp(f)^A; gain = (1C)·res^rp (xv=log10(am/res)). Это ПОГОНА, не транскрипция. Структурная цепочка FUN_180529fe0 (IIR1/2/3 → exp2 → combine/acc → warp → dry/wet → FFT-conv) расшифрована и live-таблицы извлечены (dsp/rt_mask_tables, dsp/rt_weights), но при переносе в C++ (Phase B, da63adc) регрессировала и откачена на bridge. Дальше надо вернуться к структурной цепочке и закрыть её по-настоящему.


1. Порядок работ (обязательный порядок; каждый шаг валидируется отдельно)

Шаг 1 — Реализовать полную структурную mono-цепочку FUN_180529fe0 (КОРЕНЬ)

Заменить bridge (эмпирическую погону) на точную транскрипцию. Реализовать в dsp/framed_model.cpp (или отдельном dsp/fn529fe0.cpp) по NOTES_LEVEL:820-840:

1. scale:   band_mask *= (fVar30/0x1a0)·0x540870·0x54088c    (fVar30=1 из PRNG, locked)
2. IIR1:    y[i]=A1[i]·acc+B1[i]·x[i]  (kIIR_A1/B1, fast attack) -> f6f8
3. copy     f6f8 <- band
4. IIR2:    inline, kIIR_A2/B2 (slow release)
5. mirror   upper half = reversed lower (Hermitian)
6. blend:   f6f8 = 0x540698·(1mix)+mix·0.8;  mask = exp2(level)·f6f8
7. combine: acc[band] = 0x540678  0x5406f8; mirror;
            += 0x5406c8·upper; += 0x5406e8·lower; += 0x540678
8. warp:    mask *= kBand768[band];  mask *= kWarp        (TWO warps)
9. IIR3:    inline TWICE, kIIR_A3/B3
10. dry/wet: mask = mask·(fVar30·0x540888)+(1fVar30)     (=identity сейчас)
11. FFT-conv (Шаг 4)

Критерий: на однополосных t1kq/t1k/al/res структурная цепочка должна НЕ регрессировать ниже bridge (т.е. mean ≤ 0.6-0.8). ВАЖНО: прошлый регресс (F2) был из-за неверного домена (пробовали combine в bridge-финале). Здесь combine/exp2 — в ЕГО собственном домене (reduction/exp2), как в декомпе.

Шаг 2 — combine/аккумулятор 0x5407c8 (закрывает comb + negative-sens)

Точные веса kRTAtt/kRTRel (0x5406c8/6e8) уже в dsp/rt_weights.cpp. Реализовать combine строго по декомпу: acc = band f6f8; += w_att·upper; += w_rel·lower; += band в exp2/REDUCTION-домене (не в bridge-final-gain). Ожидается: comb перестаёт пережимать, negative-sens (band2 sens=12) даёт компенсацию. Критерий: comb mean ≤ 2-3 dB (с 15 до нейтрали), и MB-ваимодействие (comb_all ≈ 0) воспроизводится.

Шаг 3 — Bit-exact exp2 (F5b)

Заменить std::exp2/exp2d::exp2_dsp на точную табличную реализацию 0x26b820: таблицы уже извлечены (dsp/exp2_tables.{hpp,cpp}, 8×16 irr + серия kExp2_big). Нужно транскрибировать body 1:1 (Cody-Waite hi/lo, vfmadd213sd-полином, спец-ветви subnormal/ overflow). Пока не bit-exact — оставить exp2d::exp2_dsp (численно = std::exp2). Критерий: exp2_check сравнивает протв захваченных пар точка-в-точку.

Шаг 4 — FFT-conv 0x535a70 + FIR

FFT-conv сглаживает маску перед FIR (0x540658 window / freqaxis). Это последний этап mono-цепи. dsp/fftconv.cpp есть, но нужна точная блоковая обработка (overlap-save как в декомпе 0x52b550-0x52b8b5), не текущий stand-in. Критерий: маска-гейн после conv совпадает по форме с реальным ref (сглаживание нотча, двусторонний хвост).

Шаг 5 — Bit-exact DSP-FFT 0x140a70 (multi-week, P3)

Заменить dsp/fft.cpp (std::cos radix-2) на точный split-radix 2/4/8 по плану из NOTES_LEVEL:1107-1116:

  • twiddle: DAT_182616800 sin-таблица, sin(k·2π/1024), loader FUN_180039b00 (stride 2^(10-m))
  • butterfly: FUN_18000bfc0/18000c5e0 + 0x8440 elementwise mul + FUN_1800437c0 acc
  • plan-gen: FUN_18002f980 (рекурсия + per-log2 фактор-таблицы) Это НЕ блокирует Шаги 1-4 (bridge/структурная цепь рендерят и с численным FFT), нужен только для побайтовой парности FFT-conv-пути.

Шаг 6 — Внутренняя геометрия 48000/4096 vs 44100/2048

Внутренний DSP SR=48000/N=4096 (freqaxis spacing 11.713 Hz). Хост рендера 44100/2048. F4-тест показал, что простая resample_poly НЕ закрывает (даже хуже). Нужно: детектор гнать на 48000/4096 (IIR-таблицы индексированы 0..2048 = N/2+1 при N=4096), затем свести к 44100. Ожидается закрытие хвостов t1k_b1f/al (+2.2/1.6) — именно уровневой зависимости. Критерий: корректное выравнивание бина и окна между двумя сетками.

Шаг 7 — BandConfig A/B/γ (level-path ctx+0x188) live-захват под конкретные конфиги

Структурная LUT-кривая FUN_180563a60 (A/B/γ). Снято для render_long (A=24/B=28/γ=1) и t1kq (то же), но для остальных тестов не захвачено. Метод автоматизирован (NOTES_CAPTURE.md). Захватить для t1k_b1f / al / dual-конфигов → реальные A/B/γ → это закрывает уровневую зависимость, которую bridge-LUT не может (F1 closure). Критерий: mean кап-нагрузки al/t1k ≤ 0.3 dB.

Шаг 8 — Стерео M8 + полный pipeline (P4/P5)

Финальный рендер stereo (link/balance/LR-vs-MS) по M8 и multiband combine 0x5316e0. Все текущие рендеры mono. Для bit-exact графа нужны стерео-рендеры как мишены. Критерий: verify_bit_exact.py — побайтовое совпадение данных-чанка WAV.


2. Что НЕ делать (подводные камни из дока)

  • НЕ вводить эмпирию там, где есть декомп. Каждый параметр — из декомп-адреса или live-таблицы, иначе пометить EMPIRICAL и в «осталось» (золотое правило AGENTS).
  • НЕ делать combine в bridge-final-gain домене — F2 показал регресс 4.68/5.22. Combine/exp2 живут в reduction/exp2-домене реальной цепочки.
  • НЕ менять Pchip-LUT в bridge — регрессирует весь корпус (F1, параметрич. хуже). Bridge — только запасной вариант, пока структурная цепочка не пройдёт Шаг 1-2.
  • НЕ использовать битые 24-bit загрузчики — метрика render_parity.py:43 правильная (x>=0x800000 => x0x1000000). В тестовых скриптах использовать тот же код.

3. Риски и время

  • Доминирующий риск: дорогой структурный перенос (Шаг 1-2) может снова регрессировать ниже bridge, как Phase B. Митигация: валидировать каждый под-шаг (IIR1 отдельно, blend отдельно) на t1kq, не коммитить пока не ≥ bridge.
  • DSP-FFT (Шаг 5) — multi-week само по себе. Но НЕ блокирует Шаги 1-4.
  • Стерео (Шаг 8) — новая мишень-корпус, больше рендеров.
  • Оценка: Шаги 1-4 (монопуть) — 1-2 недели. Шаг 5 — до 3 недель. Шаги 6-8 — 1-2 недели. До полной байтовой парности — ориентировочно 1-2 месяца с аккуратным монофокусом.

4. Точка входа для следующей сессии

  1. Прочитать AGENTS.md, этот BITEXACT_PLAN.md, handoff/NOTES_LEVEL.md:820-840 (точная цепочка) и dsp/rt_mask_tables.hpp / dsp/rt_weights.hpp (live-таблицы).
  2. Собрать: cd dsp && cmake --build build --target framed_test.
  3. Бейзлайн: TAG=base python3 /tmp/full_corpus.py (62 случая, см. таблицу §0).
  4. Шаг 1: вынести структурную цепочку в dsp/fn529fe0.cpp, валидировать IIR1-шаг на t1kq.