teaser

Smoothmod for Apple II Prince of Persia

The original version of Prince of Persia for the Apple II suffers from slow downs and stuttering. With Smoothmod, described in this article, the game can be played smoothly on faster Apple IIs providing a pleasant gameplay experience of the legendary original.

Table of contents:

  • Introduction
  • Smoothmod
  • Salt
  • Support for Apple IIgs and other systems

Introduction

After a sluggish initial release, Jordan Mechner's Prince of Persia became a smash hit in the early 1990'ies and one of the most eagerly ported games of all time. There were ports for consoles, handhelds, 16 and 32 bit computers and to this day new ports are being made for fun and challenge. Most ports would keep faithful to the original gameplay and provide updated graphics and sound fitting the platform. Some ports would play differently and some would add completely new levels and challenges.

Prince of Persia for Apple II introduction screen The introduction screen of Prince of Persia for Apple II

As a big fan of the game and having played it on many platforms already, I wanted to visit the original to see where it all started and experience Jordan's original work. My initial reaction was that the graphics had a quirky charm and a surprisingly fluid animation. But I was disappointed by the jarring slow downs when the machine couldn't keep the frame rate up. Whenever anything happened in the game, like bricks wobbling and falling, the frame rate would drop significantly and gameplay would suffer.

Knowing that faster Apple II machines existed, I assumed those machines would be able to play the game without slowing down as much. To my surprise that was not the case. The Apple II GS with its 2.8 MHz (over the 1 MHz standard) runs the game almost entirely the same because the game simply slows down the CPU on this machine to support it. The Apple IIc Plus running at 4 MHz is not supported and would run the game way too fast making it unplayable.

The problem is that the game simply generates frames as fast as the CPU allows, resulting in an inconsistent frame rate. Smoothmod fixes this by introducing a fixed cycle time so faster CPUs can be used resulting in a smooth gameplay experience without slow downs.

Smoothmod

Smoothmod is a fork of the original source code amazingly released by Jordan Mechner years back, using build tools made by Adam Green that enables building on modern PCs. Also worth noting is Darren Stones fork with fixes, work to use the Merlin32 assembler and tools for building disk images on real hardware.

Check out the Smoothmod github page for source, build and play instructions.

Downloads

Features

Smoothmod currently only supports Apple IIc Plus.

  • The game update cycle is limited to a number of vertical blanks (frames).
  • For the Apple IIc Plus, a vertical blank counting interrupt handler is used for timing.
  • When sword fighting, the cycle time is slowed down by one frame. The same thing was done in the MS-DOS and possibly many other ports of the game. See discussion here. The slow down makes sense, as sword fighting involves a lot of extra CPU processing and ran slower than other game activities in the original. Bringing the game up to a consistent cycle speed would result in speeding up the sword fighting more. This feature mitigates that.
  • A new keystroke has been implemented: Ctrl-t to toggle "turbo mode" for faster game play:
    • Normal mode cycle time: 4 frames / 5 frames sword-fighting
    • Turbo mode cycle time: 3 frames / 4 frames sword-fighting
  • Ctrl-v shows game version including smoothmod version
  • Smoothmod speeds down the CPU to 1 MHz at cut scenes and when music plays, for correct playback.
  • Smoothmod does not attempt to autodetect the machine type. The project must be built specifically for different Apple IIs. This is done by setting the value of assembler macros: AppleIIcp, AppleIIgs ...
  • Smoothmod is intended to be run from 3.5 inch floppies and strips out most of the copy protection code to save space.
  • Smoothmod is built using a Docker image for a fully isolated and reproducible build process. The fork adds a Dockerfile and scripts to this end.

Salt

I'd like to share some of the pain and frustration I went through to develop this. I am familiar with the 6502 instruction set from working on the Commodore 64, but I had never dappled with the Apple II platform before. What I thought would be a rather simple mod turned out to be anything but easy. I felt the platform and the game shot down my hopes and efforts at every little step of the process.

While developing, I tested the game with MAME and must mention the great utility of the MAME debugger.

Here are some of the gotcha moments:

Memory architecture insanity

The original game already included a vblank interrupt handler that was used for vblank syncing on Apple IIc machines. But curiously the interrupt was turned off unless actually waiting for vblank. I was planning to simply let this interrupt run at all time and use it for the timing. But doing that would crash everything very fast. At this point I didn't understand the memory architecture of the Apple II and little did I know what a world of hurt I was in for. Apple IIs have multiple levels of memory bank switching including a concept that changes the entire memory space: the Main and Aux RAM banks. Yes, everything except IO registers would change switching those banks. The interrupt was only installed in one of the banks and things would crash if it fired with the other bank active.

In the end I found a way to make the interrupt work, as described in this code comment:

*-------------------------------
*
* smoothmod: Vertical blank ISR for IIcp
*
* A note about the interrupt handler implementation. PoP uses both Aux and Main 
* RAM banks available in higher memory Apple IIs. These RAM banks switch the 
* entire memory space and PoP may swap between them at any time making it
* challenging to install an interrupt handler that can fire at any time. The 
* solution used here is to install the handler in both RAM banks, at the same 
* address, running the same code that thus increments a counter in the currently
* active RAM bank. The correct vblank count has to be taken as the sum of these
* two counters. This is dealt with in WaitCycle.
*
* The address of the interrupt handler was chosen by observing memory usage 
* with a debugger and finding an unused spot in all banking configurations. 
* The address of the counters needed one more consideration. The game code 
* must be able to reach both counters to read and write them. The first two
* pages are best for that as they can be mapped Aux or Main RAM without 
* affecting the rest of the memory space (by ALTZPoff/on). A free spot somewhat 
* into the stack page was chosen for the counters.
*
*-------------------------------

CPU speed control

Now that I had the game running smoothly on the IIc Plus thanks to the vblank cycle timing, it was time to deal with the pops and scratchy sounds that was supposed to be music and the cut scene animations playing too fast. The issue was the increased CPU speed causing things to play too fast as everything depended on CPU cycle timing, which seems to be the norm for Apple II software. I looked into modifying the music routines and the animation timing, but quickly gave up on that idea. The only practical solution was to slow down the CPU whenever music played and that would also fix the cut scene timing.

I found an very useful post about controlling the IIc Plus CPU speed at blondihacks.com and adapted code from there. It worked but the graphics would sometimes glitch after changing the speed. This bug was tricky to find until I realized the accelerator control routines would change the value of address $0, in the zero page. It turns out memory address 0, yes where a NULL pointer points, is actually used for a variable in the game, the PAGE variable. The Apple II has two graphics pages allowing to show one while make changes to the other and the PAGE variable is used to indicate which to work on. Messing with that caused the glitch.

Crashes, glitches and crashes

Generally things often crashed when doing anything to the code. A common problem was that adding extra code to a module would overflow the space it had to fit in resulting in a very crashy build result. The assembly and link process doesn't generate any error messages when this happens!

Inspecting the generated .lst files can anticipate these problems and I realized I had to cut out stuff to save space. I decided to remove the copy protection routines, that are already not fully active in the 3.5 inch floppy version, to get things going.

Now, everything seemed to work and I thought I was done until I played the game a bit and reached level 7 where the player falls into the dungeon. Everything crashed horribly in an almost beautiful way. It turned out I had missed a trap related to the copy protection code that would purposefully crash the system at the beginning of level 7 if things weren't right.

Support for Apple IIgs and other systems

Smoothmod runs great on an emulated Apple IIc Plus at this point, but it's not a very common machine to come across as actual hardware. It would be great to support the Apple II GS and also other Apple IIs with CPU accelerators.

To support a machine a couple of things are needed:

  • A way to enable and disable the CPU acceleration (functions NORMSPEED and FASTSPEED in /01 POP Source/Source/GRAFIX.S)
  • A way to limit the game cycle speed to a number of vblanks (function WAITCYCLE in GRAFIX.S)
  • The functions CHECKIIGS and InitVBLANK, also in GRAFIX.S, can be used to initialize things
  • Note again that the project is built for specific platforms by setting assembler macro values. These macros must switch in the correct code for the targeted platform. From the start of GRAFIX.S:
; Smoothmod does not attempt to autodetect machine type.
; Program must be compiled for a specific machine set by flags:
; (NOTE these appear in multiple source files!)
AppleIIcp = 1
AppleIIgs = 0

Apple II GS

For the Apple II GS setting the CPU speed is easy, but it doesn't seem easy to set up a vblank interrupt. I imagine another approach could be to used, which is to monitor a frame counter present in the graphics system on that machine, as described here:

John Brooks, Aug 24, 2016, 6:44:38 AM

(...)

Video sync on the GS is easy due to:
c019: Bit 7 = 1 when not in VBL
C02E-C02F: Vertical scan counter & horizontal scan counter

-JB 

I hope you enjoyed this content!

ko-fi donate

Comments

Comments powered by Talkyard