Sunday, 17 November 2024

Fixing Autofire in VIC20 Bandits

Over the years of adding various games to the Penultimate cartridge, I have come across a few that behave oddly when you have a datasette or SD2IEC plugged into the VIC.

Games that automatically start or automatically fire, ones that don't register the joystick at all, and one that only moved right.

These games behave normally when there is not an SD2IEC connected, but play up when there is one.

One such game is Bandits. That seems to just autofire all the time, it does move correctly, but fire is stuck on.

For inexplicable reasons, joystick up, down, left and fire are read from port A on VIA #1. Joystick right is read from port B on VIA #2. I mean why? Who thought that was acceptable. Fire on a different port from the directions would have been more acceptable, maybe even useful, but why aren't they all just on the one port?

Port A on VIA#1 is arranged as follows:

  • VIA #1 Port A 0 -IEC Clock
  • VIA #1 Port A 1 -IEC Data
  • VIA #1 Port A 2 -Joystick Up
  • VIA #1 Port A 3 -Joystick Down
  • VIA #1 Port A 4 -Joystick Left
  • VIA #1 Port A 5 -Joystick Fire
  • VIA #1 Port A 6 -Cassette Sense
  • VIA #1 Port A 7 -IEC Attention

VIA2 Port B is keyboard column outputs

  • VIA #2 Port B 0 -Keyboard Column 0
  • VIA #2 Port B 1 -Keyboard Column 1
  • VIA #2 Port B 2 -Keyboard Column 2
  • VIA #2 Port B 3 -Keyboard Column 3 / Cassette Write
  • VIA #2 Port B 4 -Keyboard Column 4
  • VIA #2 Port B 5 -Keyboard Column 5
  • VIA #2 Port B 6 -Keyboard Column 6
  • VIA #2 Port B 7 -Keyboard Column 7 / Joystick Right

I am not sure if there were some last minute changes, but the arrangement of pins on that port, and the subset of those that appear on the userport are a little odd.

What is the point of cassette sense without the other cassette pins? What is the point of the bus side of the IEC ATN pin? Why have Up, Down, Left and Fire but not Right?

The multiple uses of VIA #1 Port A can cause problems with games, depending on how they test the port.

When you read that port, you get the joystick information you want, but also IEC and datasette signals that you don't.

It is fine if you work on the principle of testing if bit 5 is 0 to see if fire is pressed. But some games instead compare the value of the whole port against constant values. Those constant values are based on what they read back from their machines, which depends on the state of the things what they had plugged in, which does not necessarily match what you have plugged into your machine.

The standard resting state of that port on a VIC20 without a datasette or IEC device is $7E. If you press fire on a joystick, bit 5 changes, so the value now reads $5E. So you can write your game to check if the value changes to $5E, and if it does, fire must have been pressed, so you launch a rocket at a poor defenceless alien menace.

However, if the game was loaded from tape and the play button is still pressed down, the resting state would instead be $3E and when fire was pressed, would change to $1E. If you code was checking for $5E it would fail and you would be invaded by the aliens.

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Value
IEC ATN
Cassette Sense
Joystick Fire
Joystick Left
Joystick Down
Joystick Up
IEC Data
IEC Clock
0
1
1
1
1
1
1
0
$7E
0
1
0
1
1
1
1
0
$5E
0
0
1
1
1
1
1
0
$3E
0
0
0
1
1
1
1
0
$1E

I have seen a few games with "please stop tape" messages, because you will get different values back from the port if the play button is still pressed down, and their code has been written assuming it was not. So rather than fixing their code, they just ask the user to press stop.

Aside: If I don't mention this, everyone will tell me about it in the comments, so yes, I am aware of the video by Robin (8 bit show and tell) about the C64 datasette controls.

And yes, I am aware of the Aquarius game from Aleksi Eeben that uses the datasette as a game controller (I was the one that arranged for TFW8b to duplicate the tapes for Aleksi).

End of aside.

With the IEC port, the power on state of the signals is usually the same, clock held low (asserted) by the VIC20, and data floating high.

That changes when you access the device, after that, the clock line is usually released by the VIC20, so both signals float high and stay high.

In that case, the resting value of $7E changes after an IEC access to $7F (or 126 changes to 127 in decimal in the BASIC demonstration below).

Again, if your code is checking for absolute values, then all your tests will fail because that one bit has changed.

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Value
IEC ATN
Cassette Sense
Joystick Fire
Joystick Left
Joystick Down
Joystick Up
IEC Data
IEC Clock
0
1
1
1
1
1
1
0
$7E
0
1
0
1
1
1
1
0
$5E
0
1
1
1
1
1
1
1
$7F
0
1
0
1
1
1
1
1
$5F

An example of that is the game Multitron. It reads the port and compares against the resting state value it expects, if it does not see that, it assumes the user has pressed fire or moved the joystick and starts the game.

In the game, it tests for absolute values to see if you have moved left or pressed fire, all of which fail because bit 0 is now a 1 and on the developers machine it was a 0. Right is on a separate port, so reading that and comparing absolute values does work, so you can move right, but only right. In my testing notes, I had this down as a keyboard only game, as that is how it appeared both in the vice emulator and on a real VIC20.

They did have code in place to fix the datasette sense problem. When they read the value in, they OR it with $40. That forces bit 6 (datasette sense) to be a 1, so their comparisons become valid.

Fixing the IEC as well was just a case of changing that to OR the value read with $c3, this forces all the IEC and datasette bits high. I also had to adjust all the comparison values to expect the new resting state of $ff.

Rather unexpectedly, the game now has a title screen. This was never displayed before (or only for a blink of an eye) as the first time the game checked, it looked like fire was pressed, so started the game immediately.

It is quite a nice space invaders with a different mechanic for the invaders movements and limited firepower that takes a while to recharge, and plays much better with joystick.

I have fixed a couple of games like that, and a couple of others just needed the IEC port initialising correctly before starting the game.

Bandits, however, is proving to be more of a challenge. The test it is doing for fire is failing, so it is always firing. The other directions are fine, only fire is a problem.

The code is rather convoluted. I did go through it all in an excessively long Patreon post on the subject, but I decided to cut a lot of that out.

   lda $9111

   asl

   asl

   asl

   bcs label_b410

   and #$ef

label_b410

   asl $9120

   ror

   ora #$04

What it is doing is shifting bits around to rearrange what has been read from the port, and then doing comparisons on that.

The first two left shifts get rid of the IEC ATN and datasette sense signals, so that's good. It then reads the other port, extracts the joystick right and rolls that back into the first result, so they are all in the same byte. The OR at the end gets rid of the IEC clock signal.

For some reason, it tests the actual fire signal by shifting it out into the carry. If fire is pressed, it then sets one of the remaining bits low and uses that for future tests.

Unfortunately, that spare bit is the one used for the IEC data signal. It assumes that it will be high at the start, and sets it low if fire was pressed.

With an IEC device attached, that line ends up low, so the value ends up as a 0 whether fire is pressed or not, so the game thinks fire is pressed all the time.

The autofire can score quite well just sitting there, but ideally you would have control.

The code is quite tight, so there is no space to alter that in place to fix the issue. I did get it to work by changing the read port command to jump elsewhere, read the port, OR the data line high and return, but that adds quite a few cycles and I din't want to alter the timing in the middle of the game loop.

Checking out the signals on the scope, this is how Bandits starts up with no IEC device present. All the lines go low during reset, and then data (yellow, top) and ATN (blue, bottom) are released and float high. Clock (green, middle) stays low throughout.

However, if there is an IEC device present, the startup looks like this.

ATN goes back high, but for some reason, the data line does not. And that line reading 0 is what makes Bandits autofire.

VIC20 cartridges are started very early on in the boot process, so nothing is initialised. (frustratingly there is an entirely unnecessary jump to a routine a few bytes away and back that means the RAM which means the stack has to be working to do this, so VIC20 Dead Test cartridges need that RAM to be working before they can start)

Most cartridges just call the standard KERNAL routines to initialise the IO devices etc.

The normal start up those looks like this.

Do you see the little blip on the middle trace, the clock line? Let's zoom in.

It first releases the data line, then ATN, then releases the clock for 25uS and then goes back to the usual state of ATN and data high and clock low.

If there is an IEC device present, that will see the combination of ATN low and clock low as the start of a conversation, and will pull data low to acknowledge that it is ready to talk.

The normal VIC20 initialisation process pulses the clock line high to indicate to the IEC device that the conversation has ended, and as you can see, it releases the data line and we get back to normal.

Bandits is setting up various timers, so it makes sense to just initialise all the values in one go, just with the unfortunate effect of the IEC bus initialiation being skipped.

It initialises the port, releasing the data line but still driving the clock line low (like the normal VIC20 initialisation), and then releases ATN.

That leaves the "start of a conversation" signal in place, so the drive is duty bound to respond by pulling the data line low. And it stays low. There is no pulse on the clock line to tidy things up.

One of the odd things about Bandits, is that you can "fix" the problem by pressing the drive reset button. That snaps it out of this "waiting for a conversation" state and it releases the data line and the auto-firing stops.

There didn't seem an appropriate place I could add in the clock pulse that would clear things, so I went for the alternate option of changing the initialisation values so that it released the clock line and then releasing the ATN line. That means it should never enter the "start of a conversation" state, and so the drive should not assert the data line, and the guns will be silenced.

Hurrah.

All it took was changing two of the 65536 bits in the 8K ROM - it just took a while to work out which ones.

Can I take the rest of the week off now?


Advertisements

The patched versions of Bandits and Multitron are included in the new Penultimate +3 version which also has a built in SD2IEC drive.

More of those should be shipping this week, I will go into more detail in a full post to follow.


Minstrel 2 and 3 kits are available from my Tindie store, with worldwide shipping. Versions avaiable for ZX81 case or standalone with keyboard, and also Misntrel 3 with ZXpand microSD card interface (andn now available as PCB only).

I will slowly be moving things over there from my SellMyRetro store, so if there is anything that you want, let me know and I'll add it.

More info can be found here:

Bluesky

For those interested in such things, I can now be found on Bluesky, and I expect to be posting more there.

Patreon

You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. This particular post was originally about twice as long before I cut it down, so if you want all that detail, join the Patreon. This also includes access to my Patreon only Discord server for even more regular updates.