KAZIK/tools/tests/check_coin_scene.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

84 lines
3.9 KiB
Python

"""Check coin pack layout, native framebuffer captures and shipping MASM parity."""
import argparse
import hashlib
import json
import struct
from pathlib import Path
from PIL import Image, ImageDraw
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
NAMES = ("idle", "hold", "release", "air", "apex", "descent", "land", "bounce", "result", "tails")
def assets():
manifest = json.loads((ROOT / "assets/animations/coin/manifest-v1.json").read_text())
for name, shape in (("table", (640, 480, 1)), ("faces", (128, 128, 2)), ("hands", (224, 224, 16))):
version = 2 if name == "hands" else 1
data = (ROOT / "assets/animations/coin" / f"{name}-v{version}.bin").read_bytes()
width, height, count = shape
size = width * height * 4
assert manifest[name] == list(shape), name
assert struct.unpack_from("<4s4I", data) == (b"ANM1", *shape, size * count), name
assert len(data) == 20 + size * count, name
assert set(data[23::4]) == ({255} if name == "table" else {0, 255}), name
hashes = []
for i in range(count):
pixels = data[20 + i * size:20 + (i + 1) * size]
hashes.append(hashlib.sha256(pixels).hexdigest())
opaque = sum(alpha == 255 for alpha in pixels[3::4])
assert opaque > width * height // 10, (name, i)
if name == "hands" and i < 5:
frame = Image.frombytes("RGBA", (width, height), pixels, "raw", "BGRA")
anchor = frame.crop((64, 12, 112, 32))
gold = sum(a == 255 and r > 130 and g * 100 > r * 70 and b * 100 < r * 40
for r, g, b, a in anchor.get_flattened_data())
assert gold > 10, ("Held coin lost its contact anchor", i, gold)
assert len(set(hashes)) == count, name
print("PASS: opaque table, two coin faces, sixteen hand frames; held coin/fingers share one sprite")
def captures():
sheet = Image.new("RGB", (1280, 5 * 504), (20, 25, 29))
labels = ImageDraw.Draw(sheet)
hashes = []
for i, name in enumerate(NAMES):
path = ROOT / "build/actions" / f"coin-{name}.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
sheet.paste(frame, (x, y + 24))
labels.text((x + 8, y + 6), name, fill="white")
assert len(set(hashes)) == len(NAMES), "Toss captures must be distinct"
sheet.save(ROOT / "build/actions/coin-contact-v1.png")
print("PASS: 10 distinct, nonblank native frames including both result faces")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
for name, arguments in (("InitCoinGame", 0), ("HandleCoinInput", 0), ("RenderCoinGame", 0),
("InitNativeCoin3D", 0), ("LoadNativeCoinArt3D", 0),
("FreeNativeCoinArt3D", 0), ("HandleNativeCoin3D", 0),
("RenderNativeCoin3D", 0), ("CoinEscape3D", 0),
("CoinSettle3D", 0), ("CoinPose3D", 4), ("DrawCoinShadow3D", 20),
("CardTableEscape3D", 0)):
symbol = f"_{name}@{arguments}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: published coin renderer, controls, payout and exit match native-tested MASM")
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()