← Back to blog

SOTA Log Parser v0.0.4 — готов к загрузке!

Автоматический перевод с английского. Открыть оригинал

By admin · 7/31/2026

Что логи уже знали

Обзор sotalogparser и неверных ответов, которые его сформировали.

Shroud of the Avatar записывает всё. Каждый удар, каждый улов, каждый неудачный шедевр, каждое слово, сказанное рядом с вами, с меткой времени, в виде простого текста, в папке, которая уже у вас есть. Игрок со стажем в два года сидит на нескольких миллионах строк таких записей.

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

Правила были выведены на основе реального корпуса из 3,84 миллиона строк и распознают 99,91% из них. Именно этот корпус — причина, по которой существуют интересные части этого поста. Каждое неочевидное решение ниже сначала было правдоподобным ответом, и именно корпус доказал его ошибочность.


Структура проекта

Три слоя, строго однонаправленных.

SotaLogParser.Core

Разбор, обнаружение, индексация, отчёты. Без зависимости от UI.

SotaLogParser.App

Представления и модели представлений Avalonia.

SotaLogParser.Desktop

Только точка входа.

.NET 10, Avalonia 12, MVVM. Одна кодовая база создаёт автономные одностраничные сборки для шести платформ — Windows, macOS и Linux, x64 и arm64 для каждой. Всё, что заслуживает тестирования, находится в Core, поэтому тестов 522, и ни одному из них не нужно окно.

Конвейер обработки

LogLineReader разделяет [метка времени] сообщение и сворачивает строки продолжения. ChatLogParser выполняет упорядоченный список правил, первое совпадение побеждает. Всё, что не распознано, становится UnrecognizedEvent.

Последняя часть — это правило, а не запасной вариант. Никогда не теряйте строку. Корзина нераспознанного — единственный механизм, благодаря которому пробел в наборе правил вообще виден — молчаливое отбрасывание того, что не совпадает, скрыло бы именно ту информацию, которая нужна для исправления. Приложение показывает процент покрытия и позволяет отфильтровать лог до нераспознанных строк, именно так 99,91% стали 99,91%.

Индекс

Один файл лога получает LogSummary. Всё по всему корпусу проходит через индекс SQLite: разбор, привязка контекста сцены, запись строк фактов, одна транзакция на файл, повторное чтение только тех файлов, чей размер или время изменения изменились. 3,84 млн строк индексируются примерно за двадцать секунд в примерно 19 МБ.

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

Две вещи, которые потребовали корпуса, чтобы их понять.

Парсер не сохраняет состояние между строками, поэтому только строка смены зоны знает, где что произошло. Трекер фиксирует последнюю вошедшую сцену и сбрасывается при начале сессии. Примерно четверть реальных событий происходит до любой строки Entering — они попадают в сцену (unknown), что является реальной категорией, которую должны показывать отчёты, а не пробелом, который нужно отфильтровать.

У боя есть направление. Четверть строк атак в корпусе — это удары мобов по игроку, и их способности выглядят точно так же, как у игрока. Без разделения по направлению «ваши навыки» показывают навыки, которые игрок никогда не использовал.


Тринадцать отчётов

Панель группирует их. Каждый загружается лениво при первом открытии и перезагружается при изменении фильтра даты, сцены или персонажа; всё экспортируется в CSV.

Обзор и Просмотр логов

Что покрывает индекс, и каждая строка одного лога в том виде, в котором её увидел парсер. Просмотр существует для правила покрытия, описанного выше — отфильтруйте по «Нераспознанному», и вы увидите оставшиеся слепые зоны парсера по порядку.

Бой — Навыки против мобов

Как каждый навык работает против каждой цели, и где пара превосходит то, что предсказывают остальные данные. 65 навыков, пересекающихся с 282 целями, — это двумерная таблица, и интересная ячейка — та, которая превосходит и свою строку, и свой столбец. Три решения, каждое из которых сначала было ошибочным:

  • Медианный сглаживание, а не средние. Со средними одна исключительная ячейка тянет вверх свою строку навыка и свой столбец цели, поэтому все соседи читаются как устойчивые. Один реальный вывод, шесть выдуманных.

  • Без весов. Взвешивание медианы позволяет любой ячейке, содержащей более половины наблюдений своей строки, определять собственный базовый уровень — что как раз и есть та самая активно фармящаяся пара, которую вы больше всего хотите измерить.

  • Порог разброса, а также порог выборки. Навык, использованный только против двух целей, не имеет независимого базового уровня, поэтому разрыв между его двумя ячейками читается как огромное взаимодействие. На реальном корпусе этот единственный порог — разница между одним обоснованным выводом и восемью артефактами.

Бой — Что вас бьёт

Зеркальный вопрос и самый новый отчёт: какие существа атакуют вас, чем, насколько сильно и как часто ваша защита это останавливает. Это категорически не предыдущий отчёт с обратной стрелкой — повторное использование исходящей механики было бы ошибкой в четырёх отношениях, каждая из которых даёт правдоподобное число, а не видимый сбой.

Более половины входящего огня приходится на питомца.

попытки

нацелено на игрока

51 138

нацелено на питомца

64 248

И ведут они себя совершенно по-разному: 52% ударов, нацеленных на игрока, проходят без урона, против 0,5% ударов, нацеленных на питомца. При объединении профиль защиты не принадлежит ни одному из них. Питомцы исключаются — а их доля указывается, потому что «это покрывает 44% того, что в вас летело» — это то, что читатель должен знать.

Очевидная цифра снижения урона упускает большую часть снижения. Формула исходящего отчёта учитывает уклонения, блоки, парирования и скользящие удары и даёт 18% входящего урона. Но 52% входящих атак прошли, но не нанесли урона — эта формула их исключает. Честная цифра — 66,2%. Между тем блоки и сопротивления полностью отсутствуют во входящих строках — ноль из каждого в 3,84 млн строк — поэтому эти столбцы опущены, а не показаны как структурные нули, потому что столбец, который всегда равен нулю, читается как «вы никогда ничего не блокировали».

Уклонение никогда не называет то, от чего уклонились. Все 7 120 входящих уклонений и все 17 474 исходящих находятся в безымянной корзине, независимо от того, что их вызвало. Поэтому каждый показатель по способности делится на атаки, которые назвали способность, а показатель уклонения живёт в сводке по существам, где он точен.

И ранжирование идёт по урону за удар, потому что три правдоподобных порядка едва ли совпадают:

попытки

за удар

за попадание

проходит

Massive Crocodile, базовый

4 310

2,1

58

4%

Razorback (T18), базовый

217

23,4

461,5

5%

Vigorem Cavas (T14), Shadow Blast

309

247,9

247,9

100%

Ранжируйте по частоте — и отчёт открывается на крокодиле, наносящем два урона. Ранжируйте по урону за попадание — и он открывается на событии, которое происходит один раз из двадцати. Только урон за удар учитывает частоту попаданий и отвечает на вопрос во что мне это обойдётся.

Бой — Рекорды

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

игрок

питомец

самый большой крит

1 843 (Chain Lightning)

6 735 (Wyvern Sting)

самый сильный полученный удар

1 206

2 628

По питомцу попадает больше ударов, чем по игроку — 64 245 против 44 329 — поэтому «самый сильный удар, который вы получили» — это чей-то чужой синяк, если вы не проверите. Рекорды ведутся по каждому персонажу отдельно и никогда не объединяются, при равенстве побеждает самый ранний, а урон со временем исключается: самый большой из них в корпусе — 13 555, что превосходит любую атаку, и молчаливое включение его отвечает на другой вопрос.

Мир — Активность в сценах

Куда ушло время и чем хороша была каждая локация. Три вещи, которые нужно сделать правильно:

  • Для показателя нужен знаменатель, которому можно доверять. Три минуты в Arlia принесли одну награду в 10 000 XP — 206 897 в час, второе место впереди локации, которую фармили восемьдесят часов. Показатель по сцене всё равно показывается, рядом с минутами, из которых он получен, но рейтинги применяют минимум в 30 минут и указывают, сколько сцен было исключено.

  • Убийства и смерти — это одни и те же строки, прочитанные с противоположных концов. 281 строка смерти — это 246 убийств, 22 смерти, 15 наблюдателей и 2 самоубийства. Подсчёт строк сообщает о пятой части лишних убийств и скрывает каждую смерть.

  • Добыча с трупов — это бой; сбор — это собирательство. В Tanglemire зафиксировано 743 атаки, 1 305 сборов и ни одной добычи с трупов. Одна объединённая категория добычи маркируется по тому, какой итог больше, поэтому место для рыбалки с волками оказывается боевой зоной.

Мир — Добыча и Рыбалка

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

Ремесло — Рецепты

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

  • Попытка — это одно создание. Две строки завершения — это альтернативы, никогда не обе. Корпус сразу выдаёт ошибку: Dark Red Dye фиксирует 73 231 обычное и 73 433 исключительных, поэтому трактовка исключительных как подмножества попыток даёт процент успеха выше 100%.

  • Неудача шедевра называет предмет на одну ступень ниже той, где была сделана попытка. Подсчёт по напечатанному числу создаёт гладкую кривую из ничего.

  • Значимость и размер эффекта — это разные величины. Интервал решает, действительно ли рецепт отличается от общего показателя; наблюдаемый показатель решает, отличается ли он достаточно, чтобы упоминать. Их смешение выделило один из трёх рецептов еды, сгруппированных около 45%, и оставило его двух соседей без пометок.

Ещё — Прогрессия

Полученный опыт и обученные навыки. Тихие периоды сохраняются — ряд, построенный только из дней, когда что-то происходило, рисует месяц как непрерывную полосу, что прямо противоположно тому, что читатель открыл, чтобы узнать.

И шаги — это не уровни. Игра не объявляет каждый уровень: Tailoring идёт 44, 48, 51, 54, поэтому 85 объявлений покрывают 117 уровней. Отчёт показывает шаги и наблюдаемый диапазон и помечает строки, где уровней прошло больше, чем было объявлено. «Получено уровней» просто нет в логе, и отчёт говорит об этом, а не гадает.

Связанная ловушка, общая для корпуса: показатели опыта — это нарастающие итоги, и их четыре разных на каждый пул — получено за всё время, пул персонажа, общий пул и их сумма, записанные вместе как блок. Показатели в сумме дают 1,57 триллиона против 187 миллионов фактически начисленных. Награды выбираются положительно по виду, а не как «всё, что не является показателем», потому что такая формулировка молча допускает все виды показателей, добавленные позже.

Ещё — Разговоры

Плоский поток чата, снова сшитый в обмены. В логе вообще нет маркера разговора, поэтому серия разбивается там, где разговор прекращался на десять минут. Два правила заслуживают своего места: серия, в которой никого нет, кроме игрока, — это не разговор (иначе сессия, потраченная на рекламу в Traders, заполнит отчёт односторонними «обменами»), и сообщение без метки времени сохраняет свою позицию в файле (сортировка нулей в начало открывает разрыв шириной во весь разговор).

«Я» разрешается для каждой строки относительно персонажа самого лога — в корпусе с шестью персонажами есть шесть ответов на вопрос кто я.

Ещё — Экономика

В основном примечателен тем, от чего отказывается вычислять. В логе нет цен и нет строк покупок, поэтому количество проданных единиц известно, а выручка — нет. Единственная цифра золота где-либо — это You picked up N gold., которая не имеет отношения к вашему торговцу. Отчёт говорит об этом, потому что цифра золота, напечатанная рядом с таблицей продаж, будет прочитана как выручка.

Что он действительно показывает, так это разбивку по постоянным заказам — выполненные заказы против просмотренных — что является единственным, на что продавец может повлиять в данных, и что в корпусе чётко разделилось: все сырьевые материалы — 100% заказы, все готовые товары — 100% просмотры.

Ещё — Торговля

Торговля между игроками, снова свёрнутая вместе. Торговля — это серия строк — открыта, предметы выставлены и возвращены, закрыта — и ни одна строка не говорит, что перешло из рук в руки. Четыре вещи:

  • Золото — это нарастающий итог, а не изменение. Строка указывает новую цифру. Реальная сделка в корпусе доводит золото до 99 000 и обратно до 0 перед завершением; суммирование строк сообщает о девяноста девяти тысячах золота, которое никогда не двигалось.

  • Две стороны логируются асимметрично. Игра записывает, как партнёр добавляет и удаляет предметы, но игрок только добавляет — никогда не снимает. Поэтому сторона игрока — это «что было предложено» и может быть завышена; сторона партнёра точна. Отчёт указывает, что есть что.

  • Большинство «сделок» — с самим собой. 64 из 106 завершённых сделок — между собственными альтернативными персонажами игрока, и когда логи обоих альтов присутствуют, одна и та же сделка хранится дважды, по одному разу с каждой стороны. Отмечено и фильтруется.

  • Имя партнёра иногда искажается игройZealoty,,1]Zealoty. Это в логе, а не то, что делает парсер. Исправление соответствует только этой точной удвоенной форме, потому что настоящие имена содержат запятые.


Сопутствующий сайт

Приложение может по желанию войти на сопутствующий веб-сайт и публиковать четыре набора данных: личные рекорды, уловы рыбы, ячейки навык-против-цели и способности монстров.

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

Что публикуется, так это достаточные статистики — количество, Σln(1+урон), Σln(1+урон)² — а не подогнанный результат, потому что медианное сглаживание не объединяется между игроками, а суммы объединяются. Они аддитивны, что и делает возможным точное объединение сообщества и исключение по одному за постоянное время. (1 + здесь критично: обратная операция — exp(x) − 1, и чтение их обратно с простым exp даёт ошибку менее процента при нескольких сотнях урона и треть при трёх. Правдоподобно везде и неправильно везде. Именно так была выпущена первая реализация сайта.)

И здесь корпус тоже научил кое-чему. Способность — это в значительной степени свойство своего существа — 22 из 74 способностей принадлежат ровно одному, а 169 из 272 существ имеют две или меньше. Поэтому нет двумерной модели для подгонки; вместо неё способность сравнивается с самой собой только там, где её используют три или более существа, с исключением по одному, чтобы существо, владеющее большей частью наблюдений способности, не могло определить свой собственный базовый уровень и сообщить о себе как об обычном. Earth Elemental владеет 2 955 из 3 637 ударов Earthquake и получает 0,08×; самовключающий пул показал бы около 1,0 и ничего не сказал.

Аккаунт необязателен и остаётся необязательным. Восстановление сессии читает файл и не делает запросов. Первый пакет покидает машину, когда вы нажимаете «Войти» или «Опубликовать», и никогда раньше. Ни один отчёт не зависит от входа в систему, а элементы управления публикацией скрыты, когда вы вышли, а не показаны неактивными.


Конфиденциальность

Чат-логи содержат сообщения других игроков, и этот факт ограничивает код, а не просто упоминается в политике.

Индекс хранит необработанный текст сообщений — это и есть отчёт о разговорах. Он находится в каталоге данных вашего приложения, вне репозитория, и ничто из него никогда не загружается. Никакая будущая нагрузка не должна менять это без явного запроса у пользователя.

Что публикация действительно отправляет, так это имена персонажей, названия навыков и существ, цифры, сцены и времена. Одно из них заслуживает осторожности: лог никогда не говорит, был ли атакующий существом или другим игроком, поэтому имя атакующего может быть именем реального человека. Именно поэтому публикация показывает фактические строки один раз для каждого набора данных перед первой загрузкой, а не только их количество. Загрузка направлена наружу и не может быть отменена отсюда.

Реальные логи игнорируются git; тестовые фикстуры отредактированы вручную.


Где это находится

Работает от начала до конца. Тринадцать отчётов, в панели не осталось заглушек, 522 теста, шесть целевых релизов. Парсер покрывает 99,91% корпуса, а оставшиеся 0,09% видны в приложении, а не заметены под ковёр.

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

Downloads