"""Execute shipping MASM maze/entry/collision/render, inspect native output.""" from collections import deque from pathlib import Path import struct import subprocess from PIL import Image from check_artifacts import toolchain ROOT = Path(__file__).resolve().parents[2] OUT = ROOT / 'build/scratch' def check_exit_faces(): raw = (OUT/'backrooms-exit-faces.bin').read_bytes() assert len(raw) == 8*640*480*4 frames = [Image.frombytes('RGBA', (640, 480), raw[i:i+640*480*4], 'raw', 'BGRA').convert('RGB') for i in range(0, len(raw), 640*480*4)] # Source glyph at runtime's exact nearest-neighbor UV scale. Compare both # orientations rather than merely counting green pixels (mirrors pass that). pack = (ROOT/'assets/sprites/backrooms/materials.bin').read_bytes() art = Image.frombytes('RGBA', (128, 128), pack[20+4*65536:], 'raw', 'BGRA') glyph = [(x, y) for y in range(48, 70) for x in range(27, 101) if min(art.getpixel((x, y))[:3]) > 240] for face, name in enumerate(('north', 'south', 'west', 'east')): clean, vhs = frames[2*face:2*face+2] clean.save(OUT/f'backrooms-exit-{name}.png') vhs.save(OUT/f'backrooms-exit-{name}-vhs.png') white = [(x, y) for y in range(0, 300) for x in range(60, 580) if min(clean.getpixel((x, y))) > 240] assert white, f'{name}: EXIT missing' x0, x1 = min(x for x, y in white), max(x for x, y in white) y0, y1 = min(y for x, y in white), max(y for x, y in white) gx0, gx1 = min(x for x, y in glyph), max(x for x, y in glyph) gy0, gy1 = min(y for x, y in glyph), max(y for x, y in glyph) def score(mirror): hits = 0 for x, y in glyph: u = (x-gx0)/(gx1-gx0) if mirror: u = 1-u px = round(x0+u*(x1-x0)) py = round(y0+(y-gy0)/(gy1-gy0)*(y1-y0)) hits += min(clean.getpixel((px, py))) > 240 return hits/len(glyph) normal, mirrored = score(False), score(True) assert normal > .85 and normal > mirrored+.12, (name, normal, mirrored) # White glyphs may darken on scanlines, never wrap into black/color noise. corrupt = sum(min(vhs.getpixel(p)) < 180 or max(vhs.getpixel(p))-min(vhs.getpixel(p)) > 12 for p in white) assert corrupt == 0, (name, 'VHS corrupt white pixels', corrupt, len(white)) def main(): OUT.mkdir(parents=True, exist_ok=True) ml, link = toolchain() sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib') lib = next(p/'um/x86' for p in sorted(sdk.iterdir(), reverse=True) if (p/'um/x86/kernel32.lib').exists()) for args in ([ml, '/c', '/coff', '/Cp', '/nologo', '/I.', '/DBACKROOMS_TEST=1', '/Fobuild/scratch/backrooms.obj', 'poker.asm'], [link, '/nologo', '/SUBSYSTEM:CONSOLE', '/LIBPATH:'+str(lib), '/OUT:build/scratch/backrooms-test.exe', 'build/scratch/backrooms.obj', 'kernel32.lib', 'winmm.lib', 'user32.lib', 'ws2_32.lib', 'gdi32.lib']): subprocess.run(list(map(str, args)), cwd=ROOT, check=True, timeout=120) subprocess.run([str(OUT/'backrooms-test.exe')], cwd=ROOT, check=True, timeout=90) raw = (OUT/'backrooms-native.bin').read_bytes() assert len(raw) == 256 * 1052 fingerprints = set() minimum = 10000 for offset in range(0, len(raw), 1052): grid = raw[offset:offset+1024] exit_cell, *games = struct.unpack_from('<7I', raw, offset+1024) assert all(grid[x] for x in range(32)) assert all(grid[31*32+x] for x in range(32)) assert all(grid[y*32] and grid[y*32+31] for y in range(32)) distance, queue = {33: 0}, deque([33]) while queue: cell = queue.popleft() for neighbor in (cell-32, cell+1, cell+32, cell-1): if grid[neighbor] == 0 and neighbor not in distance: distance[neighbor] = distance[cell]+1 queue.append(neighbor) assert len(distance) == grid.count(0), 'Disconnected open component' assert distance[exit_cell] == max(distance.values()) assert len(set(games)) == 6 and exit_cell not in games and 33 not in games assert all(cell in distance for cell in games) minimum = min(minimum, distance[exit_cell]) fingerprints.add(grid) assert len(fingerprints) == 256 assert minimum >= 40, minimum luck = (OUT/'backrooms-luck.bin').read_bytes() assert len(luck) == 1000*452 wins = dict.fromkeys(('coin', 'dice', '777', 'clover', 'blackjack', 'poker'), 0) for row in struct.iter_unpack('<113I', luck): coin, d1, d2, s1, s2, s3, clover, bj = row[:8] assert coin in (1, 2) and 1 <= d1 <= 6 and 1 <= d2 <= 6 wins['coin'] += coin == 1 wins['dice'] += (d1+d2) % 2 == 0 assert (s1, s2, s3) in ((1, 1, 1), (2, 5, 3)) wins['777'] += s1 == s2 == s3 assert clover in (0, 78) wins['clover'] += clover > 0 deck, poker, button = row[8:60], row[60:112], row[112] assert sorted(deck) == sorted(poker) == list(range(52)), 'Duplicate/missing card' assert bj in (19, 21) assert bj == (21 if deck[0] == 12 else 19) wins['blackjack'] += bj == 21 # Actual non-button-first deal order, plus actual burn/flop/turn/river offsets. seat = button ^ 1 hand = [poker[seat], poker[seat+2], *(poker[i] for i in (5, 6, 7, 9, 11))] wins['poker'] += set(range(8, 13)).issubset(hand) assert all(860 <= n <= 940 for n in wins.values()), wins image = Image.frombytes('RGBA', (640, 480), (OUT/'backrooms-frame.bin').read_bytes(), 'raw', 'BGRA').convert('RGB') image.save(OUT/'backrooms-frame.png') assert len(image.getcolors(640*480)) > 100 image = Image.frombytes('RGBA', (640, 480), (OUT/'backrooms-exit.bin').read_bytes(), 'raw', 'BGRA').convert('RGB') image.save(OUT/'backrooms-exit.png') assert sum(g > 1.4*r and g > 1.2*b for r, g, b in image.get_flattened_data()) > 100, 'Exit not visibly green' check_exit_faces() print('PASS: readable non-mirrored EXIT on all four wall faces; VHS preserves white glyphs') print(f'PASS: 256 native seeds; sealed connected mazes; farthest exit >= {minimum} steps; unique reachable games; entry hold/reset; collision; isolation; profile roundtrip; render') print(f'PASS: 1000 production rounds per game, valid unique decks, wins: {wins}') print('PASS: all blackjack party sizes; 7 scratch designs; roulette; Durak conservation/trump; 9 Russian party/bullet setups; Buckshot counts; preview saves; repeat reward') if __name__ == '__main__': main()