fs2port/port/PORT_STATUS.md
2026-05-13 21:32:05 -05:00

338 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FS2 C Port — Status vs Original
Comparison of the original Apple II FS2 (disassembled in `src/chunk*.s`)
against the C port in `port/`. Generated 2026-05-06.
Legend: ✅ done · 🟡 partial / approximation · ❌ missing
---
## Flight model
| Feature | Original | Port |
|---|---|---|
| Position integrator (24-bit XYZ) | `IntegratePhysicsStep` | ✅ `aircraftStep` (Q16.16) |
| Pitch / bank / yaw rates | `UpdateAutoTrimAndYaw` etc | ✅ `stepFlight` |
| Auto-coordination (bank → yaw) | `ApplyAutoCoordination` | ✅ `stepFlight` |
| Wind / turbulence | chunk2 `ApplyWind` (64K) | ✅ `windCompute` / `windApply` |
| Stall detection & break | per-instrument check | 🟡 stalled flag, no spin |
| Spin recovery | implicit in stall handling | ❌ |
| High-G / VNE bleed | `CheckFlightEnvelope` | 🟡 `envelopeWarning` flag, no bleed |
| Flap/gear speed effects | `RefreshElevatorIndicator` | ❌ |
| Mixture too-lean = engine cut | implicit | ❌ |
| Carb heat icing | chunk5 `CarbHeat` | 🟡 audio penalty added, no icing model |
| Magneto on/off effect on engine | `UpdateEngineWithMagneto` | 🟡 audio penalty added (no full model) |
| Engine fault dispatch | `FailureProcTable` | ✅ `aircraftStep` reality dispatch |
| Engine knock audio | per-fault sound | ✅ `audioUpdate` wobble |
| Crash detection | `HandleCrashOrSplash` | ✅ `aircraftStep` ground/water |
| Splash detection (water) | `CheckSplash` | 🟡 land-only crash |
| Building / mountain crash | `crash_msg_table` | 🟡 type set but no scenery-aware test |
## Modes
| Feature | Original | Port |
|---|---|---|
| Free flight | default | ✅ |
| Slew mode | `SlewMode` (chunk5) | ✅ `aircraftToggleSlew` |
| Slew digit overlay | `DrawSlewOverlays` | ✅ `instruments.c` north/east/alt |
| Demo mode | `DemoMode64K` | 🟡 `autopilotDemo` (basic) |
| Demo waypoint sequence | per-mode auto-flight | ❌ |
| Edit mode | `EditModeFlag` | 🟡 toggleable, no state save/restore |
| Reality mode | `RealityMode` | ✅ instrument & engine failures |
| Radar view | `RadarView` | ✅ `worldRenderRadar` |
| WW1 ace combat | `WW1AceMode` | ✅ `ww1ace.c` |
| Course Plotter | chunk2 `CoursePlottingMenu` | ✅ `coursePlotter.c` (record/display) |
| Pause | `TogglePause` | ✅ P key |
| Boot DOS | `BootDOS` | ❌ (no DOS to boot) |
## Instruments
| Feature | Original | Port |
|---|---|---|
| Airspeed needle | `UpdateAirspeedIndicator` | ✅ `instruments.c::airspeedGauge` |
| Altimeter main hand | `UpdateAltimeterIndicator` | ✅ |
| Altimeter 10K hand | `UpdateAltimeter10K` (64K) | ✅ |
| Attitude indicator | tilted disc | ✅ `drawHorizonDisc` |
| Heading bug | `DrawHeading` | ✅ digit readout |
| Magnetic compass | `DrawMagCompass` | 🟡 digit only, no rotating compass card |
| Vertical speed | `UpdateVerticalSpeedIndicator` | ✅ |
| Turn coordinator | `UpdateTurnCoordinator` | ✅ |
| Slip/skid ball | `PLSlipSkidIndicator` | ✅ |
| Throttle position | `UpdateThrottleIndicator` | ❌ (no graphical needle) |
| Mixture position | `UpdateMixtureControlIndicator` | ❌ |
| Flap position | `UpdateFlapsIndicator` | ❌ |
| Trim position | implicit in auto-trim | ❌ (no key bindings) |
| Fuel tank L/R | `UpdateFuelTankGauges` | ❌ |
| Oil temp/pressure | `UpdateOilTempAndPressureGauges` | ❌ |
| RPM display | `DrawRPM` | ✅ digit |
| Magneto state visual | `DrawMagnetoState` | ✅ MAG OFF/L/R/START indicator |
| Carb heat state | switch position | ✅ "CARB HEAT" indicator |
| Lights state | switch position | ✅ "LIGHTS ON" indicator |
| Failure indicator (X over gauge) | `DrawX` per gauge | ✅ `drawFailX` |
| Stall warning | `STALL` text | ✅ |
| VNE warning | `VNE` text | ✅ |
## Radios / Navigation
| Feature | Original | Port |
|---|---|---|
| NAV1 frequency | `NAV1` ($08F7) | ✅ `radios.c` |
| NAV2 frequency | `NAV2` ($08F5) | ✅ |
| ADF frequency | `ADFFreq*` | ✅ |
| COM1 frequency | str_com1 | ✅ |
| Station database (deduped) | per-region | ✅ 695 entries from extractstations |
| BCD frequency increment | step keys | ✅ shift+digit / digit |
| BCD per-digit entry | `KeyDecreasePatch` | ❌ |
| OBS course knob | OBS-related | ✅ `,`/`.` keys |
| VOR CDI needle | `DrawVOR1IndicatorChanges` | ✅ |
| VOR TO/FROM flag | `msg_vor_flags` | ✅ "TO"/"FR"/"OFF" |
| ILS glide slope | not in original | ❌ |
| DME readout | `DrawATISMessage` ATIS bound | ✅ |
| ADF needle | `DrawADFPanel` | ✅ |
| ADF heading digits | `DrawADFHeadingDigits` | ✅ |
| ATIS message | `UpdateCOMMessageChunks` | 🟡 freq shown, no chunked text |
| Tune-to-nearest button | not in original | ✅ T key (port-only) |
## Scenery system
| Feature | Original | Port |
|---|---|---|
| Disk loader (`SceneryReadUntilC0`) | chunk3 `SceneryLoaderEntry1` | 🟡 RAM dump pre-load + .SD demand-load |
| Block-list indirection | chunk4 `ComputeBlockFromSector` | ✅ `doHeader` correct mapping |
| Nibble decode | `SceneryNibbleDecode` | ❌ (only used by Entry4 path) |
| HEADER opcode ($0D) | `SceneryOpHeader` + `LA63A` | ✅ `doHeader` (with cache) |
| L631D section base | `L631D` | ✅ `sceneryComputeBaseL631D` |
| EnterLocalFrame ($07) | `SceneryOpEnterLocalFrame` | 🟡 simplified passthrough |
| Vertex emit + transform ($00-$02, $40-$42) | `SceneryOpEmitV*` | ✅ |
| Cull ($20/$21/$22) | `SceneryOpCullIfOutside*` | ✅ `doCullN` |
| Cull by outcode list ($04) | `SceneryOpCullByOutcodeList` | 🟡 walks list, no actual cull |
| Jump-if-beyond-XY/XYZ ($13/$14) | `SceneryOpJumpIfBeyondXY*` | ✅ |
| REL_JUMP ($0B) | `SceneryOpJumpRelative` | ✅ |
| SUB_INVOKE ($18) / RETURN ($19) | `SceneryOpSubInvoke` | ✅ |
| RESET_STATE ($2F) | `SceneryOpResetState` | ✅ |
| MODE_WHITE ($1B) | line-kernel patch | ✅ semantic equivalent |
| DAY_ONLY ($1C) | line-kernel patch | ✅ skip-on-night flag |
| WriteWord ($1A) / StoreImmWord ($25) | self-mod patches | ✅ |
| Vertex-cache ops ($31/$32/$33/$35/$42) | cached vertex pool at $0140 | ✅ pool reads, no full $31/$42 transform |
| ADF/NAV/COM record ($05/$1D/$1E) | station records | ✅ |
| SET_COLOR ($12) | `SceneryOpSetColor` | ✅ |
| Polygon edge emit | `EmitClippedLine` | 🟡 line draw only, no polygon close |
| Polygon scanline fill | `DrawColorSpan` etc | 🟡 2D scanline edge-intercept (`rendererFillPolygon`); not chunk5's 3D-clipped scanline emitter |
| Polygon 4-pass 3D clipper | `PolygonScanFillSetup` + Top/Right/Bottom passes | ✅ `sceneryClipPolygon3D` (source-faithful Sutherland-Hodgman against Z-X / Z-Y / X+Z / Y+Z planes; ping-pongs PrimVerts↔SecVerts; produces expanded wedge polygons that span the frustum). `PORT_LEGACY_POLY_FILL=1` reverts to 2D-only fill. |
| Sky/ground tilted fill | `FlipPagesFillViewport` | ✅ `rendererFillTiltedSkyGround` |
| Frustum clipping (3D) | `ClipBothVerticesToFrustum` | ✅ outcode + perspective divide |
| Vertex pool / EmitPrimaryVertex | $0AB8 column array | 🟡 small pool, no polygon closure |
## Display
| Feature | Original | Port |
|---|---|---|
| 280×192 framebuffer | hires page 1/2 | ✅ |
| Page flip | `FlipPagesFillViewport` | 🟡 single buffer (no flip needed) |
| Color/B&W mode | `ColorModePatch` / `BWModePatch` | 🟡 always color |
| Dotted-pattern night | `SceneryOpDayOnly` etc | ✅ DAY_ONLY skip |
| Panel bitmap | hires loaded from disk | ✅ res/loading_panel.bin |
| Panel lights overlay (64K) | `UpdateInstrumentLights` | 🟡 lights state shown as text |
| Message text (`DrawMultiMessage`) | string blit | ✅ font.c |
| Crash message overlay | `crash_msg_table` | ✅ MOUNTAIN/BUILDING/SPLASH/CRASH |
| Wing/tail overlays in side views | `DrawWingsOrTailOverlays` | ❌ |
| Bomb sight | WW1 bombsight pixels | ✅ `ww1aceHudDraw` |
| Gunsight | WW1 only | ✅ `ww1aceHudDraw` |
## Audio
| Feature | Original | Port |
|---|---|---|
| Engine sound | speaker click | ✅ sawtooth + throttle modulation |
| Engine fault wobble | not present in original | ✅ phase-modulated wobble |
| Magneto-off engine cut | engine flag | ✅ amp = 0 when MAG OFF |
| Stall horn | beeper trill | ✅ 800 Hz square wave |
| Crash impact | speaker noise | ✅ noise burst |
| Gun fire (WW1) | not in original | ✅ rapid sawtooth burst |
| Bomb drop (WW1) | not in original | ✅ pitch sweep |
| Carb heat icing audio | not directly | 🟡 power penalty only |
| Wind hiss | not in original | ❌ |
## Input
| Feature | Original | Port |
|---|---|---|
| Yoke (arrows / WASD) | arrow + paddle | ✅ |
| Rudder | `/` and Ctrl | ✅ Q/E |
| Throttle | `[` `]` Ctrl+H | ✅ Up/PgUp/Dn/PgDn |
| Brake | space | ✅ space cuts throttle |
| Slew controls | 8/9, 0/-, ,/. , +/= | 🟡 W/A/S/D in slew mode |
| View directions (F1-F5) | 1-5 keys | ✅ F1-F5 |
| Magneto select | 1-3 keys | ✅ Shift+M cycles |
| Lights toggle | L key | ✅ L |
| Carb heat | H key | ✅ H |
| Pause | Ctrl-P | ✅ P |
| Edit mode | Ctrl+[ | ✅ F7 |
| Demo mode | Ctrl+D | ✅ F10 |
| Slew toggle | Ctrl+S | ✅ F12 |
| Reality mode | Ctrl+R | ✅ Tab |
| Radar view | F | ✅ ` (backquote) |
| Course plotter menu | Ctrl+C | ✅ C/V/B/N (record/precision/display/off) |
| Joystick | game port | ✅ SDL_Joystick |
## Persisted state
| Feature | Original | Port |
|---|---|---|
| Edit mode revert (instrument save buffer) | $FC00+ | ❌ |
| Saved instrument state for crash recovery | yes | ❌ |
## Subsystems addressed in latest pass
- ✅ **BCD per-digit frequency entry**: `radiosEnterDigit()` mirrors FS2
KeyDecreasePatch — shifts current freq left, drops the high digit,
appends new digit, snaps to 0.05 MHz step for NAV/COM.
- ✅ **Throttle/Mixture/Flaps/Trim/Fuel** bar indicators in
`instruments.c`; trim/flap/mix bound to keys.
- ✅ **Fuel gauges** (left/right): per-frame burn, alternating tanks.
- ✅ **Color/B&W mode toggle**: Ctrl+F2 flips `ac->monochrome`; viewport
switches to black backdrop.
- ✅ **State save/restore for edit mode**: snapshot on toggle in,
restore on toggle out (`editSavedState`).
- ✅ **L6BB0 axis permutations** for `$07` SceneryOpEnterLocalFrame:
variant 0 (×16 hi-byte), 2 (byte-swap), 4 (×16 lo-byte), 5/default
(passthrough).
- ✅ **Near-plane clip in vertex emit**: when one endpoint is in front
and one behind, interpolate to z=1 and draw the visible portion.
- ✅ **Audio fixed-point**: engine freq + amp now Q8.8, fault wobble
via `math6502Sin` Q1.15 phase.
## Visible scenery — UNBLOCKED 2026-05-06
After tracing the unit/sign mismatch:
1. `pipe.proj.camX/camZ` was being set to **metres** while the bytecode
stream encodes scenery units (= metres × 3). Fixed in
`sceneryAttachCamera` to scale by `AC_SCENERY_UNITS_PER_METRE` (= 3)
before storing.
2. `cameraGet2x3Matrix` was producing matRow2 with FS2's right-handed
Z (forward = +world-Z) but the bytecode expects FS2's left-handed
convention (Z increases southward). Negated `cam->rot[i][2]` in the
matrix output.
After both fixes: SD3 scenery actually renders. With aircraft at
metres `(-3500, 200)` (= scenery `(-10500, 600)`), section 2's HEADER
demand-loads the geometry, and 308 vertex emits produce visible lines
on screen. At yaw=64 (= 90°, looking east), 308 draws hit the
viewport showing a road + building at distance.
`port/screenshot_first_visible_scenery.png` and
`port/screenshot_yaw64.png` are saved milestones.
## City scenery (Chicago / LA / Seattle / NY) - 2026-05-06
The Apple II FS2 base disk really does ship Chicago + LA + Seattle +
NY scenery, not just WWI. The path to load each:
1. The boot's main-menu sequence (color/BW prompt, demo/regular
prompt, then a city/database menu at .po block 236) ends with
`JSR $8758`/`$875B`/`$875E`/`$8761` -- these are jump thunks to
`LoadSceneryFile1..4` at `$A674/$A67D/$A686/$A68F`.
2. Each `LoadSceneryFile*` reads the city's dispatcher into `LA7E0+`
(256 bytes-1.5 KB, depending on descriptor).
3. The first `MainLoop` iteration calls `LoadDispatcherPointer`
(`$A61B`) → `ProcessScenery` (via `$L6006`). This walks the
city's dispatcher and fires `$0D` HEADER opcodes that demand-load
the actual polygon data via SmartPort block reads.
`port/tools/fs2trace` now exposes `FS2TRACE_CITY=N` (1=Chicago,
2=LA, 3=Seattle, 4=NY) which runs `MainGameEntry` → `LoadSceneryFile*`
→ `LoadDispatcherPointer` → `L6006` so the resulting RAM dump has
both the city dispatcher (at `LA7E0`) and the demand-loaded polygons
baked in.
### Bug fixes that unblocked rendering
1. **Vertex op `$40/$41/$42` mismapping**: port had them as
draw/silent/draw, but chunk5's `SceneryOpcodeTable` says
`$40 = SceneryOpEmitV1Xform7EBC` (silent V1 emit),
`$41 = SceneryOpEmitV2Xform7EBC` (V2 emit + line draw v1->v2),
`$42 = SceneryOpRefreshCachedXform7EBC` (cache refresh, advance 1).
2. **`doEmitV2` drew prev-V2 → new-V2**; chunk5's `EmitClippedLine`
draws current-V1 → new-V2. Fixed.
3. **fs2trace block-list cap was 200 entries**; `ComputeBlockFromSector`
for higher-numbered sectors needs entries up to 256. Bumped.
### Current rendering status
| Region | Default `(X, Z)` | Result |
|-------------------------|------------------|----------------------------------|
| `SCENERY_FS2_1` | any | WWI training map (fixture-rendered) |
| `SCENERY_FS2_1_CHICAGO` | `(0, 1000)` | 957 vertex / 957 draws -- Sears Tower visible |
| `SCENERY_FS2_1_LA` | `(0, 1000)` | 905 vertex / 905 draws -- LA skyline visible |
| `SCENERY_FS2_1_SEATTLE` | n/a | dispatcher loaded; no section's cull passes at (0,0). Needs starting-position research |
| `SCENERY_FS2_1_NY` | n/a | same as Seattle |
`port/screenshots/chicago_marquee.png` is the canonical Chicago
shot showing the Sears Tower spire and downtown silhouette.
## Walk-all-paths mode (2026-05-06)
`SCENERY_WALK_ALL=1` makes the interpreter take BOTH branches at every
conditional opcode (`$13/$14` JumpIfBeyondXY, `$20/$21/$22` CullN,
`$04` CullByOutcodeList, `$1C` DAY_ONLY). The visited[] array bounds
the work to one visit per cursor position. With this on, every section
in the dispatcher's `$0D` HEADER chain fires, so any scenery the .SD
file holds for that region is demand-loaded into the working RAM.
For `SCENERY_FS2_1` this unfortunately doesn't conjure more polygons:
the FS2 base disk's scenery payload is read by chunk5's per-frame disk
loader during the initial main-loop iteration, not from a separate
flat `.SD` file. Cities in `chunk5 InitialZeroPageData` reference
positions outside the dispatcher's first-section bounds, but their
polygon geometry is brought in by extra block reads chunk5 issues
when the dispatcher's cull passes for that section -- a flow we don't
yet replicate offline. See `port/tools/fs2trace.c` `FS2TRACE_INIT_X/Z` for
the work-in-progress per-city RAM-dump capture path.
## Multi-region scenery (2026-05-06)
The FS2 base disk (`SCENERY_FS2_1`) ships only the WW1 ace training
field as renderable polygons; its dispatcher's section culls cover a
narrow ~0..500 unit envelope. The COM/NAV database lists US cities
(Chicago/Meigs at worldX=1548, NY/JFK at worldX=1196, LA/LAX at
worldX=599, Seattle/SEA at worldX=2912), but their *polygon* data lives
on the matching scenery disks:
| City | Scenery region |
|-------------|----------------|
| WW1 ace | `SCENERY_FS2_1` |
| LA / SF | `SCENERY_SD3` (renders at e.g. metres `(-3500, 200)` yaw 64) |
| Seattle | `SCENERY_SD4` |
| (Chicago / NY have no Apple II SubLOGIC scenery disk released) |
The `SCENERY_REGION` env var on `--screenshot` selects the region.
`SCENERY_FORCE_X` / `SCENERY_FORCE_Z` (in metres) teleport the
aircraft into a section. `port/tools/fs2trace` now accepts
`FS2TRACE_INIT_X` / `FS2TRACE_INIT_Z` (16-bit upper words of the 24-bit
zero-page scenery position) so a per-region RAM dump can be made for
sections outside the dispatcher's default cull window — useful for
forcing demand-loads in regions with multiple sub-sections.
## Still missing
- **Polygon scanline fill**: scenery emits line edges only. FS2's
scenery is mostly wireframe so this is mostly cosmetic, but
surface fills (water, runway) are unfilled.
- **Demo waypoint sequence**: chunk5/chunk2 `DemoMode64K` flies a
programmed circuit. Port's `autopilotDemo` is a simple altitude/
throttle hold.
- **Stuck-key magneto auto-alternation** — chunk5
`MagnetosLeft/Right` handle key-held edge cases.
- **ATIS chunked text scroll** — chunk5 `UpdateCOMMessageChunks`
cycles through airport names; port shows current frequency only.
- **Wing/tail/cowling overlays** in side/back/down views — chunk5
`DrawViewOverlays` / `DrawWingsOrTailOverlays`.
- **Day-side detailed runway striping** — chunk5
`DrawHorizonDisc` etc has runway-specific colour-only details.
- **Oil temp/pressure gauges** — chunk5
`UpdateOilTempAndPressureGauges`.
- **Section anchor / $07 EnterLocalFrame in real bytecode**: works
when the bytecode actually fires $07 (mostly doesn't in the streams
we walk), but full multi-section navigation may need additional
fixes around section-base init (e.g., reading anchor coords from
the loaded section's preamble).