106 lines
4.1 KiB
Python
106 lines
4.1 KiB
Python
"""Admit validated Worker-local Gaussian + collision assets, sending metadata only.
|
|
|
|
Run with the prepared Isaac Python environment on the Worker. The descriptor
|
|
declares hashes, provenance, size and scene settings; registration does not
|
|
assert that AI has completed a route. No model/profile is changed here.
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "src"))
|
|
|
|
from core_client import CoreClient
|
|
from local_state import read_json, write_json
|
|
from terrain import load_glb
|
|
|
|
from k1link.simulation.ai_polygon.contracts import WorkerWorldCreate
|
|
from k1link.simulation.ai_polygon.terrain_contract import terrain_matches
|
|
from k1link.simulation.ai_polygon.worlds import inspect_gaussian_ply
|
|
|
|
|
|
def digest(path):
|
|
with path.open("rb") as stream:
|
|
return hashlib.file_digest(stream, "sha256").hexdigest()
|
|
|
|
|
|
def preserve(source, target, expected):
|
|
if target.exists():
|
|
if digest(target) != expected:
|
|
raise ValueError("Existing immutable asset identity differs")
|
|
return
|
|
temporary = target.with_suffix(target.suffix + ".part")
|
|
try:
|
|
os.link(source, temporary)
|
|
except OSError:
|
|
shutil.copyfile(source, temporary)
|
|
if digest(temporary) != expected:
|
|
raise ValueError("Asset identity changed while staging")
|
|
temporary.replace(target)
|
|
|
|
|
|
def stage(root, source, collider, descriptor):
|
|
if (root / "state/active.json").exists():
|
|
raise RuntimeError("Finish the active simulation before importing a scene")
|
|
request = WorkerWorldCreate.model_validate(descriptor)
|
|
if (
|
|
source.stat().st_size != request.byte_length
|
|
or digest(source) != request.sha256
|
|
or inspect_gaussian_ply(source) != request.splat_count
|
|
or digest(collider) != request.collider_sha256
|
|
):
|
|
raise ValueError("Paired asset does not match its declared identity")
|
|
load_glb(collider) # Reject unsupported transforms, external buffers and non-finite data.
|
|
cache = root / "state/worlds"
|
|
cache.mkdir(parents=True, exist_ok=True)
|
|
preserve(source, cache / (request.sha256 + ".ply"), request.sha256)
|
|
directory = root / "assets/prepared-worlds" / request.sha256
|
|
directory.mkdir(parents=True, exist_ok=True)
|
|
destination = directory / "collision.glb"
|
|
preserve(collider, destination, request.collider_sha256)
|
|
manifest = {
|
|
"schema_version": "missioncore.ai-polygon-terrain/v1",
|
|
"source_sha256": request.sha256,
|
|
"settings": request.settings.model_dump(mode="json"),
|
|
"generator": "paired-source",
|
|
"collider": str(destination),
|
|
"collider_sha256": request.collider_sha256,
|
|
"source_url": str(request.source_url) if request.source_url else None,
|
|
"qualification": "paired-source-requires-contact-and-route-validation",
|
|
}
|
|
path = directory / "terrain.json"
|
|
if path.exists():
|
|
existing = read_json(path)
|
|
if existing.get("collider_sha256") != request.collider_sha256 or not terrain_matches(
|
|
existing, request.model_dump(mode="json")
|
|
):
|
|
raise ValueError("Existing paired-scene calibration differs")
|
|
else:
|
|
write_json(path, manifest)
|
|
return request
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--root", type=Path, required=True)
|
|
parser.add_argument("--source", type=Path, required=True)
|
|
parser.add_argument("--collider", type=Path, required=True)
|
|
parser.add_argument("--descriptor", type=Path, required=True)
|
|
parser.add_argument("--core", default="http://127.0.0.1:18080")
|
|
args = parser.parse_args()
|
|
request = stage(args.root, args.source, args.collider, read_json(args.descriptor))
|
|
identity = read_json(args.root / "state/realtime-identity.json")
|
|
client = CoreClient(args.core, args.root / "private/worker.token", identity["instance_id"])
|
|
result = client.request("/worker/worlds", request.model_dump(mode="json"))
|
|
write_json(args.root / "assets/prepared-worlds" / request.sha256 / "registration.json", result)
|
|
print(json.dumps(result, ensure_ascii=True))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|