Files
pgames-manager/docs/ARCHITECTURE.md
T
mrleo1nidandClaude Opus 5.5 364b312064 Initial MVP: local visual novel library with Ren'Py → PGVN converter
- FastAPI backend: admin bootstrap with forced credential change, admin-created
  users with one-time generated passwords, roles as permission sets
- Games and versions with content-addressed blob storage; forks copy only the
  manifest, so mods share assets with the original
- Worker: archive/RPA extraction, unrpyc for .rpyc, own .rpy parser, Python
  subset translated via ast, media converted to WebP/Opus/WebM
- React panel (library search, game page, upload, users, roles) and a
  framework-free web player with saves, history, skip/auto, transitions
- docker compose: postgres, backend, worker, nginx on a single HTTP port

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 02:41:32 +03:00

134 lines
7.6 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, /blobs → том │
│ 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>` — неизменяемые файлы, отдаются nginx с
`Cache-Control: immutable`. Манифест версии:
```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` — индексы
внутри вложенных блоков, так что сохранения стабильны и компактны.
Узлы (`op`): `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, быстрые и слотовые сохранения
на сервере, громкость каналов, предзагрузка ближайших картинок.
## Дорожная карта
* Веб-редактор форка: правка скриптов и замена ассетов (манифест уже готов).
* Поддержка screens/ATL-трансформаций в IR.
* Другие движки (TyranoBuilder, RPG Maker MV) через тот же IR.
* Сборщик мусора блобов по расписанию.