← Rally results

Rally

Gemma 4 31b · basic

Initial view of Rally

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 18,830 bytes · SHA-256 26df50047d51
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>JS Rally Racer 3D</title>
    <style>
        body { margin: 0; overflow: hidden; background: #87CEEB; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; user-select: none; }
        canvas { display: block; }
        #ui { position: absolute; top: 20px; left: 20px; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); pointer-events: none; }
        .stat { font-size: 24px; font-weight: bold; margin-bottom: 5px; }
        #speedometer { font-size: 48px; color: #ffeb3b; }
        #countdown { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 120px; color: white; font-weight: bold; text-shadow: 4px 4px 10px rgba(0,0,0,0.5); pointer-events: none; display: none; }
        #menu { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 30px; border-radius: 15px; text-align: center; display: none; }
        button { padding: 10px 20px; font-size: 20px; cursor: pointer; background: #f44336; color: white; border: none; border-radius: 5px; margin-top: 20px; }
        button:hover { background: #d32f2f; }
        #minimap { position: absolute; bottom: 20px; right: 20px; width: 200px; height: 200px; background: rgba(0,0,0,0.3); border: 3px solid white; border-radius: 50%; overflow: hidden; }
        #minimap-canvas { width: 100%; height: 100%; }
        #mobile-controls { position: absolute; bottom: 20px; left: 20px; display: none; }
        .btn-ctrl { width: 60px; height: 60px; background: rgba(255,255,255,0.3); border-radius: 50%; display: inline-block; margin: 5px; text-align: center; line-height: 60px; color: white; font-weight: bold; pointer-events: auto; cursor: pointer; }
        #touch-right { position: absolute; bottom: 20px; right: 20px; display: none; }
    </style>
</head>
<body>

<div id="ui">
    <div id="speedometer" class="stat">0 km/h</div>
    <div id="lap-info" class="stat">Lap 1/3</div>
    <div id="timer" class="stat">00:00.00</div>
    <div id="best-lap" class="stat" style="font-size: 18px;">Best: --:--.--</div>
</div>

<div id="countdown">3</div>

<div id="menu">
    <h1 id="menu-title">JS RALLY</h1>
    <p id="menu-score"></p>
    <button onclick="game.reset()">START RACE</button>
</div>

<div id="minimap">
    <canvas id="minimap-canvas"></canvas>
</div>

<div id="mobile-controls">
    <div class="btn-ctrl" id="btn-left">←</div>
    <div class="btn-ctrl" id="btn-right">→</div>
</div>
<div id="touch-right">
    <div class="btn-ctrl" id="btn-gas">GAS</div><br>
    <div class="btn-ctrl" id="btn-brake">BRK</div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.min.js"></script>

<script>
/**
 * GAME ENGINE
 */
const game = {
    // State
    running: false,
    paused: false,
    countdown: 3,
    lap: 1,
    totalLaps: 3,
    startTime: 0,
    currentTime: 0,
    bestLapTime: Infinity,
    checkpointIndex: 0,
    
    // Physics constants
    accel: 0.15,
    friction: 0.98,
    brakeForce: 0.3,
    steerSpeed: 0.04,
    offroadMult: 0.4,
    
    // Audio context
    audioCtx: null,
    engineOsc: null,
    engineGain: null,

    init() {
        this.setupScene();
        this.setupInput();
        this.createTrack();
        this.createCar();
        this.createEnvironment();
        this.setupUI();
        this.loop();
        
        document.getElementById('menu').style.display = 'block';
    },

    setupScene() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x87CEEB);
        this.scene.fog = new THREE.FogExp2(0x87CEEB, 0.005);

        this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.shadowMap.enabled = true;
        document.body.appendChild(this.renderer.domElement);

        const ambient = new THREE.AmbientLight(0xffffff, 0.6);
        this.scene.add(ambient);

        const sun = new THREE.DirectionalLight(0xffffff, 1);
        sun.position.set(100, 200, 100);
        sun.castShadow = true;
        sun.shadow.camera.left = -200;
        sun.shadow.camera.right = 200;
        sun.shadow.camera.top = 200;
        sun.shadow.camera.bottom = -200;
        sun.shadow.mapSize.width = 1024;
        sun.shadow.mapSize.height = 1024;
        this.scene.add(sun);

        this.camMode = 'chase';
    },

    setupInput() {
        this.keys = {};
        window.addEventListener('keydown', e => {
            this.keys[e.code] = true;
            if (e.code === 'KeyC') this.camMode = this.camMode === 'chase' ? 'cinematic' : 'chase';
            if (e.code === 'KeyP') this.togglePause();
            if (e.code === 'KeyR') this.reset();
        });
        window.addEventListener('keyup', e => this.keys[e.code] = false);

        // Mobile
        const bindTouch = (id, code) => {
            const el = document.getElementById(id);
            el.addEventListener('touchstart', (e) => { e.preventDefault(); this.keys[code] = true; });
            el.addEventListener('touchend', (e) => { e.preventDefault(); this.keys[code] = false; });
        };
        bindTouch('btn-left', 'ArrowLeft');
        bindTouch('btn-right', 'ArrowRight');
        bindTouch('btn-gas', 'ArrowUp');
        bindTouch('btn-brake', 'ArrowDown');

        if ('ontouchstart' in window) {
            document.getElementById('mobile-controls').style.display = 'block';
            document.getElementById('touch-right').style.display = 'block';
        }
    },

    createTrack() {
        // Procedural track loop using CatmullRomCurve3
        const pts = [
            new THREE.Vector3(0, 0, 0),
            new THREE.Vector3(100, 0, 50),
            new THREE.Vector3(200, 10, 0),
            new THREE.Vector3(250, 5, -100),
            new THREE.Vector3(150, -5, -200),
            new THREE.Vector3(0, 0, -250),
            new THREE.Vector3(-150, 10, -200),
            new THREE.Vector3(-200, 0, -100),
            new THREE.Vector3(-100, -5, -50),
            new THREE.Vector3(0, 0, 0),
        ];
        this.trackCurve = new THREE.CatmullRomCurve3(pts);
        this.trackPoints = this.trackCurve.getPoints(200);
        
        const trackGeo = new THREE.TubeGeometry(this.trackCurve, 200, 8, 12, true);
        const trackMat = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.8 });
        this.trackMesh = new THREE.Mesh(trackGeo, trackMat);
        this.trackMesh.receiveShadow = true;
        this.scene.add(this.trackMesh);

        // Grass ground
        const groundGeo = new THREE.PlaneGeometry(2000, 2000);
        const groundMat = new THREE.MeshStandardMaterial({ color: 0x3a5f0b });
        const ground = new THREE.Mesh(groundGeo, groundMat);
        ground.rotation.x = -Math.PI / 2;
        ground.position.y = -10;
        ground.receiveShadow = true;
        this.scene.add(ground);

        // Checkpoints (invisible)
        this.checkpoints = [];
        for (let i = 0; i < 10; i++) {
            const t = i / 10;
            const pos = this.trackCurve.getPointAt(t);
            const cp = new THREE.Mesh(
                new THREE.BoxGeometry(20, 10, 2), 
                new THREE.MeshBasicMaterial({ visible: false })
            );
            cp.position.copy(pos);
            cp.lookAt(this.trackCurve.getPointAt((t + 0.01) % 1));
            this.scene.add(cp);
            this.checkpoints.push(cp);
        }
    },

    createCar() {
        const carGroup = new THREE.Group();
        
        // Body
        const body = new THREE.Mesh(
            new THREE.BoxGeometry(2, 0.8, 4),
            new THREE.MeshStandardMaterial({ color: 0xd32f2f })
        );
        body.position.y = 0.6;
        body.castShadow = true;
        carGroup.add(body);

        // Roof
        const roof = new THREE.Mesh(
            new THREE.BoxGeometry(1.6, 0.6, 2),
            new THREE.MeshStandardMaterial({ color: 0xffffff })
        );
        roof.position.set(0, 1.3, -0.2);
        roof.castShadow = true;
        carGroup.add(roof);

        // Spoiler
        const spoil = new THREE.Mesh(
            new THREE.BoxGeometry(2, 0.1, 0.5),
            new THREE.MeshStandardMaterial({ color: 0x222222 })
        );
        spoil.position.set(0, 1.1, 1.8);
        carGroup.add(spoil);

        // Wheels
        const wheelGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.4, 12);
        const wheelMat = new THREE.MeshStandardMaterial({ color: 0x111111 });
        const wheelPos = [[-1, 0.4, 1.2], [1, 0.4, 1.2], [-1, 0.4, -1.2], [1, 0.4, -1.2]];
        wheelPos.forEach(p => {
            const w = new THREE.Mesh(wheelGeo, wheelMat);
            w.rotation.z = Math.PI / 2;
            w.position.set(...p);
            carGroup.add(w);
        });

        this.car = carGroup;
        this.car.position.set(0, 0, 0);
        this.scene.add(this.car);

        // Car physics state
        this.carPhys = {
            speed: 0,
            angle: 0,
            drift: 0,
            targetRot: 0
        };
    },

    createEnvironment() {
        // Simple Trees
        for (let i = 0; i < 150; i++) {
            const angle = Math.random() * Math.PI * 2;
            const dist = 30 + Math.random() * 200;
            const x = Math.cos(angle) * dist;
            const z = Math.sin(angle) * dist;
            
            const tree = new THREE.Group();
            const trunk = new THREE.Mesh(
                new THREE.CylinderGeometry(0.5, 0.5, 3),
                new THREE.MeshStandardMaterial({ color: 0x5d4037 })
            );
            trunk.position.y = 1.5;
            const leaves = new THREE.Mesh(
                new THREE.ConeGeometry(3, 6, 8),
                new THREE.MeshStandardMaterial({ color: 0x2e7d32 })
            );
            leaves.position.y = 5;
            tree.add(trunk, leaves);
            tree.position.set(x, -10, z);
            tree.castShadow = true;
            this.scene.add(tree);
        }
    },

    setupUI() {
        this.minimapCanvas = document.getElementById('minimap-canvas');
        this.mctx = this.minimapCanvas.getContext('2d');
        this.minimapCanvas.width = 200;
        this.minimapCanvas.height = 200;
    },

    initAudio() {
        if (this.audioCtx) return;
        this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        this.engineOsc = this.audioCtx.createOscillator();
        this.engineGain = this.audioCtx.createGain();
        this.engineOsc.type = 'sawtooth';
        this.engineOsc.frequency.setValueAtTime(100, this.audioCtx.currentTime);
        this.engineGain.gain.setValueAtTime(0, this.audioCtx.currentTime);
        this.engineOsc.connect(this.engineGain);
        this.engineGain.connect(this.audioCtx.destination);
        this.engineOsc.start();
    },

    playSound(freq, type, dur, vol) {
        if (!this.audioCtx) return;
        const osc = this.audioCtx.createOscillator();
        const g = this.audioCtx.createGain();
        osc.type = type;
        osc.frequency.setValueAtTime(freq, this.audioCtx.currentTime);
        g.gain.setValueAtTime(vol, this.audioCtx.currentTime);
        g.gain.exponentialRampToValueAtTime(0.01, this.audioCtx.currentTime + dur);
        osc.connect(g);
        g.connect(this.audioCtx.destination);
        osc.start();
        osc.stop(this.audioCtx.currentTime + dur);
    },

    reset() {
        this.initAudio();
        this.car.position.set(0, 0, 0);
        this.carPhys.speed = 0;
        this.carPhys.angle = 0;
        this.lap = 1;
        this.checkpointIndex = 0;
        this.bestLapTime = Infinity;
        this.running = false;
        this.paused = false;
        document.getElementById('menu').style.display = 'none';
        
        const cdEl = document.getElementById('countdown');
        cdEl.style.display = 'block';
        this.countdown = 3;
        
        const timer = setInterval(() => {
            this.countdown--;
            if (this.countdown > 0) {
                cdEl.innerText = this.countdown;
                this.playSound(440, 'sine', 0.2, 0.1);
            } else if (this.countdown === 0) {
                cdEl.innerText = "GO!";
                this.playSound(880, 'sine', 0.4, 0.2);
                this.startTime = Date.now();
                this.running = true;
                clearInterval(timer);
                setTimeout(() => cdEl.style.display = 'none', 1000);
            }
        }, 1000);
    },

    togglePause() {
        this.paused = !this.paused;
        document.getElementById('menu').style.display = this.paused ? 'block' : 'none';
        document.getElementById('menu-title').innerText = "PAUSED";
    },

    updatePhysics(dt) {
        if (!this.running || this.paused) return;

        const p = this.carPhys;
        
        // Traction check
        let onTrack = false;
        const carPos = this.car.position;
        const closestPt = this.trackCurve.getPointAt(this.trackCurve.getU(carPos));
        if (carPos.distanceTo(closestPt) < 12) onTrack = true;

        const currentAccel = this.accel * (onTrack ? 1 : this.offroadMult);
        const currentFric = onTrack ? this.friction : 0.95;

        // Input
        if (this.keys['ArrowUp'] || this.keys['KeyW']) p.speed += currentAccel;
        if (this.keys['ArrowDown'] || this.keys['KeyS']) p.speed -= this.brakeForce;

        // Steering
        const steerDir = (this.keys['ArrowLeft'] || this.keys['KeyA']) ? 1 : (this.keys['ArrowRight'] || this.keys['KeyD']) ? -1 : 0;
        const speedFactor = Math.min(1, Math.abs(p.speed) / 10);
        p.angle += steerDir * this.steerSpeed * speedFactor * (p.speed > 0 ? 1 : -1);

        // Apply speed and friction
        p.speed *= currentFric;
        
        // Position update
        this.car.position.x += Math.sin(p.angle) * p.speed;
        this.car.position.z += Math.cos(p.angle) * p.speed;
        this.car.rotation.y = p.angle;

        // Simple height following
        const height = this.trackCurve.getPointAt(this.trackCurve.getU(this.car.position)).y;
        this.car.position.y = THREE.MathUtils.lerp(this.car.position.y, height, 0.1);

        // Checkpoints & Laps
        const cp = this.checkpoints[this.checkpointIndex];
        if (this.car.position.distanceTo(cp.position) < 15) {
            this.checkpointIndex = (this.checkpointIndex + 1) % this.checkpoints.length;
            if (this.checkpointIndex === 0) {
                const lapTime = (Date.now() - this.startTime) / 1000;
                if (lapTime < this.bestLapTime) this.bestLapTime = lapTime;
                this.lap++;
                this.startTime = Date.now();
                this.playSound(660, 'square', 0.3, 0.1);
                if (this.lap > this.totalLaps) {
                    this.finishRace();
                }
            }
        }

        // Audio update
        if (this.engineGain) {
            const pitch = 100 + Math.abs(p.speed) * 50;
            this.engineOsc.frequency.setTargetAtTime(pitch, this.audioCtx.currentTime, 0.1);
            this.engineGain.gain.setTargetAtTime(0.1 + Math.abs(p.speed) * 0.02, this.audioCtx.currentTime, 0.1);
        }
    },

    finishRace() {
        this.running = false;
        const totalTime = document.getElementById('timer').innerText;
        document.getElementById('menu').style.display = 'block';
        document.getElementById('menu-title').innerText = "FINISH!";
        document.getElementById('menu-score').innerText = `Final Time: ${totalTime}\nBest Lap: ${this.formatTime(this.bestLapTime)}`;
        this.playSound(880, 'sine', 0.5, 0.2);
        this.playSound(1100, 'sine', 0.8, 0.2);
    },

    formatTime(s) {
        if (s === Infinity) return "--:--.--";
        const mins = Math.floor(s / 60);
        const secs = Math.floor(s % 60);
        const ms = Math.floor((s % 1) * 100);
        return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(2, '0')}`;
    },

    updateCamera() {
        const relOffset = new THREE.Vector3(0, 5, -12);
        relOffset.applyAxisAngle(new THREE.Vector3(0, 1, 0), this.carPhys.angle);
        
        const targetPos = this.car.position.clone().add(relOffset);
        
        if (this.camMode === 'chase') {
            this.camera.position.lerp(targetPos, 0.1);
            this.camera.lookAt(this.car.position);
        } else {
            this.camera.position.lerp(new THREE.Vector3(this.car.position.x, 50, this.car.position.z), 0.05);
            this.camera.lookAt(this.car.position);
        }
    },

    updateUI() {
        document.getElementById('speedometer').innerText = `${Math.floor(Math.abs(this.carPhys.speed) * 20)} km/h`;
        document.getElementById('lap-info').innerText = `Lap ${Math.min(this.lap, this.totalLaps)}/${this.totalLaps}`;
        if (this.running) {
            const elapsed = (Date.now() - this.startTime) / 1000;
            document.getElementById('timer').innerText = this.formatTime(elapsed);
        }
        document.getElementById('best-lap').innerText = `Best: ${this.formatTime(this.bestLapTime)}`;

        // Minimap
        const ctx = this.mctx;
        ctx.clearRect(0,0,200,200);
        ctx.strokeStyle = "white";
        ctx.beginPath();
        this.trackPoints.forEach((p, i) => {
            const mx = 100 + p.x / 3;
            const mz = 100 + p.z / 3;
            if (i === 0) ctx.moveTo(mx, mz); else ctx.lineTo(mx, mz);
        });
        ctx.stroke();
        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(100 + this.car.position.x / 3, 100 + this.car.position.z / 3, 3, 0, Math.PI * 2);
        ctx.fill();
    },

    loop() {
        requestAnimationFrame(() => this.loop());
        const dt = 1/60;
        this.updatePhysics(dt);
        this.updateCamera();
        this.updateUI();
        this.renderer.render(this.scene, this.camera);
    }
};

window.addEventListener('resize', () => {
    game.camera.aspect = window.innerWidth / window.innerHeight;
    game.camera.updateProjectionMatrix();
    game.renderer.setSize(window.innerWidth, window.innerHeight);
});

game.init();
</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"basic","sessionId":"8620fae6-7b58-42b6-b0dc-79fda1a72bf7","tokensIn":1139,"tokensOut":6049,"tokensTotal":7188,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-14T21:56:30.205Z"} -->