Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 94 additions & 5 deletions AI-Car-Racer/buttonResponse.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,109 @@
function pauseGame(){
pause=!pause;
// App not booted yet (classic scripts still loading). Queue the intent;
// main.js drains __pendingStart after begin/animate are defined.
if (typeof pause === 'undefined' || typeof phase === 'undefined'){
window.__pendingStart = true;
markStartOverlayStarting();
return;
}

// First Start click: leave the page-load gate and build+run the swarm.
// (Population build is deferred until this moment so the Start CTA is
// clickable as soon as the overlay paints — not after a multi-second
// worker/brain setup.)
if (window.__awaitingStart){
window.__awaitingStart = false;
window.__firstStart = false;
window.__pendingStart = false;
pause = false;
const btn = document.getElementById("pause");
if (btn){
btn.textContent = "Pause";
btn.classList.remove('start-cta');
}
markStartOverlayStarting();
try { if (typeof syncStartOverlay === 'function') syncStartOverlay(); } catch (_) {}
if (typeof begin === 'function'){
begin();
} else {
// main.js still evaluating — re-queue so the boot footer can start.
window.__pendingStart = true;
}
return;
}

// Normal Pause / Play toggle after training has started.
pause = !pause;
const btn = document.getElementById("pause");
if (btn){
btn.textContent = pause?"Play":"Pause";
// First-press of the Start CTA graduates the button to the normal
// Pause/Play cycle.
btn.textContent = pause ? "Play" : "Pause";
btn.classList.remove('start-cta');
}
window.__firstStart = false;
// Halt / resume the worker's AI step loop too. Without this, sim-worker
// would keep burning CPU while the user has paused — and on resume the
// accumulator would stampede a huge backlog of physics steps at once.
if (typeof simWorker !== 'undefined' && simWorker){
simWorker.postMessage({ type: 'setPause', pause });
}
try { if (typeof syncStartOverlay === 'function') syncStartOverlay(); } catch (_) {}
}

function markStartOverlayStarting(){
const ob = document.getElementById('startOverlayBtn');
if (!ob) return;
ob.classList.add('is-starting');
ob.disabled = true;
const label = ob.querySelector('.start-overlay-label');
if (label) label.textContent = 'Starting\u2026';
}

// Canvas-centered Start overlay — present in index.html for first paint,
// kept in sync here once the app boots. Mirrors the panel's ▶ Start CTA.
function syncStartOverlay(){
let el = document.getElementById('startOverlay');
// Show while awaiting Start on the training screen. Before main.js sets
// `phase`, treat as showable so the static HTML overlay stays visible
// during script load (that was the multi-second "can't click yet" gap).
const show = !!window.__awaitingStart &&
(typeof phase === 'undefined' || phase === 4);
if (!show){
if (el) el.hidden = true;
return;
}
if (!el){
el = document.createElement('div');
el.id = 'startOverlay';
el.setAttribute('role', 'dialog');
el.setAttribute('aria-modal', 'true');
el.setAttribute('aria-label', 'Start training');
el.innerHTML =
'<button type="button" class="start-overlay-btn" id="startOverlayBtn">' +
'<span class="start-overlay-icon" aria-hidden="true">▶</span>' +
'<span class="start-overlay-label">Start Training</span>' +
'<span class="start-overlay-hint">or press the Start button in the panel</span>' +
'</button>';
const host = document.getElementById('canvasDiv') || document.body;
host.appendChild(el);
}
const btn = el.querySelector('#startOverlayBtn');
if (btn && btn.dataset.earlyBound !== '1' && btn.dataset.bound !== '1'){
btn.dataset.bound = '1';
btn.addEventListener('click', () => {
if (typeof pauseGame === 'function') pauseGame();
else window.__pendingStart = true;
});
}
// Reset busy state when re-showing (e.g. Customize Track → back to train).
if (btn && window.__awaitingStart){
btn.disabled = false;
btn.classList.remove('is-starting');
const label = btn.querySelector('.start-overlay-label');
if (label) label.textContent = 'Start Training';
}
el.hidden = false;
}
window.syncStartOverlay = syncStartOverlay;
window.markStartOverlayStarting = markStartOverlayStarting;

// Route a phase-4 user back into the track editor. Mirrors backPhase()'s
// cleanup (pause the worker, close any SONA trajectory) but sets phase=0
Expand Down
51 changes: 40 additions & 11 deletions AI-Car-Racer/demoPresentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
// • Optional 3D perspective view of the same 2D world
// • Demo mode: auto-start, follow, story beats
//
// URL flags: ?demo=1 ?follow=1 ?view=3d ?trails=0 ?heatmap=0
// Keyboard: F follow · 3 3D · T trails · H heatmap · D demo · 0 reset cam
// URL flags: ?demo=1 ?follow=1 ?view=3d ?trails=0 ?heatmap=0 ?rays=0
// Keyboard: F follow · 3 3D · T trails · H heatmap · R rays · M demo · 0 reset cam
// Note: W/A/S/D are reserved for driving and never bind presentation toggles.

(function (global) {
'use strict';
Expand All @@ -33,6 +34,7 @@
view3d: params.get('view') === '3d',
trails: flag('trails', true),
heatmap: flag('heatmap', true),
rays: flag('rays', true),
rankColors: true,
// camera
camScale: 1,
Expand Down Expand Up @@ -96,7 +98,7 @@
syncControlButtons();
setStory(state.demoMode
? 'Demo mode — watch a swarm learn to drive.'
: 'Train a neural net to race. Press D for demo mode.', 6000);
: 'Train a neural net to race. Press M for demo mode (W/A/S/D drive).', 6000);
}

function ensureHud() {
Expand Down Expand Up @@ -129,7 +131,8 @@
'<button type="button" data-act="view3d" title="3D perspective (3)">3D</button>' +
'<button type="button" data-act="trails" title="Motion trails (T)">Trails</button>' +
'<button type="button" data-act="heatmap" title="Crash heatmap (H)">Heat</button>' +
'<button type="button" data-act="demo" title="Cinematic demo (D)" class="cinema-demo">Demo</button>';
'<button type="button" data-act="rays" title="Champion sensor rays (R)">Rays</button>' +
'<button type="button" data-act="demo" title="Cinematic demo (M) — W/A/S/D reserved for driving" class="cinema-demo">Demo</button>';
el.addEventListener('click', (ev) => {
const btn = ev.target.closest('[data-act]');
if (!btn) return;
Expand All @@ -138,6 +141,7 @@
else if (act === 'view3d') setView3d(!state.view3d);
else if (act === 'trails') { state.trails = !state.trails; if (!state.trails) state.trail.length = 0; }
else if (act === 'heatmap') state.heatmap = !state.heatmap;
else if (act === 'rays') setRays(!state.rays);
else if (act === 'demo') startDemoMode();
syncControlButtons();
});
Expand All @@ -152,6 +156,7 @@
view3d: state.view3d,
trails: state.trails,
heatmap: state.heatmap,
rays: state.rays,
demo: state.demoMode,
};
state.controlsEl.querySelectorAll('[data-act]').forEach((btn) => {
Expand All @@ -167,15 +172,32 @@
const t = e.target;
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT' || t.isContentEditable)) return;
const k = e.key;
// Never steal driving keys. WASD controls the player car (phase 3) and
// must not toggle Demo / Follow / other presentation chrome. Demo used
// to bind D, which flipped Follow on every right-turn press.
if (k === 'w' || k === 'a' || k === 's' || k === 'd' ||
k === 'W' || k === 'A' || k === 'S' || k === 'D') {
return;
}
// While the user is actively driving (physics-tune phase), ignore all
// presentation hotkeys so arrow keys and free driving stay clean.
try {
if (typeof phase !== 'undefined' && phase === 3) return;
} catch (_) {}
if (k === 'f' || k === 'F') { setFollow(!state.followBest); syncControlButtons(); }
else if (k === '3') { setView3d(!state.view3d); syncControlButtons(); }
else if (k === 't' || k === 'T') { state.trails = !state.trails; if (!state.trails) state.trail.length = 0; syncControlButtons(); }
else if (k === 'h' || k === 'H') { state.heatmap = !state.heatmap; syncControlButtons(); }
else if (k === 'd' || k === 'D') { startDemoMode(); syncControlButtons(); }
else if (k === 'r' || k === 'R') { setRays(!state.rays); syncControlButtons(); }
else if (k === 'm' || k === 'M') { startDemoMode(); syncControlButtons(); }
else if (k === '0') { resetCamera(); }
});
}

function setRays(on) {
state.rays = !!on;
}

// --------------------------------------------------------------- road cache
function useRoadCache() {
// Cache only during training when the editor is locked — during track
Expand Down Expand Up @@ -785,8 +807,8 @@
const x = best.x, y = best.y, ang = best.angle;
if (state.view3d) {
drawProjectedCar(ctx, x, y, ang, CHAMPION, 1, state.carHeight * 1.15);
// Sensor rays
if (best.sensor && best.sensor.rays && best.sensor.rays.length) {
// Sensor rays (toggle via Rays chip / R)
if (state.rays && best.sensor && best.sensor.rays && best.sensor.rays.length) {
for (let i = 0; i < best.sensor.rays.length; i++) {
const ray = best.sensor.rays[i];
const reading = best.sensor.readings[i];
Expand Down Expand Up @@ -832,7 +854,7 @@
ctx.stroke();
ctx.restore();

if (best.sensor && best.sensor.rays && best.sensor.rays.length) {
if (state.rays && best.sensor && best.sensor.rays && best.sensor.rays.length) {
for (let i = 0; i < best.sensor.rays.length; i++) {
const ray = best.sensor.rays[i];
const reading = best.sensor.readings[i];
Expand Down Expand Up @@ -1006,12 +1028,18 @@
if (ss) ss.value = '5';
} catch (_) {}

// Unpause if waiting on first start.
// Unpause / first-start: pauseGame() clears __awaitingStart and calls
// begin() to build the swarm. A second begin() is only needed when the
// sim was already past the Start gate (restart with demo knobs).
try {
if (typeof pause !== 'undefined' && pause && typeof pauseGame === 'function') {
if (window.__awaitingStart && typeof pauseGame === 'function') {
pauseGame();
} else if (typeof pause !== 'undefined' && pause && typeof pauseGame === 'function') {
pauseGame();
if (typeof begin === 'function') begin();
} else if (typeof begin === 'function') {
begin();
}
if (typeof begin === 'function') begin();
} catch (_) {}

state.demoStarted = true;
Expand Down Expand Up @@ -1066,6 +1094,7 @@
onGenEnd,
setFollow,
setView3d,
setRays,
resetCamera,
startDemoMode,
setStory,
Expand Down
53 changes: 47 additions & 6 deletions AI-Car-Racer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</head>

<body>
<div id="fullDisplay">
<div id="fullDisplay" class="panel-collapsed">
<div id="canvasDiv">
<canvas id="myCanvas"></canvas>
<!-- P3.E A/B comparison canvas. Hidden by default; toggled on via
Expand All @@ -63,6 +63,17 @@
<div id="bottomText">
<h1>Train Your Model</h1>
</div>
<!-- Start CTA is in the HTML so it paints on first frame — before
main.js / wasm / the 500-car worker build finish. A tiny
inline script (below the heavy scripts? no — right here)
records early clicks as __pendingStart so boot can honor them. -->
<div id="startOverlay" role="dialog" aria-modal="true" aria-label="Start training">
<button type="button" class="start-overlay-btn" id="startOverlayBtn">
<span class="start-overlay-icon" aria-hidden="true">▶</span>
<span class="start-overlay-label">Start Training</span>
<span class="start-overlay-hint">or press the Start button in the panel</span>
</button>
</div>
</div>
<div id="rightPanel">
<div id="verticalButtons"></div>
Expand All @@ -75,7 +86,7 @@ <h1>Train Your Model</h1>
</div>
<!-- Edge toggle: collapses the right panel so the track can use the
whole viewport width. State persisted in localStorage (main.js). -->
<button id="panelToggle" type="button" aria-label="Collapse control panel" aria-expanded="true" title="Collapse panel (press P)">
<button id="panelToggle" type="button" aria-label="Expand control panel" aria-expanded="false" title="Expand panel (press P)">
<span aria-hidden="true">›</span>
</button>

Expand All @@ -99,6 +110,31 @@ <h1>Train Your Model</h1>
<button id="track-preset-load" type="button" style="background:#3a7bd5; color:#fff; border:none; border-radius:3px; padding:3px 10px; cursor:pointer;">Load</button>
</div>

<!-- Interactive-as-soon-as-possible gate. Sets flags before any heavy
classic script runs, and records Start clicks that happen while the
rest of the app is still downloading/parsing. -->
<script>
window.__awaitingStart = true;
window.__pendingStart = false;
(function () {
var btn = document.getElementById('startOverlayBtn');
if (!btn || btn.dataset.earlyBound === '1') return;
btn.dataset.earlyBound = '1';
btn.addEventListener('click', function () {
window.__pendingStart = true;
btn.classList.add('is-starting');
btn.disabled = true;
var label = btn.querySelector('.start-overlay-label');
if (label) label.textContent = 'Starting\u2026';
// If the full app already booted, start immediately. Otherwise
// main.js will drain __pendingStart at the end of its boot.
if (typeof pauseGame === 'function' && typeof begin === 'function') {
window.__pendingStart = false;
try { pauseGame(); } catch (e) {}
}
});
})();
</script>
<script src="inputVisual.js"></script>
<script src="grapher.js"></script>
<script src="roadEditor.js"></script>
Expand Down Expand Up @@ -187,10 +223,15 @@ <h1>Train Your Model</h1>
toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
toggle.setAttribute('aria-label', collapsed ? 'Expand control panel' : 'Collapse control panel');
};
// Restore — default to expanded. localStorage read is wrapped because
// Safari private mode throws synchronously on access.
let saved = false;
try { saved = localStorage.getItem(KEY) === '1'; } catch (_) {}
// Restore preference. Default to collapsed so the track claims the
// viewport; the canvas Start overlay stays reachable either way.
// localStorage read is wrapped because Safari private mode throws.
let saved = true; // default collapsed
try {
const v = localStorage.getItem(KEY);
if (v === '0') saved = false;
else if (v === '1') saved = true;
} catch (_) {}
apply(saved);
toggle.addEventListener('click', () => {
const nowCollapsed = !display.classList.contains('panel-collapsed');
Expand Down
Loading
Loading