-
Notifications
You must be signed in to change notification settings - Fork 0
Document.The failure model
@elite-dangerous-almanac/core / The failure model
The library distinguishes "there is no such thing" from "you passed me nonsense" from "I do not have the data", and each gets a different shape. Once you know the four, you can write a consumer that never guesses.
| Outcome | Means | Example |
|---|---|---|
null |
Nothing matched, or the input did not parse as that kind of thing | getShipBySymbol('nope') |
TypeError |
The input was malformed | toSystemAddress(2 ** 53) |
RangeError |
Well-formed, but outside a supported range | massCodeToSizeClass('z') |
SyntaxError |
The text was not JSON | parseSlef('{') |
import { getShipBySymbol } from '@elite-dangerous-almanac/core/ships/ships';
import { toSystemAddress } from '@elite-dangerous-almanac/core/astro/system-address-input';
import { parseSlef } from '@elite-dangerous-almanac/core/ships/slef';
getShipBySymbol('no such hull'); // -> null, an ordinary answer
try {
toSystemAddress(2 ** 53); // a number too large to be exact
} catch (error) {
error instanceof TypeError; // -> true
}
try {
parseSlef('{'); // JSON.parse fails before anything else runs
} catch (error) {
error instanceof SyntaxError; // -> true
}Two of those rows need qualifying on the SLEF entry points that accept a string —
parseSlef, inspectSlef and ShipLoadout.fromSlef. All three throw SyntaxError,
which comes from JSON.parse before any validation runs. Past that point a payload
number outside its documented journal range — a module Priority of 5, a Health of
-0.1 — counts as malformed alongside every other bad field rather than as a range
violation: parseSlef and ShipLoadout.fromSlef throw TypeError naming the field, and
inspectSlef records it as that entry's diagnostic.
A wrong-typed argument is malformed input, not a miss. Passing a number where a
symbol belongs is the same kind of failure as passing an unusable address, and an entry
point that guards it names the parameter and what arrived —
ShipLoadout.empty(42) throws TypeError: ShipLoadout.empty: shipSymbol must be a string, received number 42. So does a missing one: ProceduralSystem.fromName(undefined)
throws rather than answering null, because "you passed me nothing" is not "the naming
scheme does not cover that system".
Human-readable errors and validation messages abbreviate an oversized argument or
capture field with an ellipsis. They identify the bad value without copying a whole
payload into a log or UI; structured diagnostic fields such as slot and symbol keep
the original value for programmatic handling.
Which half of your call the message names depends on what you handed over, and that is
worth knowing before you write a catch:
-
An entry point that takes a value names the parameter and the value, and names
itself — a lookup you reach through a facade reports the function you called, not the
one it delegates to, so
getShipSlots(42)saysgetShipSlots: symbol, nevergetShipBySymbol: symbol.toSystemAddressprints the value it rejected without a parameter to name, having only the one. -
An entry point that takes a structure names the offending field.
parseSlefandShipLoadout.fromSlefcheck every one of them (parseSlef: entries[0].data.Modules[0].Priority must be an integer from 0 to 4) — the more useful half when the argument is a whole export, and the same textinspectSlefreports as that entry's diagnostic.ShipLoadout.fromLoadoutchecks the structure a build is assembled from — an object, an array of module objects, aSlotandItemon each, no two modules claiming one slot, and anEngineeringthat is an object holding an array ofModifiers, each a labelled object, whenever their key is there at all — plus a requiredShipthat names a known hull, and the block's two ids when they carry a value. It trusts the remaining numeric and boolean values, so usefromSleffor an event you did not produce yourself.
A missing argument is not a wrong-typed one, and the two get different answers:
import { getShipBySymbol } from '@elite-dangerous-almanac/core/ships/ships';
getShipBySymbol(undefined as unknown as string); // -> null, the answer an unknown symbol gets
try {
getShipBySymbol(42 as unknown as string);
} catch (error) {
(error as Error).message;
// -> 'getShipBySymbol: symbol must be a string, received number 42'
}A lookup that answers null for a symbol no record carries answers null for no symbol
at all — asking for nothing found nothing. So do parseSystemName,
canonicalizeSystemName and isProceduralSystemName, which answer null and false
for a nullish name.
Where a missing argument is loud instead: everything that is not a search. A function
that hands you back a value has no "no such thing" answer to give, so there is nothing for
a missing argument to mean — ProceduralSystem.fromName(undefined) and
ShipLoadout.empty(undefined) throw rather than answering null, and so do
toSystemAddress, massCodeToSizeClass and resolveBlueprintForModule's fdname,
which convert or resolve what you pass rather than looking it up. The rule is what the
function does with the argument, not what it returns: massCodeToSizeClass hands back a
number and is still strict.
A build's slot key is loud too, across every method that takes one, and it is the
exception worth knowing because several of them do look like searches —
fittedModuleAt('NoSuchMount') answers null the way a catalogue miss does. The
difference is that the key names a mount on this build rather than a record to find:
removeModule(undefined) is not "empty the slot that is not there", it is a caller who
has not said which slot. So build.fittedModuleAt(undefined) throws
ShipLoadout: slotKey must be a string, received undefined, where
getShipBySymbol(undefined) would answer null.
null is not an error. A lookup that finds nothing has answered you. Journal symbols
may be absent from the catalogues, so consumers must handle null as an ordinary miss.
For input you do not control, prefer the try… form where one exists, which converts a
throw into a null:
import { toSystemAddress, tryToSystemAddress } from '@elite-dangerous-almanac/core/astro/system-address-input';
toSystemAddress('3309179996515'); // -> 3309179996515n, throws on bad input
tryToSystemAddress('not an address'); // -> null, never throwsThe three aggregate figures always have an answer:
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout;
build.unladenMass; // number
build.cargoCapacity; // number
build.fuelCapacity; // { main, reserve }
build.buildMass(); // { hull, modules, unladen, fuel, cargo, total }Nothing a build can hold is unweighable: import discards an article no catalogue
identifies, and setModule refuses one — as it refuses a record that drops the article's
mass, cargoCapacity or fuelCapacity, the three figures every build sums from its
fit. A hull with no rack has a cargo capacity of 0, and it means 0. buildMass is
those figures decomposed, so it always answers too.
The metrics that depend on build state are a different question, and those keep their
diagnostic pairs. The nullable method is the convenience; its …Result companion is what
you show a user when the convenience is null:
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout;
build.mobilityMetrics(); // MobilityMetrics | null
build.mobilityMetricsResult(); // the value, or why it is unavailable
build.thrusters; // ThrusterParams | null — the fitted curve, whatever the power state
build.shieldMetrics();
build.shieldMetricsResult();
build.shieldRecovery();
build.shieldRecoveryResult();
build.standardLoadResult('maximum');Each result is a CalculationResult: complete: true carries a non-null value and no
issues; complete: false carries value: null and one or more issues. The issue's stable
reason says which unavailable state the caller should present:
| Reason | Means |
|---|---|
missing |
The shield generator is not fitted |
unresolved |
The fitted record lacks a numeric fact this metric needs, such as part of a thruster's mass curve |
disabled |
The required fitted module is switched off |
shed |
The retracted priority budget does not power the required module |
invalid |
A known build dependency is non-physical, such as a non-positive or non-finite power-plant capacity or a negative module draw |
These reasons describe build state. A malformed method option still throws its documented
TypeError or RangeError before a result is returned.
A figure the import already stated wins while its fitted set remains intact. A build
read from a Loadout event reports the game's UnladenMass, CargoCapacity and
FuelCapacity directly. If import strips an unrecognised module or stocks a fixed mount,
it drops the capture's aggregates too: mass, cargo and fuel are recomputed from the
normalized fit, while modulesValue and rebuy read null, because nothing records
what the discarded article cost. Stocking an absent bulkhead or cargo hatch changes none
of them — both stock articles are free and weightless.
Absent is not zero, anywhere in the library. A catalogue field the source did not
carry is omitted rather than defaulted, and a capture that priced no module for a slot
reports null rather than 0 — a cockpit no journal prices was not free.
Two questions, kept apart in the type — though on a build they agree, since every build fills its core and armour mounts:
import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
declare const build: ShipLoadout;
build.validation.valid; // is the fit structurally legal?
build.validation.complete; // legal *and* every operational mount filled
build.validation.issues; // what specificallyEach issue carries a stable code and a severity:
-
error— the fit is wrong: a module in a mount that cannot take it (incompatibleModule), or a mount the hull does not have (unknownSlot). This is the user's problem, and you should say so. (duplicateSlotis an error too, but only ever reaches you fromvalidateLoadouton a module list you assembled yourself — aShipLoadoutthrowsTypeErroron a duplicate rather than reporting one, so do not write a UI branch for it on a build.) -
incomplete—missingRequiredSlot: a core or armour mount left empty. NoShipLoadoutreports it, since every build fills those mounts; likeduplicateSlotit reaches you only fromvalidateLoadouton a list you assembled yourself.
Neither question reports normalization. A build whose unknown power plant was stocked
from the hull defaults is valid and complete with no issues — the fit that remains
really is legal and really is filled. build.importOutcomes is the only record that the
figures now describe that fit rather than the capture.
Two rules that look like they conflict and do not:
-
Identifiers are matched case-insensitively, with surrounding whitespace ignored. A
symbol straight off a journal line works without normalizing it, and slot keys match
whether the producer wrote
FrameShiftDriveorframeshiftdrive. -
Structure is checked strictly. A malformed entry is rejected rather than
half-read, and
parseSlefrejects the whole payload on any bad entry. UseinspectSlefwhen you would rather have the good entries plus indexed diagnostics.
Exported catalogues are deeply frozen, so nothing a consumer does can mutate the shared singleton a later lookup sees. Units are stated on the exported types: resistances are fractions, masses tonnes, power megawatts, distances light-years, shield strength megajoules — unless a symbol's own name says otherwise.
Guides
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 (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()
- 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
- 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