Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.9 KiB
Running and observing the game as an agent
How an agent runs a build into gameplay without a person at the controls, captures what the game shows, and reads the results without flooding its context. Setting names and file formats live in settings.md and controls.md; this page says which to use when. How to judge a performance change: pr-review-kit/02-performance.md.
Before the first run
- A build dir per change. Build into its own folder (
tools/build_game.py --out build/<ver>-<name>) so a comparison has two exes side by side. After every build check the exe's modification time: a failed link leaves the oldtotk.exein place and you test old code. - Prepared shaders in that folder.
tools/prepare_shaders.py build/<dir> --version <ver>(building.md step 6). Without it the game compiles shaders during play, and every timing and every "a draw is missing" shot of the first minutes says nothing about your change. Shader and pipeline caches are not copied between build folders. - The same
totk.inion both sides of any comparison. A folder without one runs at the defaults; check the log'sresolution:line of each run before comparing them. - Saves.
tools/run_game.pycopies the profilebuild/profile, swaps in a save frombuild/save-backup/<name>/and deletes the copy afterwards, so the real profile is never written. To make a backup, copy the user folder frombuild/profile/save/intobuild/save-backup/<name>/. Pick a save that starts where the change acts.
Running
.venv\Scripts\python tools\run_game.py build\<dir> --save <name> --seconds 30 [--script <input.txt>] [--shot out.png] [--env KEY=VALUE] [--quiet] [--tag <name>]
--secondsis game time since the world loaded (the log lineframe step: world clock startsmarks the start), at any frame rate. Measure and script by game time; a frame count at another fps is another scene.--scriptreplays input (controls.md, "Scripted input"). A new script starts with the presses oftools/input/load_save.txt, which load the first save from the title.TOTK_INPUT_RECORDturns a person's play into a script; ask the user to record the repro rather than guessing the button sequence.--shotsaves the last frame and builds pipelines synchronously: use it for pictures, never for timing.--quietwaits until no othertotk.exeorninja.exeruns. Every timing you report comes from a--quietrun.- The log goes to
<dir>\logs\<tag>.log; the tool prints<log>: rc=<code>.
Run one game at a time: two instances share the GPU's memory and the CPU, and both runs' numbers are void.
Running totk.exe yourself: set TOTK_ASSUME_FOCUS=1 (an unfocused window otherwise pauses the game),
TOTK_INPUT=none (the user's keyboard and pad stay out) and TOTK_BACKGROUND_WINDOW=1. Stop only the process you
started, by PID: taskkill /IM totk.exe also closes the user's game.
Check that the run reached the world before trusting it: the world clock line is in the log and the shot shows the scene you expected. Title menus on a slow boot eat presses, and runs of one script drift apart after 10-15 game seconds (enemy AI, physics, weather). Two runs are comparable only when shots at the same game second show the same scene.
Observing
| You need | Use |
|---|---|
| pictures over a time span | TOTK_SHOTS=w<lo>-<hi>:<step> (world seconds) and TOTK_SHOTS_DIR; 480x270 by default |
| the frames around a hitch or a one-frame glitch | TOTK_FLIGHT=1: keeps the last 600 frames with timings and thumbnails, dumps on F8, on a frame over TOTK_FLIGHT_SPIKE_MS or at TOTK_FLIGHT_AT=<frame>; python tools/flight_view.py <dump dir> lists the worst frames and writes a contact sheet |
| every render pass of one frame | TOTK_DUMP=<frame> and TOTK_DUMP_DIR: a PNG per pass target plus passes.txt (draws, bound textures with swizzles, storage buffers, copies, clears) and images.txt; TOTK_DUMP_BUF adds a guest buffer |
| Vulkan misuse | TOTK_VK_VALIDATION=1 (needs the Vulkan SDK), run into the world; compare the message kinds with a run without your change. The layer caps repeats, so counts are not totals |
| which pass the GPU was in at a device lost | the vk: pass marks lines; on by default on AMD, TOTK_PASS_CRUMBS=1 elsewhere |
| a crash | the crash report at the end of the log: exception, guest and host addresses, registers |
| a run on a large card that acts like a small one | TOTK_VRAM_LIMIT_MIB=<MiB> |
Frame dumps: a draw's argument lines come before its pass header. Pass numbers shift between runs, so find a pass by its draw count or its targets, not by its number. RenderDoc cannot capture this renderer (controls.md, "RenderDoc capture").
Measuring
| What | How |
|---|---|
fps, worst frame, render-thread waits, GPU busy time, VRAM, bound: gpu|cpu|pace |
--env TOTK_TRACE=perf,sched, then python tools\perf_summary.py <log> <lo> <hi> |
| slow frames | count the perf: slow frame lines; each names what the render thread was blocked on (pipeline, translation, texture, fence, pacing) |
| the frame's critical path, by thread and state | TOTK_CRIT=w<lo>-<hi> and TOTK_CRIT_OUT=<absolute path> with sched tracing, then python tools\crit_path.py <file> |
| where CPU time goes | TOTK_PROFILE=<path> and TOTK_PROFILE_FRAMES=w<lo>-<hi>, then python tools\prof_report.py <profile> <totk.map>; TOTK_PROFILE_RENDER=1 samples the render thread with call chains |
| a switch on a static scene | a setting set to ab with TOTK_AB=<seconds> alternates the two sides in blocks of game time; python tools\ab_summary.py <log> --skip <seconds>. On a moving scene compare whole runs instead |
A single run proves little: runs of one script on one build spread by a third in their 1% lows. Compare medians of at least two whole runs per side, on a quiet machine.
Probes
Each probe is a mode of totk.exe, run from the build folder; the list with what each covers is at the top of
runtime/main.c. --selftest needs no game files. --probe-kernel, --probe-vkexec, --probe-nvn-tex and
--probe-shader cover most runtime changes and run in seconds: run them before any game run.
Reading output without filling the context
- Game logs run to megabytes.
grepthe lines you need,tailthe end, pipeperf_summary.pythroughgrep SUMMARY. - Generated C runs to hundreds of thousands of lines per file.
grep -nthe function, then print its line range. - Look at screenshots downscaled: paste several into one 1280x720 grid and read the grid once. Write down what it shows instead of opening it again.
- Send long builds and runs to the background with their output in a file; read the last lines when they end.
- Keep a notes file in the build folder from the first finding: what is ruled out, the leading hypothesis, the next step, temporary debug edits with file and line. A fresh session continues from it.