Files
pgames-manager/docs/ARCHITECTURE.md
T
mrleo1nidandClaude Opus 5.5 e1d7b29f77 Localization stage 1: stable IDs, import of tl/ translations, language picker
- every script node gets a permanent id, say nodes a Ren'Py-style tid
- all languages from the game's tl/ are imported at conversion: dialogue blocks
  matched by their source-location comment or by label + original text,
  `translate strings` become a per-language strings table
- source language from config.language or detected from the dialogue
- manifest stores locales/<lang>/<module>.json and strings.json; game.json
  lists sourceLang and available locales with progress
- player: language picker in main menu and settings, fallback to source,
  __(), [var!t] and _preferences.language
- parser: `default` with init priority, and one bad statement no longer drops
  the whole file
- docs/LOCALIZATION.md: design for translations, publishing and mods

Line endings of several converter/player files go back to LF: an earlier
commit accidentally stored them as CRLF.

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

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

{ "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, у реплик — ещё 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, быстрые и слотовые сохранения на сервере, громкость каналов, предзагрузка ближайших картинок.

Дорожная карта

  • Веб-редактор форка: правка скриптов и замена ассетов (манифест уже готов).
  • Поддержка screens/ATL-трансформаций в IR.
  • Другие движки (TyranoBuilder, RPG Maker MV) через тот же IR.
  • Сборщик мусора блобов по расписанию.