Spaces:
Running on Zero
Running on Zero
Add current-policy star checks without campaign progression
Browse files- BINARY_PROVENANCE.json +1 -1
- FRONTEND_SOURCE.json +1 -1
- RELEASE_MANIFEST.json +1 -1
- TRACK_LIBRARY.md +7 -3
- UPSTREAM.json +1 -1
- bin/altd-sim +2 -2
- bucket_storage.py +11 -0
- builtin_tracks.py +1 -1
- client.js +69 -7
- index.html +4 -3
- native/analysis.patch +269 -15
- native/build-receipt.json +1 -89
- native/source-receipt.json +1 -50
- portable_bundle.py +16 -2
- server.py +2 -0
- star_check.py +524 -0
- worker.py +59 -0
BINARY_PROVENANCE.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"artifact_origins":{"bin/altd-sim":{"build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"
|
|
|
|
| 1 |
+
{"artifact_origins":{"bin/altd-sim":{"build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348","bytes":3604272,"origin_type":"local_linux_build","patch_sha256":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","sha256":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849","source_receipt_sha256":"36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93","source_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"},"bin/extract_saved_track":{"build_receipt_path":"IMPORTER_BUILD_RECEIPT.json","build_receipt_sha256":"4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d","bytes":3247776,"compatibility_receipt_path":"native/build-receipt.json","compatibility_receipt_sha256":"559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348","evidence_path":"IMPORTER_BUILD_RECEIPT.json","evidence_sha256":"4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d","origin_type":"retained_verified_importer_binary","sha256":"3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a","source_repository":"https://github.com/Zorro909/driving_sim_rs","source_revision":"d8ab74c25df70b6a86ed101080e33d5fb785ea6f"},"bin/libaltd_gpu.so":{"build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348","bytes":4711704,"origin_type":"retained_cuda_b90","patch_sha256":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","sha256":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca","source_receipt_sha256":"36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93","source_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"}},"artifacts":{"bin/altd-sim":{"bytes":3604272,"sha256":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849"},"bin/extract_saved_track":{"bytes":3247776,"sha256":"3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a"},"bin/libaltd_gpu.so":{"bytes":4711704,"sha256":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca"}},"build_receipt":"native/build-receipt.json","build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348","importer_source_revision":"d8ab74c25df70b6a86ed101080e33d5fb785ea6f","native_patch_sha256":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","note":"The simulator is a fresh Linux build from b90 plus the hashed local analysis patch. The CUDA library is retained b90 and has layout ABI evidence only; this package does not claim local GPU kernel execution. The track importer remains separately identified with bounded compatibility evidence.","source_repository":"https://github.com/Zorro909/driving_sim_rs","source_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"}
|
FRONTEND_SOURCE.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"base_provenance":{"output_sha256":"a145fbb303a85c728a530d98fbea998677c1c62a2589c46af9b8b287e4ce1f48","scope":"Frontend only; no engine, private data, track assets, API calls or storage copied","source":"Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas","source_sha256":"65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"},"identity_note":"The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.","local_customization_id":"v1.2-winners-frozen-evaluation","packaged_frontend_files":{"assets/track-atlas.json":"d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4","client.js":"
|
|
|
|
| 1 |
+
{"base_provenance":{"output_sha256":"a145fbb303a85c728a530d98fbea998677c1c62a2589c46af9b8b287e4ce1f48","scope":"Frontend only; no engine, private data, track assets, API calls or storage copied","source":"Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas","source_sha256":"65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"},"identity_note":"The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.","local_customization_id":"v1.2-winners-frozen-evaluation","packaged_frontend_files":{"assets/track-atlas.json":"d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4","client.js":"747b85d297a9459ce17aefb468b3688f24e2efc6b6e1598f6b8da48d3b63a677","hf-auth.js":"3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe","index.html":"6f1b8992ed6bb8483ac09ef24c85da01f23e4a8df2b807a7f7fe5e67d4da27ed","track-library.js":"351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6","track-map.js":"ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"},"packaged_frontend_sha256":"4bb0f248bf75289d0d67c6d57b1dad80216e575b352f0664931f49ea5df3ca47"}
|
RELEASE_MANIFEST.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"backend":"gpu","files":{".gitattributes":"c2ad16718fc51c888d32efc88c8f6e23ebe5fcf6f48e2954f896ba9770f24d72","BETA_TESTING.md":"27e2492f9fe87cc04f52de2974b685b44041f7de9fa1155926b66c436f0bc613","BINARY_PROVENANCE.json":"
|
|
|
|
| 1 |
+
{"backend":"gpu","files":{".gitattributes":"c2ad16718fc51c888d32efc88c8f6e23ebe5fcf6f48e2954f896ba9770f24d72","BETA_TESTING.md":"27e2492f9fe87cc04f52de2974b685b44041f7de9fa1155926b66c436f0bc613","BINARY_PROVENANCE.json":"fa2b6c8962a0ec704ce7db417e7747abfc8af79f9f31effd242c013d8bdeca5f","BUCKET_BUNDLE.md":"a802f658220f5eabc1a98ea4a5217989b54084d4993943c1faafbad36d421131","CUDA_REFERENCE.md":"f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c","DATA_POLICY.md":"7e8b31b5ad30f35e5d7242c13bdb356191ae05c81dcea2709172974e82546e4d","FEEDBACK.md":"f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a","FRONTEND_SOURCE.json":"d684d7aefd4ea5a332571b80abd435911b22c7ae883372dcd4f7f7acc8ca1ffe","GODOT_LICENSE":"fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304","IMPORTER_BUILD_RECEIPT.json":"4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d","LICENSE":"53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8","README.md":"cb234a47d1aef9ec17ac72cddca0d7b82abe868cbd3acd992bb8abb0759a18c2","STORAGE_BOUNDARY.md":"ac6946e1fd54be0786d71c24ec499bb898e63c20fd064c3c0bb3bba7046a0c57","TRACK_LIBRARY.md":"be64c91633ef6696233df537afeef4c5175b2d55daac9336758d9eb3d24763c9","UPSTREAM.json":"e71d256ab4fea9a684c211f91086cd8a3eec518686eb54474e79352b20eeed79","WINNERS_AND_EVALUATION.md":"c3df1b0e7c6ce76c2660159c9b74bd7c35af02be2fcad1b8022e3bb47677bebe","analysis_result.py":"0a5d73fe8dbca934b21823b8ffbd50b0baaef8e15a39e32604eefe16f01d1cf2","app.py":"78ddccd56e5c217b89681c5dcbee451b6a5868398c4f75bd2b4db33c235d800f","app_release.py":"7482559c667a665fd3cb6aa8af6c78422a822b515e14589db8306f7fd4860a8b","assets/formula_template.json":"80f745df82bb7be45af52785fd582460bcddc63c5a4e1d8375d85b3122db4c6b","assets/rally_template.json":"7350a3fe28d9b4da7a0965ae6ea7ca3472fb7885303e342310d63a0dce80851c","assets/snowmobile_template.json":"12d30545876011fd6fccdd3e721f9c465938c9c58ac99338205481d5e7ac6742","assets/track-atlas.json":"d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4","assets/truck_template.json":"a83bffea4d5e818c135ab3aba68a47aa288d6ad0e7b8bd9c95ed0dea9e9df2f7","bin/altd-sim":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849","bin/extract_saved_track":"3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a","bin/libaltd_gpu.so":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca","bucket_storage.py":"73977f5c23c513848a6451fd603609211d829126438db803288ebf4469d1893d","build_info.py":"03efdd1bca81022669a9184667b89d4f1096faba6a83c0514322dd419a938e1f","builtin_tracks.py":"52cb4a285a6079cec7f7610de8af80570f1644a02cf9979e47d2d49736e24cb5","builtin_tracks/catalog.json":"65006e70a87a5a53e3738166b0895bc4db9f99767dd891d917b68029abb4e80a","builtin_tracks/tracks/Replica A01 Beginner.track":"ca5749537450430794e2eade2e0ac72fa141a4f225e13e2f8ef842abb948ff15","builtin_tracks/tracks/Replica A02 Sharp Turns.track":"097f1ea8edf041112b690d0638075630716ddc03c7d0b1980ddcbeaf5071c8d4","builtin_tracks/tracks/Replica A03 High Speed.track":"88917c63847be1854b989bb28a23af7eeacb964c3e7df9579953aebc9496e6af","builtin_tracks/tracks/Replica A04 Dirt.track":"7f4fe58516227b15898d14bb0abb9a0856126d24fcd793884f051f7a4fae521d","builtin_tracks/tracks/Replica A05 Two Terrains.track":"c4ee2842920d99cdfe3eebdcad59a77e24b626380acef0d02a39ea1e23823e2a","builtin_tracks/tracks/Replica A06 Ice.track":"fccb9a159a06d1b986c6fe7d5bb89c0cabad3a005531f4e0d71f6e37254ada34","builtin_tracks/tracks/Replica A07 Three Terrains.track":"a8fa652ad451e2702a23d5f3429eb0b6a6408bf6a2a76a73d549023e481875c6","builtin_tracks/tracks/Replica A08 Long.track":"3e259bd227776ca847d35baaea88b5e8af57cc920643bf71563176bd6554ad65","builtin_tracks/tracks/Replica A09 Sharpest.track":"5362cea3c564dce8f4f22fcd4b798347931dfaa4e65b84624944952b3e0b0d2a","builtin_tracks/tracks/Replica B01 Oval.track":"0df5755150c145a35edc6c8693f429e5b9ff4b12210ba67cbfcb932c58faf158","builtin_tracks/tracks/Replica B02 Snake.track":"210e699b1fa5f2624a2ff0969b73a20af7bc56bf774f4fd282f6109f80bfdb3c","builtin_tracks/tracks/Replica B03 Slalom.track":"a8bab86f93e3072bf27a81a354220c137207b9fc0b548372c8dab93639a18a24","builtin_tracks/tracks/Replica B04 Rally.track":"9d2fc668a7c86aabb9ca9dbfdbd21dff0d099c5d77029aa2ae96b6c7b9efd3f0","builtin_tracks/tracks/Replica B05 Racetrack.track":"d0804ebc17646d2f7c3be65cfe0f6e94033417e75b101a8c598a69131a170079","builtin_tracks/tracks/Replica B06 Hard.track":"8b1a10fd8485405d7f73abca8890f4845145227177c9ac07ac9a1d79f28908f9","builtin_tracks/tracks/Replica B07 Drift.track":"d6d46216882cb63d55b1e8ce81ffa67f1c95ffd0a86a72806b8fd23973c6ce0a","builtin_tracks/tracks/Replica B08 Arctic Circle.track":"229128e86a87089f781b072a4c677ff729cf8488b1d74ad75cfb43655b9f4a16","builtin_tracks/tracks/Replica B09 Checkered.track":"bec4dbd0edc35e0e4f4d1035c608a0c56bbc99e7301f640f8e2eddba0100b2fe","builtin_tracks/tracks/Replica R01 Blitzring.track":"14b52a7cbbf7de5606c772c74730c0e8776f39ed72955fef5ae1f7c2d8ccef77","builtin_tracks/tracks/Replica R02 Desert Rally.track":"653286b00f2f04450e698d8b8e9d4ecb268064b323b182d2de9db7d017b22238","builtin_tracks/tracks/Replica R03 Glacier.track":"0e1b42ce51ae91a8702e173013a090d00145e518bb47d7a659cb1d1fdff011b5","builtin_tracks/tracks/Replica R04 Truck Maze.track":"d7b89d64e118506e6cec259057886d9267ca4c6fab570e7ba0d6641e8f822f11","builtin_tracks/tracks/Replica R05 Endurance 500.track":"dde36ad5f627bce65d45a96d46a68497f8d446f6011c9df19ee51ce71375cba8","builtin_tracks/tracks/Replica R06 Mix.track":"9de8d912a59001ea13baa7f1b4f92884b8cddf91978f31e19f3237975713f739","builtin_tracks/tracks/Replica R07 Mirage Marina.track":"8c6764f3accb1a8372d910d57f4f681572dd40c590c4a3d03517379bb360fcd7","builtin_tracks/tracks/Replica R08 Demolition Derby.track":"6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef","builtin_tracks/tracks/Replica R09 Eau Noir.track":"3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9","builtin_tracks/tracks/Replica The Champions Trial.track":"a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1","client.js":"747b85d297a9459ce17aefb468b3688f24e2efc6b6e1598f6b8da48d3b63a677","delivery.py":"6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b","gpu_tickets.py":"b81aadb5ed42cec9212d44da7e7bbb7b413c5bd517383cac0b49ec9b4d659c92","hf-auth.js":"3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe","hf_identity.py":"913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37","index.html":"6f1b8992ed6bb8483ac09ef24c85da01f23e4a8df2b807a7f7fe5e67d4da27ed","native/analysis.patch":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","native/build-receipt.json":"559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348","native/source-receipt.json":"36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93","portable_bundle.py":"aeeb2a81bcdf50bd013f3fa76e20c7c955c8f15344836596cd1043b1b7171026","requirements.txt":"fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4","server.py":"da8ade96d68b89f328ee371d177f7272bd6f1d78832bff34c6eedaa3fe93ef96","star_check.py":"61d35bcede5dab75911b2734ff20cb38f7a06bde9fd58c2eff67f5833b260d40","steam_leaderboard.py":"d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481","track-library.js":"351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6","track-map.js":"ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0","training_config.py":"3d7fcaad585b474380e825649e4add3412494bbd68b48b3b14bd1b954e817ad0","vendor/gradio-client/LICENSE":"c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4","vendor/gradio-client/dist/index.min.js":"fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd","vendor/gradio-client/package.json":"96c5b9e6d3587fb7bd8e5bb28775ece222de1043045afc4e6389c88db137ad0e","vendor/hf-oauth/LICENSE":"d6e7b19451c1b3d1d66353bb4b1274138953b3bfd9ae60c56e77b1f548796982","vendor/hf-oauth/index.js":"53282844000a171011e547f6472a3846bb4cecb5071adafea079fcac3a2aee7e","vendor/hf-oauth/package.json":"fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8","worker.py":"9d7d0d8e8730067658bbd6d5cddb20dca194a42cf885d389e193784fe8f15438"},"interface":"single-engine-runner-with-map-winners-analysis-v1","upstream_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"}
|
TRACK_LIBRARY.md
CHANGED
|
@@ -12,9 +12,13 @@ contains each file's SHA-256 and byte count. Private provenance paths, original
|
|
| 12 |
packaged files, artwork, textures, opponents/controllers, original race parameters
|
| 13 |
and the native geometry catalog are not part of this library.
|
| 14 |
|
| 15 |
-
Training-track numeric challenge conditions were preserved.
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
in this selector is not a claim of official game approval, legal clearance,
|
| 19 |
game-editor load verification, physics parity or leaderboard qualification.
|
| 20 |
|
|
|
|
| 12 |
packaged files, artwork, textures, opponents/controllers, original race parameters
|
| 13 |
and the native geometry catalog are not part of this library.
|
| 14 |
|
| 15 |
+
Training-track numeric challenge conditions were preserved. After each run the
|
| 16 |
+
worker checks supported conditions in a fresh fixed-weight replay of the primary
|
| 17 |
+
result. This predicts this network's performance only; it grants no campaign
|
| 18 |
+
stars and carries no progression between runs. Race replicas contain dummy lap
|
| 19 |
+
conditions in their editor files. The star checker recognizes those built-in
|
| 20 |
+
files and reports their original opponent races as not checked, rather than
|
| 21 |
+
counting placeholder laps as original-game stars. Availability
|
| 22 |
in this selector is not a claim of official game approval, legal clearance,
|
| 23 |
game-editor load verification, physics parity or leaderboard qualification.
|
| 24 |
|
UPSTREAM.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"commit":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0","importer_source_revision":"d8ab74c25df70b6a86ed101080e33d5fb785ea6f","math_profile":"proton","native_build":{"build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"
|
|
|
|
| 1 |
+
{"commit":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0","importer_source_revision":"d8ab74c25df70b6a86ed101080e33d5fb785ea6f","math_profile":"proton","native_build":{"build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348","cuda_library_origin":"retained b90 CUDA library; layout ABI checked, no local device execution","patch_path":"native/analysis.patch","patch_sha256":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","simulator_origin":"locally built Linux executable from b90 plus reviewed local patch","source_receipt_path":"native/source-receipt.json","source_receipt_sha256":"36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93","source_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"},"open_prs_not_included":[19,21],"repository":"https://github.com/Zorro909/driving_sim_rs","role":"gpu","ui_refresh_date":"2026-10-12"}
|
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:3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849
|
| 3 |
+
size 3604272
|
bucket_storage.py
CHANGED
|
@@ -342,6 +342,17 @@ def _save_files(bundle_bytes: bytes, bundle: dict, files: dict[str, bytes], *, n
|
|
| 342 |
for name, content in files.items():
|
| 343 |
if name.startswith("analysis/"):
|
| 344 |
uploads[name] = content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
for role in ("fitness", "fastest_lap", "clean_laps"):
|
| 346 |
name = f"analysis/winners/{role}.evolution"
|
| 347 |
if name in files:
|
|
|
|
| 342 |
for name, content in files.items():
|
| 343 |
if name.startswith("analysis/"):
|
| 344 |
uploads[name] = content
|
| 345 |
+
if "analysis/stars.json" in files:
|
| 346 |
+
stars=json.loads(files["analysis/stars.json"])
|
| 347 |
+
# Plain text avoids interpreting user-provided track labels as markup.
|
| 348 |
+
lines=["Star challenge check for the run's primary network", stars['basis'],
|
| 349 |
+
"This check grants no game stars and keeps no campaign progression.",
|
| 350 |
+
"Policy: "+stars['network_sha256'],
|
| 351 |
+
"Check duration: "+str(stars['horizon_seconds'])+" seconds", ""]
|
| 352 |
+
for challenge in stars['challenges']:
|
| 353 |
+
lines.extend([challenge['text'], "Result: "+challenge['status'].replace('_',' '),
|
| 354 |
+
"Reason: "+(challenge['reason'] or 'Condition observed.'), ""])
|
| 355 |
+
uploads['star-check.txt']=("\n".join(lines)+"\n").encode('utf-8')
|
| 356 |
for role in ("fitness", "fastest_lap", "clean_laps"):
|
| 357 |
name = f"analysis/winners/{role}.evolution"
|
| 358 |
if name in files:
|
builtin_tracks.py
CHANGED
|
@@ -39,7 +39,7 @@ class TrackLibrary:
|
|
| 39 |
return {'schema':'ald-builtin-track-library-v1',
|
| 40 |
'method':'programmatically reconstructed editor-format replicas',
|
| 41 |
'editor_authored':False,'clean_room':False,
|
| 42 |
-
'challenge_runtime_status':'
|
| 43 |
'tracks':[dict(entry) for _,entry in self._entries.values()]}
|
| 44 |
|
| 45 |
def read(self, identifier):
|
|
|
|
| 39 |
return {'schema':'ald-builtin-track-library-v1',
|
| 40 |
'method':'programmatically reconstructed editor-format replicas',
|
| 41 |
'editor_authored':False,'clean_room':False,
|
| 42 |
+
'challenge_runtime_status':'per-run fixed-policy prediction; original opponent races not evaluated',
|
| 43 |
'tracks':[dict(entry) for _,entry in self._entries.values()]}
|
| 44 |
|
| 45 |
def read(self, identifier):
|
client.js
CHANGED
|
@@ -530,6 +530,7 @@ async function acknowledge(current,received){
|
|
| 530 |
}
|
| 531 |
async function sha(bytes){return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),x=>x.toString(16).padStart(2,'0')).join('');}
|
| 532 |
const ANALYSIS_ARCHIVE_MAX=32*1024*1024,ANALYSIS_EXPANDED_MAX=256*1024*1024,ANALYSIS_ROWS_MAX=128000;
|
|
|
|
| 533 |
const hex64=value=>typeof value==='string'&&/^[a-f0-9]{64}$/.test(value);
|
| 534 |
function exactAnalysisKeys(value,keys){if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).sort().join()!==[...keys].sort().join())throw Error('Analysis archive contains unexpected fields');}
|
| 535 |
function stableJson(value){if(Array.isArray(value))return '['+value.map(stableJson).join(',')+']';if(value&&typeof value==='object')return '{'+Object.keys(value).sort().map(key=>JSON.stringify(key)+':'+stableJson(value[key])).join(',')+'}';return JSON.stringify(value);}
|
|
@@ -596,8 +597,35 @@ function validateComparisonRequest(plan,actual){
|
|
| 596 |
throw Error('The frozen comparison conditions or selected policy changed. The comparison was stopped before sending this run. Start a new comparison after restoring the matching settings.');
|
| 597 |
return true;
|
| 598 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
function renderAnalysisPanel(){
|
| 600 |
const panel=$('analysisPanel');if(!panel)return;
|
|
|
|
| 601 |
panel.hidden=!localAnalysis;
|
| 602 |
if(!localAnalysis){$('winnerTools').hidden=true;$('evaluationTools').hidden=true;$('winnerRows').replaceChildren();$('winner_continue').replaceChildren();$('evaluationRows').replaceChildren();return;}
|
| 603 |
const {summary}=localAnalysis,train=summary.mode==='train';
|
|
@@ -714,12 +742,13 @@ async function parseRunAnalysisArchive(bytes,descriptor,reportedSummary){
|
|
| 714 |
if(descriptor.format!==undefined&&descriptor.format!=='ald.run-analysis-archive+gzip')throw Error('Unsupported analysis archive format');
|
| 715 |
const raw=await expandAnalysisGzip(bytes);let envelope;
|
| 716 |
try{envelope=JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(raw));}catch{throw Error('Analysis archive is not valid UTF-8 JSON');}
|
| 717 |
-
exactAnalysisKeys(envelope,['schema','version','files']);if(envelope.schema!=='ald.run-analysis-archive'||envelope.version!==1||!Array.isArray(envelope.files)||envelope.files.length>
|
| 718 |
-
const files=new Map(),allowed=new Set(['analysis.json','descriptors.jsonl','evaluation.json','winners/fitness.evolution','winners/fastest_lap.evolution','winners/clean_laps.evolution']);
|
| 719 |
for(const file of envelope.files){
|
| 720 |
exactAnalysisKeys(file,['name','bytes','sha256','data_base64']);
|
| 721 |
-
|
| 722 |
-
|
|
|
|
| 723 |
if(content.byteLength!==file.bytes||await sha(content)!==file.sha256)throw Error('Analysis archive inner file failed size or SHA-256 verification');
|
| 724 |
files.set(file.name,{bytes:content,sha256:file.sha256});
|
| 725 |
}
|
|
@@ -757,9 +786,42 @@ async function parseRunAnalysisArchive(bytes,descriptor,reportedSummary){
|
|
| 757 |
if(evaluation.schema!=='ald.frozen-evaluation'||evaluation.version!==1||stableJson(evaluation.conditions)!==stableJson(summary.conditions)||evaluation.input_sha256!==summary.conditions.network_sha256||evaluation.weights_unchanged!==true)throw Error('Frozen evaluation identity is invalid');
|
| 758 |
validateDescriptorRow(evaluation.measurements,metricNames,rewardChannels);
|
| 759 |
}else if(files.has('evaluation.json'))throw Error('Training archive unexpectedly contains frozen evaluation data');
|
| 760 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
if(files.size!==expectedFiles.size||[...files.keys()].some(name=>!expectedFiles.has(name)))throw Error('Analysis archive contains missing or unexpected files');
|
| 762 |
-
return {mode:summary.mode,summary,analysis,winnerNetworks,evaluation,archiveBytes:bytes,descriptorText:descText,descriptorOffsets:offsets,descriptorRows,rawMetrics:metricNames,rewardChannels};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
}
|
| 764 |
function validatePrimaryAnalysisMatch(primarySha,parsed){
|
| 765 |
if(!hex64(primarySha)||!parsed?.summary)throw Error('Primary result and analysis are not verifiable');
|
|
@@ -771,7 +833,7 @@ function validatePrimaryAnalysisMatch(primarySha,parsed){
|
|
| 771 |
}else throw Error('Analysis mode is unsupported');
|
| 772 |
return true;
|
| 773 |
}
|
| 774 |
-
window.RunAnalysis={parseArchive:parseRunAnalysisArchive,validatePrimaryMatch:validatePrimaryAnalysisMatch,validateComparisonRequest,uniqueWinnerRoles};
|
| 775 |
function showDetails(r){
|
| 776 |
const metric=n=>typeof n==='number'?n.toFixed(3):'Not reported';
|
| 777 |
if($('effectiveSettings'))$('effectiveSettings').textContent=JSON.stringify(r?.settings??{},null,2);
|
|
|
|
| 530 |
}
|
| 531 |
async function sha(bytes){return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),x=>x.toString(16).padStart(2,'0')).join('');}
|
| 532 |
const ANALYSIS_ARCHIVE_MAX=32*1024*1024,ANALYSIS_EXPANDED_MAX=256*1024*1024,ANALYSIS_ROWS_MAX=128000;
|
| 533 |
+
const STAR_CHECK_MAX=512*1024;
|
| 534 |
const hex64=value=>typeof value==='string'&&/^[a-f0-9]{64}$/.test(value);
|
| 535 |
function exactAnalysisKeys(value,keys){if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).sort().join()!==[...keys].sort().join())throw Error('Analysis archive contains unexpected fields');}
|
| 536 |
function stableJson(value){if(Array.isArray(value))return '['+value.map(stableJson).join(',')+']';if(value&&typeof value==='object')return '{'+Object.keys(value).sort().map(key=>JSON.stringify(key)+':'+stableJson(value[key])).join(',')+'}';return JSON.stringify(value);}
|
|
|
|
| 597 |
throw Error('The frozen comparison conditions or selected policy changed. The comparison was stopped before sending this run. Start a new comparison after restoring the matching settings.');
|
| 598 |
return true;
|
| 599 |
}
|
| 600 |
+
function renderStarCheck(){
|
| 601 |
+
const panel=$('starCheckPanel');if(!panel)return;
|
| 602 |
+
const status=$('starCheckStatus'),details=$('starCheckDetails'),rows=$('starCheckRows');rows.replaceChildren();
|
| 603 |
+
panel.hidden=!(localResult||localAnalysis);
|
| 604 |
+
if(!localResult&&!localAnalysis){status.textContent='';details.hidden=true;return;}
|
| 605 |
+
if(pairedAnalysisPending){status.textContent='Challenge check is pending until the result and its analysis are fully verified.';details.hidden=true;return;}
|
| 606 |
+
const check=localAnalysis?.starCheck;
|
| 607 |
+
if(!check){status.textContent='No challenge check was included in this result archive. Older results may not contain one.';details.hidden=true;return;}
|
| 608 |
+
details.hidden=false;
|
| 609 |
+
const summary=localAnalysis.summary,policyHash=check.network_sha256;
|
| 610 |
+
const policy=summary.mode==='evaluate'?'Frozen evaluation input':`Final fitness leader from this run${summary.winners?.fitness?.candidate_id?' · '+summary.winners.fitness.candidate_id:''}`;
|
| 611 |
+
$('starCheckPolicy').textContent=policy+' · '+policyHash.slice(0,12);
|
| 612 |
+
$('starCheckPolicy').title=policyHash;
|
| 613 |
+
status.textContent=check.status==='complete'?'Challenge check completed for the policy named below. It does not award or accumulate game stars.':check.status==='unavailable'?'Challenge check was unavailable for the policy named below.':'No star challenges are defined for this selected replica track.';
|
| 614 |
+
$('starCheckBasis').textContent=check.basis;
|
| 615 |
+
const vehicle=['Formula','Rally','Snowmobile','Truck'][check.vehicle_type]??'Unknown vehicle';
|
| 616 |
+
$('starCheckConditions').textContent=vehicle+' · '+(check.execution_backend==='cuda'?'CUDA':'CPU')+' · horizon '+check.horizon_seconds.toFixed(2)+' s · collision elimination '+(check.collision_elimination?'on':'off');
|
| 617 |
+
const resultLabels={met:'Met',not_met:'Not met',not_checked:'Not checked'};
|
| 618 |
+
for(const challenge of check.challenges){
|
| 619 |
+
const tr=document.createElement('tr'),name=document.createElement('td'),result=document.createElement('td'),reason=document.createElement('td'),time=document.createElement('td');
|
| 620 |
+
name.textContent=challenge.text||challenge.id;name.title=challenge.id+' · '+challenge.type;
|
| 621 |
+
result.textContent=resultLabels[challenge.status];reason.textContent=challenge.reason??'No additional reason reported.';
|
| 622 |
+
time.textContent=challenge.completion_seconds===null?'Not recorded':challenge.completion_seconds.toFixed(3)+' s';
|
| 623 |
+
tr.append(name,result,reason,time);rows.append(tr);
|
| 624 |
+
}
|
| 625 |
+
}
|
| 626 |
function renderAnalysisPanel(){
|
| 627 |
const panel=$('analysisPanel');if(!panel)return;
|
| 628 |
+
renderStarCheck();
|
| 629 |
panel.hidden=!localAnalysis;
|
| 630 |
if(!localAnalysis){$('winnerTools').hidden=true;$('evaluationTools').hidden=true;$('winnerRows').replaceChildren();$('winner_continue').replaceChildren();$('evaluationRows').replaceChildren();return;}
|
| 631 |
const {summary}=localAnalysis,train=summary.mode==='train';
|
|
|
|
| 742 |
if(descriptor.format!==undefined&&descriptor.format!=='ald.run-analysis-archive+gzip')throw Error('Unsupported analysis archive format');
|
| 743 |
const raw=await expandAnalysisGzip(bytes);let envelope;
|
| 744 |
try{envelope=JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(raw));}catch{throw Error('Analysis archive is not valid UTF-8 JSON');}
|
| 745 |
+
exactAnalysisKeys(envelope,['schema','version','files']);if(envelope.schema!=='ald.run-analysis-archive'||envelope.version!==1||!Array.isArray(envelope.files)||envelope.files.length>7)throw Error('Analysis archive envelope is invalid');
|
| 746 |
+
const files=new Map(),allowed=new Set(['analysis.json','descriptors.jsonl','evaluation.json','stars.json','winners/fitness.evolution','winners/fastest_lap.evolution','winners/clean_laps.evolution']);
|
| 747 |
for(const file of envelope.files){
|
| 748 |
exactAnalysisKeys(file,['name','bytes','sha256','data_base64']);
|
| 749 |
+
const fileMax=file.name==='stars.json'?STAR_CHECK_MAX:ANALYSIS_EXPANDED_MAX;
|
| 750 |
+
if(typeof file.name!=='string'||!allowed.has(file.name)||files.has(file.name)||!Number.isInteger(file.bytes)||file.bytes<1||file.bytes>fileMax||!hex64(file.sha256))throw Error('Analysis archive file manifest is invalid');
|
| 751 |
+
const content=b64Bytes(file.data_base64,fileMax);
|
| 752 |
if(content.byteLength!==file.bytes||await sha(content)!==file.sha256)throw Error('Analysis archive inner file failed size or SHA-256 verification');
|
| 753 |
files.set(file.name,{bytes:content,sha256:file.sha256});
|
| 754 |
}
|
|
|
|
| 786 |
if(evaluation.schema!=='ald.frozen-evaluation'||evaluation.version!==1||stableJson(evaluation.conditions)!==stableJson(summary.conditions)||evaluation.input_sha256!==summary.conditions.network_sha256||evaluation.weights_unchanged!==true)throw Error('Frozen evaluation identity is invalid');
|
| 787 |
validateDescriptorRow(evaluation.measurements,metricNames,rewardChannels);
|
| 788 |
}else if(files.has('evaluation.json'))throw Error('Training archive unexpectedly contains frozen evaluation data');
|
| 789 |
+
let starCheck=null;
|
| 790 |
+
if(files.has('stars.json')){
|
| 791 |
+
try{starCheck=JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(files.get('stars.json').bytes));}catch{throw Error('Star check file is invalid JSON');}
|
| 792 |
+
validateStarCheck(starCheck,summary);
|
| 793 |
+
}
|
| 794 |
+
const expectedFiles=new Set(['analysis.json','descriptors.jsonl',...(summary.mode==='evaluate'?['evaluation.json']:[]),...(files.has('stars.json')?['stars.json']:[]),...Object.entries(summary.winners).filter(([,w])=>w.status==='available').map(([role])=>'winners/'+role+'.evolution')]);
|
| 795 |
if(files.size!==expectedFiles.size||[...files.keys()].some(name=>!expectedFiles.has(name)))throw Error('Analysis archive contains missing or unexpected files');
|
| 796 |
+
return {mode:summary.mode,summary,analysis,winnerNetworks,evaluation,starCheck,archiveBytes:bytes,descriptorText:descText,descriptorOffsets:offsets,descriptorRows,rawMetrics:metricNames,rewardChannels};
|
| 797 |
+
}
|
| 798 |
+
function validateStarCheck(value,summary){
|
| 799 |
+
exactAnalysisKeys(value,['schema','version','network_sha256','track_sha256','vehicle_type','execution_backend','status','horizon_seconds','collision_elimination','scope','basis','challenges']);
|
| 800 |
+
const expectedNetwork=summary.mode==='train'?summary.winners.fitness.artifact?.sha256:summary.conditions.network_sha256;
|
| 801 |
+
if(value.schema!=='ald.star-check'||value.version!==1||!hex64(value.network_sha256)||value.network_sha256!==expectedNetwork||
|
| 802 |
+
!hex64(value.track_sha256)||value.track_sha256!==summary.conditions.track_sha256||value.vehicle_type!==summary.conditions.vehicle_type||
|
| 803 |
+
value.execution_backend!==summary.execution_backend||!['complete','unavailable','no_challenges'].includes(value.status)||
|
| 804 |
+
!Number.isFinite(value.horizon_seconds)||value.horizon_seconds<0||value.horizon_seconds>300||
|
| 805 |
+
typeof value.collision_elimination!=='boolean'||value.collision_elimination!==summary.conditions.collision||
|
| 806 |
+
value.scope!=='current_network'||typeof value.basis!=='string'||!value.basis.length||value.basis.length>1000||
|
| 807 |
+
!Array.isArray(value.challenges)||value.challenges.length>64)throw Error('Star check identity or metadata is invalid');
|
| 808 |
+
if((value.status==='no_challenges')!==(value.challenges.length===0))throw Error('Star check status does not match its challenge list');
|
| 809 |
+
const ids=new Set();
|
| 810 |
+
for(const challenge of value.challenges){
|
| 811 |
+
exactAnalysisKeys(challenge,['id','type','text','status','reason','completion_seconds']);
|
| 812 |
+
if(typeof challenge.id!=='string'||!challenge.id.length||challenge.id.length>128||ids.has(challenge.id)||
|
| 813 |
+
typeof challenge.type!=='string'||!challenge.type.length||challenge.type.length>96||
|
| 814 |
+
typeof challenge.text!=='string'||challenge.text.length>256||
|
| 815 |
+
!['met','not_met','not_checked'].includes(challenge.status)||
|
| 816 |
+
!(challenge.reason===null||(typeof challenge.reason==='string'&&challenge.reason.length>0&&challenge.reason.length<=256))||
|
| 817 |
+
!(challenge.completion_seconds===null||(Number.isFinite(challenge.completion_seconds)&&challenge.completion_seconds>=0&&challenge.completion_seconds<=300)))throw Error('Star check challenge entry is invalid');
|
| 818 |
+
if(challenge.status==='met'){
|
| 819 |
+
if(challenge.reason!==null||challenge.completion_seconds===null||challenge.completion_seconds>value.horizon_seconds)throw Error('Completed challenge evidence is inconsistent');
|
| 820 |
+
}else if(challenge.reason===null||challenge.completion_seconds!==null)throw Error('Uncompleted challenge evidence is inconsistent');
|
| 821 |
+
if(value.status==='unavailable'&&challenge.status!=='not_checked')throw Error('Unavailable star check cannot report a challenge result');
|
| 822 |
+
ids.add(challenge.id);
|
| 823 |
+
}
|
| 824 |
+
return value;
|
| 825 |
}
|
| 826 |
function validatePrimaryAnalysisMatch(primarySha,parsed){
|
| 827 |
if(!hex64(primarySha)||!parsed?.summary)throw Error('Primary result and analysis are not verifiable');
|
|
|
|
| 833 |
}else throw Error('Analysis mode is unsupported');
|
| 834 |
return true;
|
| 835 |
}
|
| 836 |
+
window.RunAnalysis={parseArchive:parseRunAnalysisArchive,validatePrimaryMatch:validatePrimaryAnalysisMatch,validateComparisonRequest,uniqueWinnerRoles,validateStarCheck};
|
| 837 |
function showDetails(r){
|
| 838 |
const metric=n=>typeof n==='number'?n.toFixed(3):'Not reported';
|
| 839 |
if($('effectiveSettings'))$('effectiveSettings').textContent=JSON.stringify(r?.settings??{},null,2);
|
index.html
CHANGED
|
@@ -28,8 +28,8 @@
|
|
| 28 |
};
|
| 29 |
})();
|
| 30 |
</script>
|
| 31 |
-
<header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Pick a track and network, run a bounded training session, and keep your recipes in this browser.</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas.
|
| 32 |
-
<nav class="tabs" aria-label="Lab sections"><button data-tab="drive" aria-selected="true">1 · Drive & view</button><button data-tab="training" aria-selected="false">2 · Training runs & presets</button><button data-tab="batch" aria-selected="false">3 · Batches</button><button data-tab="editor" aria-selected="false">4 · Network editor</button></nav><div id="drive" class="pane"><div class="grid"><section class="card"><h2>Simulation</h2><label for="run_mode">Action</label><select id="run_mode"><option value="train" selected>Train a population</option><option value="evaluate">Frozen evaluation · one policy, unchanged weights</option></select><p id="run_mode_help" class="hint">Training uses the selected recipe. Frozen evaluation runs the selected policy in a fresh car without changing its weights.</p><button id="run" disabled>Run training</button><p class="hint">Select or create a network below to enable training. Review settings on tab 2 before running.</p><p>Supports Formula, Rally, Snowmobile and Truck networks. Control outputs follow the original vehicle editor rules. Network shape and supported sensors are bounded by the original game contract.</p>
|
| 33 |
<p id="computeBlock"><label for="backend">Compute</label><select id="backend" disabled><option value="cpu">CPU — no GPU allowance</option><option id="gpuOption" value="gpu" disabled>GPU — uses GPU allowance</option></select></p><p id="backendNote" class="hint">Checking available backends…</p>
|
| 34 |
<div id="connectionRecovery" hidden><button id="retry" type="button">Reconnect to app</button><p class="hint">Checks server availability. Does not restart training, sign you in, or reset GPU allowance.</p></div>
|
| 35 |
<div id="gpuAccount" aria-label="Optional GPU account"><p><button id="hfSignIn" type="button" disabled>Sign in with HF for account GPU allowance</button><span id="hfAccount" hidden></span> <button id="hfSignOut" type="button" disabled hidden>Disconnect account</button></p><p id="hfIdentity" role="status" class="hint">CPU needs no sign-in. Checking optional GPU account connection…</p>
|
|
@@ -57,12 +57,13 @@
|
|
| 57 |
</div>
|
| 58 |
<div id="evaluationTools" hidden><p id="evaluationStatus" role="status" aria-live="polite"></p><p class="hint">Frozen evaluation does not select winners or compare population-relative fitness. Each comparison row keeps its network hash and must match the same track, vehicle, horizon, control rate, collision/idle rules, backend and engine build.</p><button id="compare_add" type="button">Add this frozen result to comparison</button><div class="table-scroll"><table><thead><tr><th>Policy</th><th>Vehicle</th><th>Track</th><th>Ticks</th><th>Best lap</th><th>Forward laps</th><th>Physical contacts</th><th>Distance (simulation units)</th><th>Comparison</th></tr></thead><tbody id="evaluationRows"></tbody></table></div><button id="compare_clear" type="button">Clear comparison</button></div>
|
| 59 |
</section>
|
|
|
|
| 60 |
<label class="keep-result"><input id="leaderboard_opt_in" type="checkbox"> After a completed lap, check its position against the public Steam leaderboard</label><p class="privacy-note">Optional, read-only check for supported built-in tracks. It sends the track ID and best lap to this app server; Steam receives only a public leaderboard read. No score is submitted. The result is an estimate, not an official rank.</p><p id="leaderboard_status" role="status" aria-live="polite"></p>
|
| 61 |
<p><button id="run_export" disabled>Export run receipt</button> <button id="player_report_download" disabled>Download player report (.txt)</button> <button id="history_csv_download" disabled>Download generation history (.csv)</button></p><p class="hint">Reports use only server-reported run and generation fields. The engine does not report per-reward contribution, so reward weights are shown as configured values only.</p>
|
| 62 |
<details id="portableBundle"><summary>Portable bucket bundle</summary><p>Build, export, or restore one validated bundle. Bucket storage is optional. When connected, automatic save is on by default; turn it off here to keep saves manual. Disconnected runs never call bucket storage.</p><label><input id="bundle_track" type="checkbox"> Include the selected track</label><label><input id="bundle_recipe" type="checkbox"> Include the recipe in the selected slot, if saved</label><label><input id="bundle_receipt" type="checkbox"> Include a sanitized receipt for the selected completed result</label><div class="toolbar"><button id="bundle_export" type="button">Export portable bundle</button><label for="bundle_import">Restore portable bundle (.json)<input id="bundle_import" type="file" accept=".json,application/json"></label><button id="bundle_receipt_download" type="button" disabled>Download staged receipt</button></div><p id="bundle_status" role="status" aria-live="polite">No bucket action has been requested.</p>
|
| 63 |
<details id="bucketStorage"><summary>Connect to an existing private bucket</summary><p>Bucket storage is optional. Authorizing storage also identifies your account for GPU requests; ordinary GPU sign-in remains available with the basic identity permission. Then enter your existing private bucket ID. Connected runs automatically save by default; uncheck this option to keep run saves manual. No personal token is requested or stored.</p><p id="bucket_scope_notice" class="privacy-note">HF describes the requested <code>write-repos</code> permission as read/write access to your personal repositories. That grant is broader than one selected bucket and is not a bucket-only permission; the app limits its own operations to the bucket you enter. The minimum current scope for bucket read/write has not been confirmed by HF.</p><button id="bucket_authorize" type="button" hidden disabled>Authorize bucket access with HF</button><p id="bucket_auth_unavailable" class="hint" hidden>HF bucket authorization is unavailable for this app. Local bundle export remains available.</p><p id="bucket_auth_status" role="status" aria-live="polite">Checking optional HF bucket authorization…</p><div id="bucketConnectForm" hidden><label for="bucket_id">Private bucket ID</label><input id="bucket_id" type="text" autocomplete="off" placeholder="username/bucket-name" disabled><button id="bucket_connect" type="button" disabled>Connect bucket</button></div><div id="bucketConnected" hidden><p id="bucket_account"></p><label class="keep-result"><input id="bucket_autosave" type="checkbox" checked> Automatically save every verified completed network and run log to this bucket</label><p class="hint">For each completed run, the browser fetches and verifies the network, then confirms server deletion before sending the network and sanitized log to your bucket. If deletion is not confirmed, no save occurs. If bucket saving is not confirmed, the verified local candidate stays available to download or retry; the remote save may have completed. Each batch step saves before the next starts, and a failed save stops the batch.</p><button id="bucket_disconnect" type="button">Disconnect bucket and clear HF authorization</button><div class="toolbar"><button id="bucket_save" type="button" disabled>Save selected bundle</button><button id="bucket_refresh" type="button" disabled>Refresh saved bundles</button></div><label for="bucket_items">Saved bundles from this app</label><select id="bucket_items" size="4" disabled><option value="">Refresh to list saved bundles</option></select><button id="bucket_restore" type="button" disabled>Restore selected bundle</button><hr><h3 id="bucketRecipeTitle" tabindex="-1">Recipe library (settings only)</h3><p class="hint">Save and import the validated recipes and batch definitions from this browser. This 32 KB library contains settings only; networks, tracks, run results and access tokens are never included. Import asks before replacing the local recipe library and never starts a run.</p><div class="toolbar"><button id="bucket_recipe_save" type="button" disabled>Save recipe library</button><button id="bucket_recipe_refresh" type="button" disabled>Refresh saved recipe libraries</button></div><label for="bucket_recipe_items">Saved recipe libraries</label><select id="bucket_recipe_items" size="4" disabled><option value="">Refresh to list saved recipe libraries</option></select><button id="bucket_recipe_import" type="button" disabled>Import selected recipe library</button><p id="bucket_recipe_status" role="status" aria-live="polite">No recipe library bucket action requested.</p></div><p id="bucket_status" role="status" aria-live="polite">Not connected. Bucket storage is optional.</p></details></details>
|
| 64 |
<p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
|
| 65 |
-
<details><summary>Effective training settings</summary><pre id="effectiveSettings"></pre></details><details><summary>Generation results</summary><pre id="generationHistory"></pre></details><details><summary>
|
| 66 |
</div><section id="training" class="pane" hidden><div class="card"><h2>Training settings</h2><p class="hint">Choose the track on Drive & view. Choose the training settings and reward mix here, then start the run on Drive & view. Recipes and batch sequences are saved in this browser.</p><p><label for="trackPresetTraining">Training track</label><select id="trackPresetTraining" data-track-library disabled><option value="">Upload a custom track</option></select></p><p data-track-status role="status" class="hint">Loading built-in replica tracks…</p><div id="training_controls"><label for="training_ticks">Simulated ticks per generation</label><input id="training_ticks" type="number" min="1" max="18000" value="60"><small>60 ticks = one simulated second. Current preview limit: population × generations × ticks ≤ 4,000,000; execution timeout 25 seconds.</small>
|
| 67 |
<div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="128" value="8" disabled><small>1–128 cars in this beta.</small></div><div><label for="generations">Generations</label><input id="generations" type="number" min="1" max="1000" value="30" disabled><small>1–1,000; try 20–50 before review.</small></div></div>
|
| 68 |
<label id="mutation_label" for="mutation">Mutation rate</label><input id="mutation" type="number" min="0" max="4.2" step="0.01" value="0.1" disabled><small>Use a positive mutation rate, including when continuing an uploaded network. A one-generation run still evaluates the uploaded parent.</small>
|
|
|
|
| 28 |
};
|
| 29 |
})();
|
| 30 |
</script>
|
| 31 |
+
<header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Pick a track and network, run a bounded training session, and keep your recipes in this browser.</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. For supported conditions, the lab checks the run’s final fitness policy (or frozen input) with a bounded fresh replay. It predicts conditions only and does not award or accumulate original-game stars; opponent races and other unsupported conditions remain unchecked.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p></details><p><a id="feedbackLink" href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Report a problem on GitHub</a> · Opens an editable draft and never submits it. Review details before posting; export a run receipt separately if it helps explain the issue.</p><p id="buildLabel" class="hint">Build: connecting…</p></header>
|
| 32 |
+
<nav class="tabs" aria-label="Lab sections"><button data-tab="drive" aria-selected="true">1 · Drive & view</button><button data-tab="training" aria-selected="false">2 · Training runs & presets</button><button data-tab="batch" aria-selected="false">3 · Batches</button><button data-tab="editor" aria-selected="false">4 · Network editor</button></nav><div id="drive" class="pane"><div class="grid"><section class="card"><h2>Simulation</h2><label for="run_mode">Action</label><select id="run_mode"><option value="train" selected>Train a population</option><option value="evaluate">Frozen evaluation · one policy, unchanged weights</option></select><p id="run_mode_help" class="hint">Training uses the selected recipe. Frozen evaluation runs the selected policy in a fresh car without changing its weights.</p><button id="run" disabled>Run training</button><p id="starCheckNotice" class="hint">After a run, the app automatically checks challenges for the current network when this track provides them. It uses the same driving settings with fixed weights and the selected run duration, or the track's longer challenge deadline up to 300 seconds, so the run may take longer. It checks this network only; it does not award game stars or combine progress across runs.</p><p class="hint">Select or create a network below to enable training. Review settings on tab 2 before running.</p><p>Supports Formula, Rally, Snowmobile and Truck networks. Control outputs follow the original vehicle editor rules. Network shape and supported sensors are bounded by the original game contract.</p>
|
| 33 |
<p id="computeBlock"><label for="backend">Compute</label><select id="backend" disabled><option value="cpu">CPU — no GPU allowance</option><option id="gpuOption" value="gpu" disabled>GPU — uses GPU allowance</option></select></p><p id="backendNote" class="hint">Checking available backends…</p>
|
| 34 |
<div id="connectionRecovery" hidden><button id="retry" type="button">Reconnect to app</button><p class="hint">Checks server availability. Does not restart training, sign you in, or reset GPU allowance.</p></div>
|
| 35 |
<div id="gpuAccount" aria-label="Optional GPU account"><p><button id="hfSignIn" type="button" disabled>Sign in with HF for account GPU allowance</button><span id="hfAccount" hidden></span> <button id="hfSignOut" type="button" disabled hidden>Disconnect account</button></p><p id="hfIdentity" role="status" class="hint">CPU needs no sign-in. Checking optional GPU account connection…</p>
|
|
|
|
| 57 |
</div>
|
| 58 |
<div id="evaluationTools" hidden><p id="evaluationStatus" role="status" aria-live="polite"></p><p class="hint">Frozen evaluation does not select winners or compare population-relative fitness. Each comparison row keeps its network hash and must match the same track, vehicle, horizon, control rate, collision/idle rules, backend and engine build.</p><button id="compare_add" type="button">Add this frozen result to comparison</button><div class="table-scroll"><table><thead><tr><th>Policy</th><th>Vehicle</th><th>Track</th><th>Ticks</th><th>Best lap</th><th>Forward laps</th><th>Physical contacts</th><th>Distance (simulation units)</th><th>Comparison</th></tr></thead><tbody id="evaluationRows"></tbody></table></div><button id="compare_clear" type="button">Clear comparison</button></div>
|
| 59 |
</section>
|
| 60 |
+
<section id="starCheckPanel" class="card" hidden aria-labelledby="starCheckTitle"><h2 id="starCheckTitle">Star challenge check</h2><p id="starCheckStatus" role="status" aria-live="polite"></p><div id="starCheckDetails" hidden><p id="starCheckPolicy" class="hint"></p><p id="starCheckBasis" class="hint"></p><p id="starCheckConditions" class="hint"></p><div class="table-scroll"><table><thead><tr><th>Challenge</th><th>Result</th><th>Reason</th><th>Completion time</th></tr></thead><tbody id="starCheckRows"></tbody></table></div><p class="hint">This is an independent check of the named run policy. It does not grant or accumulate original-game stars; replica challenges may differ from the original game.</p></div></section>
|
| 61 |
<label class="keep-result"><input id="leaderboard_opt_in" type="checkbox"> After a completed lap, check its position against the public Steam leaderboard</label><p class="privacy-note">Optional, read-only check for supported built-in tracks. It sends the track ID and best lap to this app server; Steam receives only a public leaderboard read. No score is submitted. The result is an estimate, not an official rank.</p><p id="leaderboard_status" role="status" aria-live="polite"></p>
|
| 62 |
<p><button id="run_export" disabled>Export run receipt</button> <button id="player_report_download" disabled>Download player report (.txt)</button> <button id="history_csv_download" disabled>Download generation history (.csv)</button></p><p class="hint">Reports use only server-reported run and generation fields. The engine does not report per-reward contribution, so reward weights are shown as configured values only.</p>
|
| 63 |
<details id="portableBundle"><summary>Portable bucket bundle</summary><p>Build, export, or restore one validated bundle. Bucket storage is optional. When connected, automatic save is on by default; turn it off here to keep saves manual. Disconnected runs never call bucket storage.</p><label><input id="bundle_track" type="checkbox"> Include the selected track</label><label><input id="bundle_recipe" type="checkbox"> Include the recipe in the selected slot, if saved</label><label><input id="bundle_receipt" type="checkbox"> Include a sanitized receipt for the selected completed result</label><div class="toolbar"><button id="bundle_export" type="button">Export portable bundle</button><label for="bundle_import">Restore portable bundle (.json)<input id="bundle_import" type="file" accept=".json,application/json"></label><button id="bundle_receipt_download" type="button" disabled>Download staged receipt</button></div><p id="bundle_status" role="status" aria-live="polite">No bucket action has been requested.</p>
|
| 64 |
<details id="bucketStorage"><summary>Connect to an existing private bucket</summary><p>Bucket storage is optional. Authorizing storage also identifies your account for GPU requests; ordinary GPU sign-in remains available with the basic identity permission. Then enter your existing private bucket ID. Connected runs automatically save by default; uncheck this option to keep run saves manual. No personal token is requested or stored.</p><p id="bucket_scope_notice" class="privacy-note">HF describes the requested <code>write-repos</code> permission as read/write access to your personal repositories. That grant is broader than one selected bucket and is not a bucket-only permission; the app limits its own operations to the bucket you enter. The minimum current scope for bucket read/write has not been confirmed by HF.</p><button id="bucket_authorize" type="button" hidden disabled>Authorize bucket access with HF</button><p id="bucket_auth_unavailable" class="hint" hidden>HF bucket authorization is unavailable for this app. Local bundle export remains available.</p><p id="bucket_auth_status" role="status" aria-live="polite">Checking optional HF bucket authorization…</p><div id="bucketConnectForm" hidden><label for="bucket_id">Private bucket ID</label><input id="bucket_id" type="text" autocomplete="off" placeholder="username/bucket-name" disabled><button id="bucket_connect" type="button" disabled>Connect bucket</button></div><div id="bucketConnected" hidden><p id="bucket_account"></p><label class="keep-result"><input id="bucket_autosave" type="checkbox" checked> Automatically save every verified completed network and run log to this bucket</label><p class="hint">For each completed run, the browser fetches and verifies the network, then confirms server deletion before sending the network and sanitized log to your bucket. If deletion is not confirmed, no save occurs. If bucket saving is not confirmed, the verified local candidate stays available to download or retry; the remote save may have completed. Each batch step saves before the next starts, and a failed save stops the batch.</p><button id="bucket_disconnect" type="button">Disconnect bucket and clear HF authorization</button><div class="toolbar"><button id="bucket_save" type="button" disabled>Save selected bundle</button><button id="bucket_refresh" type="button" disabled>Refresh saved bundles</button></div><label for="bucket_items">Saved bundles from this app</label><select id="bucket_items" size="4" disabled><option value="">Refresh to list saved bundles</option></select><button id="bucket_restore" type="button" disabled>Restore selected bundle</button><hr><h3 id="bucketRecipeTitle" tabindex="-1">Recipe library (settings only)</h3><p class="hint">Save and import the validated recipes and batch definitions from this browser. This 32 KB library contains settings only; networks, tracks, run results and access tokens are never included. Import asks before replacing the local recipe library and never starts a run.</p><div class="toolbar"><button id="bucket_recipe_save" type="button" disabled>Save recipe library</button><button id="bucket_recipe_refresh" type="button" disabled>Refresh saved recipe libraries</button></div><label for="bucket_recipe_items">Saved recipe libraries</label><select id="bucket_recipe_items" size="4" disabled><option value="">Refresh to list saved recipe libraries</option></select><button id="bucket_recipe_import" type="button" disabled>Import selected recipe library</button><p id="bucket_recipe_status" role="status" aria-live="polite">No recipe library bucket action requested.</p></div><p id="bucket_status" role="status" aria-live="polite">Not connected. Bucket storage is optional.</p></details></details>
|
| 65 |
<p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
|
| 66 |
+
<details><summary>Effective training settings</summary><pre id="effectiveSettings"></pre></details><details><summary>Generation results</summary><pre id="generationHistory"></pre></details><details><summary>About star challenge checks</summary><p>After a run, supported conditions are checked against its named policy with a bounded fresh replay. Results appear in the Star challenge check panel when available. These are predictions for the reconstructed replica track, not original-game awards; opponent races and unsupported conditions are left unchecked.</p></details></section></div>
|
| 67 |
</div><section id="training" class="pane" hidden><div class="card"><h2>Training settings</h2><p class="hint">Choose the track on Drive & view. Choose the training settings and reward mix here, then start the run on Drive & view. Recipes and batch sequences are saved in this browser.</p><p><label for="trackPresetTraining">Training track</label><select id="trackPresetTraining" data-track-library disabled><option value="">Upload a custom track</option></select></p><p data-track-status role="status" class="hint">Loading built-in replica tracks…</p><div id="training_controls"><label for="training_ticks">Simulated ticks per generation</label><input id="training_ticks" type="number" min="1" max="18000" value="60"><small>60 ticks = one simulated second. Current preview limit: population × generations × ticks ≤ 4,000,000; execution timeout 25 seconds.</small>
|
| 68 |
<div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="128" value="8" disabled><small>1–128 cars in this beta.</small></div><div><label for="generations">Generations</label><input id="generations" type="number" min="1" max="1000" value="30" disabled><small>1–1,000; try 20–50 before review.</small></div></div>
|
| 69 |
<label id="mutation_label" for="mutation">Mutation rate</label><input id="mutation" type="number" min="0" max="4.2" step="0.01" value="0.1" disabled><small>Use a positive mutation rate, including when continuing an uploaded network. A one-generation run still evaluates the uploaded parent.</small>
|
native/analysis.patch
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
--- a/src/bin/altd-sim/args.rs
|
| 2 |
+++ b/src/bin/altd-sim/args.rs
|
| 3 |
-
@@ -290,6 +290,
|
| 4 |
/// Export the final complete batch fitness leader before reproduction.
|
| 5 |
#[arg(long)]
|
| 6 |
save_final_candidate: bool,
|
|
@@ -10,6 +10,9 @@
|
|
| 10 |
+ /// Evaluate the exact init-network once without initializing an optimizer population.
|
| 11 |
+ #[arg(long, requires = "init_network")]
|
| 12 |
+ evaluate_frozen: bool,
|
|
|
|
|
|
|
|
|
|
| 13 |
+ /// Forward laps required for the zero-physical-contact winner.
|
| 14 |
+ #[arg(long, default_value_t = 2)]
|
| 15 |
+ clean_min_laps: u64,
|
|
@@ -18,27 +21,29 @@
|
|
| 18 |
stop_score_above: Option<f64>,
|
| 19 |
--- a/src/bin/altd-sim/main.rs
|
| 20 |
+++ b/src/bin/altd-sim/main.rs
|
| 21 |
-
@@ -161,6 +161,
|
| 22 |
batch_count,
|
| 23 |
checkpoint_every,
|
| 24 |
save_final_candidate,
|
| 25 |
+ analysis,
|
| 26 |
+ evaluate_frozen,
|
|
|
|
| 27 |
+ clean_min_laps,
|
| 28 |
resume,
|
| 29 |
eliminate_on_wall,
|
| 30 |
no_eliminate_on_wall,
|
| 31 |
-
@@ -203,6 +
|
| 32 |
batch_count,
|
| 33 |
checkpoint_every,
|
| 34 |
save_final_candidate,
|
| 35 |
+ analysis,
|
| 36 |
+ evaluate_frozen,
|
|
|
|
| 37 |
+ clean_min_laps,
|
| 38 |
resume,
|
| 39 |
stop,
|
| 40 |
eliminate_on_wall: flag(eliminate_on_wall, no_eliminate_on_wall),
|
| 41 |
-
---
|
| 42 |
+++ b/src/bin/altd-sim/scratch/analysis.rs
|
| 43 |
@@ -0,0 +1,253 @@
|
| 44 |
+//! Read-only generation-boundary observations and immutable winner snapshots.
|
|
@@ -294,21 +299,83 @@
|
|
| 294 |
+ assert!(better_lap(&row(2, 0, Some(9.0)), Some(&old)));
|
| 295 |
+ }
|
| 296 |
+}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
--- a/src/bin/altd-sim/scratch/mod.rs
|
| 298 |
+++ b/src/bin/altd-sim/scratch/mod.rs
|
| 299 |
-
@@ -1,5 +1,
|
| 300 |
//! Scratch training configuration, checkpoints, and stopping rules.
|
| 301 |
|
| 302 |
+mod analysis;
|
|
|
|
| 303 |
mod checkpoint;
|
| 304 |
mod runner;
|
| 305 |
mod stop;
|
| 306 |
-
@@ -37,6 +
|
| 307 |
pub(super) batch_count: usize,
|
| 308 |
pub(super) checkpoint_every: usize,
|
| 309 |
pub(super) save_final_candidate: bool,
|
| 310 |
+ pub(super) analysis: bool,
|
| 311 |
+ pub(super) evaluate_frozen: bool,
|
|
|
|
| 312 |
+ pub(super) clean_min_laps: u64,
|
| 313 |
pub(super) stop: StopRules,
|
| 314 |
pub(super) eliminate_on_wall: Option<bool>,
|
|
@@ -324,7 +391,7 @@
|
|
| 324 |
"mutation rates must be positive"
|
| 325 |
);
|
| 326 |
|
| 327 |
-
@@ -110,6 +110,
|
| 328 |
.validate_algorithm()
|
| 329 |
.unwrap_or_else(|e| panic!("settings: {e}"));
|
| 330 |
|
|
@@ -336,11 +403,15 @@
|
|
| 336 |
+ !config.evaluate_frozen
|
| 337 |
+ || (config.analysis && config.population == 1 && config.generations == 1 && config.checkpoint_every == 0),
|
| 338 |
+ "frozen evaluation requires analysis, one exact policy, one generation, and no checkpoint turnover"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
+ );
|
| 340 |
let dir = &config.out_dir;
|
| 341 |
let best_dir = dir.join("best_laps");
|
| 342 |
std::fs::create_dir_all(&best_dir).unwrap_or_else(|e| panic!("{}: {e}", best_dir.display()));
|
| 343 |
-
@@ -338,7 +
|
| 344 |
);
|
| 345 |
generation as usize
|
| 346 |
} else {
|
|
@@ -353,7 +424,7 @@
|
|
| 353 |
std::fs::write(&log_path, "").expect("log file");
|
| 354 |
0
|
| 355 |
};
|
| 356 |
-
@@ -370,6 +
|
| 357 |
started.elapsed().as_secs_f64()
|
| 358 |
);
|
| 359 |
|
|
@@ -361,10 +432,41 @@
|
|
| 361 |
+ let mut analysis = config
|
| 362 |
+ .analysis
|
| 363 |
+ .then(|| super::analysis::Analysis::new(dir, config.clean_min_laps, config.evaluate_frozen));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
let fmt_lap = |lap: Option<f64>| lap.map_or(" - ".to_string(), |t| format!("{t:7.2}"));
|
| 365 |
let mut stop_state = StopState {
|
| 366 |
best_lap: None,
|
| 367 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
}
|
| 369 |
let simulate_seconds = generation_started.elapsed().as_secs_f64();
|
| 370 |
let summary = batch.finish();
|
|
@@ -383,7 +485,7 @@
|
|
| 383 |
let best_score = summary.mean_scores.iter().copied().fold(f64::NEG_INFINITY, f64::max);
|
| 384 |
let mean_score = py_sum(summary.mean_scores.iter().copied()) / config.population as f64;
|
| 385 |
let mean_lap = (lap_count > 0).then(|| lap_time_sum / lap_count as f64);
|
| 386 |
-
@@ -521,7 +
|
| 387 |
}
|
| 388 |
let last = stop_reason.is_some();
|
| 389 |
let final_checkpoint = last && config.checkpoint_every > 0;
|
|
@@ -392,17 +494,20 @@
|
|
| 392 |
let leader = final_candidate_leader(&summary.fitness);
|
| 393 |
let mut export = runner.agents[leader].network.to_json();
|
| 394 |
export["schema_version"] = json!(1);
|
| 395 |
-
@@ -606,6 +
|
| 396 |
break;
|
| 397 |
}
|
| 398 |
}
|
| 399 |
+ if let Some(writer) = analysis {
|
| 400 |
+ writer.finish();
|
|
|
|
|
|
|
|
|
|
| 401 |
+ }
|
| 402 |
let _ = std::fs::remove_file(dir.join("progress.json"));
|
| 403 |
println!(
|
| 404 |
"done: best lap {} (generation {}), {:.1} h",
|
| 405 |
-
@@ -614,3 +
|
| 406 |
started.elapsed().as_secs_f64() / 3600.0
|
| 407 |
);
|
| 408 |
}
|
|
@@ -455,9 +560,126 @@
|
|
| 455 |
}
|
| 456 |
|
| 457 |
/// The adaptive mutation scale; its log is the C runtime's (.NET Math.Log).
|
| 458 |
-
--- a/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
+++ b/tests/winner_analysis.rs
|
| 460 |
-
@@ -0,0 +1,
|
| 461 |
+//! Native boundary checks: observations preserve evolution and frozen policies.
|
| 462 |
+use altd_sim::nn::network::Network;
|
| 463 |
+use serde_json::{json, Value};
|
|
@@ -536,6 +758,38 @@
|
|
| 536 |
+ }
|
| 537 |
+ root
|
| 538 |
+}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
+#[test]
|
| 540 |
+fn observation_is_rng_neutral_and_final_winner_matches_exact_pre_turnover_parameters() {
|
| 541 |
+ let root = workspace("neutral");
|
|
|
|
| 1 |
--- a/src/bin/altd-sim/args.rs
|
| 2 |
+++ b/src/bin/altd-sim/args.rs
|
| 3 |
+
@@ -290,6 +290,18 @@
|
| 4 |
/// Export the final complete batch fitness leader before reproduction.
|
| 5 |
#[arg(long)]
|
| 6 |
save_final_candidate: bool,
|
|
|
|
| 10 |
+ /// Evaluate the exact init-network once without initializing an optimizer population.
|
| 11 |
+ #[arg(long, requires = "init_network")]
|
| 12 |
+ evaluate_frozen: bool,
|
| 13 |
+
+ /// Observe every game statistics callback of a fresh fixed-policy evaluation.
|
| 14 |
+
+ #[arg(long, requires = "evaluate_frozen")]
|
| 15 |
+
+ challenge_observations: bool,
|
| 16 |
+ /// Forward laps required for the zero-physical-contact winner.
|
| 17 |
+ #[arg(long, default_value_t = 2)]
|
| 18 |
+ clean_min_laps: u64,
|
|
|
|
| 21 |
stop_score_above: Option<f64>,
|
| 22 |
--- a/src/bin/altd-sim/main.rs
|
| 23 |
+++ b/src/bin/altd-sim/main.rs
|
| 24 |
+
@@ -161,6 +161,10 @@
|
| 25 |
batch_count,
|
| 26 |
checkpoint_every,
|
| 27 |
save_final_candidate,
|
| 28 |
+ analysis,
|
| 29 |
+ evaluate_frozen,
|
| 30 |
+
+ challenge_observations,
|
| 31 |
+ clean_min_laps,
|
| 32 |
resume,
|
| 33 |
eliminate_on_wall,
|
| 34 |
no_eliminate_on_wall,
|
| 35 |
+
@@ -203,6 +207,10 @@
|
| 36 |
batch_count,
|
| 37 |
checkpoint_every,
|
| 38 |
save_final_candidate,
|
| 39 |
+ analysis,
|
| 40 |
+ evaluate_frozen,
|
| 41 |
+
+ challenge_observations,
|
| 42 |
+ clean_min_laps,
|
| 43 |
resume,
|
| 44 |
stop,
|
| 45 |
eliminate_on_wall: flag(eliminate_on_wall, no_eliminate_on_wall),
|
| 46 |
+
--- /dev/null
|
| 47 |
+++ b/src/bin/altd-sim/scratch/analysis.rs
|
| 48 |
@@ -0,0 +1,253 @@
|
| 49 |
+//! Read-only generation-boundary observations and immutable winner snapshots.
|
|
|
|
| 299 |
+ assert!(better_lap(&row(2, 0, Some(9.0)), Some(&old)));
|
| 300 |
+ }
|
| 301 |
+}
|
| 302 |
+
--- /dev/null
|
| 303 |
+
+++ b/src/bin/altd-sim/scratch/challenge.rs
|
| 304 |
+
@@ -0,0 +1,57 @@
|
| 305 |
+
+//! Fresh-policy statistics callback telemetry only; no challenge predicates.
|
| 306 |
+
+use altd_sim::training::TrainingRunner;
|
| 307 |
+
+use serde_json::json;
|
| 308 |
+
+use std::{fs::File, io::{BufWriter, Write}, path::{Path, PathBuf}};
|
| 309 |
+
+
|
| 310 |
+
+pub(super) struct ChallengeObservations {
|
| 311 |
+
+ output: BufWriter<File>,
|
| 312 |
+
+ dir: PathBuf,
|
| 313 |
+
+ requested_ticks: u64,
|
| 314 |
+
+ lap_score: f64,
|
| 315 |
+
+ backend: &'static str,
|
| 316 |
+
+ frames: u64,
|
| 317 |
+
+}
|
| 318 |
+
+
|
| 319 |
+
+impl ChallengeObservations {
|
| 320 |
+
+ pub(super) fn new(dir: &Path, requested_ticks: u64, lap_score: f64, backend: &'static str) -> Self {
|
| 321 |
+
+ assert!(lap_score.is_finite() && lap_score > 0.0, "challenge observations require a positive native lap score");
|
| 322 |
+
+ Self { output: BufWriter::new(File::create(dir.join("challenge-observations.jsonl")).expect("create callback observations")),
|
| 323 |
+
+ dir: dir.to_owned(), requested_ticks, lap_score, backend, frames: 0 }
|
| 324 |
+
+ }
|
| 325 |
+
+
|
| 326 |
+
+ pub(super) fn sample(&mut self, runner: &TrainingRunner, callback_active: bool) {
|
| 327 |
+
+ assert_eq!(runner.agents.len(), 1);
|
| 328 |
+
+ assert_eq!(runner.tick % 6, 0);
|
| 329 |
+
+ let agent = &runner.agents[0];
|
| 330 |
+
+ let stats = &agent.stats;
|
| 331 |
+
+ // An active callback updates previous_speed before driving. An inactive
|
| 332 |
+
+ // terminal callback stops before driving and its car speed is exact.
|
| 333 |
+
+ let velocity = altd_sim::math::godot_math::F2::from(agent.car.velocity);
|
| 334 |
+
+ let speed = if callback_active { stats.previous_speed }
|
| 335 |
+
+ else { velocity.dot(velocity).sqrt() as f64 };
|
| 336 |
+
+ let row = json!({"tick":runner.tick, "game_time":((runner.tick - 6) / 6) as f64 * 0.1,
|
| 337 |
+
+ "stats_update_count":stats.update_count, "current_score":stats.score.total_score,
|
| 338 |
+
+ "current_best_score":stats.score.total_score, "worst_score":stats.score.total_score,
|
| 339 |
+
+ "went_backwards":stats.score.went_backwards, "forward_laps":stats.score.lap_count,
|
| 340 |
+
+ "drift_cumulative":stats.total_drifting_amount,
|
| 341 |
+
+ "continuous_drift_time":stats.continuous_drift_time,
|
| 342 |
+
+ "max_continuous_drift_time":stats.max_continuous_drift_time,
|
| 343 |
+
+ "speed_kph":speed / 15.0 * 3.6, "collision_count":stats.collision_count,
|
| 344 |
+
+ "active":callback_active, "best_lap_seconds":stats.best_lap_time});
|
| 345 |
+
+ serde_json::to_writer(&mut self.output, &row).expect("write callback observation");
|
| 346 |
+
+ self.output.write_all(b"\n").expect("write callback newline");
|
| 347 |
+
+ self.frames += 1;
|
| 348 |
+
+ }
|
| 349 |
+
+
|
| 350 |
+
+ pub(super) fn finish(mut self) {
|
| 351 |
+
+ self.output.flush().expect("flush all callback observations");
|
| 352 |
+
+ let meta = json!({"schema":"ald.challenge-observations", "version":1, "mode":"evaluate",
|
| 353 |
+
+ "lap_score":self.lap_score, "stats_interval_seconds":0.1, "first_stats_tick":6,
|
| 354 |
+
+ "requested_ticks":self.requested_ticks, "frames":self.frames, "execution_backend":self.backend,
|
| 355 |
+
+ "active_semantics":"after_statistics_before_drive",
|
| 356 |
+
+ "collision_semantics":"AgentStats collision update count, not physical contact count",
|
| 357 |
+
+ "score_semantics":"Signed path score; single-car current_best_score and worst_score equal current_score"});
|
| 358 |
+
+ std::fs::write(self.dir.join("challenge-observations-meta.json"), serde_json::to_vec(&meta).unwrap())
|
| 359 |
+
+ .expect("write callback metadata");
|
| 360 |
+
+ }
|
| 361 |
+
+}
|
| 362 |
--- a/src/bin/altd-sim/scratch/mod.rs
|
| 363 |
+++ b/src/bin/altd-sim/scratch/mod.rs
|
| 364 |
+
@@ -1,5 +1,7 @@
|
| 365 |
//! Scratch training configuration, checkpoints, and stopping rules.
|
| 366 |
|
| 367 |
+mod analysis;
|
| 368 |
+
+mod challenge;
|
| 369 |
mod checkpoint;
|
| 370 |
mod runner;
|
| 371 |
mod stop;
|
| 372 |
+
@@ -37,6 +39,10 @@
|
| 373 |
pub(super) batch_count: usize,
|
| 374 |
pub(super) checkpoint_every: usize,
|
| 375 |
pub(super) save_final_candidate: bool,
|
| 376 |
+ pub(super) analysis: bool,
|
| 377 |
+ pub(super) evaluate_frozen: bool,
|
| 378 |
+
+ pub(super) challenge_observations: bool,
|
| 379 |
+ pub(super) clean_min_laps: u64,
|
| 380 |
pub(super) stop: StopRules,
|
| 381 |
pub(super) eliminate_on_wall: Option<bool>,
|
|
|
|
| 391 |
"mutation rates must be positive"
|
| 392 |
);
|
| 393 |
|
| 394 |
+
@@ -110,6 +110,19 @@
|
| 395 |
.validate_algorithm()
|
| 396 |
.unwrap_or_else(|e| panic!("settings: {e}"));
|
| 397 |
|
|
|
|
| 403 |
+ !config.evaluate_frozen
|
| 404 |
+ || (config.analysis && config.population == 1 && config.generations == 1 && config.checkpoint_every == 0),
|
| 405 |
+ "frozen evaluation requires analysis, one exact policy, one generation, and no checkpoint turnover"
|
| 406 |
+
+ );
|
| 407 |
+
+ assert!(
|
| 408 |
+
+ !config.challenge_observations || (config.evaluate_frozen && config.ticks <= 18_000),
|
| 409 |
+
+ "challenge observations require a bounded fresh frozen evaluation"
|
| 410 |
+ );
|
| 411 |
let dir = &config.out_dir;
|
| 412 |
let best_dir = dir.join("best_laps");
|
| 413 |
std::fs::create_dir_all(&best_dir).unwrap_or_else(|e| panic!("{}: {e}", best_dir.display()));
|
| 414 |
+
@@ -338,7 +351,11 @@
|
| 415 |
);
|
| 416 |
generation as usize
|
| 417 |
} else {
|
|
|
|
| 424 |
std::fs::write(&log_path, "").expect("log file");
|
| 425 |
0
|
| 426 |
};
|
| 427 |
+
@@ -370,6 +387,14 @@
|
| 428 |
started.elapsed().as_secs_f64()
|
| 429 |
);
|
| 430 |
|
|
|
|
| 432 |
+ let mut analysis = config
|
| 433 |
+ .analysis
|
| 434 |
+ .then(|| super::analysis::Analysis::new(dir, config.clean_min_laps, config.evaluate_frozen));
|
| 435 |
+
+ let mut challenge = config.challenge_observations.then(|| super::challenge::ChallengeObservations::new(
|
| 436 |
+
+ dir, config.ticks, runner.world.track.path_length() * (5.0 / 384.0),
|
| 437 |
+
+ if gpu_sim.is_some() { "cuda" } else { "cpu" },
|
| 438 |
+
+ ));
|
| 439 |
let fmt_lap = |lap: Option<f64>| lap.map_or(" - ".to_string(), |t| format!("{t:7.2}"));
|
| 440 |
let mut stop_state = StopState {
|
| 441 |
best_lap: None,
|
| 442 |
+
@@ -422,12 +447,22 @@
|
| 443 |
+
let track_config = current_track.as_ref().map(|track| track.track.config.clone());
|
| 444 |
+
match (&mut gpu_sim, &gpu_world) {
|
| 445 |
+
(Some(sim), Some(world)) => {
|
| 446 |
+
- runner
|
| 447 |
+
- .advance_generation_gpu(sim, world, config.ticks)
|
| 448 |
+
- .unwrap_or_else(|e| panic!("--gpu: {e}"));
|
| 449 |
+
+ if let Some(observer) = &mut challenge {
|
| 450 |
+
+ runner.advance_generation_gpu_observed(sim, world, config.ticks,
|
| 451 |
+
+ |runner, active| observer.sample(runner, active))
|
| 452 |
+
+ .unwrap_or_else(|e| panic!("--gpu: {e}"));
|
| 453 |
+
+ } else {
|
| 454 |
+
+ runner.advance_generation_gpu(sim, world, config.ticks)
|
| 455 |
+
+ .unwrap_or_else(|e| panic!("--gpu: {e}"));
|
| 456 |
+
+ }
|
| 457 |
+
}
|
| 458 |
+
_ => {
|
| 459 |
+
- runner.advance_generation(config.ticks);
|
| 460 |
+
+ if let Some(observer) = &mut challenge {
|
| 461 |
+
+ runner.advance_generation_observed(config.ticks,
|
| 462 |
+
+ |runner, active| observer.sample(runner, active));
|
| 463 |
+
+ } else {
|
| 464 |
+
+ runner.advance_generation(config.ticks);
|
| 465 |
+
+ }
|
| 466 |
+
}
|
| 467 |
+
}
|
| 468 |
+
simulated_ticks += runner.tick;
|
| 469 |
+
@@ -493,6 +528,18 @@
|
| 470 |
}
|
| 471 |
let simulate_seconds = generation_started.elapsed().as_secs_f64();
|
| 472 |
let summary = batch.finish();
|
|
|
|
| 485 |
let best_score = summary.mean_scores.iter().copied().fold(f64::NEG_INFINITY, f64::max);
|
| 486 |
let mean_score = py_sum(summary.mean_scores.iter().copied()) / config.population as f64;
|
| 487 |
let mean_lap = (lap_count > 0).then(|| lap_time_sum / lap_count as f64);
|
| 488 |
+
@@ -521,7 +568,7 @@
|
| 489 |
}
|
| 490 |
let last = stop_reason.is_some();
|
| 491 |
let final_checkpoint = last && config.checkpoint_every > 0;
|
|
|
|
| 494 |
let leader = final_candidate_leader(&summary.fitness);
|
| 495 |
let mut export = runner.agents[leader].network.to_json();
|
| 496 |
export["schema_version"] = json!(1);
|
| 497 |
+
@@ -606,6 +653,12 @@
|
| 498 |
break;
|
| 499 |
}
|
| 500 |
}
|
| 501 |
+ if let Some(writer) = analysis {
|
| 502 |
+ writer.finish();
|
| 503 |
+
+ }
|
| 504 |
+
+ if let Some(writer) = challenge {
|
| 505 |
+
+ writer.finish();
|
| 506 |
+ }
|
| 507 |
let _ = std::fs::remove_file(dir.join("progress.json"));
|
| 508 |
println!(
|
| 509 |
"done: best lap {} (generation {}), {:.1} h",
|
| 510 |
+
@@ -614,3 +667,7 @@
|
| 511 |
started.elapsed().as_secs_f64() / 3600.0
|
| 512 |
);
|
| 513 |
}
|
|
|
|
| 560 |
}
|
| 561 |
|
| 562 |
/// The adaptive mutation scale; its log is the C runtime's (.NET Math.Log).
|
| 563 |
+
--- a/src/training/runner/gpu.rs
|
| 564 |
+
+++ b/src/training/runner/gpu.rs
|
| 565 |
+
@@ -96,6 +96,37 @@
|
| 566 |
+
) -> Result<u64, String> {
|
| 567 |
+
let (ticks, time_limit) = self.gpu_generation_window(time_limit_ticks)?;
|
| 568 |
+
self.advance_window_gpu(sim, world, ticks, true, Some(time_limit))
|
| 569 |
+
+ }
|
| 570 |
+
+
|
| 571 |
+
+ /// Observe every existing statistics callback while keeping device state
|
| 572 |
+
+ /// resident. Readback is observation only; never re-upload rounded host state.
|
| 573 |
+
+ #[cfg(not(target_arch = "wasm32"))]
|
| 574 |
+
+ pub fn advance_generation_gpu_observed<F>(
|
| 575 |
+
+ &mut self,
|
| 576 |
+
+ sim: &mut crate::gpu::simulation::GpuSim,
|
| 577 |
+
+ world: &crate::gpu::hip::GpuWorld,
|
| 578 |
+
+ time_limit_ticks: u64,
|
| 579 |
+
+ mut observe: F,
|
| 580 |
+
+ ) -> Result<u64, String>
|
| 581 |
+
+ where F: FnMut(&Self, bool) {
|
| 582 |
+
+ assert_eq!(self.agents.len(), 1, "callback observations require one fixed policy");
|
| 583 |
+
+ let start = self.tick;
|
| 584 |
+
+ let (remaining, limit) = self.gpu_generation_window(time_limit_ticks)?;
|
| 585 |
+
+ let end = start + remaining;
|
| 586 |
+
+ self.upload_state_gpu(sim, world)?;
|
| 587 |
+
+ while self.tick < end {
|
| 588 |
+
+ let previous_updates = self.agents[0].stats.update_count;
|
| 589 |
+
+ let count = (6 - self.tick % 6).min(end - self.tick);
|
| 590 |
+
+ let executed = self.window_gpu(sim, count, true, Some(limit))?;
|
| 591 |
+
+ self.download_state_gpu(sim, world)?;
|
| 592 |
+
+ if self.tick % 6 == 0 {
|
| 593 |
+
+ let callback_active = self.statistics_callback_active(previous_updates);
|
| 594 |
+
+ observe(self, callback_active);
|
| 595 |
+
+ if !callback_active || ((self.tick - 6) / 6) as f64 * 0.1 >= limit { break; }
|
| 596 |
+
+ }
|
| 597 |
+
+ if executed == 0 { break; }
|
| 598 |
+
+ }
|
| 599 |
+
+ Ok(self.tick - start)
|
| 600 |
+
}
|
| 601 |
+
|
| 602 |
+
/// The window length and time limit (seconds) of `advance_generation_gpu`.
|
| 603 |
+
--- a/src/training/runner/schedule.rs
|
| 604 |
+
+++ b/src/training/runner/schedule.rs
|
| 605 |
+
@@ -112,6 +112,37 @@
|
| 606 |
+
pub fn advance_generation(&mut self, time_limit_ticks: u64) -> u64 {
|
| 607 |
+
let (ticks, time_limit) = self.generation_window(time_limit_ticks);
|
| 608 |
+
self.advance_window(ticks, true, Some(time_limit))
|
| 609 |
+
+ }
|
| 610 |
+
+
|
| 611 |
+
+ /// Observe one fixed-policy car at each existing statistics callback.
|
| 612 |
+
+ /// Windows retain the absolute physics and inference cursors. No optimizer runs.
|
| 613 |
+
+ pub fn advance_generation_observed<F>(&mut self, time_limit_ticks: u64, mut observe: F) -> u64
|
| 614 |
+
+ where F: FnMut(&Self, bool) {
|
| 615 |
+
+ assert_eq!(self.agents.len(), 1, "callback observations require one fixed policy");
|
| 616 |
+
+ assert!(!self.user_paused, "callback observations require an unpaused evaluation");
|
| 617 |
+
+ let start = self.tick;
|
| 618 |
+
+ let (remaining, limit) = self.generation_window(time_limit_ticks);
|
| 619 |
+
+ let end = start + remaining;
|
| 620 |
+
+ while self.tick < end {
|
| 621 |
+
+ let previous_updates = self.agents[0].stats.update_count;
|
| 622 |
+
+ let count = (6 - self.tick % 6).min(end - self.tick);
|
| 623 |
+
+ let executed = self.advance_window(count, true, Some(limit));
|
| 624 |
+
+ if self.tick % 6 == 0 {
|
| 625 |
+
+ let callback_active = self.statistics_callback_active(previous_updates);
|
| 626 |
+
+ observe(self, callback_active);
|
| 627 |
+
+ if !callback_active || ((self.tick - 6) / 6) as f64 * 0.1 >= limit { break; }
|
| 628 |
+
+ }
|
| 629 |
+
+ if executed == 0 { break; }
|
| 630 |
+
+ }
|
| 631 |
+
+ self.tick - start
|
| 632 |
+
+ }
|
| 633 |
+
+
|
| 634 |
+
+ /// Stats precede driving. A drive-step wall contact may already have changed
|
| 635 |
+
+ /// car.active at readback; the update counter and idle rule recover the
|
| 636 |
+
+ /// callback's actual post-statistics/pre-drive state without altering it.
|
| 637 |
+
+ pub fn statistics_callback_active(&self, previous_updates: u64) -> bool {
|
| 638 |
+
+ let stats = &self.agents[0].stats;
|
| 639 |
+
+ stats.update_count > previous_updates && !(self.eliminate_when_idle && stats.idle_ticks > 80)
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
/// The remaining window of a generation with `time_limit_ticks`: the tick
|
| 643 |
+
--- a/src/training/runner/tests.rs
|
| 644 |
+
+++ b/src/training/runner/tests.rs
|
| 645 |
+
@@ -382,3 +382,34 @@
|
| 646 |
+
);
|
| 647 |
+
}
|
| 648 |
+
}
|
| 649 |
+
+
|
| 650 |
+
+
|
| 651 |
+
+#[test]
|
| 652 |
+
+fn challenge_callbacks_preserve_idle_and_inactive_cutoffs() {
|
| 653 |
+
+ let seed = Network::from_vector(&[20, 5], vec![0.0; 105]);
|
| 654 |
+
+ for inactive in [false, true] {
|
| 655 |
+
+ let mut plain = generated_runner(1);
|
| 656 |
+
+ let mut observed = generated_runner(1);
|
| 657 |
+
+ plain.resume(&[seed.clone()], 0);
|
| 658 |
+
+ observed.resume(&[seed.clone()], 0);
|
| 659 |
+
+ if inactive {
|
| 660 |
+
+ plain.agents[0].car.deactivate();
|
| 661 |
+
+ observed.agents[0].car.deactivate();
|
| 662 |
+
+ } else {
|
| 663 |
+
+ plain.agents[0].stats.idle_ticks = 79;
|
| 664 |
+
+ observed.agents[0].stats.idle_ticks = 79;
|
| 665 |
+
+ }
|
| 666 |
+
+ let expected = plain.advance_generation(600);
|
| 667 |
+
+ let mut frames = Vec::new();
|
| 668 |
+
+ let actual = observed.advance_generation_observed(600, |runner, active| {
|
| 669 |
+
+ frames.push((runner.tick, active, runner.agents[0].stats.update_count));
|
| 670 |
+
+ });
|
| 671 |
+
+ assert_eq!(expected, actual, "callback mode must stop at the same pre-drive frame");
|
| 672 |
+
+ assert_same_agents(&plain, &observed);
|
| 673 |
+
+ if inactive {
|
| 674 |
+
+ assert_eq!(frames, vec![(6, false, 0)]);
|
| 675 |
+
+ } else {
|
| 676 |
+
+ assert_eq!(frames, vec![(6, true, 1), (12, false, 2)]);
|
| 677 |
+
+ }
|
| 678 |
+
+ }
|
| 679 |
+
+}
|
| 680 |
+
--- /dev/null
|
| 681 |
+++ b/tests/winner_analysis.rs
|
| 682 |
+
@@ -0,0 +1,179 @@
|
| 683 |
+//! Native boundary checks: observations preserve evolution and frozen policies.
|
| 684 |
+use altd_sim::nn::network::Network;
|
| 685 |
+use serde_json::{json, Value};
|
|
|
|
| 758 |
+ }
|
| 759 |
+ root
|
| 760 |
+}
|
| 761 |
+
+
|
| 762 |
+
+#[test]
|
| 763 |
+
+fn challenge_callbacks_preserve_frozen_schedule_and_capture_every_statistics_update() {
|
| 764 |
+
+ let root = workspace("challenge-callbacks");
|
| 765 |
+
+ run(&root, "plain", &["--analysis", "--evaluate-frozen"]);
|
| 766 |
+
+ run(&root, "observed", &["--analysis", "--evaluate-frozen", "--challenge-observations"]);
|
| 767 |
+
+ let plain = fs::read(root.join("plain/analysis/descriptors.jsonl")).unwrap();
|
| 768 |
+
+ let observed = fs::read(root.join("observed/analysis/descriptors.jsonl")).unwrap();
|
| 769 |
+
+ assert_eq!(plain, observed, "callback readback must not alter any final physical/statistical measurement or weights");
|
| 770 |
+
+ let rows: Vec<Value> = fs::read_to_string(root.join("observed/challenge-observations.jsonl"))
|
| 771 |
+
+ .unwrap().lines().map(|line| serde_json::from_str(line).unwrap()).collect();
|
| 772 |
+
+ assert_eq!(rows.len(), 11, "60 requested ticks use callbacks 6 through 66 inclusive");
|
| 773 |
+
+ let final_row: Value = serde_json::from_slice(&observed).unwrap();
|
| 774 |
+
+ for (index, row) in rows.iter().enumerate() {
|
| 775 |
+
+ assert_eq!(row["tick"], (index + 1) * 6);
|
| 776 |
+
+ assert_eq!(row["game_time"].as_f64().unwrap(), index as f64 * 0.1);
|
| 777 |
+
+ assert_eq!(row["stats_update_count"], index + 1);
|
| 778 |
+
+ assert_eq!(row["current_score"], row["worst_score"]);
|
| 779 |
+
+ assert_eq!(row["current_score"], row["current_best_score"]);
|
| 780 |
+
+ assert_eq!(row["active"], true);
|
| 781 |
+
+ assert!(row["speed_kph"].as_f64().unwrap().is_finite());
|
| 782 |
+
+ }
|
| 783 |
+
+ let speed_sum: f64 = rows.iter().map(|row| row["speed_kph"].as_f64().unwrap() / 3.6 * 15.0).sum();
|
| 784 |
+
+ assert!((speed_sum - final_row["raw_metrics"]["total_speed"].as_f64().unwrap()).abs() < 1e-8,
|
| 785 |
+
+ "every speed sample must describe the statistics callback, not its following drive step");
|
| 786 |
+
+ assert_eq!(rows.last().unwrap()["current_score"], final_row["raw_metrics"]["total_score"]);
|
| 787 |
+
+ assert_eq!(rows.last().unwrap()["collision_count"], final_row["objectives"]["collision_updates"]);
|
| 788 |
+
+ let meta = load(root.join("observed/challenge-observations-meta.json"));
|
| 789 |
+
+ assert_eq!(meta["frames"], rows.len());
|
| 790 |
+
+ assert!(meta["lap_score"].as_f64().unwrap() > 0.0);
|
| 791 |
+
+ fs::remove_dir_all(root).unwrap();
|
| 792 |
+
+}
|
| 793 |
+#[test]
|
| 794 |
+fn observation_is_rng_neutral_and_final_winner_matches_exact_pre_turnover_parameters() {
|
| 795 |
+ let root = workspace("neutral");
|
native/build-receipt.json
CHANGED
|
@@ -1,89 +1 @@
|
|
| 1 |
-
{
|
| 2 |
-
"schema": "ald-winners-linux-build-v1",
|
| 3 |
-
"status": "passed",
|
| 4 |
-
"created_utc": "2026-10-14T04:35:53.223545+00:00",
|
| 5 |
-
"source": {
|
| 6 |
-
"upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0",
|
| 7 |
-
"source_receipt_sha256": "33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34",
|
| 8 |
-
"patch_sha256": "56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63",
|
| 9 |
-
"source_inventory_sha256": "3311905f217e08a927ca73d71db6687aaaf68f01cc2758e4028b92fa703bcebe"
|
| 10 |
-
},
|
| 11 |
-
"artifacts": {
|
| 12 |
-
"cpu/bin/altd-sim": {
|
| 13 |
-
"path": "cpu/bin/altd-sim",
|
| 14 |
-
"bytes": 3580296,
|
| 15 |
-
"sha256": "6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d",
|
| 16 |
-
"origin_type": "local_linux_build"
|
| 17 |
-
},
|
| 18 |
-
"gpu/bin/altd-sim": {
|
| 19 |
-
"path": "gpu/bin/altd-sim",
|
| 20 |
-
"bytes": 3580296,
|
| 21 |
-
"sha256": "6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d",
|
| 22 |
-
"origin_type": "local_linux_build"
|
| 23 |
-
},
|
| 24 |
-
"gpu/bin/libaltd_gpu.so": {
|
| 25 |
-
"path": "gpu/bin/libaltd_gpu.so",
|
| 26 |
-
"bytes": 4711704,
|
| 27 |
-
"sha256": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca",
|
| 28 |
-
"origin_type": "retained_cuda_b90"
|
| 29 |
-
}
|
| 30 |
-
},
|
| 31 |
-
"checks": {
|
| 32 |
-
"native_tests": {
|
| 33 |
-
"status": "passed",
|
| 34 |
-
"receipt_path": "build/receipts/linux-native-tests.json",
|
| 35 |
-
"receipt_sha256": "ddb35821b16094bc0da624748e1f07e0b7a06cea4d4bc9baa28a7b6356bfacdb"
|
| 36 |
-
},
|
| 37 |
-
"cpu_smoke": {
|
| 38 |
-
"status": "passed",
|
| 39 |
-
"receipt_path": "build/receipts/linux-cpu-smoke.json",
|
| 40 |
-
"receipt_sha256": "afb4107c84bb38f21b9fabd6bbd9401b236ebff16ae2ce23230640994b8fd85c"
|
| 41 |
-
},
|
| 42 |
-
"gpu_layout": {
|
| 43 |
-
"status": "passed",
|
| 44 |
-
"receipt_path": "build/receipts/linux-gpu-layout.json",
|
| 45 |
-
"receipt_sha256": "45462cb6f4047e7715d6258fea60b9b39b1f8ea2e88ebbe3479454d53801cc00"
|
| 46 |
-
}
|
| 47 |
-
},
|
| 48 |
-
"gpu_device_execution": "not_run",
|
| 49 |
-
"compiler": {
|
| 50 |
-
"version": "rustc 1.95.0 (59807616e 2026-04-14)",
|
| 51 |
-
"host": "aarch64-apple-darwin",
|
| 52 |
-
"target": "x86_64-unknown-linux-gnu",
|
| 53 |
-
"cross_compiled": true,
|
| 54 |
-
"standard_library": {
|
| 55 |
-
"origin": "official_rust_distribution",
|
| 56 |
-
"url": "https://static.rust-lang.org/dist/2026-04-16/rust-std-1.95.0-x86_64-unknown-linux-gnu.tar.xz",
|
| 57 |
-
"sha256": "047ea7098803d3500fa1072e9cee5392697e21525559e4458128a2bf874aa382",
|
| 58 |
-
"installed_manifest_sha256": "46c1f8d1bcef24174217545ece8c22eb395a42e3534f618736c17a759a31e255"
|
| 59 |
-
},
|
| 60 |
-
"linker": {
|
| 61 |
-
"clang_version": "Apple clang version 21.0.0 (clang-2100.3.34.2)",
|
| 62 |
-
"lld_origin": "installed Rust1.95 bundled rust-lld",
|
| 63 |
-
"wrapper_path": "build/cross-tools/linux-linker",
|
| 64 |
-
"wrapper_sha256": "53fcd6801c00d79b669c231841079988a285e583142e59b1cdf29f8c19b1fc5f"
|
| 65 |
-
},
|
| 66 |
-
"sysroot": {
|
| 67 |
-
"origin": "read-only Ubuntu22.04 libraries copied from our existing nvidia/cuda:13.0.2-devel-ubuntu22.04 container",
|
| 68 |
-
"inventory_path": "build/receipts/linux-sysroot-inventory.json",
|
| 69 |
-
"inventory_sha256": "a4a9d33b5ec227a18b7f76f2df085fba372c5b6acd827cf28de84867df53ba6f"
|
| 70 |
-
}
|
| 71 |
-
},
|
| 72 |
-
"build_command": "/Users/tiga/.cargo/bin/cargo +1.95.0 build --target x86_64-unknown-linux-gnu --manifest-path build/source/Cargo.toml --release --locked --offline --no-default-features --features cli --bin altd-sim",
|
| 73 |
-
"build_environment": {
|
| 74 |
-
"CARGO_TARGET_DIR": "/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-target",
|
| 75 |
-
"CARGO_PROFILE_RELEASE_LTO": "false",
|
| 76 |
-
"CARGO_PROFILE_RELEASE_CODEGEN_UNITS": "16",
|
| 77 |
-
"CARGO_PROFILE_RELEASE_DEBUG": "0",
|
| 78 |
-
"CARGO_BUILD_JOBS": "4",
|
| 79 |
-
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-tools/linux-linker",
|
| 80 |
-
"RUSTFLAGS": "--sysroot /Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-sysroot -C target-cpu=x86-64"
|
| 81 |
-
},
|
| 82 |
-
"build_recipe_path": "build/recipes/native-cross-build.py",
|
| 83 |
-
"build_recipe_sha256": "17c731594d158bbc6627340954c1b2c53e90232fe2f89b48c2c33d73ae56fcc4",
|
| 84 |
-
"build_log_path": "build/receipts/native-cross-build.log",
|
| 85 |
-
"build_log_sha256": "d201fa42f1e379210dd385d394207e4afccb2d837e281ee86f6b4d992aa087f8",
|
| 86 |
-
"source_preserved": true,
|
| 87 |
-
"installed_toolchain_unchanged": true,
|
| 88 |
-
"original_baseline_binaries_unchanged": true
|
| 89 |
-
}
|
|
|
|
| 1 |
+
{"artifact":{"bytes":3604272,"path":"build/artifacts/altd-sim-stars","sha256":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849"},"artifacts":{"cpu/bin/altd-sim":{"bytes":3604272,"origin_type":"local_linux_build","path":"build/artifacts/altd-sim-stars","sha256":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849"},"gpu/bin/altd-sim":{"bytes":3604272,"origin_type":"local_linux_build","path":"build/artifacts/altd-sim-stars","sha256":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849"},"gpu/bin/libaltd_gpu.so":{"bytes":4711704,"origin_type":"retained_cuda_b90","path":"gpu/bin/libaltd_gpu.so","sha256":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca"}},"based_on_build_receipt_sha256":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","baseline_build_evidence":{"artifacts":{"cpu/bin/altd-sim":{"bytes":3580296,"origin_type":"local_linux_build","path":"cpu/bin/altd-sim","sha256":"6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d"},"gpu/bin/altd-sim":{"bytes":3580296,"origin_type":"local_linux_build","path":"gpu/bin/altd-sim","sha256":"6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d"},"gpu/bin/libaltd_gpu.so":{"bytes":4711704,"origin_type":"retained_cuda_b90","path":"gpu/bin/libaltd_gpu.so","sha256":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca"}},"build_command":"/Users/tiga/.cargo/bin/cargo +1.95.0 build --target x86_64-unknown-linux-gnu --manifest-path build/source/Cargo.toml --release --locked --offline --no-default-features --features cli --bin altd-sim","build_environment":{"CARGO_BUILD_JOBS":"4","CARGO_PROFILE_RELEASE_CODEGEN_UNITS":"16","CARGO_PROFILE_RELEASE_DEBUG":"0","CARGO_PROFILE_RELEASE_LTO":"false","CARGO_TARGET_DIR":"/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-target","CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER":"/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-tools/linux-linker","RUSTFLAGS":"--sysroot /Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-sysroot -C target-cpu=x86-64"},"build_log_path":"build/receipts/native-cross-build.log","build_log_sha256":"d201fa42f1e379210dd385d394207e4afccb2d837e281ee86f6b4d992aa087f8","build_recipe_path":"build/recipes/native-cross-build.py","build_recipe_sha256":"17c731594d158bbc6627340954c1b2c53e90232fe2f89b48c2c33d73ae56fcc4","checks":{"cpu_smoke":{"receipt_path":"build/receipts/linux-cpu-smoke.json","receipt_sha256":"afb4107c84bb38f21b9fabd6bbd9401b236ebff16ae2ce23230640994b8fd85c","status":"passed"},"gpu_layout":{"receipt_path":"build/receipts/linux-gpu-layout.json","receipt_sha256":"45462cb6f4047e7715d6258fea60b9b39b1f8ea2e88ebbe3479454d53801cc00","status":"passed"},"native_tests":{"receipt_path":"build/receipts/linux-native-tests.json","receipt_sha256":"ddb35821b16094bc0da624748e1f07e0b7a06cea4d4bc9baa28a7b6356bfacdb","status":"passed"}},"compiler":{"cross_compiled":true,"host":"aarch64-apple-darwin","linker":{"clang_version":"Apple clang version 21.0.0 (clang-2100.3.34.2)","lld_origin":"installed Rust1.95 bundled rust-lld","wrapper_path":"build/cross-tools/linux-linker","wrapper_sha256":"53fcd6801c00d79b669c231841079988a285e583142e59b1cdf29f8c19b1fc5f"},"standard_library":{"installed_manifest_sha256":"46c1f8d1bcef24174217545ece8c22eb395a42e3534f618736c17a759a31e255","origin":"official_rust_distribution","sha256":"047ea7098803d3500fa1072e9cee5392697e21525559e4458128a2bf874aa382","url":"https://static.rust-lang.org/dist/2026-04-16/rust-std-1.95.0-x86_64-unknown-linux-gnu.tar.xz"},"sysroot":{"inventory_path":"build/receipts/linux-sysroot-inventory.json","inventory_sha256":"a4a9d33b5ec227a18b7f76f2df085fba372c5b6acd827cf28de84867df53ba6f","origin":"read-only Ubuntu22.04 libraries copied from our existing nvidia/cuda:13.0.2-devel-ubuntu22.04 container"},"target":"x86_64-unknown-linux-gnu","version":"rustc 1.95.0 (59807616e 2026-04-14)"},"created_utc":"2026-10-14T04:35:53.223545+00:00","gpu_device_execution":"not_run","installed_toolchain_unchanged":true,"original_baseline_binaries_unchanged":true,"schema":"ald-winners-linux-build-v1","source":{"patch_sha256":"56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63","source_inventory_sha256":"3311905f217e08a927ca73d71db6687aaaf68f01cc2758e4028b92fa703bcebe","source_receipt_sha256":"33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34","upstream_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"},"source_preserved":true,"status":"passed"},"baseline_build_evidence_sha256":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","build_log_sha256":"ef5114e8176eac9e23105c3d9bd0f1689edbb0e0e798171df6a56094851a93d1","callback_runtime_evidence":{"binary_sha256":"3aa1d364e0117f553a52404ae336efcfebd827fc0ca347e811196cbe31e32849","callbacks":{"observed":{"descriptors_sha256":"9533425b75286e6d3bc08f31bd375da048825294ea40e40f82dba36868339714","log_path":"build/receipts/star-linux-observed.log","log_sha256":"33572e0fb5dce3daa5ba8bc3ab5779585e3a76e4b489c4a04c1e9422dc076742"},"plain":{"descriptors_sha256":"9533425b75286e6d3bc08f31bd375da048825294ea40e40f82dba36868339714","log_path":"build/receipts/star-linux-plain.log","log_sha256":"f8843fd74b9a528c42c1677dfe98cceeece050e1e17b566c82b69fac98a8caf2"}},"cpu_frames":11,"cpu_observer_neutrality":true,"cpu_vehicle_type":2,"gpu_device_execution":"not_run","gpu_layout":{"device":null,"library_sha256":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca","log_path":"build/receipts/star-linux-gpu-info.txt","log_sha256":"87dd7d86457a64e623ae15a29a97fe45d9f5610d4e68eb4cd0bd52b5d8147e3b","status":"passed"},"schema":"ald-star-linux-runtime-v1","status":"passed","test_harness_note":"Initial human-text layout assertion failed on valid JSON output; corrected by parsing existing output, no repeat runtime call."},"command":["/Users/tiga/.cargo/bin/cargo","+1.95.0","build","--target","x86_64-unknown-linux-gnu","--manifest-path","build/source/Cargo.toml","--release","--locked","--offline","--no-default-features","--features","cli","--bin","altd-sim"],"compiler":{"cross_compiled":true,"host":"aarch64-apple-darwin","linker":{"clang_version":"Apple clang version 21.0.0 (clang-2100.3.34.2)","lld_origin":"installed Rust1.95 bundled rust-lld","wrapper_path":"build/cross-tools/linux-linker","wrapper_sha256":"53fcd6801c00d79b669c231841079988a285e583142e59b1cdf29f8c19b1fc5f"},"standard_library":{"installed_manifest_sha256":"46c1f8d1bcef24174217545ece8c22eb395a42e3534f618736c17a759a31e255","origin":"official_rust_distribution","sha256":"047ea7098803d3500fa1072e9cee5392697e21525559e4458128a2bf874aa382","url":"https://static.rust-lang.org/dist/2026-04-16/rust-std-1.95.0-x86_64-unknown-linux-gnu.tar.xz"},"sysroot":{"inventory_path":"build/receipts/linux-sysroot-inventory.json","inventory_sha256":"a4a9d33b5ec227a18b7f76f2df085fba372c5b6acd827cf28de84867df53ba6f","origin":"read-only Ubuntu22.04 libraries copied from our existing nvidia/cuda:13.0.2-devel-ubuntu22.04 container"},"target":"x86_64-unknown-linux-gnu","version":"rustc 1.95.0 (59807616e 2026-04-14)"},"device_execution":"not_run","elapsed_seconds":20.25416398048401,"environment":{"CARGO_BUILD_JOBS":"4","CARGO_PROFILE_RELEASE_CODEGEN_UNITS":"16","CARGO_PROFILE_RELEASE_DEBUG":"0","CARGO_PROFILE_RELEASE_LTO":"false","CARGO_TARGET_DIR":"/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-target","CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER":"/Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-tools/linux-linker","RUSTFLAGS":"--sysroot /Volumes/PATRIOT/ALD-TAO-lab/hf-winners-evaluation-v1.2-20261013/build/cross-sysroot -C target-cpu=x86-64"},"evidence_ceiling":"Mac callback tests and Linux CPU observer-neutral smoke; CUDA layout only, device observation not run","exit_code":0,"gpu_device_execution":"not_run","runtime_receipt_path":"build/receipts/star-linux-runtime-receipt.json","runtime_receipt_sha256":"d19f5045d769e68458d6b4f4599981f3ffdda1baa28052ca4dde15513f880c04","schema":"ald-star-observer-linux-build-v1","source":{"patch_sha256":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","source_inventory_sha256":"b65738094ba8b06513586348f60b8a93f8bdba280774ca96c18fac5401e66c8a","source_receipt_sha256":"36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93","upstream_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"},"source_receipt_path":"build/receipts/star-source-receipt.json","source_receipt_sha256":"dbe7c216a3b4bb581ff6384a1a0db228542237f7e7a1c66e259e9f3f1d570959","star_delta_source_evidence":{"based_on_source_receipt_sha256":"33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34","changed_source_files":[{"baseline_sha256":"7e94398fdae67500735a7be1ba6de6c576ed021d9c96011fd20ef0a3f3d3f3d7","path":"src/bin/altd-sim/args.rs","sha256":"877157de1e635235c12bd5f2645c6856bdb01b119d433ff0a987767a302e2490"},{"baseline_sha256":"3e679871a0978b2d68ce7cbb520584f1951adc8a0b584145966e58b6ca20e933","path":"src/bin/altd-sim/main.rs","sha256":"2b3d88f6ff030fa9b5f006f679e04345e951b20dd824889976cc5c29546eb33e"},{"baseline_sha256":"1edaa2580112e4d0645743ab14bba16cf780d75800cd1036bd19f940bfc36680","path":"src/bin/altd-sim/scratch/mod.rs","sha256":"615bb5b05513bb8425dafc96aad50ac5dd9c1384ee89b9d099b8439a3916463f"},{"baseline_sha256":"0cab956fb0d63a20d6b62e01aadeb3d6592f4f12d601df095f41840a89329c55","path":"src/bin/altd-sim/scratch/runner.rs","sha256":"776c677d1de4f3416485a82c24ee64a414a5a165c30ee5b353f80f1e875f8a7c"},{"baseline_sha256":"12475cb92731f5e9c311a9ca62c4e17f0dfde17bc46efe9f827f6ba4a91ebbac","path":"src/training/runner/gpu.rs","sha256":"0588cd4487729707ac19b8294a20b9f44dd505005842f54e7a12b21b7c48ece3"},{"baseline_sha256":"7bae3c881f2f2b086f1007734008056fcedab3316626cd9a5b13909daec02890","path":"src/training/runner/schedule.rs","sha256":"9021bd9c70024eaa205f8c75219dded02ce7b5c3ac8f2cf064abdf41f47db65c"},{"baseline_sha256":"26fe931db6894bd72813136daf77fe0991307d1d715ffceea5f0e398e3e05b86","path":"src/training/runner/tests.rs","sha256":"c20b5d235aeee573479ae29dfdb1df0cab07d44148fa6d49aa96e84942b49e5d"},{"baseline_sha256":"df3afd6faca830679afbefa4520b6947126bc972895df76504df6d42362ede28","path":"tests/winner_analysis.rs","sha256":"8f6915059e02ff9935faefbd6e876283fc7c60bb4a5384385433a4e7e7fc6624"},{"baseline_sha256":null,"path":"src/bin/altd-sim/scratch/challenge.rs","sha256":"7d612882684a767bb1fdc8333bc1c407e2e1b8059493837eeef0c9843c447d01"}],"cuda_kernel_and_abi_changed":false,"cuda_kernel_and_abi_checks":{"all_match_b90":true,"source_members":25},"gpu_device_execution":"not_run","mac_binary":{"path":"build/mac-target/debug/altd-sim","sha256":"793eedfff6d3a355378120767c562409dcf27de75a56f174296f1f79d82d051d"},"mac_tests":{"binary_unit":8,"evidence_sha256":{"build/receipts/star-mac-cutoff-tests.log":"8bd7668cba009c80a8fb20de45108c4df56a49d8430a7d91fb7f22f42ae920d9","build/receipts/star-mac-tests-final.log":"4698b5af2b7a7b042bf0c485ca33568a474bcc47477d34b57b425659db3c1d58"},"integration":3,"library_cutoff":1,"paths":["build/receipts/star-mac-tests-final.log","build/receipts/star-mac-cutoff-tests.log"],"status":"passed"},"ordinary_training_changed":false,"patch_path":"build/receipts/star-native.patch","patch_sha256":"b37ea9586f3e1915153bd85b6c5c7fc70d025fba684db158914ff1137e08d3dd","schema":"ald-star-source-v1","source_inventory_sha256":"b65738094ba8b06513586348f60b8a93f8bdba280774ca96c18fac5401e66c8a","upstream_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"},"status":"passed"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
native/source-receipt.json
CHANGED
|
@@ -1,50 +1 @@
|
|
| 1 |
-
{
|
| 2 |
-
"schema": "ald-native-analysis-source-v1",
|
| 3 |
-
"upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0",
|
| 4 |
-
"source_inventory_sha256": "3311905f217e08a927ca73d71db6687aaaf68f01cc2758e4028b92fa703bcebe",
|
| 5 |
-
"patch_sha256": "56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63",
|
| 6 |
-
"changed_source_files": [
|
| 7 |
-
{
|
| 8 |
-
"path": "src/bin/altd-sim/args.rs",
|
| 9 |
-
"baseline_sha256": "9f833f026d1f71ef76fe05d948464c619c76c15e43cedc11b5f510bfc337bda2",
|
| 10 |
-
"sha256": "7e94398fdae67500735a7be1ba6de6c576ed021d9c96011fd20ef0a3f3d3f3d7"
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
"path": "src/bin/altd-sim/main.rs",
|
| 14 |
-
"baseline_sha256": "841956a5a1a927b1a31369ccbd9aa18161b6328745a5a3ca4f02bdcfedd7230c",
|
| 15 |
-
"sha256": "3e679871a0978b2d68ce7cbb520584f1951adc8a0b584145966e58b6ca20e933"
|
| 16 |
-
},
|
| 17 |
-
{
|
| 18 |
-
"path": "src/bin/altd-sim/scratch/analysis.rs",
|
| 19 |
-
"baseline_sha256": null,
|
| 20 |
-
"sha256": "294c5c738de7fd573a3f3b89e4bdc8b35ba1c992b00959caa0bc75f19e41c3ba"
|
| 21 |
-
},
|
| 22 |
-
{
|
| 23 |
-
"path": "src/bin/altd-sim/scratch/mod.rs",
|
| 24 |
-
"baseline_sha256": "524e9033a1fb29ac7b4d689a21fede12b7bb77a772c078fce4c0bf4763568bbe",
|
| 25 |
-
"sha256": "1edaa2580112e4d0645743ab14bba16cf780d75800cd1036bd19f940bfc36680"
|
| 26 |
-
},
|
| 27 |
-
{
|
| 28 |
-
"path": "src/bin/altd-sim/scratch/runner.rs",
|
| 29 |
-
"baseline_sha256": "20a78bd99dc6bd69cc9314ba2d531dac13b5d9f21fb6ca676d34c0fa472a46c0",
|
| 30 |
-
"sha256": "0cab956fb0d63a20d6b62e01aadeb3d6592f4f12d601df095f41840a89329c55"
|
| 31 |
-
},
|
| 32 |
-
{
|
| 33 |
-
"path": "src/training/evolution.rs",
|
| 34 |
-
"baseline_sha256": "d050036c9346561411feefc6c94d598cb0f3ee69e85a8960cf911d60b2129a61",
|
| 35 |
-
"sha256": "488729530a2562295b44835716c7a4e055543614d0f262da682712cdac084460"
|
| 36 |
-
},
|
| 37 |
-
{
|
| 38 |
-
"path": "tests/winner_analysis.rs",
|
| 39 |
-
"baseline_sha256": null,
|
| 40 |
-
"sha256": "df3afd6faca830679afbefa4520b6947126bc972895df76504df6d42362ede28"
|
| 41 |
-
}
|
| 42 |
-
],
|
| 43 |
-
"gpu_abi_source_unchanged": true,
|
| 44 |
-
"physics_source_unchanged": true,
|
| 45 |
-
"retained_cuda_library_sha256": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca",
|
| 46 |
-
"local_patch_semantics": "Host-only observations; unchanged simulation/evolution kernels. Descriptor helper reads native rewards without RNG; scratch frozen path resumes exact single policy and disables checkpoint turnover. Training state may be reused; evaluation uses fresh physical state.",
|
| 47 |
-
"mac_compiler": "rustc 1.95.0 (59807616e 2026-04-14)",
|
| 48 |
-
"mac_build_command": "cargo +1.95.0 --locked --offline --no-default-features --features cli; separate build/mac-target",
|
| 49 |
-
"linux_status": "passed"
|
| 50 |
-
}
|
|
|
|
| 1 |
+
{"changed_source_files":[{"baseline_sha256":"9f833f026d1f71ef76fe05d948464c619c76c15e43cedc11b5f510bfc337bda2","path":"src/bin/altd-sim/args.rs","sha256":"877157de1e635235c12bd5f2645c6856bdb01b119d433ff0a987767a302e2490"},{"baseline_sha256":"841956a5a1a927b1a31369ccbd9aa18161b6328745a5a3ca4f02bdcfedd7230c","path":"src/bin/altd-sim/main.rs","sha256":"2b3d88f6ff030fa9b5f006f679e04345e951b20dd824889976cc5c29546eb33e"},{"baseline_sha256":null,"path":"src/bin/altd-sim/scratch/analysis.rs","sha256":"294c5c738de7fd573a3f3b89e4bdc8b35ba1c992b00959caa0bc75f19e41c3ba"},{"baseline_sha256":null,"path":"src/bin/altd-sim/scratch/challenge.rs","sha256":"7d612882684a767bb1fdc8333bc1c407e2e1b8059493837eeef0c9843c447d01"},{"baseline_sha256":"524e9033a1fb29ac7b4d689a21fede12b7bb77a772c078fce4c0bf4763568bbe","path":"src/bin/altd-sim/scratch/mod.rs","sha256":"615bb5b05513bb8425dafc96aad50ac5dd9c1384ee89b9d099b8439a3916463f"},{"baseline_sha256":"20a78bd99dc6bd69cc9314ba2d531dac13b5d9f21fb6ca676d34c0fa472a46c0","path":"src/bin/altd-sim/scratch/runner.rs","sha256":"776c677d1de4f3416485a82c24ee64a414a5a165c30ee5b353f80f1e875f8a7c"},{"baseline_sha256":"d050036c9346561411feefc6c94d598cb0f3ee69e85a8960cf911d60b2129a61","path":"src/training/evolution.rs","sha256":"488729530a2562295b44835716c7a4e055543614d0f262da682712cdac084460"},{"baseline_sha256":"12475cb92731f5e9c311a9ca62c4e17f0dfde17bc46efe9f827f6ba4a91ebbac","path":"src/training/runner/gpu.rs","sha256":"0588cd4487729707ac19b8294a20b9f44dd505005842f54e7a12b21b7c48ece3"},{"baseline_sha256":"7bae3c881f2f2b086f1007734008056fcedab3316626cd9a5b13909daec02890","path":"src/training/runner/schedule.rs","sha256":"9021bd9c70024eaa205f8c75219dded02ce7b5c3ac8f2cf064abdf41f47db65c"},{"baseline_sha256":"26fe931db6894bd72813136daf77fe0991307d1d715ffceea5f0e398e3e05b86","path":"src/training/runner/tests.rs","sha256":"c20b5d235aeee573479ae29dfdb1df0cab07d44148fa6d49aa96e84942b49e5d"},{"baseline_sha256":null,"path":"tests/winner_analysis.rs","sha256":"8f6915059e02ff9935faefbd6e876283fc7c60bb4a5384385433a4e7e7fc6624"}],"cuda_kernel_and_abi_changed":false,"linux_status":"passed","patch_sha256":"42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8","schema":"ald-native-analysis-source-v1","scope":"b90 plus winners/frozen analysis and fixed-policy statistics callback observation","source_inventory_sha256":"b65738094ba8b06513586348f60b8a93f8bdba280774ca96c18fac5401e66c8a","star_delta_receipt_sha256":"dbe7c216a3b4bb581ff6384a1a0db228542237f7e7a1c66e259e9f3f1d570959","upstream_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
portable_bundle.py
CHANGED
|
@@ -24,7 +24,7 @@ MAX_DESCRIPTOR_ROWS = 128000
|
|
| 24 |
ANALYSIS_JSON_LIMIT = 512 * 1024
|
| 25 |
ANALYSIS_WINNER_LIMIT = 1024 * 1024
|
| 26 |
ANALYSIS_FILES = {
|
| 27 |
-
"analysis.json", "descriptors.jsonl", "evaluation.json",
|
| 28 |
"winners/fitness.evolution", "winners/fastest_lap.evolution", "winners/clean_laps.evolution",
|
| 29 |
}
|
| 30 |
HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
@@ -74,7 +74,7 @@ class BundleError(ValueError):
|
|
| 74 |
|
| 75 |
|
| 76 |
def _analysis_member_limit(name: str) -> int:
|
| 77 |
-
if name in {"analysis.json", "evaluation.json"}:
|
| 78 |
return ANALYSIS_JSON_LIMIT
|
| 79 |
if name.startswith("winners/") and name.endswith(".evolution"):
|
| 80 |
return ANALYSIS_WINNER_LIMIT
|
|
@@ -329,6 +329,20 @@ def load_analysis_archive(raw: bytes) -> tuple[dict[str, Any], dict[str, bytes]]
|
|
| 329 |
if mode == "evaluate" and any(winners[role]["status"] != "unavailable" for role in winners):
|
| 330 |
raise BundleError("evaluation analysis cannot select winners")
|
| 331 |
expected_files = {"analysis.json", "descriptors.jsonl"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
if mode == "train":
|
| 333 |
expected_files |= {f"winners/{role}.evolution" for role in winners if winners[role]["status"] == "available"}
|
| 334 |
else:
|
|
|
|
| 24 |
ANALYSIS_JSON_LIMIT = 512 * 1024
|
| 25 |
ANALYSIS_WINNER_LIMIT = 1024 * 1024
|
| 26 |
ANALYSIS_FILES = {
|
| 27 |
+
"analysis.json", "descriptors.jsonl", "evaluation.json", "stars.json",
|
| 28 |
"winners/fitness.evolution", "winners/fastest_lap.evolution", "winners/clean_laps.evolution",
|
| 29 |
}
|
| 30 |
HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
|
|
| 74 |
|
| 75 |
|
| 76 |
def _analysis_member_limit(name: str) -> int:
|
| 77 |
+
if name in {"analysis.json", "evaluation.json", "stars.json"}:
|
| 78 |
return ANALYSIS_JSON_LIMIT
|
| 79 |
if name.startswith("winners/") and name.endswith(".evolution"):
|
| 80 |
return ANALYSIS_WINNER_LIMIT
|
|
|
|
| 329 |
if mode == "evaluate" and any(winners[role]["status"] != "unavailable" for role in winners):
|
| 330 |
raise BundleError("evaluation analysis cannot select winners")
|
| 331 |
expected_files = {"analysis.json", "descriptors.jsonl"}
|
| 332 |
+
if "stars.json" in inner:
|
| 333 |
+
from star_check import validate_star_check
|
| 334 |
+
star_check = _parse_json(inner["stars.json"], limit=ANALYSIS_JSON_LIMIT, label="star check")
|
| 335 |
+
primary_sha = (winners["fitness"]["artifact"]["sha256"] if mode == "train"
|
| 336 |
+
else conditions["network_sha256"])
|
| 337 |
+
try:
|
| 338 |
+
validate_star_check(star_check, network_sha256=primary_sha,
|
| 339 |
+
track_sha256=conditions["track_sha256"],
|
| 340 |
+
vehicle_type=conditions["vehicle_type"], backend=analysis["execution_backend"])
|
| 341 |
+
if star_check["collision_elimination"] != conditions["collision"]:
|
| 342 |
+
raise ValueError("Star check elimination setting mismatch")
|
| 343 |
+
except (KeyError, TypeError, ValueError) as exc:
|
| 344 |
+
raise BundleError("Star check does not match this result") from exc
|
| 345 |
+
expected_files.add("stars.json")
|
| 346 |
if mode == "train":
|
| 347 |
expected_files |= {f"winners/{role}.evolution" for role in winners if winners[role]["status"] == "available"}
|
| 348 |
else:
|
server.py
CHANGED
|
@@ -157,6 +157,8 @@ def create_app(worker=run_reported,store=None,gpu_worker=None,track_library=None
|
|
| 157 |
if isinstance(bundle_files,dict) and 'analysis_archive.gz' in bundle_files:
|
| 158 |
required.add('analysis-archive.gz')
|
| 159 |
required.update(name for name in bundle_files if name.startswith('analysis/'))
|
|
|
|
|
|
|
| 160 |
key_match=AUTOSAVE_APP_KEY.fullmatch(result.get('key',''))
|
| 161 |
if key_match is None:
|
| 162 |
raise HTTPException(502,'Storage operation failed')
|
|
|
|
| 157 |
if isinstance(bundle_files,dict) and 'analysis_archive.gz' in bundle_files:
|
| 158 |
required.add('analysis-archive.gz')
|
| 159 |
required.update(name for name in bundle_files if name.startswith('analysis/'))
|
| 160 |
+
if 'analysis/stars.json' in bundle_files:
|
| 161 |
+
required.add('star-check.txt')
|
| 162 |
key_match=AUTOSAVE_APP_KEY.fullmatch(result.get('key',''))
|
| 163 |
if key_match is None:
|
| 164 |
raise HTTPException(502,'Storage operation failed')
|
star_check.py
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Ephemeral challenge-condition check for one fixed network replay.
|
| 2 |
+
|
| 3 |
+
This predicts conditions attached to a supplied hosted track. It never grants
|
| 4 |
+
or persists original-game stars, and never scores an evolutionary population.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import math
|
| 9 |
+
import re
|
| 10 |
+
from collections.abc import Mapping
|
| 11 |
+
|
| 12 |
+
MAX_TICKS = 18_000
|
| 13 |
+
MAX_ROWS = 18_001
|
| 14 |
+
MAX_CHALLENGES = 64
|
| 15 |
+
EPSILON = 1e-5
|
| 16 |
+
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
|
| 17 |
+
SUPPORTED = {
|
| 18 |
+
"LapChallenge", "LapInTimeChallenge", "AnyLapInTimeChallenge",
|
| 19 |
+
"ScoreChallenge", "ScoreInTimeChallenge", "NegativeLapChallenge",
|
| 20 |
+
"NegativeLapInTimeChallenge", "NegativeScoreChallenge",
|
| 21 |
+
"LimitedNeuronsLapChallenge", "ScoreWithMoreNeuronsThanChallenge",
|
| 22 |
+
"LapWithMoreNeuronsThanChallenge",
|
| 23 |
+
"LimitedSensorsLapChallenge", "LapWithVehicleChallenge",
|
| 24 |
+
"EliminationEnabledLapChallenge", "EliminationDisabledLapChallenge",
|
| 25 |
+
"EliminationEnabledScoreChallenge", "EliminationDisabledScoreChallenge",
|
| 26 |
+
"DriftInTimeChallenge", "TopSpeedInTimeChallenge", "SlowLapChallenge",
|
| 27 |
+
"CollisionLapChallenge",
|
| 28 |
+
}
|
| 29 |
+
POPULATION_OR_HISTORY = {
|
| 30 |
+
"RaceChallenge", "SimultaneousPositiveNegativeLapChallenge",
|
| 31 |
+
"NoEliminationInTimeChallenge", "LimitedAgentsCountLapChallenge",
|
| 32 |
+
"LimitedAgentsCountScoreChallenge", "LimitedEliminationCountLapChallenge",
|
| 33 |
+
"LimitedEliminationCountScoreChallenge", "LapInGenerationsChallenge",
|
| 34 |
+
"ScoreInGenerationsChallenge",
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
# These are SHA-256 hashes of exact compressed hosted replica track files. The
|
| 38 |
+
# matching captured game tracks use opponent races, while hosted replicas carry
|
| 39 |
+
# placeholder 1/2/3 lap challenges. Never report those placeholders as stars.
|
| 40 |
+
_ORIGINAL_RACE_TRACKS = {
|
| 41 |
+
"14b52a7cbbf7de5606c772c74730c0e8776f39ed72955fef5ae1f7c2d8ccef77": (
|
| 42 |
+
("52746c9d-bedb-4679-8de1-1339478b250a", "R01 Easy"),
|
| 43 |
+
("b3389d76-a6aa-41b9-a0c3-af4ab7c45d0a", "R01 Medium"),
|
| 44 |
+
("5a5a9ce3-60c6-4a99-a64a-e3789ff1af4a", "R01 Expert"),
|
| 45 |
+
),
|
| 46 |
+
"653286b00f2f04450e698d8b8e9d4ecb268064b323b182d2de9db7d017b22238": (
|
| 47 |
+
("4c8ea415-592d-4825-b066-041eaed6401b", "R02 Easy"),
|
| 48 |
+
("da2851f3-6cc8-4c50-b84a-e291ca2ae1fe", "R02 Medium"),
|
| 49 |
+
("fbee8c65-d248-4e23-a021-e89e6314012d", "R02 Expert"),
|
| 50 |
+
),
|
| 51 |
+
"0e1b42ce51ae91a8702e173013a090d00145e518bb47d7a659cb1d1fdff011b5": (
|
| 52 |
+
("bcaf746b-9131-4c41-aea5-319ccde5784d", "R03 Easy"),
|
| 53 |
+
("21b8fabb-c77d-4f74-8e84-7b70521459c4", "R03 Medium"),
|
| 54 |
+
("5b4dfbef-2bbb-4bd2-9e37-23ccc8b4bac1", "R03 Expert"),
|
| 55 |
+
),
|
| 56 |
+
"d7b89d64e118506e6cec259057886d9267ca4c6fab570e7ba0d6641e8f822f11": (
|
| 57 |
+
("5ddafd37-573c-4494-a592-744e5b77d0d3", "R04 Easy"),
|
| 58 |
+
("68f6356e-5b31-40b1-842e-ed2e476c4467", "R04 Medium"),
|
| 59 |
+
("2610f9d7-503a-490c-ae27-99f5b0b4d00f", "R04 Expert"),
|
| 60 |
+
),
|
| 61 |
+
"dde36ad5f627bce65d45a96d46a68497f8d446f6011c9df19ee51ce71375cba8": (
|
| 62 |
+
("ab8c27e8-8396-4a72-b129-2b9befb436de", "R05 Easy"),
|
| 63 |
+
("6711b983-4b76-40af-bff1-69a3ce41878c", "R05 Medium"),
|
| 64 |
+
("350b58fa-d4dd-4fff-91b2-8dae87307997", "R05 Expert"),
|
| 65 |
+
),
|
| 66 |
+
"9de8d912a59001ea13baa7f1b4f92884b8cddf91978f31e19f3237975713f739": (
|
| 67 |
+
("79b8188b-ea6b-446f-b1c4-3ff2c507b563", "R06 Formula"),
|
| 68 |
+
("af069dc8-d9a2-430d-90f2-d2687f7cbf78", "R06 Rally"),
|
| 69 |
+
("9d26d9f4-6255-4a4d-a65a-c1849b57ed6f", "R06 Snowmobile"),
|
| 70 |
+
),
|
| 71 |
+
"8c6764f3accb1a8372d910d57f4f681572dd40c590c4a3d03517379bb360fcd7": (
|
| 72 |
+
("0fa65261-2c31-4117-bcc1-b4e2b223e9ba", "R07 Easy"),
|
| 73 |
+
("22bf973d-1fee-465b-b7f8-6b4940f30ff4", "R07 Medium"),
|
| 74 |
+
("90305aff-b44b-49ab-9e3d-ff4d054f7c21", "R07 Expert"),
|
| 75 |
+
),
|
| 76 |
+
"6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef": (
|
| 77 |
+
("641bb72e-48aa-4d31-8ad3-d057a2e22406", "R08 Raging Bison"),
|
| 78 |
+
("18f648c4-b239-496e-89db-f0cabc8e54b6", "R08 Ferret"),
|
| 79 |
+
("4a832686-e433-4411-8f7f-4be20d693188", "R08 Silverfish"),
|
| 80 |
+
),
|
| 81 |
+
"3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9": (
|
| 82 |
+
("4a9ef57b-f720-4110-b943-834052261c72", "R09 301rng"),
|
| 83 |
+
("196f36bb-786a-4a58-b043-3b1446905f8a", "R09 Mars"),
|
| 84 |
+
("2ce49670-7b19-4f10-ac6b-ded1573bf9f9", "R09 null"),
|
| 85 |
+
),
|
| 86 |
+
"a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1": (
|
| 87 |
+
("5e2acbda-7374-4d91-a428-086a18142aa9", "Champion null"),
|
| 88 |
+
("7baae2cc-c483-4323-85e6-ec38fe1929ab", "Champion Mars"),
|
| 89 |
+
("ac17fb21-1a39-4e2d-b0fb-8364455f3eca", "Champion 301rng"),
|
| 90 |
+
),
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _finite(value):
|
| 95 |
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
| 96 |
+
return None
|
| 97 |
+
value = float(value)
|
| 98 |
+
return value if math.isfinite(value) else None
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _integer(value, low=0, high=MAX_TICKS):
|
| 102 |
+
if isinstance(value, bool) or not isinstance(value, int):
|
| 103 |
+
return None
|
| 104 |
+
return value if low <= value <= high else None
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _seconds(value):
|
| 108 |
+
value = _finite(value)
|
| 109 |
+
return value if value is not None and 0.0 <= value <= 300.1 else None
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _time_parameter(value):
|
| 113 |
+
value = _finite(value)
|
| 114 |
+
return value if value is not None and 0.0 <= value <= 999.0 else None
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _has_unobserved_deadline(challenge, horizon):
|
| 118 |
+
for key in ("TimeLimit", "TimeToReach", "MinTime"):
|
| 119 |
+
seconds = _finite(challenge.get(key))
|
| 120 |
+
if seconds is not None and seconds > horizon:
|
| 121 |
+
return True
|
| 122 |
+
return False
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def check_ticks(track, config):
|
| 126 |
+
"""Bound observation ticks by config and any declared challenge deadline."""
|
| 127 |
+
base = _integer(config.get("ticks"), 1, MAX_TICKS) if isinstance(config, Mapping) else None
|
| 128 |
+
ticks = base or 1
|
| 129 |
+
challenges = track.get("Challenges", []) if isinstance(track, Mapping) else []
|
| 130 |
+
if isinstance(challenges, list):
|
| 131 |
+
for challenge in challenges[:MAX_CHALLENGES]:
|
| 132 |
+
if not isinstance(challenge, Mapping):
|
| 133 |
+
continue
|
| 134 |
+
for key in ("TimeLimit", "TimeToReach", "MinTime"):
|
| 135 |
+
seconds = _finite(challenge.get(key))
|
| 136 |
+
if seconds is not None and seconds > 0:
|
| 137 |
+
ticks = MAX_TICKS if seconds >= MAX_TICKS / 60 else max(ticks, math.ceil(seconds * 60.0))
|
| 138 |
+
return max(1, min(MAX_TICKS, ticks))
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _validated_observation_rows(observations, requested_ticks):
|
| 142 |
+
"""Accept only the native 10 Hz tick/time sequence; never truncate it."""
|
| 143 |
+
max_tick = min(MAX_TICKS + 6, requested_ticks + 6)
|
| 144 |
+
if not isinstance(observations, (list, tuple)) or len(observations) > MAX_ROWS:
|
| 145 |
+
return [], False
|
| 146 |
+
rows = []
|
| 147 |
+
previous_tick = None
|
| 148 |
+
for index, row in enumerate(observations):
|
| 149 |
+
if not isinstance(row, Mapping):
|
| 150 |
+
return [], False
|
| 151 |
+
tick = _integer(row.get("tick"), 6, max_tick)
|
| 152 |
+
game_time = _seconds(row.get("game_time"))
|
| 153 |
+
update_count = _integer(row.get("stats_update_count"), 1, MAX_ROWS)
|
| 154 |
+
expected_time = (tick - 6) / 60.0 if tick is not None else None
|
| 155 |
+
if (tick is None or tick % 6 != 0 or game_time is None or update_count != index + 1
|
| 156 |
+
or not math.isclose(game_time, expected_time, rel_tol=0.0, abs_tol=1e-7)
|
| 157 |
+
or previous_tick is not None and tick != previous_tick + 6):
|
| 158 |
+
return [], False
|
| 159 |
+
previous_tick = tick
|
| 160 |
+
rows.append(row)
|
| 161 |
+
return rows, True
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def _shape_metadata(template):
|
| 165 |
+
try:
|
| 166 |
+
shape = template["NeuralNetwork"]["Shape"]
|
| 167 |
+
sensors = template["Sensors"]
|
| 168 |
+
vehicle = template["Type"]
|
| 169 |
+
if (not isinstance(shape, list) or not shape or len(shape) > 12
|
| 170 |
+
or any(_integer(n, 1, 4096) is None for n in shape)):
|
| 171 |
+
raise ValueError
|
| 172 |
+
if not isinstance(sensors, list) or len(sensors) > 128:
|
| 173 |
+
raise ValueError
|
| 174 |
+
if _integer(vehicle, 0, 3) is None:
|
| 175 |
+
raise ValueError
|
| 176 |
+
return sum(shape), sum(1 for sensor in sensors if isinstance(sensor, Mapping) and sensor.get("$type") == "raycast"), vehicle
|
| 177 |
+
except (KeyError, TypeError, ValueError):
|
| 178 |
+
return None, None, None
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _approx_le(a, b):
|
| 182 |
+
return a < b or abs(a - b) < EPSILON
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def _approx_ge(a, b):
|
| 186 |
+
return a > b or abs(a - b) < EPSILON
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _meters_to_score(meters):
|
| 190 |
+
# Captured UnitConstants.MetersToScore: 15 px/m, game score scale 5/384.
|
| 191 |
+
return (meters / (1.0 / 15.0)) * (5.0 / 384.0)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def _threshold(challenge, key):
|
| 195 |
+
value = challenge.get(key)
|
| 196 |
+
return _finite(value)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _evaluate(challenge, row, meta, lap_score, elimination):
|
| 200 |
+
kind = challenge["$type"]
|
| 201 |
+
time = row["game_time"]
|
| 202 |
+
current = row["current_score"]
|
| 203 |
+
best = row["current_best_score"]
|
| 204 |
+
worst = row["worst_score"]
|
| 205 |
+
lap_count = challenge.get("LapCount", 1)
|
| 206 |
+
lap_count = _integer(lap_count, 1, 100)
|
| 207 |
+
target_lap = lap_score * lap_count if lap_count is not None else None
|
| 208 |
+
|
| 209 |
+
if kind in POPULATION_OR_HISTORY:
|
| 210 |
+
return None
|
| 211 |
+
if kind == "RaceChallenge":
|
| 212 |
+
return None
|
| 213 |
+
if kind == "LapChallenge":
|
| 214 |
+
return best >= target_lap
|
| 215 |
+
if kind == "LapInTimeChallenge":
|
| 216 |
+
limit = _time_parameter(challenge.get("TimeToReach"))
|
| 217 |
+
return None if limit is None else best >= target_lap and _approx_le(time, limit)
|
| 218 |
+
if kind == "AnyLapInTimeChallenge":
|
| 219 |
+
lap = row.get("best_lap_seconds")
|
| 220 |
+
limit = _time_parameter(challenge.get("TimeToReach"))
|
| 221 |
+
return None if limit is None else (False if lap is None else _approx_le(lap, limit))
|
| 222 |
+
if kind in ("ScoreChallenge", "ScoreInTimeChallenge", "EliminationEnabledScoreChallenge", "EliminationDisabledScoreChallenge"):
|
| 223 |
+
meters = _threshold(challenge, "MetersToReach")
|
| 224 |
+
if meters is None:
|
| 225 |
+
return None
|
| 226 |
+
reached = _approx_ge(best, _meters_to_score(meters))
|
| 227 |
+
if kind == "ScoreInTimeChallenge":
|
| 228 |
+
# Unlike lap-in-time, source ScoreInTime uses ordinary <=.
|
| 229 |
+
limit = _time_parameter(challenge.get("TimeToReach"))
|
| 230 |
+
return None if limit is None else reached and time <= limit
|
| 231 |
+
if kind == "EliminationEnabledScoreChallenge":
|
| 232 |
+
return reached and elimination is True
|
| 233 |
+
if kind == "EliminationDisabledScoreChallenge":
|
| 234 |
+
return reached and elimination is False
|
| 235 |
+
return reached
|
| 236 |
+
if kind == "NegativeLapChallenge":
|
| 237 |
+
return worst <= -target_lap
|
| 238 |
+
if kind == "NegativeLapInTimeChallenge":
|
| 239 |
+
limit = _time_parameter(challenge.get("TimeToReach"))
|
| 240 |
+
return None if limit is None else worst <= -target_lap and _approx_le(time, limit)
|
| 241 |
+
if kind == "NegativeScoreChallenge":
|
| 242 |
+
meters = _threshold(challenge, "MetersToReach")
|
| 243 |
+
if meters is None:
|
| 244 |
+
return None
|
| 245 |
+
return worst < _meters_to_score(meters) and abs(worst - _meters_to_score(meters)) >= EPSILON
|
| 246 |
+
if kind == "LimitedNeuronsLapChallenge":
|
| 247 |
+
limit = _integer(challenge.get("MaxNeurons"), 0, 100000)
|
| 248 |
+
return None if limit is None or meta["neurons"] is None else best >= target_lap and meta["neurons"] <= limit
|
| 249 |
+
if kind == "LapWithMoreNeuronsThanChallenge":
|
| 250 |
+
limit = _integer(challenge.get("MinNeurons"), 0, 100000)
|
| 251 |
+
return None if limit is None or meta["neurons"] is None else best >= target_lap and meta["neurons"] >= limit
|
| 252 |
+
if kind == "ScoreWithMoreNeuronsThanChallenge":
|
| 253 |
+
limit = _integer(challenge.get("MinNeurons"), 0, 100000)
|
| 254 |
+
meters = _threshold(challenge, "MetersToReach")
|
| 255 |
+
return None if limit is None or meters is None or meta["neurons"] is None else _approx_ge(best, _meters_to_score(meters)) and meta["neurons"] >= limit
|
| 256 |
+
if kind == "LimitedSensorsLapChallenge":
|
| 257 |
+
limit = _integer(challenge.get("MaxSensors"), 0, 128)
|
| 258 |
+
return None if limit is None or meta["vision_sensors"] is None else best >= target_lap and meta["vision_sensors"] <= limit
|
| 259 |
+
if kind == "LapWithVehicleChallenge":
|
| 260 |
+
vehicle = _integer(challenge.get("VehicleType"), 0, 3)
|
| 261 |
+
return None if vehicle is None or meta["vehicle"] is None else best >= target_lap and meta["vehicle"] == vehicle
|
| 262 |
+
if kind == "EliminationEnabledLapChallenge":
|
| 263 |
+
return None if elimination is None else best >= target_lap and elimination
|
| 264 |
+
if kind == "EliminationDisabledLapChallenge":
|
| 265 |
+
return None if elimination is None else best >= target_lap and not elimination
|
| 266 |
+
if kind == "SlowLapChallenge":
|
| 267 |
+
minimum = _time_parameter(challenge.get("MinTime"))
|
| 268 |
+
return None if minimum is None else best >= target_lap and _approx_ge(time, minimum)
|
| 269 |
+
if kind == "DriftInTimeChallenge":
|
| 270 |
+
min_drift = _time_parameter(challenge.get("MinDriftTime"))
|
| 271 |
+
limit = _time_parameter(challenge.get("TimeLimit"))
|
| 272 |
+
drift = row.get("drift_cumulative")
|
| 273 |
+
if min_drift is None or limit is None or drift is None:
|
| 274 |
+
return None
|
| 275 |
+
return _approx_le(time, limit) and drift >= min_drift
|
| 276 |
+
if kind == "TopSpeedInTimeChallenge":
|
| 277 |
+
threshold = _threshold(challenge, "TopSpeedKph")
|
| 278 |
+
limit = _time_parameter(challenge.get("TimeLimit"))
|
| 279 |
+
speed = row.get("speed_kph")
|
| 280 |
+
if threshold is None or limit is None or speed is None:
|
| 281 |
+
return None
|
| 282 |
+
return _approx_le(time, limit) and speed > threshold
|
| 283 |
+
if kind == "CollisionLapChallenge":
|
| 284 |
+
collision_limit = _integer(challenge.get("MinCollisions"), 1, 999)
|
| 285 |
+
collisions = _integer(row.get("collision_count"), 0, 4_000_000)
|
| 286 |
+
if collision_limit is None or collisions is None:
|
| 287 |
+
return None
|
| 288 |
+
# Source: candidates stay within target + 5 score units, must cross
|
| 289 |
+
# strictly beyond target, and collision count must be strictly greater.
|
| 290 |
+
return current <= target_lap + 5.0 and current > target_lap and collisions > collision_limit
|
| 291 |
+
return None
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def _track_entries(track):
|
| 295 |
+
challenges = track.get("Challenges", []) if isinstance(track, Mapping) else []
|
| 296 |
+
if not isinstance(challenges, list) or len(challenges) > MAX_CHALLENGES:
|
| 297 |
+
return []
|
| 298 |
+
entries = []
|
| 299 |
+
used_ids = set()
|
| 300 |
+
for index, challenge in enumerate(challenges):
|
| 301 |
+
if not isinstance(challenge, Mapping):
|
| 302 |
+
challenge = {"$type": "UnknownChallenge", "Text": "Invalid challenge definition"}
|
| 303 |
+
else:
|
| 304 |
+
challenge = dict(challenge)
|
| 305 |
+
cid = challenge.get("Id")
|
| 306 |
+
if not isinstance(cid, str) or not cid or len(cid) > 128 or cid in used_ids:
|
| 307 |
+
cid = f"challenge-{index + 1}"
|
| 308 |
+
suffix = 1
|
| 309 |
+
while cid in used_ids:
|
| 310 |
+
suffix += 1
|
| 311 |
+
cid = f"challenge-{index + 1}-{suffix}"
|
| 312 |
+
used_ids.add(cid)
|
| 313 |
+
challenge["Id"] = cid
|
| 314 |
+
entries.append(challenge)
|
| 315 |
+
return entries
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def needs_observations(track, track_sha256):
|
| 319 |
+
"""Whether at least one provided challenge is eligible for a single-network pass."""
|
| 320 |
+
if not isinstance(track_sha256, str) or not SHA256_RE.fullmatch(track_sha256):
|
| 321 |
+
return False
|
| 322 |
+
if not _track_entries(track) or track_sha256 in _ORIGINAL_RACE_TRACKS:
|
| 323 |
+
return False
|
| 324 |
+
return any(challenge.get("$type") in SUPPORTED for challenge in _track_entries(track))
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def resolve_check(track, template, config, observations, *, network_sha256, track_sha256, backend, error=None):
|
| 328 |
+
"""Resolve the supplied track's conditions against one fixed network replay."""
|
| 329 |
+
if not isinstance(network_sha256, str) or not SHA256_RE.fullmatch(network_sha256):
|
| 330 |
+
raise ValueError("network_sha256 must be lowercase SHA-256")
|
| 331 |
+
if not isinstance(track_sha256, str) or not SHA256_RE.fullmatch(track_sha256):
|
| 332 |
+
raise ValueError("track_sha256 must be lowercase SHA-256")
|
| 333 |
+
if backend not in ("cpu", "cuda"):
|
| 334 |
+
raise ValueError("backend must be cpu or cuda")
|
| 335 |
+
if not isinstance(track, Mapping) or not isinstance(config, Mapping):
|
| 336 |
+
raise ValueError("track and config must be objects")
|
| 337 |
+
source_challenges = track.get("Challenges", [])
|
| 338 |
+
if not isinstance(source_challenges, list) or len(source_challenges) > MAX_CHALLENGES:
|
| 339 |
+
raise ValueError("track challenge list is malformed or exceeds its bound")
|
| 340 |
+
ticks = check_ticks(track, config)
|
| 341 |
+
neurons, vision_sensors, vehicle = _shape_metadata(template)
|
| 342 |
+
meta = {"neurons": neurons, "vision_sensors": vision_sensors, "vehicle": vehicle}
|
| 343 |
+
elimination = config.get("collision") if isinstance(config.get("collision"), bool) else False
|
| 344 |
+
source_races = _ORIGINAL_RACE_TRACKS.get(track_sha256)
|
| 345 |
+
source_basis = ("Original built-in race challenge definitions identified by exact hosted track digest; "
|
| 346 |
+
"opponent race assets are absent, so this is not an original-game award check." if source_races else
|
| 347 |
+
"Track-defined conditions checked against one fresh replay of the current network; this is a prediction, not a game award.")
|
| 348 |
+
entries = _track_entries(track)
|
| 349 |
+
if source_races:
|
| 350 |
+
entries = [{"Id": cid, "$type": "RaceChallenge", "Text": f"Race against {opponent}"} for cid, opponent in source_races]
|
| 351 |
+
result_rows = []
|
| 352 |
+
if error is not None:
|
| 353 |
+
unavailable_reason = "Current-network observation is unavailable."
|
| 354 |
+
else:
|
| 355 |
+
unavailable_reason = "The bounded current-network observation was not available."
|
| 356 |
+
raw_rows, rows_valid = _validated_observation_rows(observations, ticks) if error is None else ([], True)
|
| 357 |
+
horizon = max((_finite(row.get("game_time")) for row in raw_rows), default=0.0)
|
| 358 |
+
for challenge in entries:
|
| 359 |
+
kind = challenge.get("$type") if isinstance(challenge.get("$type"), str) else "UnknownChallenge"
|
| 360 |
+
text = challenge.get("Text") if isinstance(challenge.get("Text"), str) else kind
|
| 361 |
+
text = " ".join(text.split())[:256]
|
| 362 |
+
item = {"id": str(challenge.get("Id", "unknown"))[:128], "type": kind[:96], "text": text,
|
| 363 |
+
"status": "not_checked", "reason": None, "completion_seconds": None}
|
| 364 |
+
if error is not None:
|
| 365 |
+
item["reason"] = unavailable_reason
|
| 366 |
+
result_rows.append(item)
|
| 367 |
+
continue
|
| 368 |
+
if source_races or kind == "RaceChallenge":
|
| 369 |
+
item["reason"] = "Opponent race results and controller assets are not available."
|
| 370 |
+
result_rows.append(item)
|
| 371 |
+
continue
|
| 372 |
+
if kind in POPULATION_OR_HISTORY:
|
| 373 |
+
item["reason"] = "This condition depends on population or training history, which the current-network check does not infer."
|
| 374 |
+
result_rows.append(item)
|
| 375 |
+
continue
|
| 376 |
+
if kind not in SUPPORTED:
|
| 377 |
+
item["reason"] = "This challenge type is not supported by the current-network check."
|
| 378 |
+
result_rows.append(item)
|
| 379 |
+
continue
|
| 380 |
+
if not rows_valid:
|
| 381 |
+
item["reason"] = "Observation sequence is incomplete or invalid."
|
| 382 |
+
result_rows.append(item)
|
| 383 |
+
continue
|
| 384 |
+
if not raw_rows:
|
| 385 |
+
item["reason"] = unavailable_reason
|
| 386 |
+
result_rows.append(item)
|
| 387 |
+
continue
|
| 388 |
+
lap_score = _finite(track.get("LapScore")) if isinstance(track, Mapping) else None
|
| 389 |
+
if lap_score is None or lap_score <= 0:
|
| 390 |
+
item["reason"] = "Track lap threshold is missing or invalid."
|
| 391 |
+
result_rows.append(item)
|
| 392 |
+
continue
|
| 393 |
+
missing = False
|
| 394 |
+
malformed = False
|
| 395 |
+
completion = None
|
| 396 |
+
running_best = 0.0
|
| 397 |
+
collision_impossible = False
|
| 398 |
+
for row in raw_rows:
|
| 399 |
+
time = _seconds(row.get("game_time"))
|
| 400 |
+
tick = _integer(row.get("tick"), 0, MAX_TICKS + 6)
|
| 401 |
+
current_score = _finite(row.get("current_score"))
|
| 402 |
+
callback_best = _finite(row.get("current_best_score"))
|
| 403 |
+
worst_score = _finite(row.get("worst_score"))
|
| 404 |
+
if callback_best is not None:
|
| 405 |
+
running_best = max(running_best, callback_best)
|
| 406 |
+
best_score = running_best
|
| 407 |
+
normalized = dict(row)
|
| 408 |
+
normalized.update(game_time=time, current_score=current_score,
|
| 409 |
+
current_best_score=best_score, worst_score=worst_score)
|
| 410 |
+
required = [time is not None, tick is not None, current_score is not None,
|
| 411 |
+
callback_best is not None, best_score is not None, worst_score is not None]
|
| 412 |
+
if kind == "AnyLapInTimeChallenge":
|
| 413 |
+
lap = _finite(row.get("best_lap_seconds"))
|
| 414 |
+
normalized["best_lap_seconds"] = lap if lap is not None and lap > 0 else None
|
| 415 |
+
required.append("best_lap_seconds" in row)
|
| 416 |
+
elif kind == "DriftInTimeChallenge":
|
| 417 |
+
drift = _finite(row.get("drift_cumulative"))
|
| 418 |
+
normalized["drift_cumulative"] = drift if drift is not None and drift >= 0 else None
|
| 419 |
+
required.append(normalized["drift_cumulative"] is not None)
|
| 420 |
+
elif kind == "TopSpeedInTimeChallenge":
|
| 421 |
+
speed = _finite(row.get("speed_kph"))
|
| 422 |
+
normalized["speed_kph"] = speed if speed is not None and speed >= 0 else None
|
| 423 |
+
required.append(normalized["speed_kph"] is not None)
|
| 424 |
+
elif kind == "CollisionLapChallenge":
|
| 425 |
+
collisions = _integer(row.get("collision_count"), 0, 4_000_000)
|
| 426 |
+
normalized["collision_count"] = collisions
|
| 427 |
+
required.append(collisions is not None)
|
| 428 |
+
if not all(required):
|
| 429 |
+
missing = True
|
| 430 |
+
malformed = malformed or tick is None
|
| 431 |
+
break
|
| 432 |
+
try:
|
| 433 |
+
if kind == "CollisionLapChallenge" and current_score > lap_score * challenge.get("LapCount", 1) + 5.0:
|
| 434 |
+
collision_impossible = True
|
| 435 |
+
passed = False if collision_impossible else _evaluate(challenge, normalized, meta, lap_score, elimination)
|
| 436 |
+
except (KeyError, TypeError, ValueError, OverflowError):
|
| 437 |
+
passed = None
|
| 438 |
+
if passed is None:
|
| 439 |
+
missing = True
|
| 440 |
+
continue
|
| 441 |
+
if passed:
|
| 442 |
+
completion = time
|
| 443 |
+
break
|
| 444 |
+
if completion is not None:
|
| 445 |
+
item["status"] = "met"
|
| 446 |
+
item["completion_seconds"] = completion
|
| 447 |
+
elif missing:
|
| 448 |
+
item["reason"] = ("Observation fields are incomplete or invalid." if malformed else
|
| 449 |
+
"Required evidence for this condition is unavailable.")
|
| 450 |
+
elif _has_unobserved_deadline(challenge, horizon):
|
| 451 |
+
item["reason"] = "The challenge deadline exceeds the bounded observation horizon."
|
| 452 |
+
else:
|
| 453 |
+
item["status"] = "not_met"
|
| 454 |
+
item["reason"] = "Condition was not met during the bounded current-network replay."
|
| 455 |
+
result_rows.append(item)
|
| 456 |
+
status = "no_challenges" if not result_rows else (
|
| 457 |
+
"unavailable" if error is not None or not rows_valid or not raw_rows and needs_observations(track, track_sha256)
|
| 458 |
+
else "complete")
|
| 459 |
+
if source_races and error is None:
|
| 460 |
+
status = "complete"
|
| 461 |
+
return {"schema": "ald.star-check", "version": 1, "network_sha256": network_sha256,
|
| 462 |
+
"track_sha256": track_sha256, "vehicle_type": vehicle, "execution_backend": backend,
|
| 463 |
+
"status": status, "horizon_seconds": horizon, "collision_elimination": elimination,
|
| 464 |
+
"scope": "current_network", "basis": source_basis, "challenges": result_rows}
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def validate_star_check(value, network_sha256, track_sha256, vehicle_type, backend):
|
| 468 |
+
"""Raise ValueError for malformed or mismatched archive/UI star-check data."""
|
| 469 |
+
if not isinstance(value, Mapping) or set(value) != {
|
| 470 |
+
"schema", "version", "network_sha256", "track_sha256", "vehicle_type",
|
| 471 |
+
"execution_backend", "status", "horizon_seconds", "collision_elimination",
|
| 472 |
+
"scope", "basis", "challenges",
|
| 473 |
+
}:
|
| 474 |
+
raise ValueError("invalid star check keys")
|
| 475 |
+
if value["schema"] != "ald.star-check" or type(value["version"]) is not int or value["version"] != 1:
|
| 476 |
+
raise ValueError("invalid star check schema")
|
| 477 |
+
if value["network_sha256"] != network_sha256 or value["track_sha256"] != track_sha256:
|
| 478 |
+
raise ValueError("star check identity mismatch")
|
| 479 |
+
if not isinstance(network_sha256, str) or not SHA256_RE.fullmatch(network_sha256) or not isinstance(track_sha256, str) or not SHA256_RE.fullmatch(track_sha256):
|
| 480 |
+
raise ValueError("invalid star check digest")
|
| 481 |
+
if value["vehicle_type"] != vehicle_type or value["execution_backend"] != backend:
|
| 482 |
+
raise ValueError("star check execution identity mismatch")
|
| 483 |
+
if _integer(value["vehicle_type"], 0, 3) is None or backend not in ("cpu", "cuda"):
|
| 484 |
+
raise ValueError("invalid star check vehicle or backend")
|
| 485 |
+
horizon = _finite(value["horizon_seconds"])
|
| 486 |
+
if horizon is None or not 0 <= horizon <= 300.0:
|
| 487 |
+
raise ValueError("invalid star check horizon")
|
| 488 |
+
if not isinstance(value["collision_elimination"], bool):
|
| 489 |
+
raise ValueError("invalid star check elimination setting")
|
| 490 |
+
if value["scope"] != "current_network" or not isinstance(value["basis"], str) or not 1 <= len(value["basis"]) <= 1000:
|
| 491 |
+
raise ValueError("invalid star check scope or basis")
|
| 492 |
+
status = value["status"]
|
| 493 |
+
if status not in ("complete", "unavailable", "no_challenges"):
|
| 494 |
+
raise ValueError("invalid star check status")
|
| 495 |
+
challenges = value["challenges"]
|
| 496 |
+
if not isinstance(challenges, list) or len(challenges) > MAX_CHALLENGES:
|
| 497 |
+
raise ValueError("invalid star check challenge list")
|
| 498 |
+
if (status == "no_challenges") != (len(challenges) == 0):
|
| 499 |
+
raise ValueError("star check status/challenge mismatch")
|
| 500 |
+
required = {"id", "type", "text", "status", "reason", "completion_seconds"}
|
| 501 |
+
for challenge in challenges:
|
| 502 |
+
if not isinstance(challenge, Mapping) or set(challenge) != required:
|
| 503 |
+
raise ValueError("invalid star check challenge keys")
|
| 504 |
+
if not isinstance(challenge["id"], str) or not 1 <= len(challenge["id"]) <= 128:
|
| 505 |
+
raise ValueError("invalid star check challenge id")
|
| 506 |
+
if not isinstance(challenge["type"], str) or not 1 <= len(challenge["type"]) <= 96:
|
| 507 |
+
raise ValueError("invalid star check challenge type")
|
| 508 |
+
if not isinstance(challenge["text"], str) or len(challenge["text"]) > 256:
|
| 509 |
+
raise ValueError("invalid star check challenge text")
|
| 510 |
+
state = challenge["status"]
|
| 511 |
+
if state not in ("met", "not_met", "not_checked"):
|
| 512 |
+
raise ValueError("invalid star check challenge status")
|
| 513 |
+
completion = challenge["completion_seconds"]
|
| 514 |
+
if state == "met":
|
| 515 |
+
completion = _finite(completion)
|
| 516 |
+
if completion is None or not 0 <= completion <= horizon or challenge["reason"] is not None:
|
| 517 |
+
raise ValueError("invalid completed star check challenge")
|
| 518 |
+
elif completion is not None or not isinstance(challenge["reason"], str) or not 1 <= len(challenge["reason"]) <= 256:
|
| 519 |
+
raise ValueError("invalid uncompleted star check challenge")
|
| 520 |
+
if status == "unavailable" and state != "not_checked":
|
| 521 |
+
raise ValueError("unavailable star check contains evaluated challenge")
|
| 522 |
+
if len({challenge["id"] for challenge in challenges}) != len(challenges):
|
| 523 |
+
raise ValueError("duplicate star check challenge id")
|
| 524 |
+
return value
|
worker.py
CHANGED
|
@@ -17,6 +17,7 @@ import uuid
|
|
| 17 |
from training_config import validate_config
|
| 18 |
from build_info import build_identity
|
| 19 |
from analysis_result import assemble_analysis
|
|
|
|
| 20 |
|
| 21 |
MODULE_DIR = Path(__file__).resolve().parent
|
| 22 |
# Source checkout is nested; a hosted release is flat under /app. Deployment
|
|
@@ -112,6 +113,16 @@ def network_input(data):
|
|
| 112 |
|
| 113 |
def track_input(data):
|
| 114 |
value=read_json(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
tiles=value['Tiles']
|
| 116 |
if not isinstance(tiles,dict) or not 4<=len(tiles)<=256: raise ValueError('Track tile limit')
|
| 117 |
start=value.get('StartTileConnection')
|
|
@@ -150,6 +161,51 @@ def vehicle_scene_template(scene_template, vehicle_type):
|
|
| 150 |
# directory; uploaded JSON never supplies a template path.
|
| 151 |
return (formula.parent/(name+'_template.json')).resolve()
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_template=None,with_report=False,gpu_library=None,config=None):
|
| 154 |
started=time.perf_counter()
|
| 155 |
config=validate_config(config)
|
|
@@ -205,6 +261,9 @@ def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_templ
|
|
| 205 |
history=[json.loads(line) for line in (out/'log.jsonl').read_text().splitlines() if line.strip()]
|
| 206 |
last=history[-1] if history else {}
|
| 207 |
files,summary,evaluation=assemble_analysis(out,template,result,network_bytes,track_bytes,config,'cuda' if gpu_library is not None else 'cpu',network_input)
|
|
|
|
|
|
|
|
|
|
| 208 |
report={'mode':config['mode'],'analysis_summary':summary,'_analysis_files':files,'build':build_identity(),'state':'completed','elapsed_seconds':time.perf_counter()-started,
|
| 209 |
'generations_completed':len(history),'generations_requested':config['generations'],'population':config['population'],
|
| 210 |
'best_score':last.get('best_score'),'mean_score':last.get('mean_score'),
|
|
|
|
| 17 |
from training_config import validate_config
|
| 18 |
from build_info import build_identity
|
| 19 |
from analysis_result import assemble_analysis
|
| 20 |
+
from star_check import check_ticks, needs_observations, resolve_check
|
| 21 |
|
| 22 |
MODULE_DIR = Path(__file__).resolve().parent
|
| 23 |
# Source checkout is nested; a hosted release is flat under /app. Deployment
|
|
|
|
| 113 |
|
| 114 |
def track_input(data):
|
| 115 |
value=read_json(data)
|
| 116 |
+
challenges=value.get('Challenges',[])
|
| 117 |
+
if not isinstance(challenges,list) or len(challenges)>64:
|
| 118 |
+
raise ValueError('Track supports at most 64 challenge definitions')
|
| 119 |
+
for challenge in challenges:
|
| 120 |
+
if (not isinstance(challenge,dict) or not isinstance(challenge.get('$type'),str)
|
| 121 |
+
or not 1<=len(challenge['$type'])<=96):
|
| 122 |
+
raise ValueError('Invalid track challenge type')
|
| 123 |
+
for key,limit in (('Text',2048),('Id',128)):
|
| 124 |
+
if key in challenge and (not isinstance(challenge[key],str) or len(challenge[key])>limit):
|
| 125 |
+
raise ValueError('Invalid track challenge label')
|
| 126 |
tiles=value['Tiles']
|
| 127 |
if not isinstance(tiles,dict) or not 4<=len(tiles)<=256: raise ValueError('Track tile limit')
|
| 128 |
start=value.get('StartTileConnection')
|
|
|
|
| 161 |
# directory; uploaded JSON never supplies a template path.
|
| 162 |
return (formula.parent/(name+'_template.json')).resolve()
|
| 163 |
|
| 164 |
+
def check_current_network(work, command, env, track, template, config, result, track_bytes, backend):
|
| 165 |
+
"""One independent fixed-weight check; its failure must not lose trained weights."""
|
| 166 |
+
network_sha=hashlib.sha256(result).hexdigest()
|
| 167 |
+
track_sha=hashlib.sha256(track_bytes).hexdigest()
|
| 168 |
+
kwargs=dict(network_sha256=network_sha,track_sha256=track_sha,backend=backend)
|
| 169 |
+
if not needs_observations(track,track_sha):
|
| 170 |
+
return resolve_check(track,template,config,[],**kwargs)
|
| 171 |
+
check_command=list(command)
|
| 172 |
+
def option(name,value):
|
| 173 |
+
check_command[check_command.index(name)+1]=str(value)
|
| 174 |
+
n=template['NeuralNetwork']
|
| 175 |
+
policy=work/'star-policy.json'
|
| 176 |
+
# The template contains the exported final leader after training, or the
|
| 177 |
+
# exact input weights after evaluation. No random population is initialized.
|
| 178 |
+
policy.write_text(json.dumps({'shape':n['Shape'],'weights':n['Weights'],'biases':n['Biases'],
|
| 179 |
+
'inputs':[f'sensor-{i}' for i in range(n['Shape'][0])],
|
| 180 |
+
'outputs':[OUTPUT_NAMES[i] for i in template['Outputs']]},allow_nan=False))
|
| 181 |
+
star_out=work/'star-result'
|
| 182 |
+
for key,value in (('--network',policy),('--init-network',policy),('--out-dir',star_out),
|
| 183 |
+
('--population',1),('--generations',1),('--ticks',check_ticks(track,config)),
|
| 184 |
+
('--mutation-start',0),('--mutation-end',0)):
|
| 185 |
+
option(key,value)
|
| 186 |
+
if '--evaluate-frozen' not in check_command:check_command.append('--evaluate-frozen')
|
| 187 |
+
check_command.append('--challenge-observations')
|
| 188 |
+
try:
|
| 189 |
+
subprocess.run(check_command,cwd=work,env=env,stdout=subprocess.DEVNULL,
|
| 190 |
+
stderr=subprocess.DEVNULL,timeout=25,check=True)
|
| 191 |
+
from analysis_result import bounded_read, parsed
|
| 192 |
+
meta=parsed(bounded_read(star_out/'challenge-observations-meta.json',32*1024))
|
| 193 |
+
rows=bounded_read(star_out/'challenge-observations.jsonl',4*1024*1024)
|
| 194 |
+
observations=[parsed(line) for line in rows.splitlines()]
|
| 195 |
+
if (meta.get('schema')!='ald.challenge-observations' or meta.get('version')!=1
|
| 196 |
+
or meta.get('execution_backend')!=backend or meta.get('mode')!='evaluate'
|
| 197 |
+
or meta.get('frames')!=len(observations) or len(observations)>3001
|
| 198 |
+
or meta.get('requested_ticks')!=check_ticks(track,config)
|
| 199 |
+
or type(meta.get('lap_score')) not in (float,int) or not math.isfinite(meta['lap_score'])
|
| 200 |
+
or meta['lap_score']<=0):
|
| 201 |
+
raise ValueError('Invalid challenge observations')
|
| 202 |
+
check_track=dict(track,LapScore=meta['lap_score'])
|
| 203 |
+
return resolve_check(check_track,template,config,observations,**kwargs)
|
| 204 |
+
except subprocess.TimeoutExpired:
|
| 205 |
+
return resolve_check(track,template,config,[],error='The star check reached its time limit; trained weights are preserved.',**kwargs)
|
| 206 |
+
except (OSError,subprocess.CalledProcessError,ValueError,KeyError,TypeError):
|
| 207 |
+
return resolve_check(track,template,config,[],error='The star check could not obtain complete observations; trained weights are preserved.',**kwargs)
|
| 208 |
+
|
| 209 |
def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_template=None,with_report=False,gpu_library=None,config=None):
|
| 210 |
started=time.perf_counter()
|
| 211 |
config=validate_config(config)
|
|
|
|
| 261 |
history=[json.loads(line) for line in (out/'log.jsonl').read_text().splitlines() if line.strip()]
|
| 262 |
last=history[-1] if history else {}
|
| 263 |
files,summary,evaluation=assemble_analysis(out,template,result,network_bytes,track_bytes,config,'cuda' if gpu_library is not None else 'cpu',network_input)
|
| 264 |
+
stars=check_current_network(work,command,env,track,template,config,result,track_bytes,
|
| 265 |
+
'cuda' if gpu_library is not None else 'cpu')
|
| 266 |
+
files['stars.json']=json.dumps(stars,allow_nan=False,separators=(',',':')).encode()
|
| 267 |
report={'mode':config['mode'],'analysis_summary':summary,'_analysis_files':files,'build':build_identity(),'state':'completed','elapsed_seconds':time.perf_counter()-started,
|
| 268 |
'generations_completed':len(history),'generations_requested':config['generations'],'population':config['population'],
|
| 269 |
'best_score':last.get('best_score'),'mean_score':last.get('mean_score'),
|