Thursday 1 June 2023

Galaxian star field

I've been chipping away at Galaxian as updates come from jotd. It's all-but-finished now, with a few sounds to implement and/or debug on the Neo Geo, and the mystery of randomly disappearing bombs.

I've just finished implementing the starfield, although I realise now I made one mistake that I may or may not decide to go back and rectify. The end result would be practically indistinguishable though.

The starfield in Galaxian (and similar games) is generated in hardware with some rather simple circuitry. A maximal-length LFSR is used to generate - and scroll - the star positions, with some simple combinatorial logic to generate the colours and blinking.

The original (MAME's approximation anyway)

Of course there's no way to replicate that mechanism on the Neo Geo, so I had to come up with a way to implement something similar with sprites. I wanted to be as accurate as possible, and I think I've managed that (almost) except for the blinking pattern (which you couldn't possibly tell anyway).

The LFSR has a period of 131071 (2^17-1) but is effectively clocked twice per pixel which, given a 256x256 pixel virtual display, causes the starfield to scroll every frame. The combinatorial logic generates 252 stars each frame (which of course just repeats every frame). Star colours are derived from certain bits in the LFSR on that clock.

The Neo Geo requires just 16 sprites of 16 tiles each to cover a 256x256 pixel area - IOW 256 sprite tiles. The first step was to generate the stars using the same LFSR and render them in the respective Neo Geo tile, taking into account the relative rotations of both the Galaxian and Neo Geo displays. [I've since realised I made a wrong assumption about the orientation of the Galaxian display, which wouldn't be difficult to fix.]

Once I had the star positions, I had to think not only about the colours, but also the blinking. Fortunately the Neo Geo has an extensive palette which allows up to ~4096 colours on-screen at the one time. This allowed me to use a separate palette entry for each of the 252 individual stars. The only added complication was partitioning the palette entries across those 252 stars. I calculated that each 16-colour palette would be sufficient for one half (left/right) of each sprite. Not 100% efficient use of the palette, but easier to calculate and also code up.

Now to blinking. No prizes for guessing I used colour-cycling. With a unique palette entry for each star, I can blink any/all stars independently. But what algorithm should I use?

Although there are only 252 stars, each of the 16 sprites requires 2 palettes (with 16 colours/palette) for a total of 512 palette entries. So I came up with a 9-bit (Galois) LFSR that effectively selects a random palette entry (star) each clock - though not all entries correspond to stars of course. To blink the stars, I needed two (identical) LFSRs, each clocked at the same frequency, but out of phase. The leading LFSR would blank out palette entries, and the following LFSR would restore them. How long the stars are blanked out depends on the phase difference between the two LFSRs.

I played with them only briefly before coming up with an acceptable solution; the LFSRs are 1/4 of a cycle out of phase, and they're each clocked 4 times per VBLANK. This seems to give a fairly decent emualtion of the starfield in terms of blinking.

Not that anyone will ever notice any of this!

Starfield on the Neo Geo. Can you still tell it's Galaxian?

If I do go back and change the starfield layout (as mentioned above) I'll update with results. In theory it should generate the exact pattern of the original. Blinking will never be the same though.

UPDATE: It took just 2 simple changes to reverse the rotation of the Galaxian screen and re-generate the starfield. However I still can't visually confirm it's the same as the original. But it'll probably stay this way now regardless, as I need to manually render an opaque tile for the display masking sprites. Oh well, I tried...

No comments:

Post a Comment