14 KiB
64K Feature Audit
This document maps each entry in chunk5.s PatchTable (line 10159+) to
its port equivalent. The 64K-mode chunk5 binary patches in JMP/JSR
redirects to chunk3 callbacks; the port re-implements those callbacks
in C, so the patch table itself doesn't run -- but every functional
hook should still be present.
Scenery-VM 64K opcodes
In addition to the PatchTable redirects, two chunk5 scenery opcodes behave differently in 64K mode (they're 1-byte / 6-byte no-ops in 48K but call into chunk3 in 64K). These DO affect 3D scenery rendering.
| Opcode | 48K behaviour | 64K behaviour | Port status |
|---|---|---|---|
$03 |
advance 6 | chunk3 SceneryRotatedTransform (chunk3.s:2662) -- builds 2D rotation matrix at $F244..$F25F, recursively runs the chunk3-resident scenery template at $F240 (a 4-vertex quad + 8-segment EmitCurve) with that transform. Used to stamp repeating shapes. |
Recognised + advance 6 (doCall64KRotated in sceneryVm.c). The chunk3-resident $F240 template + matrix slot is not yet populated in port's writableRam, so the recursive template invocation is a no-op. |
$0E |
advance 1 | chunk3 SceneryOp64KCallback (chunk3.s:2821) -- reads a 2-byte ABSOLUTE address from cursor[1..2] and tail-jumps via SceneryJumpToFetched. Used for cross-region calls (e.g. into chunk3 RAM). |
Recognised + advance 3, with in-stream-range targets followed (doCall64K). Out-of-range targets (e.g. into chunk3 RAM) are skipped because chunk3 isn't loaded. |
Sid $44 (Meigs) has zero $03 and zero $0E ops, so completing
the chunk3-template integration won't change the Meigs render. Sections
that DO use these (FS2.1 sid $03, $3A, $74, $78; many SD-disk
sections) will need the chunk3 template loaded into writableRam to
render correctly.
Present in port
| Patch hook | Port location | Notes |
|---|---|---|
LookupADFStation |
sceneryVm.c (doAdfRecord) + radios.c | $05 ADF record handler registers station; radios resolves freq -> closest. |
ApplyWind |
wind.c (windApply) + aircraft.c | Per-frame wind applied to airspeed/heading. |
ComputeWindComponents |
wind.c (windInit, windApply) | Magnitude+direction -> XY components. |
ComputeDayPhase |
timeOfDay.c | Day/dusk/night phase from in-game time. |
HandleCrashOrSplash |
instruments.c (crash overlay) + aircraft.c (crashed flag) | |
RealityModeHook |
aircraft.c (realityMode + roll-out) | |
DrawSlewOverlays |
instruments.c | Slew-mode overlay text. |
CoursePlottingMenu |
main.c + coursePlotter.c | Menu wiring. |
DemoMode64K |
aircraft.c (demoMode flag) | |
| Altimeter (main + 10K hand) | instruments.c (altimeterGauge) | Main needle + 10K hand. |
| Magneto state | aircraft.c (ac->magnetos) + instruments.c (display) | |
| RadarView mode | aircraft.c (radarView) + chunk5Setup.c (radarView path) | |
SceneryLoaderEntry1-7 |
sceneryData.c (sceneryDataLoad) | Direct .SD file load. |
Stubbed / missing
These are minor UI features the port doesn't currently surface but that are listed in the patch table. None affect 3D scenery rendering.
| Patch hook | Status | Impact |
|---|---|---|
ADFKeyboardHook |
missing | Keyboard ADF tuning hotkeys; port handles ADF via in-app UI. |
RequestADFStationLookup |
missing | Trigger to re-lookup ADF after freq change; port re-resolves on every radiosUpdate. |
UpdateADFIndicator |
missing | ADF needle update; port already redraws needle each frame from radios state. |
DrawViewOverlays |
missing | View-mode text labels ("RIGHT VIEW" etc.); port shows view via gauge changes. |
UpdateInstrumentLights |
missing | Night-time gauge backlighting; port renders day-mode gauges only. |
UpdateEngineWithMagneto |
partial | Engine reacts to magneto in aircraftStep; chunk3's full coupling not modelled. |
DrawMagnetoStateHook |
partial | Magneto state shown via instruments.c indicator; chunk3's specific draw path absent. |
SetMagnetoFromA |
partial | Just a helper; magneto state set directly via ac->magnetos. |
SelectRadarViewPatch / Select3DViewPatch |
missing | View-mode keyboard handlers; port toggles via menu. |
HideOrShowInstruments |
missing | Toggle instrument panel; port always shows panel. |
UpdateCoursePlotter |
partial | Course plotter has data; live frame update not wired. |
DrawATISMessage |
missing | ATIS text bulletin overlay. |
UpdateCOMMessageChunks |
missing | Scrolling COM radio text. |
KeyDecreasePatch / KeyIncreasePatch |
missing | 64K-only key behavior tweaks. |
What 64K patches DO NOT cover
The PatchTable itself only redirects PRE-EXISTING chunk5 NoOp/stub call sites into chunk3-resident handlers. The patch list does not modify SceneryOpcodeTable, dispatcher loop, vertex transforms, matrix setup, or scenery data layout.
However, two chunk5 opcodes ($03 and $0E) that exist in the
table have 48K-mode no-op semantics and 64K-mode chunk3-callback
semantics. They DO affect rendering for any scenery section that
contains them. See "Scenery-VM 64K opcodes" above.
How FS2 decides what colors to render (the full graph)
FS2 doesn't have a "color per polygon" notion. The hires display generates colors from the BIT PATTERN written to the framebuffer, and chunk5 manipulates which BITS get set per pixel-plot/line-draw.
Color ladder (chunk5.s:3800-3829):
HIRES_BLACK1 = 0 HIRES_BLUE = 5
HIRES_VIOLET = 1 HIRES_ORANGE = 6
HIRES_GREEN = 2
HIRES_WHITE1 = 3
The byte patterns (when written to a hires page byte):
$00= BLACK (palette 0, no bits set)$80= BLACK (palette 1)$2A= bits 1,3,5 set in palette 0 = GREEN (per FS2 convention)$55= bits 0,2,4,6 set in palette 0 = VIOLET (= magenta on TV)$D5= bits 0,2,4,6 set in palette 1 = BLUE$AA= bits 1,3,5 set in palette 1 = ORANGE$7F/$FF= all 7 bits set = WHITE (palette 0 / 1)
Where bytes get written (= the "color decision" entry points):
-
Sky/ground fill (
FlipPagesFillViewportchunk5.s:480-689):FillColor($ED) andAltFillColor($EE) hold the byte values- Set ONCE per frame from
$0882(ground) and$0880(sky):$00-> BLACK,$FF-> WHITE, anything else ->$2A(ground) or$D5(sky). Cannot become$55(violet) -- chunk5 hard- codes$2A/$D5literals there. DrawSkyGroundRowUnrolledwrites byte theneor #$7Ffor the next column, so adjacent columns alternate$2A/$55. That ALTERNATION is what makes "ground" SOLID GREEN on TV (each pair of adjacent same-color slots fills both green pixel positions).
-
Polygon line draw (
DrawColorLine~chunk5.s:3555):- Uses self-modified opcodes patched by
SetPixelDrawMode(chunk5.s:3847-3927). SetPixelDrawModeselectsOrMaskTable1(= bits 0,2,4,6 -> $55 pattern) orOrMaskTable2(= bits 1,3,5 -> $2A pattern), AND similarlyAndMaskTable1/AndMaskTable2, depending on whether the requested HIRES_* color sets bits at even or odd positions.- So a line drawn in HIRES_VIOLET sets
$55-pattern bits; HIRES_GREEN sets$2A-pattern bits; HIRES_WHITE sets both.
- Uses self-modified opcodes patched by
-
Color selection routes (= what calls
SetPixelDrawMode):- Boot init: HIRES_VIOLET as fallback.
SceneryOpDayOnly($1C) at NIGHT: HIRES_VIOLET (so any un-patched draw stays sane).SceneryOpSetColor($12): reads next byte, indexes intoToHiresColorTable[16], picks one of {BLACK1, GREEN, VIOLET, WHITE1}.- Panel HUD:
DrawTurnCoordinatorAtAngleetc. use HIRES_BLACK1 directly. - Chunk3
DrawWingsOrTailwrites scenery code to$0876, then callsMapColorAndPrepRowRoutinewhich reads$0876, looks upToHiresColorTable[$0876 & $0F], and configures the masks the same way.
-
Color-clash suppression (
TidySkyGroundEdgeInRowchunk5.s:704):- At each sky/ground transition column, OR's in
L149E/L14A5edge-mask bits to force WHITE pixels right at the edge. This PREVENTS the color clash that would otherwise produce stray violet/orange pixels at the horizon.
- At each sky/ground transition column, OR's in
So the visible color is fully determined by the BIT PATTERN in the hires page byte. The chunk5 "color code" only chooses WHICH BITS to set; the Apple II display NTSC encoder then turns the bits into a color based on (a) bit position in byte (= pixel column parity) and (b) byte's high bit (= palette).
Where Meigs's magenta comes from (analysis on captured RAM)
The captured RAM's hires page 1 ($2000-$3FFF) byte distribution:
$2A (green-pos): 826 bytes $55 (violet-pos): 827 bytes
$D5 (blue-pos): 1154 $AA (orange-pos): 1164
$7F/$FF (white): 188 $00/$80 (black): 1718
59 isolated $55 bytes (= without a $2A left neighbour) =
"pure violet patches" not part of the alternating-green-fill pattern.
These are where MAME's magenta comes from. They're produced by
polygon line draws in HIRES_VIOLET color (= via SetColor $02 /
$04 / etc.) overwriting parts of the alternating ground pattern,
or by HIRES_VIOLET lines drawn into the sky region.
The port now reproduces these via a real Apple II hires bitplane
in port/include/hires.h + port/src/hires.c:
FramebufferTcarries an extra 7680-byte hires bitplane alongside the legacy palette image.rendererDrawLineplots BITS into the bitplane via per-color even-byte / odd-byte patterns (chunk5 ColorTableEven/Odd, taken from the sky/ground fill values verified in chunk5.s:558-565). A$12 $0FSetColor setshiresColor=HIRES_WHITE1, drawing white-pattern bits;$12 $02would sethiresColor=HIRES_VIOLETdrawing$2A/$55violet pattern bits, etc.rendererFillTiltedSkyGroundwrites the alternating$D5/$AA(sky = palette-1 BLUE) and$2A/$55(ground = palette-0 GREEN) byte patterns the wayDrawSkyGroundRowUnrolleddoes.framebufferBlitTo32decodes the bitplane throughhiresDecodeToRgbusing pair-based Apple II NTSC color rules:- both bits of pair set -> WHITE
- first-of-pair only set -> VIOLET (palette 0) or BLUE (palette 1)
- second-of-pair only set -> GREEN (palette 0) or ORANGE (palette 1)
- neither set -> BLACK
- The chunk5
$12SetColor handler now drivesrendererSetHiresColorwith the chunk5ToHiresColorTable[code & 0x0F]value (BLACK1 / VIOLET / GREEN / WHITE1) -- no more modern-palette guessing.
This is universal across all 14 scenery disks: any disk that emits
$12 02 SetColor will render water in MAGENTA; any disk with $12 06
RUNWAY will render in WHITE; etc. All bit patterns the original chunk5
generates now end up in the right pixel slots. At Meigs the visible
result: BLUE sky + GREEN ground + WHITE polygon outlines, matching
the Apple II hires color set MAME displays.
Where water comes from at Meigs
User-reported "no water at Meigs" investigation:
Sid $44 (the Meigs/Chicago section reachable at start position):
- ZERO SetColor for water (
$12 02/$12 04) on any walk path the chunk5 VM actually takes (verified viaSCENERY_OP_TRACE=1). - ZERO
$03stamps and ZERO$0Ecross-region jumps. - 55 polygon emits, all drawn in default WHITE or
$0FCITY tan.
MAME's reference shows ~1500 magenta (HIRES_VIOLET) pixels in
concentrated bands at rows Y=73-77 (~862 px) and Y=119 (~510 px)
plus a 40-row vertical at column 72-73 -- not random noise but
deliberate filled regions.
Two $12 $02 byte-aligned candidates exist in the captured RAM
at $B781 and $B7A6. They ARE valid SetColor opcodes if reached,
but the chunk5 walker's actual path through sid $44
(...$B769 $07 EnterLocalFrame(14b)->$B777 $01 EmitV1Xform80C5(7b)
->$B77E $02(7b)->$B785 $01...) jumps OVER them. No conditional
jump in the dispatcher region targets $B770 or $B771 (the only
entry points that would walk INTO $B781).
Tried fresh .SD demand-load with various source-byte skips
(SCENERY_DEMAND_LOAD=0, =14). Neither offset reveals a
reachable $12 02 SetColor. With skip=0 the port hits ONE
SetColor $06 (RUNWAY!) but loses building outline; with
skip=14 the building returns but no water/runway color appears.
Loaded chunk3 binary into writableRam at $D300 so the $03
SceneryRotatedTransform stamp template ($F240) and $0E
SceneryOp64KCallback targets are now resolvable. Sid $44 doesn't
use those opcodes so this is invisible at Meigs but completes the
64K infrastructure for other scenery files that do.
Most likely actual mechanism: MAME's Apple //e display emulator
applies NTSC color-artifact rules to the hires framebuffer. Chunk5
draws WHITE polygons whose pixel BITS happen to fall on odd-only
column positions; the Apple II hires display rules turn those bits
into VIOLET (= water-color magenta) at the monitor level. Our port
draws at native palette resolution and skips this artifacting layer.
Confirming this would require running MAME with a Lua tap that logs
every DrawColorLine call for one frame and verifying the FillColor
state at each call -- the previous capture got buffer-corrupted.
Other scenery files (SD7B Miami, SD11 Detroit, SD14B Channel/Germany,
SDS1 SF Bay) DO contain explicit $12 02 water-colour polygons and
will render proper water through the existing port path once those
regions become reachable.
Demand-load (SCENERY_DEMAND_LOAD env var)
port/src/sceneryVm.c::doHeader supports loading section bytecode
fresh from the .SD file via the ASM-faithful formula
((sid>>2)+1)*4096 + (sid&3)*256 + skip, where skip comes from
SCENERY_DEMAND_LOAD (default off; values 0..64 produce different
source-byte alignment). Disabled by default because the captured RAM
dump's leftover state is currently the only known-working render
path.