Files
soothe2-re/BITEXACT_PLAN.md
T

155 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. Порядок работ (обязательный порядок; каждый шаг валидируется отдельно)
> **ВАЛИДАЦИЯ (обязательно, НЕ пропускать).** Чтобы не повторить регресс Phase B,
> есть защитный харнесс `scripts/corpus.py` + зафиксированный bridge-базлайн
> `scripts/baseline_bridge.json` (62 случая, правильная 24-bit метрика):
> ```bash
> cmake --build dsp/build --target framed_test
> python3 scripts/corpus.py # полный корпус, текущая сборка
> python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25
> ```
> `--compare` фейлит (exit≠0), если любая группа регрессирует по mean|err| больше tol.
> Правило: структурная цепь (Шаг 1-2) должна НЕ регрессировать ниже bridge на
> однополосных (t1kq/t1k/al/res/dual) и ЖЕЛАТЕЛЬНО улучшать comb. Каждый под-шаг
> (IIR1 → blend → combine → warp → IIR3) коммитить отдельно и прогонять `--compare` —
> если конкретный под-шаг регрессирует, откатить именно его, а не всё сразу.
> Существующие `dsp/*_check.cpp` (twin/tables/leveltrack/levelpath/exp2/fftconv) —
> модульные чёрные проверки на бит-парность под-функций; тоже гонять: `cmake --build
> dsp/build` после правок.
### Шаг 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.