TiGa-RCE commited on
Commit
2e4c103
·
verified ·
1 Parent(s): e291629

Raise measured training limits

Browse files
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": "33db7b712c5a217e9117ed988b413161a991e51c7160f701d4678368a0c8068f",
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-batch-budget",
11
- "output_sha256": "59df8e6d19a203cd17aff22264e109f6630098866c7cb61c1d8796cd1d1118ff",
12
  "packaged_frontend_files": {
13
  "assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
14
- "client.js": "33db7b712c5a217e9117ed988b413161a991e51c7160f701d4678368a0c8068f",
15
- "help.html": "d9b1e3b87641d73d4504b54ee33fa3c7d0917ea9c9c454cc632665fd41ace950",
16
  "hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
17
- "index.html": "59df8e6d19a203cd17aff22264e109f6630098866c7cb61c1d8796cd1d1118ff",
18
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
19
  "track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"
20
  },
21
- "packaged_frontend_sha256": "35fb377ec35ada7a4e20fc2da4de1502a12cac0f79fdd804dceb568cabf86de3",
22
- "status": "Local batch-budget candidate; hosted verification pending"
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": "7aba71ef147b5ea121b5d0da27cf81542653eb72744b3c7ba6058bdd64bf22e5",
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": "78ddccd56e5c217b89681c5dcbee451b6a5868398c4f75bd2b4db33c235d800f",
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": "347ad556254aa9f255209141cf37f48c58c01134318c08b7bcd6ed2caa95e7a4",
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": "33db7b712c5a217e9117ed988b413161a991e51c7160f701d4678368a0c8068f",
64
  "delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
65
- "gpu_tickets.py": "b81aadb5ed42cec9212d44da7e7bbb7b413c5bd517383cac0b49ec9b4d659c92",
66
- "help.html": "d9b1e3b87641d73d4504b54ee33fa3c7d0917ea9c9c454cc632665fd41ace950",
67
  "hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
68
  "hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
69
- "index.html": "59df8e6d19a203cd17aff22264e109f6630098866c7cb61c1d8796cd1d1118ff",
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": "b32ca0b8d79f13230fde6e914769e0af71cd44ef950d798da83499878ab28e15",
76
  "requirements.txt": "fc7e0703ad2289fd3926a36efbdce8c61f0edc00f7575a87e2b565e46588c5a4",
77
  "server.py": "1f82c9046324661d7bd4acf1cf37eef3445f985351159d88acdcaf89d1edf859",
78
- "star_check.py": "61d35bcede5dab75911b2734ff20cb38f7a06bde9fd58c2eff67f5833b260d40",
79
  "steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
80
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
81
  "track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0",
82
- "training_config.py": "59141f96e47aba5bc91e8a08d0f40384ea8c90ea0b3e5a12ad65abdad3987a8d",
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": "ebfb82560c721936520bb601e117dc697bb6a046a2fc4ef4207a80250dba917b"
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, 128, True), "generations": (1, 1000, 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, 128, True), "elite": (0, 128, True),
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) <= 5:
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>1000||!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,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>=128||!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,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>1000||!Number.isInteger(row.car)||row.car<0||row.car>=128||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,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>1000)throw Error('Generation history exceeds its 1,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>1000||!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<=128))||
1209
- !Number.isInteger(row.simulated_ticks)||row.simulated_ticks<0||row.simulated_ticks>4000000)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
  }
 
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>The runner limits a job to 4,000,000 population × generation × tick units and a 25-second training execution window. 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>
 
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. Current preview limit: population × generations × ticks ≤ 4,000,000; execution timeout 25 seconds.</small>
69
- <div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="128" value="32" disabled><small>1–128 cars in this beta. The first-run setting is 32.</small></div><div><label for="generations">Generations</label><input id="generations" type="number" min="1" max="1000" value="20" disabled><small>1–1,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,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 five 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%. Five 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,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 &amp; 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 run has its own 4,000,000 vehicle-tick limit. 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,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,128,true],['generations',1,1000,true],['ticks',1,18000,true],['mutation',0,4.2,false],['mutation_end',0,4.2,false],['seed',0,2147483647,true],['parents',1,128,true],['elite',0,128,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,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>5)throw Error('Use one to five rewards');
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>4000000))throw Error('A batch step exceeds 4,000,000 vehicle-ticks; 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,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()+' / 4,000,000 vehicle-ticks'+(work>maximum?'. 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?'#ffb5a2':'';
339
  }
340
  function executableSettings(){
341
- const data=currentSettings(),work=data.population*data.generations*data.ticks;
342
- if(q('run_mode')?.value!=='evaluate'&&work>4000000){
343
- const maxGenerations=Math.floor(4000000/(data.population*data.ticks));
344
- throw Object.assign(Error('This recipe needs '+work.toLocaleString()+' vehicle-ticks (limit 4,000,000). 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,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('add').onclick=()=>{if(q('rewards').children.length>=5)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';};
 
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 &amp; 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"] <= 1000
102
- or type(row["car"]) is not int or not 0 <= row["car"] < 128
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] <= 4_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,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"] <= 1000
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 <= 128:
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"] <= 128
288
- or type(settings.get("generations")) is not int or not 1 <= settings["generations"] <= 1000
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] <= 4_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,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, 128), "generations": (1, 1000), "ticks": (1, 18000),
527
- "seed": (0, 2147483647), "parents": (1, 128), "elite": (0, 128), "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,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) <= 5:
563
- raise BundleError("recipe must have between one and five rewards")
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, 4_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,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, 128):
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) > 1000:
650
- raise BundleError("run receipt history must contain at most 1000 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, 1000) 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,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, 128):
667
  raise BundleError("run receipt history lapped-car count is invalid")
668
- if not _is_int(row["simulated_ticks"], 0, 4_000_000):
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, 4_000_000)
286
  if collision_limit is None or collisions is None:
287
  return None
288
  # Source: candidates stay within target + 5 score units, must cross
@@ -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, 4_000_000)
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,128,True),'generations':(1,1000,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,128,True),'elite':(0,128,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,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)<=5:raise ValueError('Use one to five 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']*c['ticks']>4_000_000:raise ValueError('Run exceeds preview work limit (4,000,000 vehicle-ticks)')
 
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')