Перейти к содержанию

Метод извлечения лора ПЧК / Extraction Method

Источник: raw/*.srt — автоматические русские субтитры YouTube (DownSub), 91 файл субтитров. Промежуточный слой: clean/*.txt — субтитры без таймкодов/индексов, склеенные в абзацы с маркером [ЧЧ:ММ:СС] в начале каждого абзаца (≈12 реплик).

Принципы

  1. Цитата важнее пересказа. Везде, где возможно, приводится дословная цитата с таймкодом: > «текст» — [00:12:34].
  2. Исправляются только ошибки распознавания речи и грамматика. Смысл, лексика и порядок слов сохраняются. Если исправление меняет смысл — оригинал приводится в скобках: Вёрджинвальд (в субтитрах: «Верджин вальд», «Вёрджинвальд»).
  3. При сомнении — сохранять и помечать. Ничего не выбрасывается. Маркеры:
  4. ? — неуверенное распознавание имени/термина
  5. [СЛУХ] — неточная расшифровка, вариант реконструкции
  6. [МЕТА] — реплика игроков/ведущего вне игры (шутка, обсуждение правил), но с лорным содержанием
  7. [ПРОТИВОРЕЧИЕ] — конфликт с данными другого выпуска
  8. [НЕЯСНО] — смысл фрагмента не восстановлен
  9. Имена собственные приводятся в наиболее частом написании; все встреченные варианты перечисляются в lore/index/naming.md.
  10. Разделение внутриигрового и внеигрового. Реплики ведущего (Мастера) как нарратив — лор; шутки актёров — помечаются [МЕТА], но сохраняются, если содержат лорный факт.

Структура файла выпуска (lore/episodes/PCHK_NN.md)

# ПЧК #NN — «Название»
Метаданные: video_id, участники, длительность
## Состав партии (игрок → персонаж)
## Синопсис по сценам (с таймкодами)
## Персонажи игроков (PC)
## NPC
## Локации
## Предметы и артефакты
## Способности, заклинания, механики
## Карты / броски / игровые сущности
## Загадки, головоломки, испытания
## Фракции, организации, религии
## Существа и бестиарий
## Лорные факты о мире
## Цитаты
## Неясное и помеченное

Пустые разделы удаляются. Разделы Q&A-выпусков — см. lore/qa/.

Сводные индексы (lore/index/)

characters.md, npcs.md, locations.md, items.md, abilities.md, bestiary.md, factions.md, puzzles.md, timeline.md, naming.md, cosmology.md — собираются после обработки всех выпусков, каждая запись со ссылкой на выпуск и таймкод.

Порядок обработки

  1. Пилот: выпуски 01, 02, 03 и 50, 51, 52 — проверка методики.
  2. Остальные выпуски 04–49 по порядку.
  3. Хроники Леониса Парфия (01–09, 10–20), OST.
  4. Q&A-подкасты (16–52).
  5. Сборка сводных индексов.

Сопоставление имён — только после ручной проверки

Автоматика сама решений не принимает. Каждая привязка живёт в своём проверенном вручную словаре, и сборка читает только их:

Словарь Что связывает
lore/resheniya/formy/*.md словоформа в тексте → понятие (по полному совпадению)
lore/resheniya/slovar.md, колонка «На экране» надпись на кадре → понятие; можно сузить до выпуска: ДВАРФ@PCHK_45
lore/resheniya/kadry.md какой кадр показывать наверху страницы
lore/resheniya/fandom_stati.md статья фан-вики → понятие
lore/resheniya/fandom_kartinki.md картинка дампа → понятие, и колонка «Выпуск» для галереи

Ранний контур с tools/candidates.py и lore/matching.md удалён: его 53 проверенные привязки перенесены в slovar.md.

Проверяются три вида решений:

Что Чем опасна ошибка
Псевдонимы — «Ангелин» и «Ангелин Колосажатель» одно лицо две страницы вместо одной или слияние разных героев
Поиск по части имени — искать ли «Балан» отдельно от «Дан Балан» страница набирает чужие цитаты: «Укус ≠ обращение» ловил каждое «укус»
Имена с экрана — «ПОЛИАС» на карточке это Пэлиас чужой портрет в карточке: «КАРГА» приписывалась мечу «Кара Гелиоса»

Полное имя сущности ищется всегда и в проверке не нуждается.


Ручное и сгенерированное — раздельно

В lore/ лежит только то, что пишут люди и агенты: разборы, хроники, своды — без ссылок — и решения в lore/resheniya/. Всё, что программа из них выводит, ложится в gen/ и пересобирается командой bash tools/cycle.sh: размеченная копия корпуса gen/lore/, ленты gen/lenty/, отчёт по таблицам форм gen/formy_otchet.md. Сборка идёт в одну сторону и lore/ не меняет — это проверяется на каждом прогоне. Раньше ссылки ставились прямо в разборы, а таблицы форм пересобирались из этих ссылок: по тексту было не отличить поставленное рукой от поставленного программой, и правки смешивались с механикой.

Размеченные ленты выпусков (gen/lenty/)

Разбор в lore/episodes/ написан для человека: сущности из него выводятся. Размеченная лента — обратный порядок: сущности в ней объявлены.

Один файл на выпуск сводит расшифровку и разбор кадров на общую ось времени. Речь идёт абзацами с тайм-кодом, кадры — отдельными строками со знаком ▸, у каждой указан вид события, распознанный текст и имя файла кадра. Понятия в речи и в надписях помечены явно: Имя либо как сказано.

Порядок работы:

  1. Внести решение в lore/resheniya/slovar.md: каноническое имя, тип и — если имя видно на кадре — надпись с экрана. Формы речи там больше не задаются: их держат таблицы formy/.
  2. tools/markup.py PCHK_NN — собрать ленту (без аргументов — все выпуски).

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

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

Таблицы форм — единственный источник сопоставления

Отсечения окончаний больше нет нигде в сборке. Каждая словоформа выписана целиком в lore/resheniya/formy/ (по файлу на тип понятия) и вручную привязана к понятию; сопоставление идёт по полному совпадению. Прочерк вместо имени означает «это не упоминание».

Почему так: основа «Антошк» после отсечения окончания превращается в «Анто» и находит «Антон» — два разных персонажа склеиваются в одного. Никакая проверка отдельного выпуска этого не ловит, если имена стоят в разных местах корпуса.

У каждой формы указана область действия — файлы, где её проверили. Одно и то же слово в разных выпусках значит разное («Барон» — и Ворон Барон, и барон Эдвард), поэтому решение не переносится автоматически: новый выпуск добавляет свои строки осознанно.

tools/remark.py                    разметить копию корпуса строго по таблицам
tools/formtable.py                 отчёт: сколько раз, строки без вхождений
tools/formtable.py --kandidaty ID  известные формы, ещё не привязанные к файлу

Отсечения окончаний не осталось нигде в сборке: markcand.py, _legacy_stem.py, candidates.py, approved.py, collide.py и режим markup.py --check удалены. Нечёткое сравнение живёт только в imgcat.Matcher(guess=True) — режим сбора кандидатов, из сборки недостижимый: без проверенного словаря матчер бросает исключение.

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

tools/remark.py расставляет те же ссылки прямо в разборах. Разбор писал человек, портить его нельзя, поэтому скрипт проверяет себя: если снять со свежего файла все ссылки, должен получиться исходный текст буква в букву — иначе файл не записывается.

Название файла и названия крупных разделов (#, ##) не размечаются: из названия делается заголовок страницы и её адрес, и ссылка внутри сменила бы адреса всех выпусков. А вот разделы третьего уровня и ниже размечаются — и не ради вида. Разбор написан так, что подлежащее вынесено в заголовок:

### Бесогон — режиссёр театра «2Т»
- Убит Кукан Якой.

Пока строку читают на месте, всё понятно. Но на страницу сущности она попадает одна, и на странице Кукан Яки «Убит Кукан Якой» читается как его собственная гибель. Таких строк в корпусе больше тысячи. Размеченный заголовок позволяет генератору вернуть хозяина в строку (wikimark.subjects), а заодно доводит до заголовков переименования: «Динь-Дон» в заголовке становится тем же понятием, что «Динь-Динь» в тексте.

Так же устроены две другие опоры: хозяином бывает и жирное имя в начале пункта (следующие строки — о нём), и заголовок строки таблицы. А перечень, обещанный двоеточием, генератор дотягивает из следующих строк (wikimark.tails). Остаток, которому опоры не хватило, считает tools/checkpages.py.

Наследование словаря

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

Как называется статья

Заголовок — то, как имя написано: на карточке броска, на листе персонажа, в статье фан-вики. Не самая полная форма, произнесённая один раз при знакомстве. «Альбом в феврале» прозвучало дважды и нигде не написано, а на карточках стоит «АЛЬБО» и статья сообщества называется так же — значит, статья называется Альбо, а полная форма остаётся в строке «Также» и в разборе.

Проверка простая: если имя понятия ни разу не встречается в написанных источниках, а короткая форма стоит на карточках — заголовок надо менять. Переименование делает tools/renameents.py (SCREEN_NAME): оно переписывает и словарь, и все проставленные ссылки.

Один тип на понятие

Персонаж, сыгранный игроком, остаётся Персонаж и там, где он появляется как NPC. Иначе на одно имя выйдет две статьи. Расхождения ищет и правит tools/normtypes.py.

Слой фан-вики

Сторонний дамп не редактируется. tools/markwiki.py собирает из него производный gen/lenty/fandom.md: заголовки, инфобоксы и вступления статей с той же разметкой. Заголовки, которых нет в словаре корпуса, остаются неразмеченными и перечислены в конце файла отдельным списком.


Сверка имени по независимым источникам (tools/crosscheck.py)

Распознавание речи может искажать написание имён. Поэтому имена сверяются по пяти источникам:

Источники строго по старшинству:

Источник Что это
1 кадры надпись на экране: карта мира, лист персонажа, карточка броска — написано авторами шоу
2 фан-вики статьи сообщества, заголовки, имена файлов картинок — написано рукой, но это пересказ
3 lore/tg/ официальный канал и разборы участников
4 clean2/ Whisper large-v3 — услышано
5 raw/*.srt субтитры DownSub — самый ненадёжный источник, берётся в расчёт, только если все остальные молчат совсем

Услышанное не спорит с написанным. «Соливуха» из расшифровки проигрывает надписи СОЛЕВУХА на карте мира из #28: безударные гласные распознаются плохо, а на карте имя написано.

Кадр с картой мира (#28, PCHK_28_part1/img/007_00204.jpg) — отдельно ценный источник: это список топонимов в авторском написании.

Точечный прогон. Если написанных источников нет, а расшифровка сомнительна, надо вырезать ffmpeg'ом полминуты вокруг спорного места и распознать отдельным файлом на GPU-машине (/workspace/spot.py, beam 5 и 10). На коротком куске без окружающего контекста Whisper ошибается заметно реже: «Морелло» из общего прогона на вырезке оказалось «Мариеллой», а «Роберт» из DownSub — «Рупертом» на обоих значениях beam.

uv run --python 3.11 python tools/crosscheck.py Голиаф глиаф

Осторожно с перекрытием: если одно написание — начало другого («Милош Клыков» внутри «Милош Клыкович»), счётчики складываются друг в друга, и сравнивать их нельзя. Такие пары проверяются по контексту.