How Prince of Persia Defeated Apple II's Memory Limitations [video]
youtube.com
youtube.com
Which links to the transcript: https://cdn.arstechnica.net/wp-content/uploads/2020/03/princ...
And his journals from the time, which are now being published: http://jordanmechner.com/journals
Kind of light on details, though :)
Along with technical documentation: http://jordanmechner.com/downloads/popsource.pdf
To refresh my own memory (which is more than 48K, but often foggy), I checked my journal. Part of the answer is in this entry from 11/20/1988:
"...Put in footsteps, and improved the chomping jaws. But the real breakthrough this week was invisible: I moved a bunch of stuff around so the main game code can use the auxiliary language card. Basically, I've just freed up an extra 12K. That gives me some breathing room I'll sorely need if I'm going to put in all this sword fighting."
And a few months later, on 2/16/89: "A lot of heavy work, most of it invisible, but which will make it much easier for me to implement the enemy guards in their flowing robes and turbans."
The journal doesn't give more technical detail than that (if I'd imagined anyone would be interested in this stuff 30 years later, I'd have written more!) but the code is on github, so anyone who's motivated enough should be able to reconstruct how the final code uses the auxiliary memory. In my recollection (foggy, as warned -- it's been a while), the "auxiliary language card" was an extra 16K of RAM that Apple had squeezed into the latest generation of Apple II computers, but that was difficult to use because it wasn't contiguous with the rest of RAM. I seem to recall it shared addresses already used by the main memory, so you had to flip between which part you were using. In November '88, seeing how much Shadowman and swordfighting and Shadowman improved the game motivated me to invest the time to rewrite/strategically relocate a bunch of existing code to free up 12K that I could use to implement enemies.
As for the multiple enemies, I'm pretty sure that each level contained exactly one enemy, whose shape table was packaged with the level load. The floppy disk access (very quick, thanks to Roland Gustafsson's magical 18-sector routines) when the level was loaded would swap out the previous level's enemy with the new one. That's why you'll notice that the levels containing the "special enemies" (skeleton, Jaffar, and the Fat Guard) had no normal guards in the rest of the level -- they couldn't.
Hope this helps. Thanks for your interest!
Thanks for taking time to share!
I don't have enough time on my hands to dig into the old source code (nor I am particularly interested in the Apple II), but I'm glad to know it's available, should I or some others be suddenly curious to dig in. I much prefer such code to be opened than to be lost to history!
So, it's a bit like register banks [1] or an MMU, where you have to page in and out part of the address space, I guess? I've seen some people [1] argue that we needed less powerful machines and to break backwards compatibility, for people to get creative again. Maybe there is some truth to that?
[1]: http://www.keil.com/support/man/docs/c51/c51_le_regbankspec....
[2]: https://fosdem.org/2020/schedule/event/generation_gaps/
It really seems to be a trend these days for new accounts to submit the YouTube videos that were embedded in previously popular HN articles as new HN articles. I've seen this several times in the last week or so.
That's a heck of a lot of space to suddenly find when adding a single extra character was seemingly untenable.
Now that I'm older, I really appreciate the relentlessness of the vision. "I'm just gonna do rotoscoping! I'm gonna film my karate teacher and my brother!". I have to say it's really rare these days in the tech industry to see this kind of insane vision. It's even more rare to see someone pull off a cohesive product out of it.
I wish the interviewer had asked for more elaboration on the techniques used. It went really quickly from:"I ran out every single byte of memory with this Prince guy, how do I create an antagonist? I know, palette shifting!" to "then I added sword fighting, and enemy guards". Still, really love to hear these tales of optimization.
And it is not even palette shifting. The Apple has absolute colors. He literally made another draw routine that performed the XOR operation.
For a modern day example of similar dedication, check this out:
https://m.facebook.com/LawlessLegends/
Python scripts create in line assembly for the ray caster, PLASMA byte code interpreter, big world, animations, and excellent use of the 6 colors and artifact art.
A game this big would have been epic in the day. The team has been grinding away at it for some years now.
It will end up being an exemplary example of an RPG, and on the Apple, that kind of game is in the sweet spot.
What Jordan did was not really considered possible. PoP amazed many people!
On pretty much every other computer, the same bit patterns resulted in the dame artifact colors, but the Apple had even and odd byte patterns, because the repeat took two bytes, not one like other machines.
On the other hand, having that high bit shift the pixel clock a little meant 6 colors, plus black and white.
Because of how artifacting works, that is actually enough to do anything. Until the PC and some machines like the color computer 3, artifact colors were limited. Red blue, kinds of things.
The secondary complication was the screen addressing, which cost a table lookup. Had it been linear, overall draw speed would have been a bit faster.
So many levels of owch! And punching through all that with an 8 bit 6502. It's really hard to appreciate how fucking hard any Apple ][ game that uses hires graphics had to work just to put a dot on the screen.
https://en.wikipedia.org/wiki/Composite_artifact_colors#Appl...
Even more amazing were the text/hires graphics adventure engines that stippled together different colors to get a wider palette. It was so hard for the poor 6502, that part of the visceral pleasure of the game was just sitting there watching it draw each scene.
And all the visible text (IN UPPER CASE OF COURSE) must fit in the characteristic four lines at the bottom. Since it drew the scene so slowly, there was enough time to read the text before it scrolled off the top. Talk about making lemonade out of lemons, due to technical limitations!
Hi-Res Adventure #4: Ulysses and the Golden Fleece for the Apple II
It felt so fucking amazing to finally have a computer with a normal 1 pixel : 1 byte memory mapped display.
But before that, I played around with the Sun CG1 graphics board (on a Sun 2: I mmap'ed /dev/cgone0, but maybe it was actually a cgtwo card, not sure), which was kinda but not really memory mapped.
http://www.sunhelp.org/faq/FrameBufferHistory.html
It had 8 bit color mapped pixels, and it had an implicit read position and write position, such that you could address a row and column of pixels at a time, one pair for reading and one for writing ("rop mode memory").
When you accessed a column of the row block of memory, that set which column you could address in the other column block of memory. When you accessed a row of the the column block of memory, that set which row you could address in the other row block of memory!
As it turned out, that just happened to be useful for drawing lines and filling trapazons and performing raster operations, but it made random access quite inefficient, and was a huge pain in the ass for anything else.
(Although nowhere near as painful as Apple ][ graphics, but a lot more expensive!)
I think the early X10 and X11 servers actually supported it (SunView [SunTools at the time] certainly did), but it was really really slow.
I learned later than John Gilmore had written weird union structs the .h file to describe the memory mapped registers, when he was at Sun in its early days:
https://donhopkins.com/home/archive/forth/cg/cg2reg.h
Here's some Forth and 68K code I wrote to play around with that card (notice how register offsets gr_x_select / gr_y_select, 68k code xsel / ysel, and Forth words lineaddr / coladdr work together -- yes Forth can mmap device registers, and that is RPN 68k assembly code, with opcodes at the end of the line!):
https://donhopkins.com/home/archive/forth/cg/cgone.f
: getfb
0 bytes/fb [""] /dev/cgone0 mapin >fb !
;
https://donhopkins.com/home/archive/forth/cg/cg.f code xsel ( x --- addr )
>fb l#) d0 long move
gr_x_select gr_update + l# d0 long add
d0 sp ) add
next
c;
code ysel ( y --- addr )
>fb l#) d0 long move
gr_y_select gr_update + l# d0 long add
d0 sp ) add
next
c;
https://donhopkins.com/home/archive/forth/cg/fline.fhttps://bitshifters.github.io/posts/prods/bs-pop-beeb.html
Click the "Emulate" button at the bottom to play in your browser.
At the title screen, hit the space bar to start the game.
After the game starts, hit Control+K to enable keyboard controls.
It uses the standard I,J,K,L + U,O layout for up, down, left, right and jumping. Note that you can't press two of these keys at the same time, but you can hold one of them down.
The Option/Alt key modifies the behavior of these keys, and you will use it often:
- Option+J/L: step slowly rather than run. Use this to safely walk up to the edge of a ledge.
- Option+I: Jump up and hang on to a ledge. Hold 'I' to climb up the ledge.
- Option+K: Drop down and hang on to a ledge. Release Option to let yourself drop.
Use 'U' and 'O' to leap left or right over a ledge. This can be done while running or from a standstill.
Use 'Option' to pick up objects and drink potions.
Use the space bar to see how much time you have left.
There is a lot of trial and error at first, as you discover what things can kill you. When you die you can back to the start of the current level. Some tips:
- You can only safely fall one story. Falling two stories will reduce your life. Falling more will kill you.
- You can safely step into spikes that have popped out of the ground. If you run into them you will die.
- You can jump straight up to see which floor and roof tiles are loose. Run over floor tiles that are loose. Jump up underneath a loose ceiling tile to reveal hidden areas.
I'd recommend dosbox for replaying prince of persia!
https://www.freegameempire.com/games/Prince-of-Persia/Run-in...
And then some extra memory was found so guards were added, and the ports had even less limitations and could reach a larger audience. But the original limitation inspired creativity that might otherwise never have been.
I remember playing the PC version of it. At some point, the port was probably easier to do than the original game.
Not so long ago I shared a small video of his young brother doing the jumps and walks that he uses to create the animations for his game. Awesome work, awesome video. Inspiring.
Torches on the wall. Super. Why didn't you use that surplus for, and I'm just spitballing here, say... combat?
Then there was the author's lack of desire to make another game focused upon combat.
Which is the beauty of the shadow character. Since it is based upon a computational trick, it added a minimum to the resources required. At the same time, it afforded a twist in the plot that allowed the author to accept and integrate combat into the game. It changed the nature of the combat and of the player's decisions, permitted the antagonist to exist long enough to have it's own story, and got rid of the cheap feeling of a clone army (i.e. reused sprites).
I really enjoyed this video explaining how he got around making the gameplay easier (not just 1 life) but also made it make sense to the story.
You have 3 lives... but 3 different lives:
'Nega Scott is a reference to the "Shadow Doppelganger" stock character present in many games and anime & manga.'
[1] http://popc64.blogspot.com/2011/10/part-one-why-hell-would-a...