Spaces:
Running on Zero
Running on Zero
Improve quick-start help and recipe/batch editing
#1
by TiGa-RCE - opened
- RELEASE_MANIFEST.json +6 -6
- client.js +40 -11
- help.html +52 -11
- 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 |
-
"
|
| 62 |
-
"client.js": "cbb1dd26a8fe4256abf827903df94749dc8e4f99912d29dba7f74ba6e477b851",
|
| 63 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 64 |
"gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
|
| 65 |
-
"help.html": "
|
| 66 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 67 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 68 |
-
"index.html": "
|
| 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 |
-
"
|
| 98 |
-
"
|
| 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'))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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()
|
|
|
|
|
|
|
| 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
|
| 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">
|
| 18 |
-
<p class="small">This guide opens in a new tab
|
| 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">
|
| 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>
|
| 29 |
-
<li>
|
| 30 |
-
<li>
|
| 31 |
-
<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
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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>
|
| 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 32 |
-
<
|
| 33 |
-
<
|
|
|
|
| 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"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
<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('
|
| 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 |
-
|
| 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('
|
| 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(
|
| 415 |
-
if(book.recipes.some(
|
| 416 |
-
store.save(next);message('Saved slot '+
|
| 417 |
});
|
| 418 |
q('recipe_load').onclick=action(()=>{
|
| 419 |
-
const
|
| 420 |
-
q('recipe_name').value=
|
| 421 |
-
message('
|
| 422 |
});
|
|
|
|
| 423 |
q('recipe_export').onclick=action(()=>{
|
| 424 |
const blob=new Blob([JSON.stringify(read())],{type:'application/json'}),url=URL.createObjectURL(blob);
|
| 425 |
-
const
|
| 426 |
-
message('Recipe export requested.
|
| 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'});
|
| 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('
|
| 435 |
});
|
| 436 |
-
|
| 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
|
|
|
|
|
|
|
| 441 |
};
|
| 442 |
q('batch_save').onclick=action(async()=>{
|
| 443 |
-
const book=read(),
|
| 444 |
-
const
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
});
|
| 449 |
q('batch_load').onclick=action(()=>{
|
| 450 |
-
const batch=read().batches.find(
|
| 451 |
q('batch_name').value=batch.name;q('carry').checked=batch.carry;q('batch_recipe_sequence').value=batch.runs.join(', ');
|
| 452 |
-
|
| 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
|
| 617 |
-
|
| 618 |
-
document.querySelectorAll('.
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 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 & tutorial ↗</a></div></header>
|
| 36 |
+
<details class="about"><summary>About & 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 & results</button><button type="button" id="tab-training" role="tab" data-tab="training" aria-controls="training" aria-selected="false" tabindex="-1">Training & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 |
+
|