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

103 lines
4.9 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.

"""Validate native auxiliary-window frames and Russian CP1251 glyphs."""
import argparse
import hashlib
import json
import re
from pathlib import Path
from PIL import Image, ImageDraw
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
NAMES = ("idle", "air", "heads", "tails", "loss")
def font():
values = []
for line in (ROOT / "font8x8.inc").read_text(encoding="cp1251").splitlines():
if line.startswith("db "):
values.extend(int(v.strip()[:-1], 16) for v in line[3:].split(";")[0].split(","))
assert len(values) == 256 * 8
glyphs = [values[i * 8:(i + 1) * 8] for i in range(256)]
labels = json.loads((ROOT / "assets/data/ui-ru.json").read_text(encoding="utf-8"))
for text in labels.values():
for code in text.encode("cp1251"):
if code >= 128:
assert any(glyphs[code]), f"Empty Cyrillic glyph {code:02X}"
assert glyphs[code] != glyphs[ord("?")], f"Question mark instead of {code:02X}"
assert glyphs[0xA8] != glyphs[ord("E")], "Russian YO must have its own glyph"
widths = {
"coinFlipText3D": 6, "coinAgainText3D": 7, "coinExitText3D": 5,
"wheelRed3D": 5, "wheelBlack3D": 5, "wheelEven3D": 5, "wheelOdd3D": 5,
"wheelSpin3D": 7, "wheelClear3D": 5,
"pkCheckText3D": 8, "pkCallText3D": 8, "pkRaiseText3D": 8,
"pkFoldText3D": 8, "pkAllInText3D": 8, "cbHitText3D": 8,
"cbStandText3D": 8, "cbDoubleText3D": 8, "merchantBuy3D": 12,
"bs3d_textCuffKey": 8, "entryChoose3D": 16, "entryNew3D": 16,
"poolOpenText3D": 16, "poolSolidsText3D": 16, "poolStripesText3D": 16,
}
for symbol, maximum in widths.items():
assert len(labels[symbol]) <= maximum, f"Russian button overflow: {symbol}"
print(f"PASS: {len(labels)} translated labels have CP1251 glyphs including YO; narrow buttons fit")
return glyphs
def text_matches(frame, text, x, y, scale, ink, glyphs):
for code in text.encode("cp1251"):
for row, mask in enumerate(glyphs[code]):
for column in range(8):
if mask & (128 >> column):
for dy in range(scale):
for dx in range(scale):
assert frame.getpixel((x + column * scale + dx, y + row * scale + dy)) == ink, (text, x, y)
x += scale * 8
def captures(glyphs):
frames = {}
sheet = Image.new("RGB", (320 * 5, 424), (20, 25, 29))
draw = ImageDraw.Draw(sheet)
for i, name in enumerate(NAMES):
path = ROOT / "build/actions" / f"coin-detail-{name}.bmp"
frame = Image.open(path).convert("RGB")
assert frame.size == (320, 400), name
assert len(frame.getcolors(128000)) > 100, name
frame.save(path.with_suffix(".png"))
frames[name] = frame
sheet.paste(frame, (i * 320, 24))
draw.text((i * 320 + 8, 6), name, fill="white")
assert frame.getpixel((319, 399)) == (20, 25, 29)
assert len({hashlib.sha256(f.tobytes()).digest() for f in frames.values()}) == 5
for name, side, x in (("heads", "ОРЁЛ", 96), ("tails", "РЕШКА", 80), ("loss", "РЕШКА", 80)):
text_matches(frames[name], side, x, 284, 4, (244, 231, 200), glyphs)
text_matches(frames["heads"], "ВЫИГРЫШ", 104, 336, 2, (135, 212, 165), glyphs)
text_matches(frames["loss"], "ПРОИГРЫШ", 96, 336, 2, (233, 147, 131), glyphs)
text_matches(frames["air"], "МОНЕТА В ПОЛЁТЕ", 40, 310, 2, (229, 210, 158), glyphs)
assert frames["heads"].crop((60, 66, 260, 266)).tobytes() != frames["tails"].crop((60, 66, 260, 266)).tobytes()
assert frames["tails"].crop((60, 66, 260, 266)).tobytes() == frames["loss"].crop((60, 66, 260, 266)).tobytes()
sheet.save(ROOT / "build/actions/coin-detail-contact.png")
print("PASS: five distinct native detail frames; exact Russian result/win/loss glyphs and matching faces")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
for name, count in (("OpenCoinDetail3D", 0), ("CloseCoinDetail3D", 0), ("LayoutCoinDetail3D", 0),
("UpdateCoinDetail3D", 0), ("RenderCoinDetail3D", 0), ("CoinDetailEvents3D", 16),
("CoinDetailBox3D", 20), ("CoinDetailText3D", 20), ("CoinDetailFace3D", 8),
("CoinDetailNumber3D", 16), ("ToggleFullscreen3D", 0)):
symbol = f"_{name}@{count}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping auxiliary-window lifecycle, renderer and fullscreen match native-tested MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--parity", action="store_true")
parser.add_argument("--font-only", action="store_true")
args = parser.parse_args()
glyphs = font()
if not args.font_only:
captures(glyphs)
if args.parity:
parity()