Skip to content

Fix sprite clipping, add display wait, and whole-pixel LO-RES scrolling - #29

Open
HarianthK wants to merge 1 commit into
kurtjd:mainfrom
HarianthK:fix-clip-and-display-wait
Open

Fix sprite clipping, add display wait, and whole-pixel LO-RES scrolling#29
HarianthK wants to merge 1 commit into
kurtjd:mainfrom
HarianthK:fix-clip-and-display-wait

Conversation

@HarianthK

Copy link
Copy Markdown

This covers the three items in #22, and a fourth thing I ran into on the way.

Clipping

chip8_draw only wrapped the start position when clipping was off (and that block was commented out anyway). So in S-CHIP mode a sprite drawn at, say, (110, 50) never appeared at all. The start position always wraps around the screen; the clipping quirk only decides what happens to the part that hangs over the edge. That is the ERR2 the quirks test was showing.

There was a second problem in HI-RES. The S-CHIP 1.1 rule that adds the number of rows falling off the bottom of the screen to VF was applied to every HI-RES draw as VF += (y + rows) - 63, which goes negative for any sprite that does not reach the bottom and wraps VF to garbage. That is why the HI-RES half of the check disagreed with the LO-RES half and the row said ERR3. It now only counts rows that actually fall off.

Display wait

The original hardware could only draw one sprite per screen refresh, and programs written for it use that to pace themselves. This adds quirk 10, on by default (no wait, as S-CHIP), off in legacy mode, and toggled with -w. With it off, a second Dxyn in the same frame steps PC back and runs again after the next tick.

Getting this to pass the test needed one more change. The delay timer only accumulated time while it was running, so its tick phase depended on when DT was last set, while the refresh tick had a fixed phase. A program that draws once per frame and counts frames with the delay timer would see them agree or disagree by luck. The timers now count down on one free-running tick, which is also what releases the waiting draw. That is how the original interrupt worked, and it makes the result the same at any CPU speed (I checked 1000, 1800, 3000, 10000 and 50000 Hz).

LO-RES scrolling

The buffer is always 128x64 and a LO-RES pixel is two cells of it, so scrolling by n moved half a LO-RES pixel per unit. That is what the HP48 did and the scrolling test's "legacy" path already passed. Modern S-CHIP interpreters and XO-CHIP scroll whole pixels, so this adds quirk 11, on by default, off in XO-CHIP mode, and toggled with -g, which doubles the distance in LO-RES.

Also

-9 was never in the getopt string, so that flag was rejected even though there was a case for it. And the quirk initialiser in tests/test_opcodes.c had nine entries, which did not matter until the array grew.

How I checked

I compiled src/chip8.c into a small headless harness (no SDL, __LIBRETRO__ defined for deterministic timing) and ran the test suite ROMs through it, comparing against an interpreter that passes the suite.

Quirks test, all six rows, going through the menus:

Mode Before After
-l as CHIP-8 display wait and clipping failed all pass
default as S-CHIP modern clipping failed all pass
-w as S-CHIP legacy display wait and clipping failed all pass
-x as XO-CHIP clipping failed all pass

Scrolling test: S-CHIP legacy LO-RES already showed the complete picture; S-CHIP modern LO-RES (with -g) and XO-CHIP LO-RES now do too, and both HI-RES paths are unchanged. The opcode, flags and keypad tests are pixel for pixel unchanged in every mode.

tests/test_opcodes.c passes, and main.c and libretro.c compile clean with -Wall -Wextra (the one sign-compare warning in libretro.c was already there). I could not link the full SDL app on this machine, so the front end has only been compiled, not run.

One thing to be aware of: chip8_dump writes the raw struct, and the struct has changed (two more quirks, a new flag, and the two timer accumulators merged into one), so old dump files will not load.

…RES when asked

Three things from kurtjd#22, plus a fourth found on the way.

Clipping. A sprite drawn past the edge of the screen never appeared,
because the start position was only wrapped when clipping was off. The
start always wraps; clipping only decides what happens to the part of
the sprite that hangs over the edge. Separately, the S-CHIP 1.1 rule
that counts rows falling off the bottom of the screen into VF was
applied to every HI-RES draw, so a sprite well inside the screen added a
negative number to VF. It now only counts rows that actually fall off.

Display wait. The original hardware could only draw once per screen
refresh, and programs written for it rely on that to pace themselves.
A new quirk, off in legacy mode and toggled with -w, makes a second
draw in the same frame run again on the next one. The timers now count
down on one free-running tick which also releases the waiting draw,
because a program timing its frames against the delay timer has to see
them agree. Before, the delay timer only accumulated time while it was
running, so its phase against the refresh depended on when it was set.

Scrolling. The buffer is always 128x64, and in LO-RES each pixel is two
cells of it, so scrolling by n moved half a LO-RES pixel per unit. That
is what the HP48 did and it is kept as the default. A new quirk, toggled
with -g and off in XO-CHIP mode, scrolls whole LO-RES pixels the way
modern S-CHIP and XO-CHIP interpreters do.

Also, -9 was never in the getopt string, so that flag was rejected, and
the test's quirk initialiser was one entry short.

With these the quirks test passes every row in all four of its modes
(CHIP-8, S-CHIP modern, S-CHIP legacy, XO-CHIP), the scrolling test
shows the complete picture on all five of its paths, and the opcode,
flags and keypad tests are unchanged.
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