-
Notifications
You must be signed in to change notification settings - Fork 0
Document.Reading a player journal
@elite-dangerous-almanac/core / Reading a player journal
Elite Dangerous writes a newline-delimited JSON journal. Two of its events carry most of
what this library is for: Loadout describes the ship the commander is flying, and
FSDJump (and Location, and FSDTarget) names the system they are in.
This guide turns both into library objects, and covers what to do when the game hands you something the catalogues do not recognise.
Each line is one event. Read it, parse it, and switch on event.
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
declare const journalPath: string;
const lines = createInterface({ input: createReadStream(journalPath), crlfDelay: Infinity });
for await (const line of lines) {
if (line.trim() === '') continue;
const event = JSON.parse(line) as { event: string };
switch (event.event) {
case 'Loadout':
// → a ShipLoadout, below
break;
case 'FSDJump':
case 'Location':
// → a ProceduralSystem, below
break;
}
}ShipLoadout.fromLoadout takes the event as the game wrote it.
import { BuildMetrics } from '@elite-dangerous-almanac/core/ships/build-metrics';
import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
import type { LoadoutEvent } from '@elite-dangerous-almanac/core/ships/slef';
declare const event: LoadoutEvent;
const build = ShipLoadout.fromLoadout(event);
build.shipSymbol; // -> 'krait_light'
build.shipName; // -> 'Jenny Longuet'
build.unladenMass; // -> 388.830017 tonnes
const metrics = BuildMetrics.of(build);
metrics.maxJumpRange(); // -> 60.5478 ly, best single jump
metrics.powerBudget().withinBudget; // -> true
metrics.shieldMetrics()?.strength; // -> 743.12 MJ
metrics.armourMetrics().hitPoints; // -> 307.8Figures the event already stated — UnladenMass, CargoCapacity, FuelCapacity — are
trusted verbatim rather than recomputed, so what you read back matches what the player
sees in game — while the fit they describe survives import, which
when the game hands you something unknown
covers. MaxJumpRange is the exception either way: it is recomputed from the drive
rather than taken from the event, so it may differ in the last decimal places from the
number the capture carried.
slots() gives every mount on the hull, occupied or not. Slot keys come from the game
and are not derivable from position, so read them rather than composing them.
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout; // the `ShipLoadout.fromLoadout(event)` from above
for (const slot of build.slots()) {
slot.key; // -> 'FrameShiftDrive', 'Slot01_Size6', 'LargeHardpoint1', …
slot.name; // -> 'Frame Shift Drive'
slot.module?.symbol; // -> undefined when the mount is empty
}
build.slots('hardpoint').length; // -> 4These views are snapshots, not live handles. After setModule or removeModule, call
slots() again.
A journal's purchase figures remain separate from catalogue retail. For the source record, export options and edit behavior, see Working with SLEF.
StarSystem and SystemAddress come straight from the event.
import { ProceduralSystem } from '@elite-dangerous-almanac/core/astro/procedural-system';
const system = ProceduralSystem.fromName('Synuefe EN-H d11-96');
system?.systemAddress; // -> 3309179996515n
system?.namingRegionName; // -> 'Synuefe'fromName returns null rather than throwing when the name is not procedural — which
is the normal case for Sol, Shinrarta Dezhra and every other hand-named system. Treat
null as "this is a hand-named system", not as a failure. A missing name is different:
pass a field that was not there and it throws TypeError naming the argument, so a
StarSystem your parser never found does not read back as a hand-named system.
Addresses accept a bigint, a safe-integer number, or a decimal string, and are
always returned as bigint. A journal parsed with JSON.parse yields a number, which
is exact for every real system address; a value above 2^53 - 1 is rejected rather than
silently rounded.
FSDJump carries StarPos as [x, y, z] light-years. Reshape it before use — the
library takes {x, y, z} so the two coordinate spaces cannot be confused.
import { findHandAuthoredRegionAt } from '@elite-dangerous-almanac/core/astro/hand-authored-regions';
import { findCodexRegionAt } from '@elite-dangerous-almanac/core/astro/codex-region-lookup';
import { nearestNebulae } from '@elite-dangerous-almanac/core/astro/nebulae';
import { REAL_NEBULAE } from '@elite-dangerous-almanac/core/astro/nebulae-real';
declare const starPos: readonly [number, number, number];
const position = { x: starPos[0], y: starPos[1], z: starPos[2] };
// Answered here for StarPos [-81.625, -151.3125, -376.0625], in the Pleiades:
findHandAuthoredRegionAt(position)?.name; // -> 'Pleiades Sector'
findCodexRegionAt(position)?.name; // -> 'Inner Orion Spur'
nearestNebulae(position, REAL_NEBULAE, 1)[0]?.name; // -> 'Pleiades'Pass StarSystem to the permit-lock lookup described in
Systems, sectors and regions.
Journals can contain hulls or modules absent from the catalogues. A direct lookup that
finds nothing returns null — check it. ShipLoadout applies a narrower rule at import.
An entry is kept as the event stated it when the catalogue identifies its Item and the
mount can hold it, when its slot is a known cosmetic or hull-geometry key (PaintJob,
ShipCockpit, a numbered decal, …), or when it is a ModularCargoBayDoor* article in the
cargo-hatch mount — some hull families name their own symbol for the one built-in article
the catalogue carries.
Everything else is normalized: an unknown hull is refused; unknown modules in hardpoints,
utilities, optional internals and unrecognised slots are discarded; and a fixed mount is
filled with that hull's stock armour, core internal or cargo hatch whenever the event did
not leave a fitting article there — one the catalogues cannot resolve, one the mount
cannot hold (a cargo rack in Armour, a size-8 plant in a size-2 mount, anything at all
in the cargo hatch), or none at all. Only fixed mounts are corrected this way: an
optional, hardpoint or utility mount may stand empty, so an article the catalogue resolves
but the mount refuses is left where the event put it, for validation to report. A stock
replacement carries the source's On, Priority and Health across but none of its
engineering or captured value.
When normalization changes the fitted set, the capture's aggregates are dropped: mass,
cargo and fuel capacity are recomputed from the fit that remains, while modulesValue
and rebuy read null, since nothing records what the discarded module cost;
sourcePurchase still reports the captured figures. Stocking an absent bulkhead or cargo
hatch is the exception — both stock articles are weightless and free — while an absent
core internal stocked from the defaults invalidates them like any other change.
build.validation() therefore reports the fit that remains: optional, hardpoint and
utility modules leave empty mounts and need no diagnostic, while required armour and core
mounts remain complete through their stock replacements. build.importOutcomes is the
frozen, machine-readable account of each change: the exact slot, the source module
where the capture named one, whether it was emptied or defaulted, and the replacement
symbol when one was fitted.
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout; // the `ShipLoadout.fromLoadout(event)` from above
build.validation().issues; // -> structural problems in the normalized fit
build.fittedModuleAt('Slot01_Size5'); // -> null if its imported symbol was unknown
build.importOutcomes; // exact import changes for display or loggingThe failure model sets the validation and calculation patterns out in full.
A journal line is one Loadout event, and it is taken whole or refused: bad JSON throws
SyntaxError, and a structurally impossible event — two slot keys differing only in
case, say — throws TypeError from fromLoadout. Catch both when the bytes come from
somewhere you do not control. A SLEF file holds several builds and can be part-good,
which is its own question —
Working with SLEF
covers parseSlef against inspectSlef and what each does with a bad entry.
Guides
astro
Classes (1)
ProceduralSystem
Properties
Accessors
Methods
Interfaces (16)
Type Aliases (3)
Variables (11)
Functions (37)
- absoluteBoxelToBoxelCode
- boxelCodeToAbsoluteBoxel
- boxelCodeToLetters
- boxelEdgeLy
- boxelInternalSize
- canonicalizeSectorName
- canonicalizeSystemName
- decodeModSystemAddress
- decodeSystemAddress
- encodeModSystemAddress
- encodeSystemAddress
- findHandAuthoredRegionAt
- formatSystemName
- getCodexRegion
- getCodexRegionByName
- getHandAuthoredRegionOrigin
- getNebulaByName
- isPermitLockedRegionName
- isPermitLockedSystemName
- isProceduralSystemName
- lettersToBoxelCode
- massCodeToSizeClass
- nearestNebulae
- nebulaeWithin
- parseSystemName
- permitLockedRegionForSystemName
- permitLockedSystemForAddress
- permitLockedSystemForName
- permitLockForSystemName
- resolveNamingRegionOrigin
- sectorGridPositionFromGalacticPosition
- sectorGridPositionFromName
- sectorNameFromGalacticPosition
- sectorNameFromGridPosition
- sizeClassToMassCode
- toSystemAddress
- tryToSystemAddress
commodities
Interfaces (1)
Type Aliases (1)
Variables (3)
Functions (3)
equipment
Interfaces (6)
Type Aliases (8)
Variables (3)
Functions (10)
Subpath modules (2)
i18n
Interfaces (1)
Type Aliases (1)
Functions (17)
- getBlueprintName
- getCalculationIssueMessage
- getEngineeringGroupName
- getExperimentalEffectDescription
- getExperimentalEffectName
- getLoadoutEditErrorMessage
- getLoadoutIssueMessage
- getLoadoutSlotName
- getMaterialName
- getMicroResourceName
- getModuleName
- getOutfittingFamilyName
- getPreEngineeredVariantName
- getShipManufacturer
- getShipName
- getSlefDiagnosticMessage
- getSlotRestrictionLabel
materials
Enumerations (2)
Interfaces (2)
Type Aliases (2)
Variables (9)
ships
Classes (3)
BuildMetrics
Methods
- armourMetrics()
- buildCost()
- buildMass()
- cellBanks()
- distributorMetrics()
- distributorMetricsResult()
- frameShiftDrive()
- frameShiftDriveMassFactor()
- fuelPerJump()
- heatMetrics()
- heatMetricsResult()
- jumpRange()
- jumpRangeSummary()
- ladenJumpRange()
- loadout()
- maxJumpRange()
- mobilityCapacitorMetrics()
- mobilityCapacitorMetricsResult()
- mobilityMetrics()
- mobilityMetricsResult()
- powerBudget()
- shieldCapacitorMetrics()
- shieldCapacitorMetricsResult()
- shieldMetrics()
- shieldMetricsResult()
- shieldRecovery()
- shieldRecoveryResult()
- standardLoad()
- standardLoadResult()
- thrusters()
- totalRange()
- weaponMetrics()
- weaponsCapacitorMetrics()
- of()
LoadoutEditError
Constructors
Properties
Methods
ShipLoadout
Accessors
- cargoCapacity
- fuelCapacity
- hullValue
- importOutcomes
- modulesValue
- rebuy
- shipIdent
- shipName
- shipSymbol
- sourcePurchase
- unladenMass
Methods
- applyBlueprint()
- availableBlueprints()
- availableExperimentalEffects()
- clearEngineering()
- completeEngineeringGrade()
- fittedModuleAt()
- fittedModules()
- modulesForSlot()
- removeModule()
- repairFixedMount()
- setExperimentalEffect()
- setModule()
- setModuleEnabled()
- setModulePriority()
- setPreEngineeredVariant()
- slots()
- toLoadoutEvent()
- toSlef()
- toSlefString()
- validation()
- default()
- empty()
- fromLoadout()
- fromSlef()
Interfaces (125)
- AmmunitionCapacity
- AmmunitionStats
- ApplyBlueprintOptions
- ArmourInput
- ArmourMetrics
- AvailableBlueprint
- Blueprint
- BlueprintFeature
- BlueprintGrade
- BlueprintModuleEngineering
- BuildCost
- BuildCredits
- BuildMass
- BuildSlotBase
- BuildWeaponMetrics
- BulkheadParams
- CalculationIssue
- CellBankInput
- CellBankMetrics
- CellBankSummary
- CoreBuildSlot
- CoreSlots
- DamageComponents
- DamageDistribution
- DamageResistanceParams
- DamageSplit
- DamageTypeValues
- DistributorCapacitorMetrics
- DistributorInput
- DistributorMetrics
- DistributorOptions
- DistributorPips
- EngineeringMaterial
- EngineeringModifier
- EngineeringNormalizationUnchanged
- EngineeringNormalizationUnsupported
- EngineeringNormalized
- EngineeringOptionGroup
- ExperimentalContribution
- ExperimentalEffect
- ExperimentalEffectUnchanged
- ExperimentalEffectUnsupported
- ExperimentalEffectUpdated
- FittedModule
- FittedWeaponMetrics
- FrameShiftDriveJumpStats
- FrameShiftDriveParams
- FuelCapacity
- GunsightPoint
- HardpointBuildSlot
- HardpointSlotSpec
- HeatInput
- HeatMetrics
- HeatState
- HeatWeapon
- HullReinforcementParams
- JumpOptions
- JumpRangeSummary
- LoadoutCalculationModule
- LoadoutEvent
- LoadoutExportOptions
- LoadoutIssue
- LoadoutMass
- LoadoutModule
- LoadoutValidation
- LoadoutValidationInput
- MassCurveStats
- MobilityCapacitorInput
- MobilityCapacitorMetrics
- MobilityCapacitorOptions
- MobilityInput
- MobilityMetrics
- ModuleLimitEntry
- ModuleLimitIncrease
- ModuleLimitUsage
- ModuleReinforcementParams
- OptionalBuildSlot
- OptionalSlotSpec
- OutfittingModule
- OutfittingModuleIdentity
- OutfittingModuleStats
- ParsedSlot
- PowerBand
- PowerBudget
- PowerConsumer
- PowerConsumerResult
- PowerDistributorStats
- PowerGenerationStats
- PreEngineeredModifier
- PreEngineeredVariant
- ProjectileRangeBoundaries
- ShieldBoosterParams
- ShieldCapacitorInput
- ShieldCapacitorMetrics
- ShieldCapacitorOptions
- ShieldGeneratorParams
- ShieldInput
- ShieldMetrics
- ShieldRecovery
- ShieldRecoveryInput
- ShieldRecoveryOptions
- ShieldRegenerationStats
- Ship
- ShipSlots
- SimpleBuildSlot
- SlefDiagnostic
- SlefEntry
- SlefExportOptions
- SlefHeader
- SlefInspection
- SlefStringifyOptions
- SourceModuleValue
- SourcePurchaseRecord
- StandardLoadInputs
- ThrusterCurveParams
- ThrusterParams
- TotalRangeDetails
- ValidationModule
- WeaponDamageStats
- WeaponMetrics
- WeaponsCapacitorInput
- WeaponsCapacitorMetrics
- WeaponsOptions
- WeaponStats
- WeaponTotals
Type Aliases (44)
- BlueprintGrades
- BuildSlot
- CalculationIssueReason
- CalculationResult
- CoreSlotType
- DamageResistances
- DamageType
- EngineeringGroupId
- EngineeringNormalizationCode
- EngineeringNormalizationResult
- ExperimentalEffectMutationCode
- ExperimentalEffectMutationResult
- FixedMountRepairResult
- GunsightOffset
- HardpointRestriction
- ImmovableReason
- LoadoutEditErrorCode
- LoadoutImportOutcome
- LoadoutIssueCode
- LoadoutIssueParam
- LoadoutIssueParams
- LoadoutSlot
- ModifierMethod
- ModuleCategory
- ModuleEngineering
- ModuleExclusionGroup
- ModuleFitConstraint
- ModuleGuidance
- ModuleLimitGroup
- ModuleMount
- ModuleRating
- ModuleSlot
- OptionalRestriction
- OutfittingFamilyId
- PreEngineeredAcquisition
- ShipGunsight
- ShipGunsightCatalogue
- Slef
- SlefConstraint
- SlefDiagnosticCode
- SlotKind
- SlotRestriction
- StandardLoad
- ThrusterLoad
Variables (10)
Functions (85)
- ammunitionCapacity
- armourMetrics
- armourPiercingFactor
- calculateCargoCapacity
- calculateFuelCapacity
- calculateModuleLimits
- calculateUnladenMass
- cellBankSummary
- combinedRateOfFire
- computeModifiers
- damageFalloff
- damagePerSecond
- distributorMetrics
- effectiveHitPoints
- effectiveWeaponThermalLoad
- energyPerSecond
- enumerateSlots
- equilibriumHeatLevel
- frameShiftDriveMassFactor
- fuelPerJump
- getBlueprint
- getBlueprintGrade
- getBlueprintsForModule
- getBulkheadsForShip
- getEngineeringGroup
- getExperimentalEffect
- getExperimentalsForBlueprint
- getExperimentalsForModule
- getLoadoutModifier
- getModuleBySymbol
- getModulesByName
- getPreEngineeredJournalModifiers
- getPreEngineeredModifiers
- getPreEngineeredStats
- getPreEngineeredVariants
- getShipByName
- getShipBySymbol
- getShipGunsight
- getShipSlots
- getSourceModuleValue
- hasFrameShiftDriveJumpStats
- hasMassCurveStats
- hasPowerDistributorStats
- hasPowerGenerationStats
- hasShieldRegenerationStats
- hasWeaponDamageStats
- heatLevelAtTime
- heatMetrics
- heatPerSecond
- identifyPreEngineeredVariant
- inspectSlef
- isPreEngineered
- mapDamageTypes
- mobilityCapacitorMetrics
- mobilityMetrics
- parseSlef
- parseSlotName
- powerBudget
- projectGunsight
- resolveBlueprintForModule
- secondsToHeatLevel
- shieldCapacitorMetrics
- shieldMassCurveMultiplier
- shieldMetrics
- shieldRecovery
- shieldStrength
- singleJumpRange
- sourcePurchaseFromLoadout
- splitDamage
- stackArmourResistance
- stackShieldResistance
- stringifySlef
- sumMaterials
- sumSourceModuleValues
- sumWeaponMetrics
- sustainedDamagePerSecond
- sustainedFireFactor
- systemsResistance
- thrusterMassCurveMultiplier
- toSlef
- totalRange
- unresolvedModifiers
- validateLoadout
- weaponMetrics
- weaponsCapacitorMetrics