KAZIK/tools/tests/check_slot_frames.py
Artvasil09 881a0061dc Add native NPC visitors with contracts, Clover slots, Pyramid, and party table scenes
Implement 32x32 BFS visitor navigation and daily contracts system with companion .npc journals; add native assembly scenes for Clover slots, Pyramid cards, Blackjack Party and Russian Party; package animation and sprite packs; add comprehensive offline test suites and rebuild shipping binaries.
2026-10-13 09:47:33 +03:00

77 lines
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Inspect snapshots from the real MASM reel renderer, not a Python simulation."""
import hashlib
import struct
from pathlib import Path
from PIL import Image, ImageChops
from check_coin_detail import font, text_matches
ROOT = Path(__file__).resolve().parents[2]
OUTPUT = ROOT / "build/actions"
NAMES = ["slots-ready", "slots-600", "slots-1799", "slots-1800",
"slots-2400", "slots-win", "slots-paytable", "slots-exit"]
def main():
packs = ROOT / "assets/animations/slots"
raw = (packs / "cabinet-v1.bin").read_bytes()
assert struct.unpack_from("<4s4I", raw) == (b"ANM1", 640, 480, 1, 640 * 480 * 4)
cabinet = Image.frombytes("RGBA", (640, 480), raw[20:], "raw", "BGRA").convert("RGB")
assert set(raw[23::4]) == {255}, "Fast cabinet copy requires fully opaque pixels"
symbols = (packs / "symbols-v1.bin").read_bytes()
assert struct.unpack_from("<4s4I", symbols) == (b"ANM1", 64, 64, 8, 64 * 64 * 4 * 8)
assert len(symbols) == 20 + 64 * 64 * 4 * 8
assert set(symbols[23::4]) == {0, 255}
stride = 64 * 64 * 4
assert len({hashlib.sha256(symbols[20 + index * stride:20 + (index + 1) * stride]).digest()
for index in range(8)}) == 8
frames = {}
for name in NAMES:
frame = Image.open(OUTPUT / (name + ".bmp")).convert("RGB")
assert frame.size == (640, 480)
assert len(frame.getcolors(640 * 480)) > 2000, name
frames[name] = frame
frame.save(OUTPUT / (name + ".png"))
assert len({hashlib.sha256(frame.tobytes()).digest() for frame in frames.values()}) == len(NAMES)
# No symbol may cover the reel separators, chrome frame or marquee.
for name in NAMES:
if name == "slots-paytable":
continue
difference = ImageChops.difference(frames[name], cabinet)
for y in range(100, 272):
if 188 <= y < 194:
continue # Payline markers and win illumination are intentional.
for x in range(170, 477):
in_reel = 124 <= y < 260 and any(left <= x < left + 88 for left in (180, 278, 376))
if not in_reel:
assert difference.getpixel((x, y)) == (0, 0, 0), (name, x, y)
first = (180, 124, 268, 260)
second = (278, 124, 366, 260)
third = (376, 124, 464, 260)
assert frames["slots-1800"].crop(first).tobytes() == frames["slots-2400"].crop(first).tobytes()
# The gold winning line is the only permitted difference on a stopped drum.
for rect in (first, second):
left, top, right, bottom = rect
for box in ((left, top, right, 188), (left, 194, right, bottom)):
assert frames["slots-2400"].crop(box).tobytes() == frames["slots-win"].crop(box).tobytes()
assert frames["slots-2400"].crop(third).tobytes() != frames["slots-win"].crop(third).tobytes()
contact = Image.new("RGB", (640 * 4, 480 * 2))
for index, name in enumerate(NAMES):
contact.paste(frames[name], ((index % 4) * 640, (index // 4) * 480))
contact.save(OUTPUT / "slots-contact.png")
glyphs = font()
for name, gross, net, status, ink in (
("slots-refund", "100", "0", "ВОЗВРАТ", (208, 212, 208)),
("slots-net-win", "200", "+100", "ВЫПЛАТА", (168, 221, 185)),
("slots-loss", "0", "-100", "ПРОИГРЫШ", (238, 153, 136))):
frame = Image.open(OUTPUT / (name + ".bmp")).convert("RGB")
text_matches(frame, gross, 384, 402, 2, (244, 243, 232), glyphs)
text_matches(frame, net, 384, 422, 2, ink, glyphs)
text_matches(frame, status, 208, 298, 2, (232, 215, 173), glyphs)
frame.save(OUTPUT / (name + ".png"))
print("PASS: 8 distinct native slot frames; pixel-accurate reel clipping; sequential stops; opaque cabinet and 8 real symbol sprites")
print("PASS: exact native gross/net/refund/loss Russian glyphs; refund is not a net win")
if __name__ == "__main__":
main()