# BeyondAO12 - 

 ██████╗  ██████╗ ███╗   ███╗███████╗██████╗ ██╗████████╗ █████╗ 
██╔════╝ ██╔═══██╗████╗ ████║██╔════╝██╔══██╗██║╚══██╔══╝██╔══██╗
██║  ███╗██║   ██║██╔████╔██║█████╗  ██████╔╝██║   ██║   ███████║
██║   ██║██║   ██║██║╚██╔╝██║██╔══╝  ██╔══██╗██║   ██║   ██╔══██║
╚██████╔╝╚██████╔╝██║ ╚═╝ ██║███████╗██║  ██║██║   ██║   ██║  ██║
 ╚═════╝  ╚═════╝ ╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝   ╚═╝  ╚═╝

---

# 🏍️ BeyondAO12 — CONTEXT CHECKPOINT
# ============================================================
# Este archivo contiene el ESTADO COMPLETO del proyecto.
# Pégalo en una nueva ventana de Kimi para continuar inmediatamente.
# Fecha: 2026-09-06 | Fase: FUNCIONAL COMPLETO
# ============================================================

## 1. RESUMEN EJECUTIVO

- **Qué es:** Juego de carreras pseudo-3D estilo Road Rash para navegador.
- **Stack:** HTML5 Canvas 2D, JavaScript vanilla (modular), CSS3.
- **Propósito final:** Juego arcade de carreras con combate básico (patadas).
- **Estado actual:** ✅ FUNCIONAL — Menú principal + juego completo + sistema de carreras con rivales.
- **Próximo paso:** Mejoras visuales, optimización o expansión.

## 2. ESTRUCTURA DE ARCHIVOS

```
road-rash/
├── index.html          # Menú principal con botón START GAME
├── game.html           # Página del juego con canvas
├── css/
│   ├── reset.css       # Reset CSS + fuentes (bariol_regular, PerfectDark)
│   └── style.css       # Estilos del menú (fondo roadrash.png)
├── js/
│   ├── variables.js    # Configuración global, constantes, sprites, audio
│   ├── math.js         # Funciones matemáticas (interpolación, proyección, límites)
│   ├── gameLoad.js     # Motor principal: Game.run(), carga de imágenes, teclado
│   ├── render.js       # Renderizado: polígonos, segmentos, sprites, jugador
│   ├── dashboard.js    # Velocímetro, countdown, ranking
│   └── script.js       # Lógica del juego: update(), render(), road builder, IA
├── images/
│   ├── roadrash.png    # Fondo del menú
│   ├── background.png  # Fondo del juego (cielo, colinas, árboles)
│   └── sprites.png     # Sprites de vehículos, objetos y jugador
├── fonts/
│   ├── bariol_regular-webfont.*  # Fuente principal
│   └── pdark.ttf       # Fuente para títulos
└── music/
    ├── bike_forward.mp3  # Sonido aceleración
    ├── kick.mp3          # Sonido patada
    └── bike_crash.mp3    # Sonido choque
```

## 3. DECISIONES DE DISEÑO CRÍTICAS (NO CAMBIAR SIN DISCUTIR)

### 3.1 Arquitectura modular
Cada archivo JS tiene una responsabilidad específica:
- `variables.js` → Configuración y estado global
- `math.js` → Utilidades matemáticas (`AllFn` namespace)
- `gameLoad.js` → Motor principal (`Game` namespace)
- `render.js` → Renderizado (`Render` namespace)
- `dashboard.js` → Elementos de UI (velocímetro, countdown, ranking)
- `script.js` → Lógica de juego (update, render, construcción de pista)

### 3.2 Sistema de proyección pseudo-3D
- **Mundo 3D → pantalla 2D** mediante `AllFn.project()` con escala = cameraDepth / z
- **Curvas** se simulan acumulando offset lateral `dx` por segmento
- **Colinas** modifican la coordenada Y del mundo
- **Dibujo de atrás hacia adelante** (painter's algorithm) con `maxy`

### 3.3 Sprites en tileset
- Todas las imágenes están en un solo archivo `sprites.png`
- Cada sprite se define con `{ x, y, w, h }`
- Escala global: `SPRITES.SCALE = 0.1 * (1/SPRITES.PLAYER_STRAIGHT.w)`

### 3.4 IA de rivales
- **Bikes (8)** y **Cars (30)** con comportamiento de evasión
- Los rivales cambian de carril para evitar colisiones con el jugador y entre ellos
- Función `handleBikeDirection()` y `handleCarDirection()` con lookahead de 20 segmentos

### 3.5 Sistema de combate básico
- **Z** = patada izquierda (empuja rival a la izquierda)
- **C** = patada derecha (empuja rival a la derecha)
- Reduce velocidad del rival al impactar
- Sonido de patada (`kick.mp3`)

### 3.6 Sistema de ranking
- Calcula posición del jugador entre todos los rivales
- Basado en la posición Z de cada vehículo
- Se actualiza en cada frame con `updatePlayerPosition()`

### 3.7 Countdown inicial
- Cuenta regresiva: 3, 2, 1, GO!
- El juego comienza cuando `gameStart = true`
- Mientras tanto, el juego renderiza pero no actualiza física

### 3.8 Game Over
- Cuando el jugador cruza la línea de meta (`crossFinish = true`)
- La velocidad se establece a 0
- Se muestra "GAME OVER!" en pantalla

## 4. PARÁMETROS QUE FUNCIONAN (probados)

| Parámetro | Valor | Notas |
|---|---|---|
| width | 1280 | Ancho del canvas |
| height | 720 | Alto del canvas |
| fps | 60 | Frames por segundo |
| step | 1/60 | Delta time fijo |
| maxSpeed | segmentLength/step | Velocidad máxima |
| accel | maxSpeed/5 | Aceleración |
| breaking | -maxSpeed | Frenado |
| decel | -maxSpeed/5 | Rozamiento al soltar |
| centrifugal | 0.3 | Fuerza centrífuga en curvas |
| roadWidth | 2000 | Ancho de carretera |
| segmentLength | 200 | Longitud de segmento |
| drawDistance | 400 | Segmentos visibles |
| cameraHeight | 1000 | Altura de cámara |
| lanes | 2 | Número de carriles |
| fieldOfView | 100 | Campo de visión (grados) |
| totalBikes | 8 | Número de motos rivales |
| totalCars | 30 | Número de autos rivales |
| offRoadLimit | maxSpeed/4 | Límite fuera de pista |
| offRoadDecel | -maxSpeed/2 | Desaceleración fuera de pista |

## 5. CONTROLES

| Tecla | Acción |
|---|---|
| ↑ / W | Acelerar |
| ↓ / S | Frenar |
| ← / A | Girar izquierda |
| → / D | Girar derecha |
| Z | Patada izquierda |
| C | Patada derecha |
| Q / E | (Reservado) |

## 6. SPRITES DEFINIDOS

| Sprite | Posición | Tamaño |
|---|---|---|
| PLAYER_STRAIGHT | (0, 0) | 21×50 |
| PLAYER_LEFT | (27, 0) | 25×50 |
| PLAYER_RIGHT | (58, 0) | 25×50 |
| PLAYER_KICK_LEFT | (179, 0) | 34×50 |
| PLAYER_KICK_RIGHT | (213, 0) | 34×50 |
| BIKE01 | (111, 0) | 21×50 |
| BIKE02 | (132, 0) | 23×50 |
| BIKE03 | (155, 0) | 24×50 |
| CAR01 | (260, 0) | 86×70 |
| CAR02 | (179, 65) | 82×75 |
| GROUND | (0, 103) | 79×13 |
| BOAT01 | (354, 0) | 118×45 |
| BOAT02 | (497, 0) | 89×37 |
| BOAT03 | (354, 82) | 156×41 |
| LIGHTHOUSE | (541, 54) | 28×69 |
| BUILDING_LEFT | (1148, 3) | 558×965 |
| BUILDING_RIGHT | (549, 158) | 549×740 |

## 7. ESTRUCTURA DE LA PISTA

La pista se construye con `addRoad()` que genera segmentos con:
- **Enter** (entrada a la curva/colina)
- **Hold** (mantenimiento)
- **Leave** (salida)

### Layout actual:
1. `addStraight(25/2)` — Recta corta
2. `addStraight()` — Recta
3. `addStraight()` — Recta
4. `addStraight()` — Recta
5. `addHill(25, 20)` — Colina suave
6. `addLowRollingHills()` — Colinas onduladas
7. `addCurve(50, 4, 20)` — Curva a la derecha
8. `addLowRollingHills()` — Colinas onduladas
9. `addCurve(100, 4, 40)` — Curva larga a la derecha
10. `addStraight()` — Recta
11. `addCurve(100, -4, 40)` — Curva larga a la izquierda
12. `addHill(100, 60)` — Colina alta
13. `addCurve(100, 4, -20)` — Curva con bajada
14. `addHill(100, -40)` — Bajada
15. `addStraight()` — Recta
16. `addDownhillToEnd()` — Bajada hasta meta

### Marcadores:
- **START** en segmento 2-3 (color blanco)
- **FINISH** en últimos 3 segmentos (color negro con rumble rojo)

## 8. CÓDIGO COMPLETO DE CADA ARCHIVO

---

### index.html
```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Road Rash Menu</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" type="text/css" media="screen" href="css/reset.css" />
  <link rel="stylesheet" type="text/css" media="screen" href="css/style.css" />

</head>
<body>

    <a href = "game.html"><button class="start-game">START GAME</button></a>

</body>
</html>
```

---

### game.html
```html
<!DOCTYPE html>
<html>
<head>
	<title>Road-Rash</title>
	<link rel="stylesheet" type="text/css" href="css/reset.css">
</head>
<body>
 <canvas id="canvas" style="border: 1px solid green; margin-left: 7%; margin-right:7%;"></canvas>



	<script src="js/variables.js"></script>
	<script src="js/math.js"></script>
	<script src="js/gameLoad.js"></script>
	<script src="js/render.js"></script>
	<script src="js/dashboard.js"></script>
	<script src="js/script.js"></script>


</body>
</html>
```

---

### css/reset.css
```css
*{
  margin: 0;
  padding: 0;
}

@font-face {
	font-family: 'bariol_regular';
	src: url('../fonts/bariol_regular-webfont.eot'),
			 url('../fonts/bariol_regular-webfont.woff') format('woff'),
			 url('../fonts/bariol_regular-webfont.woff2') format('woff2'),
			 url('../fonts/bariol_regular-webfont.ttf') format('truetype');;
	font-weight: normal;
	font-style: normal;
}

@font-face {
  font-family: 'PerfectDark';
  src: url("../fonts/pdark.ttf");
}

*{

	font-family: 'bariol_regular';
}
```

---

### css/style.css
```css
body{
background: url('../images/roadrash.png');
background-size: 1280px 720px;
background-repeat: no-repeat;
/* background-position:center; */
}

button {
  border: none;
 background-color: #FF7373;
 box-shadow: 0px 5px 0px 0px #CE4646;
 outline: none;
 border-radius: 5px;
 padding: 10px 15px;
 font-size: 22px;
 text-decoration: none;
 /* margin: 20px; */
 margin-top: 100px;
 margin-left: 1120px;
 color: #fff;
 /* position: relative; */
 display: inline-block;
 cursor: pointer;

}

button:active {
 transform: translate(0px, 5px);
 box-shadow: 0px 1px 0px 0px;
}
```

---

### js/variables.js
```javascript
var bikeForward = new Audio('./music/bike_forward.mp3');
var kick = new Audio('./music/kick.mp3');
var crash = new Audio('./music/bike_crash.mp3');


var playerFinish = false ;
var crossFinish = false;
var enemyFinish= [];

var lastPosition = 0;

var playerPosition = 0;
var currentPosition=0;



var fps           = 60;
var step          = 1/fps;
var width         = 1280;
var height        = 720;
var centrifugal   = 0.3;
var offRoadDecel  = 0.99;
var skySpeed      = 0.001;
var hillSpeed     = 0.002;
var treeSpeed     = 0.003;
var skyOffset     = 0;
var hillOffset    = 0;
var treeOffset    = 0;
var segments      = [];

var cars           = [];
var totalCars = 30;

var bikes         =[];
var totalBikes = 8;



var background    = null;
var sprites       = null;
var resolution    = null;
var roadWidth     = 2000;
var segmentLength = 200;
var rumbleLength  = 3;
var trackLength   = null;
var lanes         = 2;
var fieldOfView   = 100;
var cameraHeight  = 1000;
var cameraDepth   = null;
var drawDistance  = 400;
var playerX       = 0;
var playerZ       = null;

var position      = 0;
var speed         = 0;
var maxSpeed      = segmentLength/step;
var accel         =  maxSpeed/5;
var breaking      = -maxSpeed;
var decel         = -maxSpeed/5;
var offRoadDecel  = -maxSpeed/2;
var offRoadLimit  =  maxSpeed/4;

var keyLeft       = false;
var keyRight      = false;
var keyFaster     = false;
var keySlower     = false;
var keyQ = false;
var keyE = false;
var keyZ = false;
var keyC = false;

var initialCD = false;


var countdown = 3;
var gameStart = false;
var gameOver = false;
var gmOv = 0;
// var crossFinish = false;

var rank = 0 ;
var KEY = {
  LEFT:  37,
  UP:    38,
  RIGHT: 39,
  DOWN:  40,
  A:     65,
  D:     68,
  S:     83,
  W:     87,
  Q:     81,
  E:     69,
  Z:     90,
  C:     67,
};

var COLORS = {
  SKY:  '#72D7EE',
  TREE: '#005108',
  LIGHT:  { road: '#696969',  rumble: 'white', lane: 'white'},
  DARK:   { road: '#696969',  rumble: 'grey' },
  START:  { road: 'white',   grass: 'white',   rumble: 'white'},
  FINISH: { road: 'black',   grass: 'black',   rumble: 'red'}
};

var BACKGROUND = {
  HILLS: { x:   10, y:   5, w: 1280, h: 480 },
  SKY:   { x:   5, y: 495, w: 1280, h: 480 },
  TREES: { x:   5, y: 985, w: 1280, h: 480 }
};

var SPRITES = {
  BIKE01: {x: 111,y:  0,w:  21, h:50},
  BIKE02: {x: 132,y:  0,w:  23, h:50},
  BIKE03: {x: 155,y:  0,w:  24, h:50},

  GROUND:{x:  0,y:  103,w:  79,h: 13},

  BOAT01: {x:354,y:0,w:118,h:45},
  BOAT02: {x:497,y:0,w:89,h:37},
  BOAT03: {x:354,y:82,w:156,h:41},

  LIGHTHOUSE:{x:541,y:54,w:28,h:69},


  CAR01:  {x: 260,y:  0,w:  86,h: 70},
  CAR02:  {x: 179,y:  65,w: 82,h: 75},

  BUILDING_LEFT: {x:1148, y: 3, w: 558, h: 965},
  BUILDING_RIGHT: {x:549, y:158,w: 549, h: 740},

  PLAYER_KICK_LEFT:       { x:  179, y:  0, w:   34,h:  50 },
  PLAYER_KICK_RIGHT:      { x:  213, y:  0, w:   34,h:  50 },
  PLAYER_LEFT:            { x:  27 , y:  0, w:   25,h:  50 },
  PLAYER_STRAIGHT:        { x:  0  , y:  0, w:   21,h:  50 },
  PLAYER_RIGHT:           { x:  58 , y:  0, w:   25,h:  50 }
};

SPRITES.SCALE = 0.1 * (1/SPRITES.PLAYER_STRAIGHT.w) //scaling the images

SPRITES.CARS       = [SPRITES.CAR01, SPRITES.CAR02];
SPRITES.BIKES      = [SPRITES.BIKE01, SPRITES.BIKE02, SPRITES.BIKE03];
SPRITES.SHIPS      = [SPRITES.BOAT01,SPRITES.BOAT02,SPRITES.BOAT03];
SPRITES.BUILDINGS  = [SPRITES.BUILDING_LEFT,SPRITES.BUILDING_RIGHT];


var getOn = {
  get:  function(id){
    return ((id instanceof HTMLElement) || (id === document)) ? id : document.getElementById(id);
  },
  on:   function(ele, type, fn, capture) {
    getOn.get(ele).addEventListener(type, fn, capture);
  },
}

  var canvas        = getOn.get('canvas');
  var ctx           = canvas.getContext('2d');

  // var degrees = 0;
  // var new_degrees = 0;
  // var difference = 0;
  var color ;
  var bgcolor = "#222";
  var text;
```

---

### js/math.js
```javascript
//mathematical calculations

var AllFn = {

  timestamp:function(){
    return new Date().getTime();
  },

  toInt:function(obj, def){
    if (obj !== null) {
     var x = parseInt(obj, 10);
     if (!isNaN(x))
      return x;
    }
      return AllFn.toInt(def, 0);
  },

  //to limit values
  limit:function(value, min, max){
    return Math.max(min, Math.min(value, max));
  },
  randomInt:function(min, max){
    return Math.round(AllFn.interpolate(min, max, Math.random()));
  },
  randomChoice:function(options){
    return options[AllFn.randomInt(0, options.length-1)];
  },
  percentRemaining: function(n, total){
    return (n%total)/total;
  },

  //acceleration
  accelerate:function(v, accel, dt){
    return v + (accel * dt);
  },

  //for smooth transition in curves
  interpolate:function(a,b,percent){
    return a + (b-a)*percent;
  },
  transIn:function(a,b,percent){
    return a + (b-a)*Math.pow(percent,2);
  },
  transInOut:function(a,b,percent){ return a + (b-a)*((-Math.cos(percent*Math.PI)/2) + 0.5);
  },

  increase:  function(start, increment, max) {
    var result = start + increment;
    while (result >= max)
      result -= max;
    while (result < 0)
      result += max;
    return result;
  },

  //projection
  project: function(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth) {
    p.camera.x     = (p.world.x || 0) - cameraX;
    p.camera.y     = (p.world.y || 0) - cameraY;
    p.camera.z     = (p.world.z || 0) - cameraZ;
    p.screen.scale = cameraDepth/p.camera.z;
    p.screen.x     = Math.round((width/2)  + (p.screen.scale * p.camera.x  * width/2));
    p.screen.y     = Math.round((height/2) - (p.screen.scale * p.camera.y  * height/2));
    p.screen.w     = Math.round(             (p.screen.scale * roadWidth   * width/2));
  },

//for collision avoidance
  overlap: function(x1, w1, x2, w2, percent) {
    var half = (percent || 1)/2;
    var min1 = x1 - (w1*half);
    var max1 = x1 + (w1*half);
    var min2 = x2 - (w2*half);
    var max2 = x2 + (w2*half);
    return ! ((max1 < min2) || (min1 > max2));
  }

}
```

---

### js/gameLoad.js
```javascript
//game start/loop

var Game = {



  run: function(options) {

    Game.loadImages(options.images, function(images) {

      options.ready(images);

      Game.setKeyListener(options.keys);

      var canvas = options.canvas,
          update = options.update,
          render = options.render,
          step   = options.step,
          now    = null,
          last   = AllFn.timestamp(),
          dt     = 0,
          gdt    = 0;

// console.log(last);
      function frame() {
        now = AllFn.timestamp();

        if(gameStart){



          dt  = Math.min(1, (now - last) / 1000);
          gdt = gdt + dt;
          while (gdt > step) {
            gdt = gdt - step;
            update(step);
          }
          render();
          last = now;
          if(speed == 0 && crossFinish){
            ctx.fillStyle = "black";
            ctx.font = "80px PerfectDark";
            text_width= ctx.measureText("GAME OVER!").width;
            ctx.fillText("GAME OVER!",1280/2 - text_width,320);
            // speed=AllFn.accelerate(speed,breaking,dt);
          }
        }
        else{

          if(now - last >= 1000){
            render();
            if(countdown == 0){
              displayCountdown("GO!");
              gameStart = true;
            }
            else  {
              displayCountdown(countdown);
              countdown--;

            }
            last = now;
          }
        }
        requestAnimationFrame(frame, canvas);
      }
      frame();
      });
  },

  loadImages: function(names, callback) {
    var result = [];
    var count  = names.length;

    var onload = function() {
      if (--count == 0)
        callback(result);
    };

    for(var n = 0 ; n < names.length ; n++) {
      var name = names[n];
      result[n] = document.createElement('img');
      getOn.on(result[n], 'load', onload);
      result[n].src = "images/" + name + ".png";
    }
  },

  //---------------------------------------------------------------------------

  setKeyListener: function(keys) {
    var onkey = function(keyCode, mode) {
      var n, k;
      for(n = 0 ; n < keys.length ; n++) {
        k = keys[n];
        k.mode = k.mode || 'up';
        if ((k.key == keyCode) || (k.keys && (k.keys.indexOf(keyCode) >= 0))) {
          if (k.mode == mode) {
            k.action.call();
          }
        }
      }
    };

    getOn.on(document, 'keydown', function(ev) { onkey(ev.keyCode, 'down'); } );
    getOn.on(document, 'keyup',   function(ev) { onkey(ev.keyCode, 'up');   } );
  },

}
```

---

### js/render.js
```javascript
//render


var Render = {

  polygon: function(ctx, x1, y1, x2, y2, x3, y3, x4, y4, color) {
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.lineTo(x3, y3);
    ctx.lineTo(x4, y4);
    ctx.closePath();
    ctx.fill();
  },

  segment: function(ctx, width, lanes, x1, y1, w1, x2, y2, w2, color,sprites) {

    var r1 = Render.rumbleWidth(w1, lanes),
        r2 = Render.rumbleWidth(w2, lanes),
        l1 = Render.laneMarkerWidth(w1, lanes),
        l2 = Render.laneMarkerWidth(w2, lanes),
        lanew1, lanew2, lanex1, lanex2, lane;

    var gnd=SPRITES.GROUND;
    ctx.drawImage(sprites,gnd.x,gnd.y,gnd.w,gnd.h,0, y2, width, y1 - y2);

    Render.polygon(ctx, x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, color.rumble);
    Render.polygon(ctx, x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, color.rumble);
    Render.polygon(ctx, x1-w1,    y1, x1+w1, y1, x2+w2, y2, x2-w2,    y2, color.road);

    if (color.lane) {
      lanew1 = w1*2/lanes;
      lanew2 = w2*2/lanes;
      lanex1 = x1 - w1 + lanew1;
      lanex2 = x2 - w2 + lanew2;
      for(lane = 1 ; lane < lanes ; lanex1 += lanew1, lanex2 += lanew2, lane++)
        Render.polygon(ctx, lanex1 - l1/2, y1, lanex1 + l1/2, y1, lanex2 + l2/2, y2, lanex2 - l2/2, y2, color.lane);
    }

  },

  background: function(ctx, background, width, height, layer, rotation, offset) {

    rotation = rotation || 0;
    offset   = offset   || 0;

    var imageW = layer.w/2;
    var imageH = layer.h;

    var sourceX = layer.x + Math.floor(layer.w * rotation);
    var sourceY = layer.y
    var sourceW = Math.min(imageW, layer.x+layer.w-sourceX);
    var sourceH = imageH;

    var destX = 0;
    var destY = offset;
    var destW = Math.floor(width * (sourceW/imageW));
    var destH = height;

    ctx.drawImage(background, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
    if (sourceW < imageW)
      ctx.drawImage(background, layer.x, sourceY, imageW-sourceW, sourceH, destW-1, destY, width-destW, destH);
  },



  sprite: function(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY, clipY) {

    var destW  = (sprite.w * scale * width/2) * (SPRITES.SCALE * roadWidth);
    var destH  = (sprite.h * scale * width/2) * (SPRITES.SCALE * roadWidth);

    destX = destX + (destW * (offsetX || 0));
    destY = destY + (destH * (offsetY || 0));

    var clipH = clipY ? Math.max(0, destY+destH-clipY) : 0;
    if (clipH < destH)
      ctx.drawImage(sprites, sprite.x, sprite.y, sprite.w, sprite.h - (sprite.h*clipH/destH), destX, destY, destW, destH - clipH);

  },

  //---------------------------------------------------------------------------

  player: function(ctx, width, height, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer) {

    var bounce = (1.5 * Math.random() * speedPercent * resolution) * AllFn.randomChoice([-1,1]);
    var sprite;
    if (steer < 0)
      sprite =  SPRITES.PLAYER_LEFT;
    else if (steer > 0)
      sprite = SPRITES.PLAYER_RIGHT;
    else
      sprite =  SPRITES.PLAYER_STRAIGHT;

    if(keyZ){sprite = SPRITES.PLAYER_KICK_LEFT;}
    else if (keyC){ sprite = SPRITES.PLAYER_KICK_RIGHT;}

    Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY + bounce, -0.5, -1);
  },


  rumbleWidth:     function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(6,  2*lanes); },
  laneMarkerWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(32, 8*lanes); }

}
```

---

### js/dashboard.js
```javascript
function speedoMeter(sp){


  ctx.beginPath();
  ctx.strokeStyle = bgcolor;
  ctx.lineWidth = 30;
  ctx.arc(130, 130, 100, -Math.PI, 0, false);
  ctx.stroke();


  var cp =5 * Math.round(sp/500);

 if(cp <100)
    color = "green";
  else
    color = "red";


  var radians = cp * Math.PI / 180  * 1.5;
  radians -= Math.PI;
  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.lineWidth = 30;
  ctx.arc(130, 130, 100, -Math.PI ,radians  , false);
  ctx.stroke();



  //text part
  ctx.fillStyle = color;
  // ctx.font = "50px bariol_regular";
  ctx.font = "50px PerfectDark";
  text_width = ctx.measureText(cp).width;
  ctx.fillText(cp, 130 - text_width/2, 125);

  ctx.fillStyle = "black";
  ctx.font = "30px bariol_regular";
  text_width = ctx.measureText("MPH").width;
  ctx.fillText("MPH",130 - text_width/2,75);
}


function displayCountdown(c){

  ctx.fillStyle = "black";
  ctx.font = "80px PerfectDark";
  text_width= ctx.measureText(c).width;
  ctx.fillText(c,1280/2 - text_width,300,500);

}


function displayRank(a,b){
  // console.log(a+"/"+b);
ctx.fillStyle = "orange";
ctx.font = "80px PerfectDark";

ctx.fillText(a+"/"+b,width/2-80,145);
}
```

---

### js/script.js
```javascript
//update function
function update(dt) {


  var n, car, carW,bike,bikeW,sprite, spriteW;
  var playerSegment = findSegment(position+playerZ);
  var speedPercent  = speed/maxSpeed;
  var dx            = dt * 2 * speedPercent;
  // var unow = AllFn.timestamp();
  var playerW       = SPRITES.PLAYER_STRAIGHT.w * SPRITES.SCALE;

  // var startPosition = position;

  position = AllFn.increase(position, dt * speed, trackLength);
  currentPosition += dt * speed;

  if (currentPosition > trackLength) {
    crossFinish = true;
  }

  skyOffset  = AllFn.increase(skyOffset,  skySpeed  * playerSegment.curve * speedPercent, 1);
  hillOffset = AllFn.increase(hillOffset, hillSpeed * playerSegment.curve * speedPercent, 1);
  treeOffset = AllFn.increase(treeOffset, treeSpeed * playerSegment.curve * speedPercent, 1);

// console.log(countdown);

  if(!crossFinish){

    updateBikes(dt,playerSegment, playerW);
    updateCars(dt, playerSegment,playerW);

    updatePlayerPosition();

    if (keyLeft)
      playerX = playerX - dx;
    else if (keyRight)
      playerX = playerX + dx;

    playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);

    if (keyFaster){
      bikeForward.play();
      // bikeForward.loop = true;
      speed = AllFn.accelerate(speed, accel, dt);


    }
    else if (keySlower){
      speed = AllFn.accelerate(speed, breaking, dt);
      bikeForward.pause();
      }
    else
      speed = AllFn.accelerate(speed, decel, dt);
  }
  else{
    speed = 0;
    // ctx.fillStyle = "black";
    // ctx.font = "80px PerfectDark";
    // text_width= ctx.measureText("GAME OVER!").width;
    // ctx.fillText("GAME OVER!",1280/2 - text_width,320);
    // speed=AllFn.accelerate(speed,breaking,dt);
  }



  if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
    speed = AllFn.accelerate(speed, offRoadDecel, dt);

  if ((playerX < -1) || (playerX > 1)) {

    if (speed > offRoadLimit)
      speed = AllFn.accelerate(speed, offRoadDecel, dt);

    for(n = 0 ; n < playerSegment.sprites.length ; n++) {
      sprite  = playerSegment.sprites[n];
      spriteW = sprite.source.w * SPRITES.SCALE;
      if (AllFn.overlap(playerX, playerW, sprite.offset + spriteW/2 * (sprite.offset > 0 ? 1 : -1), spriteW)) {
        crash.play();
        speed = maxSpeed/5;
        position = AllFn.increase(playerSegment.p1.world.z, -playerZ, trackLength);
        break;
      }
    }
  }


  for(n = 0 ; n < playerSegment.cars.length ; n++) {
    car  = playerSegment.cars[n];
    carW = car.sprite.w * SPRITES.SCALE;
    if (speed > car.speed) {
      if (AllFn.overlap(playerX, playerW, car.offset, carW, 0.8)) {
        crash.play();
        speed    = car.speed * (car.speed/speed);
        position = AllFn.increase(car.z, -playerZ, trackLength);
        break;
      }
    }
  }


  for(n = 0 ; n < playerSegment.bikes.length ; n++) {
    bike  = playerSegment.bikes[n];
    bikeW = bike.sprite.w * SPRITES.SCALE;
    if (speed > bike.speed) {
      if (AllFn.overlap(playerX, playerW, bike.offset, bikeW, 0.8)) {

        if(playerX < bike.offset && keyC){
          kick.play();
          bike.offset += 0.5;
        }

        if(playerX > bike.offset && keyZ){
          kick.play();
          bike.offset -= 0.5;
        }
        crash.play();
        speed    = bike.speed * (bike.speed/speed);
        position = AllFn.increase(bike.z, -playerZ, trackLength);
        break;
      }
    }
  }



  playerX = AllFn.limit(playerX, -2, 2);
  speed   = AllFn.limit(speed, 0, maxSpeed);


  // console.log(AllFn.toInt(position));
  // if(position < playerZ){
  //   console.log(player);
  //   // if(gmOv>4){
  //   //   // gameOver();
  //   //   // console.log(gmOv);
  //   // }
  //   // else
  //     gmOv++;
  // }


}





function updateBikes(dt, playerSegment, playerW){

  // console.log(playerSegment);
  var m,bike, oldSegment, newSegment;
  for(m = 0; m< bikes.length; m++){
    bike        = bikes[m];
    oldSegment  = findSegment(bike.z);
    bike.offset = bike.offset + handleBikeDirection(bike, oldSegment, playerSegment, playerW);
    bike.z      = AllFn.increase(bike.z, dt * bike.speed, trackLength);
    bike.percent= AllFn.percentRemaining(bike.z, segmentLength);
    newSegment  = findSegment(bike.z);
    if(oldSegment != newSegment){
      index = oldSegment.bikes.indexOf(bike);
      oldSegment.bikes.splice(index,1);
      newSegment.bikes.push(bike);
    }
  }

}

function handleBikeDirection(bike, bikeSegment, playerSegment, playerW){

  var i,j,dir,segment,otherbike, otherbikeW, lookahead = 20, bikeW = bike.sprite.w * SPRITES.SCALE;
  if((bikeSegment.index - playerSegment.index)>drawDistance)
    return 0;

  for(i=1 ; i < lookahead; i++){
    segment = segments[(bikeSegment.index+i)%segments.length];

    if ((segment === playerSegment) && (bike.speed > speed) && (AllFn.overlap(playerX, playerW, bike.offset, bikeW, 1.2))) {
      if (playerX > 0.5)
        dir = -1;
      else if (playerX < -0.5)
        dir = 1;
      else
        dir = (bike.offset > playerX) ? 1 : -1;
      return dir * 1/i * (bike.speed-speed)/maxSpeed;
    }

    for(j = 0 ; j < segment.bikes.length ; j++) {
      otherbike  = segment.bikes[j];
      otherbikeW = otherbike.sprite.w * SPRITES.SCALE;
      if ((bike.speed > otherbike.speed) && AllFn.overlap(bike.offset, bikeW, otherbike.offset, otherbikeW, 1.2)) {
        if (otherbike.offset > 0.5)
          dir = -1;
        else if (otherbike.offset < -0.5)
          dir = 1;
        else
          dir = (bike.offset > otherbike.offset) ? 1 : -1;
        return dir * 1/i * (bike.speed-otherbike.speed)/maxSpeed;
      }
    }
  }

  if (bike.offset < -0.9)
    return 0.1;
  else if (bike.offset > 0.9)
    return -0.1;
  else
    return 0;
}

function updateCars(dt, playerSegment, playerW) {
  var n, car, oldSegment, newSegment;
  for(n = 0 ; n < cars.length ; n++) {
    car         = cars[n];
    oldSegment  = findSegment(car.z);
    car.offset  = car.offset + handleCarDirection(car, oldSegment, playerSegment, playerW);
    car.z       = AllFn.increase(car.z, dt * car.speed, trackLength);
    car.percent = AllFn.percentRemaining(car.z, segmentLength);
    newSegment  = findSegment(car.z);
    if (oldSegment != newSegment) {
      index = oldSegment.cars.indexOf(car);
      oldSegment.cars.splice(index, 1);
      newSegment.cars.push(car);
    }
  }
}

function handleCarDirection(car, carSegment, playerSegment, playerW) {

  var i, j, dir, segment, othercar, othercarW, lookahead = 20, carW = car.sprite.w * SPRITES.SCALE;

  //when out of render distance, no need to handle the offset
  if ((carSegment.index - playerSegment.index) > drawDistance)
    return 0;

  for(i = 1 ; i < lookahead ; i++) {
    segment = segments[(carSegment.index+i)%segments.length];

    if ((segment === playerSegment) && (car.speed > speed) && (AllFn.overlap(playerX, playerW, car.offset, carW, 1.2))) {
      if (playerX > 0.5)
        dir = -1;
      else if (playerX < -0.5)
        dir = 1;
      else
        dir = (car.offset > playerX) ? 1 : -1;
      return dir * 1/i * (car.speed-speed)/maxSpeed;
    }

    for(j = 0 ; j < segment.cars.length ; j++) {
      othercar  = segment.cars[j];
      othercarW = othercar.sprite.w * SPRITES.SCALE;
      if ((car.speed > othercar.speed) && AllFn.overlap(car.offset, carW, othercar.offset, othercarW, 1.2)) {
        if (othercar.offset > 0.5)
          dir = -1;
        else if (othercar.offset < -0.5)
          dir = 1;
        else
          dir = (car.offset > othercar.offset) ? 1 : -1;
        return dir * 1/i * (car.speed-othercar.speed)/maxSpeed;
      }
    }
  }

  if (car.offset < -0.9)
    return 0.1;
  else if (car.offset > 0.9)
    return -0.1;
  else
    return 0;
}


function render() {

  var baseSegment   = findSegment(position);
  var basePercent   = AllFn.percentRemaining(position, segmentLength);
  var playerSegment = findSegment(position+playerZ);
  var playerPercent = AllFn.percentRemaining(position+playerZ, segmentLength);
  var playerY       = AllFn.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
  var maxy          = height;

  var x  = 0;
  var dx = - (baseSegment.curve * basePercent);

  ctx.clearRect(0, 0, width, height);

  Render.background(ctx, background, width, height, BACKGROUND.SKY,   skyOffset,  resolution * skySpeed  * playerY);
  Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset, resolution * hillSpeed * playerY);
  Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset, resolution * treeSpeed * playerY);

  var n, i, segment, car,bike, sprite, spriteScale, spriteX, spriteY;

  for(n = 0 ; n < drawDistance ; n++) {


    segment        = segments[(baseSegment.index + n) % segments.length];
    segment.looped = segment.index < baseSegment.index;
    segment.clip   = maxy;

    AllFn.project(segment.p1, (playerX * roadWidth) - x,      playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
    AllFn.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);

    x  = x + dx;
    dx = dx + segment.curve;

    if ((segment.p1.camera.z <= cameraDepth)         ||
        (segment.p2.screen.y >= segment.p1.screen.y) ||
        (segment.p2.screen.y >= maxy))
      continue;

    Render.segment(ctx, width, lanes,
                   segment.p1.screen.x,
                   segment.p1.screen.y,
                   segment.p1.screen.w,
                   segment.p2.screen.x,
                   segment.p2.screen.y,
                   segment.p2.screen.w,
                   segment.color,
                   sprites);

    maxy = segment.p1.screen.y;
  }


  for(n = (drawDistance-1) ; n > 0 ; n--) {
    segment = segments[(baseSegment.index + n) % segments.length];




    for(i = 0 ; i < segment.cars.length ; i++) {
      car        = segment.cars[i];
      sprite      = car.sprite;
      spriteScale = AllFn.interpolate(segment.p1.screen.scale, segment.p2.screen.scale, car.percent);
      spriteX     = AllFn.interpolate(segment.p1.screen.x,     segment.p2.screen.x,     car.percent) + (spriteScale * car.offset * roadWidth * width/2);
      spriteY     = AllFn.interpolate(segment.p1.screen.y,     segment.p2.screen.y,     car.percent);
      Render.sprite(ctx, width, height, resolution, roadWidth, sprites, car.sprite, spriteScale, spriteX, spriteY, -0.5, -1, segment.clip);
    }

    for(i = 0 ; i < segment.bikes.length ; i++) {
      bike        = segment.bikes[i];
      sprite      = bike.sprite;
      // console.log(sprite);
      spriteScale = AllFn.interpolate(segment.p1.screen.scale, segment.p2.screen.scale, bike.percent);
      spriteX     = AllFn.interpolate(segment.p1.screen.x,     segment.p2.screen.x,     bike.percent) + (spriteScale * bike.offset * roadWidth * width/2);
      spriteY     = AllFn.interpolate(segment.p1.screen.y,     segment.p2.screen.y,     bike.percent);


      Render.sprite(ctx, width, height, resolution, roadWidth, sprites, bike.sprite, spriteScale, spriteX, spriteY, -0.5, -1, segment.clip);
    }

    for(i = 0 ; i < segment.sprites.length ; i++) {
      sprite      = segment.sprites[i];
      spriteScale = 4 * segment.p1.screen.scale;
      // console.log(spriteScale);
      spriteX     = segment.p1.screen.x + (spriteScale * sprite.offset * roadWidth * width/2);
      spriteY     = segment.p1.screen.y;
      Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite.source, spriteScale, spriteX, spriteY, (sprite.offset < 0 ? -1 : 0), -1, segment.clip);
    }


    if (segment == playerSegment) {
      Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
                    cameraDepth/playerZ,
                    width/2,
                    (height/2) - (cameraDepth/playerZ * AllFn.interpolate(playerSegment.p1.camera.y, playerSegment.p2.camera.y, playerPercent) * height/2),
                    speed * (keyLeft ? -1 : keyRight ? 1 : 0),
                    playerSegment.p2.world.y - playerSegment.p1.world.y);
    }

  }
  speedoMeter(speed);
  displayRank(rank,bikes.length+1);

}


//build the road

function lastY() {
  return (segments.length == 0) ? 0 : segments[segments.length-1].p2.world.y;
  }

function addSegment(curve, y) {
  var n = segments.length;
  segments.push({
     index: n,
     p1: { world: { y: lastY(), z:  n   *segmentLength }, camera: {}, screen: {} },
     p2: { world: { y: y,       z: (n+1)*segmentLength }, camera: {}, screen: {} },
     curve: curve,
     sprites: [],
     cars: [],
     bikes: [],
     color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
  });
}


function addSprite(n, sprite, offset) {
  segments[n].sprites.push({ source: sprite, offset: offset });
}


function addRoad(enter, hold, leave, curve, y) {
  var startY   = lastY();
  var endY     = startY + (AllFn.toInt(y, 0) * segmentLength);
  var n, total = enter + hold + leave;
  for(n = 0 ; n < enter ; n++)
    addSegment(AllFn.transIn(0, curve, n/enter), AllFn.transInOut(startY, endY, n/total));
  for(n = 0 ; n < hold  ; n++)
    addSegment(curve, AllFn.transInOut(startY, endY, (enter+n)/total));
  for(n = 0 ; n < leave ; n++)
    addSegment(AllFn.transInOut(curve, 0, n/leave), AllFn.transInOut(startY, endY, (enter+hold+n)/total));
}

function addStraight(x) {
  x = x || 50;
  addRoad(x, x, x, 0, 0);
}

function addHill(x, height) {
  x    = x    || 50;
  height = height || 40;
  addRoad(x, x, x, 0, height);
}

function addCurve(x, curve, height) {
  x    = x    || 50;
  curve  = curve  || 4;
  height = height || 0;
  addRoad(x, x, x, curve, height);
}

function addLowRollingHills(x, height) {
  x    = x    || 25;
  height = height || 20;
  addRoad(x, x, x,  0,  height/2);
  addRoad(x, x, x,  0, -height);
  addRoad(x, x, x,  0,  height);
  addRoad(x, x, x,  0,  0);
  addRoad(x, x, x,  0,  height/2);
  addRoad(x, x, x,  0,  0);
}

function addSCurves() {
  addRoad(50, 50, 50,  -2,    0);
  addRoad(50, 50, 50,   4,  40);
  addRoad(50, 50, 50,   2,   -20);
  addRoad(50, 50, 50,  -2,    40);
  addRoad(50, 50, 50,  -4, -40);
}

function addDownhillToEnd(x) {
  x = x || 200;
  addRoad(x, x, x, -2, -lastY()/segmentLength);
}

function resetRoad() {
  segments = [];

  addStraight(25/2);
  addStraight();
  addStraight();
  addStraight();
  addHill(25, 20);
  addLowRollingHills();
  addCurve(50, 4, 20);
  addLowRollingHills();
  addCurve(100, 4, 40);
  addStraight();
  addCurve(100, -4, 40);
  addHill(100, 60);
  addCurve(100, 4, -20);
  addHill(100, -40);
  addStraight();
  addDownhillToEnd();

  resetSprites();
  resetBikes();
  resetCars();

  segments[findSegment(playerZ).index + 2].color = COLORS.START;
  segments[findSegment(playerZ).index + 3].color = COLORS.START;
  for(var n = 0 ; n < rumbleLength ; n++)
    segments[segments.length-1-n].color       = COLORS.FINISH;

  trackLength = segments.length * segmentLength;
}

function findSegment(z) {
  return segments[Math.floor(z/segmentLength) % segments.length];
}

//playerposition


function updatePlayerPosition(){
  if(true){ //to be checked if  crossFinish is true or not=================================================


      var playerPosition=[];
      var alreadyFinished=0;

      for (var i = 0; i < bikes.length; i++) {
          if(bikes[i].crossFinish){
              alreadyFinished++;
          }
          else{
              var key='enemy'+i;
              playerPosition.push({'name':key,
                  'position' : bikes[i].z});
          }

      }
      playerPosition.push({'name':'player',
          'position':position});

      playerPosition.sort((a,b)=>{
          return b.position-a.position;
      });

      for(var i=0;i<playerPosition.length;i++){
          if(playerPosition[i].name=='player'){
              var pposition=i+1+alreadyFinished;
              rank = pposition;
              // console.log(pposition, bikes.length+1);
              lastPosition=pposition;
              break;
          }
      }
  }
  else{
      console.log(lastPosition,enemies.length + 1);
  }

}


function resetCars() {
  cars = [];
  var n, car, segment, offset, z, sprite, speed;
  for (var n = 0 ; n < totalCars ; n++) {
    offset = Math.random() * AllFn.randomChoice([-0.8, 0.8]);
    z      = Math.floor(Math.random() * segments.length) * segmentLength;
    sprite = AllFn.randomChoice(SPRITES.CARS);
    speed  = maxSpeed/4;
    car = { offset: offset, z: z, sprite: sprite, speed: speed };
    segment = findSegment(car.z);
    segment.cars.push(car);
    cars.push(car);
  }
}




function resetBikes() {
  bikes = [];
  var n, bike, segment, offset, z, sprite, speed;
  // offset = 0;
  for (var n = 0 ; n < totalBikes ; n++) {
    offset = Math.random() * AllFn.randomChoice([-0.8, 0.8]);
    z = (cameraHeight * cameraDepth) + 5 * segmentLength;
    sprite = AllFn.randomChoice(SPRITES.BIKES);
    // speed  = maxSpeed/(Math.random() * AllFn.randomChoice([2,4]));
    speed = maxSpeed/1.5;
    bike = { offset: offset, z: z, sprite: sprite, speed: speed };
    segment = findSegment(bike.z);
    segment.bikes.push(bike);
    bikes.push(bike);
  }
}


function resetSprites() {
  var n,i;
  for(n=20;n<4000;n++){

    // addSprite(n,SPRITES.BUILDING_LEFT,-1);
    // addSprite(n,SPRITES.BUILDING_RIGHT,1);
    // n+=19;
    if(n%60 == 0)
      addSprite(n,SPRITES.LIGHTHOUSE,3);
    if(n%80 == 0)
      addSprite(n,SPRITES.LIGHTHOUSE,-2);
  }

  for(i = 200 ; i < segments.length ; i += 3) {
    addSprite(i, AllFn.randomChoice(SPRITES.SHIPS), AllFn.randomChoice([1,-1]) * (2 + Math.random() * 5));
  }


}


Game.run({
  canvas: canvas,
  render: render,
  update: update,
  step: step,
  images: ["background", "sprites"],
  keys: [
    { keys: [KEY.LEFT,  KEY.A], mode: 'down', action: function() { keyLeft   = true;  } },
    { keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight  = true;  } },
    { keys: [KEY.UP,    KEY.W], mode: 'down', action: function() { keyFaster = true;  } },
    { keys: [KEY.DOWN,  KEY.S], mode: 'down', action: function() { keySlower = true;  } },
    { keys: [KEY.Q],            mode: 'down', action: function() { keyQ      = true;  } },
    { keys: [KEY.E],            mode: 'down', action: function() { keyE      = true;  } },
    { keys: [KEY.Z],            mode: 'down', action: function() { keyZ      = true;  } },
    { keys: [KEY.C],            mode: 'down', action: function() { keyC      = true;  } },
    { keys: [KEY.LEFT,  KEY.A], mode: 'up',   action: function() { keyLeft   = false; } },
    { keys: [KEY.RIGHT, KEY.D], mode: 'up',   action: function() { keyRight  = false; } },
    { keys: [KEY.UP,    KEY.W], mode: 'up',   action: function() { keyFaster = false; } },
    { keys: [KEY.DOWN,  KEY.S], mode: 'up',   action: function() { keySlower = false; } },
    { keys: [KEY.Q],            mode: 'up',   action: function() { keyQ      = false; } },
    { keys: [KEY.E],            mode: 'up',   action: function() { keyE      = false; } },
    { keys: [KEY.Z],            mode: 'up',   action: function() { keyZ      = false; } },
    { keys: [KEY.C],            mode: 'up',   action: function() { keyC      = false; } },
  ],
  ready: function(images) {
    background = images[0];
    sprites    = images[1];
    reset();
  }
});

function reset(options) {
  options       = options || {};
  canvas.width  = width  = AllFn.toInt(options.width,          width);
  canvas.height = height = AllFn.toInt(options.height,         height);
  lanes                  = AllFn.toInt(options.lanes,          lanes);
  roadWidth              = AllFn.toInt(options.roadWidth,      roadWidth);
  cameraHeight           = AllFn.toInt(options.cameraHeight,   cameraHeight);
  drawDistance           = AllFn.toInt(options.drawDistance,   drawDistance);
  fieldOfView            = AllFn.toInt(options.fieldOfView,    fieldOfView);
  segmentLength          = AllFn.toInt(options.segmentLength,  segmentLength);
  rumbleLength           = AllFn.toInt(options.rumbleLength,   rumbleLength);
  cameraDepth            = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
  playerZ                = (cameraHeight * cameraDepth);
  resolution             = height/480;

  if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
    resetRoad();
}
```

## 9. NOTAS PARA CONTINUAR EN NUEVA VENTANA

1. El proyecto es un **juego de carreras completo** con menú principal y juego.
2. Tiene **8 motos rivales** y **30 autos rivales** con IA de evasión.
3. El **combate** se hace con Z (patada izquierda) y C (patada derecha).
4. El **ranking** se actualiza automáticamente mostrando la posición del jugador.
5. El juego comienza con un **countdown** de 3, 2, 1, GO!
6. **Game Over** cuando el jugador cruza la meta.
7. El **velocímetro** muestra la velocidad en MPH con aguja analógica.
8. Los **sprites** están en un tileset `sprites.png` con coordenadas definidas.
9. La **pista** se genera proceduralmente con curvas, colinas y objetos.
10. **Audio**: aceleración, patada y crash.

---

**FIN DEL CONTEXT CHECKPOINT — PROYECTO ROAD RASH**