Ren'Py
Status: Planned — No implementation started.
Format
Ren'Py is a Python-based visual novel engine. Games ship with:
.rpy— source scripts (human-readable).rpyc— compiled bytecode (Python marshal format — essentially Python.pyc)game/— directory containing scripts, images, audio, video, fontsrenpy/— bundled copy of the Ren'Py engine (Python source)
Compiled .rpyc files contain Python bytecode (CPython marshal). They can be decompiled with unrpyc (open source). Source .rpy files are often present alongside compiled files in distributed games.
The Ren'Py script language is a DSL layered on Python:
label name:— passage/scene entry pointsscene bg_name/show character_name— display commandsplay music "file"/play sound "file"— audio commandsmenu:/"Choice text":— player choice menusjump label/call label/return— control flow$ python_expression— inline Python[python_expression]— interpolated expression in dialoguewith transition_name— visual transitionsdefine/default— variable declarations
Lifting Strategy
Full recompilation (Tier 2).
- Prefer
.rpysource if available; fall back to decompiled.rpyc - Parse the Ren'Py DSL — it's a superset of Python indented block syntax
- Emit IR per
label(each label is a function with jump/call/return) - Identify
renpy.*API boundaries (display, audio, store variables) - Model Ren'Py's execution model: statement-by-statement coroutine with rollback
The key complexity is Ren'Py's rollback system — the engine can roll back through arbitrary script history for undo. This is implemented via Python generators in the original; in the lifted version it maps to the IR coroutine model (Yield/CoroutineCreate/CoroutineResume).
What Needs Building
Format Parser (new crate: reincarnate-frontend-renpy)
- [ ]
.rpysource parser — Ren'Py DSL (statement grammar, expression grammar)- Script statements:
label,scene,show,hide,play,stop,queue,menu,jump,call,return,if/elif/else,while,for,with,image,define,default,init,python - ATL (Animation and Transformation Language) —
atclauses, transform definitions - Inline Python (
$andpython:blocks) — need Python expression parser or delegate to Python AST
- Script statements:
- [ ]
.rpycdecoder — Python marshal format for CPython 2.x/3.x - [ ] Asset catalog extraction (images, audio, video references from script)
IR Mapping
label name:→ functionjump label→Op::CallorOp::Brto target functioncall label→Op::Callwith return point$ expr→ inline IR from Python expressionmenu: "choice": ...→Op::SystemCall("RenPy.ShowMenu", choices)+ branch on resultshow char at pos with trans→Op::SystemCall("RenPy.Show", ...)scene bg with trans→Op::SystemCall("RenPy.Scene", ...)play music "file"→Op::SystemCall("RenPy.PlayMusic", ...)- Rollback → IR coroutine model with
Yieldat each displayable statement
Replacement Runtime (runtime/renpy/ts/)
- [ ] Display layer: character sprites (show/hide/at), scene backgrounds, z-ordering
- [ ] Dialogue box: name + text rendering with text tags (
{b},{color=},{i}, etc.) - [ ] Choice menu: list of options → player selects → callback
- [ ] Audio: background music (loop, fade), sound effects, voice
- [ ] Transitions: fade, dissolve, pixellate, slide, wipe, etc. (CSS animations or Canvas)
- [ ] ATL transforms: position, rotation, scale, alpha, animation timeline
- [ ] Rollback: history stack of yields, undo/redo navigation
- [ ] Save/load: serialize current yield point + all
store.*variables - [ ] Text tags:
{b},{i},{u},{s},{color=},{size=},{alpha=},{font=},{image=},{nw}(no-wait) - [ ] NVL mode: multi-line message overlay
- [ ]
renpy.input()— text input box - [ ]
renpy.pause()— wait for click/time - [ ]
renpy.notify()— notification popup
Python Integration
Primary approach (reincarnate): transpile Unpack .rpa, decompile .rpyc with unrpyc to recover .rpy source, then lower the Ren'Py AST to IR. Each label becomes a function; scene/show/play/menu become SystemCalls; rollback points become Yield ops. The existing transform pipeline and TypeScript backend produce clean emitted code — analysable, optimisable, and eventually targetable to Rust.
Note: WASM/interpreter alternatives existrenpyweb (official, as of Ren'Py 7.4+) ships the full CPython engine as WASM. Pyodide is a heavier alternative. Both run the original interpreter loop — output is not emitted code, can't target Rust, and is opaque to analysis. These are fine for quick one-off deployment but are not the reincarnate approach.
Known Challenges
- ATL (Animation and Transformation Language) — complex timeline/transform DSL; requires its own parser and animation system
- Python interop —
init python:blocks and$statements may contain arbitrary Python; full fidelity requires Pyodide or a Python→TS transpiler - Persistent variables —
persistent.*variables survive across game sessions and may differ fromstore.*save variables - Custom displayables — games can define
renpy.Displayablesubclasses in Python; these require Python execution - Screens language — the
screenstatement (UI screens) is a separate DSL for constructing UI from widgets; complex to model