257 lines
14 KiB
Markdown
257 lines
14 KiB
Markdown
# 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` | ok | `rendererDrawWingOverlay` paints wing-strut / fin / wheel-well shapes for non-forward views. |
|
|
| `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` | ok | `panelDigits.c` scrolls a synthesized "<station> WIND 270/12 ALT 30.05 RWY 18L TIME hh:00" through an 8-char window beside COM freq. |
|
|
| `UpdateCOMMessageChunks` | ok | Same scrolling-window implementation drives both. |
|
|
| `KeyDecreasePatch` / `KeyIncreasePatch` | ok | `radiosEnterDigit()` mirrors FS2's per-digit BCD entry; armed via Ctrl+1..4 for NAV1/NAV2/COM1/ADF. |
|
|
|
|
## 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):
|
|
|
|
1. **Sky/ground fill** (`FlipPagesFillViewport` chunk5.s:480-689):
|
|
- `FillColor` (`$ED`) and `AltFillColor` (`$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`/`$D5` literals there.
|
|
- `DrawSkyGroundRowUnrolled` writes byte then `eor #$7F` for 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).
|
|
|
|
2. **Polygon line draw** (`DrawColorLine` ~chunk5.s:3555):
|
|
- Uses self-modified opcodes patched by `SetPixelDrawMode`
|
|
(chunk5.s:3847-3927).
|
|
- `SetPixelDrawMode` selects `OrMaskTable1` (= bits 0,2,4,6 -> $55
|
|
pattern) or `OrMaskTable2` (= bits 1,3,5 -> $2A pattern), AND
|
|
similarly `AndMaskTable1`/`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.
|
|
|
|
3. **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 into
|
|
`ToHiresColorTable[16]`, picks one of {BLACK1, GREEN, VIOLET,
|
|
WHITE1}.
|
|
- Panel HUD: `DrawTurnCoordinatorAtAngle` etc. use HIRES_BLACK1
|
|
directly.
|
|
- Chunk3 `DrawWingsOrTail` writes scenery code to `$0876`, then
|
|
calls `MapColorAndPrepRowRoutine` which reads `$0876`, looks up
|
|
`ToHiresColorTable[$0876 & $0F]`, and configures the masks the
|
|
same way.
|
|
|
|
4. **Color-clash suppression** (`TidySkyGroundEdgeInRow` chunk5.s:704):
|
|
- At each sky/ground transition column, OR's in `L149E`/`L14A5`
|
|
edge-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.
|
|
|
|
**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`:
|
|
|
|
- `FramebufferT` carries an extra 7680-byte hires bitplane alongside
|
|
the legacy palette image.
|
|
- `rendererDrawLine` plots 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 $0F` SetColor sets `hiresColor=HIRES_WHITE1`, drawing
|
|
white-pattern bits; `$12 $02` would set `hiresColor=HIRES_VIOLET`
|
|
drawing `$2A`/`$55` violet pattern bits, etc.
|
|
- `rendererFillTiltedSkyGround` writes the alternating `$D5/$AA`
|
|
(sky = palette-1 BLUE) and `$2A/$55` (ground = palette-0 GREEN)
|
|
byte patterns the way `DrawSkyGroundRowUnrolled` does.
|
|
- `framebufferBlitTo32` decodes the bitplane through `hiresDecodeToRgb`
|
|
using 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 `$12` SetColor handler now drives `rendererSetHiresColor`
|
|
with the chunk5 `ToHiresColorTable[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 via `SCENERY_OP_TRACE=1`).
|
|
- ZERO `$03` stamps and ZERO `$0E` cross-region jumps.
|
|
- 55 polygon emits, all drawn in default WHITE or `$0F` CITY 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 (default-on)
|
|
|
|
`port/src/sceneryVm.c::doHeader` loads section bytecode fresh from the
|
|
`.SD` file every HEADER opcode via the ASM-faithful formula
|
|
`((sid>>2)+1)*4096 + (sid&3)*256 + skip`. The `SCENERY_DEMAND_LOAD` env
|
|
var now controls only an optional source-byte SKIP for offline
|
|
experiments (default 0 = natural ASM behaviour). All four FS2.1 region
|
|
variants share the same `FS2.1` .SD source via the `sdSourceFile` field
|
|
in `RegionMetaT` (`sceneryData.c`); when adding new SCENERY_* regions,
|
|
populate that field. Run with `SCENERY_DEMAND_TRACE=1` to log every
|
|
fired load.
|