A scenery build produces four kinds of file. Two are plain text and can be
read with zcat, which makes them the fastest way to find out where a
build went wrong. The other two are binary, and one of them changed format
recently in a way nothing documents.
The inputs have their own formats too:
apt.dat for airports, and SRTM
.hgt for elevation.
The chain
| File | Written by | Contains | Readable? |
|---|---|---|---|
.hgt | n/a | Input. Raw SRTM heights, big-endian int16, no header | Binary |
.arr.gz | hgtchop | One tile’s height grid | Binary |
.fit.gz | terrafit | The fitted mesh nodes | Text |
.btg.gz | tg-construct | The terrain itself | Binary |
.stg | tg-construct | What FlightGear loads for a tile | Text |
.fit.gz; the one to check first
$ zcat 942050.fit.gz | head -3
1000
-122.50000000 +37.50000000 0.00
-122.50000000 +37.55250000 96.00
A node count, then one longitude latitude elevation per line.
If a tile’s elevations are all zero, or the coordinates are somewhere you did not
ask for, the problem is upstream of the terrain builder and you can see it in one
command.
.stg; what FlightGear actually loads
Also plain text, and more interesting than it sounds:
OBJECT_BASE 942050.btg
OBJECT KSFO.btg
OBJECT_SHARED Models/Airport/windsock_lit.xml -122.382294 37.610056 2.6 0.00
OBJECT_SHARED Models/Airport/beacon.xml -122.383256 37.636909 0.3 0.00
OBJECT_SIGN {@L}D{@R}10R-28L -122.383436 37.621469 2.0 328.08 3
OBJECT_SIGN {@L}F1{@Y,^l}B{^r} -122.378337 37.616397 2.6 85.14 3
OBJECT_BASE is the terrain mesh for the tile,
OBJECT lines are airports laid on top, OBJECT_SHARED
places a model from $FG_ROOT at a coordinate, and
OBJECT_SIGN is a taxiway sign with its legend encoded; @L
switches to the location style, @R to runway, @Y to
direction, so {@L}D{@R}10R-28L is the sign at taxiway D pointing at
runway 10R-28L.
If your scenery loads but an airport is missing, the .stg for
that tile tells you whether it was ever written.
.btg.gz; the terrain
Gzipped binary. The authority is SimGear’s SGBinObject
(simgear/io/sg_binobj.cxx), and reading that source is how the layout
below was established, then confirmed by parsing 97 files from two builds.
Header
uint32 header little-endian: 'S' in the top byte, 'G' in the next,
version in the low 16 bits
uint32 creation time
object count version >= 10 : int32
version >= 7 : uint16
older : int16
The magic is easy to get wrong. Read the first four bytes as a little-endian
uint32 and the version is the low half, the letters the
high half; so a version 7 file starts with the bytes 07 00 47 53.
Checking the first two bytes for “SG” will fail on every valid file.
Objects
Then, per object:
uint8 object type
property count, element count
version >= 10 : uint32, uint32
version >= 7 : uint16, uint16
older : int16, int16
per property: uint8 type, uint32 byte count, bytes
per element: uint32 byte count, bytes
Object types, from the same source:
| Code | Object |
|---|---|
| 0 | Bounding sphere |
| 1, 2, 3, 4 | Vertex, normal, texture coordinate and colour lists |
| 5, 6 | Vertex-attribute float and integer lists |
| 9 | Points; lighting, in practice |
| 10, 11, 12 | Triangle faces, strips and fans |
Version 10, and why you will meet both
Version 10 widens the counts to 32 bits. Support for it landed in SimGear in December 2023, as part of the World Scenery 3.0 work.
The reason to care: one build can write both. The two builds described on this site produced 97 files between them; the terrain tiles came out at version 10, the airport files at version 7. A reader that assumes one version will fail on half of them, and the failure looks like corruption rather than like a version it does not know.
A prediction that turned out wrong is worth recording: FlightGear 2020.3.16 predates version 10 by three years, so it was expected to reject those tiles. It reads them without complaint.
Checking your output
Walking the structure is enough to catch a truncated or corrupt file, and it
needs no libraries; the parse either lands exactly on end-of-file or it does
not. validate-btg.py, published with
the build record, does this in about sixty lines:
files checked : 44
parse cleanly to EOF: 44
BTG versions seen : [7]
total vertices : 115,989
And for the land-cover build, 53 files, versions 7 and 10, 1,265,905 vertices.
Reading the material names out of the triangle-face properties is a useful
second check, because it tells you what the terrain is actually made of.
Without land cover the build contained airport paint and ocean and little
else; lf_* line features, pa_* pavement,
Ocean, Airport. With it, the terrain materials appear:
Default, Lake, EvergreenForest,
Scrub, Marsh, Grassland,
Urban. Eighty-five distinct materials against seventy-five.
That difference is visible in the simulator too, and the gallery shows it, but the material list tells you before you render anything.
sg_binobj.cxx is the authority for
both. What is here is the structure, which is what you need to check a file
rather than to render one.