Add native Durak, poker, blackjack, roulette, Buckshot and billiards scenes with animation assets and regression tests. Improve arrival, city, hall, profiles, shops, audio and exit handling; remove the portable C game core and refresh shipping executables.
89 lines
4.5 KiB
Python
89 lines
4.5 KiB
Python
"""Validate generated packs and snapshots from the actual MASM renderer."""
|
|
import hashlib
|
|
import argparse
|
|
import json
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
OUTPUT = ROOT / 'build/actions'
|
|
|
|
|
|
def main(packs_only=False):
|
|
manifest = json.loads((ROOT / 'assets/animations/buckshot/manifest-v2.json').read_text())
|
|
for name, expected in manifest.items():
|
|
data = (ROOT / 'assets/animations/buckshot' / name).read_bytes()
|
|
magic, width, height, count, length = struct.unpack_from('<4s4I', data)
|
|
assert magic == b'ANM1'
|
|
assert [width, height] == expected['size'] and count == expected['frames']
|
|
assert len(data) == 20 + length == 20 + width * height * 4 * count
|
|
hashes = set()
|
|
for i in range(count):
|
|
payload = data[20 + i * width * height * 4:20 + (i + 1) * width * height * 4]
|
|
frame = Image.frombytes('RGBA', (width, height), payload, 'raw', 'BGRA')
|
|
hashes.add(hashlib.sha256(payload).hexdigest())
|
|
if name == 'self-flash-v4.bin' and i < 4:
|
|
hot = [(x, y, a) for y in range(height) for x in range(width)
|
|
for r, g, b, a in [frame.getpixel((x, y))]
|
|
if r > 240 and g > 220 and b > 150 and a > 64]
|
|
weight = sum(a for x, y, a in hot)
|
|
assert weight > 0, (name, i)
|
|
cx = sum(x * a for x, y, a in hot) / weight
|
|
cy = sum(y * a for x, y, a in hot) / weight
|
|
assert abs(cx - 64) < 2 and abs(cy - 64) < 2, (name, i, cx, cy)
|
|
if name not in ('room-v2.bin', 'blood-v3.bin', 'self-flash-v4.bin'):
|
|
assert set(frame.getchannel('A').tobytes()) == {0, 255}, (name, i)
|
|
bounds = frame.getchannel('A').getbbox()
|
|
assert bounds and 0 < bounds[0] < bounds[2] < width, (name, i, bounds)
|
|
assert len(hashes) == count, name
|
|
|
|
if packs_only:
|
|
print(f'PASS: {len(manifest)} packs, frontal ignition anchors centered within 2 pixels')
|
|
return
|
|
|
|
snapshots = sorted(OUTPUT.glob('buckshot-*.bmp'))
|
|
assert len(snapshots) == 30, len(snapshots)
|
|
report = {}
|
|
for path in snapshots:
|
|
frame = Image.open(path).convert('RGB')
|
|
assert frame.size == (640, 480)
|
|
colors = len(frame.getcolors(640 * 480))
|
|
assert colors > 1000, (path.name, colors)
|
|
cinematic = any(part in path.stem for part in ('revive-', 'death-')) or path.stem in (
|
|
'buckshot-self-fire', 'buckshot-self-peak', 'buckshot-self-smoke', 'buckshot-dealer-fire')
|
|
if not cinematic:
|
|
assert frame.getpixel((0, 0)) == (17, 17, 22), path.name
|
|
assert frame.getpixel((0, 479)) == (17, 22, 23), path.name
|
|
else:
|
|
assert frame.getpixel((0, 479)) != (17, 22, 23), ('HUD hides cinematic', path.name)
|
|
report[path.stem] = {'colors': colors, 'sha256': hashlib.sha256(frame.tobytes()).hexdigest()}
|
|
frame.save(path.with_suffix('.png'))
|
|
assert len({item['sha256'] for item in report.values()}) == len(snapshots)
|
|
peak = Image.open(OUTPUT / 'buckshot-self-peak.bmp').convert('RGB')
|
|
r, g, b = peak.getpixel((315, 228))
|
|
assert r > 240 and g > 220 and b > 150, ('Muzzle ignition missed the bore', r, g, b)
|
|
|
|
names = ['ready', 'self-peak', 'death-dealer', 'revive-dealer-shock']
|
|
contact = Image.new('RGB', (1280, 1010), '#191c19')
|
|
draw = ImageDraw.Draw(contact)
|
|
for i, name in enumerate(names):
|
|
x, y = i % 2 * 640, i // 2 * 505
|
|
draw.text((x + 8, y + 5), name, fill='white')
|
|
contact.paste(Image.open(OUTPUT / f'buckshot-{name}.bmp'), (x, y + 25))
|
|
contact.save(OUTPUT / 'buckshot-contact-sheet.png')
|
|
|
|
sequence = ['self-rise', 'self-aim', 'self-fire', 'self-peak', 'self-smoke', 'death-player',
|
|
'revive-player-arms', 'revive-player-shock', 'lose']
|
|
frames = [Image.open(OUTPUT / f'buckshot-{name}.bmp').convert('RGB') for name in sequence]
|
|
frames[0].save(OUTPUT / 'buckshot-keyframes.gif', save_all=True, append_images=frames[1:],
|
|
duration=[300, 300, 30, 50, 300, 800, 700, 800, 1000], loop=0)
|
|
(OUTPUT / 'buckshot-qa.json').write_text(json.dumps(report, indent=2), encoding='utf-8')
|
|
print(f'PASS: {len(manifest)} packs, 148 distinct sprite/VFX poses, 30 MASM framebuffer snapshots')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--packs-only', action='store_true')
|
|
main(parser.parse_args().packs_only)
|