.lrb binary track format proposal
rationale
- LRO intends to abandon .trk (for good reasons)
- .track.json supports very little beyond vanilla .com features, relies instead on js scripts that make tracks very difficult to load properly on other implementations
- .track.json files with lots of data can end up much larger and become a burden quicker than track files in a binary format like .trk
- modifying an implementation for the purposes of creating a certain effect in a track often creates track files that are incompatible, either crashing, or loading improperly in the unmodified implementation
- even when only considering the two most common implementations, features vary and current formats don’t have a good way to handle this
goals
- small(ish, comparable to trk probably) file sizes by default
- implementation in LRO
- conversion to .track.json for tracks where the feature set fits with what .track.json supports
- conversion from existing formats
- support for all current features of both implementations and the flexibility to add features arbitrarily by modding
- information on required features so implementations can show good messages to users trying to load the track without proper support (“requires x mod of version y”, possibly “mod implementing this feature can be found here” or “no known mod implementing this feature”, and even “optional mod missing, [mod defined message about what it does and what happens if you load the track without it]”)
details
mods
the basic building block of the format would be a “mod”, which is declared in a list at the start of the file and has it’s own spec to describe how it modifies the IO. even features considered “vanilla” would be implemented as mods, allowing implementations to implement these features incrementally.
mod names
a mod name is an arbitrary string up to 255 characters, however there are some conventions:
- mod names are “namespaced”, usually by the mod developer’s name but sometimes by other groupings. examples:
- “moss7.invincibility” would be a mod made by moss7.
- “base.riderspawn” would be a mod from the “base” namespace, which contains mods describing features that implementations usually share.
- “linerider.com.script” would be a mod from the “linerider.com” namespace (notably, not parseable with a “.” in the namespace, but this is OK because the purpose of namespaces isn’t for implementations to read them) which describes how a js script is stored in the track file for use in linerider.com.
the mod table
“the mod table” is a structure at the start of the file, and the only thing not defined by mods. it is a list of mods, starting with:
- the LRB magic number (4C 52 42)
- the .lrb version (this document defines .lrb version 0 as a draft for the format, once it is finalized at 1 it will hopefully never increment)
- the number of mods and then followed by a list of that many mod entries. each entry with the following information:
- the mod name
- the mod version
- the section table, which contains pointers to each section of the file that the mod writes. starting with:
- the number of sections
- and each entry containing:
- the pointer to the section in the file
- the length in bytes of the section in the file
- a bool for “is this mod optional?”
- if the mod is optional, a string describing to the user what happens when the track loads without the mod.
What Mods Actually Do
many mods simply define a section and write their own data to that, however a mod might also do any of the following:
- make a game modification without writing anything (it exists only to note a feature of the track, and doesn’t require any extra data) an example would be something like an invincibility mod.
- modify how another mod writes it’s data. an example would be that the mod “base.halfprecisionscenery” might modify “base.scnline” such that scenery lines are written with 32 bit floats instead of 64 bit floats. this type of mod can’t be optional as it means the mod that it changes wouldn’t load properly.
implementation details
- all numbers described in this spec written in little endian
- integers are named as
u
for unsigned andi
for signed, followed by the number of bits, so au8
is one byte. - floats are named as
f
followed by the number of bits, so anf64
is a double precision float and anf32
is a single precision float. - a bool is one byte, which is zero if it is false, or otherwise true.
Mod Table
at the start of a .lrb file, the following is written:
name | type | description |
---|---|---|
magic number | byte[3] | should always be 0x4C 0x52 0x42 (LRB). |
lrb version | u8 | for this spec, value should be 0. |
mod count | u16 | the amount of entries in the mod table |
[mod entries] | modtable_entry[mod count] | variable sized list of entries to the mod table, each one reading as described below |
Mod Table Entry
for each mod in the mod table, the following is written:
name | type | description |
---|---|---|
name length | u8 | the length of the name string |
name | utf-8 string of length [name length] | the name of the mod |
version | u16 | starts at 0 and increments with each breaking change to a mod, such that an implementation can know if it’s current version of a mod will properly load the file. |
section count | u8 | the number of entries in this mod’s section table |
[section entries] | section_entry[section count] | variable sized list of entries to this mod’s section table, each one reading as described below. |
optional flag | bool | indicates that this is an optional mod, and that the following string is present (if false the optional string part of the entry is omitted) |
optional string length | u8 | the length of the optional string |
optional string | utf-8 string of length [optional string length] | a string intended to be displayed for the user if the track loads without this mod present |
Section Table Entry
for each section in a mod’s section table, the following is written:
name | type | description |
---|---|---|
pointer | u64 | an absolute pointer within the file (if the mod table is 0xFF bytes long and this section immediately follows then the value of this pointer is 0xFF ) |
length | u64 | length in bytes of this section |
Base Mods
here are a few mods from the “base” namespace as an example for how mod specifications might look:
base.simline
this mod describes how simulation lines (those that bosh collides with) are written. it has one section:
name | type | description |
---|---|---|
count | u64 | number of lines |
entries | simline[count] | the list of lines |
where each simline is written like:
name | type | description |
---|---|---|
flags | u8 | a few bitflags packed into a byte as described below |
start | f64[2] | the x then y coordinate of the line’s starting point |
end | f64[2] | the x then y coordinate of the line’s ending point |
and where the flags byte contains one bit for each of the following (from most to least significant):
name | description |
---|---|
red | is 1 if this is an accel line |
inverted | is 1 if this line is inverted |
left extension | is 1 if the line extension is present on the left side of this line |
right extension | is 1 if the line extension is present on the right side of this line |
the rest of the bits are unused.
base.redmult
this mod lists fractional multipliers for acceleration lines. every time base.simline
encounters an acceleration line it reads the next multiplier value from this mod’s list. it has one section:
name | type | description |
---|---|---|
count | u64 | number of red lines |
multipliers | f64[count] | one double precision float per red line. |
base.scnline
this mod lists scenery lines in its one section:
name | type | description |
---|---|---|
count | u64 | number of scenery lines |
entries | scnline[count] | the list of lines |
where each scnline is written like:
name | type | description |
---|---|---|
start | f64[2] | the x then y coordinate of the line’s starting point |
end | f64[2] | the x then y coordinate of the line’s ending point |
base.scnwidth
this mod lists line width for scenery lines. for every line in base.scnline
, it reads an entry from this mod’s list for the width value. it has one section:
name | type | description |
---|---|---|
count | u64 | number of scenery lines |
entries | f32[count] | one f32 for the width of each scenery line |
base.halfprecisionscenery
this mod has no sections, but it’s presence in the mod table denotes that base.scnline
’s lines are written with single precision f32s instead of f64s.
all of these mod specs are version 0 alongside the main spec in this document. this doesn’t describe the complete feature set of any current implementation but should serve as a good example for the modular nature of the format.