Files
aetherbound-guild/tools/verify_fg2_media.py
T

139 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Validate FG-2 media provenance and deterministic runtime outputs."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
MEDIA_ROOT = ROOT / "docs/runtime/fg2/media"
MANIFEST_PATH = ROOT / "runtime/assets/fg2/asset-manifest.json"
EXPECTED_SIZES = {(380, 180), (422, 195), (320, 180)}
EXPECTED_ACTORS = {"PF-B01": 6, "EN-R1-09": 4, "EN-R1-08": 4}
EXPECTED_TASKS = {
"task_01M04FDED6E16D7SBPJ5C03TAS",
"task_01M04FDDTANDVT4FC1RG9BG76F",
"task_01M04FDED19RK2PKRZ4NEP7R2Z",
"task_01M04FHH85F3MFKC5NWWSZXDV8",
}
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def require(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def runtime_path(relative: str) -> Path:
return ROOT / "runtime" / relative
def verify_provider_reports() -> None:
tasks = set()
total_cost = 0.0
for report_path in sorted((MEDIA_ROOT / "reports").glob("*.json")):
report = json.loads(report_path.read_text(encoding="utf-8"))
require(report.get("status") == "completed", f"incomplete report: {report_path}")
tasks.add(report["task_id"])
total_cost += float(report["cost"])
output = ROOT / report["output"]
require(output.is_file(), f"missing provider output: {output}")
require(output.stat().st_size == int(report["bytes"]), f"provider byte mismatch: {output}")
require(float(report["credits_cost"]) == 0.14, f"credit mismatch: {report_path}")
require(tasks == EXPECTED_TASKS, "provider task inventory mismatch")
require(abs(total_cost - 0.056) < 0.000001, "provider cost must be exactly USD 0.056")
def verify_cluster_pitch(image: Image.Image) -> None:
pixels = image.load()
for top in range(0, image.height - 1, 2):
for left in range(0, image.width - 1, 2):
block = {pixels[left, top], pixels[left + 1, top], pixels[left, top + 1], pixels[left + 1, top + 1]}
require(len(block) == 1, f"non-integer environment cluster at {left},{top}")
def verify_environment(manifest: dict) -> int:
environment = manifest["environment"]
require(environment["source_band_cuts"] == [0, 682, 1365, 2048], "source thirds changed")
require(environment["palette_size"] == 32, "environment palette contract changed")
seen_sizes = set()
checked = 0
for layer_name in ("background", "midground", "foreground"):
records = environment["layers"][layer_name]
for size_key, record in records.items():
width, height = (int(value) for value in size_key.split("x"))
seen_sizes.add((width, height))
path = runtime_path(record["path"])
require(path.is_file(), f"missing environment layer: {path}")
require(sha256(path) == record["sha256"], f"environment hash mismatch: {path}")
with Image.open(path) as image:
rgba = image.convert("RGBA")
require(rgba.size == (width, height), f"environment size mismatch: {path}")
require(record["cluster_pitch"] == 2, f"cluster pitch mismatch: {path}")
require(int(record["colors"]) <= 32, f"palette exceeds 32 colors: {path}")
alphas = {pixel[3] for pixel in rgba.getdata()}
require(alphas <= {0, 255}, f"soft alpha found: {path}")
if layer_name == "background":
require(alphas == {255}, f"background must be opaque: {path}")
else:
require(alphas == {0, 255}, f"overlay alpha missing: {path}")
key_pixels = sum(1 for r, g, b, a in rgba.getdata() if a and r >= 240 and b >= 240 and g <= 24)
require(key_pixels == 0, f"magenta residue found: {path}")
verify_cluster_pitch(rgba)
checked += 1
require(seen_sizes == EXPECTED_SIZES, "environment target-size inventory mismatch")
return checked
def verify_actors(manifest: dict) -> int:
require(set(manifest["actors"]) == set(EXPECTED_ACTORS), "actor identity inventory mismatch")
checked = 0
for identity_id, expected_frames in EXPECTED_ACTORS.items():
actor = manifest["actors"][identity_id]
require(actor["play_size"] == [36, 36], f"play size mismatch: {identity_id}")
require(actor["pivot"] == [18, 34], f"pivot mismatch: {identity_id}")
require(len(actor["frames"]) == expected_frames, f"frame count mismatch: {identity_id}")
atlas = runtime_path(actor["atlas"])
require(atlas.is_file() and sha256(atlas) == actor["atlas_sha256"], f"atlas mismatch: {identity_id}")
for frame in actor["frames"]:
path = runtime_path(frame["path"])
require(path.is_file() and sha256(path) == frame["sha256"], f"frame hash mismatch: {path}")
with Image.open(path) as image:
rgba = image.convert("RGBA")
require(rgba.size == (72, 72), f"actor cell mismatch: {path}")
alpha = rgba.getchannel("A")
require(alpha.getbbox() is not None, f"empty actor frame: {path}")
left, top, right, bottom = alpha.getbbox()
require(left > 0 and top > 0 and right < 72 and bottom < 72, f"actor touches edge: {path}")
require(set(alpha.getdata()) <= {0, 255}, f"soft actor alpha found: {path}")
checked += 1
return checked
def main() -> int:
verify_provider_reports()
manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
require(manifest.get("kind") == "abg-fg2-runtime-media", "runtime manifest kind mismatch")
require(manifest.get("filter") == "nearest", "runtime filter must be nearest")
require(manifest.get("integer_placement") is True, "integer placement must be true")
environment_count = verify_environment(manifest)
frame_count = verify_actors(manifest)
print(
"ABG_FG2_MEDIA_OK "
f"tasks=4 cost_usd=0.056 environment={environment_count} actor_frames={frame_count} "
"palette_max=32 cluster_pitch=2 edge=0 chroma=0 alpha=binary"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())