#include "game_bg.h" #include "game_logic.h" #include #include #include #include #include namespace { // Helper functions to pack 4bpp pixels into 16-bit halfwords constexpr uint16_t row_left(uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3) { return static_cast((static_cast(p0 & 0x0FU)) | (static_cast(p1 & 0x0FU) << 4U) | (static_cast(p2 & 0x0FU) << 8U) | (static_cast(p3 & 0x0FU) << 12U)); } constexpr uint16_t row_right(uint8_t p4, uint8_t p5, uint8_t p6, uint8_t p7) { return static_cast((static_cast(p4 & 0x0FU)) | (static_cast(p5 & 0x0FU) << 4U) | (static_cast(p6 & 0x0FU) << 8U) | (static_cast(p7 & 0x0FU) << 12U)); } constexpr uint16_t make_rgb15(uint32_t r, uint32_t g, uint32_t b) { return static_cast((r & 31U) | ((g & 31U) << 5U) | ((b & 31U) << 10U)); } // 16 tiles, each tile is 8 rows * 2 halfwords = 16 halfwords (32 bytes per tile) constexpr size_t TILE_COUNT = 16; constexpr size_t WORDS_PER_TILE = 16; uint16_t bg_tiles[TILE_COUNT * WORDS_PER_TILE]; void generate_bg_tiles() { std::memset(bg_tiles, 0, sizeof(bg_tiles)); auto set_row = [](size_t tile_idx, size_t row_idx, uint16_t left, uint16_t right) { bg_tiles[tile_idx * WORDS_PER_TILE + row_idx * 2] = left; bg_tiles[tile_idx * WORDS_PER_TILE + row_idx * 2 + 1] = right; }; // Tile 0: Empty transparent tile (all 0) // Tile 1: Grid intersection dot at (0,0) (color 2) set_row(1, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); // Tile 2: Grid intersection with faint guide ticks set_row(2, 0, row_left(3, 1, 0, 0), row_right(0, 0, 0, 0)); set_row(2, 1, row_left(1, 0, 0, 0), row_right(0, 0, 0, 0)); // Tile 3: Twinkling star / micro node (dim) set_row(3, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); set_row(3, 3, row_left(0, 0, 0, 4), row_right(0, 0, 0, 0)); // Tile 4: Twinkling star / medium cross set_row(4, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); set_row(4, 3, row_left(0, 0, 0, 0), row_right(4, 0, 0, 0)); set_row(4, 4, row_left(0, 0, 0, 4), row_right(5, 4, 0, 0)); set_row(4, 5, row_left(0, 0, 0, 0), row_right(4, 0, 0, 0)); // Tile 5: Twinkling star / bright diamond flare set_row(5, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); set_row(5, 2, row_left(0, 0, 0, 0), row_right(5, 0, 0, 0)); set_row(5, 3, row_left(0, 0, 0, 5), row_right(6, 5, 0, 0)); set_row(5, 4, row_left(0, 0, 5, 6), row_right(7, 6, 5, 0)); set_row(5, 5, row_left(0, 0, 0, 5), row_right(6, 5, 0, 0)); set_row(5, 6, row_left(0, 0, 0, 0), row_right(5, 0, 0, 0)); // Tile 6: Circuit horizontal line set_row(6, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); set_row(6, 4, row_left(1, 1, 1, 1), row_right(1, 1, 1, 1)); // Tile 7: Circuit vertical line set_row(7, 0, row_left(2, 0, 0, 0), row_right(1, 0, 0, 0)); for (size_t r = 1; r < 8; ++r) { set_row(7, r, row_left(0, 0, 0, 0), row_right(1, 0, 0, 0)); } // Tile 8: Circuit corner (top to right) set_row(8, 0, row_left(2, 0, 0, 0), row_right(1, 0, 0, 0)); for (size_t r = 1; r < 4; ++r) { set_row(8, r, row_left(0, 0, 0, 0), row_right(1, 0, 0, 0)); } set_row(8, 4, row_left(0, 0, 0, 0), row_right(5, 1, 1, 1)); // Tile 9: Circuit corner (left to down) set_row(9, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); set_row(9, 4, row_left(1, 1, 1, 1), row_right(5, 0, 0, 0)); for (size_t r = 5; r < 8; ++r) { set_row(9, r, row_left(0, 0, 0, 0), row_right(1, 0, 0, 0)); } // Tile 10: Circuit T-junction set_row(10, 0, row_left(2, 0, 0, 0), row_right(0, 0, 0, 0)); set_row(10, 4, row_left(1, 1, 1, 1), row_right(5, 1, 1, 1)); for (size_t r = 5; r < 8; ++r) { set_row(10, r, row_left(0, 0, 0, 0), row_right(1, 0, 0, 0)); } // Tile 11: Top-Left tech bracket set_row(11, 0, row_left(9, 8, 8, 8), row_right(8, 8, 8, 0)); for (size_t r = 1; r < 7; ++r) { set_row(11, r, row_left(8, 0, 0, 0), row_right(0, 0, 0, 0)); } // Tile 12: Top-Right tech bracket set_row(12, 0, row_left(0, 8, 8, 8), row_right(8, 8, 8, 9)); for (size_t r = 1; r < 7; ++r) { set_row(12, r, row_left(0, 0, 0, 0), row_right(0, 0, 0, 8)); } // Tile 13: Bottom-Left tech bracket for (size_t r = 1; r < 7; ++r) { set_row(13, r, row_left(8, 0, 0, 0), row_right(0, 0, 0, 0)); } set_row(13, 7, row_left(9, 8, 8, 8), row_right(8, 8, 8, 0)); // Tile 14: Bottom-Right tech bracket for (size_t r = 1; r < 7; ++r) { set_row(14, r, row_left(0, 0, 0, 0), row_right(0, 0, 0, 8)); } set_row(14, 7, row_left(0, 8, 8, 8), row_right(8, 8, 8, 9)); // Tile 15: Tech border line (dashed) set_row(15, 0, row_left(8, 8, 0, 8), row_right(8, 0, 8, 8)); set_row(15, 2, row_left(0, 1, 1, 0), row_right(0, 1, 1, 0)); } // 32x32 = 1024 map entries for Sub Screen (Playfield) and Main Screen (HUD/Menu) uint16_t sub_map[1024]; uint16_t main_map[1024]; constexpr uint16_t map_entry(uint16_t tile, uint16_t pal = 1) { return static_cast((tile & 0x03FFU) | ((pal & 0x0FU) << 12U)); } void generate_sub_map() { for (size_t y = 0; y < 32; ++y) { for (size_t x = 0; x < 32; ++x) { const size_t idx = y * 32 + x; // Default to crisp grid intersection dot uint16_t tile = 1; // Major coordinate marks every 4 cells if ((x % 4 == 0) && (y % 4 == 0)) { tile = 2; } sub_map[idx] = map_entry(tile); } } // Curated celestial constellations & circuit clusters struct NodePlacement { uint8_t x, y, tile; }; const NodePlacement nodes[] = { // Sparkling stars {3, 2, 4}, {7, 5, 5}, {14, 3, 3}, {19, 6, 4}, {27, 2, 5}, {5, 9, 3}, {11, 12, 5}, {22, 10, 4}, {29, 14, 3}, {2, 17, 4}, {8, 19, 5}, {16, 16, 3}, {25, 20, 5}, {13, 22, 4}, {20, 23, 3}, // Wraparound stars (rows 24..31) {4, 26, 4}, {12, 28, 5}, {18, 25, 3}, {28, 27, 4}, {22, 30, 5}, // Circuit pathway 1 {10, 7, 8}, {11, 7, 6}, {12, 7, 9}, {12, 8, 7}, {12, 9, 10}, // Circuit pathway 2 {23, 15, 6}, {24, 15, 8}, {24, 16, 7}, {24, 17, 9}, {25, 17, 6}, }; for (const auto &n : nodes) { sub_map[n.y * 32 + n.x] = map_entry(n.tile); } } void generate_main_map() { for (size_t y = 0; y < 32; ++y) { for (size_t x = 0; x < 32; ++x) { const size_t idx = y * 32 + x; uint16_t tile = 0; // Subtle ambient stars in upper area (rows 1..15) if (y >= 1 && y <= 15) { if ((x * 7 + y * 13) % 23 == 0) { tile = 3; } else if ((x * 11 + y * 17) % 37 == 0) { tile = 4; } } // Scrolling retro horizon / cyber grid in lower area (rows 16..22 and 24..30) if ((y >= 16 && y <= 22) || (y >= 24 && y <= 30)) { if (y % 2 == 0) { tile = 6; // Horizontal grid line } else if (x % 4 == 0) { tile = 7; // Vertical perspective trace } else { tile = 1; // Subtle grid dot } } main_map[idx] = map_entry(tile); } } // High-tech terminal corner brackets (framing standard 32x24 screen) main_map[0 * 32 + 0] = map_entry(11); // Top-Left main_map[0 * 32 + 31] = map_entry(12); // Top-Right main_map[23 * 32 + 0] = map_entry(13); // Bottom-Left main_map[23 * 32 + 31] = map_entry(14); // Bottom-Right // Top and bottom tech header lines for (size_t x = 1; x < 31; ++x) { main_map[0 * 32 + x] = map_entry(15); main_map[23 * 32 + x] = map_entry(15); } } // Sine table (32 entries, values 0..31 scaled) const uint8_t SINE_32[32] = { 16, 19, 22, 25, 27, 29, 31, 31, 31, 29, 27, 25, 22, 19, 16, 12, 9, 6, 4, 2, 0, 0, 0, 2, 4, 6, 9, 12, 16, 19, 22, 25}; // Color interpolation helper constexpr uint16_t lerp_color(uint16_t c1, uint16_t c2, uint8_t t, uint8_t max_t = 31) { const int r1 = c1 & 31; const int g1 = (c1 >> 5U) & 31; const int b1 = (c1 >> 10U) & 31; const int r2 = c2 & 31; const int g2 = (c2 >> 5U) & 31; const int b2 = (c2 >> 10U) & 31; const int r = r1 + ((r2 - r1) * static_cast(t)) / static_cast(max_t); const int g = g1 + ((g2 - g1) * static_cast(t)) / static_cast(max_t); const int b = b1 + ((b2 - b1) * static_cast(t)) / static_cast(max_t); return make_rgb15(static_cast(r), static_cast(g), static_cast(b)); } } // namespace void bg_setup(BackgroundContext &bg_ctx, int top_console_bg_id, int bottom_console_bg_id) { // Initialize Background Layer 2 on Main Engine (Top Screen) // mapBase 28 (offset 56KB), tileBase 1 (offset 16KB) bg_ctx.bg_main_id = bgInit(2, BgType_Text4bpp, BgSize_T_256x256, 28, 1); // Initialize Background Layer 2 on Sub Engine (Bottom Screen / Playfield) bg_ctx.bg_sub_id = bgInitSub(2, BgType_Text4bpp, BgSize_T_256x256, 28, 1); // Set Priorities: // Main Engine: Console (Priority 0) in front of Background (Priority 3) bgSetPriority(top_console_bg_id, 0); bgSetPriority(bg_ctx.bg_main_id, 3); // Sub Engine: Sprites (Priority 0) in front of Console (Priority 1) in front // of Background (Priority 3) bgSetPriority(bottom_console_bg_id, 1); bgSetPriority(bg_ctx.bg_sub_id, 3); // Generate graphics and maps generate_bg_tiles(); generate_sub_map(); generate_main_map(); // DMA tiles to VRAM (tileBase 1) dmaCopy(bg_tiles, bgGetGfxPtr(bg_ctx.bg_main_id), sizeof(bg_tiles)); dmaCopy(bg_tiles, bgGetGfxPtr(bg_ctx.bg_sub_id), sizeof(bg_tiles)); // DMA tilemaps to VRAM (mapBase 28) dmaCopy(main_map, bgGetMapPtr(bg_ctx.bg_main_id), sizeof(main_map)); dmaCopy(sub_map, bgGetMapPtr(bg_ctx.bg_sub_id), sizeof(sub_map)); // Initialize scroll positions bg_ctx.scroll_main_x_fp = 0; bg_ctx.scroll_main_y_fp = 0; bg_ctx.scroll_sub_x_fp = 0; bg_ctx.scroll_sub_y_fp = 0; // Latch initial background states bgSetScroll(bg_ctx.bg_main_id, 0, 0); bgSetScroll(bg_ctx.bg_sub_id, 0, 0); bgUpdate(); } void bg_trigger_pulse(BackgroundContext &bg_ctx) { bg_ctx.pulse_timer = 16; // 16 frames of radiant energy pulse } void bg_set_scene(BackgroundContext &bg_ctx, Scene scene, bool hard_mode) { bg_ctx.scene = scene; bg_ctx.hard_mode = hard_mode; if (scene == Scene::Menu) { bg_ctx.anim_phase_fp = 0; } } void bg_update(BackgroundContext &bg_ctx, const GameState &state, uint8_t /*frame*/) { const bool in_game = (state.scene == Scene::Playing); const uint8_t intensity_31 = in_game ? calculate_intensity_31(state) : 0U; // Animation phase with fractional accumulator: pulses faster at higher intensity const uint16_t phase_step = static_cast( 256U + ((static_cast(intensity_31) * 280U) / 31U)); bg_ctx.anim_phase_fp = static_cast(bg_ctx.anim_phase_fp + phase_step); bg_ctx.anim_phase = static_cast((bg_ctx.anim_phase_fp >> 8U) & 31U); if (bg_ctx.pulse_timer > 0) { bg_ctx.pulse_timer--; } const bool cursed = (state.curseTicksRemaining > 0); const bool hard = state.hardMode; const bool over = state.gameOver; if (over) { if (bg_ctx.game_over_fade < 31) { bg_ctx.game_over_fade++; } } else { bg_ctx.game_over_fade = 0; } // 1. Hardware Scrolling Update if (state.scene == Scene::Menu) { // Menu: Majestic downward drift for the cyber-horizon feel bg_ctx.scroll_main_y_fp = static_cast(bg_ctx.scroll_main_y_fp + 40U); // ~0.15 px/frame bg_ctx.scroll_main_x_fp = static_cast(bg_ctx.scroll_main_x_fp + 16U); // ~0.06 px/frame bg_ctx.scroll_sub_y_fp = static_cast(bg_ctx.scroll_sub_y_fp + 32U); bg_ctx.scroll_sub_x_fp = static_cast(bg_ctx.scroll_sub_x_fp + 24U); } else { // In-Game Playfield: if (cursed && !over) { // High-speed warp and glitch jitter when cursed! const int16_t jitter = (bg_ctx.anim_phase % 4U == 0U) ? 0x0180 : -0x0100; const uint16_t warp_intensity = static_cast( (static_cast(intensity_31) * 0x00A0U) / 31U); bg_ctx.scroll_sub_x_fp = static_cast( bg_ctx.scroll_sub_x_fp + 0x0120U + warp_intensity + static_cast(jitter)); bg_ctx.scroll_sub_y_fp = static_cast( bg_ctx.scroll_sub_y_fp + 0x00E0U + (warp_intensity >> 1U)); } else { // Smooth ambient drift scaling gradually with intensity (decelerating gently if game over) const uint16_t base_x = hard ? 24U : 16U; const uint16_t base_y = hard ? 20U : 12U; const uint16_t max_x = hard ? 76U : 56U; const uint16_t max_y = hard ? 62U : 42U; const uint16_t speed_x = static_cast( base_x + ((static_cast(intensity_31) * (max_x - base_x)) / 31U)); const uint16_t speed_y = static_cast( base_y + ((static_cast(intensity_31) * (max_y - base_y)) / 31U)); uint16_t sub_step_x = speed_x; uint16_t sub_step_y = speed_y; if (bg_ctx.game_over_fade > 0) { const uint8_t fade = bg_ctx.game_over_fade; sub_step_x = static_cast( speed_x + ((static_cast(4U - speed_x) * fade) / 31)); sub_step_y = static_cast( speed_y - ((static_cast(speed_y) * fade) / 31)); } bg_ctx.scroll_sub_x_fp = static_cast(bg_ctx.scroll_sub_x_fp + sub_step_x); bg_ctx.scroll_sub_y_fp = static_cast(bg_ctx.scroll_sub_y_fp + sub_step_y); } // Top screen HUD / horizon drift scaling with intensity (halting on game over) uint16_t speed_main_y = static_cast( 16U + ((static_cast(intensity_31) * 24U) / 31U)); uint16_t speed_main_x = static_cast( 8U + ((static_cast(intensity_31) * 12U) / 31U)); if (bg_ctx.game_over_fade > 0) { const uint8_t fade = bg_ctx.game_over_fade; speed_main_y = static_cast( speed_main_y - ((static_cast(speed_main_y) * fade) / 31)); speed_main_x = static_cast( speed_main_x - ((static_cast(speed_main_x) * fade) / 31)); } bg_ctx.scroll_main_y_fp = static_cast(bg_ctx.scroll_main_y_fp + speed_main_y); bg_ctx.scroll_main_x_fp = static_cast(bg_ctx.scroll_main_x_fp + speed_main_x); } bgSetScroll(bg_ctx.bg_main_id, bg_ctx.scroll_main_x_fp >> 8U, bg_ctx.scroll_main_y_fp >> 8U); bgSetScroll(bg_ctx.bg_sub_id, bg_ctx.scroll_sub_x_fp >> 8U, bg_ctx.scroll_sub_y_fp >> 8U); bgUpdate(); // 2. Dynamic Palette Animation during VBlank // Palette Bank 1 (indices 16..31) in BG_PALETTE and BG_PALETTE_SUB const uint8_t sine_val = SINE_32[bg_ctx.anim_phase]; const uint8_t sine_val_opposite = SINE_32[(bg_ctx.anim_phase + 16U) & 31U]; // Base theme definitions uint16_t col_faint = 0; uint16_t col_subtle = 0; uint16_t col_primary = 0; uint16_t col_star_dim = 0; uint16_t col_star_med = 0; uint16_t col_star_bright = 0; uint16_t col_star_flare = 0; uint16_t col_frame = 0; uint16_t col_frame_accent = 0; if (cursed && !over) { // Radioactive neon purple / violet storm const bool strobe = (bg_ctx.anim_phase % 6U < 3U); col_faint = make_rgb15(8, 1, 10); col_subtle = make_rgb15(14, 2, 18); col_primary = strobe ? make_rgb15(24, 4, 30) : make_rgb15(18, 2, 24); col_star_dim = make_rgb15(10, 0, 14); col_star_med = make_rgb15(18, 0, 24); col_star_bright = make_rgb15(26, 4, 30); col_star_flare = make_rgb15(31, 16, 31); col_frame = make_rgb15(18, 2, 22); col_frame_accent = make_rgb15(28, 6, 31); } else if (hard) { // Fiery amber / crimson cyber grid interpolating towards volcanic hyper-crimson (toned darker for high item contrast) const uint16_t c_faint_0 = make_rgb15(3, 1, 1); const uint16_t c_subtle_0 = make_rgb15(6, 2, 1); const uint16_t c_primary_0 = make_rgb15(12, 4, 2); const uint16_t c_star_dim_0 = make_rgb15(6, 2, 1); const uint16_t c_star_med_0 = make_rgb15(11, 4, 2); const uint16_t c_star_bright_0 = make_rgb15(20, 9, 3); const uint16_t c_star_flare_0 = make_rgb15(28, 18, 6); const uint16_t c_frame_0 = make_rgb15(9, 3, 1); const uint16_t c_frame_accent_0 = make_rgb15(18, 6, 2); const uint16_t c_faint_max = make_rgb15(5, 2, 1); const uint16_t c_subtle_max = make_rgb15(9, 3, 2); const uint16_t c_primary_max = make_rgb15(18, 6, 2); const uint16_t c_star_dim_max = make_rgb15(9, 3, 1); const uint16_t c_star_med_max = make_rgb15(16, 6, 2); const uint16_t c_star_bright_max = make_rgb15(26, 12, 4); const uint16_t c_star_flare_max = make_rgb15(31, 26, 10); const uint16_t c_frame_max = make_rgb15(15, 5, 2); const uint16_t c_frame_accent_max = make_rgb15(25, 10, 3); col_faint = lerp_color(c_faint_0, c_faint_max, intensity_31); col_subtle = lerp_color(c_subtle_0, c_subtle_max, intensity_31); col_primary = lerp_color(c_primary_0, c_primary_max, intensity_31); col_star_dim = lerp_color(c_star_dim_0, c_star_dim_max, intensity_31); col_star_med = lerp_color(c_star_med_0, c_star_med_max, intensity_31); col_star_bright = lerp_color(c_star_bright_0, c_star_bright_max, intensity_31); col_star_flare = lerp_color(c_star_flare_0, c_star_flare_max, intensity_31); col_frame = lerp_color(c_frame_0, c_frame_max, intensity_31); col_frame_accent = lerp_color(c_frame_accent_0, c_frame_accent_max, intensity_31); } else { // Neon Cyan / Teal interpolating towards high-energy electric plasma cyan const uint16_t c_faint_0 = make_rgb15(1, 4, 7); const uint16_t c_subtle_0 = make_rgb15(2, 9, 14); const uint16_t c_primary_0 = make_rgb15(4, 16, 22); const uint16_t c_star_dim_0 = make_rgb15(2, 6, 12); const uint16_t c_star_med_0 = make_rgb15(5, 14, 22); const uint16_t c_star_bright_0 = make_rgb15(10, 24, 30); const uint16_t c_star_flare_0 = make_rgb15(22, 30, 31); const uint16_t c_frame_0 = make_rgb15(3, 12, 18); const uint16_t c_frame_accent_0 = make_rgb15(6, 22, 28); const uint16_t c_faint_max = make_rgb15(2, 7, 12); const uint16_t c_subtle_max = make_rgb15(4, 15, 24); const uint16_t c_primary_max = make_rgb15(8, 25, 31); const uint16_t c_star_dim_max = make_rgb15(4, 12, 22); const uint16_t c_star_med_max = make_rgb15(10, 22, 30); const uint16_t c_star_bright_max = make_rgb15(18, 29, 31); const uint16_t c_star_flare_max = make_rgb15(31, 31, 31); const uint16_t c_frame_max = make_rgb15(6, 20, 28); const uint16_t c_frame_accent_max = make_rgb15(12, 30, 31); col_faint = lerp_color(c_faint_0, c_faint_max, intensity_31); col_subtle = lerp_color(c_subtle_0, c_subtle_max, intensity_31); col_primary = lerp_color(c_primary_0, c_primary_max, intensity_31); col_star_dim = lerp_color(c_star_dim_0, c_star_dim_max, intensity_31); col_star_med = lerp_color(c_star_med_0, c_star_med_max, intensity_31); col_star_bright = lerp_color(c_star_bright_0, c_star_bright_max, intensity_31); col_star_flare = lerp_color(c_star_flare_0, c_star_flare_max, intensity_31); col_frame = lerp_color(c_frame_0, c_frame_max, intensity_31); col_frame_accent = lerp_color(c_frame_accent_0, c_frame_accent_max, intensity_31); } // Smoothly blend toward somber dark charcoal / muted red tone if game over if (bg_ctx.game_over_fade > 0) { const uint8_t fade = bg_ctx.game_over_fade; col_faint = lerp_color(col_faint, make_rgb15(3, 2, 2), fade); col_subtle = lerp_color(col_subtle, make_rgb15(6, 3, 3), fade); col_primary = lerp_color(col_primary, make_rgb15(10, 4, 4), fade); col_star_dim = lerp_color(col_star_dim, make_rgb15(5, 2, 2), fade); col_star_med = lerp_color(col_star_med, make_rgb15(8, 3, 3), fade); col_star_bright = lerp_color(col_star_bright, make_rgb15(12, 4, 4), fade); col_star_flare = lerp_color(col_star_flare, make_rgb15(16, 6, 6), fade); col_frame = lerp_color(col_frame, make_rgb15(8, 3, 3), fade); col_frame_accent = lerp_color(col_frame_accent, make_rgb15(12, 4, 4), fade); } // Deep space backdrop color dynamically illuminates with intensity if (in_game) { uint16_t bg_sub_target = 0; uint16_t bg_main_target = 0; if (hard) { bg_sub_target = lerp_color(make_rgb15(3, 1, 1), make_rgb15(6, 2, 2), intensity_31); bg_main_target = lerp_color(make_rgb15(2, 1, 1), make_rgb15(5, 1, 2), intensity_31); } else { bg_sub_target = lerp_color(make_rgb15(3, 4, 7), make_rgb15(5, 7, 12), intensity_31); bg_main_target = lerp_color(make_rgb15(2, 3, 6), make_rgb15(3, 5, 10), intensity_31); } if (bg_ctx.game_over_fade > 0) { BG_PALETTE_SUB[0] = lerp_color(bg_sub_target, make_rgb15(3, 2, 2), bg_ctx.game_over_fade); BG_PALETTE[0] = lerp_color(bg_main_target, make_rgb15(2, 1, 1), bg_ctx.game_over_fade); } else { BG_PALETTE_SUB[0] = bg_sub_target; BG_PALETTE[0] = bg_main_target; } } else { BG_PALETTE_SUB[0] = make_rgb15(2, 2, 6); BG_PALETTE[0] = make_rgb15(2, 2, 6); } // Animate twinkling stars using sine oscillations const uint16_t anim_dim = lerp_color(col_star_dim, col_star_med, sine_val); const uint16_t anim_med = lerp_color(col_star_med, col_star_bright, sine_val_opposite); const uint16_t anim_bright = lerp_color(col_star_bright, col_star_flare, sine_val); // Apple pickup wave pulse: flares bright energy through grid and stars with extra intensity uint16_t pulse_add = 0; if (bg_ctx.pulse_timer > 0) { const uint8_t pulse_val = bg_ctx.pulse_timer; const uint32_t boost = (static_cast(intensity_31) * 3U) / 31U; pulse_add = (pulse_val > 10U) ? make_rgb15(6U + boost, 12U + boost, 14U + boost) : make_rgb15(2U + (boost >> 1U), 5U + (boost >> 1U), 6U + (boost >> 1U)); } // Write Palette Bank 1 (indices 16..31) for both engines // Sub Screen (Playfield): BG_PALETTE_SUB[16] = 0; // Transparent (reveals backdrop color) BG_PALETTE_SUB[17] = col_faint; BG_PALETTE_SUB[18] = static_cast(col_subtle + pulse_add); BG_PALETTE_SUB[19] = static_cast(col_primary + pulse_add); BG_PALETTE_SUB[20] = anim_dim; BG_PALETTE_SUB[21] = anim_med; BG_PALETTE_SUB[22] = static_cast(anim_bright + pulse_add); BG_PALETTE_SUB[23] = col_star_flare; BG_PALETTE_SUB[24] = col_frame; BG_PALETTE_SUB[25] = col_frame_accent; BG_PALETTE_SUB[26] = col_faint; BG_PALETTE_SUB[27] = col_subtle; BG_PALETTE_SUB[28] = col_primary; BG_PALETTE_SUB[29] = anim_med; BG_PALETTE_SUB[30] = col_frame_accent; BG_PALETTE_SUB[31] = make_rgb15(31, 31, 31); // Main Screen (Top Screen HUD & Menu): BG_PALETTE[16] = 0; // Transparent BG_PALETTE[17] = col_faint; BG_PALETTE[18] = col_subtle; BG_PALETTE[19] = col_primary; BG_PALETTE[20] = anim_dim; BG_PALETTE[21] = anim_med; BG_PALETTE[22] = anim_bright; BG_PALETTE[23] = col_star_flare; BG_PALETTE[24] = col_frame; BG_PALETTE[25] = col_frame_accent; BG_PALETTE[26] = col_faint; BG_PALETTE[27] = col_subtle; BG_PALETTE[28] = col_primary; BG_PALETTE[29] = anim_med; BG_PALETTE[30] = col_frame_accent; BG_PALETTE[31] = make_rgb15(31, 31, 31); }