Пост про четвёртый день остановился 14 октября в 20:50. Этот пост — о том, что было дальше: остаток того вечера, всё 15-е и большая часть 16-го. В четвёртый день на карте исследования было 28 строк, и 12 из них были спецификациями после ревью. Теперь строк 29, после ревью — 28, а единственная, которая ещё нет, — самая новая, бой: утром 16-го в рамки проекта вошла симуляция игры, и это первая тема в этих постах про Diablo II, которая не про создание предметов, монстров и уровней.

Второе, что изменилось, — кто делал работу. До 15-го в каждом плане была фаза со мной: сессия, которую я играл сам, вопрос, на который отвечал я. Утром 15-го я одобрил сразу девять планов, чтобы они шли без меня, и лейны, каждый — агент со своей копией репозитория, гнали их весь день и всю ночь на харнессе, то есть на игре под отладчиком с подключённым логгером. В записях видно, что из этого вышло, и видно, куда агенты добраться не смогли и играть пришлось мне: семь сессий 16-го, чтобы убить боссов, до которых скриптовые сессии не доходили, персонаж погибал по дороге. Пост останавливается 16-го в 17:53, когда запись этих сессий влили.

Карта работы

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

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

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 не начато

7 спецификация после ревью 1 черновик спецификации 3 находки 17 не начато из 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 не начато

12 спецификация после ревью 1 черновик спецификации 6 находки 2 в плане 7 не начато из 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 в плане ↑

Стрелка отмечает строку, которая сдвинулась с прошлого снимка. Источник: docs/specs/README.md на коммитах 8faba4f2, c8753c82, 2c8eb42 и 33dc648. Полоса «Симуляция» добавлена 16 октября решением ADR-0014.

Показать таблицей
Подсистема День 1День 3День 4День 6
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 не начатоне начатоне начатов плане

За два дня шестнадцать строк дошли до ревью. Последние шесть строк ядра: аффиксы, свойства предметов, генератор лабиринтов, генератор открытой местности, переходы между уровнями и объекты. Последние три опоры: байткод формул, строковые таблицы и файл сохранения. И все семь путей создания — та полоса, про которую в четвёртый день я написал «ждёт ядро»: торговцы, Horadric Cube, сокеты и рунные слова, квестовые дропы, характеристики монстров, наёмники и создание персонажа. Как только ядро прошло ревью, пути закрылись за два дня.

Каждая строка прошла те же стадии, что и раньше, по схеме из поста про первый день: находки, прочитанные в коде и помеченные [I], запуск на харнессе, который поднимает их до [C] там, где модель воспроизводит игру, спецификация, ревью агентом, который её не писал. Каждая запись запуска, добавленная за это время, заканчивается нулём расхождений. Те из них, что несут результат для этого поста:

ЗапускЧто воспроизвела модельРасхождений
Аффиксы3 102 создания предмета: 366 магических, 105 редких, 35 превосходных, 31 низкого качества, 2 472 обычных, 36 automagic0
Свойства2 281 создание: базовые характеристики, броски свойств, эфирность, сокеты0
Лабиринты, связи, переходы14 сессий, 189 355 проверок0
Открытая местность11 новых логов и 93 старых, 572 211 проверок по новым0
Объекты13 сессий, 142 540 проверок; объекты комнаты, которые в четвёртый день ещё брались из лога, теперь предсказываются0
Характеристики монстров4 571 создание монстра, 62 991 проверка0
Пресеты актов III–V6 логов, 155 215 проверок0
Строковые таблицы336 поисков по индексу и 3 928 по ключу0
Сокеты и рунные слова22 сессии, 351 вставка, 50 рунных слов0
Куб182 превращения, из них 51 крафт0
Предметы от начала до конца889 созданий, 219 прослежены от выбора до записанных характеристик0
Торговцы94 сборки ассортимента, 325 окон gamble, 9 206 созданных предметов0
Создание персонажа35 новых персонажей, 269 копий предметов, 20 повышений уровня, 11 152 сравнения0
Вычислитель формул44 529 вычислений, все 14 опкодов, которые есть в стандартных данных0
Наёмники53 таблицы найма, 49 наймов, 33 сохранённых наёмника0
Квестовые дропы53 скриптовые сессии и 7 моих; в моих — 221 вызов раскрытия и 4 737 уровней монстров0

Проверка здесь — одно значение, которое модель предсказала, а игра записала в лог. Сессии вёл агент по скриншотам, тем же пилотом d2pilot.py, что и в четвёртый день, скриптом, который водит персонажа по сетке коллизий и кликает за него, кроме семи в конце строки про квестовые дропы.

Ночь с 14-го на 15-е

Четвёртый день закончился тем, что пять находок ждали своих запусков, а на харнессе появились слоты, чтобы несколько игр шли сразу. Запуски пришли той же ночью, первый в 22:37, последний в 04:14, а последний из пяти планов закрылся в 07:50.

Запуск по лабиринтам нашёл первое, что в четвёртый день прочитали неверно. Сопоставление переходов между двумя уровнями прочитали как совпадение по позициям ранга. Запуск показал, что есть запасной путь: «сопоставление переходов уходит на запасной путь, когда на уровне назначения нет комнаты с битом нужной позиции… У уровня Durance есть только та позиция, в которую была повёрнута его последняя комната, так что одно сопоставление по рангу там ошибается». Находка исправлена, и это в ней написано. Тот же запуск выяснил, что красные порталы пятого акта — не объекты из файлов пресетов и не объекты из групп; они берутся откуда-то ещё.

Запуск по открытой местности поймал харнесс на ошибке. Запуск по лабиринтам назвал одну сессию классической. Запуск по открытой местности загрузил того же персонажа и увидел в меню вейпоинтов пять вкладок актов. В байте статуса сохранения стоял бит дополнения. Запись про лабиринты поправили, а в руководство по игре на харнессе добавили правило: «перед классической сессией проверять, что персонаж действительно классический: в байте статуса .d2s бит 0x20 (дополнение) должен быть снят, а в меню вейпоинтов четыре вкладки актов, а не пять». Теперь в каждом рецепте персонажа должно быть сказано, для какой игры он собран, а рецепт без этого сборщик сохранений не принимает.

У характеристик монстров было три своих сюрприза. Первый запуск показал, что регенерация у членов группы растёт вместе с множителем HP группы, хотя обработчик, который прочитал реверсер, такую характеристику не пишет. Её пишет колбэк характеристик юнита. Там же поправили чтение четвёртого дня про классику: характеристика HP делится пополам вместе со своей дробной частью, а в находке было написано, что та остаётся без деления. И ещё: идентификаторы юнитов внутри одной игры используются повторно, и модели, которая на них опиралась, пришлось это учесть.

Сколько созданий предметов воспроизвели запуски по предметам

Одно создание — один предмет, который сделала игра и который модель собрала заново по сиду.

Торговцы (ассортимент и gamble) 9 206
Аффиксы 3 102
Свойства 2 281
Предметы от начала до конца 889
Сокеты и рунные слова (вставки) 351
Создание персонажа (копии предметов) 269
Куб (превращения) 182

Записи запусков в docs/findings/items/experiments.md и docs/findings/player/experiments.md.

Показать числа
созданий
Торговцы (ассортимент и gamble) 9 206
Аффиксы 3 102
Свойства 2 281
Предметы от начала до конца 889
Сокеты и рунные слова (вставки) 351
Создание персонажа (копии предметов) 269
Куб (превращения) 182

Очередь без присмотра

15-го в 08:05 одним коммитом легли девять планов и один ADR под названием «очередь без присмотра». В индексе планов записано решение: «Владелец одобрил планы 0027–0036 вместе, чтобы они шли без него. Фаз, где нужен живой владелец, больше нет: фаза 4 плана 0020 ведётся агентом, а игра владельца — запасной вариант». В очереди были две опоры, строковые таблицы и язык формул, шесть из семи путей создания и вики.

Харнесс под это пришлось менять. 15-го менеджер памяти убил все сессии разом, а я обнаружил это утром: «systemd-oomd убил весь scope редактора (все сессии сразу), когда память и своп перешли за 90%». Теперь у каждого запуска свой лимит памяти в своём scope, так что запуск, который разъелся по памяти, останавливают один, а не все сессии, а правило стало: не больше двух лейнов на харнессе одновременно. Слот 0, первая игра, на моём собственном дисплее, остаётся моим.

Ещё одно правило появилось после того, как лейны убили сами себя. В четвёртый день правило уборки уже было «никакого pkill -f, когда идёт несколько игр». Теперь — никакого pkill -f вообще: «шаблон совпадает и с оболочкой, в которой запущен сам pkill, и с любым другим процессом, в командной строке которого он есть (два лейна так убили собственную оболочку)». Цикл записывает свой PID на старте, и останавливают его по нему.

Очередь шла тридцать один час: сначала сокеты, 15-го в 19:34, последней вики, 16-го в 15:27, а между ними, ночью, закрылись Куб, создание персонажа, вычислитель формул, торговцы, наёмники и квестовые дропы. Что написано в спецификациях — ниже. Там, где они расходятся с сообществом, расхождение почти всегда одного рода: делитель, константа, бит.

Торговцы и gamble

Когда игра создаёт новую игру, первые числа из сида игры она тратит на заранее известные вещи, и два из них важны для этого поста:

flowchart LR
gs["сид игры"] --> d1["число 1: регионы монстров"] --> d2["число 2: регионы объектов"] --> d3["число 3: сид магазина"] --> d4["число 4: сид квестов"]

Когда вы первый раз в игре заговариваете с торговцем, сервер собирает его ассортимент из сида магазина.

Gamble — то место, где сообщество ошиблось в сто раз. На Arreat Summit шанс, что предмет из gamble окажется улучшенным, записан как «(ilvl − qlvl) × 0,9% + 1%», где ilvl — уровень предмета, а qlvl — уровень качества базового предмета. Наклон верный. А константа — один на 10 000, а не один на 100. Словами спецификации: «R(S, 10000) < (lvl − qlvl)·GambleUber + 1 даёт (lvl − qlvl)·0,9 % + 0,01 %». Запуск воспроизвёл все 2 292 проверки улучшения. Уровень gamble берётся из clvl − 5 .. clvl + 4, от пяти ниже уровня персонажа до четырёх выше, и используется трижды: для отбора базы, как уровень создания предмета и как уровень улучшения. На Arreat Summit на странице про магические предметы написано «от плюс шести до минус трёх», а на странице того же издателя про gamble — «от минус пяти до плюс четырёх», и права вторая. Первые две клетки экрана gamble — всегда кольцо и амулет, и числа для их уровня и выбора берутся и выбрасываются. Со стандартными таблицами доли качества такие: 50 уникальных, 100 сетовых, 10 000 редких и 89 850 магических на 100 000. А порядок строк одного уровня на экране — тот, в каком их оставил qsort из библиотеки C, а не порядок файла данных.

Куб

Сервер просматривает таблицу рецептов по порядку строк и берёт первый рецепт, под который подходят положенные предметы: условия заголовка и до семи входов. В строке рецепта два процента: столько-то от уровня персонажа плюс столько-то от уровня входного предмета, и созданный предмет на выходе получает их сумму. У крафтовых рецептов 50 и 50, так что крафт сидит ровно посередине между вами и входом. Выход, который переиспользует вход, сохраняет уровень входа. В запуске одна строка не подошла редкому амулету: её выходу нужны сокеты, а у амулета их быть не может.

Коровий портал в запуске открылся трижды, на Normal, Nightmare и Hell, и ещё трижды игра отказала, каждый раз по другой причине. Она откажет, если в этой игре его уже открывали, если этот персонаж уже убил Cow King, если в дополнении не убит Baal, или если вы стоите не в Rogue Encampment. Два рецепта Uber Tristram в одиночной игре 1.14d — заглушки: «PandemoniumPortalStub, PandemoniumFinalePortalStub: return 0».

Наёмники

Когда вы первый раз в игре заговариваете с продавцом наёмников, сервер собирает таблицу найма из сида магазина: по одному сиду на каждое имя из диапазона имён продавца, потом десять имён выбираются в список. Уровни в списке — от вашего минус пять до вашего минус один, выше вас никого. Когда десятый наём опустошает список, он тут же собирается заново из сцепленного сида, и запуск поймал это один раз: «10-й наём опустошил список, HIRE_RebuildIfEmpty построил новую таблицу из сцепленного S внутри HIRE_Hire».

Характеристики по уровню — значения из таблицы плюс прибавка за уровень, и одно деление сообщество знает неверно. Столбец сопротивления за уровень в 1.14d делится на четыре, а в справочнике сообщества написано «восьмые». Опыт до следующего уровня — (L + 1) × L² × Exp/Lvl, а не формула из справочников. Когда убивает наёмник, игра складывает его собственный magic find с хозяйским: «на трёх убийствах, которые сделала Rogue MercMF, лог показал 0 у наёмника и 40 у игрока». А у одного класса в таблице монстров, 560, нет строки наёмника, так что «ни сохранение, ни наём не могут его создать».

Создание персонажа

Новый персонаж начинается как сохранение с одним заголовком, которое пишет клиент. Сервер читает его, видит бит «новый» и собирает персонажа из строки его класса. Жизнь при создании — это живучесть плюс прибавка класса, без множителя за очко: волшебница начинает с 40 жизни, 35 маны, 74 выносливости; варвар с 55, 10 и 92. Повышение уровня не берёт ни одного числа ни из какого сида. Штрафы к опыту после 25-го уровня — не те проценты, что перечислены на Arreat Summit; игра считает в долях от 256, и спецификация так и пишет: «207/256 — это 80,9 %, 13/256 — это 5,1 %».

Язык формул

В данных навыков и предметов у игры есть маленький язык формул, и в четвёртый день я писал, что ни одно свойство предмета им не пользуется. Теперь он описан целиком. В стандартных данных каждая формула уже скомпилирована в постфиксный байткод в четырёх пулах без заголовков, и игра гоняет по байтам стековую машину на 64 целых числа. В стандартных пулах ровно 14 опкодов. Запуск вычислил 44 529 формул и воспроизвёл каждую. Случайное число вычислитель может взять только через одну функцию, rand, и ни одна стандартная формула в игре её не вызвала: через вычислитель не проходит ни один сид.

Квестовые дропы

Сид квестов — четвёртое число на схеме выше, сразу после сида магазина. Из него берутся камни Andariel, камни и руна Hellforge и предметы квестовых наград. Treasure class — это одна строка таблицы дропа игры: что может выпасть с монстра или из сундука и с какими весами, и у босса есть квестовый treasure class, который он использует, пока его квест не выполнен. Состояние квеста — набор битов в сохранении, и про эти биты игроки спорят о двух вещах:

  • Квестовый дроп Andariel. Её скрипт смерти ставит один бит, а разговор с Warriv превращает его в другой. Так что её квестовый treasure class, с двумя сколотыми камнями и одним обычным, используется снова, «если игра была сохранена до того, как квест сдан».
  • Дроп Duriel. Его бит ставится при убийстве и никогда не снимается, так что его квестовый treasure class используется до первого убийства. У сохранения со всеми выполненными квестами квестовый дроп Duriel всё ещё есть, потому что бит завершения его квеста — не тот, который проверяет дроп.

Награда imbue создаёт редкий предмет на уровне max(clvl, 1), плюс четыре, если вышло больше пяти, и сдвигает сид игры на два шага. Imbue принимает только базы низкого, обычного и превосходного качества.

Семь сессий, которые сыграл я

У плана по квестовым дропам была фаза 2 из 53 скриптовых сессий. Они подтвердили квестовый treasure class, сид квестов, особые дропы и награды. До четырёх вещей они не добрались: «Mephisto с выполненным квестом (4 попытки), Countess во всех четырёх состояниях (10 попыток), Hellforge на Hell (3 смерти), Cow King». Все сессии с Countess закончились тем, что её так и не убили. За сессию с Cow King убили 565 Hell Bovine, а его самого так и не встретили. Hellforge на Hell: «по дороге погибал трижды». План закрылся с этими строками на [I] и с аддендумом владельца в бэклоге.

Так что 16-го я их сыграл: семь сессий на слоте 0, сиды с 3501 по 3507, на весь экран и со звуком, с подключённым логгером, без пошаговой отладки. Все семь дошли до цели, минут по десять каждая; с заготовленным персонажем это было легко. В записи аккуратно разделено, что моё, а что из лога: «звук и полный экран в заголовке не записаны и держатся на словах владельца».

Три из четырёх целей, как игра рисует их класс

  • Hell Bovine: корова на двух ногах с алебардой Hell Bovine, класс Cow King
  • Hephasto the Armorer: большой рогатый демон с молотом Hephasto, который сторожит Hellforge
  • Dark Stalker: порченая Rogue с мечом и щитом Dark Stalker, класс Countess

Собрано из слоёв спрайтов самой игры инструментом проекта tools/cof.py: первый кадр спокойной позы, без смены цвета, которую суперуникальный получает в игре. Графика Diablo II © Blizzard Entertainment.

Что решили семь сессий:

  • С Mephisto с выполненным квестом падает его обычный treasure class, 848, а не квестовый, 851. Soulstone после убийства нет, потому что у убийцы стоит бит квеста.
  • Countess использует один и тот же treasure class в любом состоянии своего квеста: 1004 на Normal и 1005 на Nightmare. Её скрипт смерти ничего не создаёт и ничего не берёт из сида квестов. Один из её дропов, из лога: gld tsc vbt r06 r06 — золото, свиток портала в город, сапоги и две руны.
  • Cow King сначала раскрывает свой собственный treasure class, потом ставит бит убийце, потом запрашивает восемь зелий выносливости с качеством 0. Восемь зелий, которые из этого выходят, — качества 2, его уровня, и ни одного числа из сида не берут. Почему запрос качества 0 даёт качество 2 — открытый вопрос в спецификации.
  • Hellforge на Hell. Hephasto раскрыл свой treasure class, потом выдал Hellforge Hammer, качества 7 и уровня 88, не взяв ни одного числа. Потом четыре волны камней, «идеальный, безупречный, безупречный, обычный», и руна: сид квестов по модулю 11 дал 4, а это в таблице для Hell r19. Все пять обычного качества, уровня предмета 85 — уровень монстров этой зоны на Hell. Молот должен быть надет, и его характеристика 356 должна быть не меньше номера сложности; границу находка проверила: «молот, у которого характеристика 356 меньше сложности, игра не находит».

Четыре квестовых предмета

  • Hellforge Hammer: большой молот в инвентаре, кадры его падения и персонаж с ним в руках Hellforge Hammer
  • Horadric Cube в инвентаре и на земле Horadric Cube
  • Mephisto's Soulstone, маленький синий камень, в инвентаре и на земле Mephisto's Soulstone
  • Зелье выносливости, маленькая жёлтая колба, в инвентаре и на земле Зелье выносливости, дроп Cow King

На каждом листе: картинка из инвентаря и кадры падения предмета на землю. Отрисовано инструментом проекта tools/item_art.py. Графика Diablo II © Blizzard Entertainment.

В проект входит симуляция

Второй ADR проекта, в первый день, держал симуляцию игры вне рамок проекта, пока хотя бы одна спецификация по предметам не пройдёт ревью. Утром 16-го, когда ревью прошли почти все строки, ADR-0014 включил её в проект, пятью фазами: формулы боя, характеристики и состояния, навыки и снаряды, ИИ монстров, тайминги анимации. Другой порядок, сначала характеристики и состояния, в ADR записан как отвергнутый вариант: он проиграл, потому что «владелец хочет сначала формулы боя». Особой причины у этого порядка не было. На карте исследования появилась четвёртая полоса, «Симуляция», с одной строкой, а строки остальных четырёх фаз добавятся, когда начнутся их планы.

Самое трудное — в правиле о доказательствах, и ADR в основном о нём. Всё, что проект воспроизводил до сих пор, происходит один раз: уровень строится, предмет создаётся. Бой идёт каждый кадр, на многих юнитах сразу, и под wine «кадр, на котором происходит событие, зависит от времени по часам и от того, насколько каждая точка останова задерживает поток… Сессию нельзя проиграть заново кадр в кадр». Поэтому утверждение о симуляции доходит до [C] воспроизведением событий, а не кадров или сессий: «событие — это один вызов именованной точки входа внутри рамок проекта (например, одна атака по одной цели, досчитанная до результата, или один тик периодического урона)». Модель предсказывает каждое взятое число и каждый результат по входам из лога и сверяет сид на выходе с залогированным. Два события одного вида подряд на одном сиде сцепляются: сид на входе второго предсказывается по выходу первого, и такое сцепление обязательно для каждого вида событий. Значения характеристик, прочитанные с юнита, — оракулы: берутся из лога, а не предсказываются. Любое утверждение о времени — в кадрах игры; о времени по часам не утверждается ничего.

В ADR сказано и то, почему именно здесь числа сообщества значат меньше всего: «большая часть этого фольклора выведена из кода 1.09 и 1.10, так что его числа и формулы могут не выполняться в 1.14d».

Дальше пошёл план 0037. Его фаза 0 описала боевые столбцы таблиц, с одним сюрпризом: ни у одной записи монстра с уроном S1 нет режима S1. Фаза 1 — статическое чтение серверного модуля боя, 77 именованных символов, всё на [I], потому что запуска ещё не было. Как игра считает атаку, по прочитанному:

flowchart TB
ar["шанс попасть = 100 x AR / (AR + DEF), умножить на 2 x alvl / (alvl + dlvl), в пределах 5..95"]
ar --> roll["одно число с сида атакующего: low % 100"]
roll -->|попал| def["сид защищающегося, по порядку: блок щитом; потом Evade или блок оружием; потом Dodge (ближний бой) или Avoid (снаряд)"]
roll -->|промах| none["урона нет"]
def -->|не избежал| red["снижения: таблица из 12 строк; сопротивления, снижение урона, поглощение"]
red --> fx["эффекты при попадании: crushing blow, deadly strike, open wounds"]

Из тридцати утверждений сообщества, с которыми чтение сравнили: 15 подтвердились, 9 подтвердились с уточнением, 5 опровергнуты (четыре разных поведения: два источника одинаково ошибаются про crushing blow), 1 не покрыто.

Тридцать утверждений сообщества о бое против кода

  • подтвердилось
  • подтвердилось с уточнением
  • опровергнуто
  • не покрыто
combat/0001, статическое чтение 30

docs/findings/combat/0001-formulas.md § 13. Пока всё на [I]: фаза с харнессом впереди.

Показать числа
подтвердилосьподтвердилось с уточнениемопровергнутоне покрыто всего
combat/0001, статическое чтение 15951 30

Подтвердилось то, что знает каждый игрок: шанс попасть не бывает ниже 5 и выше 95 процентов, блок ограничен 75. Опровергнуты:

  • Бонус класса к уровню атакующего в формуле попадания с Arreat Summit: 20, 15, 5, −10, −15 по классам. Константы существуют, но это ToHitFactor, и прибавляются они к attack rating; а в слагаемом с уровнем никакого бонуса класса нет.
  • Лечение от поглощения идёт только от абсолютного поглощения. Лечит и процентное, и фиксированное поглощение.
  • Crushing blow — одна восьмая на боссе или чемпионе. Одна восьмая у боссов и суперуникальных; чемпионы и случайные уникальные получают четверть.
  • Crushing blow не зависит от числа игроков. Зависит: k += MulDiv(k, PlayerHpPct(n), 100).

И одно, чего нет в списке сообщества, но есть в собственных таблицах проекта: столбец таблицы сложностей, про который в описании таблицы было написано, что его никто не читает, HireableBossDamagePercent, код боя читает. Описание таблицы поправили на ревью.

16-го появились ещё два ADR, оба о том, что запуск может доказать. ADR-0013 переписывает правило из ADR четвёртого дня о запусках с подменой таблицы. То правило было написано на уровне таблицы: запуск, который подменяет таблицу, ничего не доказывает о коде, который эту таблицу читает. Ревьюеры с тех пор решали тоньше, чем написано в правиле, и «следующий ревьюер, применив ADR-0010 буквально, мог бы откатить каждый [C] выше». Новое правило — по ячейкам: утверждение может ссылаться на запуск с подменой как на доказательство, только если «код, который утверждение описывает, не читает ни одной подменённой ячейки». ADR-0015 разрешает эмулятор не только для чистых функций. Ghidra, дизассемблер, умеет интерпретировать одну функцию бинарника саму по себе, без запущенной игры; собственный, неизменённый код бинарника, прогнанный так, с объявленным образом памяти, считается [C] для того, что код делает с этими входами, и никогда — для того, что лежит в памяти игры во время игры. Первый запуск по этому правилу воспроизвёл функции формул min, max и rand и код снарядов, который читает формулы, без запущенной игры.

Ревизия

Когда все строки прошли ревью, встал вопрос, который планы никогда не задавали разом. План 0038 его задаёт: «В спецификациях после ревью 1 526 утверждений [I] против 2 531 [C]. Каждый план поднял то, до чего дошёл его собственный запуск, а остальное оставил на [I], так что оставшиеся [I] никто не смотрел рядом друг с другом». 16-го я выбрал этот план и пустил его без присмотра. Он не добавляет ни одной подсистемы и не меняет статус ни одной спецификации.

Его фаза 0 — опись всех 1 526, по файлу на спецификацию, и сумма в каждом файле сходится с числом в шапке его спецификации. Каждое утверждение получило класс: значения уже есть в сохранённом логе, или нужна новая сессия, или статический второй источник, или эмулятор, или заблокировано. Итоги после ревью:

Куда идут 1 526 выведенных утверждений из спецификаций после ревью

Десять статических пакетов: сохранённые логи, вторые источники 634
Двадцать один пакет на харнессе: новые сессии 516
Заблокировано: пока до них ничем не добраться 364
Подняты при описи 10
Отложены 2

docs/plans/0038-inventory/README.md § 2.4. Из заблокированных: у 239 до доказательства не добраться, 69 нужна подменённая таблица, 26 нужна симуляция, 14 нужны два игрока в одной игре, чего харнесс пока не умеет, 8 нужна моя игра, 8 — строки квестовых дропов из аддендума владельца.

Показать числа
утверждений
Десять статических пакетов: сохранённые логи, вторые источники 634
Двадцать один пакет на харнессе: новые сессии 516
Заблокировано: пока до них ничем не добраться 364
Подняты при описи 10
Отложены 2

Два статических пакета прошли до отсечки. Первый прогнал 166 сохранённых логов из 11 запусков по предметам через модели treasure class и качества со счётчиками: 9 390 вызовов, 9 390 воспроизведены, и из этого 29 утверждений подняты, 25 подняты частично, 3 недобора, 8 переклассифицированы. На ревью по дороге записали принцип: «Контрфактические прогоны сами по себе не доказательство. Если переключить и прогон разойдётся, это проверка чувствительности, не больше… Доказательство — по-прежнему сам прогон». Второй пакет прошёл по логам уровней и объектов: 57 подняты, 18 частично, 2 недобора, 12 переклассифицированы, и 24 адреса в таблице символов подняты до подтверждённых. Одна из его переклассификаций — как раз такое утверждение, какие находит статическая ревизия: «ни одно из 39 096 залогированных слияний по биту 2 не прошло через строки 0–5, не вернулось на строке −2 и не попало под правило края 8/9». Оно не ошибочно; просто ничто из того, что мы логировали, его не задело.

Вики

15-го я попросил вики для людей, и ADR-0012 сделал её правилом: страницы, которые человек читает, чтобы понять системы игры, без собственных утверждений. План 0036 написал её 16-го: оглавление и десять страниц, около 45 000 слов, по каждой спецификации после ревью. Правила из её README: «Никаких собственных утверждений. … Если страница и спецификация расходятся, права спецификация. Ничего не скрывать. … Каждая тематическая страница заканчивается разделом „Что пока неизвестно“. Не публикуется». Ни тегов уверенности, ни якорей, ни адресов. Начинается она со случайности, потому что «почти всё остальное — это последовательность случайных чисел на каком-то сиде», и в ней 29 диаграмм. Вторым проходом внесли около 80 правок формулировок, «в основном вернули оговорки на местах, где доказательство — только код». В гейт добавили три правила и предупреждение об устаревании, чтобы вики не разъезжалась со спецификациями, а главный README репозитория теперь начинается с неё. Я её прочитал. Хорошая, но её нужно дорабатывать.

ADR отверг два других варианта: краткое изложение в начале каждой спецификации, потому что у них разные читатели, и собранный сайт на локальной машине, потому что это ещё один набор инструментов, а любой собранный вид сразу ставит вопрос о публикации. Так что вики остаётся markdown-файлами в репозитории.

Числа

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

Спецификации после ревью (из 28 строк, с шестого дня 29)

3 день 1
7 день 3
12 день 4
28 день 6

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

48 день 1
240 день 3
446 день 4
1 150 день 6

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

175 день 1
455 день 3
721 день 4
1 189 день 6

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

278 день 1
626 день 3
1 143 день 4
1 814 день 6

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

26 день 1
38 день 3
56 день 4
91 день 6

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

5 день 1
14 день 3
19 день 4
36 день 6

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

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

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

  • [C] подтверждено
  • [I] выведено
0 200 400 600 800 1000 1200 11 окт. 12 окт. 13 окт. 14 окт. 15 окт. 16 окт. [C] 1150 [I] 1189

Ровный участок 15-го — день, когда шла очередь: чтения путей создания легли вместе вечером. Скачок [I] 16-го в 16:08 — чтение боя.

Показать числа
Время[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

В четвёртый день доля подтверждённых утверждений в находках была около 38 процентов. Теперь это 1 150 против 1 189, 49 процентов, а в спецификациях, которые и есть результат, 2 546 подтверждённых против 1 539 выведенных. Те 1 526 из ревизии посчитаны 16-го в 14:50; за вторую половину дня спецификации ещё сдвинулись. Именованных адресов в Game.exe стало 1 814 вместо 1 143. Скриптов верхнего уровня в каталоге инструментов — 91 вместо 56, а рецептов сохранений — 112 вместо 10. Рецепт — это один персонаж под одну задачу: волшебница с Nova и всеми вейпоинтами, классический варвар, персонаж для проверки наёмников. Очередь написала по рецепту почти на каждую сессию, которая ей была нужна.

Что открыто

  • Бой, фаза 2. Запуск формул боя на харнессе по правилу «по событиям»: 2 000 атак игрока и 1 000 атак монстров, не меньше 300 на каждой сложности, не меньше 300 попаданий и 300 промахов, 20 сцепленных пар на каждый вид события, 30 crushing blow, 30 deadly strike, 30 open wounds с проверкой тиков в кадрах. Под это названы четыре персонажа: варвар ближнего боя с наёмником, волшебница с Nova, защитник со щитом и классический персонаж.
  • Остальные четыре фазы симуляции: характеристики и состояния, навыки и снаряды, ИИ монстров, тайминги анимации. Каждая получит свою строку на карте, когда начнётся её план.
  • Оставшиеся пакеты ревизии: восемь статических, двадцать один на харнессе. И 364 заблокированных утверждения, восьми из которых нужна моя игра.
  • Строки из аддендума. На отсечке в спецификации квестовых дропов Mephisto, Countess, Cow King и Hellforge всё ещё стоят на [I] с пометкой «аддендум владельца открыт»; перетегирование влили через двадцать четыре минуты после отсечки.

А в тот же день, что и ревизия, в другом репозитории воксельный движок получил свою первую пещеру. Его самое новое решение называет этот проект по имени: «Каждая система, которую документирует проект рекомпиляции, — это целочисленная арифметика на собственном сиде каждой вещи». Запуск по бою, первые две тысячи атак игрока, ещё не состоялся.