// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // SCB stress screen: the per-scanline palette under the heaviest load a // program can reasonably put on it. Written for the ports that honour the SCB // by reloading ONE hardware palette as the beam scans (the X68000's text-plane // present, the Atari ST's Timer B), where every palette change is a deadline // an interrupt has to meet. Portable C only: nothing here knows which port it // runs on. // // All sixteen palettes are in use, and colour i of each palette differs from // colour i of every other palette, so a line shown in the wrong palette shows // the wrong colour everywhere but in colour 0 (black in every palette by // library contract). Twenty 16-pixel vertical stripes run the full height, // stripe s in colour s mod 16, so every line uses every colour. // // Colour i of palette p is $0RGB with red = p, green = i and // blue = (p * 7 + i) mod 16. // // The SCB, in three zones: // lines 0- 63 a new palette every line: 0, 1, ... 15, four times over // lines 64-135 runs of irregular length: run k is 1 + (k * 5 mod 7) lines // (1, 6, 4, 2, 7, 5, 3, repeating) in palette (k * 7 + 3) mod 16, // the last run cut short by the zone's end // lines 136-199 two palettes alternating every line: 6 on even lines, 9 on // odd ones // Every boundary is a palette change, the frame wrap included (line 199 is // palette 9, line 0 palette 0): 147 changes a frame, back to back for the 64 // lines of each outer zone. scripts/verify-x68000-scb.sh computes the same // screen from the same rules -- change both together. // // Every pass of the loop redraws and presents the whole stage, so the palette // reloads land while the present is streaming into video memory, and sets all // sixteen palettes again -- to the same colours, so the picture never changes // -- so a port that keeps per-band state rebuilds it every pass while the // frame it describes is on screen. Typed characters are read and dropped, so a // gate can keep the keyboard interrupt busy during the check; ESC exits. #include #include #include // ----- Constants ----- #define STRIPE_WIDTH 16u // two bytes of a plane: whole-byte fills #define STRIPE_COUNT (SURFACE_WIDTH / STRIPE_WIDTH) #define COLOR_CHANNEL_MASK 0x0Fu #define RED_SHIFT 8u #define GREEN_SHIFT 4u #define BLUE_PALETTE_STEP 7u // Zone boundaries: [0, EVERY_LINE_END) changes every line, // [EVERY_LINE_END, IRREGULAR_END) runs irregularly, the rest alternates. #define EVERY_LINE_END 64u #define IRREGULAR_END 136u #define RUN_LENGTH_STEP 5u #define RUN_LENGTH_SPAN 7u #define RUN_PALETTE_STEP 7u #define RUN_PALETTE_BASE 3u #define EVEN_LINE_PALETTE 6u #define ODD_LINE_PALETTE 9u #define CODEGEN_DEFAULT 0u // ----- Prototypes ----- static void buildPalettes(jlSurfaceT *screen); static void buildScb(jlSurfaceT *screen); static void drawStripes(jlSurfaceT *screen); static bool escapeTyped(void); // ----- Helpers (alphabetical) ----- static void buildPalettes(jlSurfaceT *screen) { uint16_t colors[SURFACE_COLORS_PER_PALETTE]; uint16_t pal; uint16_t col; uint16_t blue; for (pal = 0u; pal < SURFACE_PALETTE_COUNT; pal++) { for (col = 0u; col < SURFACE_COLORS_PER_PALETTE; col++) { blue = (uint16_t)((pal * BLUE_PALETTE_STEP + col) & COLOR_CHANNEL_MASK); colors[col] = (uint16_t)((pal << RED_SHIFT) | (col << GREEN_SHIFT) | blue); } jlPaletteSet(screen, (uint8_t)pal, colors); } } static void buildScb(jlSurfaceT *screen) { uint16_t line; uint16_t run; uint16_t runLeft; uint8_t runPalette; for (line = 0u; line < EVERY_LINE_END; line++) { jlScbSet(screen, line, (uint8_t)(line % SURFACE_PALETTE_COUNT)); } run = 0u; runLeft = 0u; runPalette = 0u; for (line = EVERY_LINE_END; line < IRREGULAR_END; line++) { if (runLeft == 0u) { runLeft = (uint16_t)(1u + (run * RUN_LENGTH_STEP) % RUN_LENGTH_SPAN); runPalette = (uint8_t)((run * RUN_PALETTE_STEP + RUN_PALETTE_BASE) % SURFACE_PALETTE_COUNT); run++; } jlScbSet(screen, line, runPalette); runLeft--; } for (line = IRREGULAR_END; line < SURFACE_HEIGHT; line++) { jlScbSet(screen, line, (uint8_t)(((line & 1u) != 0u) ? ODD_LINE_PALETTE : EVEN_LINE_PALETTE)); } } static void drawStripes(jlSurfaceT *screen) { uint16_t stripe; for (stripe = 0u; stripe < STRIPE_COUNT; stripe++) { jlFillRect(screen, (int16_t)(stripe * STRIPE_WIDTH), 0, STRIPE_WIDTH, SURFACE_HEIGHT, (uint8_t)(stripe % SURFACE_COLORS_PER_PALETTE)); } } // Drain the typed-character queue; true if ESC was among them. static bool escapeTyped(void) { int ch; bool escape; escape = false; for (ch = jlInputGetChar(); ch >= 0; ch = jlInputGetChar()) { if (ch == JL_CHAR_ESCAPE) { escape = true; } } return escape; } int main(void) { jlConfigT config; jlSurfaceT *screen; config.codegenBytes = CODEGEN_DEFAULT; if (!jlInit(&config)) { fprintf(stderr, "jlInit failed: %s\n", jlLastError()); return EXIT_FAILURE; } screen = jlStageGet(); buildScb(screen); for (;;) { drawStripes(screen); buildPalettes(screen); jlStagePresent(); jlInputPoll(); if (escapeTyped()) { break; } } jlShutdown(); return EXIT_SUCCESS; }