The map format
An Airtime map is two layers (engine.md):
- The collision layer (where you can run, jump, slide, swim and water-jump) is Quake 3 brushes and patches, simulated exactly like Urban Terror. It decides every time on every board.
- The visual layer (everything you see) is modern: meshes, physically based materials, light, sky and effects. It can change at any time without touching a single record.
The map also carries what Quake 3's BSP never could: typed entities, routes, medals, rules and metadata. Section 3 is the collision layer (built from the pieces in section 4), section 5 the visual layer.
What a map holds#
1. Identity and metadata#
- Name, authors, version, created / updated dates, license, credits (e.g. Rambetter's curve generators).
- Description, tags, difficulty tier per route, levelshots and preview video.
- Content hash of the collision and gameplay data. Boards and replays point at it. A change in looks only (light, materials) keeps the hash; a change in collision makes a new one.
2. Rules#
- The rule set the map is played with: the settings (
g_stamina,g_walljumps,g_gravity,g_allowPosSaving, …) and the moves the map uses (gravity zones, a jetpack, air jumps, …). The boards follow it automatically. - What is allowed: save / load, practice tools, styles, and weapons: the knife and pistol for shootable triggers, or a gun in sections built for it. Movement comes first; gunplay is secondary.
3. Collision (the exact part)#
- Convex brushes and patches exactly as Quake 3 traces them: our collision is
bit-identical to ioq3's
cm_*. Stored compactly, loaded into the same structures. - Contents per brush: solid, player clip, water / slime / lava (swimming, water jumps), trigger.
- Surface flags per side, named directly: normal, slick, icy (looks), ladder, no walljump,
no fall damage, no impact, player clip vs. visible, and Airtime surfaces such as bounce and
wall-run. (Classic maps encode these in Q3 shader names like
_sl,_icy,SURF_SLICK; the importer translates them.) - The source of each brush: which piece or curve made it, for the editor, for error messages ("the curve at … has an open edge") and for the curve clip checks.
- Checks at build time: no gaps or wrong clip strips on curves, no leaks, spawn present, every route complete.
4. Pieces (the editable source)#
- What the editor works on: boxes, ramps, curves (Rambetter's generators and assemblies) and decorative meshes. Each has an id that never changes, so edits, undo and building together can refer to it.
- The compiled collision is derived from pieces. A map holds both: the source for editing, the compiled part for playing without compiling.
5. Visuals (separate from collision)#
- Materials: physically based (base colour, roughness, metal, normal, emissive), with the slick / icy look as material layers (gloss, frost). Q3 shaders stay supported for classic maps.
- Render geometry: finer than collision (rounded edges, decorative meshes), as long as what you see never lies about where you can stand. Decoration that isn't solid is marked as such.
- Light: the sun, sky and light entities as data. Baked light (lightmaps, light probes) as a build output, cached and rebuilt when geometry changes, plus real-time light.
- Environment: sky, fog, time of day, weather, ambient sound zones, music.
6. Gameplay entities (typed, not key / value text)#
| Entity | Fields |
|---|---|
| Spawn | position, yaw, which route |
| Timer gates | see timing/triggers.md: start, stop, cancel, checkpoint, stage, filters |
| Teleport | volume, destination, keep / set velocity (UrT: 400 ups exit, dest +1 z) |
| Jump pad, push | volume, velocity or target |
| Mover, door, button | path, speed, trigger (shootable or touched) |
| Filters | speed, stamina, run, team (from Fliserv, the UJM servers' custom server) |
| Move elements | gravity zones and switches, zones (shape, condition, effect: push, teleport, reset, gravity, stamina, …), power-ups (kind, respawn), route pads |
| Enemies (story) | pursuer type, path (a recorded trace), weapon, seed |
| Print / guide text | message, who sees it |
| Light, sound | position, colour, range / file, volume |
| Hint markers | leet gates, runner-vision lines, camera spots for levelshots |
Each entity has a stable id. Links between entities are ids, not target names.
7. Routes and the run structure#
- A list of routes: name, colour, start gate, stop gate, checkpoints (required or not, in order or free), stages, tier, medal times (finished / bronze / silver / gold / author). Routes refer to gates by id, so several routes can share a start or a stop and differ only in their checkpoints (timing/triggers.md).
- Bonuses, leets and secrets as named items.
- This is data, so the HUD, the scoreboard and the boards read it directly.
8. Packaging#
- One file per map version: a small manifest (metadata, rules, routes, hash) plus content blobs (collision, pieces, render data, light, textures, sounds).
- Content-addressed blobs (hash names), so shared textures and sounds are downloaded once and a new map version only downloads what changed.
- The manifest can be read without the rest (map browser, boards).
- Streaming: the map is playable before all textures have loaded.
Classic maps#
- Import UrT BSPs: the thousands of existing jump maps load as they are. Their entities map
onto the typed entities (
ut_jumpstart→ start gate of route "type", and so on), and a timing overlay adds checkpoints or fixes broken timers without touching the BSP (like surf zones). - A modern visual layer for a classic map can be made without touching its collision, so its records stay valid.
- Export to UrT: a map that stays within UrT's rules and stock entities exports to BSP and
.map(Radiant) and plays in real UrT. Airtime-only features are listed when exporting.
Open questions#
- One file with everything, or source and compiled data separate (editor file vs. play file)?
- The BSP tree for collision, or a new acceleration structure that gives exactly the same traces? (Exactness first: only if it is proven bit-identical.)
- Mesh collision for decorative pieces: allowed, or only brushes and patches?
- How is the hash taken: per route (only that route's area), or the whole map?