acron.testing¶
Drivers for proving a game against its real engine — offscreen and in-window.
Three tiers of proof a headless suite cannot give, each grown out of a shipped game’s tools:
Offscreen app driving — boot the real app with no window, pin the clock, step frames, screenshot. The rules that make it behave:
offscreen()must run BEFORE ShowBase (import time, run as a script, never from a live app); the clock must be non-real-time or the fixed-step loop under-runs (offscreen renders as fast as it can); anything the app drives per frame (a chase rig, an input poll) is overridden after its own update, not fought.Frame-diff probing — consecutive rendered frames compared in RGB, the only detector for the render-blindness class (state-order flicker, winding, dead uniforms) that value-comparing tests cannot see.
Pointer/click injection — a REAL window, the pointer moved onto a control and the press injected through the window’s own input device (
GraphicsWindowInputDevice.button_down). Synthesising at the OS level instead (QuartzCGEventPost) is silently dropped without an Accessibility grant — a false pass waiting to happen. Element offsets are already in WINDOW pixels; do not scale them by the dp ratio.
PASS/FAIL lines with a failure tally — the shape of a proving tool. |
|
|
Inject pointer moves and mouse presses through a real window. |
|
One rendered frame as 8-bit RGB, TOP row first (the drawing frame: |
Global Functions
- after_call(obj, name: str, extra) None¶
Wrap
obj.namesoextra()runs AFTER it, every call.The way to drive through a layer that rewrites its outputs per frame (an input poll, a touch overlay): setting fields directly does not work — the layer overwrites them — so the override rides behind its own update.
- fixed_clock(base, dt: float = 0.016666666666666666) None¶
Pin the global clock to a fixed dt: offscreen renders as fast as it can, and a real-time clock makes a fixed-step loop under-run.
- flicker_scan(base, frames: int = 40) list[tuple[float, float]]¶
Step
framesframes and compare each to the one before.Returns per-pair (mean, pct) stats; the caller picks its threshold (mean |ΔRGB| > 1 on an 8-bit frame is the proven “flashing” bar for a static scene). Everything that legitimately animates — sun, grain, a chase rig — must be stood down first, or the probe measures the game instead of the renderer.
- gl_spam_frame(base, path)¶
Render ONE frame with the engine’s GL call stream spammed to
path(glUseProgram, every glBindTexture, each program’s altered-uniform mask), then restore. Returnsframe_rgb()of the frame, so the caller can keep only pairs that differ on screen.Spam is on only while the frame renders — the screenshot’s own readback stays out of the stream.
- memory_footprint() int¶
The process’s physical memory footprint, in bytes — what a memory invariant should measure.
On macOS this is
task_info(TASK_VM_INFO).phys_footprint(Activity Monitor’s “Memory”): resident pages the process is charged for. RSS is the wrong number there — pages a freed allocation left behind stay resident-but-reusable until the kernel wants them, so RSS barely moves when a scene is released even though every object is gone. Elsewhere the resident set from/proc/self/statm(Linux) orru_maxrss(a peak, the last resort).
- offscreen(size: str | None = None) None¶
Ask for an offscreen window. MUST run before ShowBase — call it at import time in a tool script.
sizelike"960x540".