Diablo II, дни пятый и шестой
Пост про четвёртый день остановился 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 automagic | 0 |
| Свойства | 2 281 создание: базовые характеристики, броски свойств, эфирность, сокеты | 0 |
| Лабиринты, связи, переходы | 14 сессий, 189 355 проверок | 0 |
| Открытая местность | 11 новых логов и 93 старых, 572 211 проверок по новым | 0 |
| Объекты | 13 сессий, 142 540 проверок; объекты комнаты, которые в четвёртый день ещё брались из лога, теперь предсказываются | 0 |
| Характеристики монстров | 4 571 создание монстра, 62 991 проверка | 0 |
| Пресеты актов III–V | 6 логов, 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 делится пополам вместе со своей дробной частью, а в находке было написано, что та остаётся без деления. И ещё: идентификаторы юнитов внутри одной игры используются повторно, и модели, которая на них опиралась, пришлось это учесть.
Очередь без присмотра
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, класс Cow King
-
Hephasto, который сторожит Hellforge
-
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
-
Horadric Cube
-
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 не покрыто.
Подтвердилось то, что знает каждый игрок: шанс попасть не бывает ниже 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, по файлу на спецификацию, и сумма в каждом файле сходится с числом в шапке его спецификации. Каждое утверждение получило класс: значения уже есть в сохранённом логе, или нужна новая сессия, или статический второй источник, или эмулятор, или заблокировано. Итоги после ревью:
Два статических пакета прошли до отсечки. Первый прогнал 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)
[C] подтверждённые утверждения в находках
[I] выведенные утверждения в находках
Именованные адреса в Game.exe
Инструменты на Python
Закрытые планы
У каждого показателя своя шкала. Источники: карта исследования, docs/findings, re/symbols.csv, tools/ и docs/plans/done на каждой отсечке.
Подтверждённые и выведенные утверждения в находках
[C] — прочитано в коде и подтверждено чем-то независимым, обычно запуском. [I] — только прочитано в коде. Ночи вырезаны.
- [C] подтверждено
- [I] выведено
Ровный участок 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]с пометкой «аддендум владельца открыт»; перетегирование влили через двадцать четыре минуты после отсечки.
А в тот же день, что и ревизия, в другом репозитории воксельный движок получил свою первую пещеру. Его самое новое решение называет этот проект по имени: «Каждая система, которую документирует проект рекомпиляции, — это целочисленная арифметика на собственном сиде каждой вещи». Запуск по бою, первые две тысячи атак игрока, ещё не состоялся.