refactor: move game-specific import logic into profiles; unblock Tomie import

- Add app/games: GameProfile base, registry, ReaderHooks and renpy/naninovel/
  tomie profiles. pipeline, preflight, detect and mods dispatch via the registry
- Move tomie_* converter modules to app/games/tomie and split out reader hooks,
  import, mod validation and known defects
- Tolerate the 31 authoring defects of the pinned Tomie build as report
  warnings (exact list, per-script counts); any new defect still blocks import
- Frontend: runtime registry (runtimes.ts) with lazy Tomie launcher
- Add profile/registry tests and docs/GAME_PROFILES.md; update IMPORT.md

Full media export of the original Tomie build is not yet verified.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 11:35:23 +03:00
1 parent bbbcc6a5d8
commit 12ec82c7e6
30 files changed
+700 -196

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
# Профили игр
Общий конвейер (загрузка, очередь, хранилище блобов, моды, оболочка плеера) не знает
подробностей конкретных игр. Всё, что относится к движку или к одной сборке игры,
живёт в профиле — модуле в `backend/app/games/`.
## Что такое профиль
Класс `GameProfile` (`app/games/base.py`):
| Член | Назначение |
| --- | --- |
| `id` | Стабильный идентификатор в отчётах, например `tomie/expansion-1.3801` |
| `engine` | Семейство движка из `compatibility.detect` (`renpy`, `naninovel`, …) |
| `specific` | `True` для профиля, привязанного к одной сборке: он выбирается раньше общего профиля движка |
| `runtime` | Значение `game.runtime`, если игре нужен отдельный плеер |
| `claims(root)` | Дешёвая проверка, что профиль подходит (например, SHA-256 сборки) |
| `inspect(root, work)` | Проверка совместимости без записи блобов; при отказе — `CompatibilityError` |
| `convert(root, work, progress)` | Полный импорт в PGVN |
| `validate_mod(game, scripts, manifest)` | Дополнительные проверки изменённого мода |
| `unity_hooks()` | Расширение общего читателя Unity/Naninovel (`app/games/hooks.py`) |
`app/games/registry.py` выбирает профиль: `select(engine, root)` — сначала
`specific`-профили, затем общий профиль движка; `for_runtime(runtime)` — для
валидации модов. Остальной код обращается только к реестру: `pipeline`,
`preflight`, `compatibility.detect`, `mods`.
## Как добавить игру со своим конвейером
1. Создайте `backend/app/games/<имя>/` с классом профиля. Задайте `engine`,
`specific = True`, `claims()` (хэш сборки, а не имя папки) и `inspect`/`convert`.
2. Особенности чтения Unity-данных вынесите в наследника `ReaderHooks`
(`begin`, `accept_duplicate_script`, `scan_object`) и передайте его в
`read_package(..., hooks=...)`.
3. Зарегистрируйте профиль в `app/games/__init__.py`.
4. Если игре нужен свой плеер, добавьте `runtime` и запись в
`frontend/src/player/runtimes.ts` (загрузчик подгружается лениво).
5. Проверки: профиль закрывается по принципу «закрыто по умолчанию» — неизвестное
отклоняется. Добавьте тесты по образцу `tests/test_tomie_*.py` и
`tests/test_game_profiles.py`.
## Профили сейчас
* `renpy` — `app/games/renpy.py`; конвертация остаётся в `converter/pipeline.py`,
потому что объединяет распаковку архива, RPA и медиа.
* `naninovel` — общий импорт Naninovel (`converter/naninovel.py`).
* `tomie/expansion-1.3801` — `app/games/tomie/`: хэш сборки (`profile.py`),
формат и нативный интерпретатор команд (`format.py`, `validation.py`), медиа
(`assets.py`), хуки читателя (`reader.py`), известные дефекты оригинала
(`defects.py`), проверка модов (`mod.py`), импорт (`convert.py`).
## Известные дефекты оригинала
Профиль может перечислить ошибки самой игры (переход на несуществующую метку,
ветка без `EndIf`), которые оригинал тоже не обрабатывает. Они не блокируют
импорт, а попадают в отчёт как предупреждения; плеер на таком месте останавливается
с явной ошибкой. Список точный: ключ — (код, сообщение, сценарий), значение —
максимальное число. Новые или лишние случаи, в том числе в моде, по-прежнему
отклоняются. Добавлять записи ради прохождения импорта нельзя.