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.
84 lines
3.9 KiB
Python
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()
|