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