Raw JSON Combat Logs

Concept · Combat logs

In short: The server’s own record of a battle: build and time, two fleet snapshots with identities, stats, crew and standing buffs, and a compact numeric event stream with exact damage components.

A raw JSON combat log is the server journal of one battle, and the closest available source of truth for it. The normal shape is an object containing journal.battle_log, with a names lookup beside it; the parser also accepts common wrappers such as data.journal, a one-element array, and the older battle sidecar shape. A JSON object without battle_log is not a combat log, and each imported file holds one battle.

Around the journal sit two fields worth reading first. server_version is the game build the fight ran on, which is the right way to split logs around a patch: builds change mid-day, and a file’s date is the download, not the fight. battle_time is when the fight happened, written without a zone marker and in UTC; the mod’s CSV Timestamp column is the same clock. system_id names the system, coords the position in it, and battle_type distinguishes the kinds of engagement.

Every battle has an initiator and a target, and the journal keeps a full snapshot of each in initiator_fleet_data and target_fleet_data. Which one is the hostile is told by the ids: a player is a p followed by a long hex string, a marauder hostile is mar_ followed by its level, and armada or event targets are npc_ or dyn_npc_ ids. The same uid is repeated on each side’s deployed_fleet. initiator_fired_first and initiator_wins record who shot first and who won; a hostile that attacks you is the initiator, so neither side is always the player. The flag does not give the order inside a sub-round: against a hostile, the hostile fires first in every sub-round, also in fights where the flag says the initiator did (see A Round, Step by Step).

The hostile’s identity is ref_ids.loca_id on its side of the journal. That number is the hostile family in the hostile catalog, the same across every level of the family, and it is the key the name lookup uses. hull_ids on that side is the hostile’s hull in the game’s ship-id space, which is not the catalog’s id space and does not identify the hostile reliably. Its level is initial_ship_levels. chest_drop.loot_roll_key spells out level, grade, faction and hull class in text, but it is a loot key, not an identity, and is only a fallback for a name. On the player side hull_ids is the ship’s hull, which does resolve through the ship catalog.

Each ship on a side has a ship id, listed in ship_ids in the same order as hull_ids, initial_ship_levels and the hull and shield pools. The hostile’s ship id is 0. Player ship ids are integers larger than a double can hold exactly, so a JSON reader that parses them as numbers rounds the last digits and they no longer match the keys of the maps below; read them as text, or match them approximately. The ship id is also the target token an attack names in battle_log, which is why token 0 is the hostile on both sides’ logs. max_ship_hps and max_ship_shps are the hull and shield pools the fight started from; initial_ and final_ship_hps and _shps bracket what was lost.

deployed_fleet is the fleet as it stood when the fight began, keyed by ship id. ship_components lists the installed component ids in slot order, with -1 for an empty slot; components tier independently of the hull, so these, not ship_tiers, decide which weapons fired and their base damage. ship_tiers and ship_levels are the hull’s own tier and level. ship_stats is the ship’s combat stat sheet keyed by stat id: 6 accuracy, 7 armor piercing, 8 shield piercing, 9 critical chance, 10 critical damage, 11 dodge, -2 shield deflection, -3 armor, 60 shield health, 61 hull health and -1 damage per round. attributes holds the fleet ratings the ship screen shows, -8 attack, -9 defense and -10 health, and stats holds the non-combat figures such as 62 impulse speed, 63 warp speed, 64 warp range and 66 cargo capacity.

The crew is in fleets_officers, keyed by fleet id, one entry per seat in seat order: the three bridge seats first, then the below deck seats, with null for an empty seat. Each entry is the officer id with the level and rank the officer was fought at, so a battle records its own crew state rather than the account’s current one. bridge_officers carries the same seat list. The list includes officers assigned to seats the ship has not unlocked yet; active_officer_slots is the number of seats, counted from the first, that were live in the fight, and an officer past it took no part (see Officer Seats). A hostile has neither.

active_buffs is the fleet’s standing buff list at battle time. Each entry names a buff_id, which resolves through the buff index to its source (a research node, a starbase or Holo Haven module, a Syndicate, Emerald Chain or Independent Archive reward, a consumable, a forbidden tech, an officer ability) with its modifier, trigger and conditions, and ranks, the level the source was at, counted from zero (rank 9 is level 10), which indexes the source’s value table. The game files Syndicate, Emerald Chain and Archive rewards as consumables, so the buff index names the track that grants each one. activator_id is the source’s own id for research, modules and officers, -1 for track rewards, consumables and tech, and the ship’s id for the ship’s own buffs. activation_time is when the buff was granted and expiry_time when a timed one runs out; both are empty for a permanent one.

The list holds only buffs that take hold when the ship launches, and of those only the ones that fit the ship that fought: a research node for another hull type or faction is left out. A buff that fires at combat start, at a round’s start or on a hit is never listed, even when it applied in this fight (damage against marauders, a Holo Haven critical bonus for interceptors), and neither is an always-on station buff such as a cost efficiency. So the list is the launch stat sheet, and what it lacks is not evidence that a source was not owned; the event stream and the delivered damage show what the combat engine applied. Each ship also carries buffs of its own, activated by the ship: one whose ranks list every tier past the first that the ship has reached, and one ranked at its level less one. The game publishes the tier and level stat gains without these buff ids, so they do not resolve through the buff index.

journal.battle_log is not an array of named event objects. It is a flat numeric token stream: negative numbers mark boundaries and the following values form that event payload. Important markers include -96 start round, -90 start sub-round, -98 start attack, -99 end attack, -80/-79 hull repair, -93/-94 officer abilities firing, and -97 end round. A sub-round is a weapon slot: every round lists the same number of sub-rounds, one per slot of the best-armed ship, including slots in which nothing fires, so a shot’s sub-round is its weapon’s slot. The decoder carries the current attacker because an attack payload reliably names its target but does not always repeat its attacker. Officer and forbidden-tech ability events carry the ability id and the value it fired with, so the stream is also the record of which abilities actually triggered and at what magnitude.

For a damage attack, take the values between -98 and -99 as a zero-based payload. Known positions are [0] weapon/component ID, [1] target ship token, [5] critical flag, [6]/[7] hull damage and hull remaining, [8]/[9] shield damage and shield remaining, [10] standard mitigation, [11] isolytic damage surviving Isolytic Defense, [12] isolytic mitigation, [13] Apex Barrier mitigation, [14] critical mitigation’s second cut (from the merged standard + isolytic pool, after Apex), and [15] its first cut (from gross isolytic). These bracketed numbers are JSON payload positions, never CSV columns. A payload whose [1] is -95 is Charging Weapons rather than damage: [0] is the weapon and [2] its charge, which counts toward the weapon’s next fire on the scale of its reload, warm-up included: 1 − (rounds until it fires − 1) ÷ (reload − 1). It reads 1 in the round before the weapon fires, and a weapon with a 1-round reload always reads 1. While the fire is more than the reload away the charge is below zero: a warm-up at least two rounds longer than the reload starts the fight there, and a delay such as Chang’s can push a reload there. The CSV export mishandles negative values (see CSV combat logs).

The CSV-style isolytic amount is reconstructed as Total Isolytic Damage = [11] + [12]. Gross isolytic damage before its critical cut is [11] + [12] + [15]. CSV-style Total Damage is not stored directly; standard damage is reconstructed by conservation as Hull Damage + Shield Damage - [11] + [10] + [13] + [14]. [13] and [14] come off the merged pool, the isolytic remainder included, which is why the whole of [11] is subtracted; [15] came off before Isolytic Defense, so it belongs to the isolytic side and not in that equation. Infusion and Polaron that landed are taken out of Hull Damage first: they never join the merged pool. Because a weapon’s per-shot base is known from its component, that reconstructed standard damage divided by the base is the multiplier the shot fired with, and the ratio of a critical shot to a non-critical shot of the same weapon is the critical multiplier the fight actually used.

The two critical components demonstrate why the mitigation pipeline is branch-specific: [15] is removed from gross iso before Isolytic Defense, while [14] is removed after Apex Barrier and before the shield/hull split. Their sum becomes the CSV Mitigated Critical Damage field. See Critical Mitigation for the percentage denominators.

Rewards close the journal. resources_dropped is keyed by resource id and signed: positive entries were gained and a negative entry was spent, such as a consumable used for the fight. resources_transferred is the subset that moved into cargo. chest_drop lists any chests rolled.

Raw values may be floating point even though the game displays whole damage units. Preserve their precision while decoding and deriving amounts, then floor only when producing CSV-style rows. Numeric ship, officer, ability, component, system, resource, and hostile IDs should be resolved through data tables rather than hard-coded in the stream decoder; unknown IDs remain usable placeholders. After decoding, the parser emits the same summary, combat, reward, fleet-stat, sub-round, and damage-summary model used for CSV combat logs.

See also: CSV Combat Logs, Mitigation Pipeline, Critical Damage Mitigation, Critical Damage, Isolytic Defense, Apex Barrier