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
| Parameter | Value | Notes |
|---|---|---|
| Radar size | 40 × 40 mi (800 px) | 20 px = 1 mile; range rings every 5 mi |
| Time scale | ~13× real time | 250 kt moves 18 px/s ≈ 0.9 mi/s, so a ~15 min real approach takes about a minute here |
| Runway | 27, threshold 5 mi west of centre | Planes land westbound (heading 270). Drawn 4 mi long so it's visible; the grey dotted line is the 24 mi final approach course |
| Arrivals | one every 18 s | Enter 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 aircraft | leaves the radar | Counted as Lost |
Flight model
| Parameter | Value | Notes |
|---|---|---|
| Turn rate | 25°/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 / descent | 300 ft/s (game time) | ≈ 1,400 ft/min in real-time terms |
| Speed change | 5 kt/s (game time) | Accelerates or decelerates toward the assigned speed |
| Speed range | 160–300 kt | Commands 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.
| STAR | From | Route (altitude, speed) |
|---|---|---|
| KEEPR ONE | north-west | KEEPR 9,000 → ALPHA 6,000 → NIKKO 4,000 / 210 kt → right downwind |
| MARLO ONE | north-east | MARLO 10,000 → DUNES 8,000 → ALPHA 7,000 → NIKKO 4,000 / 210 kt → right downwind |
| PIKES ONE | south-west | PIKES 9,000 → BRAVO 6,000 → SOLAR 4,000 / 210 kt → left downwind |
| ROWAN ONE | south-east | ROWAN 10,000 → CEDAR 8,000 → BRAVO 7,000 → SOLAR 4,000 / 210 kt → left downwind |
- Clearance limit: in Jev ATC mode, ALPHA and BRAVO are each arrival's clearance limit. A plane continues past it only when cleared "via the arrival". Otherwise it enters the published hold there by itself, as a pilot would. In your own mode there's no clearance limit: planes fly the whole STAR, and after NIKKO or SOLAR they fly heading 090 (the downwind) and wait for your vectors.
- Holding stacks: a new aircraft joins the top of the stack, 1,000 ft above the highest, so it never descends through traffic. The stack steps down 1,000 ft at a time as levels below free up, and only the lowest aircraft can leave.
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):
| Phase | Tag | What happens | Altitude · speed limit |
|---|---|---|---|
| Arrival | ARR | Flying a STAR (above) | per fix · 250 → 210 kt |
| Vectors | VEC | Headings 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 |
| Downwind | DW | Heading 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 |
| Base | BASE | Heading 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 it | descend 3,000 · 180 kt |
| Intercept | FIN | 30° 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 |
| Localizer | LOC | Tracks the course to the threshold and rides a 3° glideslope (≈ 318 ft per mile), joined from below at 3,000 ft | glideslope · 160 kt |
| Holding | HLD | Orbits 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:
- Single aircraft: 544 approach starts (edge arrivals, random positions and headings, both holding fixes) all landed. Each joined final 12.6 mi or more from the threshold, at a 30° intercept or less. Every STAR lands both when cleared straight through and when held at its clearance limit first.
- Busy traffic: six 30-minute runs with the same decision rules Jev is given each landed 62–64 aircraft, with none lost, no collisions and no go-arounds. At most 5 were holding, and the tightest spacing on final was 6.5 mi.
Separation
| Event | Threshold | Effect |
|---|---|---|
| Conflict | < 2 mi and < 1,000 ft apart | Both planes turn red |
| Collision | < 0.4 mi and < 300 ft apart | Game 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:
| Question | Type | Options |
|---|---|---|
| callsign | Choice | every aircraft on frequency, or none |
| heading | Choice | 010–360 in 10° steps, or none |
| turn_direction | Choice | left, right, or none (the shorter turn) |
| relative_turn | Choice | 10–90° for calls like "turn 20 degrees left", or none |
| altitude | Choice | 1,000–10,000 ft in 1,000 ft steps, or none |
| speed | Choice | 160–300 kt in 10 kt steps, or none. Relative calls ("reduce by 30") are worked out from the plane's current speed |
| approach | Noul (yes/no) | is this an ILS approach clearance? |
| land | Noul (yes/no) | is this a landing clearance? |
| hold | Noul (yes/no) | is the aircraft told to hold? It joins the top of the nearest stack |
| resume | Noul (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 fix | cleared_via_arrival · not_yet (it will hold at the clearance limit) · avoid |
| on a STAR, already cleared | continue · avoid |
| holding, lowest in the stack | leave_hold (continue on the arrival) · hold · avoid |
| holding, higher in the stack | hold · 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 downwind | continue · avoid |
| on the downwind, before the base window | continue · avoid |
| on the downwind, in the base window | extend_downwind (build spacing) · turn_base · avoid |
| any phase before the localizer | a speed: 250 (> 10 mi in trail) · 210 (6–10) · 180 (4–6, or on base) · 160 (< 4) · keep |
| on the localizer, not cleared | clear 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:
- Jev's answers are applied only at ≥ 0.35 confidence (
JEV_ATC_CONF). Jev picks from 2–5 options, so this threshold is lower than for radio calls. - Flow limit: at most 3 aircraft in the approach sequence (past their clearance limit, in the pattern or on the localizer). Extra
cleared_via_arrival,leave_holdandstart_approachanswers are ignored until a slot frees up. - Base turns under the legal minimum of 3 mi spacing on final are ignored. Jev aims for 5.
- Once an aircraft is being vectored for the approach, it isn't sent back to hold. Without this rule, Jev switched planes back and forth between holding and approach.
- The phase speed limits above cap whatever speed Jev picks.
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
| Element | Meaning |
|---|---|
| Label line 1 | callsign |
| Label line 2 | altitude in hundreds of feet, heading (e.g. 030 270 = 3,000 ft, heading 270) |
| Label line 3 | speed in knots, plus the phase: ARR VEC DW BASE FIN LOC HLD |
| Colours | green normal · blue cleared to land · red in conflict |
| Line from the dot | current heading · dots behind show the track, one every 2 s |
| Lines | grey dotted: final approach course · dark green dotted: the two downwinds · dark solid: the STAR routes |
| Triangles | fixes. ALPHA and BRAVO are the holding and merge fixes |
| Log colours | teal: 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.
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.