Text fields (`input`) with InputValue classes games can extend, continue/break in screen loops, Confirm,
CaptureFocus/ClearFocus/InvertSelected, generator functions and iter/next, starred tuple items,
renpy.file and json over data files loaded before the game starts, renpy.Container and
register_sl_displayable, DiamondBox layout, ConditionSwitch/DynamicImage/Animation, renpy.show(what=...),
callable transforms (`store.move_in(0.3)`), call_in_new_context as a call, im.matrix/im.Image/
MatrixColor, colorsys, fnmatch, FontGroup, persistent._clear and small renpy helpers.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Games write their own displayables with render(), renpy.Render, blit, fill, canvas() and renpy.redraw.
render.ts keeps a tree of placed pieces (child renders, ordinary displayables, fills and drawing
commands) and turns it into DOM elements that stay put between redraws; pointer events reach event()
as pygame-style objects, and a returned value ends the interaction. Shaders and GPU properties are
accepted and ignored. The converter whitelists the calls and methods and accepts `import pygame`.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
screens: `drag` with draggable True moves with the pointer (the place is kept while the screen is
shown); `at icon["at"]` picks a transform that Python code stored in a dict (transform names now
evaluate to a reference); PreviewSlowText and Move(...) exist as values. Python: Matrix([16 numbers])
for matrixcolor, renpy.has_live2d() is false, renpy.fetch fails like a missing network (games catch
it), cache.get_total_size and the texture helpers are no-ops.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Letters of these custom tags animate one after another (CSS). A closing tag now closes the style that
it opened (with anything left open inside) instead of popping whatever was on top, and an unknown tag
that has a closing tag later shows its text plainly and stays balanced.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
MIST's cutscene screen has a 0.1s timer that changes a variable, and every change rebuilt the whole
screen: the <video> restarted each time and "Skip" was replaced between press and release, so the
click never landed. Videos (Movie(play=...) and the movie channel picture) are now kept per shown
screen and resumed if the browser paused them, and a redraw requested while a pointer is down waits
for its release.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Games with their own save screens work: FileAction/FileSave/FileLoad/FileDelete/FilePage*, FileLoadable,
FileTime, FileSaveName, FileJson and friends use browser slots (a slot has no screenshot). Added
renpy.has_image/has_screen/list_files/get_mouse_pos/log/jump_out_of_context/random.seed,
renpy.translation.translate_string, type/id/vars/Flatten/LiveComposite/Composite, bare defaultdict/
deepcopy, md5/sha256 (checked against test vectors), uuid4, time.monotonic, re.compile().fullmatch,
math.isfinite and `{**a, "k": v}`. The converter whitelists them and accepts the hashlib/uuid imports.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`frame at (a if cond else b)` is kept as a choice that the player resolves each time the screen is
drawn. An ATL line splits at top-level commas into separate statements, so `zoom 1.2, 0.2` is a zoom
followed by a 0.2s pause, as Ren'Py compiles it (two-value properties such as `pos 0.5, 0.5` stay
whole). The inert events deselected/unselected, which Ren'Py never sends, are accepted. With the
build profile for The Only Doe 0.05 the game now passes the strict script check.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Screens get an invisible mousearea that reports the pointer entering and leaving without taking
clicks. datetime.date/datetime/timedelta support construction and date arithmetic. dir() lists the
game's variables, renpy.style.rebuild() is a no-op, and renpy.image_size answers only for pictures
the browser already holds (otherwise an IOError the game can catch).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
renpy.random/random get choices, sample, randrange and Random(seed); frozenset, iter and hash
are builtins. User classes may define __hash__ (dict keys), __iter__, __getitem__ and
__contains__; the converter accepts them instead of rejecting the whole class, which
removes the Perk/Skill/Status wall in SaintlyCorruption.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
show text _("...") and literal screen texts are listed in Names and menu; typing in search switches the filter from To do to All; the translator is told how stutters are spelled.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Decorative Ren'Py tags cannot survive translation mid-word; retry without them when the translator rejects the answer.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Total shown right away, lines saved per poll, retry reasons in the card; layout fix of the options row.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The run is recorded at every page and picked up from the translator's finished job; one run across tabs via Web Locks.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Language picker with search and SVG flags, real pagination with locate, one global machine translation run shown next to uploads.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Shared translator session, game description/genres as optional context, live header progress.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Translator panel in the translation editor and a /translator setup page. The browser talks to
local-translator directly; the server only receives lines marked as machine.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Each ready version gets a "Languages" dropdown next to Play: the original language
first, then translations with their completion; picking one starts the version in
that language
- The game header gets the same dropdown, aggregated over published, ready versions.
Each language points at the version that has it best (most complete, the main
version on a tie); an original language counts as complete
- The old row of language tags linking to the translation editor is replaced
- Backend: versions carry source_name (the name of the original language)
- pages/languages.ts holds the aggregation logic with tests; LanguageMenu closes on
outside click and Escape
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Music, sound and voice volumes are shared by all games by default; a game that
has never been launched with its own volumes plays with the shared ones
- The sound section of the player settings has a "own volume for this game" switch:
turning it on starts from the current values and then the sliders change only this
game, turning it off removes the override and returns to the shared volumes
- The override belongs to the game (game_id from /api/versions/{id}), so forks and mods
share it; it falls back to the version id when the id is unavailable
- Reading speed and auto delay stay shared
- volume-store.ts keeps the storage logic (damaged data ignored, values clamped,
unavailable storage tolerated) and has its own tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- math: sin/cos/tan/asin/acos/atan/atan2, exp, log, log10, fabs, fmod, modf, trunc,
hypot, degrees, radians, isnan, isinf, copysign, e
- renpy.curry, get_showing_tags, run(action), pop_call; _preferences.get_mute/get_volume
- The compatibility check knows these names
Removes a few more blockers in TheFixer and That_New_Teacher; the rest are custom
systems of those games (AlphaMask wardrobe, custom statements) and authoring defects.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- pyparse.ts: parser for expressions and simple statements built at run time, producing
the sandbox's own IR; backs eval() and exec() (e.g. exec("persistent." + var + " = 0"))
- Replay/EndReplay and renpy.end_replay: the engine stashes the full game state, runs the
label with the given scope and restores everything when it ends; locked replays are
insensitive. Save loading shared as applySave()
- Screen actions are callable (MainMenu(confirm=False)())
- jump main_menu_screen is Ren'Py's own label: it opens the player's main menu
- Screens: a closing quote ends the word before a letter ("text"size 15) but not before
an operator ("a"+b); Text(...) pictures in image blocks
- README: current capabilities and limits of the Ren'Py support
Strict compatibility check on the local collection: Tomie, Twisted-Thoughts, Hooked,
iNSight-of-you and SomethingBetter pass; TheFixer (custom AlphaMask wardrobe, missing
labels) and That_New_Teacher (custom statements, Render API) do not.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Python containing renpy.input, narrator("..."), character calls, renpy.say,
renpy.pause, renpy.display_menu, renpy.with_statement or renpy.call_screen now
runs through loops, try, functions, methods and expressions.
Player
- python.ts: execG/evalG/callG run only the code that may wait (mayBlock);
everything else stays on the synchronous fast path. A waiting call returns a
BlockingCall; outside the generator interpreter it is an explicit error. The
interpreter scope is set aside while the player decides
- engine: drives the generator and answers requests (dialogue, text input with
length/allow/exclude, pause, choice menu, transition, call screen)
- renpy.show/hide/scene, transitions and transforms as Python values,
Character(...) objects, Borders, set_tag_attributes
Converter
- blocking_names() lists game functions that may wait, transitively, and ships
them as game.json "blocking"; character calls are allowed
- hide screen of an unknown screen is harmless; images defined by a parenthesised
expression are supported
Hooked now passes the strict compatibility check (with Tomie and Twisted-Thoughts).
Tests: waiting.py fixture run through the generator interpreter; blocking-name analysis.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>
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>
- Unsupported ATL constructs in a transform now block the import only when the
transform is used (at clause, another transform, even before its definition).
Stock screens.rpy transforms for unused screens no longer fail games
- image blocks with one picture plus linear/ease/parallel/repeat keep their
animation (image-level ATL); the renderer also starts it
- Python: skip @gui.variant/@renpy.variant functions (inactive variants), treat
@renpy.pure as transparent, read renpy.store.x as x
- Accept renpy.transition, end_replay, block_rollback and music set_pause/set_pan
as presentation-only no-ops, and implement them in the player (set_pause and
set_pan were allowed by the converter but missing at runtime)
- Skip the 0x52-URM add-on directory (in-game console / save editor) when
parsing scripts and media
- Tests for lazy ATL warnings, moving images and variants
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Python sandbox
- try/except/else/finally, raise (incl. re-raise), assert, for/while ... else
- Multi-generator and dict comprehensions
- Builtin exception hierarchy; user exceptions subclass it
- Inside try, missing keys/indexes, division by zero, attribute access on None
and unknown names raise Python exceptions; lookups stay lenient elsewhere
- int()/float() raise ValueError on non-numeric strings
ATL
- converter/atl.py: set/move (linear, ease, easein, easeout), pause, repeat,
parallel, choice, block, on show/hide and named transforms become a timeline
shipped as game.json "atl"; Move() becomes a linear slide
- player/atl.ts: timeline runner wired into the renderer; `on hide` plays
before the picture is removed
- function, time, contains, matrixcolor and expressions still block the import
Tests: exceptions/ATL fixtures and runtime tests, converter ATL tests; README
and ARCHITECTURE updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Converter: def/return/global/nonlocal, class (inheritance, property,
staticmethod, classmethod), lambda, *args/**kwargs and call-site unpacking.
Unsupported dunder methods, generators and unknown decorators are still refused
- Player: closures, super(), instances that survive save/load, isinstance,
hasattr/getattr/setattr, map/filter/sorted(key=), recursion limit
- audit_ir accepts calls to names, classes and methods the game defines itself;
unknown renpy.* calls still block the import. Volume/pan/pause/replay calls
are accepted as presentation-only no-ops; build.* settings are skipped
- Tests: IR fixture compiled from tests/fixtures/classes.py (tests/make_ir.py)
and run in the interpreter; converter tests updated
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Add app/games: GameProfile base, registry, ReaderHooks and renpy/naninovel/
tomie profiles. pipeline, preflight, detect and mods dispatch via the registry
- Move tomie_* converter modules to app/games/tomie and split out reader hooks,
import, mod validation and known defects
- Tolerate the 31 authoring defects of the pinned Tomie build as report
warnings (exact list, per-script counts); any new defect still blocks import
- Frontend: runtime registry (runtimes.ts) with lazy Tomie launcher
- Add profile/registry tests and docs/GAME_PROFILES.md; update IMPORT.md
Full media export of the original Tomie build is not yet verified.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add immutable revisions, PGVN editor, asset replacement, merge and rebase workflows, authenticated media delivery and reachability GC. Harden the script interpreter, pin unrpyc and remove tracked build metadata. Include current compatibility checks, Naninovel import and regression tests.
Admin/uploader pastes a thread link and presses "Подтянуть" on the new
game and edit forms; the backend reads the public first post (no login).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- games.main_version_id, version visibility (draft/published) and kind
(original/translation/mod), version_history; migration 0002
- permissions translations.edit and versions.publish
- "Translate" creates a translation draft; publishing merges the changed
languages into the main version in place (players keep their saves), the
previous manifest goes to the history and can be restored
- translation API: overview with per-state stats, entries with filters,
search and scene context, save, glossary, locale font, PO/CSV export/import
- translation editor page: languages and progress, lines and strings,
filters, autosave, approve/outdated/fuzzy actions, glossary highlighting,
"play from here" with the scene restored
- player: ?lang= and ?at= from the editor, per-locale font
(models.py and permissions.py for this landed earlier in 9ee3e1f)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tokens and components in styles.css, shared Ui (dialogs, toasts), icons,
bottom tab bar on mobile, keyboard/gamepad navigation, library shelf with
filters and conversion badges, hero game page, restyled player screens.
Games list now returns per-game version counts. Adds docs/UI_DESIGN.md.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- 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>
- Show a readable message when a reverse proxy rejects the archive with 413
- Delete the freshly created game if its archive upload fails
- Turn ClientDisconnect during upload into a short 400 instead of a traceback
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checked against six real games (Ren'Py 7.5 and 8.x):
- nested labels and named menus become jump anchors inside label bodies
- call with arguments and labels with parameters (dynamically scoped)
- frame-by-frame ATL images, Movie frames, layeredimage, camera, Shake,
custom audio channels via register_channel, show text with expressions
- generator expressions in Python, no SyntaxWarning noise from decompiled code
Also:
- app.import_dir converts an unpacked game folder without changing it
- app.converter.analyze dry-runs the script part of the converter
- imports use their own "importing" status so a worker restart can't hijack them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>