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.
103 lines
4.9 KiB
Python
103 lines
4.9 KiB
Python
"""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()
|