Пост про дни с седьмого по одиннадцатый остановился 21 октября в 12:11: на карте исследования 36 строк, 35 из них после ревью, а первому сквозному запуску спецификаций ядра до планки не хватало около шестнадцати сессий. Этот пост доходит до 27 октября, 18:44. Теперь на карте 42 строки, и 38 из них после ревью. Цепочка от сида игры до предмета на полу прошла свою планку в тот же день после обеда. У фазы навыков, третьей фазы симуляции, есть спецификация после ревью. У четвёртой, анимации и тайминга кадров, есть находки и первый запуск. Пятая, ИИ монстров, одобрена, а за ней добавлена шестая.

Шесть дней, из них рабочих три. 21-го линии работали до 19:30 с небольшим; 25-го — около полутора часов вечером; 27-го — с 06:42 до отсечки. 22-го, 23-го, 24-го и 26-го не было ни одного коммита: у меня кончился недельный лимит. Почти всё, о чём пойдёт речь, случилось 27-го: из 146 строк, добавленных в журнал ревью за эти дни, 117 датированы этим днём. Между ними, в три часа ночи 25-го, кончилось летнее время, так что время 21-го указано по UTC+2, а остальное — по UTC+1.

Я и на этот раз не играл. Что я делал, по записям: 21-го, уже после отсечки прошлого поста, шесть раз просил поставить линии на паузу, потому что кончался недельный лимит. 25-го одобрил два плана и порядок работы, третий придержал и вычеркнул одну из сессий, которые оставили мне сыграть самому, — запись, оставшуюся от плана 0042. В тот же вечер линии остановил мой лимит использования. 27-го я вписал одну заметку в порядок запусков, попросил ускорить гейт коммитов, одобрил план по ИИ монстров и его решение о рамках, согласился опробовать новую схему ревью и одобрил план, который починил гейт.

Карта работы

Карта исследования к каждой отсечке

  • не начато
  • в плане
  • находки
  • черновик спецификации
  • спецификация после ревью

3 спецификация после ревью 1 находки 1 в плане 23 не начато из 28

Ядро

  • rng спецификация после ревью
  • items/tc находки
  • items/quality не начато
  • items/affixes не начато
  • items/uniques-sets не начато
  • items/properties не начато
  • monsters/pool не начато
  • monsters/spawn не начато
  • monsters/special не начато
  • drlg/overview в плане
  • drlg/preset не начато
  • drlg/maze не начато
  • drlg/outdoor не начато
  • drlg/warps не начато
  • objects не начато

Опоры

  • data/excel спецификация после ревью
  • data/calc не начато
  • data/strings не начато
  • rng/seeds спецификация после ревью
  • drlg/collision не начато
  • save/d2s не начато

Пути создания

  • items/vendors не начато
  • items/cube не начато
  • items/sockets-runewords не начато
  • items/quest-drops не начато
  • monsters/stats не начато
  • hirelings не начато
  • player/creation не начато

28 спецификация после ревью 1 в плане из 29

Ядро

  • rng спецификация после ревью
  • items/tc спецификация после ревью ↑
  • items/quality спецификация после ревью ↑
  • items/affixes спецификация после ревью ↑
  • items/uniques-sets спецификация после ревью ↑
  • items/properties спецификация после ревью ↑
  • monsters/pool спецификация после ревью ↑
  • monsters/spawn спецификация после ревью ↑
  • monsters/special спецификация после ревью ↑
  • drlg/overview спецификация после ревью ↑
  • drlg/preset спецификация после ревью ↑
  • drlg/maze спецификация после ревью ↑
  • drlg/outdoor спецификация после ревью ↑
  • drlg/warps спецификация после ревью ↑
  • objects спецификация после ревью ↑

Опоры

  • data/excel спецификация после ревью
  • data/calc спецификация после ревью ↑
  • data/strings спецификация после ревью ↑
  • rng/seeds спецификация после ревью
  • drlg/collision спецификация после ревью ↑
  • save/d2s спецификация после ревью ↑

Пути создания

  • items/vendors спецификация после ревью ↑
  • items/cube спецификация после ревью ↑
  • items/sockets-runewords спецификация после ревью ↑
  • items/quest-drops спецификация после ревью ↑
  • monsters/stats спецификация после ревью ↑
  • hirelings спецификация после ревью ↑
  • player/creation спецификация после ревью ↑

Симуляция

  • combat/formulas в плане

35 спецификация после ревью 1 находки из 36

Ядро

  • rng спецификация после ревью
  • items/tc спецификация после ревью
  • items/quality спецификация после ревью
  • items/affixes спецификация после ревью
  • items/uniques-sets спецификация после ревью
  • items/properties спецификация после ревью
  • monsters/pool спецификация после ревью
  • monsters/spawn спецификация после ревью
  • monsters/special спецификация после ревью
  • drlg/overview спецификация после ревью
  • drlg/preset спецификация после ревью
  • drlg/maze спецификация после ревью
  • drlg/outdoor спецификация после ревью
  • drlg/warps спецификация после ревью
  • objects спецификация после ревью
  • chain находки

Опоры

  • data/excel спецификация после ревью
  • data/calc спецификация после ревью
  • data/strings спецификация после ревью
  • rng/seeds спецификация после ревью
  • drlg/collision спецификация после ревью
  • save/d2s спецификация после ревью
  • drlg/lifecycle спецификация после ревью

Пути создания

  • items/vendors спецификация после ревью
  • items/cube спецификация после ревью
  • items/sockets-runewords спецификация после ревью
  • items/quest-drops спецификация после ревью
  • monsters/stats спецификация после ревью
  • hirelings спецификация после ревью
  • player/creation спецификация после ревью

Симуляция

  • combat/formulas спецификация после ревью ↑
  • stats/aggregate спецификация после ревью
  • stats/states спецификация после ревью

Правила игры

  • quests/progression спецификация после ревью
  • quests/travel спецификация после ревью
  • items/economy спецификация после ревью

38 спецификация после ревью 3 находки 1 в плане из 42

Ядро

  • rng спецификация после ревью
  • items/tc спецификация после ревью
  • items/quality спецификация после ревью
  • items/affixes спецификация после ревью
  • items/uniques-sets спецификация после ревью
  • items/properties спецификация после ревью
  • monsters/pool спецификация после ревью
  • monsters/spawn спецификация после ревью
  • monsters/special спецификация после ревью
  • drlg/overview спецификация после ревью
  • drlg/preset спецификация после ревью
  • drlg/maze спецификация после ревью
  • drlg/outdoor спецификация после ревью
  • drlg/warps спецификация после ревью
  • objects спецификация после ревью
  • chain спецификация после ревью ↑

Опоры

  • data/excel спецификация после ревью
  • data/calc спецификация после ревью
  • data/strings спецификация после ревью
  • rng/seeds спецификация после ревью
  • drlg/collision спецификация после ревью
  • save/d2s спецификация после ревью
  • drlg/lifecycle спецификация после ревью

Пути создания

  • items/vendors спецификация после ревью
  • items/cube спецификация после ревью
  • items/sockets-runewords спецификация после ревью
  • items/quest-drops спецификация после ревью
  • monsters/stats спецификация после ревью
  • hirelings спецификация после ревью
  • player/creation спецификация после ревью

Симуляция

  • combat/formulas спецификация после ревью
  • stats/aggregate спецификация после ревью
  • stats/states спецификация после ревью
  • skills/dispatch спецификация после ревью
  • skills/missiles спецификация после ревью
  • skills/catalogue находки
  • anim/timing находки
  • anim/movement находки
  • monsters/ai в плане

Правила игры

  • quests/progression спецификация после ревью
  • quests/travel спецификация после ревью
  • items/economy спецификация после ревью

Стрелка отмечает строку, которая сдвинулась с прошлого снимка. Источник: docs/specs/README.md на коммитах 8faba4f2, 33dc648, 483728e и a2d31a99. Все шесть новых строк — в полосе «Симуляция».

Показать таблицей
Подсистема День 1День 6День 11День 17
rng спецификация после ревьюспецификация после ревьюспецификация после ревьюспецификация после ревью
items/tc находкиспецификация после ревьюспецификация после ревьюспецификация после ревью
items/quality не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
items/affixes не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
items/uniques-sets не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
items/properties не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
monsters/pool не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
monsters/spawn не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
monsters/special не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/overview в планеспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/preset не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/maze не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/outdoor не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/warps не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
objects не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
chain ——находкиспецификация после ревью
data/excel спецификация после ревьюспецификация после ревьюспецификация после ревьюспецификация после ревью
data/calc не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
data/strings не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
rng/seeds спецификация после ревьюспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/collision не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
save/d2s не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
drlg/lifecycle ——спецификация после ревьюспецификация после ревью
items/vendors не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
items/cube не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
items/sockets-runewords не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
items/quest-drops не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
monsters/stats не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
hirelings не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
player/creation не начатоспецификация после ревьюспецификация после ревьюспецификация после ревью
combat/formulas —в планеспецификация после ревьюспецификация после ревью
stats/aggregate ——спецификация после ревьюспецификация после ревью
stats/states ——спецификация после ревьюспецификация после ревью
skills/dispatch ———спецификация после ревью
skills/missiles ———спецификация после ревью
skills/catalogue ———находки
anim/timing ———находки
anim/movement ———находки
monsters/ai ———в плане
quests/progression ——спецификация после ревьюспецификация после ревью
quests/travel ——спецификация после ревьюспецификация после ревью
items/economy ——спецификация после ревьюспецификация после ревью

Одна строка сдвинулась, шесть новых. chain перешла из находок в «после ревью». Две строки навыков, диспетчеризация и снаряды, новые и сразу после ревью: их добавили во время фазы навыков, а спецификация по ним прошла ревью 27-го. У третьей, каталога всех нумерованных функций навыков, находки готовы, а спецификации пока нет. У фазы анимации две строки с находками: тайминг и перемещение. А monsters/ai стоит на карте как запланированная: план одобрен, первая фаза прошла ревью, находок нет.

От сида к миру, запуск 2

В прошлом посте план 0048 остановился на 1 222 заселениях комнат из 2 000 и двух расхождениях в модели объектов. Ревью объяснило их так: это монстры, созданные заново после того, как их комнату усыпили и разбудили, и залогированной отметки у них нет. Запуск 2 закрыл оба вопроса. Пересоздания идут через RoomInactiveUnitsRestore и отмечают карту коллизий теми же вызовами UNIT_CollMark, что и первое создание; логгер их пропускал, потому что они выполняются вне его окна. Когда он начал их ловить, запуск 21-го дал 37 логов и ни одной проваленной проверки:

Сквозной запуск и его планка, запуски 1 и 2

Что просила фаза 2 — предсказать от сида игры, не читая из игры ни одного сида, — и что дали логи каждого запуска, прошедшие проверку.

  • требовалось
  • запуск 1
  • запуск 2
Заселения комнат 2 000 1 222 2 189
Дропы с монстров, предсказанные до предмета 500 849 1 314
Группы чемпионов и уникальных 100 154 225

docs/plans/done/0048-seed-to-world.md, условие готовности фазы 2 и числа из ревью обоих запусков. Запуск 2 ещё построил 176 уровней и 13 056 объектов при планке в 300 объектов.

Показать числа
требовалосьзапуск 1запуск 2
Заселения комнат 2 0001 2222 189
Дропы с монстров, предсказанные до предмета 5008491 314
Группы чемпионов и уникальных 100154225

Из 7 290 пересозданий 1 903 сверили построчно с тем, что записала сама игра: 1 729 живых, 174 мёртвых. Контроль: при запуске с --cf recreation-off модель «воспроизводит ровно те» два расхождения запуска 1, а если выключить другой механизм, снаряжение монстров, ломаются два новых случая, оба на Blood Raven. Ревью закрыло фазу в 13:11 и решило, насколько засчитывать непроверенную часть: 5 284 пересоздания, применённые к логам запуска 1, где сравнивать было не с чем, «годятся для 0 расхождений и для заселений, но не как доказательство отметок».

Спецификация, specs/chain/README.md, прошла ревью в 13:51: 89 подтверждённых утверждений ([C]: прочитано в коде и подтверждено запуском) против 7 выведенных ([I]: только прочитано в коде). Вики-страница прошла ревью в 14:19. Первой же правкой ревью вики убрало фразу, которую я сам написал в графике прошлого поста: «от одного сида игры». Теперь на странице сказано, что 37 игр восстановлены с нулём расхождений, «если взять из записи маршрут и исходы боёв и несколько счётных значений, которые всё ещё читаются из неё». Выходит, мир цепочка предсказывает по сиду, а то, что в нём делал игрок, — шаги и бои — берёт из записи. План 0048 закрылся 21-го в 14:23.

Навыки

План 0045 — это механика навыков и снарядов: диспетчеризация, сетевые пакеты, снаряды, обработчики событий. В прошлом посте у него была закрыта статическая фаза, а пять утверждений сообщества опровергнуты на бумаге. Фаза 2 началась 21-го, дважды вставала на паузу по моей просьбе и прошла 25-го: 18 сессий с восемью персонажами, 335 618 проверок, ноль расхождений. Вторая часть, 27-го, добавила 15 сессий, тоже без расхождений, и перепроверила 18 логов первой части. Сессии проходит пилот, скрипт проекта, который ведёт игру по скриншотам, а навык виден в логах как две функции: стартовая, которая выполняется, когда каст начинается, и функция действия, которая выполняется на кадре действия.

Что залогировали два запуска по навыкам

События за обе части, 33 сессии.

Функции действия навыков 3 183
Стартовые функции навыков 2 985
Проверки, можно ли применить навык 1 340
Срабатывания обработчиков событий 1 303
Снаряды, закончившиеся на юните 276
Призывы 219
Броски пробивания 130
Наложения «не лечиться» 86

docs/findings/skills/experiments.md, run:2026-10-25-skills и run:2026-10-27-skills. Первая часть насчитала ещё 31 391 создание снарядов и 21 860 сцепленных пар сида игры.

Показать числа
событий
Функции действия навыков 3 183
Стартовые функции навыков 2 985
Проверки, можно ли применить навык 1 340
Срабатывания обработчиков событий 1 303
Снаряды, закончившиеся на юните 276
Призывы 219
Броски пробивания 130
Наложения «не лечиться» 86

Пять опровержений и что с ними вышло в запусках:

  • Уровень Raise Skeleton. Сообщество считает, что скелет поднимается на уровне заклинателя. Код даёт min(max(L × 3/4 + уровень навыка, 1), L). У заклинателя 86-го уровня с навыком на 20 все его скелеты поднимались 84-го уровня.
  • Дальность снаряда. Сообщество прибавляет LevRange за каждый уровень после первого; код умножает его на уровень. Проверяли на Blaze, у которого LevRange равен 25, и первая часть этого не получила: «пилот телепортируется», так что пешего огненного следа в логе нет. Во второй части пилот ходил пешком: 271 создание Blaze на втором уровне навыка, и у каждого дальность по коду, а не по версии сообщества: там счёт идёт со второго уровня, и LevRange вышло бы на один меньше.
  • Ускорение прибавляется на каждом пятом шаге пути снаряда, а не каждый кадр: 1 657 шагов с ускорением, из них 278 пятых, 60 упёрлись в предел.
  • Снаряд с пробиванием, у которого не прошёл бросок на попадание, по словам сообщества, летит дальше и урона не наносит. Код заканчивает его на этом юните.
  • Скорость снаряда, ((VelLev × lvl) / 8 + Vel) << 8, — единственное, чего запуски показать не смогли. Все монстры, которые стреляют нужным снарядом, попались на первом уровне, где слагаемое с уровнем равно нулю: «22 создания spike2 … все на 1-м уровне». Решили не отправлять пилота на Nightmare ради quill rats; те, что в Blood Moor, остаются проверкой на случай, если попадутся.

В одном месте запуск ещё и поправил чтение. Статическая спецификация утверждала, что Raise Skeleton не берёт ни одного числа с сида заклинателя. Первые отладочные сессии 21-го видели, как сид делает один шаг после спавна, три раза из трёх, а запуск 25-го нашёл, где это число берётся: 0x005C4430.

Что было в руках у варвара во втором запуске по навыкам

  • Fleshripper, уникальный клыкастый нож: картинка из инвентаря, кадры его падения и персонаж с ним в руках Fleshripper
  • Lacerator, уникальный метательный топор: картинка из инвентаря, кадры его падения и варвар с ним в руках Lacerator

Из инструмента проекта tools/item_art.py. Рецепт SkillsBarH даёт варвару оба оружия, по одному в каждую руку, а во всех остальных слотах снаряжения — уникальные вещи с максимальными значениями. Графика Diablo II © Blizzard Entertainment.

Fleshripper — тот самый нож, который неделей раньше сломал модель боя — 101 расхождение в обработчике убийства, до которого никто не добрался с чтением. Теперь модель обработчиков воспроизводит все 3 461 старое срабатывание, включая лог той сессии, а второй запуск по навыкам дал варвару этот нож в одну руку и Lacerator в другую. «Не лечиться» нож наложил 86 раз.

Одна находка касается клиента. Навык, в котором отказано, до сервера не доходит: «клиент каждый кадр выполняет тот же SKILLS_CanUse на своей копии игрока (0x004D9FC5) и не шлёт запроса на навык, в котором отказано», так что случаи отказа — это недобор, который со стороны сервера не закрыть. Другая — Kick. У стандартной строки Kick минимальный и максимальный урон равны нулю, так что сам удар наносит ноль физического урона: 25 раз удар дошёл до COMBAT_ApplyDamage «с результатом 9, физический 0 … там, где ветка Kick в GetPhysMin дала бы не меньше 49 920». Ветка пинков, которая считает от силы и ловкости, существует, но до неё доходят Dragon Talon, Dragon Tail и Dragon Flight у ассасина, а из монстров навык с битом пинка есть только у Shadow Warrior и Shadow Master.

Фаза закрылась 27-го. Спецификации прошли ревью в 09:30 с девятью правками; одна из них — что при лимите питомцев убирается «первый в списке», а не «самый старый». Вики-страница — в 09:58, ещё с девятью; среди них «Kick „даёт фиксированный удар“», отмеченное как завышение.

Каталог, 310 из 310

План 0046, каталог всех нумерованных функций навыков, прогоняет их по одной в эмуляторе процессора из Ghidra, как описывал прошлый пост. Последние четыре пакета прошли 21-го, 42 600 случаев, ноль расхождений, и фаза 1 закрылась на 310 записях из 310. Что показал эмулятор:

  • Функция монстра do 152 «— это do 95 … инструкция в инструкцию»: 147 инструкций, ни одна не отличается.
  • Do 112 накладывает одно из пяти фиксированных проклятий, а какое — решает сид заклинателя по модулю пять. У одного из пяти, Lower Resist, есть ветка сопротивления, до которой дойти нельзя.
  • Взрывающиеся и ядовитые святилища роняют от пяти до девяти зелий. «От пяти до десяти» — это всего лишь текст столбца в редакторе святилищ.
  • Щупальце Baal берёт число два или три раза, а сколько именно — зависит от локальной переменной, в которую его функция «ни разу не пишет до вызова … Никакая эмуляция не даст значения, которое там оставляет игра; это может только залогированное применение навыка». Утверждение остаётся выведенным и уходит в фазу на харнессе, которой нужен Baal.

Анимация и тайминг кадров

Четвёртую фазу одобрили 25-го в том порядке, который я задал 20-го: анимация и тайминг кадров раньше ИИ монстров, «потому что таймингу навыков и боя сначала нужны счётчики кадров». Первая фаза плана 0050 прочитала данные анимации: animdata.d2 — это 256 хеш-блоков из записей по 160 байт, и приоритет у файла дополнения. Ещё один файл, eanimdata.d2, вообще не упоминается ни в одной строке бинарника. События последовательностей монстров совпадают со своей записью анимации только в 81 шаге из 805.

Из чтения кода видно кое-что о том, как устроен ход игры: «большинство режимов на сервере не продвигаются кадр за кадром; SUNIT_ScheduleModeEvents сразу в начале ставит в очередь события кадров (коды 1–4) и конец режима». Скорости считает одна функция с девятью ветками, а бонус faster cast, hit recovery, block и остальных — убывающая формула B = k·s / (k + s), где k равно 120, а для бега и ходьбы 150; подтверждено на 3 413 эмулированных случаях. И крайний случай: «отрицательный коэффициент (FCR ≤ −55, FHR или FBR ≤ −36) переполняется и даёт максимальную скорость».

О брейкпоинтах сообществу есть что сказать, так что и сравнение вышло большим: 61 утверждение со страниц Maxroll и Basin, из старой темы про бег и ходьбу для 1.09, из уточнения про перемещение для 1.13 и из таблиц PureDiablo. Их записали вслепую, до того как линия прочитала код.

Шестьдесят одно утверждение сообщества о кадрах и скоростях в сверке с кодом

  • подтверждено
  • подтверждено с уточнением
  • опровергнуто
  • не покрыто
анимация и тайминг 61

docs/findings/anim/, план 0050, P1b, часть 2. Из 19 опровергнутых 7 взяты только из источников по Resurrected и 3 только по 1.09; 9 — прямые опровержения для 1.14d.

Показать числа
подтвержденоподтверждено с уточнениемопровергнутоне покрыто всего
анимация и тайминг 733192 61

Сами таблицы брейкпоинтов оказались верны: «каждая точка смены совпадает (78/78)». Число кадров в 76 из них на единицу меньше, чем у кода, и ревью признало это разницей в способе счёта, а не ошибкой. Вот что опровергнуто, среди прочего. Из утверждений о самой 1.14d: коэффициент атаки ограничен значениями от 15 до 175, а у hit recovery и блока потолка нет вообще; после отбрасывания игрок оказывается в нейтральной позе без hit recovery; а у блока есть перезарядка, frame − last block > ⌊stat102 / 8⌋ + 15, хотя сообщество говорит, что её нет. Из источников по Resurrected: база Double Swing — 70, а не 120, как у Maxroll, и у друида, некроманта, Werewolf и Werebear кадры каста отличаются у всех. Из темы по 1.09: на Charge персонаж движется в 2,5 раза быстрее базовой скорости, а не в 1,5, а коэффициент бега «65» «сходится, только если читать его как hex». 0x65 — это 101.

Первый запуск фазы прошёл 27-го: 55 сессий, ноль расхождений, 298 373 вызова расчёта скорости, 99 241 шаг перемещения. На Fallen видно, что особые режимы классического монстра берут скорость движения, 1 266 вызовов. Blood Raven на Hell отбросила игрока 13 раз, и в 12 из них он оказался в нейтральной позе в том же кадре. Следующий каст Frozen Orb приходился на кадр действия плюс задержку, 72 из 73. Недобор — get-hit, реакция на удар: «каждый хрупкий персонаж умирал после 2–9: для get-hit нужен удар на шестнадцатую часть жизни».

ИИ монстров и шестая фаза

План 0052 написали 25-го, придержали, переписали 27-го, и в 17:34 я его одобрил. Он покрывает «331 пробел по монстрам», оставшийся после запусков по навыкам, и строится вокруг таблицы ИИ по адресу 0x0073CA18, 148 строк по 16 байт. План задал мне восемь вопросов. На семь я ответил так, как рекомендовалось: поиск пути станет отдельным планом, наёмники и призванные входят в рамки, слот харнесса один и так далее. На восьмой я ответил против рекомендации, и в плане это так и записано: «Покрытие каталога: вся таблица ИИ (не как в рекомендации). Каждая строка таблицы ИИ получает запись в каталоге, независимо от того, ссылается ли на неё стандартная строка MonStats, PetType или Hireling».

Фаза 0 прошла меньше чем за час. Из 148 строк на 146 ссылается какой-нибудь стандартный монстр; до 63 игра доходит через пулы уровней, до 44 — через суперуникальных, до 16 — через призыв, до одной — через наёмников. Поле aidist задано только у Mephisto и его Uber-копии. А ревью нашло то, чего фаза не искала: вторую таблицу ИИ по адресу 0x0073D358, восемнадцать строк по четыре слова, и в десяти из них функции, которых нет ни в одной строке первой таблицы.

Тем же одобрением я принял и ADR-0022. Фазы проекта заканчиваются на ИИ монстров, и ADR добавляет после него ещё одну, кадр сервера: «игровой тик (GameTick и каждый проход, который он делает), порядок обновления юнитов … очередь событий … таймеры … и порядок активации комнат внутри кадра». Доказательство для неё тоже новое, окно кадров: «заявленная серия последовательных кадров в небольшой сцене … в которой залогировано каждое относящееся к фазе событие каждого юнита и проверен каждый входной сид, без единой повторной синхронизации». До сих пор любая проверка сверяла выход функции с её залогированными входами; окно кадров проверяет, что между ними ничего не пропущено. ADR прямо называет свой риск: «Под wine порядок может оказаться отчасти недетерминированным». В моей утренней заметке было сказано, что пятая фаза — последняя в старых рамках, а для всего, что дальше, нужен ADR о рамках. ADR-0022 и есть этот ADR.

Ревизия

План 0038 разложил по пакетам 1 526 выведенных утверждений из спецификаций после ревью. За эти дни прошли одиннадцать пакетов на харнессе, десять из них 27-го.

Что поднял каждый пакет на харнессе, 21–27 октября

Утверждение поднято, когда доказательство пакета покрывает его целиком; частично — когда часть его случаев; недобор — утверждение, до которого пакет собирался дойти и не дошёл.

  • поднято
  • поднято частично
  • недобор
H1a, загрузка сохранений 44
H1b, сохранения во время игры 46
H6a, Куб 27
H3b, загрузка данных 25
H4a, монстры 32
H6f, наёмники 21
H6e1, квестовые NPC 22
H5d, коллизии 16
H6b, сокеты 21
H6c, торговцы 11
H6e2, квестовые предметы, с продолжением 24

docs/plans/0038-inferred-claims-sweep.md и ревью пакетов. H4a прошёл 21-го, ревью по нему было 25-го; остальные прошли 27-го.

Показать числа
поднятоподнято частичнонедобор всего
H1a, загрузка сохранений 3248 44
H1b, сохранения во время игры 25147 46
H6a, Куб 1881 27
H3b, загрузка данных 1690 25
H4a, монстры 1598 32
H6f, наёмники 1281 21
H6e1, квестовые NPC 11110 22
H5d, коллизии 1042 16
H6b, сокеты 993 21
H6c, торговцы 722 11
H6e2, квестовые предметы, с продолжением 3156 24

Кое-что из найденного пакетами видно и игроку:

  • В одиночной игре сохранение происходит при смерти. H1b нашёл этот вызов. Таймер автосохранения за весь запуск не сохранил ничего.
  • Если золота больше лимита, при загрузке оно обнуляется целиком. Персонаж с 900 000 золотых при себе, больше потолка в 800 000, загружается без золота; стэш с 3 000 000, больше 2 500 000, загружается пустым.
  • Персонаж класса 7, следующего за последним классом в файле сохранения, при загрузке останавливает игру с внутренней ошибкой.
  • Крафтовый предмет может уронить игру. Когда крафт в Кубе выбирает аффикс из пустого списка при низком уровне предмета, игра падает с нарушением доступа. H6a сохранил это как доказательство.
  • Коровий портал в классической игре проверяет слово квеста 26, а в дополнении — слово 40.
  • Сундуки Khalim сначала роняют золото, а потом саму часть: пять кучек перед глазом, семь перед мозгом, а в сундуке с сердцем — восемь. Уровень с мозгом сервер строит дважды, и до правки 27-го пилот перечислял объекты только одной копии.
  • Malah предлагает зелье только в одном состоянии квеста, 4. Бесплатный наёмник от Kashya выдаётся без списания денег. Наёмник получает двойной опыт, и только пока его уровень ниже, чем у хозяина.

Три части, ради которых пакет по квестовым предметам открывал сундуки

  • Khalim's Eye: маленький круглый глаз, картинка из инвентаря и кадры его падения Khalim's Eye
  • Khalim's Brain: серый мозг, картинка из инвентаря и кадры его падения Khalim's Brain
  • Khalim's Heart: красное сердце, картинка из инвентаря и кадры его падения Khalim's Heart

Из tools/item_art.py. Пакет-продолжение открыл все три сундука, и порядок, золото раньше части, подтверждён для каждого. Графика Diablo II © Blizzard Entertainment.

H4a, пакет по монстрам, за одну сессию открыл четыре печати из пяти в Chaos Sanctuary и залогировал Infector of Souls и Grand Vizier of Chaos. До пятой печати так и не дошли, поэтому нет ни Lord De Seis, ни Diablo; ревью решило эти строки «сейчас владельцу не передавать», раз похожую сессию я вычеркнул 25-го.

Два ревью проверили собственные итоги пакетов и пишут об этом одной строкой: «„Недобор 0“ — честно» про квестовых NPC и «„Поднято меньше половины“ — честно (9 из 25)» про сокеты. А один пакет нарушил правило, которого у проекта ещё не было. Зонды H3b нарочно роняли игру и писали файлы аварий в общую папку игры, и «не сохранились ни логи прогонов зондов, ни удалённые файлы аварий». Теперь правило такое: зонды, которые могут уронить игру, работают в своей папке игры, а папки аварий сохраняются.

После пакетов спецификации перетегируют и пересчитывают:

Подтверждённые утверждения в перетегированных спецификациях, до и после

  • до
  • после
save/d2s 91 240
objects 139 189
items/cube 119 167
items/quest-drops 86 159
drlg/collision 111 157
monsters/special 107 149
data/excel 42 144
hirelings 95 137
monsters/spawn 112 136
monsters/stats 110 130
items/sockets-runewords 80 129
items/vendors 77 113
items/uniques-sets 71 100
monsters/pool 81 99
data/strings 60 88

Числа из шапок, которые записывает каждое перетегирование фазы 3, до и после. У save/d2s выведенных стало 68 вместо 149.

Показать числа
допосле
save/d2s 91240
objects 139189
items/cube 119167
items/quest-drops 86159
drlg/collision 111157
monsters/special 107149
data/excel 42144
hirelings 95137
monsters/spawn 112136
monsters/stats 110130
items/sockets-runewords 80129
items/vendors 77113
items/uniques-sets 71100
monsters/pool 8199
data/strings 6088

Опись на отсечке: 567 утверждений в статических пакетах, 509 в пакетах на харнессе, 431 заблокировано. Заблокированных стало больше, было 414, потому что чем внимательнее смотрит пакет, тем чаще выясняется, что у части утверждений нечего наблюдать: это мёртвый код или условие, которое на стандартных данных всегда выполняется. Таких теперь 302. В плане осталось два пакета, два перетегирования, а в конце — список того, что могу сыграть только я.

План 0047, недоборы симуляции, закрылся 25-го. Одна строка из него — про харнесс: ожидания в проверках харнесса на истечение эффектов «рассчитаны на 25 кадров в секунду, а под логгером характеристик игра идёт примерно на 17». И одна — про игру: суккубы в Crystalline Passage накладывают Defense Curse на персонажа под Fade, а это 84 процента сопротивления проклятиям, и из 275 кадров, на которые было рассчитано проклятие, оно продержалось 44.

Гейт, который перестал останавливать коммиты

В проекте есть гейт, tools/gate.py, который на каждом коммите сверяет документы друг с другом: счётчики тегов в спецификациях, перекрёстные ссылки, символы, которых никто не упоминает. Хук коммита запускает его с таймаутом в 20 секунд, и хук намеренно мягкий: «Сломанный гейт никогда не блокирует работу … таймаут … проходит с предупреждением в одну строку». Я попросил ускорить гейт, и план 0053 выяснил, во что на деле обходилась эта мягкость: «быстрый режим занимал от 153 до 183 с, из них 151 с — цикл W3 (около 50 000 вызовов re.search по склеенному корпусу), так что при таймауте хука коммита в 20 с проверка просто пропускалась». W3 ищет символ из re/symbols.csv, который не назван ни в одной спецификации и ни в одной находке. При 3 666 символах проверка гоняла каждый как регулярное выражение по всему тексту. Каждый коммит проходил с предупреждением в одну строку, и с каких пор, по записям не понять. Когда регулярные выражения заменили поиском по множеству слов, гейт по staged-изменениям стал занимать 5,6 с, W3 выдаёт те же ноль предупреждений, а хук снова может остановить коммит. План заодно добавил правила для того, что ревью раз за разом правили вручную: строка таблицы, в которой ячеек больше, чем в заголовке; статус на карте, который не совпадает с шапкой спецификации; индекс планов, который не совпадает с самим планом.

Гейт коммитов и таймаут хука

Секунды быстрого режима гейта на одном коммите, до и после того, как план 0053 заменил цикл W3.

Хук ждёт, потом пропускает коммит 20 с
Гейт до, самый медленный замер 183 с
Гейт после, staged-файлы 5,6 с

Лог плана 0053 и .claude/hooks/gate-on-commit.js. До исправления быстрый режим занимал от 153 до 183 с, из них 151 с приходилась на W3.

Показать числа
секунд
Хук ждёт, потом пропускает коммит 20 с
Гейт до, самый медленный замер 183 с
Гейт после, staged-файлы 5,6 с

Что правили ревью с 21 по 27 октября

Каждая правка посчитана один раз, в первой подходящей категории, в том порядке, в каком их перечисляет журнал.

Завышение: тег или формулировка сильнее доказательства 86
Пропуск 84
Неверное чтение: утверждение, которому противоречат код или данные 49
Устаревшая ссылка 20
Занижение 19
Ошибка в счёте: число не пересчитано 15
Другое 14
Неверный якорь 13
Инструмент не сохранён 6
Конфликт символов 1

docs/plans/metrics.md, 68 ревью, добавленных после 21 октября, 12:11: 64 приняты с правками, 4 приняты, ни одно не отклонено. Одно занижение записано как «under-tag». Оговорка из самого файла остаётся в силе: ревью, которое читает больше, находит больше.

Показать числа
правок
Завышение: тег или формулировка сильнее доказательства 86
Пропуск 84
Неверное чтение: утверждение, которому противоречат код или данные 49
Устаревшая ссылка 20
Занижение 19
Ошибка в счёте: число не пересчитано 15
Другое 14
Неверный якорь 13
Инструмент не сохранён 6
Конфликт символов 1

Вместе с планом 0053 пришёл ADR-0023, принятый в ту же минуту, — о том, во что обходятся ревью. Его контекст умещается в одну фразу: «Недельный лимит токенов владельца кончается примерно за день, когда работают несколько линий». Снимок журнала в нём: 362 прогона, 83,6 миллиона токенов, из них на ревью архитектора приходится 172 прогона и 30,9 процента токенов; 708 правок, 19 процентов из них механические, такие, которые мог бы поймать гейт. Поэтому теперь у ревью два уровня. Уровень A — полное ревью, для линий со спецификациями, изменений ADR и аудитов. Уровень B «идёт на Sonnet и читает только git diff main...<lane>», и «каждая пятая линия уровня B получает ещё и ревью уровня A». ADR называет, чем за это платят: «Между линиями [C] у находки слабее». Я принял его в 18:19 как эксперимент, итог которого подведёт четвёртая фаза плана после десяти линий. Ещё появился инструмент tools/metrics.py: он сводит журнал «не загружая его в контекст агента», а журнал к этому времени весит около 100 КБ.

Как это было организовано

Линии шли как раньше: линии на харнессе на слотах 1 и 2, слот 0 мой и не занят, рядом статические линии с Ghidra, у каждой линии своя копия репозитория, и каждое закрытие проходит ревью. В журнале за эти дни 78 строк линий и 68 ревью. Линии потратили 22,8 миллиона токенов, ревью — 10,5 миллиона, это около 52 и 13 часов реального времени. 27-го три долгие линии на харнессе — пакет по сохранениям, квестовые предметы и запуск по анимации — каждую примерно через четыре часа сменили новой: линия останавливается, пишет заметку для продолжения, и новая линия продолжает с неё. В записях за эти дни нет ни сна машины, ни убийства из-за нехватки памяти. И каждый раз работу останавливал мой недельный лимит: паузы 21-го, остановка 25-го и пустые дни между ними.

Числа

Где проект сейчас, в сравнении с первым, шестым и одиннадцатым днём

Спецификации после ревью (28 строк до дня 4, 29 в день 6, 35 в день 8, 36 в день 11, 42 в день 17)

3 день 1
28 день 6
35 день 11
38 день 17

[C] подтверждённые утверждения в находках

48 день 1
1 150 день 6
2 540 день 11
3 032 день 17

[I] выведенные утверждения в находках

175 день 1
1 189 день 6
2 667 день 11
3 221 день 17

Именованные адреса в Game.exe

278 день 1
1 814 день 6
3 373 день 11
3 666 день 17

Инструменты на Python

26 день 1
91 день 6
170 день 11
215 день 17

Закрытые планы

5 день 1
36 день 6
44 день 11
48 день 17

У каждого показателя своя шкала. Источники: карта исследования, docs/findings, re/symbols.csv, tools/ и docs/plans/done на каждой отсечке.

Подтверждённые и выведенные утверждения в находках

[C] — прочитано в коде и подтверждено чем-то независимым, обычно запуском. [I] — только прочитано в коде. Ночи и дни, когда ничего не менялось, вырезаны.

  • [C] подтверждено
  • [I] выведено
0 200 400 600 800 1000 1200 1400 1600 1800 2000 2200 2400 2600 2800 3000 3200 3400 11 окт. 12 окт. 13 окт. 14 окт. 15 окт. 16 окт. 17 окт. 18 окт. 19 окт. 20 окт. 21 окт. 25 окт. 27 окт. [C] 3032 [I] 3221

У 25-го одна точка — вечерний пакет по монстрам. 27-го выведенные растут быстрее: то, что прочитали в коде анимации и ИИ, идёт как [I], а перетегирования того дня сдвигают утверждения в спецификациях, а не в находках.

Показать числа
Время[C][I]
11 окт. 10:31 0 0
11 окт. 13:35 7 15
11 окт. 15:05 21 58
11 окт. 16:00 28 59
11 окт. 16:25 42 62
11 окт. 17:57 45 104
11 окт. 19:06 48 148
11 окт. 20:11 48 175
11 окт. 22:12 57 176
11 окт. 22:23 57 176
12 окт. 08:04 80 173
12 окт. 08:41 103 183
12 окт. 09:02 104 184
12 окт. 09:31 108 217
12 окт. 09:58 109 228
12 окт. 10:16 117 278
12 окт. 10:42 135 295
12 окт. 11:51 139 295
12 окт. 12:21 159 291
12 окт. 13:41 161 292
12 окт. 14:10 168 295
12 окт. 15:19 168 320
12 окт. 15:46 184 354
12 окт. 15:58 184 354
13 окт. 08:05 196 348
13 окт. 08:26 204 374
13 окт. 08:33 205 422
13 окт. 10:11 206 448
13 окт. 13:43 223 449
13 окт. 15:59 225 452
13 окт. 18:56 229 453
13 окт. 19:21 236 452
13 окт. 19:28 240 455
13 окт. 21:15 279 429
13 окт. 22:02 279 442
14 окт. 08:11 279 442
14 окт. 08:49 282 443
14 окт. 09:21 282 493
14 окт. 09:36 282 495
14 окт. 09:49 290 536
14 окт. 10:00 291 537
14 окт. 10:09 293 586
14 окт. 10:20 293 587
14 окт. 11:05 332 567
14 окт. 13:10 356 562
14 окт. 17:35 376 560
14 окт. 19:22 376 611
14 окт. 19:56 395 651
14 окт. 20:36 402 691
14 окт. 20:50 446 721
14 окт. 20:59 451 757
14 окт. 21:16 461 796
14 окт. 22:37 506 772
14 окт. 22:50 545 743
15 окт. 02:10 574 733
15 окт. 02:44 587 732
15 окт. 02:54 612 737
15 окт. 03:05 619 727
15 окт. 03:29 629 739
15 окт. 03:57 671 738
15 окт. 04:14 711 716
15 окт. 07:39 718 733
15 окт. 08:47 729 737
15 окт. 08:48 737 779
15 окт. 08:54 746 782
15 окт. 09:05 771 791
15 окт. 09:17 773 848
15 окт. 19:08 774 890
15 окт. 19:10 786 941
15 окт. 19:13 797 940
15 окт. 19:25 801 982
15 окт. 19:33 829 1015
15 окт. 19:38 829 1057
15 окт. 19:48 841 1064
15 окт. 19:56 845 1066
15 окт. 20:13 845 1072
15 окт. 20:24 848 1095
15 окт. 23:42 875 1092
15 окт. 23:54 886 1095
16 окт. 00:15 886 1094
16 окт. 00:21 889 1096
16 окт. 01:43 905 1093
16 окт. 02:23 918 1098
16 окт. 02:28 923 1103
16 окт. 03:49 956 1090
16 окт. 06:40 987 1065
16 окт. 14:29 1003 1072
16 окт. 15:31 1023 1075
16 окт. 16:08 1023 1152
16 окт. 17:21 1080 1181
16 окт. 17:36 1140 1189
16 окт. 17:53 1150 1189
16 окт. 18:24 1174 1194
16 окт. 18:34 1228 1208
16 окт. 18:35 1267 1217
16 окт. 19:26 1281 1238
16 окт. 19:29 1321 1250
16 окт. 20:49 1391 1257
16 окт. 21:01 1412 1263
16 окт. 21:29 1486 1263
16 окт. 22:07 1505 1269
16 окт. 22:19 1515 1306
16 окт. 22:26 1566 1314
16 окт. 22:41 1577 1319
17 окт. 07:51 1577 1367
17 окт. 07:56 1585 1375
17 окт. 08:02 1597 1380
17 окт. 08:35 1612 1432
17 окт. 08:47 1650 1434
17 окт. 09:04 1647 1430
17 окт. 09:09 1647 1512
17 окт. 09:45 1657 1536
17 окт. 09:55 1669 1539
17 окт. 10:44 1670 1729
17 окт. 10:50 1670 1796
17 окт. 10:58 1670 1808
17 окт. 11:07 1681 1816
17 окт. 11:15 1683 1826
17 окт. 16:44 1707 1827
17 окт. 17:18 1707 1838
17 окт. 17:33 1707 1839
17 окт. 18:26 1708 1849
17 окт. 19:16 1746 1858
17 окт. 19:27 1747 1870
17 окт. 20:06 1747 1871
17 окт. 20:56 1770 1884
18 окт. 20:09 1773 1896
19 окт. 08:00 1788 1907
19 окт. 08:10 1804 1923
19 окт. 08:44 1805 1924
19 окт. 09:26 1814 1929
20 окт. 08:30 1849 1953
20 окт. 09:11 1849 1955
20 окт. 09:54 1853 2032
20 окт. 10:17 1856 2059
20 окт. 10:29 1914 2070
20 окт. 10:51 1917 2110
20 окт. 10:58 1947 2125
20 окт. 11:19 1973 2133
20 окт. 11:51 1973 2188
20 окт. 12:29 1973 2189
20 окт. 13:11 2022 2217
20 окт. 13:20 2036 2266
20 окт. 14:53 2037 2268
20 окт. 14:53 2076 2308
20 окт. 14:56 2099 2293
20 окт. 14:57 2108 2300
20 окт. 15:43 2110 2302
20 окт. 15:47 2154 2360
20 окт. 18:55 2178 2374
20 окт. 18:55 2230 2414
20 окт. 19:19 2230 2416
20 окт. 19:54 2272 2450
20 окт. 21:31 2312 2484
20 окт. 22:21 2347 2508
20 окт. 23:11 2381 2538
21 окт. 07:17 2420 2570
21 окт. 07:37 2431 2581
21 окт. 07:46 2445 2586
21 окт. 08:21 2479 2619
21 окт. 11:24 2489 2628
21 окт. 11:53 2539 2667
21 окт. 11:57 2540 2667
21 окт. 13:01 2579 2697
21 окт. 13:13 2584 2699
21 окт. 13:57 2626 2739
21 окт. 14:45 2663 2771
21 окт. 15:18 2667 2779
21 окт. 19:30 2700 2810
25 окт. 21:38 2718 2820
27 окт. 07:05 2737 2838
27 окт. 07:46 2750 2844
27 окт. 07:50 2760 2869
27 окт. 08:11 2760 2925
27 окт. 08:57 2779 2942
27 окт. 09:14 2810 2959
27 окт. 09:16 2814 3007
27 окт. 09:41 2817 3014
27 окт. 10:02 2836 3025
27 окт. 10:04 2836 3078
27 окт. 10:34 2840 3086
27 окт. 10:45 2840 3087
27 окт. 10:54 2874 3096
27 окт. 11:22 2877 3102
27 окт. 11:36 2891 3117
27 окт. 11:51 2891 3119
27 окт. 12:06 2896 3122
27 окт. 13:03 2897 3159
27 окт. 13:18 2914 3168
27 окт. 13:45 2920 3173
27 окт. 14:42 2934 3185
27 окт. 15:21 2956 3193
27 окт. 16:24 2984 3204
27 окт. 17:28 3005 3211
27 окт. 17:35 3032 3221

В одиннадцатый день в находках было 2 540 подтверждённых утверждений против 2 667 выведенных; теперь 3 032 против 3 221, и доля подтверждённых теперь 48,5 процента, а была 48,8. Спецификации, то есть то, что будет читать человек, который переписывает игру, сдвинулись сильнее: от 3 791 подтверждённого и 2 322 выведенных к 4 857 и 2 451, потому что это была неделя перетегирований, а перетегирование поднимает утверждения спецификации, не трогая находок. Именованных адресов в Game.exe стало 3 666 вместо 3 373, инструментов 215 вместо 170, рецептов сохранений 435 вместо 233, закрытых планов 48 вместо 44.

Что открыто

  • Анимация, вторая часть запуска (get-hit в зависимости от hit recovery, блоки, метания, ступени скоростей) и её спецификация; они идут параллельно.
  • ИИ монстров, план 0052, начиная с фазы каталога, и вторая таблица отдана туда же. Потом кадр сервера, который получит строку на карте, когда для него напишут план.
  • Фаза каталога на харнессе, после запуска по анимации, и в ней щупальце Baal.
  • Хвост ревизии: два пакета, два перетегирования и список для меня.
  • То, что сыграть могу только я, всё необязательное: Uber Mephisto и Baal, Countess и Cow King в нужных состояниях квестов, Hellforge на Hell, где пилот ни в одной из четырёх попыток не добрался до комнаты, и пара сессий с Diablo, где поиск у пилота закончился по таймауту.
  • Уровни ревью: следующие десять линий без спецификаций идут через проверку уровня B, а пятая и десятая проходят аудит на уровне A.

У воксельного движка в те же дни было своё окно, и в нём появилось освещение.