Files
pgames-manager/docs/ARCHITECTURE.md
T
mrleo1nidandClaude Sonnet 5.5 ff82d882c0 feat: add ATL animation timelines and try/raise to the Ren'Py player
Python sandbox
- try/except/else/finally, raise (incl. re-raise), assert, for/while ... else
- Multi-generator and dict comprehensions
- Builtin exception hierarchy; user exceptions subclass it
- Inside try, missing keys/indexes, division by zero, attribute access on None
  and unknown names raise Python exceptions; lookups stay lenient elsewhere
- int()/float() raise ValueError on non-numeric strings

ATL
- converter/atl.py: set/move (linear, ease, easein, easeout), pause, repeat,
  parallel, choice, block, on show/hide and named transforms become a timeline
  shipped as game.json "atl"; Move() becomes a linear slide
- player/atl.ts: timeline runner wired into the renderer; `on hide` plays
  before the picture is removed
- function, time, contains, matrixcolor and expressions still block the import

Tests: exceptions/ATL fixtures and runtime tests, converter ATL tests; README
and ARCHITECTURE updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:11:07 +03:00

153 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pgames-manager — архитектура
Локальная библиотека визуальных новелл (сначала Ren'Py). Игры не запускаются
оригинальным движком: архив разбирается на сервере, скрипт и ассеты
перекладываются в собственный формат **PGVN**, а играется копия в нашем
веб-плеере.
## Развёртывание
```
Proxmox host ── Caddy (TLS, домен) ──► http://<IP VM>:8080
│
VM: docker compose│
┌─────────────────────────────────────┴───────────┐
│ web (nginx) : SPA, API → backend, media internal │
│ backend : FastAPI (uvicorn) │
│ worker : конвертер, тот же образ │
│ db : PostgreSQL 16 │
└──────────────────────────────────────────────────┘
volume data/: blobs/ (ассеты), uploads/ (временные архивы)
```
Приложение отдаёт только HTTP, TLS — на Caddy. Пример Caddyfile:
```
games.example.lan {
reverse_proxy 192.168.1.50:8080
}
```
Очередь задач — сама таблица версий в Postgres (`SELECT … FOR UPDATE SKIP
LOCKED`), Redis не нужен.
## Пользователи и права
* При первом старте создаётся `admin` (пароль из `ADMIN_PASSWORD`, по
умолчанию `admin`) с флагом `must_change_credentials` — при первом входе он
обязан сменить логин и пароль.
* Регистрации нет. Админ создаёт пользователя, сервер генерирует случайный
пароль и показывает его **один раз**; админ передаёт его человеку. У нового
пользователя стоит `must_change_password`.
* Роль = именованный набор прав. Права зашиты в коде
(`backend/app/permissions.py`), роли редактируются в UI. Системные роли:
`admin` (все права, нельзя удалить), `moderator`, `player`.
* Сессия — JWT в httpOnly-cookie; в токене `token_version`, при смене пароля
или деактивации он увеличивается и старые сессии умирают.
## Игры и версии
* `game` — карточка в каталоге (название, описание, обложка, теги).
* `game_version` — конкретная играбельная сборка: `owner`, `parent_id`,
`status` (`uploaded → converting → ready | failed`), `manifest`, отчёт
конвертации. Оригинал — версия без родителя, созданная загрузкой архива.
* Форк версии копирует только манифест (килобайты). Хранилище блобов общее,
поэтому мод, меняющий код, хранит только изменённые файлы скрипта.
### Хранилище блобов
`data/blobs/ab/cd/<sha256>` — неизменяемые файлы. Доступ проверяет backend,
затем nginx отдаёт файл через закрытый internal location с `private, no-cache`.
Манифест версии:
```json
{ "files": { "game.json": {"hash": "…", "size": 1234},
"script/script.json": {"hash": "…", "size": 5678},
"images/bg room.webp": {"hash": "…", "size": 91011} } }
```
Удаление версии не удаляет блобы сразу — их чистит сборщик мусора (блобы,
на которые не ссылается ни один манифест).
## Конвейер конвертации (worker)
1. Распаковка архива (zip — stdlib, остальное — `7zz`), защита от zip-slip.
2. Поиск каталога `game/`.
3. Распаковка `.rpa` (RPA-2/3) внутрь `game/`.
4. Для `.rpyc` без `.rpy` — декомпиляция через unrpyc (best effort).
5. Парсинг `.rpy` собственным парсером → PGVN IR. Python-выражения
разбираются модулем `ast` и транслируются в JSON-дерево выражений;
неподдерживаемое попадает в отчёт как предупреждение.
6. Ассеты: изображения → WebP (с альфой), аудио → Opus (ogg), видео → WebM,
шрифты копируются. Всё складывается в блобы.
7. Манифест сохраняется, исходники удаляются. При ошибке архив остаётся для
повторной попытки.
## Формат PGVN v1
`game.json`:
```json
{
"format": "pgvn/1",
"title": "…", "resolution": [1920, 1080],
"entry": "start",
"scripts": ["script/script.json", "script/ch1.json"],
"characters": {"e": {"name": "Eileen", "color": "#c8ffc8"}},
"images": {"bg room": {"file": "images/bg room.webp"},
"black": {"color": "#000000"}},
"files": {"bg/room.jpg": "bg/room.webp"},
"defaults": [["points", {"k": "const", "v": 0}]]
}
```
Скрипт-модуль (`script/*.json`) — упорядоченный список меток, у каждой тело
из узлов-операторов. Структура остаётся деревом (близко к исходнику, удобно
редактировать), состояние плеера — стек `{label, path}` где `path` — индексы
внутри вложенных блоков. Новые сохранения дополнительно содержат ревизию и ID
узлов для проверки позиции при изменении скрипта; совместимость переменных
после изменения игровой логики требует отдельной проверки.
У каждого узла есть постоянный `id`, у реплик — ещё `tid` для перевода.
Переводы лежат отдельно, в `locales/<язык>/…` (см. [LOCALIZATION.md](LOCALIZATION.md)).
Узлы (`op`): `label` (точка входа внутри блока), `camera`, `say`, `scene`, `show`, `hide`, `with`, `play`, `stop`, `queue`,
`pause`, `menu`, `if`, `while`, `jump`, `call`, `return`, `exec`, `window`,
`noop`. Выражения: `const`, `name`, `attr`, `sub`, `bin`, `bool`, `cmp`,
`unary`, `if`, `list`, `tuple`, `dict`, `call`, `fmt`.
Поддерживаемые встроенные функции в выражениях — белый список
(`len`, `min`, `max`, `int`, `str`, `abs`, `round`, `renpy.random.*` и т.п.).
## Плеер
Отдельный TS-модуль без привязки к React (`frontend/src/player`):
* интерпретатор IR + вычислитель выражений;
* DOM-рендер слоёв (`<img>` с GPU-композицией), сцена масштабируется под
окно, переходы через CSS;
* текстбокс с эффектом печати, теги `{b} {i} {color} {size} {w} {p} {nw}`,
интерполяция `[var]`;
* меню выборов, история реплик, skip/auto, быстрые и слотовые сохранения
на сервере, громкость каналов, предзагрузка ближайших картинок.
## Состояние и планы
Реализовано:
* редактор PGVN-форка, замена ассетов, ревизии, трёхстороннее слияние и перенос
на новый оригинал — см. [MODDING.md](MODDING.md);
* импорт Naninovel и адаптер Tomie, пакетный импорт и строгая проверка
совместимости — см. [IMPORT.md](IMPORT.md);
* редактор переводов — см. [LOCALIZATION.md](LOCALIZATION.md);
* сборщик мусора (`python -m app.gc`, есть периодический режим);
* ограничение неудачных попыток входа (`app/ratelimit.py`), CI в GitHub Actions.
Планы:
* поддержка `screen`-языка и ATL `function`/`matrixcolor`/шейдеров (базовый ATL
с движением уже есть: `backend/app/converter/atl.py`, `frontend/src/player/atl.ts`);
* другие движки (TyranoBuilder, RPG Maker MV) через тот же IR;
* дальнейшее дробление `engine.ts` на модули (рендер сцены, UI, сохранения);
* типизация backend под `mypy` (сейчас проверка справочная).