fs2port/port/src/chunk3.c

1361 lines
52 KiB
C

// See chunk3.h.
#include <stdio.h>
#include "chunk3.h"
#include "chunk4.h"
#include "chunk5Main.h"
#include "chunk5Panel.h"
#include "chunk5Zp.h"
#include "fs2Symbols.h"
#include "hires.h"
#include "machine.h"
#include "sceneryVm.h"
#define ZP_A0 0xA0
#define ZP_B6 0xB6
#define ZP_BA 0xBA // L00BA
#define ZP_INSTR_FB 0xFB
#define ZP_INSTR_FC 0xFC
#define ZP_INSTR_FD 0xFD
#define ZP_ROT_ANGLE 0xF2
#define ZP_ROT_XDISP 0x3E
#define ZP_ROT_YDISP 0x9A
#define ZP_ROT_XCOS 0x18
#define ZP_ROT_XSIN 0x1B
#define ZP_ROT_YCOS 0x1E
#define ZP_ROT_SIN 0xA9
#define RAM_ADF_LOOKUP_GATE 0x08A8
#define RAM_ADF_LOOKUP_LATCH 0x08A9
#define RAM_ADF_ENGAGED 0x08F1
#define RAM_STATION_SLOT 0x08F9
#define RAM_PROBLEM_TIMER 0x08BC
#define RAM_DAMAGE 0x099E
#define RAM_DAMAGE_TARGET 0x09A0
#define RAM_WEAR 0x08AB
#define RAM_LIGHT_A 0x0917
#define RAM_LIGHT_SRC 0x089B
#define RAM_WATER_PROBE 0x2A36
#define RAM_ROTATED_FLAG 0x0877
#define RAM_TRAPEZOID_X 0xDFB0
#define RAM_TRAPEZOID_DX 0xDFB2
#define RAM_TRAPEZOID_W 0xDFB4
#define RAM_TRAPEZOID_DW 0xDFB6
#define RAM_TRAPEZOID_Y 0xDFB8
#define RAM_TRAPEZOID_H 0xDFB9
#define RAM_MIXTURE 0x0A49
#define RAM_MAG1_OK 0x0999
#define RAM_MAG2_OK 0x099A
#define RAM_TIME_ZONE 0x0854
#define RAM_CEILING 0x0856
#define RAM_INSTR_MASK_A 0x0977
#define RAM_INSTR_MASK_B 0x0978
#define RAM_INSTR_MASK_C 0x0979
#define RAM_INSTR_FLAG_B 0x0915
#define RAM_INSTR_FLAG_C 0x0916
#define RAM_XSTART 0xE676
#define RAM_YSTART 0xE678
#define RAM_YSIZE 0xE679
#define RAM_XSIZE 0xE67A
#define RAM_XCOORD 0xE67B
#define RAM_XCOUNT 0xE67D
#define RAM_ROT_TEMPLATE 0xF240
#define RAM_ROT_242 0xF242
#define RAM_ROT_244 0xF244
#define RAM_ROT_247 0xF247
#define RAM_ROT_249 0xF249
#define RAM_ROT_24C 0xF24C
#define RAM_ROT_24E 0xF24E
#define RAM_ROT_251 0xF251
#define RAM_ROT_253 0xF253
#define RAM_ROT_258 0xF258
#define RAM_ROT_25A 0xF25A
#define RAM_ROT_25C 0xF25C
#define RAM_ROT_25E 0xF25E
#define RAM_NIBBLE_DEST 0x3B60
#define RAM_NIBBLE_END_PAGE 0x3E
#define RAM_NIBBLE_CHECKSUM 0x3B5E
#define ADF_MODE_FIRST 0x0D
#define ADF_MODE_END 0x10
#define ADF_NEEDLE 4
#define ADF_INIT_ANGLE 0x0A
#define MIXTURE_MAX 8
#define PROBLEM_PERIOD 0x0A
#define PROBLEM_CHANCE_MAX 100
#define DAMAGE_STEP 2
#define WEAR_STEP 2
#define SPLASH_FRAMES 5
#define WATER_BYTE 0x55
#define CRASH_DELAY 0xFF
#define LIGHT_DIM_A 0x10
#define LIGHT_DIM_B 0x80
#define LIGHT_DIM_C 0x01
#define OVERLAY_ALT_PAGE 0x20
#define WING_COLOR_CODE 9
#define RPM_CURVE_MAX 0x1F
#define RPM_START_THRESHOLD 0x25
#define MAGNETO_START 4
#define MAGNETO_BOTH 3
#define SPINUP_DELAY 0x64
#define CARB_HEAT_BIAS 0xF8
#define SPINUP_BIAS 0xE0
#define MIXTURE_CLAMP 0x10
#define MAGNETO_DEGRADE 4
#define RPM_DRAG_MIN 0x0E
#define PROP_DRAG_SCALE 0x07D0
#define CHT_ON 6
#define CHT_FAULT 0x0A
#define CHT_STEP 0x0A
#define OIL_ON 6
#define OIL_FAULT 2
#define OIL_STEP 0x64
#define ATIS_ROW_STEP 7
#define ATIS_ROW_MAX 90
#define ATIS_COL_MAX 100
#define ATIS_COL_STEP 4
#define ATIS_START 3
#define ATIS_DELAY_INNER 0x78
#define SAVE_RECORD_LEN 6
#define SAVE_RECORDS 8
#define ROT_ANGLE_BIAS 0x41
#define ROT_RECORD_LEN 6
#define LOADER_STREAM_END 0x46
#define LOADER_BLOCK_BIAS 0x02
#define LOADER_INIT_OFFSET 0x20
#define LOADER_ROM_PAGE 0xC0
#define RAM_ENEMIES_SHOT_DOWN 0x0898
#define RAM_DAMAGE_BY_ENEMY 0x08A4
#define OVL_WW1_BOMB_HITS 0xA81B // in the WW1 overlay
#define OVL_WW1_ENEMY_STATE 0xA972
#define OVL_WW1_ENEMY_STRIDE 0x1C
#define WAR_REPORT_ENEMIES 6
#define DIGIT_ZERO 0x30
#define NIBBLE_MASK 0xAA
#define ADF_SCALE 0x58
typedef enum LoaderEntryE {
LOADER_ENTRY_READ_UNTIL_C0 = 1,
LOADER_ENTRY_READ_FIXED = 2,
LOADER_ENTRY_NO_OP = 3,
LOADER_ENTRY_READ_DECODED = 4,
LOADER_ENTRY_BUFFER1 = 5,
LOADER_ENTRY_INIT = 6,
LOADER_ENTRY_BUFFER2 = 7
} LoaderEntryE;
static void atisAdvance(void);
static void atisAdvanceCursor(void);
static void atisDrawChar(uint8_t c);
static void atisRun(void);
static uint8_t aTo2Digits(uint8_t value, uint8_t *outOnes);
static uint8_t axTo3Digits(uint16_t value, uint8_t *outTens, uint8_t *outOnes);
static uint16_t calcPixelAddrAndMask(uint8_t xLo, uint8_t y, uint8_t *outMask);
static void drawADFHeadingDigits(void);
static void drawWingsOrTail(uint8_t index);
static uint8_t erasePixelReturnIfSet(uint8_t xLo, uint8_t y);
static void hideOrShow8Instruments(uint16_t records);
static void hideOrShowInstrument(void);
static void initADFPanel(void);
static void loaderExtension(void);
static uint8_t loadPixelState(void);
static void maybeXORPixel(uint8_t toggle, uint8_t xLo, uint8_t y);
static void negate16To(uint16_t dst, uint16_t value);
static void populateA5ThruA8(void);
static void realityModeOff(void);
static void savePixelState(uint8_t wasSet);
static bool sceneryComputeBlockOffset(void);
static bool sceneryEnsureOpen(void);
static void sceneryLoaderInit(void);
static void sceneryNibbleDecode(void);
static bool sceneryReadDecoded(void);
static bool sceneryReadFixed(void);
static bool sceneryReadUntilC0(void);
static void sceneryReopen(void);
static void setMagnetoState(uint8_t state);
static void stopEngine(void);
static void storeADFDigitAndRedraw(uint8_t digit, uint8_t slot);
static void triggerAircraftProblem(void);
static void atisAdvance(void) {
fs2Ram[ZP_B8 + 1] = (uint8_t)(fs2Ram[ZP_B8 + 1] + ATIS_COL_STEP);
}
static void atisAdvanceCursor(void) {
if (fs2Ram[ZP_B8 + 1] < ATIS_COL_MAX) {
atisAdvance();
return;
}
uint8_t row = (uint8_t)(fs2Ram[ZP_B8] + ATIS_ROW_STEP + 1);
if (row >= ATIS_ROW_MAX) {
chunk4ClearViewportsToBlack();
row = ATIS_START;
}
fs2Ram[ZP_B8] = row;
fs2Ram[ZP_B8 + 1] = ATIS_START;
}
// DoChar: draw one character with the inter-character delay.
static void atisDrawChar(uint8_t c) {
fs2Ram[ZP_B8 + 2] = c;
chunk4DrawMessageWhite(ZP_B8);
chunk3Delay((uint8_t)(0u - fs2Ram[SYM_ATISPacing]));
atisAdvance();
}
// IncPtr / IncPtr_TestChar: type the message at ($B6), expanding
// chunk references (bytes >= $80) with a space either side.
static void atisRun(void) {
for (;;) {
uint16_t ptr = ramRead16(ZP_B6);
uint8_t c = fs2Ram[ptr];
ramWrite16(ZP_B6, (uint16_t)(ptr + 1));
if (c == 0) {
return;
}
if (c & 0x80) {
uint16_t saved = ramRead16(ZP_B6);
uint16_t chunk = (uint16_t)(SYM_MessageChunks + fs2Ram[(uint16_t)(SYM_ChunkOffsetTable + (c - 0x80))]);
ramWrite16(ZP_B6, chunk);
atisAdvanceCursor();
atisRun();
atisAdvanceCursor();
ramWrite16(ZP_B6, saved);
continue;
}
if (c == ' ') {
atisAdvanceCursor();
continue;
}
atisDrawChar(c);
}
}
// ATo2Digits: value as tens/ones characters (Y is discarded).
static uint8_t aTo2Digits(uint8_t value, uint8_t *outOnes) {
uint8_t tens;
uint8_t ones;
axTo3Digits(value, &tens, &ones);
*outOnes = ones;
return tens;
}
// AXTo3Digits: Y = hundreds, A = tens, X = ones through TmpStr.
static uint8_t axTo3Digits(uint16_t value, uint8_t *outTens, uint8_t *outOnes) {
ramWrite16(SYM_ValueForString, value);
chunk5Set3DigitString(SYM_TmpStr);
*outTens = fs2Ram[SYM_TmpStr + 1];
*outOnes = fs2Ram[SYM_TmpStr + 2];
return fs2Ram[SYM_TmpStr];
}
// CalcPixelAddrAndMask: byte pointer ($B8) and bit mask for a pixel.
static uint16_t calcPixelAddrAndMask(uint8_t xLo, uint8_t y, uint8_t *outMask) {
uint16_t row = hiresRowAddress(y);
uint8_t hi = fs2Ram[ZP_A5];
uint8_t off = fs2Ram[(uint16_t)(SYM_HiresPixelToByteTable + (hi ? 256 : 0) + xLo)];
uint16_t addr = (uint16_t)(row + off);
ramWrite16(ZP_B8, addr);
*outMask = fs2Ram[(uint16_t)(SYM_HiresPixelToBitMaskTable + (hi ? 242 : 0) + xLo)];
return addr;
}
void chunk3ADFKeyboardHook(void) {
uint8_t mode = fs2Ram[SYM_InputMode];
if (fs2Ram[RAM_ADF_ENGAGED] != 0 && mode >= ADF_MODE_FIRST && mode < ADF_MODE_END) {
chunk5SetInputModeAndCounter((uint8_t)(mode + 1));
return;
}
chunk5SetInputModeAndCounter(ADF_MODE_FIRST);
}
// ComputeDayPhase: the minutes compare against row 0, the hours
// against the season's row (the original indexes only the hour bytes).
void chunk3ComputeDayPhase(void) {
uint8_t x = (uint8_t)((fs2Ram[SYM_Season] & 0x03) << 3);
uint8_t minutes = fs2Ram[SYM_Minutes];
uint8_t hours = fs2Ram[SYM_Hours];
uint8_t phase;
uint16_t t;
t = (uint16_t)((hours << 8) | minutes) - (uint16_t)((fs2Ram[(uint16_t)(SYM_DayPhaseTable + 1 + x)] << 8) | fs2Ram[SYM_DayPhaseTable]);
if (t & 0x8000) {
phase = 4;
} else {
t = (uint16_t)((hours << 8) | minutes) - (uint16_t)((fs2Ram[(uint16_t)(SYM_DayPhaseTable + 3 + x)] << 8) | fs2Ram[SYM_DayPhaseTable + 2]);
if (t & 0x8000) {
phase = 2;
} else {
t = (uint16_t)((hours << 8) | minutes) - (uint16_t)((fs2Ram[(uint16_t)(SYM_DayPhaseTable + 5 + x)] << 8) | fs2Ram[SYM_DayPhaseTable + 4]);
if (t & 0x8000) {
phase = 1;
} else {
t = (uint16_t)((hours << 8) | minutes) - (uint16_t)((fs2Ram[(uint16_t)(SYM_DayPhaseTable + 7 + x)] << 8) | fs2Ram[SYM_DayPhaseTable + 6]);
phase = (t & 0x8000) ? 2 : 4;
}
}
}
fs2Ram[RAM_DAY_PHASE] = phase;
uint8_t scale = 1;
uint8_t n = fs2Ram[SYM_Season];
do {
fs2Ram[RAM_SEASON_SCALE] = scale;
scale = (uint8_t)(scale << 1);
n--;
} while (n != 0);
}
void chunk3Delay(uint8_t count) {
// 255 inner iterations of about 16 cycles per outer step.
uint32_t steps = (count == 0) ? 256u : count;
machineDelayMicroseconds(steps * 255u * 16u);
}
void chunk3DrawATISMessage(void) {
if (fs2Ram[RAM_ATIS_BUSY] != 0) {
return;
}
fs2Ram[RAM_ATIS_BUSY]++;
fs2Ram[ZP_B8 + 3] = 0;
fs2Ram[ZP_B8] = ATIS_START;
fs2Ram[ZP_B8 + 1] = ATIS_START;
chunk4ClearViewportsToBlack();
ramWrite16(ZP_B6, ramRead16(RAM_ATIS_NAME_PTR));
atisRun();
}
void chunk3DrawMagnetoStateHook(uint16_t lightsMsg) {
chunk4DrawMessageWhite(lightsMsg);
uint8_t x = (uint8_t)(fs2Ram[SYM_MagnetoState] << 1);
chunk4DrawMessageWhite(ramRead16((uint16_t)(SYM_MagnetoStateMessageTable + x)));
}
void chunk3DrawSlewOverlays(void) {
if ((fs2Ram[SYM_ShowSlewDigits] & 0x01) == 0) {
return;
}
ramWrite16(ZP_B6, ramRead16(SYM_NorthPosition));
chunk4DrawMessage(SYM_msg_north);
ramWrite16(ZP_B6, ramRead16(SYM_EastPosition));
chunk4DrawMessage(SYM_msg_east);
}
void chunk3DrawViewOverlays(void) {
uint8_t x;
if (fs2Ram[SYM_RadarView] != 0) {
x = (uint8_t)(SYM_AirplaneOverlayPixels - SYM_ImageOverlays);
} else {
uint8_t dir = fs2Ram[SYM_ViewDirection];
if ((dir & 0x80) == 0) {
drawWingsOrTail((uint8_t)(dir << 2));
return;
}
if (fs2Ram[SYM_WW1AceMode] == 0) {
drawWingsOrTail((uint8_t)(0x10 << 2));
return;
}
x = (uint8_t)(SYM_BombSightOverlayPixels - SYM_ImageOverlays);
}
for (;;) {
uint8_t lo = fs2Ram[(uint16_t)(SYM_ImageOverlays + x)];
x++;
uint8_t hi = fs2Ram[(uint16_t)(SYM_ImageOverlays + x)];
if (hi == 0) {
return;
}
x++;
uint8_t mask = fs2Ram[(uint16_t)(SYM_ImageOverlays + x)];
x++;
uint16_t addr = (uint16_t)(lo | ((uint16_t)hi << 8));
fs2Ram[addr] |= mask;
addr = (uint16_t)(lo | ((uint16_t)(uint8_t)(hi + OVERLAY_ALT_PAGE) << 8));
fs2Ram[addr] |= mask;
}
}
uint8_t chunk3HandleCrashOrSplash(uint8_t x) {
uint8_t code = fs2Ram[RAM_CRASH_CODE];
uint16_t msg;
if (code != 0) {
msg = ramRead16((uint16_t)(SYM_crash_msg_table + (code & 0x06)));
fs2Ram[RAM_CRASH_CODE] = 0;
} else {
if (fs2Ram[SYM_OnGroundFlag] == 0) {
return x;
}
// CheckSplash: the screen byte under the aircraft is water.
bool splash = (fs2Ram[RAM_WATER_PROBE] == WATER_BYTE) && (fs2Ram[RAM_DAY_PHASE] & 0x01) != 0 && (fs2Ram[SYM_SlewMode] | fs2Ram[SYM_RadarView] | fs2Ram[SYM_ViewDirection]) == 0;
if (!splash) {
fs2Ram[SYM_SplashCounter] = 0;
return x;
}
fs2Ram[SYM_SplashCounter]++;
if (fs2Ram[SYM_SplashCounter] != SPLASH_FRAMES) {
return x;
}
msg = SYM_msg_splash;
}
// DrawCrashAndAbort.
chunk4DrawMessageWhite(msg);
chunk3Delay(CRASH_DELAY);
chunk5SetModeLibraryAction(1);
return 1;
}
void chunk3HideOrShowInstruments(void) {
uint8_t a = (uint8_t)(fs2Ram[RAM_INSTR_MASK_A] & fs2Ram[SYM_InstrumentOperationalFlags] & fs2Ram[RAM_LIGHT_A]);
if (a != fs2Ram[ZP_INSTR_FB]) {
fs2Ram[0x9E] = a;
fs2Ram[ZP_A0] = fs2Ram[ZP_INSTR_FB];
fs2Ram[ZP_INSTR_FB] = a;
hideOrShow8Instruments(SYM_InstrumentSaveRecords_Group1);
}
a = (uint8_t)(fs2Ram[RAM_INSTR_MASK_B] & fs2Ram[RAM_INSTR_FLAG_B] & fs2Ram[RAM_LIGHT_A + 1]);
if (a != fs2Ram[ZP_INSTR_FC]) {
fs2Ram[0x9E] = a;
fs2Ram[ZP_A0] = fs2Ram[ZP_INSTR_FC];
fs2Ram[ZP_INSTR_FC] = a;
hideOrShow8Instruments(SYM_InstrumentSaveRecords_Group2);
}
a = (uint8_t)(fs2Ram[RAM_INSTR_MASK_C] & fs2Ram[RAM_INSTR_FLAG_C] & fs2Ram[RAM_LIGHT_A + 2]);
if (a != fs2Ram[ZP_INSTR_FD]) {
fs2Ram[0x9E] = a;
fs2Ram[ZP_A0] = fs2Ram[ZP_INSTR_FD];
fs2Ram[ZP_INSTR_FD] = a;
hideOrShow8Instruments(SYM_InstrumentSaveRecords_Group3);
}
}
uint8_t chunk3KeyDecreasePatch(void) {
if (fs2Ram[SYM_ADFMode] != 0) {
uint8_t mode = fs2Ram[SYM_InputMode];
if (mode >= ADF_MODE_FIRST && mode < ADF_MODE_END) {
uint8_t slot = (uint8_t)(mode - ADF_MODE_FIRST);
uint8_t digit = fs2Ram[(uint16_t)(SYM_str_adf_frequency + 1 + slot)];
if (digit == '0') {
digit = '9' + 1;
}
storeADFDigitAndRedraw((uint8_t)(digit - 1), slot);
}
}
if (fs2Ram[SYM_InputMode] == INPUT_MODE_MAGNETO) {
uint8_t x = (uint8_t)(fs2Ram[RAM_MIXTURE] + 1);
if (x != MIXTURE_MAX) {
chunk4UpdateMixtureControlIndicator(x);
}
}
return fs2Ram[SYM_InputMode];
}
uint8_t chunk3KeyIncreasePatch(void) {
if (fs2Ram[SYM_ADFMode] != 0) {
uint8_t mode = fs2Ram[SYM_InputMode];
if (mode >= ADF_MODE_FIRST && mode < ADF_MODE_END) {
uint8_t slot = (uint8_t)(mode - ADF_MODE_FIRST);
uint8_t digit = fs2Ram[(uint16_t)(SYM_str_adf_frequency + 1 + slot)];
if (digit == '9') {
digit = '0' - 1;
}
storeADFDigitAndRedraw((uint8_t)(digit + 1), slot);
}
}
if (fs2Ram[SYM_InputMode] == INPUT_MODE_MAGNETO) {
uint8_t x = (uint8_t)(fs2Ram[RAM_MIXTURE] - 1);
if ((x & 0x80) == 0) {
chunk4UpdateMixtureControlIndicator(x);
}
}
return fs2Ram[SYM_InputMode];
}
void chunk3LookupADFStation(void) {
if (fs2Ram[RAM_ADF_LOOKUP_LATCH] == 0) {
return;
}
fs2Ram[RAM_ADF_LOOKUP_GATE] = 0;
uint16_t cur = ramRead16(ZP_STREAM_PTR);
if (fs2Ram[SYM_ADFFreqLowPacked] != fs2Ram[(uint16_t)(cur + 1)]) {
return;
}
if (fs2Ram[SYM_ADFFreqHighDigit] != fs2Ram[(uint16_t)(cur + 2)]) {
return;
}
fs2Ram[RAM_STATION_SLOT] = 0;
fs2Ram[RAM_STATION_SLOT + 4] = 0;
fs2Ram[RAM_STATION_SLOT + 1] = fs2Ram[(uint16_t)(cur + 3)];
fs2Ram[RAM_STATION_SLOT + 2] = fs2Ram[(uint16_t)(cur + 4)];
fs2Ram[RAM_STATION_SLOT + 3] = fs2Ram[(uint16_t)(cur + 5)];
fs2Ram[RAM_STATION_SLOT + 5] = fs2Ram[(uint16_t)(cur + 6)];
fs2Ram[RAM_STATION_SLOT + 6] = fs2Ram[(uint16_t)(cur + 7)];
fs2Ram[RAM_STATION_SLOT + 7] = fs2Ram[(uint16_t)(cur + 8)];
fs2Ram[SYM_ADFStationActive] = 1;
}
void chunk3RealityModeHook(void) {
if (fs2Ram[SYM_RealityMode] == 0) {
realityModeOff();
return;
}
bool carry = false;
if (fs2Ram[SYM_SlewMode] == 0) {
// Trim drift toward the auto-trim target.
uint8_t yoke = fs2Ram[SYM_YokeVertPos];
int diff = (int)(int8_t)yoke - (int)(int8_t)fs2Ram[SYM_ElevatorTrim];
if (diff != 0) {
yoke = (uint8_t)(yoke + ((diff < 0) ? 2 : -2));
fs2Ram[SYM_YokeVertPos] = yoke;
}
carry = chunk5RefreshElevatorIndicator();
}
// Aircraft-problem trigger every 10 ticks.
fs2Ram[RAM_PROBLEM_TIMER]--;
if (fs2Ram[RAM_PROBLEM_TIMER] & 0x80) {
fs2Ram[RAM_PROBLEM_TIMER] = PROBLEM_PERIOD;
uint8_t chance = (uint8_t)(fs2Ram[0x63] + fs2Ram[0x5B] + (carry ? 1 : 0));
if (chance < PROBLEM_CHANCE_MAX && chance >= fs2Ram[SYM_ReliabilityFactor]) {
triggerAircraftProblem();
}
}
// Damage accumulator slew toward the target.
uint16_t damage = ramRead16(RAM_DAMAGE);
uint16_t diff16 = (uint16_t)(damage - fs2Ram[RAM_DAMAGE_TARGET]);
if (diff16 & 0x8000) {
damage = (uint16_t)(damage + DAMAGE_STEP);
} else if (diff16 != 0) {
damage = (uint16_t)(damage - DAMAGE_STEP);
}
ramWrite16(RAM_DAMAGE, damage);
// Wear counter += 2.
ramWrite16(RAM_WEAR, (uint16_t)(ramRead16(RAM_WEAR) + WEAR_STEP));
}
void chunk3RequestADFStationLookup(void) {
fs2Ram[SYM_ADFStationActive] = 0;
fs2Ram[RAM_ADF_LOOKUP_GATE] = 1;
}
bool chunk3SceneryLoaderEntry(int index) {
switch (index) {
case LOADER_ENTRY_READ_UNTIL_C0:
return sceneryReadUntilC0();
case LOADER_ENTRY_READ_FIXED:
return sceneryReadFixed();
case LOADER_ENTRY_NO_OP:
return false;
case LOADER_ENTRY_READ_DECODED:
return sceneryReadDecoded();
case LOADER_ENTRY_INIT:
sceneryLoaderInit();
return false;
default:
// SceneryBuffer1 / SceneryBuffer2 hold no code unless a
// protected-disk stage loads some; nothing does.
fprintf(stderr, "scenery loader entry %d has no code\n", index);
return true;
}
}
// SceneryRotatedTransform ($03): rotate the record's displacement by
// the angle and run the chunk3 template with the results filled in.
void chunk3SceneryRotatedTransform(void) {
uint16_t cur = ramRead16(ZP_STREAM_PTR);
uint8_t angle = (uint8_t)(fs2Ram[(uint16_t)(cur + 1)] + fs2Ram[RAM_COURSE_STEP_B]);
angle = (uint8_t)(angle + fs2Ram[RAM_COURSE_STEP_A]);
angle = (uint8_t)((uint8_t)(~angle) + ROT_ANGLE_BIAS);
fs2Ram[ZP_ROT_ANGLE] = angle;
uint16_t sinV = (uint16_t)chunk4SinByteAngle(angle);
ramWrite16(ZP_ROT_SIN, sinV);
uint16_t cosV = (uint16_t)chunk4SinShiftedByteAngle(angle);
ramWrite16(ZP_BE, cosV);
ramWrite16(ZP_C4, cosV);
uint16_t xDisp = ramRead16((uint16_t)(cur + 2));
ramWrite16(ZP_ROT_XDISP, xDisp);
ramWrite16(ZP_C2, xDisp);
uint16_t xCos = (uint16_t)chunk4ScaleC2ByC4();
ramWrite16(ZP_ROT_XCOS, xCos);
ramWrite16(RAM_ROT_25A, xCos);
negate16To(RAM_ROT_25E, xCos);
ramWrite16(ZP_C4, sinV);
ramWrite16(ZP_C2, xDisp);
uint16_t xSin = (uint16_t)chunk4ScaleC2ByC4();
ramWrite16(ZP_ROT_XSIN, xSin);
ramWrite16(RAM_ROT_258, xSin);
negate16To(RAM_ROT_25C, xSin);
uint16_t yDisp = ramRead16((uint16_t)(cur + 4));
ramWrite16(ZP_ROT_YDISP, yDisp);
ramWrite16(ZP_C2, yDisp);
ramWrite16(ZP_C4, cosV);
uint16_t yCos = (uint16_t)chunk4ScaleC2ByC4();
ramWrite16(ZP_ROT_YCOS, yCos);
ramWrite16(ZP_C2, yDisp);
ramWrite16(ZP_C4, sinV);
uint16_t ySin = (uint16_t)chunk4ScaleC2ByC4();
ramWrite16(ZP_ROT_SIN, ySin);
uint16_t r244 = (uint16_t)(ySin + xCos);
ramWrite16(RAM_ROT_244, r244);
negate16To(RAM_ROT_24E, r244);
uint16_t r247 = (uint16_t)(xSin + yCos);
ramWrite16(RAM_ROT_247, r247);
negate16To(RAM_ROT_251, r247);
uint16_t r242 = (uint16_t)(xSin - yCos);
ramWrite16(RAM_ROT_242, r242);
negate16To(RAM_ROT_24C, r242);
uint16_t r249 = (uint16_t)(xCos - ySin);
ramWrite16(RAM_ROT_249, r249);
negate16To(RAM_ROT_253, r249);
// Run the template with the cursor saved.
uint16_t saved = ramRead16(ZP_STREAM_PTR);
ramWrite16(ZP_STREAM_PTR, RAM_ROT_TEMPLATE);
fs2Ram[RAM_FILL_COLOR] = 0;
fs2Ram[RAM_ROTATED_FLAG] = 0;
sceneryProcessScenery();
ramWrite16(ZP_STREAM_PTR, saved);
sceneryAdvance(ROT_RECORD_LEN);
}
void chunk3Select3DViewPatch(void) {
if (fs2Ram[SYM_InputMode] == INPUT_MODE_MAGNETO) {
chunk5ApplyMagnetoState(3, MAGNETO_START);
return;
}
if (fs2Ram[SYM_RadarView] != 0) {
chunk5StoreRadarViewFromX(1);
return;
}
fs2Ram[SYM_InputMode] = 1;
}
void chunk3SelectRadarViewPatch(void) {
if (fs2Ram[SYM_InputMode] == INPUT_MODE_MAGNETO) {
chunk5ApplyMagnetoState(3, MAGNETO_BOTH);
return;
}
fs2Ram[SYM_InputMode] = 2;
chunk5StoreRadarViewFromX(2);
}
void chunk3SetMagnetoFromA(uint8_t state, uint8_t bits) {
fs2Ram[SYM_InputMode] = 0;
fs2Ram[SYM_RightMagnetoOn] = (uint8_t)(state >> 1);
fs2Ram[SYM_LeftMagnetoOn] = (uint8_t)(state & 1);
setMagnetoState(bits);
}
// ShowWarReport ($F13E): after a war the tallies and the six enemy
// states, then wait for a key.
void chunk3ShowWarReport(void) {
if (fs2Ram[RAM_WAR_REPORT_DUE] == 0) {
return;
}
fs2Ram[RAM_WAR_REPORT_DUE] = 0;
fs2Ram[SYM_ValueForString] = fs2Ram[RAM_ENEMIES_SHOT_DOWN];
fs2Ram[SYM_ValueForString + 1] = 0;
chunk5Set3DigitString(SYM_str_enemy_shot_down);
fs2Ram[SYM_ValueForString] = fs2Ram[OVL_WW1_BOMB_HITS];
fs2Ram[SYM_ValueForString + 1] = 0;
chunk5Set3DigitString(SYM_str_bomb_hits);
fs2Ram[SYM_ValueForString] = fs2Ram[RAM_DAMAGE_BY_ENEMY];
fs2Ram[SYM_ValueForString + 1] = 0;
chunk5Set3DigitString(SYM_str_damage_by_enemy);
static const uint16_t kStatus[WAR_REPORT_ENEMIES] = { SYM_str_enemy1_status, SYM_str_enemy2_status, SYM_str_enemy3_status, SYM_str_enemy4_status, SYM_str_enemy5_status, SYM_str_enemy6_status };
for (int i = 0; i < WAR_REPORT_ENEMIES; i++) {
fs2Ram[kStatus[i]] = (uint8_t)(fs2Ram[(uint16_t)(OVL_WW1_ENEMY_STATE + i * OVL_WW1_ENEMY_STRIDE)] | DIGIT_ZERO);
}
chunk4ClearViewportsToBlack();
chunk4DrawMultiMessage(SYM_msg_war_report);
chunk4DrawMessageOrange(SYM_msg_wr2);
chunk4DrawMessageOrange(SYM_msg_wr3);
chunk4DrawMessageOrange(SYM_msg_wr4);
chunk4DrawMessageOrange(SYM_msg_wr5);
chunk4DrawMessageOrange(SYM_msg_wr6);
chunk4DrawMessageOrange(SYM_msg_wr7);
chunk4DrawMessageOrange(SYM_msg_wr8);
chunk4DrawMessageOrange(SYM_msg_wr9);
chunk4DrawMessageOrange(SYM_msg_wr10);
chunk5TogglePause();
}
void chunk3UpdateADFIndicator(void) {
if (fs2Ram[SYM_ADFMode] == 0) {
return;
}
bool first = (fs2Ram[SYM_ADFNeedsInit] & 1) != 0;
fs2Ram[SYM_ADFNeedsInit] >>= 1;
if (first) {
initADFPanel();
}
if (fs2Ram[SYM_ADFStationActive] == 0) {
return;
}
bool carry = chunk5ComputeStationDelta(0);
// lda $B6; sbc $70 (carry from ComputeStationDelta); ... the
// 16-bit deviation minus the heading, plus the course offsets.
uint16_t d = (uint16_t)(ramRead16(ZP_B6) - ramRead16(ZP_ATT_HEADING) - (carry ? 0 : 1));
uint8_t lo = (uint8_t)d;
uint8_t hi = (uint8_t)((d >> 8) + 0x40);
hi = (uint8_t)(hi + fs2Ram[RAM_COURSE_STEP_B]);
hi = (uint8_t)(hi + fs2Ram[RAM_COURSE_STEP_A]);
fs2Ram[ZP_C2] = lo;
fs2Ram[ZP_BE] = lo;
uint8_t hiHalf = (uint8_t)(hi >> 1);
fs2Ram[ZP_C2 + 1] = hiHalf;
fs2Ram[ZP_BE + 1] = hiHalf;
fs2Ram[ZP_C2] = (uint8_t)((lo >> 1) | ((hi & 1) << 7));
fs2Ram[ZP_BE] = (uint8_t)((lo >> 1) | ((lo & 1) << 7));
uint16_t s = (uint16_t)chunk4ScaleC2ByAX(ADF_SCALE);
uint8_t angle = (uint8_t)((uint8_t)(~(uint8_t)s) + ADF_SCALE);
uint8_t last = fs2Ram[SYM_ADFLastDrawnAngle];
if (last & 0x80) {
return;
}
fs2Ram[SYM_ADFLastDrawnAngle] = angle;
if (angle == last) {
return;
}
chunk4DrawIndicatorDialNeedle(last, ADF_NEEDLE);
chunk4DrawIndicatorDialNeedle(fs2Ram[SYM_ADFLastDrawnAngle], ADF_NEEDLE);
drawADFHeadingDigits();
}
void chunk3UpdateCOMMessageChunks(void) {
uint8_t ones;
uint8_t temp = (uint8_t)(fs2Ram[(uint16_t)(SYM_SeasonTempTable + fs2Ram[SYM_Season])] + fs2Ram[SYM_BaseTemp]);
fs2Ram[SYM_str_temp_digits] = aTo2Digits(temp, &ones);
fs2Ram[SYM_str_temp_digits + 1] = ones;
// Wind direction: (dir * 360) >> 16 as three digits.
ramWrite16(ZP_C2, DEGREES_360);
uint16_t p = chunk4MultiplyAXByC2(ramRead16(SYM_WindDirection));
uint8_t tens;
uint8_t hundreds = axTo3Digits((uint16_t)((p >> 8) | ((uint16_t)fs2Ram[0xC9] << 8)), &tens, &ones);
fs2Ram[SYM_str_wind_dir_digits + 1] = tens;
fs2Ram[SYM_str_wind_dir_digits + 2] = ones;
if (hundreds == '0') {
hundreds = ' ';
}
fs2Ram[SYM_str_wind_dir_digits] = hundreds;
uint8_t speedTens = aTo2Digits(fs2Ram[SYM_WindSpeed], &ones);
if (speedTens == '0') {
speedTens = ' ';
}
fs2Ram[SYM_str_wind_speed_digits] = speedTens;
fs2Ram[SYM_str_wind_speed_digits + 1] = ones;
uint8_t hours = (uint8_t)(fs2Ram[SYM_Hours] + fs2Ram[RAM_TIME_ZONE]);
if (hours >= 24) {
hours = (uint8_t)(hours - 24);
}
fs2Ram[SYM_str_time_digits] = aTo2Digits(hours, &ones);
fs2Ram[SYM_str_time_digits + 1] = ones;
// Ceiling: ($0856 * $0866) >> 16, or omitted when zero.
ramWrite16(ZP_C2, 0x0866);
p = chunk4MultiplyAXByC2(ramRead16(RAM_CEILING));
fs2Ram[SYM_str_ceiling_include] = 0;
uint16_t ceiling = (uint16_t)((p >> 8) | ((uint16_t)fs2Ram[0xC9] << 8));
if (ceiling != 0) {
fs2Ram[SYM_str_ceiling_include] = ' ';
hundreds = axTo3Digits(ceiling, &tens, &ones);
if (hundreds == '0') {
hundreds = ' ';
if (tens == '0') {
tens = ' ';
}
}
fs2Ram[SYM_str_ceiling_digits] = hundreds;
fs2Ram[SYM_str_ceiling_digits + 1] = tens;
fs2Ram[SYM_str_ceiling_digits + 2] = ones;
}
uint8_t runway = fs2Ram[(uint16_t)(SYM_RunwaysTable + (fs2Ram[SYM_WindDirection + 1] >> 6))];
fs2Ram[SYM_str_runway_digits] = aTo2Digits(runway, &ones);
fs2Ram[SYM_str_runway_digits + 1] = ones;
}
void chunk3UpdateEngineWithMagneto(void) {
bool engineOff = false;
if (fs2Ram[SYM_WW1AceMode] == 0 && fs2Ram[SYM_RealityMode] != 0) {
if (fs2Ram[RAM_MIXTURE] == 7) {
engineOff = true;
} else {
uint8_t mags = (uint8_t)((fs2Ram[SYM_LeftMagnetoOn] & fs2Ram[RAM_MAG1_OK]) | (fs2Ram[SYM_RightMagnetoOn] & fs2Ram[RAM_MAG2_OK]));
if (mags == 0) {
engineOff = true;
}
}
}
if (!engineOff) {
// Fuel check on the selected tank.
if (fs2Ram[RAM_TANK_SELECT] == 0) {
if (fs2Ram[RAM_FUEL_LEFT + 2] == 0) {
engineOff = true;
}
} else if (fs2Ram[RAM_FUEL_RIGHT + 2] == 0) {
engineOff = true;
}
}
if (engineOff) {
stopEngine();
} else if ((fs2Ram[SYM_RealityMode] | fs2Ram[SYM_EngineOnFlag]) == 0) {
fs2Ram[SYM_EngineOnFlag] = 1;
} else if (fs2Ram[SYM_EngineOnFlag] == 0) {
if (fs2Ram[RAM_AIRSPEED + 1] >= RPM_START_THRESHOLD) {
fs2Ram[SYM_EngineOnFlag] = 1;
} else if (fs2Ram[SYM_MagnetoState] == MAGNETO_START) {
chunk3Delay(SPINUP_DELAY);
setMagnetoState(MAGNETO_BOTH);
if (fs2Ram[SYM_Season] != 1 || (fs2Ram[SYM_UpdateCounter] & 0x04) != 0) {
fs2Ram[SYM_EngineOnFlag] = 1;
}
}
}
// Phase 1: RPM curve selection.
uint8_t x = (uint8_t)(fs2Ram[RAM_AIRSPEED + 1] >> 2);
if (x >= RPM_CURVE_MAX) {
x = RPM_CURVE_MAX;
}
fs2Ram[ZP_BA] = fs2Ram[(uint16_t)(SYM_RPMCurveRun + x)];
uint8_t y = fs2Ram[RAM_THROTTLE];
if (fs2Ram[SYM_EngineOnFlag] == 0) {
y = 0;
} else if (fs2Ram[RAM_CARB_HEAT] != 0) {
uint8_t bias = CARB_HEAT_BIAS;
if (fs2Ram[SYM_EngineSpinupCounter] != 0) {
bias = SPINUP_BIAS;
fs2Ram[SYM_EngineSpinupCounter]--;
}
y = (uint8_t)(bias + fs2Ram[RAM_THROTTLE]);
if (y & 0x80) {
y = 0;
}
}
if ((fs2Ram[RAM_ENGINE_FAULTS] & 0x03) != 0 && y >= MIXTURE_CLAMP) {
y = MIXTURE_CLAMP;
}
if ((fs2Ram[SYM_LeftMagnetoOn] & fs2Ram[RAM_MAG1_OK]) == 0 || (fs2Ram[SYM_RightMagnetoOn] & fs2Ram[RAM_MAG2_OK]) == 0) {
y = (uint8_t)(y - MAGNETO_DEGRADE);
if (y & 0x80) {
y = 0;
}
}
uint8_t rpm = fs2Ram[(uint16_t)(SYM_RPMCurveIdle + (y >> 2))];
if (fs2Ram[SYM_EngineOnFlag] == 0) {
rpm = 0;
}
fs2Ram[RAM_TARGET_RPM] = rpm;
uint8_t drag = (rpm >= RPM_DRAG_MIN) ? rpm : 0;
fs2Ram[ZP_C2 + 1] = drag;
fs2Ram[ZP_C2] = 0;
chunk4ScaleC2ByAXIntoC2(PROP_DRAG_SCALE);
ramWrite16(RAM_PROP_DRAG, (uint16_t)chunk4ScaleC2ByAX(ramRead16(0x09A9)));
// Cap at the run curve.
uint8_t cap = fs2Ram[ZP_BA];
if (((uint8_t)(cap - fs2Ram[RAM_TARGET_RPM]) & 0x80) == 0) {
fs2Ram[RAM_TARGET_RPM] = cap;
}
// Phase 2: idle floor outside reality mode.
if (fs2Ram[SYM_RealityMode] == 0 && fs2Ram[RAM_TARGET_RPM] < RPM_IDLE_FLOOR) {
fs2Ram[RAM_TARGET_RPM] = RPM_IDLE_FLOOR;
}
// Phase 3 / 4: cylinder head and oil temperature slews.
if (fs2Ram[ZP_INSTR_FC] & 0x40) {
uint8_t target = 0;
if (fs2Ram[SYM_EngineOnFlag] != 0) {
target = ((fs2Ram[RAM_ENGINE_FAULTS] & 0x01) != 0) ? CHT_FAULT : CHT_ON;
}
uint8_t cur = fs2Ram[RAM_CHT];
if (target != cur) {
uint16_t frac = (uint16_t)(fs2Ram[SYM_CHTSlewFraction] | ((uint16_t)cur << 8));
// The sbc has no sec: borrow from the cpx (target < cur).
frac = (target < cur) ? (uint16_t)(frac - CHT_STEP - 1) : (uint16_t)(frac + CHT_STEP + 1);
fs2Ram[SYM_CHTSlewFraction] = (uint8_t)frac;
fs2Ram[RAM_CHT] = (uint8_t)(frac >> 8);
}
target = 0;
if (fs2Ram[SYM_EngineOnFlag] != 0) {
target = ((fs2Ram[RAM_ENGINE_FAULTS] & 0x01) != 0) ? OIL_FAULT : OIL_ON;
}
cur = fs2Ram[RAM_OIL_TEMP];
if (target != cur) {
uint16_t frac = (uint16_t)(fs2Ram[SYM_OilTempSlewFraction] | ((uint16_t)cur << 8));
frac = (target < cur) ? (uint16_t)(frac - OIL_STEP - 1) : (uint16_t)(frac + OIL_STEP + 1);
fs2Ram[SYM_OilTempSlewFraction] = (uint8_t)frac;
fs2Ram[RAM_OIL_TEMP] = (uint8_t)(frac >> 8);
}
}
// Phase 5: fuel consumption every 32 ticks.
if ((fs2Ram[SYM_InputTickCounter] & 0x1F) != 0 || fs2Ram[SYM_EngineOnFlag] == 0) {
return;
}
uint8_t burnIndex = FUEL_BURN_INDEX;
if (fs2Ram[SYM_WW1AceMode] != 0) {
burnIndex = (uint8_t)(burnIndex << 2);
}
uint16_t burn = chunk4MultiplyXYAndHalve(fs2Ram[RAM_THROTTLE], burnIndex);
fs2Ram[ZP_A5] = (uint8_t)burn;
fs2Ram[ZP_A5 + 1] = (uint8_t)(burn >> 8);
if (fs2Ram[RAM_ENGINE_FAULTS] & 0x04) {
fs2Ram[RAM_FUEL_LEFT + 2]--;
if (fs2Ram[RAM_FUEL_LEFT + 2] & 0x80) {
fs2Ram[RAM_FUEL_LEFT + 2]++;
}
}
if (fs2Ram[RAM_TANK_SELECT] == 0) {
uint16_t s = (uint16_t)fs2Ram[ZP_A5] + fs2Ram[RAM_FUEL_LEFT];
fs2Ram[RAM_FUEL_LEFT] = (uint8_t)s;
if ((burn >> 8) & 0x80) {
uint16_t s2 = (uint16_t)(burn >> 8) + fs2Ram[RAM_FUEL_LEFT + 1] + (s >> 8);
fs2Ram[RAM_FUEL_LEFT + 1] = (uint8_t)s2;
uint16_t s3 = (uint16_t)0xFF + fs2Ram[RAM_FUEL_LEFT + 2] + (s2 >> 8);
if ((uint8_t)s3 & 0x80) {
return;
}
fs2Ram[RAM_FUEL_LEFT + 2] = (uint8_t)s3;
} else {
return;
}
}
if (fs2Ram[RAM_ENGINE_FAULTS] & 0x08) {
fs2Ram[RAM_FUEL_RIGHT + 2]--;
if (fs2Ram[RAM_FUEL_RIGHT + 2] & 0x80) {
fs2Ram[RAM_FUEL_RIGHT + 2]++;
}
}
if (fs2Ram[RAM_TANK_SELECT] == 0) {
return;
}
uint16_t s = (uint16_t)fs2Ram[ZP_A5] + fs2Ram[RAM_FUEL_RIGHT];
fs2Ram[RAM_FUEL_RIGHT] = (uint8_t)s;
if (((burn >> 8) & 0x80) == 0) {
return;
}
uint16_t s2 = (uint16_t)(burn >> 8) + fs2Ram[RAM_FUEL_RIGHT + 1] + (s >> 8);
fs2Ram[RAM_FUEL_RIGHT + 1] = (uint8_t)s2;
uint16_t s3 = (uint16_t)0xFF + fs2Ram[RAM_FUEL_RIGHT + 2] + (s2 >> 8);
if (((uint8_t)s3 & 0x80) == 0) {
fs2Ram[RAM_FUEL_RIGHT + 2] = (uint8_t)s3;
}
}
void chunk3UpdateInstrumentLights(void) {
if ((fs2Ram[RAM_DAY_PHASE] & 0x01) == 0) {
fs2Ram[RAM_LIGHT_A] = LIGHT_DIM_A;
fs2Ram[RAM_LIGHT_A + 1] = LIGHT_DIM_B;
fs2Ram[RAM_LIGHT_A + 2] = LIGHT_DIM_C;
if (fs2Ram[SYM_PanelLights] == 0) {
return;
}
}
fs2Ram[RAM_LIGHT_A] = fs2Ram[RAM_LIGHT_SRC];
fs2Ram[RAM_LIGHT_A + 1] = fs2Ram[RAM_LIGHT_SRC + 1];
fs2Ram[RAM_LIGHT_A + 2] = fs2Ram[RAM_LIGHT_SRC + 2];
}
static void drawADFHeadingDigits(void) {
fs2Ram[ZP_C2] = fs2Ram[ZP_BE];
fs2Ram[ZP_C2 + 1] = (uint8_t)((fs2Ram[ZP_BE + 1] + 0x20) & 0x7F);
ramWrite16(SYM_ValueForString, (uint16_t)chunk4ScaleC2ByAX(DEGREES_360));
chunk5Set3DigitString(SYM_str_adf_heading);
chunk4DrawMessageOrange(SYM_msg_adf_heading);
}
// DrawWingsOrTail: one trapezoid record, painted as spans.
static void drawWingsOrTail(uint8_t index) {
fs2Ram[RAM_FILL_COLOR] = WING_COLOR_CODE;
hiresMapColorAndPrepRowRoutine();
uint16_t rec = (uint16_t)(SYM_WingOrTailTrapezoids + index);
fs2Ram[RAM_TRAPEZOID_W] = 0;
fs2Ram[RAM_TRAPEZOID_X] = 0;
fs2Ram[RAM_TRAPEZOID_W + 1] = fs2Ram[rec];
if (fs2Ram[rec] == 0) {
return;
}
ramWrite16(RAM_TRAPEZOID_DW, ramRead16((uint16_t)(rec + 1)));
fs2Ram[RAM_TRAPEZOID_Y] = fs2Ram[(uint16_t)(rec + 3)];
fs2Ram[RAM_TRAPEZOID_X + 1] = fs2Ram[(uint16_t)(rec + 4)];
ramWrite16(RAM_TRAPEZOID_DX, ramRead16((uint16_t)(rec + 5)));
fs2Ram[RAM_TRAPEZOID_H] = fs2Ram[(uint16_t)(rec + 7)];
do {
fs2Ram[0x27] = fs2Ram[RAM_TRAPEZOID_X + 1];
hiresSetRowPtr(fs2Ram[RAM_TRAPEZOID_Y]);
hiresDrawColorSpan(fs2Ram[RAM_TRAPEZOID_W + 1]);
ramWrite16(RAM_TRAPEZOID_X, (uint16_t)(ramRead16(RAM_TRAPEZOID_X) + ramRead16(RAM_TRAPEZOID_DX)));
ramWrite16(RAM_TRAPEZOID_W, (uint16_t)(ramRead16(RAM_TRAPEZOID_W) + ramRead16(RAM_TRAPEZOID_DW)));
fs2Ram[RAM_TRAPEZOID_Y]++;
fs2Ram[RAM_TRAPEZOID_H]--;
} while (fs2Ram[RAM_TRAPEZOID_H] != 0);
}
// ErasePixelReturnIfSet: clear the pixel on both pages, return 1 if it
// was set.
static uint8_t erasePixelReturnIfSet(uint8_t xLo, uint8_t y) {
uint8_t mask;
uint16_t addr = calcPixelAddrAndMask(xLo, y, &mask);
uint8_t was = ((mask & fs2Ram[addr]) != 0) ? 1 : 0;
fs2Ram[ZP_A7] = was;
uint8_t v = (uint8_t)(fs2Ram[addr] & (uint8_t)~mask);
fs2Ram[addr] = v;
uint8_t hi = (uint8_t)((addr >> 8) + 0x20);
if (hi >= 0x60) {
hi = (uint8_t)(hi - 0x40);
}
fs2Ram[ZP_B8 + 1] = hi;
fs2Ram[(uint16_t)((addr & 0xFF) | ((uint16_t)hi << 8))] = v;
return was;
}
static void hideOrShow8Instruments(uint16_t records) {
ramWrite16(ZP_BC, records);
fs2Ram[0xA2] = SAVE_RECORDS;
do {
fs2Ram[ZP_B6] = (uint8_t)(fs2Ram[ZP_A0] & 0x01);
fs2Ram[ZP_B7] = (uint8_t)(fs2Ram[0x9E] & 0x01);
ramWrite16(ZP_B8, ramRead16(ZP_BC));
hideOrShowInstrument();
fs2Ram[0x9E] >>= 1;
fs2Ram[ZP_A0] >>= 1;
ramWrite16(ZP_BC, (uint16_t)(ramRead16(ZP_BC) + SAVE_RECORD_LEN));
fs2Ram[0xA2]--;
} while (fs2Ram[0xA2] != 0);
}
// HideOrShowInstrument: save (hide) or restore (show) the pixels of one
// panel rectangle, one bit per pixel in the record's buffer.
static void hideOrShowInstrument(void) {
if (fs2Ram[ZP_B7] == fs2Ram[ZP_B6]) {
return;
}
fs2Ram[SYM_HideOrShow] = fs2Ram[ZP_B7];
uint16_t rec = ramRead16(ZP_B8);
fs2Ram[RAM_XSTART] = fs2Ram[rec];
uint8_t packed = fs2Ram[(uint16_t)(rec + 1)];
fs2Ram[RAM_YSIZE] = (uint8_t)(packed >> 1);
fs2Ram[RAM_XSTART + 1] = (uint8_t)(packed & 0x01);
fs2Ram[RAM_YSTART] = fs2Ram[(uint16_t)(rec + 2)];
fs2Ram[RAM_XSIZE] = fs2Ram[(uint16_t)(rec + 3)];
ramWrite16(ZP_BA, ramRead16((uint16_t)(rec + 4)));
fs2Ram[SYM_BitNumber] = 0;
do {
fs2Ram[RAM_XCOUNT] = fs2Ram[RAM_XSIZE];
ramWrite16(RAM_XCOORD, ramRead16(RAM_XSTART));
do {
uint8_t y = fs2Ram[RAM_YSTART];
if (fs2Ram[SYM_HideOrShow] != 0) {
uint8_t toggle = loadPixelState();
fs2Ram[ZP_A5] = fs2Ram[RAM_XCOORD + 1];
maybeXORPixel(toggle, fs2Ram[RAM_XCOORD], y);
} else {
fs2Ram[ZP_A5] = fs2Ram[RAM_XCOORD + 1];
savePixelState(erasePixelReturnIfSet(fs2Ram[RAM_XCOORD], y));
}
ramWrite16(RAM_XCOORD, (uint16_t)(ramRead16(RAM_XCOORD) + 1));
fs2Ram[RAM_XCOUNT]--;
} while (fs2Ram[RAM_XCOUNT] != 0);
fs2Ram[RAM_YSTART]++;
fs2Ram[RAM_YSIZE]--;
} while (fs2Ram[RAM_YSIZE] != 0);
if (fs2Ram[SYM_HideOrShow] != 0) {
return;
}
// Pad the last buffer byte to 8 bits.
uint16_t buf = ramRead16(ZP_BA);
uint8_t a = fs2Ram[buf];
for (uint8_t x = (uint8_t)(fs2Ram[SYM_BitNumber] + 1); x != 9; x++) {
a = (uint8_t)(a << 1);
}
fs2Ram[buf] = a;
}
static void initADFPanel(void) {
chunk4DrawMultiMessage(SYM_DrawADFPanel);
fs2Ram[SYM_ADFLastDrawnAngle] = ADF_INIT_ANGLE;
chunk4DrawIndicatorDialNeedle(ADF_INIT_ANGLE, ADF_NEEDLE);
chunk4DrawMessageOrange(SYM_msg_adf_heading);
chunk4DrawMessageOrange(SYM_msg_adf_frequency);
}
// LD5C8: the loader's self-extension stub (the protected-disk boot
// decodes real code here; the port's disk never needs it).
static void loaderExtension(void) {
}
// LoadPixelState: next saved bit (bit 7 of the buffer byte, which is
// rotated up with the carry the bit-count compare left).
static uint8_t loadPixelState(void) {
fs2Ram[SYM_BitNumber]++;
bool wrapped = (fs2Ram[SYM_BitNumber] == 9);
if (wrapped) {
fs2Ram[SYM_BitNumber] = 1;
ramWrite16(ZP_BA, (uint16_t)(ramRead16(ZP_BA) + 1));
}
uint16_t buf = ramRead16(ZP_BA);
uint8_t v = fs2Ram[buf];
uint8_t bit = (uint8_t)(v >> 7);
fs2Ram[buf] = (uint8_t)((v << 1) | (wrapped ? 1 : 0));
return bit;
}
// MaybeXORPixel: toggle the pixel on both pages when `toggle` is set.
static void maybeXORPixel(uint8_t toggle, uint8_t xLo, uint8_t y) {
uint8_t mask;
uint16_t addr = calcPixelAddrAndMask(xLo, y, &mask);
uint8_t v = fs2Ram[addr];
if (toggle != 0) {
v = (uint8_t)(v ^ mask);
}
fs2Ram[addr] = v;
uint8_t hi = (uint8_t)((addr >> 8) + 0x20);
if (hi >= 0x60) {
hi = (uint8_t)(hi - 0x40);
}
fs2Ram[ZP_B8 + 1] = hi;
fs2Ram[(uint16_t)((addr & 0xFF) | ((uint16_t)hi << 8))] = v;
}
static void negate16To(uint16_t dst, uint16_t value) {
ramWrite16(dst, (uint16_t)(0u - value));
}
static void populateA5ThruA8(void) {
chunk4PopulateA5ThruA8From1E03();
}
static void realityModeOff(void) {
ramWrite16(RAM_DAMAGE, 0);
ramWrite16(RAM_WEAR, 0);
}
// SavePixelState: shift the pixel's bit into the buffer.
static void savePixelState(uint8_t wasSet) {
fs2Ram[ZP_A5] = wasSet;
fs2Ram[SYM_BitNumber]++;
if (fs2Ram[SYM_BitNumber] == 9) {
fs2Ram[SYM_BitNumber] = 1;
ramWrite16(ZP_BA, (uint16_t)(ramRead16(ZP_BA) + 1));
}
uint16_t buf = ramRead16(ZP_BA);
fs2Ram[buf] = (uint8_t)((fs2Ram[buf] << 1) | (wasSet & 1));
}
// SceneryComputeBlockOffset: false = carry clear (in range, re-opened).
static bool sceneryComputeBlockOffset(void) {
uint8_t v = (uint8_t)((fs2Ram[SYM_SceneryLoaderSector] << 1) + LOADER_BLOCK_BIAS);
if (v >= LOADER_STREAM_END) {
return true;
}
fs2Ram[SYM_SceneryLoaderBlockOffset] = v;
sceneryReopen();
return false;
}
static bool sceneryEnsureOpen(void) {
if (chunk4ComputeBlockFromSector()) {
fs2Ram[SYM_SceneryLoaderOpen] = 0;
return true;
}
sceneryReopen();
return false;
}
static void sceneryLoaderInit(void) {
fs2Ram[SYM_SceneryLoaderOpen] = 0;
fs2Ram[SYM_SceneryLoaderBlockOffset] = LOADER_INIT_OFFSET;
}
// SceneryNibbleDecode: 6+2 split of the sector into $3B60.., with the
// EOR checksum at $3B5E/$3B5F.
static void sceneryNibbleDecode(void) {
populateA5ThruA8();
ramWrite16(ZP_A7, RAM_NIBBLE_DEST);
fs2Ram[ZP_B6] = 0;
do {
uint16_t src = ramRead16(ZP_A5);
uint8_t b = fs2Ram[src];
fs2Ram[ZP_B6] ^= b;
uint16_t dst = ramRead16(ZP_A7);
fs2Ram[dst] = (uint8_t)((b >> 1) | NIBBLE_MASK);
dst++;
fs2Ram[dst] = (uint8_t)(b | NIBBLE_MASK);
dst++;
ramWrite16(ZP_A7, dst);
ramWrite16(ZP_A5, (uint16_t)(src + 1));
} while (fs2Ram[ZP_A7 + 1] < RAM_NIBBLE_END_PAGE);
fs2Ram[RAM_NIBBLE_CHECKSUM + 1] = (uint8_t)(fs2Ram[ZP_B6] | NIBBLE_MASK);
fs2Ram[RAM_NIBBLE_CHECKSUM] = (uint8_t)((fs2Ram[ZP_B6] >> 1) | NIBBLE_MASK);
}
static bool sceneryReadDecoded(void) {
if (sceneryComputeBlockOffset()) {
return true;
}
sceneryNibbleDecode();
fs2Ram[SYM_SceneryLoaderOpen] = 0;
loaderExtension();
ramWrite16(SYM_SceneryLoaderSector, (uint16_t)(ramRead16(SYM_SceneryLoaderSector) + 1));
ramWrite16(SYM_SceneryLoaderPtr, ramRead16(ZP_A5));
return false;
}
// SceneryReadFixed: four pages from the loader pointer to the staging
// buffer.
static bool sceneryReadFixed(void) {
if (sceneryEnsureOpen()) {
return true;
}
populateA5ThruA8();
for (int x = 0; x < 4; x++) {
uint16_t src = ramRead16(ZP_A5);
uint16_t dst = ramRead16(ZP_A7);
for (int y = 0; y < 256; y++) {
fs2Ram[(uint16_t)(dst + y)] = fs2Ram[(uint16_t)(src + y)];
}
fs2Ram[ZP_A5 + 1]++;
fs2Ram[ZP_A7 + 1]++;
fs2Ram[SYM_SceneryLoaderPtr + 1]++;
}
ramWrite16(SYM_SceneryLoaderSector, (uint16_t)(ramRead16(SYM_SceneryLoaderSector) + 1));
loaderExtension();
fs2Ram[SYM_SceneryLoaderOpen] = 0;
return false;
}
// SceneryReadUntilC0: four pages from the staging buffer to the loader
// pointer, stopping at $C000.
static bool sceneryReadUntilC0(void) {
if (sceneryEnsureOpen()) {
return true;
}
populateA5ThruA8();
fs2Ram[ZP_A0] = 4;
uint8_t y = 0;
for (;;) {
if ((fs2Ram[ZP_A5 + 1] & 0xF0) == LOADER_ROM_PAGE) {
break;
}
fs2Ram[ramRead16(ZP_A5)] = fs2Ram[ramRead16(ZP_A7)];
ramWrite16(ZP_A7, (uint16_t)(ramRead16(ZP_A7) + 1));
ramWrite16(ZP_A5, (uint16_t)(ramRead16(ZP_A5) + 1));
y++;
if (y != 0) {
continue;
}
fs2Ram[ZP_A0]--;
if (fs2Ram[ZP_A0] == 0) {
break;
}
}
ramWrite16(SYM_SceneryLoaderSector, (uint16_t)(ramRead16(SYM_SceneryLoaderSector) + 1));
ramWrite16(SYM_SceneryLoaderPtr, ramRead16(ZP_A5));
return false;
}
static void sceneryReopen(void) {
chunk4FetchSectorFromDisk();
fs2Ram[SYM_SceneryLoaderOpen] = 0xFF;
}
static void setMagnetoState(uint8_t state) {
fs2Ram[SYM_MagnetoState] = state;
chunk5DrawCarbHeatAndLights();
}
static void stopEngine(void) {
fs2Ram[SYM_EngineOnFlag] = 0;
}
static void storeADFDigitAndRedraw(uint8_t digit, uint8_t slot) {
fs2Ram[(uint16_t)(SYM_str_adf_frequency + 1 + slot)] = digit;
chunk4DrawMessageOrange(SYM_msg_adf_frequency);
uint8_t mid = (uint8_t)(fs2Ram[SYM_str_adf_frequency + 2] << 4);
fs2Ram[ZP_B7] = mid;
fs2Ram[SYM_ADFFreqLowPacked] = (uint8_t)((fs2Ram[SYM_str_adf_frequency + 3] & 0x0F) | mid);
fs2Ram[SYM_ADFFreqHighDigit] = (uint8_t)(fs2Ram[SYM_str_adf_frequency + 1] & 0x0F);
chunk3RequestADFStationLookup();
}
// TriggerAircraftProblem: announce and pick one of eight failures.
static void triggerAircraftProblem(void) {
chunk4ClearViewportsToBlack();
chunk4DrawMessageOrange(SYM_msg_problem);
uint8_t x = (uint8_t)((fs2Ram[SYM_UpdateCounter] + fs2Ram[0x5F]) & 0x0E);
uint16_t proc = ramRead16((uint16_t)(SYM_FailureProcTable + x));
ramWrite16(ZP_BA, proc);
switch (proc) {
case SYM_FailInstrumentBit0:
fs2Ram[SYM_InstrumentOperationalFlags] &= 0xFE;
break;
case SYM_SetEngineFault01:
fs2Ram[RAM_ENGINE_FAULTS] |= 0x03;
break;
case SYM_FailInstrumentBit2:
fs2Ram[SYM_InstrumentOperationalFlags] &= 0xFB;
break;
case SYM_FailInstrumentBit3:
fs2Ram[SYM_InstrumentOperationalFlags] &= 0xF7;
break;
case SYM_SetEngineFault23:
fs2Ram[RAM_ENGINE_FAULTS] |= 0x0C;
break;
case SYM_FailInstrumentBit5:
fs2Ram[SYM_InstrumentOperationalFlags] &= 0xDF;
break;
case SYM_FailInstrumentBit6:
fs2Ram[SYM_InstrumentOperationalFlags] &= 0xBF;
break;
default:
fs2Ram[SYM_InstrumentOperationalFlags] &= 0x7F;
break;
}
}