Skip to content

codewithfourtix/blueline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

83 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Blueline β€” a self-driving car navigating a city loop, entirely in the browser: a Frenet lattice planner picks the green path, Stanley + PID steer it, a Kalman filter tracks the surrounding traffic.

▍ BΒ LΒ UΒ EΒ LΒ IΒ NΒ E

Try the live demo

A self-driving car that runs a real autonomy stack in your browser β€”
and a neural driver you train, evolve, and race against it. Live. No backend.

Perception β†’ Planning β†’ Control Β· a classical stack Β Β β€’Β Β  Trained live Β· imitation + neuroevolution Β Β β€’Β Β  0 collisions Β· 9 scenarios

TypeScript strict Three.js r161 Vite 5 Neural nets from scratch 0 collisions 100% client-side

How it works Β Β·Β  Watch it drive Β Β·Β  The neural drivers Β Β·Β  Proof Β Β·Β  Run it


Two schools of autonomy, side by side

Blueline is a working implementation of the modular autonomy pipeline from real self-driving
research β€” visualised like a Tesla β€” plus a second, learned stack you train yourself.
Two entire approaches, directly comparable, at 60Β FPS in a browser tab.
~6,400 lines of TypeScript. No game engine. No ML library. No server.

🧭 Classical stack

Every named algorithm, wired end to end β€” Frenet lattice planning, IDM/MOBIL traffic, Kalman tracking, Stanley control. Verified headless in Node.

🧠 Learned stack

A pure-TS MLP (backprop + Adam) trained by behavioural cloning + DAgger, and a policy found by neuroevolution β€” no teacher, no framework.

πŸ“Š Scorecard + race

Live safety / comfort / efficiency scoring, and a head-to-head benchmark of all three drivers on one identical seeded course.



🎬 Watch it drive

Chase-cam view cruising the city block with live HUD telemetry
Chase cam β€” cruising the block, lane markings, live telemetry.
A from-scratch neural network driving after training, with its loss curve falling in real time
Neural net at the wheel β€” trained live (watch the loss fall), then driving.
Top-down view of a rounded 90-degree junction with cross-streets, radar minimap, and occupancy grid
Top-down β€” a rounded 90Β° junction, cross-streets, radar, occupancy grid.
Rain weather that physically degrades the sensor range from 95 to 68 metres
Rain β€” weather physically degrades sensor range (95 m β†’ 68 m).



The modular autonomy pipeline

Every arrow is a real module. The ego never reads ground truth β€” it plans off noisy
sensor detections fused by a Kalman tracker, exactly like the real thing.

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#1b1d22','primaryTextColor':'#e8eaed','primaryBorderColor':'#1fd18b','lineColor':'#5fe6ad','fontSize':'14px'}}}%%
flowchart LR
  subgraph WORLD["🌍 World"]
    ENV["Traffic Β· IDM + MOBIL<br/>Pedestrians Β· Lights Β· Weather"]
  end
  subgraph PERCEPTION["πŸ‘ Perception"]
    SENSE["Sensor<br/>noise Β· occlusion Β· dropout"] --> TRACK["Kalman Tracker<br/>const-velocity + association"]
    TRACK --> GRID["Occupancy Grid"]
  end
  subgraph DECIDE["🧠 Decision"]
    PRED["Prediction"] --> FSM["Behaviour FSM<br/>cruise Β· follow Β· overtake<br/>yield Β· stop Β· emergency"]
    FSM --> PLAN["Frenet Lattice Planner<br/>quintic + quartic Β· cost-ranked"]
  end
  subgraph ACT["πŸ•Ή Control"]
    STANLEY["Stanley + PID"] --> BICYCLE["Kinematic Bicycle"]
  end
  ENV --> SENSE
  TRACK --> PRED
  GRID --> PLAN
  PLAN --> STANLEY
  BICYCLE -->|"ego pose"| ENV

  LEARN["πŸŽ“ Learned driver<br/>MLP Β· imitation / evolved"] -.->|"can replace<br/>Decision + Control"| STANLEY
  PLAN -.->|"demonstrations"| LEARN

  classDef s fill:#16181d,stroke:#2b2e34,color:#e8eaed;
  classDef learn fill:#1a2b22,stroke:#1fd18b,color:#5fe6ad;
  class ENV,SENSE,TRACK,GRID,PRED,FSM,PLAN,STANLEY,BICYCLE s;
  class LEARN learn;
Loading
The stack, module by module β€” with source links
Layer Technique Source
World model Arc-length Frenet frame (station s, lateral d) + curvature over a closed centreline world/ReferencePath.ts
Perception β€” sensing Range + line-of-sight sensor with Gaussian noise, occlusion & random dropout perception/Sensor.ts
Perception β€” tracking Kalman-filter multi-object tracker (const-velocity), NN data association, track lifecycle, object classes perception/Tracker.ts
Occupancy Ego-centred occupancy grid perception/OccupancyGrid.ts
Prediction Constant-velocity roll of each track's estimated velocity (lateral term for pedestrians) planner/FrenetPlanner.ts
Behaviour Finite-state machine: cruise / follow / overtake / emergency / yield / stop behavior/BehaviorPlanner.ts
Planning Frenet lattice: quintic + quartic polynomial trajectories, cost-based selection, collision checking planner/FrenetPlanner.ts Β· core/poly.ts
Control Stanley lateral tracker + PID longitudinal control/
Vehicle Kinematic bicycle model vehicle/Vehicle.ts
Traffic IDM car-following + MOBIL lane changes traffic/
Urban Traffic lights with stop-line planning world/TrafficLight.ts

The entire simulation core imports zero Three.js and runs headless in Node β€” which is exactly why every claim below is a passing test, not a screenshot.



πŸ“ The mathematics behind the blue line

Every box in that diagram is a real equation the car solves ~60 times a second.
Here is the whole stack, straight from the source β€” nothing hand-waved.

πŸ›£ Geometry β€” the Frenet frame

The world is re-expressed along the road: station $s$ (distance along) and lateral offset $d$ (distance off centre). A world point, and the road's curvature, are

$$\mathbf{p}(s,d) = \mathbf{c}(s) + d\mathbf{n}(s) \qquad \mathbf{n}(s) = \big(-\sin\theta(s),\ \cos\theta(s)\big)$$

$$\kappa(s) = \left|\frac{d\theta}{ds}\right| \approx \frac{\big|\theta(s+\Delta s) - \theta(s-\Delta s)\big|}{2\Delta s}$$

so the car knows precisely how hard to slow for a bend, capping speed at the lateral-acceleration limit $a^{\text{lat}}_{\max}=2.2$ m/sΒ²:

$$v_{\max}(s) = \sqrt{\frac{a^{\text{lat}}_{\max}}{\kappa(s)}}$$

β†’ world/ReferencePath.ts Β· sim/Simulation.ts

🧭 Planning β€” the Werling Frenet lattice

Each frame samples a lattice of manoeuvres. Lateral motion is a quintic (six boundary conditions β€” position, velocity and acceleration pinned at both ends); longitudinal is a quartic (terminal position left free):

$$d(t) = \sum_{i=0}^{5} a_i t^i \qquad\qquad s(t) = \sum_{i=0}^{4} b_i t^i$$

Every candidate is scored, and the car draws the arg-min:

$$J = \underbrace{k_j\int_0^T\big(\dddot{d}^2 + \dddot{s}^2\big)dt + k_t T}_{\text{comfort}} + \underbrace{k_v(v_d - v_T)^2}_{\text{speed}} + \underbrace{k_{lc}|\ell - \ell_0| + k_b|\ell - \ell^{\star}|}_{\text{lane}} + \underbrace{k_p\sum_{\text{obstacles}}\frac{1}{\text{gap}}}_{\text{proximity}}$$

colliding candidates take a +10⁢ penalty, dynamically-infeasible ones +10⁡ β†’ planner/FrenetPlanner.ts Β· core/poly.ts

πŸš— Traffic β€” IDM + MOBIL

Ambient cars follow the Intelligent Driver Model β€” a desired gap, and an acceleration that closes it:

$$s^{\star}(v,\Delta v) = s_0 + \max\left(0,\ vT + \frac{v\Delta v}{2\sqrt{ab}}\right) \qquad a_{\text{IDM}} = a\left[1 - \left(\frac{v}{v_0}\right)^{\delta} - \left(\frac{s^{\star}}{s}\right)^{2}\right]$$

and change lanes by MOBIL only when the net incentive beats a threshold β€” never braking the new follower past $b_{\text{safe}}$:

$$\Delta a_{\text{self}} + p\big(\Delta a_{\text{new}} + \Delta a_{\text{old}}\big) + a_{\text{bias}} > \Delta a_{\text{th}}$$

β†’ traffic/IDM.ts Β· traffic/MOBIL.ts

πŸ•Ή Control β€” Stanley + PID + bicycle

Steering drives both the heading error $\psi_e$ and the cross-track error $e$ to zero:

$$\delta = \psi_e + \arctan\left(\frac{ke}{v + k_{\text{soft}}}\right)$$

Speed is a PID on the target-speed error; the vehicle itself is a kinematic bicycle β€” nonholonomic, "can't slide sideways":

$$u = k_p e + k_i\int e\ dt + k_d\dot e \qquad\qquad \dot x = v\cos\psi \quad \dot y = v\sin\psi \quad \dot\psi = \frac{v}{L}\tan\delta \quad \dot v = a$$

β†’ control/Stanley.ts Β· control/PID.ts Β· vehicle/Vehicle.ts

πŸ‘ Perception β€” the Kalman filter

Each tracked object is a constant-velocity state $\mathbf{x} = [p_x, p_y, v_x, v_y]^{\top}$, smoothed by a Kalman filter that predicts, then corrects against the noisy detection $\mathbf{z}$:

$$\hat{\mathbf{x}}^{-} = F\hat{\mathbf{x}} \qquad P^{-} = F P F^{\top} + Q$$

$$\mathbf{y} = \mathbf{z} - H\hat{\mathbf{x}}^{-} \qquad S = H P^{-} H^{\top} + R \qquad K = P^{-} H^{\top} S^{-1}$$

$$\hat{\mathbf{x}} = \hat{\mathbf{x}}^{-} + K\mathbf{y} \qquad P = (I - K H)P^{-}$$

the ego plans off the estimate, never ground truth β†’ perception/Tracker.ts

🧠 Learning β€” MLP, backprop, Adam

The neural driver is a tanh MLP with a linear head, trained to minimise MSE against the expert's action:

$$a^{(l)} = \tanh\big(W^{(l)}a^{(l-1)} + b^{(l)}\big) \qquad \mathcal{L} = \frac{1}{2N}\sum_{n} \big\lVert a^{(L)}_n - y_n \big\rVert^2$$

Gradients flow back through the tanh, and Adam takes the step:

$$\delta^{(L)} = a^{(L)} - y \qquad \delta^{(l)} = \big(W^{(l+1)\top}\delta^{(l+1)}\big)\odot\big(1 - a^{(l)}\odot a^{(l)}\big)$$

$$m \leftarrow \beta_1 m + (1-\beta_1)g \quad v \leftarrow \beta_2 v + (1-\beta_2)g^2 \quad \theta \leftarrow \theta - \frac{\eta\hat{m}}{\sqrt{\hat{v}} + \epsilon}$$

β₁=0.9, Ξ²β‚‚=0.999, Ξ΅=10⁻⁸, Xavier init, tanhβ€² = 1βˆ’aΒ² β†’ learn/NN.ts

🧬 Evolution β€” survival of the fittest driver

With no teacher, genomes are scored by the worst of two rollouts (so the winner must generalise, not memorise), then bred by tournament selection + uniform crossover + annealed Gaussian mutation:

$$\mathcal{F}(\theta) = \min_{r\in{1,2}} \sum_{t}\Big(\underbrace{v_t\Delta t}_{\text{progress}} - 0.6\max(0,\ |d_t|-2)\Delta t\Big) - \text{penalty}_{\text{crash / off-road}}$$

$$\sigma_g = 0.25\left(1 - \frac{g}{G}\right) + 0.03 \qquad \text{(mutation scale, annealed over generations)}$$

β†’ learn/Evolution.ts

πŸ“Š The scorecard

And all three drivers are ranked on one honest scale:

$$\text{safety} = 100 - 30 n_{\text{col}} - 120 f_{\text{off-road}} - 5\max(0,\ 6 - g_{\min})$$

$$\text{comfort} = 100 - 14\overline{|a|} - 5\overline{|j|} \qquad\quad \text{efficiency} = 100\cdot\frac{\bar{v}}{v_d}$$

$$\text{overall} = 0.5\cdot\text{safety} + 0.25\cdot\text{comfort} + 0.25\cdot\text{efficiency}$$

β†’ sim/Metrics.ts

Geometry to gradients β€” that's the whole car, ~6,400 lines of it.



🧠 The learned drivers β€” no ML library

A pure-TypeScript MLP with backprop + Adam (learn/NN.ts) learns to drive from a
16-feature view of the road. Two ways to teach it β€” watch the loss curve fall in real time.

The neural driver panel: loss curve falling to 0.0144 over 29,400 samples, NEURAL NET at the wheel, drive score 84

πŸŽ“ Imitation + DAgger

The classical stack drives; the network records (state β†’ action) demonstrations and clones them. Then DAgger flips it β€” the learner drives while the expert labels the states it actually visits, curing the covariate-shift drift that sinks naive cloning.

Result: holds its lane at max 0.99 m off centre (half-width 5.55 m) over 1.05 km, 0 collisions.

β†’ ImitationAgent.ts Β· Trainer.ts

🧬 Neuroevolution β€” no teacher

A genetic algorithm evolves the weights, scored purely by driving-rollout fitness (distance minus penalties for leaving the road or hitting traffic). Selection + crossover + mutation discover a driver from random weights.

Result: fitness climbs 53 β†’ 378 over 16 generations; the champion then drives 1.52 km, 0 collisions.

β†’ Evolution.ts

A safety shield (AEB + lane-keeping + virtual guardrail) guards the learned drivers in play β€”
but switches off during evolution, so fitness judges the raw policy, not the shield.



πŸ“Š Proof it actually works

Everything is verified headless and deterministically in Node (seeded RNG) β€” no browser,
no hand-waving. Reproduce every number yourself (see Run it).

In-browser head-to-head benchmark: Classical 78 (winner), Neural Net 75, Evolved 62 β€” all with zero collisions over one identical seeded course

In-browser head-to-head β€” all three drivers over one identical seeded course. Classical wins on balance;
the evolved policy is fastest but rougher. All three: zero collisions.

smoke.ts β€” the classical stack across 9 scenarios

[highway]     0.74 km   CRUISE                                  crashes 0   ped-hits 0
[highway@fast]1.09 km   CRUISE                                  crashes 0   ped-hits 0
[stalled]     0.51 km   CRUISE Β· OVERTAKE                       crashes 0   ped-hits 0
[cutin]       0.47 km   CRUISE Β· OVERTAKE                       crashes 0   ped-hits 0
[trucks]      0.58 km   CRUISE Β· OVERTAKE Β· FOLLOW              crashes 0   ped-hits 0
[crossing]    0.53 km   CRUISE Β· YIELD Β· OVERTAKE Β· STOP        crashes 0   ped-hits 0
[occluded]    0.58 km   CRUISE Β· OVERTAKE Β· YIELD Β· STOP        crashes 0   ped-hits 0
[jaywalker]   0.53 km   CRUISE Β· YIELD Β· STOP                   crashes 0   ped-hits 0
[rush]        0.62 km   CRUISE Β· YIELD Β· STOP                   crashes 0   ped-hits 0

SMOKE PASS β€” all scenarios: on-road, zero collisions, pedestrians safe.

learn-test.ts β€” the net learns

DAgger 4: samples 60000, loss 0.0230
network now driving:
  [highway] 1.05 km | max|d| 0.99 (half 5.55) | crash 0
  [trucks]  0.61 km | max|d| 1.14 (half 5.55) | crash 0
LEARN-TEST PASS

evo-test.ts β€” evolution from scratch

gen 16/16: best 378.2, avg 151.4
fitness: 53.1 -> 378.2
champion drives: 1.52 km | crash 0
EVO-TEST PASS

Plus lights-test.ts (stops on red, goes on green) and weather-test.ts (rain/fog degrade the sensor). All green.



🎬 One-click scenarios

Highway Β· Trucks (overtake a convoy) Β· Stalled car Β· Cut-in Β· Dense traffic Β·
Crossing pedestrian Β· Occluded ped (steps out from behind a stalled car, seen late) Β·
Jaywalker (emergency stop) Β· 🚦 Lights Β· πŸŒ† Rush hour

Deep-link any of them: ?scenario=occluded&cam=top&weather=rain


Run it

npm install
npm run dev        # β†’ http://localhost:5173
npm run build      # typecheck + production bundle β†’ dist/  (drop on any static host)
Headless verification β€” reproduce every number above, no browser needed
npx tsx scripts/smoke.ts        # 9 scenarios: on-road, zero vehicle & pedestrian collisions
npx tsx scripts/learn-test.ts   # the neural net learns to drive (imitation + DAgger)
npx tsx scripts/evo-test.ts     # neuroevolution discovers a driver from random weights
npx tsx scripts/lights-test.ts  # ego obeys the traffic light
npx tsx scripts/weather-test.ts # rain/fog degrade perception

Each asserts real, measurable behaviour β€” stays on road, zero collisions, loss falls, fitness climbs, red light obeyed.

Keyboard Β Β·Β  1–9 scenarios Β Β·Β  T train Β Β·Β  E evolve Β Β·Β  C/N/V switch driver Β Β·Β  Space pause Β Β·Β  R reset Β Β·Β  B benchmark


Under the hood

TypeScript Β· Three.js Β· Vite. Fixed-timestep deterministic physics & planning, decoupled from the render loop. The neural networks β€” MLP with backprop + Adam, and the genetic algorithm β€” are written by hand; there is no TensorFlow, no PyTorch, no ONNX. No backend, no build-time secrets. It's a static site.

The render layer (Three.js + UnrealBloom, the green path ribbon, candidate-trajectory viz, the Tesla-style DOM HUD) is a thin skin over a headless core β€” which is why the same code that renders in the browser is the code the tests drive in Node.

Roadmap

  • Classical AV stack β€” Frenet planner, IDM/MOBIL, Stanley + PID, Kalman tracking
  • Pedestrians + hard cases β€” crossing / occluded / jaywalker
  • City map β€” rounded 90Β° junctions, cross-streets, traffic lights
  • Imitation learning driver β€” behavioural cloning + DAgger
  • Neuroevolution driver β€” learn from scratch, no teacher
  • Analytics β€” live scorecard + head-to-head benchmark
  • Weather β€” rain/fog that degrades perception
  • Intersections with cross-traffic & turns
  • Reinforcement learning (policy-gradient) driver
  • Recording & replay

Built by @codewithfourtix β€” perception, planning, and a little bit of evolution.

About

A self-driving car that runs a real autonomy stack in your browser (Frenet planning, IDM/MOBIL traffic, Kalman tracking, Stanley control), plus neural drivers you train, evolve, and race against it. Live demo. TypeScript + Three.js, no ML library.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors