# 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 ` (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.