Skip to content

RetroArch same-process replay diverges at identical frame numbers, ~40% of runs #40

Description

@drewstone

The failure

same-process replay diverged in the RetroArch adapter gate. Two replays of one boot state and one input log, inside one process, agree byte for byte through emuFrame 811, then differ on channel values at identical frame numbers.

expected  ch_c581_c582: 16665   ch_c57e_c57f: 9000   ch_c4e6...: 4294902274
actual    ch_c581_c582:   404   ch_c57e_c57f: 9001   ch_c4e6...: 4294902279

Identical emuFrame values in both passes rule out a miscounted advance. The diverging channels are counter-shaped, which is what accumulated timing drift inside the emulator looks like.

Rate

Measured 2026-08-27 across pull requests and main:

period pass fail
before the frame-advance barrier fix 0 4
since that fix 3 2

requiredSampleSize for a 20% → 80% effect at alpha 0.05, power 0.8 is n≈10 per arm. There are 4 and 5. The barrier fix appears to have helped and provably did not eliminate the divergence.

What was already tried

gap() sends one run-loop iteration between frame advances and is a barrier: FRAMEADVANCE returns as soon as RetroArch accepts it, so without an intervening iteration the next advance can be issued while the previous frame is still settling. command returns null when it gives up and gap discarded that return, so a barrier that did not happen looked exactly like one that did. That is fixed and the failure still recurs.

Ruled out: COMMAND_TIMEOUT is 5s with a resend loop, so a timeout there means RetroArch is wedged rather than busy.

Also seen once: RetroArch never reported PAUSED — a boot failure, a separate infrastructure symptom on the same job.

Current state

The job is quarantined out of requiredCiJobs in release-gate.mts. It still runs on every pull request and its result is still visible; a release no longer claims RetroArch replay as a verified property.

That was chosen over the alternative that actually occurred twice: rerunning the job until green and then tagging, which launders a red into a green and is the exact failure the 0.9.0 gate was built to stop.

To close this

Root-cause the divergence and restore the job to requiredCiJobs. A test case in release-gate.test.mts asserts the quarantine and will fail on restoration, deliberately, so putting it back is a conscious act.

The repo's own docs/adapters.md already notes that several libretro cores "are known to be non-reproducible across processes and would honestly be trusted-recorder." This is worse than that: it is non-reproducible within one process. If root-causing fails, changing RetroArch's declared verification mode is the honest alternative.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions