Airport Simulator in the Browser: Making Landings, Departures, and Sim Pace Add Up
You’re watching a little airport dashboard in your browser. Counters tick up: Landings, Departures… and then a third line that feels more subtle: Pace. Underneath, Duration quietly tells you how long the simulation has been running.
That combination is a fun hint at what an “airport simulator” really is, even when it looks tiny: it’s not the graphics that matter first. It’s time, events, and state. Once those are correct, the visuals become almost an afterthought.
Let’s build the mental model behind that kind of browser-based airport simulator—and learn how to implement the counters, “pace,” and “duration” in a way that doesn’t fall apart after a few minutes.
The three numbers your simulator is really telling you
When an airport simulator shows:
- Landings: count of completed arrivals (an airplane reached the end goal: touchdown).
- Departures: count of completed takeoffs (an airplane reached the end goal: the runway run ended, or the aircraft “departed” from the system).
- Duration: how much simulated time has passed.
- Pace: a derived metric that describes how quickly the sim is progressing.
…the design challenge is: how do we decide what “time” means? In most browser simulations, we use two clocks:
- Real time: how much wall-clock time has passed in your computer.
- Simulated time: the fictional time inside the simulation.
If simulated time runs faster than real time, the simulator feels “busy.” If it runs slower, it feels “chill.” Pace is usually the bridge between those two.
Real time vs simulated time (and why “pace” depends on it)
In JavaScript, the browser gives us timing tools like setInterval() and requestAnimationFrame().
- requestAnimationFrame is a browser API that calls your function before the next screen repaint (often ~60 times per second). It’s smoother for animation-driven updates.
- setInterval calls repeatedly on a schedule, but the timing can drift when the tab is busy.
For a simulator dashboard, we want two things:
- The state updates at a steady, predictable rate.
- The UI updates don’t destroy performance.
A common approach is:
- Use real-time measurements (
performance.now()) to compute how much time passed. - Multiply that by a time scale (example:
timeScale = 10means 10 simulated minutes per real minute). - Advance the simulation clock by that simulated delta.
That’s where Duration comes from: it’s just the simulation clock.
Then Pace can be expressed as a “rate,” like:
Pace = simulated minutes per real minute, or completed operations per simulated minute.
What makes a simulator feel satisfying? Usually it’s that the displayed pace matches what the simulation actually does, not what it intends to do.
Discrete-event simulation: treat landings and departures as events
An airport simulator often doesn’t need physics. It can still be realistic-feeling by modeling events.
A discrete event is something that happens at a specific simulation time: “Landing #17 completes at 14:32,” or “Departure #9 is ready at 14:45.”
Instead of simulating every second of every plane, we keep an event queue.
- An event queue is a list of scheduled future events, usually sorted by event time.
- At each step, we pop events whose time is <= current simulated time.
- Each processed event updates counts and schedules follow-up events.
This keeps the simulator responsive even when lots of planes are queued.
A tiny implementation sketch (JavaScript)
Below is a simplified pattern that tracks Duration, Landings, Departures, and computes a pace-like metric.
// Helper: keep times in milliseconds for JavaScript, but interpret them as sim time.
const sim = {
simTimeMs: 0, // simulated clock
realStartMs: performance.now(),
lastFrameMs: performance.now(),
landings: 0,
departures: 0,
// How fast sim time moves vs real time.
timeScale: 30, // 30x: 1 real minute -> 30 sim minutes
// Event queue: { t: simTimeMs when it should happen, type: 'landing'|'departure' }
queue: [],
};
function scheduleEvent(tMs, type) {
sim.queue.push({ t: tMs, type });
sim.queue.sort((a, b) => a.t - b.t);
}
// Seed events so the sim starts doing something.
function seed() {
scheduleEvent(0, 'landing');
scheduleEvent(300000, 'departure'); // 5 sim minutes later
}
function processEvents() {
while (sim.queue.length > 0 && sim.queue[0].t <= sim.simTimeMs) {
const evt = sim.queue.shift();
if (evt.type === 'landing') {
sim.landings++;
// Schedule the next landing. Example: every 2-6 sim minutes.
const next = sim.simTimeMs + (120000 + Math.random() * 240000);
scheduleEvent(next, 'landing');
}
if (evt.type === 'departure') {
sim.departures++;
// Schedule the next departure.
const next = sim.simTimeMs + (180000 + Math.random() * 300000);
scheduleEvent(next, 'departure');
}
}
}
function updateUI() {
// Duration: how much simulated time has passed.
const simMinutes = sim.simTimeMs / 60000;
// A simple pace example: landings per simulated minute (avoid division by 0).
const paceLandings = simMinutes > 0 ? (sim.landings / simMinutes) : 0;
// Render to DOM (pseudo-code).
// landingsEl.textContent = sim.landings;
// departuresEl.textContent = sim.departures;
// durationEl.textContent = `${simMinutes.toFixed(1)} min`;
// paceEl.textContent = `${paceLandings.toFixed(2)} landings/min`;
}
function frame() {
const now = performance.now();
const realDeltaMs = now - sim.lastFrameMs;
sim.lastFrameMs = now;
// Advance simulated time using timeScale.
sim.simTimeMs += realDeltaMs * sim.timeScale / 1; // timeScale is unitless multiplier
processEvents();
updateUI();
requestAnimationFrame(frame);
}
seed();
requestAnimationFrame(frame);
This is intentionally small, but the structure is the important part:
simTimeMsis Duration.landingsanddeparturesare simple counters incremented when events are processed.queuedecides when those counters change.
And requestAnimationFrame keeps the loop visually responsive.
Computing pace without lying to yourself
Pace can be defined many ways. The risk is accidental “lying” when pace is calculated from different clocks.
Common pace strategies:
- Operations per simulated minute (stable, feels consistent even if timeScale changes).
- Simulated minutes per real minute (captures speed-up directly).
- Average interval between landings or departures (nice for debugging).
If the simulator UI shows both Duration and Pace, the definitions should match the same notion of time. Otherwise, the dashboard looks like it’s correcting itself.
A subtle bug people hit: updating pace using real elapsed time, while Duration uses simulated time. The UI still moves, but the math fights itself.
Keeping the simulation from drifting
Even if we use requestAnimationFrame, a browser tab can get throttled when it’s in the background. That means realDeltaMs may jump.
There are two ways to handle that:
- Allow large steps: advance simTime by the full delta and process all due events.
- Cap the delta: only advance up to a maximum per frame to prevent processing an absurd number of events at once.
For an airport dashboard, allowing large steps is usually fine because event processing is discrete. But capping can keep the app from freezing when the tab returns after hours.
This is one of those tricky parts that becomes clear only after seeing it happen once: everything “works” until the browser decides to nap.
From a counter toy to a real airport model
So far, our airport simulator treats the runway like a magic slot machine. Real airport movement needs more structure, such as:
- Runway occupancy time: how long the runway is blocked by an aircraft.
- Taxi time: time spent moving between gates and runways.
- Separate arrival/departure flows: rules that prevent conflicts.
To model runway occupancy with the same event-queue approach, we’d stop scheduling “landing complete” as a single event and instead schedule phases:
- start approach
- touchdown moment
- runway clear moment
Then departures can only be scheduled after runway-clear events. Suddenly landings and departures stop feeling like independent streams, and the simulator gains believable pressure.
The satisfying part: why counters make learning easier
One unexpected benefit of the Landings/Departures/Duration/Pace UI is that it teaches simulation thinking.
You can change timeScale and immediately watch how pace responds. You can adjust event spacing and see landings and departures drift apart. That’s the core discovery behind building simulators: small rules create visible behavior.
Conclusion
An “Airport Simulator” doesn’t need flashy graphics to be engaging. The heart of it is a clean separation between real time and simulated time, a discrete-event approach that schedules landings and departures, and careful pace calculations that use the same clock as Duration. Once those pieces line up, the counters become more than numbers—they become a trustworthy window into how the simulated airport is operating.
Comments (0)
No comments yet. Be the first to respond!
Leave a Comment
Your comment will be visible after review.