[Tooling] Deterministic snippet screenshots as a visual-regression oracle (rendered into the doc-site repo, not the engine) #381
Labels
No labels
Alpha Release Requirement
Bugfix
Demo Target
Documentation
Major Feature
Minor Feature
priority:tier1-active
priority:tier2-foundation
priority:tier3-future
priority:tier4-deferred
Refactoring & Cleanup
system:animation
system:documentation
system:grid
system:input
system:performance
system:procgen
system:python-binding
system:rendering
system:ui-hierarchy
Tiny Feature
workflow:blocked
workflow:needs-benchmark
workflow:needs-documentation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
Reference
john/McRogueFace#381
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Generate a rendered preview PNG for each of the ~283 gated snippets in
tests/snippets/and surface it on the docs site next to the code. The snippets-as-tests harness already proves each one runs clean and leaves a scene with children — i.e. every snippet is renderable — so this is a pipeline extension, not new engine capability.Requirements
Screenshots are a visual-regression oracle, not decoration
A changed image is a signal that behavior changed and needs review — the same golden/snapshot discipline we already apply to the API surface. This is the whole reason to make them deterministic: a diff in the rendered output is a reviewable event, not noise.
Deterministic capture
randomand the engine RNG in the harness so procgen/WFC/cellular snippets (086_procgen_cellular,163_grid_dungeon_generator,170_procgen_cellular_caves,171_procgen_wfc,273_part_03_dungeon_generation, ...) reproduce byte-for-byte.# mcrf:header the stamper already owns).Motion → sim-time frame selection
For animated/timer snippets, advance the render loop with
step()to a chosen sim time and capture the frame that best demonstrates the behavior — deterministic frame selection, not an arbitrary "settled" grab. #350 (headlessstep()now runsupdatePythonScenes()) unblocks this;step()also fires exactly one timer event per call regardless of dt, so frame selection must account for that.Storage: doc-site repo, NEVER the engine repo
PNGs are generated artifacts. By the docs-pipeline rule (the site pulls from the engine; generated things are regenerated, never hand-edited), a
make snippet-shotstarget in the engine writes to a gitignored dir; the doc-site build consumes them and the site repo is where they're committed. The engine repo must not accumulate hundreds of binaries.Sketch of the pieces
tests/snippets/_harness.pypath) to seed RNG +automation.screenshot()at the selected frame.make snippet-shotstarget → gitignored output dir.# mcrf:header +tools/stamp_snippets.py).build_library.pyconsumption + commit into the doc-site repo.Related
43a6cc3→07442dd;make {docs,stamp-snippets,check-snippets,release-docs}).tests/demo/screensorphans).Audit results (4-agent fan-out over all 282 snippets)
Capture-strategy taxonomy
Most snippets render their headline visual at load — many that attach
on_key/on_clickare already fully drawn, so a plain post-setup screenshot is correct (input handler is a secondary tag, not an auto-trigger). Distribution under that lens:noshot: 142, 161, 225, 226, 251, 253, 254, 255, 265, 268A default STATIC capture (setup + 3 steps + screenshot) covers ~180 snippets with zero per-snippet tuning. Only animation (target time) and truly-hidden interaction (modals/tooltips) need hand-annotation.
Metadata model (
# mcrf:header extension)Not a 3-way enum — STATIC is the degenerate default:
Traps to encode (not infer)
on_keycalls undefined functions → a keypress raises. Never send keys.step(), 169 callsautomation.screenshot()): don't double-drive.Determinism — resolved, stays harness-only
25 of 29 random snippets go byte-stable with one
random.seed(N)in_harness.py(shared interpreter RNG).NoiseSource/HeightMapsnippets already pass explicitseed=. The only gap was BSP:split_recursive()with noseed=pulls libtcod'stime(0)-seeded global RNG (mersenne_c.c:150), unreachable from Python (there is nomcrfpy.seed()). Of the 7 BSP snippets, 3 arenoshottext-only demos, so only 4 need deterministic pixels: 060, 102, 224, 243.Decision: edit those 4 to pass
seed=42(also better docs — shows users how to get a reproducible dungeon). No engine change; #381 stays a Minor Feature.Proposed phasing
random.seed()in_harness.py+seed=42in the 4 BSP snippets; default STATIC capture (make snippet-shots→ gitignored dir); prove byte-stability by capturing twice and diffing. Ships previews for ~180+ snippets immediately.shot_attarget-time annotations for the ~77 ANIMATION snippets.actionfor the ~25 hidden-interaction snippets + headless mouse simulation.noshoton the 10 opt-outs throughout.