Files
soothe2-re/BITEXACT_PLAN.md
T

271 lines
24 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`.**
> **ОБНОВЛЕНИЕ 2026-08-22 (сессии 22a22e):**
> 1. Phase-B оффлайн-гипотезы (pooling/temporal/scalar-ρ) — все ОПРОВЕРГНУТЫ (22a).
> 2. Шаг 7 выполнен → премиса опровергнута: BandConfig A/B/γ = 24/28/1 у ВСЕХ конфигов,
> но это GUI-timer кривые; аудио их не читает (22b). LUT-константы структурной цепи
> помечены EMPIRICAL.
> 3. Насыщение редукции НАЙДЕНО ЖИВЬЁМ и РЕШЕНО АЛГЕБРАИЧЕСКИ (22d/e):
> hot-тон упирается в ЖЁСТКИЙ пол gain=**20.72 dB**, pin ≥24 dB драйва;
> `gain_floor = 20·log10(blend·ln10/20)` — совпадение 0.0055 dB!
> Константа ln10/20=0.11513 @0x1824c3cd4, аудио-юзеры FUN_180529c60/52baa0/bad0/bba0.
> 4. Инфраструктура: официальный параметр-мост (`setparam.lua`), XML `<PARAM>` в RPP =
> декоративная копия (не источник стейта); depth=±18dB/trim=±24dB; WAV bext/junk
> грабли подтверждены живьём.
> Новый приоритет №1 — Шаг 9 (ниже).
>
> **ОБНОВЛЕНИЕ 2026-08-23 (сессии 22s22z):** Шаг 9 закрыт иначе — пол решён
> алгебраически ещё в 22e; фронт сместился. Отвергнуты: скалярные законы A/S
> (насыщение ~1.871.93, 22s/t), двухфакторные quad/resrp (22t), точечные
> lvl=f(am,res^α) — «теорема об отсутствии α» (22x), спрединг IDFT→окно→DFT (22z).
> Новый приоритет №1 — семантика входов `bands[]` (см. AGENTS.md и NOTES_LEVEL
> 22s–22z); карта метода — handoff/BLOCKMAP_529fe0.md.
> **ОБНОВЛЕНИЕ 2026-08-25 (сессии 24j24kk2):** ПРИМЕНЕНИЕ ДЕКОДИРОВАНО ДО ФОРМУЛ.
> Применённый фильтр = побиновное умножение кадра на вещественную маску
> `10^((α·ln1p(lvl/β)+c)/20)`; α/β/c — константы контент-семейства (таблица
> калибровок в NOTES 24ee); слой STFT без синтез-окна. dual-корпус 0.193
> (флаги RT_VLAW/SYN/NOWARP/NOIIR3/IIR12). Гейт смены канона = BIT EXACT
> (решение пользователя 24hh2). Остаток до полного покрытия: (1) каскадный
> симулятор шагов 9–19 по dataflow BLOCKMAP 24hh/24ii + тела bigkernel
> 1803a06a0 и со. (24jj); (2) k-маппинг фронтенда (twin/am формулы);
> (3) Δ-правило вторых пиков. Отвергнуто: ×1.805-свёртка, клампы параметров,
> GUI-LUT в аудио-пути, B∝am, скалярные комбинации lvl/res.
Цель фазы C (bit-exact): воспроизвести `FramedDetector` (= fn `FUN_180529fe0` mono-path)
настолько точно, что `verify_bit_exact.py` даёт побайтовое совпадение на рендерах
(`t1kq_*`, `dual_*`, `comb_*`). Ниже — конкретный порядок, что и зачем.
---
## 0. Текущее состояние (честная метрика, 24-bit, trimmed, 62 случая)
| Группа | bridge mean|err| | bridge max | struct 48k mean|err| | struct max |
|--------|----------|-----|----------|---------|
| t1kq (fc-scan) | 0.226 | 0.500 | 0.768 | 1.013 |
| t1k (loud 0 dBFS) | 1.801 | 2.227 | 2.114 | 2.753 |
| al (level sweep) | 0.638 | 1.596 | 0.986 | 1.822 |
| res | 0.628 | 1.535 | **0.437** ✓ | 1.252 |
| dual (q-sweep) | 0.726 | 2.252 | 3.264 ✗ | 6.455 |
| comb (4-band) | **10.149** ✗ | 14.752 | **6.116** ✓ | 9.148 |
| **TOTAL** | **1.594** | 14.752 | 2.286 | 9.148 |
(struct = структурная цепь на внутренней сетке 48000/4096 через `dsp/build/render48k`,
прогон `scripts/corpus_structural.py`, NOTES_LEVEL:21c. Bridge = `framed_test` 44.1k.)
> **21d**: найден закон редукции — эффективное ослабление АФФИННО в dB(lvl):
> `x_exp2 = 0.6646 0.05877·dB` (маска ≈ 0.8·lvl^0.354, γ≈decomp 0.344).
> На тонах al-свеепа линейность ±0.13 dB (42 dB диапазона!). Прекалибровка
> pre-IIR (X0=1.8/S=0.11) даёт TOTAL 2.633: t1kq/t1k/al/comb лучше, res/dual
> хуже. Grid-search семейства (X0,S,floor,CMAX) на валидированной траекторной
> модели: оптимум rms 0.75 dB, но НИ ОДНА точка семейства не закрывает тон+шум
> одновременно → канон не меняем (gate «без регресса групп»), ищем
> контент-зависимый механизм (детектор att/rel на флюктуациях / combine-
> консюмер). Инструментарий RT_DUMP_BIN/RT_DUMP_ALL оставлен opt-in.
**Корень проблемы**: активный канон — ЭМПИРИЧЕСКАЯ bridge-модель
`C = G·LUT(xv) + W·warp(f)^A; gain = (1C)·res^rp` (xv=log10(am/res)). Это ПОГОНА, не
транскрипция. Структурная цепочка FUN_180529fe0 перенесена в C++ на внутренней сетке
(коммиты 7bf5a4a..b2cb923): уже ОБГОНЯЕТ bridge на comb (4 dB) и res, но регрессирует
на однополосных t1kq/t1k/al и проваливает dual@500 (константный −6.7 dB при всех q —
отсутствие НАСЫЩЕНИЯ редукции: реальная кривая упирается в C_max≈0.70, exp2(lvl) не
ограничен). Свип γ×MULT подтвердил: текущий оптимум (0.344/4.2) лучший, слепая
подкрутка исчерпана — проблема в ФОРМЕ кривой.
> **КОРРЕКЦИЯ 22d/e**: «насыщение C_max≈0.70» старой модели = артефакт placeholder
> MULT=4.2 / Pchip cap 0.667, НЕ свойство плагина: живой плагин на ±24 dB trim
> редуцирует БЕЗ потолка (R 1.4→15.7 dB), а на горячем тоне (+6 dBFS) упирается в
> ЖЁСТКИЙ пол gain=20.72 dB = **blend·ln10/20 точно** (Δ0.0055 dB). Механизм пола —
> в нетранскрибированном куске аудио-пути (след: FUN_180529c60, ln10/20-фактор),
> см. Шаг 9.
---
## 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 (ПЕРЕ-СКОУП 2026-08-21)
Семантика декодирована на уровне thunk'ов (NOTES_LEVEL:21b, consumers_out.txt:833-885):
```
f6f8 = mask acc (0x8d60, dst=3-й аргумент; acc НЕ перезаписывается)
mirror f6f8
f6f8_upper += kRTAtt·acc_upper ; f6f8_lower += kRTRel·acc_lower (0x3c40)
acc += mask (0x5a20, персистентный per-band аккумулятор)
```
**КРИТИЧЕСКОЕ**: в online однополосном пути обновлённые f6f8/acc НЕ имеют консюмера до
warp/IIR3/dry-wet (проверено исчерпывающим grep). Combine НЕ может влиять на single-band
вывод сам по себе. Ожидалось, что combine закроет comb — частично закрыл уже сам
структурный каркас (comb 6.1 vs bridge 10.1 без wiring). ДАЛЬНЕЙШИЙ ШАГ: найти точку
потребления acc/f6f8 (межполосный уровень или FFT-conv каскад) и только потом wire.
НЕ изобретать track→mask feedback (эмпирика, запрещено золотым правилом).
ВНИМАНИЕ: `FUN_1805316e0` — это WRITER КОЭФФИЦИЕНТОВ полос (17 case), НЕ масковый
комбинер (ошибка в старой редакции этого плана и в Шаге 8).
### Шаг 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 (ПРИОРИТЕТ ПОНИЖЕН 2026-08-21)
FFT-conv сглаживает маску перед FIR (0x540658 window / freqaxis). Это последний этап
mono-цепи. `dsp/fftconv.cpp` есть, но нужна точная блоковая обработка (overlap-save как
в декомпе 0x52b550-0x52b8b5), не текущий stand-in. Критерий: маска-гейн после conv
совпадает по форме с реальным ref (сглаживание нотча, двусторонний хвост).
**ПОНИЖЕНО до P3**: NOTES_LEVEL:18c — окно 0x540658 near-flat (0.8→1.0 plateau при
N/2≥2048), эффект построения FIR на форму маски минимален при N=4096. Последовательность
уже размечена (fwd → kill mirror → fill xmm13/xmm9 → inv → complex op → fwd → window
copy → inv → FIR[0]=1.0); остались неизвестные скаляры xmm13/xmm9 и complex-op шага 4.
### Шаг 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 — ✅ ПАЙПЛАЙН СДЕЛАН (2026-08-21)
Внутренний 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) — именно уровневой
зависимости. Критерий: корректное выравнивание бина и окна между двумя сетками.
**Статус**: пайплайн `dsp/render48k.cpp` (resample 44.1→48 → SpectralProcessor(4096,1024,48000)
→ resample обратно) реализован и гоняет полный корпус (`scripts/corpus_structural.py`,
коммиты 7659eb0/6924e53). Выравнивание бина/окна валидировано smoke; уровневые хвосты
t1k/al НЕ закрыты самим по себе — см. §0 и Шаг 7. Известный артефакт: zero-pad последнего
BLK-блока даёт спад am в ~последних 0.06s (косметика, на метрику почти не влияет).
### Шаг 7 — BandConfig A/B/γ (level-path ctx+0x188) live-захват под конкретные конфиги
> **СТАТУС 2026-08-22: ВЫПОЛНЕН → ПРЕМиса ОПРОВЕРГНУТА (NOTES_LEVEL 22b).** Захват по 7
> конфигам дал идентичные A=−24/B=28/γ=1, но весь кластер FUN_180563440/563a60 —
> GUI-timer only; аудио FUN_180529fe0 BandConfig не читает. Насыщение кривой редукции
> искать в теле аудио-функции (см. NOTES_LEVEL 22b, выводы).
Структурная 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 и межполосное суммирование.
Все текущие рендеры mono. Для bit-exact графа нужны стерео-рендеры как мишени.
ВНИМАНИЕ: `FUN_1805316e0` = writer коэффициентов полос, НЕ комбинер масок — точка
межполосного суммирования масок/acc ещё не локализована (см. пере-скоуп Шага 2).
Критерий: `verify_bit_exact.py` — побайтовое совпадение данных-чанка WAV.
### Шаг 9 — Механизм пола редукции blend·ln10/20 (НОВЫЙ ПРИОРИТЕТ №1, 2026-08-22)
Живой факт (22d): hot-тон упирается в ЖЁСТКИЙ пол gain=20.72 dB = `20·log10(0.8·ln10/20)`
с точностью 0.0055 dB; колено между L=−6..0 (trim-шкала); холодный тон на ±24 trim
пола НЕ достигает. t-clamp в транскрибированной LUT НЕ даёт pin (grid MULT≤12/B≥18 —
NOTES 22e) ⇒ пол живёт в другом куске аудио-пути.
9a. Декодировать `FUN_180529c60` полностью: фактор
`expf((p87c·30 90)·ln10/20)` на band-буферы 0x540678[band] через
`FUN_1804d56b0` (f_529c60.dis:94-101) — что именно считает 1804d56b0 и куда
идёт произведение (это vtbl-метод рядом с аудио-entry).
9b. Найти сайт клампа: grep ln10/20-константы (0x1824c3cd4) по остальным юзерам
(18052baa0/bad0/bba0) и по f529fe0.dis/f_52d650.dis на предмет floor/clamp маски.
9c. Проверка предсказаний модели пола: (i) floor_gain(mix) сдвигается как
20log10(blend/0.8); (ii) пол частотно-зависит только через blend(freqaxis)
при mix<100; (iii) knee-позиция от sens/depth. Рендеры через setparam.lua.
9d. Порт клампа в `process_band_structural` → smoke → корпус с гейтом
(`corpus_structural.py --vs-bridge`; цель: dual@500 уходит с 6.7, res/dual/comb ≥ bridge).
Критерий: воспроизвести pin −20.72 dB в рендере модели + отсутствие регресса групп.
---
## 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). В тестовых скриптах использовать тот же код.
- **НЕ редактировать XML `<PARAM>` в RPP ради изменения звука** — это декоративная
UI-копия, плагин берёт стейт из бинарной части чанка (NOTES 22c). Только мост
`setparam.lua`.
- **НЕ читать WAV наивным readframes+reshape** — REAPER пишет bext/junk-чанки; только
каноничный `render_parity.load` (иначе фантомный шум/клиппинг, NOTES 22c).
## 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`, затем NOTES_LEVEL **UPDATE 22s22z** (журнал актуальной
фазы) и `handoff/BLOCKMAP_529fe0.md` (блок-карта FUN_180529fe0 по raw asm).
2. Собрать: `touch dsp/framed_model.cpp && cmake --build dsp/build --target render48k framed_test`
(TOOLING HAZARD: touch перед сборкой обязателен).
3. Бейзлайны: канон = `RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1
RT_LAWAFFINE=7.4,1.85 python3 scripts/corpus_structural.py` → TOTAL 1.931;
bridge-гейт: `--vs-bridge scripts/baseline_bridge.json` (1.594).
4. **ПРИОРИТЕТ №1 — семантика входов `bands[]`**:
a. Статика: декод тел conv 0x1802a24c0 (float)/0x1802fa420 (double,
резолв ILT-стабов через idx@0x1826159a0, см. BLOCKMAP thunk-таблицу) и
complex-op шагов с буферами ctx+0x540548/550/598.
b. Динамика: синхронная трасса буферов bands[]/f6f8/acc в момент аудио-
колбека (`scripts/dualtrace.py` + `play_loop.lua`; между кадрами acc/f6f8
нули — снапшот должен попадать В колбек, см. NOTES_CAPTURE 22y).
5. Инструменты: `scripts/lawfit22r.py` (офлайн реплей, 2 c корпус — ЛЮБОЙ закон),
`scripts/resalpha.py` (тракты+теорема α), live-capture `dualtrace.py`.
6. Факт для калибровки интуиции: dual-таблица по q в NOTES 22x (cut@центра
константен; Δскайрта ≈ линейно по −ln res, b≈2.25 дБ/e-fold).