ATL
- Image blocks become timelines: picture steps with dissolve, parallel, contains,
finite repeat, time, function f(trans, st, at), calls of other transforms with
arguments, on hover/idle events for buttons. Animations stop when their element
leaves the page
- Pictures defined by an expression (get_face_path("base")) are drawn at show time
- Purely graphical hints (shader, mesh, gl_*, u_*) are accepted and dropped
Colour
- matrixcolor and im.matrix.* as 5x5 matrices combined with `*`, applied through SVG
filters; im.MatrixColor/Scale/Crop/Blur/Composite and Text/Image as values
Python
- match statement, re/copy/time/datetime/defaultdict/functools.partial, more
str/dict methods, Character(...) created by code
- narrator("..."), Character calls, renpy.say and renpy.show/hide with expressions in
Python blocks become script nodes
- Audit follows reachability: functions and classes nothing refers to (custom
statement lexers, unused tools) no longer block an import
Docs: README and ARCHITECTURE now describe a multi-engine library (GameMaker and
QSP planned). Tests for the new runtime pieces.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
10 KiB
pgames-manager — архитектура
Локальная библиотека игр, которые играются в браузере: визуальные новеллы, «песочницы», текстовые
квесты (см. README). Игры не запускаются оригинальным движком: архив разбирается на
сервере, скрипт и ассеты перекладываются в собственный формат PGVN, а играется копия в нашем
веб-плеере. Для каждого движка и для особенных игр есть профиль (GAME_PROFILES.md),
у игр с собственным устройством — собственная среда исполнения (game.runtime).
Развёртывание
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)
- Распаковка архива (zip — stdlib, остальное —
7zz), защита от zip-slip. - Поиск каталога
game/. - Распаковка
.rpa(RPA-2/3) внутрьgame/. - Для
.rpycбез.rpy— декомпиляция через unrpyc (best effort). - Парсинг
.rpyсобственным парсером → PGVN IR. Python-выражения разбираются модулемastи транслируются в JSON-дерево выражений; неподдерживаемое попадает в отчёт как предупреждение. - Ассеты: изображения → WebP (с альфой), аудио → Opus (ogg), видео → WebM, шрифты копируются. Всё складывается в блобы.
- Манифест сохраняется, исходники удаляются. При ошибке архив остаётся для повторной попытки.
Формат 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.
Планы:
- шейдеры и детали
im.*(экраны, ATL,matrixcolorуже есть: SCREENS.md,backend/app/converter/atl.py,frontend/src/player/atl.ts); - движки без своего профиля: GameMaker (
data.win, виртуальная машина GML — отдельный проект), QSP (текстовые квесты.qsp: свой формат, локации, переменные, действия и вставки HTML), затем TyranoBuilder, RPG Maker MV; - Ren'Py: код Python, который ждёт игрока (
renpy.input,narrator(),renpy.sayвнутри функций и циклов) — для него нужен интерпретатор на генераторах; - дальнейшее дробление
engine.tsна модули (рендер сцены, UI, сохранения); - типизация backend под
mypy(сейчас проверка справочная).