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

17 KiB
Raw Blame History

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 MainGameEntryLoadSceneryFile*LoadDispatcherPointerL6006 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).