Files
soothe2-re/BITEXACT_PLAN.md
T
Matiq 3e5085e06d phase A: reduction law found (affine in dB), canon unchanged; keep RT_DUMP tooling
- Effective reduction is affine in dB(lvl): x_exp2 = -0.6646 - 0.05877*dB
  (mask ~ 0.8*lvl^-0.354, gamma ~= decomp 0.344); tones +-0.13 dB over 42 dB
- Pre-IIR calibrated variant (X0=1.8/S=0.11): TOTAL 2.633 vs HEAD 2.286;
  wins t1kq/t1k/al/comb, loses res/dual -> gate fails, canon stays LUT
- Grid search (X0,S,floor,CMAX) on validated trajectory model: no scalar
  law of the family fits tone+noise simultaneously (res tension)
- Refuted: high-bin/IIR3-backward propagation, direct-affine bypass,
  positive floors, ceilings
- Kept opt-in instrumentation RT_DUMP_BIN / RT_DUMP_ALL for Step 7 capture
- Docs: NOTES_LEVEL 21d, BITEXACT_PLAN s.0 note, roadmap, AGENTS
2026-08-21 19:29:21 +03:00

200 lines
17 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 случая)
| Группа | 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) лучший, слепая
подкрутка исчерпана — проблема в ФОРМЕ кривой, см. Шаг 7 / bigkernel-вход.
---
## 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-захват под конкретные конфиги
Структурная 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.
---
## 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`
(точная цепочка) + апдейты 20j/21a/21b/21c (IIR3 bidi, combine-семантика,
структурный корпус) и `dsp/rt_mask_tables.hpp` / `dsp/rt_weights.hpp` (live-таблицы).
2. Собрать: `cmake --build dsp/build --target render48k framed_test`.
3. Бейзлайны: `python3 scripts/corpus.py` (bridge, 62 случая, §0) и
`python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json`
(структурная цепь на внутренней сетке).
4. ПРИОРИТЕТ (закрытие разрыва формы кривой редукции):
a. СТАТИКА: hunt clamp/насыщения в level-пути — `vminpd/vmaxpd/minsd/maxsd` в
FUN_18052d650 (IIR1), инлайн IIR2, scale-шаг; семантика FUN_180323f20 и
depth-скаляра 0x540888. Если clamp найден — транскрибировать, smoke + корпус.
b. Если статика исчерпана — Шаг 7: live-захват BandConfig ctx+0x188 по
NOTES_CAPTURE.md; сначала разрешить противоречие A/B/γ между сессиями
(render_long/t1kq: 24/28/1 vs fit по точкам рефов: 13.78/68.29/0.344).