Files
pgames-manager/docs/ARCHITECTURE.md
T
mrleo1nidandClaude Sonnet 5.5 62ece25902 feat: Ren'Py screens (screen/call screen/show screen) with actions and styles
Converter
- screens.py parses the screen language: containers (fixed, frame, vbox, hbox,
  grid, vpgrid, viewport, button, has), widgets (text, textbutton, imagebutton,
  add, bar ...), if/for/use/transclude, default, python, key, timer, on, inline ATL,
  conditional properties, and decompiled forms (unparenthesised operators,
  glued property values, comma separated actions)
- style statements, call/show/hide screen operators and Python-level
  renpy.show_screen are translated; only screens the game shows are audited,
  unused stock screens no longer block an import; undefined screens do
- ATL values may be computed at run time and transforms take parameters
  (at blink(0.2, 0.3)); blur property

Player
- screens.ts builds the DOM from the IR and rebuilds it on state changes;
  buttons with idle/hover/selected/insensitive states, tooltips, sounds, modal
  screens, timers and keys that survive re-rendering, saved with the save file
- Actions: Jump, Call, Return, SetVariable, Toggle*, SetScreenVariable, Show, Hide,
  ToggleScreen, Function, Play, Notify, If, Language, ShowMenu, OpenURL ...
- Crop/LiveCrop/Solid/Frame/Transform/DynamicDisplayable values, crop property,
  globals(), renpy.variant/change_language/seen_image, transition stubs

Tests: screen parser and runtime (linkedom DOM), screens fixture compiled from
tests/fixtures/screens.rpy. Docs: docs/SCREENS.md.

Twisted-Thoughts now passes the strict compatibility check.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 13:17:11 +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.
Планы:
* ATL `function`, `matrixcolor`, шейдеры и `im.*` (экраны и ATL с движением уже есть:
[SCREENS.md](SCREENS.md), `backend/app/converter/atl.py`, `frontend/src/player/atl.ts`);
* другие движки (TyranoBuilder, RPG Maker MV) через тот же IR;
* дальнейшее дробление `engine.ts` на модули (рендер сцены, UI, сохранения);
* типизация backend под `mypy` (сейчас проверка справочная).