Files
soothe2-re/BITEXACT_PLAN.md
T

24 KiB
Raw Permalink 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.

ОБНОВЛЕНИЕ 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 22s22z); карта метода — 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 метрика):

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).