mobilegl-dev/mobilegl/trace-fixture-authoring
Claude Code and agent skill by mobilegl-dev/mobilegl
- community
Checking that the SKILL.md file is still in the repository.
Author a deterministic MobileGL trace-replay fixture from a captured apitrace - build the in-tree apitrace fork, capture a reproducible scene, frame-trim with gltrim, generate and verify a golden image, package under the archive-size budget, register the case in trace_cases.json, and validate on Linux and Android. Use when adding or re-trimming a trace_replay regression fixture.
Install
npx vskill@latest install mobilegl-dev/mobilegl/trace-fixture-authoringSKILL.md
Trace fixture authoring
Variables
Prerequisites
Install:
- CMake
- Ninja
- C++ compiler
- Python 3
- Mesa OpenGL/EGL runtime
- Vulkan loader and ICD for
DirectVulkan - Pillow or ImageMagick for alpha cleanup
- Android SDK, Android NDK, JDK, Gradle, and
adbfor Android replay
Build apitrace
Build the in-tree fork, not an upstream release: it carries the frametrim handlers for DSA and ARB_multi_bind call streams and shadow-based tracking of persistent-mapped buffers, all of which modern Minecraft mod stacks need.
On Windows, set APITRACE and GLRETRACE to the corresponding .exe files and also build the wgltrace target: apitrace trace fails with "failed to find opengl32.dll wrapper" unless wrappers/opengl32.dll was built. gltrim may be built and run on a Linux/WSL checkout instead; traces are portable, and trimming a multi-hundred-MB trace is much faster from a native filesystem copy than through /mnt/c.
Prepare the capture
- Set the target window size to
WIDTHxHEIGHT. - Disable unintended overlays, frame counters, notifications, and launcher UI.
- Fix language, resource packs, mods, shader pack, world seed, time, weather,
player position, camera direction, FOV, GUI scale, and render distance.
- Trace the final OpenGL process, not the launcher.
- For Minecraft, document version, mod loader, mods, shader pack, language,
world, time, and camera setup.
Minecraft specifics that keep the capture deterministic and small:
- Freeze the world in
level.dat:doDaylightCycle,doWeatherCycle,
doMobSpawning, randomTickSpeed 0, a fixed DayTime, and the player Rotation that frames the intended subject. The camera snaps to the saved rotation on world join, so composition is edited in the save, not in-game.
options.txt:pauseOnLostFocus:false, a lowmaxFps(10 works), a small
renderDistance (3), and the capture resolution pinned to WIDTH x HEIGHT (854x480) via overrideWidth/overrideHeight (or --width/--height). These are the main levers on fixture size: a low frame rate keeps the full trace short, a small render distance keeps per-frame geometry down, and the 854x480 resolution keeps every render target the frame references small (a trimmed frame's framebuffer/attachment textures scale with resolution squared). A ~35 s in-world session at 10 fps and 854x480 lands well under the archive budget after repack.
maxFpshas a practical floor: Minecraft ignores values below ~10 and falls
back to unlimited/vsync (a maxFps:1 capture rendered ~60 fps and ballooned the trace). 10 is as low as this lever goes, so do not count on a lower frame rate to shrink the frame count further.
- Enter the world non-interactively with
--quickPlaySingleplayer <world>so
every capture takes the same path from boot to gameplay.
- Keep the game window UNFOCUSED for the whole capture (focus the desktop
right after launch, and again before closing). A focused Minecraft window grabs the mouse, and any physical mouse motion rotates the camera - the resulting goldens show a drifted view that is easy to misread as a rendering bug.
- On Windows with JDK 21+, pass
-Djdk.net.unixdomain.tmpdir=<short-path-without-spaces>: NIO selectors create AF_UNIX sockets under %TEMP%, which fails on some hosts and kills the game at boot with "Unable to establish loopback connection".
Capture
For Java:
An @argfile with the full JVM+game command line keeps the invocation reproducible across recaptures.
NEVER put a real credential on the traced command line. apitrace records the traced process's argv into the trace as a process.commandLine property, so anything passed there - --accessToken, session tokens, API keys - is embedded in the trace and ships inside the committed fixture. Minecraft never validates --accessToken for singleplayer, so pass a placeholder (--accessToken 0); --username/--uuid are public and may stay real. Before packaging, grep the UNCOMPRESSED trace for the secret to confirm it is absent:
If a secret has already been captured, it can be scrubbed in place instead of recapturing: apitrace's snappy container is [length][raw snappy] chunks with no checksum, and a high-entropy secret is stored as literal bytes, so replacing those bytes with an EQUAL-LENGTH filler keeps the container valid and leaves the GL call stream byte-identical. Blank every maximal run of the secret (it splits across chunks), then verify: frame count unchanged, the decompressed trace no longer contains the secret, and the replayed target frame still matches the golden. Treat any already-pushed trace as leaked regardless - rotate the credential, since a force-push does not purge the LFS object from the remote.
Watch for vendor-gated shader paths. Shader packs branch on the GL vendor that Iris injects (MC_GL_VENDOR_NVIDIA / _AMD / ...) and compile a vendor-exclusive path, so capturing on an NVIDIA card can bake NVIDIA-only GLSL into the fixture (iterationRP selects subgroupPartitionNV / GL_NV_shader_subgroup_partitioned instead of the portable subgroupShuffleXor). Iris resolves the #ifdef before glShaderSource, so only the taken branch is in the trace and the fixture cannot replay on the mobile GPUs MobileGL targets. Rather than hunting for a second GPU (the Windows per-app GPU preference does NOT change which OpenGL ICD is loaded), mask the vendor at capture time with apitrace's own config - point GLTRACE_CONF at a file containing:
The wrapper then returns that from glGetString, so the pack compiles the portable path while still running on the fast driver. Pick a string that does NOT contain the real vendor name as a substring (Iris matches by substring, so "Not NVIDIA ..." would still match) and that is self-describing, so nobody later mistakes the trace for a capture on different hardware. Afterwards, grep the decoded trace to confirm the vendor-exclusive symbols are gone:
Software rasterisers are not a substitute here: llvmpipe exposes no GL_KHR_shader_subgroup at all, and packs that use subgroup ops unguarded cannot run on it in any vendor configuration.
Keep full.trace until both backends are validated.
Persistent-mapped buffers: apps may legally write a GL_MAP_PERSISTENT_BIT mapping and let the GPU read it without an explicit flush (Flywheel's indirect backend writes its compute scatter descriptors this way). Stock apitrace never records those writes, so the trimmed fixture silently loses the content that depends on them - the symptom is geometry that renders live but disappears in replay. The in-tree fork shadow-tracks persistent mappings unconditionally; if a replay of full.trace is already missing content that the live run showed, fix capture (wrapper) first - no amount of trimming will bring the data back, and the case must be recaptured. There is also a replay-side requirement: MobileGL only forwards such never-flushed writes when MOBILEGL_COHERENT_AS_FLUSH=1, so register the case with "coherent_as_flush": true (see "Register the case").
Select target frame
Fixture selection must be frame-based. Do not trim the fixture from a full trace by filtering arbitrary call ranges or single full-trace calls. Pick a rendered frame, then trim with gltrim -f.
A quick way to bound the choice is the total frame count from a benchmark replay:
Pick a LATE frame (roughly N - 20): early frames still contain loading screens, chunk pop-in, and animation warm-up, while the last few frames may overlap the window-close path. Inspect frames.txt or snapshot the candidate frame to confirm it contains the intended visual state, then set:
Trim and package
Then VERIFY the trim before doing anything else: replay trace.trace, snapshot its final swap, and compare the content against the same frame of full.trace. gltrim bugs fail silently - the classic symptom is a trimmed trace whose static world renders fine while everything driven by less common call patterns (DSA texture binds, glBindBuffersRange multi-bind setup, compute-written buffers) is missing or garbled, often with "invalid buffer name"-style retrace warnings. If content is missing from the trimmed trace but present in the full trace, the fix belongs in 3rdparty/apitrace's frametrim, not in the fixture.
Temporal shaders (auto-exposure / eye adaptation, TAA, temporal reflections - e.g. the iterationRP shader pack) break a single-frame gltrim -f: the target frame reads its predecessors' feedback buffers, which the isolated frame no longer contains, so a mid-sequence frame replays overexposed to white (and any timed name/version overlay the pack draws in its first seconds silently drops). The symptom is a trimmed frame that looks blown-out or washed while the same frame of full.trace renders correctly, and it gets worse the later the frame. When a single-frame trim of such a pack cannot be made to render correctly, keep the temporal history instead of the dependency slice: select an early in-world target frame and trim a PREFIX with apitrace trim --calls=0-<target-swap-call> (it preserves call numbers, so target_call is just that swap call). The prefix replays every frame up to the target, so its temporal buffers are correct. Prefer the earliest frame that already shows the intended subject - fewer lead-in frames means a smaller archive and a faster CI replay. This deviates from the single-frame rule deliberately; note it in the README entry.
Generate golden
Generate frame snapshots from the trimmed trace, then choose the snapshot that matches the selected frame. The target call used by replay registration must come from the trimmed trace, not from a call-filtered full-trace selection.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingtools/trace_replay/skills/trace-fixture-authoring/SKILL.md
Popularity
- Repository stars
- 175
- Repository forks
- 87
- 7-day trend score
- 0
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/mobilegl-dev/mobilegl/trace-fixture-authoring)