Dev Diary: TM002

Kicking Bishop Brennan

The other day, John Linville sent out a challenge. Basically, "get something done before CoCoFEST!" (an annual US-based event for Colour Computer users, apparently spelt with the exclamation mark). Admirable! Blogging makes an implied commitment, and I don't know about you, but deadlines are usually what get my brain out of "thinking it through" and into "actually doing".

But I won't be doing that.

Ok, what I mean is, it's incredibly unlikely I'd attend CoCoFEST! (see footnote 1) as it's in the US. But we do have our own two-day event coming up in July, here in Little England: the now-annual Dragon meet-up in Cambridge (I'll link to this once Tony posts the announcement to something other than Facebook). I've used this for motivation before, why not again? And... I have an idea. Not necessarily a good one, but let's see where it leads.

Most of the things I try out are due to wanting to explore some specific thing in software, or some interesting aspect of the hardware (with notable exceptions based on nostalgia). This time it's hardware-oriented, so unfortunately I'm going to blather on about that for a while to justify myself.

Draw it again, SAM

SAM Dividers
SAM datasheet: VDG address modifier

The video system in the Dragon supports multiple vertical resolutions, achieved by using dividers to draw the same line data a selectable number of times. Supported modes have line heights of 1, 2, 3 or 12 pixels. If you get the timing right, you should be able to vary the mode as the frame progresses.

Some years ago now, I tried to implement a simple vertical magnifying glass. It's a noddy effect where you take a high resolution image and "zoom" lines by temporarily switching to a lower resolution (taller line height). It can look kinda neat, and even appears in some early Amiga demos. The result would be to "push" the rest of the screen down, and that would be an interesting precursor to another use for this approach which I'll get to later.

Unfortunately, it didn't seem to work. Something was happening, but not what I expected.

Actually, skip that

As I've taken a certain amount of pride in researching the classical model of SAM-VDG interaction, and indeed implement the result in XRoar, the fact I'd hit something not covered by it was a trifle irksome, so I tried to find out what was going on.

I used a logic analyser to see which RAM address the SAM was fetching for video, and wrote a custom program that let me change mode at an arbitrary position within the frame. The main thing that became obvious was that when you change the SAM mode, it doesn't just set it up to do something differently the next time a divider is strobed. Something—sometimes—happens immediately. Usually, the SAM appears to have skipped a bunch of addresses.

Perl code
Describing address transitions in Perl

Now, this sort of thing can be explained by how binary counters work. If the input to one part of the counter is moved around, you can see a high-low transition which cascades to the rest of the counter. I could find a bunch of interesting transitions, but despite a lot of banging my head against the numbers, I couldn't spot the pattern. Yet Motorola surely does not play dice with video addressing.

All those dividers could be organised in a number of different ways. So I noted what changed to what, when, and wrote a small Perl script to try and brute force the problem. It went through every permutation of dividers I could think of—with varying duty cycles—along with all the effects I could imagine the horizontal sync signal having, and simulated the VDG clocking in a new address. All it had to do was find some combination that would work for all the values I'd noted down. It was my last, best, hope for understanding.

It failed.

Enter stage left

But in last year's Dragon meetup, it turned out that Stewart Orchard (him again) has also been tackling this problem. A proof-of-concept vertically scrolling demo that worked in XRoar wasn't happening on real hardware, and so he started his own investigations (this is the other thing that being able to arbitrarily push the screen down allows you to do).

What Stewart brings to the table is being an actual electronics engineer with a very large brain (see footnote 2), and he realised that mode changes may well reconfigure a set of dividers, but in real life these things don't happen instantly. The internal design of the SAM, it would appear, means that in some cases a "glitch" occurs, and the input to one part of the counter may be briefly pulled low (or indeed connected elsewhere) before being connected "correctly".

This opened up a whole new set of solutions to the problem, and Stewart did all the legwork in putting together a detailed analysis of what's really going on. Some transitions seem to end up floating internally, and the result is uncertainty over one of two possible outcomes (the screen jumps around even though you're doing the same thing each frame), but for the most part, the model seems to be far more complete (and this behaviour is now mostly reflected in the development branch of XRoar).

So what is it?

So instead of "pushing" the screen down, it turns out you can quite easily "pull" the screen up by causing the SAM to skip addresses.

By being careful about blanking and redrawing (a relatively small number of) lines, you can use this to achieve a form of "hardware vertical scrolling". You've burnt a bit of CPU time getting in sync with the display and manipulating some registers at the top of the active area, but that's nothing compared to copying a whole screenful of data around. The main limitation is that you absolutely have to get everything in your program done in one frame, as you need to be ready to tweak modes again at the top of the next one.

So this is the effect I'd like to use in an original (ish) game of my own. A car game (because I have limited imagination (see footnote 3)) where the background scrolls past your vehicle with some speed, and where maybe you have to blast some enemies.

A game tenuously called... Speed Blaster! (spelt with the exclamation mark).

Maybe.

So that name's pretty much just the combination of {Road, Speed} {Blaster, Fighter} that wasn't already the title of a video game. And I might change it yet if I can't work the blasting of anything into the gameplay. Curious aside: it's amazing how few Dragon games appear to have invoked the word "speed" in their title. Hopefully I can live up to the promises this name offers.

Originally I was thinking of doing the bare minimum for a hi-res version of Ken Reighard's Nightmare Highway. I figured that with the jokes Ken makes about it ("if you got it for free, you paid too much!" (see footnote 4)), a sequel involving clever hardware trickery would be quite amusing. But in the end I think I want to aim for something a bit different.

So here's what I'd like my game to have:

Yeah, a bit vague. I'll try and develop those ideas a bit, but before I write again, I think I'll need a proof-of-concept of my own.

Eating a whole pie

So the proof of the pudding is in the, er, scrolling. No, I'm not sure that makes sense. But anyway, I need a proof-of-concept.

We already know that we can easily set the SAM's video base address to a multiple of 512; it's part of the spec of the SAM. That gives us course-grained scrolling of 16 lines (of 32 bytes each). Can our new knowledge about SAM mode transitions then be used to then skip an arbitrary number of lines between 0 and 15? And how quickly can we do that? How many scanlines do we lose to having to perform this trick during the active area?

It's just a model

As usual, I start writing some Perl. Actually that's not true: first, I started manually plotting out transitions, keeping track of the various divider state changes as I went. Eventually, I realised I had to do this fourteen more times and that I kept losing track anyway, so then I started writing some Perl. Yes, I use computers every day and still it takes a while to twig that they're kinda helpful for this sort of thing.

The Perl script models the behaviour as implemented in the development version of XRoar, and allows me to enter instructions that change the mode at a particular cycle. At each significant change, it dumps the state of each divider so that I can see how best to next change the mode.

My working assumptions were:

Keeping the status bar as compact as possible is desirable, as there will be fewer lines of data that need manipulating every frame: 5 scanlines (0–4) is enough to represent a score, for sure. Thus, SAM mode changes can occur during scanlines 5 and 6. Actually, you can pretty much start right after the VDG has fetched the last byte it needs for scanline 4.

You can find the Perl here. Run it with a number from 1–15 for a view on what happens to skip that number of lines.

It turns out that the weird way in which you interact with the SAM registers (clearing/setting bits is performed by writes to alternating even/odd addresses) works to our favour for a change. You can change modes twice with a single STD instruction, taking 5 cycles (with direct addressing), meaning you can make up to 22 mode changes in a scanline without resorting to doubling the CPU speed (which some machines object to, even for short periods). I've reduced this to 18 mode changes to avoid doing things around the horizontal sync pulse: it may be that this is excessive paranoia.

Brilliant Bouncing Ifors

Proof-of-concept 1
Bouncing Ifor proof-of-concept

So rather surprisingly, all the required combinations can be achieved within 2 scanlines! That leaves a nice large play area of 185 lines. Hooray! And starting at the end of line 4 actually turns out to make the code quite simple. By that point certain of the dividers have already transitioned high, and that gives us something to bounce mode changes off.

Plugging those results into a bit of test code, here's my proof that it all hangs together: just click on Ifor for an in-browser demonstration.

Verified on a real machine (and Stewart has kindly also verified it on a whole host of other real machines), we see a nice smooth scroll at 50 fps.

This really is the bare bones of a demonstration to show it can work. It's simply varying the base of a viewport within a single oversized image held in RAM. You can see part of the image "sticks" at the top of the screen: this is the area that will eventually become the status bar and includes the reserved lines.

The next thing to do will be to demonstrate an infinitely scrolling playfield. Obviously a whole level cannot just be drawn into RAM at once! At least, not a very big one. This also means I can burn through another blog post just with example code and not have to think about the actual game for a while: ideal for a theorist...

Filling the void

So a bouncing cat is all well and good; we can make our display show any bit of RAM with single-line accuracy. But as already noted, that's not really good enough for a game (see footnote 5).

I need the play area to have "infinite scroll", in my case downwards, constantly filling it from the top with ever-changing level data. How do we go about that? It's actually very straightforward when you understand how memory is being treated.

Most of what's described here will be common to games on many other systems where hardware scrolling is combined with a fixed status area. The only minor complication is the variable number of lines we have to skip with the mode changing trick.

First, time to sort out the "sticky" junk lines at the top of the screen from last time. The screen will always start on a 512 byte boundary. From there, we see:

Proof-of-concept 2
Rolling Bluebelle proof-of-concept

Watch very carefully

All that should allow us to scroll the playfield while the area at the top of the screen now presents useful information and looks a lot tidier.

There's one thing we haven't discussed though! As we're scrolling downwards, the base address for our viewport decreases as we go. But we still don't have an infinite amount of RAM; what happens when the viewport comes to move beyond the lower bound?

We should reserve two screens of RAM so that we can then reset the viewport to the "other" screen before we continue. But most of that screen needs to remain the same as the one we just moved from! The whole point of this exercise is that we don't have enough CPU grunt to copy an entire screen quickly enough, so how do we make sure the data is there?

The question near enough answers itself. When we draw the new lines for each frame it's important, if these lines would appear on the last frame before we wrap around, that we copy them to the appropriate part of the other section of screen RAM. Get that right and you don't even notice the join; for all intents and purposes, there is now an infinitely scrolling playfield.

Click on Bluebelle for an in-browser demonstration!

Footnotes

  1. It's now quite unlikely that anyone will: CoCoFEST! is cancelled (just for 2020) due to COVID-19.

  2. I have evidence.

  3. Another idea I think might be realisable is a Dragon version of Rainbow Islands. We might not be flush with colours, but I reckon it could be done. If anyone from Taito/Square Enix wants to talk to me about funding the bound-to-be-lucrative Dragon port, do get in touch.

  4. Don't believe Ken, by the way. Nightmare Highway might be an incredibly low resolution game, but it plays well and has a lot of professional flourishes. My only criticism would be that at that resolution, the highest "gear" demands a near-impossible reaction time. Possibly says more about my aging brain than the game, of course.


  5. Well, there are exceptions where the play area is fixed but too tall for our screen. For example, anyone want to write a pinball game a-la Pinball Dreams? Or how about Pac-Man?

Updated 17 Mar 2020