feat: conditional "at" transforms in screens and comma-separated ATL statements
CI / frontend (push) Failing after 18s
CI / backend (push) Failing after 35s

`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>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-26 03:21:55 +03:00
1 parent 0822c76321
commit b3f435f416
5 files changed
+85 -3

No files matched your search

+49 -1
View File
@@ -52,7 +52,9 @@ NAMED_POSITIONS = {
"topright": {"xpos": 1.0, "xanchor": 1.0, "ypos": 0.0, "yanchor": 0.0},
}
EVENTS = {"show", "hide", "replace", "replaced", "start", "update", "appear", "default", "hover", "idle",
"selected_hover", "selected_idle", "insensitive", "selected_insensitive", "selected"}
"selected_hover", "selected_idle", "insensitive", "selected_insensitive", "selected",
# Ren'Py itself never sends these (its selected states are the selected_* ones): the handler is inert
"deselected", "unselected"}
GLUED_NUMBER = re.compile(r"^(\d+(?:\.\d*)?|\.\d+)([A-Za-z_]\w*)$")
@@ -102,6 +104,40 @@ def _split(text: str) -> list[str]:
return out
#: properties that take two numbers; `pos 0.5, 0.5` stays one statement
TWO_VALUE = {"pos", "anchor", "align", "offset", "around", "size", "crop", "xysize", "xycenter", "alignaround"}
def statements(text: str) -> list[str]:
"""Split an ATL line at commas outside brackets and quotes: `zoom 1.2, 0.2` is `zoom 1.2` then a pause."""
pieces, cur, depth, quote = [], [], 0, ""
for ch in text:
if quote:
quote = "" if ch == quote else quote
elif ch in "\"'":
quote = ch
elif ch in "([{":
depth += 1
elif ch in ")]}":
depth -= 1
elif ch == "," and depth == 0:
pieces.append("".join(cur).strip())
cur = []
continue
cur.append(ch)
pieces.append("".join(cur).strip())
merged: list[str] = []
for piece in pieces:
if not piece:
continue
previous = merged[-1].split() if merged else []
if len(previous) == 2 and previous[0] in TWO_VALUE and len(piece.split()) == 1:
merged[-1] = f"{merged[-1]} {piece}"
else:
merged.append(piece)
return merged
class AtlParser:
def __init__(self, warn: Callable[[str, Line], None], is_static: Callable[[str], dict | None],
on_use: Callable[[str], None] = lambda name: None,
@@ -188,7 +224,19 @@ class AtlParser:
# --- block ----------------------------------------------------------------------
def expand(self, lines: list[Line]) -> list[Line]:
"""Lines holding several comma-separated statements become one line per statement."""
out: list[Line] = []
for line in lines:
pieces = statements(line.text.strip()) if not line.block and "," in line.text else []
if len(pieces) > 1:
out.extend(Line(piece, line.lineno, line.indent, line.physical) for piece in pieces)
else:
out.append(line)
return out
def block(self, lines: list[Line]) -> Block:
lines = self.expand(lines)
steps: list[dict[str, Any]] = []
repeat: Any = None
i = 0