mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
Add contributor and agent guides for pull requests, running and debugging
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f9ed079614
commit
bdcfb42c07
6 files changed
+409
-7
No files matched your search
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
Target branch: dev. Rules: CONTRIBUTING.md, "Pull requests". Write each section in your own words about this diff;
|
||||
delete the rows that do not apply. Never attach game files, keys, extracted data or logs that contain them.
|
||||
-->
|
||||
|
||||
## Problem
|
||||
|
||||
<!-- What the player sees or what is wrong in the code. Issue link if there is one. -->
|
||||
|
||||
## Change
|
||||
|
||||
<!-- What the diff does and why this way. Name the switch (totk.ini setting or TOTK_* variable) that turns it off. -->
|
||||
|
||||
## Where it reproduces
|
||||
|
||||
<!-- OS, CPU, GPU and driver, game update (1.2.1 / 1.4.3), settings. Hardware it cannot appear on, if known. -->
|
||||
|
||||
## Proof
|
||||
|
||||
<!-- Before and after on the same repro: screenshots, log lines, a test that failed and now passes. -->
|
||||
|
||||
## Checks run
|
||||
|
||||
<!-- The rows of CONTRIBUTING.md "Checks by what you changed" that match the diff, each with its result. -->
|
||||
|
||||
- [ ] `python tools/check_content.py --staged`
|
||||
- [ ] `totk.exe --selftest`
|
||||
|
||||
## Measurements
|
||||
|
||||
<!-- Frame path, timing or scheduling only. Per run, switch off and on, at least two each: average fps, 1% low or
|
||||
worst frame, `perf: slow frame` count. Scene, save, seconds, CPU, GPU, driver, resolution. -->
|
||||
|
||||
## Not run
|
||||
|
||||
<!-- Paths you could not run (Linux, HDR, a vendor you do not have) and how they are gated. -->
|
||||
@@ -29,6 +29,15 @@ This repository is public. Game data lives outside it and every derived file sta
|
||||
- Graphics bugs: [docs/maxwell.md](docs/maxwell.md) lists the Maxwell behaviour the Vulkan path must match and the TotK bugs other ports fixed.
|
||||
- Comments say what the code does and why, in words that stand on their own. Cite our code by file plus function (a line number may follow as a hint) and upstream code by project, file and function: `refs/` moves, so its line numbers rot. Commit hashes belong to the attribution of copied code.
|
||||
|
||||
## Running and debugging
|
||||
|
||||
- Running the game, screenshots, frame dumps, timings: [docs/agents/running.md](docs/agents/running.md).
|
||||
- Investigating a bug, glitch or crash: search [docs/agents/symptoms.md](docs/agents/symptoms.md) by the player's words first, then follow [docs/agents/debugging.md](docs/agents/debugging.md). Read the code before running the game.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Preparing a pull request: target `dev`; the checks by changed area, the proof and the performance runs it carries are in [CONTRIBUTING.md](CONTRIBUTING.md), "Pull requests". Reviewing one: [docs/pr-review-kit/](docs/pr-review-kit/).
|
||||
|
||||
## Writing
|
||||
|
||||
Code, comments, docs and commit messages are in English. Commit messages are plain sentences without type prefixes.
|
||||
+86
-7
@@ -29,14 +29,93 @@ Use the issue form. Include the port version and game update from the title line
|
||||
|
||||
## Pull requests
|
||||
|
||||
- One topic per pull request.
|
||||
- Describe the problem, then what changed.
|
||||
- Say how you tested it: which version, which scene, how long.
|
||||
- A change to the lifter (`recomp/`) or the CPU headers passes the differential tests in [docs/building.md](docs/building.md), "Checks".
|
||||
- Commit messages are plain sentences, without `feat:` or other prefixes.
|
||||
- AI-assisted code is welcome. You are responsible for having read and run it.
|
||||
### Branch
|
||||
|
||||
Open pull requests against `dev`. Branch from the current `dev` and rebase on it before you open the pull request. `main` receives `dev` at a release; a pull request against `main` is retargeted to `dev` before review.
|
||||
|
||||
### Before you start
|
||||
|
||||
- Search the open pull requests and issues on the topic. When another pull request already covers it, improve that one or say in yours how they differ.
|
||||
- Fixes are reviewed before features, in order of how many players the bug hits. A feature with a design in [docs/milestones/](docs/milestones/) (mods, cheats, the menu) follows that design. For a feature without one, open an issue first.
|
||||
- Find out where the bug reproduces: OS, GPU vendor and generation, driver, game update, settings. Put that in the description.
|
||||
|
||||
### What the change must keep
|
||||
|
||||
- **The default for everyone.** A fix for one setup (a GPU vendor, a memory size, a display) is gated on that setup. Players without the problem see no change.
|
||||
- **A switch.** New behaviour on the frame path or in rendering sits behind a `totk.ini` setting or a `TOTK_*` variable, so a player can turn it off and a reviewer can compare. A new setting is described in [totk.ini.example](totk.ini.example) and [docs/settings.md](docs/settings.md).
|
||||
- **The fix, not a disabled feature.** A crash that goes away with a feature off points at the bug in that feature. Turning the feature off for a vendor is not a fix.
|
||||
- **The frame time on weak hardware.** Game threads and the render thread do no file I/O, no GPU allocations, no per-draw state copies and no global atomic counters. Memory kept per frame or per object is freed. The full list with measured costs: [docs/pr-review-kit/02-performance.md](docs/pr-review-kit/02-performance.md), "What the hot path must not do".
|
||||
- **Other GPUs.** New Vulkan code queries each feature, extension, format and limit it uses and keeps a working path without it. Nothing is hardcoded from what one GPU reports.
|
||||
- **Text in the catalogs.** Text a player sees gets an id in `runtime/ui_text.h` and an entry in `runtime/ui_text.c` for every menu language (English, Russian, Brazilian Portuguese, Simplified Chinese); installer text goes into `installer/text/*.json`.
|
||||
- **The generator owns generated C.** Change `recomp/`, never `generated/`.
|
||||
|
||||
### Checks by what you changed
|
||||
|
||||
Run every row that matches your diff and list the results in the description. Commands and details: [docs/building.md](docs/building.md), "Checks".
|
||||
|
||||
| You changed | Run |
|
||||
|---|---|
|
||||
| anything | `python tools/check_content.py --staged`; `build\<ver>\totk.exe --selftest` |
|
||||
| `recomp/`, `runtime/cpu.h`, `runtime/simd.h` | the differential tests against Unicorn: `tests\diff\lift.py`, `simd.py`, `atomic.py`, `func.py`, `tests\flags_check.py`, `tests\excl_mt.py`; the same with a `--cpu v2` build |
|
||||
| `recomp/jumptables.py` | `tests\jumptables_check.py`: 0 disagreements on 1.2.1 and 1.4.3 |
|
||||
| `runtime/` | `--probe-kernel`, `--probe-vkexec`, `--probe-nvn-tex`, `--probe-shader`, plus the probe for the file you touched (list at the top of `runtime/main.c`) |
|
||||
| settings | `totk.exe --probe-cfg`; `python tests\settings_doc_check.py` |
|
||||
| button prompts | `.venv-py314\Scripts\python tests\romfs_prompts_test.py` |
|
||||
| mods, ExeFS patches | `.venv\Scripts\python tests\exefs_patches_test.py`; `totk.exe --probe-mods` |
|
||||
| `installer/` | `python tests\installer_test.py`; `python tests\installer_gui_test.py` |
|
||||
| shader translator | `tools\xlate_smoke.py`; `tools\shader_census.py --version <ver> --translate` with no new failure groups |
|
||||
| rendering | a run in a scene where the change acts, with `TOTK_VK_VALIDATION=1`: no new validation messages; screenshots before and after |
|
||||
| frame path, timing, scheduling | the measurements below |
|
||||
|
||||
Checks that need game files run on your own copy of the game. Results go into the pull request as text: pass or fail, counts, log lines. Never attach logs or dumps that contain game data.
|
||||
|
||||
### Proof
|
||||
|
||||
- **A bug fix** shows the bug before the change and gone after it, on the same repro: a screenshot pair, a log line, a probe or test that fails before and passes after. A scripted repro (`TOTK_INPUT_RECORD`, [docs/controls.md](docs/controls.md), "Scripted input") is the strongest. If the bug cannot be reproduced on your hardware, say so and point at the code that causes it.
|
||||
- **A crash fix** names the crash: the log's crash report, `vk: pass marks` lines for a lost device.
|
||||
- **Parts you could not run** (Linux-only code, HDR, vendor-only paths) are listed as unrun and gated away from everyone else.
|
||||
|
||||
### Performance measurements
|
||||
|
||||
A change on the frame path, in timing or in scheduling carries these numbers, or it waits until someone measures it.
|
||||
|
||||
1. **Ceiling first.** Before the work, estimate the share of the frame the changed code holds on the critical path (`TOTK_CRIT` + `tools/crit_path.py`). A ceiling of a few percent is not worth a hot-path change.
|
||||
2. **Build 1.4.3** with prepared shaders in that build folder (`tools/prepare_shaders.py`, [docs/building.md](docs/building.md) step 6). Caches copied from another build folder do not count.
|
||||
3. **A heavy scene** where the change acts: a fight with a large enemy and a crowd, or a town, with `TOTK_FPS_LIMIT=360`.
|
||||
4. **Whole runs, switch off and on, at least two each**, the same save and the same input script, timed by game seconds:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python tools\run_game.py build\v143 --script <input.txt> --seconds 60 --quiet --env TOTK_TRACE=perf,sched --env TOTK_FPS_LIMIT=360 --env <SWITCH>=0
|
||||
```
|
||||
|
||||
`--quiet` waits until no other game or build runs. Close everything heavy on the machine. Before reading the numbers, check that each run reached the world and showed the same scene.
|
||||
5. **Report per run**: average fps, 1% low or worst frame, the number of `perf: slow frame` lines, VRAM; `tools/perf_summary.py` condenses the log. Add CPU, GPU, driver, RAM, resolution and the settings you changed.
|
||||
|
||||
A switch toggled inside one run on a moving scene gives false results: compare whole runs. Zero new slow frames is a hard bar; an fps gain does not buy back new hitches. Background and the reasons behind each rule: [docs/pr-review-kit/02-performance.md](docs/pr-review-kit/02-performance.md).
|
||||
|
||||
### Description and commits
|
||||
|
||||
Fill in the pull request template: problem, change, where it reproduces, checks, measurements, what stayed unrun. One topic per pull request.
|
||||
|
||||
Commit messages are plain English sentences, without `feat:`, `fix:` or other prefixes. A commit that works on an issue ends its subject with the issue numbers: `Render the handheld scene at the asked size on 1.4.3 (#998, #536)`.
|
||||
|
||||
### Review and merge
|
||||
|
||||
The maintainers review every pull request against [docs/pr-review-kit/](docs/pr-review-kit/): read it to see what gets a pull request rejected, which bug classes are already fixed and which invariants a change must keep. A good pull request that misses the project's conventions is fixed up while merging rather than sent back. Part of a pull request can be taken; the reason for what was left out or rejected is posted on the pull request.
|
||||
|
||||
Accepted work merges into `dev` and reaches players through test builds. `main` moves only at a release.
|
||||
|
||||
## Coding agents
|
||||
|
||||
AI-assisted code is welcome. You are responsible for having read and run it, and the pull request says which checks ran on your machine.
|
||||
|
||||
Point your agent at [AGENTS.md](AGENTS.md) (Claude Code reaches it through `CLAUDE.md`), this file and [docs/pr-review-kit/](docs/pr-review-kit/) before it writes code. [docs/agents/](docs/agents/) holds what it needs to run the game unattended, measure, and debug: the method, the bug classes of a recompiled Switch game, and a symptom catalog. The rules in them bind the agent as they bind you:
|
||||
|
||||
- Key values stay out of prompts, logs, commands and files. The agent passes the keys file path to the tools.
|
||||
- Results in the pull request come from runs that happened. A check the agent did not run is listed as not run.
|
||||
- The agent writes the description in its own words about this diff. A review kit checklist pasted in with every box ticked tells the reviewer nothing.
|
||||
|
||||
## Code and docs
|
||||
|
||||
- Code, comments, commit messages and documentation are in English.
|
||||
- Generated code is never edited by hand: change the generator.
|
||||
- Comments say what the code does and why. Cite our code by file and function, upstream code by project, file and function.
|
||||
@@ -0,0 +1,117 @@
|
||||
# Debugging the port: method for agents
|
||||
|
||||
A game run costs minutes and a full rebuild costs several more, while reading code costs seconds. Order the work so the
|
||||
cheap steps decide as much as they can before the game starts. Each step ends on its stated condition.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Evidence.** Hold one artifact that shows the symptom: the player's screenshot, log or input recording, a
|
||||
flight-recorder dump, a frame dump ([running.md](running.md), "Observing"). Done when you can point at the wrong
|
||||
pixel, line or frame.
|
||||
2. **Where it reproduces.** OS, GPU vendor and generation, driver, game update, settings, mods, the save. Done when you
|
||||
know whether your hardware can show the bug at all. NVIDIA bugs do not reproduce on AMD; RDNA1/2 bugs do not
|
||||
reproduce on RDNA3/4 (another driver branch); a Ryzen 7000 iGPU is RDNA2 and runs that branch. When your hardware
|
||||
cannot show it, say so and work from the code and the reporter's logs; runs that cannot decide anything are skipped.
|
||||
3. **Known already?** Search [symptoms.md](symptoms.md), [pr-review-kit/04-knowledge.md](../pr-review-kit/04-knowledge.md)
|
||||
and [maxwell.md](../maxwell.md) sections 6-7 with the words the player used (black, magenta, flicker, slow motion,
|
||||
missing, stale, streaks, stuck) and the object (sky, grass, shadow, fog, HUD, weapon). Then the upstream notes:
|
||||
`refs/nxnative/docs`, `refs/MacNativeGames/docs` and the SavageWindRecomp issue tracker (`tools/fetch_refs.sh`
|
||||
fetches `refs/`). Done when every hit is either ruled out or is your first hypothesis. A hit on a class we already
|
||||
fixed: confirm that fix still holds before deriving anything new.
|
||||
4. **Read the code.** The game's function in Ghidra ([building.md](../building.md), "Reverse engineering") or in
|
||||
`generated/<ver>/`, and our runtime path in `runtime/`. Done when the code either rules the hypothesis out or names
|
||||
the exact condition a run must show. A run confirms what the code could not rule out.
|
||||
5. **The cheapest falsifying check.** An existing switch (a `totk.ini` setting or a `TOTK_*` variable), a dump, a trace
|
||||
line, a probe. When it refutes the hypothesis, stop: write down what was checked and why it fell, and start again
|
||||
at step 3 with the next hypothesis. No build, no benchmark for a refuted idea.
|
||||
6. **Repro red, then green.** The same repro shows the bug before the fix and its absence after. A fix without a
|
||||
reproduced failure is a guess.
|
||||
7. **Fix the cause.** A crash that goes away with a feature off points at the bug in that feature; fix the feature.
|
||||
A fix that one setup needs is gated on that setup, and everyone else keeps today's behaviour.
|
||||
|
||||
## Traps
|
||||
|
||||
- **One-off failures.** A failure that happens once and not again: rerun before you debug it as a code bug. A crash or
|
||||
a forced shutdown leaves zero-filled cache files, and memory errors on the host flip bits in files and objects.
|
||||
- **Every build fails the same way at once.** Suspect the inputs and caches (game files, the OS file cache, shader and
|
||||
pipeline caches), not the code. Bisecting code is pointless when the old builds fail too.
|
||||
- **Compile errors in generated C** on a line the generator could not have written are a flipped bit. Regenerate and
|
||||
compare hashes; `recomp/` is fine.
|
||||
- **A crash that looks like a race.** Try the save first: some saves crash the same way on a real Switch.
|
||||
- **Theories about locks and threads.** Read the game's own protocol in Ghidra before running anything. Ten runs on
|
||||
lock theories once went where one read of the game's double-buffer code would have narrowed it.
|
||||
- **Numbers from a fast machine.** A change neutral on a fast desktop can matter on a weak laptop or the Steam Deck,
|
||||
in either direction. Say which hardware your numbers come from.
|
||||
|
||||
## Bug classes of a recompiled Switch game
|
||||
|
||||
The port runs the game's own code on another OS and another GPU. Most bugs come from places where the host differs
|
||||
from the console.
|
||||
|
||||
### Variable frame step and epsilon-gated timers
|
||||
|
||||
The console steps the game by whole 30 Hz units, so a countdown started at a whole value reaches exactly 0. With the
|
||||
real-time step (`realtime = 1`, the default) the step is a measured ratio, and the last step can leave a remainder of
|
||||
about 1e-8. Many of the game's updaters step a timer only while it is above `FLT_EPSILON`; a remainder in
|
||||
(0, `FLT_EPSILON`] is never stepped again. A consumer that tests the same field with `> 0` then sees it active
|
||||
forever, until a load reinitialises the object. Issue #18 (weapons locked after a Fuse) was this.
|
||||
|
||||
Symptom words: stuck until a reload, a teleport or a shrine; locked after an action; works sometimes; only above
|
||||
30 fps. To check a report: find the field the stuck behaviour reads, then compare the updater's threshold with the
|
||||
consumer's test. A change to the frame step, the pacer or the fps limit is checked for this class.
|
||||
|
||||
### Thread scheduling: Horizon is not a desktop OS
|
||||
|
||||
- Horizon runs a woken better-priority thread at once on its core, before the thread that woke it goes on. A host
|
||||
runs both in parallel, so state can be read one step early or freed while another thread still uses it.
|
||||
- Threads created with ideal core -2 start pinned to one core on Horizon. The game keys per-core data by
|
||||
`GetCurrentProcessorNumber` without locks and expects that pinning.
|
||||
- The game has hundreds of spin loops without a system call. On the console a spinning thread holds one core of three;
|
||||
on the host it burns a host core and can keep an equal-priority waiter out.
|
||||
- ARMv8 `stlr` followed by `ldar` is ordered (RCsc). C11 release/acquire lets x86 reorder that store and load. A
|
||||
two-thread flag handshake that loses a wake points here; lifter changes to barriers and atomics pass
|
||||
`tests\diff\atomic.py` and `tests\excl_mt.py`.
|
||||
- A scheduling change is judged by how long a ready, better thread waits for a core it may use, plus the slow-frame
|
||||
count. A check that made one bug rarer once added 50-75 ms hitches.
|
||||
|
||||
### Maxwell behaviour versus Vulkan defaults
|
||||
|
||||
The port must do what the Switch GPU does: last-vertex provoking, (0,0,0,1) for unwritten vertex outputs, colour
|
||||
targets outside the shader's output map keep their texels, the G-buffer pass samples its own targets. The full list
|
||||
with sources: [maxwell.md](../maxwell.md), section 7.
|
||||
|
||||
### Memory shared by the CPU and the GPU
|
||||
|
||||
- The game fills textures and buffers with the CPU and samples them on the GPU without the flush a desktop API needs.
|
||||
Missing uploads show as magenta, brown or stale textures.
|
||||
- The game reads GPU-rendered textures with the CPU (grass maps, cut and burn masks). Missing write-backs show as
|
||||
effects that never happen: cut grass stays up, fire does not spread.
|
||||
- Memory only the GPU writes looks idle to write-watch tracking: a store from a shader leaves no fault and no dirty bit.
|
||||
- Anything kept per texture, per frame or per object without a free grows host memory until a commit failure after
|
||||
hours. Watch private memory over a few idle minutes.
|
||||
|
||||
### Device lost and GPU hangs
|
||||
|
||||
- The `vk: pass marks` lines name the pass in flight. Validation names the misuse when there is one.
|
||||
- Submits and presents share one queue under one lock. A present from its own thread on a second queue gave device
|
||||
lost on NVIDIA before Turing in another port.
|
||||
- Caches from another driver or GPU (pipeline caches, translated shaders) gave black windows after driver updates;
|
||||
every cache is validated on load and dropped when vendor, device or `pipelineCacheUUID` change.
|
||||
- A first-time shader stall over 2 s on a slow GPU trips the Windows GPU timeout (display event 4101). Large one-shot
|
||||
submissions (a boot-time decode of every texture) do the same.
|
||||
- Point lists from vertex shaders that write no point size are undefined without `VK_KHR_maintenance5`.
|
||||
|
||||
### Windows and Wine host costs
|
||||
|
||||
What the hot path must not do, with measured costs: [pr-review-kit/02-performance.md](../pr-review-kit/02-performance.md),
|
||||
"What the hot path must not do". For debugging, also:
|
||||
|
||||
- `SuspendThread` returns at once, but `GetThreadContext` waits until the thread leaves the kernel. A sampler or a
|
||||
debugger stalls for as long as the stopped thread's file open or driver call runs.
|
||||
- A thread stopped between its `sub rsp` and its stack probe has its stack pointer inside the guard page. Check
|
||||
`PAGE_GUARD` with `VirtualQuery` before reading another thread's stack.
|
||||
- Paths go through the W APIs with UTF-8 to UTF-16 conversion. An A API call breaks on a path with non-Latin letters,
|
||||
such as a Windows user name in Cyrillic or Chinese.
|
||||
- Wine emulates `GetWriteWatch` with `mprotect` and one fault per first write. Anything relying on cheap OS memory
|
||||
tricks (write-watch, protection changes, fault handlers) costs more under Proton.
|
||||
- `win32u.dll` frames in a host stack are kernel graphics calls: memory allocation or submission.
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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](../settings.md) and
|
||||
[controls.md](../controls.md); this page says which to use when. How to judge a performance change:
|
||||
[pr-review-kit/02-performance.md](../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
|
||||
old `totk.exe` in place and you test old code.
|
||||
- **Prepared shaders in that folder.** `tools/prepare_shaders.py build/<dir> --version <ver>` ([building.md](../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.ini` on both sides** of any comparison. A folder without one runs at the defaults; check the log's
|
||||
`resolution:` line of each run before comparing them.
|
||||
- **Saves.** `tools/run_game.py` copies the profile `build/profile`, swaps in a save from `build/save-backup/<name>/`
|
||||
and deletes the copy afterwards, so the real profile is never written. To make a backup, copy the user folder from
|
||||
`build/profile/save/` into `build/save-backup/<name>/`. Pick a save that starts where the change acts.
|
||||
|
||||
## Running
|
||||
|
||||
```powershell
|
||||
.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>]
|
||||
```
|
||||
|
||||
- `--seconds` is game time since the world loaded (the log line `frame step: world clock starts` marks the start), at
|
||||
any frame rate. Measure and script by game time; a frame count at another fps is another scene.
|
||||
- `--script` replays input ([controls.md](../controls.md), "Scripted input"). A new script starts with the presses of
|
||||
`tools/input/load_save.txt`, which load the first save from the title. `TOTK_INPUT_RECORD` turns a person's play into
|
||||
a script; ask the user to record the repro rather than guessing the button sequence.
|
||||
- `--shot` saves the last frame and builds pipelines synchronously: use it for pictures, never for timing.
|
||||
- `--quiet` waits until no other `totk.exe` or `ninja.exe` runs. Every timing you report comes from a `--quiet` run.
|
||||
- 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](../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. `grep` the lines you need, `tail` the end, pipe `perf_summary.py` through `grep SUMMARY`.
|
||||
- Generated C runs to hundreds of thousands of lines per file. `grep -n` the 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.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Symptoms and their usual causes
|
||||
|
||||
What a player reports, the causes this port and other Switch ports (nxnative, MacNativeGames, SavageWindRecomp,
|
||||
LAS Recomp) found behind it, and the first check. Search by the player's words and by the object. Which of these are
|
||||
already fixed here, and where: [pr-review-kit/04-knowledge.md](../pr-review-kit/04-knowledge.md), "Fixed bug classes".
|
||||
GPU semantics behind the graphics rows: [maxwell.md](../maxwell.md). The method around this table:
|
||||
[debugging.md](debugging.md).
|
||||
|
||||
## Graphics
|
||||
|
||||
| Symptom | Usual causes | First check |
|
||||
|---|---|---|
|
||||
| magenta, brown or stale textures; caves or sky wrong | the CPU filled the texture and the GPU never got the upload (no flush on the console) | frame dump: the texture's upload count and mirror state in `passes.txt` |
|
||||
| effects that never happen: cut grass stays up, fire does not spread | the CPU reads a GPU-rendered texture that was never written back | which texture the game reads after its fence; write-back of that target |
|
||||
| black scene, black dialog background or loading map | deferred binds read at record time; a 3D target rejected; a texture compare ignoring the channel mapping | frame dump: the first pass whose target goes black |
|
||||
| lime silhouettes, rectangles in the sky | a target outside the shader's output map overwritten instead of kept | dump: the target's previous contents versus after the pass |
|
||||
| dark or magenta 16x16 blocks, missing characters | a pass samples its own render targets (G-buffer feedback) | feedback-loop layout and barriers on that pass |
|
||||
| tile rectangles, wrong data in tiles | storage buffers bound in the wrong slot order | `passes.txt`: storage buffers with their c0 slots |
|
||||
| streaks on cliffs, characters stretched into streaks | primitive restart ignored; integer vertex inputs against a float format | validation run; the draw's topology and vertex formats in the dump |
|
||||
| garbage geometry on title or load screens | 8-bit indices widened on the CPU before a copy in the same submission wrote them | whether a copy to the index buffer precedes the draw in one submission |
|
||||
| black tiles, masks not cleared | clears with a partial channel or stencil mask skipped or done in full | `passes.txt` clears and their masks |
|
||||
| garbage in feedback effects at first use | new GPU-only images start undefined; the console starts them at zero | clear on creation and compare |
|
||||
| empty clouds or fog for the whole session | a one-shot draw skipped while its pipeline built, never drawn again | `perf: slow frame` lines naming pipeline waits; the draw's first frame |
|
||||
| fog that never parts, flicker in tile rows | driver constants (c0: dispatch size, point size, base instance) not filled | the c0 contents the translator expects for that stage |
|
||||
| no sun shadows, wrong lighting | unwritten vertex outputs read as zero instead of (0,0,0,1) | the shader's output map |
|
||||
| one flat ambient colour | a 1-layer 2D array sampled as 2D | the view type the shader expects |
|
||||
| upside-down text | lower-left window origin | origin mode in the log |
|
||||
| flicker or a texture swapped with a similar one | the newest texture picked at shared storage instead of the one with the matching view | texture lookup by id in the dump |
|
||||
|
||||
## Timing and frame pacing
|
||||
|
||||
| Symptom | Usual causes | First check |
|
||||
|---|---|---|
|
||||
| stuck until a reload or teleport, locked after an action | a timer remainder below `FLT_EPSILON` with the real-time step ([debugging.md](debugging.md)) | `realtime = 0` in `totk.ini`; if the bug is gone, find the timer field |
|
||||
| slow motion in GPU-heavy scenes, dynamic resolution never drops | the engine's GPU time comes from report timestamps; wrong timestamps make the frame look free | what the engine reads as GPU time and what the report writes |
|
||||
| frame counter wanders 50-60 | two 60 Hz clocks (vsync event and present acquire) drifting apart | whether both follow one display tick |
|
||||
| a 30 Hz thread runs at 27 Hz, slow cutscenes | host sleep overshoot: Windows waitable timers wake 0.3-0.5 ms late | `TOTK_TRACE=sched` sleep asked versus took |
|
||||
| 20 fps after a scheduler change | a yield back-off that starts too early | yield handling against the console's |
|
||||
| climbing or movement wrong only above some fps | game code that assumes a fixed step count per frame | compare at 30, 60 and the failing rate |
|
||||
|
||||
## Threads and crashes
|
||||
|
||||
| Symptom | Usual causes | First check |
|
||||
|---|---|---|
|
||||
| state read one step early, one frame of inconsistent data | a woken better-priority thread does not run before the waker goes on | sched trace around the handoff |
|
||||
| per-core data corrupted, rare crash in a container walk | threads the console pins to one core run on any host core | the thread's core mask from its creator; a run on one core |
|
||||
| rare hang or lost wake | address-arbiter signal values or wake order differ from Horizon | the arbiter path against mesosphere |
|
||||
| a flag handshake loses a wake | `stlr`/`ldar` ordering lost in x86 release/acquire | the guest code's barrier pattern |
|
||||
| a host core at 100% while the game waits | game spin loops without system calls | profile: which guest function spins |
|
||||
| crash on world load | sizes the game reads from the driver (command memory used) differ from the console's | the sizes the game asks for against the console's |
|
||||
| crash with one save only | the save itself; some crash on a real Switch | the same save on another build and, if possible, on hardware |
|
||||
|
||||
## Device lost, hangs, black window
|
||||
|
||||
| Symptom | Usual causes | First check |
|
||||
|---|---|---|
|
||||
| device lost a few frames in, GPU writes to address 0 (older NVIDIA) | present from another thread or queue | every submit and present on one queue under one lock |
|
||||
| device lost or garbage on one AMD generation | views the driver branch mishandles (BC block views of 3D textures) | `vk: pass marks`, validation; the formats in the pass |
|
||||
| device lost on one scene, every run, one vendor | a shader or pass that driver cannot run | pass marks name the pass; skip shaders by hash to bisect |
|
||||
| GPU timeout after 1-2 min, single frames over 2 s | first-time shader stalls or a huge one-shot submission on a slow GPU | prepared shaders; submission sizes at boot |
|
||||
| GPU hang at boot with pipeline libraries | point lists without a point size | the shader writes point size or the device clamps it |
|
||||
| black window after a driver update | caches from another driver | the log line `vk_pipeline: ... written by another device or driver`; delete the caches next to `totk.exe` |
|
||||
|
||||
## Memory
|
||||
|
||||
| Symptom | Usual causes | First check |
|
||||
|---|---|---|
|
||||
| host memory grows in play until a commit failure after hours | state kept per texture init or per frame and never freed | private memory over a few idle minutes |
|
||||
| multi-GB peaks while travelling | upload staging grows inside one submission | staging bytes per submission |
|
||||
| out of memory after minutes | dead textures re-entering tables | live images and allocator bytes over time |
|
||||
| stutter as VRAM fills | new memory blocks allocated on the render thread (8-23 ms each) | profile the render thread over the hitch: `vkAllocateMemory` under the allocator's lock |
|
||||
Reference in new issue
Block a user