TiGa-RCE commited on
Commit
24cc077
·
verified ·
1 Parent(s): 8c7fd93

Audit single-engine UI wiring and release GPU tickets on quota failure

Browse files

Correct browser network status after batches, reward preset labels, map sizing and quota guidance. Add immediate discard for quota-rejected pending inputs; retain 120-second expiry for ambiguous failures.

Files changed (10) hide show
  1. BETA_TESTING.md +3 -0
  2. DATA_POLICY.md +4 -1
  3. RELEASE_MANIFEST.json +9 -9
  4. STORAGE_BOUNDARY.md +1 -1
  5. app.py +7 -1
  6. client.js +29 -8
  7. gpu_tickets.py +5 -0
  8. index.html +18 -15
  9. server.py +1 -1
  10. track-map.js +1 -1
BETA_TESTING.md CHANGED
@@ -37,6 +37,9 @@ Before remote testers receive the expanded interface:
37
 
38
  - Use CPU first; it does not acquire a GPU lease. GPU is explicit and has no
39
  automatic CPU fallback. A failed/quota-limited GPU call is not a successful run.
 
 
 
40
  - Check that a loaded recipe changes the echoed effective settings and generation
41
  results, not just the form. Reload and load again to check browser persistence.
42
  - Export/import a recipe backup. Imported data must not contain tracks, networks,
 
37
 
38
  - Use CPU first; it does not acquire a GPU lease. GPU is explicit and has no
39
  automatic CPU fallback. A failed/quota-limited GPU call is not a successful run.
40
+ - When HF rejects a staged GPU run for exhausted allowance, confirm the page
41
+ gives CPU/account guidance without a personal-token prompt, confirms pending
42
+ input deletion, and allows another visitor to prepare a request immediately.
43
  - Check that a loaded recipe changes the echoed effective settings and generation
44
  results, not just the form. Reload and load again to check browser persistence.
45
  - Export/import a recipe backup. Imported data must not contain tracks, networks,
DATA_POLICY.md CHANGED
@@ -19,7 +19,10 @@ See TRACK_LIBRARY.md in the release package for provenance and limitations.
19
 
20
  GPU inputs await dispatch in application memory for at most 120 seconds. The
21
  queue receives a random one-use ticket, not the uploaded track or network bytes.
22
- Inputs are released from the pending store on dispatch, expiry or shutdown.
 
 
 
23
  The framework may retain ticket and result metadata in its transient queue/session
24
  buffers; result metadata includes the temporary job access key. Framework file
25
  uploads and run history are disabled. These restrictions are not secure RAM erasure.
 
19
 
20
  GPU inputs await dispatch in application memory for at most 120 seconds. The
21
  queue receives a random one-use ticket, not the uploaded track or network bytes.
22
+ Inputs are released from the pending store on dispatch, expiry or shutdown. When
23
+ HF explicitly rejects a request for exhausted GPU allowance, the browser asks
24
+ the app to discard that pending ticket immediately and reports whether deletion
25
+ was confirmed. If that request fails, the fixed 120-second expiry still applies.
26
  The framework may retain ticket and result metadata in its transient queue/session
27
  buffers; result metadata includes the temporary job access key. Framework file
28
  uploads and run history are disabled. These restrictions are not secure RAM erasure.
RELEASE_MANIFEST.json CHANGED
@@ -2,18 +2,18 @@
2
  "backend": "cpu+zerogpu",
3
  "files": {
4
  ".gitattributes": "c2ad16718fc51c888d32efc88c8f6e23ebe5fcf6f48e2954f896ba9770f24d72",
5
- "BETA_TESTING.md": "b3c8ca006e67bea3511a9e3e8cc976991a57a14fed93085739842d5e312818b4",
6
  "BINARY_PROVENANCE.json": "1116e1757afc6c8629cb2d957e3d77380fe4f7e3186c3832d16a5a08abce6aa8",
7
- "DATA_POLICY.md": "aaf42a8d3f2b82f4410fa2477bd5c09779c265baadda41ce567ad44bd23fea73",
8
  "FRONTEND_SOURCE.json": "0b9d9983a6feb93ae56ea95c037d7f744ade111824617785a0cd9f7e2f61308e",
9
  "GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
10
  "IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
11
  "LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
12
  "README.md": "caf4a2d7ac5ab949f9d9b9f5adb75c598c5669b23a040751041fc0709dc9b79f",
13
- "STORAGE_BOUNDARY.md": "0cda17053a343c2c83055e5a7e1191f1567199622a6eea422b48e11ec70e702e",
14
  "TRACK_LIBRARY.md": "7e4829c5e0933bfb317c21d7441ad0ba2ee00ab7f9f1635549452d504aa0777b",
15
  "UPSTREAM.json": "40a2f31fa9abc11e47e247c56909b93b587d1f5be524ab158b24f1f9eed93765",
16
- "app.py": "704f789a41846b0a5bc1d3f6f5305d7ac5a6b1719ff77c23585b89fd3e68d55d",
17
  "app_release.py": "7482559c667a665fd3cb6aa8af6c78422a822b515e14589db8306f7fd4860a8b",
18
  "assets/formula_template.json": "80f745df82bb7be45af52785fd582460bcddc63c5a4e1d8375d85b3122db4c6b",
19
  "assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
@@ -50,16 +50,16 @@
50
  "builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
51
  "builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
52
  "builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
53
- "client.js": "90a682cf27f4b8abc2de792fa90f6a32d859ca9918b7e92386b2c4d9366dd638",
54
  "delivery.py": "07453999c3831a16e8b1fcc2b4ae5fb28beb7d3952a3fae8de65d16eb6f11e5b",
55
- "gpu_tickets.py": "c32cb5de751d49d3e1cd970c7d6328a42ebe71602dd9fd3788c872eef42611da",
56
  "hf-auth.js": "b59691ce2c517f193a5d5141538ebe61c961bb163cefa1fcf09b414c388984c1",
57
  "hf_identity.py": "6993d0238ada835606dbdc3275c17a3fe5a5cac5ad2156ab78230acf2d051eba",
58
- "index.html": "d329965febb49db4939093ec0c512474db109a7b3a24e97cbabb18f97674757b",
59
  "requirements.txt": "9b532747e51bd052e82090ece860d6dafe027407f4512dc74272c148ea104c22",
60
- "server.py": "44eee069364f1a5d952ed316d30e27908111394ce53d776ccf0fb8099c6aee81",
61
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
62
- "track-map.js": "ebf0ffe889983b677060c785edd6398afb53c9e680e1ea7f13614a6a16712ac7",
63
  "training_config.py": "2b9768876fdc60b16afb45b98023c6c31666e30f7c56f1292f6071775e6da0c5",
64
  "vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
65
  "vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
 
2
  "backend": "cpu+zerogpu",
3
  "files": {
4
  ".gitattributes": "c2ad16718fc51c888d32efc88c8f6e23ebe5fcf6f48e2954f896ba9770f24d72",
5
+ "BETA_TESTING.md": "0b948552c6eb7594f51dcf6b96489cf18ec43edf88e386f544f41635f6d120b2",
6
  "BINARY_PROVENANCE.json": "1116e1757afc6c8629cb2d957e3d77380fe4f7e3186c3832d16a5a08abce6aa8",
7
+ "DATA_POLICY.md": "28e4c392d2c98e4b9069f97aa483d17d54265672d7bdd2a962be34bc517d2ca8",
8
  "FRONTEND_SOURCE.json": "0b9d9983a6feb93ae56ea95c037d7f744ade111824617785a0cd9f7e2f61308e",
9
  "GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
10
  "IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
11
  "LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
12
  "README.md": "caf4a2d7ac5ab949f9d9b9f5adb75c598c5669b23a040751041fc0709dc9b79f",
13
+ "STORAGE_BOUNDARY.md": "9a1ffb5854230690dba3f0895f9a8e570716de47be2c553de13a6892dd740015",
14
  "TRACK_LIBRARY.md": "7e4829c5e0933bfb317c21d7441ad0ba2ee00ab7f9f1635549452d504aa0777b",
15
  "UPSTREAM.json": "40a2f31fa9abc11e47e247c56909b93b587d1f5be524ab158b24f1f9eed93765",
16
+ "app.py": "f0aacacfcd68ac3223e215af691e84f01c7c1983505088b768d38500f258d56b",
17
  "app_release.py": "7482559c667a665fd3cb6aa8af6c78422a822b515e14589db8306f7fd4860a8b",
18
  "assets/formula_template.json": "80f745df82bb7be45af52785fd582460bcddc63c5a4e1d8375d85b3122db4c6b",
19
  "assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
 
50
  "builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
51
  "builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
52
  "builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
53
+ "client.js": "decc744db4d1241b5f37bb4b76e6b505454230721f4b2117e5fe4d32cb2a560a",
54
  "delivery.py": "07453999c3831a16e8b1fcc2b4ae5fb28beb7d3952a3fae8de65d16eb6f11e5b",
55
+ "gpu_tickets.py": "b81aadb5ed42cec9212d44da7e7bbb7b413c5bd517383cac0b49ec9b4d659c92",
56
  "hf-auth.js": "b59691ce2c517f193a5d5141538ebe61c961bb163cefa1fcf09b414c388984c1",
57
  "hf_identity.py": "6993d0238ada835606dbdc3275c17a3fe5a5cac5ad2156ab78230acf2d051eba",
58
+ "index.html": "dea8ee713484a551421883599af8ffbcd91cab7a31087699e6711c5bd1fc5412",
59
  "requirements.txt": "9b532747e51bd052e82090ece860d6dafe027407f4512dc74272c148ea104c22",
60
+ "server.py": "91cb5f18442fe38ed5cbd35b74faa326596a0c9d1cf197276ed89657a55d1fbd",
61
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
62
+ "track-map.js": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0",
63
  "training_config.py": "2b9768876fdc60b16afb45b98023c6c31666e30f7c56f1292f6071775e6da0c5",
64
  "vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
65
  "vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
STORAGE_BOUNDARY.md CHANGED
@@ -10,7 +10,7 @@ same settings contract is used independently of the selected storage policy.
10
  | Built-in replica library | Read-only, allow-listed release files; anonymous catalog/file endpoints | Intentionally persistent shared references; not erased by job deletion; user uploads never enter the catalog |
11
  | Selected built-in track | Synchronized selectors and one verified Blob | Page memory across runs; no cookie, recipe or localStorage persistence |
12
  | Worker inputs / intermediate files | Temporary directory | Removed before successful result publication |
13
- | GPU pending inputs | Opaque single-use ticket plus memory store | Dispatch, fixed 120-second expiry, or shutdown |
14
  | Single-run output | Memory result store and random job key | Default receipt ACK; opt-in repeat download until fixed five-minute expiry; manual delete or shutdown |
15
  | Batch intermediates | Same job contract, always automatic | Verified size/hash and deletion ACK before advancement |
16
  | Received candidate / receipt | Browser page memory | Explicit discard, replacement, page reload/close; explicit export to disk |
 
10
  | Built-in replica library | Read-only, allow-listed release files; anonymous catalog/file endpoints | Intentionally persistent shared references; not erased by job deletion; user uploads never enter the catalog |
11
  | Selected built-in track | Synchronized selectors and one verified Blob | Page memory across runs; no cookie, recipe or localStorage persistence |
12
  | Worker inputs / intermediate files | Temporary directory | Removed before successful result publication |
13
+ | GPU pending inputs | Opaque single-use ticket plus memory store | Dispatch, confirmed quota-rejection discard, fixed 120-second expiry, or shutdown |
14
  | Single-run output | Memory result store and random job key | Default receipt ACK; opt-in repeat download until fixed five-minute expiry; manual delete or shutdown |
15
  | Batch intermediates | Same job contract, always automatic | Verified size/hash and deletion ACK before advancement |
16
  | Received candidate / receipt | Browser page memory | Explicit discard, replacement, page reload/close; explicit export to disk |
app.py CHANGED
@@ -1,4 +1,4 @@
1
- """Private ZeroGPU integration candidate. Never enable on CPU-only hosting.
2
 
3
  Browser must invoke /gpu_train with @gradio/client for caller quota propagation.
4
  The queue carries only an opaque ticket; result bytes stay in the existing store.
@@ -78,6 +78,12 @@ def build_app(cuda_call):
78
  except Exception:raise HTTPException(400,'Invalid GPU input') from None
79
  finally:data.clear()
80
 
 
 
 
 
 
 
81
  @app.api(name='gpu_train',concurrency_limit=1,concurrency_id='gpu',api_visibility='undocumented')
82
  def gpu_train(ticket:str)->dict:
83
  if not admission.acquire(blocking=False):raise ValueError('GPU worker busy')
 
1
+ """Hosted ZeroGPU entrypoint for bounded Formula training.
2
 
3
  Browser must invoke /gpu_train with @gradio/client for caller quota propagation.
4
  The queue carries only an opaque ticket; result bytes stay in the existing store.
 
78
  except Exception:raise HTTPException(400,'Invalid GPU input') from None
79
  finally:data.clear()
80
 
81
+ @app.delete('/api/gpu/prepare/{ticket}')
82
+ def discard_prepared(ticket:str):
83
+ # A quota rejection can happen after preparation but before dispatch.
84
+ # The opaque ticket is the sole deletion capability for pending inputs.
85
+ return {'deleted':tickets.discard(ticket)}
86
+
87
  @app.api(name='gpu_train',concurrency_limit=1,concurrency_id='gpu',api_visibility='undocumented')
88
  def gpu_train(ticket:str)->dict:
89
  if not admission.acquire(blocking=False):raise ValueError('GPU worker busy')
client.js CHANGED
@@ -13,6 +13,12 @@ let batchReceipt = null;
13
  // Display context affects quota guidance, never whether GPU can be selected.
14
  const hostedStandalone = location.hostname.endsWith('.hf.space') && window.self === window.top;
15
  const say = text => { $('status').textContent = text; };
 
 
 
 
 
 
16
  const trackLibrary=window.TrackLibrary?.mount({document,fetch,digest:sha});
17
  window.TrackMap?.mount({document,fetch,picker:trackLibrary});
18
  const trackSource=()=>trackLibrary?trackLibrary.file():Promise.resolve($('track').files[0]);
@@ -71,8 +77,20 @@ async function submit(body){
71
  if(body.backend==='gpu' && gpuTransport==='gradio-ticket'){
72
  await verifyGpuIdentity();
73
  const prepared=await (await request('/api/gpu/prepare',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json();
74
- const result=await gradioClient.predict('/gpu_train',[prepared.ticket]);
75
- return result.data[0];
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
  return (await request('/api/process',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json();
78
  }
@@ -139,7 +157,7 @@ $('run').onclick = async () => {
139
  'Best score: '+metric(r.best_score)+' · Mean score: '+metric(r.mean_score),
140
  'Best lap: '+(r.best_lap_seconds==null?'No completed lap':metric(r.best_lap_seconds)+' s'),
141
  'Cars completing a lap: '+(r.lapped_cars??'Not reported'),
142
- 'Reported simulation ticks: '+(r.simulated_ticks??'Not reported')+' · Requested: '+r.requested_ticks,
143
  'Candidate validated: '+(r.candidate_validated?'Yes':'No'),
144
  'A completed test does not establish improved driving.'
145
  ].join('\n'):'Server returned a candidate; detailed metrics unavailable.';
@@ -152,7 +170,7 @@ $('run').onclick = async () => {
152
  $('runState').textContent='ERROR — no successful result confirmed';
153
  $('runState').style.color='#ffb5a2';
154
  $('runSummary').textContent='Elapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\nNo downloadable result was received. A connection error does not prove the server stopped.';
155
- say(error.message);
156
  }
157
  finally {clearInterval(progress);busy=false;$('keepServer').checked=false;buttons();}
158
  };
@@ -218,13 +236,16 @@ if($('batch_start')){
218
  say('Batch results are in browser memory only; no server result remains for completed steps.');
219
  }catch(error){
220
  batchReceipt={version:1,state:'error',plan:plan??null,history,error:error.message,elapsed_seconds:(Date.now()-started)/1000};
221
- $('batch_status').textContent='ERROR · '+history.length+' confirmed steps. '+error.message;
222
  $('runState').textContent='BATCH ERROR — '+history.length+' completed steps';
223
  $('runState').style.color='#ffb5a2';
224
- $('runSummary').textContent='Confirmed completed steps: '+history.length+'\n'+(job?'A server result remains accessible; use Delete now or await expiry.':'No current server result was received.')+'\n'+error.message;
225
- say(error.message+(job?' Use Delete server-side job data now or await expiry.':''));
226
  }finally{
227
- if(batchActive){$('track').value='';$('network').value='';stagedNetwork=null;}
 
 
 
228
  busy=false;batchActive=false;buttons();
229
  }
230
  };
 
13
  // Display context affects quota guidance, never whether GPU can be selected.
14
  const hostedStandalone = location.hostname.endsWith('.hf.space') && window.self === window.top;
15
  const say = text => { $('status').textContent = text; };
16
+ const isGpuQuotaError = error => /ZeroGPU.*(?:limit|quota)|(?:limit|quota).*ZeroGPU/i.test(String(error?.message??error));
17
+ function displayError(error){
18
+ return isGpuQuotaError(error)
19
+ ? 'GPU allowance is exhausted for this request. Choose CPU, or sign in with HF above to use your account allowance. No personal token is needed in this app.'
20
+ : String(error?.message??error);
21
+ }
22
  const trackLibrary=window.TrackLibrary?.mount({document,fetch,digest:sha});
23
  window.TrackMap?.mount({document,fetch,picker:trackLibrary});
24
  const trackSource=()=>trackLibrary?trackLibrary.file():Promise.resolve($('track').files[0]);
 
77
  if(body.backend==='gpu' && gpuTransport==='gradio-ticket'){
78
  await verifyGpuIdentity();
79
  const prepared=await (await request('/api/gpu/prepare',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json();
80
+ try {
81
+ const result=await gradioClient.predict('/gpu_train',[prepared.ticket]);
82
+ return result.data[0];
83
+ } catch(error) {
84
+ if(isGpuQuotaError(error)){
85
+ let removed=false;
86
+ try {
87
+ const response=await request('/api/gpu/prepare/'+encodeURIComponent(prepared.ticket),{method:'DELETE'});
88
+ removed=(await response.json()).deleted===true;
89
+ } catch {}
90
+ throw Error(displayError(error)+(removed?' Pending inputs were deleted.':' Pending inputs expire within two minutes if dispatch did not start.'));
91
+ }
92
+ throw error;
93
+ }
94
  }
95
  return (await request('/api/process',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json();
96
  }
 
157
  'Best score: '+metric(r.best_score)+' · Mean score: '+metric(r.mean_score),
158
  'Best lap: '+(r.best_lap_seconds==null?'No completed lap':metric(r.best_lap_seconds)+' s'),
159
  'Cars completing a lap: '+(r.lapped_cars??'Not reported'),
160
+ 'Actual frames (including stats cutoff): '+(r.simulated_ticks??'Not reported')+' · Configured time limit: '+r.requested_ticks+' ticks',
161
  'Candidate validated: '+(r.candidate_validated?'Yes':'No'),
162
  'A completed test does not establish improved driving.'
163
  ].join('\n'):'Server returned a candidate; detailed metrics unavailable.';
 
170
  $('runState').textContent='ERROR — no successful result confirmed';
171
  $('runState').style.color='#ffb5a2';
172
  $('runSummary').textContent='Elapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\nNo downloadable result was received. A connection error does not prove the server stopped.';
173
+ say(displayError(error));
174
  }
175
  finally {clearInterval(progress);busy=false;$('keepServer').checked=false;buttons();}
176
  };
 
236
  say('Batch results are in browser memory only; no server result remains for completed steps.');
237
  }catch(error){
238
  batchReceipt={version:1,state:'error',plan:plan??null,history,error:error.message,elapsed_seconds:(Date.now()-started)/1000};
239
+ $('batch_status').textContent='ERROR · '+history.length+' confirmed steps. '+displayError(error);
240
  $('runState').textContent='BATCH ERROR — '+history.length+' completed steps';
241
  $('runState').style.color='#ffb5a2';
242
+ $('runSummary').textContent='Confirmed completed steps: '+history.length+'\n'+(job?'A server result remains accessible; use Delete now or await expiry.':'No current server result was received.')+'\n'+displayError(error);
243
+ say(displayError(error)+(job?' Use Delete server-side job data now or await expiry.':''));
244
  }finally{
245
+ if(batchActive){
246
+ $('track').value='';$('network').value='';stagedNetwork=null;
247
+ if($('networkChoice'))$('networkChoice').textContent='Network input released after the batch.';
248
+ }
249
  busy=false;batchActive=false;buttons();
250
  }
251
  };
gpu_tickets.py CHANGED
@@ -38,5 +38,10 @@ class InputTickets:
38
  if ticket not in self.pending:raise ValueError('GPU input unavailable')
39
  return self.pending.pop(ticket)[1:]
40
 
 
 
 
 
 
41
  def clear(self):
42
  with self.lock:self.pending.clear()
 
38
  if ticket not in self.pending:raise ValueError('GPU input unavailable')
39
  return self.pending.pop(ticket)[1:]
40
 
41
+ def discard(self,ticket):
42
+ with self.lock:
43
+ self.expire()
44
+ return self.pending.pop(ticket,None) is not None
45
+
46
  def clear(self):
47
  with self.lock:self.pending.clear()
index.html CHANGED
@@ -1,7 +1,7 @@
1
  <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>AI Learns To Drive</title>
2
  <style>
3
  :root{color-scheme:dark;font:16px system-ui;background:#0c1420;color:#e5edf5}body{max-width:1400px;margin:auto;padding:22px}h1{font-size:32px;margin:0}h2{font-size:21px;margin:0 0 14px}small,.hint{color:#a2b4c8;line-height:1.6}.grid{display:grid;grid-template-columns:minmax(360px,.9fr) minmax(0,1.25fr);gap:24px}.card{background:#152233;border:1px solid #2a3d52;border-radius:16px;padding:22px;margin-top:20px}label{display:block;margin:12px 0 5px}input,select,button{font:inherit;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:7px;padding:9px}input[type=number],select{width:100%;box-sizing:border-box}input[type=file]{max-width:100%;box-sizing:border-box}button{cursor:pointer}button.primary{background:#62dfb1;color:#102522;font-weight:700}button:disabled{opacity:.4;cursor:default}.row{display:grid;grid-template-columns:1fr 1fr;gap:15px}.reward{display:grid;grid-template-columns:1.7fr 1fr 70px 30px;gap:5px;margin:8px 0}.reward input{width:70px;box-sizing:border-box}a{color:#7ce4c3}canvas{width:100%;background:#09111b;border-radius:10px}#status{white-space:pre-wrap;line-height:1.7}table{width:100%;text-align:left;border-collapse:collapse;font-size:14px}td,th{padding:9px;border-bottom:1px solid #2a3d52}.pill{display:inline-block;background:#284539;border-radius:20px;padding:5px 12px;color:#91efc6;margin:6px 0}#error{color:#ffb5a2;white-space:pre-wrap}.toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding-bottom:16px;border-bottom:1px solid #2a3d52;margin-bottom:15px}.run-card{position:sticky;top:20px}details{margin-top:18px;border-top:1px solid #2a3d52;padding-top:14px}summary{cursor:pointer;font-weight:600;color:#c6d9e9}details[open]>summary{margin-bottom:12px}[hidden]{display:none!important}#map_note{font-size:14px}#runs{max-height:380px;overflow:auto}.table-scroll{overflow:auto}@media(max-width:850px){.grid{grid-template-columns:1fr}#drive .grid>section:nth-child(2){order:-1}.run-card{position:static}body{padding:16px}}
4
- .tabs{display:flex;flex-wrap:wrap;gap:10px;margin:20px 0}.tabs button[aria-selected=true]{background:#62dfb1;color:#102522}.library{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}.library input[type=text]{width:100%;box-sizing:border-box}.queue-row{display:flex;gap:8px;margin:8px 0}.queue-row select{flex:1}.pane{min-width:0}.map{min-height:300px;background:#09111b;border-radius:12px;padding:10px}.map canvas{display:block;width:100%;height:auto;max-height:320px;object-fit:contain}.map-legend{display:flex;gap:18px;flex-wrap:wrap;margin:10px 0;color:#c6d9e9;font-size:14px}pre{white-space:pre-wrap;overflow-wrap:anywhere}input[type=text]{width:100%;box-sizing:border-box}#editor_parameters input{max-width:240px} .eyebrow{color:#62dfb1;font-size:12px;letter-spacing:.18em;font-weight:800}.intro{font-size:19px;color:#b9cddd;max-width:820px;margin:10px 0 0}header{padding:12px 0 4px}header h1{font-size:clamp(30px,4vw,45px);letter-spacing:-.03em;margin-top:7px}.about{max-width:850px;font-size:14px;margin-top:15px}.about p{line-height:1.65}.card{box-shadow:0 10px 35px #050a1280}.tabs{position:sticky;top:0;z-index:3;background:#0c1420eF;padding:12px 0;border-bottom:1px solid #2a3d52}.tabs button{border-radius:12px;padding:11px 15px}.map-status{margin:7px 0;color:#dbe9f1;font-size:14px}#run{background:#62dfb1;color:#102522;font-weight:800;width:100%;margin-top:12px;padding:13px}.privacy-note{font-size:13px;line-height:1.5;color:#a2b4c8}.keep-result{display:flex;gap:10px;align-items:flex-start;padding:12px;border:1px solid #3b566b;border-radius:10px;background:#102030;line-height:1.4}.keep-result input{margin:3px 0 0;flex:none} .terrain{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:4px;background:#607e96}.terrain.dirt{background:#b89561}.terrain.ice{background:#b9d9ed}.terrain.start{background:#66e5aa}</style>
5
  <header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Pick a track and network, run a bounded training session, and keep your recipes in this browser.</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. Star challenges are not evaluated here.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p></details></header>
6
  <nav class="tabs" aria-label="Lab sections"><button data-tab="drive" aria-selected="true">1 · Drive & view</button><button data-tab="training" aria-selected="false">2 · Training runs & presets</button><button data-tab="batch" aria-selected="false">3 · Batches</button><button data-tab="editor" aria-selected="false">4 · Network editor</button></nav><div id="drive" class="pane"><div class="grid"><section class="card"><h2>Simulation</h2><p>Training uses the settings on the Training runs & presets tab. Formula networks, steering and acceleration; speed and raycast sensors. Supported networks: Formula, steering and acceleration, speed/raycast sensors, 2–4 layers with up to 16 nodes.</p>
7
  <p id="computeBlock"><label for="backend">Compute</label><select id="backend" disabled><option value="cpu">CPU — no GPU allowance</option><option id="gpuOption" value="gpu" disabled>GPU — uses GPU allowance</option></select></p><p id="backendNote" class="hint">Checking available backends…</p>
@@ -22,13 +22,13 @@
22
  <p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
23
  <details><summary>Effective training settings</summary><pre id="effectiveSettings"></pre></details><details><summary>Generation results</summary><pre id="generationHistory"></pre></details><details><summary>Star challenges — not evaluated</summary><p>The current runner has star-challenge reporting. Its evaluator and required statistics have not been ported to HF; no star result is inferred from a completed test.</p></details></section></div>
24
  </div><section id="training" class="pane" hidden><div class="card"><h2>Training settings</h2><p class="hint">Choose the track on Drive & view. Choose the training settings and reward mix here, then start the run on Drive & view. Recipes and batch sequences are saved in this browser.</p><p><label for="trackPresetTraining">Training track</label><select id="trackPresetTraining" data-track-library disabled><option value="">Upload a custom track</option></select></p><p data-track-status role="status" class="hint">Loading built-in replica tracks…</p><div id="training_controls"><label for="training_ticks">Simulated ticks per generation</label><input id="training_ticks" type="number" min="1" max="18000" value="60"><small>60 ticks = one simulated second. Current preview limit: population × generations × ticks ≤ 4,000,000; execution timeout 25 seconds.</small>
25
- <div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="128" value="8" disabled><small>Population limits will follow the selected compute allocation.</small></div><div><label for="generations">Generations</label><input id="generations" type="number" min="1" max="1000" value="30" disabled><small>1–1,000; try 20–50 before review.</small></div></div>
26
  <label id="mutation_label" for="mutation">Mutation rate</label><input id="mutation" type="number" min="0" max="4.2" step="0.01" value="0.1" disabled><small>Mutation settings are passed to the upstream simulator.</small>
27
 
28
  <details id="native_settings"><summary>Native evolution settings</summary>
29
  <label for="preset">Recommended starting recipe</label><select id="preset" disabled><option value="custom">Custom / keep current settings</option><option value="starter">First laps · mutation 0.3, decay 0.0005</option><option value="explore">Wide exploration · mutation 0.5, decay 0.0005</option><option value="refine">Established driver · mutation 0.1, decay 0.0001</option><option value="speed">Lap refinement · mutation 0.05, decay 0</option></select><small>Starting hypotheses, not proven optimums. Change stages after stable improvement.</small>
30
  <label for="schedule">Mutation schedule for this run</label><select id="schedule" disabled><option value="fixed">Fixed · change stages manually</option><option value="linear">Linear start → end</option><option value="geometric">Geometric start → end</option></select>
31
- <div id="end_rate_group" hidden><label for="mutation_end">End mutation rate</label><input id="mutation_end" type="number" min="0.000001" max="4.2" step="0.01" value="0.1" disabled><small>Both schedule rates must be positive.</small></div>
32
  <label><input id="adaptive_mutation" type="checkbox" disabled> Adaptive mutation · normalize for network size</label><small>Architecture scaling, not progress-aware adjustment. Decay is fixed; adaptive decay isn't exposed by this CLI.</small>
33
  <label for="selection">Parent selection</label><select id="selection" disabled><option value="best">Best</option><option value="tournament">Tournament</option><option value="roulette">Roulette</option></select>
34
  <div class="row"><div><label for="parents">Selected parents</label><input id="parents" type="number" min="1" value="1" disabled><small>1–population; start with 3.</small></div><div><label for="elite">Preserved elites</label><input id="elite" type="number" min="0" value="1" disabled><small>0–population; start with 1.</small></div></div>
@@ -36,19 +36,19 @@
36
  <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>
37
  </details>
38
  <details id="reward_settings"><summary>Rewards · presets and up to five metrics</summary>
39
- <label for="reward_preset">Reward preset</label><select id="reward_preset" disabled><option value="custom">Custom</option><option value="distance">First laps · distance only</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><option value="multilap">Endurance · distance only, longer horizon</option></select><p id="reward_preset_note" class="hint"></p>
40
- <div id="rewards"></div><p id="budget" class="hint"></p><button id="add" disabled>+ Add reward</button><p id="guidance" class="hint"></p><small>Weights −100…+100; absolute shares normalize to 100%. Five distinct metrics maximum. Available rewards depend on the uploaded network and backend.</small>
41
  </details></div>
42
- <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>30</option><option>15</option></select></div><div><label for="workers">Parallel simulations</label><select id="workers" disabled><option>1</option><option selected>2</option><option>3</option><option>4</option></select></div></div><small>Physics always 60 Hz. Start with 2 workers; frozen replay uses one car.</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></details></div><div id="library_status" role="status"></div><p> </p><div id="libraries" class="card"><h3>Recipes · A–J · saved in this browser</h3>
43
  <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>
44
  <div class="row"><label for="recipe_slot">Recipe slot<select id="recipe_slot"><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><label for="recipe_name">Recipe name<input id="recipe_name" maxlength="80" value="My recipe"></label></div>
45
  <button id="recipe_save">Save current settings</button> <button id="recipe_load">Load recipe</button>
46
  <p><button id="recipe_export">Export recipe library</button> <label for="recipe_import">Import recipe library (.json)<input id="recipe_import" type="file" accept=".json"></label></p>
47
- <button id="recipe_clear">Clear saved recipes on this device</button><p id="recipe_feedback" role="status" aria-live="polite"></p>
48
  <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. Browser privacy settings may prevent saving.</p>
49
  <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>
50
- <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 and 4,000,000 vehicle-ticks in total. Recipes freeze at start. Each server result is automatically deleted after verified receipt. The last candidate is kept in browser memory. 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 uploaded 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><div id="queue"></div><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 id="preset_message" role="status"></div></div></section>
51
- <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>. Fresh networks use one speed input and five raycasts (−60°, −30°, 0°, 30°, 60°; length 800), with steering and acceleration outputs. This is an editor starter configuration, not a claim of identical game presets. Larger or other-control networks can be inspected, but the current worker accepts only the displayed supported contract.</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_shape">Layer sizes for fresh creation</label><input id="editor_shape" type="text" value="6,4,2" disabled><small>Fresh: 6 inputs → up to two hidden layers of 1–16 nodes → 2 outputs. Loaded networks may be inspected up to 20 layers / 100,000 parameters.</small></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><details id="editor_lab_panel" hidden><summary>Training receipt</summary><p id="editor_lab_summary" class="hint"></p><pre id="editor_lab_history"></pre></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. */
52
  (function(root){
53
  const KEY='tiga.ald.recipes.v1';
54
  const defaults={population:4,generations:1,ticks:60,mutation:0.01,seed:42,
@@ -203,7 +203,9 @@
203
  for(const name of api.metrics)metric.add(new Option(name.replaceAll('_',' '),name));metric.value=r.metric;
204
  for(const name of ['default','average'])type.add(new Option(name,name));type.value=r.type;
205
  weight.type='number';weight.min=-100;weight.max=100;weight.step=1;weight.value=r.weight;
206
- remove.textContent='×';remove.setAttribute('aria-label','Remove reward');remove.onclick=()=>row.remove();
 
 
207
  row.append(metric,type,weight,remove);q('rewards').append(row);
208
  }
209
  function currentSettings(){
@@ -227,10 +229,10 @@
227
  for(const node of q('rewards').querySelectorAll('input,select,button'))node.disabled=busy;
228
  for(const id of ['recipe_save','recipe_load','recipe_apply_draft','recipe_slot','recipe_name','recipe_import','recipe_clear','batch_save','batch_load','batch_slot','batch_name','batch_recipe_sequence','carry','queue_add','batch_add_recipe'])q(id).disabled=busy;
229
  };
230
- 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});};
231
  q('reward_preset').onchange=()=>{
232
  const key=q('reward_preset').value;if(key==='custom')return;
233
- 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]],multilap:[['total_score',100]]};
234
  q('rewards').replaceChildren();rows[key].forEach(([metric,weight])=>rewardRow({metric,type:'default',weight}));
235
  };
236
  q('preset').onchange=()=>{
@@ -238,13 +240,14 @@
238
  const data=currentSettings();data.mutation={starter:.3,explore:.5,refine:.1,speed:.05}[k];data.mutation_end=data.mutation;
239
  data.decay={starter:.0005,explore:.0005,refine:.0001,speed:0}[k];applySettings(data);
240
  };
 
241
  let store;
242
  try{store=api.createStore(window.localStorage);store.load();message('Browser-local library ready.');}
243
  catch(error){message('Local library unavailable: '+error.message+'. Existing data was not changed.');}
244
  const read=()=>{if(!store)throw Error('Browser storage unavailable');return store.load();};
245
  window.getBatchPlan=()=>window.BatchTraining.plan(read(),q('batch_slot').value);
246
  const action=fn=>async()=>{try{await fn();}catch(error){message('Not completed: '+error.message);}};
247
- q('recipe_apply_draft').onclick=action(()=>{applySettings(JSON.parse(q('recipe_draft').value));message('Draft applied to the training form. No run started.');});
248
  q('recipe_save').onclick=action(()=>{
249
  const book=read(),slot=q('recipe_slot').value;
250
  const next=api.validate({...book,recipes:[...book.recipes.filter(r=>r.slot!==slot),{slot,name:q('recipe_name').value,settings:currentSettings()}]});
@@ -253,7 +256,7 @@
253
  });
254
  q('recipe_load').onclick=action(()=>{
255
  const r=read().recipes.find(r=>r.slot===q('recipe_slot').value);if(!r)throw Error('This slot is empty');
256
- q('recipe_name').value=r.name;applySettings(r.settings);
257
  message('Loaded '+r.slot+' into the training form. Loading a recipe does not start training.');
258
  });
259
  q('recipe_export').onclick=action(()=>{
@@ -269,7 +272,7 @@
269
  message('Imported '+book.recipes.length+' recipes locally. No run was started.');q('recipe_import').value='';
270
  });
271
  q('recipe_clear').onclick=action(()=>{
272
- if(!confirm('Clear only saved recipes for this app on this device? Server run data and downloaded files are unaffected.'))return;
273
  if(!store)throw Error('Browser storage unavailable');store.clear();message('Local recipes cleared. Server data and downloads are unaffected.');
274
  });
275
  q('batch_save').disabled=false;q('batch_load').disabled=false;
 
1
  <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>AI Learns To Drive</title>
2
  <style>
3
  :root{color-scheme:dark;font:16px system-ui;background:#0c1420;color:#e5edf5}body{max-width:1400px;margin:auto;padding:22px}h1{font-size:32px;margin:0}h2{font-size:21px;margin:0 0 14px}small,.hint{color:#a2b4c8;line-height:1.6}.grid{display:grid;grid-template-columns:minmax(360px,.9fr) minmax(0,1.25fr);gap:24px}.card{background:#152233;border:1px solid #2a3d52;border-radius:16px;padding:22px;margin-top:20px}label{display:block;margin:12px 0 5px}input,select,button{font:inherit;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:7px;padding:9px}input[type=number],select{width:100%;box-sizing:border-box}input[type=file]{max-width:100%;box-sizing:border-box}button{cursor:pointer}button.primary{background:#62dfb1;color:#102522;font-weight:700}button:disabled{opacity:.4;cursor:default}.row{display:grid;grid-template-columns:1fr 1fr;gap:15px}.reward{display:grid;grid-template-columns:1.7fr 1fr 70px 30px;gap:5px;margin:8px 0}.reward input{width:70px;box-sizing:border-box}a{color:#7ce4c3}canvas{width:100%;background:#09111b;border-radius:10px}#status{white-space:pre-wrap;line-height:1.7}table{width:100%;text-align:left;border-collapse:collapse;font-size:14px}td,th{padding:9px;border-bottom:1px solid #2a3d52}.pill{display:inline-block;background:#284539;border-radius:20px;padding:5px 12px;color:#91efc6;margin:6px 0}#error{color:#ffb5a2;white-space:pre-wrap}.toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding-bottom:16px;border-bottom:1px solid #2a3d52;margin-bottom:15px}.run-card{position:sticky;top:20px}details{margin-top:18px;border-top:1px solid #2a3d52;padding-top:14px}summary{cursor:pointer;font-weight:600;color:#c6d9e9}details[open]>summary{margin-bottom:12px}[hidden]{display:none!important}#map_note{font-size:14px}#runs{max-height:380px;overflow:auto}.table-scroll{overflow:auto}@media(max-width:850px){.grid{grid-template-columns:1fr}#drive .grid>section:nth-child(2){order:-1}.run-card{position:static}body{padding:16px}}
4
+ .tabs{display:flex;flex-wrap:wrap;gap:10px;margin:20px 0}.tabs button[aria-selected=true]{background:#62dfb1;color:#102522}.library{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}.library input[type=text]{width:100%;box-sizing:border-box}.queue-row{display:flex;gap:8px;margin:8px 0}.queue-row select{flex:1}.pane{min-width:0}.map{min-height:300px;background:#09111b;border-radius:12px;padding:10px}.map canvas{display:block;width:100%;height:auto;max-height:320px;object-fit:contain}.map-legend{display:flex;gap:18px;flex-wrap:wrap;margin:10px 0;color:#c6d9e9;font-size:14px}pre{white-space:pre-wrap;overflow-wrap:anywhere}input[type=text]{width:100%;box-sizing:border-box}#libraries .row label>input,#libraries .row label>select{display:block;margin-top:6px}#editor_parameters input{max-width:240px} .eyebrow{color:#62dfb1;font-size:12px;letter-spacing:.18em;font-weight:800}.intro{font-size:19px;color:#b9cddd;max-width:820px;margin:10px 0 0}header{padding:12px 0 4px}header h1{font-size:clamp(30px,4vw,45px);letter-spacing:-.03em;margin-top:7px}.about{max-width:850px;font-size:14px;margin-top:15px}.about p{line-height:1.65}.card{box-shadow:0 10px 35px #050a1280}.tabs{position:sticky;top:0;z-index:3;background:#0c1420eF;padding:12px 0;border-bottom:1px solid #2a3d52}.tabs button{border-radius:12px;padding:11px 15px}.map-status{margin:7px 0;color:#dbe9f1;font-size:14px}#run{background:#62dfb1;color:#102522;font-weight:800;width:100%;margin-top:12px;padding:13px}.privacy-note{font-size:13px;line-height:1.5;color:#a2b4c8}.keep-result{display:flex;gap:10px;align-items:flex-start;padding:12px;border:1px solid #3b566b;border-radius:10px;background:#102030;line-height:1.4}.keep-result input{margin:3px 0 0;flex:none} .terrain{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:4px;background:#607e96}.terrain.dirt{background:#b89561}.terrain.ice{background:#b9d9ed}.terrain.start{background:#66e5aa}</style>
5
  <header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Pick a track and network, run a bounded training session, and keep your recipes in this browser.</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. Star challenges are not evaluated here.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p></details></header>
6
  <nav class="tabs" aria-label="Lab sections"><button data-tab="drive" aria-selected="true">1 · Drive & view</button><button data-tab="training" aria-selected="false">2 · Training runs & presets</button><button data-tab="batch" aria-selected="false">3 · Batches</button><button data-tab="editor" aria-selected="false">4 · Network editor</button></nav><div id="drive" class="pane"><div class="grid"><section class="card"><h2>Simulation</h2><p>Training uses the settings on the Training runs & presets tab. Formula networks, steering and acceleration; speed and raycast sensors. Supported networks: Formula, steering and acceleration, speed/raycast sensors, 2–4 layers with up to 16 nodes.</p>
7
  <p id="computeBlock"><label for="backend">Compute</label><select id="backend" disabled><option value="cpu">CPU — no GPU allowance</option><option id="gpuOption" value="gpu" disabled>GPU — uses GPU allowance</option></select></p><p id="backendNote" class="hint">Checking available backends…</p>
 
22
  <p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
23
  <details><summary>Effective training settings</summary><pre id="effectiveSettings"></pre></details><details><summary>Generation results</summary><pre id="generationHistory"></pre></details><details><summary>Star challenges — not evaluated</summary><p>The current runner has star-challenge reporting. Its evaluator and required statistics have not been ported to HF; no star result is inferred from a completed test.</p></details></section></div>
24
  </div><section id="training" class="pane" hidden><div class="card"><h2>Training settings</h2><p class="hint">Choose the track on Drive & view. Choose the training settings and reward mix here, then start the run on Drive & view. Recipes and batch sequences are saved in this browser.</p><p><label for="trackPresetTraining">Training track</label><select id="trackPresetTraining" data-track-library disabled><option value="">Upload a custom track</option></select></p><p data-track-status role="status" class="hint">Loading built-in replica tracks…</p><div id="training_controls"><label for="training_ticks">Simulated ticks per generation</label><input id="training_ticks" type="number" min="1" max="18000" value="60"><small>60 ticks = one simulated second. Current preview limit: population × generations × ticks ≤ 4,000,000; execution timeout 25 seconds.</small>
25
+ <div class="row"><div><label for="population">Population</label><input id="population" type="number" min="1" max="128" value="8" disabled><small>1–128 cars in this beta.</small></div><div><label for="generations">Generations</label><input id="generations" type="number" min="1" max="1000" value="30" disabled><small>1–1,000; try 20–50 before review.</small></div></div>
26
  <label id="mutation_label" for="mutation">Mutation rate</label><input id="mutation" type="number" min="0" max="4.2" step="0.01" value="0.1" disabled><small>Mutation settings are passed to the upstream simulator.</small>
27
 
28
  <details id="native_settings"><summary>Native evolution settings</summary>
29
  <label for="preset">Recommended starting recipe</label><select id="preset" disabled><option value="custom">Custom / keep current settings</option><option value="starter">First laps · mutation 0.3, decay 0.0005</option><option value="explore">Wide exploration · mutation 0.5, decay 0.0005</option><option value="refine">Established driver · mutation 0.1, decay 0.0001</option><option value="speed">Lap refinement · mutation 0.05, decay 0</option></select><small>Starting hypotheses, not proven optimums. Change stages after stable improvement.</small>
30
  <label for="schedule">Mutation schedule for this run</label><select id="schedule" disabled><option value="fixed">Fixed · change stages manually</option><option value="linear">Linear start → end</option><option value="geometric">Geometric start → end</option></select>
31
+ <div id="end_rate_group" hidden><label for="mutation_end">End mutation rate</label><input id="mutation_end" type="number" min="0" max="4.2" step="0.01" value="0.1" disabled><small>Geometric schedules require positive start and end rates.</small></div>
32
  <label><input id="adaptive_mutation" type="checkbox" disabled> Adaptive mutation · normalize for network size</label><small>Architecture scaling, not progress-aware adjustment. Decay is fixed; adaptive decay isn't exposed by this CLI.</small>
33
  <label for="selection">Parent selection</label><select id="selection" disabled><option value="best">Best</option><option value="tournament">Tournament</option><option value="roulette">Roulette</option></select>
34
  <div class="row"><div><label for="parents">Selected parents</label><input id="parents" type="number" min="1" value="1" disabled><small>1–population; start with 3.</small></div><div><label for="elite">Preserved elites</label><input id="elite" type="number" min="0" value="1" disabled><small>0–population; start with 1.</small></div></div>
 
36
  <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>
37
  </details>
38
  <details id="reward_settings"><summary>Rewards · presets and up to five metrics</summary>
39
+ <label for="reward_preset">Reward preset</label><select id="reward_preset" disabled><option value="custom">Custom</option><option value="distance">First laps · distance only</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>
40
+ <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 Formula runner accepts these metrics; choose them for the behavior you want to train.</small>
41
  </details></div>
42
+ <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>30</option><option>15</option></select></div><div><label for="workers">Parallel simulations</label><select id="workers" disabled><option>1</option><option selected>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></details></div><div id="libraries" class="card"><h3>Recipes · A–J · saved in this browser</h3>
43
  <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>
44
  <div class="row"><label for="recipe_slot">Recipe slot<select id="recipe_slot"><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><label for="recipe_name">Recipe name<input id="recipe_name" maxlength="80" value="My recipe"></label></div>
45
  <button id="recipe_save">Save current settings</button> <button id="recipe_load">Load recipe</button>
46
  <p><button id="recipe_export">Export recipe library</button> <label for="recipe_import">Import recipe library (.json)<input id="recipe_import" type="file" accept=".json"></label></p>
47
+ <button id="recipe_clear">Clear saved recipes and batches on this device</button><p id="recipe_feedback" role="status" aria-live="polite"></p>
48
  <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. Browser privacy settings may prevent saving.</p>
49
  <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>
50
+ <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 and 4,000,000 vehicle-ticks in total. Recipes freeze at start. Each server result is automatically deleted after verified receipt. The last candidate is kept in browser memory. 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>
51
+ <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>. Fresh networks use one speed input and five raycasts (−60°, −30°, 0°, 30°, 60°; length 800), with steering and acceleration outputs. This is an editor starter configuration, not a claim of identical game presets. Larger or other-control networks can be inspected, but the current worker accepts only the displayed supported contract.</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_shape">Layer sizes for fresh creation</label><input id="editor_shape" type="text" value="6,4,2" disabled><small>Fresh: 6 inputs → up to two hidden layers of 1–16 nodes → 2 outputs. Loaded networks may be inspected up to 20 layers / 100,000 parameters.</small></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. */
52
  (function(root){
53
  const KEY='tiga.ald.recipes.v1';
54
  const defaults={population:4,generations:1,ticks:60,mutation:0.01,seed:42,
 
203
  for(const name of api.metrics)metric.add(new Option(name.replaceAll('_',' '),name));metric.value=r.metric;
204
  for(const name of ['default','average'])type.add(new Option(name,name));type.value=r.type;
205
  weight.type='number';weight.min=-100;weight.max=100;weight.step=1;weight.value=r.weight;
206
+ const custom=()=>{q('reward_preset').value='custom';};
207
+ metric.onchange=custom;type.onchange=custom;weight.oninput=custom;
208
+ remove.textContent='×';remove.setAttribute('aria-label','Remove reward');remove.onclick=()=>{row.remove();custom();};
209
  row.append(metric,type,weight,remove);q('rewards').append(row);
210
  }
211
  function currentSettings(){
 
229
  for(const node of q('rewards').querySelectorAll('input,select,button'))node.disabled=busy;
230
  for(const id of ['recipe_save','recipe_load','recipe_apply_draft','recipe_slot','recipe_name','recipe_import','recipe_clear','batch_save','batch_load','batch_slot','batch_name','batch_recipe_sequence','carry','queue_add','batch_add_recipe'])q(id).disabled=busy;
231
  };
232
+ 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';};
233
  q('reward_preset').onchange=()=>{
234
  const key=q('reward_preset').value;if(key==='custom')return;
235
+ 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]]};
236
  q('rewards').replaceChildren();rows[key].forEach(([metric,weight])=>rewardRow({metric,type:'default',weight}));
237
  };
238
  q('preset').onchange=()=>{
 
240
  const data=currentSettings();data.mutation={starter:.3,explore:.5,refine:.1,speed:.05}[k];data.mutation_end=data.mutation;
241
  data.decay={starter:.0005,explore:.0005,refine:.0001,speed:0}[k];applySettings(data);
242
  };
243
+ for(const id of ['mutation','mutation_end','decay'])q(id).addEventListener('input',()=>{q('preset').value='custom';});
244
  let store;
245
  try{store=api.createStore(window.localStorage);store.load();message('Browser-local library ready.');}
246
  catch(error){message('Local library unavailable: '+error.message+'. Existing data was not changed.');}
247
  const read=()=>{if(!store)throw Error('Browser storage unavailable');return store.load();};
248
  window.getBatchPlan=()=>window.BatchTraining.plan(read(),q('batch_slot').value);
249
  const action=fn=>async()=>{try{await fn();}catch(error){message('Not completed: '+error.message);}};
250
+ q('recipe_apply_draft').onclick=action(()=>{applySettings(JSON.parse(q('recipe_draft').value));q('preset').value='custom';q('reward_preset').value='custom';message('Draft applied to the training form. No run started.');});
251
  q('recipe_save').onclick=action(()=>{
252
  const book=read(),slot=q('recipe_slot').value;
253
  const next=api.validate({...book,recipes:[...book.recipes.filter(r=>r.slot!==slot),{slot,name:q('recipe_name').value,settings:currentSettings()}]});
 
256
  });
257
  q('recipe_load').onclick=action(()=>{
258
  const r=read().recipes.find(r=>r.slot===q('recipe_slot').value);if(!r)throw Error('This slot is empty');
259
+ q('recipe_name').value=r.name;applySettings(r.settings);q('preset').value='custom';q('reward_preset').value='custom';
260
  message('Loaded '+r.slot+' into the training form. Loading a recipe does not start training.');
261
  });
262
  q('recipe_export').onclick=action(()=>{
 
272
  message('Imported '+book.recipes.length+' recipes locally. No run was started.');q('recipe_import').value='';
273
  });
274
  q('recipe_clear').onclick=action(()=>{
275
+ if(!confirm('Clear saved recipes and batches for this app on this device? Server run data and downloaded files are unaffected.'))return;
276
  if(!store)throw Error('Browser storage unavailable');store.clear();message('Local recipes cleared. Server data and downloads are unaffected.');
277
  });
278
  q('batch_save').disabled=false;q('batch_load').disabled=false;
server.py CHANGED
@@ -1,4 +1,4 @@
1
- """Anonymous local integration test server; not yet a ZeroGPU entrypoint."""
2
  import base64
3
  import json
4
  from pathlib import Path
 
1
+ """Shared CPU routes and delivery API for hosted ALD training."""
2
  import base64
3
  import json
4
  from pathlib import Path
track-map.js CHANGED
@@ -55,7 +55,7 @@
55
  context.clearRect(0,0,w,h);
56
  context.fillStyle='#0c1926';context.fillRect(0,0,w,h);
57
  const b=track.bounds,columns=b.maxX-b.minX+1,rows=b.maxY-b.minY+1;
58
- const size=Math.min(100,(w-70)/columns,(h-70)/rows);
59
  const left=(w-columns*size)/2,top=(h-rows*size)/2;
60
  for(const tile of track.tiles){
61
  const x=left+(tile.x-b.minX)*size,y=top+(tile.y-b.minY)*size;
 
55
  context.clearRect(0,0,w,h);
56
  context.fillStyle='#0c1926';context.fillRect(0,0,w,h);
57
  const b=track.bounds,columns=b.maxX-b.minX+1,rows=b.maxY-b.minY+1;
58
+ const size=Math.min(160,(w-70)/columns,(h-70)/rows);
59
  const left=(w-columns*size)/2,top=(h-rows*size)/2;
60
  for(const tile of track.tiles){
61
  const x=left+(tile.x-b.minX)*size,y=top+(tile.y-b.minY)*size;