KAZIK/tools/tests/check_european_wheel.py
Artvasil09 4f8ae0893b Implement native slots, coin toss, European roulette and hall furniture
Add native assembly scenes and assets for Slots (777), Coin flip, and European Roulette; add 3D hall furniture packs and prompts; improve Russian UI strings in CP1251; update launcher hash and rebuild shipping binaries.
2026-10-10 14:43:30 +03:00

85 lines
3.9 KiB
Python

"""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()