// See chunk3.h. #include #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; } }