"""Validate roulette assets, native captures and tested/shipping procedure parity.""" import argparse import hashlib import json import re import struct from pathlib import Path from PIL import Image, ImageDraw from check_card_exit_build import Coff ROOT = Path(__file__).resolve().parents[2] ORDER = [0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26] RED = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36} def assets(): manifest = json.loads((ROOT / "assets/animations/wheel/manifest-v1.json").read_text()) assert manifest == {"order": ORDER, "zero_pockets": 1} source = (ROOT / "src_graphics/native_wheel_data.inc").read_text(encoding="ascii") order = re.search(r"wheelOrder3D dd ([0-9,]+)", source).group(1) assert [int(value) for value in order.split(",")] == ORDER low, high = re.search(r"wheelRedMask3D dd ([0-9A-F]+)h,([0-9A-F]+)h", source).groups() mask = int(low, 16) | int(high, 16) << 32 assert {n for n in range(37) if mask & (1 << n)} == RED for name, size in (("table", (640, 480)), ("closeup", (640, 480)), ("rotor", (480, 480))): version = 2 if name == "rotor" else 1 data = (ROOT / "assets/animations/wheel" / f"{name}-v{version}.bin").read_bytes() assert struct.unpack_from("<4s4I", data) == ( b"ANM1", *size, 1, size[0] * size[1] * 4), name assert len(data) == 20 + size[0] * size[1] * 4, name alpha = set(data[23::4]) assert alpha == ({0, 255} if name == "rotor" else {255}), name print("PASS: three valid packs; exact European order, one zero and correct red/black colors") def captures(): names = ("draft", "launch", "fast", "slow", "land", "result") contact = Image.new("RGB", (1280, 3 * 504), (20, 25, 29)) labels = ImageDraw.Draw(contact) hashes = [] for i, name in enumerate(names): path = ROOT / "build/actions" / ("wheel-" + name + "-v1.bmp") frame = Image.open(path).convert("RGB") assert frame.size == (640, 480), name assert len(frame.getcolors(640 * 480)) > 1000, name hashes.append(hashlib.sha256(frame.tobytes()).hexdigest()) frame.save(path.with_suffix(".png")) x, y = i % 2 * 640, i // 2 * 504 contact.paste(frame, (x, y + 24)) labels.text((x + 8, y + 6), name, fill="white") assert len(set(hashes)) == len(names), "Animation captures must be distinct" contact.save(ROOT / "build/actions/wheel-contact-v1.png") print("PASS: six distinct nonblank native roulette frames at draft/spin/landing/result") def parity(): tested = Coff(ROOT / "build/actions/actions-test.obj") shipped = Coff(ROOT / "build/graphics/poker.obj") functions = (("InitNativeWheel3D", 0), ("HandleNativeWheel3D", 0), ("RenderNativeWheel3D", 0), ("WheelSettle3D", 0), ("WheelAddBet3D", 4), ("WheelBetMultiplier3D", 8), ("WheelEscape3D", 0), ("WheelTravel3D", 12), ("DrawNativeWheelDisc3D", 20), ("RenderWheelScene3D", 0), ("LoadNativeWheelArt3D", 0), ("FreeNativeWheelArt3D", 0), ("DrawWheelDigit3D", 20), ("DrawWheelValue3D", 20), ("DrawWheelLabels3D", 20), ("AnimateCityPedestrians3D", 0), ("HandleArrival3D", 0)) for name, arguments in functions: symbol = f"_{name}@{arguments}" assert tested.signature(symbol) == shipped.signature(symbol), name print("PASS: shipping roulette and street animation procedures match native-tested code") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--assets-only", action="store_true") parser.add_argument("--parity", action="store_true") options = parser.parse_args() assets() if not options.assets_only: captures() if options.parity: parity()