docs: archive 4024стр NOTES_LEVEL, единый источник TOTAL README, runbook AGENTS, 3-шаг план

- handoff/NOTES_LEVEL.md 4413→397 (голова 24mm5+), хвост → handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md
- handoff/NOTES_LEVEL_INDEX.md NEW навигатор, handoff/archive/ whitelist .gitignore
- SESSION_HANDOFF/summary → archive + редиректы, NOTES_TWIN/NOTES_CAPTURE сжаты
- README.md:13 единственный источник TOTAL 1.594/dual 0.193, AGENTS runbook, BITEXACT_PLAN 9→3 шага (каскад 9-19/k-маппинг/Δ)
This commit is contained in:
2026-08-29 03:15:42 +03:00
parent 2eb3b690c9
commit f73724fee7
14 changed files with 4732 additions and 5325 deletions
+38 -185
View File
@@ -1,211 +1,64 @@
# Roadmap: bit-exact реверс DSP-ядра oeksound soothe2 (v1.1.2)
## Цель
Воспроизвести DSP-путь soothe2 в виде **собираемого standalone C++** с **побайтово
идентичным** выходом (bit-exact) на верификационных свипах, с пониманием логики
и подписанными функциями.
Воспроизвести DSP-путь soothe2 в виде **собираемого standalone C++** с **побайтово идентичным** выходом (bit-exact).
## Результат
`re-tools/dsp/``*.cpp/*.h` (классы DSP, подписаны), `CMakeLists.txt`, `harness.cpp`
(WAV16 → рендер → WAV24), `dsp_notes.md` (карта функция↔смысл, константы, LUT),
`verify_bit_exact.py` (прогон всех свипов, побайтовое сравнение).
`re-tools/dsp/``*.cpp/*.h` (классы DSP), `CMakeLists.txt`, `harness.cpp` (WAV16 → рендер → WAV24), `dsp_notes.md` (карта функция↔смысл), `verify_bit_exact.py`.
## Текущий статус (2026-08-21, P4)
## Статус — см. `README.md:13` (единственный источник TOTAL)
> **2026-08-21: bit-exact НЕ достигнут** — честная dB-параллель. Два канона:
> bridge (эмпирическая погона, корпус mean 1.594 dB, comb 10.1) и СТРУКТУРНАЯ цепь
> FUN_180529fe0 на внутренней сетке 48000/4096 (`dsp/build/render48k`, корпус mean
> 2.286 dB, comb **6.12** — уже лучше bridge на мультиполосе, res тоже лучше; хуже на
> однополосных). Разрыв формы кривой редукции: реальная насыщается (C_max≈0.70),
> exp2(−lvl) нет → Шаг 7 захват A/B/γ или статический hunt clamp'а в level-пути.
> Путь к байтам — `BITEXACT_PLAN.md` (8 шагов, пере-скоуп Шага 2: acc не имеет
> однополосного консюмера; FUN_1805316e0 = writer коэффициентов, НЕ комбинер).
> Полный журнал — `handoff/NOTES_LEVEL.md` (апдейты 20j21d).
> **Итог 2026-08-28 (24mm14):** применение декодировано до формул `mask=10^(cut_D/20)`, `cut_D=α·ln1p(lvl/β)+c` (rms ≤0.016, 3 семейства), слой STFT без синтез-окна, dual-корпус 0.193 dB (канон 3.264). Гейт = bit-exact. Остаток: 3 шага `BITEXACT_PLAN.md:1` (каскад 919, k-маппинг, Δ-правило). Архив B-фаз ниже свёрнут.
> **21d**: закон редукции найден — АФФИННО в dB (маска≈0.8·lvl^0.354, γ≈0.344
> decomp); на тонах линейность ±0.13 dB. Но scalar-семейство не закрывает тон+шум
> одновременно (res-тенция) → канон не меняем; следующий рычаг — контент-зависимость
> (att/rel на флюктуациях, combine-консюмер). Инструментарий RT_DUMP_BIN/RT_DUMP_ALL.
<details><summary>История B-фаз (B.1…P4.3) — свёрнуто, детали → handoff/archive/ + git log</summary>
> **2026-08-25 (24j24kk2): ПРИМЕНЕНИЕ ДЕКОДИРОВАНО ДО ФОРМУЛ.**
> mask=10^(cut_D/20); cut_D=α·ln1p(lvl/β)+c (пер-контент константы, rms≤0.016);
> слой STFT без синтез-окна; dual-корпус 0.193 (канон 3.264). ГЕЙТ = BIT EXACT.
> Детали: NOTES_LEVEL 24j24kk2, BLOCKMAP_529fe0 23b24jj, NEXT_PROMPT.md.
- **Статический декомп ~95%**: twin, generator case8, level-path (0x529fe0/0x563440/0x563a60), mask-apply, IIR-трекеры, FFT-conv (0x535a70), render-loop FUN_18052e260 — `.dis` в `handoff/nls_dasm/`.
- **Структурная цепь** `dsp/framed_model.cpp::process_band_structural` (48k/4096): `scale→LUT(level)→IIR1→IIR2→mirror→blend/exp2→combine→dual-warp→IIR3×2→dry/wet`; live-таблицы `rt_mask_tables`. Корпус: comb 6.12 (bridge 10.15), res 0.44.
- **Численная модель B.10…B.15**: `C=g·LUT(xv)+w·warp^a`, `gain=(1C)·res^rp`, rp=0.0275·Q^0.2159 (mean 0.175), Pchip LUT (dual 0.027). Канон — `framed_render.py` (исторический).
- **Фазы P1P5**: twin float-parity 1.27e-5, FFT-conv, leveltrack, LUT, mask-apply — в основном готово; осталось каскад 9–19 + twin/am + Δ.
- **Статический декомп DSP-ядра — закрыт (~95%)**: twin-резонатор, генератор case8,
level-path (0x529fe0/0x563440/0x563a60), mask-apply (FUN_180529fe0 mono-path), IIR-трекеры,
FFT-conv (0x535a70), main render-loop (FUN_18052e260) декодированы; `.dis` в `handoff/nls_dasm/`.
- **Структурная цепь в C++** (`dsp/framed_model.cpp::process_band_structural`, сетка 48k/4096):
`scale→LUT(level-domain)→IIR1→IIR2→mirror→blend/exp2→combine(acc-update)→dual-warp→IIR3 bidirectional×2→dry/wet`;
live-таблицы в `dsp/rt_mask_tables.{hpp,cpp}`, `rt_weights.{hpp,cpp}`. Прогон:
`scripts/corpus_structural.py`. IIR3 = [fwd+bwd]×2 по декомпу (коммит 1a616e1).
- **Результаты структурной цепи (62 кейса)**: comb 6.12 (bridge 10.15), res 0.44 (0.63),
t1kq 0.77 / t1k 2.11 / al 0.99 / dual 3.26 (хуже bridge). Детали: NOTES_LEVEL:21c.
- **Честная тримнутая метрика** (длина выхода = длина входа) — см. AGENTS.md.
- **Цель bit-exact (Phase C) — в работе**: twin-транскрипция в `dsp/` готова и проходит
- **Бит-экзактная C++ транскрипция (Phase C)**: twin (float-parity 1.27e-5, tables_check ALL OK),
FFT-conv (P1.2/P1.3 fftconv.cpp), маска-канон (P1.4/P2), level-tracker (P2, leveltrack.cpp),
LUT (P2.5 levelpath.cpp), mask-apply цепь (P4 framed_model.cpp) — в основном готово.
- **ВАЖНО про метрику**: ранние записи "mean=0.160/0.144" считались на выходе НЕтримнутой
длины (`nfr*HOP+N`) и были артефактом; честный базлайн для scalar `rp=0.0169` = **0.280**.
Все числа в этом файле — на ТРИМНУТОЙ метрике.
### Ключевые находки (итог, свёрнуто)
## Историческая численная модель (B-фазы; канон теперь — C++ FramedDetector)
- Маска аддитивная `C=g·LUT+w·warp^a` (не мультипликация).
- `res_power` `gain=(1C)·res^rp` — закрывает 500Hz residual.
- `0x530d30` дважды NEGATIVE — тилт не оттуда.
- LUT — гамма-монотонная `FUN_180563440` (Pchip немонотонна).
- Константы: SCALE=1/1024, DB_CONV=8.6859, IIR α=0.1, LCG 0x3cdca.
```python
xv = log10(A_k / res_k) # A_k = 2|X_k|/wsum (twin-env), res = |2B/A| case8
C = G * LUT(xv) + W * warp(f_k)**A # additive mask (НЕ мультипликация warp·LUT)
gain = max(1 - C, eps) * res_k**rp # rp = rp0 * Q**drp (res_power)
```
</details>
- LUT — Pchip; warp — `0.87·7.942·x/(7.942+x)`, x=f/2000 (аппроксимация runtime-вычисления FUN_180563440);
- Q-dep rp: `rp0=0.0275, drp=0.2159` (mean 0.175); B.15 joint-free-knot LUT: G/W/A/rp0/drp =
`0.9752/0.3394/1.0222/0.0254/0.2231` (dual mean 0.027);
- Запуск (историч.): `python3 framed_render.py dual`.
## Контракт из manual (`soothe2_ManualFAQ.pdf`)
## Статус по фазам
- **Фаза 0** — сделано (OCR стр.30 manual → `pipeline_ocr.txt`, топология ниже).
- **Фаза A (статический RE)** — закрыта ~90%. Карта «что декодировано → где детали»:
| Компонент | Функция | Детали |
|---|---|---|
| Twin-резонатор | FUN_180535880/536f90 | `handoff/NOTES_TWIN.md`, `dsp/twin.*` |
| Генератор case8 | FUN_180533ec0 (RBJ bell, middle=2cos w0) | `handoff/SESSION_HANDOFF.md §4` |
| Writer коэффициентов | FUN_1805316e0 (17 case, mode→тип полосы) | `NOTES_LEVEL.md`, B.7 |
| Level-weight | FUN_180530d30 (численно НЕ даёт tilt) | `NOTES_LEVEL.md`, B.9/B.14 |
| LUT-кривая | FUN_180563440 (linear/power-law, 1024 бин) | `handoff/nls_dasm/f_563440.dis`, B.13 |
| IIR level-трекеры | FUN_180563ce0 (init: 341 бин, α=0.1) | `handoff/nls_dasm/f_563ce0.dis`, B.13 |
| Mask-применитель | FUN_180529fe0 (PRNG, depth, mask 1C) | `handoff/nls_dasm/f529fe0.dis` |
| FFT / twiddle / Cody-Waite | FUN_180008140 / FUN_180039b00 / 1801de760 | `notes_giant_fft.md`, `dsp/fft*` |
- **Фаза B (численная модель)** — пройдена до B.15: от B.10 (rmse 0.109 dB) до full frame-рендера
Q-dep rp (0.175) и joint free-knot LUT (dual 0.027). Канон — `framed_render.py`.
- **Фаза C (C++ bit-exact)** — in progress: `dsp/twin.*` готов и проходит float-parity (1.27e-5);
`dsp/{tables_data, fftconv, leveltrack}.cpp` + `dsp_ctx.hpp` готовы (P1-P2 структурно);
`verify_bit_exact.py` даёт базлайн (все DIFF — ожидаемо до подключения живой маски).
Оставшийся код по фазам:
| Фаза | Компонент | Статус |
|---|---|---|
| P1.5 | live-коэффициенты level-tracker A[] + скаляры RPP | ✅ DONE (rtctx_live.json, leveltrack_data.hpp) |
| P2 | mask-accumulator combine kernels (8d60/5a20/3c40) | ✅ DONE (levelpath.cpp combine_sub/acc_add/acc_fma) |
| P2.5 | twin-mask factory (56e3e0) + band LUT apply (563a60) | ✅ DONE (levelpath.cpp) |
| P3 | бит-экзактная base-CRT FFT | ⚠️ partial: архитектура размечена, vlog(ln) транскрибирован; butterfly+shuffle+plan-gen осталось |
| P4 | render-loop 52e260 + связка mask→FFT-conv + full pipeline | ✅ **существенно продвинут (2026-08-21)**: mono-path FUN_180529fe0 декодирован и транскрибирован на внутренней сетке 48k/4096 (`render48k`, LUT в level-домене, IIR3 bidirectional по декомпу); полный структурный корпус: comb **6.12** (bridge 10.15), res 0.44; однополосные хуже bridge — нужен шаг насыщения кривой редукции. Combine-семантика декодирована до thunk'ов; acc не имеет однополосного консюмера (пере-скоуп). Остаток: точка потребления acc/f6f8, PRNG-пролог, бит-экзактный exp2, FFT-conv (P3), SR-хвосты |
| P5 | стерео M8 (link/balance/LR-vs-MS) + multiband combine 5316e0 | TODO (в скоупе, не начат) |
Обновлённая оценка (2026-08-21): структурно ~80% готово — mask-apply цепь FUN_180529fe0
декодирована (scale→LUT→IIR1/2→exp2→combine→warp→IIR3-bidi→dry/wet→FFT-conv) и перенесена
в C++ на внутренней сетке, live-таблицы (IIR A/B, warp, band768, PRNG LUT) извлечены,
полный корпус гоняется. Осталось: (а) полная бит-экзактная FFT (butterfly+shuffle+plan-gen
— недели), (б) насыщение кривой редукции (Шаг 7 захват A/B/γ или clamp-hunt в level-пути),
(в) точка потребления combine acc/f6f8, (г) PRNG-пролог + FFT-conv + бит-экзактный exp2,
(д) стерео M8 (P5).
## Ключевые находки (итог)
- **Маска аддитивная**: `C = g·LUT(xv) + w·warp^a`, НЕ мультипликация warp·LUT (та проваливается >10 dB).
warp-terм ≈ `warp^3.14·w` — кратный каскад freq-axis ∏0x540688/FFT-conv.
- **res_power**: `gain = (1C)·res^rp` — закрывает 500Hz-residual для q0.1/q1 (envRmse 0.64→0.05 dB).
- **0x530d30 дважды NEGATIVE**: спектральные веса численно ≈0 (10⁻⁴⁰…10⁻²⁰⁰) → тилт 2000-vs-500
из них НЕ идёт (подтверждено при N=1024 и N=2048).
- **Форма LUT**: Pchip-эмпирика была немонотонна (провал при xv≈−0.5); настоящая кривая
FUN_180563440 — гамма/монотонная; при замене на монотонную (free-knot или структурную)
q1@2000 закрывается до 0.000.
- **Константы из дампа**: SCALE=1/1024, ONE=1.0, TWO=2.0, NEG1=1.0, HALF=0.5, DEPTH_SCALE=4.0,
DB_CONV=8.6859, FLOOR=6.9078, IIR α=0.1, LCG_OFFSET=0x3cdca.
## Контракт из manual (`soothe2_ManualFAQ.pdf`, v1.0.0)
| # | Факт | Следствие для DSP |
| # | Факт | Следствие |
|---|---|---|
| M1 | Soft/Hard: режим меняет всё; depth 3.0 в soft ≠ hard (стр.7) | mode = глобальный препроцессор детектора |
| M2 | Depth — референтный dB (−18..18), реальная глубина до 60dB (стр.8) | вход depthcurve-LUT; clip 60dB |
| M3 | Sharpness: выше = глубже и уже (стр.8) | Q_notch растёт с sharp |
| M4 | Selectivity: 0 = «everything must go» (стр.9) | отбор/порог пиков, число нотчей НЕ лимитирует |
| M5 | Attack/Release: реальные времена частотно-зависимы (стр.11) | Env НЕ один one-pole; нужен частотный масштаб |
| M6 | Oversample = расчёт reduction-фильтра в высшем разрешении (стр.11) | интерполяция детекторной сетки → фильтра |
| M7 | Resolution = частота обновления детекции/фильтра (стр.11) | период пересчёта коэффициентов |
| M8 | Stereo: link 100% = сумма каналов; 0% = dual mono; balance; L/R vs M/S | детектор на sum; коррекция симметрична/раздельна |
| M9 | Trim регулирует ТОЛЬКО wet; Delta = xwet; Mix 0..100% (стр.14) | порядок: detect→notch→trim→mix/delta/bypass |
| M10 | Банды: 2 cut + 4 general; general: peak/shelf/reject/tilt (стр.17-19) | `FilterGraph<float,6,0x400>`, `DigitalFilter` мультитипный |
| M11 | Кривая = inverse EQ, сумма band-кривых = белая (стр.17) | `red(f)=base+ΣW_i·H_i` |
| M12 | Sidechain; Input trim — скрытый предусилитель анализа (стр.26) | отдельный вход/гейн анализа |
| M1 | Soft/Hard меняет всё | mode = препроцессор детектора |
| M2 | Depth ±18, до 60 dB | depthcurve-LUT, clip 60 dB |
| M5 | Attack/Release частотно-зависимы | Env — частотный масштаб |
| M8 | Stereo link/balance/L-R vs M/S | детектор на sum |
| M10 | Банды: 2 cut + 4 general | `FilterGraph<float,6,0x400>` |
## Топология пайплайна (Приложение A, стр.30 — `pipeline_ocr.txt`)
## Топология пайплайна (Приложение A, стр.30)
```
main inputs ──┐
sidechain ────┤ (sidechain enabled?)
input trim ──► analysis (детекция; depth/sharpness/selectivity; EQ из per-band balances)
stereo apply + balance (channel link + global balance)
ms encode? ──► processing (нотч-синтез в M/S) ──► ms decode?
mix ──► bypass? ──► output
main/sidechain → input trim → analysis (depth/sharpness/selectivity; EQ per-band)
stereo apply + balance → ms encode? → processing (M/S) → mix → bypass → output
```
- mid/side ручка: обработка в M/S-области — стерео-путь подтверждён диаграммой.
- GUI/visualisation — побочная ветвь, в DSP-ядро не входит (кроме параметров).
## Журнал вех (свёрнуто)
## Журнал вех (детали — в handoff/ и git log)
| Веха | Дата | Суть / ключевые числа |
| Веха | Дата | Суть |
|---|---|---|
| B.1 | 08-17 | C++ каркас `dsp/*` (twin, spectral, filter, detect, ms, fft, cody, twiddle, harness) |
| B.2 | 08-17 | `libsoothe2_dsp.so` + harness WAV16→WAV24; фиксы WAV-алиасинга / in-place |
| B.3 | 08-17 | TODO: twiddle-loader FUN_18014ec20 (Dekker+Cody-Waite), bit-reverse 18003b6c0, транскрипция fft.cpp |
| B.4 | 08-17 | burst500: corr 0.995, SNR 19.8 dB, diff 0.065 dB; детектор = уровнезависимый floor |
| B.5 | 08-17 | FUN_180535880/536f90 — реальный SSE-код в дампе; .vst3 совпадает с дампом в `.text` |
| B.6 | 08-17 | Dispatch-модель исправлена (у каждого стаба своя таблица); полный twin-алгоритм; артефакты `det_*.dis` |
| B.7 | 08-18 | band-структура (+0x800 mode/0x804 freq/0x808 sens/0x80c Q), writer 5316e0 (17 case); XML mode=1 → case8 (RBJ bell) |
| B.8 | 08-18 | Коррекция middle=2cos(w0); `mask(f)=|2B/A|(fc,Q,gain)`; freq-path 530850 + warp, level-path 56e3e0 |
| B.9 | 08-18 | Level-path замкнут (потребитель 529fe0, аккумулятор 0x5407c8); формула 530d30; warp-ось sigmoid; конструктор 535ae0 (0x24=40000) |
| B.10 | 08-18 | Единая модель `C=depth·tilt·D0·(L0/res)^p`; парадокс dual_b1q решён (tilt-наклон); rmse 0.109 dB (31 pt); `model_dual.py` |
| B.11 | 08-18 | LUT-кривая извлечена (36 pt на одну кривую); PCHIP 10 узлов rmse 0.0718 dB; колено x≈0.58; `model_lut.py` |
| B.12 | 08-18 | Bridge-модель `C=g·LUT(xv)+w·warp^a` (a≈π, аддитивно), rmse 0.236; FFT-conv размаплен; freq-axis offline const; `model_fir.py` |
| Ph.5 | 08-18 | `render_parity.py`; dB-parity 36 pt rmse 0.268 dB (рефит 0.203); остаток структурный (0x540658) |
| B.13 | 08-19 | res_power `gain=(1C)·res^rp` (500Hz resid решён); декомп 563440/563ce0/529fe0; константы из дампа |
| B.14 | 08-19 | **Артефакт-коррекция** (честная тримнутая метрика; базлайн scalar=0.280); Q-dep rp → mean 0.175; 530d30 повторно NEGATIVE (N=2048) |
| B.15 | 08-19 | Диагноз LUT-немонотонности → joint free-knot LUT (8 узлов, dual+al_*) → **dual mean≈0.027 dB**; остаток al_* lv24 dC=+0.10 |
| P4.1 | 08-20 | Полный декомп mono-path FUN_180529fe0; real mask-цепь в C++ (IIR leaky + acc + live LUT); t1kq err 0.5 dB; `framed_test` |
| P4.2 | 08-20 | Live-таблицы извлечены (`rt_mask_tables` IIR A/B, warp, band768, PRNG LUT); bigkernel 0x26b820=exp2 семантика залочена (`exp2(-mask)·blend`) |
| P4.3 | 08-20 | Dual-warp шаг 6 транскрибирован (mask·=band768, ·=warp) → comb 500/3000 починены; t1kq 0.43 dB; comb ≤6.4 dB |
| B.1B.6 | 08-17 | C++ каркас, twin, dispatch, band-структура |
| B.10B.15 | 08-18…19 | Модели C/LUT, bridge `C=g·LUT+w·warp^a` (0.236), res_power, dB-parity 0.268 |
| P4.1P4.3 | 08-20 | Декомп FUN_180529fe0, live-таблицы, dual-warp |
## Риски
- **FFT bit-exact**: DSP-STFT FFT найден и транскрибирован; главный нерешённый риск — порядок
double-операций в dispatch-ядрах при побайтовом сравнении.
- **Хост-зависимость**: точный размер буфера/фрейма (рендеры при разных RENDER_RANGE должны
давать одинаковые байты).
- **Stereo**: все текущие свипы mono; для bit-exact графа M8 нужны стерео-рендеры (link/balance/ms).
- **Честная метрика**: тримнутая длина выхода обязательна (артефакт B.14); fit-цели проверять на
`render_trim`/`framed_render.synthe`.
## Открытые вопросы
1. **Форма LUT в зоне xv<0.3**: joint-fit даёт dual≤0.07, но al_* lv24 dC=+0.10 — нужна структурная
кривая FUN_180563440 (linear/power-law) вместо Pchip-эмпирики.
2. **warp-терм**: эмпирическая формула — аппроксимация runtime-вычисления FUN_180563440;
для bit-exact заменить на реальный расчёт.
3. **Стерео-верификация (M8)** — приоритет mono-путь или сразу стерео-граф.
4. **0x540658 window** — ✅ **ЗАКРЫТ 2026-08-19**: live-захват через registry heartbeat
(`{u64 count, u64 ptr}` run at psy. 0x29b06c0) во время offline-рендера. Окно = registry[01]
(8193 f32, 0.5→1.0, сатурация), freq-axis = registry[0d] (0..23988 Hz, spacing 11.713 ⇒
**внутренний SR=48000**), веса [03][04][05][06] (WA/WB/WC/WD). Сохранено: `handoff/rtwin_freq_44100.npy`,
`rtfreqaxis_48000_internal.npy`, `rtwa/rtwb/rtwc/rtwd_*.npy`. Инфра: `rtsnap_fast.py`,
`rtsnap2.py` (двухточечный diff подтвердил стабильность таблиц), `findctx.py`. Полное описание:
`handoff/NOTES_CAPTURE.md`. vptr/44100-marker объект-скан (rtobj) — тупик (производная vtable
не адресуется статически; регистр-бикон — рабочий путь).
Полный журнал — `handoff/NOTES_LEVEL_INDEX.md` + `git log`.
## Где лежат детали
- `handoff/SESSION_HANDOFF.md` — инвентарь декомпа (§0), трансляция/ключевые адреса (§2), Phase-5 план (§6).
- `handoff/NOTES_TWIN.md` — twin-резонатор, caller, grid/oversample.
- `handoff/NOTES_LEVEL.md` — level-path, LUT-нога, res_power протокол, спектральные веса.
- `handoff/nls_dasm/` — 120+ дизассемблей (f_563440, f_563ce0, f529fe0, twin, iface, fft).
- `notes_giant_fft.md` — FFT-планировщики/ядра/twiddle/Cody-Waite.
- `git log` — полная журнальная хронология с номерами коммитов.
- `README.md:13` — актуальный статус (TOTAL).
- `handoff/NOTES_LEVEL.md` (живая голова) + `handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md` — журнал.
- `handoff/BLOCKMAP_529fe0.md` — карта метода 9–19.
- `handoff/NOTES_TWIN.md` — twin краткая справка.
- `handoff/NOTES_CAPTURE.md` — registry heartbeat.
- `notes_giant_fft.md` — FFT/twiddle.