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

9.1 KiB
Raw Blame History

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. Манифест версии:

{ "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:

{
  "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).

Узлы (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;
  • импорт Naninovel и адаптер Tomie, пакетный импорт и строгая проверка совместимости — см. IMPORT.md;
  • редактор переводов — см. LOCALIZATION.md;
  • сборщик мусора (python -m app.gc, есть периодический режим);
  • ограничение неудачных попыток входа (app/ratelimit.py), CI в GitHub Actions.

Планы:

  • ATL function, matrixcolor, шейдеры и im.* (экраны и ATL с движением уже есть: SCREENS.md, backend/app/converter/atl.py, frontend/src/player/atl.ts);
  • другие движки (TyranoBuilder, RPG Maker MV) через тот же IR;
  • дальнейшее дробление engine.ts на модули (рендер сцены, UI, сохранения);
  • типизация backend под mypy (сейчас проверка справочная).