-
Notifications
You must be signed in to change notification settings - Fork 0
Document.Working with SLEF
@elite-dangerous-almanac/core / Working with SLEF
SLEF — the Ship Loadout Export Format — is how Inara, EDSY, Coriolis and the rest pass
builds around. A SLEF payload is an array of entries, each a header naming the
producer and a data half that is a journal Loadout event.
import { parseSlef } from '@elite-dangerous-almanac/core/ships/slef';
declare const slefJsonString: string;
const [entry] = parseSlef(slefJsonString);
entry?.data.Ship; // the hull symbol
entry?.header.appName; // which tool wrote itparseSlef accepts a JSON string or an already-parsed value, and it also accepts a bare
Loadout event — a journal line pasted straight in is a valid input, not a special case.
To go from a payload to something you can ask questions of, hand it to ShipLoadout instead:
import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const slefJsonString: string;
const build = ShipLoadout.fromSlef(slefJsonString); // first entry by default
const second = ShipLoadout.fromSlef(slefJsonString, 1); // or pick oneThey differ in what a bad entry costs you.
import { inspectSlef, parseSlef } from '@elite-dangerous-almanac/core/ships/slef';
declare const mixed: string; // some entries good, some malformed
parseSlef(mixed); // throws TypeError — the whole payload is rejected
const seen = inspectSlef(mixed);
seen.entries; // the entries that did parse
seen.diagnostics; // one per rejected entry, with its index and a stable codeUse parseSlef when the payload is yours and a malformed entry is a bug you want to
hear about. Use inspectSlef when you are importing a file a user handed you and would
rather show them which of their five builds failed than reject all five.
Inspection validates the SLEF structure, not catalogue support. A structurally valid
entry whose Ship is absent from the hull catalogue remains in entries, but
ShipLoadout.fromSlef rejects it when selected. Catch that TypeError when converting
each inspected entry into a build.
Neither survives input that is not JSON. Both call JSON.parse on a string first,
so a truncated or non-JSON file throws SyntaxError from both — catch that separately.
import { inspectSlef } from '@elite-dangerous-almanac/core/ships/slef';
declare const bytes: string;
try {
inspectSlef(bytes);
} catch (error) {
if (error instanceof SyntaxError) {
// not JSON at all
}
}import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout;
build.toSlefString({ header: { appName: 'MyApp', appVersion: '1.0.0' } });
build.toSlef({ header: { appName: 'MyApp', appVersion: '1.0.0' } }); // the object form
build.toLoadoutEvent(); // just the journal eventThe header is required, and naming your own app in it is the point: a downstream reader
needs to know which tool produced the build. If you re-export a build you imported, say
so — the SLEF specification expects the exporting application to identify itself, and
this repository's data/ships/SOURCES.md records the same requirement for captures it
redistributes.
This is the part most consumers get wrong, so the library keeps the two apart.
Everything the library computes is catalogue retail — a property of the fit. What a
capture states it paid is provenance about that capture: it carries station discounts,
it can price only part of the build, and two producers do not even agree on whether
HullValue includes the hull's stock fittings.
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout;
build.toLoadoutEvent(); // retail: hull cost plus every module's list price
build.toLoadoutEvent({ credits: 'source' }); // the capture's figures, less what was narrowedThe captured figures live on a read-only record that no edit changes:
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
import { getSourceModuleValue } from '@elite-dangerous-almanac/core/ships/source-purchase';
declare const build: ShipLoadout;
const paid = build.sourcePurchase; // null for a build you assembled yourself
paid?.hullValue; // -> 37472252 as the capture stated it
paid && getSourceModuleValue(paid, 'FrameShiftDrive')?.value; // -> 4976355
paid && getSourceModuleValue(paid, 'ShipCockpit'); // -> null — unpriced is not "free"Each captured figure stays pinned to the article it was paid for, so losing an article
narrows the source export rather than staling it. Swap or remove a module and it
exports unpriced, taking ModulesValue and Rebuy with it; engineer a module or fill an
empty mount and both still stand. HullValue always stands, because it names no slot to
narrow.
Import normalization narrows it the same way before you have edited anything, and on one
more ground than an edit: a module the catalogue cannot resolve, one a stocked mount cannot
hold, and a core internal the capture named no module for are all discarded or stocked
from the hull defaults, which leaves that slot unpriced and drops the two totals. Filling
an empty mount yourself leaves them standing because you can see the change; this one you
did not make. Three stocked-from-absence articles are the exception and leave the totals
alone: a bulkhead, a cargo hatch the capture left unpriced or priced at zero, and a
planetary approach suite, whose 500 Cr is too little to drop a purchase record over — so
a source total may understate the fit by that much, and by no more.
build.importOutcomes says which — see
Reading a player journal.
One limit: what a capture never priced, it also never explains, so losing an unpriced module, to a removal or a replacement, cannot be detected. LoadoutExportOptions.credits records that and the other boundary cases.
Frontier writes FrameShiftDrive; Inara writes frameshiftdrive, as the SLEF
specification's own example does. Both name the same mount, and lookups are
case-insensitive in both directions. What a build already carries is never rewritten, so
re-exporting an import returns the producer's own spelling untouched.
A journal writes the modifier block beside the recipe. SLEF permits stating the recipe
alone — BlueprintName, Level and Quality, no Modifiers — and Inara writes it that
way for every engineered module. Import rolls that recipe at the grade and quality the
block states, so the module publishes the figures the commander built rather than the
ones it was sold with.
Two rules settle what a bare identity names, because a module's fixed articles can carry the same blueprint at the same grade as one of its craftable recipes:
-
The module's engineering menu offers the recipe — the block is an ordinary roll of
it. That is what nearly every such block is, and a fixed article of the same module
carrying that blueprint does not change the reading. It does make the reading a choice,
though, so
importOutcomescarries anambiguousEngineeringentry for that slot with the article it passed over inpreEngineeredVariant. Hand that straight to setPreEngineeredVariant to take the other reading. - The menu does not offer it — no ordinary roll could have written the block, so a single catalogued article answering to the stated blueprint, grade and effect is fitted and its fixed stats stand.
Where neither answers, the module keeps unengineered figures and says so: importOutcomes
carries an unresolvedEngineering entry naming the slot, the module and the recipe.
A block that does state Modifiers is the source's own account of the module and is
kept verbatim — its figures are what the game reported, and outrank anything the library
would recompute. The exception is a block that moves nothing: every label naming a stat
the module has no value for, or no labels at all. Such a block describes some other
module, so the recipe stated beside it is rolled in its place and importOutcomes reports
the slot as rerolledEngineering.
import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
const build = ShipLoadout.fromLoadout({
Ship: 'Anaconda',
Modules: [
{
Slot: 'TinyHardpoint4',
Item: 'hpt_heatsinklauncher_turret_tiny',
Engineering: { BlueprintName: 'misc_heatsinkcapacity', Level: 1, Quality: 1 },
},
],
});
build.fittedModuleAt('TinyHardpoint4')?.effectiveStats?.reloadTime; // -> 15Guides
astro
Classes (1)
ProceduralSystem
Properties
Accessors
Methods
Interfaces (28)
- AbsoluteBoxel
- AtmosphereComponent
- BodyComposition
- BodyParent
- BodyProperties
- BodyRing
- BodyScanEvent
- BoxelLetters
- CodexRegion
- CodexRegionBounds
- DecodedAddress
- GalacticPlanePosition
- GalacticPosition
- HandAuthoredRegion
- HandAuthoredSphere
- IsProceduralSystemNameOptions
- MassStabilityAssessment
- NamingRegionOrigin
- Nebula
- NebulaWithDistance
- OrbitExtents
- PermitLockedSystem
- RingDynamics
- RocheLimits
- SectorGridPosition
- SpinOrbitResonance
- SurfaceMaterial
- SystemNameParts
Type Aliases (6)
Variables (22)
- BASE_BOXEL_LY
- CHANDRASEKHAR_LIMIT_SOLAR_MASSES
- CODEX_REGIONS
- GALAXY_ORIGIN
- GRAVITATIONAL_CONSTANT
- HAND_AUTHORED_REGIONS
- KG_PER_EARTH_MASS
- KG_PER_SOLAR_MASS
- MASS_CODE_COUNT
- NEUTRON_STAR_MASS_DROP_OFF_SOLAR_MASSES
- PERMIT_LOCKED_REGIONS
- PERMIT_LOCKED_SYSTEMS
- PROCGEN_NEBULAE
- REAL_NEBULAE
- RING_NOMINAL_RADIUS_FRACTION
- SECTOR_EDGE_LY
- SECTOR_INTERNAL_SIZE
- SOLAR_RADIUS
- SPEED_OF_LIGHT
- TOV_LIMIT_SOLAR_MASSES
- VISIBLE_RING_MAX_WIDTH
- VISIBLE_RING_MIN_SURFACE_DENSITY
Functions (57)
- absoluteBolometricMagnitude
- absoluteBoxelToBoxelCode
- assessMassStability
- bodyMass
- boxelCodeToAbsoluteBoxel
- boxelCodeToLetters
- boxelEdgeLy
- boxelInternalSize
- bulkDensity
- canonicalizeSectorName
- canonicalizeSystemName
- classifyEccentricity
- classifyNeutronStar
- decodeModSystemAddress
- decodeSystemAddress
- encodeModSystemAddress
- encodeSystemAddress
- equatorialVelocity
- findHandAuthoredRegionAt
- formatSystemName
- getCodexRegion
- getCodexRegionByName
- getHandAuthoredRegionOrigin
- getNebulaByName
- hillRadius
- isInvisibleRing
- isPermitLockedRegionName
- isPermitLockedSystemName
- isProceduralSystemName
- lettersToBoxelCode
- mainSequenceLifetime
- massCodeToSizeClass
- nearestNebulae
- nebulaeWithin
- orbitExtents
- parseSystemName
- permitLockedRegionForSystemName
- permitLockedSystemForAddress
- permitLockedSystemForName
- permitLockForSystemName
- primaryAngularDiameter
- resolveNamingRegionOrigin
- ringDynamics
- ringParticleDensity
- ringRocheLimits
- ringSurfaceDensity
- rocheLimits
- rocheLimitsForDensity
- schwarzschildRadius
- sectorGridPositionFromGalacticPosition
- sectorGridPositionFromName
- sectorNameFromGalacticPosition
- sectorNameFromGridPosition
- sizeClassToMassCode
- spinOrbitResonance
- toSystemAddress
- tryToSystemAddress
commodities
Interfaces (1)
Type Aliases (1)
Variables (3)
Functions (3)
equipment
Interfaces (18)
- FittedPersonalModification
- FittedPersonalWeapon
- PersonalEngineeringIngredient
- PersonalModification
- PersonalModifier
- PersonalMount
- PersonalTool
- PersonalWeapon
- PersonalWeaponGrade
- PersonalWeaponMetrics
- ReloadTime
- ScopeMagnification
- Suit
- SuitGrade
- SuitLoadout
- SuitLoadoutEvent
- SuitLoadoutImportOutcome
- SuitLoadoutModuleEvent
Type Aliases (9)
Variables (4)
Functions (14)
- applyPersonalModifiers
- getPersonalModification
- getPersonalToolById
- getPersonalWeaponByName
- getPersonalWeaponBySymbol
- getPersonalWeaponGrade
- getSuitByFamily
- getSuitByName
- getSuitBySymbol
- getSuitGrade
- parseSuitLoadout
- personalWeaponMetrics
- resolvePersonalModificationForWeapon
- sumPersonalEngineeringIngredients
Subpath modules (2)
galaxy-map
Interfaces (1)
Variables (1)
Functions (2)
i18n
Interfaces (1)
Type Aliases (1)
Functions (23)
- getBlueprintName
- getCalculationIssueMessage
- getCodexRegionName
- getCommodityName
- getExperimentalEffectDescription
- getExperimentalEffectName
- getLoadoutEditErrorMessage
- getLoadoutIssueMessage
- getLoadoutSlotName
- getMaterialName
- getMicroResourceName
- getModuleName
- getOutfittingFamilyName
- getPersonalModificationDescription
- getPersonalModificationName
- getPersonalMountName
- getPersonalToolName
- getPersonalWeaponDescription
- getPreEngineeredVariantName
- getSlefDiagnosticMessage
- getSlotRestrictionLabel
- getSuitDescription
- getSuitName
materials
Enumerations (2)
Interfaces (2)
Type Aliases (2)
Variables (9)
ships
Classes (3)
BuildMetrics
Methods
- armourMetrics()
- buildCost()
- buildMass()
- cellBanks()
- distributorMetricsResult()
- frameShiftDrive()
- frameShiftDriveMassFactor()
- fuelPerJump()
- heatMetricsResult()
- jumpRange()
- jumpRangeSummary()
- ladenJumpRange()
- loadout()
- maxJumpRange()
- mobilityCapacitorMetricsResult()
- mobilityMetricsResult()
- powerBudget()
- shieldCapacitorMetricsResult()
- shieldMetricsResult()
- shieldRecoveryResult()
- standardLoadResult()
- thrusters()
- totalRange()
- weaponMetrics()
- weaponsCapacitorMetrics()
- of()
LoadoutEditError
Constructors
Properties
Methods
ShipLoadout
Accessors
- cargoCapacity
- fuelCapacity
- hullValue
- importOutcomes
- modulesValue
- passengerCapacity
- 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 (86)
- ammunitionCapacity
- armourMetrics
- armourPiercingFactor
- calculateCargoCapacity
- calculateFuelCapacity
- calculateModuleLimits
- calculatePassengerCapacity
- 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