`init -490 screen`, `import x as y` and `from m import A as B`, classes with @renpy.pure, pickle hooks
on classes, `*` items in tuples and lists, generator functions (yield statements; the player runs them
to the end), multi-line expressions that need brackets, Null() and Animation() images, images given as
expressions (`"path/{}.png".format(x)`, f-strings), window statements in init, `offset` in ATL, input
widgets with their values and the focus/confirm actions, `continue` and `break` in screens, `drag`
and DiamondBox-style `fixed` with cell_size, `getattr(...)()` calls, transforms called with arguments,
and the whitelist for the new runtime. JSON files of a game are shipped as data files. Also fixes
blocking_names failing on a call of a call during full conversion.
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>
- 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>
- 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>
- 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>
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>