README media¶
Every file the README embeds. Most of it is generated by a script and needs no screen, which
is the part worth knowing. The exceptions are in the table: three tile-*.png screenshots and
the hand-drawn family.svg are final and are not regenerated here. ../viewer-trace.png, the picture at the top of
the README, is a copy of engine-trace.png.
The generator is RoyaleGym/docs/media/make_media.py,
which is ../../../RoyaleGym/docs/media/make_media.py from here in a side-by-side clone. The link
is the web URL rather than that relative path because a relative link with more .. than the page
is deep escapes the repository, and GitHub does not clamp it: it rewrites the path under this
repo's own tree and serves a 404. It lives in RoyaleGym because it writes into all four repos.
From the folder that holds them:
The tile-compare and tile-paths-and-targets crops have a generator of their own here,
because they are crops of this viewer's window on the recordings that ship with the tests:
Underneath it is this repo's own capture(), which writes the window to a png, mp4 or gif
with no window, no display and no clock. The same source and arguments give the same bytes,
so regenerating is a clean diff. See docs/internals.md for the function.
| File | How it is made | What it shows |
|---|---|---|
engine-trace.png / .gif / .mp4 |
make_media.py engine-trace |
An engine trace opened at tick 900, with a unit pinned in the inspector. |
compare-ghost.gif / .mp4 |
make_media.py compare-ghost |
The two recordings of the scripted battle, the second drawn on the first as hollow ghosts. |
replay-scrubbed-4x.gif / .mp4 |
make_media.py replay-scrubbed |
The scripted battle that ships with the tests, played back. |
live-training-env.png |
make_media.py live-training-env |
A real environment in another process, streaming over UDP to the viewer. |
tile-compare.png |
make_tiles.py compare |
The compare panel at the end of the scripted battle, recorded from both seats: 395 ticks compared, 0 differ. |
tile-paths-and-targets.png |
make_tiles.py paths-and-targets |
A recorded unit's route and another's target line, at tick 162 of the scripted battle. |
tile-event-log.png, tile-inspector.png |
make_tiles.py event-log inspector (the engine-trace battle at tick 900) |
The event list, and every raw field of a pinned unit. |
tile-live-stream.png, ../viewer-learning.png |
make_tiles.py live-stream learning (tests/run_stream.py) |
The viewer attached over UDP to a scripted battle and a scripted learner. |
tile-synthetic-battle.png |
capture() on the scripted battle (tests/synthetic.py), tick 1015, crop="full" |
The whole window, rendered with no display. |
family.svg |
hand-drawn | the Royale repos and how they depend on each other; final |
Two things the captions have to keep saying¶
The scripted battle is a script. tests/synthetic.py builds frames directly and no engine
is involved. In its recording form the units move at six times their scripted speed. It is
honest as a picture of the window and dishonest as a picture of how the engine plays, so
replay-scrubbed-4x and compare-ghost are captioned as the scripted battle and never as a
real match. The two recordings are also identical to each other, so a caption must not imply
the ghost clip shows a disagreement it does not show.
The live shot is the one that is not reproducible, on purpose. Everything else here has
its on-screen timing frozen, because a picture that changes every run is a diff nobody can
review. live-training-env keeps the real frame rate and the real drop count, because the
frame rate is the thing it is showing. The copy taken for the README admits 86 dropped frames
of 116, which is what a busy machine looks like, and the README says so rather than cropping
it out.
Some of these are not freely regenerable¶
Regenerating an image invalidates any prose that quotes what is inside it, and the generator cannot know which prose that is.
live-training-env.png is the live case. The README's caption cites its status line, "86
dropped frames out of 116", to make a point about what a busy machine looks like. Re-shooting it
produces a different frame count and a different drop count, and the caption goes stale in the
same breath. It happened once already: the picture was re-shot on a report that its learner
string had changed, the string turned out to be conditional and had not changed, and the only
effect was to break a caption that had been true.
So before regenerating any picture, grep the README for the numbers written on it. If the prose quotes them, the regeneration is a prose job as well as a rendering one.
Where each picture's battle comes from¶
Every picture in this repo is drawn from the engine, from a running environment, or from the
scripted battle in tests/fixtures, which is committed. None is drawn from a recording of a
real match: those recordings are private, so a picture made from one is a picture nobody else
can make or check, and no caption here names one.
A picture made from a live engine records WHICH engine. royalegym's config() carries
engine_binary_sha256, the hash of the compiled extension that produced the frames, beside
build_digest, which is the data it was built from. The two move independently: a rebuild
from a changed Rust tree leaves build_digest alone, so a picture stamped only with the data
cannot tell you that the thing being pictured changed. Write the binary hash into the note
beside any new picture taken from a running engine or a fresh trace, and take it in the
process that produced the frames. The hash is read off the extension file on disk the
first time it is asked for and kept for that process, so one fetched afterwards, from a
second process, describes whatever is on disk by then rather than what drew the picture:
the after-the-fact stamp again, one level down. Inside a single process they cannot
diverge on Windows, where the loaded .pyd is refused for writing while it is held. The pictures already
here predate the stamp and are not being given one after the fact; the ones drawn from
tests/fixtures need none, because the fixtures are the committed input and the generator is
in the repo.
| Picture | Its battle |
|---|---|
engine-trace.*, tile-event-log.png, tile-inspector.png, ../viewer-trace.png |
an engine trace |
live-training-env.png, tile-live-stream.png, ../viewer-learning.png |
a running environment, streaming over UDP |
replay-scrubbed-4x.*, compare-ghost.*, tile-compare.png, tile-paths-and-targets.png, tile-synthetic-battle.png |
the scripted battle in tests/fixtures |
family.svg |
hand-drawn, no battle |
Two of these were hand-taken screenshots of a REAL match until 2026-09-22: tile-compare.png
and tile-paths-and-targets.png. They were the only pictures here nobody else could
reproduce, and the compare tile printed a recording's name on its own face. Both are now
crops of the window on the committed recordings, made by make_tiles.py.
The other four hand-taken screenshots went the same way on 2026-10-01: tile-event-log,
tile-inspector, tile-live-stream and ../viewer-learning.png are made by make_tiles.py
too, on the current window, so no picture here is taken by hand any more. A generated picture
updates when the window changes and can be diffed; what makes it publishable is still where its
BATTLE came from, not that a script pressed the button.