Fix championship mode progression (win recorded as loss) - #2
Merged
Conversation
… jump table
Championship/story mode recorded every match as a loss regardless of the
actual result (no prize money, story took the failure branch). Root cause is
a novel N64Recomp miscompile: the storyline-condition evaluator func_800EE1F0
dispatches through a jump table (0x801036D0) indexed by a LOOP-CARRIED
INDUCTION POINTER (base set once above the loop, +4 per iteration). With no
in-block `sll idx,2` to pattern-match, N64Recomp emitted a constant index
(jr_addend = 0), collapsing the 5-way switch to case 0 -- the mask sequence
{0x7,0x3000,0x70,0xF00,0x70000} degenerated to {0x7,...}, so the win
condition never matched and the evaluator fell through to the lose node.
fix_switches does not catch this: the table is fully present, only the index
is wrong. New post-regen pipeline step tools/fix_jraddend.py detects the
`jr_addend_* = 0` signature (the only non-register addend N64Recomp emits) and
rewrites it to `(uint32_t)ctx->rD - <table_base>` -- the live induction pointer
drives the dispatch. The (uint32_t) cast is required: the guest pointer is
sign-extended to 64 bits, so a zero-extended base subtraction would overflow
the switch into switch_error. Idempotent; fails loud. Registered after
fix_switches in BUILDING.md, validate.yml, and the PR template.
The fix ships as the tool: RecompiledFuncs/ is generated (gitignored), so CI
regenerates and applies fix_jraddend during the release build.
Claude-Session: https://claude.ai/code/session_018PLyfT4WLQsgPdjQ3RguJH
Patch release carrying the championship-progression fix (#1). Claude-Session: https://claude.ai/code/session_018PLyfT4WLQsgPdjQ3RguJH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1.
Symptom
In championship/story mode, every match was recorded as a loss regardless of the actual result — no prize money, and the story advanced down the failure branch. Confirmed on World Heavyweight and IC titles (matches the issue report; reproduced on Windows).
Root cause
A novel N64Recomp miscompile. The storyline-condition evaluator
func_800EE1F0dispatches through a jump table at0x801036D0that is indexed by a loop-carried induction pointer ($s2: base set once above the loop,+4per iteration on the back-edge). With no in-blocksll idx,2for N64Recomp to pattern-match, it emitted a constant index (jr_addend = 0) — the only non-register addend in the whole build — collapsing the 5-way switch to case 0. The per-slot mask sequence{0x7, 0x3000, 0x70, 0xF00, 0x70000}degenerated to all0x7, so the win condition could never match and the evaluator returned the lose/default node.fix_switchesdoes not catch this: the table is fully present; only the index is wrong.Fix
New post-regen pipeline step
tools/fix_jraddend.py. It detects thejr_addend_* = 0signature and rewrites it to(uint32_t)ctx->rD - <table_base>, so the live induction pointer drives the dispatch. The(uint32_t)cast is required — the guest pointer is sign-extended to 64 bits, and a zero-extended base subtraction would overflow the switch intoswitch_error(that failure mode was hit and corrected during bring-up). Idempotent; fails loud. Registered afterfix_switchesinBUILDING.md,validate.yml, and the PR template.The generated
RecompiledFuncs/is gitignored, so the fix ships as the tool — CI regenerates and appliesfix_jraddendduring the build. This PR's CI run exercises exactly that path.Testing
Also