Spaces:
Running on Zero
Running on Zero
Polish run summary, replay identity and responsive workspace
Browse filesCompact result and download actions; keep replay map and playback together beside native black box; show validated recording identity and current setup state; compact initial guide/agent utilities; measure wrapped navigation height. Verified isolated private preview before production using fresh Luna and Sol visual GPU training, native CPU recordings, downloads, deletion and imports; strict artifact validation, 91 frontend/controller checks and responsive geometry. Only three frontend files and their two hash manifests change. No simulator, Godot, Swift, auth/storage, visibility or retained parent changes.
- FRONTEND_SOURCE.json +8 -8
- RELEASE_MANIFEST.json +4 -4
- client.js +80 -12
- index.html +33 -20
- track-map.js +1 -1
FRONTEND_SOURCE.json
CHANGED
|
@@ -5,23 +5,23 @@
|
|
| 5 |
"source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
|
| 6 |
"source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"
|
| 7 |
},
|
| 8 |
-
"client_sha256": "
|
| 9 |
"identity_note": "The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.",
|
| 10 |
-
"local_customization_id": "
|
| 11 |
-
"output_sha256": "
|
| 12 |
"packaged_frontend_files": {
|
| 13 |
"agent-manifest.json": "7187c85327d588cb3b81f5ddf60bae099567a656bcceac8147ecb2227e07f561",
|
| 14 |
"agent-tools.js": "134b8c511a04671edf2129b7b4f42d2c0399713471feefaaeee542efab7127b8",
|
| 15 |
"agents.html": "3e129500b30299ce628f04208dc7e77a2592991adb9c0d92042b63249077caa6",
|
| 16 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 17 |
-
"client.js": "
|
| 18 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 19 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 20 |
-
"index.html": "
|
| 21 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 22 |
-
"track-map.js": "
|
| 23 |
},
|
| 24 |
-
"packaged_frontend_sha256": "
|
| 25 |
"packaged_frontend_sha256_format": "SHA-256 of UTF-8 JSON for packaged_frontend_files, sorted keys, compact separators, no trailing newline",
|
| 26 |
-
"status": "
|
| 27 |
}
|
|
|
|
| 5 |
"source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
|
| 6 |
"source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8"
|
| 7 |
},
|
| 8 |
+
"client_sha256": "401011a6fd4ad5133c7d7688080a53c0ad22246e6b12b7f3bb9e92534a0639d3",
|
| 9 |
"identity_note": "The historical source fields describe the base frontend; local packaged file hashes describe this candidate's derived frontend.",
|
| 10 |
+
"local_customization_id": "five-issue-ui-polish-v2-20261023",
|
| 11 |
+
"output_sha256": "8d33f953dc2decc0895841940b7d8019684c968fdda17309c2b2405486e8bf3f",
|
| 12 |
"packaged_frontend_files": {
|
| 13 |
"agent-manifest.json": "7187c85327d588cb3b81f5ddf60bae099567a656bcceac8147ecb2227e07f561",
|
| 14 |
"agent-tools.js": "134b8c511a04671edf2129b7b4f42d2c0399713471feefaaeee542efab7127b8",
|
| 15 |
"agents.html": "3e129500b30299ce628f04208dc7e77a2592991adb9c0d92042b63249077caa6",
|
| 16 |
"assets/track-atlas.json": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
|
| 17 |
+
"client.js": "401011a6fd4ad5133c7d7688080a53c0ad22246e6b12b7f3bb9e92534a0639d3",
|
| 18 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 19 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 20 |
+
"index.html": "8d33f953dc2decc0895841940b7d8019684c968fdda17309c2b2405486e8bf3f",
|
| 21 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 22 |
+
"track-map.js": "c5f56f1083f4c9487f8d3d663c939c736bdc478217e2b60142f820f3263adb44"
|
| 23 |
},
|
| 24 |
+
"packaged_frontend_sha256": "ab985a034eddf2c9a04a7bf8e4d98d92168db81b254a7a9cb0e41d2cd4a9efdc",
|
| 25 |
"packaged_frontend_sha256_format": "SHA-256 of UTF-8 JSON for packaged_frontend_files, sorted keys, compact separators, no trailing newline",
|
| 26 |
+
"status": "Compact run summary, truthful recording identity, paired map and transport, compact utilities and measured navigation offsets"
|
| 27 |
}
|
RELEASE_MANIFEST.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
| 7 |
"CUDA_REFERENCE.md": "f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c",
|
| 8 |
"DATA_POLICY.md": "fc5978528abe1c21c14882417ac8a706e04a0831bbd3fdb3de8c64442c1a7b3d",
|
| 9 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 10 |
-
"FRONTEND_SOURCE.json": "
|
| 11 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 12 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 13 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
|
@@ -63,13 +63,13 @@
|
|
| 63 |
"builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
|
| 64 |
"builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
|
| 65 |
"builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
|
| 66 |
-
"client.js": "
|
| 67 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 68 |
"gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
|
| 69 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 70 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 71 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 72 |
-
"index.html": "
|
| 73 |
"native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 74 |
"native/blackbox-build-receipt.json": "de3eea6c45a5ae4897287a97b0e2822fdae552a0655250bf940cef20d9b60d96",
|
| 75 |
"native/blackbox-source.tar.gz": "56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28",
|
|
@@ -91,7 +91,7 @@
|
|
| 91 |
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 92 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 93 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 94 |
-
"track-map.js": "
|
| 95 |
"training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
|
| 96 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 97 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
|
|
|
| 7 |
"CUDA_REFERENCE.md": "f7453c3254d244f8b0de41fde5851cb025159f5cea4a4d1a5d8208a955550c6c",
|
| 8 |
"DATA_POLICY.md": "fc5978528abe1c21c14882417ac8a706e04a0831bbd3fdb3de8c64442c1a7b3d",
|
| 9 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 10 |
+
"FRONTEND_SOURCE.json": "485c8c25299da7e0adf80e471eb8f19007a2015c07e65783a4d26ae46bc51085",
|
| 11 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 12 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 13 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
|
|
|
| 63 |
"builtin_tracks/tracks/Replica R08 Demolition Derby.track": "6f11a9ffbcb477c9d19964566b1b612d2a94675605246e93e21f27bbbdb357ef",
|
| 64 |
"builtin_tracks/tracks/Replica R09 Eau Noir.track": "3ec0a78478cca1a4f0d2d98ac81f40783928adf271b70f0c32d295a6da2cdaa9",
|
| 65 |
"builtin_tracks/tracks/Replica The Champions Trial.track": "a3bfb76422affc774b44c739b0636d08acb901fa7615b9adcb52c069f79834e1",
|
| 66 |
+
"client.js": "401011a6fd4ad5133c7d7688080a53c0ad22246e6b12b7f3bb9e92534a0639d3",
|
| 67 |
"delivery.py": "6bdba2e87c3e19a41440cb16103695af27105f8bafbae38948bbb1b936ed0c1b",
|
| 68 |
"gpu_tickets.py": "20928fcf1ca24648809ad8712c929377bdcc8b5e6b4387bfff3503ec5711928b",
|
| 69 |
"help.html": "58959e994a8e4bbd3c70b2a6256cb699ac615cde718c6d0288fabeee909098a2",
|
| 70 |
"hf-auth.js": "3f3938bbc0935e4e9230c094029ef46e03beea74be199887c50e053e03dafcfe",
|
| 71 |
"hf_identity.py": "913fb1e089af21f376e8359a20cbc838d5bcd7971def6989a4382d53c1127b37",
|
| 72 |
+
"index.html": "8d33f953dc2decc0895841940b7d8019684c968fdda17309c2b2405486e8bf3f",
|
| 73 |
"native/analysis.patch": "42f79de67cb0734cffb2a7582f5479cab42ab82675884b8b42b9f158d46a98e8",
|
| 74 |
"native/blackbox-build-receipt.json": "de3eea6c45a5ae4897287a97b0e2822fdae552a0655250bf940cef20d9b60d96",
|
| 75 |
"native/blackbox-source.tar.gz": "56b43430c68b95bf7ac0d8312975c366c920f99b1653d790fb15b59453728e28",
|
|
|
|
| 91 |
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 92 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 93 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 94 |
+
"track-map.js": "c5f56f1083f4c9487f8d3d663c939c736bdc478217e2b60142f820f3263adb44",
|
| 95 |
"training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
|
| 96 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 97 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
client.js
CHANGED
|
@@ -4,6 +4,10 @@ let job = null;
|
|
| 4 |
let busy = false;
|
| 5 |
let replayBusy = false;
|
| 6 |
let localReplay = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
let replayRequestId = 0;
|
| 8 |
let localResult = null;
|
| 9 |
let localAnalysis = null;
|
|
@@ -133,7 +137,56 @@ function buttons() {
|
|
| 133 |
if($('bucket_recipe_import'))$('bucket_recipe_import').disabled=unavailable||!bucketToken||!$('bucket_recipe_items')?.value;
|
| 134 |
if($('bucket_autosave'))$('bucket_autosave').disabled=unavailable||!bucketToken;
|
| 135 |
syncRunModeUI(settingsUnavailable);
|
|
|
|
|
|
|
| 136 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
const trainingOnlyControls=['population','generations','mutation','mutation_end','schedule','adaptive_mutation','selection','parents','elite','crossover','decay','preset','reward_preset','add','rewards'];
|
| 138 |
function syncRunModeUI(unavailable=busy||bundleRestoring||bucketOperation){
|
| 139 |
const evaluation=$('run_mode')?.value==='evaluate';
|
|
@@ -469,7 +522,8 @@ const networkSource=()=>stagedNetwork || $('network').files[0];
|
|
| 469 |
function replayNetworkSource(){return networkSource()||localResult;}
|
| 470 |
function invalidateReplay(reason){
|
| 471 |
replayRequestId++;
|
| 472 |
-
localReplay=null;
|
|
|
|
| 473 |
window.TrackReplay?.clearRecording?.(reason);
|
| 474 |
if($('replay_download'))$('replay_download').disabled=true;
|
| 475 |
}
|
|
@@ -508,7 +562,7 @@ if($('replay_record'))$('replay_record').onclick=async()=>{
|
|
| 508 |
if(!window.TrackReplay?.setRecording)throw Error('The recorded-drive viewer is unavailable. Reload this page.');
|
| 509 |
await window.TrackReplay.setRecording(recording,trackSha,networkSha);
|
| 510 |
if(attempt!==replayRequestId){window.TrackReplay.clearRecording();throw Error('Selection changed during recording. Record the current selection again.');}
|
| 511 |
-
localReplay=recording;
|
| 512 |
}catch(error){
|
| 513 |
if(attempt===replayRequestId&&$('replay_status'))$('replay_status').textContent=
|
| 514 |
error?.name==='AbortError'?'Recording timed out. Try a 5-second drive.':'Recording unavailable: '+error.message;
|
|
@@ -517,6 +571,13 @@ if($('replay_record'))$('replay_record').onclick=async()=>{
|
|
| 517 |
}
|
| 518 |
};
|
| 519 |
if($('replay_focus'))$('replay_focus').onclick=()=>{const focused=$('drive').classList.toggle('replay-focused');$('replay_focus').textContent=focused?'Return to run setup':'Expand replay workspace';$('replay_focus').setAttribute('aria-pressed',String(focused));};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 520 |
if($('replay_import'))$('replay_import').onchange=async()=>{
|
| 521 |
const file=$('replay_import').files[0];$('replay_import').value='';
|
| 522 |
if(!file||busy||job||batchActive||bucketOperation||bundleRestoring)return;
|
|
@@ -527,7 +588,7 @@ if($('replay_import'))$('replay_import').onchange=async()=>{
|
|
| 527 |
const [trackSha,networkSha]=await Promise.all([track.arrayBuffer().then(sha),network.arrayBuffer().then(sha)]);
|
| 528 |
const recording=JSON.parse(await file.text());
|
| 529 |
if(attempt!==replayRequestId)throw Error('Selection changed while opening recording.');
|
| 530 |
-
window.TrackReplay.setRecording(recording,trackSha,networkSha);localReplay=recording;
|
| 531 |
}catch(error){if(attempt===replayRequestId)window.TrackReplay?.clearRecording?.('Recording unavailable: '+error.message);}
|
| 532 |
finally{busy=false;replayBusy=false;buttons();}
|
| 533 |
};
|
|
@@ -665,7 +726,9 @@ function retainVerifiedPrimary(error){
|
|
| 665 |
}
|
| 666 |
async function acknowledge(current,received){
|
| 667 |
const body={receipt:received.receipt};if(received.analysisReceipt)body.analysis_receipt=received.analysisReceipt;
|
| 668 |
-
|
|
|
|
|
|
|
| 669 |
}
|
| 670 |
async function sha(bytes){return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),x=>x.toString(16).padStart(2,'0')).join('');}
|
| 671 |
const ANALYSIS_ARCHIVE_MAX=32*1024*1024,ANALYSIS_EXPANDED_MAX=256*1024*1024,ANALYSIS_ROWS_MAX=128000;
|
|
@@ -832,7 +895,7 @@ function showDescriptor(){
|
|
| 832 |
if(!Number.isInteger(generation)||!Number.isInteger(car)){ $('descriptor_status').textContent='Enter whole generation and car numbers.';return; }
|
| 833 |
const row=localAnalysis.descriptorRows.find(item=>item.generation===generation&&item.car===car);
|
| 834 |
if(!row){$('descriptor_status').textContent='No descriptor was recorded for that generation and car.';$('descriptor_tables').replaceChildren();return;}
|
| 835 |
-
|
| 836 |
const wrap=document.createElement('div');
|
| 837 |
const summary=document.createElement('p');summary.textContent='Candidate '+row.candidate_id+' · ticks '+row.ticks_elapsed+' · stats updates '+row.stats_update_count+' · weights '+row.weights_sha256;wrap.append(summary);
|
| 838 |
const table=(title,entries)=>{const h=document.createElement('h3');h.textContent=title;wrap.append(h);const t=document.createElement('table'),body=document.createElement('tbody');for(const [name,value] of entries){const tr=document.createElement('tr'),a=document.createElement('th'),b=document.createElement('td');a.textContent=name;b.textContent=value===null?'Not observed':typeof value==='number'?String(value):String(value);tr.append(a,b);body.append(tr);}t.append(body);wrap.append(t);};
|
|
@@ -1050,7 +1113,8 @@ $('run').onclick = async () => {
|
|
| 1050 |
const started=performance.now();
|
| 1051 |
$('runState').textContent='RUNNING — waiting for server confirmation';
|
| 1052 |
$('runState').style.color='#ffd073';
|
| 1053 |
-
runReceipt=null;pairedAnalysisPending=false;
|
|
|
|
| 1054 |
runResultContext=null;
|
| 1055 |
showDetails(null);
|
| 1056 |
const tick=()=>{$('runSummary').textContent='Backend: '+$('backend').value.toUpperCase()+'\nElapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\nCompletion is not yet confirmed.';};
|
|
@@ -1074,7 +1138,7 @@ $('run').onclick = async () => {
|
|
| 1074 |
const body={track:await encoded(trackFile),network:await encoded(networkFile),automatic:autosaveSnapshot?true:!$('keepServer').checked,backend:$('backend').value};
|
| 1075 |
if(window.getTrainingSettings){body.settings={...settings};body.settings.mode=$('run_mode')?.value==='evaluate'?'evaluate':'train';body.settings.clean_min_laps=cleanMinLaps;
|
| 1076 |
if(body.settings.mode==='evaluate')Object.assign(body.settings,{population:1,generations:1,mutation:0,mutation_end:0,schedule:'fixed',selection:'best',parents:1,elite:0,crossover:'none',decay:0,adaptive_mutation:false});}
|
| 1077 |
-
job=await submit(body);localResult=null;localAnalysis=null;pairedAnalysisPending=false;selectedWinner=null;renderAnalysisPanel();
|
| 1078 |
clearInterval(progress);
|
| 1079 |
$('runState').textContent='COMPLETE — candidate ready to download';
|
| 1080 |
$('runState').style.color='#62dfb1';
|
|
@@ -1100,7 +1164,7 @@ $('run').onclick = async () => {
|
|
| 1100 |
if($('leaderboard_opt_in')?.checked)void checkSteamLeaderboard(trackInfo,r);
|
| 1101 |
else if($('leaderboard_status'))$('leaderboard_status').textContent='';
|
| 1102 |
if(!comparisonPlan)$('track').value=''; $('network').value='';
|
| 1103 |
-
stagedNetwork=null;window.setFreezeNetwork?.(null);if($('networkChoice'))$('networkChoice').textContent='
|
| 1104 |
say('Result ready ('+job.backend.toUpperCase()+'). Inputs and intermediate worker files have been removed.');
|
| 1105 |
if(autosaveSnapshot){
|
| 1106 |
try{
|
|
@@ -1182,7 +1246,7 @@ $('run').onclick = async () => {
|
|
| 1182 |
: 'No downloadable result was received. A connection error does not prove the server stopped.');
|
| 1183 |
say(displayError(error));
|
| 1184 |
}
|
| 1185 |
-
finally {clearInterval(progress);busy=false;$('keepServer').checked=false;buttons();if(comparisonPlan)launchNextComparison();}
|
| 1186 |
};
|
| 1187 |
$('download').onclick = async () => {
|
| 1188 |
busy=true; buttons();
|
|
@@ -1203,7 +1267,7 @@ $('download').onclick = async () => {
|
|
| 1203 |
$('erase').onclick=async()=>{
|
| 1204 |
if(!await window.appConfirm('Delete remaining server-side result and revoke download access? Local files are unaffected.',{heading:'Delete server result',confirmLabel:'Delete result',danger:true}))return;
|
| 1205 |
busy=true; buttons();
|
| 1206 |
-
try {await request('/api/jobs/'+job.id,{method:'DELETE',headers:headers()}); job=null; say('Server result deleted. Local files are unaffected.');}
|
| 1207 |
catch(error){say(error.message);}
|
| 1208 |
finally {busy=false; buttons();}
|
| 1209 |
};
|
|
@@ -1218,12 +1282,14 @@ if($('batch_start')){
|
|
| 1218 |
let plan,failedCandidateStep=null;const history=[];const started=Date.now();
|
| 1219 |
try{
|
| 1220 |
plan=window.getBatchPlan();
|
|
|
|
| 1221 |
busy=true;buttons(); // Freeze both selectors before an asynchronous track fetch.
|
| 1222 |
const trackFile=await trackSource();
|
| 1223 |
if(!trackFile)throw Error('Choose a track in Run & results before running a batch.');
|
| 1224 |
const trackBytes=new Uint8Array(await trackFile.arrayBuffer()),track=await encoded(trackFile),trackInfo=trackLibrary?.info()??{source:'upload'},original=networkSource();
|
| 1225 |
await encoded(original); // Reject missing/oversize inputs before dispatch.
|
| 1226 |
const backend=$('backend').value,autosaveSnapshot=bucketToken&&$('bucket_autosave')?.checked?bucketSnapshot():null;
|
|
|
|
| 1227 |
busy=true;batchActive=true;batchStopRequested=false;batchReceipt=null;buttons();
|
| 1228 |
localResult=null;localAnalysis=null;pairedAnalysisPending=false;
|
| 1229 |
$('batch_history').replaceChildren();
|
|
@@ -1233,8 +1299,10 @@ if($('batch_start')){
|
|
| 1233 |
showDetails(null);
|
| 1234 |
const result=await window.BatchTraining.execute(plan,original,async(source,settings,i)=>{
|
| 1235 |
$('batch_status').textContent='RUNNING · '+plan.name+' · step '+(i+1)+' / '+plan.runs.length+' · '+plan.runs[i].slot+' · '+plan.runs[i].name;
|
|
|
|
| 1236 |
const inputHash=await sha(await source.arrayBuffer());
|
| 1237 |
job=await submit({track,network:await encoded(source),settings,backend,automatic:true});
|
|
|
|
| 1238 |
const stepJob=job,report=stepJob.report,stepRunReceipt={version:1,state:'completed',build:report?.build??appBuild,backend,output_sha256:stepJob.sha256,report};
|
| 1239 |
let received;
|
| 1240 |
try{received=await receive(job);}catch(error){if(retainVerifiedPrimary(error)){runReceipt=stepRunReceipt;runResultContext={trackInfo,trackBytes,inputNetworkSha256:inputHash,autosaveSnapshot};}throw error;}
|
|
@@ -1295,7 +1363,7 @@ setInterval(()=>{
|
|
| 1295 |
if(!job){$('deadline').textContent='';return;}
|
| 1296 |
const remaining=Math.max(0,Math.ceil(job.expires_at-Date.now()/1000));
|
| 1297 |
$('deadline').textContent='Automatic expiry: '+new Date(job.expires_at*1000).toLocaleTimeString()+' · '+Math.floor(remaining/60)+'m '+remaining%60+'s remaining';
|
| 1298 |
-
if(!remaining && !busy){job=null; buttons(); say('Result access has expired. Server cleanup runs independently of this browser.');}
|
| 1299 |
},1000);
|
| 1300 |
buttons();
|
| 1301 |
async function connect(){
|
|
@@ -1588,7 +1656,7 @@ async function applyPreparedPortable(prepared,{isCurrent=null}={}){
|
|
| 1588 |
}
|
| 1589 |
window.TrainingClient.useNetwork(networkFile);
|
| 1590 |
if(parsed.recipe)window.stagePortableRecipe(parsed.recipe);
|
| 1591 |
-
portableReceipt=parsed.receipt||null;
|
| 1592 |
localAnalysis=parsed.analysis||null;selectedWinner=null;renderAnalysisPanel();
|
| 1593 |
$('bundle_receipt_download').disabled=!portableReceipt;
|
| 1594 |
bundleMessage('Bundle restored in this page only. Network and optional track/recipe are staged; no run started and no browser storage or server was changed.'+(portableReceipt?' Sanitized receipt is available for separate download.':''));
|
|
|
|
| 4 |
let busy = false;
|
| 5 |
let replayBusy = false;
|
| 6 |
let localReplay = null;
|
| 7 |
+
let localReplayName = null;
|
| 8 |
+
let localReplayDriver = null;
|
| 9 |
+
let serverCopyStatus = 'no server result has been published.';
|
| 10 |
+
let runDisplayContext = null;
|
| 11 |
let replayRequestId = 0;
|
| 12 |
let localResult = null;
|
| 13 |
let localAnalysis = null;
|
|
|
|
| 137 |
if($('bucket_recipe_import'))$('bucket_recipe_import').disabled=unavailable||!bucketToken||!$('bucket_recipe_items')?.value;
|
| 138 |
if($('bucket_autosave'))$('bucket_autosave').disabled=unavailable||!bucketToken;
|
| 139 |
syncRunModeUI(settingsUnavailable);
|
| 140 |
+
renderResultSummary();
|
| 141 |
+
renderRecordingIdentity();
|
| 142 |
}
|
| 143 |
+
// Presentation only: these summaries reuse the verified run/replay state and existing controls.
|
| 144 |
+
function selectedReplayDriverLabel(){
|
| 145 |
+
const selected=networkSource();
|
| 146 |
+
if(selected)return selected.name||'Selected driver';
|
| 147 |
+
if(localResult){
|
| 148 |
+
const winner=localAnalysis?.summary?.winners?.fitness;
|
| 149 |
+
return winner?.status==='available'?'Final fitness leader · '+winner.candidate_id:'Latest received result';
|
| 150 |
+
}
|
| 151 |
+
return null;
|
| 152 |
+
}
|
| 153 |
+
function renderResultSummary(){
|
| 154 |
+
const panel=$('resultSummary');if(!panel)return;
|
| 155 |
+
const running=$('runState').textContent.startsWith('RUNNING')||$('runState').textContent.startsWith('BATCH RUNNING');
|
| 156 |
+
panel.hidden=!runDisplayContext&&!runReceipt&&!portableReceipt&&!localResult&&!job&&!running;
|
| 157 |
+
const receipt=runReceipt||portableReceipt,report=runDisplayContext?runDisplayContext.report:receipt?.report;
|
| 158 |
+
const mode=report?.mode??report?.settings?.mode??report?.analysis_summary?.mode??runDisplayContext?.mode;
|
| 159 |
+
$('result_backend').textContent=(runDisplayContext?.backend||receipt?.backend||job?.backend||$('backend').value).toUpperCase()+(running?' · running':'');
|
| 160 |
+
$('result_outcome').textContent=running?'Completion is not yet confirmed.':report
|
| 161 |
+
?((mode==='evaluate'?'Frozen evaluation':'Training')+' · '+(report.generations_completed??'—')+' / '+(report.generations_requested??'—')+' generations · '+(report.best_lap_seconds==null?'No completed lap observed':Number(report.best_lap_seconds).toFixed(3)+' s best lap observed')+'. A completed run does not establish improved driving.')
|
| 162 |
+
:'No verified run measurements are available.';
|
| 163 |
+
const driver=selectedReplayDriverLabel();
|
| 164 |
+
$('result_driver').textContent=networkSource()?'Selected for replay / next run: '+driver+'.':localResult?'Received driver for replay: '+driver+'. To use it for another run or batch, select a winner below or upload the downloaded driver.':'No driver selected for replay or the next run.';
|
| 165 |
+
const serverState=running?'waiting for a published result.':!runDisplayContext&&portableReceipt&&!runReceipt?'not tracked for this restored bundle.':serverCopyStatus;
|
| 166 |
+
$('result_server').textContent='Server copy: '+serverState+(localResult&&!running?' Verified browser driver remains available.':'');
|
| 167 |
+
$('result_replay').disabled=busy||replayBusy||!!job||!driver;
|
| 168 |
+
$('download').hidden=!job;
|
| 169 |
+
$('saveLocal').hidden=!!job&&!localResult;
|
| 170 |
+
$('saveLocal').textContent='Download received driver';
|
| 171 |
+
if($('setup_next_step'))$('setup_next_step').textContent=running?'Run in progress. Its result will appear above when confirmed.':networkSource()
|
| 172 |
+
?'Selected driver is ready. Review Training & recipes, then start the next run.'
|
| 173 |
+
:localResult?'Your received driver is available in Latest run above. Download it, replay it, or select a winner below to continue.'
|
| 174 |
+
:job?'Receive the completed driver using Download driver in Latest run above. Its weights can then be replayed or inspected.'
|
| 175 |
+
:'Choose or create a driver, then review Training & recipes and start a run.';
|
| 176 |
+
if($('starter_network_status'))$('starter_network_status').textContent=networkSource()?'Selected driver: '+(networkSource().name||'Browser driver')+'. A new starter replaces this selection without starting a run.':localResult?'The latest trained or evaluated driver is received. Use Latest run above to download or replay it. Create a starter only to begin a new experiment.':job?'The run has finished. Receive the driver using Latest run above, or delete the server result before starting another experiment.':'Creates a small network in this page only. It does not start a run or change saved recipes.';
|
| 177 |
+
}
|
| 178 |
+
function renderRecordingIdentity(){
|
| 179 |
+
const label=$('replay_loaded');if(!label)return;
|
| 180 |
+
label.hidden=!localReplay;
|
| 181 |
+
label.textContent=localReplay?'Recording loaded: '+localReplayName+' · '+(localReplay.requested_ticks/60).toFixed(2)+' seconds · '+localReplay.frames.length+' measured samples · Matching driver: '+localReplayDriver+'. Fresh CPU evaluation.':'';
|
| 182 |
+
}
|
| 183 |
+
function openReplayWorkspace({focusRecord=false}={}){
|
| 184 |
+
$('drive').classList.add('replay-focused');
|
| 185 |
+
$('replay_focus').textContent='Return to run setup';$('replay_focus').setAttribute('aria-pressed','true');
|
| 186 |
+
const target=focusRecord?$('replayControls'):$('replay_workspace');
|
| 187 |
+
target.tabIndex=-1;target.scrollIntoView({block:'start'});target.focus({preventScroll:true});
|
| 188 |
+
}
|
| 189 |
+
if($('result_replay'))$('result_replay').onclick=()=>openReplayWorkspace({focusRecord:true});
|
| 190 |
const trainingOnlyControls=['population','generations','mutation','mutation_end','schedule','adaptive_mutation','selection','parents','elite','crossover','decay','preset','reward_preset','add','rewards'];
|
| 191 |
function syncRunModeUI(unavailable=busy||bundleRestoring||bucketOperation){
|
| 192 |
const evaluation=$('run_mode')?.value==='evaluate';
|
|
|
|
| 522 |
function replayNetworkSource(){return networkSource()||localResult;}
|
| 523 |
function invalidateReplay(reason){
|
| 524 |
replayRequestId++;
|
| 525 |
+
localReplay=null;localReplayName=null;localReplayDriver=null;
|
| 526 |
+
renderRecordingIdentity();
|
| 527 |
window.TrackReplay?.clearRecording?.(reason);
|
| 528 |
if($('replay_download'))$('replay_download').disabled=true;
|
| 529 |
}
|
|
|
|
| 562 |
if(!window.TrackReplay?.setRecording)throw Error('The recorded-drive viewer is unavailable. Reload this page.');
|
| 563 |
await window.TrackReplay.setRecording(recording,trackSha,networkSha);
|
| 564 |
if(attempt!==replayRequestId){window.TrackReplay.clearRecording();throw Error('Selection changed during recording. Record the current selection again.');}
|
| 565 |
+
localReplay=recording;localReplayName='ald-recorded-drive.json';localReplayDriver=selectedReplayDriverLabel();renderRecordingIdentity();openReplayWorkspace();
|
| 566 |
}catch(error){
|
| 567 |
if(attempt===replayRequestId&&$('replay_status'))$('replay_status').textContent=
|
| 568 |
error?.name==='AbortError'?'Recording timed out. Try a 5-second drive.':'Recording unavailable: '+error.message;
|
|
|
|
| 571 |
}
|
| 572 |
};
|
| 573 |
if($('replay_focus'))$('replay_focus').onclick=()=>{const focused=$('drive').classList.toggle('replay-focused');$('replay_focus').textContent=focused?'Return to run setup':'Expand replay workspace';$('replay_focus').setAttribute('aria-pressed',String(focused));};
|
| 574 |
+
// Returning from the black box keeps the map in view as well as keyboard focus on playback.
|
| 575 |
+
document.querySelectorAll('a[href="#replay_transport"]').forEach(link=>link.addEventListener('click',event=>{
|
| 576 |
+
event.preventDefault();
|
| 577 |
+
const viewer=$('trackMap').closest('.replay-viewer');
|
| 578 |
+
viewer.scrollIntoView({block:'start'});
|
| 579 |
+
$('replay_transport').focus({preventScroll:true});
|
| 580 |
+
}));
|
| 581 |
if($('replay_import'))$('replay_import').onchange=async()=>{
|
| 582 |
const file=$('replay_import').files[0];$('replay_import').value='';
|
| 583 |
if(!file||busy||job||batchActive||bucketOperation||bundleRestoring)return;
|
|
|
|
| 588 |
const [trackSha,networkSha]=await Promise.all([track.arrayBuffer().then(sha),network.arrayBuffer().then(sha)]);
|
| 589 |
const recording=JSON.parse(await file.text());
|
| 590 |
if(attempt!==replayRequestId)throw Error('Selection changed while opening recording.');
|
| 591 |
+
window.TrackReplay.setRecording(recording,trackSha,networkSha);localReplay=recording;localReplayName=file.name;localReplayDriver=selectedReplayDriverLabel();renderRecordingIdentity();openReplayWorkspace();
|
| 592 |
}catch(error){if(attempt===replayRequestId)window.TrackReplay?.clearRecording?.('Recording unavailable: '+error.message);}
|
| 593 |
finally{busy=false;replayBusy=false;buttons();}
|
| 594 |
};
|
|
|
|
| 726 |
}
|
| 727 |
async function acknowledge(current,received){
|
| 728 |
const body={receipt:received.receipt};if(received.analysisReceipt)body.analysis_receipt=received.analysisReceipt;
|
| 729 |
+
const acknowledgment=await (await request('/api/jobs/'+current.id+'/ack',{method:'POST',headers:{'X-Job-Key':current.key,'Content-Type':'application/json'},body:JSON.stringify(body)})).json();
|
| 730 |
+
serverCopyStatus=acknowledgment.deleted===true?'deleted after verified receipt.':'retained for repeat download until its five-minute deadline. Use Delete server-side job data now to remove it sooner.';
|
| 731 |
+
return acknowledgment;
|
| 732 |
}
|
| 733 |
async function sha(bytes){return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),x=>x.toString(16).padStart(2,'0')).join('');}
|
| 734 |
const ANALYSIS_ARCHIVE_MAX=32*1024*1024,ANALYSIS_EXPANDED_MAX=256*1024*1024,ANALYSIS_ROWS_MAX=128000;
|
|
|
|
| 895 |
if(!Number.isInteger(generation)||!Number.isInteger(car)){ $('descriptor_status').textContent='Enter whole generation and car numbers.';return; }
|
| 896 |
const row=localAnalysis.descriptorRows.find(item=>item.generation===generation&&item.car===car);
|
| 897 |
if(!row){$('descriptor_status').textContent='No descriptor was recorded for that generation and car.';$('descriptor_tables').replaceChildren();return;}
|
| 898 |
+
invalidateReplay('Descriptor selected. Record the selected policy again to inspect a drive.');
|
| 899 |
const wrap=document.createElement('div');
|
| 900 |
const summary=document.createElement('p');summary.textContent='Candidate '+row.candidate_id+' · ticks '+row.ticks_elapsed+' · stats updates '+row.stats_update_count+' · weights '+row.weights_sha256;wrap.append(summary);
|
| 901 |
const table=(title,entries)=>{const h=document.createElement('h3');h.textContent=title;wrap.append(h);const t=document.createElement('table'),body=document.createElement('tbody');for(const [name,value] of entries){const tr=document.createElement('tr'),a=document.createElement('th'),b=document.createElement('td');a.textContent=name;b.textContent=value===null?'Not observed':typeof value==='number'?String(value):String(value);tr.append(a,b);body.append(tr);}t.append(body);wrap.append(t);};
|
|
|
|
| 1113 |
const started=performance.now();
|
| 1114 |
$('runState').textContent='RUNNING — waiting for server confirmation';
|
| 1115 |
$('runState').style.color='#ffd073';
|
| 1116 |
+
runReceipt=null;pairedAnalysisPending=false;serverCopyStatus='no new result is confirmed.';
|
| 1117 |
+
runDisplayContext={backend:$('backend').value,mode:$('run_mode')?.value??'train',report:null};
|
| 1118 |
runResultContext=null;
|
| 1119 |
showDetails(null);
|
| 1120 |
const tick=()=>{$('runSummary').textContent='Backend: '+$('backend').value.toUpperCase()+'\nElapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\nCompletion is not yet confirmed.';};
|
|
|
|
| 1138 |
const body={track:await encoded(trackFile),network:await encoded(networkFile),automatic:autosaveSnapshot?true:!$('keepServer').checked,backend:$('backend').value};
|
| 1139 |
if(window.getTrainingSettings){body.settings={...settings};body.settings.mode=$('run_mode')?.value==='evaluate'?'evaluate':'train';body.settings.clean_min_laps=cleanMinLaps;
|
| 1140 |
if(body.settings.mode==='evaluate')Object.assign(body.settings,{population:1,generations:1,mutation:0,mutation_end:0,schedule:'fixed',selection:'best',parents:1,elite:0,crossover:'none',decay:0,adaptive_mutation:false});}
|
| 1141 |
+
job=await submit(body);runDisplayContext={backend:job.backend,mode:body.settings?.mode??'train',report:job.report};serverCopyStatus='available until verified receipt, deletion or expiry. Receive and download the driver below.';localResult=null;localAnalysis=null;pairedAnalysisPending=false;selectedWinner=null;renderAnalysisPanel();
|
| 1142 |
clearInterval(progress);
|
| 1143 |
$('runState').textContent='COMPLETE — candidate ready to download';
|
| 1144 |
$('runState').style.color='#62dfb1';
|
|
|
|
| 1164 |
if($('leaderboard_opt_in')?.checked)void checkSteamLeaderboard(trackInfo,r);
|
| 1165 |
else if($('leaderboard_status'))$('leaderboard_status').textContent='';
|
| 1166 |
if(!comparisonPlan)$('track').value=''; $('network').value='';
|
| 1167 |
+
stagedNetwork=null;window.setFreezeNetwork?.(null);if($('networkChoice'))$('networkChoice').textContent='Run input released. The latest result and its download controls are above.';
|
| 1168 |
say('Result ready ('+job.backend.toUpperCase()+'). Inputs and intermediate worker files have been removed.');
|
| 1169 |
if(autosaveSnapshot){
|
| 1170 |
try{
|
|
|
|
| 1246 |
: 'No downloadable result was received. A connection error does not prove the server stopped.');
|
| 1247 |
say(displayError(error));
|
| 1248 |
}
|
| 1249 |
+
finally {clearInterval(progress);busy=false;$('keepServer').checked=false;buttons();if(comparisonPlan)launchNextComparison();else if(!batchActive&&!$('resultSummary').hidden){const heading=$('result_summary_title');heading.scrollIntoView({block:'start'});heading.focus({preventScroll:true});}}
|
| 1250 |
};
|
| 1251 |
$('download').onclick = async () => {
|
| 1252 |
busy=true; buttons();
|
|
|
|
| 1267 |
$('erase').onclick=async()=>{
|
| 1268 |
if(!await window.appConfirm('Delete remaining server-side result and revoke download access? Local files are unaffected.',{heading:'Delete server result',confirmLabel:'Delete result',danger:true}))return;
|
| 1269 |
busy=true; buttons();
|
| 1270 |
+
try {await request('/api/jobs/'+job.id,{method:'DELETE',headers:headers()}); job=null; serverCopyStatus='deleted by request. Local files are unaffected.';say('Server result deleted. Local files are unaffected.');}
|
| 1271 |
catch(error){say(error.message);}
|
| 1272 |
finally {busy=false; buttons();}
|
| 1273 |
};
|
|
|
|
| 1282 |
let plan,failedCandidateStep=null;const history=[];const started=Date.now();
|
| 1283 |
try{
|
| 1284 |
plan=window.getBatchPlan();
|
| 1285 |
+
runDisplayContext={backend:$('backend').value,mode:'train',report:null};serverCopyStatus='no new result is confirmed.';
|
| 1286 |
busy=true;buttons(); // Freeze both selectors before an asynchronous track fetch.
|
| 1287 |
const trackFile=await trackSource();
|
| 1288 |
if(!trackFile)throw Error('Choose a track in Run & results before running a batch.');
|
| 1289 |
const trackBytes=new Uint8Array(await trackFile.arrayBuffer()),track=await encoded(trackFile),trackInfo=trackLibrary?.info()??{source:'upload'},original=networkSource();
|
| 1290 |
await encoded(original); // Reject missing/oversize inputs before dispatch.
|
| 1291 |
const backend=$('backend').value,autosaveSnapshot=bucketToken&&$('bucket_autosave')?.checked?bucketSnapshot():null;
|
| 1292 |
+
runDisplayContext={backend,mode:'train',report:null};serverCopyStatus='no new result is confirmed.';
|
| 1293 |
busy=true;batchActive=true;batchStopRequested=false;batchReceipt=null;buttons();
|
| 1294 |
localResult=null;localAnalysis=null;pairedAnalysisPending=false;
|
| 1295 |
$('batch_history').replaceChildren();
|
|
|
|
| 1299 |
showDetails(null);
|
| 1300 |
const result=await window.BatchTraining.execute(plan,original,async(source,settings,i)=>{
|
| 1301 |
$('batch_status').textContent='RUNNING · '+plan.name+' · step '+(i+1)+' / '+plan.runs.length+' · '+plan.runs[i].slot+' · '+plan.runs[i].name;
|
| 1302 |
+
runDisplayContext={backend,mode:settings.mode??'train',report:null};serverCopyStatus='no new result is confirmed.';
|
| 1303 |
const inputHash=await sha(await source.arrayBuffer());
|
| 1304 |
job=await submit({track,network:await encoded(source),settings,backend,automatic:true});
|
| 1305 |
+
runDisplayContext={backend:job.backend,mode:settings.mode??'train',report:job.report};serverCopyStatus='available until verified receipt, deletion or expiry. Receive and download the driver below.';
|
| 1306 |
const stepJob=job,report=stepJob.report,stepRunReceipt={version:1,state:'completed',build:report?.build??appBuild,backend,output_sha256:stepJob.sha256,report};
|
| 1307 |
let received;
|
| 1308 |
try{received=await receive(job);}catch(error){if(retainVerifiedPrimary(error)){runReceipt=stepRunReceipt;runResultContext={trackInfo,trackBytes,inputNetworkSha256:inputHash,autosaveSnapshot};}throw error;}
|
|
|
|
| 1363 |
if(!job){$('deadline').textContent='';return;}
|
| 1364 |
const remaining=Math.max(0,Math.ceil(job.expires_at-Date.now()/1000));
|
| 1365 |
$('deadline').textContent='Automatic expiry: '+new Date(job.expires_at*1000).toLocaleTimeString()+' · '+Math.floor(remaining/60)+'m '+remaining%60+'s remaining';
|
| 1366 |
+
if(!remaining && !busy){job=null; serverCopyStatus='access expired. Server cleanup runs independently of this browser.';buttons(); say('Result access has expired. Server cleanup runs independently of this browser.');}
|
| 1367 |
},1000);
|
| 1368 |
buttons();
|
| 1369 |
async function connect(){
|
|
|
|
| 1656 |
}
|
| 1657 |
window.TrainingClient.useNetwork(networkFile);
|
| 1658 |
if(parsed.recipe)window.stagePortableRecipe(parsed.recipe);
|
| 1659 |
+
portableReceipt=parsed.receipt||null;runDisplayContext=null;
|
| 1660 |
localAnalysis=parsed.analysis||null;selectedWinner=null;renderAnalysisPanel();
|
| 1661 |
$('bundle_receipt_download').disabled=!portableReceipt;
|
| 1662 |
bundleMessage('Bundle restored in this page only. Network and optional track/recipe are staged; no run started and no browser storage or server was changed.'+(portableReceipt?' Sanitized receipt is available for separate download.':''));
|
index.html
CHANGED
|
@@ -8,6 +8,13 @@
|
|
| 8 |
.replay-bar{background:#101d2c;border:1px solid #2a3d52;border-radius:11px;padding:10px 12px;margin:10px 0}.replay-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.replay-row button{min-height:36px;padding:6px 11px;font-size:13px;font-weight:650}.replay-row select{width:auto;min-height:36px;padding:5px 9px;font-size:13px}.replay-row input[type=range]{flex:1;min-width:120px;accent-color:#62dfb1;padding:0}.replay-time{font:600 12px ui-monospace,SFMono-Regular,monospace;color:#91efc6;white-space:nowrap}.replay-layers{display:flex;gap:6px 12px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px solid #223447;font-size:12.5px;color:#b9cddd}.replay-layers label{display:inline-flex;align-items:center;gap:5px;margin:0;cursor:pointer}.replay-layers input{margin:0}.kpi-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:12px 0}.kpi-tile{background:#101d2c;border:1px solid #2a3d52;border-radius:10px;padding:10px 12px}.kpi-tile .kpi-label{font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;color:#a2b4c8}.kpi-tile .kpi-val{font:800 19px ui-monospace,SFMono-Regular,monospace;color:#62dfb1;margin-top:3px}.kpi-tile .kpi-sub{font-size:11.5px;color:#8fa6bc;margin-top:2px}.chart-box{background:#09111b;border:1px solid #2a3d52;border-radius:10px;padding:10px;margin:10px 0}.chart-box canvas{max-height:190px;display:block}.subtab-bar{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.subtab-bar button{min-height:36px;padding:6px 12px;font-size:13px;border-radius:18px}.subtab-bar button[aria-pressed=true]{background:#62dfb1;color:#102522;font-weight:700}.editor-visual-grid{display:grid;grid-template-columns:minmax(260px,.85fr) minmax(0,1.35fr);gap:16px;align-items:start;margin:14px 0}@media(max-width:850px){.kpi-strip{grid-template-columns:1fr 1fr}.editor-visual-grid{grid-template-columns:minmax(0,1fr)}}
|
| 9 |
|
| 10 |
.replay-workbench{display:grid;gap:14px;align-items:start}#replayControls,#replay_transport,#replay_blackbox{scroll-margin-top:160px}#replayControls{min-width:0}.replay-row>*{max-width:100%}#replay_blackbox{padding:12px;border:1px solid #2a3d52;border-radius:10px;min-width:0}#replay_blackbox h3{margin-top:0}#replay_blackbox td,#replay_blackbox th{padding:6px;font-size:12px}#drive.replay-focused>.grid{grid-template-columns:minmax(0,1fr)}#drive.replay-focused>.grid>section:first-child{display:none}#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1.35fr) minmax(320px,1fr)}#drive.replay-focused .map{position:sticky;top:70px}#drive.replay-focused .map canvas{max-height:500px}@media(max-width:850px){#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1fr)}#drive.replay-focused .map{position:static}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
</style>
|
| 12 |
<dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
|
| 13 |
<h2 id="appConfirmTitle">Confirm action</h2>
|
|
@@ -36,9 +43,9 @@
|
|
| 36 |
})();
|
| 37 |
</script>
|
| 38 |
<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#start-here" target="_blank" rel="noopener">Help & tutorial ↗</a></div></header>
|
| 39 |
-
<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>The map follows the selected track’s native centerline. Record a short CPU drive to watch an unchanged policy using real vehicle positions. A new recording can differ from an earlier training run.
|
| 40 |
-
<details id="firstRunGuide" class="quick-start"><summary id="firstRunTitle">New here? Start a first run <span class="hint">
|
| 41 |
-
<
|
| 42 |
<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>
|
| 43 |
<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>
|
| 44 |
<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>
|
|
@@ -53,27 +60,23 @@
|
|
| 53 |
</details></section><section class="card"><h2>Visual replay & car black box</h2><p id="mapStatus" class="map-status" role="status">Loading selected track…</p><button type="button" id="replay_focus" aria-pressed="false">Expand replay workspace</button><div class="replay-bar" id="replayControls" tabindex="-1" aria-label="Recorded drive controls">
|
| 54 |
<div class="replay-row"><button type="button" id="replay_record" disabled>Record selected policy</button><label for="replay_duration" style="margin:0">Length</label><select id="replay_duration"><option value="300" selected>5 seconds</option><option value="900">15 seconds</option></select><button type="button" id="replay_download" disabled>Download recording</button><label for="replay_import">Open recording<input type="file" id="replay_import" accept=".json,application/json" disabled></label></div>
|
| 55 |
<p id="replay_policy" class="hint">Choose or create a network to record a drive.</p>
|
| 56 |
-
<p class="hint">Fresh CPU drive · unchanged weights · 60 Hz
|
| 57 |
-
<p id="
|
| 58 |
-
<
|
| 59 |
-
<div class="replay-
|
|
|
|
| 60 |
<div class="replay-layers"><label><input type="checkbox" id="replay_layer_outline" checked disabled> Car outline</label><label><input type="checkbox" id="replay_layer_line" checked disabled> Recorded path</label><label><input type="checkbox" id="replay_layer_hud" checked disabled> Recorded telemetry</label></div>
|
| 61 |
<div class="replay-row"><label><input type="checkbox" id="replay_interpolate" disabled> Smooth pose between measured samples</label><label for="replay_event">Contact / active-state interval</label><select id="replay_event" aria-label="Recorded event interval" disabled><option value="">No recording</option></select></div>
|
| 62 |
|
| 63 |
-
<p id="replay_readout" style="font-size:16px;font-variant-numeric:tabular-nums" aria-live="off"></p>
|
| 64 |
-
<
|
| 65 |
-
</div><div class="replay-workbench"><div class="map"><canvas id="trackMap" width="1000" height="500" role="img" aria-label="Selected track tile layout and terrain"></canvas></div><section id="replay_blackbox" tabindex="-1" hidden aria-label="Recorded car black box"><div class="section-heading"><h3>Car black box</h3><a href="#replay_transport">Back to playback</a></div><p id="blackbox_phase" class="hint"></p>
|
| 66 |
<p class="hint">All values below belong to the selected native sample. Sensor inputs were consumed before physics at the decision tick; vehicle state is captured after physics. Samples are 0.1 seconds apart. Contact events identify intervals, not exact impact ticks; the counter does not count unique crashes.</p>
|
| 67 |
<details><summary>Recorded vehicle state</summary><div class="table-scroll"><table><thead><tr><th>Vehicle state</th><th>Recorded value</th></tr></thead><tbody id="blackbox_state"></tbody></table></div></details>
|
| 68 |
<h4>Native control commands</h4><div class="table-scroll"><table><thead><tr><th>Control</th><th>Raw command</th><th>Applied after normalization</th><th>Source</th></tr></thead><tbody id="blackbox_controls"></tbody></table></div>
|
| 69 |
<details><summary>Inputs consumed by the native network</summary><div class="table-scroll"><table><thead><tr><th>Input index</th><th>Sensor</th><th>Parameters</th><th>Native input value</th></tr></thead><tbody id="blackbox_inputs"></tbody></table></div><p class="hint">These are the input values the policy consumed. Ray inputs are not ray-hit positions. Hidden-layer activations are not recorded.</p></details>
|
| 70 |
-
<details><summary>Recorded cumulative reward metrics</summary><p class="hint">Metrics accumulate at native statistics callbacks; they are not instantaneous forces or per-reward contributions.</p><div class="table-scroll"><table><thead><tr><th>Metric</th><th>Recorded cumulative value</th></tr></thead><tbody id="blackbox_metrics"></tbody></table></div></details><p><a href="#replay_transport">Back to playback</a></p></section></div><div class="map-legend"><span><i class="terrain asphalt"></i> Asphalt</span><span><i class="terrain dirt"></i> Dirt</span><span><i class="terrain ice"></i> Ice</span><span><i class="terrain start"></i> Start</span></div><p id="mapLegend" class="hint"></p><
|
| 71 |
-
<p><button id="saveLocal" disabled>Download received result (browser only)</button> <button id="forgetLocal" disabled>Discard browser result</button></p>
|
| 72 |
-
<p>The browser keeps the received result in memory until discarded, replaced by a new run, or this page is closed. It is not saved to browser storage.</p>
|
| 73 |
-
<h3 id="runState" role="status" aria-live="polite">READY — no run started</h3>
|
| 74 |
-
<div id="runKpiStrip" class="kpi-strip" hidden><div class="kpi-tile"><div class="kpi-label">Best Fitness</div><div id="kpi_best_score" class="kpi-val">—</div><div id="kpi_best_sub" class="kpi-sub">Final generation</div></div><div class="kpi-tile"><div class="kpi-label">Mean Fitness</div><div id="kpi_mean_score" class="kpi-val" style="color:#58a6ff">—</div><div id="kpi_mean_sub" class="kpi-sub">Population mean</div></div><div class="kpi-tile"><div class="kpi-label">Best Lap Time</div><div id="kpi_best_lap" class="kpi-val" style="color:#ffd073">—</div><div id="kpi_lap_sub" class="kpi-sub">Fastest observed</div></div><div class="kpi-tile"><div class="kpi-label">Lapped Cars</div><div id="kpi_lapped_cars" class="kpi-val" style="color:#c792ea">—</div><div id="kpi_lapped_sub" class="kpi-sub">Completed lap</div></div></div>
|
| 75 |
<div id="historyChartWrap" class="chart-box" hidden><canvas id="historyChartCanvas" width="920" height="185" role="img" aria-label="Generation fitness and lap time progression chart"></canvas></div>
|
| 76 |
-
|
| 77 |
<section id="analysisPanel" hidden aria-labelledby="analysisTitle">
|
| 78 |
<h2 id="analysisTitle">Winners and frozen evaluation</h2>
|
| 79 |
<p id="analysisStatus" role="status" aria-live="polite">No complete analysis is available yet.</p>
|
|
@@ -84,12 +87,12 @@
|
|
| 84 |
<div class="toolbar"><button id="compare_winners" type="button" disabled>Compare selected winners</button></div><p class="hint">Comparison starts one frozen evaluation per distinct selected policy, in sequence. It uses the currently selected track, vehicle, tick limit, control rate and collision/idle settings, then holds those conditions fixed for the full comparison.</p>
|
| 85 |
<details><summary>Inspect complete measurements for an evaluated car</summary><div class="row"><label for="descriptor_generation">Generation<input id="descriptor_generation" type="number" min="1" value="1"></label><label for="descriptor_car">Car index<input id="descriptor_car" type="number" min="0" value="0"></label></div><button id="descriptor_show" type="button" disabled>Show all descriptors</button><p id="descriptor_status" role="status"></p><div id="descriptor_tables"></div></details>
|
| 86 |
</div>
|
| 87 |
-
|
| 88 |
<div id="evaluationTools" hidden><p id="evaluationStatus" role="status" aria-live="polite"></p><p class="hint">Frozen evaluation does not select winners or compare population-relative fitness. Each comparison row keeps its network hash and must match the same track, vehicle, horizon, control rate, collision/idle rules, backend and engine build.</p><button id="compare_add" type="button">Add this frozen result to comparison</button><div class="table-scroll"><table><thead><tr><th>Policy</th><th>Vehicle</th><th>Track</th><th>Ticks</th><th>Best lap</th><th>Forward laps</th><th>Physical contacts</th><th>Distance (simulation units)</th><th>Comparison</th></tr></thead><tbody id="evaluationRows"></tbody></table></div><button id="compare_clear" type="button">Clear comparison</button></div>
|
| 89 |
</section>
|
| 90 |
<section id="starCheckPanel" class="card" hidden aria-labelledby="starCheckTitle"><h2 id="starCheckTitle">Star challenge check</h2><p id="starCheckStatus" role="status" aria-live="polite"></p><div id="starCheckDetails" hidden><p id="starCheckPolicy" class="hint"></p><p id="starCheckBasis" class="hint"></p><p id="starCheckConditions" class="hint"></p><div class="table-scroll"><table><thead><tr><th>Challenge</th><th>Result</th><th>Reason</th><th>Completion time</th></tr></thead><tbody id="starCheckRows"></tbody></table></div><p class="hint">This is an independent check of the named run policy. It does not grant or accumulate original-game stars; replica challenges may differ from the original game.</p></div></section>
|
| 91 |
<label class="keep-result"><input id="leaderboard_opt_in" type="checkbox"> After a completed lap, check its position against the public Steam leaderboard</label><p class="privacy-note">Optional, read-only check for supported built-in tracks. It sends the track ID and best lap to this app server; Steam receives only a public leaderboard read. No score is submitted. The result is an estimate, not an official rank.</p><p id="leaderboard_status" role="status" aria-live="polite"></p>
|
| 92 |
-
|
| 93 |
<details id="portableBundle"><summary>Portable bucket bundle</summary><p>Build, export, or restore one validated bundle. Bucket storage is optional. When connected, automatic save is on by default; turn it off here to keep saves manual. Disconnected runs never call bucket storage.</p><label><input id="bundle_track" type="checkbox"> Include the selected track</label><label><input id="bundle_recipe" type="checkbox"> Include the recipe in the selected slot, if saved</label><label><input id="bundle_receipt" type="checkbox"> Include a sanitized receipt for the selected completed result</label><div class="toolbar"><button id="bundle_export" type="button">Export portable bundle</button><label for="bundle_import">Restore portable bundle (.json)<input id="bundle_import" type="file" accept=".json,application/json"></label><button id="bundle_receipt_download" type="button" disabled>Download staged receipt</button></div><p id="bundle_status" role="status" aria-live="polite">No bucket action has been requested.</p>
|
| 94 |
<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>
|
| 95 |
<p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
|
|
@@ -805,10 +808,20 @@
|
|
| 805 |
// Navigation and help preference only; these controls never start a run.
|
| 806 |
(() => {
|
| 807 |
const tabs=[...document.querySelectorAll('.tabs [data-tab]')];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
function activate(button,{focusPanel=true}={}){
|
|
|
|
| 809 |
document.querySelectorAll('.pane').forEach(pane=>pane.hidden=pane.id!==button.dataset.tab);
|
| 810 |
tabs.forEach(tab=>{const active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;});
|
| 811 |
-
const heading=document.getElementById(button.dataset.tab)?.
|
| 812 |
if(heading){heading.tabIndex=-1;heading.scrollIntoView({block:'start',behavior:'instant'});if(focusPanel)heading.focus({preventScroll:true});}
|
| 813 |
window.dispatchEvent(new CustomEvent('ald:section-change'));
|
| 814 |
}
|
|
@@ -831,7 +844,7 @@
|
|
| 831 |
const target=requested&&!requested.matches(':disabled')&&!requested.closest('[hidden]')?requested:tab;
|
| 832 |
target.scrollIntoView({block:'center'});target.focus();
|
| 833 |
}));
|
| 834 |
-
const guide=document.getElementById('firstRunGuide'),toggle=document.getElementById('quick_start_toggle'),key='tiga.ald.quick-start.
|
| 835 |
try{guide.open=window.localStorage.getItem(key)==='open';}catch{}
|
| 836 |
const syncHelp=()=>toggle.setAttribute('aria-expanded',String(guide.open));syncHelp();
|
| 837 |
guide.addEventListener('toggle',()=>{syncHelp();try{window.localStorage.setItem(key,guide.open?'open':'closed');}catch{}});
|
|
|
|
| 8 |
.replay-bar{background:#101d2c;border:1px solid #2a3d52;border-radius:11px;padding:10px 12px;margin:10px 0}.replay-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.replay-row button{min-height:36px;padding:6px 11px;font-size:13px;font-weight:650}.replay-row select{width:auto;min-height:36px;padding:5px 9px;font-size:13px}.replay-row input[type=range]{flex:1;min-width:120px;accent-color:#62dfb1;padding:0}.replay-time{font:600 12px ui-monospace,SFMono-Regular,monospace;color:#91efc6;white-space:nowrap}.replay-layers{display:flex;gap:6px 12px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px solid #223447;font-size:12.5px;color:#b9cddd}.replay-layers label{display:inline-flex;align-items:center;gap:5px;margin:0;cursor:pointer}.replay-layers input{margin:0}.kpi-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:12px 0}.kpi-tile{background:#101d2c;border:1px solid #2a3d52;border-radius:10px;padding:10px 12px}.kpi-tile .kpi-label{font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;color:#a2b4c8}.kpi-tile .kpi-val{font:800 19px ui-monospace,SFMono-Regular,monospace;color:#62dfb1;margin-top:3px}.kpi-tile .kpi-sub{font-size:11.5px;color:#8fa6bc;margin-top:2px}.chart-box{background:#09111b;border:1px solid #2a3d52;border-radius:10px;padding:10px;margin:10px 0}.chart-box canvas{max-height:190px;display:block}.subtab-bar{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.subtab-bar button{min-height:36px;padding:6px 12px;font-size:13px;border-radius:18px}.subtab-bar button[aria-pressed=true]{background:#62dfb1;color:#102522;font-weight:700}.editor-visual-grid{display:grid;grid-template-columns:minmax(260px,.85fr) minmax(0,1.35fr);gap:16px;align-items:start;margin:14px 0}@media(max-width:850px){.kpi-strip{grid-template-columns:1fr 1fr}.editor-visual-grid{grid-template-columns:minmax(0,1fr)}}
|
| 9 |
|
| 10 |
.replay-workbench{display:grid;gap:14px;align-items:start}#replayControls,#replay_transport,#replay_blackbox{scroll-margin-top:160px}#replayControls{min-width:0}.replay-row>*{max-width:100%}#replay_blackbox{padding:12px;border:1px solid #2a3d52;border-radius:10px;min-width:0}#replay_blackbox h3{margin-top:0}#replay_blackbox td,#replay_blackbox th{padding:6px;font-size:12px}#drive.replay-focused>.grid{grid-template-columns:minmax(0,1fr)}#drive.replay-focused>.grid>section:first-child{display:none}#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1.35fr) minmax(320px,1fr)}#drive.replay-focused .map{position:sticky;top:70px}#drive.replay-focused .map canvas{max-height:500px}@media(max-width:850px){#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1fr)}#drive.replay-focused .map{position:static}}
|
| 11 |
+
|
| 12 |
+
/* Compact release workspace. All scroll targets use the measured wrapped tab height. */
|
| 13 |
+
:root{--lab-nav-height:76px;--lab-scroll-offset:calc(var(--lab-nav-height) + 14px)}
|
| 14 |
+
.pane h2,#editor_status,#agent-interface,#replayControls,#replay_transport,#replay_blackbox,#replay_workspace,button,a,summary,input,select,textarea,[tabindex="-1"]{scroll-margin-top:var(--lab-scroll-offset)}
|
| 15 |
+
.lab-utilities{display:flex;gap:8px 22px;flex-wrap:wrap;align-items:start;margin:2px 0 6px}.lab-utilities>details{border:0;margin:0;padding:0;font-size:13px}.lab-utilities>details>summary{display:flex;align-items:center;min-height:34px;font-size:13px}.lab-utilities>details[open]{flex-basis:100%;border:1px solid #2a3d52;border-radius:10px;padding:0 14px 12px}.lab-utilities #agent-interface{margin:0}.quick-start{margin:4px 0 0;padding:0 12px}.quick-start>summary{padding:8px 0;min-height:40px;font-size:14px}.quick-steps{gap:12px;padding:6px 0}.quick-steps strong{font-size:14px;margin-bottom:4px}.quick-steps p{font-size:13px;line-height:1.4;margin-bottom:6px}.quick-steps button{font-size:13px;padding:6px 9px;min-height:36px}.quick-footer{padding:8px 0;font-size:13px}.quick-footer button{font-size:13px;min-height:36px;padding:6px 10px}.quick-start>summary .hint{font-size:12px}.lab-header{gap:8px}.lab-header .intro{margin-bottom:8px}.batch-setup{top:var(--lab-scroll-offset)}
|
| 16 |
+
.result-summary{padding:16px 20px}.result-summary h3{font-size:16px;margin:8px 0}.result-summary .pill{margin:0;font-size:12px}.result-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.result-actions button{font-size:14px;min-height:40px;padding:8px 12px}.result-summary details{padding-top:8px;margin-top:10px}.result-summary details>summary{font-size:13px;min-height:32px}.result-summary pre{font-size:13px}.result-summary .settings-summary{margin:5px 0;font-size:13px}.result-summary #result_server{color:#91efc6}.replay-viewer{min-width:0;scroll-margin-top:var(--lab-scroll-offset)}.replay-playback{margin-top:0}.replay-playback #replay_readout{font-size:13px!important;line-height:1.45;margin:8px 0}.replay-playback #replay_status{font-size:12px;color:#a2b4c8;line-height:1.4;margin:6px 0}.replay-options{margin-top:8px;padding-top:5px}.replay-options>summary{font-size:12px;min-height:30px}.replay-options .replay-row{margin-top:8px}.replay-options label{font-size:12px}.recording-identity{font-size:13px;line-height:1.5;padding:8px 10px;background:#14362f;border:1px solid #3d7561;border-radius:7px;overflow-wrap:anywhere}.replay-row input[type=file]{font-size:12px;padding:5px;max-width:260px}.replay-row label[for=replay_import]{font-size:13px;margin:0}.replay-row label[for=replay_import] input{display:block}.replay-row button{min-height:40px}.replay-row input[type=range]{flex-basis:160px}.replay-time{white-space:normal}.replay-bar>details{margin-top:8px;padding-top:5px}.replay-bar>details>summary{font-size:12px;min-height:28px}.replay-bar>p.hint{font-size:12px;line-height:1.45;margin:8px 0}.replay-workbench{gap:16px}#drive.replay-focused .replay-viewer{position:sticky;top:var(--lab-scroll-offset)}#drive.replay-focused .map{position:static;min-height:0}#drive.replay-focused .map canvas{height:min(38vh,300px);max-height:300px}#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1.2fr) minmax(320px,1fr)}
|
| 17 |
+
@media(max-width:850px){#drive.replay-focused .replay-workbench{grid-template-columns:minmax(0,1fr)}#drive.replay-focused .replay-viewer{position:static}.result-summary{padding:14px}.result-actions>*{flex:1 1 160px}#drive.replay-focused .map canvas{height:auto;max-height:300px}}@media(max-width:600px){.quick-start>summary .hint{margin-left:0}.quick-steps{gap:10px}.quick-steps li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:0 8px;align-items:center}.quick-steps strong{grid-column:1}.quick-steps p{grid-column:1;margin:0}.quick-steps button{grid-column:2;grid-row:1 / 3;max-width:135px}.header-actions{width:auto}.header-actions>*{flex:initial}.help-link,#quick_start_toggle{font-size:13px;padding:7px 10px;min-height:38px}.lab-header h1{font-size:26px}.lab-header .intro{font-size:14px}.lab-utilities{gap:8px 18px}.replay-row label[for=replay_import]{width:100%}.replay-row label[for=replay_import] input{max-width:100%;width:100%}}
|
| 18 |
</style>
|
| 19 |
<dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
|
| 20 |
<h2 id="appConfirmTitle">Confirm action</h2>
|
|
|
|
| 43 |
})();
|
| 44 |
</script>
|
| 45 |
<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#start-here" target="_blank" rel="noopener">Help & tutorial ↗</a></div></header>
|
| 46 |
+
<div class="lab-utilities"><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>The map follows the selected track’s native centerline. Record a short CPU drive to watch an unchanged policy using real vehicle positions. A new recording can differ from an earlier training run. Native control commands and consumed sensor inputs appear in the black box. Ray-hit positions and neural activations are not recorded.</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>
|
| 47 |
+
<details id="agent-interface" class="agent-utility"><summary>Agent tools</summary><p>Use the same local controls as the lab: read the current state, open a section, create a starter, or inspect the active driver. These tools do not submit jobs or save data. <a href="/agents" target="_blank" rel="noopener">Agent interface guide</a> · <a href="/agent-manifest.json" target="_blank" rel="noopener">Action manifest (JSON)</a></p><p id="agent_tools_status" role="status" aria-live="polite">Checking browser tool support…</p><p>The state below is also available to ordinary browser automation. Each listed action identifies a stable control in this page.</p><pre id="agent_state" aria-label="Current lab state (JSON)"></pre></details></div><details id="firstRunGuide" class="quick-start"><summary id="firstRunTitle">New here? Start a first run <span class="hint">Track → driver → settings → run</span></summary><ol class="quick-steps"><li><strong>Choose a track</strong><p>Built-in replica or your own track.</p><button id="first_run_track" type="button">Choose a track</button></li><li><strong>Create a driver</strong><p>Create a starter or upload a driver.</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>Short first-run defaults are ready. Adjust them or save a recipe.</p><button id="first_run_settings" type="button">Review first-run settings</button></li><li><strong>Run and inspect</strong><p>Train, download and replay. 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#start-here" 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>
|
| 48 |
+
<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"><section id="resultSummary" class="card result-summary" hidden aria-labelledby="result_summary_title"><div class="section-heading"><h2 id="result_summary_title" tabindex="-1">Latest run</h2><span id="result_backend" class="pill"></span></div><h3 id="runState" role="status" aria-live="polite">READY — no run started</h3><p id="result_outcome" class="settings-summary"></p><p id="result_driver" class="settings-summary"></p><p id="result_server" class="settings-summary" role="status"></p><div class="result-actions"><button id="download" class="primary" disabled hidden>Download driver</button><button id="saveLocal" class="primary" disabled>Download received driver</button><button id="result_replay" type="button" disabled>Replay selected driver</button><button id="player_report_download" disabled>Download player report (.txt)</button></div><details class="result-exports"><summary>More downloads & server controls</summary><div class="result-actions"><button id="analysis_download" type="button" disabled>Download full analysis archive</button><button id="run_export" disabled>Export run receipt</button><button id="history_csv_download" disabled>Download generation history (.csv)</button><button id="erase" disabled>Delete server-side job data now</button><button id="forgetLocal" disabled>Discard browser result</button></div><p class="privacy-note">The received driver and analysis stay in this page until discarded, replaced, or closed. Save downloads to keep them. Reports describe server-reported measurements; reward weights are configured values, not measured contributions.</p></details><details><summary>Run details</summary><pre id="runSummary" style="white-space:pre-wrap;line-height:1.7" aria-live="polite"></pre></details></section><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 id="setup_next_step" class="hint">Choose or create a driver, then review Training & recipes and start a run.</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>
|
| 49 |
<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>
|
| 50 |
<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>
|
| 51 |
<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>
|
|
|
|
| 60 |
</details></section><section class="card"><h2>Visual replay & car black box</h2><p id="mapStatus" class="map-status" role="status">Loading selected track…</p><button type="button" id="replay_focus" aria-pressed="false">Expand replay workspace</button><div class="replay-bar" id="replayControls" tabindex="-1" aria-label="Recorded drive controls">
|
| 61 |
<div class="replay-row"><button type="button" id="replay_record" disabled>Record selected policy</button><label for="replay_duration" style="margin:0">Length</label><select id="replay_duration"><option value="300" selected>5 seconds</option><option value="900">15 seconds</option></select><button type="button" id="replay_download" disabled>Download recording</button><label for="replay_import">Open recording<input type="file" id="replay_import" accept=".json,application/json" disabled></label></div>
|
| 62 |
<p id="replay_policy" class="hint">Choose or create a network to record a drive.</p>
|
| 63 |
+
<p class="hint">Fresh CPU drive, including for GPU-trained drivers · unchanged weights · 60 Hz decisions. Playback uses measured samples in your browser.</p>
|
| 64 |
+
<p id="replay_loaded" class="recording-identity" role="status" aria-live="polite" hidden></p>
|
| 65 |
+
<details><summary>What does this recording show?</summary><p>A single fresh CPU drive records native state, raw and normalized control commands, and the exact cached sensor inputs every 0.1 seconds. Physics and policy decisions run at 60 Hz. Playback starts paused and shows measured poses by default; optional pose smoothing only interpolates between adjacent measured samples. It does not simulate the car. Black-box values always remain at an actual recorded sample. Ray-hit positions, skid marks and hidden-layer activations are not recorded. This is a selected-policy CPU replay, including for GPU-trained policies; it is not a recording of the original training population.</p><p>The recording remains in this page until replaced, cleared by a changed selection, or the page is closed. Use Download recording to keep its measured samples.</p></details>
|
| 66 |
+
</div><div class="replay-workbench" id="replay_workspace"><div class="replay-viewer"><div class="map"><canvas id="trackMap" width="1000" height="500" role="img" aria-label="Selected track tile layout and terrain"></canvas></div><div class="replay-bar replay-playback"><div class="replay-row" id="replay_transport" role="group" aria-label="Playback controls" tabindex="-1"><button type="button" id="replay_play" disabled>Play</button><button type="button" id="replay_reset" disabled>Restart</button><button type="button" id="replay_prev" disabled>Previous sample</button><button type="button" id="replay_next" disabled>Next sample</button><input type="range" id="replay_scrub" min="1" max="300" value="1" disabled aria-label="Replay tick scrubber"><span id="replay_time_label" class="replay-time">No recording</span></div>
|
| 67 |
+
<details class="replay-options"><summary>Camera, speed & recorded events</summary><div class="replay-row" style="margin-top:8px"><label for="replay_camera" style="margin:0">Camera</label><select id="replay_camera" aria-label="Replay camera mode" disabled><option value="full" selected>Full circuit</option><option value="follow">Follow car</option></select><label for="replay_speed" style="margin:0">Speed</label><select id="replay_speed" aria-label="Replay playback speed" disabled><option value="0.25">0.25×</option><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="2">2×</option></select></div>
|
| 68 |
<div class="replay-layers"><label><input type="checkbox" id="replay_layer_outline" checked disabled> Car outline</label><label><input type="checkbox" id="replay_layer_line" checked disabled> Recorded path</label><label><input type="checkbox" id="replay_layer_hud" checked disabled> Recorded telemetry</label></div>
|
| 69 |
<div class="replay-row"><label><input type="checkbox" id="replay_interpolate" disabled> Smooth pose between measured samples</label><label for="replay_event">Contact / active-state interval</label><select id="replay_event" aria-label="Recorded event interval" disabled><option value="">No recording</option></select></div>
|
| 70 |
|
| 71 |
+
</details><p id="replay_readout" style="font-size:16px;font-variant-numeric:tabular-nums" aria-live="off"></p>
|
| 72 |
+
<p id="replay_status" role="status">No replay recorded. The map shows the selected track and its saved starting point.</p></div></div><section id="replay_blackbox" tabindex="-1" hidden aria-label="Recorded car black box"><div class="section-heading"><h3>Car black box</h3><a href="#replay_transport">Back to playback</a></div><p id="blackbox_phase" class="hint"></p>
|
|
|
|
| 73 |
<p class="hint">All values below belong to the selected native sample. Sensor inputs were consumed before physics at the decision tick; vehicle state is captured after physics. Samples are 0.1 seconds apart. Contact events identify intervals, not exact impact ticks; the counter does not count unique crashes.</p>
|
| 74 |
<details><summary>Recorded vehicle state</summary><div class="table-scroll"><table><thead><tr><th>Vehicle state</th><th>Recorded value</th></tr></thead><tbody id="blackbox_state"></tbody></table></div></details>
|
| 75 |
<h4>Native control commands</h4><div class="table-scroll"><table><thead><tr><th>Control</th><th>Raw command</th><th>Applied after normalization</th><th>Source</th></tr></thead><tbody id="blackbox_controls"></tbody></table></div>
|
| 76 |
<details><summary>Inputs consumed by the native network</summary><div class="table-scroll"><table><thead><tr><th>Input index</th><th>Sensor</th><th>Parameters</th><th>Native input value</th></tr></thead><tbody id="blackbox_inputs"></tbody></table></div><p class="hint">These are the input values the policy consumed. Ray inputs are not ray-hit positions. Hidden-layer activations are not recorded.</p></details>
|
| 77 |
+
<details><summary>Recorded cumulative reward metrics</summary><p class="hint">Metrics accumulate at native statistics callbacks; they are not instantaneous forces or per-reward contributions.</p><div class="table-scroll"><table><thead><tr><th>Metric</th><th>Recorded cumulative value</th></tr></thead><tbody id="blackbox_metrics"></tbody></table></div></details><p><a href="#replay_transport">Back to playback</a></p></section></div><div class="map-legend"><span><i class="terrain asphalt"></i> Asphalt</span><span><i class="terrain dirt"></i> Dirt</span><span><i class="terrain ice"></i> Ice</span><span><i class="terrain start"></i> Start</span></div><p id="mapLegend" class="hint"></p><div id="runKpiStrip" class="kpi-strip" hidden><div class="kpi-tile"><div class="kpi-label">Best Fitness</div><div id="kpi_best_score" class="kpi-val">—</div><div id="kpi_best_sub" class="kpi-sub">Final generation</div></div><div class="kpi-tile"><div class="kpi-label">Mean Fitness</div><div id="kpi_mean_score" class="kpi-val" style="color:#58a6ff">—</div><div id="kpi_mean_sub" class="kpi-sub">Population mean</div></div><div class="kpi-tile"><div class="kpi-label">Best Lap Time</div><div id="kpi_best_lap" class="kpi-val" style="color:#ffd073">—</div><div id="kpi_lap_sub" class="kpi-sub">Fastest observed</div></div><div class="kpi-tile"><div class="kpi-label">Lapped Cars</div><div id="kpi_lapped_cars" class="kpi-val" style="color:#c792ea">—</div><div id="kpi_lapped_sub" class="kpi-sub">Completed lap</div></div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
<div id="historyChartWrap" class="chart-box" hidden><canvas id="historyChartCanvas" width="920" height="185" role="img" aria-label="Generation fitness and lap time progression chart"></canvas></div>
|
| 79 |
+
|
| 80 |
<section id="analysisPanel" hidden aria-labelledby="analysisTitle">
|
| 81 |
<h2 id="analysisTitle">Winners and frozen evaluation</h2>
|
| 82 |
<p id="analysisStatus" role="status" aria-live="polite">No complete analysis is available yet.</p>
|
|
|
|
| 87 |
<div class="toolbar"><button id="compare_winners" type="button" disabled>Compare selected winners</button></div><p class="hint">Comparison starts one frozen evaluation per distinct selected policy, in sequence. It uses the currently selected track, vehicle, tick limit, control rate and collision/idle settings, then holds those conditions fixed for the full comparison.</p>
|
| 88 |
<details><summary>Inspect complete measurements for an evaluated car</summary><div class="row"><label for="descriptor_generation">Generation<input id="descriptor_generation" type="number" min="1" value="1"></label><label for="descriptor_car">Car index<input id="descriptor_car" type="number" min="0" value="0"></label></div><button id="descriptor_show" type="button" disabled>Show all descriptors</button><p id="descriptor_status" role="status"></p><div id="descriptor_tables"></div></details>
|
| 89 |
</div>
|
| 90 |
+
|
| 91 |
<div id="evaluationTools" hidden><p id="evaluationStatus" role="status" aria-live="polite"></p><p class="hint">Frozen evaluation does not select winners or compare population-relative fitness. Each comparison row keeps its network hash and must match the same track, vehicle, horizon, control rate, collision/idle rules, backend and engine build.</p><button id="compare_add" type="button">Add this frozen result to comparison</button><div class="table-scroll"><table><thead><tr><th>Policy</th><th>Vehicle</th><th>Track</th><th>Ticks</th><th>Best lap</th><th>Forward laps</th><th>Physical contacts</th><th>Distance (simulation units)</th><th>Comparison</th></tr></thead><tbody id="evaluationRows"></tbody></table></div><button id="compare_clear" type="button">Clear comparison</button></div>
|
| 92 |
</section>
|
| 93 |
<section id="starCheckPanel" class="card" hidden aria-labelledby="starCheckTitle"><h2 id="starCheckTitle">Star challenge check</h2><p id="starCheckStatus" role="status" aria-live="polite"></p><div id="starCheckDetails" hidden><p id="starCheckPolicy" class="hint"></p><p id="starCheckBasis" class="hint"></p><p id="starCheckConditions" class="hint"></p><div class="table-scroll"><table><thead><tr><th>Challenge</th><th>Result</th><th>Reason</th><th>Completion time</th></tr></thead><tbody id="starCheckRows"></tbody></table></div><p class="hint">This is an independent check of the named run policy. It does not grant or accumulate original-game stars; replica challenges may differ from the original game.</p></div></section>
|
| 94 |
<label class="keep-result"><input id="leaderboard_opt_in" type="checkbox"> After a completed lap, check its position against the public Steam leaderboard</label><p class="privacy-note">Optional, read-only check for supported built-in tracks. It sends the track ID and best lap to this app server; Steam receives only a public leaderboard read. No score is submitted. The result is an estimate, not an official rank.</p><p id="leaderboard_status" role="status" aria-live="polite"></p>
|
| 95 |
+
|
| 96 |
<details id="portableBundle"><summary>Portable bucket bundle</summary><p>Build, export, or restore one validated bundle. Bucket storage is optional. When connected, automatic save is on by default; turn it off here to keep saves manual. Disconnected runs never call bucket storage.</p><label><input id="bundle_track" type="checkbox"> Include the selected track</label><label><input id="bundle_recipe" type="checkbox"> Include the recipe in the selected slot, if saved</label><label><input id="bundle_receipt" type="checkbox"> Include a sanitized receipt for the selected completed result</label><div class="toolbar"><button id="bundle_export" type="button">Export portable bundle</button><label for="bundle_import">Restore portable bundle (.json)<input id="bundle_import" type="file" accept=".json,application/json"></label><button id="bundle_receipt_download" type="button" disabled>Download staged receipt</button></div><p id="bundle_status" role="status" aria-live="polite">No bucket action has been requested.</p>
|
| 97 |
<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>
|
| 98 |
<p id="deadline"></p><p id="status" role="status">Ready. Inputs remain on this device until you run the test.</p>
|
|
|
|
| 808 |
// Navigation and help preference only; these controls never start a run.
|
| 809 |
(() => {
|
| 810 |
const tabs=[...document.querySelectorAll('.tabs [data-tab]')];
|
| 811 |
+
const navigation=document.querySelector('.tabs');
|
| 812 |
+
function syncNavigationOffset(){
|
| 813 |
+
const height=Math.ceil(navigation.getBoundingClientRect().height);
|
| 814 |
+
document.documentElement.style.setProperty('--lab-nav-height',height+'px');
|
| 815 |
+
}
|
| 816 |
+
syncNavigationOffset();
|
| 817 |
+
if(typeof ResizeObserver==='function')new ResizeObserver(syncNavigationOffset).observe(navigation);
|
| 818 |
+
window.addEventListener('resize',syncNavigationOffset);
|
| 819 |
+
document.fonts?.ready?.then(syncNavigationOffset);
|
| 820 |
function activate(button,{focusPanel=true}={}){
|
| 821 |
+
syncNavigationOffset();
|
| 822 |
document.querySelectorAll('.pane').forEach(pane=>pane.hidden=pane.id!==button.dataset.tab);
|
| 823 |
tabs.forEach(tab=>{const active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;});
|
| 824 |
+
const heading=[...(document.getElementById(button.dataset.tab)?.querySelectorAll('h2')??[])].find(node=>node.getClientRects().length);
|
| 825 |
if(heading){heading.tabIndex=-1;heading.scrollIntoView({block:'start',behavior:'instant'});if(focusPanel)heading.focus({preventScroll:true});}
|
| 826 |
window.dispatchEvent(new CustomEvent('ald:section-change'));
|
| 827 |
}
|
|
|
|
| 844 |
const target=requested&&!requested.matches(':disabled')&&!requested.closest('[hidden]')?requested:tab;
|
| 845 |
target.scrollIntoView({block:'center'});target.focus();
|
| 846 |
}));
|
| 847 |
+
const guide=document.getElementById('firstRunGuide'),toggle=document.getElementById('quick_start_toggle'),key='tiga.ald.quick-start.v2';
|
| 848 |
try{guide.open=window.localStorage.getItem(key)==='open';}catch{}
|
| 849 |
const syncHelp=()=>toggle.setAttribute('aria-expanded',String(guide.open));syncHelp();
|
| 850 |
guide.addEventListener('toggle',()=>{syncHelp();try{window.localStorage.setItem(key,guide.open?'open':'closed');}catch{}});
|
track-map.js
CHANGED
|
@@ -423,7 +423,7 @@
|
|
| 423 |
if(state){
|
| 424 |
const frame=state.frame;blackbox(frame);const speed=Math.hypot(frame.vx,frame.vy)/15*3.6;
|
| 425 |
const contacts=frame.collision_count===undefined?'unavailable':String(frame.collision_count);
|
| 426 |
-
setText('replay_time_label',`${(cursor/60).toFixed(2)}s / ${(recording.requested_ticks/60).toFixed(2)}s · Tick ${Math.round(cursor)} ·
|
| 427 |
setText('replay_readout',`Recorded sample tick ${frame.tick} (${(frame.tick/60).toFixed(2)}s). Speed ${speed.toFixed(1)} km/h, derived from recorded velocity. Position (${frame.x.toFixed(2)}, ${frame.y.toFixed(2)}). Contacts ${contacts}. ${frame.active?'Active':'Inactive'} at this sample. ${frame.decision?'Native controls and inputs captured at tick '+frame.decision.tick+'.':'No captured decision at this sample.'}`);
|
| 428 |
if(layers.hud){
|
| 429 |
ctx.save();ctx.fillStyle='rgba(9,17,27,0.88)';ctx.beginPath();ctx.roundRect(12,12,310,80,8);ctx.fill();
|
|
|
|
| 423 |
if(state){
|
| 424 |
const frame=state.frame;blackbox(frame);const speed=Math.hypot(frame.vx,frame.vy)/15*3.6;
|
| 425 |
const contacts=frame.collision_count===undefined?'unavailable':String(frame.collision_count);
|
| 426 |
+
setText('replay_time_label',`${(cursor/60).toFixed(2)}s / ${(recording.requested_ticks/60).toFixed(2)}s · Tick ${Math.round(cursor)} · Measured tick ${frame.tick}`);
|
| 427 |
setText('replay_readout',`Recorded sample tick ${frame.tick} (${(frame.tick/60).toFixed(2)}s). Speed ${speed.toFixed(1)} km/h, derived from recorded velocity. Position (${frame.x.toFixed(2)}, ${frame.y.toFixed(2)}). Contacts ${contacts}. ${frame.active?'Active':'Inactive'} at this sample. ${frame.decision?'Native controls and inputs captured at tick '+frame.decision.tick+'.':'No captured decision at this sample.'}`);
|
| 428 |
if(layers.hud){
|
| 429 |
ctx.save();ctx.fillStyle='rgba(9,17,27,0.88)';ctx.beginPath();ctx.roundRect(12,12,310,80,8);ctx.fill();
|