Skip to content

Latest commit

 

History

894 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tailfin

A real-time online airline management sim. One shared persistent world, running at 2× wall-clock time, that never pauses.

You start with one leased aircraft and one airport. You paint it, you fit the cabin, you pick a route, and you watch it fly — in real time, on a live world map, alongside every other player's fleet.

Quick start

Requires Node (version pinned in .nvmrc) and pnpm.

pnpm install
pnpm verify

pnpm verify runs typecheck, lint, formatting, aircraft-asset validation, the production build, coverage tests and an indicative performance pass in CI's cheap-fails-first order. It prints what passed and what was skipped; CI remains authoritative for the protected merge checks. Its typecheck stage also emits the declaration files that packages resolve each other through.

The database-backed tests skip without DATABASE_URL, and pnpm verify says so in its summary rather than folding that gap into a pass. They are destructive by design and refuse to run against any database whose name does not end in _test or _ci; the verifier also distinguishes a refused URL from a disposable database it cannot reach.

Layout

packages/
  assets/   deterministic aircraft intake  — depends on shared + glTF tooling
  shared/   types and zod schemas          — depends on nothing
  sim/      pure deterministic simulation  — depends on shared only
  server/   Fastify web + Worker, database  — depends on shared, sim
  web/      React client and admin console  — depends on shared only
docs/
  adr/      architecture decision records
deploy/     server runbook, systemd units, Caddy, backups

The dependency directions above are enforced by lint, not convention. packages/sim may not import server, web or any node:* builtin; packages/web may not import sim. Both rules exist so the simulation stays deterministic and the server stays authoritative — see CONTRIBUTING.md.

What runs on a pull request

Check Asks Blocks?
typecheck · lint · test Do builds, tests and the running Caddy policy pass? Yes
dependency review Did this PR add a known-vulnerable dependency? High/critical advisories
CodeQL analyze (…) Does Tailfin's own code contain a dangerous pattern? Error or high/critical only

main is protected and required approvals are zero — the pull request is the gate, not a second person. CodeQL's measured thresholds and baseline decisions are recorded in ADR-0013.

Status

Pre-MVP, and pre-launch. The list below describes what is in the current repository; the live milestone list owns sequencing and completion status. The public site still serves a holding page: promoting the client is one environment variable (WEB_SURFACE) plus a deploy, not a different build.

What exists

  • The world clock. epoch + speed × (now − launch_date), derived and never stored, so a reset is two columns and offline progression is free (ADR-0005).
  • Airport data. ~86,000 aerodromes imported from OurAirports, tiered, with catchment and a packed great-circle distance matrix.
  • Flight mechanics, as pure functions. State machine with its failure branches, phase timeline, position interpolation along a great circle, and reachability and payload/range checks that name the limit that bound them.
  • A flight's economics. Block time, phase-integrated fuel burn, a world fuel price curve, turnaround, and settlement into an itemised flight_result when the aircraft lands — reconciled to the design doc's own published P&L to within a percent.
  • Rotations and schedules that survive a restart and an edit, and refuse the ones that assume an aeroplane can be in two places at once.
  • Disruption and weather. Seeded per world and per flight so a replay reproduces them exactly, with climatological weather feeding delays, cancellations, diversions and de-icing.
  • Demand pools. Appendix A.2's gravity model, sized for every viable city pair and split into business, leisure and VFR.
  • Accounts. Google, Discord and Twitch OAuth — each independently configured, and an instance offers only the ones it holds credentials for — plus identity linking, database-backed sessions, atomic login rotation, immediate per-player revocation, shorter admin lifetimes, admin grants, and an append-only audit log the database itself refuses to let anyone edit (ADR-0015).
  • One authorization error contract. A missing session is 401, a signed-in actor without a disclosed grant is 403, and a malformed, missing or cross-owner private resource is the same 404. Ownership is resolved inside the query so player endpoints cannot become object-existence oracles (ADR-0020).
  • A repository-specific threat model. Security work prioritises the persistent world's integrity, then the identities and control paths that can change it. The model records the deployed web, worker, database, SSH and provider boundaries, attackers, ordinary operator mistakes and explicit non-goals in ADR-0012.
  • A browser security boundary at Caddy. CSP restricts code, connections and framing; powerful unused browser features are denied; the three sign-in providers' avatar hosts are the only image-source exceptions, one per provider and nothing else. The edge rollout was observed in report-only mode before enforcement, both live hosts pass the enforced-policy verifier, and HSTS preload is deliberately deferred (ADR-0014).
  • Recoverable off-box backups. Nightly DreamObjects dumps and their checksums are restored repeatably into a guarded _test database, migrated, booted and checked against real domain data and the world clock, with observed recovery time and up-to-24-hour data loss stated in the server runbook.
  • Known migration failure states. PostgreSQL applies the complete pending migration batch atomically; future SQL is checked for expand/contract compatibility with the previous release, and a verified local dump gates every non-empty deploy batch. A failure reports rolled back, all applied, or unknown instead of implying that failed code means unchanged schema (ADR-0016).
  • Airline founding. An authenticated player can found one airline in an open world, choosing its identity, base country and first hub. Ownership, config-backed opening cash, initial reputation and the free hub commit together or not at all; database constraints arbitrate code collisions and return the submitted code in the refusal. The no-menu /found desk reads those starting terms from the server, searches real tiered airports, warns without blocking an ambitious flagship choice, offers taken-code alternatives inline, and lands a successful founder on the network page.
  • A versioned airline starting position. Worlds pin an immutable, runtime-validated economy version that supplies opening cash and the free-hub allowance to founding. Unknown versions are refused when a world is created rather than when its first player arrives (ADR-0008).
  • Explainable airline cash. Every game-balance change records its amount, cause, reference, game time and resulting balance in the transaction that caused it. Database constraints make cause replay idempotent and refuse any balance that does not equal the movement fold (ADR-0011).
  • Race-safe airline code allocation. Founding allocates IATA and ICAO designators through the per-world unique constraints. An advisory checker and constraint refusals offer deterministic, name-derived alternatives without leaking unowned reservations (ADR-0009).
  • One player-airline context boundary. Player operations resolve ownership from the authenticated session and active world before a handler runs, then query only inside that airline. A world is selected explicitly when several are possible; no-airline and ambiguous-world states have stable responses shared by every guarded endpoint (ADR-0010).
  • Airline identity guardrails. One shared schema gives Unicode display names and operational callsigns/codes explicit rules, with field-level failures. A permissive moderation interface sits on both founding and an audited admin force-rename remedy; the stable airline id keeps its network and history attached through a rename (ADR-0007).
  • A private airline record and paid rebrands. The owner can read current identity, stable codes, cash and reputation from one typed endpoint; having no airline is a normal discovery result. Players may change the validated name, callsign and base country for a versioned price, while codes, cash and reputation remain immutable inputs. The event, identity and reconciling cash movement commit atomically (ADR-0017).
  • A retained airline lifecycle. Active airlines can make new commitments; restricted airlines remain recoverable and may operate what already exists; ceased airlines become read-only history. Cessation deactivates instructions but preserves flights, results and audit readability, releases designators from the live per-world namespace, and excludes the record from live caps and rankings. Player anonymisation removes sign-in authority while keeping that world history intact (ADR-0018).
  • Three atomic aircraft acquisition paths. Active airlines can lease an immediately available aircraft for a two-month deposit, buy a persisted used listing with its prior configuration intact, or pay for a factory build whose pinned options extend a wall-clock delivery date. The order, used-listing claim, explaining cash movement and any immediate airframe commit together; the Worker materialises due new orders exactly once. The typed API and its ownership boundary are documented in docs/aircraft-acquisition.md.
  • Maintenance that bites. Airframes accrue block hours and cycles from every settled flight; A, C and D checks fall due on whichever of the two limits arrives first, so a short-haul turboprop and a long-haul widebody wear out differently. Deferring a check raises the airframe’s technical-fault risk on a ramp rather than a cliff, and deferring it half again past the limit grounds the aeroplane — which a schedule then refuses as a conflict. See docs/maintenance.md, including what the risk does not yet feed.
  • A used aircraft market with a history. A world offers a bounded, self-refilling inventory of second-hand airframes, each with a build date, hours, cycles and a previous owner’s configuration, priced by a depreciation curve and an unusual-configuration discount. Every asking price arrives taken apart, so App. C.5’s claim that an odd specification is cheap to buy is something a player can see rather than infer. The market is the Worker’s, so it renews on dev only — see docs/used-aircraft-market.md.
  • A fleet you can take apart. The Fleet page lists every airframe an airline owns, most urgent first, with where it is, what it is doing with its time and which check binds next. Opening one shows its effective specification option by option — the base value, the amount each option actually moved it, and the running total — because a percentage from a brochure and the kilograms an aeroplane really burns are not the same number. The decomposition is arithmetically exact by construction and is tested against the airframe's own stored spec. See docs/fleet-management.md, including the two bulk actions that have nothing to act on yet.
  • Ground handling you can be wrong about. Every station offers three grades of handler on six service lines, with finite capacity competing airlines exhaust — and since the milestone's money landed, a grade changes what a turn costs as well as how fast and how reliably it goes. So the cheap handler is a genuine choice rather than a strictly worse one, and handling bought on the day is dearer than handling bought on a term. A contract runs a fixed term with a volume commitment, bills a shortfall if the airline never flew what it promised, and costs a pro-rated penalty to break — including by switching grades, because that is breaking one. Self-handling is the alternative: it needs a hub and heads on a monthly payroll, lands just short of a premium contractor when fully staffed and well below budget when it is not, and trades a per-turn fee for a fixed cost that does not shrink when the schedule does. The term and the payroll are the Worker's, so both bite on dev only — see docs/ground-handling.md, including why there is no web UI yet.
  • Crew who get better at what you actually make them fly. Every completed flight awards §10.2's XP to the crew aboard: a base figure from the sector length, a type factor from the aeroplane's weight, and a difficulty multiplier built from how hard the fields are, the weather they landed in, whether it was dark, whether they flew a diversion, and how far and how oceanic the sector was. A hard winter northern network levels crew measurably faster than easy domestic hops, which is the whole point — your route network shapes your crew, not just your balance sheet. Airport difficulty is data: pnpm data:difficulty derives a rating from runway length and elevation and raises it from a committed list of the famously hard fields, with an audit trail on every row. Every input is a stored or reproducible fact, so an old arrival re-derives the same XP and the factors that made it are written onto the flight result. Nothing spends it yet — levels and skill trees are M9-03. See docs/crew.md.
  • Crew who earn a name, and a skill tree you choose for them. §9.1 says never to manage individuals; §10.2 is the sanctioned exception, and it is opt-in. Crew stay counts in a pool until one crosses a level threshold, at which point a named person emerges from it with their own experience, a career history and points to spend — four branches for a pilot, three for cabin crew. The points make the airline cheaper and faster, never more popular: each one feeds one of the design's six efficiency ceilings, stacked with diminishing returns and hard capped, so a veteran airline is leaner rather than unbeatable. Type Mastery is the trade — the biggest bonus, and it goes inert the moment you sell the fleet it was earned on, points kept rather than refunded. Naming is the Worker's, so it bites on dev only, and the names come from the world's seed so a replay produces the same roster. See docs/crew.md, including why there is no respec and no public profile to put anybody on yet.
  • A training academy that gates a ceiling and grants no boost. §10.1's academy is built at a crew base and climbs five levels, each unlocking a higher rank an airline may train, a higher research tier it may later reach, and more of the finite training slots crew occupy while they are in a classroom. Modules go in independently — a CBT suite, a cabin mock-up, fixed-base and per-family full-flight simulators — and together they decide whether a type conversion is trained in-house at a fraction of the market rate or simply bought in, which is all the academy does to conversion: it is a discount and a ceiling, never a gate. Levelling the building grants no performance bonus of any kind; that is the research and the boosts, which are not built. Construction takes weeks of the world's calendar and no amount of money shortens it, and both the commissioning and the monthly upkeep are the Worker's, so both bite on dev only. See docs/training-academy.md, including which modules are priced but inert and why the design doc names two different academies.
  • Fuel that costs what the station charges. Every airport prices its own Jet A-1: a commodity factor for its region, an into-plane fee that scales with how hard the field is to fuel, and a per-station spread fixed for the life of the world. A sector out of a Gulf hub therefore costs measurably less to fuel than the same sector out of an African one, and the world price itself walks a curve over the world's own calendar rather than sitting at its opening level. Every number is EconomyConfig, so retuning fuel is an audited re-pin and not a deploy — and unlike almost everything else in M4 and M5 this needs no Worker, because the curve is a pure function of a game instant. See docs/fuel-pricing.md, including why tankering is not built.
  • One founded-airline database fixture. Server tests that need a player airline go through the real founding transaction, so they receive an open world, owner, founder hub, allocated per-world codes and configured opening cash with its AIR-06 movement. The harness cleans only the row identities it created; no fixture truncates shared tables.
  • One authorization test harness. Server route tests receive deterministic guest, playerA, playerB and admin identities with real session cookies, declare all expected HTTP statuses in one case, and clean only their own players. The Vitest setup also refuses every configured database whose name does not end in _test or _ci.
  • The admin console, at /admin for accounts holding a grant: an overview with server-decided alerts, world creation, speed changes, the full open/lock/archive/reset lifecycle, world health, a read-only player browser, linkable airline support records with current and historical routes and the complete paginated AIR-06 cash-movement ledger, an economy page listing every config version with which worlds pin it, a direct comparison of any two, and a re-pin that states what moves first, and the audit log. The airline record has no balance-edit control; it states instead whether its balance still equals the sum of its movements, and the overview raises an alert naming any airline where it does not.
  • One world renderer in two projections. The World page uses one deck.gl layer stack for a repeating flat map and a 3D globe, with a persisted device-aware default, shared camera and layer controls, bundled Natural Earth land, antimeridian-safe great-circle routes, and sustained-FPS degradation. Day and night are a sampled darkness field uploaded as a filtered texture, so the terminator is a smooth curve rather than a staircase of flat-shaded cells. The renderer belongs to that page and no other: it was a shell-wide backdrop, which meant every opaque page hid it while still paying for its WebGL context, and page content took every drag aimed at the map. Its contract and performance policy are documented in docs/world-renderer.md.
  • A deterministic 3D aircraft intake pipeline. Licensed source GLBs pass the official glTF Validator plus Tailfin's transform, naming, material, UV, LOD, budget and no-external-resource rules. Lossless optimisation is revalidated against the source contract; generated registry paths, hashes, GPU estimates, review reports and projected source/runtime comparisons are reproducible. CI rejects orphan runtime GLBs, while rollback changes the active version without rewriting exact published livery bindings. The registry remains empty until the first licensed asset is actually admitted — see docs/aircraft-asset-pipeline.md.

What does not exist yet

  • No production worker or complete flight-event lifecycle. The dev simulation runs in dist/worker.js, separated from the web process by ADR-0019, but production has no worker. The Worker currently handles FLIGHT_ARRIVE plus M4-04's real-time factory-delivery sweep; event types without a handler are parked as unsupported rather than destroyed. The exact live topology is maintained in CLAUDE.md.
  • No fleet editing, crew or cabin management. The Fleet page now lists the airframes an airline owns beside the world's era-gated catalogue, and the aircraft detail takes an effective spec apart option by option. It is read-only: reconfiguring a build, editing a registration, booking a check from the page, and the used-market and order screens all remain to be built. Crew, ground handling, server-applied liveries and cabin management are future work. The versioned livery document contract and paired SVG templates for every launch aircraft family exist. The base-fill builder autosaves a validated airline-scoped local draft, but no livery is server-saved or applied yet — so an aircraft has no livery to show and no cabin fitted, and the fleet table says so rather than inventing either.
  • Most of the player client. The standalone founding desk, private airline/rebrand desk, network/fare pages, aircraft catalogue, and dual-projection world surface are real. The world does not yet receive live aircraft/route data; finance, crew, design and board remain labelled placeholders, and the guided ninety-minute onboarding is still M10-01. The production front door still serves a holding page.

Where it runs

The canonical live node, service, database and deploy-command table is CLAUDE.md's operational topology. It is kept in one place so README does not become a second, stale topology after the next OPS change. The deployment reasoning is in docs/deploy.md, and the exact operator procedures are in deploy/README.md.

Merging does not deploy anything. Production moves only when somebody runs ./deploy/deploy.sh on the server, which is ADR-0003's deliberate choice and was re-affirmed by OPS-06: merge means staged, and a human promotes.

To see where things actually are, from anywhere and without an SSH session:

pnpm ops:status

Licence

Code: AGPL-3.0-only. Documentation: reserved (docs/LICENSE).

Copyright (C) 2026 Tailfinsim.

Tailfin is source-available and copyleft. You may read, run, fork and modify the code, and if you run a modified Tailfin as a service you have to offer your users its source — that is the AGPL's §13, and it is the reason this licence rather than the GPL. Tailfin is a hosted persistent world that nobody downloads, so plain GPL copyleft would essentially never trigger and a closed hosted fork would be permitted. The AGPL is what closes that.

docs/ is not covered. The design document and the ADRs are reserved, and that is deliberate: a fork inherits the simulation, not the appendices that explain why it behaves as it does.

Third-party terms — dependencies, the public-domain OurAirports dataset, and the position on manufacturer names — are recorded in THIRD-PARTY-NOTICES.md. No dependency is copyleft and none is licence-incompatible.

About

A real-time online airline management sim - one shared persistent world, running at 2× wall-clock time, that never pauses.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages