feat: full math module and small renpy helpers
CI / frontend (push) Successful in 34s
CI / backend (push) Successful in 57s

- math: sin/cos/tan/asin/acos/atan/atan2, exp, log, log10, fabs, fmod, modf, trunc,
  hypot, degrees, radians, isnan, isinf, copysign, e
- renpy.curry, get_showing_tags, run(action), pop_call; _preferences.get_mute/get_volume
- The compatibility check knows these names

Removes a few more blockers in TheFixer and That_New_Teacher; the rest are custom
systems of those games (AlphaMask wardrobe, custom statements) and authoring defects.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 14:47:34 +03:00
1 parent 3ac4f0371f
commit 52f6e7c18f
2 files changed
+34 -2

No files matched your search

+7 -1
View File
@@ -222,7 +222,13 @@ ENGINE_LABELS = {"main_menu_screen", "_main_menu_screen", "main_menu"}
BLOCKING_BUILTINS = {"renpy.input", "renpy.say", "renpy.pause", "renpy.display_menu", "renpy.with_statement",
"renpy.call_screen"}
CALLS |= BLOCKING_BUILTINS | {"renpy.show", "renpy.hide", "renpy.scene", "Borders", "renpy.set_tag_attributes",
"eval", "exec", "Replay", "EndReplay", "renpy.end_replay"}
"eval", "exec", "Replay", "EndReplay", "renpy.end_replay",
"renpy.get_showing_tags", "renpy.run", "renpy.pop_call",
"renpy.curry", "_preferences.get_mute", "_preferences.get_volume",
"preferences.get_mute", "preferences.get_volume"}
CALLS |= {"math." + name for name in (
"sin cos tan asin acos atan atan2 exp fabs trunc hypot log log10 fmod modf degrees radians isnan isinf "
"copysign").split()}
def blocking_names(data) -> list[str]:
+27 -1
View File
@@ -344,6 +344,20 @@ export class Engine {
throw new JumpSignal("jump", "__quit__");
},
version: () => "pgvn",
get_showing_tags: () => this.renderer.snapshot().map((image) => image.tag),
run: (action: any) => {
this.screens.run(action, null);
return null;
},
pop_call: () => {
this.stack.pop();
return null;
},
curry: (f: any) => {
const arity = f?.__def ? f.__def.params.filter((p: any) => p.kind === "p" && !p.d).length : f?.length ?? 0;
const curried = (...args: any[]): any => (args.length >= arity ? f(...args) : (...more: any[]) => curried(...args, ...more));
return curried;
},
...this.screens.renpyApi(),
// presentation-only: nothing to roll back or replay, and the next transition is chosen by `with`
end_replay: () => {
@@ -391,7 +405,17 @@ export class Engine {
set: (_t, k: string, v) => ((this.store[k] = v), true) }),
audio: this.game.audio,
random,
math: { floor: Math.floor, ceil: Math.ceil, sqrt: Math.sqrt, pi: Math.PI, pow: Math.pow },
math: {
floor: Math.floor, ceil: Math.ceil, sqrt: Math.sqrt, pi: Math.PI, e: Math.E, pow: Math.pow,
sin: Math.sin, cos: Math.cos, tan: Math.tan, asin: Math.asin, acos: Math.acos, atan: Math.atan,
atan2: Math.atan2, exp: Math.exp, fabs: Math.abs, trunc: Math.trunc, hypot: Math.hypot,
log: (x: number, base?: number) => (base ? Math.log(x) / Math.log(base) : Math.log(x)),
log10: Math.log10, fmod: (a: number, b: number) => a % b,
modf: (x: number) => [x - Math.trunc(x), Math.trunc(x)],
degrees: (x: number) => (x * 180) / Math.PI, radians: (x: number) => (x * Math.PI) / 180,
isnan: Number.isNaN, isinf: (x: number) => x === Infinity || x === -Infinity,
copysign: (a: number, b: number) => Math.abs(a) * (b < 0 || Object.is(b, -0) ? -1 : 1),
},
len: (x: any) => (x instanceof PyDict ? x.map.size : x?.length ?? 0),
// a read-only view: `"name" in globals()` works, writes do not reach the game
globals: () => new PyDict(Object.entries(this.store)),
@@ -450,6 +474,8 @@ export class Engine {
get language() {
return engine.tr.renpyName;
},
get_mute: () => false,
get_volume: () => 1,
};
}