Files
psxrecomp/docs/PLAYTEST_DIAGNOSTICS.md
T
Matthew Stanley 56a6cfe782 Add one shared title release command with a required channel
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:46:47 -07:00

229 lines
13 KiB
Markdown

# Shared playtest diagnostics
The PSX recorder follows the bounded background writer used by SNES Mega Man X
and the current MediEvil II recorder. It records the controller state actually
latched in SIO at each guest VBlank: both button words, analog capability, four
stick bytes per pad and independent controller presence. A rotating digest reads
16 KiB of main RAM each VBlank, covering every word across 128 frames. It is a
sampled divergence check, not a full machine-state checksum.
Each accepted row is copied to a bounded queue. Formatting and disk writes run
on a worker that flushes every 100 ms, including while gameplay is paused.
Normal shutdown drains accepted rows. Queue overflow or a write error stops
capture explicitly; it never silently drops an input row or blocks gameplay
waiting for queue space. Session creation, starting-card/settings copies and
the once-per-process executable hash happen outside steady-state recording.
## Add a title
Call after creating the runtime target:
```cmake
include("${PSXRECOMP_ROOT}/runtime/playtest_diagnostics.cmake")
psxrecomp_add_playtest_diagnostics(psx-runtime
GAME_ID "SLUS-00000" MOD_ID "game.diagnostics")
```
Create a title mod package with a recording feature whose plugin is
`game.diagnostics`, and a separate input-display feature whose plugin is
`game.diagnostics.osd`. Both features are always player-selectable mods (never
`channel = "developer"`); the build decides their default (below). The helper also
covers an existing `psx-runtime-pgxp` sibling and stages `replay-diagnostics.py`
beside both executables. The bundled-release packager includes this script when
present, as does the setup-host kit packager; AppImage wrapping preserves it in
the payload. Input Display also works with Recording disabled, with no RAM
digest or file capture. Session changes and netplay suspension clear the display.
No texture feature,
texture package or other enhancement is enabled or exposed by this helper.
### Default state: public releases off, everything else on
Owner policy (2026-10-19): Recording and Input Display are off by default only
in public production releases. Dev builds and playtest/pre-release packages
default them on.
- The source manifest carries the production default:
`default_enabled = false` on both the `recording` and `osd` features.
- `PSX_PUBLIC_RELEASE` is the one build-level notion of a public release. It
is derived from `PSX_RELEASE_CHANNEL` (`public` => ON; `playtest` or unset
=> OFF; `runtime/psx_release_channel.cmake`). Releases are cut only by
`tools/package_title_release.py --channel public|playtest`
([RELEASING_A_TITLE.md](RELEASING_A_TITLE.md)); `-DPSX_PUBLIC_RELEASE=ON`
without a channel fails configure.
- After the framework stages the catalog into `<exe-dir>/mods/bundled`,
`runtime/psx_playtest_diagnostics_defaults.cmake` edits only the staged copy:
with `PSX_PUBLIC_RELEASE=OFF` it sets both features to `default_enabled =
true`; with `ON` it fails the build unless the source says `false`.
- `psxrecomp_add_playtest_diagnostics` applies this to its `MOD_ID`. A title
with its own diagnostics package and recorder opts in with
`psxrecomp_playtest_diagnostics_defaults(psx-runtime MOD_ID game.diagnostics)`
(after `psxrecomp_add_game_runtime`; it also covers the `-pgxp` sibling).
- `tools/ci/audit_playtest_diagnostics_package.py --mode public|playtest`
checks a finished ZIP: the staged `mods/bundled` copy must be `false` in
`public` mode and `true` in `playtest` mode, and any shipped source copy
must stay `false`. `--mode` is required.
A player's own choice in `mods/state.toml` always overrides the default.
The implementation reads no title-specific addresses. `telemetry.csv` stores
host intervals, incident markers, CD activity, ordinary GP0 draw commands,
host presentation calls, effective video configuration and optional render-pass
state-verification counters. These are passive evidence and never feed replay
or simulation. Press L3 + R3 together
to mark an incident and queue a composed screenshot on supported presenters;
holding the chord produces one marker/request. Game-specific
gameplay samplers remain separate, validated adapters.
## Feedback bundle
Recording starts at the first guest VBlank and creates
`saves/diagnostics/session-<timestamp>` beneath the runtime data root. On normal
desktop installs this is beside the executable; AppImage launchers use their
writable data root. A blocked destination falls back to the OS temporary
directory under `psxrecomp-diagnostics/<game-id>/session-<timestamp>`.
`diagnostics-location.txt`, when writable beside the game, points to the actual
folder. The runtime log also reports its full path.
Quit normally after reproducing a problem and zip the complete session folder:
- `inputs.csv` and `session.json` describe inputs and execution/build identity.
- `telemetry.csv` contains timing, incident, CD and renderer evidence.
- `incident-<number>.png`, when the presenter completed an incident capture,
shows composed output rather than only native VRAM. OpenGL and software
support this path; headless and Vulkan currently reject it explicitly.
- `mods-state.toml`, optional `settings.toml`, and starting cards or explicit
absence markers preserve the original choices and save data.
- A slot-state restore starts a new segment with its complete `baseline.pst`.
Rewind segments without a reusable slot image are marked unreplayable.
Discs and BIOS images are never copied into feedback bundles. Starting cards
contain the player's saves. Recording and input display are independently
controlled by the title's mod features. Netplay frames are skipped; this
offline recorder does not claim to reproduce network sessions.
## Coverage and effective video state
`psxrecomp-telemetry-v2` preserves the original thirteen CSV columns and appends
the following evidence. Input-v2 and strict replay checks are unchanged.
- `gpu_draw_commands`: cumulative ordinary GP0 drawing commands observed by the
GPU front end. This is command activity, not a count of correct rendered pixels.
- `host_presents` / `host_present_errors`: cumulative returned/failed calls
from the actual host present API. OpenGL's direct and threaded paths, Vulkan,
and software presentation publish these without requiring debug tools.
`host_present_status_available` says whether the latest host present API
supplied a result. SDL2 has no return status, so its activity counter only
proves the call returned; zero errors under SDL2 is missing error coverage. Vulkan
success includes `VK_SUBOPTIMAL_KHR`. Neither API proves monitor scanout.
Counts include repeated images, pause/overlay presents and generated frames;
do not label them unique game frames. GP0 commands and presents need not be 1:1.
- `video_renderer`: the active backend, after fallback (`0` none/headless,
`1` software, `2` OpenGL, `3` Vulkan). `video_resolution_preset`,
`video_aspect_num/den` and `video_widescreen` sample the emulation owner's
settings after mod overrides, including changes during the session.
- `video_snapshot_valid`: whether a coherent passive backend snapshot was
available. `composed_*` fields describe the last composed image's backend,
actual internal scale, native source dimensions, drawable dimensions and
fitted viewport dimensions. Dimensions unavailable on a path are zero.
A threaded renderer can compose ahead of the swap counter; these fields do
not identify the exact image a particular swap displayed. FMV/menus may use
a narrower viewport than the configured widescreen mode.
- `render_verify_enabled`, existing `render_verify_checks/mismatch`, and
`render_verify_ever_executed`: enablement, cumulative checks/failures and
whether any optional extra render-pass state-preservation check has run.
These do not monitor ordinary rendering or texture correctness. An enabled
check that never executes supplies no coverage. To measure a recording
interval, compare the counter increase, not just a preexisting nonzero value.
- `visual_verify_available` / `visual_verify_checks`: currently zero because
this recorder has no independent pixel oracle. This is explicit missing
coverage, never a successful texture test. Screenshots/clips and matched
original-game comparisons remain necessary for visible defects.
- `incident_capture_request`: `-1` means no request on this row, `0` rejected,
and `1` queued on the existing composed-output capture path. The next
presented image is written as `incident-<number>.png` if capture succeeds;
an accepted request alone is not a completed screenshot. The analyzer lists
accepted/rejected requests separately from image files present in the bundle.
Capturing on an explicit incident may cost a readback/PNG write; ordinary
frames do not request pixel capture. A pending diagnostic request can be
replaced by a later explicit screenshot request before it is fulfilled.
The backend observer does not read pixels, query GL errors, synchronize the
render thread or change guest state. Its snapshot makes at most three attempts
and reports unavailable if a coherent publication cannot be sampled.
Analyze either v1 or v2 without launching a game:
```text
python replay-diagnostics.py <session-dir> --analyze
```
The JSON report separates activity, optional state checks and visual coverage.
Old v1 bundles explicitly report absent ordinary-renderer/effective-mode data.
Missing probes, zero executed checks, sequence discontinuities and counter
resets are visible; zero mismatches alone is never presented as a pass. A torn
final row is ignored, while a malformed complete row is rejected.
A diagnostic recording is helpful for exact input/state reproduction, but is
not a prerequisite for investigating a visual report. Reproduce known menu,
attract-demo, transition and resize cases directly with a fresh private save
directory and muted automated output. Capture the composed presentation, since
raw/native VRAM screenshots can omit the very widescreen/HD transformation at
issue. Preserve location, camera direction, effective mode and before/after
images. Escalate to an incident-marked recording when the trigger is unclear
or depends on intermittent timing; do not classify an untried case as
improbable merely because the reporter supplied no log.
## Replay
Use Python 3.8+ (install `tomli` before Python 3.11) with the recording's original
native executable, disc and game configuration. The tool copies the executable, runtime dependencies and
starting state into a newly created private directory. Personal settings/cards
and the original installation are untouched.
```text
python replay-diagnostics.py <session-dir> --exe <original-executable> \
--disc <disc.cue> --game <game.toml> --output <new-private-directory>
```
The source copy is `psxrecomp/tools/replay_diagnostics.py`. For an AppImage,
extract the original image, pass the native `usr/bin/<game>` executable and
the payload's `game.toml`. Direct AppImage execution is rejected because its
launcher can seed personal data. Automated playback uses dummy SDL audio
output while retaining emulation; ordinary interactive play keeps normal audio.
Strict playback requires the same input format, execution identity, portable
mod-plan identity and build identity. The build identity includes the game's
serial/version, game/framework revisions and SHA-256 of the running executable.
The binary hash also distinguishes source archives, dirty builds, compiler
changes and signed packages. A baseline segment must restore exactly the saved
image before inputs are consumed. Playback checks every accepted pad field and
sampled RAM digest, stops at the first observed divergence, and reports success
only after all complete rows have been checked. A torn final row is ignored.
Developer builds additionally accept `--input-only` to explore a route across
changes. This checks accepted inputs but skips build/mod/RAM equivalence and
reports route completion separately. Player builds reject it. Legacy v1 routes
require an explicit `--legacy-connected` assumption because they lacked pad
presence. These routes are never counted as verified replay.
## Standalone validation
No game, BIOS, SDL window or speakers are needed:
```text
cmake -S runtime/tests/playtest_diagnostics -B build-diagnostics
cmake --build build-diagnostics --parallel 2
ctest --test-dir build-diagnostics --output-on-failure
```
The fixtures cover queue saturation/owned rows/order/wall-time flushing,
record/replay alignment, digital and analog controller removal, incident
debouncing, first divergence, identity mismatches, save-state baselines, readonly
output fallback, release-build input-only rejection and isolated replay setup.
They establish the shared engine-boundary contract. Actual title/disc playtests
are still needed to qualify each game's original-build replay.
The video tests additionally cover concurrent passive snapshots, composition
versus completed presentation, failed presents, live scale/aspect changes,
ordinary activity with no optional checks, legacy coverage reporting and the
analysis-only CLI.