Skyline’s world view is no longer a map. It’s a planet.

Over the last development cycle we replaced the map-library world screen with a fully custom WebGL globe renderer: a single continuous camera that goes from watching the whole Earth turn in space down to reading the threshold piano keys on runway 27R, with no mode switches, no loading screens, and no tiles popping in your face. Every aircraft in the simulation flies across it live. Every airport you can click is a place.

This post is the full tour: what it does, how it’s built, and why we didn’t use a globe library.

GLOBE · ORBIT TO APRON One uncut camera move: full orbit, through the cloud decks, down to aircraft at their stands. No mode switch.

Why custom

The classic Skyline map runs on MapLibre, and it’s still there in some places. But a tile-based slippy map is fundamentally a flat, north-up document viewer. We wanted the strategy-game planet: a lit, breathing Earth where the day/night terminator tracks the sim clock, your network glows on the night side, and descending onto your hub feels like arriving somewhere.

So the globe renderer owns a raw Three.js scene and nothing else. No MapLibre, no globe framework, no basemap imagery. The entire planet surface is one sphere and one fragment shader; everything on it (terrain, oceans, cities, weather, roads, buildings, runways) is either generated procedurally or streamed as raw geometry from open data and drawn with our own materials. React never touches the scene: the component feeds data through a setter API and calls frame() from a rAF loop, and nothing re-renders per frame.

The whole thing is about 5,400 lines of TypeScript and GLSL.

The planet: data decides where, shaders decide how

The core design rule of the surface shader: open data supplies the where, procedural materials supply the look. There is no satellite imagery anywhere. Four small equirectangular rasters feed the shader:

  • Land mask: Natural Earth 50m land polygons, rasterized to a canvas at load time. Re-thresholded in screen space so coastlines stay a crisp antialiased edge at every zoom.
  • Elevation: GEBCO land heights. Drives hillshading (central-difference gradients under a fixed cartographic NW light), rock above the treeline, and snow.
  • Landcover: baked from ESA WorldCover, so real forest, farmland, and urban footprints.
  • Hydrology: inland water and wetlands, so the Great Lakes, Ladoga, and the Pantanal exist even though the land mask only knows oceans.

On top of that, the shader runs a compact climate model: latitude plus altitude fold into a “coldness” score (about 9 °C-equivalent per km), and moisture noise picks between biome pairs: rainforest vs savanna in the tropics, forest vs steppe in the temperate belt, a dryness-gated desert band with procedural dune striping, boreal forest, tundra. Real forests from the data get climate-correct hues (a dry Iberian forest shifts olive; a Siberian one goes boreal). Farmland renders as rectangular field parcels with per-field tones: dry wheat, green pasture, the occasional ploughed brown.

And it’s seasonal. The snow line follows elevation and the sim calendar: uSeason comes from the game date, so northern winters push snow down the mountains in January while the southern hemisphere is bare, and mid-latitude vegetation desaturates in its winter. Fly the same route in July and December and the planet outside the window agrees with the schedule.

Four crops from the same surface shader showing four climates: Sahara dune banding, the Alps with a snow line and hillshade, Amazon rainforest, and US Midwest field parcels.

SURFACE · SEASONS The snow line tracks the sim calendar, breathing up and down the range across a year.

The ocean gets the same treatment without any bathymetry data: a static low-frequency noise stands in for basin relief, a blurred copy of the land mask acts as a roughly 250 km continental-shelf proximity field (the North Sea and the Grand Banks read teal, the abyssal Pacific reads near-black), and the mask’s own filtered falloff gives a bright nearshore ribbon at the waterline. Plus a restrained sun glint that softens into the horizon haze.

The civilization layer is the game’s own data

Here’s our favorite part: the night lights aren’t a NASA texture. They’re derived from the same masters the demand model uses.

At load, we paint a population field from the game’s METROS database: amplitude scaling with log(population), radius with the square root of population, and soft corridor strokes connecting big metros under 350 km apart so the Ruhr, Benelux, BosWash, and Kansai read as connected regions of civilization instead of isolated dots. Every airport seeds a small patch of cargo-and-warehouse-grade density. That one field drives everything urban: night lights, day-side urbanization (roof-gray districts replacing terrain), pollution haze, airport aprons.

Then it’s unioned with the real WorldCover urban footprints, so Tokyo Bay, the Nile ribbon, and LA’s sprawl glow in their true shapes: the Black-Marble-from-space read, derived instead of photographed. Zoom in and the smooth glow granulates. First into district-scale clusters (commercial cores in cool glass-gray, warm residential carpet, industrial wedges, park gaps so the fabric breathes), then into density-thresholded individual light dots: continuous downtown, clumpy suburbs with dark gaps, sparse pinpricks on the rural fringe.

What glows at night is exactly what the demand model flies people to.

The night side from orbit over Europe and North Africa, the day-side terminator glowing at the left edge. A web of derived city lights with corridor strokes linking neighboring metros, the EGLL hub marked in amber, built from the game's own demand masters rather than a satellite texture.

CIVILIZATION · GRANULATION A smooth city glow resolving into districts, then individual lights, across three zoom regimes.

One atmosphere, shared by everything

The lighting model is deliberately stylized, but the atmosphere leans physical. A three-layer analytic shell (dense lower air hugging the surface, a thin Rayleigh-blue upper falloff, and a crisp scattering rim right at the silhouette) brightens toward the sun, catches a warm band at the terminator, and keeps an extremely subtle blue edge on the night side so the planet never vanishes into space.

More important is what happens on the surface: a shared optical-depth function is compiled into every surface material: terrain, ocean, clouds, roads, buildings, runways, water. Optical depth grows with real path length and grazing angle, so nearby ground stays perfectly clear at street zoom while anything sliding toward the horizon loses contrast, desaturates, and shifts into Rayleigh blue. City lights bloom softly through the slant air column at night: the atmosphere provides the glow, not a bloom pass. There is no post-processing anywhere. The horizon reads as one continuous medium because every layer literally runs through the same air.

Above it: two cloud decks at different radii, scales, and drift rates (a broken low deck and a thin high veil) giving real parallax and slowly evolving cover rather than one sliding sheet. The low deck’s coverage field is re-sampled along the sun direction in the terrain shader, so cloud banks drag soft moving shadows across the ground. Clouds catch the terminator’s warm scatter, nearly vanish on the night side (city lights stay crisp), and fade out as you dive so terrain stays sharp.

Streaming the real world

Below about 2,000 km altitude the procedural planet starts resolving into the actual planet. The renderer streams real OpenStreetMap geometry from a Protomaps pmtiles archive, the same preprocessed-OSM binary the classic map uses, but read directly: HTTP range requests, MVT protobuf decode, and our own mesh generators. No map library in the loop.

Four tile streams form a zoom ladder, each activating by camera altitude:

StreamZoomActivates belowCarries
Continentalz8~1,900 kmMotorway and major-road web
Regionalz11~320 kmClassified road network, land-use districts, lakes and rivers
Airportz13~320 kmAprons, runway and taxiway ribbons
Detailz15~40 kmBuilding footprints, minor and service roads, small parcels

Tiles are fetched with an in-flight cap, LRU-cached (re-visiting a hub is instant), and, crucially, nothing pops. Streams activate well before their content is visually dominant, materials fade in over altitude bands, and the draped layers dissolve radially at the coverage rim so the edge of the loaded world is never visible. Arrival reads as detail resolving, not loading.

Buildings are real footprints, earcut-triangulated with holes, extruded using OSM height where present (about half of urban buildings) and a deterministic hash where not. Roofs draw from a small palette; walls carry meter-space window UVs so at night each roughly 3.5 m pane is individually lit or dark at random. Land-use parcels (parks, residential, industrial, farmland) tint the procedural terrain into district structure at modest alpha rather than painting over it.

Two more layers are baked offline so the planet-wide reads have zero streaming gaps: the entire global motorway network as one static mesh (about 1.4 MB, 12k lines, 164k points, the glowing web that space sees at night), and the global major-river web from Natural Earth centerlines at stylized widths, so the Nile, Rhine, and Mississippi read from continental altitude the way motorways do. Both hand off to the live streams as you descend.

STREAMING · THE ZOOM LADDER About 300 km down to street level: roads then lit buildings resolving, z8 through z15, nothing pops.

Work in progress. The streamed street-level detail here is the playtest baseline. Building shapes, road styling, and how the layers resolve on descent will keep improving across the playtest, into Early Access, and through to release.

A night orbit over North America, the day-side terminator glowing at the upper-left. The derived city-light field resolves into individual metros across the continent; at this altitude the baked motorway and river webs carry the continental read as one static mesh.

Airports are places now

This is an airline game, so airports got their own stream and their own materials.

Work in progress. The airport rendering shown here is the playtest baseline, not the finished look. Markings, lighting, apron detail, and stand fidelity will keep improving across the playtest, into Early Access, and through to release. Expect these shots to look dated fast.

Runways and taxiways are ribbons built from real OSM centerlines, but the geometry carries meters-from-each-true-runway-end per vertex, so the shader paints every marking in true proportion: centerline dashes (36 m stripe, 24 m gap), threshold piano keys, touchdown-zone bar pairs, aiming-point blocks, taxiway yellow centerlines. Rubber deposits darken the touchdown zones about 250 m from each threshold. When a runway continues into a neighboring tile, the clipped end’s distance is poisoned so threshold markings only ever appear at real runway ends.

At night, paint swaps for lighting: white edge and centerline light rows with green thresholds on runways, green centerline and blue edge rows on taxiways. From altitude these are continuous glow lines; close in, they resolve into individually spaced fixture dots. Aprons drape as concrete floors with slab-scale wear, roughly 40 m expansion-joint grids, and a warm, patchy floodlight wash after dark.

And the stands are gameplay. Stand markings come from the engine’s own gate pools, the identical data the dispatcher uses to park and gate aircraft, so every lead-in ring on the apron is a stand the sim can actually assign. Contact gates paint warm yellow, remote and cargo stands cool gray-blue. When a parked aircraft sits on a ring, it’s parked there in the simulation too.

A whole airport top-down by day, ringed by city and water: intersecting runways and a taxiway network built from real OSM centerlines, aprons dotted with stand lead-in rings, and real building footprints across the field.

Live traffic: the whole sim in the air

Every flight in the simulation, yours and all the AI carriers’, renders as live traffic, up to 4,096 aircraft GPU-instanced in a handful of draw calls. A Web Worker computes aircraft states off the main thread; the renderer interpolates between snapshots against the measured worker cadence, re-anchoring at the displayed position so tracks stay continuous even when a snapshot arrives late. Motion never freezes, never jumps.

Three LODs swap under one screen-constant scale rule:

  • Orbit: a bare notched dart. Nothing else: no labels, no leaders, just the flow of the world’s traffic.
  • Regional: a crisp top-down airliner silhouette, plus a faint velocity leader scaled by ground speed.
  • Airport: a low-poly 3D jet, sun-lit, with real pitch from the flight phase.

Aircraft bank. Displayed heading eases toward the worker’s bearing, and the applied turn rate drives a subtle roll (capped at 25°), so a jet entering a hold visibly leans into the turn, while tight taxi turns keep the airframe flat, because banking is an airborne read only. Cruise altitude is exaggerated 10× so it reads from orbit, and the exaggeration collapses smoothly as you descend so low traffic stays between you and the ground. Traffic sliding toward the limb tints into the air column and loses saturation, the same atmosphere as everything else.

Close to an airport, ground traffic appears: parked, pushback, and taxiing aircraft at their real positions from the engine’s taxi graphs, size-capped at true aircraft scale so parked rows never overlap their neighbors’ stands.

And every labeled aircraft gets an ATC-style datablock: no box, a hairline accent rule over near-transparent text, carrying synthesized callsign, flight level and type, ICAO route pair, and (at airport zoom) speed, heading, and climb trend. The blocks are draggable, real-radar style: each keeps its per-tail offset with an SVG connector line back to the blip. Player traffic is always labeled; AI traffic labels only close-in and capped, so a hub apron never becomes a wall of callsigns.

The camera and the chrome

The orbit camera is spherical lat/lng plus distance with inertial coasting, altitude-scaled drag speed (precise near the ground, fast at orbit), and an eased fly-to when you click an airport or change hubs. It bottoms out around 3 km altitude, low enough that streamed buildings resolve into individual structures, with a dynamic near plane so depth precision holds at both extremes. A 2,200-star deterministic starfield sits behind everything.

Airport markers follow the OCC design language: precise, not VFX. A static crisp dot (hubs add one thin ring, the ”◎” read) over a small dark contrast plate for legibility on bright day terrain. No glow, no pulsing at idle. Motion is reserved for interaction: hover eases in over 175 ms, selection fires one expanding ring, then everything returns to rest. Clicking opens the same airport dossier as the classic map: city photo, FIDS board, runway diagram, slots, competitors.

If WebGL init fails (ancient GPU, headless), the pane silently hands back to the classic MapLibre renderer. And there’s a toggle to just let it rotate. It’s a good screensaver.

The full world view at orbit inside the OCC chrome: the lit globe with airport markers, the hub marked in amber, and network labels.

Performance notes

For the graphics-inclined, the discipline underneath:

  • Zero per-frame allocation. The frame loop reuses a fixed set of scratch matrices and vectors; instance buffers are written in place with DynamicDrawUsage.
  • One draw call per tile per layer: merged BufferGeometries, shared materials per stream, per-vertex attributes instead of per-feature meshes.
  • Exactly one aircraft LOD populated per frame; the other instanced meshes sit at count 0.
  • React renders nothing per frame. Labels and datablocks are imperative DOM, transformed from the rAF loop; text nodes only touch the DOM when their content actually changes.
  • Tile-set re-evaluation is throttled to 300 ms; the coverage rim fade updates every frame (it’s two uniform writes).
  • Everything random is hash-derived and deterministic (stars, building heights, window lighting), consistent with the engine’s replay discipline.
  • One hard-won Apple-GPU lesson lives in a comment: pow() with a negative base NaN-poisons the fragment. All the runway-light falloffs are hand-squared Gaussians now.

What’s next

The renderer is shipping in the 17 July playtest as the default world view (classic map one click away). On the list beyond that: route arcs and bank visualization on the globe surface, weather cells driven by the sim’s climatology, and putting the season and terminator system to work in the dispatch UI (night curfews you can see coming).

Tell us what your GPU does with it: frame rates, glitches, screenshots of your hub at dusk. Playtest feedback channel on Discord.

GLOBE · LET IT ROTATE Slow ambient rotation at dusk, the terminator crossing the player's network, traffic flowing.

Timeline recap

DateWhat happens
17 July 2026Playtest opens for registered founding members
17 August 2026Playtest closes
Christmas 2026Early Access target

Wishlist on Steam · Discord · skylinesim.app