Stat tables

shipstats.bin, gunstats.bin, missilestats.bin and pilotstats.bin define every ship, gun, missile and pilot. The game ships two identical copies of each: one in LANCER.CAB and one in resource.hog.

sltool stats list <file>

lists a table's records with their field names, and marks the records the engine never loads.

Frame

Each file is an array of 352-byte (0x160) records with no header. All four use the same frame: a 64-byte name padded with NULs, followed by the table's fields.

Mod scripts can read and change these records (Scripting). The tables below list each field's name in scripts; the record name is name.

File Records Fields end at
shipstats.bin 256 0x7C
gunstats.bin 15 0x58
missilestats.bin 16 0x64
pilotstats.bin 124 0x5C

Each table has its own loader in the payload executable. It reads one record at a time into a buffer on the stack and copies the fields it needs into a runtime table. No loader reads the name, or anything after the table's last field, and in the shipped files those bytes are zero in every record.

Table Loader Records read Runtime tables
Ships stats_load_ships (0x00466500) Exactly 256 ship_flight_stats (0x4F9E70) and ship_combat_stats (0x4FC670), 0x28 and 0x30 bytes per ship
Guns stats_load_guns (0x004788F0) Until the end of the file gun_stats (0x500CA4), 16 entries of 0x2C bytes
Missiles stats_load_missiles (0x00494BC0) At most 11 missile_flight_stats (0x5035E8) and missile_stats (0x5037A8), 0x28 bytes per missile in each
Pilots stats_load_pilots (0x0049CAE0) Until the end of the file pilot_stats (0x58A968), 194 entries of 0x24 bytes

Ships and missiles use the same runtime layout for their flight model: an object points to its flight model at +0x14, whether it's a ship or a missile. The flight model holds the max speed, the roll, pitch and yaw rates, the four inertias, and the max speed divided by the pitch rate, which the ship loader computes after reading the file. A missile's flight model only has its speed and rates. The word at +0x24 doesn't come from any file: the executable has a value for each ship type and missile, which sets how the AI turns the ship (Steering). The runtime layouts, with the source of every field, are in the modules of the files that load them: create.zig, guns.zig, missiles.zig and pilots.zig.

The gun and pilot loaders have no limit: a file with more records than the runtime table has entries writes past its end. The missile loader stops after 11, so the last five of the 16 missiles, Blazer, Iron Tooth, Death Claw, Brute and Hell Fire, are never read. They are all zero, and so is the eleventh, Stalker.

Where the names come from

The loadout screen labels ship and missile stats with strings from LANGUAGE.DLL. For ships, a layout table of eight 8-byte rows at 0x4EC020 gives each row on the screen a string ID and a display kind (a ten-segment bar or a number), and row r shows the ship's rth loadout value. The code that fills in those values shows which field is behind each label. Names marked Screen come from there.

Names marked Mods come from Starlancer-OSS stats-format.md, which found the fields by comparing known mods with the original files.

Ships

Offset Field Script name Loaded as Evidence
0x40 Max speed max_speed float Screen: Max Speed bar
0x44 Inertia inertia float Screen: Acceleration bar. Mods: Inertia
0x48 Yaw rate yaw_rate float Screen: Agility bar. Mods: YawMax
0x4C Yaw inertia yaw_inertia float Mods
0x50 Pitch rate pitch_rate float Mods
0x54 Pitch inertia pitch_inertia float Mods
0x58 Roll rate roll_rate float Mods
0x5C Roll inertia roll_inertia float Mods
0x60 Shield power shield_power truncated Screen: Shield Power bar
0x64 Armor class armor_class truncated Screen: Armor Class bar
0x68 Afterburner fuel afterburner_fuel truncated Screen: Afterburner Fuel, a number labelled SECS
0x6C Shield recharge shield_recharge float; 0 becomes 10 Screen: Shield Recharge bar
0x70 Gun energy gun_energy float The guns' maximum charge: create_object gives a new ship this much (GameObject.gun_charge), the guns recharge up to it, and the display's right arc shows the charge against it. Mods: GunEnergy
0x74 Gun recharge gun_recharge float The seconds the guns take to charge fully (guns_step). Mods: GunRecharge
0x78 Rounds rounds truncated The rounds a new ship's guns have. Each shot from a gun that fires rounds uses one (guns_step). Mods: Ammo

"Truncated" means the loader converts the float to an integer with _ftol.

The loader copies 0x40 to 0x5C into the ship's flight model in the order speed, 0x58, 0x50, 0x48, 0x44, 0x5C, 0x54, 0x4C: the three rates, then the four inertias, matching the pairs the mod comparisons found. It also computes 0x40 / 0x50 for each ship.

The loadout screen shows each stat as a bar scaled between the minimum and maximum of that stat across the ships it lists: the Alliance fighters the player can fly, and in a second list Coalition fighters (Loadout).

Guns

Offset Field Script name Loaded as Into Evidence
0x40 Range range truncated +0x14 How many ticks a shot lasts, which sets the gun's range. Mods
0x44 Speed speed float +0x18 How fast a shot flies (bullet_place)
0x48 Shield damage damage.shield float +0x1C The damage a hit does to a shield (object_damage). Also used by the threat check below. Mods: DamageMin
0x4C Hull damage damage.hull float +0x20 The damage a hit does to a hull or a component. Damage that gets through a shield is scaled by this over the shield damage. Mods: DamageMax
0x50 Fire rate fire_rate 100 / x, truncated +0x24 The ticks between shots. Mods: CyclicRate
0x54 Shot energy shot_energy truncated +0x28 The energy a shot takes from the guns' charge (guns_step). Zero for every gun that fires rounds. Mods: energy or heat per shot

The loader stores 100 / fire_rate, the interval between shots.

gun_stats is indexed by the gun type in a model's muzzle, from 1 to 15, so the file's first record is type 1, and type 0 means no gun. The loader only fills +0x14 to +0x28 of each record. The first five words come from the executable, and say what a shot costs the ship (energy for types 1 to 7, a round for the rest) and which sound it makes. OpenReliant's copy of those words is in guns/stats.zig, which make gun-tables generates from the executable.

The two damage values are not a minimum and a maximum: in several guns the shield damage is larger. player_spectral_shields_set (0x00415430) only uses the shield damage: when the spectral shields are turned on, it counts each gun type among the nearby hostile ships, weights each count by that damage, and tunes the shields to the most dangerous type, ignoring the two capital ship guns.

Missiles

Offset Field Script name Loaded as Evidence
0x40 Speed speed float Screen: Speed bar. Mods: MaxVelocity
0x44 Turn rate turn_rate float Copied into all three rates of the missile's flight model
0x48 Flight time flight_time x * 100, truncated Screen: Range is speed * flight_time. Mods: Range
0x4C Shield damage damage.shield float Screen: Damage is 0x4C + 0x50. The damage a hit does to a shield (missile_collide)
0x50 Hull damage damage.hull float Screen. The damage a hit does to a hull
0x54 Lock time lock_time truncated Screen: Locking Time is 0x54 * 0.01, labelled SECS
0x58 Decoy chance decoy_chance truncated In percent: the chance that a countermeasure decoys the missile (object_spend_countermeasure)
0x5C Lock range lock_range float The distance at which the missile can lock on to a target, for the player, the AI and missile turrets
0x60 Component damage component_damage float The damage a hit does to a component, on ships that have components

0x48 is the flight time, not the range: the loadout screen computes the range as speed times flight time. 0x54 is in hundredths of a second.

The screen hides the locking time for Screamer and Solomon, always shows Jack Hammer's damage bar as full, and hides Stalker's speed, range and damage.

Pilots

The record index is the pilot ID that missions use. The loader fills all 194 runtime slots with defaults, then applies each record it reads. Three fields are tier selectors: 0, 1 or 2 selects one of three presets for a group of runtime values, and any other value keeps the default. The four 32-bit words after them are copied with 16-bit moves, so only their low halves are used, and the high halves are never read. The shipped data uses tiers 1 and 2.

Offset Field Script name Effect
0x40 Tier A tier_a Six 16-bit values
0x44 Tier B tier_b One float
0x48 Tier C tier_c Two floats and a 16-bit value
0x4C Skill skill 0 low, 1 medium, 2 high, used by the Fight order's maneuvers (Maneuvers). Other values match none of the three
0x50 Copied Not available The pilot answers the radio's What's your status? only if this is above 0. Unknown: what else it does
0x54 Copied Not available Unknown: what it does
0x58 Copied Not available The radio checks it without any effect. Unknown: what it does

In scripts, the tiers are "level_0", "level_1" or "level_2" (or a number), and the skill is "low", "medium" or "high" (or a number).

Tier A B C
0 10, 40, 800, 1600, 400, 800 5.0 0.6, 0.4, 100
1 30, 50, 400, 800, 300, 600 3.0 0.8, 0.2, 50
2 100, 100, 200, 400, 200, 400 1.5 1.0, 0.0, 25
Default 30, 50, 400, 800, 200, 400 3.0 0.8, 0.2, 50

The loader applies B, then A, then C; tier 2 of C also sets the last two values of A, to 50 and 100.

Unknown: what the runtime values do. Each group changes monotonically from tier 0 to tier 2.

Prior art

The record frame, the counts and the names marked Mods are from Starlancer-OSS stats-format.md, which builds on Userunfriendly's hexcheat mod pack. It treats the bytes after each table's fields as an undecoded tail; they are never read and always zero.

Edit this page on GitHub. The documentation is under CC BY-SA 4.0.