# City data format (`world.json`, version 1)

Engine-neutral description of the city data Grand Theft Frankfurt (GTF) bakes and plays, so that the Godot, Unity
and Unreal versions can load it without the GTF code. Everything here was checked against the GTF writer
(`tools/bake.mjs`), the GTF reader (`src/world/WorldData.ts`), the modules that read the loaded world (`src/world/*.ts`,
`src/ai/*.ts`) and the Frankfurt file itself (`shared/cities/fra/world.json`, identical to GTF's
`public/data/world.json` of 2026-10-19). If GTF and this page ever disagree, GTF's `bake.mjs` and `WorldData.ts` win:
fix this page.

- Served at `https://grandtheft.world/data/cities/<code>/world.json` (see `cities.json`; `playable: true` means the
  file exists). Frankfurt (`fra`) is also in git at `shared/cities/fra/world.json`.
- One JSON object, UTF-8, about 10.6 MB for Frankfurt. No comments, no binary parts.
- Licence: see [Licence and attribution](#licence-and-attribution) – the attribution must be shown in the game.

## Coordinates, units, encoding

| What | Rule |
| --- | --- |
| Plane | Local tangent plane in metres around `origin` (lat/lon of (0, 0)). Frankfurt: 50.1105 N, 8.6700 E. |
| Axes | **+x = east, +z = south, +y = up** (right-handed, the three.js and Godot convention). North is **−z**. |
| Ground | The file has **no elevation**. GTF's ground is flat at y = 0 (only the river bed is dug in, see `water`). Every height (`h`, `mh`, `rh`) is metres above that ground. |
| Projection | `x = (lon − origin.lon) · mPerDegLon`, `z = −(lat − origin.lat) · mPerDegLat`; inverse `lat = origin.lat − z / mPerDegLat`, `lon = origin.lon + x / mPerDegLon`. Both factors are in the file (Frankfurt 111231.157 and 71531.303 m per degree). Equirectangular, good to about a metre over the box. |
| Lengths | **Integers in decimetres** – divide by 10. This holds for every coordinate, width, length, height, radius and bound. Quantised to ±5 cm. |
| Angles | **Integers in milliradians** – divide by 1000. Three different conventions, see [Angles](#angles). |
| Not decimetres | `origin` (degrees), `mPerDegLat/Lon` (m per degree), `ms` (km/h), counts (`l`, `lv`, `c`, `n`), enums, strings. |
| Points | Flat arrays `[x0, z0, x1, z1, …]` (a list of trees is one flat array, a ring is one flat array). |
| Rings | Open: the last point is not the first (drop a repeated closing point if you meet one – 4 of 24,843 building rings have it). At least 3 points. |
| Winding | **Not normalised** in the file (Frankfurt: 15,896 outer building rings positive, 8,947 negative; holes mixed). GTF normalises on load, so must you: outer rings to signed area `A = ½ Σ (x_i·z_{i+1} − x_{i+1}·z_i) > 0`, holes to `A < 0`. With `A > 0` (clockwise seen from above on a north-up map, because +z points south) the outward normal of edge `(dx, dz)` is `(dz, −dx) / len`. |
| Holes | `hl`: array of rings (each a flat array), present only where there are holes (courtyards, islands). |
| Optional fields | Left out when empty or false; a flag is `1` when present, never `0`. Always written as 0/1: `br` (edges), `b` (rails), `r` (racks). Defaults: [table](#defaults-for-missing-fields). |

### Engine axis mapping

| Engine | Position from file (after ÷10) | Notes |
| --- | --- | --- |
| Godot 4 | `(x, y, z)` unchanged | Same right-handed Y-up frame as three.js. North = −Z = Godot's forward. |
| Unity | `(x, y, −z)` | Left-handed; +X east, +Z north. Flipping one axis mirrors the winding: swap two indices of every triangle, or build triangles after converting and check them against the intended normal. |
| Unreal 5 | `(x, z, y) · 100` (cm) | X east, Y south, Z up (left-handed). An axis swap is also a mirror: reverse the winding as for Unity. |

Robust rule for all three: every face has a known intended normal (walls: the outward normal above; roofs, ground,
roads: up). After building a triangle in engine space, compare its geometric normal with the intended one and flip it
if they disagree. GTF does exactly this (`GeomBuilder.tri()` in `src/world/Buildings.ts`).

### Angles

Convert the stored angle to a **direction vector in GTF space first**, map it like a position, then derive your yaw.

| Field | Direction in GTF (x, z) | Meaning |
| --- | --- | --- |
| `shops[].a` | `(sin a, cos a)` | Outward normal of the shop's facade (from the wall towards the street). |
| `garages[].e[2]` | `(sin a, cos a)` | From the entrance into the building (towards the footprint centroid). |
| `racks/scooters/boards/cargo/motos[].a` | `(cos a, sin a)` | Along the nearest street or path (the kerb direction). Note the swapped sin/cos. |

Yaw from a GTF direction `(dx, dz)`: Godot `rotation.y = atan2(dx, dz)` for a model whose front is +Z,
`atan2(−dx, −dz)` for a front of −Z (Godot's convention); Unity `yaw = atan2(dx, −dz)` (degrees, model front +Z);
Unreal `Yaw = atan2(dz, dx)` (degrees, model front +X). Three.js/GTF uses `rotation.y = a` for the `(sin a, cos a)` fields.

## Top-level keys

Counts are Frankfurt's. Keys marked optional may be missing (GTF then uses an empty list); GTF's loader fails
without any of the others.

| Key | Type | Count | Meaning | How GTF uses it |
| --- | --- | --- | --- | --- |
| `version` | int | 1 | Format version. | – |
| `origin` | `{lat, lon}` degrees | – | Geographic point of (0, 0). | Lat/lon conversion. |
| `mPerDegLat`, `mPerDegLon` | float | – | Metres per degree at the origin. | Lat/lon conversion. |
| `bounds` | `{minX, minZ, maxX, maxZ}` dm | – | The extraction box (Frankfurt ±2861.3 m × ±1946.5 m = lat 50.0930–50.1280, lon 8.6300–8.7100). Content reaches past it: buildings up to 250 m, roads, rails and hedges up to 150 m. | Ground grid = bounds + 200 m on each side. Make your ground at least bounds + 250 m. |
| `roadTypes` | string[25] | 25 | Names for `graph.edges[].t`. | [Road types](#road-types). |
| `buildings` | object[] | 24,843 | Extruded footprints with roofs. | [Buildings](#buildings). |
| `graph` | `{nodes, edges}` | 24,519 / 33,250 | Road and path network. | [Road graph](#road-graph). |
| `rails` | object[] | 1,703 | Above-ground rail and tram centrelines. | [Rails](#rails). |
| `water` `green` `parking` `landuse` `piers` `bridges` | object[] | 58 / 1,305 / 222 / 145 / 4 / 61 | Area polygons. | [Areas](#areas). |
| `hedges` | int[][] | 343 | Hedge polylines (optional). | Hedges along the line. |
| `trees`, `treeKinds` | int[], int[] | 29,513 | Tree positions and crown types (`treeKinds` optional). | [Trees](#trees-lamps-signals). |
| `lamps`, `signals` | int[] | 4,852 / 974 | Street lamps, traffic lights. | [Trees, lamps, signals](#trees-lamps-signals). |
| `pois` | object[] | 2,488 | Points of interest. | [Points](#points-of-interest-stops-districts). |
| `stops` | object[] | 501 | Bus and tram stops. | [Points](#points-of-interest-stops-districts). |
| `districts` | object[] | 16 | District name circles. | [Points](#points-of-interest-stops-districts). |
| `shops`, `shopKinds` | object[], string[15] | 3,567 | Shopfronts (optional). | [Shops](#shops). |
| `racks` `scooters` `boards` `cargo` `motos` | object[] | 3,002 / 406 / 7 / 180 / 9 | Parked bikes, e-scooters, skateboards, cargo bikes, motorbikes (optional). | [Spots](#vehicle-spots). |
| `garages` | object[] | 13 | Multi-storey car parks you can drive into (optional). | [Garages](#garages). |
| `attribution` | string | – | `"Map data © OpenStreetMap contributors, ODbL"`. | Show it. |

## Buildings

One record per extruded body. A building's **array index is its id**: GTF seeds facade palette, colour variation and
roof tint from it, so keep the order if you want the same look.

| Field | Type | Unit | Meaning |
| --- | --- | --- | --- |
| `p` | int[] | dm | Outer footprint ring (3–377 points in Frankfurt). |
| `h` | int | dm | **Wall top (eave) height** above ground, 25–2618. The roof sits **on top** of it. |
| `mh` | int | dm | Wall bottom (OSM `min_height`); > 0 for bodies that start in the air (199: canopies, passages, the upper stages of towers). |
| `r` | int | enum | Roof shape: `0` flat, `1` gabled (OSM gabled, round), `2` hipped (hipped, half-hipped, gambrel, mansard), `3` pyramidal (pyramidal, skillion; spires), `4` dome (dome, onion). Frankfurt: 10,258 / 13,321 / 1,208 / 50 / 6. |
| `rh` | int | dm | Roof height above `h`; the top is `h + rh`. **Ignore `rh` when `r = 0`** (122 records carry a value there; GTF never uses it). |
| `k` | int | enum | Kind: `0` other (`building=yes`), `1` residential, `2` retail/shop, `3` office/commercial/hotel/civic, `4` industrial/warehouse/garage/shed/carport, `5` church, `6` station, `7` landmark (named in the baker's landmark table: skyscrapers, museums, towers). |
| `ra` | 1? | flag | The ridge of a gabled roof runs **across** the long axis of the footprint's oriented bounding box (surveyed ridge). Default: along it. |
| `hl` | int[][] | dm | Holes (courtyards), 87 buildings. |
| `c` | string? | hex | Facade colour `rrggbb`, no `#` (OSM `building:colour`/`colour`; names mapped to hex by the baker). |
| `rc` | string? | hex | Roof colour `rrggbb` (OSM `roof:colour`). |
| `m` | string? | – | OSM `building:material`/`material`, raw: plaster, glass, stone, brick, metal, concrete, limestone, sandstone, tiles, mirror, timber_framing, metal_plates, wood, concrete_reinforced, pebblestone, copper. |
| `s` | string? | enum | Facade style override `glass`, `stone` or `sandstone` (landmark table; bodies ≥ 60 m lifted out of a host get `glass`). |
| `n` | string? | – | Name (OSM `name` or `addr:housename`). |
| `a` | string? | – | OSM `amenity` (place_of_worship, restaurant, kindergarten, …). |
| `sh` | string? | – | OSM `shop`. |
| `sf` | 1? | flag | Shopfront: a business at street level (mapped shop inside, retail tag, or on a main street). |

Where the numbers come from (for judging them): the official Hessian LoD2 survey wherever it matches a footprint
(eave → `h`, ridge − eave → `rh`, form → `r`, ridge direction → `ra`), else OSM `height`/`building:levels × 3.3 m`,
else a per-type estimate. Underground structures and tunnels are left out; multi-storey car parks that are in
`garages` are taken out of `buildings`.

How GTF draws them (`src/world/Buildings.ts`): walls from `y = mh` to `y = h` (skipped when `h − mh < 0.2 m`), the
roof from `h` up, an underside cap at `mh` when `mh > 0.05 m`. Facade: `s`, or else kind and height, picks the facade
layer (glass, Gründerzeit, brick, concrete panel, modern plaster, sandstone) and `m` overrides it (brick, glass,
sandstone/stone); `c` tints it (clamped to a plausible lightness), else a palette entry picked by the id. Pitched
roofs (`r` 1, 2) get tiles, `r` 3 and 4 metal, flat roofs gravel; `rc` overrides the roof tint. Shopfront band (4.2 m
ground floor) when `sf = 1`, `mh = 0`, `h > 7 m`, not glass, not a landmark and `k` not 4, 5, 6. Physics: each
footprint is a solid prism collider (walls plus flat top).

## Road graph

`graph.nodes` is a flat int array in dm: node `i` is `(nodes[2i], nodes[2i+1])`. `graph.edges` are OSM ways split at
every node shared with another way, so edges meet only at nodes and every junction is a node. Every node is used
(degree 1–7); an edge's polyline starts exactly at node `a` and ends exactly at node `b`; 14 edges are loops
(`a = b`). Left out by the baker: tunnels (except `building_passage`), `indoor=yes`, negative `level`, `area=yes`,
proposed/abandoned/razed ways. Ways are clipped to bounds + 150 m, and paths that ran into a building wall are cut at
the wall.

| Field | Type | Unit | Meaning |
| --- | --- | --- | --- |
| `a`, `b` | int | index | Start and end node. |
| `p` | int[] | dm | Centreline polyline including both end points (≥ 2 points). |
| `t` | int | index | Road type, index into `roadTypes`. |
| `l` | int | count | Lanes, both directions together (OSM `lanes`, else the type default), 1–6. |
| `o` | int | enum | One-way: `1` only a → b (drawing direction), `−1` only b → a, `0` both. Roundabouts are `1`. Frankfurt has no `−1`, but handle it. |
| `w` | int | dm | Full surface width without pavements: OSM `width`, else lane width × lanes (paths: the path width), +1.0 m for residential and living_street; pedestrian streets under 5 m become 6 m. 0.5–20.4 m. |
| `br` | int | 0/1 | Bridge. |
| `ly` | int | – | OSM `layer` (−1 to 2), 0 when untagged. |
| `len` | int | dm | Polyline length. |
| `n` | string? | – | Street name (10,471 edges). |
| `ms` | int? | km/h | OSM `maxspeed` (5–80). |
| `lit` | 1? | flag | `lit=yes`. |
| `rb` | 1? | flag | `junction=roundabout`. |
| `sv` | string? | – | OSM `service`: driveway, parking_aisle, emergency_access, alley, drive-through, commuter. |
| `sf` | string? | – | OSM `surface`: asphalt, paving_stones, compacted, sett, concrete, fine_gravel, paved, concrete:plates, dirt, pebblestone, metal, ground, unhewn_cobblestone, gravel, sand, grass_paver, wood, grass, unpaved, cobblestone. |
| `bk` | 1? | flag | Open to bicycles (cycleways and ordinary streets; paths; footways only with `bicycle=yes/designated/permissive`; never steps, platforms, piers, motorways, or where `bicycle=no/dismount`). |

Flags GTF derives on load (not in the file):

- **drivable** = type is drivable in the table below **and** `sv` is not `parking_aisle`, `driveway` or
  `emergency_access`.
- **walkable** = type is walkable in the table below. **rideable** = `bk = 1`. **major** = see table.
- **maxspeed** when `ms` is missing: 60 on major roads, 20 on living_street and service, else 50.
- Lanes for traffic: one-way edges use `l` lanes in their direction; two-way edges `floor(l / 2)` per direction (at
  least 1), lane width `w / l`. Driving side per city is in `cities.json` (`drivingSide`; Frankfurt right).

### Road types

`roadTypes` index, name, Frankfurt edge count, default width (per lane, or total for path-like types marked †),
default lanes, the derived flags, and how GTF draws it: ribbon height above ground in metres (tiny offsets that keep
overlapping ribbons from z-fighting), and whether GTF adds pavements.

| # | Type | FRA | Width m | Lanes | Drivable | Walkable | Major | Ribbon y | Pavements |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | motorway | 5 | 3.6 | 3 | yes | – | yes | 0.075 | – |
| 1 | trunk | 0 | 3.5 | 2 | yes | – | yes | 0.072 | – |
| 2 | primary | 1,170 | 3.4 | 2 | yes | yes | yes | 0.070 | yes |
| 3 | secondary | 1,101 | 3.3 | 2 | yes | yes | yes | 0.065 | yes |
| 4 | tertiary | 830 | 3.2 | 2 | yes | yes | – | 0.060 | yes |
| 5 | residential | 5,434 | 3.0 (+1.0) | 2 | yes | yes | – | 0.050 | yes |
| 6 | unclassified | 381 | 3.0 | 2 | yes | yes | – | 0.048 | yes |
| 7 | living_street | 154 | 2.8 (+1.0) | 1 | yes | yes | – | 0.046 | yes |
| 8 | service | 4,284 | 2.8 | 1 | yes* | yes | – | 0.040 | – |
| 9 | pedestrian | 578 | 5.0 † (< 5 → 6) | 1 | – | yes | – | 0.055 | – |
| 10 | footway | 14,421 | 2.0 † | 1 | – | yes | – | 0.030 | – |
| 11 | cycleway | 685 | 2.2 † | 1 | – | yes | – | 0.032 | – |
| 12 | steps | 506 | 2.0 † (drawn 2.5) | 1 | – | yes | – | 0.030 | – |
| 13 | path | 3,298 | 1.5 † | 1 | – | yes | – | 0.028 | – |
| 14 | track | 6 | 2.5 | 1 | – | yes | – | 0.026 | – |
| 15 | motorway_link | 0 | 3.6 | 1 | yes | – | yes | 0.070 | – |
| 16 | trunk_link | 0 | 3.5 | 1 | yes | – | yes | 0.068 | – |
| 17 | primary_link | 83 | 3.4 | 1 | yes | – | yes | 0.066 | yes |
| 18 | secondary_link | 70 | 3.3 | 1 | yes | – | yes | 0.062 | yes |
| 19 | tertiary_link | 16 | 3.2 | 1 | yes | – | – | 0.058 | yes |
| 20 | busway | 71 | 3.2 | 1 | yes | – | – | 0.050 | – |
| 21 | platform | 98 | 3.0 † | 1 | – | yes | – | 0.20 slab | – |
| 22 | pier | 24 | 3.0 † | 1 | – | yes | – | 0.40 slab | – |
| 23 | construction | 35 | 3.0 | 1 | – | – | – | 0.020 | – |
| 24 | corridor | 0 | 2.0 † | 1 | – | yes | – | 0.030 | – |

\* `service` is not drivable when `sv` is parking_aisle, driveway or emergency_access. Unknown type names (other
cities) should fall back to ribbon y 0.04 and not drivable/walkable, as GTF does.

## Rails

| Field | Type | Unit | Meaning |
| --- | --- | --- | --- |
| `p` | int[] | dm | Centreline polyline of one OSM way (`railway` = rail, tram, light_rail, subway above ground; tunnels left out). Not split at junctions: consecutive ways share end points where OSM connects them. |
| `t` | int | enum | `1` tram, `0` everything else (rail, light_rail, subway). |
| `b` | int | 0/1 | Bridge. |
| `l` | int | – | OSM `layer`. |

GTF draws a rail bed ribbon 3.4 m wide at y = 0.045 and a tram bed 2.7 m wide at y = 0.078 (above every road, so
tracks in the street show), keeps the ground flat under them, drops pavements to road level where a road crosses
tracks, and runs its trams along the `t = 1` polylines.

## Areas

`water`, `green`, `parking`, `landuse`, `piers`, `bridges`: records `{ p, hl?, t?, n? }` – outer ring, holes, type
string, name.

| Layer | `t` values (Frankfurt counts) | GTF use |
| --- | --- | --- |
| `water` | OSM `water=*`, else `waterway` (riverbank), else `water`: water 24, basin 13, pond 12, river 6, harbour 1, canal 1, reservoir 1 | **Big water** (`t` river/harbour/canal, or bounding box > 40,000 m²): surface at **y = −5.0**; the bed is −6.6 m at the polygon edge and deepens to −10.1 m 45 m in; outside the polygon the bank rises from −6.6 m to 0 over 24 m (smoothstep), roads on it held near 0, paths on it terraced. Small water: surface at y = −0.35. One water mesh. |
| `green` | leisure park, garden, playground, pitch, sports_centre; landuse grass, forest, cemetery, meadow, recreation_ground, village_green, allotments, flowerbed, farmland, orchard, plant_nursery, vineyard; natural wood, scrub, grassland, shrubbery, heath (natural: no `n`) | Ground texture class; sheds on allotments, bushes in scrub/shrubbery/heath. The trees of parks and woods are already in `trees` – do not scatter more. |
| `parking` | – (surface car parks) | Asphalt ground. |
| `landuse` | railway 68, residential 34, commercial 26, industrial 12, retail 5 | Railway and industrial change the ground texture; the map draws railway land. |
| `piers` | – (`man_made=pier` areas) | Not used by GTF today (pier ways are in the graph as type `pier`). |
| `bridges` | – (`man_made=bridge` outlines) | Not used by GTF today (bridges are drawn from edges with `br = 1`). |

`hedges` is `int[][]`: each entry one flat polyline in dm (`barrier=hedge`, 2–19 points); a closed hedge repeats its
first point at the end.

## Trees, lamps, signals

- `trees`: flat `[x, z, …]` dm. `treeKinds`: one int per tree, same order (missing → all 0): `0` round crown (lime,
  maple; 14,000), `1` spreading (plane, chestnut, oak, beech; 8,226), `2` columnar (hornbeam, poplar, birch, alder;
  3,771), `3` small/ornamental (cherry, magnolia, rowan; 2,840), `4` conifer (676). Holds the OSM trees, trees sampled
  every 10–13 m along OSM tree rows, a seeded fill of parks and woods, and seeded street trees (2 m behind the kerb,
  every 14–16 m); all of them already moved out of carriageways and buildings. GTF draws them instanced per 400 m cell.
- `lamps`: flat pairs, OSM `highway=street_lamp`. GTF adds its own lamps along drivable and pedestrian streets at
  runtime (every 26–32 m, alternating sides, none within 12 m of a mapped one) – they are not in the file.
- `signals`: flat pairs, OSM `highway=traffic_signals` nodes (on the road centreline at or near a junction). GTF puts
  signal heads there and its traffic AI stops at them.

## Points of interest, stops, districts

| Layer | Record | Meaning |
| --- | --- | --- |
| `pois` | `{x, z, t, n}` (dm, string, string or null) | `t`: `station`, `subway` (entrance), amenity fuel, police, hospital, bank, cafe, restaurant, bar, fast_food, pharmacy, theatre, cinema, place_of_worship, casino, nightclub; tourism attraction, viewpoint, museum; `shop_clothes`, `shop_supermarket`, `shop_car`, `shop_department_store`, `shop_mall`. Used by the baker (shopfronts); GTF's runtime does not read them today. |
| `stops` | `{x, z, t, n}` | `t` = `bus` or `tram` (OSM `highway=bus_stop`, `railway=tram_stop`), `n` the stop name or null. GTF puts stop furniture there; e-scooter clusters were placed from them. |
| `districts` | `{n, x, z, r}` (r in dm) | Hand-set district circles. GTF's HUD name for a point is the district with the smallest `distance / r`; with no districts, the city name. Street names come from the nearest named edge within 30 m. |

## Shops

`shops`: one record per business that found a street-facing wall.

| Field | Type | Unit | Meaning |
| --- | --- | --- | --- |
| `x`, `z` | int | dm | Door position on the facade line. |
| `a` | int | mrad | Facade outward normal: direction `(sin a, cos a)`. |
| `w` | int | dm | Frontage width, 3–9 m, centred on the door. |
| `k` | int | index | Kind, index into `shopKinds`. |
| `n` | string or null | – | Business name. |

`shopKinds` = `restaurant, cafe, fast_food, bar, bakery, supermarket, deli, clothes, salon, pharmacy, kiosk, bank,
florist, books, shop` (indices 0–14; 14 = any other shop). GTF builds sign and awning per shop (and pavement tables
for food and drink), merged per 260 m cell, and skips a shop when another building stands within 1.2 m in front of it.

## Vehicle spots

`racks`, `scooters`, `boards`, `cargo`, `motos`: records `{x, z, a, c?, n?, r?, f?}`.

| Field | Meaning |
| --- | --- |
| `x`, `z` | Position in dm, already out of the road and out of buildings. |
| `a` | mrad, direction **along** the nearest street (or path): `(cos a, sin a)`. |
| `c` | Capacity: bicycle stands (racks, 2–40) or bays (motos, 2–10). |
| `n` | Number of vehicles: scooters 2–5, boards 1–2, cargo bikes 1–2. |
| `r` | Racks only: `1` rental station, `0` ordinary rack. |
| `f` | Cargo only: frame `0` long-john, `1` trike. |

Count = `c`, else `n`, else 1. GTF spawns the parked vehicles when the player comes near (a rack shows up to four
bikes side by side along the `a` direction, 0.68 m apart, noses to the street).

## Garages

`garages`: multi-storey car parks the player drives into. Their footprints are **removed from `buildings`**, so an
engine must build something there (GTF builds decks and ramps; a solid block is the minimum).

| Field | Type | Unit | Meaning |
| --- | --- | --- | --- |
| `p` | int[] | dm | Footprint ring (600–5,200 m²). |
| `lv` | int | count | Decks, 3–8. |
| `e` | int[3] | dm, dm, mrad | Entrance: point on the footprint edge nearest a carriageway, and the angle pointing **into** the building `(sin a, cos a)`. |
| `c` | int? | cars | Capacity (default 0). |
| `n` | string? | – | Name (default null). |

GTF lays the ground deck at the entrance's ground height + 0.12 m and runs two-lane ramps along the long axis.

## Defaults for missing fields

| Where | Missing field | GTF default |
| --- | --- | --- |
| top level | `hedges`, `racks`, `scooters`, `boards`, `cargo`, `motos`, `garages`, `shops`, `shopKinds` | `[]` |
| top level | `treeKinds` | every tree kind 0 |
| building | `hl` | no holes |
| building | `ra`, `sf` | false |
| building | `c`, `rc` | palette colour by facade layer and building index; roof tint by roof shape |
| building | `m`, `s`, `n`, `a`, `sh` | none |
| edge | `n`, `sv`, `sf` | none (surface: asphalt if drivable or busway, paving otherwise; cobbles for `sf` matching `cobble` or `sett`) |
| edge | `ms` | 60 major, 20 living_street/service, else 50 km/h |
| edge | `lit`, `rb`, `bk` | false |
| area | `t` | `""` |
| area | `hl`, `n` | none |
| spot | `c`/`n`, `r`, `f` | count 1, not rental, frame 0 |
| garage | `c`, `n` | 0, null |

## How to build meshes

GTF's way, which the engine versions should match in look and cost.

1. **Ground.** A flat plane at y = 0 over bounds + 250 m. Dig the river: inside big water the bed runs from −6.6 m
   at the edge to −10.1 m 45 m in; outside it the bank climbs from −6.6 m back to 0 over 24 m (smoothstep). Keep the
   ground flat under drivable roads, pedestrian streets, rails and building footprints. Water surfaces: big water at y = −5.0, small at y = −0.35 (earcut of ring plus holes).
2. **Walls.** Normalise the winding, then one quad per ring edge (outer ring and holes) from `mh` to `h`, normal
   `(dz, −dx)` outward. GTF's facade UVs: u = metres along the wall / 3.6 (one window column), v = metres up / 3.3
   (one storey); the shopfront band is the lowest 4.2 m.
3. **Roofs**, using the footprint's minimal-area oriented bounding box (long side L, short side W, tried over every
   edge direction) and `fill` = footprint area / box area:
   - `r = 0`, or any shape that does not fit below: flat cap at `h` (earcut with holes).
   - Pitched roofs need `rh > 0.3 m`. `r = 1, 2` on a near-rectangular footprint (`fill > 0.9` and ≤ 6 points, or a small house: `fill > 0.78`,
     W < 14 m, L < 24 m, no holes; churches `fill > 0.55`) with W > 4 m: box roof over the bounding box, ridge along L (along W
     when `ra = 1`, swap L and W), ridge height `h + min(rh, 0.6·W)`; gabled = ridge the full length with gable
     triangles, hipped = ridge shortened at each end by `min(W/2, 0.3·L)`. Eaves overhang `min(0.45, 0.055·W)` m.
   - `r = 1, 2` on any other footprint without holes: "inset hip" – a sloped band from the outline at `h` to the
     outline offset inward by `min(max(3, 0.55·rh'), 0.4·W)` at `h + rh'`, flat cap on the inner ring, with
     `rh' = min(rh, 6 m)` (churches `min(rh, max(6, 0.6·W))`); shrink the inset by ×0.55 (up to 7 tries) while the
     offset ring folds over itself, else fall back to flat.
   - `r = 1, 2` with holes: inset hip along the outer ring with `rh' = min(rh, 5 m)`, flat roof with holes below.
   - `r = 3` (`fill > 0.8`, or ≥ 8 points and `fill > 0.72`): pyramid to an apex over the vertex centroid at
     `h + min(rh, 1.2·W)` (churches and stations `4·W`).
   - `r = 4` (`fill > 0.85`): half ellipsoid on the box (semi-axes 0.49·L and 0.49·W), height `min(rh, min(L, W)/2)`,
     over a flat cap.
   - `mh > 0`: close the underside at `mh`.
4. **Roads.** For each edge a flat ribbon along `p`, half-width `w/2`, mitred joints (miter length capped at 1.8×),
   at the type's ribbon y; u across, v along in metres. Junctions: at every node with three or more drivable edges
   (and at sharp bends of two) a disc of radius `1.02 · max(w)/2` just under the highest ribbon there. Pavements on
   the "Pavements" types (not on bridges): on each side a 0.26 m gutter at road level, a 0.25 m kerb and paving at
   0.14 m, 2.6 m in all from the asphalt edge, clipped back `max(w)/2 + 1 m` from junction nodes and ramped down at
   crossings and tracks. Platforms: 0.2 m slab (≥ 3 m wide) with kerb faces. Bridges (`br = 1`): deck at the ribbon
   height (bridges are flat, the river lies 5 m below), a slab down to −1.3 m, 1.1 m parapets at half-width
   `w/2 + 2.6` (pavement types) or `w/2 + 0.3`, pillars every 45 m. Materials: asphalt (drivable and busway),
   paving (everything else), cobbles (`sf` matching `cobble|sett`), kerb, rail, tram, concrete (bridge sides).
5. **Rails.** Ribbons as above: rail bed 3.4 m at y = 0.045, tram bed 2.7 m at y = 0.078.

**Who moves where.** Cars: drivable edges (route on the graph with `o`). Pedestrians and walking AI: walkable edges;
the player on foot may also cross open ground. Bicycles: rideable edges. Everywhere: building footprints are solid
(prism colliders), big water is 5 m down, a bridge deck carries its own collider, platforms (0.2 m)
and piers (0.4 m) are steps up rather than ground.

**Draw calls (what GTF does, and the advice):** never one node or actor per building or edge – Frankfurt alone has
24,843 buildings and 33,250 edges. Merge **per tile and per material**:

- Buildings: one merged mesh per 250 m × 250 m tile (by footprint centroid) with a **single material** (a texture
  array; facade layer and tint are vertex attributes), plus a far-LOD mesh per tile (drops buildings under 24 m and
  900 m², no roof details except spires and the roofs of tall buildings). Near LOD within `min(1000 m, 0.45 × draw
  distance)`, far LOD out to the draw distance (2,600 m by default).
- Roads: one merged mesh per material for the whole city (asphalt, paving, cobbles, kerb, rail, tram, bridge
  concrete: 7 draws), frustum-culled; collision surfaces cut per cell as the player streams in.
- Trees instanced per 400 m cell, allotment sheds and bushes merged per 320 m cell, shopfronts per 260 m cell, all
  water in one mesh.

## Minimal example

Valid file with one record of each kind, taken from Frankfurt's `world.json` (only the edge's node indices are
renumbered to 0 and 1; in the real file they are 44 and 45).

```json
{
  "version": 1,
  "origin": { "lat": 50.1105, "lon": 8.67 },
  "mPerDegLat": 111231.15650877914,
  "mPerDegLon": 71531.30290219605,
  "bounds": { "minX": -28613, "minZ": -19465, "maxX": 28613, "maxZ": 19465 },
  "roadTypes": ["motorway", "trunk", "primary", "secondary", "tertiary", "residential", "unclassified",
    "living_street", "service", "pedestrian", "footway", "cycleway", "steps", "path", "track", "motorway_link",
    "trunk_link", "primary_link", "secondary_link", "tertiary_link", "busway", "platform", "pier", "construction",
    "corridor"],
  "buildings": [
    { "p": [1118, -2283, 1011, -2462, 884, -2388, 990, -2208], "h": 225, "mh": 0, "r": 1, "rh": 56, "k": 3,
      "c": "cccccc", "n": "Frankfurter Sparkasse", "a": "bank", "sf": 1 }
  ],
  "graph": {
    "nodes": [-21653, 17191, -21982, 17406],
    "edges": [
      { "a": 0, "b": 1, "p": [-21653, 17191, -21800, 17272, -21982, 17406], "t": 3, "l": 1, "o": 1, "w": 33,
        "br": 0, "ly": 0, "len": 394, "n": "Gutleutstraße", "ms": 50, "lit": 1, "sf": "asphalt", "bk": 1 }
    ]
  },
  "rails": [{ "p": [-24273, 15379, -23808, 15451], "t": 0, "b": 0, "l": 0 }],
  "water": [{ "p": [-2834, 8311, -2857, 8329, -2733, 8485, -2710, 8466], "t": "water" }],
  "green": [{ "p": [-25421, -7806, -24991, -7801, -24996, -7293, -25502, -7296], "t": "playground",
    "n": "Karl-Schwarzschild-Platz" }],
  "parking": [{ "p": [15646, 11142, 15811, 11044, 15869, 11174, 15607, 11333] }],
  "landuse": [{ "p": [13648, 13979, 13666, 13981, 13663, 14018, 13645, 14017], "t": "industrial" }],
  "piers": [{ "p": [-7570, 12514, -7532, 12492, -7512, 12481, -7446, 12444, -7386, 12550, -7503, 12620] }],
  "bridges": [{ "p": [-26277, -14530, -26286, -14568, -25863, -14666, -25853, -14636] }],
  "hedges": [[-19619, 16625, -19677, 16503]],
  "racks": [{ "x": -27515, "z": 18468, "c": 6, "r": 1, "a": 330 }],
  "scooters": [{ "x": -18219, "z": -9784, "n": 3, "a": 2894 }],
  "boards": [{ "x": 6438, "z": -3671, "n": 2, "a": 64 }],
  "cargo": [{ "x": 2451, "z": 18820, "f": 0, "n": 2, "a": -359 }],
  "motos": [{ "x": -12793, "z": 15292, "c": 4, "a": 1027 }],
  "garages": [{ "p": [-10627, 19614, -10680, 19609, -10855, 19589, -11001, 19570, -11059, 19565, -11001, 19058,
    -10569, 19107, -10587, 19266], "lv": 8, "e": [-11030, 19312, 1126], "c": 905,
    "n": "Parkhaus Uni-Kliniken, Sandhofstraße" }],
  "trees": [-20276, 6502],
  "treeKinds": [0],
  "lamps": [16896, 12618],
  "signals": [1992, -4966],
  "pois": [{ "x": -11851, "z": -8560, "t": "attraction", "n": "Adorno Ampel" }],
  "stops": [{ "x": -18219, "z": -9784, "t": "tram", "n": "Adalbert-/Schloßstraße" }],
  "districts": [{ "n": "Altstadt", "x": 9299, "z": -334, "r": 4200 }],
  "shops": [{ "x": -10487, "z": 2419, "a": 552, "w": 30, "k": 11, "n": "Sparda-Bank Hessen" }],
  "shopKinds": ["restaurant", "cafe", "fast_food", "bar", "bakery", "supermarket", "deli", "clothes", "salon",
    "pharmacy", "kiosk", "bank", "florist", "books", "shop"],
  "attribution": "Map data © OpenStreetMap contributors, ODbL"
}
```

Reading it: the Sparkasse is a gabled office (`k` 3) with walls to 22.5 m and a 5.6 m roof (top 28.1 m) around
(100, −234) m, i.e. 234 m north and 100 m east of the origin. Gutleutstraße here is a one-way secondary street, one
3.3 m lane, 39.4 m long, 50 km/h. The car park's entrance `a` = 1.126 rad points `(sin, cos)` = (0.90, 0.43): east
and a little south, into the building from its west wall.

## Parsing notes

- Numbers are plain JSON numbers; all coordinates fit easily in a double or a 32-bit int (Godot's `JSON` returns
  floats – exact for these values).
- Unity's `JsonUtility` cannot read jagged arrays (`hl`, `hedges`) or the mixed records: use Newtonsoft Json.NET or
  `System.Text.Json`. Unreal: `FJsonSerializer` / `FJsonObject` works.
- Treat every optional key as possibly absent and every array as possibly empty: other cities may lack Frankfurt's
  hand-placed layers (`districts`, `boards`) and its LoD2-based fields (`ra`).
- Sanity checks worth running on load: every `p` has an even length; rings ≥ 3 points, polylines ≥ 2; edge `a`/`b` are
  valid node indices; `treeKinds.length == trees.length / 2`.

## Licence and attribution

The data is derived from OpenStreetMap and is under the **Open Database Licence (ODbL) 1.0**. Show
**"Map data © OpenStreetMap contributors"** (the file's `attribution` string) in every engine version, for example on
the loading screen or in the credits. A changed or extended version of the data itself, if published, stays under
the ODbL. Building heights, roof forms and ridge directions come partly from the Hessian LoD2 survey (HVBG,
dl-de/zero-2-0, no conditions); see `shared/LICENSES.md`.
