fs2port/port/PORT_STATUS.md

6.9 KiB

FS2 C Port - Status vs Original

How the C port in port/ relates to the original Apple II FS2 (the disassembly in src/). Updated 2026-09-06.

What the port is

fs2port is a plain C translation of the original program. It keeps a 64K Apple II RAM image (fs2Ram in machine.c), loads the four game chunks and the loading panel into it at their original addresses, mounts the ProDOS disk image and reads the FS2.1 file's blocks with the same loader logic the game uses, and then runs C functions that are transliterations of the 6502 routines. Every routine reads and writes the same RAM cells as the original (zero page, the $08xx/$09xx/ $0Axx state, the hires pages, the scenery buffers), so the RAM image can be compared byte for byte against a 6502 run of the real binary.

There is no 6502 emulator in the game. The only emulator in the tree is port/tools/fs2trace.c, the offline oracle the regression compares against.

Verification

port/tools/verify/regress.sh boots both the oracle (fs2trace) and the port from the real boot entry, feeds each the same key script (one key per ProcessInputTick), snapshots the whole 64K RAM at every tick on both sides, and reports the first tick where any non-scratch byte differs (snapDiff.py; the scratch list is the handful of zero-page temporaries the original leaves in different states between routines). Silence means identical.

Script Ticks Covers
boot 30 colour prompt, first frames at Meigs
flight 1200 full throttle, rotation, climb, trim, flaps
modes 800 radar view and zoom, pause, war/bomb/guns keys, course plotter menu, magnetos, demo exit, mode library save/load
edit 400 the edit-mode overlay (text page editor), digits, next/previous field, slew keys
ww1 700 Europe 1917 via the editor, war declared, guns, bomb, radar/3D view with the WW1 overlay resident
radios 250 NAV1/NAV2/COM/ADF/transponder/altimeter/heading/VOR digit entry, fuel, carb heat, lights, magnetos, pause
slew 500 slew mode via the editor: pitch/yoke/throttle/reset/digits keys
reality 1000 reality mode via the editor, then a take-off
night 500 hour 22 via the environmental page (dotted night lines, lights), then a take-off
course 400 course plotter record / display / precision / off
logdisk 100 Ctrl+E scenery disk log overlay
demo 800 no keys: the demo mode after the prompt times out

All of these are byte-identical for their whole length as of 2026-09-06. Draw lists (every line, span and pixel with its coordinates) and the frame work counter $32/$33 were compared the same way while the pipeline was being brought up.

Source to port map

Original Port
chunk4 math (MultiplyAXByC2, DivideSigned16, MultiplyXY, ScaleC2ByAX, sin/cos) chunk4.c
chunk4 indicators, needles, pixel lists, messages, characters chunk4.c
chunk4 disk loader (FetchSectorFromDisk, ReadBlocks, screen-hole stash swaps) chunk4.c + machine.c (SmartPort ReadBlock)
chunk5 boot, main loop, keys, input tick, pause chunk5Main.c
chunk5 flight model, slew, envelope, engine chunk5Flight.c
chunk5 panel: headings, radios, VOR/DME/OMI, needles chunk5Panel.c
chunk5 page flip, sky/ground fill, artificial horizon, turn coordinator chunk5Screen.c
chunk5 view matrix (SetupViewProjection) chunk5Setup.c
chunk5 vertex transforms A/B chunk5Transform.c
chunk5 classify/clip/project/curve chunk5Vertex.c
chunk5 polygon clip passes and scan fill chunk5Polygon.c
chunk5 scenery interpreter (all opcodes) sceneryVm.c
chunk5 scenery file loading, dispatcher pointer, patch slots chunk5Loader.c
chunk5 hires kernels (PlotColorPixel, DrawColorLine, DrawColorSpan, pens) hires.c
chunk3 (64K hooks: ADF, reality mode, overlays, lights, engine, ATIS, war report, loader entries, rotated template) chunk3.c
chunk2 (course plotter, wind, altimeter 10K hand, demo mode) chunk2.c
FS2.1 overlay: edit mode / mode library (src/ovl25.s) ovlEdit.c
FS2.1 overlay: WW1 Ace (src/ovl2d.s) ovlWw1.c
FS2.1 overlay: Ctrl+E disk log (src/ovl42.s) ovlLogDisk.c
Apple II hardware: soft switches, keyboard, paddles, speaker, language card, text page machine.c, textScreen.c, main.c (SDL2 host)

Symbols for every label in the disassembly come from port/include/fs2Symbols.h, generated by port/tools/genSymbols.py from the ca65 debug file, so the C names the same addresses the source does.

Overlays

The four PatchSlot_* slots at $A7E8..$A7FF are rewritten by the scenery file: each LoadSceneryFile* call reads its first page over $A7E0, and the loaded page turns the slot into jmp <entry> (or rts). The edit-mode editor (6K, mostly 6502 code at $B04B..$BA16), the WW1 mode (3K, code at $AA03..$B1D6) and the Ctrl+E log page live in the FS2.1 file, not in the resident chunks. They are disassembled in src/ovl25.s, src/ovl2d.s, src/ovl42.s and src/ovl2b.s (the boot page), each routine labelled and commented, validated byte for byte by make validate, and translated in port/src/ovl*.c. The port walks the slot bytes the way the 6502 does (chunk5CallPatchSlot) and dispatches the overlay entry only when the expected code is resident in RAM.

Deliberate differences

  • Timing. ProcessInputTick is paced to eight per second of wall time; the original's rate depends on the frame's cycle count. The work counter $32/$33 that decides when a tick fires is charged with the same constants as the original, so the tick sequence is the same.
  • Reset key. The original's reset vector reloads the game from disk (LoadSceneryFile0 + InitFromReset). The port restarts by reloading the chunk images from orig/ and res/.
  • Failure paths. Where the original drops into the monitor or hangs (BRK pads, LoadDispatcherPointer hang, loader errors), the port prints a message and restarts.
  • Text font. The 40-column text page is rendered with a built-in 5x7 font that approximates the Apple II character ROM.
  • Sound. Speaker toggles are turned into clicks by the SDL host; the engine sound's pitch is not cycle-accurate.
  • Disk writes. The mode library lives in RAM ($D000..$D3CF) as in the original; the dead code in the editor that would write it to disk is not reachable in this build and is not implemented.

Not translated (unreachable in the shipped binary)

  • The in-place ATIS pipeline at $B148..$B337 and the WW1 helpers at $B000..$B04A of the chunk5 image are overwritten by scenery data on the first frame; the shipped code never runs them.
  • The residue at $ABCC..$ABEB (the file's bytes differ from the RAM image there) is skipped over by MainGameEntry.
  • The editor's disk load/save branches (cmp #$53 three times in a row) can never be taken.