tools/asset_manifest.py records path/size/sha256 of pack/Index, every .eix/.epk and Eternexus/root/*.msm (audit/assets/40250-client.json). The gate script sets MT_ASSETS_STRICT=1, verifies the manifest, runs the native pack/proto tests and four Godot tests, and fails on a missing client, a mismatch or a skip. MT_40250_CLIENT now comes from the environment for the native tests too. remote_player_test accepts both weighted WAIT variants (wait/wait_1). PORT-PLAN §5 records the 2D status. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
3.6 KiB
Python
Executable File
101 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Provenance manifest of the 40250 client assets the tests and the runtime read (docs/PORT-PLAN.md 2D step 3).
|
|
|
|
Records path (relative to the Client directory) + size + sha256 for pack/Index, every .eix/.epk in pack/ and the
|
|
loose Eternexus/root/*.msm race specs. `verify` fails on a missing, changed or untracked file, so a strict
|
|
run proves it read exactly the bytes the manifest names.
|
|
|
|
tools/asset_manifest.py generate <Client dir> [-o audit/assets/40250-client.json]
|
|
tools/asset_manifest.py verify <Client dir> [-m audit/assets/40250-client.json]
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
DEFAULT = os.path.join(os.path.dirname(__file__), "..", "audit", "assets", "40250-client.json")
|
|
|
|
|
|
def sha256(path):
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
for block in iter(lambda: f.read(1 << 20), b""):
|
|
h.update(block)
|
|
return h.hexdigest()
|
|
|
|
|
|
def tracked(client):
|
|
files = ["pack/Index"]
|
|
pack = os.path.join(client, "pack")
|
|
files += sorted("pack/" + n for n in os.listdir(pack) if n.lower().endswith((".eix", ".epk")))
|
|
root = os.path.join(client, "Eternexus", "root")
|
|
if os.path.isdir(root):
|
|
files += sorted("Eternexus/root/" + n for n in os.listdir(root) if n.lower().endswith(".msm"))
|
|
return files
|
|
|
|
|
|
def entry(client, rel):
|
|
p = os.path.join(client, rel)
|
|
return {"path": rel, "size": os.path.getsize(p), "sha256": sha256(p)}
|
|
|
|
|
|
def generate(args):
|
|
files = [entry(args.client, rel) for rel in tracked(args.client)]
|
|
out = {
|
|
"schema": 1,
|
|
"source": "40250 Server Client TMP4/Client",
|
|
"note": "path relative to the Client directory; regenerate with tools/asset_manifest.py generate",
|
|
"files": files,
|
|
}
|
|
os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
|
|
with open(args.output, "w") as f:
|
|
json.dump(out, f, indent=1)
|
|
f.write("\n")
|
|
print("%d files, %d bytes -> %s" % (len(files), sum(e["size"] for e in files), args.output))
|
|
return 0
|
|
|
|
|
|
def verify(args):
|
|
try:
|
|
with open(args.manifest) as f:
|
|
want = json.load(f)["files"]
|
|
except (OSError, ValueError, KeyError) as e:
|
|
print("asset_manifest: cannot read manifest %s: %s" % (args.manifest, e), file=sys.stderr)
|
|
return 1
|
|
if not os.path.isfile(os.path.join(args.client, "pack", "Index")):
|
|
print("asset_manifest: no pack/Index under %s" % args.client, file=sys.stderr)
|
|
return 1
|
|
bad = 0
|
|
for e in want:
|
|
p = os.path.join(args.client, e["path"])
|
|
if not os.path.isfile(p):
|
|
print("missing %s" % e["path"], file=sys.stderr)
|
|
bad += 1
|
|
elif os.path.getsize(p) != e["size"] or sha256(p) != e["sha256"]:
|
|
print("changed %s" % e["path"], file=sys.stderr)
|
|
bad += 1
|
|
extra = sorted(set(tracked(args.client)) - {e["path"] for e in want})
|
|
for rel in extra:
|
|
print("untracked %s" % rel, file=sys.stderr)
|
|
bad += len(extra)
|
|
print("%d files checked, %d mismatch(es)" % (len(want), bad))
|
|
return 1 if bad else 0
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
g = sub.add_parser("generate")
|
|
g.add_argument("client")
|
|
g.add_argument("-o", "--output", default=DEFAULT)
|
|
v = sub.add_parser("verify")
|
|
v.add_argument("client")
|
|
v.add_argument("-m", "--manifest", default=DEFAULT)
|
|
args = ap.parse_args()
|
|
return generate(args) if args.cmd == "generate" else verify(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|