# BLOCK MAP: FUN_180529fe0 (полная разметка по raw asm, 22v) Источник: `handoff/nls_dasm/f529fe0_full.dis` (soothe_mem.bin, база 0x180000000, диапазон 529c60–52ba00; старый f529fe0.dis был ОБРЕЗАН на 52a813). Регистры: r13 = индекс полосы, r12 = state-ptr ctx+0x440518, esi/r8d = nbin, rbx = nbands ([rsp+0x30]), rdi = ctx. Флаг 0x5408b8 выбирает float/double вариант thunk-операций (семантика пар идентична). ## Исправление адресации (главное) «Скалярные» буферы NOTES 21b — МАССИВЫ пер-полосных векторов (шаг 16 байт): - bands[i] = [ctx+0x540678+i·16] — кривые полос - acc[i] = [ctx+0x5407c8+i·16] — ПЕР-ПОЛОСНЫЙ аккумулятор combine - track[i] = [ctx+0x540768+i·16] — трек основного цикла ## Thunk-таблица (ILT-стаб 180001xxx -> jmp [table + idx*8], idx @1826159a0) Резолв при live idx=4; семантика из тел + NOTES 21b: | стаб float | стаб double | impl | op | |---|---|---|---| | 0x180001f10 | 0x180001c70 | dc40 / **8d60** | dst = B − A (sub) | | 0x180001fa0 | 0x180001940 | ee20→487a0 / **3c40** | fma att/rel половин | | 0x180001d60 | —(встречен в 1b80-паре?) | — / **5a20** | dst += B (add) | | 0x180001970 | — | **3f40** | sub (float) | | 0x1800019a0 | — | **4200** | mul scalar? (делегирует 181a63fe0) | | 0x180001a00 | — | **4720** | mul | | 0x180001a60 | — | **5160** | sub DOUBLE | | 0x180001850 | — | **25e0** | add DOUBLE | | 0x180002000 | 0x180001c40 | ? | axpy-класс (band ⊕ track) | | 0x180002060 | 0x180002120 | 10860/11940 | transform со скаляром xmm3 | | 0x180002270 | 0x1800022a0 | 14c40/15060 | transform со скаляром xmm0/xmm1 | | 0x180002030 | 0x180001d30 | ? | transform float/double | | in-place кернелы | | 140950/140980/1409b0/1409e0/140a40/140ad0/140b00/140b60 | bigkernel-семейство (exp2/mask) | ## Карта блоков ### Пролог - `52a03a–52a396`: PRNG-пролог. LCG state @ctx+0x2404e0, шаги +0x3cdca, +0x140236, +0x10d56, +0xdf6b6, +0xa8c5e, +0x72916; LUT ptr @ctx+0x5408b0; константы 262b704/262b5c8(int)/262b700(int)/24c3c58. Результат: индексы iVar7/iVar8 ([rsp+0x130]/счётчики) и стартовая позиция цикла. Live-поведение залочено раньше (fVar30=1). - `52a397`: r14 = &bands[0]. ### Pre-combine #1 (52a397–52a421) - `52dbc0(f6f8_data, bands[0], nbin)` — copy bands[0] → f6f8. - цикл i=1..nbands−1 по массиву @0x540688 (= &bands[1]!): transform(f6f8, bands[i]) через 20f0(float)/1850(double=25e0 ADD). ⇒ **f6f8 = Σ_{i≥1} bands[i]** (или min/max — точный op 20f0 не залочен, кандидат ADD по double-паре!). ### Нормировка + mix-вес (52a421–52a458) - `52d920(f6f8, xmm11/[rsp+0x148](int), nbin)` — f6f8 /= K. - `xmm7 = powf([ctx+0x2c], xmm13)` — mix^p. ### Pre-combine #2 (52a45e–52a4f6) по всем полосам - band[i] *= (xmm11 − xmm7) [thunk 2030/1d30] - f6f8 ⊕= ... с весом xmm7 [thunk 1fd0/2150] ⇒ взвешенное смешение кривых полос ДО основного цикла. ### Основной цикл по полосам (52a4fb..52b3c7, тело с 52a580) На каждую полосу i (r13): 1. `scale` (52a583–52a5c4): s = xmm11·[0x540870]; если флаг 0x5408b8: s ← exp(PRNG)-ветка; затем ·[0x54088c]. Читается track_i (@0x540768[i]) в [rsp+0x40]. 2. `transform(bands[i], s)` (52a5cc–52a607): bands[i] *= s [2030/1d30]. 3. флаг-оп (52a608–52a645): transform(bands[i], bands[i]) через 140980/1409b0 (bigkernel, вероятно exp2-кернел) — только при флаге. 4. `52d650(state@0x440518, f6f8, bands[i], nbin)` (52a646–52a658) — БИДИР-IIR #1: band → f6f8 (double, коэф down@0x440528/up@0x4c0528, acc@0x540528, reset внутри). 5. Инлайн БИДИР-IIR #2 (52a65d–52a85a, только при флаге; иначе прыжок на 6): вход f6f8 → выход bands[i] (тот же state 0x440518, reset отдельно). 6. `transform(f6f8 ⊕ bands[i])` (52a85b–52a896) [1970/1a60 = 3f40 SUB float / 5160 SUB double]: f6f8 −= bands[i]? (аргументы rcx=f6f8, rdx=band). 7. **БИДИР-IIR #3** (52a897–52aa6e, инлайн): state base 0x3404f8 (down@0x2404f8, up@0x2c04f8, acc@0x3404f8), длина из поля ctx+0x2404e8, IN-PLACE по bands[i]. Reset каждый вызов. 8. `op(bands[i], скаляр xmm9=0?)` (52aa6e–52aaaf) [2060/2120]. 9. Ветка флага==0 (52aabc–52ab90): - op(vec@0x540698, f6f8, scalar=(конст1 − [0x54087c])) [1a00/1be0 = mul] - op(f6f8, scalar=[0x54087c]·xmm10) [2270/22a0] - bigkernel `140b60/140950(bands[i], f6f8, bands[i], nbin)` 10. **COMBINE (52ab90–52abd7)**: acc_i = [0x5407c8+i·16]; `op(rcx=acc_i, rdx=bands[i], r8=f6f8, r9=nbin)` [1f10→dc40 float-SUB / 1c70→8d60 double-SUB]: **f6f8 = bands[i] − acc_i** ✓ NOTES 21b. 11. Половины f6f8 + fma (52abd7–52ad04): - rbp = f6f8 + nbin (верхняя половина); op(f6f8_low?, ...) [2060/2120, scalar 0] - op(rcx=f6f8_upper, rdx=vec@0x5406c8, r8=acc_i) [1fa0/1940 = **3c40 fma**] - op(rcx=f6f8_lower, rdx=vec@0x5406e8, r8=acc_i) [1fa0/1940] ⇒ fma с коэф-массивами 0x5406c8 (upper/att) и 0x5406e8 (lower/rel) ✓. 12. `acc_i += bands[i]` (52acf4–52ad03) [1b80→6840 / 1d60→**5a20** add] ✓. 13. Ветка флага!=0 (52ad04–52ae57): повтор п.9 с теми же адресами (0x540698, f6f8-blend, bigkernel 1409e0/140ad0). 14. `op(bands[i], скаляры 0x1824c4680 / xmm13)` (52ae58–52ae56 хвост). 15. `bands[i] ⊕= track_i [rsp+0x40]` (52ae74–52ae8a) [2000/1c40]. 16. `op(vec@0x5406a8, bands[i])` (52ae8f–52aecа) [2000/1c40]. 17. флаг → bigkernel in-place 140980/1409b0 (52aecb–52af08). 18. **БИДИР-IIR #4 ×2** (52af09–52b2af и повтор 52b0de–52b2af): state base 0x440510 (down@0x340510, up@0x3c0510, acc@0x440510), длина ctx+0x340500, in-place bands[i]. ДВА прохода подряд (каждый с reset). 19. Финальные scale/op полосы (52b2af–52b3af): - скаляр `[ctx+0x1c+i·4] · xmm14` → transform [2030/1d30] - bigkernel in-place 1409e0/140ad0 (при флаге) ИЛИ op(scalar=xmm12/xmm8) [2270/22a0] + `140a40/140b00(band,band,scalar)` 20. Инкремент i (52b3af–52b3c7), выход при i ≥ nbands. ### Эпилог (52b3cd–52b935) - GUI-snapshot блок (флаг 0x2404bc): копии векторов @0x540728 ← bands[i] (совпадает с consumers_out.txt:144-183). - Остаток до 52b935: восстановить при транскрипции (вероятно dry/wet + mirror). ## Состояния бидир-IIR (4 базы, лейаут {down,+0x80000 up,+0x80010/+0x100010 acc}) | # | база | downCoef | upCoef | acc | длина | |---|------|----------|--------|-----|-------| | 1–2 (52d650+инлайн) | 0x440518 | +0x10 | +0x80010 | +0x100010 | nbin | | 3 | 0x2c04f8-группа | 0x2404f8 | 0x2c04f8 | 0x3404f8 | поле ctx+0x2404e8 | | 4 ×2 | 0x340510/0x3c0510 | 0x340510 | 0x3c0510 | 0x440510 | поле ctx+0x340500 | ## Открытые вопросы к транскрипции 1. Точный op thunk 20f0/1850 в pre-combine #1 (ADD — кандидат). 2. Аргументный порядок 2000/1c40 (axpy) и 2060/2120. 3. Семантика bigkernel-семейства 1409xx/140axx/140bxx (какой где: exp2(-x), exp2(-x)·blend, mirror?). 4. Что пишется в track_i @0x540768[i] (writer вне метода — искать отдельной охотой; кандидат FUN_18052e9b0). 5. Эпилог 52b4eb–52b935. ## ДОПОЛНЕНИЕ 22v: декод генератора коэффициентов ### FUN_180530b60 (апдейт параметров, size=462) - fVar2 = powf([ctx+0x540878], K1); fVar3 = powf([ctx+0x54087c], K2) (оба live = 0.5 — параметры attack/release-класса); - лог-интерполяция констант → [ctx+0x540894] и [ctx+0x540898] (тау-скаляры); - вызовы генератора на все три состояния: - 180533340(ctx+0x2404e8, tau=0x540894-ветка, C=DAT_1824c459c, sr, ...) - 180533340(ctx+0x340500, tau=[0x540894], ...) - 180533340(ctx+0x440518, tau=[0x540898], ..., mult=DAT_1824c4564) ### FUN_180533340(state, tau, C, sr, p, mult) — ГЕНЕРАТОР КОЭФ. БИДИР-IIR ``` sr' = sr · DAT_1824c3d8c acc = 0; downCoef[0] = 1.0 (int-пара {0, 0x3ff00000} @+0x10) n = state[0]; fc_norm = (C/sr')·n for i in 1..n-1: g = (i <= fc_norm) ? fc_norm/i : powf(fc_norm/i, p) # частотный варп! c = 1/(g·tau/mult + 1) up[i] = exp(c·g·tau/state[2] · DAT_1824c46b8) down[i] = 1 − up[i] ``` ЛЕЙАУТ СОСТОЯНИЯ СОШЁССЯ: downCoef[] @base+0x10, upCoef[] @base+0x80010 (pdVar6[0x10000] = +0x80000 байт), acc — скаляр в хвосте. Это ЧАСТОТНО- ЗАВИСИМОЕ сглаживание маски: сила растёт к низким бинам (1/i) с питч-законом выше кросса. Вот где «размазывание» нотча в реале — НЕ плоский IIR, который мы отвергли офлайн (22u), а пер-биновый варп! ### Статус сбора (решение: без коммитов до первой валидации) Незакоммичено: f529fe0_full.dis, BLOCKMAP_529fe0.md, thunk-резолвер, правки NOTES_LEVEL (22v будет добавлена при транскрипции). Следующий шаг — транскрипция process_frame_faithful по этой карте. ## ДОПОЛНЕНИЕ 23b: резолв ILT + декод FIR-цикла (52b550–52b8bb) ### Резолвер `scripts/ilt_resolve.py`: стаб = `mov eax,[rip+idx]; lea r10,[rip+tbl]; jmp [r10+rax*8]`; live idx=4 у всех; таблицы @0x1826xxxx. Полная таблица «стаб→impl»: | стаб | impl | роль (уточн.) | |---|---|---| | 0x180002210 | 0x136e0 | copy scratch→FIR? (валидатор → 39de0/3a040/3a220) | | 0x180002180 | 0x125e0 | complex-op A/C float (воркер 3a4a0; r9 НЕ передаётся ⇒ длина из объекта) | | 0x180001bb0 | 0x6a40 | complex-op A/C double | | 0x180001a90 | 0x5560 | complex-op B/D float — ЧИСТЫЙ FMA (fma=28, mul/add=0!) | | 0x1800019d0 | 0x4340 | complex-op B/D double | | 0x180001880 | 0x2c60 | paired-scalar op 1 (флаг-ветка) | | 0x180001ca0 | 0x9380 | paired-scalar op 2 | | 0x180001df0 | 0xb3c0 | final op float | | 0x180001f70 | 0xe360 | final op double | | 0x180140a10 | 0x141400→[1826181d8]=**0x1802a24c0** | дизайн float | | 0x180140a70 | 0x141580→[1826183c8]=**0x1802fa420** | дизайн double | | 2030/1d30 | ffe0 / 9be0 | scalar-transform | | 2270/22a0 | 14c40 / 15060 | scalar-transform-2 | | 2000/1c40 | fb60 / 8700 | axpy-класс | | остальное | dc40,8d60,5a20,4720,4200,3f40,5160,25e0,ee20,3c40 | как в BLOCKMAP ✓ | ### Дизайн-тело 0x1802a24c0 (float, вход=rcx bands[i], выход=rdx scratch@0x540628) Функция входа 0..0x675 (~1.6КБ), далее сиблинги. Алгоритм = **ВЕКТОРНЫЙ LOG2** (range reduction бит-трюком с 0.6666667, полином Хорнера на YMM-константах −0.5, 0.3333656, −0.2500466, 0.198225, −0.1646246, 0.1696488, −0.1517721, реконструкция ×ln2=0.6931472; константы @FN+0x1cdfac0..d20). Маскированный хвост через vmaskmovps/popcnt-таблицы (@0x181F8xxxx). ИТОГ: **scratch = log(bands[i])**. Диспетчер 535a70 делает swap аргументов (rcx↔rdx) перед прыжком! ### Структура FIR-цикла (полностью залочена) На полосу: 2×scalar-transform(bands[i], xmm8·[540888] при флаге 540890==0, иначе xmm8; затем xmm12−xmm8) [ср. decomp 184-185: A=[0x540874]−expf(K), wet=[0x540888]] → DESIGN=log(bands[i])→scratch → copy(scratch→FIR@0x540668) [2210] → opA(FIR,buf548,buf598) [2180/1bb0] → FIR[n]=0 (n=0x540534=4096!) → 52d920(&FIR[1], xmm13, n/2−1) деление; 52db50(&FIR[2049], xmm9, n/2−1) → opB [1a90/19d0] → bigkernel in-place 140b30/140aa0 (n/2+1) → opC [2180/1bb0] → FIR[n]=0 → 52d990(FIR, WIN_freq+n/2, n/2) УМНОЖЕНИЕ на **падающую половину Hann** (WIN_freq=wperiodicHann4096: w[1024]=0.5,w[2048]=1.0!) → 52db50(&FIR[2048], xmm9, n/2) → opD [1a90/19d0] → FIR[0]=1.0, FIR[1]=0 → флаг-ветка: парно-скалярные 1880/ca0 → dry/wet scale ×[0x540888] над **2n флоатов** → final(df0/f70) с сохранённым track_i ([rsp+0x138][i]). ### Живые буферы (firtrace.py по s1/s2 + свежие захваты dual_b1q_0.5) - 0x540658 WIN_freq = периодический Hann(4096), пик 1.0 @bin2048 (НЕ рамп 0.5→0.8!) - 0x540758 freqaxis = линейный, шаг ≈11.713 Гц (≈48000/4096=11.71875, лёгкое занижение — похоже на накопительную ошибку f32 при построении суммой) - 0x540598 = SIMD-маски: 8×1.0 / 8×0.0 периодом 16 флоатов, ровно 1024 шт (YMM lane-select для комплексных кернелов) - 0x540550 NULL (float-путь активен, флаг 0x5408b8=0); 0x540628 scratch нулевой между колбеками; 0x540668 FIR = комплексная единица (1,0)×2049 бинов МЕЖДУ колбеками — кернел потребляется и сбрасывается внутри колбека - 0x5406f8 хранит аудио-масштабный сигнал (~±0.1) между колбеками — overlap/STFT буфер conv-движка (не «нулевой acc»!) - 0x540788 vs 0x5407f8 различаются (max 1.108; @171: 1.751 vs 1.409) — две РАЗНЫЕ кривые; ни одна не достигает нужных 3.89×@171 (11.82 дБ) ⇒ применённый фильтр ≠ поточечная копия любой из живых кривых (подтверждение разрыва 22y) ## ДОПОЛНЕНИЕ 24l: точный декод FIR-цикла по дизасму (52b550–52b8bb) + константы Инструмент: scripts/disasm.py (capstone, base 0x180000000 над soothe_mem.bin). ILT-резолв bigkernel-стабов даёт impl в 141100..141940 — все они IAT-thunk массивы (`mov rax,[rip+..]; jmp rax`) вне дампа ⇒ тела за импортами; НО последовательность и КОНСТАНТЫ цикла видны полностью: ### Пошагово (float-ветка, флаг 0x540890==0): ``` 52b5a3: xmm6 = xmm8 · [ctx+0x540888] 52b5bf: th2030(bands[i], xmm6, n) ; bands *= 1.0·s888 (xmm8=double 1.0!) 52b5f6: th2270(bands[i], xmm7, n) ; scalar-transform-2 52b62f: 535a70(scratch@540628, bands[i], n/2+1) ; DESIGN: swap→scratch=log(bands) 52b644: th2210(rcx=scratch, rdx=0, r8=FIR, r9=n/2+1) ; копия (+упаковка?) 52b696: FIR[n]=0 ; float-индекс n=4096 52b69e: 52d920(&FIR[1], xmm13=-1.0, n/2-1) ; РАЗДЕЛЕНИЕ НА −1 ⇒ ИНВЕРСИЯ ЗНАКА бинов 1..n/2! 52b6ab: 52db50(&FIR[n/2+1], xmm9=0, n/2-1) ; ОБНУЛЕНИЕ верхней половины до exp! 52b6e1: opB = th1a90(FIR, buf548, mask598, n/2-1) ; FMA-complex (twiddle!) 52b716: BIGKERNEL 140b30(FIR, FIR, n/2+1) ; EXP in-place (тело за IAT) 52b74b: opC = th2180(FIR, buf548, mask598, n/2-1) 52b76d: FIR[n]=0 52b77c: 52d990(FIR, WINfreq+n/2, n/2) ; окно: th2000-класс (FMA axpy!) 52b78c: 52db50(&FIR[n/2], xmm9=0, n/2) ; верхняя половина ×0 снова 52b7ba: opD = th1a90(...) 52b7d4: FIR[0]=1.0f; FIR[1]=0 если флаг f890!=0: 52b803: th1880(FIR, {xmm10,xmm9}, n) ; парно-скалярный с (−1|0.8, 0) 52b81f: th1ca0(FIR, {xmm12,xmm9}, n) ; парно-скалярный с (1.0, 0) 52b857: th2030(FIR, s888, 2n) ; ×wet (=1 live) 52b893: final df0(FIR, track_i, n) ``` ### Константы (статические значения из дампа): | reg | адрес | значение | роль | |-----|-------|----------|------| | xmm8 | 1824c4140 (double) | 1.0 | множитель шага 1 = NO-OP при s888=1 | | xmm13 | 1824c46a0 (double) | −1.0 | делитель бинов 1..n/2 (ИНВЕРСИЯ!) | | xmm9 | xorps | 0.0 | обнуление верхних половин | | xmm10 | 1824c4680 / 1824c3e28 | −1.0 / **0.8** | парный скаляр (0.8 активен в цикле) | | xmm12 | 1824c3ea4 | 1.0 | парный скаляр 2 | | xmm14 | 1824c4674 | −0.7 | (использование вне FIR) | | xmm15 | 1824c4670 | −0.5 | (использование вне FIR) | | xmm7 | вычисл. | sens·[540870]-цепочка | аргумент th2270 | ### СЛЕДСТВИЯ (меняют понимание построения кернела): 1. Перед EXP: бины 1..n/2 = −log(bands) ⇒ после exp = **1/bands** (обратная величина!), бины выше = exp(0)=1. Экспонента применяется НЕ к спектру маски напрямую — вокруг неё twiddle-FMA стадии (ops B/C/D = радиальные проходы FFT-класса над упакованным вещественным спектром; buf548 = cos/sin таблица, mask598 = lane-select). 2. «×0.984» из 23d НЕ найден как константа цикла — либо внутри exp-IAT-обёртки, либо следствие нормировки twiddle-стадий. Требуется численная репликация пайплайна против живых захватов (rendersnap2 ph*.npz содержат готовые пары). 3. Шаг 1 и шаг ×s888 — no-op при дефолтных параметрах (s888=1, live 24j). 4. 52d920/52d990/52db50 = тонкие обёртки: 920→th2030/1d30 (scalar-op), 990→th2000/1c40 (axpy!), db50→то же. «УМНОЖЕНИЕ на окно» реализовано axpy-кернелом, «деление» — scalar-op. 5. Ops A–D работают над дескрипторными векторами (проверка тега [obj]==6 в 4ca80; ошибка 0xfffffff3 при несоответствии). ### Динамика (попытки перехвата; окружение) - realtime-playback НЕ тикает DSP (нет аудио-девайса; треды спят в futex); кривые 22y = результат инициализации при загрузке проекта - `-renderproject`: полный цикл init+render+exit хоста занимает ~0.8 c (host@1.2s, workers@1.4s, wav@1.6s, exit@2.0s); обработка идёт в окне ~0.5 c - INT3-ptrace: SEIZE+TRACECLONE обязательны до CONT (иначе untraced thread ловит SIGTRAP и убивает процесс — подтверждено); DR-брейкпоинты: DR0@user+0x380, но POKEUSER DR7 даёт EIO. Скрипты: fntrace*.py, fnhw.py, fnall.py, firstop.py, firtrace.py, hotips.py (см. scripts/) - Вывод: FUN_180529fe0 и 52d650/536300/52e260 НЕ ловятся в рендер-окне — маск-цепь выполняется при ЗАГРУЗКЕ/изменении параметров, стационарный рендер использует закэшированный кернел; либо трассировать надо момент инициализации - Выход плагина НЕдетерминирован: md5 двух свободных рендеров различен при одинаковом rms (PRNG-дизеринг из LCG-прологов) — метрика только спектральная!