# Game testing
Running a game is a big effort for the community: a legally dumped title, a suitable machine, hours of setup, often a PR build, and time spent watching it run. Many runs end early with a crash or a stall. Every run is still useful, so this page asks for as much information as possible from each one. A failed run with a complete log helps as much as a successful one.
For user options see [USAGE](../user/USAGE.md); for internals see [ARCHITECTURE](ARCHITECTURE.md).
## Before a run
- Build `main` or the PR head in a clean worktree, including the `libs` target (it is not in the default build). If a new test breaks the build, `cmake --build build --target libs relinker` is enough to convert and run a title.
- Convert the title from plain ELF files (`tools/unwrap_self.py` unwraps a dump's `eboot.bin` and `.prx`):
- Linux: `relinker --registry src/eboot.elf app.elf`
- Windows: `relinker --windows --registry src/eboot.elf app.exe`. Without `--windows` the output is an ELF file whatever its name, and Windows refuses to start it (exit code 216, "not compatible with the version of Windows").
- A dump whose `app0` holds `ampr_assets.index` and `ampr_assets-*.pak` was repacked for [ampr_emu](https://github.com/drakmor/ampr_emu): the packed files were removed from `app0`, and only ampr_emu reads the packs. Restore them first with ampr_emu's `tools/ampr_pack.py unpack --index app0/ampr_assets.index --output
` and copy the files missing from `app0`. Without them a Unity title stops at start-up on the missing `Media/Resources/unity default resources`.
- Convert every module the title loads from its own folders (`sce_module/`, `Media/Modules/`, `Media/Plugins/`). Unity titles need `--module-dir Media/Modules --module-dir Media/Plugins`. Put each printed `Guest module:` file next to its original `.prx` in `app0/`, and keep the original there.
- `libs/` holds only AnyPS5's own libraries, from `build/core/libs/libs` (not `unpatched/`). A game module placed there fails to load (error 193 on Windows). A self-built `libs/` on Windows also needs `libgcc_s_seh-1.dll`, `libstdc++-6.dll` and `libwinpthread-1.dll` from the toolchain's `mingw64/bin`, or every library fails with error 126 ([USAGE](../user/USAGE.md#input-and-conversion)).
- Run `tools/import_audit.py` on the registries. Exit code 1 lists imports that will fail or throw.
- Run one title at a time. Titles share the GPU and the controller file. Some titles start a second process (Peace Walker's `mgspw.mgspw.elf`) that can outlive a time cap on the first: check that every process of the previous run is gone.
## Launch
Linux:
```sh
APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1 APS5_LOG_TIMESTAMPS=1 \
APS5_PAD_INPUT_FILE=/tmp/anyps5-pad \
setarch x86_64 -R timeout 600 ./app.elf > run.log 2>&1; echo "exit=$?"
```
Windows PowerShell:
```powershell
$env:APS5_PROFILE_DRAW = '1'
$env:APS5_PAD_INPUT_FILE = "$PWD\pad.txt"
cmd /c '.\app.exe > run.log 2>&1'
"exit=$LASTEXITCODE"
```
- On Windows PowerShell 5.1, `*>` writes the log as UTF-16 and wraps the first stderr line in an error record, so `grep` can't read it; let `cmd` do the redirection, as above. `$LASTEXITCODE` still carries the exit code.
- `APS5_PROFILE_DRAW=1` prints `[present]` lines every 10 s. Without it a working title looks frozen.
- `APS5_NO_PAD_INPUT=1` ignores keyboard and mouse, so a stray press doesn't move a measurement run.
- `setarch x86_64 -R` (Linux only) turns off address randomisation so addresses match between runs.
- On Windows, a `--windows-gui` build started without a console writes its output to `anyps5.log` beside the executable, replacing the previous run's file.
- On Windows the title needs a signed-in desktop. Started from an SSH session (session 0) it has no usable window. Start it inside the user's session instead, for example with a scheduled task that runs interactively. A newly created task does not start on battery power by default and fails silently; allow that in its settings.
## Check the GPU and the caches
- **GPU:** copy the GPU from the log's `Selected GPU name=` line. Without presentation support AnyPS5 can select a software renderer: under Xvfb (no DRI3) that is llvmpipe, without an error, and the title still runs. Set `ANYPS5_GPU` to part of the expected device name; the start then fails if that device can't be used. For headless Linux runs on a real GPU, Weston headless with the GL renderer plus Xwayland provides DRI3.
- **Caches:** first and later runs can differ a lot. On a slow GPU a first frame took minutes with empty caches and seconds on the next run. Two caches matter:
- AnyPS5's `shader_cache/` beside the executable, or `ANYPS5_SHADER_CACHE_DIR`. `ANYPS5_NO_SHADER_CACHE=1` disables it.
- The driver's own: Mesa (`MESA_SHADER_CACHE_DIR`, or `MESA_SHADER_CACHE_DISABLE=true`), AMD on Windows (`%LOCALAPPDATA%\AMD\VkCache`). Back a driver cache up instead of deleting it; it isn't AnyPS5's.
Say in the report whether the caches were empty or warm. A first run that ends in device lost (`VK_ERROR_DEVICE_LOST`) or a GPU wait timeout while large pipelines are still being built, seen on AMD, often is the cache and not a regression: run again with the caches warm and report both runs.
- **Power and display:** on a laptop, run on AC power; a run on battery can make a neutral change look like a regression. Note the display's refresh rate: a title that asks for 120 Hz output can stop at start on a 60 Hz display.
## What to read
| Check | Where | Meaning |
|---|---|---|
| Exit code | shell | Linux: 124 time cap, 134 uncaught exception, 139 segfault, 137 killed. Windows: the exception code, `0xc0000005` access violation, `0xc0000409` uncaught exception or `abort()` (the log ends with `FATAL: abort() (SIGABRT) on thread N`) (PowerShell shows it signed, -1073740791) |
| Last lines | `run.log` or `anyps5.log` | `terminate called`, `what()`, `presentation failed`, `ASSERT`, `FATAL:` crash report |
| Frames | `[present] N presents over 10 s` | Steady N above 0 means frames are moving |
| Memory | `[texture] N storage images (X MiB)` | Growth before a `vkAllocateMemory ... -2` error |
| Queue waits | `[packets] ... WAIT_REG_MEM_64 ...` | A queue spending the window waiting on the GPU |
| Milestones | `Level has started: ` | How far the title got |
| Kernel (Linux) | `dmesg \| grep app.elf` | Fault address and instruction pointer |
| System log (Windows) | Event Viewer, System | A hard hang with nothing logged shows up afterwards as Kernel-Power 41 |
## Stall, slowdown, or crash
- **Stall:** no `[present]` lines for a long time while other periodic lines keep coming. Some titles present only when the view changes, so first send one input (`tools/pad_input.py`) and check whether presents resume.
- **Slowdown:** presents per 10 s far below earlier runs of the same scene. Record the scene (save file or level), since it changes the numbers, and check power and caches first.
- **Crash on Linux:** take the `ip` from `dmesg` and its offset in the image. Repeat the run under `gdb` (launched by gdb, not attached) for registers, the faulting instructions and all thread stacks.
- **Crash on Windows:** the `FATAL: unhandled exception ...` report in the log lists the faulting `rip` (for example `app.exe+0x...`), the registers and a stack scan. `APS5_CRASH_INDIRECT_MEMORY=1` adds up to 64 bytes from each pointer in the register dump. `APS5_HANG_DUMP_SECS=` writes a dump after a hang of n seconds. `APS5_TRACE_EXIT=1` reports the process exit point.
- **Traces (heavy):** `APS5_TRACE_FRAMES=:` or `APS5_TRACE_SECONDS=:` writes a capture for `tools/trace_digest.py`. Traces slow the title and can move the crash; say so when reporting.
## Measuring performance
- Measure in gameplay, not in cinematics, menus or loading screens.
- Use the title's default settings, and say so. A changed graphics mode makes a number incomparable with everyone else's.
- Measure the same spot every time: load a save, wait for the scene to settle, then measure a fixed window with no input.
- Compare builds on the same machine, alternating them (A, B, A, B), at least two runs each. Throw away a run that was disturbed (another program, a sleeping display) and say so.
- `APS5_TRACE_SUMMARY_EVERY=10` writes one line per 10 s to `aps5-summary.jsonl` without slowing the title; `tools/trace_digest.py` turns it into a table, and `tools/performance_compare.py` compares two captures ([BUILD](BUILD.md#performance-trace)). Pull requests labelled `perf` need a capture of `main` and one with the change.
- `APS5_DUMP_FRAMES=` with `APS5_DUMP_FRAMES_EVERY=` saves frames as `frame_.bmp` to check what was on screen. A second process of the same title writes to the same names and overwrites the first one's frames.
## Title notes
- **PlayGo:** dumps without PlayGo metadata stop at start unless `APS5_PLAYGO_UNLISTED_CHUNKS` is set; report the value used.
- **UE4 titles:** keep the startup movies in place. Renaming them to skip the intro has made a level render without its 3D view, which looked like an emulator bug and wasn't.
## Input
`tools/pad_input.py` writes presses to `APS5_PAD_INPUT_FILE`; they reach the title without focusing its window. Keys sent to the window instead (for example with `SendInput` on Windows) only arrive while it has focus. Physical controllers are opened at start and when plugged in later; the log names them with `Pad: connected game controller`.
## Saves
Save data is kept under the runtime's `_sd/` folder, and save-data memory under `_sd_mem/`. Copy them into a new runtime to continue from the same point.
## Reporting a result
Include, in this order:
1. The build: the `main` commit, each PR applied on top with its head, and any local change. A result from an integration branch should stay distinguishable after one of its PRs merges.
2. Title ID and content version, not only the game name.
3. Host: CPU, OS (and kernel on Linux), the GPU as the `Selected GPU name=` line names it, the driver version, AC or battery, and the display refresh rate.
4. The exact command and every `APS5_*` / `ANYPS5_*` variable, the in-game settings if they differ from the defaults, and whether the caches were empty or warm.
5. How far the title got (menu, a save, a scene) and what stopped it: the exit code and the last 200 log lines, plus the kernel line (Linux) or crash report (Windows) for a crash.
6. Presents per 10 s, and storage-image lines if memory is involved.
7. How many runs and how long each lasted. One run is an observation, not a reproduction.
8. The date of the test. Leave unknown fields empty rather than filling them from another report.
Keep AI-generated analysis separate from measured data, and say which is which.