← Rally results

Rally

GLM 5.3 Flash · full

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,090 bytes · SHA-256 9290bc2c89e0
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Dusty Ridge Rally</title>
<style>
  :root{ --hud:#fff; --accent:#ffb347; }
  *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
  html,body{width:100%;height:100%;overflow:hidden;background:#0a0d14;
    font-family:"Segoe UI",system-ui,sans-serif;color:var(--hud)}
  #wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
  /* Canvas keeps a 16:9 aspect box centered in the window */
  #stage{position:relative;width:100%;height:100%;max-width:100vw;max-height:100vh}
  canvas#game{display:block;width:100%;height:100%}
  /* ---------- HUD ---------- */
  .hud{position:absolute;pointer-events:none;text-shadow:0 2px 4px rgba(0,0,0,.7)}
  #speedo{right:2.5%;bottom:5%;text-align:right}
  #speedVal{font-size:min(9vh,64px);font-weight:800;line-height:.95;font-variant-numeric:tabular-nums}
  #speedUnit{font-size:min(2.6vh,16px);letter-spacing:3px;opacity:.8}
  #gearBox{font-size:min(3.4vh,22px);color:var(--accent);font-weight:700}
  #rpmBar{width:26vmin;max-width:220px;height:8px;background:rgba(255,255,255,.15);
    border-radius:4px;margin-top:6px;overflow:hidden}
  #rpmFill{height:100%;width:0;background:linear-gradient(90deg,#7CFC00,#ffd700,#ff4040);
    border-radius:4px;transition:none}
  #topLeft{left:2.5%;top:3%;font-size:min(3vh,20px);line-height:1.5;font-variant-numeric:tabular-nums}
  #lapLine b,#timeLine b{color:var(--accent)}
  #bestLap{opacity:.85;font-size:.8em}
  #minimapWrap{position:absolute;left:2.5%;bottom:4%;width:24vmin;height:24vmin;
    max-width:190px;max-height:190px;background:rgba(10,14,22,.45);
    border:1px solid rgba(255,255,255,.25);border-radius:10px;pointer-events:none}
  #minimap{width:100%;height:100%}
  #centerMsg{left:50%;top:32%;transform:translate(-50%,-50%);text-align:center;
    font-size:min(18vh,140px);font-weight:900;color:var(--accent);
    text-shadow:0 0 30px rgba(255,179,71,.6),0 4px 8px rgba(0,0,0,.8)}
  #subMsg{left:50%;top:46%;transform:translateX(-50%);font-size:min(3.4vh,22px);
    letter-spacing:2px;opacity:0}
  #offTrackWarn{left:50%;top:62%;transform:translateX(-50%);font-size:min(2.8vh,18px);
    color:#ff6b6b;letter-spacing:3px;opacity:0;transition:opacity .2s}
  /* ---------- Menus ---------- */
  .overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
    justify-content:center;background:rgba(6,9,16,.72);backdrop-filter:blur(4px);z-index:20}
  .overlay h1{font-size:min(9vh,58px);letter-spacing:4px;margin-bottom:.4em;
    color:var(--accent);text-shadow:0 4px 16px rgba(0,0,0,.8)}
  .overlay h2{font-size:min(5vh,30px);margin-bottom:.6em}
  .overlay p{margin:.25em 0;font-size:min(3vh,19px);opacity:.9}
  .btn{pointer-events:auto;cursor:pointer;margin:8px;padding:12px 38px;font-size:min(3.4vh,21px);
    font-weight:700;letter-spacing:2px;color:#111;background:var(--accent);border:none;
    border-radius:8px;box-shadow:0 4px 0 #b36f1e;transition:transform .08s}
  .btn:active{transform:translateY(3px);box-shadow:0 1px 0 #b36f1e}
  .btn.alt{background:#cfd8e3;box-shadow:0 4px 0 #7c8698}
  .hidden{display:none!important}
  #controlsHelp{position:absolute;right:2.5%;top:3%;text-align:right;font-size:min(2.2vh,13px);
    opacity:.65;line-height:1.55;pointer-events:none;text-shadow:0 1px 3px #000}
  /* ---------- Touch controls ---------- */
  #touchUI{position:absolute;inset:0;pointer-events:none;z-index:15;display:none}
  body.touch #touchUI{display:block}
  .tbtn{position:absolute;pointer-events:auto;width:15vmin;height:15vmin;max-width:84px;max-height:84px;
    border-radius:50%;background:rgba(255,255,255,.14);border:2px solid rgba(255,255,255,.35);
    display:flex;align-items:center;justify-content:center;font-size:min(5.5vmin,30px);
    font-weight:800;user-select:none;touch-action:none}
  .tbtn.on{background:rgba(255,179,71,.45)}
  #tLeft{left:4%;bottom:6%} #tRight{left:calc(4% + 17vmin);bottom:6%}
  #tGas{right:4%;bottom:6%} #tBrake{right:calc(4% + 17vmin);bottom:6%}
  #tHand{right:4%;bottom:calc(6% + 17vmin);font-size:min(4vmin,20px)}
</style>
</head>
<body>
<div id="wrap"><div id="stage">
  <canvas id="game"></canvas>

  <div class="hud" id="topLeft">
    <div id="lapLine">LAP <b id="lapNum">1</b> / <span id="totalLaps">3</span></div>
    <div id="timeLine">TIME <b id="curTime">0:00.00</b></div>
    <div id="bestLap">BEST —</div>
    <div id="lastLap">LAST —</div>
  </div>

  <div class="hud" id="speedo">
    <div id="gearBox">N</div>
    <div id="speedVal">0</div>
    <div id="speedUnit">KM/H</div>
    <div id="rpmBar"><div id="rpmFill"></div></div>
  </div>

  <div id="controlsHelp">WASD / Arrows drive · Space handbrake<br>C camera · P pause · R restart</div>

  <div class="hud" id="centerMsg"></div>
  <div class="hud" id="subMsg"></div>
  <div class="hud" id="offTrackWarn">OFF TRACK!</div>

  <div id="minimapWrap"><canvas id="minimap" width="200" height="200"></canvas></div>

  <div id="touchUI">
    <div class="tbtn" id="tLeft">◀</div><div class="tbtn" id="tRight">▶</div>
    <div class="tbtn" id="tBrake">▼</div><div class="tbtn" id="tGas">▲</div>
    <div class="tbtn" id="tHand">DRIFT</div>
  </div>

  <!-- Start screen -->
  <div class="overlay" id="startScreen">
    <h1>DUSTY RIDGE RALLY</h1>
    <p>3 laps · hills, dirt &amp; mud · beat your best time</p>
    <p style="opacity:.7;font-size:.85em">Stay on the asphalt — grass is slow, mud is worse.</p>
    <button class="btn" id="startBtn">START RACE</button>
  </div>

  <!-- Pause menu -->
  <div class="overlay hidden" id="pauseScreen">
    <h2>PAUSED</h2>
    <button class="btn" id="resumeBtn">RESUME</button>
    <button class="btn alt" id="restartBtn1">RESTART</button>
  </div>

  <!-- Finish screen -->
  <div class="overlay hidden" id="finishScreen">
    <h1>FINISH!</h1>
    <p>Total time: <b id="finalTime">—</b></p>
    <p>Best lap: <b id="finalBest">—</b></p>
    <p id="finalNote" style="color:var(--accent)"></p>
    <button class="btn" id="restartBtn2">RACE AGAIN</button>
  </div>
</div></div>

<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
'use strict';
/* =========================================================================
   DUSTY RIDGE RALLY — single-file Three.js rally racer
   Modules: Audio (WebAudio synth), Input, Track, Scenery, Car (physics+mesh),
            FX (particles/skids), CameraRig, HUD/UI, Game (state machine)
   ========================================================================= */
const Game = (() => {

/* ---------------- Config ---------------- */
const CFG = {
  laps: 3,
  trackWidth: 11,          // drivable half-width = width/2
  samples: 420,            // spline resolution for road mesh & lookups
  fogColor: 0xcfe3ef,
  fogNear: 90, fogFar: 520,
  gravity: 22,
};

/* Surface multipliers: grip, topSpeed */
const SURF = {
  road: {grip: 1.00, top: 1.00},
  grass:{grip: 0.42, top: 0.52},
  gravel:{grip:0.55, top: 0.66},
  mud:  {grip: 0.28, top: 0.35},
};

/* ---------------- Utilities ---------------- */
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
const lerp=(a,b,t)=>a+(b-a)*t;
const rand=(a,b)=>a+Math.random()*(b-a);
const TAU=Math.PI*2;

/* ================= AUDIO (all synthesized) ================= */
const Audio = (() => {
  let ctx=null, master=null, engOsc=null, engOsc2=null, engGain=null,
      skidSrc=null, skidGain=null, skidFilter=null, started=false;
  function ensure(){
    if(ctx) return;
    ctx = new (window.AudioContext||window.webkitAudioContext)();
    master = ctx.createGain(); master.gain.value=.55; master.connect(ctx.destination);
    // engine: two detuned saws through lowpass
    engGain = ctx.createGain(); engGain.gain.value=0;
    const lp = ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=900;
    engOsc  = ctx.createOscillator(); engOsc.type='sawtooth'; engOsc.frequency.value=60;
    engOsc2 = ctx.createOscillator(); engOsc2.type='square';  engOsc2.frequency.value=31;
    const g2 = ctx.createGain(); g2.gain.value=.4;
    engOsc.connect(lp); engOsc2.connect(g2); g2.connect(lp); lp.connect(engGain); engGain.connect(master);
    engOsc.start(); engOsc2.start();
    // skid/gravel: looping filtered noise
    const len = ctx.sampleRate*1.5, buf = ctx.createBuffer(1,len,ctx.sampleRate),
          d = buf.getChannelData(0);
    for(let i=0;i<len;i++) d[i]=Math.random()*2-1;
    skidSrc = ctx.createBufferSource(); skidSrc.buffer=buf; skidSrc.loop=true;
    skidFilter = ctx.createBiquadFilter(); skidFilter.type='bandpass';
    skidFilter.frequency.value=800; skidFilter.Q.value=.6;
    skidGain = ctx.createGain(); skidGain.gain.value=0;
    skidSrc.connect(skidFilter); skidFilter.connect(skidGain); skidGain.connect(master);
    skidSrc.start();
    started=true;
  }
  function resume(){ ensure(); if(ctx.state==='suspended') ctx.resume(); }
  // engine update: rpm 0..1, throttle 0..1
  function engine(rpm, load, vol){
    if(!started) return;
    const f = 48 + rpm*230;
    engOsc.frequency.setTargetAtTime(f, ctx.currentTime, .03);
    engOsc2.frequency.setTargetAtTime(f*.502, ctx.currentTime, .03);
    engGain.gain.setTargetAtTime(vol*(0.05+load*0.10+rpm*0.06), ctx.currentTime, .05);
  }
  function skid(amount, freq){
    if(!started) return;
    skidGain.gain.setTargetAtTime(clamp(amount,0,1)*.16, ctx.currentTime,.05);
    skidFilter.frequency.setTargetAtTime(freq||700, ctx.currentTime,.1);
  }
  function beep(freq,dur,type){
    if(!started) return;
    const o=ctx.createOscillator(), g=ctx.createGain();
    o.type=type||'square'; o.frequency.value=freq;
    g.gain.setValueAtTime(.25,ctx.currentTime);
    g.gain.exponentialRampToValueAtTime(.001,ctx.currentTime+dur);
    o.connect(g); g.connect(master); o.start(); o.stop(ctx.currentTime+dur);
  }
  function crash(mag){
    if(!started) return;
    const dur=.25, n=ctx.createBufferSource(),
          buf=ctx.createBuffer(1,ctx.sampleRate*dur,ctx.sampleRate),
          d=buf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*(1-i/d.length);
    n.buffer=buf;
    const f=ctx.createBiquadFilter(); f.type='lowpass'; f.frequency.value=300+mag*500;
    const g=ctx.createGain(); g.gain.value=clamp(mag,.1,1)*.6;
    n.connect(f); f.connect(g); g.connect(master); n.start();
  }
  function finishTone(){ if(!started)return;
    [523,659,784,1047].forEach((f,i)=>setTimeout(()=>beep(f,.35,'triangle'),i*160));
  }
  return {resume, engine, skid, beep, crash, finishTone};
})();

/* ================= INPUT ================= */
const Input = (() => {
  const keys={}, touch={left:0,right:0,gas:0,brake:0,hand:0};
  let camToggleCb=null;
  addEventListener('keydown',e=>{
    if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Space'].includes(e.code)) e.preventDefault();
    if(e.repeat) return;
    keys[e.code]=true;
    if(e.code==='KeyC'&&camToggleCb) camToggleCb();
    if(e.code==='KeyP') Game.togglePause();
    if(e.code==='KeyR') Game.restart();
  });
  addEventListener('keyup',e=>keys[e.code]=false);
  // touch buttons
  function bind(id,name){
    const el=document.getElementById(id);
    const on=e=>{e.preventDefault();touch[name]=1;el.classList.add('on');};
    const off=e=>{e.preventDefault();touch[name]=0;el.classList.remove('on');};
    el.addEventListener('touchstart',on,{passive:false});
    el.addEventListener('touchend',off);el.addEventListener('touchcancel',off);
    el.addEventListener('mousedown',on);el.addEventListener('mouseup',off);
    el.addEventListener('mouseleave',()=>{touch[name]=0;el.classList.remove('on');});
  }
  ['tLeft:left','tRight:right','tGas:gas','tBrake:brake','tHand:hand']
    .forEach(s=>{const[a,b]=s.split(':');bind(a,b);});
  return {
    get throttle(){return (keys.ArrowUp||keys.KeyW||touch.gas)?1:0;},
    get brake(){return (keys.ArrowDown||keys.KeyS||touch.brake)?1:0;},
    get steer(){return ((keys.ArrowLeft||keys.KeyA||touch.left)?1:0)-((keys.ArrowRight||keys.KeyD||touch.right)?1:0);},
    get hand(){return (keys.Space||touch.hand)?1:0;},
    setCamCb(cb){camToggleCb=cb;}
  };
})();

/* ================= TRACK =================
   Closed CatmullRom loop through control points with elevation.
   Builds road ribbon, shoulders, center dashes, checkpoint gates,
   and provides surface lookup (road/grass/mud) for physics. */
const Track = (() => {
  const pts = [
    [0,0],[60,-6],[120,4],[170,30],[195,80],[185,135],
    [140,165],[85,158],[40,180],[10,225],[-40,240],[-95,220],
    [-125,175],[-115,120],[-150,75],[-185,30],[-160,-25],[-105,-45],
    [-50,-30]
  ].map(p=>new THREE.Vector3(p[0], 0, p[1]));
  // elevation profile: gentle hills & dips added procedurally
  pts.forEach((p,i)=>{
    p.y = Math.sin(i/pts.length*TAU*2)*4 + Math.sin(i/pts.length*TAU*5)*1.8;
  });
  const curve = new THREE.CatmullRomCurve3(pts,true,'catmullrom',0.5);
  const HW = CFG.trackWidth/2;

  // sampled frames
  const N=CFG.samples;
  const centers=[], tangents=[], normals=[]; // normals = left vectors (x,z only)
  for(let i=0;i<N;i++){
    const t=i/N, c=curve.getPointAt(t), tg=curve.getTangentAt(t);
    tg.y=0; tg.normalize();
    centers.push(c); tangents.push(tg);
    normals.push(new THREE.Vector3(-tg.z,0,tg.x));
  }
  const centerYs = centers.map(c=>c.y);

  // mud pits: parameter positions + radius across the track
  const mudPits=[{t:.14,r:9},{t:.44,r:8},{t:.78,r:10}];
  const waterPit={t:.93,r:7};

  /* --- geometry builders --- */
  function ribbon(halfInner,halfOuter,colorFn,yOff){
    // builds a closed ribbon between two lateral offsets from centerline
    const g=new THREE.BufferGeometry(), pos=[], col=[], idx=[];
    const cA=new THREE.Color();
    for(let i=0;i<=N;i++){
      const k=i%N, c=centers[k], n=normals[k];
      pos.push(c.x-n.x*halfOuter, c.y+yOff, c.z-n.z*halfOuter);
      pos.push(c.x+n.x*halfOuter, c.y+yOff, c.z+n.z*halfOuter);
      const c1=colorFn(k/N,0), c2=colorFn(k/N,1);
      cA.copy(c1); col.push(cA.r,cA.g,cA.b);
      cA.copy(c2); col.push(cA.r,cA.g,cA.b);
      if(i<N){const a=i*2; idx.push(a,a+1,a+2, a+1,a+3,a+2);}
    }
    g.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));
    g.setAttribute('color',new THREE.Float32BufferAttribute(col,3));
    g.setIndex(idx); g.computeVertexNormals();
    return new THREE.Mesh(g,new THREE.MeshLambertMaterial({vertexColors:true}));
  }

  function build(scene){
    const grp=new THREE.Group();
    // ground plane (big, follows nothing — static under whole map)
    const gg=new THREE.CircleGeometry(700,48);
    const gm=new THREE.MeshLambertMaterial({color:0x6f9e4f});
    const ground=new THREE.Mesh(gg,gm); ground.rotation.x=-Math.PI/2;
    ground.position.y=-0.35; ground.receiveShadow=true; grp.add(ground);

    // grass shoulder (slightly wider than road, darker green->edge blend)
    grp.add(ribbon(HW+.2, HW+4.5,(u)=>new THREE.Color(0x74924f).lerp(new THREE.Color(0x6f9e4f),Math.random()*.3),0.02));
    // gravel verge
    grp.add(ribbon(HW-.1, HW+.35,(u)=>new THREE.Color().setHSL(.09,.28,.42+Math.random()*.05),0.04));
    // asphalt road with subtle variation + edge tinting
    grp.add(ribbon(0,HW,(u,side)=>{
      const base=new THREE.Color(0x4a4a4e).offsetHSL(0,0,(Math.random()-.5)*.035);
      if(side===1&&u>0) {} // keep simple
      return base;
    },0.06));
    // center dashes: thin bright quads every few samples
    const dashGeo=new THREE.PlaneGeometry(0.35,2.2); dashGeo.rotateX(-Math.PI/2);
    const dashMat=new THREE.MeshBasicMaterial({color:0xf2ead8});
    const dashes=new THREE.InstancedMesh(dashGeo,dashMat,N/6|0);
    const m4=new THREE.Matrix4(), q=new THREE.Quaternion(), up=new THREE.Vector3(0,1,0);
    let di=0;
    for(let i=0;i<N;i+=6){
      const c=centers[i],tg=tangents[i];
      q.setFromAxisAngle(up,Math.atan2(tg.x,tg.z));
      m4.compose(new THREE.Vector3(c.x,c.y+.09,c.z),q,new THREE.Vector3(1,1,1));
      dashes.setMatrixAt(di++,m4);
    }
    dashes.count=di; grp.add(dashes);

    // mud pits (dark brown discs) + one shallow "water" pit (blue-brown)
    const discGeo=new THREE.CircleGeometry(1,20); discGeo.rotateX(-Math.PI/2);
    mudPits.forEach(mp=>{
      const c=curve.getPointAt(mp.t);
      const m=new THREE.Mesh(discGeo,new THREE.MeshLambertMaterial({color:0x513a22}));
      m.scale.set(mp.r,1,mp.r*.8); m.position.set(c.x,c.y+.08,c.z); grp.add(m);
    });
    const wc=curve.getPointAt(waterPit.t);
    const wm=new THREE.Mesh(discGeo,new THREE.MeshPhongMaterial({color:0x4a6a8a,shininess:90}));
    wm.scale.set(waterPit.r,1,waterPit.r*.85); wm.position.set(wc.x,wc.y+.07,wc.z); grp.add(wm);

    scene.add(grp);
  }

  /* --- spatial lookup: nearest sample index for a position --- */
  function nearest(x,z,hint){
    // search around hint index for speed; fall back to coarse scan
    let best=-1,bd=Infinity;
    if(hint>=0){
      for(let o=-14;o<=14;o++){
        const k=((hint+o)%N+N)%N, dx=x-centers[k].x, dz=z-centers[k].z,
              d=dx*dx+dz*dz;
        if(d<bd){bd=d;best=k;}
      }
      if(bd<40*40) return {k:best,dist:Math.sqrt(bd)};
    }
    for(let k=0;k<N;k+=4){
      const dx=x-centers[k].x,dz=z-centers[k].z,d=dx*dx+dz*dz;
      if(d<bd){bd=d;best=k;}
    }
    return {k:best,dist:Math.sqrt(bd)};
  }

  /* surface type at world pos: returns {type, lat} lat=lateral offset signed */
  function surfaceAt(x,z,hint){
    const {k,dist}=nearest(x,z,hint);
    const c=centers[k],n=normals[k];
    const lat=(x-c.x)*n.x+(z-c.z)*n.z;
    let type='grass';
    if(dist<HW) type='road';
    else if(dist<HW+2.2) type='gravel';
    // mud?
    for(const mp of mudPits.concat([waterPit])){
      const mc=curve.getPointAt(mp.t);
      const dx=x-mc.x,dz=z-mc.z;
      if(dx*dx/(mp.r*mp.r)+dz*dz/(mp.r*mp.r*.8)<1){type=mp===waterPit?'mud':'mud';break;}
    }
    return {type,lat,k};
  }
  function pointAt(t){return curve.getPointAt(((t%1)+1)%1);}
  return {build,surfaceAt,pointAt,curve,centers,normals,N,HW,mudPits,waterPit};
})();