diff --git a/defects/mednafen-0001/patch/mednafen-0001.patch b/defects/mednafen-0001/patch/mednafen-0001.patch new file mode 100644 index 000000000..ae07d3d47 --- /dev/null +++ b/defects/mednafen-0001/patch/mednafen-0001.patch @@ -0,0 +1,58 @@ +# UNDF: UNDF-2026-000001042 +--- a/mednafen/mempatcher.cpp ++++ b/mednafen/mempatcher.cpp +@@ -476,15 +476,55 @@ bool MDFNI_DecodeGBGG(const char *instr, uint32 *a, uint8 *v, uint8 *c, char *t + } + +-static int GGtobin(char c) ++/* ++ * GGtobin: map a Game Genie nibble character to its 4-bit value. ++ * ++ * Original: O(16) linear scan over a 16-char lookup string, called 8 times ++ * per code decode (MDFNI_DecodeGG) -- up to 128 comparisons per 8-char ++ * Game Genie code. The character set is fixed and known at compile time. ++ * ++ * Fix: replace with a 256-byte direct-index LUT initialised at compile time. ++ * Every GGtobin() call becomes a single array access O(1), reducing decode ++ * cost from O(16*8) = 128 comparisons to 8 array reads. ++ * ++ * Game Genie NES alphabet: A=0 P=1 Z=2 L=3 G=4 I=5 T=6 Y=7 ++ * E=8 O=9 X=10 U=11 K=12 S=13 V=14 N=15 ++ * ++ * Complexity before: O(16) per character, O(128) per 8-char code ++ * Complexity after: O(1) per character, O(8) per 8-char code ++ * Speedup: 16x per character lookup ++ */ ++#define _ (-1) ++static const signed char GGLut[256] = { ++/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ ++/* 00 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* 10 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* 20 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* 30 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* 40 */ _, 0, _, _, _, 8, _, 4, _, 5, _, 12, 3, _, 15, 9, ++/* 50 */ 1, _, _, 13, 6, 11, 14, _, 10, 7, 2, _, _, _, _, _, ++/* 60 */ _, 0, _, _, _, 8, _, 4, _, 5, _, 12, 3, _, 15, 9, ++/* 70 */ 1, _, _, 13, 6, 11, 14, _, 10, 7, 2, _, _, _, _, _, ++/* 80 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* 90 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* A0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* B0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* C0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* D0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* E0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++/* F0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ++}; ++#undef _ ++ ++static int GGtobin(char c) + { +- static char lets[16]={'A','P','Z','L','G','I','T','Y','E','O','X','U','K','S','V','N'}; +- int x; +- +- for(x=0;x<16;x++) +- if(lets[x] == toupper(c)) return(x); +- return(0); ++ int v = (int)GGLut[(unsigned char)c]; ++ return (v < 0) ? 0 : v; + } diff --git a/defects/mednafen-0001/test/test_mednafen_0001 b/defects/mednafen-0001/test/test_mednafen_0001 new file mode 100755 index 000000000..bd509fc17 Binary files /dev/null and b/defects/mednafen-0001/test/test_mednafen_0001 differ diff --git a/defects/mednafen-0001/test/test_mednafen_0001.c b/defects/mednafen-0001/test/test_mednafen_0001.c new file mode 100644 index 000000000..85464ea2a --- /dev/null +++ b/defects/mednafen-0001/test/test_mednafen_0001.c @@ -0,0 +1,242 @@ +/* + * test_mednafen_0001.c + * + * Unit test for mednafen-0001: GGtobin() O(16) linear scan over a 16-char + * Game Genie alphabet replaced with a 256-byte direct-index LUT for O(1). + * + * Validates that the LUT produces identical results to the original linear scan + * for all 256 input bytes, and measures the iteration reduction. + */ + +#include +#include +#include +#include +#include + +/* ---- original O(16) implementation ---- */ + +static int GGtobin_linear(char c) +{ + static char lets[16] = {'A','P','Z','L','G','I','T','Y','E','O','X','U','K','S','V','N'}; + int x; + for (x = 0; x < 16; x++) + if (lets[x] == toupper((unsigned char)c)) return x; + return 0; +} + +/* Count actual iterations for one character */ +static int GGtobin_linear_iters(char c) +{ + static char lets[16] = {'A','P','Z','L','G','I','T','Y','E','O','X','U','K','S','V','N'}; + int x; + for (x = 0; x < 16; x++) { + if (lets[x] == toupper((unsigned char)c)) return x + 1; + } + return 16; /* scanned all, fell through to return 0 */ +} + +/* ---- O(1) LUT implementation ---- */ +/* + * Game Genie NES alphabet: A=0 P=1 Z=2 L=3 G=4 I=5 T=6 Y=7 + * E=8 O=9 X=10 U=11 K=12 S=13 V=14 N=15 + * Generated from: static char lets[16]={'A','P','Z','L','G','I','T','Y','E','O','X','U','K','S','V','N'}; + * LUT indexed by ASCII byte value; -1 = not in alphabet. + */ +#define _ (-1) +static const signed char GGLut[256] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ +/* 00 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* 10 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* 20 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* 30 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* 40 */ _, 0, _, _, _, 8, _, 4, _, 5, _, 12, 3, _, 15, 9, +/* 50 */ 1, _, _, 13, 6, 11, 14, _, 10, 7, 2, _, _, _, _, _, +/* 60 */ _, 0, _, _, _, 8, _, 4, _, 5, _, 12, 3, _, 15, 9, +/* 70 */ 1, _, _, 13, 6, 11, 14, _, 10, 7, 2, _, _, _, _, _, +/* 80 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* 90 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* A0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* B0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* C0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* D0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* E0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +/* F0 */ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +}; +#undef _ + +static int GGtobin_lut(char c) +{ + int v = (int)GGLut[(unsigned char)c]; + return (v < 0) ? 0 : v; /* preserve original return-0 on unknown */ +} + +/* ---- Game Genie decode stub to verify end-to-end ---- */ + +static int decode_gg_6(const char *str, int use_lut, uint16_t *a_out, uint8_t *v_out) +{ + int (*fn)(char) = use_lut ? GGtobin_lut : GGtobin_linear; + uint16_t A = 0x8000; + uint8_t V = 0; + uint8_t t; + if (strlen(str) != 6) return 0; + + t = fn(*str++); V |= (t & 0x07); V |= (t & 0x08) << 4; + t = fn(*str++); V |= (t & 0x07) << 4; A |= (t & 0x08) << 4; + t = fn(*str++); A |= (t & 0x07) << 4; + t = fn(*str++); A |= (t & 0x07) << 12; A |= (t & 0x08); + t = fn(*str++); A |= (t & 0x07); A |= (t & 0x08) << 8; + t = fn(*str++); A |= (t & 0x07) << 8; V |= (t & 0x08); + + *a_out = A; + *v_out = V; + return 1; +} + +int main(void) +{ + int i, tests = 0, pass = 0; + + /* Test 1: LUT matches linear scan for all 16 valid chars (uppercase) */ + printf("Test 1: LUT matches linear for all 16 valid uppercase chars\n"); + { + static const char valid[] = "APZLGITYEOXUKSVN"; + for (i = 0; i < 16; i++) { + int lin = GGtobin_linear(valid[i]); + int lut = GGtobin_lut(valid[i]); + tests++; + if (lin == lut) pass++; + else printf(" FAIL char='%c' linear=%d lut=%d\n", valid[i], lin, lut); + } + printf(" %d/%d\n", pass, tests); + } + + /* Test 2: LUT matches linear scan for all 16 valid chars (lowercase) */ + printf("Test 2: LUT matches linear for all 16 valid lowercase chars\n"); + { + static const char valid_lc[] = "apzlgityeoxuksvn"; + for (i = 0; i < 16; i++) { + int lin = GGtobin_linear(valid_lc[i]); + int lut = GGtobin_lut(valid_lc[i]); + tests++; + if (lin == lut) pass++; + else printf(" FAIL char='%c' linear=%d lut=%d\n", valid_lc[i], lin, lut); + } + printf(" %d/%d\n", pass, tests); + } + + /* Test 3: correct nibble values for known chars */ + printf("Test 3: correct nibble values for known chars\n"); + { + struct { char c; int expected; } cases[] = { + {'A', 0}, {'P', 1}, {'Z', 2}, {'L', 3}, + {'G', 4}, {'I', 5}, {'T', 6}, {'Y', 7}, + {'E', 8}, {'O', 9}, {'X',10}, {'U',11}, + {'K',12}, {'S',13}, {'V',14}, {'N',15}, + }; + for (i = 0; i < 16; i++) { + tests++; + int v = GGtobin_lut(cases[i].c); + if (v == cases[i].expected) pass++; + else printf(" FAIL '%c': expected %d got %d\n", + cases[i].c, cases[i].expected, v); + } + printf(" %d/%d\n", pass, tests); + } + + /* Test 4: end-to-end 6-char decode identical between linear and LUT */ + printf("Test 4: end-to-end 6-char decode identical\n"); + { + /* Synthetic codes exercising different nibble positions */ + static const char *codes[] = { + "SXIOPO", /* uses S=13, X=10, I=5, O=9, P=1, O=9 */ + "YEAAAA", /* Y=7, E=8, A=0, A=0, A=0, A=0 */ + "APZLGN", /* all first 5 of alphabet + N */ + "TYVUKS", /* T=6, Y=7, V=14, U=11, K=12, S=13 */ + "NNNNNN", /* all N=15 */ + }; + int ncodes = sizeof(codes) / sizeof(codes[0]); + for (i = 0; i < ncodes; i++) { + uint16_t a_lin, a_lut; + uint8_t v_lin, v_lut; + decode_gg_6(codes[i], 0, &a_lin, &v_lin); + decode_gg_6(codes[i], 1, &a_lut, &v_lut); + tests++; + if (a_lin == a_lut && v_lin == v_lut) { + pass++; + printf(" PASS code=%-8s addr=0x%04x val=0x%02x\n", + codes[i], a_lut, v_lut); + } else { + printf(" FAIL code=%-8s lin=0x%04x/0x%02x lut=0x%04x/0x%02x\n", + codes[i], a_lin, v_lin, a_lut, v_lut); + } + } + } + + /* Test 5: iteration reduction -- O(16) vs O(1) */ + printf("Test 5: iteration count reduction\n"); + { + /* 'N' is last in alphabet: 16 iterations */ + int iter_last = GGtobin_linear_iters('N'); + int iter_first = GGtobin_linear_iters('A'); + int lut_ops = 1; + + printf(" linear 'A' (first): %d iters\n", iter_first); + printf(" linear 'N' (last): %d iters\n", iter_last); + printf(" LUT any char: %d op\n", lut_ops); + printf(" worst-case speedup: %dx\n", iter_last / lut_ops); + + int linear_code_worst = 16 * 8; + int lut_code_cost = 8; + printf(" per 8-char code: linear worst=%d comps, LUT=%d ops, speedup=%dx\n", + linear_code_worst, lut_code_cost, + linear_code_worst / lut_code_cost); + + assert(iter_first == 1); + assert(iter_last == 16); + + tests++; pass++; + } + + /* Test 6: invalid characters handled consistently */ + printf("Test 6: invalid characters\n"); + { + char invalids[] = { 'B', 'C', 'D', 'F', 'H', 'J', 'M', 'Q', 'R', 'W', '0', '1', ' ' }; + int n = (int)sizeof(invalids); + int all_match = 1; + for (i = 0; i < n; i++) { + int lin = GGtobin_linear(invalids[i]); + int lut = GGtobin_lut(invalids[i]); + if (lin != lut) { + all_match = 0; + printf(" FAIL char=0x%02x lin=%d lut=%d\n", + (unsigned char)invalids[i], lin, lut); + } + } + tests++; + if (all_match) { pass++; printf(" PASS: all invalid chars match (both return 0)\n"); } + } + + /* Test 7: LUT matches linear for all 256 byte values */ + printf("Test 7: full 256-byte equivalence\n"); + { + int mismatches = 0; + for (i = 0; i < 256; i++) { + int lin = GGtobin_linear((char)i); + int lut = GGtobin_lut((char)i); + if (lin != lut) { + mismatches++; + if (mismatches <= 5) + printf(" MISMATCH byte=0x%02x lin=%d lut=%d\n", i, lin, lut); + } + } + tests++; + if (mismatches == 0) { pass++; printf(" PASS: all 256 bytes match\n"); } + else printf(" FAIL: %d mismatches\n", mismatches); + } + + printf("\n%d/%d tests passed\n", pass, tests); + if (pass != tests) { printf("FAIL\n"); return 1; } + printf("PASS: O(1) LUT replaces O(16) linear scan in GGtobin()\n"); + return 0; +}