Freeze HD review libraries and checkpoint 441 approved frames

This commit is contained in:
raptorreplica committed 2026-10-11 17:11:36 +11:00
1 parent 26ed9ccdbb
commit aeca7501bb
7 files changed
+34070 -2385

No files matched your search

+1 -1
View File
@@ -276,7 +276,7 @@ def markdown(report):
lines.extend(["","| Generation job | Status |","| --- | --- |"])
for job in report["jobs"]:
lines.append(f"| {job['name']} | {job['status']} |")
lines.extend(["",f"The catalog also contains **{report['cinematics']['frames']} AGX cinematic delta frames in {len(report['cinematics']['sequences'])} sequences** and **{len(report['fonts'])} bitmap fonts**. These have explicit native-art preservation policies: keep the classic cinematics and sharp original glyphs, validate complete playback/alignment, and never count them as generated HD replacements. The AGX frames are separate from standalone pictures.","","## Sequence priorities","","1. Keep all light/dark-player banking poses and firing-flash poses coherent as complete families. Preserve the exact firing-flash source mask, which differs from the light/dark mask for the straight pose.","2. Ground defenses, airborne enemies and complete boss animation sequences; share palettes and the original logical canvases, rather than treating each enemy frame as an unrelated illustration.","3. Original projectiles and pickup/equipment animations. Tiny projectiles need careful readability checks; per-asset nearest fallback remains temporary until a clean remaster passes review.","4. Explosions, flame/spark sequences and other effects. Check full animation timing, transparency and palette cycling, not only a still image.","5. All four terrain sets, including unnamed catalog tiles. Generate with neighboring context and verify joined seams before changing runtime coverage.","6. Every original menu background, portrait, HUD and retained unused picture. Gameplay UI text and button labels must remain readable and match their actions. Verify the preserved native cinematic/font paths separately.","","## Reproduce the audit","","```sh","python3 tools/hd_art_audit.py --data ../data \\"," --json ../data/hd/coverage-audit.json --markdown docs/hd-backlog.md","python3 tools/hd_job_queue.py --data ../data","```","","The JSON gives every picture's asset ID, path, logical dimensions, original header options, source hash and HD status, plus each declared animation/set. The [completion ledger](hd-completion.json) records every required picture and native-art verification target, including complete families, ownership and candidate/review/registration stages. Both contain metadata only. Original and original-derived artwork remains outside Git.","","Folder totals: " + ", ".join(f"`{name}/`: {count:,}" for name,count in report["by_folder"].items()) + ". These folders are not semantic categories; terrain and other pictures also live in `misc/`.",""])
lines.extend(["",f"The catalog also contains **{report['cinematics']['frames']} AGX cinematic delta frames in {len(report['cinematics']['sequences'])} sequences** and **{len(report['fonts'])} bitmap fonts**. These have explicit native-art preservation policies: keep the classic cinematics and sharp original glyphs, validate complete playback/alignment, and never count them as generated HD replacements. The AGX frames are separate from standalone pictures.","","## Sequence priorities","","1. Keep all light/dark-player banking poses and firing-flash poses coherent as complete families. Preserve the exact firing-flash source mask, which differs from the light/dark mask for the straight pose.","2. Ground defenses, airborne enemies and complete boss animation sequences; share palettes and the original logical canvases, rather than treating each enemy frame as an unrelated illustration.","3. Original projectiles and pickup/equipment animations. Tiny projectiles need careful readability checks; per-asset nearest fallback remains temporary until a clean remaster passes review.","4. Explosions, flame/spark sequences and other effects. Check full animation timing, transparency and palette cycling, not only a still image.","5. All four terrain sets, including unnamed catalog tiles. Generate from exact canonical source tiles or a literal native object assembly; use neighboring context for review rather than importing foreign scenery into artwork. Verify joined seams and destroyed-state pairs before changing runtime coverage.","6. Every original menu background, portrait, HUD and retained unused picture. Gameplay UI text and button labels must remain readable and match their actions. Verify the preserved native cinematic/font paths separately.","","## Reproduce the audit","","```sh","python3 tools/hd_art_audit.py --data ../data \\"," --json ../data/hd/coverage-audit.json --markdown docs/hd-backlog.md","python3 tools/hd_job_queue.py --data ../data","```","","The JSON gives every picture's asset ID, path, logical dimensions, original header options, source hash and HD status, plus each declared animation/set. The [completion ledger](hd-completion.json) records every required picture and native-art verification target, including complete families, ownership and candidate/review/registration stages. Both contain metadata only. Working originals, source references and generation/review drafts under `../data` remain outside this repository. The bundled original catalog and approved HD runtime snapshot under `data/` are tracked in Git; the snapshot is refreshed from reviewed development artwork when packaging.","","Folder totals: " + ", ".join(f"`{name}/`: {count:,}" for name,count in report["by_folder"].items()) + ". These folders are not semantic categories; terrain and other pictures also live in `misc/`.",""])
effects=next(row for row in report["by_category"] if row["category"]==CATEGORIES[4])
effect_ranges=effects["declared_sequences_or_sets"]
lines.extend(["## Secondary effect coverage", "", f"Native animation registrations, pickup masks and beam impact/power companion arrays contribute {effects['pictures']:,} distinct effect frames. The {effect_ranges} declared ranges include one five-frame descending-smoke subset of the nine-frame smoke family, leaving {effect_ranges-1} complete families. This includes muzzle flashes/casings, laser impacts, smoke trails, lightning, sparks and the super-shield glow as well as explosions. Pickup glow uses indexed light-table mask semantics and needs a different HD treatment from an ordinary color sprite. These assets are not UI artwork merely because they are absent from the content registries. Renderer event reachability needs its own check before claiming complete gameplay effect coverage.", ""])
+49 -2
View File
@@ -67,6 +67,49 @@ def binding_digest(rows):
return hashlib.sha256(json.dumps(rows, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def freeze_library(project, library):
"""Keep exact tested bytes and the original build time across Cargo rebuilds."""
before = library.stat()
payload = library.read_bytes()
after = library.stat()
identity = lambda value: (value.st_ino, value.st_size, value.st_mtime_ns)
if identity(before) != identity(after):
raise ValueError("Rust library changed while taking review snapshot; retry")
digest = hashlib.sha256(payload).hexdigest()
directory = project / "target/hd-review-libraries" / digest
directory.mkdir(parents=True, exist_ok=True)
frozen = directory / library.name
if frozen.exists():
if hashlib.sha256(frozen.read_bytes()).hexdigest() != digest:
raise ValueError("review library snapshot is corrupt")
if frozen.stat().st_mtime_ns != before.st_mtime_ns:
# Identical bytes rebuilt later must not make an old snapshot look
# fresh. Separate build times retain an honest source-age check.
frozen = directory / (library.stem + "-build-" + str(before.st_mtime_ns) + library.suffix)
if not frozen.exists():
descriptor, temporary = tempfile.mkstemp(prefix="library-", dir=directory)
try:
with os.fdopen(descriptor, "wb") as output:
output.write(payload)
output.flush()
os.fsync(output.fileno())
os.utime(temporary, ns=(before.st_atime_ns, before.st_mtime_ns))
os.chmod(temporary, 0o444)
try:
# Publish without replacing an identical snapshot another
# reviewer may already be using. Concurrent first readers
# must retain one stable inode as well as identical bytes.
os.link(temporary, frozen)
except FileExistsError:
pass
finally:
if os.path.exists(temporary):
os.unlink(temporary)
if hashlib.sha256(frozen.read_bytes()).hexdigest() != digest or frozen.stat().st_mtime_ns != before.st_mtime_ns:
raise ValueError("review library snapshot identity differs")
return frozen, digest, before
def main():
project = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser(description=__doc__)
@@ -95,9 +138,10 @@ def main():
libraries = [library for profile in profiles for library in (project / "target" / profile / "deps").glob("libraptor_replica-*.rlib")]
if not libraries:
raise ValueError("build the Rust game library before candidate review")
library = max(libraries, key=lambda path: path.stat().st_mtime_ns)
built_library = max(libraries, key=lambda path: path.stat().st_mtime_ns)
dependency_directory = built_library.parent
library, library_sha256, built_library_at_start = freeze_library(project, built_library)
library_at_start = library.stat()
dependency_directory = library.parent
serde = max(dependency_directory.glob("libserde_json-*.rlib"), key=lambda path: path.stat().st_mtime_ns)
output = (args.output or args.candidate.parent / "runtime-review").resolve()
output.mkdir(parents=True, exist_ok=True)
@@ -140,6 +184,9 @@ def main():
raise ValueError("candidate renderer bindings changed during runtime review")
qa["library"] = str(library.relative_to(project))
qa["library_modified_ns"] = library_at_start.st_mtime_ns
qa["library_sha256"] = library_sha256
qa["library_build_origin"] = str(built_library.relative_to(project))
qa["library_build_origin_modified_ns"] = built_library_at_start.st_mtime_ns
qa["candidate_sha256"] = hashlib.sha256(candidate_at_finish).hexdigest()
qa["candidate_sha256_at_start"] = hashlib.sha256(candidate_at_start).hexdigest()
qa["candidate_notes_changed_during_review"] = candidate_at_start != candidate_at_finish
+2
View File
@@ -55,6 +55,8 @@ def approved_job(root, identity, project):
library = (project / qa["library"]).resolve()
if not library.is_relative_to(project / "target") or library.stat().st_mtime_ns != qa["library_modified_ns"]:
raise ValueError(f"runtime library was replaced after review: {identity}")
if qa.get("library_sha256") and hashlib.sha256(library.read_bytes()).hexdigest() != qa["library_sha256"]:
raise ValueError(f"runtime library bytes changed after review: {identity}")
for name in ["assets.rs", "hd.rs"]:
if (project / "src" / name).stat().st_mtime_ns > qa["library_modified_ns"]:
raise ValueError(f"asset/HD code changed after review: {identity}")