Spaces:
Running on Zero
Running on Zero
Add measured replay black box with native controls and sensor inputs
Browse filesCapture actual cached pre-physics inputs and decision ticks, raw and normalized control commands, and post-physics snapshots from one bounded native CPU drive. Add manual sample/event navigation, side-by-side replay workspace, recording import and strict legacy/v2 handling. Native CPU states and frozen measurements match the retained simulator; CUDA library, importer, physics and policies are unchanged. Hosted acceptance is recorded separately.
- .gitattributes +1 -0
- BINARY_PROVENANCE.json +17 -58
- FRONTEND_SOURCE.json +8 -8
- README.md +4 -0
- RELEASE_MANIFEST.json +15 -11
- REPLAY_BLACKBOX.md +13 -0
- UPSTREAM.json +7 -6
- bin/altd-sim +2 -2
- client.js +16 -0
- index.html +13 -4
- native/blackbox-build-receipt.json +70 -0
- native/blackbox-source.tar.gz +3 -0
- native/pre-blackbox-binary-provenance.json +69 -0
- replay.py +55 -22
- server.py +6 -3
- track-map.js +112 -12
.gitattributes
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
bin/* filter=lfs diff=lfs merge=lfs -text
|
| 2 |
native/selective-freeze-source.tar.gz filter=lfs diff=lfs merge=lfs -text
|
| 3 |
native/physics-source.tar.gz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 1 |
bin/* filter=lfs diff=lfs merge=lfs -text
|
| 2 |
native/selective-freeze-source.tar.gz filter=lfs diff=lfs merge=lfs -text
|
| 3 |
native/physics-source.tar.gz filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
native/blackbox-source.tar.gz filter=lfs diff=lfs merge=lfs -text
|
BINARY_PROVENANCE.json
CHANGED
|
@@ -1,69 +1,28 @@
|
|
| 1 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
"artifacts": {
|
| 3 |
"bin/altd-sim": {
|
| 4 |
-
"
|
| 5 |
-
"
|
|
|
|
| 6 |
},
|
| 7 |
"bin/extract_saved_track": {
|
| 8 |
-
"bytes": 3247776,
|
| 9 |
-
"sha256": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a"
|
| 10 |
-
},
|
| 11 |
-
"bin/libaltd_gpu.so": {
|
| 12 |
-
"bytes": 4711704,
|
| 13 |
-
"sha256": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca"
|
| 14 |
-
}
|
| 15 |
-
},
|
| 16 |
-
"build_receipt_path": "native/selective-freeze-build-receipt.json",
|
| 17 |
-
"build_receipt_sha256": "ed804b1cd49a9ba34abc6f7cadaf4fb97807e27f3d14af117f8292a2d3f2d172",
|
| 18 |
-
"compiler": "rustc 1.95.0 aarch64-apple-darwin -> x86_64-unknown-linux-gnu",
|
| 19 |
-
"historical_native_files": [
|
| 20 |
-
"native/analysis.patch",
|
| 21 |
-
"native/build-receipt.json",
|
| 22 |
-
"native/source-receipt.json"
|
| 23 |
-
],
|
| 24 |
-
"linux_runtime_status": "hosted execution pending",
|
| 25 |
-
"retained_artifact_origins": {
|
| 26 |
-
"bin/extract_saved_track": {
|
| 27 |
-
"build_receipt_path": "IMPORTER_BUILD_RECEIPT.json",
|
| 28 |
-
"build_receipt_sha256": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 29 |
-
"bytes": 3247776,
|
| 30 |
-
"compatibility_receipt_path": "native/build-receipt.json",
|
| 31 |
-
"compatibility_receipt_sha256": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 32 |
-
"evidence_path": "IMPORTER_BUILD_RECEIPT.json",
|
| 33 |
-
"evidence_sha256": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 34 |
-
"origin_type": "retained_verified_importer_binary",
|
| 35 |
"sha256": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
},
|
| 39 |
"bin/libaltd_gpu.so": {
|
| 40 |
-
"
|
| 41 |
-
"
|
| 42 |
-
"
|
| 43 |
-
"origin_type": "retained_cuda_b90",
|
| 44 |
-
"patch_sha256": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 45 |
-
"sha256": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca",
|
| 46 |
-
"source_receipt_sha256": "36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93",
|
| 47 |
-
"source_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
|
| 48 |
}
|
| 49 |
},
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"source_repository": "https://github.com/Zorro909/driving_sim_rs",
|
| 53 |
-
"source_snapshot_path": "native/selective-freeze-source.tar.gz",
|
| 54 |
-
"source_snapshot_sha256": "297d2ce34569994acd2a477e64c5315cb2081699b4333b8abdabb3ff7451d8db",
|
| 55 |
-
"physics_update": {
|
| 56 |
-
"upstream_version": "1.3.0",
|
| 57 |
-
"upstream_revision": "ab3ec67",
|
| 58 |
-
"local_additions_preserved": true,
|
| 59 |
-
"source_receipt": "native/physics-source-identities.json",
|
| 60 |
-
"status": "candidate; requires deployment and hosted app acceptance",
|
| 61 |
-
"artifacts": {
|
| 62 |
-
"bin/altd-sim": "792139f30bcfecf25f2d78595d2611234d30b0a254b5ece0ec587f1d27a80df2",
|
| 63 |
-
"bin/libaltd_gpu.so": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05"
|
| 64 |
-
},
|
| 65 |
-
"source_archive": "native/physics-source.tar.gz",
|
| 66 |
-
"source_archive_sha256": "6abefb234de18dee58d69758c6cc8900f259993663ac37f90ce39aa114a30718",
|
| 67 |
-
"cuda_original_cases_passed": 57
|
| 68 |
-
}
|
| 69 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"schema": "ald-native-blackbox-binary-provenance/1",
|
| 3 |
+
"source_repository": "https://github.com/Zorro909/driving_sim_rs",
|
| 4 |
+
"base_hf_revision": "9c2dbdc053f255e4f928ba087ddad3337c287352",
|
| 5 |
+
"source_snapshot_path": "native/blackbox-source.tar.gz",
|
| 6 |
+
"source_snapshot_sha256": "56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28",
|
| 7 |
+
"build_receipt_path": "native/blackbox-build-receipt.json",
|
| 8 |
+
"build_receipt_sha256": "de3eea6c45a5ae4897287a97b0e2822fdae552a0655250bf940cef20d9b60d96",
|
| 9 |
"artifacts": {
|
| 10 |
"bin/altd-sim": {
|
| 11 |
+
"sha256": "225de9c2b7dd0d9cb11c2578f50fff8a34f4cf6223580035ab0fa64e700beac8",
|
| 12 |
+
"bytes": 3661976,
|
| 13 |
+
"origin": "new telemetry CLI build"
|
| 14 |
},
|
| 15 |
"bin/extract_saved_track": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
"sha256": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 17 |
+
"bytes": 3247776,
|
| 18 |
+
"origin": "retained unchanged from base HF release"
|
| 19 |
},
|
| 20 |
"bin/libaltd_gpu.so": {
|
| 21 |
+
"sha256": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05",
|
| 22 |
+
"bytes": 4707512,
|
| 23 |
+
"origin": "retained unchanged from base HF release"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
},
|
| 26 |
+
"historical_provenance_path": "native/pre-blackbox-binary-provenance.json",
|
| 27 |
+
"release_ceiling": "Local CPU and blackbox checks; fresh hosted GPU acceptance recorded separately before final release"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
FRONTEND_SOURCE.json
CHANGED
|
@@ -5,20 +5,20 @@
|
|
| 5 |
"source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
|
| 6 |
"source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"
|
| 7 |
},
|
| 8 |
-
"client_sha256": "
|
| 9 |
"identity_note": "The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.",
|
| 10 |
-
"local_customization_id": "
|
| 11 |
-
"output_sha256": "
|
| 12 |
"packaged_frontend_files": {
|
| 13 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 14 |
-
"client.js": "
|
| 15 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 16 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 17 |
-
"index.html": "
|
| 18 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 19 |
-
"track-map.js": "
|
| 20 |
},
|
| 21 |
-
"packaged_frontend_sha256": "
|
| 22 |
"packaged_frontend_sha256_format": "SHA-256 of UTF-8 JSON for packaged_frontend_files, sorted keys, compact separators, no trailing newline",
|
| 23 |
-
"status": "
|
| 24 |
}
|
|
|
|
| 5 |
"source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
|
| 6 |
"source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"
|
| 7 |
},
|
| 8 |
+
"client_sha256": "0526336091391457de43dc1fc3d3281a78a5458620fb49fde7a7e3d67ef3ce9e",
|
| 9 |
"identity_note": "The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.",
|
| 10 |
+
"local_customization_id": "native-blackbox-v2-20261023",
|
| 11 |
+
"output_sha256": "c23258d7493ce1d2304fe8d27b220bab1b8f09187c647e4048259cb9dc8e53c7",
|
| 12 |
"packaged_frontend_files": {
|
| 13 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 14 |
+
"client.js": "0526336091391457de43dc1fc3d3281a78a5458620fb49fde7a7e3d67ef3ce9e",
|
| 15 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 16 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 17 |
+
"index.html": "c23258d7493ce1d2304fe8d27b220bab1b8f09187c647e4048259cb9dc8e53c7",
|
| 18 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 19 |
+
"track-map.js": "1612b75e95ccbbb2da4a47d8c8bf2ee406e8f40c626475ae2a1c3c19e119a139"
|
| 20 |
},
|
| 21 |
+
"packaged_frontend_sha256": "077552d65d5a065443907db931a7c09b7d6161357f3517fa2f967f63ea64d42d",
|
| 22 |
"packaged_frontend_sha256_format": "SHA-256 of UTF-8 JSON for packaged_frontend_files, sorted keys, compact separators, no trailing newline",
|
| 23 |
+
"status": "Manual measured replay with native cached controls and sensor input black box"
|
| 24 |
}
|
README.md
CHANGED
|
@@ -39,6 +39,10 @@ Recipes and batches are saved in this browser by default. After connecting an ex
|
|
| 39 |
|
| 40 |
The packaged worker supports Formula, Rally, Snowmobile, and Truck. Networks can use up to 20 inputs (13 raycasts plus 7 other sensors), 12 total layers, and 16 neurons per hidden layer. Formula, Rally, and Truck support up to five outputs; Snowmobile supports up to four and cannot use Handbrake. Fresh networks for every vehicle require Steering and Acceleration. Formula imports retain previously supported ordered output subsets; Rally, Snowmobile, and Truck imports must include Steering and Acceleration and follow their vehicle-specific output rules. Supported sensors use original-game defaults when optional parameters are omitted. These are the vanilla worker limits, not support for arbitrary modified-game networks. See [TRACK_LIBRARY.md](TRACK_LIBRARY.md) for track scope and provenance, and [WINNERS_AND_EVALUATION.md](WINNERS_AND_EVALUATION.md) for result interpretation.
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
## Results and evaluation
|
| 43 |
|
| 44 |
A training result can include the final-generation fitness winner, the fastest completed-lap controller, and a clean-lap controller when one qualifies. A controller may fill more than one role; unavailable roles are labeled. Per-car records cover supported reward channels, but population-relative ranks are not absolute scores across separate runs. Frozen evaluation uses one controller with unchanged weights. See [WINNERS_AND_EVALUATION.md](WINNERS_AND_EVALUATION.md) for the exact rules and comparison limits.
|
|
|
|
| 39 |
|
| 40 |
The packaged worker supports Formula, Rally, Snowmobile, and Truck. Networks can use up to 20 inputs (13 raycasts plus 7 other sensors), 12 total layers, and 16 neurons per hidden layer. Formula, Rally, and Truck support up to five outputs; Snowmobile supports up to four and cannot use Handbrake. Fresh networks for every vehicle require Steering and Acceleration. Formula imports retain previously supported ordered output subsets; Rally, Snowmobile, and Truck imports must include Steering and Acceleration and follow their vehicle-specific output rules. Supported sensors use original-game defaults when optional parameters are omitted. These are the vanilla worker limits, not support for arbitrary modified-game networks. See [TRACK_LIBRARY.md](TRACK_LIBRARY.md) for track scope and provenance, and [WINNERS_AND_EVALUATION.md](WINNERS_AND_EVALUATION.md) for result interpretation.
|
| 41 |
|
| 42 |
+
## Visual replay and debugging
|
| 43 |
+
|
| 44 |
+
Record a selected policy for 5 or 15 seconds, then play, pause, scrub, or step through actual native samples. The car black box shows measured motion, contacts, boost, raw/applied controls and the sensor inputs consumed by the policy at its recorded decision tick. Playback starts paused, the camera is manual, and optional pose smoothing does not interpolate telemetry. Recordings can be downloaded and reopened. A GPU-trained policy is replayed in a fresh CPU evaluation; this is separate from its original training generation. See [REPLAY_BLACKBOX.md](REPLAY_BLACKBOX.md) for capture phases and limits.
|
| 45 |
+
|
| 46 |
## Results and evaluation
|
| 47 |
|
| 48 |
A training result can include the final-generation fitness winner, the fastest completed-lap controller, and a clean-lap controller when one qualifies. A controller may fill more than one role; unavailable roles are labeled. Per-car records cover supported reward channels, but population-relative ranks are not absolute scores across separate runs. Frozen evaluation uses one controller with unchanged weights. See [WINNERS_AND_EVALUATION.md](WINNERS_AND_EVALUATION.md) for the exact rules and comparison limits.
|
RELEASE_MANIFEST.json
CHANGED
|
@@ -2,19 +2,20 @@
|
|
| 2 |
"backend": "gpu",
|
| 3 |
"files": {
|
| 4 |
"BETA_TESTING.md": "829dae67ec37dac2a4e7b178ab5ee4fc5f63896af132fbff457a5e35141fe94d",
|
| 5 |
-
"BINARY_PROVENANCE.json": "
|
| 6 |
"BUCKET_BUNDLE.md": "cb7a0f023d5a1f93f0288c6a5646b64f8f3e9d36150c0730aea94590e5b80260",
|
| 7 |
"CUDA_REFERENCE.md": "f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c",
|
| 8 |
"DATA_POLICY.md": "fc5978528abe1c21c14882417ac8a706e04a0831bbd3fdb3de8c64442c1a7b3d",
|
| 9 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 10 |
-
"FRONTEND_SOURCE.json": "
|
| 11 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 12 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 13 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
| 14 |
-
"README.md": "
|
|
|
|
| 15 |
"STORAGE_BOUNDARY.md": "1d0cab6c82723f1879d7b437bab6b0d21248436bce161f3fbfe6c0e83b9e87a1",
|
| 16 |
"TRACK_LIBRARY.md": "be64c91633ef6696233df537afeef4c5175b2d55daac9336758d9eb3d24763c9",
|
| 17 |
-
"UPSTREAM.json": "
|
| 18 |
"WINNERS_AND_EVALUATION.md": "f281cf496763c59c50a79161cfff1a46d3529edfcfaaa14962c9a3f2d4f9446f",
|
| 19 |
"analysis_result.py": "0a5d73fe8dbca934b21823b8ffbd50b0baaef8e15a39e32604eefe16f01d1cf2",
|
| 20 |
"app.py": "62dfcda6f47ea8c86d3f7919638d7bc1329507e6ec21377c13fcc33bea9d8f2d",
|
|
@@ -24,7 +25,7 @@
|
|
| 24 |
"assets/snowmobile_template.json": "8232f4d54829445c44de9292e12557bfe66b3d43e0363e07d2ae0b4118721c08",
|
| 25 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 26 |
"assets/truck_template.json": "a83bffea4d5e818c135ab3aba68a47aa288d6ad0e7b8bd9c95ed0dea9e9df2f7",
|
| 27 |
-
"bin/altd-sim": "
|
| 28 |
"bin/extract_saved_track": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 29 |
"bin/libaltd_gpu.so": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05",
|
| 30 |
"bucket_storage.py": "f3044964724de322c1372adc23fa31f83f33ff0096f2b8509eb137953d983ddb",
|
|
@@ -59,32 +60,35 @@
|
|
| 59 |
"builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
|
| 60 |
"builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
|
| 61 |
"builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
|
| 62 |
-
"client.js": "
|
| 63 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 64 |
"gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
|
| 65 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 66 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 67 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 68 |
-
"index.html": "
|
| 69 |
"native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
|
|
|
|
|
|
| 70 |
"native/build-receipt.json": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 71 |
"native/physics-build-receipt.json": "63561c108554a09884cc68a1bfadfb8a3f530a800de0700ff671d99e558609fe",
|
| 72 |
"native/physics-parity.json": "006cc6b455e8f49af395ed60f3c5c88dbb2865ee6cad51d8cfdae040293d9f9e",
|
| 73 |
"native/physics-source-identities.json": "e3414e24144d517f4a0309e1f9d0dbab8309b4c0b70bd61ccb2c0f40b8f14998",
|
| 74 |
"native/physics-source.tar.gz": "6abefb234de18dee58d69758c6cc8900f259993663ac37f90ce39aa114a30718",
|
| 75 |
"native/physics.patch": "bae8eeb9342cad1e52ad77dc8026b8bd2ff510a1b836165fc753c043a04859b3",
|
|
|
|
| 76 |
"native/selective-freeze-build-receipt.json": "ed804b1cd49a9ba34abc6f7cadaf4fb97807e27f3d14af117f8292a2d3f2d172",
|
| 77 |
"native/selective-freeze-source.tar.gz": "297d2ce34569994acd2a477e64c5315cb2081699b4333b8abdabb3ff7451d8db",
|
| 78 |
"native/source-receipt.json": "36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93",
|
| 79 |
"native/upstream-v1.3.patch": "a9810c9abd40ddcbac5fcf6914320d5fc04fcde23849df9437ec293c83be15ed",
|
| 80 |
"portable_bundle.py": "58df5b87956745ea909c41c4b12a23cf9ab8c40a7df47d1529f47ae4de9bf51f",
|
| 81 |
-
"replay.py": "
|
| 82 |
"requirements.txt": "fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4",
|
| 83 |
-
"server.py": "
|
| 84 |
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 85 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 86 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 87 |
-
"track-map.js": "
|
| 88 |
"training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
|
| 89 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 90 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
|
@@ -94,7 +98,7 @@
|
|
| 94 |
"vendor/hf-oauth/package.json": "fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8",
|
| 95 |
"worker.py": "d18053363baf8147425808e9fd939f44f00af23781662d353196ade974144974"
|
| 96 |
},
|
| 97 |
-
"interface": "single-engine-
|
| 98 |
"physics_revision": "20261017-v1.3-candidate",
|
| 99 |
"upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
|
| 100 |
}
|
|
|
|
| 2 |
"backend": "gpu",
|
| 3 |
"files": {
|
| 4 |
"BETA_TESTING.md": "829dae67ec37dac2a4e7b178ab5ee4fc5f63896af132fbff457a5e35141fe94d",
|
| 5 |
+
"BINARY_PROVENANCE.json": "4c8fbc45f4766fecbc04cc23bfd953e7fd8f4d96532150524ef75c76fbe569ce",
|
| 6 |
"BUCKET_BUNDLE.md": "cb7a0f023d5a1f93f0288c6a5646b64f8f3e9d36150c0730aea94590e5b80260",
|
| 7 |
"CUDA_REFERENCE.md": "f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c",
|
| 8 |
"DATA_POLICY.md": "fc5978528abe1c21c14882417ac8a706e04a0831bbd3fdb3de8c64442c1a7b3d",
|
| 9 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 10 |
+
"FRONTEND_SOURCE.json": "927344c920ee813ab9cfcec43a1adebdb7a8e8ec3de7892596ec570e97f92fb2",
|
| 11 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 12 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 13 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
| 14 |
+
"README.md": "d40518e2b817bc95e28253352d75752a410a85d4ad662e6cbb5398b921769d87",
|
| 15 |
+
"REPLAY_BLACKBOX.md": "08cbb1fca44e54ac127d049fbad4d34f7e2048c3609f362277bd86dfe524931f",
|
| 16 |
"STORAGE_BOUNDARY.md": "1d0cab6c82723f1879d7b437bab6b0d21248436bce161f3fbfe6c0e83b9e87a1",
|
| 17 |
"TRACK_LIBRARY.md": "be64c91633ef6696233df537afeef4c5175b2d55daac9336758d9eb3d24763c9",
|
| 18 |
+
"UPSTREAM.json": "78472cb79a40839face9ccf81f5b87f3f20a5f1d663101287070222f54ee4608",
|
| 19 |
"WINNERS_AND_EVALUATION.md": "f281cf496763c59c50a79161cfff1a46d3529edfcfaaa14962c9a3f2d4f9446f",
|
| 20 |
"analysis_result.py": "0a5d73fe8dbca934b21823b8ffbd50b0baaef8e15a39e32604eefe16f01d1cf2",
|
| 21 |
"app.py": "62dfcda6f47ea8c86d3f7919638d7bc1329507e6ec21377c13fcc33bea9d8f2d",
|
|
|
|
| 25 |
"assets/snowmobile_template.json": "8232f4d54829445c44de9292e12557bfe66b3d43e0363e07d2ae0b4118721c08",
|
| 26 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 27 |
"assets/truck_template.json": "a83bffea4d5e818c135ab3aba68a47aa288d6ad0e7b8bd9c95ed0dea9e9df2f7",
|
| 28 |
+
"bin/altd-sim": "225de9c2b7dd0d9cb11c2578f50fff8a34f4cf6223580035ab0fa64e700beac8",
|
| 29 |
"bin/extract_saved_track": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 30 |
"bin/libaltd_gpu.so": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05",
|
| 31 |
"bucket_storage.py": "f3044964724de322c1372adc23fa31f83f33ff0096f2b8509eb137953d983ddb",
|
|
|
|
| 60 |
"builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
|
| 61 |
"builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
|
| 62 |
"builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
|
| 63 |
+
"client.js": "0526336091391457de43dc1fc3d3281a78a5458620fb49fde7a7e3d67ef3ce9e",
|
| 64 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 65 |
"gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
|
| 66 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 67 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 68 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 69 |
+
"index.html": "c23258d7493ce1d2304fe8d27b220bab1b8f09187c647e4048259cb9dc8e53c7",
|
| 70 |
"native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 71 |
+
"native/blackbox-build-receipt.json": "de3eea6c45a5ae4897287a97b0e2822fdae552a0655250bf940cef20d9b60d96",
|
| 72 |
+
"native/blackbox-source.tar.gz": "56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28",
|
| 73 |
"native/build-receipt.json": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 74 |
"native/physics-build-receipt.json": "63561c108554a09884cc68a1bfadfb8a3f530a800de0700ff671d99e558609fe",
|
| 75 |
"native/physics-parity.json": "006cc6b455e8f49af395ed60f3c5c88dbb2865ee6cad51d8cfdae040293d9f9e",
|
| 76 |
"native/physics-source-identities.json": "e3414e24144d517f4a0309e1f9d0dbab8309b4c0b70bd61ccb2c0f40b8f14998",
|
| 77 |
"native/physics-source.tar.gz": "6abefb234de18dee58d69758c6cc8900f259993663ac37f90ce39aa114a30718",
|
| 78 |
"native/physics.patch": "bae8eeb9342cad1e52ad77dc8026b8bd2ff510a1b836165fc753c043a04859b3",
|
| 79 |
+
"native/pre-blackbox-binary-provenance.json": "99c24677a039993b6100ce24551a01753c0187980f6c9c54be865b3dc7a65d00",
|
| 80 |
"native/selective-freeze-build-receipt.json": "ed804b1cd49a9ba34abc6f7cadaf4fb97807e27f3d14af117f8292a2d3f2d172",
|
| 81 |
"native/selective-freeze-source.tar.gz": "297d2ce34569994acd2a477e64c5315cb2081699b4333b8abdabb3ff7451d8db",
|
| 82 |
"native/source-receipt.json": "36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93",
|
| 83 |
"native/upstream-v1.3.patch": "a9810c9abd40ddcbac5fcf6914320d5fc04fcde23849df9437ec293c83be15ed",
|
| 84 |
"portable_bundle.py": "58df5b87956745ea909c41c4b12a23cf9ab8c40a7df47d1529f47ae4de9bf51f",
|
| 85 |
+
"replay.py": "5c2167adb2557c9fc798f2ad308805e4b5fcead344ba9b7de3cf9948bd568d76",
|
| 86 |
"requirements.txt": "fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4",
|
| 87 |
+
"server.py": "35747ac597969b2ec8625669bb5e6eb528f3905379f650092b2b7ed8308333af",
|
| 88 |
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 89 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 90 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 91 |
+
"track-map.js": "1612b75e95ccbbb2da4a47d8c8bf2ee406e8f40c626475ae2a1c3c19e119a139",
|
| 92 |
"training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
|
| 93 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 94 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
|
|
|
| 98 |
"vendor/hf-oauth/package.json": "fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8",
|
| 99 |
"worker.py": "d18053363baf8147425808e9fd939f44f00af23781662d353196ade974144974"
|
| 100 |
},
|
| 101 |
+
"interface": "single-engine-manual-replay-native-blackbox-v2",
|
| 102 |
"physics_revision": "20261017-v1.3-candidate",
|
| 103 |
"upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
|
| 104 |
}
|
REPLAY_BLACKBOX.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Measured replay and car black box
|
| 2 |
+
|
| 3 |
+
Recording runs the selected unchanged policy in one fresh native CPU drive, including when the policy was trained on GPU. It is not a recording of an earlier training population. Physics and decisions run at 60 Hz with batch_count=1; snapshots are saved at tick 1, every sixth tick and the requested endpoint (5 or 15 seconds). The new single-pass recorder replaces repeated prefix runs. No commands or sensor values are reconstructed in the browser.
|
| 4 |
+
|
| 5 |
+
Playback starts paused. Full-circuit and follow cameras are manual. Previous/next sample selects actual measurements. Measured poses are the default; optional pose smoothing interpolates position/heading only. The inspector always displays the preceding actual native sample, not an interpolated control or sensor value.
|
| 6 |
+
|
| 7 |
+
The car black box shows post-physics position, velocity, heading, angular velocity, boost energy, active status, physical contact counter and statistics update count. Native control commands appear beside their normalized values applied by physics. The ordered sensor vector is copied from the inference cache that actually drove the controls, with its pre-physics decision tick. Held decisions retain their original tick; reset clears the cache. Missing output slots are native defaults, not claimed neural outputs. Hidden-layer activations and ray-hit positions are not recorded.
|
| 8 |
+
|
| 9 |
+
Contact and active-state events mark sampled intervals. A contact increase in (tick A, tick B] does not identify an exact impact tick, and the engine contact counter is not a count of unique crashes. Reward metrics are cumulative native statistics, not instantaneous reward contributions.
|
| 10 |
+
|
| 11 |
+
Version 2 JSON contains explicit capture provenance and phases, the ordered sensor descriptors/output slots, each frame's raw/applied commands, and its cached decision tick/input vector. The browser rejects mismatched selected track/policy hashes, missing samples, nonfinite values, invalid sensor layouts, normalization mismatches, future or backward decision clocks and decreasing contact counters. Legacy version 1 recordings remain viewable with controls/sensors explicitly unavailable. Open recording and Download recording support repeatable inspection without starting another simulation. Imported recordings are data, not authenticated provenance claims.
|
| 12 |
+
|
| 13 |
+
The native extension does not change CUDA kernels, GPU ABI, physics or evolutionary policy rules. An actual Linux CPU comparison checks old versus new physical samples exactly. Native cache tests check pre-physics inputs and last decision/reset semantics. GPU execution of the new candidate is a separate quota-checked release gate; CPU replay cannot establish general CPU/GPU or original-game parity.
|
UPSTREAM.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
| 3 |
"importer_source_revision": "d8ab74c25df70b6a86ed101080e33d5fb785ea6f",
|
| 4 |
"math_profile": "proton",
|
| 5 |
"native_build": {
|
| 6 |
-
"build_receipt_path": "native/
|
| 7 |
-
"build_receipt_sha256": "
|
| 8 |
"cuda_library_origin": "newly compiled library; 57 original-engine cases passed on HF ZeroGPU",
|
| 9 |
-
"simulator_origin": "
|
| 10 |
"source_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0",
|
| 11 |
-
"source_snapshot_path": "native/
|
| 12 |
-
"source_snapshot_sha256": "
|
| 13 |
},
|
| 14 |
"open_prs_not_included": [
|
| 15 |
19,
|
|
@@ -19,5 +19,6 @@
|
|
| 19 |
"role": "gpu",
|
| 20 |
"ui_refresh_date": "2026-10-12",
|
| 21 |
"physics_revision": "ald-parity-20261017",
|
| 22 |
-
"applied_upstream_delta": "65810f5..ab3ec67 (release/build delta; HF feature branch retained)"
|
|
|
|
| 23 |
}
|
|
|
|
| 3 |
"importer_source_revision": "d8ab74c25df70b6a86ed101080e33d5fb785ea6f",
|
| 4 |
"math_profile": "proton",
|
| 5 |
"native_build": {
|
| 6 |
+
"build_receipt_path": "native/blackbox-build-receipt.json",
|
| 7 |
+
"build_receipt_sha256": "de3eea6c45a5ae4897287a97b0e2822fdae552a0655250bf940cef20d9b60d96",
|
| 8 |
"cuda_library_origin": "newly compiled library; 57 original-engine cases passed on HF ZeroGPU",
|
| 9 |
+
"simulator_origin": "Retained physics branch plus read-only cached decision telemetry and bounded single-pass recorder",
|
| 10 |
"source_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0",
|
| 11 |
+
"source_snapshot_path": "native/blackbox-source.tar.gz",
|
| 12 |
+
"source_snapshot_sha256": "56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28"
|
| 13 |
},
|
| 14 |
"open_prs_not_included": [
|
| 15 |
19,
|
|
|
|
| 19 |
"role": "gpu",
|
| 20 |
"ui_refresh_date": "2026-10-12",
|
| 21 |
"physics_revision": "ald-parity-20261017",
|
| 22 |
+
"applied_upstream_delta": "65810f5..ab3ec67 (release/build delta; HF feature branch retained)",
|
| 23 |
+
"replay_revision": "native-blackbox-v2-20261023"
|
| 24 |
}
|
bin/altd-sim
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:225de9c2b7dd0d9cb11c2578f50fff8a34f4cf6223580035ab0fa64e700beac8
|
| 3 |
+
size 3661976
|
client.js
CHANGED
|
@@ -90,6 +90,7 @@ function buttons() {
|
|
| 90 |
$('forgetLocal').disabled = busy || !localResult;
|
| 91 |
$('track').disabled=unavailable;
|
| 92 |
$('network').disabled=unavailable;
|
|
|
|
| 93 |
if($('replay_record'))$('replay_record').disabled=unavailable||batchActive||!capabilitiesReady||!replayNetworkSource();
|
| 94 |
if($('replay_duration'))$('replay_duration').disabled=settingsUnavailable;
|
| 95 |
if($('replay_download'))$('replay_download').disabled=replayBusy||!localReplay;
|
|
@@ -515,6 +516,21 @@ if($('replay_record'))$('replay_record').onclick=async()=>{
|
|
| 515 |
clearTimeout(timeout);replayBusy=false;busy=false;buttons();
|
| 516 |
}
|
| 517 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
if($('replay_download'))$('replay_download').onclick=()=>{
|
| 519 |
if(localReplay)downloadTextFile(JSON.stringify(localReplay),'ald-recorded-drive.json','application/json');
|
| 520 |
};
|
|
|
|
| 90 |
$('forgetLocal').disabled = busy || !localResult;
|
| 91 |
$('track').disabled=unavailable;
|
| 92 |
$('network').disabled=unavailable;
|
| 93 |
+
if($('replay_import'))$('replay_import').disabled=unavailable||batchActive||!replayNetworkSource();
|
| 94 |
if($('replay_record'))$('replay_record').disabled=unavailable||batchActive||!capabilitiesReady||!replayNetworkSource();
|
| 95 |
if($('replay_duration'))$('replay_duration').disabled=settingsUnavailable;
|
| 96 |
if($('replay_download'))$('replay_download').disabled=replayBusy||!localReplay;
|
|
|
|
| 516 |
clearTimeout(timeout);replayBusy=false;busy=false;buttons();
|
| 517 |
}
|
| 518 |
};
|
| 519 |
+
if($('replay_focus'))$('replay_focus').onclick=()=>{const focused=$('drive').classList.toggle('replay-focused');$('replay_focus').textContent=focused?'Return to run setup':'Expand replay workspace';$('replay_focus').setAttribute('aria-pressed',String(focused));};
|
| 520 |
+
if($('replay_import'))$('replay_import').onchange=async()=>{
|
| 521 |
+
const file=$('replay_import').files[0];$('replay_import').value='';
|
| 522 |
+
if(!file||busy||job||batchActive||bucketOperation||bundleRestoring)return;
|
| 523 |
+
busy=true;replayBusy=true;invalidateReplay('Opening recorded drive…');const attempt=replayRequestId;buttons();
|
| 524 |
+
try{
|
| 525 |
+
if(file.size<1||file.size>2*1024*1024)throw Error('Choose a recording under 2 MiB.');
|
| 526 |
+
const track=await trackSource(),network=replayNetworkSource();if(!track||!network)throw Error('Select the matching track and policy first.');
|
| 527 |
+
const [trackSha,networkSha]=await Promise.all([track.arrayBuffer().then(sha),network.arrayBuffer().then(sha)]);
|
| 528 |
+
const recording=JSON.parse(await file.text());
|
| 529 |
+
if(attempt!==replayRequestId)throw Error('Selection changed while opening recording.');
|
| 530 |
+
window.TrackReplay.setRecording(recording,trackSha,networkSha);localReplay=recording;
|
| 531 |
+
}catch(error){if(attempt===replayRequestId)window.TrackReplay?.clearRecording?.('Recording unavailable: '+error.message);}
|
| 532 |
+
finally{busy=false;replayBusy=false;buttons();}
|
| 533 |
+
};
|
| 534 |
if($('replay_download'))$('replay_download').onclick=()=>{
|
| 535 |
if(localReplay)downloadTextFile(JSON.stringify(localReplay),'ald-recorded-drive.json','application/json');
|
| 536 |
};
|
index.html
CHANGED
|
@@ -6,6 +6,8 @@
|
|
| 6 |
*{box-sizing:border-box}button,select,summary{min-height:44px}button,a,summary,input,select,textarea{scroll-margin-top:90px}:focus-visible{outline:3px solid #ffd073;outline-offset:3px}.lab-header{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:6px 0}.lab-header h1{font-size:clamp(26px,3vw,34px);margin-top:4px}.lab-header .intro{font-size:16px;margin-top:5px}.header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.help-link{display:inline-flex;align-items:center;min-height:44px;border:1px solid #49627c;border-radius:8px;padding:9px 13px;text-decoration:none;font-weight:650}.about{max-width:none;margin:4px 0 0;padding:0;border:0}.about>summary{display:flex;align-items:center;font-size:14px;width:fit-content;gap:8px}.about>summary::before{content:'▸'}.about[open]>summary::before{content:'▾'}.about[open]{border:1px solid #2a3d52;border-radius:10px;padding:0 16px 12px}.quick-start{border:1px solid #385568;background:#102232;border-radius:12px;margin-top:8px;padding:0 16px}.quick-start>summary{padding:12px 0;line-height:1.4}.quick-start>summary .hint{font-size:14px;font-weight:400;margin-left:12px}.quick-start[open]>summary{margin-bottom:0}.quick-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;list-style:none;padding:12px 0;margin:0;counter-reset:quick}.quick-steps li{counter-increment:quick;min-width:0}.quick-steps strong{display:block;margin-bottom:8px}.quick-steps strong::before{content:counter(quick) '. ';color:#62dfb1}.quick-steps p{font-size:14px;line-height:1.55;color:#b9cddd;margin:0 0 10px}.quick-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:12px 0;border-top:1px solid #2a3d52}.tabs{margin:12px 0 0;gap:8px}.tabs button{border-radius:9px}.pane>.card{margin-top:16px}.section-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.section-heading h2,.section-heading h3{margin:0}.section-heading a{font-size:14px}.section-intro{max-width:85ch;margin:10px 0 18px;color:#b9cddd;line-height:1.55}.recipe-panel{border:1px solid #385568;border-radius:12px;background:#102030;padding:16px;margin:16px 0 24px}.recipe-panel .row label{margin-top:0}.recipe-actions,.batch-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}.form-state{font-size:14px;font-weight:650;color:#ffd073;margin:10px 0 0;line-height:1.55}.form-state[data-state="saved"]{color:#82e6bb}.form-state[data-state="invalid"]{color:#ffb5a2}.settings-summary{font-size:14px;color:#b9cddd;line-height:1.55;margin:8px 0 0;overflow-wrap:anywhere}.recipe-panel details{margin-top:12px;padding-top:10px}.recipe-panel textarea{box-sizing:border-box;width:100%;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:8px;padding:10px}.recipe-panel .manage-actions{display:flex;align-items:start;gap:12px;flex-wrap:wrap}.destructive-action{margin-top:14px;color:#ffb5a2}.batch-layout{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(280px,1fr);gap:24px;align-items:start}.batch-setup{background:#102030;border:1px solid #385568;border-radius:12px;padding:18px;position:sticky;top:92px}.batch-setup h3{margin:0 0 10px}.batch-context{margin:12px 0}.batch-context div{padding:9px 0;border-bottom:1px solid #2a3d52}.batch-context dt{font-size:13px;color:#a2b4c8}.batch-context dd{margin:3px 0 0;overflow-wrap:anywhere;font-weight:600}.sequence-heading{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:22px}.sequence-heading h3{margin:0}.step-count{font-size:14px;color:#b9cddd;white-space:nowrap}.batch-steps{list-style:none;counter-reset:steps;padding:0;margin:12px 0}.batch-step{border:1px solid #3b5268;border-radius:10px;padding:14px;margin-bottom:10px;background:#101d2c}.batch-step[data-invalid="true"]{border-color:#d78970}.step-header{display:grid;grid-template-columns:30px minmax(0,1fr);align-items:center;gap:10px}.step-number{color:#82e6bb;font-size:14px;font-weight:800;text-align:center}.step-summary{font-size:14px;line-height:1.5;color:#b9cddd;margin:10px 0 8px;padding-left:40px}.step-actions{display:flex;gap:8px;flex-wrap:wrap;padding-left:40px}.step-actions button{font-size:14px;padding:8px 10px}.batch-empty{border:1px dashed #49627c;border-radius:10px;padding:18px;margin:12px 0;color:#b9cddd;line-height:1.55}.batch-empty p{margin:0 0 12px}.add-step{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:end}.add-step label{margin:0}.add-step select{margin-top:6px}.batch-mode{margin:18px 0 6px;display:flex;align-items:flex-start;gap:10px;line-height:1.5}.batch-mode input{margin-top:5px;flex:none}.batch-progress{border-top:1px solid #2a3d52;margin-top:24px;padding-top:20px}.batch-progress h3{margin:0 0 12px}.batch-progress #batch_status{line-height:1.6}.batch-setup #batch_start{flex:1;min-width:140px}.batch-setup #batch_ready{line-height:1.55}.batch-actions button{min-height:46px}#recipe_feedback:empty,#batches_feedback:empty{display:none}.muted-note{font-size:14px;line-height:1.6;color:#b9cddd}.training-track{margin-top:18px}.training-track label{margin-top:0}
|
| 7 |
@media(max-width:1000px){.batch-layout{grid-template-columns:minmax(0,1fr)}.batch-setup{position:static}.quick-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:600px){body{padding:12px}.card{padding:16px}.row{grid-template-columns:minmax(0,1fr)}.quick-steps{grid-template-columns:minmax(0,1fr)}.header-actions{width:100%}.header-actions>*{flex:1;justify-content:center;text-align:center}.lab-header .intro{font-size:15px}.quick-start>summary .hint{display:block;margin-left:18px}.tabs{display:grid;grid-template-columns:1fr 1fr;gap:6px}.tabs button{font-size:14px;padding:9px 7px}.step-actions,.step-summary{padding-left:0}.add-step{grid-template-columns:minmax(0,1fr)}.batch-setup{padding:14px}.recipe-actions>*{flex:1}.reward{grid-template-columns:minmax(0,1fr) minmax(85px,.7fr) 65px 44px}.reward input{width:65px}.recipe-panel{padding:12px}}
|
| 8 |
.replay-bar{background:#101d2c;border:1px solid #2a3d52;border-radius:11px;padding:10px 12px;margin:10px 0}.replay-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.replay-row button{min-height:36px;padding:6px 11px;font-size:13px;font-weight:650}.replay-row select{width:auto;min-height:36px;padding:5px 9px;font-size:13px}.replay-row input[type=range]{flex:1;min-width:120px;accent-color:#62dfb1;padding:0}.replay-time{font:600 12px ui-monospace,SFMono-Regular,monospace;color:#91efc6;white-space:nowrap}.replay-layers{display:flex;gap:6px 12px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px solid #223447;font-size:12.5px;color:#b9cddd}.replay-layers label{display:inline-flex;align-items:center;gap:5px;margin:0;cursor:pointer}.replay-layers input{margin:0}.kpi-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:12px 0}.kpi-tile{background:#101d2c;border:1px solid #2a3d52;border-radius:10px;padding:10px 12px}.kpi-tile .kpi-label{font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;color:#a2b4c8}.kpi-tile .kpi-val{font:800 19px ui-monospace,SFMono-Regular,monospace;color:#62dfb1;margin-top:3px}.kpi-tile .kpi-sub{font-size:11.5px;color:#8fa6bc;margin-top:2px}.chart-box{background:#09111b;border:1px solid #2a3d52;border-radius:10px;padding:10px;margin:10px 0}.chart-box canvas{max-height:190px;display:block}.subtab-bar{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.subtab-bar button{min-height:36px;padding:6px 12px;font-size:13px;border-radius:18px}.subtab-bar button[aria-pressed=true]{background:#62dfb1;color:#102522;font-weight:700}.editor-visual-grid{display:grid;grid-template-columns:minmax(260px,.85fr) minmax(0,1.35fr);gap:16px;align-items:start;margin:14px 0}@media(max-width:850px){.kpi-strip{grid-template-columns:1fr 1fr}.editor-visual-grid{grid-template-columns:minmax(0,1fr)}}
|
|
|
|
|
|
|
| 9 |
</style>
|
| 10 |
<dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
|
| 11 |
<h2 id="appConfirmTitle">Confirm action</h2>
|
|
@@ -48,16 +50,23 @@
|
|
| 48 |
<p><label>Track (.track) <input id="track" type="file" accept=".track"></label></p>
|
| 49 |
<p><label>Vehicle network (.evolution) <input id="network" type="file" accept=".evolution"></label></p>
|
| 50 |
<p id="networkChoice" role="status">Choose an uploaded file or use a network from the browser editor.</p><p class="privacy-note">Uploads are processed for this run. By default, the server copy is deleted after the browser verifies the download.</p><details><summary>Optional repeat download</summary><label class="keep-result"><input id="keepServer" type="checkbox"> Keep this result on the server for up to five minutes so I can download it again</label><p class="privacy-note">This is optional and resets after each run. When bucket auto-save is on, verified receipt must delete the server copy first, so repeat download is disabled. All results expire after five minutes. <a href="/policy" target="_blank" rel="noopener">Data policy</a></p>
|
| 51 |
-
</details></section><section class="card"><h2>
|
| 52 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
<p id="replay_policy" class="hint">Choose or create a network to record a drive.</p>
|
| 54 |
<p class="hint">Fresh CPU drive · unchanged weights · 60 Hz physics and decisions · wall-hit and idle elimination off. Recording takes a few seconds; playback runs in your browser.</p>
|
| 55 |
<p id="replay_status" role="status">No replay recorded. The map shows the selected track and its saved starting point.</p>
|
| 56 |
-
<div class="replay-row"><button type="button" id="replay_play" disabled>Play</button><button type="button" id="replay_reset" disabled>Restart</button><input type="range" id="replay_scrub" min="1" max="300" value="1" disabled aria-label="Replay tick scrubber"><span id="replay_time_label" class="replay-time">No recording</span></div>
|
| 57 |
<div class="replay-row" style="margin-top:8px"><label for="replay_camera" style="margin:0">Camera</label><select id="replay_camera" aria-label="Replay camera mode" disabled><option value="full" selected>Full circuit</option><option value="follow">Follow car</option></select><label for="replay_speed" style="margin:0">Speed</label><select id="replay_speed" aria-label="Replay playback speed" disabled><option value="0.25">0.25×</option><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="2">2×</option></select></div>
|
| 58 |
<div class="replay-layers"><label><input type="checkbox" id="replay_layer_outline" checked disabled> Car outline</label><label><input type="checkbox" id="replay_layer_line" checked disabled> Recorded path</label><label><input type="checkbox" id="replay_layer_hud" checked disabled> Recorded telemetry</label></div>
|
|
|
|
|
|
|
| 59 |
<p id="replay_readout" style="font-size:16px;font-variant-numeric:tabular-nums" aria-live="off"></p>
|
| 60 |
-
<details><summary>What does this recording show?</summary><p>
|
| 61 |
</div><div class="map-legend"><span><i class="terrain asphalt"></i> Asphalt</span><span><i class="terrain dirt"></i> Dirt</span><span><i class="terrain ice"></i> Ice</span><span><i class="terrain start"></i> Start</span></div><p id="mapLegend" class="hint"></p><h2>Run & results</h2><button id="download" disabled>Download result</button> <button id="erase" disabled>Delete server-side job data now</button>
|
| 62 |
<p><button id="saveLocal" disabled>Download received result (browser only)</button> <button id="forgetLocal" disabled>Discard browser result</button></p>
|
| 63 |
<p>The browser keeps the received result in memory until discarded, replaced by a new run, or this page is closed. It is not saved to browser storage.</p>
|
|
|
|
| 6 |
*{box-sizing:border-box}button,select,summary{min-height:44px}button,a,summary,input,select,textarea{scroll-margin-top:90px}:focus-visible{outline:3px solid #ffd073;outline-offset:3px}.lab-header{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:6px 0}.lab-header h1{font-size:clamp(26px,3vw,34px);margin-top:4px}.lab-header .intro{font-size:16px;margin-top:5px}.header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.help-link{display:inline-flex;align-items:center;min-height:44px;border:1px solid #49627c;border-radius:8px;padding:9px 13px;text-decoration:none;font-weight:650}.about{max-width:none;margin:4px 0 0;padding:0;border:0}.about>summary{display:flex;align-items:center;font-size:14px;width:fit-content;gap:8px}.about>summary::before{content:'▸'}.about[open]>summary::before{content:'▾'}.about[open]{border:1px solid #2a3d52;border-radius:10px;padding:0 16px 12px}.quick-start{border:1px solid #385568;background:#102232;border-radius:12px;margin-top:8px;padding:0 16px}.quick-start>summary{padding:12px 0;line-height:1.4}.quick-start>summary .hint{font-size:14px;font-weight:400;margin-left:12px}.quick-start[open]>summary{margin-bottom:0}.quick-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;list-style:none;padding:12px 0;margin:0;counter-reset:quick}.quick-steps li{counter-increment:quick;min-width:0}.quick-steps strong{display:block;margin-bottom:8px}.quick-steps strong::before{content:counter(quick) '. ';color:#62dfb1}.quick-steps p{font-size:14px;line-height:1.55;color:#b9cddd;margin:0 0 10px}.quick-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:12px 0;border-top:1px solid #2a3d52}.tabs{margin:12px 0 0;gap:8px}.tabs button{border-radius:9px}.pane>.card{margin-top:16px}.section-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.section-heading h2,.section-heading h3{margin:0}.section-heading a{font-size:14px}.section-intro{max-width:85ch;margin:10px 0 18px;color:#b9cddd;line-height:1.55}.recipe-panel{border:1px solid #385568;border-radius:12px;background:#102030;padding:16px;margin:16px 0 24px}.recipe-panel .row label{margin-top:0}.recipe-actions,.batch-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}.form-state{font-size:14px;font-weight:650;color:#ffd073;margin:10px 0 0;line-height:1.55}.form-state[data-state="saved"]{color:#82e6bb}.form-state[data-state="invalid"]{color:#ffb5a2}.settings-summary{font-size:14px;color:#b9cddd;line-height:1.55;margin:8px 0 0;overflow-wrap:anywhere}.recipe-panel details{margin-top:12px;padding-top:10px}.recipe-panel textarea{box-sizing:border-box;width:100%;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:8px;padding:10px}.recipe-panel .manage-actions{display:flex;align-items:start;gap:12px;flex-wrap:wrap}.destructive-action{margin-top:14px;color:#ffb5a2}.batch-layout{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(280px,1fr);gap:24px;align-items:start}.batch-setup{background:#102030;border:1px solid #385568;border-radius:12px;padding:18px;position:sticky;top:92px}.batch-setup h3{margin:0 0 10px}.batch-context{margin:12px 0}.batch-context div{padding:9px 0;border-bottom:1px solid #2a3d52}.batch-context dt{font-size:13px;color:#a2b4c8}.batch-context dd{margin:3px 0 0;overflow-wrap:anywhere;font-weight:600}.sequence-heading{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:22px}.sequence-heading h3{margin:0}.step-count{font-size:14px;color:#b9cddd;white-space:nowrap}.batch-steps{list-style:none;counter-reset:steps;padding:0;margin:12px 0}.batch-step{border:1px solid #3b5268;border-radius:10px;padding:14px;margin-bottom:10px;background:#101d2c}.batch-step[data-invalid="true"]{border-color:#d78970}.step-header{display:grid;grid-template-columns:30px minmax(0,1fr);align-items:center;gap:10px}.step-number{color:#82e6bb;font-size:14px;font-weight:800;text-align:center}.step-summary{font-size:14px;line-height:1.5;color:#b9cddd;margin:10px 0 8px;padding-left:40px}.step-actions{display:flex;gap:8px;flex-wrap:wrap;padding-left:40px}.step-actions button{font-size:14px;padding:8px 10px}.batch-empty{border:1px dashed #49627c;border-radius:10px;padding:18px;margin:12px 0;color:#b9cddd;line-height:1.55}.batch-empty p{margin:0 0 12px}.add-step{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:end}.add-step label{margin:0}.add-step select{margin-top:6px}.batch-mode{margin:18px 0 6px;display:flex;align-items:flex-start;gap:10px;line-height:1.5}.batch-mode input{margin-top:5px;flex:none}.batch-progress{border-top:1px solid #2a3d52;margin-top:24px;padding-top:20px}.batch-progress h3{margin:0 0 12px}.batch-progress #batch_status{line-height:1.6}.batch-setup #batch_start{flex:1;min-width:140px}.batch-setup #batch_ready{line-height:1.55}.batch-actions button{min-height:46px}#recipe_feedback:empty,#batches_feedback:empty{display:none}.muted-note{font-size:14px;line-height:1.6;color:#b9cddd}.training-track{margin-top:18px}.training-track label{margin-top:0}
|
| 7 |
@media(max-width:1000px){.batch-layout{grid-template-columns:minmax(0,1fr)}.batch-setup{position:static}.quick-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:600px){body{padding:12px}.card{padding:16px}.row{grid-template-columns:minmax(0,1fr)}.quick-steps{grid-template-columns:minmax(0,1fr)}.header-actions{width:100%}.header-actions>*{flex:1;justify-content:center;text-align:center}.lab-header .intro{font-size:15px}.quick-start>summary .hint{display:block;margin-left:18px}.tabs{display:grid;grid-template-columns:1fr 1fr;gap:6px}.tabs button{font-size:14px;padding:9px 7px}.step-actions,.step-summary{padding-left:0}.add-step{grid-template-columns:minmax(0,1fr)}.batch-setup{padding:14px}.recipe-actions>*{flex:1}.reward{grid-template-columns:minmax(0,1fr) minmax(85px,.7fr) 65px 44px}.reward input{width:65px}.recipe-panel{padding:12px}}
|
| 8 |
.replay-bar{background:#101d2c;border:1px solid #2a3d52;border-radius:11px;padding:10px 12px;margin:10px 0}.replay-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.replay-row button{min-height:36px;padding:6px 11px;font-size:13px;font-weight:650}.replay-row select{width:auto;min-height:36px;padding:5px 9px;font-size:13px}.replay-row input[type=range]{flex:1;min-width:120px;accent-color:#62dfb1;padding:0}.replay-time{font:600 12px ui-monospace,SFMono-Regular,monospace;color:#91efc6;white-space:nowrap}.replay-layers{display:flex;gap:6px 12px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px solid #223447;font-size:12.5px;color:#b9cddd}.replay-layers label{display:inline-flex;align-items:center;gap:5px;margin:0;cursor:pointer}.replay-layers input{margin:0}.kpi-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:12px 0}.kpi-tile{background:#101d2c;border:1px solid #2a3d52;border-radius:10px;padding:10px 12px}.kpi-tile .kpi-label{font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;color:#a2b4c8}.kpi-tile .kpi-val{font:800 19px ui-monospace,SFMono-Regular,monospace;color:#62dfb1;margin-top:3px}.kpi-tile .kpi-sub{font-size:11.5px;color:#8fa6bc;margin-top:2px}.chart-box{background:#09111b;border:1px solid #2a3d52;border-radius:10px;padding:10px;margin:10px 0}.chart-box canvas{max-height:190px;display:block}.subtab-bar{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.subtab-bar button{min-height:36px;padding:6px 12px;font-size:13px;border-radius:18px}.subtab-bar button[aria-pressed=true]{background:#62dfb1;color:#102522;font-weight:700}.editor-visual-grid{display:grid;grid-template-columns:minmax(260px,.85fr) minmax(0,1.35fr);gap:16px;align-items:start;margin:14px 0}@media(max-width:850px){.kpi-strip{grid-template-columns:1fr 1fr}.editor-visual-grid{grid-template-columns:minmax(0,1fr)}}
|
| 9 |
+
|
| 10 |
+
.replay-workbench{display:grid;gap:14px;align-items:start}#replay_blackbox{max-height:520px;overflow:auto;padding:12px;border:1px solid #2a3d52;border-radius:10px;min-width:0}#replay_blackbox h3{margin-top:0}#replay_blackbox td,#replay_blackbox th{padding:6px;font-size:12px}#drive.replay-focused>.grid{grid-template-columns:minmax(0,1fr)}#drive.replay-focused>.grid>section:first-child{display:none}#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1.35fr) minmax(320px,1fr)}#drive.replay-focused .map{position:sticky;top:70px}#drive.replay-focused .map canvas{max-height:500px}@media(max-width:850px){#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1fr)}#drive.replay-focused .map{position:static}#replay_blackbox{max-height:400px}}
|
| 11 |
</style>
|
| 12 |
<dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
|
| 13 |
<h2 id="appConfirmTitle">Confirm action</h2>
|
|
|
|
| 50 |
<p><label>Track (.track) <input id="track" type="file" accept=".track"></label></p>
|
| 51 |
<p><label>Vehicle network (.evolution) <input id="network" type="file" accept=".evolution"></label></p>
|
| 52 |
<p id="networkChoice" role="status">Choose an uploaded file or use a network from the browser editor.</p><p class="privacy-note">Uploads are processed for this run. By default, the server copy is deleted after the browser verifies the download.</p><details><summary>Optional repeat download</summary><label class="keep-result"><input id="keepServer" type="checkbox"> Keep this result on the server for up to five minutes so I can download it again</label><p class="privacy-note">This is optional and resets after each run. When bucket auto-save is on, verified receipt must delete the server copy first, so repeat download is disabled. All results expire after five minutes. <a href="/policy" target="_blank" rel="noopener">Data policy</a></p>
|
| 53 |
+
</details></section><section class="card"><h2>Visual replay & car black box</h2><p id="mapStatus" class="map-status" role="status">Loading selected track…</p><button type="button" id="replay_focus" aria-pressed="false">Expand replay workspace</button><div class="replay-workbench"><div class="map"><canvas id="trackMap" width="1000" height="500" role="img" aria-label="Selected track tile layout and terrain"></canvas></div><section id="replay_blackbox" hidden aria-label="Recorded car black box"><h3>Car black box</h3><p id="blackbox_phase" class="hint"></p>
|
| 54 |
+
<p class="hint">All values below belong to the selected native sample. Sensor inputs were consumed before physics at the decision tick; vehicle state is captured after physics. Samples are 0.1 seconds apart. Contact events identify intervals, not exact impact ticks; the counter does not count unique crashes.</p>
|
| 55 |
+
<details><summary>Recorded vehicle state</summary><div class="table-scroll"><table><thead><tr><th>Vehicle state</th><th>Recorded value</th></tr></thead><tbody id="blackbox_state"></tbody></table></div></details>
|
| 56 |
+
<h4>Native control commands</h4><div class="table-scroll"><table><thead><tr><th>Control</th><th>Raw command</th><th>Applied after normalization</th><th>Source</th></tr></thead><tbody id="blackbox_controls"></tbody></table></div>
|
| 57 |
+
<details><summary>Inputs consumed by the native network</summary><div class="table-scroll"><table><thead><tr><th>Input index</th><th>Sensor</th><th>Parameters</th><th>Native input value</th></tr></thead><tbody id="blackbox_inputs"></tbody></table></div><p class="hint">These are the input values the policy consumed. Ray inputs are not ray-hit positions. Hidden-layer activations are not recorded.</p></details>
|
| 58 |
+
<details><summary>Recorded cumulative reward metrics</summary><p class="hint">Metrics accumulate at native statistics callbacks; they are not instantaneous forces or per-reward contributions.</p><div class="table-scroll"><table><thead><tr><th>Metric</th><th>Recorded cumulative value</th></tr></thead><tbody id="blackbox_metrics"></tbody></table></div></details></section></div><div class="replay-bar" id="replayControls" aria-label="Recorded drive controls">
|
| 59 |
+
<div class="replay-row"><button type="button" id="replay_record" disabled>Record selected policy</button><label for="replay_duration" style="margin:0">Length</label><select id="replay_duration"><option value="300" selected>5 seconds</option><option value="900">15 seconds</option></select><button type="button" id="replay_download" disabled>Download recording</button><label for="replay_import">Open recording<input type="file" id="replay_import" accept=".json,application/json" disabled></label></div>
|
| 60 |
<p id="replay_policy" class="hint">Choose or create a network to record a drive.</p>
|
| 61 |
<p class="hint">Fresh CPU drive · unchanged weights · 60 Hz physics and decisions · wall-hit and idle elimination off. Recording takes a few seconds; playback runs in your browser.</p>
|
| 62 |
<p id="replay_status" role="status">No replay recorded. The map shows the selected track and its saved starting point.</p>
|
| 63 |
+
<div class="replay-row"><button type="button" id="replay_play" disabled>Play</button><button type="button" id="replay_reset" disabled>Restart</button><button type="button" id="replay_prev" disabled>Previous sample</button><button type="button" id="replay_next" disabled>Next sample</button><input type="range" id="replay_scrub" min="1" max="300" value="1" disabled aria-label="Replay tick scrubber"><span id="replay_time_label" class="replay-time">No recording</span></div>
|
| 64 |
<div class="replay-row" style="margin-top:8px"><label for="replay_camera" style="margin:0">Camera</label><select id="replay_camera" aria-label="Replay camera mode" disabled><option value="full" selected>Full circuit</option><option value="follow">Follow car</option></select><label for="replay_speed" style="margin:0">Speed</label><select id="replay_speed" aria-label="Replay playback speed" disabled><option value="0.25">0.25×</option><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="2">2×</option></select></div>
|
| 65 |
<div class="replay-layers"><label><input type="checkbox" id="replay_layer_outline" checked disabled> Car outline</label><label><input type="checkbox" id="replay_layer_line" checked disabled> Recorded path</label><label><input type="checkbox" id="replay_layer_hud" checked disabled> Recorded telemetry</label></div>
|
| 66 |
+
<div class="replay-row"><label><input type="checkbox" id="replay_interpolate" disabled> Smooth pose between measured samples</label><label for="replay_event">Contact / active-state interval</label><select id="replay_event" aria-label="Recorded event interval" disabled><option value="">No recording</option></select></div>
|
| 67 |
+
|
| 68 |
<p id="replay_readout" style="font-size:16px;font-variant-numeric:tabular-nums" aria-live="off"></p>
|
| 69 |
+
<details><summary>What does this recording show?</summary><p>A single fresh CPU drive records native state, raw and normalized control commands, and the exact cached sensor inputs every 0.1 seconds. Physics and policy decisions run at 60 Hz. Playback starts paused and shows measured poses by default; optional pose smoothing only interpolates between adjacent measured samples. It does not simulate the car. Black-box values always remain at an actual recorded sample. Ray-hit positions, skid marks and hidden-layer activations are not recorded. This is a selected-policy CPU replay, including for GPU-trained policies; it is not a recording of the original training population.</p><p>The recording remains in this page until replaced, cleared by a changed selection, or the page is closed. Use Download recording to keep its measured samples.</p></details>
|
| 70 |
</div><div class="map-legend"><span><i class="terrain asphalt"></i> Asphalt</span><span><i class="terrain dirt"></i> Dirt</span><span><i class="terrain ice"></i> Ice</span><span><i class="terrain start"></i> Start</span></div><p id="mapLegend" class="hint"></p><h2>Run & results</h2><button id="download" disabled>Download result</button> <button id="erase" disabled>Delete server-side job data now</button>
|
| 71 |
<p><button id="saveLocal" disabled>Download received result (browser only)</button> <button id="forgetLocal" disabled>Discard browser result</button></p>
|
| 72 |
<p>The browser keeps the received result in memory until discarded, replaced by a new run, or this page is closed. It is not saved to browser storage.</p>
|
native/blackbox-build-receipt.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "ald-native-blackbox-build/1",
|
| 3 |
+
"source_archive_sha256": "56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28",
|
| 4 |
+
"base_source_archive_sha256": "6abefb234de18dee58d69758c6cc8900f259993663ac37f90ce39aa114a30718",
|
| 5 |
+
"build": {
|
| 6 |
+
"command": [
|
| 7 |
+
"/Users/tiga/.cargo/bin/cargo",
|
| 8 |
+
"+1.95.0",
|
| 9 |
+
"build",
|
| 10 |
+
"--manifest-path",
|
| 11 |
+
"/Volumes/PATRIOT/Codex/2026-10-17/c/work/hf-ald-release-20261023/native-source/Cargo.toml",
|
| 12 |
+
"--target",
|
| 13 |
+
"x86_64-unknown-linux-gnu",
|
| 14 |
+
"--release",
|
| 15 |
+
"--locked",
|
| 16 |
+
"--offline",
|
| 17 |
+
"--no-default-features",
|
| 18 |
+
"--features",
|
| 19 |
+
"cli",
|
| 20 |
+
"--bin",
|
| 21 |
+
"altd-sim"
|
| 22 |
+
],
|
| 23 |
+
"environment": {
|
| 24 |
+
"CARGO_TARGET_DIR": "/Volumes/PATRIOT/Codex/2026-10-17/c/work/hf-ald-release-20261023/linux-target",
|
| 25 |
+
"CARGO_BUILD_JOBS": "2",
|
| 26 |
+
"CARGO_PROFILE_RELEASE_LTO": "false",
|
| 27 |
+
"CARGO_PROFILE_RELEASE_CODEGEN_UNITS": "16",
|
| 28 |
+
"CARGO_PROFILE_RELEASE_DEBUG": "0",
|
| 29 |
+
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-tools/linux-linker",
|
| 30 |
+
"RUSTFLAGS": "--sysroot /Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-sysroot -C target-cpu=x86-64"
|
| 31 |
+
},
|
| 32 |
+
"exit_code": 0,
|
| 33 |
+
"seconds": 8.920037874951959,
|
| 34 |
+
"scope": "Isolated cross-target; existing official Rust target and linker/sysroot read only",
|
| 35 |
+
"sha256": "225de9c2b7dd0d9cb11c2578f50fff8a34f4cf6223580035ab0fa64e700beac8",
|
| 36 |
+
"bytes": 3661976
|
| 37 |
+
},
|
| 38 |
+
"binary_sha256": "225de9c2b7dd0d9cb11c2578f50fff8a34f4cf6223580035ab0fa64e700beac8",
|
| 39 |
+
"native_library_tests": {
|
| 40 |
+
"passed": 88,
|
| 41 |
+
"ignored": 2
|
| 42 |
+
},
|
| 43 |
+
"cpu_recording_validation": {
|
| 44 |
+
"status": "pass",
|
| 45 |
+
"cases": [
|
| 46 |
+
{
|
| 47 |
+
"ticks": 300,
|
| 48 |
+
"samples": 51,
|
| 49 |
+
"seconds": 0.7552870421204716,
|
| 50 |
+
"old_physical_fields_exact": true,
|
| 51 |
+
"inference_ticks_current": true
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"ticks": 900,
|
| 55 |
+
"samples": 151,
|
| 56 |
+
"seconds": 2.2923885830678046,
|
| 57 |
+
"old_physical_fields_exact": true,
|
| 58 |
+
"inference_ticks_current": true
|
| 59 |
+
}
|
| 60 |
+
],
|
| 61 |
+
"five_second_prefix_exact": true,
|
| 62 |
+
"constant_commands_verified": true,
|
| 63 |
+
"constant_contacts": 743,
|
| 64 |
+
"scope": "Actual Linux x86_64 native binary in network-isolated Docker; exact comparison against previously downloaded hosted native states"
|
| 65 |
+
},
|
| 66 |
+
"cuda_library_changed": false,
|
| 67 |
+
"gpu_abi_changed": false,
|
| 68 |
+
"hosted_gpu_execution": "Not run for this candidate; requires separate quota-checked acceptance",
|
| 69 |
+
"scope": "Actual cached CPU inference telemetry and bounded single-pass CLI recording. Physics and evolution algorithms unchanged."
|
| 70 |
+
}
|
native/blackbox-source.tar.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28
|
| 3 |
+
size 922923
|
native/pre-blackbox-binary-provenance.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"artifacts": {
|
| 3 |
+
"bin/altd-sim": {
|
| 4 |
+
"bytes": 3647808,
|
| 5 |
+
"sha256": "ce31f754f6b6f3afaf4dcc44c1e6fda3e6414eb2924fa167b8778523bcaf2070"
|
| 6 |
+
},
|
| 7 |
+
"bin/extract_saved_track": {
|
| 8 |
+
"bytes": 3247776,
|
| 9 |
+
"sha256": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a"
|
| 10 |
+
},
|
| 11 |
+
"bin/libaltd_gpu.so": {
|
| 12 |
+
"bytes": 4711704,
|
| 13 |
+
"sha256": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca"
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"build_receipt_path": "native/selective-freeze-build-receipt.json",
|
| 17 |
+
"build_receipt_sha256": "ed804b1cd49a9ba34abc6f7cadaf4fb97807e27f3d14af117f8292a2d3f2d172",
|
| 18 |
+
"compiler": "rustc 1.95.0 aarch64-apple-darwin -> x86_64-unknown-linux-gnu",
|
| 19 |
+
"historical_native_files": [
|
| 20 |
+
"native/analysis.patch",
|
| 21 |
+
"native/build-receipt.json",
|
| 22 |
+
"native/source-receipt.json"
|
| 23 |
+
],
|
| 24 |
+
"linux_runtime_status": "hosted execution pending",
|
| 25 |
+
"retained_artifact_origins": {
|
| 26 |
+
"bin/extract_saved_track": {
|
| 27 |
+
"build_receipt_path": "IMPORTER_BUILD_RECEIPT.json",
|
| 28 |
+
"build_receipt_sha256": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 29 |
+
"bytes": 3247776,
|
| 30 |
+
"compatibility_receipt_path": "native/build-receipt.json",
|
| 31 |
+
"compatibility_receipt_sha256": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 32 |
+
"evidence_path": "IMPORTER_BUILD_RECEIPT.json",
|
| 33 |
+
"evidence_sha256": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 34 |
+
"origin_type": "retained_verified_importer_binary",
|
| 35 |
+
"sha256": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 36 |
+
"source_repository": "https://github.com/Zorro909/driving_sim_rs",
|
| 37 |
+
"source_revision": "d8ab74c25df70b6a86ed101080e33d5fb785ea6f"
|
| 38 |
+
},
|
| 39 |
+
"bin/libaltd_gpu.so": {
|
| 40 |
+
"build_receipt_path": "native/build-receipt.json",
|
| 41 |
+
"build_receipt_sha256": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 42 |
+
"bytes": 4711704,
|
| 43 |
+
"origin_type": "retained_cuda_b90",
|
| 44 |
+
"patch_sha256": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 45 |
+
"sha256": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca",
|
| 46 |
+
"source_receipt_sha256": "36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93",
|
| 47 |
+
"source_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
"schema": "ald-selective-freeze-binary-provenance-v1",
|
| 51 |
+
"source_base_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0",
|
| 52 |
+
"source_repository": "https://github.com/Zorro909/driving_sim_rs",
|
| 53 |
+
"source_snapshot_path": "native/selective-freeze-source.tar.gz",
|
| 54 |
+
"source_snapshot_sha256": "297d2ce34569994acd2a477e64c5315cb2081699b4333b8abdabb3ff7451d8db",
|
| 55 |
+
"physics_update": {
|
| 56 |
+
"upstream_version": "1.3.0",
|
| 57 |
+
"upstream_revision": "ab3ec67",
|
| 58 |
+
"local_additions_preserved": true,
|
| 59 |
+
"source_receipt": "native/physics-source-identities.json",
|
| 60 |
+
"status": "candidate; requires deployment and hosted app acceptance",
|
| 61 |
+
"artifacts": {
|
| 62 |
+
"bin/altd-sim": "792139f30bcfecf25f2d78595d2611234d30b0a254b5ece0ec587f1d27a80df2",
|
| 63 |
+
"bin/libaltd_gpu.so": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05"
|
| 64 |
+
},
|
| 65 |
+
"source_archive": "native/physics-source.tar.gz",
|
| 66 |
+
"source_archive_sha256": "6abefb234de18dee58d69758c6cc8900f259993663ac37f90ce39aa114a30718",
|
| 67 |
+
"cuda_original_cases_passed": 57
|
| 68 |
+
}
|
| 69 |
+
}
|
replay.py
CHANGED
|
@@ -1,11 +1,8 @@
|
|
| 1 |
-
"""Bounded
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
and disables wall/idle elimination. With population one its preserved parent
|
| 7 |
-
is the supplied policy; every dump is checked against that exact parameter
|
| 8 |
-
vector before any recording is returned.
|
| 9 |
"""
|
| 10 |
|
| 11 |
import hashlib
|
|
@@ -132,7 +129,17 @@ def _parameter_bytes(values):
|
|
| 132 |
return b''.join(struct.pack('!d', _number(value, 'policy parameter')) for value in values)
|
| 133 |
|
| 134 |
|
| 135 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
agents = dump.get('agents')
|
| 137 |
networks = dump.get('next_networks')
|
| 138 |
if (not isinstance(agents, list) or len(agents) != 1 or not isinstance(agents[0], dict)
|
|
@@ -157,6 +164,23 @@ def _frame(dump, tick, expected_parameters):
|
|
| 157 |
'vy': velocity[1],
|
| 158 |
'active': agent['active'],
|
| 159 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
for field in ('angular_velocity', 'boost_energy'):
|
| 161 |
if field in agent:
|
| 162 |
frame[field] = _number(agent[field], field)
|
|
@@ -236,21 +260,22 @@ def record_replay(track_bytes, network_bytes, *, binary, importer, scene_templat
|
|
| 236 |
'--spawn-trace', spawn_path, '--population', '1', '--warmup-ticks', '0',
|
| 237 |
'--seed', '0', '--spawn-index', '0', '--batch-count', '1', '--dump-state', str(dump_path),
|
| 238 |
]
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
|
|
|
| 250 |
|
| 251 |
result = {
|
| 252 |
'schema': 'ald.recorded-replay',
|
| 253 |
-
'version':
|
| 254 |
'kind': 'fresh_cpu_evaluation',
|
| 255 |
'physics_hz': 60,
|
| 256 |
'sample_interval_ticks': SAMPLE_INTERVAL_TICKS,
|
|
@@ -268,7 +293,15 @@ def record_replay(track_bytes, network_bytes, *, binary, importer, scene_templat
|
|
| 268 |
'population': 1,
|
| 269 |
'warmup_ticks': 0,
|
| 270 |
'policy_weights_verified': True,
|
| 271 |
-
'sampling_method': '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
'position_units': 'game_world_units',
|
| 273 |
'velocity_units': 'game_world_units_per_second',
|
| 274 |
'rotation_units': 'radians',
|
|
|
|
| 1 |
+
"""Bounded native CPU replay with actual control and inference-cache telemetry.
|
| 2 |
+
|
| 3 |
+
One unchanged policy runs once. The native bench exports post-physics state
|
| 4 |
+
and its actual cached pre-physics inference inputs at each requested sample.
|
| 5 |
+
No sensor readings, commands or poses are reconstructed by this module.
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import hashlib
|
|
|
|
| 129 |
return b''.join(struct.pack('!d', _number(value, 'policy parameter')) for value in values)
|
| 130 |
|
| 131 |
|
| 132 |
+
def _controls(value, label):
|
| 133 |
+
fields = {'steering', 'acceleration', 'brake', 'handbrake', 'boost'}
|
| 134 |
+
if not isinstance(value, dict) or set(value) != fields:
|
| 135 |
+
raise ReplayError('Native replay returned invalid ' + label)
|
| 136 |
+
result = {key: _number(value[key], label) for key in sorted(fields)}
|
| 137 |
+
if any(abs(v) > 1 for v in result.values()):
|
| 138 |
+
raise ReplayError('Native replay returned out-of-range ' + label)
|
| 139 |
+
return result
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _frame(dump, tick, expected_parameters, *, sensor_count):
|
| 143 |
agents = dump.get('agents')
|
| 144 |
networks = dump.get('next_networks')
|
| 145 |
if (not isinstance(agents, list) or len(agents) != 1 or not isinstance(agents[0], dict)
|
|
|
|
| 164 |
'vy': velocity[1],
|
| 165 |
'active': agent['active'],
|
| 166 |
}
|
| 167 |
+
commands = _controls(agent.get('controls'), 'control commands')
|
| 168 |
+
applied = _controls(agent.get('applied_controls'), 'applied controls')
|
| 169 |
+
expected_applied = {key: max(0.0, value) if key in {'handbrake', 'boost'} else value
|
| 170 |
+
for key, value in commands.items()}
|
| 171 |
+
if applied != expected_applied:
|
| 172 |
+
raise ReplayError('Native replay control normalization does not match')
|
| 173 |
+
if 'decision' not in agent:
|
| 174 |
+
raise ReplayError('Native replay omitted its inference snapshot')
|
| 175 |
+
decision = agent['decision']
|
| 176 |
+
if decision is not None:
|
| 177 |
+
if (not isinstance(decision, dict) or set(decision) != {'tick', 'inputs'}
|
| 178 |
+
or type(decision['tick']) is not int or not 1 <= decision['tick'] <= tick
|
| 179 |
+
or not isinstance(decision['inputs'], list) or len(decision['inputs']) != sensor_count):
|
| 180 |
+
raise ReplayError('Native replay returned an invalid inference snapshot')
|
| 181 |
+
decision = {'tick': decision['tick'],
|
| 182 |
+
'inputs': [_number(value, 'sensor input') for value in decision['inputs']]}
|
| 183 |
+
frame.update(controls=commands, applied_controls=applied, decision=decision)
|
| 184 |
for field in ('angular_velocity', 'boost_energy'):
|
| 185 |
if field in agent:
|
| 186 |
frame[field] = _number(agent[field], field)
|
|
|
|
| 260 |
'--spawn-trace', spawn_path, '--population', '1', '--warmup-ticks', '0',
|
| 261 |
'--seed', '0', '--spawn-index', '0', '--batch-count', '1', '--dump-state', str(dump_path),
|
| 262 |
]
|
| 263 |
+
_run(command + ['--ticks', str(ticks), '--record-interval', str(SAMPLE_INTERVAL_TICKS)],
|
| 264 |
+
work=work, env=environment, deadline=deadline, stage='recording')
|
| 265 |
+
dump = _read_json(dump_path, MAX_DUMP_BYTES, 'car state')
|
| 266 |
+
samples = dump.get('replay_frames')
|
| 267 |
+
if (not isinstance(samples, list) or len(samples) != len(sample_ticks)
|
| 268 |
+
or any(not isinstance(sample, dict) or sample.get('tick') != tick
|
| 269 |
+
for sample, tick in zip(samples, sample_ticks))):
|
| 270 |
+
raise ReplayError('Native replay omitted its expected trace samples')
|
| 271 |
+
frames = [_frame({'agents': sample.get('agents'), 'next_networks': dump.get('next_networks')},
|
| 272 |
+
tick, expected_parameters, sensor_count=network['Shape'][0])
|
| 273 |
+
for sample, tick in zip(samples, sample_ticks)]
|
| 274 |
+
_remaining(deadline)
|
| 275 |
|
| 276 |
result = {
|
| 277 |
'schema': 'ald.recorded-replay',
|
| 278 |
+
'version': 2,
|
| 279 |
'kind': 'fresh_cpu_evaluation',
|
| 280 |
'physics_hz': 60,
|
| 281 |
'sample_interval_ticks': SAMPLE_INTERVAL_TICKS,
|
|
|
|
| 293 |
'population': 1,
|
| 294 |
'warmup_ticks': 0,
|
| 295 |
'policy_weights_verified': True,
|
| 296 |
+
'sampling_method': 'single_native_bench_trace',
|
| 297 |
+
'telemetry': {
|
| 298 |
+
'source': 'native_cached_inference',
|
| 299 |
+
'state_phase': 'post_physics',
|
| 300 |
+
'decision_phase': 'pre_physics_at_decision_tick',
|
| 301 |
+
'sensors': template['Sensors'],
|
| 302 |
+
'output_ids': template['Outputs'],
|
| 303 |
+
'controls': 'raw_network_commands_and_native_normalized_controls',
|
| 304 |
+
},
|
| 305 |
'position_units': 'game_world_units',
|
| 306 |
'velocity_units': 'game_world_units_per_second',
|
| 307 |
'rotation_units': 'radians',
|
server.py
CHANGED
|
@@ -25,12 +25,16 @@ from portable_bundle import (BundleError, load_bundle, MAX_BUNDLE, MAX_NETWORK,
|
|
| 25 |
ROOT=Path(__file__).resolve().parent
|
| 26 |
EVOLUTION_SIDECAR=re.compile(r'^[a-z0-9][a-z0-9-]{0,63}-(formula|rally|snowmobile|truck)-[0-9]{8}T[0-9]{6}Z-[0-9a-f]{8}(?:-(fitness-winner|fastest-lap-winner|clean-laps-winner))?\.evolution$')
|
| 27 |
|
| 28 |
-
def create_app(worker=run_reported,store=None,gpu_worker=None,track_library=None,require_analysis=False):
|
| 29 |
app=create_api(store)
|
| 30 |
admission=threading.BoundedSemaphore(1)
|
| 31 |
leaderboard_admission=threading.BoundedSemaphore(1)
|
| 32 |
bucket_admission=threading.BoundedSemaphore(1)
|
| 33 |
library=track_library if track_library is not None else TrackLibrary(ROOT/'builtin_tracks')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
@app.middleware('http')
|
| 36 |
async def bucket_no_store(request:Request, call_next):
|
|
@@ -392,8 +396,7 @@ def create_app(worker=run_reported,store=None,gpu_worker=None,track_library=None
|
|
| 392 |
from replay import record_replay, ReplayError
|
| 393 |
try:
|
| 394 |
recording=await run_in_threadpool(record_replay,track,network,
|
| 395 |
-
|
| 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):
|
|
|
|
| 25 |
ROOT=Path(__file__).resolve().parent
|
| 26 |
EVOLUTION_SIDECAR=re.compile(r'^[a-z0-9][a-z0-9-]{0,63}-(formula|rally|snowmobile|truck)-[0-9]{8}T[0-9]{6}Z-[0-9a-f]{8}(?:-(fitness-winner|fastest-lap-winner|clean-laps-winner))?\.evolution$')
|
| 27 |
|
| 28 |
+
def create_app(worker=run_reported,store=None,gpu_worker=None,track_library=None,require_analysis=False,replay_tools=None):
|
| 29 |
app=create_api(store)
|
| 30 |
admission=threading.BoundedSemaphore(1)
|
| 31 |
leaderboard_admission=threading.BoundedSemaphore(1)
|
| 32 |
bucket_admission=threading.BoundedSemaphore(1)
|
| 33 |
library=track_library if track_library is not None else TrackLibrary(ROOT/'builtin_tracks')
|
| 34 |
+
# Trusted application configuration only; request bodies never select tools.
|
| 35 |
+
replay_tools = dict(replay_tools) if replay_tools is not None else dict(
|
| 36 |
+
binary=ROOT/'bin/altd-sim', importer=ROOT/'bin/extract_saved_track',
|
| 37 |
+
scene_template=ROOT/'assets/formula_template.json')
|
| 38 |
|
| 39 |
@app.middleware('http')
|
| 40 |
async def bucket_no_store(request:Request, call_next):
|
|
|
|
| 396 |
from replay import record_replay, ReplayError
|
| 397 |
try:
|
| 398 |
recording=await run_in_threadpool(record_replay,track,network,
|
| 399 |
+
**replay_tools,ticks=body['ticks'])
|
|
|
|
| 400 |
recording['build']=build_identity()
|
| 401 |
return JSONResponse(recording,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'})
|
| 402 |
except (TimeoutError,subprocess.TimeoutExpired):
|
track-map.js
CHANGED
|
@@ -212,8 +212,56 @@
|
|
| 212 |
if(typeof value!=='string'||!/^[a-f0-9]{64}$/i.test(value))throw Error(label+' must be a SHA-256 hash');
|
| 213 |
return value.toLowerCase();
|
| 214 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
function validateRecording(payload,expectedTrackSha,expectedNetworkSha){
|
| 216 |
-
if(!payload||payload.schema!=='ald.recorded-replay'||payload.version
|
| 217 |
throw Error('This file is not a supported recorded CPU evaluation');
|
| 218 |
if(payload.physics_hz!==60||payload.control_hz!==60||payload.sample_interval_ticks!==6||payload.first_sample_tick!==1||
|
| 219 |
payload.execution_backend!=='cpu'||payload.math_profile!=='proton'||payload.collision_elimination!==false||payload.idle_elimination!==false)
|
|
@@ -229,6 +277,7 @@
|
|
| 229 |
if(expectedTicks[expectedTicks.length-1]!==ticks)expectedTicks.push(ticks);
|
| 230 |
if(!Array.isArray(payload.frames)||payload.frames.length!==expectedTicks.length)
|
| 231 |
throw Error('Recording is missing its expected native samples');
|
|
|
|
| 232 |
const frames=payload.frames.map((frame,index)=>{
|
| 233 |
if(!frame||frame.tick!==expectedTicks[index]||typeof frame.active!=='boolean')throw Error('Recorded frame ticks or active state are invalid');
|
| 234 |
const copy={tick:frame.tick,active:frame.active};
|
|
@@ -242,9 +291,27 @@
|
|
| 242 |
if((key==='collision_count'||key==='update_count')&&(!Number.isInteger(frame[key])||frame[key]<0))throw Error('Recorded '+key+' is invalid');
|
| 243 |
copy[key]=frame[key];
|
| 244 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
return copy;
|
| 246 |
});
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
first_sample_tick:1,track_sha256:trackSha,network_sha256:networkSha,vehicle_type:payload.vehicle_type,
|
| 249 |
requested_ticks:ticks,execution_backend:'cpu',math_profile:'proton',collision_elimination:false,idle_elimination:false,frames};
|
| 250 |
}
|
|
@@ -276,6 +343,7 @@
|
|
| 276 |
let playSpeed=Math.max(0.25,Math.min(4,Number(element('replay_speed')?.value)||1));
|
| 277 |
let cameraMode=element('replay_camera')?.value==='follow'?'follow':'full';
|
| 278 |
const layers={outline:true,line:true,hud:true};
|
|
|
|
| 279 |
const noRecording='No replay recorded. The map shows the selected track and its saved starting point.';
|
| 280 |
const summaryOnly='No replay recorded. This run contains summary measurements, not vehicle positions or control samples.';
|
| 281 |
function setText(id,text){const node=element(id);if(node&&node.textContent!==text)node.textContent=text;}
|
|
@@ -283,20 +351,48 @@
|
|
| 283 |
const last=recording?.frames[recording.frames.length-1].tick;
|
| 284 |
const play=element('replay_play');
|
| 285 |
if(play){play.disabled=!recording||cursor>=last;play.textContent=playing?'Pause':'Play';}
|
| 286 |
-
for(const id of ['replay_reset','replay_scrub','replay_speed','replay_camera','replay_layer_outline','replay_layer_line','replay_layer_hud']){
|
| 287 |
const node=element(id);if(node)node.disabled=!recording;
|
| 288 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
const scrub=element('replay_scrub');
|
| 290 |
if(scrub){scrub.min='1';scrub.max=String(last??1);scrub.step='1';scrub.value=String(Math.round(cursor));}
|
| 291 |
if(!recording){
|
| 292 |
setText('replay_status',reason||(hasRunSummary?summaryOnly:noRecording));
|
| 293 |
setText('replay_time_label','No recorded samples');
|
| 294 |
-
setText('replay_readout','Record a drive to inspect position and speed.
|
| 295 |
}else{
|
| 296 |
const state=cursor>=last?'Finished. Use Restart to view it again.':playing?'Playing.':'Paused.';
|
| 297 |
-
setText('replay_status',`Fresh recorded CPU evaluation · ${recording.frames.length} samples · 60 Hz physics and controls · 10 Hz recording. ${state}`);
|
| 298 |
}
|
| 299 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
function stopPlayback(){
|
| 301 |
if(animId!==null&&typeof root?.cancelAnimationFrame==='function')root.cancelAnimationFrame(animId);
|
| 302 |
animId=null;playing=false;lastFrameTime=null;
|
|
@@ -305,7 +401,8 @@
|
|
| 305 |
function draw(){
|
| 306 |
if(!currentTrack){renderEmpty();return;}
|
| 307 |
const ctx=canvas.getContext('2d'),view=viewport(canvas,currentTrack);
|
| 308 |
-
|
|
|
|
| 309 |
const position=state?worldPoint(view,state.x,state.y):null;
|
| 310 |
ctx.clearRect(0,0,canvas.width,canvas.height);ctx.fillStyle='#0c1926';ctx.fillRect(0,0,canvas.width,canvas.height);
|
| 311 |
ctx.save();
|
|
@@ -324,20 +421,20 @@
|
|
| 324 |
}
|
| 325 |
ctx.restore();
|
| 326 |
if(state){
|
| 327 |
-
const frame=state.frame
|
| 328 |
const contacts=frame.collision_count===undefined?'unavailable':String(frame.collision_count);
|
| 329 |
setText('replay_time_label',`${(cursor/60).toFixed(2)}s / ${(recording.requested_ticks/60).toFixed(2)}s · Tick ${Math.round(cursor)} · Recorded sample ${frame.tick}`);
|
| 330 |
-
setText('replay_readout',`Recorded sample tick ${frame.tick} (${(frame.tick/60).toFixed(2)}s). Speed ${speed.toFixed(1)} km/h, derived from recorded velocity. Position (${frame.x.toFixed(2)}, ${frame.y.toFixed(2)}). Contacts ${contacts}. ${frame.active?'Active':'Inactive'} at this sample.
|
| 331 |
if(layers.hud){
|
| 332 |
ctx.save();ctx.fillStyle='rgba(9,17,27,0.88)';ctx.beginPath();ctx.roundRect(12,12,310,80,8);ctx.fill();
|
| 333 |
ctx.fillStyle='#62dfb1';ctx.font='700 12px system-ui,sans-serif';ctx.fillText('RECORDED CPU EVALUATION',24,33);
|
| 334 |
ctx.fillStyle='#e5edf5';ctx.font='600 12px ui-monospace,monospace';ctx.fillText(`Sample ${frame.tick} · ${speed.toFixed(1)} km/h from velocity`,24,55);
|
| 335 |
-
ctx.fillStyle='#b4c5d5';ctx.font='12px system-ui,sans-serif';ctx.fillText('
|
| 336 |
}
|
| 337 |
}
|
| 338 |
}
|
| 339 |
function clearRecording(reason){
|
| 340 |
-
const hadRecording=recording!==null;stopPlayback();recording=null;cursor=1;
|
| 341 |
if(hadRecording)draw();syncControls(reason);
|
| 342 |
}
|
| 343 |
function step(now){
|
|
@@ -364,6 +461,9 @@
|
|
| 364 |
if(!recording)return;const value=Number(element('replay_scrub').value);if(!Number.isFinite(value))return;
|
| 365 |
stopPlayback();cursor=Math.max(recording.frames[0].tick,Math.min(recording.frames[recording.frames.length-1].tick,value));draw();syncControls();
|
| 366 |
});
|
|
|
|
|
|
|
|
|
|
| 367 |
on('replay_speed','change',()=>{playSpeed=Math.max(0.25,Math.min(4,Number(element('replay_speed').value)||1));});
|
| 368 |
on('replay_camera','change',()=>{cameraMode=element('replay_camera').value==='follow'?'follow':'full';draw();});
|
| 369 |
for(const key of ['outline','line','hud']){
|
|
@@ -413,7 +513,7 @@
|
|
| 413 |
const next=validateRecording(payload,expectedTrackSha,expectedNetworkSha);
|
| 414 |
if(!currentTrack||!currentTrackSha)throw Error('The selected track must finish loading and hash verification before replay');
|
| 415 |
if(next.track_sha256!==currentTrackSha)throw Error('The recording belongs to a different selected track');
|
| 416 |
-
recording=next;cursor=next.frames[0].tick;draw();syncControls();
|
| 417 |
return {frames:next.frames.length,first_tick:cursor,last_tick:next.frames[next.frames.length-1].tick};
|
| 418 |
}catch(error){clearRecording('Replay unavailable: '+error.message);throw error;}
|
| 419 |
},
|
|
@@ -422,5 +522,5 @@
|
|
| 422 |
bind();syncControls();document.addEventListener('track-library-change',refresh);refresh();
|
| 423 |
return controller;
|
| 424 |
}
|
| 425 |
-
return {mount,model,decode,render,validateRecording,sampleRecording};
|
| 426 |
});
|
|
|
|
| 212 |
if(typeof value!=='string'||!/^[a-f0-9]{64}$/i.test(value))throw Error(label+' must be a SHA-256 hash');
|
| 213 |
return value.toLowerCase();
|
| 214 |
}
|
| 215 |
+
const CONTROL_KEYS=['steering','acceleration','handbrake','brake','boost'];
|
| 216 |
+
const SCALAR_SENSORS=['speed','velocityFront','velocitySide','angularVelocity','wheelAngle','boostCapacity','correctDirection'];
|
| 217 |
+
function exactKeys(value,keys,label){
|
| 218 |
+
if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).sort().join()!==[...keys].sort().join())throw Error('Invalid '+label);
|
| 219 |
+
}
|
| 220 |
+
function finiteValue(value,label,limit=1e12){if(typeof value!=='number'||!Number.isFinite(value)||Math.abs(value)>limit)throw Error('Invalid '+label);return value;}
|
| 221 |
+
function sensorDescriptor(sensor){
|
| 222 |
+
const kind=sensor?.$type,keys=SCALAR_SENSORS.includes(kind)?[]:({raycast:['Degrees','Length'],accelerationFront:['MaxAcceleration'],accelerationSide:['MaxAcceleration'],distanceFromWall:['MaxDistance'],trackCurvature:['MinLookaheadDistance','MaxLookaheadDistance'],grip:['PositionOffset']})[kind];
|
| 223 |
+
if(!keys)throw Error('Unsupported recorded sensor');exactKeys(sensor,['$type',...keys],'sensor descriptor');
|
| 224 |
+
const clean={$type:kind};
|
| 225 |
+
for(const key of keys){
|
| 226 |
+
if(key==='PositionOffset'){exactKeys(sensor[key],['X','Y'],'grip offset');clean[key]={X:finiteValue(sensor[key].X,'grip X',1e6),Y:finiteValue(sensor[key].Y,'grip Y',1e6)};}
|
| 227 |
+
else clean[key]=finiteValue(sensor[key],'sensor '+key,1e6);
|
| 228 |
+
}
|
| 229 |
+
if(kind==='raycast'&&(Math.abs(clean.Degrees)>360||clean.Length<1||clean.Length>10000))throw Error('Invalid ray parameters');
|
| 230 |
+
if(keys.includes('MaxAcceleration')&&clean.MaxAcceleration<=0||kind==='distanceFromWall'&&clean.MaxDistance<=0)throw Error('Invalid sensor normalization');
|
| 231 |
+
return clean;
|
| 232 |
+
}
|
| 233 |
+
function recordingTelemetry(payload){
|
| 234 |
+
if(payload.version===1)return null;
|
| 235 |
+
if(payload.policy_weights_verified!==true||payload.sampling_method!=='single_native_bench_trace'||payload.population!==1||payload.warmup_ticks!==0||payload.seed!==0)throw Error('Unsupported native recording conditions');
|
| 236 |
+
const value=payload.telemetry;exactKeys(value,['source','state_phase','decision_phase','sensors','output_ids','controls'],'telemetry');
|
| 237 |
+
if(value.source!=='native_cached_inference'||value.state_phase!=='post_physics'||value.decision_phase!=='pre_physics_at_decision_tick'||value.controls!=='raw_network_commands_and_native_normalized_controls')throw Error('Unsupported telemetry phases');
|
| 238 |
+
if(!Array.isArray(value.sensors)||!value.sensors.length||value.sensors.length>20)throw Error('Invalid recorded sensor count');
|
| 239 |
+
const sensors=value.sensors.map(sensorDescriptor),ids=value.output_ids;
|
| 240 |
+
if(sensors.filter(x=>x.$type==='raycast').length>13||sensors.filter(x=>x.$type!=='raycast').length>7)throw Error('Invalid sensor layout');
|
| 241 |
+
if(!Array.isArray(ids)||!ids.length||ids.length>5||new Set(ids).size!==ids.length||ids.some(x=>!Number.isInteger(x)||x<0||x>4)||!ids.includes(0)||!ids.includes(1)||payload.vehicle_type===2&&ids.includes(2))throw Error('Invalid recorded output slots');
|
| 242 |
+
return {...value,sensors,output_ids:[...ids]};
|
| 243 |
+
}
|
| 244 |
+
function recordedControls(value,label){
|
| 245 |
+
exactKeys(value,CONTROL_KEYS,label);return Object.fromEntries(CONTROL_KEYS.map(key=>[key,finiteValue(value[key],label,1)]));
|
| 246 |
+
}
|
| 247 |
+
function recordingEvents(recording){
|
| 248 |
+
const result=[];
|
| 249 |
+
recording.frames.forEach((frame,index)=>{
|
| 250 |
+
const previous=recording.frames[index-1],from=previous?.tick??frame.tick;
|
| 251 |
+
if(frame.collision_count!==undefined){
|
| 252 |
+
const count=previous?.collision_count===undefined?frame.collision_count:frame.collision_count-previous.collision_count;
|
| 253 |
+
if(count>0)result.push({from_tick:from,to_tick:frame.tick,kind:'contacts',count,label:previous?`${count} contact(s) recorded in (${from}, ${frame.tick}]`:`${count} contact(s) at first sample ${frame.tick}`});
|
| 254 |
+
}
|
| 255 |
+
if(previous&&previous.active!==frame.active)result.push({from_tick:from,to_tick:frame.tick,kind:'active',label:`${frame.active?'Active':'Inactive'} by sample ${frame.tick}; changed in (${from}, ${frame.tick}]`});
|
| 256 |
+
});return result;
|
| 257 |
+
}
|
| 258 |
+
function adjacentSampleTick(recording,tick,direction){
|
| 259 |
+
if(direction!==-1&&direction!==1||!Number.isFinite(tick))throw Error('Invalid sample step');
|
| 260 |
+
const frames=recording.frames;
|
| 261 |
+
return direction>0?(frames.find(frame=>frame.tick>tick)?.tick??frames[frames.length-1].tick):([...frames].reverse().find(frame=>frame.tick<tick)?.tick??frames[0].tick);
|
| 262 |
+
}
|
| 263 |
function validateRecording(payload,expectedTrackSha,expectedNetworkSha){
|
| 264 |
+
if(!payload||payload.schema!=='ald.recorded-replay'||![1,2].includes(payload.version)||payload.kind!=='fresh_cpu_evaluation')
|
| 265 |
throw Error('This file is not a supported recorded CPU evaluation');
|
| 266 |
if(payload.physics_hz!==60||payload.control_hz!==60||payload.sample_interval_ticks!==6||payload.first_sample_tick!==1||
|
| 267 |
payload.execution_backend!=='cpu'||payload.math_profile!=='proton'||payload.collision_elimination!==false||payload.idle_elimination!==false)
|
|
|
|
| 277 |
if(expectedTicks[expectedTicks.length-1]!==ticks)expectedTicks.push(ticks);
|
| 278 |
if(!Array.isArray(payload.frames)||payload.frames.length!==expectedTicks.length)
|
| 279 |
throw Error('Recording is missing its expected native samples');
|
| 280 |
+
const telemetry=recordingTelemetry(payload);
|
| 281 |
const frames=payload.frames.map((frame,index)=>{
|
| 282 |
if(!frame||frame.tick!==expectedTicks[index]||typeof frame.active!=='boolean')throw Error('Recorded frame ticks or active state are invalid');
|
| 283 |
const copy={tick:frame.tick,active:frame.active};
|
|
|
|
| 291 |
if((key==='collision_count'||key==='update_count')&&(!Number.isInteger(frame[key])||frame[key]<0))throw Error('Recorded '+key+' is invalid');
|
| 292 |
copy[key]=frame[key];
|
| 293 |
}
|
| 294 |
+
if(frame.metrics!==undefined){
|
| 295 |
+
if(!frame.metrics||typeof frame.metrics!=='object'||Array.isArray(frame.metrics)||Object.keys(frame.metrics).length>64)throw Error('Invalid recorded metrics');
|
| 296 |
+
copy.metrics=Object.fromEntries(Object.entries(frame.metrics).map(([key,value])=>{if(!key.length||key.length>96)throw Error('Invalid metric name');return [key,value===null?null:finiteValue(value,'metric')];}));
|
| 297 |
+
}
|
| 298 |
+
if(telemetry){
|
| 299 |
+
copy.controls=recordedControls(frame.controls,'control commands');copy.applied_controls=recordedControls(frame.applied_controls,'applied controls');
|
| 300 |
+
for(const key of CONTROL_KEYS){const expected=['boost','handbrake'].includes(key)?Math.max(0,copy.controls[key]):copy.controls[key];if(copy.applied_controls[key]!==expected)throw Error('Control normalization differs');}
|
| 301 |
+
if(frame.decision===null)copy.decision=null;
|
| 302 |
+
else{
|
| 303 |
+
exactKeys(frame.decision,['tick','inputs'],'recorded decision');
|
| 304 |
+
if(!Number.isInteger(frame.decision.tick)||frame.decision.tick<1||frame.decision.tick>frame.tick||!Array.isArray(frame.decision.inputs)||frame.decision.inputs.length!==telemetry.sensors.length)throw Error('Invalid decision tick or sensor vector');
|
| 305 |
+
copy.decision={tick:frame.decision.tick,inputs:frame.decision.inputs.map(value=>finiteValue(value,'sensor input'))};
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
return copy;
|
| 309 |
});
|
| 310 |
+
for(let i=1;i<frames.length;i++){
|
| 311 |
+
if(frames[i].collision_count!==undefined&&frames[i-1].collision_count!==undefined&&frames[i].collision_count<frames[i-1].collision_count)throw Error('Contact counter moved backwards');
|
| 312 |
+
if(telemetry&&frames[i-1].decision&&(frames[i].decision===null||frames[i].decision.tick<frames[i-1].decision.tick))throw Error('Decision clock moved backwards');
|
| 313 |
+
}
|
| 314 |
+
return {schema:payload.schema,version:payload.version,telemetry,kind:payload.kind,physics_hz:60,control_hz:60,sample_interval_ticks:6,
|
| 315 |
first_sample_tick:1,track_sha256:trackSha,network_sha256:networkSha,vehicle_type:payload.vehicle_type,
|
| 316 |
requested_ticks:ticks,execution_backend:'cpu',math_profile:'proton',collision_elimination:false,idle_elimination:false,frames};
|
| 317 |
}
|
|
|
|
| 343 |
let playSpeed=Math.max(0.25,Math.min(4,Number(element('replay_speed')?.value)||1));
|
| 344 |
let cameraMode=element('replay_camera')?.value==='follow'?'follow':'full';
|
| 345 |
const layers={outline:true,line:true,hud:true};
|
| 346 |
+
let smoothPose=false,blackboxTick=null;
|
| 347 |
const noRecording='No replay recorded. The map shows the selected track and its saved starting point.';
|
| 348 |
const summaryOnly='No replay recorded. This run contains summary measurements, not vehicle positions or control samples.';
|
| 349 |
function setText(id,text){const node=element(id);if(node&&node.textContent!==text)node.textContent=text;}
|
|
|
|
| 351 |
const last=recording?.frames[recording.frames.length-1].tick;
|
| 352 |
const play=element('replay_play');
|
| 353 |
if(play){play.disabled=!recording||cursor>=last;play.textContent=playing?'Pause':'Play';}
|
| 354 |
+
for(const id of ['replay_reset','replay_scrub','replay_speed','replay_camera','replay_layer_outline','replay_layer_line','replay_layer_hud','replay_interpolate','replay_prev','replay_next','replay_event']){
|
| 355 |
const node=element(id);if(node)node.disabled=!recording;
|
| 356 |
}
|
| 357 |
+
if(element('replay_prev'))element('replay_prev').disabled=!recording||cursor<=recording.frames[0].tick;
|
| 358 |
+
if(element('replay_next'))element('replay_next').disabled=!recording||cursor>=last;
|
| 359 |
+
if(element('replay_event'))element('replay_event').disabled=!recording||!recordingEvents(recording).length;
|
| 360 |
+
if(element('replay_blackbox'))element('replay_blackbox').hidden=!recording;
|
| 361 |
const scrub=element('replay_scrub');
|
| 362 |
if(scrub){scrub.min='1';scrub.max=String(last??1);scrub.step='1';scrub.value=String(Math.round(cursor));}
|
| 363 |
if(!recording){
|
| 364 |
setText('replay_status',reason||(hasRunSummary?summaryOnly:noRecording));
|
| 365 |
setText('replay_time_label','No recorded samples');
|
| 366 |
+
setText('replay_readout','Record a drive to inspect position and speed. Native controls and sensor inputs appear after a version 2 recording is loaded.');
|
| 367 |
}else{
|
| 368 |
const state=cursor>=last?'Finished. Use Restart to view it again.':playing?'Playing.':'Paused.';
|
| 369 |
+
setText('replay_status',`Fresh recorded CPU evaluation · ${recording.frames.length} samples · 60 Hz physics and controls · 10 Hz recording. ${smoothPose?'Pose interpolation on.':'Measured sample poses.'} ${state}`);
|
| 370 |
}
|
| 371 |
}
|
| 372 |
+
function rows(id,entries){
|
| 373 |
+
const body=element(id);if(!body)return;
|
| 374 |
+
const nodes=entries.map(cells=>{const row=document.createElement('tr');cells.forEach((value,index)=>{const cell=document.createElement(index===0?'th':'td');cell.textContent=String(value);row.append(cell);});return row;});body.replaceChildren(...nodes);
|
| 375 |
+
}
|
| 376 |
+
function blackbox(frame){
|
| 377 |
+
if(blackboxTick===frame.tick)return;blackboxTick=frame.tick;
|
| 378 |
+
const number=value=>value===undefined||value===null?'Unavailable':typeof value==='number'?Number(value.toPrecision(8)).toString():String(value);
|
| 379 |
+
const decision=frame.decision;
|
| 380 |
+
setText('blackbox_phase',`Post-physics snapshot: tick ${frame.tick}. ${decision?`Native decision: tick ${decision.tick}, pre-physics${decision.tick<frame.tick?' (held from an earlier tick)':''}.`:recording.telemetry?'No inference captured before this snapshot.':'Legacy recording: controls and sensors unavailable.'}`);
|
| 381 |
+
rows('blackbox_state',[
|
| 382 |
+
['Snapshot time',(frame.tick/60).toFixed(3)+' s'],['Position X / Y',number(frame.x)+' / '+number(frame.y)+' game units'],
|
| 383 |
+
['Velocity X / Y',number(frame.vx)+' / '+number(frame.vy)+' game units/s'],['Speed',number(Math.hypot(frame.vx,frame.vy))+' game units/s'],
|
| 384 |
+
['Heading',number(frame.rotation)+' rad'],['Angular velocity',number(frame.angular_velocity)+' rad/s'],['Boost energy',number(frame.boost_energy)],
|
| 385 |
+
['Physical contacts',number(frame.collision_count)],['Active',frame.active?'Yes':'No'],['Statistics updates',number(frame.update_count)]
|
| 386 |
+
]);
|
| 387 |
+
rows('blackbox_controls',recording.telemetry?CONTROL_KEYS.map((key,index)=>[key,number(frame.controls[key]),number(frame.applied_controls[key]),recording.telemetry.output_ids.includes(index)?'Policy output':'Native default (no output slot)']):[['Unavailable','Not captured in this recording','Unavailable','Unavailable']]);
|
| 388 |
+
rows('blackbox_inputs',recording.telemetry?recording.telemetry.sensors.map((sensor,index)=>[index,sensor.$type,JSON.stringify(Object.fromEntries(Object.entries(sensor).filter(([key])=>key!=='$type'))),decision?number(decision.inputs[index]):'Not captured yet']):[['Unavailable','Legacy recording','No input capture','Unavailable']]);
|
| 389 |
+
rows('blackbox_metrics',Object.entries(frame.metrics??{}).map(([key,value])=>[key,number(value)]));
|
| 390 |
+
}
|
| 391 |
+
function eventOptions(){
|
| 392 |
+
const select=element('replay_event');if(!select)return;select.replaceChildren();
|
| 393 |
+
const empty=document.createElement('option');empty.value='';empty.textContent=recording?'Choose contact / active-state interval':'No recording';select.append(empty);
|
| 394 |
+
if(recording)recordingEvents(recording).forEach(event=>{const option=document.createElement('option');option.value=String(event.to_tick);option.textContent=event.label;select.append(option);});
|
| 395 |
+
}
|
| 396 |
function stopPlayback(){
|
| 397 |
if(animId!==null&&typeof root?.cancelAnimationFrame==='function')root.cancelAnimationFrame(animId);
|
| 398 |
animId=null;playing=false;lastFrameTime=null;
|
|
|
|
| 401 |
function draw(){
|
| 402 |
if(!currentTrack){renderEmpty();return;}
|
| 403 |
const ctx=canvas.getContext('2d'),view=viewport(canvas,currentTrack);
|
| 404 |
+
let state=recording?sampleRecording(recording,cursor):null;
|
| 405 |
+
if(state&&!smoothPose)state={...state,x:state.frame.x,y:state.frame.y,rotation:state.frame.rotation};
|
| 406 |
const position=state?worldPoint(view,state.x,state.y):null;
|
| 407 |
ctx.clearRect(0,0,canvas.width,canvas.height);ctx.fillStyle='#0c1926';ctx.fillRect(0,0,canvas.width,canvas.height);
|
| 408 |
ctx.save();
|
|
|
|
| 421 |
}
|
| 422 |
ctx.restore();
|
| 423 |
if(state){
|
| 424 |
+
const frame=state.frame;blackbox(frame);const speed=Math.hypot(frame.vx,frame.vy)/15*3.6;
|
| 425 |
const contacts=frame.collision_count===undefined?'unavailable':String(frame.collision_count);
|
| 426 |
setText('replay_time_label',`${(cursor/60).toFixed(2)}s / ${(recording.requested_ticks/60).toFixed(2)}s · Tick ${Math.round(cursor)} · Recorded sample ${frame.tick}`);
|
| 427 |
+
setText('replay_readout',`Recorded sample tick ${frame.tick} (${(frame.tick/60).toFixed(2)}s). Speed ${speed.toFixed(1)} km/h, derived from recorded velocity. Position (${frame.x.toFixed(2)}, ${frame.y.toFixed(2)}). Contacts ${contacts}. ${frame.active?'Active':'Inactive'} at this sample. ${frame.decision?'Native controls and inputs captured at tick '+frame.decision.tick+'.':'No captured decision at this sample.'}`);
|
| 428 |
if(layers.hud){
|
| 429 |
ctx.save();ctx.fillStyle='rgba(9,17,27,0.88)';ctx.beginPath();ctx.roundRect(12,12,310,80,8);ctx.fill();
|
| 430 |
ctx.fillStyle='#62dfb1';ctx.font='700 12px system-ui,sans-serif';ctx.fillText('RECORDED CPU EVALUATION',24,33);
|
| 431 |
ctx.fillStyle='#e5edf5';ctx.font='600 12px ui-monospace,monospace';ctx.fillText(`Sample ${frame.tick} · ${speed.toFixed(1)} km/h from velocity`,24,55);
|
| 432 |
+
ctx.fillStyle='#b4c5d5';ctx.font='12px system-ui,sans-serif';ctx.fillText(frame.decision?'Decision tick '+frame.decision.tick+' · native controls + inputs':'No captured decision at this sample',24,77);ctx.restore();
|
| 433 |
}
|
| 434 |
}
|
| 435 |
}
|
| 436 |
function clearRecording(reason){
|
| 437 |
+
const hadRecording=recording!==null;stopPlayback();recording=null;cursor=1;blackboxTick=null;eventOptions();
|
| 438 |
if(hadRecording)draw();syncControls(reason);
|
| 439 |
}
|
| 440 |
function step(now){
|
|
|
|
| 461 |
if(!recording)return;const value=Number(element('replay_scrub').value);if(!Number.isFinite(value))return;
|
| 462 |
stopPlayback();cursor=Math.max(recording.frames[0].tick,Math.min(recording.frames[recording.frames.length-1].tick,value));draw();syncControls();
|
| 463 |
});
|
| 464 |
+
for(const [id,direction]of [['replay_prev',-1],['replay_next',1]])on(id,'click',()=>{if(!recording)return;stopPlayback();cursor=adjacentSampleTick(recording,cursor,direction);draw();syncControls();});
|
| 465 |
+
on('replay_event','change',()=>{if(!recording||!element('replay_event').value)return;stopPlayback();cursor=Number(element('replay_event').value);draw();syncControls();});
|
| 466 |
+
on('replay_interpolate','change',()=>{smoothPose=element('replay_interpolate').checked;draw();syncControls();});
|
| 467 |
on('replay_speed','change',()=>{playSpeed=Math.max(0.25,Math.min(4,Number(element('replay_speed').value)||1));});
|
| 468 |
on('replay_camera','change',()=>{cameraMode=element('replay_camera').value==='follow'?'follow':'full';draw();});
|
| 469 |
for(const key of ['outline','line','hud']){
|
|
|
|
| 513 |
const next=validateRecording(payload,expectedTrackSha,expectedNetworkSha);
|
| 514 |
if(!currentTrack||!currentTrackSha)throw Error('The selected track must finish loading and hash verification before replay');
|
| 515 |
if(next.track_sha256!==currentTrackSha)throw Error('The recording belongs to a different selected track');
|
| 516 |
+
recording=next;blackboxTick=null;cursor=next.frames[0].tick;eventOptions();draw();syncControls();
|
| 517 |
return {frames:next.frames.length,first_tick:cursor,last_tick:next.frames[next.frames.length-1].tick};
|
| 518 |
}catch(error){clearRecording('Replay unavailable: '+error.message);throw error;}
|
| 519 |
},
|
|
|
|
| 522 |
bind();syncControls();document.addEventListener('track-library-change',refresh);refresh();
|
| 523 |
return controller;
|
| 524 |
}
|
| 525 |
+
return {mount,model,decode,render,validateRecording,sampleRecording,recordingEvents,adjacentSampleTick};
|
| 526 |
});
|