1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00

platformer: stabilise sprite-0 HUD by splitting below the HUD row

The previous commit parked the sprite-0 hit anchor on top of
the visible HUD coin at NT col 2, row 1 — the hit therefore
fired at dot 19 of scanline 15 (the HUD's last scanline) and
jsnes's PPU model applied the scroll change to the rest of that
same scanline, smearing the bottom row of every HUD glyph as
`camera_x` drifted. The score "0"/"2" digit, the heart, and
the lives "3" each flickered a handful of different pixel
patterns per frame as the scroll shifted. An in-harness
check for unique HUD states across the 180-frame window saw
~20-40 distinct states.

Fix: move both anchors off the HUD row entirely.

- `TILE_SPRITE0_ANCHOR` still has its single opaque pixel at
  row 7, col 3, but now draws at OAM `(248, 8)` so the pixel
  lands at screen `(251, 16)` — the first scanline of the
  playfield.
- New `TILE_BG_ANCHOR` (tile 28) mirrors it with a single opaque
  pixel at row 0, col 3; the map pre-paints it at NT `(col 31,
  row 2)` via the new `a` legend entry. Its one opaque pixel
  lands at the same `(251, 16)`, so the PPU's sprite-0 hit
  fires there instead.
- An explicit `scroll(0, 0)` right before `sprite_0_split`
  defensively re-latches scroll to zero in case jsnes has
  carried stale `$2005` state over from the previous frame.

With the hit on scanline 16, `$2005` writes in HBLANK of
scanline 16 (or thereabouts) only affect scanline 17 onward;
rows 8-15 all render at scroll=0 and the HUD glyphs stay
pixel-stable. The unique-state count across the 180-frame
harness drops from ~40 to 4 — and those 4 correspond to the
legitimate score / lives transitions (initial title, post-
stomp-1, post-stomp-2, etc.), not per-frame jitter.

Column 31 and OAM x=248 both sit inside jsnes's right-edge
overscan so the anchor pixels are invisible in the committed
golden. Goldens + gif refreshed.
This commit is contained in:
Claude 2026-04-20 17:54:55 +00:00
parent c5b04c2065
commit 615490fbe5
No known key found for this signature in database
6 changed files with 93 additions and 42 deletions

View file

@ -367,18 +367,22 @@ aaaaaaaa
...aa...
........",
},
// Sprite-0 hit anchor — a single opaque pixel at row 7, col 3,
// everything else transparent. Sits behind the HUD row's coin
// tile as OAM slot 0 in every `on frame`; its one opaque pixel
// aligns with column 3 of the coin's row 7 (`...bb...`), so
// the PPU sets the sprite-0 hit flag at scanline 15 — the
// last scanline of the HUD row. Writing `$2005` the moment
// that flag sets means the horizontal scroll flip takes
// effect at scanline 16 (the PPU latches horizontal scroll at
// the next HBLANK), which pins NT rows 0-1 at scroll=0 and
// lets scanlines 16+ render at `camera_x`. A sprite that
// hit on its *top* row (e.g. using the coin as sprite 0)
// would flip the scroll mid-HUD-row and smear the glyphs.
// Sprite-0 hit pair — two single-pixel tiles whose one opaque
// pixel sits at row 7 col 3 (sprite side) and row 0 col 3 (bg
// side). OAM slot 0 gets `Sprite 0 anchor` at (248, 8) and NT
// (col 31, row 2) gets `BG anchor`, so both opaque pixels
// land at screen (251, 16) — the first scanline of the
// playfield, one scanline below the HUD row. The PPU's
// sprite-0 hit fires at dot 251 of scanline 16, `$2005`
// writes from `sprite_0_split` land in HBLANK, and the
// horizontal-scroll reload for scanline 17 latches the
// playfield's `camera_x` without touching scanlines 8-15
// (where the HUD glyphs live). A hit on scanline 15 itself
// would smear the bottom row of every HUD glyph across the
// split in jsnes. Both tiles are transparent everywhere
// except that one pixel, so the only BG footprint is a
// single dot at (251, 16) — inside jsnes's right-edge
// overscan, invisible in the committed golden.
Tile {
name: "Sprite 0 anchor",
art: "\
@ -391,6 +395,18 @@ aaaaaaaa
........
...c....",
},
Tile {
name: "BG anchor",
art: "\
...c....
........
........
........
........
........
........
........",
},
];
// ── Named CHR tile indices used by the nametable layout below ──