Skip to content

Fix replay verify for replays > 720 frames; add SSB64_RIG_EXIT and port_exit_process() (decomp bump + docs) - #265

Open
teslat0mic wants to merge 4 commits into
JRickey:mainfrom
teslat0mic:rig/t1-state-trace
Open

Fix replay verify for replays > 720 frames; add SSB64_RIG_EXIT and port_exit_process() (decomp bump + docs)#265
teslat0mic wants to merge 4 commits into
JRickey:mainfrom
teslat0mic:rig/t1-state-trace

Conversation

@teslat0mic

Copy link
Copy Markdown

Disclosure: the code and this write-up were written by Claude (Fable 5, via Claude Code) under my direction. I test on real hardware — the live controller recording in the verification section is mine — but I cannot read or judge C myself, so please review with that in mind, and tell me if you'd prefer a different process for AI-authored contributions.

Summary

Companion to the decomp PR JRickey/ssb-decomp-re#14. Bumps the decomp submodule to pick up the netreplay/netinput fix, adds the one port-layer helper it needs, and documents it.

The bug: SSB64_REPLAY_PLAY verification re-read sSYNetInputHistory — the 720-entry rollback input ring — at the end of playback, so for the default 1800-frame recording it hashed only ticks 1080–1799 while the recorded checksum covered all 1800. Every default recording reported result=FAIL regardless of determinism.

The fix (decomp): syNetInputFuncRead() accumulates the published-input checksum at the point it advances the VS tick (so it agrees with the recorder by construction, including under the bootstrap P2P start barrier), frozen at the replay's frame count; the dead ring checksum is deleted; playback that ends before the replay stream reports INCOMPLETE; SSB64_RIG_EXIT=1 makes the verdict the process exit code (0/1/2).

This PR

  • port/port_log.{c,h}: port_exit_process(int code) — flush the log and all stdio streams, then terminate immediately: _exit() on POSIX, TerminateProcess() on Windows. On Windows _exit() isn't enough — it runs ExitProcess, which kills the other threads and then runs every DLL's DLL_PROCESS_DETACH on the calling thread; at VS scene teardown that produced STATUS_FAIL_FAST_EXCEPTION (0xC0000602) instead of the exit code. (+28 lines.)
  • docs/bugs/replay_verify_ring_window_2026-08-29.md — symptom, root cause with the arithmetic proof, final design, exactly what was and wasn't verified, audit hook ("end-of-run validation that re-reads a bounded ring is only valid for runs shorter than the ring"). Indexed in docs/bugs/README.md.
  • docs/netplay_architecture.md, docs/netcode_agent_rules.md — API table and validation-path steps updated for the new functions (no more references to the deleted one), SSB64_RIG_EXIT and exit code 2 documented.
  • decompdbac284 (3 commits; details and the verification table in the decomp PR).

Verification

Live round trip: a controller-recorded 1800-frame match (human Fox vs 3 CPUs) replays PASS/exit 0 on MSVC 14.43 and clang 18; the same file with one button flipped FAILs/exit 1. Synthetic replays on both compilers: neutral CPU-vs-CPU FAIL→PASS; pseudo-random human input PASS; two corruption controls FAIL/exit 1; replay-longer-than-match INCOMPLETE/exit 2. Same checksums on both compilers. Details in the write-up.

Where this is going (so you can steer early)

I'd like to contribute rollback netplay on top of the netinput/netreplay/netpeer foundation, rig-first: a determinism harness you can trust before any snapshot/rollback code lands. Rough sequence, each as a small separate PR:

  1. this fix;
  2. a per-tick gameplay state trace (fighters / items / weapons / stage / RNG / battle-state sub-hashes) recorded next to a replay and compared on playback — turns "inputs replayed identically" into "the game evolved identically", and names the subsystem that diverged;
  3. a synthetic-replay corpus generator (metadata + input stream → automated CPU-vs-CPU matches, hundreds of seeds) and an MSVC-vs-clang trace diff — first expected divergence is the remaining host-libm trig on Windows;
  4. only then: arena snapshot/restore + a GGPO-style SyncTest mode, then GekkoNet (or ggrs) integration on the existing syNetInputFuncRead seam.

If any of that conflicts with plans you or TechnicallyComputers already have, tell me and I'll adapt — I'd rather build what you'd merge.


🤖 Generated with Claude Code

https://claude.ai/code/session_01FDMaJejZnPFRN3jP6szrCs

teslat0mic and others added 3 commits August 29, 2026 14:24
…EXIT

docs/bugs/replay_verify_ring_window_2026-08-29.md documents the false
negative (verify re-read the 720-entry input ring) and the fix; netplay
docs updated for the new verify semantics and the SSB64_RIG_EXIT batch
exit code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDMaJejZnPFRN3jP6szrCs
decomp dbac284: the published-input checksum is now accumulated inside
syNetInputFuncRead() where the VS tick advances (frozen at the replay's
frame count), the dead ring-based checksum is deleted (0919e73), playback
that ends before the replay stream reports INCOMPLETE, and SSB64_RIG_EXIT
exits 0/1/2.

port/port_log.{c,h}: port_exit_process(code) - flush the log and stdio,
then TerminateProcess() on Windows / _exit() elsewhere. _exit() on
Windows runs ExitProcess, which kills the other threads and then runs
every DLL's DLL_PROCESS_DETACH on the calling thread; at VS scene
teardown that produced STATUS_FAIL_FAST_EXCEPTION (0xC0000602) instead
of the exit code. TerminateProcess skips all of it.

Docs: bug write-up rewritten for the final design and the exact
verification done (all synthetic input files; no live-controller
record->play round trip yet), API table and validation-path text no
longer reference the deleted function, exit code 2 documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDMaJejZnPFRN3jP6szrCs
A controller-recorded 1800-frame match (MSVC build) replays PASS on both
MSVC and clang; the same file with one button flipped at tick 900 FAILs
with exit 1 on both. Replaces the "no human recording round-tripped" caveat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDMaJejZnPFRN3jP6szrCs
…ck in port_exit_process()

port_exit_process(): fall through to _exit() if TerminateProcess() ever
returns, so a failed terminate can never resume the game with a bogus exit
code. Docs: exit code 3 (LOADFAIL), the frame_count == 0 rejection, and the
write-up's Status line now lists every touched file (netinput.c/.h,
netreplay.c, port_log.c/.h).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDMaJejZnPFRN3jP6szrCs
@teslat0mic

Copy link
Copy Markdown
Author

Follow-up pushed (dd79e13, decomp 6adc1c5): exit code 3 = LOADFAIL when the replay file fails to load (previously no verdict, no exit — a batch runner would hang); loader rejects frame_count == 0; port_exit_process() falls through to _exit() if TerminateProcess() ever returns; docs updated and the write-up's Status line now lists every touched file. Details in JRickey/ssb-decomp-re#14.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant