# Изменения после 0.6-pre1 (с 2026-10-15) Замеры — RX 7800 XT / RADV, 1080p, у световых лучей. NVIDIA имитируется на AMD ключом `BB_GUEST_UNCACHED=1` (память игры для видеочипа некэшируемая, как импортированная память хоста на NVIDIA). На настоящей NVIDIA не проверено. ## 2026-10-17: ожидание готовности кадра перед выводом Исправлена маска второго ожидания семафора в `Scheduler::QueueSubmit`: `ColorAttachmentOutput` заменена на `AllCommands`. Вывод читает подготовленный кадр через Transfer blit, который прежнее ожидание не защищало. Ошибка найдена при расследовании одиночных чёрных/серых кадров; исчезновение пользовательского симптома ещё не подтверждено. Сборка, 131 Python-тест, нативные проверки и графический roundtrip проходят. Подробности: `docs/Present_wait_2026-10-17.ru.md`. ## 2026-10-17: битая сцена при первой загрузке с SSR Исправлена регрессия переключения отражений SSR на лету. Применялась только часть патча: инструкции, уменьшающие размеры цели вдвое (`0x22c428d`), оставались, а код прохода уже менялся на SSR. Сбой с цветными блоками воспроизведён при первой загрузке и в Old, и в New, до входа в FSR. Пользователь наблюдал белый кадр при включении Bloom. SSR снова применяется полным патчем при запуске; оба игровых меню предупреждают о перезапуске. Bloom и остальные live-эффекты сохраняют переключение на лету. На RX 7800 XT / RADV проверены нормальные кадры с полным стартовым SSR и с отключённым SSR при включённом Bloom. Подробности: `docs/SSR_corrupt_load_2026-10-17.ru.md`. ## 2026-10-17: GPU-очистка и кеш текстур при повторной загрузке GPU WRITE_DATA размером больше 8 байт теперь помечает текстуру с совпавшим начальным адресом для обновления из памяти. Ранее приватная очистка истории FX могла оставить старую копию изображения в кеше. Найдено при разборе белой сцены после смены сохранения; связь с конкретным случаем и влияние на FPS требуют проверки в игре: `docs/White_after_save_2026-10-17.ru.md`. ## 2026-10-17: SSAO в контекстах без ресурса Исправлен принудительный возврат SSAO в контексты, где игра отключила его из-за отсутствия ресурса. Live-настройка учитывает бит поддержки и выбранный указатель цели. Найдено при анализе SIGSEGV `GXWorker:2 +0x22a3f07` после выбора предмета. Повторение пользовательского сценария ещё требуется: `docs/Inventory_crash_2026-10-17.ru.md`. ## 2026-10-17: курсор Курсор переработан в серебряный ланцет: приглушённая сталь, светлая грань, крючковатая гарда, тёмная рукоять с обмоткой и маленькая бордовая вставка. Убран золотисто-красный градиент. Сохранены размер 32 px, вариант 64 px для HiDPI и исходная точка клика. Геометрия вычисляется один раз при создании курсора; `BB_CURSOR=0` по-прежнему оставляет системный курсор. ## 2026-10-17: GX-heap, общие хуки в Old и пересоздание swapchain - [PR #224](https://github.com/deadinside28/bloodborne_pc/pull/224): каждый из двух GX per-frame command heaps получает 1 MiB вместо 256 KiB. Это запас для команд, чьи неудачные выделения приводили к NULL+8 и NULL в memcpy (#162). Включено по умолчанию; `BB_GX_COMMAND_HEAP=0` возвращает исходный размер. Это отдельные буферы, не кеш ресурсов Лоуренса 160 → 512 MiB. - [PR #214](https://github.com/deadinside28/bloodborne_pc/pull/214): защитные хуки, aspect ratio, мышь и наши исправления загрузки/катсцены устанавливаются также в Old. Только уведомления кеша о GPU-диапазонах и соответствующие allocator/copy/collector хуки требуют New (`fresh_range != nullptr`). - [PR #211](https://github.com/deadinside28/bloodborne_pc/pull/211): resize, повторный acquire и смена HDR пересоздают swapchain под общим submit_mutex, исключая параллельную отправку команд во время ожидания device idle. Подробные результаты проверки и ограничения: `docs/PR_224_214_211_2026-10-17.ru.md`. ## Падение FPS при взгляде на источник света в новой модели (#204, PR #210) Кадр видеочипа у лучей с `BB_GUEST_UNCACHED=1`: 17,0 мс → 10,4 мс. - **Тайлинг в VRAM (PR #210).** `Detiler::TileImage` писал тайлированные тексели прямо в память игры по разбросанным адресам; на NVIDIA каждая запись шла через шину отдельно. Теперь тайлинг идёт в VRAM, затем одна копия. Время пряталось в строке прохода лучей (`vs 637500e0`): у диспетча тайлера не было своей метки в профиле. Теперь есть («tile image …»). Замер: «after the draw of vs 637500e0» 5,53 → 0,06 мс/кадр, кадр 17,0 → 12,9 мс. - **Шейдер читает тайлированную копию из VRAM** (`SynchronizeMemoryFromImage`, `LayerBind`, `ObtainBuffer`), байты за картинкой докопируются. Сам по себе выигрыш мал (3,6 → 3,5 мс/кадр): дорогими были записи. `BB_TILE_BIND_VRAM=0` — читать на месте, как раньше. - **Копия картинки в картинку для целей с «хвостом»** (`IsComputeImageCopy`, `raster_compute_hle.cpp`). Игра копирует цель 960x540 лучей в другую через память шейдером `cs 3d5ebf4e` (`dst[i] = src[i]`), туда и обратно, 5 раз за кадр. Детектор копий целей уже был, но требовал, чтобы буфер был ровно размером картинки, а здесь буфер длиннее на 6 КиБ (0x241800 против 0x240000). Шейдер шёл как есть и писал 2,3 МиБ в память игры каждый раз (блоки этих целей помечены dynamic), 3,4 мс/кадр, а цель потом заново заливалась оттуда. Теперь хвост до 64 КиБ за картинкой допускается и копируется буфером. Проверено сначала в виде отдельного HLE (картинка лучей та же, кадр 12,5 → 10,4 мс), затем перенесено в детектор. `BB_HLE_OFF=1` — выключить, `BB_HLE_STATS=1` / `BB_IMAGE_COPY_LOG=1` — счётчики и журнал. - Осталось: почему блоки целей 960x540 становятся dynamic навсегда после одной записи в смешанный диапазон (общий блок на стыке двух целей); 50-мс остановки с заливкой 14–24 МБ из #204 не проверены. ## Выбор видеокарты в лаунчере - «Режим работы» → «Видеокарта», как в shadPS4: «Авто (имя)» — выбор порта (дискретная первой), затем все устройства Vulkan; встроенная и программная (llvmpipe) подписаны. Для ноутбуков с двумя видеокартами. - Хранится по имени (`gpu` в настройках лаунчера): номер в списке Vulkan может смениться. При запуске имя переводится в `BB_GPU_ID`; не найденная видеокарта — выбор порта. `BB_GPU_ID` из оболочки важнее. - Список даёт `bb-gpu-capabilities --gpus` (тот же загрузчик Vulkan и порядок, что у игры; `run.sh --device` уже учитывал `BB_GPU_ID`). Тесты: `tests/test_launcher_gpu.py`. ## Ночь 2026-10-15 - **#205** (Debian 13, NVIDIA): `launcher/bbport_vulkan.py` ищет `libnvidia-*` рядом с ICD, затем во всех каталогах библиотек; `PickPhysicalDevice` предупреждает, если ICD NVIDIA есть, а устройства 0x10de нет. - **#204** (8 ГБ): порог давления сборщика текстур = min(60 % порога, max(30 % бюджета, 2 ГБ)) (`texture_cache_memory.cpp`): на 8 ГБ 2,7 → ~4,8 ГБ, на 16 ГБ и больше без изменений. - `BB_GUEST_UNCACHED=1` (диагностика): память игры в типе `DEVICE_UNCACHED_AMD` (`VK_AMD_device_coherent_memory`), чтобы воспроизводить поведение NVIDIA на AMD. - `BB_TRACE_SHADER` + `BB_GPU_PROFILE`: метки до и после отрисовок трассируемого вершинного шейдера; копии буферов получают свои метки по классу размера. ## Онлайн: второй модуль на shadPS4 0.20 и пул The Hunter's Dream (2026-10-16) Сетевой стек из форка Supermedo/bloodborne_pc (его онлайн собрал Awaken, BloodBorneButPc): сетевые, NP- и shadNet-исходники shadPS4 v0.20.0 (529ceb3, почти без правок: `gpu/bbnet20/vendor-changes.diff`) и бэкенд The Hunter's Dream (`gpu/bbnet20/thd`, из droogie/bbhost, GPL-3.0-or-later). Старый модуль (`libbbnet.so`, shadp2p + BBSC) не тронут. - **Новый модуль `libbbnet20.so`** (`gpu/bbnet20`): отдельная библиотека, как `libbbnet.so` (скрытые символы, `-Bsymbolic`, `bbnet_init`/`bbnet_resolve`). Рантайм берёт её по `BB_NET_MODULE`; `run.sh` ставит путь сам при `BB_NET_GEN=20` (shadNet 0.20) и `BB_NET_BACKEND=thd` (The Hunter's Dream) и выключает BBSC. - **Интерфейс модуля** (`gpu/bbnet/bbnet.h`, ABI 2): в `BbNetHost` добавлены `thread_exit` и `memory_is_mapped`; необязательные `bbnet_image_mapped` (образ игры перенесён, коды ещё не пропатчены: бэкенд THD читает исходный код) и `bbnet_start` (игра загружена: сессия shadNet / THD стартует здесь, `NpManager::InitializeRuntime`). Вызовы: `src/runtime_net.c`, `src/probe.c`; `runtime_thread_exit` в `src/runtime_thread.c`. - **Лаунчер, страница «Онлайн»**: список «Пул» — Seamless Hunt (seamlesshunt.com, по умолчанию) и The Hunter's Dream (thehuntersdream.com). Для THD: вход через Discord (код подтверждается на сайте, `online.thd_sign_in`), токен хранится в настройках лаунчера, проверка сервера. `scripts/online.py`: `thd-signin`, `thd-overrides`, `thd-check`, `overrides --plain`. - **Идентификация клиента**: shadNet видит `bbport <версия>` (`BB_CLIENT_VERSION`), THD — `bbport` (в форке-источнике было `shadPS4 Awakenchan/v0.20.0` и `BloodBorneButPc`). - Журналы классов (`gpu/common/logging/classes.h`) дополнены классами shadPS4 0.20. - Упаковка: `libbbnet20.so` в AppImage и `runtime-paths`. - Проверено без внешних серверов (адреса на 127.0.0.1:9): модуль грузится в игре, THD доходит до `sceNpMatching2ContextStart`, shadNet 0.20 сообщает о недоступном сервере и отключается, старый модуль с BBSC работает как раньше. **Не проверено**: вход через Discord, игра с живым сервером THD, совместимость с игроками bbhost, кооператив через shadNet 0.20. - Осталось: перенос BBSC (бесшовный кооператив) на стек 0.20 — пока он только в старом модуле; README (EN/RU) не обновлены. ### BBSC на стеке 0.20 (2026-10-16, позже в тот же день) - Seamless Hunt теперь работает через `libbbnet20.so`, а не через модуль shadp2p (он остался как запасной: `BB_NET_GEN=19`). Лаунчер: два пула — Seamless Hunt и The Hunter's Dream. - Перенесено на исходники shadPS4 0.20: игровой код BBSC (без изменений), пакеты сигналинга 0x20/0x22, повтор участников комнаты, маршрут призыва и заголовок размещения хоста в HTTP, UDP-реле (`core/net/p2p_relay.*`, в `p2p_transport.cpp`). Подробности: `docs/BBSC.md`. - Проверено: модуль грузится, BBSC ставит хуки (`BB_ONLINE=1 BB_BBSC=1`, сервер на 127.0.0.1:9). Не проверено: игра вдвоём (summon, следование за хостом, реле), `bbsc.dll` 0.0.1 (закрытый исходник) — его исправления не перенесены. ## Ввод, курсор, свободная камера, эффекты без перезапуска (2026-10-16, вечер) Не проверено в игре (собрано, игра запускается до заставки без сбоев). - **Клавиатура → стик** (`src/runtime_pad.c`). WASD/IJKL больше не «упор стика сразу»: направление на единичном круге (диагональ не длиннее прямой), длина стика нарастает за 70 мс и спадает за 45 мс, угол поворачивается к новому направлению по кратчайшей дуге со скоростью 1100°/с. Короткие нажатия не теряются: событие «клавиша нажата» держится до конца ближайшего чтения геймпада. Подстройка: `BB_KEY_RISE_MS`, `BB_KEY_FALL_MS`, `BB_KEY_TURN_DPS`. - **Курсор** (`gpu/shim/bbport_cursor.*`): свой курсор окна (клинок: кость → золото → кровь, тёмный контур, 32 и 64 px), рисуется кодом. `BB_CURSOR=0` — системный. - **Мышь и свободная камера** (`bbport_guest_hooks.cpp`, `InstallFreeCameraProbe`): пока включён патч «Restore Debug Camera» и камера активна (байт +0xc4 менеджера камеры), мышь крутит свободную камеру через правый стик, а не обычную камеру игры. - **Эффекты на лету** (`InstallHotEffects`, `BB_HOT_EFFECTS=0` — как раньше, патчами при старте): хроматическая аберрация, глубина резкости, размытие в движении, SSAO, сглаживание игры и тени динамических источников переключаются в меню (игровом и порта) сразу. SSAO/размытие/AA/тени — байты +0x2968/9/a/e объекта настроек рендера (адрес берётся в хвосте конструктора, 0x22c254c); аберрация и DoF — те же байты кода, что и в патчах, пишутся туда и обратно. Отражения SSR, заставки, свободная камера и debug menu остаются патчами при старте. ### Клавиатура: бег, кувырок, прыжок, «назад» (2026-10-16) - Space — кувырок: короткое нажатие Круга, даже если Space удерживать (бега от него нет). Left Shift — бег (Круг удерживается). Shift + Space — прыжок: Круг на 70 мс отпускается и нажимается снова, пока идёт бег, как B на геймпаде. Новые входы `sprint` и `dodge` в `bbport.ini` (`key.sprint=`, `key.dodge=`) и в лаунчере (страница управления); `key.circle=` у клавиатуры пуст. - Пока открыто меню игры (`bbgpu_pointer_wanted`), Escape — Круг («назад»), а не Options; вне меню Escape по-прежнему открывает меню игры. Space и Shift в меню ничего не делают. Подсказка кнопки «назад» на клавиатуре показывает Esc. Свободная камера на клавиатуре: F (Cross) + Left Ctrl (L3). - Прыжок (Shift + Space) теперь — как двойной Space на бегу: Круг отпущен 70 мс, нажат 70 мс, отпущен 70 мс, затем удерживается дальше (`BB_JUMP_GAP_MS`, `BB_JUMP_TAP_MS`). Одно отпускание и нажатие только прерывало бег и прыжка не давало. ### Вылет «guest raised debug exception 0xa0020007» (2026-10-16) Это не канарейка стека (так было записано в #207), а собственный ассерт рендерера игры (Yebis, `GPUMath.cpp(3199): nBaseSamples >= 3`, `GPUTextureUtil.cpp(18141): nSamples >= 2`) в расчёте ядра размытия; вызывается из +0xbbc3e0 и +0xc33efe. Значение берётся из данных/интерполяции параметров постэффекта. Теперь `raise_exception` (`runtime_kernel.c`) для этих двух мест пишет строку и возвращается (игра дальше считает, как розничная сборка). `BB_GAME_ASSERT_STOP=1` — как раньше. Корень (почему счётчик отсчётов мал) не найден. - Escape открывал меню игры и сразу закрывал: меню считалось «открытым» при ещё зажатой клавише, и Escape превращался в Круг («назад»). Нажатие, начатое вне меню, остаётся Options до отпускания клавиши (`options_latched`, `src/runtime_pad.c`). ### Отражения SSR на лету, мышь (2026-10-16) **Обновление 17.10:** live-путь SSR отозван из-за повреждения сцены. Используется полный стартовый патч с перезапуском; см. исправление выше. Ниже — история изменения. - **SSR переключается без перезапуска** (`bbport_guest_hooks.cpp`, таблица `hot_code`): байты и константы патча «Enable Screen Space Reflections» пишутся и возвращаются из меню. Размер цели SSR (0x22c428d, половинное разрешение) не трогается — это размер ресурса. **Не проверено**: создаёт ли игра ресурсы SSR только при включённом флаге на старте; если при включении на лету картинки нет, нужен перезапуск (`BB_HOT_EFFECTS=0` — как раньше). - **Мышь** (`window.cpp`): окно держит курсор (захват, `SDL_SetWindowMouseGrab`) всегда, пока оно в фокусе, в том числе в меню игры и при выборе места телепорта; взгляд (относительный режим) берётся сам при фокусе, без клика. F1 отпускает мышь (для другого окна), клик или F1 снова — берёт обратно. - Мышь после меню игры (`window.cpp`): при входе в меню игры указатель ставится в середину окна (всегда одно и то же место), курсор прячется после 1,5 с без движения и в меню игры, а не висит над игрой, пока доигрывает анимация закрытия (игра считает меню открытым ещё ~0,4 с и дольше). - Курсор после меню игры: меню закрывается, когда гаснет объект меню (`flow+0xa88`); хвост «фокус списка» (400 мс, для выпадающих списков без объекта) держал указатель ещё ~0,4 с. Теперь при исчезновении объекта хвост сбрасывается (`InMenu`, `bloodborne_menu_pointer.cpp`); `BB_MENU_POINTER_LOG=1` печатает сигналы со временем. ### Вылет «Bad PM4 packet» при первой загрузке (2026-10-16, не исправлено до конца) 4 сбоя из ~25 запусков, 3 из них — на первой загрузке сейва сразу после старта. Диагностика (`BB_FREE_CHECK`, `BB_WRITE_LOG`) была включена: порт в этот буфер не писал (`Write log: 0 of 51474`). По дампу: пакет `SET_SH_REG` (`c0047602`, тело 5 слов) разобран как пакет с телом в одно слово, его значения стали «заголовком» — то есть к моменту разбора заголовок в памяти был другим, к моменту дампа он уже верный. Метки на этой карте и так пишет процессор (`0.0 labels/frame written by the GPU`), так что `BB_GPU_LABELS` ни при чём. Теперь (`cp_graphics.cpp`, `RunGraphics`): если предыдущий пакет — запись регистров (`SetShReg`, `SetContextReg`, `SetUconfigReg`), заголовок которого в памяти уже другой, он разбирается заново (до 4 раз на буфер), с записью в журнал `PM4: the header at … changed after it was walked`. `BB_PM4_RECOVER=0` — прежнее поведение. Не проверено на живом сбое. ### Чёрный экран после «blur-kernel assertion ignored» (2026-10-16 17:20) Игнорирование ассерта `nBaseSamples >= 3` (см. выше) не годилось: за пятью «ignored» в логе `20261016_141307.log` следовало «GPU stall report: the guest has submitted nothing for 5 s» (чёрный экран, тот же отчёт, что у пользователя RX 580 в #203 — возможно, и у него причина эта же: в его логе ассерта не было видно из-за обрезки). Игра с числом отсчётов 2 уходит в цикл. Теперь на входе функции (`0xbbc3e0`, `InstallKernelSampleClamp`) число отсчётов поднимается до 3 (`BB_KERNEL_CLAMP=0` — выкл.); ассерт больше не должен срабатывать. ### Зависание на Havok и «живость» объектов настроек рендера (2026-10-16 17:40) Лог `20261016_142051.log`: «GPU stall … read pointer 228» при включённом `clamp on` и без игнорируемых ассертов. Стеки всех потоков (SIGUSR2 каждому) показали, что главный поток и пять рабочих потоков Havok стоят на одном ожидании (`0x1067fc5 … 0x420965`, система заданий Havok): игра не видит заданий — признак порчи памяти кучи. Подозреваемый — запись горячих эффектов в объекты настроек рендера, которые игра уже освободила (проверка «байты 0/1» проходит и нулями). Теперь объект считается живым, только если цел блок констант из конструктора (32 байта `+0x2980`, как в данных игры `0x5128a40`); в журнале «written to N of M options objects». `BB_HOT_EFFECTS=0` — без горячих эффектов. Не проверено. - Горячие эффекты, проверка «живости»: хук в хвосте конструктора стоит раньше, чем конструктор копирует блок констант, поэтому свежий объект пишется без проверки блока; остальные — только с целым блоком. Чтение объекта через `process_vm_readv` давало EFAULT (страницы игры защищены от копирования ядром) — теперь через карту рантайма (`runtime_memory_is_mapped`). - `Bad PM4 packet`: прежде чем считать слово плохим заголовком, оно перечитывается до `BB_PM4_WAIT_US` (20 мс); если игра ещё дописывала буфер, в журнале `PM4: the dword at … was not a packet header at first`. Не проверено на живом сбое. ### Лимит кадров «на лету», F3 (2026-10-16 18:10) — кинематика Лоуренса Проверено пользователем: с лимитом 30 FPS (через ограничитель) зависаний нет. В сборнике патчей Nexus #163 (`Bloodborne.xml`) у «60 FPS (With Deltatime v2)» сказано: «You may encounter softlock during Laurence (optional DLC boss) cinematic. Disable patch to progress.»; в shadPS4/ps4_cheats #72 сказано то же про катсцены Лоуренса, Людвига, Ромы и Логариуса. Сделан лимит, который меняется в игре: меню порта (Insert) → «Лимит кадров (сейчас)» и клавиша F3 (30 FPS вкл/выкл). Не сохраняется. `BbFrameRate::SetLimit`. Автоопределение катсцен не сделано (нужна разведка кода игры). Патчи из этого сборника («Cutscene Fight Fix», «no Cutscenes») порту не нужны: у порта свои хуки. - Диагностика зависаний игры: при «GPU stall report» теперь все потоки процесса печатают кадры, регистры, верх стека и по 8 слов у каждого указателя-регистра (`probe.c: dump_frames`, `command_processor.cpp: ReportStall`; один раз за запуск, `BB_STALL_THREADS=0` — выкл.). Нужно для разбора зависания кинематики Лоуренса: главный поток внутри задания ждёт объект синхронизации (виртуальный `Wait`, `0x420965 → 0x76a8e5 → 0x75b25e`), рабочие потоки Havok простаивают. ### Сбои «PM4 type N / end-of-pipe data select» при загрузке — копия буфера по умолчанию (2026-10-16 18:25) Шестой сбой за день, снова при загрузке: теперь `end-of-pipe data select 6` (`20261016_150634.log`), раньше `PM4 type 0/1`. Общее у всех: буфер команд верхнего уровня разбирается на месте, а игра успевает переписать его раньше разбора («игра переиспользовала память до того, как поток дошёл до неё» — так и написано у `BB_COPY_GPU_BUFFERS`). Режим копии при отправке уже был, но выключен. Теперь включён по умолчанию (копия dcb+ccb на отправку, килобайты; `BB_COPY_GPU_BUFFERS=0` — как раньше). Вложенные буферы (IB) не копируются. Не проверено на живом сбое: он случайный (~каждый второй запуск на первой загрузке). ### Зависание кинематики Лоуренса: утечка слотов пула заданий отрисовки (2026-10-16 19:00) Причина найдена по снимку зависшей игры (память читалась через `/proc/PID/mem`): - Шаг исполнителя заданий `0x1067f60`: берёт слот из пула (`0x1056920`, спит, пока свободных нет), проверяет ресурс задания (`0x2165150`), выполняет (`0x2169c30`), возвращает слот (`0x1056990`, единственный вызов — на пути успеха, `0x106807f`). - Оба выхода с ошибкой (ресурс не в нужном состоянии; выполнение не удалось) идут через `0x1068092` с результатом 1 и слотом, оставшимся в `[исполнитель+0x120]`; следующий вызов берёт новый слот и затирает поле: слот потерян. Слотов шесть. В дампе: все шесть флагов пула 0, шесть исполнителей держат по слоту, главный поток и пять рабочих Havok крутятся в ожидании свободного слота, исполнителям персонажей работы нет (их очереди пусты). - Ошибочный выход срабатывает и при обычной загрузке (5+ раз за 40 с в тесте), так что у игры слоты где-то пополняются; в кинематике Лоуренса ошибок за раз больше шести (на кадрах выше 30). Хук `InstallJobSlotFix` (`bbport_guest_hooks.cpp`): на общем выходе `0x1068092`, если результат 1 и слот удерживается, слот возвращается в пул функцией игры (`0x1056990`) и поле обнуляется. В журнале «a failed render job gave its slot back (N so far)». `BB_JOB_SLOT_FIX=0` — выкл. Не проверено на Лоуренсе; почему проверка ресурса `0x2165150` отказывает, не выяснено. - Хук слотов расширен (19:10): во втором зависании (`20261016_153246.log`, хук ошибочного выхода срабатывал) все шесть флагов пула были 0, но ни один исполнитель слот не держал. Слот терялся и там, где `[исполнитель+0x120]` просто обнуляют: деструктор `0x106798a` и сброс `0x1067a5c` (отменённое посреди задание). Теперь перед этим слот возвращается в пул (глобальная переменная `0x54b8788`, её отдаёт `0x1057350`); в журнале «a render job destroyed with its slot gave it back». Не проверено на Лоуренсе. ### Лоуренс: исправлена передача пула в хук возврата слота (2026-10-16, после 19:38) Живой запуск AppImage `20261016_163749.log`, PID 13705: главный поток и пять Havok workers ждут `0x1067fc5`. Чтение памяти подтвердило: пул `0x1115d72ab0`, шесть флагов равны 0, ни один слот не удерживается исполнителем. GPU завершил всю работу (submitted=decoded=72819, очереди пусты). Пять сообщений «gave its slot back» не доказывали возврат слотов. Ошибка оказалась в `InstallJobSlotFix`: `Emitter::SaveVolatile` сохраняет исходный r12 на стек и использует r12 как указатель сохранённого контекста. Последующий `mov rsi,r12` передавал `JobSlotFix` адрес стека вместо пула игры. `0x1056990` не находила слот в таком «пуле», после чего хук обнулял `[runner+0x120]` и печатал сообщение об успехе. Исправлено на `mov rsi,[r12]`: адрес настоящего пула из сохранённого регистра. Автономная проверка `out/dev/job-slot-argument-check.cpp` исполняет обе последовательности машинного кода с настоящим SaveVolatile/RestoreVolatile: старая передаёт стек, новая — заданный адрес пула; r12 восстанавливается в обоих случаях. GPU-библиотека пересобрана. Прохождение катсцены с исправлением ещё требует игрового теста. Почему исходное игровое задание возвращает ошибку выше 30 FPS, этим изменением не установлено. ### После возврата слотов: краш обработчика SRT walker (2026-10-16, запуск 19:42) `20261016_164248.log`: возврат слотов дошёл до 4096, затем массовые чтения нулевых таблиц ресурсов и ассерт `flatten_extended_userdata_pass.cpp:72` (ожидался MOV из памяти). В стеке ассерта адрес `0x767c3b804a7f`, который другой обработчик только что пропатчил. Обработчики параллельных потоков переписывали одну общую инструкцию MOV в XOR; другой обработчик видел уже XOR и падал на проверке MOV. Кроме того, такая подмена навсегда обнуляла данные этой инструкции, даже если в следующем кадре указатель был корректен. Обработчик больше не пишет машинный код: обнуляет RDI/R10 в ucontext только текущего потока, продвигает RIP за MOV, сохраняя флаги. Диапазон готового кода публикуется атомарно, без чтения меняющегося размера Xbyak из сигнального обработчика. Неизвестные инструкции и записи в память не перехватываются. Сообщения ограничены первыми пятью/степенями двойки. `srt-walker-fault-test`: 517 настоящих SIGSEGV, в том числе синхронные отказы двух потоков и восемь параллельных потоков; последующие валидные чтения дают исходные данные, код не изменён. Проверены сохранение флагов, продвижение RIP и отказ от поглощения STORE. Тот же тест с библиотекой из упавшего AppImage падает на проверке восстановления R10. В лог возврата слотов добавлены исходный код ошибки и четыре байта состояния ресурса `+0x38..3b`, чтобы исследовать первичный отказ задания. Почему ресурсы становятся недействительными, ещё не установлено; прохождение катсцены требует повторного теста. AppImage пересобран в 19:46. Дополнительный replay старого корпуса run3 (312 записей, два повтора) даёт тот же итог до/после изменения: 217 same, 95 SPIR-V differs от старого эталона, Info/bindings/unreadable/nondeterministic — 0. Эти 95 различий уже есть с библиотекой предыдущего AppImage; полным прохождением корпуса этот результат не является. Протоколы: `out/srt-walker-fault-build.log`, `out/srt-walker-old-regression.log`, `out/srt-walker-corpus{,-before}.log`, `out/srt-walker-appimage-build.log`. ### Лоуренс: SIGSEGV 139 и первичный отказ аллокатора (2026-10-16, запуск 19:47) Лог `20261016_164701.log`: ошибки заданий имеют код 9 и состояние ресурса `01010201`, после 4096 возвратов слотов идут недоступные SRT-чтения, затем host SIGSEGV в `StageSpecialization+0xfba` (`libbbgpu.so+0x3b1f0a`): `mov r10,[r14+8]`, r14=0. Это `Info::ReadUdReg` при специализации вершинных атрибутов: таблица V# нулевая, обращение к адресу 8. Текущий поток здесь не исполняет SRT walker, его обработчик неприменим. `ReadUdReg` теперь даёт пустой дескриптор для нулевой таблицы и проверяет границы SGPR-пары и прямого чтения user data. Проверка воспроизводит null+8 и последующее корректное чтение. Разбор игрового кода уточнил источник 9: проверка `0x2165150` возвращает 8, а 9 приходит из `0x2169c30 -> 0x26d0d30`: отказ выделения буфера после попытки сборки мусора либо отказ выделения служебного блока учёта. Поэтому прежнее объяснение «не то состояние ресурса» не подтверждается для последних запусков. Связь с недоступными дескрипторами наблюдается по последовательности лога; полная причинная цепочка ещё требует проверки. Добавлены проверяемые по байтам наблюдатели `0x26d0dac`, `0x26d0e82`, `0x26d0ebb`: на отказе пишут размер запроса, тип выделения, функцию аллокатора, состояние объектов; при распознанном range heap — свободные байты и наибольший блок. Наблюдатели сохраняют регистры/флаги и не меняют результат выделения. Они нужны, чтобы различить нехватку памяти, фрагментацию и отказ служебного пула. Точный первичный механизм пока не установлен. Сборка и расширенный `srt-walker-fault-test` прошли: null+8, обрезанная SGPR-пара, выход за прямой user data, корректное чтение таблицы и прежние 517 SIGSEGV. Корпус run3 сохранил прежние 217 same / 95 старых отличий SPIR-V, без отличий Info/bindings и без недетерминизма. Диагностический AppImage собран в 19:52. ### Отдельный сбой загрузки сохранения: PM4 type 0, код 23 (2026-10-16, запуск 19:54) Пользователь подтвердил вылет при загрузке сохранения, без катсцены. В последнем логе `20261016_165450.log` нет отказов render job. CP остановился на нулевом заголовке PM4 в submission 12299: snapshot buffer `0x382fc050`, длина `0x1278`, смещение `0x1b4`. Между правильными пакетами стоят семь нулевых DWORD. Стек: RunGraphics → unreachable `PM4 type 0 packet (0x0)` → exit 23. Это отдельный путь от SRT/139 и слотов Лоуренса. Этот буфер — malloc-копия (`memory type -1`), а старый отчёт искал записи порта именно в её адресах. Поэтому «0 of 74451 logged writes» не доказывает отсутствия записей в исходный гостевой буфер. Ожидание изменения плохого заголовка также перечитывало копию, которая после отправки не обновляется. Добавлена диагностика: при копировании запоминается первое нарушение границ PM4; при сбое выводятся оригинальный адрес, возраст копии, был ли snapshot плохим уже при отправке, текущее слово/окно оригинала и write log по исходному гостевому диапазону. Команды не пропускаются и не заменяются. Без сравнения исходника/копии считать доказанной гонку переиспользования или незавершённую запись нельзя. CP decoder test прошёл. Диагностический AppImage собран в 19:59. Три автоматические загрузки исходной сборки на отдельной копии savedata прошли без Bad PM4; первопричина редкого сбоя ещё не установлена. Скрипт: `out/dev/reproduce-pm4-load.py`, протокол: `out/pm4-load-repro.log`. Вылет воспроизведён на третьей автоматической загрузке именно AppImage (20:02): `out/dev/pm4-first-reproduced-crash.log`. Submission 3536, оригинал `0x1039d4eb84`, смещение `0x114`: snapshot уже некорректен при отправке; через 20,435 мс оригинал и snapshot в окне сбоя совпадают, включая нулевой заголовок. Write log оригинала: 0 записей порта из 20914. Для этого воспроизведения гипотеза позднего переиспользования буфера не подтверждается. NOP `c00a1000` перед ошибкой содержит старые слова ACQUIRE_MEM; в padding NOP старое содержимое допускается, само по себе это не доказывает неверной длины. Нужна трассировка построения буфера, а не восстановление по похожим словам в его данных. `BB_GNM_WRITES_TRACE=1` сохраняет кольцо последних вызовов драйверных строителей команд (адрес, зарезервированный размер, функция); ошибка печатает перекрывающиеся вызовы. Полный плохой snapshot сохраняется в `$BB_DATA_DIR/out/pm4-bad-.snapshot.bin`. Сборка этой диагностики — 20:08. Повторные успешные загрузки не считаются доказательством исправления плавающего сбоя. Повтор с трассировкой (20:13): шестая загрузка AppImage завершилась кодом 23, `out/dev/pm4-load-repro/appimage-load-6.log`, submission 2830. Полный DCB сохранён в `out/dev/pm4-load-repro/out/pm4-bad-2830.snapshot.bin`. Нулевой заголовок на +0x270 уже присутствовал при submit; оригинал через 20,357 мс совпадал с копией. Последний перекрывающийся драйверный строитель — `sceGnmSetCsShaderWithModifier`, 25 DWORD от +0x20c до +0x270. Его последний NOP начинается на +0x240 и корректно заканчивается непосредственно перед плохим словом. Реализация совпадает с сохранённым upstream, три найденных игровых обёртки также продвигают указатель на 25 DWORD. Это локализует границу сбоя, но не доказывает ошибку SetCsShader или длины NOP. Записи HwInit в трассе содержат размер доступного диапазона, а не фактически записанных команд; их широкое перекрытие не является доказательством перезаписи. В этой серии пятая попытка отдельно завершилась 139 до создания CSChrThread0; её нельзя объединять с PM4/23. Успех остальных попыток не подтверждает исправление. Причина формирования некорректного DCB остаётся открытой; пропуска/подмены пакетов нет. ### Продолжение поиска PM4/23 (2026-10-16, 20:26–20:31) Трасса `BB_GNM_WRITES_TRACE` расширена для `BB_GNM_OBSERVE`: кольцо хранит фактический диапазон продвижения указателя игровыми методами, offset функции/вызывающего кода, адрес command buffer, поток, первые 16 слов и результат проверки границ при возврате. Ширина драйверных диапазонов по-прежнему означает переданную ёмкость, а не объём записи. Включение старого observe выявило отдельный сбой установки detour в уже RX-страницу: `PatchImage` теперь временно разрешает запись для каждого изменяемого диапазона и восстанавливает `GuestCodeProtection`. Остановки до старта игры в первой серии observe не являются воспроизведением пользовательского вылета. Сборка AppImage — 20:26; предыдущий cp-decoder-test прошёл. Все шесть загрузок с наблюдением всех 513 функций прошли без Bad PM4 (`out/pm4-native-trace-repro2.log`). При ограничении до индексов 140:155 третья загрузка снова упала: `out/dev/pm4-load-repro/logs/20261016-202938/appimage-load-3.log`, submission 2859, snapshot `out/dev/pm4-load-repro/out/pm4-bad-2859.snapshot.bin`. В начале DCB стоят EVENT_WRITE_EOP (6 DWORD) и WAIT_REG_MEM (7 DWORD), затем на +0x34 слово 0x2000, за ним 0x2f22c и корректный marker NOP. Snapshot уже плох при submit, оригинал через 20,084 мс совпадает. В этом окне нет последнего SetCsShader: прежняя локализация возле его границы не универсальна и не устанавливает первопричину. Поиск расширен на игровые методы построения EOP/WAIT и подготовки отправки. ### PM4/23: установлена конкурентная запись main/GXRenderThread (2026-10-17, 06:38–06:43) `BB_GNM_NATIVE_ONLY=1` включает нативные наблюдатели без анализа покрытия/checksum. Истории записей теперь раздельные для потоков (общий mutex больше не сериализует все нативные строители). При `BB_GNM_WRITES_TRACE=1` также удерживается ограниченная история snapshot (512 записей, максимум 64 МиБ), чтобы искать прежние отправки того же адреса. Это диагностика, не изменение PM4 и не исправление гонки. Первая загрузка с диапазоном наблюдателей 120:170 дала доказательство гонки: `out/dev/pm4-load-repro/logs/20261017-063741/appimage-load-1.log`, submission 7659. Один и тот же CB `0x10282a2850` использовали поток 103568 (SetCsShader, native `0x1075840`, caller `0x26cd5b1`) и поток 103473 (dispatch, `0x1074c50`, caller `0x26b52c1`). SetCsShader пишет 100 байт и его обёртка прибавляет к CB.cursor 100; между входом и возвратом cursor продвинулся на 132 байта. На +100 остались 32 нулевых байта, dispatch начинается на +132. Следовательно, cursor менялся ещё одним потоком внутри вызова; нулевой промежуток не является ошибкой длины driver NOP. Повтор: `out/dev/pm4-load-repro/logs/20261017-064026/appimage-load-6.log`, submission 7660. Сохранён `.threads.json`: 105251 — главный поток (`bb-probe`), 105348 — `GXRenderThread`. Последний записывает шейдер по `0x103a7c8a64`; его наблюдатель видит продвижение на 152 вместо 100 байт. В начале уже EVENT_WRITE_EOP + WAIT_REG_MEM (52 байта), записанные поверх шейдерного участка; главный поток рядом пишет ACQUIRE_MEM через `0x1075e10`, caller `0x26b98f5`. Snapshot некорректен при submit и через 3 секунды. В write log нет записей эмулятора в этот DCB (добавлены и намерения GPU mirror copyback). Механизм вылета локализован: конкурентное построение команд главным потоком и GXRenderThread в общем CB с неатомарным cursor. Почему игра допустила совместный доступ в этом порте (ожидания/границы кадров/контекст вызова), ещё проверяется. Сериализация одних только отдельных Gnm-пакетов или пропуск нулевых слов не обеспечивает правильного порядка shader/user data/dispatch, поэтому такого «исправления» пока нет. Дамп второго запуска: `out/dev/pm4-load-repro/out/pm4-bad-7660.snapshot.bin`. ### PM4/23: изолирована инициализация FX targets (2026-10-17, сборка 06:58) Стек на входе `0x26b4ef0` уточнил источник main-записей в основной контекст: `0xebb960` (инициализация эффекта), три вызова очистки через `0x2168630` на `0xebbc86`, `0xebbca4`, `0xebbcc2`. Вызывающий `0x2276501`, выше `0x22c4fcc`. Основной context `0x10282a2840`, CB `0x10282a2850`; обычные загрузчики в этом же запуске используют отдельный context `0x10282b8f20`. Стек и descriptor: `out/dev/pm4-load-repro/logs/20261017-065307/appimage-load-1.log`. Три targets создаются до всех трёх очисток. Захваченный Gnm RenderTarget (не texture S#): `10dfe600 00f0000f 0000007f 00000000 00060714 000001ce 00020002 ...`. Это RG16F, 2x2 логических пикселя, tiled pitch 128, padded height 64, один слой/sample, 32768 байт на target, без CMASK/FMASK/compression. Clear color (0,0,0,1); хранятся только R/G, поэтому нулевое заполнение даёт нужные значения независимо от tiling. CPU запись в общий CB приводила и к порче cursor, и к отправке команд GXRenderThread до их завершения. Исправление: только эти три проверенных CALL перенаправлены на `FxInitClear`. На главном потоке для подтверждённого RG16F layout/цвета строится собственный WRITE_DATA с нулями, отправляемый в общую graphics queue без обращений к игровому context/cursor и без изменения shader/register state. Порядок работы с GPU сохраняется; исходная функция с flush основного context не вызывается. Для иных форматов/размеров/цветов и иных потоков работает исходный вызов. `BB_FX_INIT_CLEAR=0` отключает этот фикс для контрольного запуска. Обычные очистки не перехватываются. Внутренние временные команды получают обязательную собственную копию в `SubmitGfx`, включая конфигурации с отключённым `BB_COPY_GPU_BUFFERS`; временный vector не остаётся источником для асинхронного декодера. Нативный игровой буфер не меняется и не «чинится» пропуском данных. Добавлены `bbport_fx_init_clear.h` и `fx-init-clear-test`: реальный descriptor, расчёт границ, декодирование WRITE_DATA, неизменность shader/context регистров, отказ для RGBA/нечёрного цвета/MSAA/compression/другого размера/неправильных границ. Проверки прошли: новый тест, cp-decoder-test, srt-walker-fault-test (517 обработанных SIGSEGV). Исходная сборка загрузила сейв, все три targets прошли новый путь. AppImage собран в 06:58. Серия из 12 загрузок AppImage без нативных наблюдателей завершилась успешно: все 36 targets очищены новым путём, после появления CSChrThread0 каждый запуск наблюдался ещё 14 секунд, без Bad PM4, host fault и GPU assertion. Журнал: `out/fx-init-clear-appimage-stress.log`, подробные логи: `out/dev/pm4-load-repro/logs/20261017-065912/`. Снимок сцены после загрузки: `out/dev/fx-init-clear-gameplay.png` (около 100 FPS). Контроль с тем же AppImage и `BB_FX_INIT_CLEAR=0` воспроизвёл код 23 на четвёртой загрузке: нулевой PM4 header в submission 7666, уже повреждённый при submit. Журнал: `out/fx-init-clear-disabled-control.log`; подробный лог и сохранённая копия snapshot: `out/dev/pm4-load-repro/logs/20261017-070531/appimage-load-4.log` и `out/dev/pm4-load-repro/logs/20261017-070531/appimage-load-4.snapshot.bin`. После контроля фикс снова включён: дополнительная загрузка успешна, три targets очищены, ошибок нет (`out/fx-init-clear-final-smoke.log`, подробный лог в `logs/20261017-070758/` внутри `out/dev/pm4-load-repro/`). Тесты использовали отдельную копию пользовательских данных. Результат относится к плавающему PM4/23 при загрузке сохранения. Отдельная проблема allocation error 9 при катсцене Лоуренса этим прогоном не проверена и не объявляется решённой. ### Лоуренс: широкая катсцена без HUD и пик кэша ресурсов (2026-10-17) Покадрово разобрано `video_2026-10-17_07-33-43.mp4` и выполнены независимые контрольные запуски на копии сохранения. Подробности, таблица и пути к кадрам: `docs/Laurence_video_2026-10-17.ru.md`. При лимитах игрового pacer 30 и 100 FPS начало прошло без allocation error 9. При исходном лимите 480 (на экране около 100 из-за VSync) воспроизводятся отказы, недоступные SRT и взрывы вершин; штатный LOD вместо -2 и общий патч Increased Graphics Heap Sizes не помогают. JobProbeWord теперь читает stack locals/image vtables безопасным process_vm_readv: подтверждены реальные запросы 18–41 КБ, которые не помещаются в фрагменты отдельной range heap ёмкостью 160 МиБ. Перехват проверенных инструкций 0x216b20e увеличивает backing allocation именно этого resource cache до 512 МиБ: creation parameters +0x54 меняются до чтения размера для выделения и до настройки диапазона. Другие исходные размеры остаются прежними. Кандидат 320 МиБ был отклонён: он не выдержал того же пика (в том числе 4 МиБ запросы). При 512 МиБ прошёл проверенный фрагмент без отказов; после загрузки число живых диапазонов снизилось до нескольких сотен, большая часть памяти снова свободна. `BB_RENDER_RESOURCE_CACHE_MB=160` — контроль с исходным размером. Сжатие катсцены было отдельным дефектом: камера уже имела aspect 2.370370, но показанный кадр — 1.777778. Катсцена не рисует HUD, поэтому путь композиции не создавал широкого display buffer. OnDisplayPassStart теперь завершает готовую LDR сцену в выходном размере до сброса камеры/depth; с интерфейсом используется прежний путь. После исправления аспект камеры и кадра совпадает (2.370370), FOV/near/far не изменены. Обзор камеры сохраняется широким, ограничения 16:9 для катсцены не добавлено. `BB_SCENE_WITHOUT_UI=0` — контроль со старым выводом. Проверки: ui-composition-test, fx-init-clear-test, cp-decoder-test, srt-walker-fault-test (517 восстановленных faults). Журнал: `out/dev/laurence-final-build-tests.log`. AppImage пересобран (14:29:35 MSK). Финальный `appimage-final-fsr411`: 115 секунд после загрузки, лимит pacer 480, LOD -2, FSR 4.1.1, вывод 2560x1080; нет allocation error 9, SRT faults, Bad PM4 и host fault. Катсцена завершилась, на снимке 90s уже идёт бой с Лоуренсом; на 105s игра продолжает работать. Кэш после пика имеет сотни живых диапазонов и сотни МиБ свободной памяти. Сравнение кадров: `docs/Laurence_compare.html`. ## 2026-10-19: пропадали куски карты после телепорта — кэш ресурсов снова 160 МиБ После телепорта во Сон и обратно не рисовались куски карты: стены, город внизу, дальние здания (замок), а объекты (бочки, ящики, мосты) были на месте. Бисекция по сборкам: test-1016 — целая, 17.10 15:18 и позже — сломана. Причина — увеличение backing allocation кэша ресурсов рендера 160 → 512 МиБ (хук 0x216b20e, сделан для катсцены Лоуренса): эта память забирается у областей, куда грузится карта. Не виноваты (проверено A/B): окклюзия, копии памяти в VRAM, таблица страниц, модель памяти (в Old то же самое), `BB_JOB_SLOT_FIX`, `BB_HOT_EFFECTS`, `model_lod`. С `BB_RENDER_RESOURCE_CACHE_MB=160` всё на месте. Теперь по умолчанию исходные 160 МиБ; `BB_RENDER_RESOURCE_CACHE_MB=512` — прежнее поведение для катсцены Лоуренса на высоком FPS (иначе — F3, лимит 30 FPS). **Обновление 19.10, позже:** с 160 МиБ в катсцене Лоуренса на ~190 FPS вернулись отказы выделения и «взорванные» модели. Теперь кэш снова 512 МиБ, но не из аллокатора игры: игра выделяет свои 160 МиБ как в оригинале (куча для загрузки областей не меняется), а сразу после этого хук (0x216b21d) подменяет результат на блок прямой памяти порта за пределами размера, который видит игра (`runtime_extra_direct_map`, `src/runtime_memory.c`; пул увеличен на `BB_DMEM_EXTRA_MB`, по умолчанию 512; `sceKernelGetDirectMemorySize`, выделения и запросы игры этот запас не видят). Прогон Лоуренса `rc-1`: 0 отказов выделения, без вспышек. `BB_RENDER_RESOURCE_CACHE_MB=160` — кэш игры как есть. Карту после телепорта с этим вариантом ещё нужно проверить.