Spaces:
Running on Zero
Running on Zero
Raise measured training limits
Browse files- FRONTEND_SOURCE.json +8 -8
- RELEASE_MANIFEST.json +11 -11
- app.py +1 -1
- bucket_storage.py +3 -3
- client.js +7 -7
- gpu_tickets.py +1 -1
- help.html +1 -1
- index.html +19 -18
- portable_bundle.py +19 -19
- star_check.py +2 -2
- training_config.py +6 -5
- worker.py +2 -2
FRONTEND_SOURCE.json
CHANGED
|
@@ -5,19 +5,19 @@
|
|
| 5 |
"source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
|
| 6 |
"source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"
|
| 7 |
},
|
| 8 |
-
"client_sha256": "
|
| 9 |
"identity_note": "The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.",
|
| 10 |
-
"local_customization_id": "v1.2-
|
| 11 |
-
"output_sha256": "
|
| 12 |
"packaged_frontend_files": {
|
| 13 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 14 |
-
"client.js": "
|
| 15 |
-
"help.html": "
|
| 16 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 17 |
-
"index.html": "
|
| 18 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 19 |
"track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"
|
| 20 |
},
|
| 21 |
-
"packaged_frontend_sha256": "
|
| 22 |
-
"status": "Local
|
| 23 |
}
|
|
|
|
| 5 |
"source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
|
| 6 |
"source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"
|
| 7 |
},
|
| 8 |
+
"client_sha256": "cbb1dd26a8fe4256abf827903df94749dc8e4f99912d29dba7f74ba6e477b851",
|
| 9 |
"identity_note": "The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.",
|
| 10 |
+
"local_customization_id": "v1.2-measured-capacity",
|
| 11 |
+
"output_sha256": "7bdaf7ca11a455525c22432298e120ca2fa5257283b9815467e005e09da07a26",
|
| 12 |
"packaged_frontend_files": {
|
| 13 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 14 |
+
"client.js": "cbb1dd26a8fe4256abf827903df94749dc8e4f99912d29dba7f74ba6e477b851",
|
| 15 |
+
"help.html": "4ce8f3995723a419d810c60212e996d71bfc0fcb61d44bf4fa8500030c356f47",
|
| 16 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 17 |
+
"index.html": "7bdaf7ca11a455525c22432298e120ca2fa5257283b9815467e005e09da07a26",
|
| 18 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 19 |
"track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"
|
| 20 |
},
|
| 21 |
+
"packaged_frontend_sha256": "da29a0f25c5e6778b017e748a1103f2fee02b53af66bda693cbf926e72a6b62d",
|
| 22 |
+
"status": "Local measured-capacity candidate; hosted qualification pending"
|
| 23 |
}
|
RELEASE_MANIFEST.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
| 8 |
"CUDA_REFERENCE.md": "f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c",
|
| 9 |
"DATA_POLICY.md": "fc5978528abe1c21c14882417ac8a706e04a0831bbd3fdb3de8c64442c1a7b3d",
|
| 10 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 11 |
-
"FRONTEND_SOURCE.json": "
|
| 12 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 13 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 14 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
|
@@ -18,7 +18,7 @@
|
|
| 18 |
"UPSTREAM.json": "ad5ff9011f329d9a05b18943118ebee914373501453971f786c1ee037213a83f",
|
| 19 |
"WINNERS_AND_EVALUATION.md": "f281cf496763c59c50a79161cfff1a46d3529edfcfaaa14962c9a3f2d4f9446f",
|
| 20 |
"analysis_result.py": "0a5d73fe8dbca934b21823b8ffbd50b0baaef8e15a39e32604eefe16f01d1cf2",
|
| 21 |
-
"app.py": "
|
| 22 |
"app_release.py": "7482559c667a665fd3cb6aa8af6c78422a822b515e14589db8306f7fd4860a8b",
|
| 23 |
"assets/formula_template.json": "80f745df82bb7be45af52785fd582460bcddc63c5a4e1d8375d85b3122db4c6b",
|
| 24 |
"assets/rally_template.json": "7350a3fe28d9b4da7a0965ae6ea7ca3472fb7885303e342310d63a0dce80851c",
|
|
@@ -28,7 +28,7 @@
|
|
| 28 |
"bin/altd-sim": "ce31f754f6b6f3afaf4dcc44c1e6fda3e6414eb2924fa167b8778523bcaf2070",
|
| 29 |
"bin/extract_saved_track": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 30 |
"bin/libaltd_gpu.so": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca",
|
| 31 |
-
"bucket_storage.py": "
|
| 32 |
"build_info.py": "2a57f73abd9e062cf029b65b09b26b8d12e301bac2f7efec7edb4b85b47671c3",
|
| 33 |
"builtin_tracks.py": "52cb4a285a6079cec7f7610de8af80570f1644a02cf9979e47d2d49736e24cb5",
|
| 34 |
"builtin_tracks/catalog.json": "4a7bba4385713e985d1427a0da35dcc32d5ecfd73d93cad7520563deb8685670",
|
|
@@ -60,33 +60,33 @@
|
|
| 60 |
"builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
|
| 61 |
"builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
|
| 62 |
"builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
|
| 63 |
-
"client.js": "
|
| 64 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 65 |
-
"gpu_tickets.py": "
|
| 66 |
-
"help.html": "
|
| 67 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 68 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 69 |
-
"index.html": "
|
| 70 |
"native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 71 |
"native/build-receipt.json": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 72 |
"native/selective-freeze-build-receipt.json": "ed804b1cd49a9ba34abc6f7cadaf4fb97807e27f3d14af117f8292a2d3f2d172",
|
| 73 |
"native/selective-freeze-source.tar.gz": "297d2ce34569994acd2a477e64c5315cb2081699b4333b8abdabb3ff7451d8db",
|
| 74 |
"native/source-receipt.json": "36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93",
|
| 75 |
-
"portable_bundle.py": "
|
| 76 |
"requirements.txt": "fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4",
|
| 77 |
"server.py": "1f82c9046324661d7bd4acf1cf37eef3445f985351159d88acdcaf89d1edf859",
|
| 78 |
-
"star_check.py": "
|
| 79 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 80 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 81 |
"track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0",
|
| 82 |
-
"training_config.py": "
|
| 83 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 84 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
| 85 |
"vendor/gradio-client/package.json": "96c5b9e6d3587fb7bd8e5bb28775ece222de1043045afc4e6389c88db137ad0e",
|
| 86 |
"vendor/hf-oauth/LICENSE": "d6e7b19451c1b3d1d66353bb4b1274138953b3bfd9ae60c56e77b1f548796982",
|
| 87 |
"vendor/hf-oauth/index.js": "53282844000a171011e547f6472a3846bb4cecb5071adafea079fcac3a2aee7e",
|
| 88 |
"vendor/hf-oauth/package.json": "fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8",
|
| 89 |
-
"worker.py": "
|
| 90 |
},
|
| 91 |
"interface": "single-engine-runner-with-map-winners-analysis-v1",
|
| 92 |
"upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
|
|
|
|
| 8 |
"CUDA_REFERENCE.md": "f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c",
|
| 9 |
"DATA_POLICY.md": "fc5978528abe1c21c14882417ac8a706e04a0831bbd3fdb3de8c64442c1a7b3d",
|
| 10 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 11 |
+
"FRONTEND_SOURCE.json": "65733d3682f4d0b320b4d6dc5187c895e1747671d369da9bfd1fb0edd9ae2c21",
|
| 12 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 13 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 14 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
|
|
|
| 18 |
"UPSTREAM.json": "ad5ff9011f329d9a05b18943118ebee914373501453971f786c1ee037213a83f",
|
| 19 |
"WINNERS_AND_EVALUATION.md": "f281cf496763c59c50a79161cfff1a46d3529edfcfaaa14962c9a3f2d4f9446f",
|
| 20 |
"analysis_result.py": "0a5d73fe8dbca934b21823b8ffbd50b0baaef8e15a39e32604eefe16f01d1cf2",
|
| 21 |
+
"app.py": "62dfcda6f47ea8c86d3f7919638d7bc1329507e6ec21377c13fcc33bea9d8f2d",
|
| 22 |
"app_release.py": "7482559c667a665fd3cb6aa8af6c78422a822b515e14589db8306f7fd4860a8b",
|
| 23 |
"assets/formula_template.json": "80f745df82bb7be45af52785fd582460bcddc63c5a4e1d8375d85b3122db4c6b",
|
| 24 |
"assets/rally_template.json": "7350a3fe28d9b4da7a0965ae6ea7ca3472fb7885303e342310d63a0dce80851c",
|
|
|
|
| 28 |
"bin/altd-sim": "ce31f754f6b6f3afaf4dcc44c1e6fda3e6414eb2924fa167b8778523bcaf2070",
|
| 29 |
"bin/extract_saved_track": "3a7733c45b4301a786fcbfe92660a1c484749272da5f608d62f34878bd4ff31a",
|
| 30 |
"bin/libaltd_gpu.so": "3b1b88bccc06a21acc33ac4400af016d55a7e3d8727136a0c43b541f3c7887ca",
|
| 31 |
+
"bucket_storage.py": "f6132ba5f95d106a667742ec092f2799de00e4baf1bf33600da4f0daaa46012a",
|
| 32 |
"build_info.py": "2a57f73abd9e062cf029b65b09b26b8d12e301bac2f7efec7edb4b85b47671c3",
|
| 33 |
"builtin_tracks.py": "52cb4a285a6079cec7f7610de8af80570f1644a02cf9979e47d2d49736e24cb5",
|
| 34 |
"builtin_tracks/catalog.json": "4a7bba4385713e985d1427a0da35dcc32d5ecfd73d93cad7520563deb8685670",
|
|
|
|
| 60 |
"builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
|
| 61 |
"builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
|
| 62 |
"builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
|
| 63 |
+
"client.js": "cbb1dd26a8fe4256abf827903df94749dc8e4f99912d29dba7f74ba6e477b851",
|
| 64 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 65 |
+
"gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
|
| 66 |
+
"help.html": "4ce8f3995723a419d810c60212e996d71bfc0fcb61d44bf4fa8500030c356f47",
|
| 67 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 68 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 69 |
+
"index.html": "7bdaf7ca11a455525c22432298e120ca2fa5257283b9815467e005e09da07a26",
|
| 70 |
"native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 71 |
"native/build-receipt.json": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
|
| 72 |
"native/selective-freeze-build-receipt.json": "ed804b1cd49a9ba34abc6f7cadaf4fb97807e27f3d14af117f8292a2d3f2d172",
|
| 73 |
"native/selective-freeze-source.tar.gz": "297d2ce34569994acd2a477e64c5315cb2081699b4333b8abdabb3ff7451d8db",
|
| 74 |
"native/source-receipt.json": "36feba861501baf8672884476bbeed5e375254cbb4feffb9f4f90aa61a415a93",
|
| 75 |
+
"portable_bundle.py": "58df5b87956745ea909c41c4b12a23cf9ab8c40a7df47d1529f47ae4de9bf51f",
|
| 76 |
"requirements.txt": "fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4",
|
| 77 |
"server.py": "1f82c9046324661d7bd4acf1cf37eef3445f985351159d88acdcaf89d1edf859",
|
| 78 |
+
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 79 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 80 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 81 |
"track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0",
|
| 82 |
+
"training_config.py": "91079154759066ff274dea8e3764a380a3313647277cee8db7fbddaf83d05614",
|
| 83 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 84 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
| 85 |
"vendor/gradio-client/package.json": "96c5b9e6d3587fb7bd8e5bb28775ece222de1043045afc4e6389c88db137ad0e",
|
| 86 |
"vendor/hf-oauth/LICENSE": "d6e7b19451c1b3d1d66353bb4b1274138953b3bfd9ae60c56e77b1f548796982",
|
| 87 |
"vendor/hf-oauth/index.js": "53282844000a171011e547f6472a3846bb4cecb5071adafea079fcac3a2aee7e",
|
| 88 |
"vendor/hf-oauth/package.json": "fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8",
|
| 89 |
+
"worker.py": "d18053363baf8147425808e9fd939f44f00af23781662d353196ade974144974"
|
| 90 |
},
|
| 91 |
"interface": "single-engine-runner-with-map-winners-analysis-v1",
|
| 92 |
"upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
|
app.py
CHANGED
|
@@ -73,7 +73,7 @@ def build_app(cuda_call,require_analysis=False):
|
|
| 73 |
track=base64.b64decode(body['track'],validate=True)
|
| 74 |
network=base64.b64decode(body['network'],validate=True)
|
| 75 |
track_input(track);network_input(network)
|
| 76 |
-
config=validate_config(body['settings']) if 'settings' in body else None
|
| 77 |
ticket=tickets.put(track,network,body.get('automatic',True),config)
|
| 78 |
return {'ticket':ticket,'expires_in_seconds':120}
|
| 79 |
except RuntimeError:raise HTTPException(429,'GPU input capacity reached') from None
|
|
|
|
| 73 |
track=base64.b64decode(body['track'],validate=True)
|
| 74 |
network=base64.b64decode(body['network'],validate=True)
|
| 75 |
track_input(track);network_input(network)
|
| 76 |
+
config=validate_config(body['settings'], work_limit=8_000_000) if 'settings' in body else None
|
| 77 |
ticket=tickets.put(track,network,body.get('automatic',True),config)
|
| 78 |
return {'ticket':ticket,'expires_in_seconds':120}
|
| 79 |
except RuntimeError:raise HTTPException(429,'GPU input capacity reached') from None
|
bucket_storage.py
CHANGED
|
@@ -131,10 +131,10 @@ def validate_recipe_library(raw: bytes) -> dict:
|
|
| 131 |
validate_freeze(settings["freeze"])
|
| 132 |
clean = {**RECIPE_DEFAULT_SETTINGS, **settings}
|
| 133 |
numeric = {
|
| 134 |
-
"population": (1,
|
| 135 |
"ticks": (1, 18000, True), "mutation": (0, 4.2, False),
|
| 136 |
"mutation_end": (0, 4.2, False), "seed": (0, 2147483647, True),
|
| 137 |
-
"parents": (1,
|
| 138 |
"decay": (0, 1, False), "workers": (1, 4, True),
|
| 139 |
}
|
| 140 |
for key, (low, high, integer) in numeric.items():
|
|
@@ -157,7 +157,7 @@ def validate_recipe_library(raw: bytes) -> dict:
|
|
| 157 |
if clean["schedule"] != "fixed" and clean["mutation_end"] <= 0:
|
| 158 |
raise ValueError("Invalid recipe library")
|
| 159 |
rewards = clean["rewards"]
|
| 160 |
-
if not isinstance(rewards, list) or not 1 <= len(rewards) <=
|
| 161 |
raise ValueError("Invalid recipe library")
|
| 162 |
reward_metrics = set()
|
| 163 |
for reward in rewards:
|
|
|
|
| 131 |
validate_freeze(settings["freeze"])
|
| 132 |
clean = {**RECIPE_DEFAULT_SETTINGS, **settings}
|
| 133 |
numeric = {
|
| 134 |
+
"population": (1, 1024, True), "generations": (1, 2000, True),
|
| 135 |
"ticks": (1, 18000, True), "mutation": (0, 4.2, False),
|
| 136 |
"mutation_end": (0, 4.2, False), "seed": (0, 2147483647, True),
|
| 137 |
+
"parents": (1, 1024, True), "elite": (0, 1024, True),
|
| 138 |
"decay": (0, 1, False), "workers": (1, 4, True),
|
| 139 |
}
|
| 140 |
for key, (low, high, integer) in numeric.items():
|
|
|
|
| 157 |
if clean["schedule"] != "fixed" and clean["mutation_end"] <= 0:
|
| 158 |
raise ValueError("Invalid recipe library")
|
| 159 |
rewards = clean["rewards"]
|
| 160 |
+
if not isinstance(rewards, list) or not 1 <= len(rewards) <= len(RECIPE_METRICS):
|
| 161 |
raise ValueError("Invalid recipe library")
|
| 162 |
reward_metrics = set()
|
| 163 |
for reward in rewards:
|
client.js
CHANGED
|
@@ -593,7 +593,7 @@ function validateAnalysisSummary(summary){
|
|
| 593 |
const c=summary.conditions;exactAnalysisKeys(c,['track_sha256','network_sha256','vehicle_type','requested_ticks','control_hz','collision','idle','math_profile','clean_min_laps']);
|
| 594 |
if(!hex64(c.track_sha256)||!hex64(c.network_sha256)||!Number.isInteger(c.vehicle_type)||c.vehicle_type<0||c.vehicle_type>3||!Number.isInteger(c.requested_ticks)||c.requested_ticks<1||c.requested_ticks>18000||![15,30,60].includes(c.control_hz)||typeof c.collision!=='boolean'||typeof c.idle!=='boolean'||c.math_profile!=='proton'||!Number.isInteger(c.clean_min_laps)||c.clean_min_laps<1||c.clean_min_laps>100)throw Error('Analysis conditions are invalid');
|
| 595 |
const o=summary.observations;exactAnalysisKeys(o,['generations','cars','raw_metric_count','reward_channel_count']);
|
| 596 |
-
if(!Number.isInteger(o.generations)||o.generations<1||o.generations>
|
| 597 |
exactAnalysisKeys(summary.winners,['fitness','fastest_lap','clean_laps']);
|
| 598 |
for(const role of ['fitness','fastest_lap','clean_laps']){
|
| 599 |
const w=summary.winners[role];exactAnalysisKeys(w,['status','reason','candidate_id','generation','car','selection_fitness','best_lap_seconds','forward_laps','physical_contacts','collision_updates','total_actual_distance','artifact']);
|
|
@@ -601,7 +601,7 @@ function validateAnalysisSummary(summary){
|
|
| 601 |
if(w.status==='unavailable'){
|
| 602 |
if(typeof w.reason!=='string'||!w.reason||w.reason.length>300||w.candidate_id!==null||w.artifact!==null)throw Error('Unavailable winner details are invalid');
|
| 603 |
}else{
|
| 604 |
-
if(w.reason!==null||typeof w.candidate_id!=='string'||!/^g\d{6}-c\d{6}$/.test(w.candidate_id)||!Number.isInteger(w.generation)||w.generation<1||w.generation>o.generations||!Number.isInteger(w.car)||w.car<0||w.car>=
|
| 605 |
exactAnalysisKeys(w.artifact,['name','bytes','sha256']);if(w.artifact.name!=='winners/'+role+'.evolution'||!Number.isInteger(w.artifact.bytes)||w.artifact.bytes<1||w.artifact.bytes>4*1024*1024||!hex64(w.artifact.sha256))throw Error('Winner artifact reference is invalid');
|
| 606 |
}
|
| 607 |
}
|
|
@@ -613,7 +613,7 @@ function validateAnalysisSummary(summary){
|
|
| 613 |
}
|
| 614 |
function validateDescriptorRow(row,metricNames,rewardChannels){
|
| 615 |
exactAnalysisKeys(row,['schema','generation','car','candidate_id','ticks_elapsed','stats_update_count','raw_metrics','channels','selection_fitness','objectives','weights_sha256']);
|
| 616 |
-
if(row.schema!==1||!Number.isInteger(row.generation)||row.generation<1||row.generation>
|
| 617 |
exactAnalysisKeys(row.raw_metrics,metricNames);if(Object.values(row.raw_metrics).some(value=>!finiteOrNull(value)))throw Error('Analysis raw metric is invalid');
|
| 618 |
exactAnalysisKeys(row.channels,['values','population_relative','unavailable_reasons']);
|
| 619 |
if(!Array.isArray(row.channels.values)||row.channels.values.length!==18||!Array.isArray(row.channels.population_relative)||row.channels.population_relative.length!==18||!Array.isArray(row.channels.unavailable_reasons)||row.channels.unavailable_reasons.length!==18)throw Error('Analysis reward channels are incomplete');
|
|
@@ -1199,14 +1199,14 @@ const bundleFinite=value=>typeof value==='number'&&Number.isFinite(value);
|
|
| 1199 |
const runHistoryKeys=['generation','mutation','best_score','mean_score','best_lap_seconds','lapped_cars','simulated_ticks'];
|
| 1200 |
function cleanRunHistory(value){
|
| 1201 |
if(value===undefined)return undefined;
|
| 1202 |
-
if(!Array.isArray(value)||value.length>
|
| 1203 |
return value.map((row,index)=>{
|
| 1204 |
bundleExact(row,runHistoryKeys);
|
| 1205 |
-
if(!Number.isInteger(row.generation)||row.generation!==index+1||row.generation>
|
| 1206 |
!bundleFinite(row.best_score)||Math.abs(row.best_score)>1e12||!bundleFinite(row.mean_score)||Math.abs(row.mean_score)>1e12||
|
| 1207 |
!(row.best_lap_seconds===null||(bundleFinite(row.best_lap_seconds)&&row.best_lap_seconds>0&&row.best_lap_seconds<=1e12))||
|
| 1208 |
-
!(row.lapped_cars===null||(Number.isInteger(row.lapped_cars)&&row.lapped_cars>=0&&row.lapped_cars<=
|
| 1209 |
-
!Number.isInteger(row.simulated_ticks)||row.simulated_ticks<0||row.simulated_ticks>
|
| 1210 |
return {generation:row.generation,mutation:row.mutation,best_score:row.best_score,mean_score:row.mean_score,best_lap_seconds:row.best_lap_seconds,lapped_cars:row.lapped_cars,simulated_ticks:row.simulated_ticks};
|
| 1211 |
});
|
| 1212 |
}
|
|
|
|
| 593 |
const c=summary.conditions;exactAnalysisKeys(c,['track_sha256','network_sha256','vehicle_type','requested_ticks','control_hz','collision','idle','math_profile','clean_min_laps']);
|
| 594 |
if(!hex64(c.track_sha256)||!hex64(c.network_sha256)||!Number.isInteger(c.vehicle_type)||c.vehicle_type<0||c.vehicle_type>3||!Number.isInteger(c.requested_ticks)||c.requested_ticks<1||c.requested_ticks>18000||![15,30,60].includes(c.control_hz)||typeof c.collision!=='boolean'||typeof c.idle!=='boolean'||c.math_profile!=='proton'||!Number.isInteger(c.clean_min_laps)||c.clean_min_laps<1||c.clean_min_laps>100)throw Error('Analysis conditions are invalid');
|
| 595 |
const o=summary.observations;exactAnalysisKeys(o,['generations','cars','raw_metric_count','reward_channel_count']);
|
| 596 |
+
if(!Number.isInteger(o.generations)||o.generations<1||o.generations>2000||!Number.isInteger(o.cars)||o.cars<1||o.cars>ANALYSIS_ROWS_MAX||o.raw_metric_count!==14||o.reward_channel_count!==18)throw Error('Analysis observation counts are invalid');
|
| 597 |
exactAnalysisKeys(summary.winners,['fitness','fastest_lap','clean_laps']);
|
| 598 |
for(const role of ['fitness','fastest_lap','clean_laps']){
|
| 599 |
const w=summary.winners[role];exactAnalysisKeys(w,['status','reason','candidate_id','generation','car','selection_fitness','best_lap_seconds','forward_laps','physical_contacts','collision_updates','total_actual_distance','artifact']);
|
|
|
|
| 601 |
if(w.status==='unavailable'){
|
| 602 |
if(typeof w.reason!=='string'||!w.reason||w.reason.length>300||w.candidate_id!==null||w.artifact!==null)throw Error('Unavailable winner details are invalid');
|
| 603 |
}else{
|
| 604 |
+
if(w.reason!==null||typeof w.candidate_id!=='string'||!/^g\d{6}-c\d{6}$/.test(w.candidate_id)||!Number.isInteger(w.generation)||w.generation<1||w.generation>o.generations||!Number.isInteger(w.car)||w.car<0||w.car>=1024||!finiteOrNull(w.selection_fitness)||!finiteOrNull(w.best_lap_seconds,Number.MIN_VALUE,1e12)||!Number.isInteger(w.forward_laps)||w.forward_laps<0||!Number.isInteger(w.physical_contacts)||w.physical_contacts<0||!Number.isInteger(w.collision_updates)||w.collision_updates<0||!finiteOrNull(w.total_actual_distance,0,1e12))throw Error('Available winner details are invalid');
|
| 605 |
exactAnalysisKeys(w.artifact,['name','bytes','sha256']);if(w.artifact.name!=='winners/'+role+'.evolution'||!Number.isInteger(w.artifact.bytes)||w.artifact.bytes<1||w.artifact.bytes>4*1024*1024||!hex64(w.artifact.sha256))throw Error('Winner artifact reference is invalid');
|
| 606 |
}
|
| 607 |
}
|
|
|
|
| 613 |
}
|
| 614 |
function validateDescriptorRow(row,metricNames,rewardChannels){
|
| 615 |
exactAnalysisKeys(row,['schema','generation','car','candidate_id','ticks_elapsed','stats_update_count','raw_metrics','channels','selection_fitness','objectives','weights_sha256']);
|
| 616 |
+
if(row.schema!==1||!Number.isInteger(row.generation)||row.generation<1||row.generation>2000||!Number.isInteger(row.car)||row.car<0||row.car>=1024||row.candidate_id!=='g'+String(row.generation).padStart(6,'0')+'-c'+String(row.car).padStart(6,'0')||!Number.isInteger(row.ticks_elapsed)||row.ticks_elapsed<0||row.ticks_elapsed>18012||!Number.isInteger(row.stats_update_count)||row.stats_update_count<0||row.stats_update_count>18012||!finiteOrNull(row.selection_fitness)||!hex64(row.weights_sha256))throw Error('Analysis descriptor identity is invalid');
|
| 617 |
exactAnalysisKeys(row.raw_metrics,metricNames);if(Object.values(row.raw_metrics).some(value=>!finiteOrNull(value)))throw Error('Analysis raw metric is invalid');
|
| 618 |
exactAnalysisKeys(row.channels,['values','population_relative','unavailable_reasons']);
|
| 619 |
if(!Array.isArray(row.channels.values)||row.channels.values.length!==18||!Array.isArray(row.channels.population_relative)||row.channels.population_relative.length!==18||!Array.isArray(row.channels.unavailable_reasons)||row.channels.unavailable_reasons.length!==18)throw Error('Analysis reward channels are incomplete');
|
|
|
|
| 1199 |
const runHistoryKeys=['generation','mutation','best_score','mean_score','best_lap_seconds','lapped_cars','simulated_ticks'];
|
| 1200 |
function cleanRunHistory(value){
|
| 1201 |
if(value===undefined)return undefined;
|
| 1202 |
+
if(!Array.isArray(value)||value.length>2000)throw Error('Generation history exceeds its 2,000-row limit');
|
| 1203 |
return value.map((row,index)=>{
|
| 1204 |
bundleExact(row,runHistoryKeys);
|
| 1205 |
+
if(!Number.isInteger(row.generation)||row.generation!==index+1||row.generation>2000||!bundleFinite(row.mutation)||row.mutation<0||row.mutation>4.2||
|
| 1206 |
!bundleFinite(row.best_score)||Math.abs(row.best_score)>1e12||!bundleFinite(row.mean_score)||Math.abs(row.mean_score)>1e12||
|
| 1207 |
!(row.best_lap_seconds===null||(bundleFinite(row.best_lap_seconds)&&row.best_lap_seconds>0&&row.best_lap_seconds<=1e12))||
|
| 1208 |
+
!(row.lapped_cars===null||(Number.isInteger(row.lapped_cars)&&row.lapped_cars>=0&&row.lapped_cars<=1024))||
|
| 1209 |
+
!Number.isInteger(row.simulated_ticks)||row.simulated_ticks<0||row.simulated_ticks>8000000)throw Error('Generation history contains an invalid value');
|
| 1210 |
return {generation:row.generation,mutation:row.mutation,best_score:row.best_score,mean_score:row.mean_score,best_lap_seconds:row.best_lap_seconds,lapped_cars:row.lapped_cars,simulated_ticks:row.simulated_ticks};
|
| 1211 |
});
|
| 1212 |
}
|
gpu_tickets.py
CHANGED
|
@@ -23,7 +23,7 @@ class InputTickets:
|
|
| 23 |
if not isinstance(track,bytes) or not isinstance(network,bytes):raise ValueError('Expected bytes')
|
| 24 |
if not track or not network or max(len(track),len(network))>1024*1024:raise ValueError('Input size')
|
| 25 |
if type(automatic) is not bool:raise ValueError('Automatic mode')
|
| 26 |
-
if config is not None:config=validate_config(config)
|
| 27 |
with self.lock:
|
| 28 |
self.expire()
|
| 29 |
if self.pending:raise RuntimeError('GPU input capacity reached')
|
|
|
|
| 23 |
if not isinstance(track,bytes) or not isinstance(network,bytes):raise ValueError('Expected bytes')
|
| 24 |
if not track or not network or max(len(track),len(network))>1024*1024:raise ValueError('Input size')
|
| 25 |
if type(automatic) is not bool:raise ValueError('Automatic mode')
|
| 26 |
+
if config is not None:config=validate_config(config, work_limit=8_000_000)
|
| 27 |
with self.lock:
|
| 28 |
self.expire()
|
| 29 |
if self.pending:raise RuntimeError('GPU input capacity reached')
|
help.html
CHANGED
|
@@ -81,7 +81,7 @@
|
|
| 81 |
<section class="section" id="limits">
|
| 82 |
<h2>Limits and compute</h2>
|
| 83 |
<p>The worker supports Formula, Rally, Snowmobile, and Truck. Networks support at most 20 inputs, 12 total layers, and 16 neurons per hidden layer. Formula, Rally, and Truck support up to five outputs; Snowmobile supports up to four and cannot use Handbrake. Fresh controllers for every vehicle require Steering and Acceleration; Formula imports retain their supported ordered output subsets.</p>
|
| 84 |
-
<p>
|
| 85 |
<p>The public GPU app also provides CPU execution without GPU allowance. GPU runs use HF allowance; optional HF sign-in can associate a request with your account but does not guarantee quota. Sign-in can reload the page, so do it before choosing files if needed.</p>
|
| 86 |
<p>These are bounded vanilla-worker limits. They do not cover arbitrary modified-game networks or establish full original-game physics parity.</p>
|
| 87 |
</section>
|
|
|
|
| 81 |
<section class="section" id="limits">
|
| 82 |
<h2>Limits and compute</h2>
|
| 83 |
<p>The worker supports Formula, Rally, Snowmobile, and Truck. Networks support at most 20 inputs, 12 total layers, and 16 neurons per hidden layer. Formula, Rally, and Truck support up to five outputs; Snowmobile supports up to four and cannot use Handbrake. Fresh controllers for every vehicle require Steering and Acceleration; Formula imports retain their supported ordered output subsets.</p>
|
| 84 |
+
<p>CPU runs: up to 4,000,000 requested vehicle-ticks in a 25-second native execution window. GPU runs: up to 8,000,000 requested vehicle-ticks in a 45-second native execution window. Both allow up to 1,024 cars, 2,000 generations, fourteen distinct reward metrics, and 32,768 car-generation analysis rows per run. Requested vehicle-ticks are an upper-bound work estimate, not a guarantee of runtime. Results and analysis have separate size limits. These are bounded vanilla-worker limits, not support for arbitrary modified-game networks or full original-game parity.</p>
|
| 85 |
<p>The public GPU app also provides CPU execution without GPU allowance. GPU runs use HF allowance; optional HF sign-in can associate a request with your account but does not guarantee quota. Sign-in can reload the page, so do it before choosing files if needed.</p>
|
| 86 |
<p>These are bounded vanilla-worker limits. They do not cover arbitrary modified-game networks or establish full original-game physics parity.</p>
|
| 87 |
</section>
|
index.html
CHANGED
|
@@ -65,8 +65,8 @@
|
|
| 65 |
<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>
|
| 66 |
<p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
|
| 67 |
<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>
|
| 68 |
-
</div><section id="training" class="pane" hidden><div class="card"><h2>Training settings</h2><p class="hint">Choose the track on Drive & view. The first-run settings are a starting point; you can change them or load one of your saved recipes. Starting a new run never rewrites your saved recipes.</p><p><button id="starter_settings_reset" type="button">Apply first-run settings to this form</button> <span id="starter_settings_status" role="status" aria-live="polite">Saved recipes are unchanged.</span></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="900"><small>60 ticks = one simulated second. The first-run setting is 900 ticks (15 simulated seconds) per generation.
|
| 69 |
-
<div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="
|
| 70 |
<p id="work_budget" class="hint" role="status" aria-live="polite"></p>
|
| 71 |
<label id="mutation_label" for="mutation">Mutation rate</label><input id="mutation" type="number" min="0" max="4.2" step="0.01" value="0.3" disabled><small>Use a positive mutation rate, including when continuing an uploaded network. A one-generation run still evaluates the uploaded parent.</small>
|
| 72 |
|
|
@@ -81,9 +81,9 @@
|
|
| 81 |
<label for="decay">Weight decay</label><input id="decay" type="number" min="0" max="1" step="0.0001" value="0" disabled><small>Allowed 0–1; suggested 0–0.001. Decay 0.05 shrinks children by 5%.</small>
|
| 82 |
</details>
|
| 83 |
<details id="freeze_settings"><summary>Selective freezing · optional</summary><p class="hint">Keep selected sensor inputs, hidden layers, or output controls unchanged while training. Choices follow the selected network and are saved with recipes. Frozen evaluation always uses an empty freeze selection.</p><div id="freeze_status" class="hint" role="status">Select a network to choose components.</div><div id="freeze_controls" class="row"></div></details>
|
| 84 |
-
<details id="reward_settings"><summary>Rewards · presets and up to
|
| 85 |
<label for="reward_preset">Reward preset</label><select id="reward_preset" disabled><option value="custom">Custom</option><option value="distance" selected>First laps · distance only (+100 total score)</option><option value="center">Example mix · center, clearance, collisions</option><option value="clean">Clean driving · distance + collision penalty</option><option value="lap">Lap refinement · best lap only</option></select>
|
| 86 |
-
<div id="rewards"></div><button id="add" disabled>+ Add reward</button><small>Weights −100…+100; absolute shares normalize to 100%.
|
| 87 |
</details></div>
|
| 88 |
<details id="run_settings"><summary>Run settings · timing and elimination</summary><div class="row"><div><label for="control_hz">Decisions per second</label><select id="control_hz" disabled><option>60</option><option selected>30</option><option>15</option></select></div><div><label for="workers">Parallel simulations</label><select id="workers" disabled><option>1</option><option selected>1</option><option>2</option><option>3</option><option>4</option></select></div></div><small>Physics runs at 60 Hz. CPU jobs can use 1–4 worker threads.</small><div id="seed_group"><label for="seed">Random seed</label><input id="seed" type="number" min="0" max="2147483647" value="42105" disabled></div><label><input id="collision" type="checkbox" disabled> Eliminate on wall contact</label><label><input id="idle" type="checkbox" disabled> Eliminate when stuck</label><label for="clean_min_laps">Minimum forward laps for a clean-lap winner</label><input id="clean_min_laps" type="number" min="1" max="100" value="2"><small>Winner classification only. Zero physical contacts are required. This does not change your reward recipe.</small></details></div><div id="libraries" class="card"><h3>Recipes · A–J · saved in this browser</h3>
|
| 89 |
<p>Each recipe contains the training settings and reward mix. Tracks, networks and results stay out of the recipe library. Saving a recipe sends nothing to the server. Settings are sent when you start a run.</p>
|
|
@@ -93,7 +93,7 @@
|
|
| 93 |
<button id="recipe_clear">Clear saved recipes and batches on this device</button><p id="recipe_feedback" role="status" aria-live="polite"></p>
|
| 94 |
<p>Recipes are specific to this browser and site. Clearing site data removes them. Export a backup to move them between the local runner and HF, or use the connected private bucket controls on Drive & view. Browser privacy settings may prevent local saving.</p>
|
| 95 |
<details><summary>Advanced recipe JSON</summary><textarea id="recipe_draft" rows="12" style="width:100%" aria-label="Recipe settings JSON"></textarea><button id="recipe_apply_draft">Apply JSON to form</button></details></div></section>
|
| 96 |
-
<section id="batch" class="pane" hidden><div class="card"><h2>Training batches · A–J</h2><p>Choose files and CPU/GPU on Drive & view, then start a saved batch here. Up to 20 runs; each
|
| 97 |
<section id="editor" class="pane" hidden><div class="card"><h2>Network editor</h2><p class="hint">An original editor inspired by <a href="https://the-mars-guy.github.io/AI-Learns-To-Drive-Tool/" target="_blank" rel="noopener">Mars’s network creator</a>. Create fresh networks with the selected vehicle’s game-legal controls, or use its largest original-game shape. Loaded networks keep their original vehicle, output order and sensor parameters. Unsupported files can still be inspected without changing the original.</p><div class="toolbar"><label for="editor_file">Load an existing .evolution file</label><input id="editor_file" type="file" accept=".evolution"><button id="editor_import" disabled>Inspect selected network</button><button id="editor_create" disabled>Create fresh network</button><button id="editor_apply" disabled>Use edited network</button><a id="editor_download" hidden download>Download .evolution</a></div><div class="row"><div><label for="editor_name">Network name</label><input id="editor_name" type="text" maxlength="80" value="Fresh network" disabled><label for="editor_vehicle">Vehicle type for fresh networks</label><select id="editor_vehicle" disabled><option value="0">Formula</option><option value="1">Rally</option><option value="2">Snowmobile</option><option value="3">Truck</option></select><small>This selector applies only when you create a fresh network. Imported vehicle type is retained and shown with the current network.</small><label for="editor_preset">Fresh network preset</label><select id="editor_preset" disabled><option id="editor_small_label" value="small">Small starter · 6 inputs, 4 hidden nodes, 5 outputs</option><option id="editor_maximum_label" value="maximum">Largest game network · 20 inputs, 10 × 16 hidden nodes, 5 outputs</option><option value="custom">Custom</option></select><label for="editor_shape">Input and hidden layer sizes</label><input id="editor_shape" type="text" value="6,4" disabled><small id="editor_shape_hint">Enter 1–20 inputs, followed by 0–10 hidden layers of 1–16 nodes. Output width follows the selected controls. Maximum shape: 20 → ten layers of 16 → 5 (2,869 parameters).</small><fieldset id="editor_outputs" disabled><legend id="editor_output_legend">Fresh network outputs</legend><div class="output-options"><label><input id="editor_output_0" type="checkbox" value="0" checked disabled> Steering (0)</label><label><input id="editor_output_1" type="checkbox" value="1" checked disabled> Acceleration (1)</label><label><input id="editor_output_2" type="checkbox" value="2" checked disabled> Handbrake (2)</label><label><input id="editor_output_3" type="checkbox" value="3" checked disabled> Brake (3)</label><label><input id="editor_output_4" type="checkbox" value="4" checked disabled> Boost (4)</label></div><small id="editor_output_hint">Steering and Acceleration are required. Fresh output IDs preserve this order.</small></fieldset></div><div><label for="editor_seed">Initialization seed</label><input id="editor_seed" type="number" min="0" max="2147483647" value="42105" disabled><label><input id="editor_xavier" type="checkbox" checked disabled> Seeded Xavier uniform weights (browser algorithm)</label><label><input id="editor_bias" type="checkbox" disabled> Xavier uniform biases</label><small>Unchecked: zeros. Biases default to zero.</small></div></div><p id="editor_status" role="status">Choose a file or create a fresh network. Editor changes are browser-local until you explicitly start a run.</p><p id="editor_metadata" class="hint"></p><p id="editor_legality" class="hint"></p><details id="editor_history_panel"><summary>Training history · recorded generations</summary><div class="table-scroll"><table><thead><tr><th>Record</th><th>Time</th><th>Mutation</th><th>Best score</th><th>Best lap</th></tr></thead><tbody id="editor_history"></tbody></table></div><details><summary>Full saved training metadata</summary><pre id="editor_stats"></pre></details></details><canvas aria-label="Network architecture preview" id="network_map" width="1100" height="450"></canvas><details id="weight_values"><summary>Individual weight and bias values</summary><div class="table-scroll" style="max-height:420px;overflow:auto"><table><thead><tr><th>Connection / bias</th><th>Value</th></tr></thead><tbody id="editor_parameters"></tbody></table></div></details><details><summary>Weights, biases and input contract</summary><pre id="editor_contract"></pre><label for="editor_json">NeuralNetwork JSON · editable weights and biases</label><textarea id="editor_json" rows="16" style="width:100%;box-sizing:border-box;background:#0c1725;color:#e5edf5" disabled></textarea></details></div></section><script>/* Settings-only recipe contract. Storage is injected; no network or job data. */
|
| 98 |
(function(root){
|
| 99 |
const KEY='tiga.ald.recipes.v1';
|
|
@@ -111,7 +111,7 @@
|
|
| 111 |
function settings(value){
|
| 112 |
if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).some(k=>!Object.hasOwn(defaults,k)))throw Error('Unexpected settings fields');
|
| 113 |
const clean={...defaults,...value};
|
| 114 |
-
for(const [key,min,max,integer] of [['population',1,
|
| 115 |
const v=clean[key];if(typeof v!=='number'||!Number.isFinite(v)||v<min||v>max||(integer&&!Number.isInteger(v)))throw Error('Invalid '+key);
|
| 116 |
}
|
| 117 |
for(const [key,choices] of [['schedule',['fixed','linear','geometric']],['selection',['best','tournament','roulette']],['crossover',['none','single_point','uniform']],['control_hz',[60,30,15]]])if(!choices.includes(clean[key]))throw Error('Invalid '+key);
|
|
@@ -120,7 +120,7 @@
|
|
| 120 |
for(const [key,max] of [['layers',10],['inputs',19],['outputs',4]]){const values=clean.freeze[key];if(!Array.isArray(values)||values.some(v=>!Number.isInteger(v)||v<(key==='layers'?1:0)||v>max)||new Set(values).size!==values.length)throw Error('Invalid frozen '+key);clean.freeze[key]=[...values].sort((a,b)=>a-b);}
|
| 121 |
if(clean.parents>clean.population||clean.elite>clean.population)throw Error('Parents/elites exceed population');
|
| 122 |
if(clean.mutation<=0||(clean.schedule!=='fixed'&&clean.mutation_end<=0))throw Error('This training runner requires positive mutation rates, including when continuing an uploaded network');
|
| 123 |
-
if(!Array.isArray(clean.rewards)||!clean.rewards.length||clean.rewards.length>
|
| 124 |
const seen=new Set();
|
| 125 |
clean.rewards=clean.rewards.map(r=>{
|
| 126 |
exact(r,['metric','type','weight']);
|
|
@@ -161,13 +161,13 @@
|
|
| 161 |
</script><script>/* Frozen settings-only plans; execution and stop checks are injected. */
|
| 162 |
(function(root){
|
| 163 |
const recipes=typeof module!=='undefined'&&module.exports?require('./recipes.cjs'):root.RecipeLibrary;
|
| 164 |
-
function plan(book,slot){
|
| 165 |
const library=recipes.validate(book),batch=library.batches.find(b=>b.slot===slot);
|
| 166 |
if(!batch)throw Error('Save or load a batch first');
|
| 167 |
if(batch.runs.length>20)throw Error('Execution limit: 20 runs per batch');
|
| 168 |
const runs=batch.runs.map(slot=>JSON.parse(JSON.stringify(library.recipes.find(r=>r.slot===slot))));
|
| 169 |
const work=runs.reduce((n,r)=>n+r.settings.population*r.settings.generations*r.settings.ticks,0);
|
| 170 |
-
if(runs.some(r=>r.settings.population*r.settings.generations*r.settings.ticks>
|
| 171 |
return {slot:batch.slot,name:batch.name,carry:batch.carry,runs,work};
|
| 172 |
}
|
| 173 |
async function execute(plan,original,process,{stopped=()=>false,onStep=()=>{}}={}){
|
|
@@ -333,15 +333,15 @@
|
|
| 333 |
const p=Number(q('population').value),g=Number(q('generations').value),t=Number(q('training_ticks').value),box=q('work_budget');
|
| 334 |
if(q('run_mode')?.value==='evaluate'){box.textContent='Frozen evaluation uses one car and one generation; the training population and generation count do not apply.';box.style.color='';return;}
|
| 335 |
if(![p,g,t].every(n=>Number.isInteger(n)&&n>0)){box.textContent='Enter positive whole numbers to see the run size.';box.style.color='';return;}
|
| 336 |
-
const work=p*g*t,maximum=4000000,maxGenerations=Math.floor(maximum/(p*t));
|
| 337 |
-
box.textContent='Planned work: '+work.toLocaleString()+' /
|
| 338 |
-
box.style.color=work>maximum?'#ffb5a2':'';
|
| 339 |
}
|
| 340 |
function executableSettings(){
|
| 341 |
-
const data=currentSettings(),work=data.population*data.generations*data.ticks;
|
| 342 |
-
if(q('run_mode')?.value!=='evaluate'&&work>
|
| 343 |
-
const maxGenerations=Math.floor(
|
| 344 |
-
throw Object.assign(Error('This recipe needs '+work.toLocaleString()+' vehicle-ticks (
|
| 345 |
}
|
| 346 |
return data;
|
| 347 |
}
|
|
@@ -377,7 +377,8 @@
|
|
| 377 |
};
|
| 378 |
for(const id of ['population','generations','training_ticks'])q(id).addEventListener('input',updateWorkBudget);
|
| 379 |
q('run_mode')?.addEventListener('change',updateWorkBudget);
|
| 380 |
-
q('
|
|
|
|
| 381 |
q('reward_preset').onchange=()=>{
|
| 382 |
const key=q('reward_preset').value;if(key==='custom')return;
|
| 383 |
const rows={distance:[['total_score',100]],center:[['total_score',100],['total_distance_from_center',-60],['total_distance_from_wall',10],['collision_count',-100],['best_lap_performance',5]],clean:[['total_score',100],['collision_count',-30]],lap:[['best_lap_performance',100]]};
|
|
@@ -401,7 +402,7 @@
|
|
| 401 |
if(!store)throw Error('Browser storage unavailable');store.save(book);
|
| 402 |
message('Imported '+book.recipes.length+' recipes and '+book.batches.length+' batches from '+source+'. No run was started.');return true;
|
| 403 |
};
|
| 404 |
-
window.getBatchPlan=()=>window.BatchTraining.plan(read(),q('batch_slot').value);
|
| 405 |
const action=fn=>async()=>{try{await fn();}catch(error){message('Not completed: '+error.message);}};
|
| 406 |
q('starter_settings_reset').onclick=action(async()=>{
|
| 407 |
if(!(await window.appConfirm('Replace the current form settings and reward mix with the first-run starter? Saved recipes will not change.',{heading:'Apply first-run settings',confirmLabel:'Apply starter'})))return;
|
|
|
|
| 65 |
<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>
|
| 66 |
<p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
|
| 67 |
<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>
|
| 68 |
+
</div><section id="training" class="pane" hidden><div class="card"><h2>Training settings</h2><p class="hint">Choose the track on Drive & view. The first-run settings are a starting point; you can change them or load one of your saved recipes. Starting a new run never rewrites your saved recipes.</p><p><button id="starter_settings_reset" type="button">Apply first-run settings to this form</button> <span id="starter_settings_status" role="status" aria-live="polite">Saved recipes are unchanged.</span></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="900"><small>60 ticks = one simulated second. The first-run setting is 900 ticks (15 simulated seconds) per generation. Per-run limits: CPU 4,000,000 vehicle-ticks and 25 seconds; CUDA 8,000,000 vehicle-ticks and 45 seconds. Both allow up to 32,768 car-generation analysis rows.</small>
|
| 69 |
+
<div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="1024" value="32" disabled><small>1–1,024 cars per generation. The first-run setting is 32.</small></div><div><label for="generations">Generations</label><input id="generations" type="number" min="1" max="2000" value="20" disabled><small>1–2,000; the first-run setting is 20.</small></div></div>
|
| 70 |
<p id="work_budget" class="hint" role="status" aria-live="polite"></p>
|
| 71 |
<label id="mutation_label" for="mutation">Mutation rate</label><input id="mutation" type="number" min="0" max="4.2" step="0.01" value="0.3" disabled><small>Use a positive mutation rate, including when continuing an uploaded network. A one-generation run still evaluates the uploaded parent.</small>
|
| 72 |
|
|
|
|
| 81 |
<label for="decay">Weight decay</label><input id="decay" type="number" min="0" max="1" step="0.0001" value="0" disabled><small>Allowed 0–1; suggested 0–0.001. Decay 0.05 shrinks children by 5%.</small>
|
| 82 |
</details>
|
| 83 |
<details id="freeze_settings"><summary>Selective freezing · optional</summary><p class="hint">Keep selected sensor inputs, hidden layers, or output controls unchanged while training. Choices follow the selected network and are saved with recipes. Frozen evaluation always uses an empty freeze selection.</p><div id="freeze_status" class="hint" role="status">Select a network to choose components.</div><div id="freeze_controls" class="row"></div></details>
|
| 84 |
+
<details id="reward_settings"><summary>Rewards · presets and up to fourteen metrics</summary>
|
| 85 |
<label for="reward_preset">Reward preset</label><select id="reward_preset" disabled><option value="custom">Custom</option><option value="distance" selected>First laps · distance only (+100 total score)</option><option value="center">Example mix · center, clearance, collisions</option><option value="clean">Clean driving · distance + collision penalty</option><option value="lap">Lap refinement · best lap only</option></select>
|
| 86 |
+
<div id="rewards"></div><button id="add" disabled>+ Add reward</button><small>Weights −100…+100; absolute shares normalize to 100%. Fourteen distinct metrics maximum. The current vehicle runner accepts these metrics; choose them for the behavior you want to train.</small>
|
| 87 |
</details></div>
|
| 88 |
<details id="run_settings"><summary>Run settings · timing and elimination</summary><div class="row"><div><label for="control_hz">Decisions per second</label><select id="control_hz" disabled><option>60</option><option selected>30</option><option>15</option></select></div><div><label for="workers">Parallel simulations</label><select id="workers" disabled><option>1</option><option selected>1</option><option>2</option><option>3</option><option>4</option></select></div></div><small>Physics runs at 60 Hz. CPU jobs can use 1–4 worker threads.</small><div id="seed_group"><label for="seed">Random seed</label><input id="seed" type="number" min="0" max="2147483647" value="42105" disabled></div><label><input id="collision" type="checkbox" disabled> Eliminate on wall contact</label><label><input id="idle" type="checkbox" disabled> Eliminate when stuck</label><label for="clean_min_laps">Minimum forward laps for a clean-lap winner</label><input id="clean_min_laps" type="number" min="1" max="100" value="2"><small>Winner classification only. Zero physical contacts are required. This does not change your reward recipe.</small></details></div><div id="libraries" class="card"><h3>Recipes · A–J · saved in this browser</h3>
|
| 89 |
<p>Each recipe contains the training settings and reward mix. Tracks, networks and results stay out of the recipe library. Saving a recipe sends nothing to the server. Settings are sent when you start a run.</p>
|
|
|
|
| 93 |
<button id="recipe_clear">Clear saved recipes and batches on this device</button><p id="recipe_feedback" role="status" aria-live="polite"></p>
|
| 94 |
<p>Recipes are specific to this browser and site. Clearing site data removes them. Export a backup to move them between the local runner and HF, or use the connected private bucket controls on Drive & view. Browser privacy settings may prevent local saving.</p>
|
| 95 |
<details><summary>Advanced recipe JSON</summary><textarea id="recipe_draft" rows="12" style="width:100%" aria-label="Recipe settings JSON"></textarea><button id="recipe_apply_draft">Apply JSON to form</button></details></div></section>
|
| 96 |
+
<section id="batch" class="pane" hidden><div class="card"><h2>Training batches · A–J</h2><p>Choose files and CPU/GPU on Drive & view, then start a saved batch here. Up to 20 runs; each step uses the selected backend limit (CPU 4,000,000; CUDA 8,000,000 vehicle-ticks) and 32,768 analysis rows. Recipes freeze at start. Each server result is automatically deleted after verified receipt. The last candidate is kept in browser memory; connect a bucket to save each step. Stop prevents the next run; the current run finishes.</p><label for="batch_slot">Batch slot</label><select id="batch_slot" disabled><option>A</option><option>B</option><option>C</option><option>D</option><option>E</option><option>F</option><option>G</option><option>H</option><option>I</option><option>J</option></select><label for="batch_name">Display name</label><input id="batch_name" type="text" maxlength="80" value="My curriculum" disabled><label><input id="carry" type="checkbox" checked disabled> Continue from the previous run’s candidate</label><p class="hint">Unchecked: each run starts from the original selected network. Repeated run references are allowed. Recipes freeze when the batch starts.</p><label for="batch_recipe_sequence">Recipe sequence (comma-separated slots)</label><input id="batch_recipe_sequence" value="A" maxlength="300"><label for="batch_add_recipe">Add recipe<select id="batch_add_recipe"><option>A</option><option>B</option><option>C</option><option>D</option><option>E</option><option>F</option><option>G</option><option>H</option><option>I</option><option>J</option></select></label><button id="queue_add" disabled>+ Add run</button><div class="toolbar"><button id="batch_save" disabled>Save batch</button><button id="batch_load" disabled>Load batch</button><button class="primary" id="batch_start" disabled>Start saved batch</button><button id="batch_stop" disabled>Stop after current run</button><button id="batch_export" disabled>Export batch receipt</button></div><div id="batch_status" role="status">No batch selected.</div><div id="batch_history"></div><div id="batches_feedback" role="status" aria-live="polite"></div></div></section>
|
| 97 |
<section id="editor" class="pane" hidden><div class="card"><h2>Network editor</h2><p class="hint">An original editor inspired by <a href="https://the-mars-guy.github.io/AI-Learns-To-Drive-Tool/" target="_blank" rel="noopener">Mars’s network creator</a>. Create fresh networks with the selected vehicle’s game-legal controls, or use its largest original-game shape. Loaded networks keep their original vehicle, output order and sensor parameters. Unsupported files can still be inspected without changing the original.</p><div class="toolbar"><label for="editor_file">Load an existing .evolution file</label><input id="editor_file" type="file" accept=".evolution"><button id="editor_import" disabled>Inspect selected network</button><button id="editor_create" disabled>Create fresh network</button><button id="editor_apply" disabled>Use edited network</button><a id="editor_download" hidden download>Download .evolution</a></div><div class="row"><div><label for="editor_name">Network name</label><input id="editor_name" type="text" maxlength="80" value="Fresh network" disabled><label for="editor_vehicle">Vehicle type for fresh networks</label><select id="editor_vehicle" disabled><option value="0">Formula</option><option value="1">Rally</option><option value="2">Snowmobile</option><option value="3">Truck</option></select><small>This selector applies only when you create a fresh network. Imported vehicle type is retained and shown with the current network.</small><label for="editor_preset">Fresh network preset</label><select id="editor_preset" disabled><option id="editor_small_label" value="small">Small starter · 6 inputs, 4 hidden nodes, 5 outputs</option><option id="editor_maximum_label" value="maximum">Largest game network · 20 inputs, 10 × 16 hidden nodes, 5 outputs</option><option value="custom">Custom</option></select><label for="editor_shape">Input and hidden layer sizes</label><input id="editor_shape" type="text" value="6,4" disabled><small id="editor_shape_hint">Enter 1–20 inputs, followed by 0–10 hidden layers of 1–16 nodes. Output width follows the selected controls. Maximum shape: 20 → ten layers of 16 → 5 (2,869 parameters).</small><fieldset id="editor_outputs" disabled><legend id="editor_output_legend">Fresh network outputs</legend><div class="output-options"><label><input id="editor_output_0" type="checkbox" value="0" checked disabled> Steering (0)</label><label><input id="editor_output_1" type="checkbox" value="1" checked disabled> Acceleration (1)</label><label><input id="editor_output_2" type="checkbox" value="2" checked disabled> Handbrake (2)</label><label><input id="editor_output_3" type="checkbox" value="3" checked disabled> Brake (3)</label><label><input id="editor_output_4" type="checkbox" value="4" checked disabled> Boost (4)</label></div><small id="editor_output_hint">Steering and Acceleration are required. Fresh output IDs preserve this order.</small></fieldset></div><div><label for="editor_seed">Initialization seed</label><input id="editor_seed" type="number" min="0" max="2147483647" value="42105" disabled><label><input id="editor_xavier" type="checkbox" checked disabled> Seeded Xavier uniform weights (browser algorithm)</label><label><input id="editor_bias" type="checkbox" disabled> Xavier uniform biases</label><small>Unchecked: zeros. Biases default to zero.</small></div></div><p id="editor_status" role="status">Choose a file or create a fresh network. Editor changes are browser-local until you explicitly start a run.</p><p id="editor_metadata" class="hint"></p><p id="editor_legality" class="hint"></p><details id="editor_history_panel"><summary>Training history · recorded generations</summary><div class="table-scroll"><table><thead><tr><th>Record</th><th>Time</th><th>Mutation</th><th>Best score</th><th>Best lap</th></tr></thead><tbody id="editor_history"></tbody></table></div><details><summary>Full saved training metadata</summary><pre id="editor_stats"></pre></details></details><canvas aria-label="Network architecture preview" id="network_map" width="1100" height="450"></canvas><details id="weight_values"><summary>Individual weight and bias values</summary><div class="table-scroll" style="max-height:420px;overflow:auto"><table><thead><tr><th>Connection / bias</th><th>Value</th></tr></thead><tbody id="editor_parameters"></tbody></table></div></details><details><summary>Weights, biases and input contract</summary><pre id="editor_contract"></pre><label for="editor_json">NeuralNetwork JSON · editable weights and biases</label><textarea id="editor_json" rows="16" style="width:100%;box-sizing:border-box;background:#0c1725;color:#e5edf5" disabled></textarea></details></div></section><script>/* Settings-only recipe contract. Storage is injected; no network or job data. */
|
| 98 |
(function(root){
|
| 99 |
const KEY='tiga.ald.recipes.v1';
|
|
|
|
| 111 |
function settings(value){
|
| 112 |
if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).some(k=>!Object.hasOwn(defaults,k)))throw Error('Unexpected settings fields');
|
| 113 |
const clean={...defaults,...value};
|
| 114 |
+
for(const [key,min,max,integer] of [['population',1,1024,true],['generations',1,2000,true],['ticks',1,18000,true],['mutation',0,4.2,false],['mutation_end',0,4.2,false],['seed',0,2147483647,true],['parents',1,1024,true],['elite',0,1024,true],['decay',0,1,false],['workers',1,4,true]]){
|
| 115 |
const v=clean[key];if(typeof v!=='number'||!Number.isFinite(v)||v<min||v>max||(integer&&!Number.isInteger(v)))throw Error('Invalid '+key);
|
| 116 |
}
|
| 117 |
for(const [key,choices] of [['schedule',['fixed','linear','geometric']],['selection',['best','tournament','roulette']],['crossover',['none','single_point','uniform']],['control_hz',[60,30,15]]])if(!choices.includes(clean[key]))throw Error('Invalid '+key);
|
|
|
|
| 120 |
for(const [key,max] of [['layers',10],['inputs',19],['outputs',4]]){const values=clean.freeze[key];if(!Array.isArray(values)||values.some(v=>!Number.isInteger(v)||v<(key==='layers'?1:0)||v>max)||new Set(values).size!==values.length)throw Error('Invalid frozen '+key);clean.freeze[key]=[...values].sort((a,b)=>a-b);}
|
| 121 |
if(clean.parents>clean.population||clean.elite>clean.population)throw Error('Parents/elites exceed population');
|
| 122 |
if(clean.mutation<=0||(clean.schedule!=='fixed'&&clean.mutation_end<=0))throw Error('This training runner requires positive mutation rates, including when continuing an uploaded network');
|
| 123 |
+
if(!Array.isArray(clean.rewards)||!clean.rewards.length||clean.rewards.length>api.metrics.length)throw Error('Use one to fourteen distinct rewards');
|
| 124 |
const seen=new Set();
|
| 125 |
clean.rewards=clean.rewards.map(r=>{
|
| 126 |
exact(r,['metric','type','weight']);
|
|
|
|
| 161 |
</script><script>/* Frozen settings-only plans; execution and stop checks are injected. */
|
| 162 |
(function(root){
|
| 163 |
const recipes=typeof module!=='undefined'&&module.exports?require('./recipes.cjs'):root.RecipeLibrary;
|
| 164 |
+
function plan(book,slot,workLimit=4000000){
|
| 165 |
const library=recipes.validate(book),batch=library.batches.find(b=>b.slot===slot);
|
| 166 |
if(!batch)throw Error('Save or load a batch first');
|
| 167 |
if(batch.runs.length>20)throw Error('Execution limit: 20 runs per batch');
|
| 168 |
const runs=batch.runs.map(slot=>JSON.parse(JSON.stringify(library.recipes.find(r=>r.slot===slot))));
|
| 169 |
const work=runs.reduce((n,r)=>n+r.settings.population*r.settings.generations*r.settings.ticks,0);
|
| 170 |
+
if(runs.some(r=>r.settings.population*r.settings.generations*r.settings.ticks>workLimit||r.settings.population*r.settings.generations>32768))throw Error('A batch step exceeds this backend’s vehicle-tick or analysis-row limit; split that recipe into shorter runs');
|
| 171 |
return {slot:batch.slot,name:batch.name,carry:batch.carry,runs,work};
|
| 172 |
}
|
| 173 |
async function execute(plan,original,process,{stopped=()=>false,onStep=()=>{}}={}){
|
|
|
|
| 333 |
const p=Number(q('population').value),g=Number(q('generations').value),t=Number(q('training_ticks').value),box=q('work_budget');
|
| 334 |
if(q('run_mode')?.value==='evaluate'){box.textContent='Frozen evaluation uses one car and one generation; the training population and generation count do not apply.';box.style.color='';return;}
|
| 335 |
if(![p,g,t].every(n=>Number.isInteger(n)&&n>0)){box.textContent='Enter positive whole numbers to see the run size.';box.style.color='';return;}
|
| 336 |
+
const work=p*g*t,rows=p*g,maximum=q('backend')?.value==='gpu'?8000000:4000000,maxGenerations=Math.min(Math.floor(maximum/(p*t)),Math.floor(32768/p));
|
| 337 |
+
box.textContent='Planned work: '+work.toLocaleString()+' / '+maximum.toLocaleString()+' vehicle-ticks; '+rows.toLocaleString()+' / 32,768 car-generation rows'+(work>maximum||rows>32768?'. Too large; at '+p+' cars and '+t+' ticks, '+(maxGenerations?'use at most '+maxGenerations+' generations':'reduce cars or ticks first')+'.':'.');
|
| 338 |
+
box.style.color=work>maximum||rows>32768?'#ffb5a2':'';
|
| 339 |
}
|
| 340 |
function executableSettings(){
|
| 341 |
+
const data=currentSettings(),work=data.population*data.generations*data.ticks,rows=data.population*data.generations,maximum=q('backend')?.value==='gpu'?8000000:4000000;
|
| 342 |
+
if(q('run_mode')?.value!=='evaluate'&&(work>maximum||rows>32768)){
|
| 343 |
+
const maxGenerations=Math.min(Math.floor(maximum/(data.population*data.ticks)),Math.floor(32768/data.population));
|
| 344 |
+
throw Object.assign(Error('This recipe needs '+work.toLocaleString()+' vehicle-ticks and '+rows.toLocaleString()+' car-generation rows (limits '+maximum.toLocaleString()+' and 32,768). At '+data.population+' cars and '+data.ticks+' ticks, '+(maxGenerations?'use at most '+maxGenerations+' generations':'reduce cars or ticks first')+'.'),{code:'TRAINING_WORK_LIMIT'});
|
| 345 |
}
|
| 346 |
return data;
|
| 347 |
}
|
|
|
|
| 377 |
};
|
| 378 |
for(const id of ['population','generations','training_ticks'])q(id).addEventListener('input',updateWorkBudget);
|
| 379 |
q('run_mode')?.addEventListener('change',updateWorkBudget);
|
| 380 |
+
q('backend')?.addEventListener('change',updateWorkBudget);
|
| 381 |
+
q('add').onclick=()=>{if(q('rewards').children.length>=api.metrics.length)return;const used=rewards().map(r=>r.metric);rewardRow({metric:api.metrics.find(m=>!used.includes(m)),type:'default',weight:1});q('reward_preset').value='custom';};
|
| 382 |
q('reward_preset').onchange=()=>{
|
| 383 |
const key=q('reward_preset').value;if(key==='custom')return;
|
| 384 |
const rows={distance:[['total_score',100]],center:[['total_score',100],['total_distance_from_center',-60],['total_distance_from_wall',10],['collision_count',-100],['best_lap_performance',5]],clean:[['total_score',100],['collision_count',-30]],lap:[['best_lap_performance',100]]};
|
|
|
|
| 402 |
if(!store)throw Error('Browser storage unavailable');store.save(book);
|
| 403 |
message('Imported '+book.recipes.length+' recipes and '+book.batches.length+' batches from '+source+'. No run was started.');return true;
|
| 404 |
};
|
| 405 |
+
window.getBatchPlan=()=>window.BatchTraining.plan(read(),q('batch_slot').value,q('backend')?.value==='gpu'?8000000:4000000);
|
| 406 |
const action=fn=>async()=>{try{await fn();}catch(error){message('Not completed: '+error.message);}};
|
| 407 |
q('starter_settings_reset').onclick=action(async()=>{
|
| 408 |
if(!(await window.appConfirm('Replace the current form settings and reward mix with the first-run starter? Saved recipes will not change.',{heading:'Apply first-run settings',confirmLabel:'Apply starter'})))return;
|
portable_bundle.py
CHANGED
|
@@ -98,8 +98,8 @@ def _analysis_jsonl_rows(raw: bytes) -> int:
|
|
| 98 |
"raw_metrics", "channels", "selection_fitness", "objectives", "weights_sha256"}
|
| 99 |
if set(row) != required or row.get("schema") != 1:
|
| 100 |
raise BundleError("analysis descriptor row fields are unsupported")
|
| 101 |
-
if (type(row["generation"]) is not int or not 1 <= row["generation"] <=
|
| 102 |
-
or type(row["car"]) is not int or not 0 <= row["car"] <
|
| 103 |
or row["candidate_id"] != f'g{row["generation"]:06}-c{row["car"]:06}'
|
| 104 |
or type(row["ticks_elapsed"]) is not int or not 0 <= row["ticks_elapsed"] <= 18_012
|
| 105 |
or type(row["stats_update_count"]) is not int or not 0 <= row["stats_update_count"] <= 18_012
|
|
@@ -129,7 +129,7 @@ def _analysis_jsonl_rows(raw: bytes) -> int:
|
|
| 129 |
if (not isinstance(objectives, dict) or set(objectives) != objective_keys
|
| 130 |
or any(objectives[k] is not None and not _is_number(objectives[k], 0, 1e12)
|
| 131 |
for k in ("best_lap_seconds", "total_actual_distance"))
|
| 132 |
-
or any(type(objectives[k]) is not int or not 0 <= objectives[k] <=
|
| 133 |
for k in ("forward_laps", "physical_contacts", "collision_updates"))
|
| 134 |
or type(objectives["active"]) is not bool):
|
| 135 |
raise BundleError("analysis descriptor objectives are invalid")
|
|
@@ -228,14 +228,14 @@ def load_analysis_archive(raw: bytes) -> tuple[dict[str, Any], dict[str, bytes]]
|
|
| 228 |
if not isinstance(observations, dict) or not isinstance(descriptors, dict):
|
| 229 |
raise BundleError("analysis observation or descriptor metadata is missing")
|
| 230 |
if (type(observations.get("cars")) is not int or observations["cars"] != rows
|
| 231 |
-
or type(observations.get("generations")) is not int or not 1 <= observations["generations"] <=
|
| 232 |
or type(observations.get("raw_metric_count")) is not int or observations["raw_metric_count"] != 14
|
| 233 |
or type(observations.get("reward_channel_count")) is not int or observations["reward_channel_count"] != 18):
|
| 234 |
raise BundleError("analysis observation counts do not match the descriptor rows")
|
| 235 |
if rows % observations["generations"]:
|
| 236 |
raise BundleError("analysis descriptor rows are not a whole number of generations")
|
| 237 |
expected_generation_cars = rows // observations["generations"]
|
| 238 |
-
if not 1 <= expected_generation_cars <=
|
| 239 |
raise BundleError("analysis car population is outside its supported bounds")
|
| 240 |
if (descriptors.get("name") != "descriptors.jsonl"
|
| 241 |
or descriptors.get("bytes") != len(inner["descriptors.jsonl"])
|
|
@@ -284,8 +284,8 @@ def load_analysis_archive(raw: bytes) -> tuple[dict[str, Any], dict[str, bytes]]
|
|
| 284 |
or settings.get("idle") != conditions["idle"]
|
| 285 |
or settings.get("clean_min_laps") != conditions["clean_min_laps"]):
|
| 286 |
raise BundleError("analysis settings do not match its conditions")
|
| 287 |
-
if (type(settings.get("population")) is not int or not 1 <= settings["population"] <=
|
| 288 |
-
or type(settings.get("generations")) is not int or not 1 <= settings["generations"] <=
|
| 289 |
or observations["generations"] != (1 if mode == "evaluate" else settings["generations"])
|
| 290 |
or expected_generation_cars != (1 if mode == "evaluate" else settings["population"])):
|
| 291 |
raise BundleError("analysis observation dimensions do not match its validated settings")
|
|
@@ -306,7 +306,7 @@ def load_analysis_archive(raw: bytes) -> tuple[dict[str, Any], dict[str, bytes]]
|
|
| 306 |
or type(winner["car"]) is not int or not 0 <= winner["car"] < expected_generation_cars
|
| 307 |
or not _is_number(winner["selection_fitness"], -1e12, 1e12)
|
| 308 |
or (winner["best_lap_seconds"] is not None and not _is_number(winner["best_lap_seconds"], 0, 1e12))
|
| 309 |
-
or any(type(winner[k]) is not int or not 0 <= winner[k] <=
|
| 310 |
for k in ("forward_laps", "physical_contacts", "collision_updates"))
|
| 311 |
or (winner["total_actual_distance"] is not None and not _is_number(winner["total_actual_distance"], 0, 1e12))
|
| 312 |
or winner["reason"] is not None):
|
|
@@ -523,8 +523,8 @@ def validate_settings(value: Any) -> dict[str, Any]:
|
|
| 523 |
if "freeze" in settings:
|
| 524 |
validate_freeze(settings["freeze"])
|
| 525 |
bounds = {
|
| 526 |
-
"population": (1,
|
| 527 |
-
"seed": (0, 2147483647), "parents": (1,
|
| 528 |
"clean_min_laps": (1, 100),
|
| 529 |
}
|
| 530 |
if any(not _is_int(settings[k], *bounds[k]) for k in bounds):
|
|
@@ -559,8 +559,8 @@ def validate_settings(value: Any) -> dict[str, Any]:
|
|
| 559 |
if settings["parents"] > settings["population"] or settings["elite"] > settings["population"]:
|
| 560 |
raise BundleError("recipe parents or elite exceeds population")
|
| 561 |
rewards = settings["rewards"]
|
| 562 |
-
if not isinstance(rewards, list) or not 1 <= len(rewards) <=
|
| 563 |
-
raise BundleError("recipe must have between one and
|
| 564 |
seen: set[str] = set()
|
| 565 |
for reward in rewards:
|
| 566 |
_strict_keys(reward, {"metric", "type", "weight"}, {"metric", "type", "weight"}, "reward")
|
|
@@ -618,7 +618,7 @@ def validate_receipt(value: Any, *, allow_analysis_summary: bool = False) -> dic
|
|
| 618 |
if not _is_int(report["vehicle_type"], 0, 3) or report["vehicle_name"] != ("Formula", "Rally", "Snowmobile", "Truck")[report["vehicle_type"]]:
|
| 619 |
raise BundleError("run receipt vehicle is invalid")
|
| 620 |
for key in ("generations_completed", "generations_requested", "population", "simulated_ticks", "requested_ticks"):
|
| 621 |
-
if not _is_int(report[key], 0,
|
| 622 |
raise BundleError("run receipt count is invalid")
|
| 623 |
if report["generations_requested"] < 1 or report["population"] < 1 or report["requested_ticks"] < 1:
|
| 624 |
raise BundleError("run receipt requested counts and population must be positive")
|
|
@@ -631,7 +631,7 @@ def validate_receipt(value: Any, *, allow_analysis_summary: bool = False) -> dic
|
|
| 631 |
raise BundleError("run receipt best lap is invalid")
|
| 632 |
if report["best_lap_seconds"] == 0:
|
| 633 |
raise BundleError("run receipt best lap must be positive")
|
| 634 |
-
if report["lapped_cars"] is not None and not _is_int(report["lapped_cars"], 0,
|
| 635 |
raise BundleError("run receipt lapped car count is invalid")
|
| 636 |
if (report["candidate_validated"] is not True or not isinstance(report["execution_backend"], str)
|
| 637 |
or report["execution_backend"] not in {"cpu", "cuda"}):
|
|
@@ -646,13 +646,13 @@ def validate_receipt(value: Any, *, allow_analysis_summary: bool = False) -> dic
|
|
| 646 |
raise BundleError("run receipt summary does not match its validated settings")
|
| 647 |
if "history" in report:
|
| 648 |
history = report["history"]
|
| 649 |
-
if not isinstance(history, list) or len(history) >
|
| 650 |
-
raise BundleError("run receipt history must contain at most
|
| 651 |
if len(history) != report["generations_completed"]:
|
| 652 |
raise BundleError("run receipt history length does not match completed generations")
|
| 653 |
for index, row in enumerate(history, 1):
|
| 654 |
row = _strict_keys(row, HISTORY_ROW_KEYS, HISTORY_ROW_KEYS, "run receipt history row")
|
| 655 |
-
if not _is_int(row["generation"], 1,
|
| 656 |
raise BundleError("run receipt history generations must be ordered from one")
|
| 657 |
if not _is_number(row["mutation"], 0, 4.2):
|
| 658 |
raise BundleError("run receipt history mutation is invalid")
|
|
@@ -663,9 +663,9 @@ def validate_receipt(value: Any, *, allow_analysis_summary: bool = False) -> dic
|
|
| 663 |
raise BundleError("run receipt history lap is invalid")
|
| 664 |
if row["best_lap_seconds"] == 0:
|
| 665 |
raise BundleError("run receipt history lap must be positive")
|
| 666 |
-
if row["lapped_cars"] is not None and not _is_int(row["lapped_cars"], 0,
|
| 667 |
raise BundleError("run receipt history lapped-car count is invalid")
|
| 668 |
-
if not _is_int(row["simulated_ticks"], 0,
|
| 669 |
raise BundleError("run receipt history tick count is invalid")
|
| 670 |
return receipt
|
| 671 |
|
|
|
|
| 98 |
"raw_metrics", "channels", "selection_fitness", "objectives", "weights_sha256"}
|
| 99 |
if set(row) != required or row.get("schema") != 1:
|
| 100 |
raise BundleError("analysis descriptor row fields are unsupported")
|
| 101 |
+
if (type(row["generation"]) is not int or not 1 <= row["generation"] <= 2000
|
| 102 |
+
or type(row["car"]) is not int or not 0 <= row["car"] < 1024
|
| 103 |
or row["candidate_id"] != f'g{row["generation"]:06}-c{row["car"]:06}'
|
| 104 |
or type(row["ticks_elapsed"]) is not int or not 0 <= row["ticks_elapsed"] <= 18_012
|
| 105 |
or type(row["stats_update_count"]) is not int or not 0 <= row["stats_update_count"] <= 18_012
|
|
|
|
| 129 |
if (not isinstance(objectives, dict) or set(objectives) != objective_keys
|
| 130 |
or any(objectives[k] is not None and not _is_number(objectives[k], 0, 1e12)
|
| 131 |
for k in ("best_lap_seconds", "total_actual_distance"))
|
| 132 |
+
or any(type(objectives[k]) is not int or not 0 <= objectives[k] <= 8_000_000
|
| 133 |
for k in ("forward_laps", "physical_contacts", "collision_updates"))
|
| 134 |
or type(objectives["active"]) is not bool):
|
| 135 |
raise BundleError("analysis descriptor objectives are invalid")
|
|
|
|
| 228 |
if not isinstance(observations, dict) or not isinstance(descriptors, dict):
|
| 229 |
raise BundleError("analysis observation or descriptor metadata is missing")
|
| 230 |
if (type(observations.get("cars")) is not int or observations["cars"] != rows
|
| 231 |
+
or type(observations.get("generations")) is not int or not 1 <= observations["generations"] <= 2000
|
| 232 |
or type(observations.get("raw_metric_count")) is not int or observations["raw_metric_count"] != 14
|
| 233 |
or type(observations.get("reward_channel_count")) is not int or observations["reward_channel_count"] != 18):
|
| 234 |
raise BundleError("analysis observation counts do not match the descriptor rows")
|
| 235 |
if rows % observations["generations"]:
|
| 236 |
raise BundleError("analysis descriptor rows are not a whole number of generations")
|
| 237 |
expected_generation_cars = rows // observations["generations"]
|
| 238 |
+
if not 1 <= expected_generation_cars <= 1024:
|
| 239 |
raise BundleError("analysis car population is outside its supported bounds")
|
| 240 |
if (descriptors.get("name") != "descriptors.jsonl"
|
| 241 |
or descriptors.get("bytes") != len(inner["descriptors.jsonl"])
|
|
|
|
| 284 |
or settings.get("idle") != conditions["idle"]
|
| 285 |
or settings.get("clean_min_laps") != conditions["clean_min_laps"]):
|
| 286 |
raise BundleError("analysis settings do not match its conditions")
|
| 287 |
+
if (type(settings.get("population")) is not int or not 1 <= settings["population"] <= 1024
|
| 288 |
+
or type(settings.get("generations")) is not int or not 1 <= settings["generations"] <= 2000
|
| 289 |
or observations["generations"] != (1 if mode == "evaluate" else settings["generations"])
|
| 290 |
or expected_generation_cars != (1 if mode == "evaluate" else settings["population"])):
|
| 291 |
raise BundleError("analysis observation dimensions do not match its validated settings")
|
|
|
|
| 306 |
or type(winner["car"]) is not int or not 0 <= winner["car"] < expected_generation_cars
|
| 307 |
or not _is_number(winner["selection_fitness"], -1e12, 1e12)
|
| 308 |
or (winner["best_lap_seconds"] is not None and not _is_number(winner["best_lap_seconds"], 0, 1e12))
|
| 309 |
+
or any(type(winner[k]) is not int or not 0 <= winner[k] <= 8_000_000
|
| 310 |
for k in ("forward_laps", "physical_contacts", "collision_updates"))
|
| 311 |
or (winner["total_actual_distance"] is not None and not _is_number(winner["total_actual_distance"], 0, 1e12))
|
| 312 |
or winner["reason"] is not None):
|
|
|
|
| 523 |
if "freeze" in settings:
|
| 524 |
validate_freeze(settings["freeze"])
|
| 525 |
bounds = {
|
| 526 |
+
"population": (1, 1024), "generations": (1, 2000), "ticks": (1, 18000),
|
| 527 |
+
"seed": (0, 2147483647), "parents": (1, 1024), "elite": (0, 1024), "workers": (1, 4),
|
| 528 |
"clean_min_laps": (1, 100),
|
| 529 |
}
|
| 530 |
if any(not _is_int(settings[k], *bounds[k]) for k in bounds):
|
|
|
|
| 559 |
if settings["parents"] > settings["population"] or settings["elite"] > settings["population"]:
|
| 560 |
raise BundleError("recipe parents or elite exceeds population")
|
| 561 |
rewards = settings["rewards"]
|
| 562 |
+
if not isinstance(rewards, list) or not 1 <= len(rewards) <= len(METRICS):
|
| 563 |
+
raise BundleError("recipe must have between one and fourteen distinct rewards")
|
| 564 |
seen: set[str] = set()
|
| 565 |
for reward in rewards:
|
| 566 |
_strict_keys(reward, {"metric", "type", "weight"}, {"metric", "type", "weight"}, "reward")
|
|
|
|
| 618 |
if not _is_int(report["vehicle_type"], 0, 3) or report["vehicle_name"] != ("Formula", "Rally", "Snowmobile", "Truck")[report["vehicle_type"]]:
|
| 619 |
raise BundleError("run receipt vehicle is invalid")
|
| 620 |
for key in ("generations_completed", "generations_requested", "population", "simulated_ticks", "requested_ticks"):
|
| 621 |
+
if not _is_int(report[key], 0, 8_000_000):
|
| 622 |
raise BundleError("run receipt count is invalid")
|
| 623 |
if report["generations_requested"] < 1 or report["population"] < 1 or report["requested_ticks"] < 1:
|
| 624 |
raise BundleError("run receipt requested counts and population must be positive")
|
|
|
|
| 631 |
raise BundleError("run receipt best lap is invalid")
|
| 632 |
if report["best_lap_seconds"] == 0:
|
| 633 |
raise BundleError("run receipt best lap must be positive")
|
| 634 |
+
if report["lapped_cars"] is not None and not _is_int(report["lapped_cars"], 0, 1024):
|
| 635 |
raise BundleError("run receipt lapped car count is invalid")
|
| 636 |
if (report["candidate_validated"] is not True or not isinstance(report["execution_backend"], str)
|
| 637 |
or report["execution_backend"] not in {"cpu", "cuda"}):
|
|
|
|
| 646 |
raise BundleError("run receipt summary does not match its validated settings")
|
| 647 |
if "history" in report:
|
| 648 |
history = report["history"]
|
| 649 |
+
if not isinstance(history, list) or len(history) > 2000:
|
| 650 |
+
raise BundleError("run receipt history must contain at most 2000 rows")
|
| 651 |
if len(history) != report["generations_completed"]:
|
| 652 |
raise BundleError("run receipt history length does not match completed generations")
|
| 653 |
for index, row in enumerate(history, 1):
|
| 654 |
row = _strict_keys(row, HISTORY_ROW_KEYS, HISTORY_ROW_KEYS, "run receipt history row")
|
| 655 |
+
if not _is_int(row["generation"], 1, 2000) or row["generation"] != index:
|
| 656 |
raise BundleError("run receipt history generations must be ordered from one")
|
| 657 |
if not _is_number(row["mutation"], 0, 4.2):
|
| 658 |
raise BundleError("run receipt history mutation is invalid")
|
|
|
|
| 663 |
raise BundleError("run receipt history lap is invalid")
|
| 664 |
if row["best_lap_seconds"] == 0:
|
| 665 |
raise BundleError("run receipt history lap must be positive")
|
| 666 |
+
if row["lapped_cars"] is not None and not _is_int(row["lapped_cars"], 0, 1024):
|
| 667 |
raise BundleError("run receipt history lapped-car count is invalid")
|
| 668 |
+
if not _is_int(row["simulated_ticks"], 0, 8_000_000):
|
| 669 |
raise BundleError("run receipt history tick count is invalid")
|
| 670 |
return receipt
|
| 671 |
|
star_check.py
CHANGED
|
@@ -282,7 +282,7 @@ def _evaluate(challenge, row, meta, lap_score, elimination):
|
|
| 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,
|
| 286 |
if collision_limit is None or collisions is None:
|
| 287 |
return None
|
| 288 |
# Source: candidates stay within target + 5 score units, must cross
|
|
@@ -422,7 +422,7 @@ def resolve_check(track, template, config, observations, *, network_sha256, trac
|
|
| 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,
|
| 426 |
normalized["collision_count"] = collisions
|
| 427 |
required.append(collisions is not None)
|
| 428 |
if not all(required):
|
|
|
|
| 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, 8_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
|
|
|
|
| 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, 8_000_000)
|
| 426 |
normalized["collision_count"] = collisions
|
| 427 |
required.append(collisions is not None)
|
| 428 |
if not all(required):
|
training_config.py
CHANGED
|
@@ -26,13 +26,13 @@ def validate_freeze(value):
|
|
| 26 |
result[key]=items[:]
|
| 27 |
return result
|
| 28 |
|
| 29 |
-
def validate_config(value=None):
|
| 30 |
if value is None:return copy.deepcopy(DEFAULTS)
|
| 31 |
if not isinstance(value,dict) or set(value)-set(DEFAULTS):raise ValueError('Unexpected training settings')
|
| 32 |
c={**copy.deepcopy(DEFAULTS),**value}
|
| 33 |
-
bounds={'population':(1,
|
| 34 |
'mutation':(0,4.2,False),'mutation_end':(0,4.2,False),'seed':(0,2147483647,True),
|
| 35 |
-
'clean_min_laps':(1,100,True),'parents':(1,
|
| 36 |
for key,(low,high,integer) in bounds.items():
|
| 37 |
v=c[key]
|
| 38 |
if type(v) not in (int,float) or not math.isfinite(v) or not low<=v<=high or (integer and type(v) is not int):raise ValueError('Invalid '+key)
|
|
@@ -47,7 +47,7 @@ def validate_config(value=None):
|
|
| 47 |
# Echo the rate actually passed to the native CLI, not an unused form value.
|
| 48 |
if c['schedule']=='fixed':c['mutation_end']=c['mutation']
|
| 49 |
rewards=c['rewards']
|
| 50 |
-
if not isinstance(rewards,list) or not 1<=len(rewards)<=
|
| 51 |
seen=set();clean=[]
|
| 52 |
for r in rewards:
|
| 53 |
if not isinstance(r,dict) or set(r)!= {'metric','type','weight'}:raise ValueError('Unexpected reward fields')
|
|
@@ -57,5 +57,6 @@ def validate_config(value=None):
|
|
| 57 |
c['rewards']=clean
|
| 58 |
if c['mode']=='evaluate':
|
| 59 |
c.update(population=1,generations=1,mutation=0,mutation_end=0,decay=0,adaptive_mutation=False,parents=1,elite=0,crossover='none',selection='best',schedule='fixed',freeze={'layers':[],'inputs':[],'outputs':[]})
|
| 60 |
-
if c['population']*c['generations']
|
|
|
|
| 61 |
return c
|
|
|
|
| 26 |
result[key]=items[:]
|
| 27 |
return result
|
| 28 |
|
| 29 |
+
def validate_config(value=None, *, work_limit=4_000_000):
|
| 30 |
if value is None:return copy.deepcopy(DEFAULTS)
|
| 31 |
if not isinstance(value,dict) or set(value)-set(DEFAULTS):raise ValueError('Unexpected training settings')
|
| 32 |
c={**copy.deepcopy(DEFAULTS),**value}
|
| 33 |
+
bounds={'population':(1,1024,True),'generations':(1,2000,True),'ticks':(1,18000,True),
|
| 34 |
'mutation':(0,4.2,False),'mutation_end':(0,4.2,False),'seed':(0,2147483647,True),
|
| 35 |
+
'clean_min_laps':(1,100,True),'parents':(1,1024,True),'elite':(0,1024,True),'decay':(0,1,False),'workers':(1,4,True)}
|
| 36 |
for key,(low,high,integer) in bounds.items():
|
| 37 |
v=c[key]
|
| 38 |
if type(v) not in (int,float) or not math.isfinite(v) or not low<=v<=high or (integer and type(v) is not int):raise ValueError('Invalid '+key)
|
|
|
|
| 47 |
# Echo the rate actually passed to the native CLI, not an unused form value.
|
| 48 |
if c['schedule']=='fixed':c['mutation_end']=c['mutation']
|
| 49 |
rewards=c['rewards']
|
| 50 |
+
if not isinstance(rewards,list) or not 1<=len(rewards)<=len(METRICS):raise ValueError('Use one or more distinct supported rewards')
|
| 51 |
seen=set();clean=[]
|
| 52 |
for r in rewards:
|
| 53 |
if not isinstance(r,dict) or set(r)!= {'metric','type','weight'}:raise ValueError('Unexpected reward fields')
|
|
|
|
| 57 |
c['rewards']=clean
|
| 58 |
if c['mode']=='evaluate':
|
| 59 |
c.update(population=1,generations=1,mutation=0,mutation_end=0,decay=0,adaptive_mutation=False,parents=1,elite=0,crossover='none',selection='best',schedule='fixed',freeze={'layers':[],'inputs':[],'outputs':[]})
|
| 60 |
+
if c['population']*c['generations']>32_768:raise ValueError('Run exceeds 32,768 car-generation analysis rows')
|
| 61 |
+
if c['population']*c['generations']*c['ticks']>work_limit:raise ValueError('Run exceeds backend vehicle-tick limit')
|
| 62 |
return c
|
worker.py
CHANGED
|
@@ -248,7 +248,7 @@ def check_current_network(work, command, env, track, template, config, result, t
|
|
| 248 |
|
| 249 |
def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_template=None,with_report=False,gpu_library=None,config=None):
|
| 250 |
started=time.perf_counter()
|
| 251 |
-
config=validate_config(config)
|
| 252 |
track=track_input(track_bytes); template=network_input(network_bytes)
|
| 253 |
if config['mode']=='train':check_freeze_shape(config['freeze'],template['NeuralNetwork']['Shape'])
|
| 254 |
# Deployment paths are supplied by trusted application configuration, never uploads.
|
|
@@ -293,7 +293,7 @@ def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_templ
|
|
| 293 |
command.extend(('--freeze-spec',save('freeze.json',config['freeze'])))
|
| 294 |
if config['mode']=='evaluate':command.append('--evaluate-frozen')
|
| 295 |
if gpu_library is not None: command.append('--gpu')
|
| 296 |
-
subprocess.run(command,cwd=work,env=env,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=25,check=True)
|
| 297 |
if config['mode']=='train':
|
| 298 |
candidate=out/'candidate.json'
|
| 299 |
if candidate.stat().st_size>1024*1024: raise ValueError('Output too large')
|
|
|
|
| 248 |
|
| 249 |
def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_template=None,with_report=False,gpu_library=None,config=None):
|
| 250 |
started=time.perf_counter()
|
| 251 |
+
config=validate_config(config, work_limit=8_000_000 if gpu_library is not None else 4_000_000)
|
| 252 |
track=track_input(track_bytes); template=network_input(network_bytes)
|
| 253 |
if config['mode']=='train':check_freeze_shape(config['freeze'],template['NeuralNetwork']['Shape'])
|
| 254 |
# Deployment paths are supplied by trusted application configuration, never uploads.
|
|
|
|
| 293 |
command.extend(('--freeze-spec',save('freeze.json',config['freeze'])))
|
| 294 |
if config['mode']=='evaluate':command.append('--evaluate-frozen')
|
| 295 |
if gpu_library is not None: command.append('--gpu')
|
| 296 |
+
subprocess.run(command,cwd=work,env=env,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=45 if gpu_library is not None else 25,check=True)
|
| 297 |
if config['mode']=='train':
|
| 298 |
candidate=out/'candidate.json'
|
| 299 |
if candidate.stat().st_size>1024*1024: raise ValueError('Output too large')
|