Skip to content
You're reading the docs for Railroader 0.6 (the MoW Update), currently available on the Experimental branch.

Terrain Tile Format

Terrain is stored as a directory of square tiles: one Terrain.json manifest plus one .riff file per tile.

{
"origin": { "latitude": 35.3826, "longitude": -83.4954 },
"tileDimension": 500,
"heightmapResolution": 513,
"tiles": [
{ "x": 0, "y": 0, "min": 600, "max": 667 }
]
}
FieldMeaning
originLatitude/longitude of world position (0, 0), the south-west corner of tile (0, 0).
tileDimensionTile edge length in meters.
heightmapResolutionHeight samples per tile edge. Defaults to 513 when absent.
tiles[].x, tiles[].yTile position. x increases east (+X), y increases north (+Z).
tiles[].min, tiles[].maxThe tile’s height range in meters. Must match the tile’s RHGI chunk.

Leave tileDimension (500) and heightmapResolution (513) as the game writes them. Other values are untested, and changing either in an existing tile set does not convert its tiles.

Tile (x, y) covers world X from x * tileDimension to (x + 1) * tileDimension, and world Z over the same range using y. Positions may be negative.

Each tile is stored as tile_XXX_YYY.riff, with the position zero-padded to at least three digits, for example tile_004_012.riff or tile_-001_000.riff.

Tiles are standard RIFF files. All integers are little-endian.

OffsetSizeValue
04RIFF
44int32 size of everything after this field
84Form type RRT0
12…Chunks

Each chunk is a 4-byte ASCII id, an int32 payload size, then the payload. A payload with an odd size is followed by one pad byte, which the size does not include.

Chunks may appear in any order. Readers should skip chunks they do not recognize.

RHGT and RBIO hold rasters with this payload layout:

OffsetSizeValue
04Pixel format: R16 , R8 , RFLT, RGBA, RGB , or RBC4 (BC4 block-compressed)
44int32 width
84int32 height
12…Pixels, rows packed with no padding

Pixel (x, y) is at index y * width + x. Row 0 is the south edge and column 0 is the west edge.

12 bytes:

OffsetTypeValue
0int32Tile format version: 0 = no biome raster, 1 = has RBIO
4int32Minimum height, meters
8int32Maximum height, meters

R16, heightmapResolution x heightmapResolution (513 x 513). Each sample is an unsigned 16-bit value scaled linearly between the RHGI min and max:

height = sample / 65535 * (max - min) + min
sample = round((height - min) / (max - min) * 65535)

Samples sit on the tile’s grid vertices. The first and last rows and columns lie exactly on the tile edges, so neighboring tiles share their edge samples, and those samples should be equal.

The game also reads RFLT heights (32-bit float meters, min/max ignored), but always writes R16.

R8, 256 x 256. Each texel is a biome class code covering 1/256 of the tile, sampled at the texel center. The values are categories, so the chunk must stay R8. Block compression or interpolation produces codes that don’t exist.

The codes currently in use are listed below. Fetched land cover data is mapped to the family codes (multiples of 10). Dirt and Gravel are written by biome painting in the Map Editor. Ballast is applied at runtime by map features and is not normally stored in tiles.

CodeBiomeStatus
0Unknown (no data)No vegetation
10WaterSupported
20BarePlaceholder sparse grass only
21BallastBallast texture, no vegetation
22DirtDirt texture, no vegetation
23GravelGravel texture, no vegetation
30GrassSupported
40ShrubSupported
50TreesSupported
60WetlandPlaceholder vegetation
70CropsPlaceholder vegetation
80BuiltNo vegetation
90IceNot supported; for permanent ice/glaciers

Codes are grouped in families of 10, with variations in the units digit. Codes 128 and above are reserved for asset packs.