feat: add ATL animation timelines and try/raise to the Ren'Py player

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>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 12:11:07 +03:00
1 parent 962595c925
commit ff82d882c0
18 files changed
+1050 -53

No files matched your search

+31 -4
View File
@@ -89,10 +89,37 @@ def test_unsupported_renpy_logic_never_degrades_to_ready(source, reason):
require_supported_scripts(data)
def test_renpy_atl_animation_is_rejected_instead_of_becoming_static():
data = script('transform moving:\n linear 1.0 xalign 1.0\nlabel start:\n "Hi"')
with pytest.raises(CompatibilityError, match="ATL"):
require_supported_scripts(data)
def test_renpy_atl_animation_becomes_a_timeline_not_a_static_pose():
data = script('transform moving:\n xalign 0.0\n linear 1.0 xalign 1.0\n repeat\nlabel start:\n "Hi"')
require_supported_scripts(data)
assert data.transforms["moving"] == {"xalign": 0.0}
block = data.atl["moving"]
assert block["repeat"] is True
assert [step["op"] for step in block["steps"]] == ["set", "move"]
assert block["steps"][1]["props"] == {"xalign": 1.0} and block["steps"][1]["warp"] == "linear"
def test_renpy_atl_constructs_without_a_runtime_are_still_rejected():
for body in ("function slide", "time 1.0:\n xalign 1.0", "matrixcolor TintMatrix('#f00')",
"contains:\n 'a.png'"):
data = script(f'transform odd:\n {body}\nlabel start:\n "Hi"')
with pytest.raises(CompatibilityError, match="ATL"):
require_supported_scripts(data)
def test_renpy_atl_groups_events_and_named_transforms():
data = script(
"transform base:\n zoom 0.8\n"
"transform bob:\n base\n parallel:\n ease 1.0 yoffset -10\n ease 1.0 yoffset 0\n"
" repeat\n parallel:\n pause 0.5\n"
" on hide:\n linear 0.3 alpha 0.0\n"
'label start:\n "Hi"')
require_supported_scripts(data)
steps = data.atl["bob"]["steps"]
assert steps[0] == {"op": "set", "props": {"zoom": 0.8}} # known static transform is inlined
assert [s["op"] for s in steps] == ["set", "parallel", "on"]
assert len(steps[1]["blocks"]) == 2 and steps[1]["blocks"][0]["repeat"] is True
assert steps[2]["events"] == ["hide"]
def test_source_encoding_is_preserved_or_explicitly_rejected(tmp_path):