mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
`const_fold_block`'s per-block dead-code pass was collecting temp usage from only the block it was folding, so a `LoadImm` whose destination is consumed by a *sibling* block (for example via the merge block's branch terminator) was incorrectly treated as dead and dropped. The `and` / `or` short-circuit lowering emits exactly that shape: the false path writes `LoadImm(result, 0)` and joins with the right path at an `and_end` / `or_end` block whose branch terminator reads `result`. After the DCE the false path's store was gone, leaving the zero-page result slot to carry whatever value the *previous* `and` / `or` evaluation had written there — stale data that bled into subsequent conditional branches. I found this while instrumenting `examples/platformer.ne` through a puppeteer-driven jsnes harness, stepping one frame at a time and snapshotting the full zero-page trace of each scenario (title-skip, hold-right, hold-left, jump-spam, coin-drift, enemy-stomp, long-run). In a clean idle run the enemy-1 stomp bounce (`rise_count = 6`, `fall_vy = 0`) fired at emulator frames 83 and 96 with `camera_x` = 61 and 74, i.e. with `e1_sx` = 39 and 26, nowhere near the intended `[72, 96)` pickup window. The trigger turned out to be the slot alias: every time `c2_sx` landed in its pickup window (so the coin-2 `and` stored 1 into ZP(130)) and the player was mid-fall at or past `player_y = 152`, the enemy-1 stomp `and` short-circuited to its false path, left ZP(130) at 1, and the stomp `if` fired on stale data. The fix is to compute function-wide source-operand usage once before folding each function's blocks and OR it into the per-block liveness check, so a LoadImm is only dropped if nobody — neither its own block nor any other block in the function — reads the temp. Added a regression test (`const_fold_preserves_loadimm_used_by_sibling_branch`) that builds the exact CFG shape the `and` lowering emits and verifies the false-path `LoadImm(result, 0)` survives optimization. Impact on the example ROMs: - `examples/platformer.nes`: enemy-1 stomp now fires only when `e1_sx ∈ [72, 96)`, as the source intends. The pixel golden is unchanged (`player_y` converges back to the ground line before frame 180), but the audio hash flips because the spurious `play hit` sfx calls during coin-2 passage are gone. Committing the new `tests/emulator/goldens/platformer.audio.hash`. - `examples/logic_ops.nes`, `examples/bitwise_ops.nes`, `examples/match_demo.nes`, `examples/mmc3_per_state_split.nes`, `examples/two_player.nes`: byte-different but observably unchanged — their pixel + audio goldens still match to the byte. They exercise `and` / `or` in the source and now compile through the corrected DCE. All other example ROMs are byte-identical to pre-fix. `cargo fmt`, `cargo clippy --all-targets`, `cargo test --release` (498 tests), and `tests/emulator/run_examples.mjs` (22/22 goldens) are clean. https://claude.ai/code/session_013Bi4H4YQ5or5HtMB4doUFi |
||
|---|---|---|
| .. | ||
| arrays_and_functions.audio.hash | ||
| arrays_and_functions.png | ||
| audio_demo.audio.hash | ||
| audio_demo.png | ||
| bitwise_ops.audio.hash | ||
| bitwise_ops.png | ||
| bouncing_ball.audio.hash | ||
| bouncing_ball.png | ||
| coin_cavern.audio.hash | ||
| coin_cavern.png | ||
| comparisons.audio.hash | ||
| comparisons.png | ||
| function_chain.audio.hash | ||
| function_chain.png | ||
| hello_sprite.audio.hash | ||
| hello_sprite.png | ||
| inline_asm_demo.audio.hash | ||
| inline_asm_demo.png | ||
| logic_ops.audio.hash | ||
| logic_ops.png | ||
| loop_break_continue.audio.hash | ||
| loop_break_continue.png | ||
| match_demo.audio.hash | ||
| match_demo.png | ||
| mmc1_banked.audio.hash | ||
| mmc1_banked.png | ||
| mmc3_per_state_split.audio.hash | ||
| mmc3_per_state_split.png | ||
| palette_and_background.audio.hash | ||
| palette_and_background.png | ||
| platformer.audio.hash | ||
| platformer.png | ||
| scanline_split.audio.hash | ||
| scanline_split.png | ||
| sprites_and_palettes.audio.hash | ||
| sprites_and_palettes.png | ||
| state_machine.audio.hash | ||
| state_machine.png | ||
| structs_enums_for.audio.hash | ||
| structs_enums_for.png | ||
| two_player.audio.hash | ||
| two_player.png | ||
| uxrom_banked.audio.hash | ||
| uxrom_banked.png | ||