TiGa-RCE commited on
Commit
be1eee5
·
verified ·
1 Parent(s): 1d9a272

Add current-policy star checks without campaign progression

Browse files
BINARY_PROVENANCE.json CHANGED
@@ -1 +1 @@
1
- {"artifact_origins":{"bin/altd-sim":{"build_receipt_path":"native/build-receipt.json","build_receipt_sha256":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","bytes":3580296,"origin_type":"local_linux_build","patch_sha256":"56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63","sha256":"6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d","source_receipt_sha256":"33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34","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":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","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":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","bytes":4711704,"origin_type":"retained_cuda_b90","patch_sha256":"56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63","sha256":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca","source_receipt_sha256":"33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34","source_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"}},"artifacts":{"bin/altd-sim":{"bytes":3580296,"sha256":"6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d"},"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":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","importer_source_revision":"d8ab74c25df70b6a86ed101080e33d5fb785ea6f","native_patch_sha256":"56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63","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"}
 
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":"687819451dae80b0d8df13346cdf08c93fcb15e6826af62d8c0667673f62ef3e","hf-auth.js":"3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe","index.html":"c46f7ba6dccf473956e038bf2028ff6d88b494a49d0c04c7ec0e278fd8eec0b3","track-library.js":"351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6","track-map.js":"ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"},"packaged_frontend_sha256":"e4a1c99827b8ed60dcdbc6639bff3d591dfd53d174d535133c4120dc8b15157a"}
 
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":"a0da1328fde0fb141d232c563ca5b217a4e9e50da62ba818ccb148a1529c9680","BUCKET_BUNDLE.md":"a802f658220f5eabc1a98ea4a5217989b54084d4993943c1faafbad36d421131","CUDA_REFERENCE.md":"f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c","DATA_POLICY.md":"7e8b31b5ad30f35e5d7242c13bdb356191ae05c81dcea2709172974e82546e4d","FEEDBACK.md":"f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a","FRONTEND_SOURCE.json":"d7a634aeceadb4268a38518a53a3b66c38d4b24cf3fbb8545b70828ee36bb279","GODOT_LICENSE":"fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304","IMPORTER_BUILD_RECEIPT.json":"4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d","LICENSE":"53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8","README.md":"cb234a47d1aef9ec17ac72cddca0d7b82abe868cbd3acd992bb8abb0759a18c2","STORAGE_BOUNDARY.md":"ac6946e1fd54be0786d71c24ec499bb898e63c20fd064c3c0bb3bba7046a0c57","TRACK_LIBRARY.md":"66b3656ed84f137d5b6116a0075731cd467818f555cca0969084aff67875319f","UPSTREAM.json":"148fd5b06f9b7f2d6830f5b536bf05dfc95c772f124223e8e298acc77f330836","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":"6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d","bin/extract_saved_track":"3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a","bin/libaltd_gpu.so":"3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca","bucket_storage.py":"4cdc4dc37388484fdd696e6932062ca20651c5ad64cd91a8ba059e92f90ca752","build_info.py":"03efdd1bca81022669a9184667b89d4f1096faba6a83c0514322dd419a938e1f","builtin_tracks.py":"2ecc2b1d00fe15701b0271d71e1e68d2e7e59c596330879494055f9b2a99c242","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":"687819451dae80b0d8df13346cdf08c93fcb15e6826af62d8c0667673f62ef3e","delivery.py":"6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b","gpu_tickets.py":"b81aadb5ed42cec9212d44da7e7bbb7b413c5bd517383cac0b49ec9b4d659c92","hf-auth.js":"3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe","hf_identity.py":"913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37","index.html":"c46f7ba6dccf473956e038bf2028ff6d88b494a49d0c04c7ec0e278fd8eec0b3","native/analysis.patch":"56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63","native/build-receipt.json":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","native/source-receipt.json":"33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34","portable_bundle.py":"29ab4e728eb054a51d02d41114a3548c6ed7cee65f809e0f33da40b87e0a12a6","requirements.txt":"fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4","server.py":"a8f838ee9f9a7a30d977fb706337ae070bba16a7f90aad9ad66f96e1a6a0eb21","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":"0c32f658c4c7af8c6f59f039b8bafdd997f324fcb12340f238e46a2d009d4e9f"},"interface":"single-engine-runner-with-map-winners-analysis-v1","upstream_revision":"b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"}
 
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. Race replicas use
16
- dummy lap stars rather than claiming to reproduce opponent challenges. The HF
17
- worker does not evaluate these conditions or grant campaign stars. Availability
 
 
 
 
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":"b2336433fc14ddf8b7722faa8ca434eff6f73ec41ec6d48cc0e9bf230f5efb3d","cuda_library_origin":"retained b90 CUDA library; layout ABI checked, no local device execution","patch_path":"native/analysis.patch","patch_sha256":"56859bc55a946ea129d502868387f8441cbd37c30962fe3e7e484d313c4ccf63","simulator_origin":"locally built Linux executable from b90 plus reviewed local patch","source_receipt_path":"native/source-receipt.json","source_receipt_sha256":"33d09c326e4d6c3ac1d1839e4528c70c06b4ff48138b90f174f434ab685b4b34","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"}
 
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:6488c7ec651701841999687708ff128a8ffb0b88feb53e23168a3ce678b5393d
3
- size 3580296
 
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':'not evaluated by this app',
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>6)throw Error('Analysis archive envelope is invalid');
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
- if(typeof file.name!=='string'||!allowed.has(file.name)||files.has(file.name)||!Number.isInteger(file.bytes)||file.bytes<1||file.bytes>ANALYSIS_EXPANDED_MAX||!hex64(file.sha256))throw Error('Analysis archive file manifest is invalid');
722
- const content=b64Bytes(file.data_base64,ANALYSIS_EXPANDED_MAX);
 
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
- const expectedFiles=new Set(['analysis.json','descriptors.jsonl',...(summary.mode==='evaluate'?['evaluation.json']:[]),...Object.entries(summary.winners).filter(([,w])=>w.status==='available').map(([role])=>'winners/'+role+'.evolution')]);
 
 
 
 
 
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. Star challenges are not evaluated here.</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 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>Star challenges — not evaluated</summary><p>The current runner has star-challenge reporting. Its evaluator and required statistics have not been ported to HF; no star result is inferred from a completed test.</p></details></section></div>
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,15 @@
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,9 @@
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 +206,9 @@
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
- --- a/src/bin/altd-sim/scratch/analysis.rs
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,6 @@
300
  //! Scratch training configuration, checkpoints, and stopping rules.
301
 
302
  +mod analysis;
 
303
  mod checkpoint;
304
  mod runner;
305
  mod stop;
306
- @@ -37,6 +38,9 @@
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,15 @@
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 +347,11 @@
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 +383,10 @@
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
- @@ -493,6 +510,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 +550,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 +635,9 @@
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 +646,7 @@
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/tests/winner_analysis.rs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  +++ b/tests/winner_analysis.rs
460
- @@ -0,0 +1,147 @@
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'),