SURVIVAL TIME
00:00
CORTEX: LEARNING...
💎 0
ZONE UP!
CHROMAWAVE
RUSH
BEST:
v6.0 AI CORTEX
// --- -1. COMMERCIAL CONFIGURATION ---
const Config = {
VERSION: "6.0.1",
AI_LEARNING_RATE: 0.12, // Future-ready: Slightly faster adaptation
BASE_SPEED: 35,
HYPER_SPEED_BOOST: 45,
DIFFICULTY_RAMP: 5, // Metres per difficulty level
INVINCIBLE_TIME: 2.5,
};
const Utils = {
formatTime: (sec) => {
const m = Math.floor(sec / 60)
.toString()
.padStart(2, "0");
const s = Math.floor(sec % 60)
.toString()
.padStart(2, "0");
return `${m}:${s}`;
},
randRange: (min, max) => Math.random() * (max - min) + min,
clamp: (val, min, max) => Math.min(Math.max(val, min), max),
};
// --- 0. AI & NLP MODULES ---
const Narrator = {
synth: window.speechSynthesis,
voice: null,
speaking: false,
init: () => {
// Try to select a "System" or "Google US English" voice
if (Narrator.synth) {
const voices = Narrator.synth.getVoices();
Narrator.voice =
voices.find((v) => v.name.includes("Google US English")) ||
voices[0];
}
},
speak: (text, pitch = 1.0, rate = 1.0) => {
if (!Narrator.synth || Game.isMuted) return;
// Cancel prev speech to avoid queue lag
Narrator.synth.cancel();
const utter = new SpeechSynthesisUtterance(text);
if (Narrator.voice) utter.voice = Narrator.voice;
utter.pitch = pitch;
utter.rate = rate;
utter.volume = 0.8;
Narrator.synth.speak(utter);
},
};
// Markov Chain + Heuristics for Player Prediction
const Cortex = {
// 5 lanes: Indices 0 to 4 correspond to x: -6, -3, 0, 3, 6
laneHistory: [2, 2, 2, 2, 2], // Last 5 positions
transitionMatrix: Array(5)
.fill()
.map(() => Array(5).fill(0.2)), // Probabilities
learningRate: 0.1,
analyzeMove: (prevLaneIdx, newLaneIdx) => {
if (prevLaneIdx === newLaneIdx) return;
// Update Markov Matrix: Increase prob of newLane given prevLane
const row = Cortex.transitionMatrix[prevLaneIdx];
for (let i = 0; i < 5; i++) {
if (i === newLaneIdx) row[i] += Cortex.learningRate;
else row[i] -= Cortex.learningRate / 4;
row[i] = Utils.clamp(row[i], 0.05, 0.95); // Keep bounds
}
// Normalize row
const sum = row.reduce((a, b) => a + b, 0);
for (let i = 0; i < 5; i++) row[i] /= sum;
Cortex.laneHistory.push(newLaneIdx);
if (Cortex.laneHistory.length > 10) Cortex.laneHistory.shift();
document.getElementById("ai-status").innerText =
"CORTEX: ADAPTING...";
},
predictNextLane: (currentLaneIdx) => {
const row = Cortex.transitionMatrix[currentLaneIdx];
let maxP = -1;
let predicted = 2;
for (let i = 0; i < 5; i++) {
if (row[i] > maxP) {
maxP = row[i];
predicted = i;
}
}
return predicted;
},
};
// --- 1. STORAGE (Commercial Data Module) ---
const Storage = {
key: "cr_save_v6_ai",
data: { bestTime: 0, bestDist: 0, totalGems: 0, aiMatrix: null },
load: async () => {
try {
let d;
// Try CrazyGames SDK Data Module first
if (Monetization.sdk?.data) {
d = await Monetization.sdk.data.getItem(Storage.key);
}
// Fallback to localStorage
if (!d) {
d = localStorage.getItem(Storage.key);
}
if (d) {
Storage.data = typeof d === "string" ? JSON.parse(d) : d;
if (Storage.data.aiMatrix)
Cortex.transitionMatrix = Storage.data.aiMatrix;
}
if (document.getElementById("menu-best-time")) {
document.getElementById("menu-best-time").innerText =
Utils.formatTime(Storage.data.bestTime);
}
} catch (e) {
console.error("Save Load Error", e);
}
},
save: async () => {
Storage.data.aiMatrix = Cortex.transitionMatrix;
try {
if (Monetization.sdk?.data) {
await Monetization.sdk.data.setItem(Storage.key, Storage.data);
}
// Always sync to localStorage as secondary redundant save
localStorage.setItem(Storage.key, JSON.stringify(Storage.data));
} catch (e) {
console.error("Save Error", e);
}
},
updateRecords: (time, dist) => {
let improved = false;
if (time > Storage.data.bestTime) {
Storage.data.bestTime = time;
improved = true;
}
if (dist > Storage.data.bestDist) {
Storage.data.bestDist = dist;
improved = true;
}
if (improved) Storage.save();
},
};
// --- 2. AUDIO ENGINE ---
const AudioEngine = {
ctx: null,
isPlaying: false,
isMuted: false,
nextNoteTime: 0,
beatCount: 0,
scales: {
neon: [440, 523, 659, 783],
heat: [440, 493, 554, 659],
void: [220, 311, 370, 415],
},
currentScale: "neon",
init: () => {
window.AudioContext =
window.AudioContext || window.webkitAudioContext;
AudioEngine.ctx = new AudioContext();
},
toggleMute: () => {
AudioEngine.isMuted = !AudioEngine.isMuted;
Game.isMuted = AudioEngine.isMuted;
document.getElementById("mute-btn").innerText = AudioEngine.isMuted
? "🔇"
: "🔊";
if (AudioEngine.ctx)
AudioEngine.isMuted
? AudioEngine.ctx.suspend()
: AudioEngine.ctx.resume();
if (AudioEngine.isMuted && Narrator.synth) Narrator.synth.cancel();
},
start: () => {
if (!AudioEngine.ctx) AudioEngine.init();
if (AudioEngine.ctx.state === "suspended" && !AudioEngine.isMuted)
AudioEngine.ctx.resume();
AudioEngine.isPlaying = true;
AudioEngine.nextNoteTime = AudioEngine.ctx.currentTime;
AudioEngine.loop();
},
stop: () => {
AudioEngine.isPlaying = false;
},
loop: () => {
if (!AudioEngine.isPlaying) return;
const lookahead = 0.1;
const tempo = 125 + Game.difficulty * 10;
const secondsPer16th = 60.0 / tempo / 4;
while (
AudioEngine.nextNoteTime <
AudioEngine.ctx.currentTime + lookahead
) {
if (!AudioEngine.isMuted)
AudioEngine.playBeat(AudioEngine.nextNoteTime);
AudioEngine.nextNoteTime += secondsPer16th;
}
requestAnimationFrame(AudioEngine.loop);
},
playBeat: (time) => {
const beat = AudioEngine.beatCount % 16;
const scale =
AudioEngine.scales[AudioEngine.currentScale] ||
AudioEngine.scales.neon;
if (beat % 4 === 0) AudioEngine.synthKick(time);
if (beat % 2 === 0)
AudioEngine.synthBass(time, scale[0] / (beat % 8 === 0 ? 4 : 2));
if (Math.random() > 0.5)
AudioEngine.synthLead(
time,
scale[Math.floor(Math.random() * scale.length)] * 2,
);
AudioEngine.beatCount++;
if (beat % 4 === 0 && Game.active) Game.pulseWorld();
},
synthKick: (t) => {
const o = AudioEngine.ctx.createOscillator();
const g = AudioEngine.ctx.createGain();
o.connect(g);
g.connect(AudioEngine.ctx.destination);
o.frequency.setValueAtTime(150, t);
o.frequency.exponentialRampToValueAtTime(0.01, t + 0.5);
g.gain.setValueAtTime(0.8, t);
g.gain.exponentialRampToValueAtTime(0.01, t + 0.5);
o.start(t);
o.stop(t + 0.5);
},
synthBass: (t, f) => {
const o = AudioEngine.ctx.createOscillator();
o.type = "sawtooth";
const g = AudioEngine.ctx.createGain();
o.connect(g);
g.connect(AudioEngine.ctx.destination);
o.frequency.setValueAtTime(f, t);
g.gain.setValueAtTime(0.3, t);
g.gain.linearRampToValueAtTime(0, t + 0.2);
o.start(t);
o.stop(t + 0.2);
},
synthLead: (t, f) => {
const o = AudioEngine.ctx.createOscillator();
o.type = "square";
const g = AudioEngine.ctx.createGain();
o.connect(g);
g.connect(AudioEngine.ctx.destination);
o.frequency.setValueAtTime(f, t);
g.gain.setValueAtTime(0.05, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
o.start(t);
o.stop(t + 0.1);
},
};
// --- 3. MONETIZATION ---
const Monetization = {
sdk: {
game: { gameplayStart: () => {}, gameplayStop: () => {} },
ad: {
requestAd: (t, cb) => {
console.log("Mock Ad");
setTimeout(() => cb.adFinished(), 1000);
},
},
},
init: async () => {
try {
if (
typeof window.CrazyGames !== "undefined" &&
window.CrazyGames.SDK
) {
const realSdk = await window.CrazyGames.SDK.init();
if (realSdk) {
Monetization.sdk = realSdk;
console.log("Commercial | CrazyGames SDK Active");
}
} else {
console.warn(
"Commercial | SDK Not Detected - Entering Mock Mode",
);
}
} catch (e) {
console.error("Commercial | SDK Init Error", e);
}
},
watchAdForHyper: () => {
if (Monetization.sdk)
Monetization.sdk.ad.requestAd("rewarded", {
adFinished: () => {
document.getElementById("hyper-offer").classList.add("hidden");
Game.enterHyperMode();
},
adError: () => Game.resumeFromGate(),
});
else Game.resumeFromGate();
},
watchAdForRevive: () => {
const btn = document.getElementById("btn-revive");
btn.innerHTML = "LOADING...";
btn.disabled = true;
// Callback helper
const onSuccess = () => {
btn.innerHTML = "📺 REVIVE";
btn.disabled = false;
document.getElementById("game-over").classList.add("hidden");
Game.revive();
};
const onFail = () => {
btn.innerHTML = "UNAVAILABLE";
setTimeout(() => {
btn.disabled = false;
btn.innerHTML = "📺 REVIVE";
}, 2000);
};
if (Monetization.sdk) {
Monetization.sdk.ad.requestAd("rewarded", {
adFinished: onSuccess,
adError: onFail,
});
} else {
setTimeout(onSuccess, 1000); // Mock
}
},
};
// --- 4. ENGINE ---
const Engine = {
scene: null,
camera: null,
renderer: null,
clock: null,
geometries: {},
materials: {},
grid: null,
sun: null,
pool: { obstacle: [], gem: [], gate: [], particle: [] },
init: () => {
const cvs = document.getElementById("gl-canvas");
Engine.clock = new THREE.Clock();
Engine.scene = new THREE.Scene();
Engine.scene.fog = new THREE.FogExp2(0x000000, 0.02);
Engine.camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.1,
300,
);
Engine.camera.position.set(0, 3, -6);
Engine.camera.lookAt(0, 0, 15);
Engine.renderer = new THREE.WebGLRenderer({
canvas: cvs,
antialias: true,
powerPreference: "high-performance",
});
if (!Engine.renderer.capabilities.isWebGL2 && !Engine.renderer.getContext().getExtension('WEBGL_depth_texture')) {
console.warn("Commercial | Legacy Device Detected");
}
Engine.renderer.setSize(window.innerWidth, window.innerHeight);
Engine.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
// Shared Resources
Engine.geometries.box = new THREE.BoxGeometry(2, 2, 2);
Engine.geometries.gem = new THREE.OctahedronGeometry(0.5);
Engine.geometries.gate = new THREE.TorusGeometry(2.5, 0.2, 8, 20);
Engine.geometries.particle = new THREE.PlaneGeometry(0.5, 0.5);
Engine.materials.obstacle = new THREE.MeshStandardMaterial({
color: 0xff0000,
emissive: 0x440000,
});
Engine.materials.gem = new THREE.MeshBasicMaterial({
color: 0x00ffff,
});
Engine.materials.gate = new THREE.MeshBasicMaterial({
color: 0xffd700,
});
Engine.materials.particle = new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.8,
});
// World
const gridGeo = new THREE.PlaneGeometry(400, 400, 40, 40);
const gridMat = new THREE.MeshBasicMaterial({
color: 0xff00cc,
wireframe: true,
transparent: true,
opacity: 0.4,
});
Engine.grid = new THREE.Mesh(gridGeo, gridMat);
Engine.grid.rotation.x = -Math.PI / 2;
Engine.grid.position.y = -1;
Engine.scene.add(Engine.grid);
const sunGeo = new THREE.CircleGeometry(40, 32);
Engine.sun = new THREE.Mesh(
sunGeo,
new THREE.MeshBasicMaterial({ color: 0xffd700 }),
);
Engine.sun.position.set(0, 15, 200);
Engine.scene.add(Engine.sun);
const hemi = new THREE.HemisphereLight(0xffffff, 0x000000, 0.8);
Engine.scene.add(hemi);
const dir = new THREE.DirectionalLight(0xffffff, 0.8);
dir.position.set(10, 20, 10);
Engine.scene.add(dir);
window.addEventListener("resize", Engine.resize);
Engine.animate();
// Initialize Narrator Voice
if (window.speechSynthesis)
window.speechSynthesis.onvoiceschanged = Narrator.init;
Narrator.init();
},
resize: () => {
Engine.camera.aspect = window.innerWidth / window.innerHeight;
Engine.camera.updateProjectionMatrix();
Engine.renderer.setSize(window.innerWidth, window.innerHeight);
},
getFromPool: (type) => {
if (Engine.pool[type].length > 0) {
const mesh = Engine.pool[type].pop();
mesh.visible = true;
return mesh;
}
let mesh;
if (type === "obstacle")
mesh = new THREE.Mesh(
Engine.geometries.box,
Engine.materials.obstacle,
);
else if (type === "gem")
mesh = new THREE.Mesh(Engine.geometries.gem, Engine.materials.gem);
else if (type === "gate")
mesh = new THREE.Mesh(
Engine.geometries.gate,
Engine.materials.gate,
);
else if (type === "particle") {
mesh = new THREE.Mesh(
Engine.geometries.particle,
Engine.materials.particle.clone(),
);
mesh.lookAt(Engine.camera.position);
}
Engine.scene.add(mesh);
return mesh;
},
returnToPool: (type, mesh) => {
mesh.visible = false;
Engine.pool[type].push(mesh);
},
animate: () => {
requestAnimationFrame(Engine.animate);
const dt = Engine.clock.getDelta();
Game.update(dt);
Engine.renderer.render(Engine.scene, Engine.camera);
},
};
// --- 5. GAME LOGIC ---
const Game = {
active: false,
paused: false,
usedRevive: false,
speed: 0,
distance: 0,
timeAlive: 0,
gems: 0,
inputX: 0,
difficulty: 0,
invincible: 0,
isMuted: false,
currentLaneIdx: 2, // 0-4
zones: [
{ name: "NEON GRID", color: 0xff00cc, bg: 0x050011, scale: "neon" },
{ name: "SOLAR HEAT", color: 0xff4400, bg: 0x220500, scale: "heat" },
{ name: "CYBER VOID", color: 0x00ffaa, bg: 0x001111, scale: "void" },
],
currentZoneIdx: 0,
player: null,
objects: [],
particles: [],
lastSpawnZ: 0,
init: async () => {
await Storage.load();
const geo = new THREE.SphereGeometry(0.5, 32, 32);
const mat = new THREE.MeshStandardMaterial({
color: 0x00ffff,
roughness: 0.1,
metalness: 0.9,
emissive: 0x0044aa,
});
Game.player = new THREE.Mesh(geo, mat);
Engine.scene.add(Game.player);
const handleMove = (xNorm) => {
if (!Game.active || Game.paused) return;
const rawX = Math.max(-1, Math.min(1, xNorm)) * 8;
Game.inputX = rawX;
// Calculate lane index (approximate) for AI learning
// Lanes are at -6, -3, 0, 3, 6
const lane = Math.round((rawX + 6) / 3);
const clampedLane = Utils.clamp(lane, 0, 4);
if (clampedLane !== Game.currentLaneIdx) {
Cortex.analyzeMove(Game.currentLaneIdx, clampedLane);
Game.currentLaneIdx = clampedLane;
}
};
document.addEventListener("mousemove", (e) =>
handleMove(
(e.clientX - window.innerWidth / 2) / (window.innerWidth * 0.4),
),
);
document.addEventListener(
"touchmove",
(e) => {
handleMove(
(e.touches[0].clientX - window.innerWidth / 2) /
(window.innerWidth * 0.4),
);
},
{ passive: false },
);
},
start: () => {
AudioEngine.start();
Monetization.sdk.game.gameplayStart();
document.getElementById("main-menu").classList.add("hidden");
document.getElementById("hud").classList.remove("hidden");
document.getElementById("game-over").classList.add("hidden");
Narrator.speak("System initialized. Begin run.", 1.2, 1.1);
Game.resetState();
Game.setInvincible(2.0);
},
resetState: () => {
Game.active = true;
Game.paused = false;
Game.usedRevive = false;
Game.speed = 30;
Game.distance = 0;
Game.timeAlive = 0;
Game.gems = 0;
Game.difficulty = 0;
Game.currentZoneIdx = 0;
Game.objects.forEach((o) => Engine.returnToPool(o.type, o.mesh));
Game.objects = [];
Game.lastSpawnZ = 0;
Game.setZone(0);
Game.player.position.set(0, 0, 0);
Game.player.visible = true;
},
update: (dt) => {
if (!Game.active || Game.paused) return;
if (Game.invincible > 0) {
Game.invincible -= dt;
Game.player.visible = Math.floor(Game.timeAlive * 20) % 2 === 0;
} else {
Game.player.visible = true;
}
Game.timeAlive += dt;
const newDifficulty = Math.floor(Game.distance / 1000);
if (newDifficulty > Game.difficulty) {
Game.difficulty = newDifficulty;
Narrator.speak("Difficulty Increasing. Stay alert.", 1.0, 1.2);
}
// Physics
const targetSpeed =
35 + Game.difficulty * 4 + (Game.hyperMode ? 40 : 0);
Game.speed += (targetSpeed - Game.speed) * dt;
const distDelta = Game.speed * dt;
Game.distance += distDelta;
Game.player.position.z += distDelta;
const lerpFactor = 10 * dt;
Game.player.position.x +=
(Game.inputX - Game.player.position.x) * lerpFactor;
Game.player.rotation.z =
-(Game.player.position.x - Game.inputX) * 0.15;
Game.player.rotation.x -= Game.speed * dt * 0.5;
Engine.camera.position.z = Game.player.position.z - 8;
Engine.camera.lookAt(0, 0, Game.player.position.z + 20);
Engine.grid.position.z = Game.player.position.z + 50;
if (Engine.grid.material.map)
Engine.grid.material.map.offset.y -= distDelta * 0.01;
Engine.sun.position.z = Game.player.position.z + 180;
const zoneIdx = Math.floor(Game.distance / 1000) % Game.zones.length;
if (zoneIdx !== Game.currentZoneIdx) Game.setZone(zoneIdx);
if (Game.player.position.z + 120 > Game.lastSpawnZ) {
Game.spawnPattern(Game.lastSpawnZ);
Game.lastSpawnZ += Utils.randRange(20, 30);
}
// Object Update
for (let i = Game.objects.length - 1; i >= 0; i--) {
const obj = Game.objects[i];
if (obj.moveType === "slide") {
obj.mesh.position.x =
obj.origX + Math.sin(Game.timeAlive * 3) * 3;
} else if (obj.moveType === "rotate") {
obj.mesh.rotation.z += 3 * dt;
obj.mesh.rotation.y += 1 * dt;
}
if (
obj.active &&
Math.abs(obj.mesh.position.z - Game.player.position.z) < 1.3
) {
const dx = Math.abs(obj.mesh.position.x - Game.player.position.x);
if (dx < 1.4) {
if (obj.type === "obstacle") {
if (Game.invincible <= 0) Game.crash();
} else if (obj.type === "gem") {
obj.active = false;
Game.spawnParticles(obj.mesh.position, 0x00ffff);
Engine.returnToPool("gem", obj.mesh);
Game.objects.splice(i, 1);
Game.gems++;
} else if (obj.type === "gate") Game.offerHyper();
}
}
if (obj.mesh.position.z < Game.player.position.z - 15) {
Engine.returnToPool(obj.type, obj.mesh);
Game.objects.splice(i, 1);
}
}
for (let i = Game.particles.length - 1; i >= 0; i--) {
const p = Game.particles[i];
p.life -= dt * 2;
p.mesh.scale.setScalar(1 + (1 - p.life) * 2);
p.mesh.material.opacity = p.life;
if (p.life <= 0) {
Engine.returnToPool("particle", p.mesh);
Game.particles.splice(i, 1);
}
}
Game.updateHUD();
},
spawnPattern: (z) => {
if (Game.hyperMode) {
for (let i = -6; i <= 6; i += 3)
if (Math.random() > 0.5) Game.createObj("gem", i, z);
return;
}
const lanes = [-6, -3, 0, 3, 6];
// AI CORTEX PREDICTION
const predictedLaneIdx = Cortex.predictNextLane(Game.currentLaneIdx);
// Trap logic: 30% chance AI traps the predicted lane
const trapLane = Math.random() < 0.3;
// Standard random safe index logic mixed with AI
const safeIdx = Math.floor(Math.random() * lanes.length);
lanes.forEach((x, idx) => {
// If AI is trapping this lane, spawn obstacle
if (trapLane && idx === predictedLaneIdx && idx !== safeIdx) {
Game.createObj("obstacle", x, z);
return;
}
if (idx === safeIdx) {
if (Math.random() < 0.15) Game.createObj("gem", x, z);
else if (Math.random() < 0.02) Game.createObj("gate", x, z);
} else {
if (Math.random() < 0.4 + Game.difficulty * 0.05) {
let moveType = null;
if (Game.difficulty >= 1 && Math.random() < 0.3)
moveType = "slide";
else if (Game.difficulty >= 2 && Math.random() < 0.3)
moveType = "rotate";
Game.createObj("obstacle", x, z, moveType);
}
}
});
},
createObj: (type, x, z, moveType = null) => {
const mesh = Engine.getFromPool(type);
mesh.position.set(x, 1, z);
mesh.rotation.set(0, 0, 0);
Game.objects.push({ mesh, type, active: true, moveType, origX: x });
},
spawnParticles: (pos, color) => {
const p = Engine.getFromPool("particle");
p.position.copy(pos);
p.material.color.setHex(color);
p.material.opacity = 1;
p.scale.setScalar(1);
Game.particles.push({ mesh: p, life: 1.0 });
},
setInvincible: (duration) => {
Game.invincible = duration;
},
crash: () => {
Game.active = false;
Monetization.sdk.game.gameplayStop();
Narrator.speak("Critical Failure. Reboot required.", 0.8, 1.0);
Storage.data.totalGems += Game.gems;
Storage.updateRecords(Game.timeAlive, Math.floor(Game.distance));
Game.spawnParticles(Game.player.position, 0xff0044);
document.getElementById("hud").classList.add("hidden");
document.getElementById("game-over").classList.remove("hidden");
document.getElementById("final-time").innerText = Utils.formatTime(
Game.timeAlive,
);
document.getElementById("final-score").innerText =
Math.floor(Game.distance) + "m";
const revBtn = document.getElementById("revive-container");
revBtn.style.display = Game.usedRevive ? "none" : "block";
// Future-ready: Analytics Trigger (Stub)
console.log(`Commercial | Run Result: ${Math.floor(Game.distance)}m in ${Game.timeAlive.toFixed(1)}s`);
},
revive: () => {
Game.usedRevive = true;
Game.active = true;
Game.paused = false;
document.getElementById("hud").classList.remove("hidden");
Monetization.sdk.game.gameplayStart();
Narrator.speak("System restored. Good luck.", 1.2, 1.1);
// Clear area safely ahead
for (let i = Game.objects.length - 1; i >= 0; i--) {
const o = Game.objects[i];
if (
o.mesh.position.z > Game.player.position.z - 5 &&
o.mesh.position.z < Game.player.position.z + 50
) {
Engine.returnToPool(o.type, o.mesh);
Game.objects.splice(i, 1);
}
}
Game.player.position.x = 0;
Game.setInvincible(2.0);
},
restart: () => {
Monetization.sdk.game.gameplayStart();
Narrator.speak("Restarting simulation.", 1.0, 1.2);
Game.resetState();
Game.setInvincible(2.0);
},
home: () => {
Game.active = false;
AudioEngine.stop();
document.getElementById("game-over").classList.add("hidden");
document.getElementById("hud").classList.add("hidden");
document.getElementById("main-menu").classList.remove("hidden");
Engine.camera.position.set(0, 3, -6);
Engine.camera.lookAt(0, 0, 15);
},
offerHyper: () => {
Game.paused = true;
document.getElementById("hyper-offer").classList.remove("hidden");
Game.player.position.z += 2;
Narrator.speak("Hyper Gate detected.", 1.5, 1.2);
},
resumeFromGate: () => {
Game.paused = false;
document.getElementById("hyper-offer").classList.add("hidden");
},
enterHyperMode: () => {
Game.paused = false;
Game.hyperMode = true;
Engine.scene.fog.color.setHex(0xffaa00);
Narrator.speak("Hyper speed engaged.", 1.2, 1.5);
setTimeout(() => {
Game.hyperMode = false;
Game.setZone(Game.currentZoneIdx);
Narrator.speak("Speed normalizing.", 1.0, 1.0);
}, 8000);
},
setZone: (idx) => {
Game.currentZoneIdx = idx;
const cfg = Game.zones[idx];
Engine.scene.fog.color.setHex(cfg.bg);
Engine.renderer.setClearColor(cfg.bg);
Engine.grid.material.color.setHex(cfg.color);
Engine.sun.material.color.setHex(cfg.color);
AudioEngine.currentScale = cfg.scale;
const t = document.getElementById("zone-toast");
t.innerText = cfg.name;
t.style.color = "#" + cfg.color.toString(16);
t.style.opacity = 1;
setTimeout(() => (t.style.opacity = 0), 2000);
document.getElementById("zone-display").innerText = `ZONE ${idx + 1}`;
document.getElementById("zone-display").style.color =
"#" + cfg.color.toString(16);
if (Game.active) Narrator.speak(`Entering ${cfg.name}`, 0.9, 1.0);
},
pulseWorld: () => {
if (Engine.grid) {
Engine.grid.position.y = -0.5;
setTimeout(() => (Engine.grid.position.y = -1), 100);
}
},
updateHUD: () => {
document.getElementById("score-display").innerText =
Math.floor(Game.distance) + "m";
document.getElementById("time-display").innerText = Utils.formatTime(
Game.timeAlive,
);
document.getElementById("gems-display").innerText = Game.gems;
},
};
(async () => {
await Monetization.init();
Engine.init();
await Game.init();
})();
// PWA: Service Worker Registration
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("./sw.js")
.then((reg) => console.log("SW Registered", reg))
.catch((err) => console.log("SW Fail", err));
});
}