feat: mousearea, datetime dates, dir(), renpy.image_size and style.rebuild for Ren'Py games
CI / frontend (push) Failing after 19s
CI / backend (push) Failing after 40s

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>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-26 03:02:13 +03:00
1 parent 6c10d06d90
commit 4dc9836a8b
7 files changed
+119 -4

No files matched your search

+2
View File
@@ -120,6 +120,8 @@ CALLS |= {"renpy.start_predict", "renpy.stop_predict", "renpy.start_predict_scre
"renpy.ui.imagebutton", "renpy.return_statement"}
from app.games.renpy_native import NATIVE_CLASSES, NATIVE_METHODS # noqa: E402
CALLS |= set(NATIVE_CLASSES)
CALLS |= {"dir", "datetime.date", "datetime.datetime", "datetime.timedelta", "date", "timedelta",
"renpy.style.rebuild", "renpy.image_size"}
CALLS |= {"map", "filter", "iter", "hash", "hasattr", "getattr", "setattr", "callable", "super"}
# Screen actions and values the player implements (see frontend/src/player/screens.ts)
SCREEN_CALLS = set("""Jump Call Return SetVariable ToggleVariable SetDict SetField ToggleField
+1 -1
View File
@@ -31,7 +31,7 @@ Node = dict[str, Any]
OPENERS = {"Show", "ShowTransient", "ToggleScreen", "Hide", "show_screen", "call_screen", "hide_screen"}
CONTAINERS = {"fixed", "frame", "window", "vbox", "hbox", "grid", "vpgrid", "viewport", "button", "side", "nearrect"}
LEAVES = {"text", "label", "textbutton", "imagebutton", "add", "image", "null", "bar", "vbar"}
LEAVES = {"text", "label", "textbutton", "imagebutton", "add", "image", "null", "bar", "vbar", "mousearea"}
POSITIONAL = {"side": 1, "text": 1, "label": 1, "textbutton": 1, "add": 1, "image": 1, "grid": 2}
ATL_WORDS = {"pause", "repeat", "parallel", "choice", "block", "contains", "function", "time"}
+11 -2
View File
@@ -79,10 +79,10 @@ def test_screens_styles_and_calls_become_ir():
def test_shown_screens_are_audited_but_unshown_ones_are_not():
data = parse('screen odd():\n mousearea:\n pass\nlabel start:\n "Hi"\n')
data = parse('screen odd():\n frobnicate:\n pass\nlabel start:\n "Hi"\n')
audit_ir(data)
require_supported_scripts(data) # never shown: harmless
data = parse('screen odd():\n mousearea:\n pass\nlabel start:\n call screen odd\n')
data = parse('screen odd():\n frobnicate:\n pass\nlabel start:\n call screen odd\n')
audit_ir(data)
with pytest.raises(CompatibilityError, match="screen"):
require_supported_scripts(data)
@@ -169,3 +169,12 @@ def test_a_closing_quote_ends_the_word_and_text_pictures_are_understood():
first = data.screens["hud"]["body"][0]
assert first["props"]["size"] == {"k": "const", "v": 15}
assert data.images["log_line"]["textExpr"]["k"] == "call"
def test_mousearea_is_a_leaf_with_hover_actions():
data = parse('screen s():\n mousearea:\n xysize (100, 50)\n hovered SetVariable("h", True)\n'
' unhovered SetVariable("h", False)\nlabel start:\n call screen s\n')
audit_ir(data)
require_supported_scripts(data)
node = data.screens["s"]["body"][0]
assert node["w"] == "mousearea" and {"hovered", "unhovered", "xysize"} <= set(node["props"])