Conversion review · PicoMite MMBasic · September 2026

Prince of Persia on the PicoComputer 3

What it would actually take to bring Jordan Mechner’s 1989 Apple II Prince of Persia to the PC3 in MMBasic, measured against the released source rather than guessed at: what the original is, what the PicoMite gives us, what has to be transcribed, what can be deleted, and the one constraint that is not technical at all.

Every number in this document was measured from the released repository — the 6502 sources, the sixteen image tables and the fifteen level files — not taken from secondary accounts.

The argument

The most comfortable frame budget of any port so far, attached to the largest body of code

Prince of Persia is not a performance problem. It is a transcription problem, and an unusually well-posed one, because what was released is the actual source — labelled, commented, and structured into named modules — not a reverse-engineered disassembly. Against that, it is the biggest body of game logic we have looked at: roughly 20,000 lines of 6502 across 29 files, of which about 12,000 lines are game behaviour that has to come across.

The decisive number is in the game’s own timekeeping. SPECIALK.S defines a game minute as 725 frames and its author annotated the constant with a note that the actual frame rate is approximately eleven a second. That is 83 milliseconds a frame. Thrust had 30 ms and Exile 40 ms. We have more than twice Exile’s budget for a game that, once the Apple II’s memory-banking and disk machinery is stripped out, does considerably less per frame than Exile does.

The renderer is cheap, because the PicoMite already has the primitive the original spends a third of its code emulating. Prince of Persia’s HIRES.S is 1,293 lines of shift tables, carry bytes, five opacity modes and a “peel” buffer, all of it there to move a masked bitmap across a screen whose bytes hold seven pixels apiece. BLIT FLASH with a transparent colour does the same job in one call, and the RAM image slots added in b11 hold every image the game owns with room to spare.

The risk is not the frame rate and not the memory. It is volume, and it is the two subsystems that were never released: the music, and the artwork’s provenance.

In one line

Technically the easiest of the four ports we have scoped, and by a wide margin the longest. Whether it can ever leave your desk is a separate question, answered in the next section, and it should be answered before any code is written.

Decide this first

The source is public. The game is not.

Mechner’s README is unusually direct about this, and it is worth taking at face value rather than treating it as boilerplate. He released the code as a historical artifact, says he has no objection to anyone studying, modifying or running it, and then states plainly that this grants no rights to Prince of Persia itself, and that Ubisoft alone has the right to make and distribute Prince of Persia games.

That is a materially different position from the ones behind the earlier ports. The Thrust and Exile disassemblies carried no licence at all, which is a grey area; this carries an explicit reservation, which is not. It also lands differently because of what the assets are.

When Arkanoid became Picanoid, the artwork could be replaced in an afternoon by a Python script, because the artwork was bricks. Prince of Persia’s identity is 290 rotoscoped character images traced from film of a real person running, jumping and falling. Re-drawing those as original work is not a scripting job; it is an animation project of its own, and a port with different animation is not recognisably the same game. The engine can be made original. The thing people would want to see cannot, cheaply.

Option A — Personal build Recommended

Build it, run it, keep it. The engineering record and the tooling live in the repo; the game itself is not published, not packaged for the A: drive, and not put in a release. This is what the released source explicitly permits and it costs nothing to honour.

Option B — Engine only

Ship the animation-sequence interpreter, the block and blueprint renderer and the level loader as a documented MMBasic platformer engine with original art and original levels. Genuinely useful to other PicoMite users, and it is the part of the work that is ours. The rotoscoped frames never ship.

Option C — Ask

Mechner has been generous with this material for over a decade and points technical and legal questions at the community rather than answering them himself. Ubisoft is the party that would have to say yes. Low odds, but the cost of asking is one email and it can run in parallel with Phase 0.

This is a judgement for you, not for me. It is placed first only because it changes the shape of the work: Option B implies building the engine against placeholder art from day one, which is a different Phase 1 from Option A.

Ground truth

What the PC3 gives us

ItemValueWhere it comes from
Frame budget83 ms at the original’s own rate, against 30 ms for Thrust and 40 ms for Exile725 frames per game minute, SPECIALK.S
ScreenMODE 2, 320×240, 16 colours. The game’s 280×192 sits inside it at 1:1 with a 20-pixel side border, no scaling and no resamplingEQ.S screen constants
Colours neededFour. Bit 7 is set on every one of the 95,869 image bytes, so the whole game is drawn in one Apple palette group: black, white, blue, orangeMeasured across all sixteen tables
Image slotsEight on the RP2350 — three in flash, five in PSRAM — at 144 KB each, so 1.1 MB of addressable artworkMAXIMAGESLOTS, added in 6.03.02b11
Artwork to store157 KB for one level’s working set, 273 KB with both facings pre-mirrored, 571 KB for every image in the game with both facingsMeasured, converted to RGB121 at two pixels a byte
Level data15 files of 2,304 bytes — 34,560 bytes for the entire game world, permanently residentMeasured; matches the EQ.S blueprint layout byte for byte
Sprite drawingBLIT FLASH takes a transparent colour but has no mirror; SPRITE SHOW mirrors in modes 4 to 7 but needs heap buffersgraphics/Blit.c, graphics/Sprite.c
Double bufferingFRAMEBUFFER CREATE F plus COPY F,N; MODE 2 with an F buffer is 76,800 B and fits the cut-down poolProven in Thrust, Exile, Arkanoid
Program memory144 KB tokenised on HDMIWEB. Thrust was 66 KB of source, Chuckie Egg 53 KBMAX_PROG_SIZE
InputKEYDOWN reports up to six held keys; the original needs three at once at mostEstablished
SoundPLAY SOUND and PLAY BBC SOUND on four channels; PLAY MODFILE for musicEstablished

Only one row of that table is tight, and it is the program memory. Twelve thousand lines of 6502 do not become twelve thousand lines of MMBasic — a great deal of it is register shuffling and bank switching that collapses to nothing — but this is the first port where the tokenised size cap is a live concern rather than a footnote. Every table stays out of the program text from the first line written, and the CSUB-in-the-library route is held in reserve.

The original

Six subsystems, and only four of them are ours to port

ModuleLinesWhat it isPort
FRAMEADV1,528Builds the draw lists for a frame and redraws only the blocks that changedPartly
CTRL1,440The player’s state machine: what a held key means given the current pose, floor and obstacleTranscribe
MOVER1,344The world’s moving parts — spikes, slicers, gates, loose floors, pressure plates, falling debrisTranscribe
HIRES1,293The bitmap blitter: shifts, carries, five opacity modes, background peel and restoreDelete
GRAFIX1,292The auxiliary-memory thunk layer — every cross-bank call in the game goes through itDelete
AUTO1,283The guard AI and the demo playback recorderTranscribe
CTRLSUBS1,283The helpers the control machine leans on: reach tests, ledge grabs, landing choicesTranscribe
SUBS1,230Level transitions, screen linking, timers, the mirror, the potionsTranscribe
SEQTABLE1,187114 named animation sequences written in a 15-opcode byte codeConvert
TOPCTRL1,068The main loop, level restart, the 60-minute clockTranscribe
COLL924Collision: character against block, character against character, sword reachTranscribe
SPECIALK898Keyboard and joystick, the sound queue, the game clock, the cheat keysPartly
MASTER824Disk loading, bank switching, the attract sequenceMostly delete
GAMEBG771Strength meters, messages, torches, the hourglass, the flasksTranscribe
MISC690Odds and ends, including the random number generatorTranscribe
UNPACK577The title-screen decompressor and fade routinesDelete
FRAMEDEF437The frame table: for each pose, its image, sword image, movement and collision boxConvert
Plus HRTABLES (215), SOUND (191), BOOT (155) and nine smaller equate and data files. Whole-repository total: 30,889 lines, 20,022 of them neither blank nor comment.

The animation system is the game

Everything that makes Prince of Persia feel like Prince of Persia lives in two tables and an interpreter, and all three are small. FRAMEDEF.S holds 415 frame definitions in five bytes each: the image to draw, which sword image goes with it, how far the character moves in x and y that frame, and a packed collision descriptor. SEQTABLE.S holds 114 named sequences — the run cycle, the hanging drop, the block-to-strike, the stair climb — written in a byte code of fifteen instructions that can jump, turn the character round, change floor level, alter x or y, set the action class, trigger a fall, test weightlessness, kill, and advance the level.

That is a virtual machine, and porting a virtual machine is the most reliable kind of transcription there is: the interpreter is perhaps 150 lines of MMBasic, and the sequences themselves are data that a Python script converts once and never touches again. Get the interpreter exactly right and every movement in the game is exactly right, including the ones nobody can describe in words.

The world is a blueprint, not a bitmap

A level is 24 screens of 30 blocks, ten across and three down. Each block carries a type from a list of 30 — floor, spikes, gate, loose floor, pressure plate, torch, mirror, exit, slicer — and a one-byte modifier. Alongside that sits a link table saying which screen lies left, right, above and below each screen, a 96-byte map, and 256 bytes of starting information: where the Kid begins, which way he faces, where the sword lies, and the block, facing, position and behaviour program for each of up to 24 guards.

The whole thing is 2,304 bytes, and the layout in the released equates accounts for all of it with nothing left over. That is a format you can trust without a single guess, which is not something we have had before. Across the fifteen levels only 28 of the 30 block types are ever used, and 42 guards are placed in the entire game.

A block draws in five pieces

The reason the original’s renderer is more complicated than it looks is that a block is not one image. It is up to five, drawn in a fixed order: a back section, a middle section, a front-of-middle section, a floor section, and a foreground piece that draws over the character. Each has its own image number, its own vertical offset, and in two cases its own mask. This is how a character walks behind a pillar and in front of the back wall on a machine with no sprite priority hardware, and it has to be reproduced exactly or the depth reads wrong. A block’s appearance also depends on the block to its left and the row below, which is why the draw loop carries the preceding block’s identity forward.

Evidence

The measurements that shape the port

957 images, 671,083 pixels

Sixteen image tables parse cleanly against the format documented in HIRES.S: a pointer table indexed from one, then each image as a width in bytes, a height in lines, and packed rows. No compression, no surprises. One level’s working set is 468 of them.

The artwork is effectively one bit a pixel

The Apple’s palette-select bit is set on 100.0% of image bytes in every table. There is no per-byte palette decision to carry across. A pixel is on or off, and its colour follows from its position: black, white, blue and orange, four of MODE 2’s sixteen.

Every character image faces left

The frame tables note it explicitly, and the right-facing half of the game is produced by a mirror bit in the opacity byte. BLIT FLASH has no mirror. Pre-mirroring the 290 character images at build time costs 117 KB and removes the problem entirely.

83 milliseconds a frame

725 frames to a game minute, sixty game minutes to a game: 43,500 frames, 12.08 a second. The Apple never locked to the display at all — the rate was whatever the redraw cost, and the clock was calibrated to the average.

One opponent at a time

The blueprint reserves 24 guard slots a level and the busiest level uses six, but the engine tracks a single opponent. The per-frame character load is the Kid, at most one opponent, and the occasional mouse or falling debris.

Twenty sound effects, sixteen tunes, none of them portable

The effects are speaker-toggle routines with hand-counted delays — they describe a waveform, not a note, and cannot be transcribed. The music player is in the support tree; the songs it plays are not in the repository at all.

Design

Architecture for the port

Redraw everything, every frame

The largest single file in the game exists to avoid work we no longer need to avoid. FRAMEADV.S maintains eight redraw buffers, tracks which of the 30 blocks changed, saves the background behind each character before drawing it and peels it off afterwards — all so the Apple did not have to repaint 8 KB at 1 MHz. With 83 ms and a framebuffer, the port clears the F buffer, walks the 30 blocks drawing their five sections in order, draws the characters, draws the foreground pieces over them, draws the meters, and copies. The peel machinery, the redraw buffers and the dirty-block bookkeeping all go.

What must survive from that file is the order, not the optimisation: the plane model of background, middle, objects, foreground and messages, and the rule that a block’s appearance depends on its neighbours. Those are correctness, not speed.

The cost is roughly 150 BLIT FLASH calls a frame for the background plus a handful for characters. Exile measured a full frame — one tilemap draw and sixty sprites — at about 5 ms, so this should land in the low tens of milliseconds against an 83 ms budget. That is an inference from a different workload, not a measurement, and it is the first thing Phase 0 should settle.

Twelve frames a second, exactly

At 378 MHz the PC3 runs 640×480 at 60 Hz, and presenting one frame in five gives exactly 12.000 fps against the original’s 12.083. A sixty-minute game would run about twenty-five seconds long, which nobody can perceive and which keeps the interpreter at its fastest clock. The alternative — 315 MHz for a 75 Hz display — divides to 12.5, which is further out and costs interpreter speed.

As with Thrust and Arkanoid, the per-frame constants are integers and must not be rescaled. The sequence byte code moves a character by whole pixels per frame; the timers count frames. Change the tick rate and every one of them is wrong together, which is the failure mode that is hardest to see because nothing looks broken — the game just plays wrong.

Artwork in PSRAM image slots

The b11 RAM image slots land exactly where this port needs them. Everything the game owns, both facings pre-mirrored, is 571 KB — four of the five PSRAM slots, leaving the three flash slots free and the library slot untouched. Nothing needs loading between levels, which deletes the whole of the original’s set-management logic and the disk track tables that feed it.

Slots are volatile, so the installer writes them once per run from a bitmap on the A: drive, the same pattern Arkanoid uses. A 320×240 image loads into a RAM slot in 28 ms, so a cold start costs well under a second.

One pixel grid, no scaling

280×192 centred in 320×240 is the authentic grid at 1:1. The 20-pixel side borders and 24-pixel top and bottom are roughly what an Apple II displayed into an overscanned television. Resist the temptation to stretch it: the artwork was drawn for pixels of this shape, and MODE 2 pixels on the PC3 are close enough to the Apple’s that the proportions come out right by doing nothing.

Pipeline

Getting the data across

Four converters, all offline Python, all validated before a line of game logic is written. This is the pattern that worked for Thrust, Exile and Arkanoid and there is no reason to depart from it.

1Images

Image tables to one RGB121 sheet per slot

Walk the pointer table, decode each image from its width and height header, expand the packed rows applying the Apple colour rule, emit both facings for character tables, and pack the lot into sheets with an index. The output is a bitmap per slot plus a BASIC include giving every image its origin, width and height.

Validation: render a known frame and compare against an emulator screenshot pixel for pixel, as the Exile sprite sheet was validated.

2Levels

Fifteen blueprints to one data file

The layout is fully documented in the equates and the files match it exactly. Emit all fifteen as a single file read with MEMORY INPUT into an integer array at startup — 34,560 bytes, trivial on a machine with 6 MB of PSRAM free.

3Frames

Frame definitions to arrays

415 rows of five bytes, plus the two alternate sets that re-point the same poses at the opponent’s image table. Straight array data. The packed collision descriptor should be unpacked into its fields once, at conversion time, rather than masked at runtime in the interpreter.

4Sequences

Byte code to byte code

The 114 sequences assemble into a byte stream with absolute addresses for the jumps. The converter resolves the labels to array indices and emits the same stream with the same semantics. The MMBasic interpreter for it is a SELECT CASE over fifteen opcodes.

This is the highest-value converter in the set and the one to write first, because the interpreter can be exercised against it long before anything is drawn.

Scope

What we delete, and what is simply missing

Deleted — about 4,400 lines of Apple II plumbing

Missing from the release — decide what to do about each

Worth keeping that you might not expect

The demo and attract playback in AUTO.S is a recorded input stream replayed through the normal control path. That is free once the control path works, and it doubles as the best regression test the port could have: record a run, replay it, and any divergence in the state machine shows up as the Kid walking into a wall.

Plan

Phases

0Spike

Prove the frame, settle the budget

Half a day. No game logic.

Convert one background set, load it into a PSRAM image slot, draw one static screen — 30 blocks, five sections each — into the F buffer and time it. That single number decides whether the renderer is a non-issue (expected) or needs a CSUB (not expected). It also confirms the 1:1 pixel grid looks right on a real display, and that the four-colour mapping reads correctly.

1Data

The four converters

Two to three days.

Images, levels, frame definitions, sequences. Each validated on its own terms: the image sheet against an emulator screenshot, the blueprint parse against the documented layout, the sequence stream against a hand-walk of two or three known animations. Nothing runs on the board yet except the image install.

2Engine

The sequence interpreter and the frame table

Two days.

Fifteen opcodes, the frame advance, the character record. Test harness: drive a character through every one of the 114 sequences with no world around him and check he ends where the tables say. This is the piece that must be exactly right, and it can be exactly right before anything else exists.

3World

Blueprint rendering and screen linking

Three days.

The five-section block decomposition, the neighbour dependencies, the foreground plane, screen transitions. At the end of this phase the game draws any screen of any level correctly and the Kid can be placed on it, standing still.

4Control

The player state machine

A week, and the real work.

CTRL and CTRLSUBS, 2,700 lines between them: what a key means given the pose, the floor test, the ledge grab with its lead time, the landing choice by fall distance, the bump, the stoop, the drink. Running, jumping and climbing all work at the end of this phase. Falling to your death works too.

5World logic

Traps, gates and the level machinery

Four days.

MOVER and SUBS: spikes, slicers, loose floors, pressure plates and the gates they drive, rubble, the exit, the mirror, the potions, the level timer and the level transitions.

6Guards

Sword fighting

Four days.

AUTO and the fighting half of COLL: the guard behaviour programs, the strike, block and advance sequences, the strength meters, and the grace periods and patience timers that make guards feel like opponents rather than obstacles.

7Finish

Sound, messages, the clock, the demo

Three days.

Twenty effects by ear, the message boxes, the hourglass and torches, the sixty-minute clock, and the recorded demo replayed as both attract mode and regression test.

Roughly four weeks of evenings, against Thrust’s seven phases and Exile’s larger plan. The estimate is honest about volume and optimistic about nothing except the renderer, which genuinely is easy here.

Risks

What could go wrong

RiskAssessmentMitigation
RightsHigh The one risk that cannot be engineered awaySettle the distribution question before Phase 1, not after Phase 7
Tokenised program sizeMedium Twelve thousand lines of source logic against a 144 KB capEvery table external from day one; the library CSUB route held in reserve; measure at the end of each phase rather than at the end of the project
The control state machineMedium 2,700 lines where a wrong threshold is invisible until it is unplayableThe recorded demo as a regression test: it diverges visibly the moment a threshold is wrong
Sound authenticityMedium Speaker-click routines re-created by earAccept it. Thrust and Arkanoid both ended here and both sound right
MusicMedium Not in the repository at allEither drop it, or recover it from a disk image as a separate exercise. Do not let it block the port
Renderer costLow 150 blits into an 83 ms budgetPhase 0 measures it in half a day
Artwork storageLow 571 KB against 720 KB of PSRAM slotsAlready fits with both facings pre-mirrored and nothing swapped
Data format guessworkLowThere is none. This is the actual source, and the formats are documented in it
Over to you

Decisions

Recommendations listed first
  1. D1 — Distribution. Personal build, unpublished. The alternative worth real thought is the engine-only release, which changes Phase 1. Everything else in this plan is unaffected either way.
  2. D2 — Tick rate. 12.000 fps, one present in five at 60 Hz, CPU at 378 MHz. Do not rescale any per-frame constant.
  3. D3 — Screen. 280×192 at 1:1, centred in MODE 2’s 320×240, four colours from the palette.
  4. D4 — Mirroring. Pre-mirror the character images at conversion time and draw everything with BLIT FLASH and a transparent colour. Do not use sprite buffers.
  5. D5 — Redraw. Full redraw into the F buffer every frame. Delete the peel and dirty-block machinery; keep the plane order and the block dependencies.
  6. D6 — Artwork residency. Everything in PSRAM image slots at startup, nothing swapped between levels.
  7. D7 — Music. Ship without it initially. Decide separately whether recovering the songs from a disk image is a project you want.
  8. D8 — Controls. KEYDOWN with the original’s key layout and a modern alternative; gamepad optional and later.
Sources

Where this came from