Open the radar · Source on GitHub

HOW IT WORKS

Jev Approach Control is a single-runway approach sim. It uses TypeSafe's Jev model in two ways: to turn your plain-English radio calls into typed commands, and, in Jev ATC mode, to make the controller's decisions itself. Code does the flying and the geometry. Jev only chooses from small, fixed sets of options, and every answer it gives has to meet a confidence threshold before anything happens.

Architecture

browser (index.html)                 server.js (Node, no dependencies)      TypeSafe
 radar loop, flight model   POST      holds TYPESAFE_API_KEY from .env      POST /v1/systemone
 builds state + questions  ───────▶   adds model "jev-latest"   ─────────▶  model: jev-latest
 applies typed answers     ◀───────   passes the response back  ◀─────────  answers + confidence
                           /api/jev

The API key never reaches the browser. server.js also serves this page at /info and the game at /. On Vercel, the pages are static files and /api/jev runs as a serverless function (api/jev.js). Both use the same proxy code in api/_jev.js.

World and time

ParameterValueNotes
Radar size40 × 40 mi (800 px)20 px = 1 mile; range rings every 5 mi
Time scale~13× real time250 kt moves 18 px/s ≈ 0.9 mi/s, so a ~15 min real approach takes about a minute here
Runway27, threshold 5 mi west of centrePlanes land westbound (heading 270). Drawn 4 mi long so it's visible; the grey dotted line is the 24 mi final approach course
Arrivalsone every 18 sEnter on one of four STARs (below) at 9,000–10,000 ft, 250 kt. Upstream flow control holds new arrivals back while 8 aircraft are on frequency
Lost aircraftleaves the radarCounted as Lost

Flight model

ParameterValueNotes
Turn rate25°/s (game time)About a 1.7 mi turn radius at 210 kt. Turns go the way the controller said ("turn left heading…"), otherwise the shorter way
Climb / descent300 ft/s (game time)≈ 1,400 ft/min in real-time terms
Speed change5 kt/s (game time)Accelerates or decelerates toward the assigned speed
Speed range160–300 ktCommands accept 10-kt steps

STARs (standard arrival routes)

Every arrival enters on one of four made-up STARs and descends via its route, meeting each fix's altitude and speed. Arrivals from the west feed straight into their downwind. Arrivals from the east go round the outside of the pattern to the same merge fix, where the two streams on each side meet at different altitudes.

STARFromRoute (altitude, speed)
KEEPR ONEnorth-westKEEPR 9,000 → ALPHA 6,000 → NIKKO 4,000 / 210 kt → right downwind
MARLO ONEnorth-eastMARLO 10,000 → DUNES 8,000 → ALPHA 7,000 → NIKKO 4,000 / 210 kt → right downwind
PIKES ONEsouth-westPIKES 9,000 → BRAVO 6,000 → SOLAR 4,000 / 210 kt → left downwind
ROWAN ONEsouth-eastROWAN 10,000 → CEDAR 8,000 → BRAVO 7,000 → SOLAR 4,000 / 210 kt → left downwind

Arrival procedure

From the end of a STAR, or when taken off it with vectors, aircraft fly a standard traffic pattern, the way a radar approach controller runs it (compare FAA JO 7110.65, chapter 5-9):

PhaseTagWhat happensAltitude · speed limit
ArrivalARRFlying a STAR (above)per fix · 250 → 210 kt
VectorsVECHeadings to join the downwind on the aircraft's side of the field, for aircraft off the STAR (after a go-around or a traffic avoidance turn, or when looping back for spacing)descend 4,000 · 250 kt
DownwindDWHeading 090, 10 mi either side of the final course, flown eastbound. The north side is a right downwind (right turns), the south side a left downwind (left turns)4,000 · 210 kt
BaseBASEHeading 180 (north) or 360 (south). The base turn is allowed 13–22 mi east of the threshold. It's the controller's main spacing tool, and it happens automatically at 22 mi if nobody calls itdescend 3,000 · 180 kt
InterceptFIN30° intercept (heading 240 from the north, 300 from the south), started about 2.75 mi from the course so the aircraft rolls out just before it. Comes with "maintain 3,000 until established on the localizer, cleared ILS runway 27 approach"3,000 · 170 kt
LocalizerLOCTracks the course to the threshold and rides a 3° glideslope (≈ 318 ft per mile), joined from below at 3,000 ftglideslope · 160 kt
HoldingHLDOrbits ALPHA or BRAVO in a stack from 5,000 ft (see STARs above)5,000+ · 210 kt

A plane captures the localizer when it has an approach or landing clearance, is at or below 3,000 ft, 0–24 mi east of the threshold, within 1.25 mi of the course and heading within 40° of 270. It lands within 0.3 mi of the threshold. Without a landing clearance by 1.5 mi, it goes around: runway heading, climb to 3,000.

Tested:

Separation

EventThresholdEffect
Conflict< 2 mi and < 1,000 ft apartBoth planes turn red
Collision< 0.4 mi and < 300 ft apartGame over

Radio commands (you as controller)

Each call you type is sent once to Jev with the list of planes on frequency (callsign, spoken name such as "Speedbird 406", heading, altitude, speed). One request asks ten questions:

QuestionTypeOptions
callsignChoiceevery aircraft on frequency, or none
headingChoice010–360 in 10° steps, or none
turn_directionChoiceleft, right, or none (the shorter turn)
relative_turnChoice10–90° for calls like "turn 20 degrees left", or none
altitudeChoice1,000–10,000 ft in 1,000 ft steps, or none
speedChoice160–300 kt in 10 kt steps, or none. Relative calls ("reduce by 30") are worked out from the plane's current speed
approachNoul (yes/no)is this an ILS approach clearance?
landNoul (yes/no)is this a landing clearance?
holdNoul (yes/no)is the aircraft told to hold? It joins the top of the nearest stack
resumeNoul (yes/no)is it told to leave the hold and continue on the arrival?

Gating: if the callsign answer's confidence is below 0.5, the reply is "say again". Choice answers are applied only at ≥ 0.5 confidence, and clearances only when the Noul is > 0.5. A manual heading takes the plane out of any Jev plan.

Examples
  United 701 turn left heading 180, descend and maintain 4000
  Delta 986 turn 20 degrees right
  Speedbird 406 reduce speed by 30
  American 12 turn right heading 240, maintain 3000 until established, cleared ILS runway 27 approach
  American 12 runway 27 cleared to land
  Southwest 757 hold at ALPHA
  Southwest 757 leave the hold, continue on the arrival

Jev ATC mode

Every 4 s, the whole traffic picture goes to Jev in one batched request (the "speculative fan-out" pattern from TypeSafe's docs). For each aircraft the state includes its phase (including which STAR it's on and whether it's cleared past its clearance limit), altitude, speed, distance to the runway, position in the approach sequence, the aircraft ahead with the in-trail spacing between them (measured along the route, not in a straight line), and the nearest traffic. Aircraft on the downwind also get the spacing on final they'd have if they turned base now, measured to every aircraft already on base, the intercept or the localizer, ahead or behind.

Jev only gets the decisions that set the sequence, and it's only asked what makes sense for each aircraft's phase:

Aircraft is…Jev chooses between
on a STAR, not yet cleared past its holding fixcleared_via_arrival · not_yet (it will hold at the clearance limit) · avoid
on a STAR, already clearedcontinue · avoid
holding, lowest in the stackleave_hold (continue on the arrival) · hold · avoid
holding, higher in the stackhold · avoid
off the arrivals (after avoid or a go-around)start_approach (vectors to the downwind) · hold (at the nearest fix) · avoid
being vectored to the downwindcontinue · avoid
on the downwind, before the base windowcontinue · avoid
on the downwind, in the base windowextend_downwind (build spacing) · turn_base · avoid
any phase before the localizera speed: 250 (> 10 mi in trail) · 210 (6–10) · 180 (4–6, or on base) · 160 (< 4) · keep
on the localizer, not clearedclear to land (Noul)

The target is 5 mi in trail on final, with a 3 mi minimum. avoid turns the aircraft directly away from the nearest traffic and moves it 1,000 ft away from that traffic's altitude.

Who says what on frequency: JEV → lines are Jev's decisions. ATC → lines are routine calls made by code: holding at a clearance limit "as published", stepping the stack down, vectors to the downwind (amended only if 30°+ off and at most every 6 s), joining the downwind, the final intercept with the approach clearance, the mandatory base turn at 22 mi (or a loop back round for spacing if that turn would be too tight), and go-arounds. Jev decides when, and code flies the geometry.

Gating and guard rails:

Why the split? When Jev was asked to pick a raw heading out of 37 options, its confidence was only 0.2–0.3. With a handful of options that describe what to do, its answers are confident enough to act on, and code keeps the geometry exact.

Usage limit

Each browser gets 10 minutes of Jev time, starting at its first Jev call, and a fresh allowance after 24 hours. The server enforces this with a signed, HttpOnly cookie. Once time is up, /api/jev answers 429 without calling TypeSafe, and the page switches off Jev ATC and the radio input. The radar keeps running. Requests over 64 KB are rejected. SESSION_MINUTES changes the allowance.

So that clearing cookies doesn't reset the allowance, there's also a per-IP rate limit: 30 requests a minute (Jev ATC makes about 15) and 300 a day, which resets at 00:00 UTC. Counters are kept in Upstash Redis when it's configured, otherwise in memory. If the limiter can't be reached, requests are refused.

Radar display

ElementMeaning
Label line 1callsign
Label line 2altitude in hundreds of feet, heading (e.g. 030 270 = 3,000 ft, heading 270)
Label line 3speed in knots, plus the phase: ARR VEC DW BASE FIN LOC HLD
Coloursgreen normal · blue cleared to land · red in conflict
Line from the dotcurrent heading · dots behind show the track, one every 2 s
Linesgrey dotted: final approach course · dark green dotted: the two downwinds · dark solid: the STAR routes
Trianglesfixes. ALPHA and BRAVO are the holding and merge fixes
Log coloursteal: controller calls (> you, JEV →, ATC →) · green: aircraft

Running it

TYPESAFE_API_KEY=...   # in .env, or exported in your shell
node server.js         # Node 18+; PORT defaults to 3000

Tunable constants at the top of the <script> in index.html: TURN_RATE, MI, RWY, and JEV_ATC_CONF further down.

Open the radar

Created by Zeb88. MIT licensed, source on GitHub.

Unofficial demo, not affiliated with or endorsed by TypeSafe AI. Jev is TypeSafe's model, used here through its public API.