Improve quick-start help and recipe/batch editing

#1
by TiGa-RCE - opened
Files changed (4) hide show
  1. RELEASE_MANIFEST.json +6 -6
  2. client.js +40 -11
  3. help.html +52 -11
  4. index.html +249 -58
RELEASE_MANIFEST.json CHANGED
@@ -29,6 +29,7 @@
29
  "bin/libaltd_gpu.so": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05",
30
  "bucket_storage.py": "f3044964724de322c1372adc23fa31f83f33ff0096f2b8509eb137953d983ddb",
31
  "build_info.py": "bffe4eb92fe3361f4ceb43b1264ca1528962ac67fa629de047f590165d238ee8",
 
32
  "builtin_tracks/catalog.json": "4a7bba4385713e985d1427a0da35dcc32d5ecfd73d93cad7520563deb8685670",
33
  "builtin_tracks/tracks/Replica A01 Beginner.track": "ca5749537450430794e2eade2e0ac72fa141a4f225e13e2f8ef842abb948ff15",
34
  "builtin_tracks/tracks/Replica A02 Sharp Turns.track": "097f1ea8edf041112b690d0638075630716ddc03c7d0b1980ddcbeaf5071c8d4",
@@ -58,14 +59,13 @@
58
  "builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
59
  "builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
60
  "builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
61
- "builtin_tracks.py": "52cb4a285a6079cec7f7610de8af80570f1644a02cf9979e47d2d49736e24cb5",
62
- "client.js": "cbb1dd26a8fe4256abf827903df94749dc8e4f99912d29dba7f74ba6e477b851",
63
  "delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
64
  "gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
65
- "help.html": "4ce8f3995723a419d810c60212e996d71bfc0fcb61d44bf4fa8500030c356f47",
66
  "hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
67
  "hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
68
- "index.html": "99e2718ccaf02002a472d5c6dbf25e44776769c6570c3a135e6bbff0e26a0820",
69
  "native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
70
  "native/build-receipt.json": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
71
  "native/physics-build-receipt.json": "63561c108554a09884cc68a1bfadfb8a3f530a800de0700ff671d99e558609fe",
@@ -94,6 +94,6 @@
94
  "worker.py": "d18053363baf8147425808e9fd939f44f00af23781662d353196ade974144974"
95
  },
96
  "interface": "single-engine-runner-with-map-winners-analysis-v1",
97
- "upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0",
98
- "physics_revision": "20261017-v1.3-candidate"
99
  }
 
29
  "bin/libaltd_gpu.so": "e5e9a61d863bc3b429af69a13cd8d50f63cad8984b3e6a9001ae342fc5981e05",
30
  "bucket_storage.py": "f3044964724de322c1372adc23fa31f83f33ff0096f2b8509eb137953d983ddb",
31
  "build_info.py": "bffe4eb92fe3361f4ceb43b1264ca1528962ac67fa629de047f590165d238ee8",
32
+ "builtin_tracks.py": "52cb4a285a6079cec7f7610de8af80570f1644a02cf9979e47d2d49736e24cb5",
33
  "builtin_tracks/catalog.json": "4a7bba4385713e985d1427a0da35dcc32d5ecfd73d93cad7520563deb8685670",
34
  "builtin_tracks/tracks/Replica A01 Beginner.track": "ca5749537450430794e2eade2e0ac72fa141a4f225e13e2f8ef842abb948ff15",
35
  "builtin_tracks/tracks/Replica A02 Sharp Turns.track": "097f1ea8edf041112b690d0638075630716ddc03c7d0b1980ddcbeaf5071c8d4",
 
59
  "builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
60
  "builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
61
  "builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
62
+ "client.js": "0dca9e5b08a974c0f37f9ad5d405b24ff76a011e62b06feb2168860513d52744",
 
63
  "delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
64
  "gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
65
+ "help.html": "c9b2dd4e3bfaa6a7ece921760489ed70ae077af301e57deaa9334b9dd73aecf2",
66
  "hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
67
  "hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
68
+ "index.html": "56202aa8029b23f171c4fa1053c5c22c01be7b61388ac774de8fe22ceb831519",
69
  "native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
70
  "native/build-receipt.json": "559a7e4e382f9f7caa1c5ceb566a5a0bbefad2ed3a2e2cf604e31c9c3890c348",
71
  "native/physics-build-receipt.json": "63561c108554a09884cc68a1bfadfb8a3f530a800de0700ff671d99e558609fe",
 
94
  "worker.py": "d18053363baf8147425808e9fd939f44f00af23781662d353196ade974144974"
95
  },
96
  "interface": "single-engine-runner-with-map-winners-analysis-v1",
97
+ "physics_revision": "20261017-v1.3-candidate",
98
+ "upstream_revision": "b90b8af06ffc4ea4e4fd703becd1107a48b8e5e0"
99
  }
client.js CHANGED
@@ -54,6 +54,21 @@ async function runTrackSource(priorContext=null){
54
  }
55
  throw Error('Choose or select a track before starting this run.');
56
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  function buttons() {
58
  const settingsUnavailable=busy||bundleRestoring||bucketOperation;
59
  const unavailable=settingsUnavailable||!!job;
@@ -72,7 +87,11 @@ function buttons() {
72
  $('forgetLocal').disabled = busy || !localResult;
73
  $('track').disabled=unavailable;
74
  $('network').disabled=unavailable;
75
- if($('batch_start'))$('batch_start').disabled=unavailable || !capabilitiesReady || $('run_mode')?.value==='evaluate' || ($('backend').value==='gpu'&&!gpuAvailable);
 
 
 
 
76
  if($('batch_stop'))$('batch_stop').disabled=!batchActive || batchStopRequested;
77
  if($('run_export'))$('run_export').disabled=busy || !runReceipt || pairedAnalysisPending;
78
  const report=pairedAnalysisPending?null:(cleanRunReceipt(runReceipt)||cleanRunReceipt(portableReceipt));
@@ -114,17 +133,17 @@ function syncRunModeUI(unavailable=busy||bundleRestoring||bucketOperation){
114
  if($('run'))$('run').textContent=evaluation?'Evaluate policy (weights frozen)':'Run training';
115
  if($('run_mode_help'))$('run_mode_help').textContent=evaluation
116
  ?'Frozen evaluation uses the selected policy unchanged in a fresh car. Mutation, decay, crossover, reproduction and winner selection are disabled.'
117
- :'Training uses your selected recipe and records winner policies and per-car measurements.';
118
  }
119
  if($('run_mode'))$('run_mode').addEventListener('change',()=>{
120
  syncRunModeUI();buttons();
121
  if(selectedWinner)say($('run_mode').value==='train'
122
- ?'Selected policy is staged as the starting network for training. The next Run click starts a training run with the selected recipe.'
123
  :'Selected policy is ready for frozen evaluation. Its weights stay unchanged; the next Run click starts it.');
124
  });
125
  function showFirstRunTab(tab,focusId){
126
- document.querySelector('[data-tab="'+tab+'"]')?.click();
127
- requestAnimationFrame(()=>$(focusId)?.focus());
128
  }
129
  if($('first_run_track'))$('first_run_track').onclick=()=>showFirstRunTab('drive','trackPresetDrive');
130
  if($('first_run_settings'))$('first_run_settings').onclick=()=>showFirstRunTab('training','population');
@@ -436,6 +455,8 @@ async function encoded(file) {
436
  }
437
  const headers = () => ({'X-Job-Key':job.key});
438
  const networkSource=()=>stagedNetwork || $('network').files[0];
 
 
439
  async function refreshFreezeNetwork(file){
440
  if(!file){window.setFreezeNetwork?.(null);return;}
441
  try{const network=await validateNetworkBytes(new Uint8Array(await file.arrayBuffer()));if(networkSource()===file)window.setFreezeNetwork?.(network);}
@@ -1016,7 +1037,7 @@ $('run').onclick = async () => {
1016
  $('runState').textContent=workLimit?'SETTINGS NEED ADJUSTMENT — no run started':'ERROR — no successful result confirmed';
1017
  $('runState').style.color='#ffb5a2';
1018
  const quotaRejected=isGpuQuotaError(error)||String(error?.message??'').startsWith('GPU allowance is exhausted for this request.');
1019
- $('runSummary').textContent=workLimit?'No run was submitted. Change population, generations, or ticks on Training runs & presets, then try again.':
1020
  'Elapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\n'+(quotaRejected
1021
  ? 'HF rejected this GPU request before a result was published.'
1022
  : 'No downloadable result was received. A connection error does not prove the server stopped.');
@@ -1050,13 +1071,18 @@ $('erase').onclick=async()=>{
1050
  if($('batch_start')){
1051
  $('batch_stop').onclick=()=>{batchStopRequested=true;buttons();$('batch_status').textContent='Stop requested — the current run will finish; no next run will start.';};
1052
  $('batch_start').onclick=async()=>{
1053
- if(busy||job||!capabilitiesReady)return;
 
 
 
 
1054
  let plan,failedCandidateStep=null;const history=[];const started=Date.now();
1055
  try{
1056
- if(busy||job||!capabilitiesReady)throw Error('Finish the current job and connect first');
1057
  plan=window.getBatchPlan();
1058
  busy=true;buttons(); // Freeze both selectors before an asynchronous track fetch.
1059
- const trackFile=await trackSource(),trackBytes=new Uint8Array(await trackFile.arrayBuffer()),track=await encoded(trackFile),trackInfo=trackLibrary?.info()??{source:'upload'},original=networkSource();
 
 
1060
  await encoded(original); // Reject missing/oversize inputs before dispatch.
1061
  const backend=$('backend').value,autosaveSnapshot=bucketToken&&$('bucket_autosave')?.checked?bucketSnapshot():null;
1062
  busy=true;batchActive=true;batchStopRequested=false;batchReceipt=null;buttons();
@@ -1067,7 +1093,7 @@ if($('batch_start')){
1067
  $('runState').style.color='#ffd073';
1068
  showDetails(null);
1069
  const result=await window.BatchTraining.execute(plan,original,async(source,settings,i)=>{
1070
- $('batch_status').textContent='RUNNING · '+plan.name+' · step '+(i+1)+' / '+plan.runs.length+' · '+plan.runs[i].slot;
1071
  const inputHash=await sha(await source.arrayBuffer());
1072
  job=await submit({track,network:await encoded(source),settings,backend,automatic:true});
1073
  const stepJob=job,report=stepJob.report,stepRunReceipt={version:1,state:'completed',build:report?.build??appBuild,backend,output_sha256:stepJob.sha256,report};
@@ -1095,7 +1121,7 @@ if($('batch_start')){
1095
  $('batch_history').append(line);
1096
  }});
1097
  batchReceipt={version:1,build:appBuild,state:result.state,plan,track:trackLibrary?.info()??{source:'upload'},history,elapsed_seconds:(Date.now()-started)/1000};
1098
- $('batch_status').textContent=result.state.toUpperCase()+' · '+history.length+' / '+plan.runs.length+' runs. '+(localResult?'Last received candidate is available on Drive & view.':'No candidate received.');
1099
  $('runState').textContent='BATCH '+result.state.toUpperCase()+' — '+history.length+' / '+plan.runs.length+' runs; server copies deleted';
1100
  $('runState').style.color=result.state==='completed'?'#62dfb1':'#ffd073';
1101
  $('runSummary').textContent='Backend: '+backend.toUpperCase()+'\nCompleted steps: '+history.length+'\nUse Save received result again for the last candidate. A completed batch does not establish improved driving.';
@@ -1166,6 +1192,7 @@ async function connect(){
1166
  $('retry').onclick=()=>{if(!busy&&!job)connect();};
1167
  connect();
1168
 
 
1169
  if($('run_export'))$('run_export').onclick=()=>{
1170
  if(!runReceipt)return;
1171
  const cleaned=cleanRunReceipt(runReceipt);if(!cleaned)return;
@@ -1448,3 +1475,5 @@ if($('bundle_receipt_download'))$('bundle_receipt_download').onclick=()=>{
1448
  const url=URL.createObjectURL(new Blob([JSON.stringify(portableReceipt,null,2)],{type:'application/json'}));
1449
  const a=document.createElement('a');a.href=url;a.download='ald-run-receipt.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
1450
  };
 
 
 
54
  }
55
  throw Error('Choose or select a track before starting this run.');
56
  }
57
+ function batchRunContext(){
58
+ const trackInfo=trackLibrary?.info()??{source:'upload'},trackFile=$('track').files[0],network=networkSource(),backend=$('backend').value;
59
+ const hasTrack=trackInfo.source==='builtin'||!!trackFile;
60
+ let reason='';
61
+ if(busy||bundleRestoring||bucketOperation)reason='Finish the current operation before starting a batch.';
62
+ else if(job)reason='Finish or delete the current result before starting a batch.';
63
+ else if(!capabilitiesReady)reason='Connect to the training service before running a batch.';
64
+ else if($('run_mode')?.value==='evaluate')reason='Under Action in Run & results, choose Train a population before running a batch.';
65
+ else if(!network)reason='Choose or create a starting network in Run & results.';
66
+ else if(!hasTrack)reason='Choose a track in Run & results.';
67
+ else if(backend==='gpu'&&!gpuAvailable)reason='GPU is unavailable. Choose CPU in Run & results.';
68
+ return {available:!reason,reason,
69
+ track:trackInfo.source==='builtin'?trackInfo.display_name:trackFile?.name,
70
+ network:network?.name||(network?'Selected network':null),backend:backend.toUpperCase()};
71
+ }
72
  function buttons() {
73
  const settingsUnavailable=busy||bundleRestoring||bucketOperation;
74
  const unavailable=settingsUnavailable||!!job;
 
87
  $('forgetLocal').disabled = busy || !localResult;
88
  $('track').disabled=unavailable;
89
  $('network').disabled=unavailable;
90
+ if($('batch_start')){
91
+ const context=batchRunContext();
92
+ window.setBatchRunContext?.(context);
93
+ $('batch_start').disabled=!context.available||!window.getBatchReadiness?.()?.ready;
94
+ }
95
  if($('batch_stop'))$('batch_stop').disabled=!batchActive || batchStopRequested;
96
  if($('run_export'))$('run_export').disabled=busy || !runReceipt || pairedAnalysisPending;
97
  const report=pairedAnalysisPending?null:(cleanRunReceipt(runReceipt)||cleanRunReceipt(portableReceipt));
 
133
  if($('run'))$('run').textContent=evaluation?'Evaluate policy (weights frozen)':'Run training';
134
  if($('run_mode_help'))$('run_mode_help').textContent=evaluation
135
  ?'Frozen evaluation uses the selected policy unchanged in a fresh car. Mutation, decay, crossover, reproduction and winner selection are disabled.'
136
+ :'Training uses the current settings in Training & recipes and records winner policies and per-car measurements.';
137
  }
138
  if($('run_mode'))$('run_mode').addEventListener('change',()=>{
139
  syncRunModeUI();buttons();
140
  if(selectedWinner)say($('run_mode').value==='train'
141
+ ?'Selected policy is staged as the starting network for training. The next Run click starts a training run with the current settings.'
142
  :'Selected policy is ready for frozen evaluation. Its weights stay unchanged; the next Run click starts it.');
143
  });
144
  function showFirstRunTab(tab,focusId){
145
+ const section=document.querySelector('[data-tab="'+tab+'"]');section?.click();
146
+ requestAnimationFrame(()=>{const target=$(focusId);(target&&!target.matches(':disabled')&&!target.closest('[hidden]')?target:section)?.focus();});
147
  }
148
  if($('first_run_track'))$('first_run_track').onclick=()=>showFirstRunTab('drive','trackPresetDrive');
149
  if($('first_run_settings'))$('first_run_settings').onclick=()=>showFirstRunTab('training','population');
 
455
  }
456
  const headers = () => ({'X-Job-Key':job.key});
457
  const networkSource=()=>stagedNetwork || $('network').files[0];
458
+ window.addEventListener('ald:batch-draft-change',buttons);
459
+ document.addEventListener('track-library-change',buttons);
460
  async function refreshFreezeNetwork(file){
461
  if(!file){window.setFreezeNetwork?.(null);return;}
462
  try{const network=await validateNetworkBytes(new Uint8Array(await file.arrayBuffer()));if(networkSource()===file)window.setFreezeNetwork?.(network);}
 
1037
  $('runState').textContent=workLimit?'SETTINGS NEED ADJUSTMENT — no run started':'ERROR — no successful result confirmed';
1038
  $('runState').style.color='#ffb5a2';
1039
  const quotaRejected=isGpuQuotaError(error)||String(error?.message??'').startsWith('GPU allowance is exhausted for this request.');
1040
+ $('runSummary').textContent=workLimit?'No run was submitted. Change population, generations, or ticks on Training & recipes, then try again.':
1041
  'Elapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\n'+(quotaRejected
1042
  ? 'HF rejected this GPU request before a result was published.'
1043
  : 'No downloadable result was received. A connection error does not prove the server stopped.');
 
1071
  if($('batch_start')){
1072
  $('batch_stop').onclick=()=>{batchStopRequested=true;buttons();$('batch_status').textContent='Stop requested — the current run will finish; no next run will start.';};
1073
  $('batch_start').onclick=async()=>{
1074
+ const context=batchRunContext(),readiness=window.getBatchReadiness?.();
1075
+ if(!context.available||!readiness?.ready){
1076
+ $('batch_status').textContent=!readiness?.ready?(readiness?.message||'Save a valid batch before running it.'):context.reason;
1077
+ buttons();return;
1078
+ }
1079
  let plan,failedCandidateStep=null;const history=[];const started=Date.now();
1080
  try{
 
1081
  plan=window.getBatchPlan();
1082
  busy=true;buttons(); // Freeze both selectors before an asynchronous track fetch.
1083
+ const trackFile=await trackSource();
1084
+ if(!trackFile)throw Error('Choose a track in Run & results before running a batch.');
1085
+ const trackBytes=new Uint8Array(await trackFile.arrayBuffer()),track=await encoded(trackFile),trackInfo=trackLibrary?.info()??{source:'upload'},original=networkSource();
1086
  await encoded(original); // Reject missing/oversize inputs before dispatch.
1087
  const backend=$('backend').value,autosaveSnapshot=bucketToken&&$('bucket_autosave')?.checked?bucketSnapshot():null;
1088
  busy=true;batchActive=true;batchStopRequested=false;batchReceipt=null;buttons();
 
1093
  $('runState').style.color='#ffd073';
1094
  showDetails(null);
1095
  const result=await window.BatchTraining.execute(plan,original,async(source,settings,i)=>{
1096
+ $('batch_status').textContent='RUNNING · '+plan.name+' · step '+(i+1)+' / '+plan.runs.length+' · '+plan.runs[i].slot+' · '+plan.runs[i].name;
1097
  const inputHash=await sha(await source.arrayBuffer());
1098
  job=await submit({track,network:await encoded(source),settings,backend,automatic:true});
1099
  const stepJob=job,report=stepJob.report,stepRunReceipt={version:1,state:'completed',build:report?.build??appBuild,backend,output_sha256:stepJob.sha256,report};
 
1121
  $('batch_history').append(line);
1122
  }});
1123
  batchReceipt={version:1,build:appBuild,state:result.state,plan,track:trackLibrary?.info()??{source:'upload'},history,elapsed_seconds:(Date.now()-started)/1000};
1124
+ $('batch_status').textContent=result.state.toUpperCase()+' · '+history.length+' / '+plan.runs.length+' runs. '+(localResult?'Last received candidate is available on Run & results.':'No candidate received.');
1125
  $('runState').textContent='BATCH '+result.state.toUpperCase()+' — '+history.length+' / '+plan.runs.length+' runs; server copies deleted';
1126
  $('runState').style.color=result.state==='completed'?'#62dfb1':'#ffd073';
1127
  $('runSummary').textContent='Backend: '+backend.toUpperCase()+'\nCompleted steps: '+history.length+'\nUse Save received result again for the last candidate. A completed batch does not establish improved driving.';
 
1192
  $('retry').onclick=()=>{if(!busy&&!job)connect();};
1193
  connect();
1194
 
1195
+
1196
  if($('run_export'))$('run_export').onclick=()=>{
1197
  if(!runReceipt)return;
1198
  const cleaned=cleanRunReceipt(runReceipt);if(!cleaned)return;
 
1475
  const url=URL.createObjectURL(new Blob([JSON.stringify(portableReceipt,null,2)],{type:'application/json'}));
1476
  const a=document.createElement('a');a.href=url;a.download='ald-run-receipt.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
1477
  };
1478
+
1479
+
help.html CHANGED
@@ -14,26 +14,58 @@
14
  <header>
15
  <p class="eyebrow">A bounded simulator experiment</p>
16
  <h1>AI Learns to Drive — Help</h1>
17
- <p class="lead">Create or load a driving controller, train a population of candidates, and inspect what happened. The app simulates cars behind the scenes. The map is a track-layout and terrain preview; it does not show cars driving.</p>
18
- <p class="small">This guide opens in a new tab to keep your current run and unsaved choices in place. Close this tab to return to the lab.</p>
19
  <p class="callout">You can try the built-in replica tracks and browser network editor without installing the original game. This lab does not reproduce the full game, award official stars, or change game progress.</p>
20
  <nav aria-label="Help topics"><ul>
21
- <li><a href="#first-run">Start a run</a></li><li><a href="#concepts">Basic ideas</a></li><li><a href="#freeze">Selective freezing</a></li><li><a href="#continue">Continue a winner</a></li><li><a href="#compare">Compare policies</a></li><li><a href="#storage">Files and storage</a></li><li><a href="#limits">Limits and compute</a></li><li><a href="#feedback">Feedback</a></li>
22
  </ul></nav>
23
  </header>
24
 
 
25
  <section class="section" id="first-run">
26
  <h2>Start your first run</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  <ol class="steps">
28
- <li>On <strong>Drive &amp; view</strong>, click <strong>Choose a track</strong> and select a built-in replica, or upload a <code>.track</code> file.</li>
29
- <li>Pick a vehicle for a new network, then click <strong>Create starter network</strong>. It makes a small controller in this page only; it does not start training. You can instead upload a compatible <code>.evolution</code> file or use <strong>Network editor</strong>.</li>
30
- <li>Click <strong>Review first-run settings</strong>. The starting settings are 32 candidates, 20 generations, and 900 ticks per generation (15 simulated seconds per car per generation), mutation 0.3, decay 0, one parent, and one elite candidate. The reward is distance-only (+100); collision and idle elimination are off. These values are a starting experiment, not a promise of a lap.</li>
31
- <li>Choose <strong>CPU — no GPU allowance</strong> for the no-sign-in option. Under <strong>Action</strong>, select <strong>Train a population</strong>, then click <strong>Run training</strong>. For an unchanged single controller, select <strong>Frozen evaluation · one policy, unchanged weights</strong>; the run button changes to <strong>Evaluate policy (weights frozen)</strong>.</li>
32
- <li>When the run finishes, click <strong>Download result</strong> to receive the controller and reveal winner and challenge details. If connected bucket autosave is on, it handles result delivery for you.</li>
33
  </ol>
34
- <p class="small">To restore the form's starter values later, use <strong>Apply first-run settings to this form</strong>. This changes the current form only; saved recipes stay as they are.</p>
 
 
35
  </section>
36
 
 
37
  <section class="section" id="concepts">
38
  <h2>Basic ideas</h2>
39
  <ul>
@@ -42,17 +74,20 @@
42
  <li><strong>Generation:</strong> one round of simulation and selection. Training may mutate or recombine candidates between generations.</li>
43
  <li><strong>Reward:</strong> a weighted choice of measured outcomes the trainer should favor. Reward settings guide selection; they do not change the simulator's physics.</li>
44
  <li><strong>Frozen evaluation:</strong> runs one policy with unchanged weights. It does not train or create a winner population.</li>
45
- <li><strong>Recipe:</strong> a reusable set of training settings and reward choices. Recipes are saved in this browser by default; they do not contain networks, tracks, or results.</li>
 
46
  </ul>
47
  <p>Training measurements and fresh challenge checks are simulator results for reconstructed tracks. A supported challenge check replays the named current network with fixed weights; it does not accumulate stars across runs or evaluate original-game opponent races.</p>
48
  <p>Challenge checks use the selected driving settings, including collision elimination, and the chosen run duration or the track’s longer challenge deadline, capped at 300 simulated seconds. This extra replay may add processing time. A condition that is not met within that replay is not proof that the network could never meet it in a longer run.</p>
49
  </section>
50
 
 
51
  <section class="section" id="freeze">
52
  <h2>Selective freezing</h2>
53
- <p>In Training settings, open <strong>Selective freezing</strong> to keep selected parameters at their values in the starting network. Freezing an input holds its outgoing weights; freezing a hidden layer holds its incoming weights and biases; freezing an output holds its incoming weights and bias. The available choices and names come from the selected network. Freeze selections are part of saved recipes and can be edited later. Frozen evaluation runs the selected policy unchanged.</p>
54
  </section>
55
 
 
56
  <section class="section" id="continue">
57
  <h2>Continue a winner</h2>
58
  <p>After the result has been received, select a role from <strong>Winner to continue or evaluate</strong>:</p>
@@ -64,12 +99,14 @@
64
  <p>Fitness winner means the best candidate under your chosen reward in the final generation. Fastest lap is the fastest completed forward lap observed during training. A clean-lap winner must meet the configured forward-lap count with zero physical contacts. One controller may fill multiple roles, and a role can be unavailable.</p>
65
  </section>
66
 
 
67
  <section class="section" id="compare">
68
  <h2>Compare policies</h2>
69
  <p>Frozen comparison runs selected policies in fresh simulations with their weights unchanged. Use the same track, vehicle, duration, controller frequency, collision and idle settings, and compute backend when you want a direct comparison. The app marks incompatible conditions instead of treating them as equivalent.</p>
70
  <p>Look at lap time, completed laps, contacts, distance, and challenge status together. A short run may end before a lap or challenge deadline. A challenge result is a bounded prediction for the replica track conditions; it is not an original-game award or a Steam leaderboard result.</p>
71
  </section>
72
 
 
73
  <section class="section" id="storage">
74
  <h2>Files, privacy, and storage</h2>
75
  <p>Your selected files stay in the browser until you start a run or explicitly save/export them. Processing copies and undelivered results are temporary; results expire within five minutes and are deleted after verified browser receipt by default. The app's cleanup does not establish provider-wide zero-data retention.</p>
@@ -78,6 +115,7 @@
78
  <p>Read the full <a href="/policy" target="_blank" rel="noopener">data policy</a> before using private files or bucket storage.</p>
79
  </section>
80
 
 
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>
@@ -86,6 +124,7 @@
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>
88
 
 
89
  <section class="section" id="feedback">
90
  <h2>Feedback</h2>
91
  <p><a href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Open an editable GitHub issue draft</a>. It is not submitted automatically. Review the draft before posting and remove private names or data. Do not attach tokens, job keys, private networks, proprietary tracks, or unrelated logs.</p>
@@ -94,3 +133,5 @@
94
  </main>
95
  </body>
96
  </html>
 
 
 
14
  <header>
15
  <p class="eyebrow">A bounded simulator experiment</p>
16
  <h1>AI Learns to Drive — Help</h1>
17
+ <p class="lead">Choose a track, create or load a driving controller, then train and inspect the result. Cars are simulated behind the scenes; the map previews track layout and terrain, without showing cars driving.</p>
18
+ <p class="small">The app’s <strong>Quick start</strong> button opens the short <strong>New here? Start a first run</strong> checklist. <strong>Help &amp; tutorial</strong> keeps this full guide one click away. This guide opens in a new tab, so your run and unsaved choices stay in place. Close this tab to return to the lab.</p>
19
  <p class="callout">You can try the built-in replica tracks and browser network editor without installing the original game. This lab does not reproduce the full game, award official stars, or change game progress.</p>
20
  <nav aria-label="Help topics"><ul>
21
+ <li><a href="#first-run">First run</a></li><li><a href="#recipes">Recipes</a></li><li><a href="#batches">Batches</a></li><li><a href="#concepts">Basic ideas</a></li><li><a href="#freeze">Selective freezing</a></li><li><a href="#continue">Continue a winner</a></li><li><a href="#compare">Compare policies</a></li><li><a href="#storage">Files and storage</a></li><li><a href="#limits">Limits and compute</a></li><li><a href="#feedback">Feedback</a></li>
22
  </ul></nav>
23
  </header>
24
 
25
+
26
  <section class="section" id="first-run">
27
  <h2>Start your first run</h2>
28
+ <p>You can start with the built-in settings. There is no need to save a recipe or build a batch first.</p>
29
+ <ol class="steps">
30
+ <li><strong>Choose a track.</strong> On <strong>Run &amp; results</strong>, select a built-in replica or upload a <code>.track</code> file. The <strong>Choose a track</strong> button in Quick start takes you to the track controls.</li>
31
+ <li><strong>Create a controller.</strong> Pick a starter car and click <strong>Create starter network</strong>. This creates a small controller in the page without starting training. You can also upload a compatible <code>.evolution</code> file or use <strong>Network editor</strong>.</li>
32
+ <li><strong>Review the settings.</strong> Open <strong>Training &amp; recipes</strong>, or use <strong>Review first-run settings</strong> in Quick start. Keep the starter values for a first experiment, or adjust them.</li>
33
+ <li><strong>Start training.</strong> Return to <strong>Run &amp; results</strong>. Choose <strong>CPU — no GPU allowance</strong> to run without signing in. Under <strong>Action</strong>, select <strong>Train a population</strong>, then click <strong>Run training</strong>.</li>
34
+ <li><strong>Receive the result.</strong> When the run finishes, click <strong>Download result</strong> to receive the controller and reveal winner and challenge details. If connected bucket autosave is on, it handles result delivery for you.</li>
35
+ </ol>
36
+ <p class="small">The starter uses 32 candidates, 20 generations, and 900 ticks per generation (15 simulated seconds per car per generation), mutation 0.3, decay 0, one parent, and one elite candidate. The reward is distance-only (+100); collision and idle elimination are off. These values are a starting experiment, not a promise of a lap.</p>
37
+ <p class="small">To restore these values later, use <strong>Apply first-run settings to this form</strong>. Saved recipes stay as they are. To test an existing controller with unchanged weights, choose <strong>Frozen evaluation · one policy, unchanged weights</strong> under Action, then click <strong>Evaluate policy (weights frozen)</strong>.</p>
38
+ </section>
39
+
40
+
41
+ <section class="section" id="recipes">
42
+ <h2>Recipes: save a setup you want to reuse</h2>
43
+ <p>A recipe holds training settings, reward choices, and optional selective freezing. It does not contain a track, network, or result. Keep up to ten named recipes in slots <strong>A–J</strong> at the top of <strong>Training &amp; recipes</strong>.</p>
44
+ <ol class="steps">
45
+ <li><strong>Save a setup.</strong> Adjust the training form, select a recipe slot, and give it a useful name. Click <strong>Save settings to A</strong> (or your chosen slot) to save the current settings in this browser.</li>
46
+ <li><strong>Reuse it.</strong> Select a named recipe and click <strong>Apply recipe</strong> to copy its settings into the form. Choosing a slot alone does not apply it. Applying a recipe does not start training.</li>
47
+ <li><strong>Check what is saved.</strong> The status shows whether the current form matches the saved recipe or has unsaved changes. Save again when you want to update that recipe.</li>
48
+ </ol>
49
+ <p><strong>Run training</strong> uses the current form. <strong>Batches use saved recipes</strong>, so save your changes before using them in a batch. Choose the track, starting network, and CPU or GPU on <strong>Run &amp; results</strong>.</p>
50
+ <p class="small">Recipe libraries can be exported and imported as JSON. See <a href="#storage">Files and storage</a> for local and optional private-bucket storage.</p>
51
+ </section>
52
+
53
+
54
+ <section class="section" id="batches">
55
+ <h2>Batches: run saved recipes in order</h2>
56
+ <p>A batch is an ordered list of saved recipes. Use it when you want several training steps to run in sequence on the track and compute backend selected on <strong>Run &amp; results</strong>.</p>
57
  <ol class="steps">
58
+ <li><strong>Save your recipes first.</strong> On <strong>Batches</strong>, choose a batch slot and give the batch a name. Recipe slots and batch slots are separate.</li>
59
+ <li><strong>Build the sequence.</strong> Choose a saved recipe and click <strong>Add step</strong>. Use the step controls to move it up or down, duplicate it, or remove it. Repeats are allowed, with up to 20 steps per run. <strong>Advanced sequence</strong> also accepts comma-separated recipe slots, such as <code>A, B, A</code>.</li>
60
+ <li><strong>Choose where each step starts.</strong> Check <strong>Use each step’s result to train the next step</strong> to continue from the previous step’s result. Leave it unchecked to start every step from the same original network.</li>
61
+ <li><strong>Save, then run.</strong> Click <strong>Save batch</strong> to store the definition. Review the starting network, track, and CPU or GPU on <strong>Run &amp; results</strong>, then click <strong>Run batch</strong> on Batches.</li>
 
62
  </ol>
63
+ <p>For example, you could name recipe A “Explore” and recipe B “Refine”, then run <code>A, B, A</code>. With continuation checked, the network trains through all three steps. Unchecked, each step begins with the original network. These names are examples; the app uses the recipes you saved in those slots.</p>
64
+ <p><strong>Save batch and Run batch are separate actions.</strong> Changing a batch’s name, sequence, or continuation option creates an unsaved draft and disables Run batch. Save the draft to use it, or click <strong>Load saved batch</strong> to restore the saved version.</p>
65
+ <p class="small">Starting a batch takes a snapshot of its saved recipe settings. Later recipe changes apply to future batch launches. A batch stores recipe references and its continuation choice; it does not save networks, tracks, or results.</p>
66
  </section>
67
 
68
+
69
  <section class="section" id="concepts">
70
  <h2>Basic ideas</h2>
71
  <ul>
 
74
  <li><strong>Generation:</strong> one round of simulation and selection. Training may mutate or recombine candidates between generations.</li>
75
  <li><strong>Reward:</strong> a weighted choice of measured outcomes the trainer should favor. Reward settings guide selection; they do not change the simulator's physics.</li>
76
  <li><strong>Frozen evaluation:</strong> runs one policy with unchanged weights. It does not train or create a winner population.</li>
77
+ <li><strong>Recipe:</strong> a reusable set of training settings and reward choices, saved in this browser by default. <a href="#recipes">Save and apply recipes</a>.</li>
78
+ <li><strong>Batch:</strong> an ordered sequence of saved recipes, with a choice to continue from each step’s result or restart from the original network. <a href="#batches">Build a batch</a>.</li>
79
  </ul>
80
  <p>Training measurements and fresh challenge checks are simulator results for reconstructed tracks. A supported challenge check replays the named current network with fixed weights; it does not accumulate stars across runs or evaluate original-game opponent races.</p>
81
  <p>Challenge checks use the selected driving settings, including collision elimination, and the chosen run duration or the track’s longer challenge deadline, capped at 300 simulated seconds. This extra replay may add processing time. A condition that is not met within that replay is not proof that the network could never meet it in a longer run.</p>
82
  </section>
83
 
84
+
85
  <section class="section" id="freeze">
86
  <h2>Selective freezing</h2>
87
+ <p>On <strong>Training &amp; recipes</strong>, open <strong>Selective freezing</strong> to keep selected parameters at their values in the starting network. Freezing an input holds its outgoing weights; freezing a hidden layer holds its incoming weights and biases; freezing an output holds its incoming weights and bias. The available choices and names come from the selected network. Freeze selections are part of saved recipes and can be edited later. Frozen evaluation runs the selected policy unchanged.</p>
88
  </section>
89
 
90
+
91
  <section class="section" id="continue">
92
  <h2>Continue a winner</h2>
93
  <p>After the result has been received, select a role from <strong>Winner to continue or evaluate</strong>:</p>
 
99
  <p>Fitness winner means the best candidate under your chosen reward in the final generation. Fastest lap is the fastest completed forward lap observed during training. A clean-lap winner must meet the configured forward-lap count with zero physical contacts. One controller may fill multiple roles, and a role can be unavailable.</p>
100
  </section>
101
 
102
+
103
  <section class="section" id="compare">
104
  <h2>Compare policies</h2>
105
  <p>Frozen comparison runs selected policies in fresh simulations with their weights unchanged. Use the same track, vehicle, duration, controller frequency, collision and idle settings, and compute backend when you want a direct comparison. The app marks incompatible conditions instead of treating them as equivalent.</p>
106
  <p>Look at lap time, completed laps, contacts, distance, and challenge status together. A short run may end before a lap or challenge deadline. A challenge result is a bounded prediction for the replica track conditions; it is not an original-game award or a Steam leaderboard result.</p>
107
  </section>
108
 
109
+
110
  <section class="section" id="storage">
111
  <h2>Files, privacy, and storage</h2>
112
  <p>Your selected files stay in the browser until you start a run or explicitly save/export them. Processing copies and undelivered results are temporary; results expire within five minutes and are deleted after verified browser receipt by default. The app's cleanup does not establish provider-wide zero-data retention.</p>
 
115
  <p>Read the full <a href="/policy" target="_blank" rel="noopener">data policy</a> before using private files or bucket storage.</p>
116
  </section>
117
 
118
+
119
  <section class="section" id="limits">
120
  <h2>Limits and compute</h2>
121
  <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>
 
124
  <p>These are bounded vanilla-worker limits. They do not cover arbitrary modified-game networks or establish full original-game physics parity.</p>
125
  </section>
126
 
127
+
128
  <section class="section" id="feedback">
129
  <h2>Feedback</h2>
130
  <p><a href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Open an editable GitHub issue draft</a>. It is not submitted automatically. Review the draft before posting and remove private names or data. Do not attach tokens, job keys, private networks, proprietary tracks, or unrelated logs.</p>
 
133
  </main>
134
  </body>
135
  </html>
136
+
137
+
index.html CHANGED
@@ -1,7 +1,11 @@
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:minmax(0,1fr)}.card{min-width:0}.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}.output-options{display:flex;flex-wrap:wrap;gap:5px 14px}.output-options label{display:flex;align-items:center;gap:5px;margin:5px 0}.output-options input{margin:0}#editor_outputs{margin:12px 0;padding:10px 12px;border:1px solid #49627c;border-radius:8px}#editor_outputs legend{padding:0 5px;color:#c6d9e9}.freeze-group{min-width:190px;flex:1;margin:8px 0;padding:8px 12px;border:1px solid #49627c;border-radius:8px}.freeze-group summary{padding:0 5px;color:#c6d9e9;cursor:pointer}.freeze-group label{display:block;margin:6px 0}@media(max-width:850px){.map{min-height:0}}dialog#appConfirm{box-sizing:border-box;width:min(92vw,440px);border:1px solid #54718d;border-radius:16px;padding:24px;background:#152233;color:#e5edf5;box-shadow:0 24px 80px #000b}dialog#appConfirm::backdrop{background:#02060bcc}#appConfirm h2{margin:0 0 10px}#appConfirm p{line-height:1.55;color:#c4d3e1;white-space:pre-wrap}#appConfirm .actions{display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:22px}#appConfirm button{min-width:100px}#appConfirm button.danger{background:#8e3b37;color:#fff;border-color:#d7837e}</style>
 
 
 
 
5
  <dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
6
  <h2 id="appConfirmTitle">Confirm action</h2>
7
  <p id="appConfirmMessage"></p>
@@ -28,9 +32,10 @@
28
  };
29
  })();
30
  </script>
31
- <header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Choose a track and car, create or select a network, then train and inspect the result. Cars are simulated behind the scenes; the map shows track layout, not moving cars.</p><p><a href="/help#first-run" target="_blank" rel="noopener">Help &amp; tutorial</a> · <a id="feedbackLink" href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Report a problem</a> (opens an editable draft; review it before posting)</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. For supported conditions, the lab checks the run’s final fitness policy (or frozen input) with a bounded fresh replay. It predicts conditions only and does not award or accumulate original-game stars; opponent races and other unsupported conditions remain unchecked.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p></details><p id="buildLabel" class="hint">Build: connecting…</p></header>
32
- <section id="firstRunGuide" class="card" aria-labelledby="firstRunTitle"><h2 id="firstRunTitle">Start with one small run</h2><p>1. Choose a track. 2. Create or upload a network. 3. Review the first-run settings. 4. Click Run training when ready. After completion, click Download result to receive winner and challenge details; a connected bucket with automatic saving retrieves them for you. The first run may not complete a lap.</p><div class="toolbar"><button id="first_run_track" type="button">Choose a track</button><button id="first_run_settings" type="button">Review first-run settings</button></div><p><a href="/help#first-run" target="_blank" rel="noopener">Open the step-by-step tutorial</a></p></section>
33
- <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><div class="toolbar" aria-label="Create a starter network"><label for="starter_vehicle">Starter car</label><select id="starter_vehicle"><option value="0" selected>Formula</option><option value="1">Rally</option><option value="2">Snowmobile</option><option value="3">Truck</option></select><button id="starter_create" type="button">Create starter network</button></div><p id="starter_network_status" class="hint" role="status">Creates a small network in this page only. It does not start a run or change saved recipes.</p><label for="run_mode">Action</label><select id="run_mode"><option value="train" selected>Train a population</option><option value="evaluate">Frozen evaluation · one policy, unchanged weights</option></select><p id="run_mode_help" class="hint">Training uses the selected recipe. Frozen evaluation runs the selected policy in a fresh car without changing its weights.</p><button id="run" disabled>Run training</button><p id="starCheckNotice" class="hint">After each run, supported star challenges are checked with unchanged weights. Results are predictions, not game awards. <a href="/help#concepts" target="_blank" rel="noopener">How checks work</a>.</p><p class="hint">Select or create a network below to enable training. Review settings on tab 2 before running.</p><p>Supports Formula, Rally, Snowmobile and Truck networks. Control outputs follow the original vehicle editor rules. Network shape and supported sensors are bounded by the original game contract.</p>
 
34
  <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>
35
  <div id="connectionRecovery" hidden><button id="retry" type="button">Reconnect to app</button><p class="hint">Checks server availability. Does not restart training, sign you in, or reset GPU allowance.</p></div>
36
  <div id="gpuAccount" aria-label="Optional GPU account"><p><button id="hfSignIn" type="button" disabled>Sign in with HF for account GPU allowance</button><span id="hfAccount" hidden></span> <button id="hfSignOut" type="button" disabled hidden>Disconnect account</button></p><p id="hfIdentity" role="status" class="hint">CPU needs no sign-in. Checking optional GPU account connection…</p>
@@ -38,6 +43,7 @@
38
  <p><label for="trackPresetDrive">Track selection</label><select id="trackPresetDrive" data-track-library disabled><option value="">Upload a custom track</option></select></p>
39
  <p data-track-status role="status" class="hint">Loading built-in replica tracks…</p>
40
 
 
41
  <p><label>Track (.track) <input id="track" type="file" accept=".track"></label></p>
42
  <p><label>Vehicle network (.evolution) <input id="network" type="file" accept=".evolution"></label></p>
43
  <p id="networkChoice" role="status">Choose an uploaded file or use a network from the browser editor.</p><p class="privacy-note">Uploads are processed for this run. By default, the server copy is deleted after the browser verifies the download.</p><details><summary>Optional repeat download</summary><label class="keep-result"><input id="keepServer" type="checkbox"> Keep this result on the server for up to five minutes so I can download it again</label><p class="privacy-note">This is optional and resets after each run. When bucket auto-save is on, verified receipt must delete the server copy first, so repeat download is disabled. All results expire after five minutes. <a href="/policy" target="_blank" rel="noopener">Data policy</a></p>
@@ -65,11 +71,19 @@
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
 
 
73
  <details id="native_settings"><summary>Native evolution settings</summary>
74
  <label for="preset">Recommended starting recipe</label><select id="preset" disabled><option value="custom">Custom / keep current settings</option><option value="starter" selected>First laps · mutation 0.3, no decay</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>
75
  <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>
@@ -85,16 +99,16 @@
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 selected>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>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>
90
- <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>
91
- <button id="recipe_save">Save current settings</button> <button id="recipe_load">Load recipe</button>
92
- <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> <button id="recipe_bucket_nav" type="button">Bucket import / export</button></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';
100
  const defaults={population:4,generations:1,ticks:60,mutation:0.01,seed:42,
@@ -282,6 +296,7 @@
282
  })(typeof window!=='undefined'?window:globalThis);
283
  </script><script>(() => {
284
  const q=id=>document.getElementById(id),api=window.RecipeLibrary;
 
285
  const message=text=>{q('recipe_feedback').textContent=text;q('batches_feedback').textContent=text;};
286
  const fields=['population','generations','mutation','seed','mutation_end','schedule','selection','parents','elite','crossover','decay','adaptive_mutation','control_hz','workers','collision','idle'];
287
  const booleans=['adaptive_mutation','collision','idle'];
@@ -350,7 +365,7 @@
350
  for(const name of fields){if(booleans.includes(name))q(name).checked=data[name];else q(name).value=data[name];}
351
  q('training_ticks').value=data.ticks;q('rewards').replaceChildren();data.rewards.forEach(rewardRow);
352
  for(const kind of ['layers','inputs','outputs'])selectedFreeze[kind]=[...data.freeze[kind]];renderFreeze();
353
- q('recipe_draft').value=JSON.stringify(data,null,2);updateWorkBudget();
354
  }
355
  const editable=[...fields,'training_ticks','add','reward_preset','preset','starter_settings_reset'];
356
  for(const id of editable)q(id).disabled=false;
@@ -371,9 +386,11 @@
371
  };
372
  q('recipe_slot').addEventListener('change',()=>{stagedPortableRecipe=false;});
373
  window.setTrainingBusy=(settingsBusy,controlsBusy=settingsBusy)=>{
 
374
  for(const id of editable)q(id).disabled=settingsBusy;
375
  for(const node of q('rewards').querySelectorAll('input,select,button'))node.disabled=settingsBusy;
376
  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=controlsBusy;
 
377
  };
378
  for(const id of ['population','generations','training_ticks'])q(id).addEventListener('input',updateWorkBudget);
379
  q('run_mode')?.addEventListener('change',updateWorkBudget);
@@ -391,66 +408,215 @@
391
  };
392
  for(const id of ['mutation','mutation_end','decay'])q(id).addEventListener('input',()=>{q('preset').value='custom';});
393
  let store;
394
- try{store=api.createStore(window.localStorage);store.load();message('Browser-local library ready.');}
395
  catch(error){message('Local library unavailable: '+error.message+'. Existing data was not changed.');}
396
  const read=()=>{if(!store)throw Error('Browser storage unavailable');return store.load();};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  window.recipeLibraryText=()=>{const text=JSON.stringify(api.validate(read()));if(new TextEncoder().encode(text).byteLength>32768)throw Error('Recipe library exceeds 32 KB');return text;};
398
  window.importRecipeLibraryText=async(text,{source='selected file',isCurrent=()=>true}={})=>{
399
  const book=api.parse(text);
400
  if(!(await window.appConfirm('Replace this browser’s recipe library with '+book.recipes.length+' recipes and '+book.batches.length+' batches from '+source+'? Export a backup first if needed.',{heading:'Import recipe library',confirmLabel:'Replace library'})))return false;
401
  if(!isCurrent())throw Error('Bucket connection changed before import; the local recipe library was not changed.');
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;
409
  applySettings(firstRunSettings);q('preset').value='starter';q('reward_preset').value='distance';q('starter_settings_status').textContent='First-run settings applied to this form. Saved recipes are unchanged; no run started.';
410
  });
411
- 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.');});
412
  q('recipe_save').onclick=action(async()=>{
413
  const book=read(),slot=q('recipe_slot').value;
414
- const next=api.validate({...book,recipes:[...book.recipes.filter(r=>r.slot!==slot),{slot,name:q('recipe_name').value,settings:currentSettings()}]});
415
- if(book.recipes.some(r=>r.slot===slot)&&!(await window.appConfirm('Replace recipe slot '+slot+' on this device?',{heading:'Replace saved recipe',confirmLabel:'Replace'})))return;
416
- store.save(next);message('Saved slot '+slot+' in this browser.');
417
  });
418
  q('recipe_load').onclick=action(()=>{
419
- const r=read().recipes.find(r=>r.slot===q('recipe_slot').value);if(!r)throw Error('This slot is empty');
420
- q('recipe_name').value=r.name;applySettings(r.settings);q('preset').value='custom';q('reward_preset').value='custom';
421
- message('Loaded '+r.slot+' into the training form. Loading a recipe does not start training.');
422
  });
 
423
  q('recipe_export').onclick=action(()=>{
424
  const blob=new Blob([JSON.stringify(read())],{type:'application/json'}),url=URL.createObjectURL(blob);
425
- const a=document.createElement('a');a.href=url;a.download='ald-recipes.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
426
- message('Recipe export requested. No tracks, networks, results or credentials are included by the application.');
427
  });
428
  q('recipe_import').onchange=action(async()=>{
429
  const input=q('recipe_import'),file=input.files[0];if(!file)return;input.value='';if(file.size>32768)throw Error('Recipe file exceeds 32 KB');
430
- await window.importRecipeLibraryText(await file.text(),{source:'selected file'});q('recipe_import').value='';
431
  });
432
  q('recipe_clear').onclick=action(async()=>{
433
  if(!(await window.appConfirm('Clear saved recipes and batches for this app on this device? Server run data and downloaded files are unaffected.',{heading:'Clear local recipes and batches',confirmLabel:'Clear local data',danger:true})))return;
434
- if(!store)throw Error('Browser storage unavailable');store.clear();message('Local recipes cleared. Server data and downloads are unaffected.');
435
  });
436
- q('batch_save').disabled=false;q('batch_load').disabled=false;
437
- q('batch_name').disabled=false;q('batch_slot').disabled=false;q('carry').disabled=false;
438
- q('queue_add').disabled=false;
439
  q('queue_add').onclick=()=>{
440
- const text=q('batch_recipe_sequence').value.trim();q('batch_recipe_sequence').value=text?text+', '+q('batch_add_recipe').value:q('batch_add_recipe').value;
 
 
441
  };
442
  q('batch_save').onclick=action(async()=>{
443
- const book=read(),slot=q('batch_slot').value;
444
- const runs=q('batch_recipe_sequence').value.split(',').map(s=>s.trim().toUpperCase());
445
- const next=api.validate({...book,batches:[...book.batches.filter(b=>b.slot!==slot),{slot,name:q('batch_name').value,carry:q('carry').checked,runs}]});
446
- if(book.batches.some(b=>b.slot===slot)&&!(await window.appConfirm('Replace saved batch slot '+slot+'?',{heading:'Replace saved batch',confirmLabel:'Replace'})))return;
447
- store.save(next);message('Batch '+slot+' saved locally.');
448
- });
449
  q('batch_load').onclick=action(()=>{
450
- const batch=read().batches.find(b=>b.slot===q('batch_slot').value);if(!batch)throw Error('This batch slot is empty');
451
  q('batch_name').value=batch.name;q('carry').checked=batch.carry;q('batch_recipe_sequence').value=batch.runs.join(', ');
452
- message('Batch '+batch.slot+' loaded. Loading a batch does not start it.');
453
- });
 
 
 
 
 
 
 
 
454
  })();
455
  </script><script>/* Read-only public reference library. No cookies or browser-persistent storage. */
456
  (function(root,factory){
@@ -613,20 +779,43 @@
613
  }
614
  return {SCHEMA,LIMITS,sha,artifact,analysisSlot,pack,parse};
615
  });</script><script src="/client.js"></script><script src="/hf-auth.js"></script><script>'use strict';
616
- // Navigation only. Editor and training/storage adapters are separate layers.
617
- document.querySelectorAll('[data-tab]').forEach(button=>button.addEventListener('click',()=>{
618
- document.querySelectorAll('.pane').forEach(pane=>pane.hidden=pane.id!==button.dataset.tab);
619
- document.querySelectorAll('[data-tab]').forEach(tab=>tab.setAttribute('aria-selected',String(tab===button)));
620
- }));
621
- document.getElementById('recipe_bucket_nav').addEventListener('click',()=>{
622
- document.querySelector('[data-tab="drive"]').click();
623
- document.getElementById('portableBundle').open=true;
624
- const storage=document.getElementById('bucketStorage');storage.open=true;
625
- const connected=document.getElementById('bucketConnected');
626
- const recipeTitle=document.getElementById('bucketRecipeTitle');
627
- const target=connected&&!connected.hidden&&recipeTitle?recipeTitle:storage.querySelector('summary');
628
- target?.scrollIntoView({behavior:'smooth',block:'center'});target?.focus({preventScroll:true});
629
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  </script><script>// Browser-only editor; upload is controlled by the separate training client.
631
  (() => {
632
  const q=id=>document.getElementById(id),api=window.NetworkEditor;
@@ -728,3 +917,5 @@ document.getElementById('recipe_bucket_nav').addEventListener('click',()=>{
728
  };
729
  })();
730
  </script></html>
 
 
 
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:minmax(0,1fr)}.card{min-width:0}.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}.output-options{display:flex;flex-wrap:wrap;gap:5px 14px}.output-options label{display:flex;align-items:center;gap:5px;margin:5px 0}.output-options input{margin:0}#editor_outputs{margin:12px 0;padding:10px 12px;border:1px solid #49627c;border-radius:8px}#editor_outputs legend{padding:0 5px;color:#c6d9e9}.freeze-group{min-width:190px;flex:1;margin:8px 0;padding:8px 12px;border:1px solid #49627c;border-radius:8px}.freeze-group summary{padding:0 5px;color:#c6d9e9;cursor:pointer}.freeze-group label{display:block;margin:6px 0}@media(max-width:850px){.map{min-height:0}}dialog#appConfirm{box-sizing:border-box;width:min(92vw,440px);border:1px solid #54718d;border-radius:16px;padding:24px;background:#152233;color:#e5edf5;box-shadow:0 24px 80px #000b}dialog#appConfirm::backdrop{background:#02060bcc}#appConfirm h2{margin:0 0 10px}#appConfirm p{line-height:1.55;color:#c4d3e1;white-space:pre-wrap}#appConfirm .actions{display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:22px}#appConfirm button{min-width:100px}#appConfirm button.danger{background:#8e3b37;color:#fff;border-color:#d7837e}
5
+ /* Workspace layout: help stays close; recipes and batch steps stay readable. */
6
+ *{box-sizing:border-box}button,select,summary{min-height:44px}button,a,summary,input,select,textarea{scroll-margin-top:90px}:focus-visible{outline:3px solid #ffd073;outline-offset:3px}.lab-header{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:6px 0}.lab-header h1{font-size:clamp(26px,3vw,34px);margin-top:4px}.lab-header .intro{font-size:16px;margin-top:5px}.header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.help-link{display:inline-flex;align-items:center;min-height:44px;border:1px solid #49627c;border-radius:8px;padding:9px 13px;text-decoration:none;font-weight:650}.about{max-width:none;margin:4px 0 0;padding:0;border:0}.about>summary{display:flex;align-items:center;font-size:14px;width:fit-content;gap:8px}.about>summary::before{content:'▸'}.about[open]>summary::before{content:'▾'}.about[open]{border:1px solid #2a3d52;border-radius:10px;padding:0 16px 12px}.quick-start{border:1px solid #385568;background:#102232;border-radius:12px;margin-top:8px;padding:0 16px}.quick-start>summary{padding:12px 0;line-height:1.4}.quick-start>summary .hint{font-size:14px;font-weight:400;margin-left:12px}.quick-start[open]>summary{margin-bottom:0}.quick-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;list-style:none;padding:12px 0;margin:0;counter-reset:quick}.quick-steps li{counter-increment:quick;min-width:0}.quick-steps strong{display:block;margin-bottom:8px}.quick-steps strong::before{content:counter(quick) '. ';color:#62dfb1}.quick-steps p{font-size:14px;line-height:1.55;color:#b9cddd;margin:0 0 10px}.quick-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:12px 0;border-top:1px solid #2a3d52}.tabs{margin:12px 0 0;gap:8px}.tabs button{border-radius:9px}.pane>.card{margin-top:16px}.section-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.section-heading h2,.section-heading h3{margin:0}.section-heading a{font-size:14px}.section-intro{max-width:85ch;margin:10px 0 18px;color:#b9cddd;line-height:1.55}.recipe-panel{border:1px solid #385568;border-radius:12px;background:#102030;padding:16px;margin:16px 0 24px}.recipe-panel .row label{margin-top:0}.recipe-actions,.batch-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}.form-state{font-size:14px;font-weight:650;color:#ffd073;margin:10px 0 0;line-height:1.55}.form-state[data-state="saved"]{color:#82e6bb}.form-state[data-state="invalid"]{color:#ffb5a2}.settings-summary{font-size:14px;color:#b9cddd;line-height:1.55;margin:8px 0 0;overflow-wrap:anywhere}.recipe-panel details{margin-top:12px;padding-top:10px}.recipe-panel textarea{box-sizing:border-box;width:100%;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:8px;padding:10px}.recipe-panel .manage-actions{display:flex;align-items:start;gap:12px;flex-wrap:wrap}.destructive-action{margin-top:14px;color:#ffb5a2}.batch-layout{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(280px,1fr);gap:24px;align-items:start}.batch-setup{background:#102030;border:1px solid #385568;border-radius:12px;padding:18px;position:sticky;top:92px}.batch-setup h3{margin:0 0 10px}.batch-context{margin:12px 0}.batch-context div{padding:9px 0;border-bottom:1px solid #2a3d52}.batch-context dt{font-size:13px;color:#a2b4c8}.batch-context dd{margin:3px 0 0;overflow-wrap:anywhere;font-weight:600}.sequence-heading{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:22px}.sequence-heading h3{margin:0}.step-count{font-size:14px;color:#b9cddd;white-space:nowrap}.batch-steps{list-style:none;counter-reset:steps;padding:0;margin:12px 0}.batch-step{border:1px solid #3b5268;border-radius:10px;padding:14px;margin-bottom:10px;background:#101d2c}.batch-step[data-invalid="true"]{border-color:#d78970}.step-header{display:grid;grid-template-columns:30px minmax(0,1fr);align-items:center;gap:10px}.step-number{color:#82e6bb;font-size:14px;font-weight:800;text-align:center}.step-summary{font-size:14px;line-height:1.5;color:#b9cddd;margin:10px 0 8px;padding-left:40px}.step-actions{display:flex;gap:8px;flex-wrap:wrap;padding-left:40px}.step-actions button{font-size:14px;padding:8px 10px}.batch-empty{border:1px dashed #49627c;border-radius:10px;padding:18px;margin:12px 0;color:#b9cddd;line-height:1.55}.batch-empty p{margin:0 0 12px}.add-step{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:end}.add-step label{margin:0}.add-step select{margin-top:6px}.batch-mode{margin:18px 0 6px;display:flex;align-items:flex-start;gap:10px;line-height:1.5}.batch-mode input{margin-top:5px;flex:none}.batch-progress{border-top:1px solid #2a3d52;margin-top:24px;padding-top:20px}.batch-progress h3{margin:0 0 12px}.batch-progress #batch_status{line-height:1.6}.batch-setup #batch_start{flex:1;min-width:140px}.batch-setup #batch_ready{line-height:1.55}.batch-actions button{min-height:46px}#recipe_feedback:empty,#batches_feedback:empty{display:none}.muted-note{font-size:14px;line-height:1.6;color:#b9cddd}.training-track{margin-top:18px}.training-track label{margin-top:0}
7
+ @media(max-width:1000px){.batch-layout{grid-template-columns:minmax(0,1fr)}.batch-setup{position:static}.quick-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:600px){body{padding:12px}.card{padding:16px}.row{grid-template-columns:minmax(0,1fr)}.quick-steps{grid-template-columns:minmax(0,1fr)}.header-actions{width:100%}.header-actions>*{flex:1;justify-content:center;text-align:center}.lab-header .intro{font-size:15px}.quick-start>summary .hint{display:block;margin-left:18px}.tabs{display:grid;grid-template-columns:1fr 1fr;gap:6px}.tabs button{font-size:14px;padding:9px 7px}.step-actions,.step-summary{padding-left:0}.add-step{grid-template-columns:minmax(0,1fr)}.batch-setup{padding:14px}.recipe-actions>*{flex:1}.reward{grid-template-columns:minmax(0,1fr) minmax(85px,.7fr) 65px 44px}.reward input{width:65px}.recipe-panel{padding:12px}}
8
+ </style>
9
  <dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
10
  <h2 id="appConfirmTitle">Confirm action</h2>
11
  <p id="appConfirmMessage"></p>
 
32
  };
33
  })();
34
  </script>
35
+ <header class="lab-header"><div><div class="eyebrow">AI LEARNS TO DRIVE</div><h1>Training lab</h1><p class="intro">Build a driver, train it, inspect the result.</p></div><div class="header-actions"><button id="quick_start_toggle" type="button" aria-controls="firstRunGuide" aria-expanded="false">Quick start</button><a class="help-link" href="/help#first-run" target="_blank" rel="noopener">Help &amp; tutorial ↗</a></div></header>
36
+ <details class="about"><summary>About &amp; feedback</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. For supported conditions, the lab checks the run’s final fitness policy (or frozen input) with a bounded fresh replay. It predicts conditions only and does not award or accumulate original-game stars; opponent races and other unsupported conditions remain unchecked.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p><p>Cars are simulated behind the scenes. The map shows track layout, not moving cars.</p><p><a id="feedbackLink" href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Report a problem</a> · Opens an editable draft for you to review.</p><p id="buildLabel" class="hint">Build: connecting…</p></details>
37
+ <details id="firstRunGuide" class="quick-start"><summary id="firstRunTitle">New here? Start a first run <span class="hint">Four steps, whenever you need them</span></summary><ol class="quick-steps"><li><strong>Choose a track</strong><p>Pick a built-in replica or upload your own track.</p><button id="first_run_track" type="button">Choose a track</button></li><li><strong>Create a driver</strong><p>Choose a car and create a small starter network, or upload a network you already have.</p><button type="button" data-open-tab="drive" data-focus="starter_create">Set up a network</button></li><li><strong>Review the settings</strong><p>The starter settings are ready for a short first experiment. You can save them as a recipe later.</p><button id="first_run_settings" type="button">Review first-run settings</button></li><li><strong>Run and inspect</strong><p>Start training, then download the result to inspect your drivers. A first run may not finish a lap.</p><button type="button" data-open-tab="drive" data-focus="run">Go to run controls</button></li></ol><div class="quick-footer"><a href="/help#first-run" target="_blank" rel="noopener">Read the full step-by-step tutorial ↗</a><button id="quick_start_close" type="button">Close quick start</button></div></details>
38
+ <nav class="tabs" role="tablist" aria-label="Lab sections"><button type="button" id="tab-drive" role="tab" data-tab="drive" aria-controls="drive" aria-selected="true" tabindex="0">Run &amp; results</button><button type="button" id="tab-training" role="tab" data-tab="training" aria-controls="training" aria-selected="false" tabindex="-1">Training &amp; recipes</button><button type="button" id="tab-batch" role="tab" data-tab="batch" aria-controls="batch" aria-selected="false" tabindex="-1">Batches</button><button type="button" id="tab-editor" role="tab" data-tab="editor" aria-controls="editor" aria-selected="false" tabindex="-1">Network editor</button></nav><div id="drive" class="pane" role="tabpanel" aria-labelledby="tab-drive"><div class="grid"><section class="card"><h2>Simulation</h2><div class="toolbar" aria-label="Create a starter network"><label for="starter_vehicle">Starter car</label><select id="starter_vehicle"><option value="0" selected>Formula</option><option value="1">Rally</option><option value="2">Snowmobile</option><option value="3">Truck</option></select><button id="starter_create" type="button">Create starter network</button></div><p id="starter_network_status" class="hint" role="status">Creates a small network in this page only. It does not start a run or change saved recipes.</p><label for="run_mode">Action</label><select id="run_mode"><option value="train" selected>Train a population</option><option value="evaluate">Frozen evaluation · one policy, unchanged weights</option></select><p id="run_mode_help" class="hint">Training uses the selected recipe. Frozen evaluation runs the selected policy in a fresh car without changing its weights.</p><button id="run" disabled>Run training</button><p id="starCheckNotice" class="hint">After each run, supported star challenges are checked with unchanged weights. Results are predictions, not game awards. <a href="/help#concepts" target="_blank" rel="noopener">How checks work</a>.</p><p class="hint">Select or create a network below to enable training. Review Training &amp; recipes before running.</p><p>Supports Formula, Rally, Snowmobile and Truck networks. Control outputs follow the original vehicle editor rules. Network shape and supported sensors are bounded by the original game contract.</p>
39
  <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>
40
  <div id="connectionRecovery" hidden><button id="retry" type="button">Reconnect to app</button><p class="hint">Checks server availability. Does not restart training, sign you in, or reset GPU allowance.</p></div>
41
  <div id="gpuAccount" aria-label="Optional GPU account"><p><button id="hfSignIn" type="button" disabled>Sign in with HF for account GPU allowance</button><span id="hfAccount" hidden></span> <button id="hfSignOut" type="button" disabled hidden>Disconnect account</button></p><p id="hfIdentity" role="status" class="hint">CPU needs no sign-in. Checking optional GPU account connection…</p>
 
43
  <p><label for="trackPresetDrive">Track selection</label><select id="trackPresetDrive" data-track-library disabled><option value="">Upload a custom track</option></select></p>
44
  <p data-track-status role="status" class="hint">Loading built-in replica tracks…</p>
45
 
46
+
47
  <p><label>Track (.track) <input id="track" type="file" accept=".track"></label></p>
48
  <p><label>Vehicle network (.evolution) <input id="network" type="file" accept=".evolution"></label></p>
49
  <p id="networkChoice" role="status">Choose an uploaded file or use a network from the browser editor.</p><p class="privacy-note">Uploads are processed for this run. By default, the server copy is deleted after the browser verifies the download.</p><details><summary>Optional repeat download</summary><label class="keep-result"><input id="keepServer" type="checkbox"> Keep this result on the server for up to five minutes so I can download it again</label><p class="privacy-note">This is optional and resets after each run. When bucket auto-save is on, verified receipt must delete the server copy first, so repeat download is disabled. All results expire after five minutes. <a href="/policy" target="_blank" rel="noopener">Data policy</a></p>
 
71
  <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>
72
  <p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
73
  <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>
74
+ </div><section id="training" class="pane" role="tabpanel" aria-labelledby="tab-training" hidden><div class="card">
75
+ <div class="section-heading"><h2 id="trainingTitle" tabindex="-1">Training &amp; recipes</h2><a href="/help#recipes" target="_blank" rel="noopener">How recipes work ↗</a></div><p class="section-intro">A recipe saves training settings and rewards. Apply one to this form, or adjust the settings below and save your own. Choose your track and starting network in Run &amp; results.</p>
76
+ <div id="libraries" class="recipe-panel"><div class="row"><label for="recipe_slot">Recipe slot<select id="recipe_slot"><option value="A">A · Empty</option><option value="B">B · Empty</option><option value="C">C · Empty</option><option value="D">D · Empty</option><option value="E">E · Empty</option><option value="F">F · Empty</option><option value="G">G · Empty</option><option value="H">H · Empty</option><option value="I">I · Empty</option><option value="J">J · Empty</option></select></label><label for="recipe_name">Recipe name<input id="recipe_name" type="text" maxlength="80" value="First practice" placeholder="e.g. First laps"></label></div>
77
+ <p id="recipe_state" class="form-state" role="status" aria-live="polite">Working settings · not saved yet</p><p id="recipe_summary" class="settings-summary"></p><div class="recipe-actions"><button id="recipe_load" type="button" disabled>Apply recipe</button><button id="recipe_save" type="button">Save settings to A</button><button type="button" data-open-tab="batch" data-focus="batch_add_recipe">Build a batch</button></div><p id="recipe_feedback" role="status" aria-live="polite"></p>
78
+ <details><summary>Import, export &amp; manage recipes</summary><p class="muted-note">Recipes and batches are saved in this browser. Export a backup to move them to another browser or site. Clearing site data removes this library.</p><div class="manage-actions"><button id="recipe_export" type="button">Export recipe library</button><label for="recipe_import">Import recipe library (.json)<input id="recipe_import" type="file" accept=".json"></label><button id="recipe_bucket_nav" type="button">Bucket import / export</button></div><button id="recipe_clear" class="destructive-action" type="button">Clear saved recipes and batches on this device</button><p class="muted-note">The recipe library contains settings only. Saving a recipe does not start a run or upload tracks, networks, or results.</p></details>
79
+ <details><summary>Advanced recipe JSON</summary><textarea id="recipe_draft" rows="12" aria-label="Recipe settings JSON"></textarea><button id="recipe_apply_draft" type="button">Apply JSON to form</button></details></div>
80
+ <div class="section-heading"><h3>Settings for the next run</h3><button id="starter_settings_reset" type="button">Apply first-run settings to this form</button></div><p id="starter_settings_status" class="muted-note" role="status" aria-live="polite">Adjusting this form does not change a saved recipe until you save it.</p><div class="training-track"><label for="trackPresetTraining">Training track · shared with Run &amp; results</label><select id="trackPresetTraining" data-track-library disabled><option value="">Upload a custom track</option></select><p data-track-status role="status" class="hint">Loading built-in replica tracks…</p></div>
81
+ <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>
82
  <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>
83
  <p id="work_budget" class="hint" role="status" aria-live="polite"></p>
84
  <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>
85
 
86
+
87
  <details id="native_settings"><summary>Native evolution settings</summary>
88
  <label for="preset">Recommended starting recipe</label><select id="preset" disabled><option value="custom">Custom / keep current settings</option><option value="starter" selected>First laps · mutation 0.3, no decay</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>
89
  <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>
 
99
  <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>
100
  <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>
101
  </details></div>
102
+ <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 selected>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>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></section>
103
+ <section id="batch" class="pane" role="tabpanel" aria-labelledby="tab-batch" hidden><div class="card"><div class="section-heading"><h2>Training batches</h2><a href="/help#batches" target="_blank" rel="noopener">How batches work ↗</a></div><p class="section-intro">Arrange saved recipes into a sequence of up to 20 training steps. Use the same recipe more than once to keep training with those settings.</p><div class="batch-layout"><div>
104
+ <div class="row"><label for="batch_slot">Batch slot<select id="batch_slot" disabled><option value="A">A · Empty</option><option value="B">B · Empty</option><option value="C">C · Empty</option><option value="D">D · Empty</option><option value="E">E · Empty</option><option value="F">F · Empty</option><option value="G">G · Empty</option><option value="H">H · Empty</option><option value="I">I · Empty</option><option value="J">J · Empty</option></select></label><label for="batch_name">Batch name<input id="batch_name" type="text" maxlength="80" value="My training batch" disabled></label></div><p id="batch_draft_state" class="form-state" role="status" aria-live="polite">New batch · not saved yet</p><div class="batch-actions"><button id="batch_save" type="button" disabled>Save batch</button><button id="batch_load" type="button" disabled>Load saved batch</button></div>
105
+ <label class="batch-mode"><input id="carry" type="checkbox" checked disabled> Use each step’s result to train the next step</label><p id="batch_carry_help" class="muted-note">Step 1 uses your selected network. Later steps continue from the previous step’s result.</p>
106
+ <div class="sequence-heading"><h3>Training steps</h3><span id="batch_step_count" class="step-count">0 / 20 steps</span></div><div id="batch_empty" class="batch-empty"><p>Save a recipe in Training &amp; recipes, then add it here.</p><button type="button" data-open-tab="training" data-focus="recipe_name">Create a recipe</button></div><ol id="batch_steps" class="batch-steps" aria-label="Training steps"></ol>
107
+ <div class="add-step"><label for="batch_add_recipe">Saved recipe<select id="batch_add_recipe"><option value="">Save a recipe first</option></select></label><button id="queue_add" type="button" disabled>Add step</button></div><p id="batch_add_summary" class="settings-summary"></p><p id="batch_sequence_error" class="form-state" data-state="invalid" role="status" aria-live="polite"></p>
108
+ <details><summary>Advanced sequence · edit slot letters</summary><label for="batch_recipe_sequence">Recipe sequence (comma-separated slots)</label><input id="batch_recipe_sequence" type="text" value="" maxlength="300" placeholder="A, B, A" aria-describedby="batch_sequence_help batch_sequence_error"><p id="batch_sequence_help" class="muted-note">The same steps as the list above. Use saved slots A–J, separated by commas; repeated slots are allowed. Maximum 20 steps.</p></details>
109
+ </div><aside class="batch-setup" aria-labelledby="batch_setup_title"><h3 id="batch_setup_title">Ready to run?</h3><dl class="batch-context"><div><dt>Track</dt><dd id="batch_context_track">Choose a track</dd></div><div><dt>Starting network</dt><dd id="batch_context_network">Choose or create a network</dd></div><div><dt>Compute</dt><dd id="batch_context_backend">CPU</dd></div></dl><button type="button" data-open-tab="drive" data-focus="trackPresetDrive">Change run setup</button><p id="batch_plan_summary" class="settings-summary">Add recipes to see the planned work.</p><p id="batch_ready" class="form-state" role="status" aria-live="polite">Save a recipe and add a step to begin.</p><div class="batch-actions"><button class="primary" id="batch_start" type="button" aria-describedby="batch_ready" disabled>Run batch</button></div><p class="muted-note">The settings shown in these steps are saved recipe values. They are fixed when you start; changing a saved recipe affects the next batch you start.</p><details><summary>Results &amp; run limits</summary><p class="muted-note">Each step uses the selected backend limit: CPU 4,000,000 or GPU 8,000,000 vehicle-ticks, and 32,768 analysis rows. Completed results are received automatically. The last candidate stays in page memory; connect a bucket in Run &amp; results to save every step. Server copies are deleted after verified receipt.</p></details></aside></div>
110
+ <section class="batch-progress" aria-labelledby="batch_progress_title"><div class="section-heading"><h3 id="batch_progress_title">Batch progress</h3><button id="batch_stop" type="button" disabled>Stop after current step</button></div><p id="batch_status" role="status">No batch running.</p><div id="batch_history"></div><p id="batches_feedback" role="status" aria-live="polite"></p><details><summary>Batch receipt</summary><button id="batch_export" type="button" disabled>Export batch receipt</button></details></section></div></section>
111
+ <section id="editor" class="pane" role="tabpanel" aria-labelledby="tab-editor" 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. */
112
  (function(root){
113
  const KEY='tiga.ald.recipes.v1';
114
  const defaults={population:4,generations:1,ticks:60,mutation:0.01,seed:42,
 
296
  })(typeof window!=='undefined'?window:globalThis);
297
  </script><script>(() => {
298
  const q=id=>document.getElementById(id),api=window.RecipeLibrary;
299
+ let refreshWorkspace=()=>{},libraryBusy=false;
300
  const message=text=>{q('recipe_feedback').textContent=text;q('batches_feedback').textContent=text;};
301
  const fields=['population','generations','mutation','seed','mutation_end','schedule','selection','parents','elite','crossover','decay','adaptive_mutation','control_hz','workers','collision','idle'];
302
  const booleans=['adaptive_mutation','collision','idle'];
 
365
  for(const name of fields){if(booleans.includes(name))q(name).checked=data[name];else q(name).value=data[name];}
366
  q('training_ticks').value=data.ticks;q('rewards').replaceChildren();data.rewards.forEach(rewardRow);
367
  for(const kind of ['layers','inputs','outputs'])selectedFreeze[kind]=[...data.freeze[kind]];renderFreeze();
368
+ q('recipe_draft').value=JSON.stringify(data,null,2);updateWorkBudget();refreshWorkspace();
369
  }
370
  const editable=[...fields,'training_ticks','add','reward_preset','preset','starter_settings_reset'];
371
  for(const id of editable)q(id).disabled=false;
 
386
  };
387
  q('recipe_slot').addEventListener('change',()=>{stagedPortableRecipe=false;});
388
  window.setTrainingBusy=(settingsBusy,controlsBusy=settingsBusy)=>{
389
+ libraryBusy=controlsBusy;
390
  for(const id of editable)q(id).disabled=settingsBusy;
391
  for(const node of q('rewards').querySelectorAll('input,select,button'))node.disabled=settingsBusy;
392
  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=controlsBusy;
393
+ refreshWorkspace(false);
394
  };
395
  for(const id of ['population','generations','training_ticks'])q(id).addEventListener('input',updateWorkBudget);
396
  q('run_mode')?.addEventListener('change',updateWorkBudget);
 
408
  };
409
  for(const id of ['mutation','mutation_end','decay'])q(id).addEventListener('input',()=>{q('preset').value='custom';});
410
  let store;
411
+ try{store=api.createStore(window.localStorage);store.load();message('Recipes are saved in this browser.');}
412
  catch(error){message('Local library unavailable: '+error.message+'. Existing data was not changed.');}
413
  const read=()=>{if(!store)throw Error('Browser storage unavailable');return store.load();};
414
+ let renderedSteps='',batchContext={available:false,reason:'Connecting to the training service…',track:'Choose a track',network:'Choose or create a network',backend:'CPU'};
415
+ const batchMessage=text=>{q('batches_feedback').textContent=text;};
416
+ function describeSettings(settings){
417
+ return settings.population+' cars × '+settings.generations+' generations · '+Number((settings.ticks/60).toFixed(2))+' s per generation · mutation '+settings.mutation+' · '+settings.control_hz+' Hz';
418
+ }
419
+ function sequence(){const text=q('batch_recipe_sequence').value.trim();return text?text.split(',').map(slot=>slot.trim().toUpperCase()):[];}
420
+ function draftBatch(){return {slot:q('batch_slot').value,name:q('batch_name').value.trim(),carry:q('carry').checked,runs:sequence()};}
421
+ function sameBatch(a,b){return !!a&&!!b&&a.name===b.name&&a.carry===b.carry&&JSON.stringify(a.runs)===JSON.stringify(b.runs);}
422
+ function draftIssue(book,draft){
423
+ if(!draft.runs.length)return book.recipes.length?'Add a saved recipe to begin.':'Save a recipe in Training & recipes, then add it here.';
424
+ if(draft.runs.length>20)return 'This batch has '+draft.runs.length+' steps. Remove '+(draft.runs.length-20)+' to reach the 20-step limit.';
425
+ for(let i=0;i<draft.runs.length;i++){
426
+ const slot=draft.runs[i];
427
+ if(!/^[A-J]$/.test(slot))return 'Step '+(i+1)+' needs one recipe slot from A to J. Separate steps with commas.';
428
+ if(!book.recipes.some(recipe=>recipe.slot===slot))return 'Step '+(i+1)+' uses empty slot '+slot+'. Save a recipe there or choose a different recipe.';
429
+ }
430
+ if(!draft.name||draft.name.length>80)return 'Give this batch a name of 1–80 characters.';
431
+ return '';
432
+ }
433
+ function batchReadiness(){
434
+ try{
435
+ const book=read(),draft=draftBatch(),issue=draftIssue(book,draft);
436
+ if(issue)return {ready:false,message:issue};
437
+ const saved=book.batches.find(batch=>batch.slot===draft.slot);
438
+ if(!saved)return {ready:false,message:'Save this batch before running it.'};
439
+ if(!sameBatch(draft,saved))return {ready:false,message:'Unsaved changes. Save this batch to run the version shown, or load the saved batch.'};
440
+ const maximum=q('backend')?.value==='gpu'?8000000:4000000;
441
+ for(let i=0;i<draft.runs.length;i++){
442
+ const recipe=book.recipes.find(item=>item.slot===draft.runs[i]),settings=recipe.settings,work=settings.population*settings.generations*settings.ticks,rows=settings.population*settings.generations;
443
+ if(work>maximum||rows>32768)return {ready:false,message:'Step '+(i+1)+' · '+recipe.slot+' · '+recipe.name+' exceeds this compute limit ('+work.toLocaleString()+' vehicle-ticks; '+rows.toLocaleString()+' analysis rows). Shorten that recipe in Training & recipes.'};
444
+ }
445
+ return {ready:true,message:'Saved batch matches the steps shown. Ready to run '+draft.runs.length+' '+(draft.runs.length===1?'step':'steps')+'.'};
446
+ }catch(error){return {ready:false,message:'Recipe library unavailable: '+error.message};}
447
+ }
448
+ window.getBatchReadiness=batchReadiness;
449
+ window.getBatchPlan=()=>{
450
+ const readiness=batchReadiness();if(!readiness.ready)throw Error(readiness.message);
451
+ return window.BatchTraining.plan(read(),q('batch_slot').value,q('backend')?.value==='gpu'?8000000:4000000);
452
+ };
453
+ function renderRunContext(){
454
+ q('batch_context_track').textContent=batchContext.track||'Choose a track';
455
+ q('batch_context_network').textContent=batchContext.network||'Choose or create a network';
456
+ q('batch_context_backend').textContent=batchContext.backend||'CPU';
457
+ const readiness=batchReadiness(),ready=readiness.ready&&batchContext.available&&!libraryBusy;
458
+ q('batch_ready').textContent=libraryBusy?'Finish the current operation before starting another batch.':!readiness.ready?readiness.message:batchContext.available?readiness.message:batchContext.reason;
459
+ q('batch_ready').dataset.state=ready?'saved':'draft';
460
+ q('batch_start').disabled=!ready;
461
+ }
462
+ window.setBatchRunContext=context=>{batchContext={...batchContext,...context};renderRunContext();};
463
+ function slotLabels(select,entries){
464
+ for(const option of select.options){const item=entries.find(entry=>entry.slot===option.value);option.textContent=option.value+' · '+(item?item.name:'Empty');}
465
+ }
466
+ function focusStep(index,action='recipe'){
467
+ const row=q('batch_steps').children[index];if(!row)return;
468
+ const target=action==='recipe'?row.querySelector('select'):row.querySelector('[data-step-action="'+action+'"]');
469
+ (target&&!target.disabled?target:row.querySelector('select'))?.focus();
470
+ }
471
+ function writeSequence(runs,focusIndex,focusAction){
472
+ q('batch_recipe_sequence').value=runs.join(', ');refreshWorkspace();
473
+ if(focusIndex!==undefined)focusStep(Math.max(0,Math.min(focusIndex,runs.length-1)),focusAction);
474
+ else q('queue_add').focus();
475
+ }
476
+ function renderSteps(book,runs){
477
+ const signature=JSON.stringify([runs,book.recipes,libraryBusy,q('backend')?.value]);
478
+ if(signature===renderedSteps)return;renderedSteps=signature;
479
+ q('batch_steps').replaceChildren();
480
+ runs.forEach((slot,index)=>{
481
+ const recipe=book.recipes.find(item=>item.slot===slot),row=document.createElement('li');row.className='batch-step';row.dataset.index=String(index);row.dataset.invalid=String(!recipe);
482
+ const header=document.createElement('div');header.className='step-header';
483
+ const number=document.createElement('span');number.className='step-number';number.textContent=String(index+1);number.setAttribute('aria-hidden','true');
484
+ const select=document.createElement('select');select.setAttribute('aria-label','Recipe for step '+(index+1));select.disabled=libraryBusy;
485
+ if(!recipe)select.add(new Option((slot||'?')+' · Choose a saved recipe',slot));
486
+ for(const item of book.recipes)select.add(new Option(item.slot+' · '+item.name,item.slot));select.value=slot;
487
+ select.addEventListener('change',()=>{const next=sequence();next[index]=select.value;writeSequence(next,index);});
488
+ header.append(number,select);row.append(header);
489
+ const summary=document.createElement('p');summary.className='step-summary';summary.textContent=recipe?describeSettings(recipe.settings):'This step needs a saved recipe.';row.append(summary);
490
+ const actions=document.createElement('div');actions.className='step-actions';
491
+ for(const [action,label,aria,disabled] of [['up','Move up','Move step '+(index+1)+' up',index===0],['down','Move down','Move step '+(index+1)+' down',index===runs.length-1],['duplicate','Duplicate','Duplicate step '+(index+1),runs.length>=20],['remove','Remove','Remove step '+(index+1),false]]){
492
+ const button=document.createElement('button');button.type='button';button.textContent=label;button.dataset.stepAction=action;button.setAttribute('aria-label',aria);button.disabled=libraryBusy||disabled;
493
+ button.addEventListener('click',()=>{
494
+ if(libraryBusy)return;const next=sequence();let destination=index;
495
+ if(action==='up'&&index>0){[next[index-1],next[index]]=[next[index],next[index-1]];destination--;}
496
+ else if(action==='down'&&index<next.length-1){[next[index+1],next[index]]=[next[index],next[index+1]];destination++;}
497
+ else if(action==='duplicate'&&next.length<20){next.splice(index+1,0,next[index]);destination++;}
498
+ else if(action==='remove'){next.splice(index,1);}
499
+ else return;
500
+ writeSequence(next,destination,action==='remove'||action==='duplicate'?'recipe':action);
501
+ if(!next.length)q('batch_add_recipe').focus();
502
+ });actions.append(button);
503
+ }
504
+ row.append(actions);q('batch_steps').append(row);
505
+ });
506
+ }
507
+ refreshWorkspace=(notify=true)=>{
508
+ let book;
509
+ try{book=read();}
510
+ catch(error){
511
+ q('recipe_state').textContent='Recipe library unavailable: '+error.message;q('recipe_state').dataset.state='invalid';
512
+ for(const id of ['recipe_save','recipe_load','batch_save','batch_load','queue_add'])q(id).disabled=true;
513
+ renderRunContext();return;
514
+ }
515
+ slotLabels(q('recipe_slot'),book.recipes);slotLabels(q('batch_slot'),book.batches);
516
+ const savedRecipe=book.recipes.find(recipe=>recipe.slot===q('recipe_slot').value);
517
+ let formSettings=null,formError='';try{formSettings=currentSettings();}catch(error){formError=error.message;}
518
+ const sameRecipe=!!savedRecipe&&formSettings&&JSON.stringify(savedRecipe.settings)===JSON.stringify(formSettings)&&savedRecipe.name===q('recipe_name').value.trim();
519
+ q('recipe_state').textContent=formError?'Check settings: '+formError:sameRecipe?'Saved recipe '+savedRecipe.slot+' matches this form.':savedRecipe?'Unsaved changes · this form differs from recipe '+savedRecipe.slot+'.':'Working settings · not saved yet';
520
+ q('recipe_state').dataset.state=formError?'invalid':sameRecipe?'saved':'draft';
521
+ q('recipe_summary').textContent=savedRecipe?'Saved in '+savedRecipe.slot+': '+describeSettings(savedRecipe.settings):formSettings?describeSettings(formSettings):'';
522
+ q('recipe_load').disabled=libraryBusy||!savedRecipe;
523
+ q('recipe_save').textContent='Save settings to '+q('recipe_slot').value;
524
+ q('recipe_save').disabled=libraryBusy||!!formError||!q('recipe_name').value.trim();
525
+ const draft=draftBatch(),savedBatch=book.batches.find(batch=>batch.slot===draft.slot),issue=draftIssue(book,draft),same=sameBatch(draft,savedBatch);
526
+ q('batch_draft_state').textContent=same?'Saved batch '+draft.slot+' matches this editor.':savedBatch?'Unsaved changes · save to run this version.':'New batch · save when your steps are ready.';
527
+ q('batch_draft_state').dataset.state=same?'saved':'draft';
528
+ q('batch_save').disabled=libraryBusy||!!issue||same;
529
+ q('batch_load').disabled=libraryBusy||!savedBatch;
530
+ q('batch_step_count').textContent=draft.runs.length+' / 20 steps';
531
+ q('batch_empty').hidden=!!draft.runs.length;
532
+ q('batch_empty').querySelector('p').textContent=book.recipes.length?'Choose a saved recipe below and add your first step.':'Save a recipe in Training & recipes, then add it here.';
533
+ q('batch_empty').querySelector('button').hidden=!!book.recipes.length;
534
+ q('batch_sequence_error').textContent=draft.runs.length?issue:'';
535
+ q('batch_recipe_sequence').setAttribute('aria-invalid',String(!!draft.runs.length&&!!draftIssue(book,{...draft,name:'Batch'})));
536
+ q('batch_name').setAttribute('aria-invalid',String(!draft.name||draft.name.length>80));
537
+ q('batch_carry_help').textContent=draft.carry?'Step 1 uses your selected network. Later steps continue from the previous step’s result.':'Every step starts from the same original selected network. Results are not carried into the next step.';
538
+ const chooser=q('batch_add_recipe'),chosen=chooser.value,choices=book.recipes.map(recipe=>[recipe.slot,recipe.name]);
539
+ if(chooser.dataset.choices!==JSON.stringify(choices)){
540
+ chooser.replaceChildren();if(!book.recipes.length)chooser.add(new Option('Save a recipe first',''));
541
+ for(const recipe of book.recipes)chooser.add(new Option(recipe.slot+' · '+recipe.name,recipe.slot));
542
+ if(book.recipes.some(recipe=>recipe.slot===chosen))chooser.value=chosen;
543
+ chooser.dataset.choices=JSON.stringify(choices);
544
+ }
545
+ chooser.disabled=libraryBusy||!book.recipes.length;
546
+ q('queue_add').disabled=libraryBusy||!book.recipes.length||draft.runs.length>=20;
547
+ const addedRecipe=book.recipes.find(recipe=>recipe.slot===chooser.value);
548
+ q('batch_add_summary').textContent=draft.runs.length>=20?'20-step limit reached. Remove a step before adding another.':addedRecipe?describeSettings(addedRecipe.settings):'';
549
+ renderSteps(book,draft.runs);
550
+ const complete=draft.runs.length&&draft.runs.every(slot=>book.recipes.some(recipe=>recipe.slot===slot));
551
+ const work=complete?draft.runs.reduce((total,slot)=>{const settings=book.recipes.find(recipe=>recipe.slot===slot).settings;return total+settings.population*settings.generations*settings.ticks;},0):0;
552
+ q('batch_plan_summary').textContent=complete?draft.runs.length+' '+(draft.runs.length===1?'step':'steps')+' · '+work.toLocaleString()+' total vehicle-ticks across the batch. Limits apply to each step.':'Add saved recipes to see the planned work.';
553
+ renderRunContext();
554
+ if(notify)window.dispatchEvent(new Event('ald:batch-draft-change'));
555
+ };
556
  window.recipeLibraryText=()=>{const text=JSON.stringify(api.validate(read()));if(new TextEncoder().encode(text).byteLength>32768)throw Error('Recipe library exceeds 32 KB');return text;};
557
  window.importRecipeLibraryText=async(text,{source='selected file',isCurrent=()=>true}={})=>{
558
  const book=api.parse(text);
559
  if(!(await window.appConfirm('Replace this browser’s recipe library with '+book.recipes.length+' recipes and '+book.batches.length+' batches from '+source+'? Export a backup first if needed.',{heading:'Import recipe library',confirmLabel:'Replace library'})))return false;
560
  if(!isCurrent())throw Error('Bucket connection changed before import; the local recipe library was not changed.');
561
+ if(!store)throw Error('Browser storage unavailable');store.save(book);refreshWorkspace();
562
  message('Imported '+book.recipes.length+' recipes and '+book.batches.length+' batches from '+source+'. No run was started.');return true;
563
  };
564
+ const action=(fn,surface='recipe')=>async()=>{try{await fn();}catch(error){(surface==='batch'?batchMessage:message)('Not completed: '+error.message);}finally{refreshWorkspace();}};
 
565
  q('starter_settings_reset').onclick=action(async()=>{
566
  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;
567
  applySettings(firstRunSettings);q('preset').value='starter';q('reward_preset').value='distance';q('starter_settings_status').textContent='First-run settings applied to this form. Saved recipes are unchanged; no run started.';
568
  });
569
+ q('recipe_apply_draft').onclick=action(()=>{applySettings(JSON.parse(q('recipe_draft').value));q('preset').value='custom';q('reward_preset').value='custom';message('JSON applied to the training form. No run started.');});
570
  q('recipe_save').onclick=action(async()=>{
571
  const book=read(),slot=q('recipe_slot').value;
572
+ const next=api.validate({...book,recipes:[...book.recipes.filter(recipe=>recipe.slot!==slot),{slot,name:q('recipe_name').value,settings:currentSettings()}]});
573
+ if(book.recipes.some(recipe=>recipe.slot===slot)&&!(await window.appConfirm('Replace recipe '+slot+' with the settings in this form? Batches using '+slot+' will use these settings the next time they start.',{heading:'Replace saved recipe',confirmLabel:'Save recipe'})))return;
574
+ store.save(next);stagedPortableRecipe=false;message('Saved '+slot+' · '+q('recipe_name').value.trim()+'. You can use it in a batch.');
575
  });
576
  q('recipe_load').onclick=action(()=>{
577
+ const recipe=read().recipes.find(item=>item.slot===q('recipe_slot').value);if(!recipe)throw Error('Choose a saved recipe first.');
578
+ q('recipe_name').value=recipe.name;applySettings(recipe.settings);q('preset').value='custom';q('reward_preset').value='custom';stagedPortableRecipe=false;
579
+ message('Applied '+recipe.slot+' · '+recipe.name+' to this form. No run started.');
580
  });
581
+ q('recipe_slot').addEventListener('change',()=>{try{const recipe=read().recipes.find(item=>item.slot===q('recipe_slot').value);q('recipe_name').value=recipe?.name||'My recipe';}catch{}refreshWorkspace();});
582
  q('recipe_export').onclick=action(()=>{
583
  const blob=new Blob([JSON.stringify(read())],{type:'application/json'}),url=URL.createObjectURL(blob);
584
+ const link=document.createElement('a');link.href=url;link.download='ald-recipes.json';link.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
585
+ message('Recipe library export requested. It contains recipes and batches, without tracks, networks or results.');
586
  });
587
  q('recipe_import').onchange=action(async()=>{
588
  const input=q('recipe_import'),file=input.files[0];if(!file)return;input.value='';if(file.size>32768)throw Error('Recipe file exceeds 32 KB');
589
+ await window.importRecipeLibraryText(await file.text(),{source:'selected file'});
590
  });
591
  q('recipe_clear').onclick=action(async()=>{
592
  if(!(await window.appConfirm('Clear saved recipes and batches for this app on this device? Server run data and downloaded files are unaffected.',{heading:'Clear local recipes and batches',confirmLabel:'Clear local data',danger:true})))return;
593
+ if(!store)throw Error('Browser storage unavailable');store.clear();message('Saved recipes and batches cleared. Current form values and downloaded files are unchanged.');
594
  });
595
+ for(const id of ['batch_name','batch_slot','carry'])q(id).disabled=false;
 
 
596
  q('queue_add').onclick=()=>{
597
+ if(libraryBusy)return;const runs=sequence(),slot=q('batch_add_recipe').value;
598
+ if(runs.length>=20||!read().recipes.some(recipe=>recipe.slot===slot))return;
599
+ runs.push(slot);writeSequence(runs,runs.length-1);batchMessage('Added step '+runs.length+'. Save the batch when it is ready.');
600
  };
601
  q('batch_save').onclick=action(async()=>{
602
+ const book=read(),draft=draftBatch(),issue=draftIssue(book,draft);if(issue)throw Error(issue);
603
+ const next=api.validate({...book,batches:[...book.batches.filter(batch=>batch.slot!==draft.slot),draft]});
604
+ if(book.batches.some(batch=>batch.slot===draft.slot)&&!(await window.appConfirm('Replace saved batch '+draft.slot+' with these '+draft.runs.length+' steps?',{heading:'Save batch changes',confirmLabel:'Save changes'})))return;
605
+ store.save(next);q('batch_name').value=draft.name;q('batch_recipe_sequence').value=draft.runs.join(', ');batchMessage('Saved '+draft.slot+' · '+draft.name+'. Run batch will use the steps shown.');
606
+ },'batch');
 
607
  q('batch_load').onclick=action(()=>{
608
+ const batch=read().batches.find(item=>item.slot===q('batch_slot').value);if(!batch)throw Error('Choose a saved batch first.');
609
  q('batch_name').value=batch.name;q('carry').checked=batch.carry;q('batch_recipe_sequence').value=batch.runs.join(', ');
610
+ batchMessage('Loaded '+batch.slot+' · '+batch.name+'. No run started.');
611
+ },'batch');
612
+ for(const id of ['batch_name','batch_recipe_sequence'])q(id).addEventListener('input',()=>refreshWorkspace());
613
+ for(const id of ['batch_slot','carry','batch_add_recipe','backend'])q(id).addEventListener('change',()=>refreshWorkspace());
614
+ q('training').addEventListener('input',event=>{if(event.target.id!=='recipe_draft')refreshWorkspace();});
615
+ q('training').addEventListener('change',()=>refreshWorkspace());
616
+ q('training_controls').addEventListener('click',event=>{if(event.target.closest('button'))queueMicrotask(()=>refreshWorkspace());});
617
+ window.addEventListener('storage',event=>{if(event.key===api.KEY||event.key===null)refreshWorkspace();});
618
+ try{const recipe=read().recipes.find(item=>item.slot===q('recipe_slot').value);if(recipe)q('recipe_name').value=recipe.name;}catch{}
619
+ refreshWorkspace();
620
  })();
621
  </script><script>/* Read-only public reference library. No cookies or browser-persistent storage. */
622
  (function(root,factory){
 
779
  }
780
  return {SCHEMA,LIMITS,sha,artifact,analysisSlot,pack,parse};
781
  });</script><script src="/client.js"></script><script src="/hf-auth.js"></script><script>'use strict';
782
+ // Navigation and help preference only; these controls never start a run.
783
+ (() => {
784
+ const tabs=[...document.querySelectorAll('.tabs [data-tab]')];
785
+ function activate(button){
786
+ document.querySelectorAll('.pane').forEach(pane=>pane.hidden=pane.id!==button.dataset.tab);
787
+ tabs.forEach(tab=>{const active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;});
788
+ }
789
+ tabs.forEach((button,index)=>{
790
+ button.addEventListener('click',()=>activate(button));
791
+ button.addEventListener('keydown',event=>{
792
+ let next;if(event.key==='ArrowRight'||event.key==='ArrowDown')next=(index+1)%tabs.length;
793
+ else if(event.key==='ArrowLeft'||event.key==='ArrowUp')next=(index+tabs.length-1)%tabs.length;
794
+ else if(event.key==='Home')next=0;else if(event.key==='End')next=tabs.length-1;else return;
795
+ event.preventDefault();activate(tabs[next]);tabs[next].focus();
796
+ });
797
+ });
798
+ document.querySelectorAll('[data-open-tab]').forEach(button=>button.addEventListener('click',()=>{
799
+ const tab=tabs.find(item=>item.dataset.tab===button.dataset.openTab);if(!tab)return;activate(tab);
800
+ const requested=document.getElementById(button.dataset.focus);
801
+ const target=requested&&!requested.matches(':disabled')&&!requested.closest('[hidden]')?requested:tab;
802
+ target.scrollIntoView({block:'center'});target.focus();
803
+ }));
804
+ const guide=document.getElementById('firstRunGuide'),toggle=document.getElementById('quick_start_toggle'),key='tiga.ald.quick-start.v1';
805
+ try{guide.open=window.localStorage.getItem(key)==='open';}catch{}
806
+ const syncHelp=()=>toggle.setAttribute('aria-expanded',String(guide.open));syncHelp();
807
+ guide.addEventListener('toggle',()=>{syncHelp();try{window.localStorage.setItem(key,guide.open?'open':'closed');}catch{}});
808
+ toggle.addEventListener('click',()=>{guide.open=!guide.open;syncHelp();if(guide.open)guide.querySelector('summary').focus();});
809
+ document.getElementById('quick_start_close').addEventListener('click',()=>{guide.open=false;syncHelp();toggle.focus();});
810
+ document.getElementById('recipe_bucket_nav').addEventListener('click',()=>{
811
+ activate(tabs.find(tab=>tab.dataset.tab==='drive'));
812
+ document.getElementById('portableBundle').open=true;
813
+ const storage=document.getElementById('bucketStorage');storage.open=true;
814
+ const connected=document.getElementById('bucketConnected'),recipeTitle=document.getElementById('bucketRecipeTitle');
815
+ const target=connected&&!connected.hidden&&recipeTitle?recipeTitle:storage.querySelector('summary');
816
+ target?.scrollIntoView({behavior:'smooth',block:'center'});target?.focus({preventScroll:true});
817
+ });
818
+ })();
819
  </script><script>// Browser-only editor; upload is controlled by the separate training client.
820
  (() => {
821
  const q=id=>document.getElementById(id),api=window.NetworkEditor;
 
917
  };
918
  })();
919
  </script></html>
920
+
921
+