feat(simulation): add Worker AI polygon runtime and terrain navigation
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user