Spaces:
Running on Zero
Replace synthetic viewer telemetry with recorded native drives
Browse filesThe current viewer derives motion and HUD values from aggregate results, which creates off-track starting poses, discontinuous movement and unsupported telemetry.
This change replaces that reconstruction with an explicit recorded CPU drive. The native engine supplies position, velocity, heading and contact samples for one unchanged policy; the viewer keeps native track curves, validates the track/policy identities, interpolates only between recorded poses and stops at the final sample. Unavailable controls, sensor rays and neural activity are removed.
The new recording endpoint is bounded to 15 seconds of simulation with a 30-second wall deadline, shares CPU admission with training and uses the existing binary. Physics and control stay at 60 Hz; samples are captured every six ticks. These are fresh drives with wall-hit and idle elimination off, clearly distinguished from historical training results.
Validation: 36 focused checks pass (14 recorder contract, 9 geometry/playback, 5 client lifecycle/integration and 8 existing tutorial/recipe/batch workflows). JavaScript and Python syntax checks pass. Hosted native recording and final visual inspection will follow the complete package update.
|
@@ -4,6 +4,7 @@ import hashlib
|
|
| 4 |
import json
|
| 5 |
from pathlib import Path
|
| 6 |
import re
|
|
|
|
| 7 |
import threading
|
| 8 |
from fastapi import HTTPException, Request
|
| 9 |
from fastapi.responses import FileResponse, Response, HTMLResponse, JSONResponse
|
|
@@ -363,6 +364,45 @@ def create_app(worker=run_reported,store=None,gpu_worker=None,track_library=None
|
|
| 363 |
except InvalidAnalysisOutput: raise HTTPException(422,'Processing failed; complete analysis could not be verified.') from None
|
| 364 |
except Exception: raise HTTPException(422,'Processing failed; no result published. Cleanup status requires verification.') from None
|
| 365 |
finally: admission.release()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
return app
|
| 367 |
|
| 368 |
app=create_app()
|
|
|
|
| 4 |
import json
|
| 5 |
from pathlib import Path
|
| 6 |
import re
|
| 7 |
+
import subprocess
|
| 8 |
import threading
|
| 9 |
from fastapi import HTTPException, Request
|
| 10 |
from fastapi.responses import FileResponse, Response, HTMLResponse, JSONResponse
|
|
|
|
| 364 |
except InvalidAnalysisOutput: raise HTTPException(422,'Processing failed; complete analysis could not be verified.') from None
|
| 365 |
except Exception: raise HTTPException(422,'Processing failed; no result published. Cleanup status requires verification.') from None
|
| 366 |
finally: admission.release()
|
| 367 |
+
@app.post('/api/replay')
|
| 368 |
+
async def recorded_replay(request:Request):
|
| 369 |
+
# Share CPU admission with training; recordings never allocate a GPU.
|
| 370 |
+
if not admission.acquire(blocking=False):
|
| 371 |
+
raise HTTPException(429,'Worker busy; finish the current run before recording a drive')
|
| 372 |
+
data=bytearray()
|
| 373 |
+
try:
|
| 374 |
+
async for chunk in request.stream():
|
| 375 |
+
if len(data)+len(chunk)>3*1024*1024:
|
| 376 |
+
raise HTTPException(413,'Replay input exceeds the upload limit')
|
| 377 |
+
data.extend(chunk)
|
| 378 |
+
try:
|
| 379 |
+
body=json.loads(data)
|
| 380 |
+
if not isinstance(body,dict) or set(body)!={'track','network','ticks'}:
|
| 381 |
+
raise ValueError()
|
| 382 |
+
if type(body['ticks']) is not int or not 1<=body['ticks']<=900:
|
| 383 |
+
raise ValueError()
|
| 384 |
+
track=base64.b64decode(body['track'],validate=True)
|
| 385 |
+
network=base64.b64decode(body['network'],validate=True)
|
| 386 |
+
if not track or not network or max(len(track),len(network))>1024*1024:
|
| 387 |
+
raise ValueError()
|
| 388 |
+
except Exception:
|
| 389 |
+
raise HTTPException(400,'Invalid replay input; select a track, a network and up to 15 seconds') from None
|
| 390 |
+
finally:
|
| 391 |
+
data.clear()
|
| 392 |
+
from replay import record_replay, ReplayError
|
| 393 |
+
try:
|
| 394 |
+
recording=await run_in_threadpool(record_replay,track,network,
|
| 395 |
+
binary=ROOT/'bin/altd-sim',importer=ROOT/'bin/extract_saved_track',
|
| 396 |
+
scene_template=ROOT/'assets/formula_template.json',ticks=body['ticks'])
|
| 397 |
+
recording['build']=build_identity()
|
| 398 |
+
return JSONResponse(recording,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'})
|
| 399 |
+
except (TimeoutError,subprocess.TimeoutExpired):
|
| 400 |
+
raise HTTPException(504,'Recording reached its time limit. Try a 5-second drive.') from None
|
| 401 |
+
except (ReplayError,OSError,ValueError,KeyError,TypeError,subprocess.CalledProcessError):
|
| 402 |
+
raise HTTPException(422,'The engine could not produce a complete recorded drive. No estimated playback was substituted.') from None
|
| 403 |
+
finally:
|
| 404 |
+
data.clear()
|
| 405 |
+
admission.release()
|
| 406 |
return app
|
| 407 |
|
| 408 |
app=create_app()
|