mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
56a6cfe782
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
229 lines
13 KiB
Markdown
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.
|