Most airline simulators give you a demand number and expect you to trust it. You open a route, you see “340 passengers per day,” you figure out if your aircraft covers that, and you move on. The number doesn’t explain itself. It doesn’t change when an LCC undercuts you on price. It doesn’t know that the route you’re running goes to Mallorca in August, or that the year is 1973 and you just lived through the oil crisis.
We wanted demand to feel like something you could actually understand and push on. That meant building a model grounded in real aviation data, real economic history, and real behavioral patterns spanning from the jet age forward, then surfacing it in a way that makes sense in a game.
Below is the whole stack, layer by layer.
The foundation is a gravity model
The same physics that governs planets governs passengers. Two cities with large, wealthy populations close together generate a lot of air travel. Two cities that are small, poor, and far apart generate very little. The gravity model formalizes this:
D(A, B) = k · (Pop_A · Pop_B)^α · (GDPpc_A · GDPpc_B)^β / Distance^γ
k scales the absolute level. α is the population exponent. β is the income exponent. γ is the distance decay. These aren’t made up: they’re fitted to real bilateral passenger flows from US Department of Transportation T-100 segment data and Eurostat avia_par filings.
The model works at the metro-pair level, not the airport-pair level. We predict total New York metro to Los Angeles metro travel, then disaggregate to JFK, EWR, and LGA using a calibrated allocation model. That allocation is itself a conditional logit fitted to DB1B observed airport shares. It factors in population weight, slot constraints, hub status, curfews, and runway length. So LHR captures a disproportionate share of the London metro pool versus LGW or STN, because the data says it does.
We calibrate gravity parameters per era, not just per region
Here is the part most demand models skip: the structural relationship between population, income, distance, and air travel has not been constant across history. Flying in 1955 was a luxury for the wealthy few. By 1975, mass-market jets had begun democratizing travel. By 1995, deregulation had restructured route economics entirely. The same (k, α, β, γ) vector cannot honestly describe all three periods.
We fit a separate gravity calibration for each 20-year era bracket: 1950s, 1970s, 1990s, and 2010s. Each bracket carries its own parameter set derived from the best available historical traffic data for that period.
The 1950s calibration reflects an era when income elasticity was high (only the affluent flew), distance decay was steep (long-haul was genuinely rare), and the absolute scale was small. The 1970s calibration captures the post-jet mass-market expansion but also the oil shock compression on discretionary travel and the first signs of frequency competition between carriers. The 1990s set reflects the post-deregulation competitive landscape, with LCC entry beginning to flatten fare curves and stimulate new markets. The 2010s calibration is the modern anchor, fitted to contemporary DB1B and Eurostat flows.
When your campaign runs through these eras, the engine interpolates smoothly between the bracketing parameter sets rather than jumping at the boundary year. A simulation that starts in 1962 uses the 1950s parameters at full weight, blends toward the 1970s set through the late 1960s, and arrives at the 1970s calibration fully by 1975. The transition is continuous and weighted by elapsed time within the bracket.
This matters most for β, the income exponent, and for k, the absolute scale constant. In the 1950s, income elasticity was very high globally: air travel was one of the first goods people gave up under constraint and one of the first they reached for as incomes grew. By the 2010s, high-income markets were past the S-curve inflection and income elasticity had compressed significantly. A model that used 2024 β values in 1958 would dramatically overstate how responsive early-era traffic was to marginal income gains.
We fit two additional layers, and they are meaningfully different
The first-generation fit (v1) gives us per-region (k, α, β, γ) parameters for US and EU corridors, falling back to a single global constant for everything else. That gets us in the right ballpark.
The second-generation fit (v2) adds structural route-character terms in log-space, so they compose multiplicatively with the mass term:
- Tourism index: destinations like Mallorca, Santorini, and Maldives pull disproportionate leisure traffic
- Diaspora flows: migrant community corridors (quantified from World Bank migration matrices) carry persistent VFR demand
- Official language pairs: a shared official language increases bilateral propensity
- Hub-to-hub pairs: the hub premium between major connecting airports
- Capital city pairs: government, diplomatic, and business travel between capitals
- Intra-EU: the single-aviation-market effect within the European Economic Area
- Corporate HQ density: metros with dense multinational headquarters attract higher business traffic
The structural exponents (α, β, γ) stay the same between v1 and v2 within a given era bracket. The new terms layer on top of the mass relationship rather than re-estimating it.
Income grows across 75 years, and not the same way everywhere
A campaign starting in 1955 opens with the world significantly poorer than 2024. An extra dollar of income in 1958 Nigeria generates a different marginal propensity to fly than an extra dollar in 1958 West Germany, which itself differs from the same dollar in 2024. Both the level and the elasticity matter.
We model this as a multiplicative air propensity multiplier applied on top of the structural gravity mass:
multiplier(country, year) = (realGDPpc(country, year) / realGDPpc(country, year_ref))^ε_band
year_ref is the calibration anchor of the active era bracket: approximately 1960 for the 1950s set, 1975 for the 1970s set, 1995 for the 1990s set, and 2020 for the 2010s set. At year_ref, every country’s multiplier is exactly 1.0 by definition. The bracket’s k constant already encodes income at that level. The multiplier handles only within-era variation: years that fall between two bracket anchors interpolate between both multipliers (and both k values) simultaneously, so income and scale stay consistent throughout the transition.
The exponent ε_band is fitted per income band (low / lower-middle / upper-middle / high) from within-country fixed-effects regressions of log air-pax-per-capita on log real GDP per capita. High-income countries have a low exponent: demand is relatively inelastic to further income growth. Low-income countries have a high exponent, demand is very responsive as incomes rise toward the threshold where flying becomes accessible.
We deliberately use real GDP per capita (World Bank NY.GDP.PCAP.KD series) for the historical path, not nominal. Nominal GDPpc moves with exchange rates: the EUR/JPY appreciated during the GFC, which makes nominal-USD GDPpc look like it rose in 2008 to 2009. In real terms, it didn’t, and neither did their air travel. The real series strips that FX contamination. For projection years past 2024, where no open real-GDPpc forecast series exists, we fall back to the nominal metro GDP trajectory already in the engine.
History hits the demand curve hard
A campaign starting in 1955 crosses a lot of events that meaningfully changed global aviation:
- 1956 to 1958: Jet age begins. Transatlantic travel time drops from 14 hours to 7. Demand curves shift as the product fundamentally changes.
- 1973: First oil crisis. Fuel cost triples. Carriers mothball long-haul capacity. Discretionary leisure demand collapses.
- 1978 to 1980: US deregulation. Fares fall on trunk routes. LCC precursors emerge. Traffic stimulates rapidly on competitive corridors.
- 1990 to 1991: Gulf War. Oil shock combined with fear effect on long-haul. International traffic to the Middle East and adjacent regions drops sharply.
- 2001: 9/11. US traffic down roughly 30% in Q4, international down more than 20%.
- 2002 to 2003: SARS. Asia-Pacific cratered for the duration of the outbreak.
- 2008 to 2009: Global Financial Crisis. Business travel collapsed globally.
- 2020 to 2021: COVID.
These aren’t ambient flavor. We encode them as a time-series of regional demand modifiers: one per year, per region (US, EU, ROW_DEV, ROW), drawn from published traffic recovery data. COVID in 2020 dropped US domestic demand roughly 60% in our model, consistent with BTS published totals. The GFC dropped US domestic about 10% over two years, consistent with ATA data. The 1973 oil shock reduced international long-haul seat capacity by roughly 15%, which we model as a demand-side shock on the affected corridors rather than a supply constraint, since the capacity reduction itself was a rational carrier response to collapsing yields.
Cross-region pairs (intercontinental routes) feel both endpoints: we apply the geometric mean of the two regional modifiers, so a US-EU transatlantic route is dampened by both during a global shock.
The events also fire at month resolution when the engine is in daily tick mode. So 9/11 depresses traffic from September 2001 forward at the correct monthly cadence, not just as a smeared annual average.
One additional wrinkle: every new game generates its own event catalog with plus-or-minus 2 years of jitter. COVID can hit in 2022 instead of 2020 in your save, and the whole demand path shifts accordingly. The year-by-year event cache is WeakMap-keyed on the scenario’s event array reference, so the jittered path is fast to compute and shares no state with the baseline.
Seasonality is layered, not invented
Demand isn’t flat across the year. January on a leisure route to Mallorca is nothing like August. We resolve this through a four-tier fallback hierarchy:
- Per-route, from T-100 data (US): 12-month indices extracted from BTS T-100 international and domestic segment data (2020 excluded). If your route matches a T-100 entry, we use the measured monthly distribution.
- Per-route, from Eurostat (EU): Eurostat avia_par quarterly, interpolated to monthly. Same logic.
- Region-pair fallback: grouped by continent pair (NA to EU, AS to ME, etc.) using T-100 international region pairs. When a specific route isn’t in T-100 or Eurostat, the corridor’s typical shape applies.
- Hemisphere-aware leisure index: for known hot-weather leisure destinations (Santorini, Maldives, Cancun, Bali, etc.), a parametric curve peaks in the local summer: July/August for northern destinations, January/February for southern-hemisphere ones. This sits below measured data in the hierarchy, above the distance-class fallback.
- Distance-class fallback: short-haul, medium-haul, long-haul, and ultra-long-haul each have their own baseline seasonal shape, derived from published IATA industry patterns.
- Flat: the last resort, used for routes where nothing else applies.
Day-of-week follows the same tier logic but simpler: IATA-published archetypes (business, leisure, mixed) classified by hub status of the endpoints. A LHR-FRA route between two major business hubs peaks Monday through Thursday and troughs on Sunday. A route to Las Vegas or Ibiza peaks Friday through Saturday.
Every day is a little different
The gravity pool and variation layers give us a monthly-level prediction. Within a month, demand should move day to day.
We model this as a first-order autoregressive (AR(1)) daily noise process:
x_d = φ · x_{d-1} + √(1 − φ²) · z_d
factor_d = (1 + AMP · x_d) / monthly_mean
Where z_d is a zero-mean unit-variance draw from the hash stream keyed on (routeKey, scenarioSeed, year, month, day), φ = 0.60 (moderate persistence: demand today is correlated with demand yesterday, but not for a whole week), and AMP = 0.125 (plus-or-minus 12.5% swing amplitude).
The critical invariant: the monthly mean of all daily factors is normalized to exactly 1.0. No matter how the noise distributes across the 28 to 31 days of a month, the month-total and year-total demand are conserved. The AR(1) redistributes demand within the month, it doesn’t inflate or deflate it. This keeps the P&L math clean.
The whole process is deterministic. Same (routeKey, scenarioSeed, year, month) gives the same factor array. No Math.random() anywhere in the engine. When you reload a save, the same flights see the same daily variation. Replays are bit-identical.
Four passenger segments, one choice model
Not all passengers on a route are interchangeable. A Frankfurt-Singapore business traveler doesn’t make decisions the same way as a student flying home for the holidays. We split demand into four segments: business, leisure, VFR (visiting friends and relatives), and connecting.
Each segment has its own mix coefficient per route (determined by route distance, endpoint character, and whether the route touches a hub) and its own beta vector in the choice model.
The choice model is a multinomial logit (MNL):
U_{carrier, segment} = β_price · fare + β_time · block_time + β_freq · log(seats/day) + β_brand · brand_score + β_nonstop · is_nonstop + β_loyalty · loyalty_match
Business travelers have a high time beta and low price beta. They’ll pay for schedule, not for the cheapest fare. Leisure travelers flip that. The frequency term uses seats/day rather than pure frequency because capacity matters for real passengers, not just schedule count.
Every competitive set includes an outside option: the “don’t fly, drive, or take the train” alternative. Its utility is a constant plus a fare-responsive term. Without this, a monopoly route yields market share of 1.0 regardless of what you charge. With it, fares that are too high bleed demand to the outside option. An LCC entering an unserved market at a competitive fare can actually create traffic that wouldn’t have existed otherwise.
Hub banks change the connecting math
A hub isn’t just a big airport you fly into. It’s a scheduling pattern: waves of arrivals bunched into 60 to 90 minute windows, followed by a gap, then waves of departures fanning out again.
SkylineSim detects these hub banks from your actual rotation each day. We walk every tail’s schedule at the hub airport, cluster arrivals into 90-minute windows (minimum 4 flights to qualify), look for the departure wave that follows within 30 to 150 minutes, and score the resulting bank on four dimensions:
- Cluster tightness (25 pts): tighter arrival spreads score higher
- Connection feasibility (35 pts): fraction of (arrival, departure) pairs satisfying MCT to 4 hours
- Destination diversity (20 pts): unique destinations / total departures
- Arrival/departure balance (20 pts): min(arrivals, departures) / max
A well-tuned hub bank scores 75 to 85. Bank quality feeds directly into demand: a single bank at quality 100 boosts connecting passenger share on routes through that airport by up to 15%. Multiple decent banks (quality 50 or above) add up to 5% more in aggregate. The hard cap is plus 20% on connecting traffic.
Induced demand: the market responds to itself
The gravity model is exogenous. It tells you how many passengers exist in the market, regardless of what you charge or how often you fly. That’s correct as a long-run structural estimate, but it misses something important: markets respond to the quality of service in them.
We model this through a Market Quality Index applied as a multiplier on the gravity pool:
MQI = farePart × freqPart × servicePart
farePart = (cheapestFare / fairFare)^(−ε_stim): when the market’s cheapest fare is below the parametric fair fare, demand is stimulated. The elasticityε_stimis distance-tiered.freqPart: saturating curve above a frequency threshold. Adding a third daily departure on a thin route matters more than adding the tenth.servicePart: an unserved OD has latent demand. When the first carrier enters, some of that latent demand materializes.
At reference quality, MQI is exactly 1.0. The gravity pool is unchanged. Only when you deviate from reference does the pool adjust.
This is how a budget carrier opening a previously unserved European secondary airport genuinely grows the market, not just captures existing traffic. The demand was latent. The service unlocked it.
The edges that matter
Three more layers apply on top:
HSR substitution. On short-distance pairs where high-speed rail genuinely dominates (Paris-Lyon, Frankfurt-Cologne, Tokyo-Osaka), air demand is structurally capped. We apply a per-pair air_share multiplier sourced from mode-share literature. The cap is directional: Paris-London gets a higher rail share than Paris-Madrid, which gets a much lower one.
FX asymmetry. Exchange rates affect leisure tourism flows. A weak Thai Baht against the Euro makes Thailand cheaper for European tourists, and demand rises. We model this as a bilateral relative-exchange-rate term: clamp(1 + 0.20 × log(destRelStrength), 0.7, 1.4). Same-currency pairs (intra-Eurozone, intra-dollar-zone) return 1.0 and are unaffected.
Weather. Per-flight delay and cancellation rolls are seeded on the flight’s ID (deterministic across replays), severity-banded on a 0 to 100 index, and applied at the departure hour. Origin weather dominates, but destination weather contributes at 70% weight. A severity-85 snowstorm at origin gives an 80% chance of delay with a mean of 90 minutes, and a two-hour-plus sustained event triggers cancellation.
What it adds up to
Open a route from New York to London in 1967 with competitive fares and a solid bank at JFK, and the demand forecast reflects:
- A gravity estimate using 1950s-bracket parameters interpolated toward the 1970s calibration. The absolute scale is much smaller than 2024, income elasticity is higher, and distance decay is steeper.
- Income levels for both metros correctly lower than 2024, by amounts consistent with World Bank real GDPpc history for those years.
- The income propensity multiplier applies the high-band exponent: transatlantic travel is beginning to democratize but is still well above median purchasing power.
- Seasonal variation from the NA-EU corridor shape, hemisphere-aware for leisure destinations.
- Day-to-day AR(1) noise that keeps every flight different without distorting the monthly total.
- Your market share of the total pool, calculated per segment against the carriers already flying the route.
- A connecting-traffic bonus if your JFK bank is well-constructed.
Change the year to 1973, and the oil crisis modifier clips long-haul demand sharply. Change it to 1978, and deregulation begins loosening fare floors on US domestic routes, stimulating traffic through the farePart term. Change it to 2020, and the route probably doesn’t fly at all.
Real airline planning is mostly about understanding the demand environment and positioning correctly within it. That is the surface we are trying to give players: not a number to trust, but a system they can actually reason about across seventy-plus years of aviation history.
SkylineSim is in development. We’re aiming for early access in 2027.
If this is your kind of sim, the Discord is where the build gets shared first: discord.gg/HsqucsgsGd
You can also sign up for the newsletter at skylinesim.app.
the skyline team