Skip to content

Clear the index high bytes when the X flag is set - #214

Merged
mstan merged 1 commit into
RetroPortingToolKit:mainfrom
pogoflow:fix/index-high-bytes-on-x-set
Oct 27, 2026
Merged

mstan merged 1 commit into
RetroPortingToolKit:mainfrom
pogoflow:fix/index-high-bytes-on-x-set

Conversation

@pogoflow

Copy link
Copy Markdown
Contributor

On the 65816, setting the X flag makes X and Y 8-bit, and their high bytes become zero. The interpreter does this in interp816_setFlags. Generated code only zero-extends 8-bit X/Y writes, so a register that keeps its 16-bit value across SEP #$10 still holds the old high byte. The comment in _emit_increg already assumes the hardware behaviour ("SEP #$10 zeros it at the flag transition"), but nothing on the compiled side actually does it.

I ran into this on a HiROM title: an interrupt handler switched back to 16-bit index registers and pushed X, and the compiled tier stored the stale high byte where the interpreter stored zero.

Change

  • cpu_p_to_mirrors clears XH/YH whenever X ends up set. SEP, PLP and RTI all go through it, so one place covers them.
  • _emit_xce clears them when entering emulation mode, next to the existing m_flag/x_flag reset.

Test
tests/v2/test_index_high_byte_on_x_set.py compiles a small harness against cpu_state.h and the emitted XCE body. It checks that X set clears the high bytes, that X clear keeps them, and that XCE into emulation clears them. Before the fix it fails (x set: X=F000 Y=1234 exp 0000/0034, xce: X=ABCD Y=5678 exp 00CD/0078), after the fix it passes.

Full Python suite on the branch: 847 passed, 2 skipped (Windows, MinGW gcc 16.2).

On the 65816, setting X makes X and Y 8-bit with a zero high byte. The
interpreter does this in interp816_setFlags; generated code only
zero-extended 8-bit X/Y writes, so a register kept across SEP #$10 still
held its old high byte. An interrupt handler that switched back to 16-bit
and pushed X then stored that stale byte. Found on a HiROM title.

cpu_p_to_mirrors now clears XH/YH whenever X ends up set, which covers SEP,
PLP and RTI. The XCE emitter clears them when entering emulation mode.

@mstan mstan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking code findings. Clearing the high bytes in cpu_p_to_mirrors covers the generated SEP/PLP/RTI paths, and the XCE change covers entering emulation without disturbing a native-to-native exchange. This correctness fix applies by default.

Reviewed with #199 and #215 on current main 246c0d4. The combined tree passes the codegen width lint and 860 Python tests (2 platform skips), all six production PPU tests and the production runtime dispatch fixture. Extra compiled checks exercise emitted SEP then REP and both XCE directions. All three workflows on this reviewed PR head are green.

The shared regression branch is review/open-prs-final-20261026 (b19edfb). This is code and automated-fixture approval; the new #214/#215 pair still needs the normal combined title gameplay/current-build save-load/runtime gate and owner handoff before merge. Prior #199 title approval does not qualify this new pair. No games were relaunched in this review.

@mstan mstan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combined regression follow-up: the current-main #199/#214/#215 branch review/open-prs-final-20261026, frozen at b19edfb, passes all 860 Python tests (2 platform skips), six production PPU harnesses, runtime dispatch and additional emitted SEP/REP/XCE boundary checks. All eight matched Release baseline/candidate builds have recorded engine/title/UI/ROM/executable hashes.

Muted gameplay, movement, current-build save/load and nonzero emulated audio checks pass in Mega Man X, ALttP, SMW and Super Metroid. Brief built-runtime checks show no consistent introduced regression; an initial SM timing outlier did not reproduce with opposite-order matched-scene repeats. SMW remains host-paced near 60fps in Yoshi's House, so this does not qualify wider-stage margins or uncapped performance. SM uses the established original-loader Zebes setup and bypasses Ceres.

The automated combined gate is now complete for this reviewed head. Approval stands; the owner's quick visual/play check of the new #214/#215 batch remains pending before merge. No author changes requested.

mstan added a commit that referenced this pull request Oct 27, 2026
…racts-193

Share HiROM contracts and evidence while preserving execution banks.

Qualified in the combined #199/#214/#215 regression batch at b19edfb: 860 Python tests, native checks, and four representative matched gameplay/save-load/runtime checks. Owner explicitly approved merge after regression confirmation.
@mstan
mstan merged commit f430b27 into RetroPortingToolKit:main Oct 27, 2026
8 checks passed
mstan added a commit that referenced this pull request Oct 27, 2026
Draw BG modes 4-6 with the compatibility renderer under NewRenderer.

Qualified in the combined #199/#214/#215 regression batch at b19edfb: 860 Python tests, native checks, and four representative matched gameplay/save-load/runtime checks. Owner explicitly approved merge after regression confirmation.
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.

2 participants