# Warp & subwarp
Documentation: next (source: develop).

Install the preview: `pnpm add @aephia/atlas-kit@next`. These docs follow develop and may include changes not yet published to npm.

> Plan, inspect, sign, execute, and refresh one fleet move.

Markdown source of https://develop.atlas-kit-docs.pages.dev/guides/moving-a-fleet/ — see https://develop.atlas-kit-docs.pages.dev/ai/ for the full machine-readable surface.

Moving a fleet starts with an explicit choice. You choose one movement
primitive, inspect the resulting Plan, let your wallet sign it, execute it once,
and then read the fleet again. No step silently chooses a route or movement mode
for you, and the available primitives are alternatives rather than one chained
journey. The rules behind that flow are laid out in [How Atlas Kit changes the
game](/start-here/how-it-acts/); this guide walks it end to end.

This guide uses undocking because it is the smallest complete movement: the
fleet begins docked and finishes idle at the same system. Subwarp, coordinate
warp, lane warp, and docking follow the same Plan flow with their own explicit
destinations.

Subwarp start, coordinate/lane warp and explicit stop-subwarp require Game 2 and
the owning Character's existing, reconciled Career XP data. The SDK finds
and validates that data, then checks it again before signing. Missing or
unreconciled state produces a local repair remedy; planning never creates,
funds or migrates XP data. Ordinary XP gains do not invalidate the Plan.
Movement-start Plans still reject changes to Character modifiers, spent XP and
other guarded Character state.

The current unsigned PTR checks cover subwarp and coordinate warp from one
elapsed journey; lane warp and intentional early stop are covered by binding
contracts and offline regressions, without a current live qualification.
See the [scope of the evidence](https://github.com/Aephia/atlas-kit/blob/develop/docs/research/movement-xp-614.md).

## Read before you move

> **Runnable example — Read a fleet.** Walk from a profile to its character, then to the fleets that character owns. Run it in the browser at https://develop.atlas-kit-docs.pages.dev/guides/moving-a-fleet/.

The embedded [playground remains read-only](/start-here/first-read/#try-it-here).
It is useful for exploring live state before planning, but it will not request
a wallet or run this signing flow.

## Plan and inspect

Planning accepts game intent plus the Profile authorization your wallet will
satisfy later. It does not open the wallet or change the game. Here
`authoritySigner` is the Kit signer returned by your wallet integration, and
`keyIndex` is its matching Profile key slot; the example uses the first slot.
`movementFleet` is a separately loaded Fleet that is idle or finishing travel
away from `system`, because creating the undock Plan does not mutate `fleet`.

```ts
import type { PlanAuthorization } from '@aephia/atlas-kit/fleets/actions';
import {
  planFleetSubwarp,
  planFleetUndock,
} from '@aephia/atlas-kit/fleets/actions';
import { deriveFleetSubwarpEstimate } from '@aephia/atlas-kit/fleets';

const authorization = {
  profile: fleet.ownerProfile.address,
  authority: authoritySigner.address,
  keyIndex: 0,
} satisfies PlanAuthorization;

const plan = await planFleetUndock(ctx, fleet, { authorization });
console.log(plan.summary);
console.table(plan.describe());

const destinationRaw = system.coordinatesRaw;
const estimate = deriveFleetSubwarpEstimate({
  fromRaw: movementFleet.locationRaw,
  toRaw: destinationRaw,
  speedRaw: movementFleet.stats.movement.subwarpSpeed.raw,
  fuelRateRaw: movementFleet.stats.movement.subwarpFuelConsumptionRate.raw,
  speedModifierRaw: character.modifiers.values.subwarpSpeedModifierRaw,
});
const exactPlan = await planFleetSubwarp(ctx, movementFleet, {
  authorization,
  destinationRaw,
});
console.log(estimate, exactPlan.summary);
```

`describe()` returns stable game-language steps. Show those lines to the person
signing so they can decide whether the move matches their intent. If fleet state
has changed since it was read, create a new Plan instead of trying to repair the
old one.

:::game[Undock funding research]
Undocking takes no Profile lamports on the ordinary, rent-funded route we
analyzed at SAGE deployment slot 58,713,616: current ship/cargo definitions,
no active stimulants and no recovery (pending HP or respawning cargo).
Outdated definitions, stimulants and recovery were not analyzed. This finding
is not a current spending cap. Do not count an undock refund toward a following
dock until the undock is confirmed. See the [research and limits](https://github.com/Aephia/atlas-kit/blob/develop/docs/research/debit-bound-disposition.md).
:::

## Sign and execute once

The signer comes from your wallet integration. The SDK receives only the Kit
signer interface for this call; it does not take custody of keys. Here the same
signer pays the fee and satisfies the selected Profile authorization.

```ts
import type { PlanAuthorization } from '@aephia/atlas-kit/fleets/actions';
import { planFleetUndock } from '@aephia/atlas-kit/fleets/actions';
import { executePlan, simulatePlan } from '@aephia/atlas-kit/planning';

const authorization = {
  profile: fleet.ownerProfile.address,
  authority: authoritySigner.address,
  keyIndex: 0,
} satisfies PlanAuthorization;

const plan = await planFleetUndock(ctx, fleet, { authorization });
// Illustrative ceiling: measure this Plan and choose an appropriate limit.
const computeUnitLimit = 400_000;
const simulation = await simulatePlan(ctx, plan, {
  feePayer: authoritySigner.address,
  computeUnitLimit,
});
console.log(simulation.unitsConsumed);
const result = await executePlan(ctx, plan, {
  feePayer: authoritySigner,
  computeUnitLimit,
});

if (result.status === 'unknown') {
  console.log('Check wallet history before deciding what to do next.');
  return;
}

const refreshedFleet = await sage.fleets.get(fleet.address);
console.log(result.signature, refreshedFleet.state);
```

Execution checks that the supplied signer matches the Plan, verifies that the
Plan is still fresh, sends exactly once, and waits for a terminal result. A
confirmed result makes the next ordinary Fleet read fresh. An `unknown` result
means submission could not be confirmed: do not retry until wallet and chain
history prove whether the move happened.

## Compute budget

:::game[Compute limits for transaction execution]

`computeUnitLimit` is supported by assembly, simulation and execution. One PTR
subwarp consumed 224,068 compute units, exceeding the default 200,000-unit
instruction budget ([retained observation](https://github.com/Aephia/atlas-kit/pull/633)).
The example's 400,000 ceiling is a starting point, not a universal requirement.
Simulate with a sufficient finite ceiling, inspect `unitsConsumed`, and choose a
limit with headroom for changing state, within the SDK maximum of 1,400,000.
If you change the limit, simulate again with that final policy and pass the same
limit to execution. A failed low-budget simulation cannot measure the complete
transaction's cost. Successful simulation does not establish the final gameplay
outcome; refresh confirmed state afterward.

Transaction-exclusive Plans reject compute-budget options. Do not add these
options indiscriminately to every Plan or insert extra instructions to bypass
that restriction.

:::

## Other movement primitives

Choose the primitive yourself rather than asking the SDK to infer a route:

- `planFleetSubwarp` moves an idle or arrived-but-unsettled fleet to a coordinate.
- `planFleetWarpToCoordinate` warps an idle or arrived-but-unsettled fleet to a coordinate.
- `planFleetWarpLane` warps an idle or arrived-but-unsettled fleet to an explicit connected destination system.
- `planFleetDock` docks an idle fleet at its current system or an arrived Fleet at its movement destination.

Coordinate movement accepts exactly one destination representation. Use
`destination` when caller-selected display-number rounding is intentional, or
use `destinationRaw` to preserve exact signed-I8F56 axes. A Star System's
`coordinatesRaw` can feed both a qualified estimate and the matching planner
without a `Number` round-trip, as the compiled Plan example above demonstrates.

The estimate remains advisory. Its Fuel budget does not prove that the Fleet
can afford the move or that the game will accept it; refresh and simulate the
Plan before signing.

Docking resolves its faction policy internally. A Fleet whose Starbase Player
belongs to the Starbase's controlling faction docks whatever the territory and
economics settings. The controller is the legacy Starbase owner unless dynamic
capture is on and a current ownership record names another faction. A foreign
Starbase Player is refused while territory access is off. Foreign docking under
territory access, which depends on diplomacy and standing, and docking at a
Starbase run by a dynamic faction stay unsupported and fail locally with a
remedy; the SDK does not infer permission for them. Saved Plans from before this
contract repair must be recreated with the current SDK.

:::game[Upgrading an older docking instruction]
Older SDK versions that emit eight docking accounts can fail on current PTR
with error 9004 (missing faction relation tracker). Upgrade before recreating
the Plan.
:::

Faction-4-controlled Systems remain outside this supported route. The policy observed on
2026-10-09 had `territoryAccessEnabled=0`, documented by the pinned bindings as
own-faction docking only. It predicts refusal for a player Fleet at controller
4; this has not been measured on the processor. A positive qualification needs
a same-faction NPC fixture and fresh policy checks, not another player
registration. Qualification is tracked in [#831](https://github.com/Aephia/atlas-kit/issues/831).

:::note[Warp lanes are the third mode of travel]
Subwarp and coordinate warp move within reach of a coordinate; a warp lane
jumps system to system, and only where the starmap says the two connect.
Connectivity is not a guarantee of travel permission. Disabling cross-faction
economics forecloses cross-faction warp without affecting same-faction warp;
enabling it still leaves the current relationship, destination risk, and toll
to the program. `planFleetWarpLane` proves connectivity but does not treat the
flag as blanket permission or estimate the final ATLAS toll. Tested current PTR
routes work with empty Fuel tanks; do not infer a Fuel requirement from the
pinned binding description.

Lane Plans tolerate other players' shared ATLAS accounting updates. They still
recheck the configured Game, toll inputs and your Character's modifiers and
balance. Recreate previously prepared Plans to use this behavior. State can
still change between validation and landing.

The lane network, base toll inputs, and program-authoritative limits are covered
in [the Starmap guide](/guides/world/#warp-lanes).
:::

## Docking funding

`getFleetDockDebitBounds(plan)` reports a conservative maximum Profile debit
for a qualified idle Fleet. An unavailable result means the SDK cannot provide
that estimate; it does not mean docking is free. Inspect each docking Plan
separately before composing actions. This illustrative example assumes an idle
Fleet at a registered Starbase and an authorized wallet signer.

```ts illustrative
import {
  getFleetDockDebitBounds,
  planFleetDock,
} from '@aephia/atlas-kit/fleets/actions';

const dockPlan = await planFleetDock(ctx, fleet, {
  authorization: {
    profile: fleet.ownerProfile.address,
    authority: authoritySigner.address,
    keyIndex: 0,
  },
});
const dockBounds = getFleetDockDebitBounds(dockPlan);
if (dockBounds.status === 'available') {
  console.log(dockBounds.maximumProfileDebit, dockBounds.profileUnit);
} else {
  console.log(dockBounds.reason);
}
```

The estimate is available for the qualified ordinary idle route with current
definitions, no active stimulants or pending recovery, and sufficient funding.
Previously available bounds are checked again before signing. RPC failures,
missing required funding inputs, context disposal and lifecycle cancellation
reject planning; they do not create a Plan with its funding check disabled.
Unsupported estimate data, stale sequences, pending recovery, active stimulants
and insufficient funding can produce an explicit unavailable result. Within one
`executePlan` call, including a Plan made with `combinePlans`, guarded storage
debits for the same Profile must fit together while preserving its rent reserve
once. Fund the Profile itself when its combined gross ceiling is short;
prefunding the Fleet or StarbasePlayer does not reduce that quoted ceiling.
The inspection helper returns unavailable for combined Plans, so inspect each
individual docking Plan. Its result supports `JSON.stringify`, with amounts
serialized as decimal strings. A dock cannot share its Fleet or StarbasePlayer
with another action in the same Plan, and another action cannot change its
Star System, the Game or the faction policies it reads within that Plan.
If its estimate is unavailable, it also cannot share its Profile with another
action.
Refresh and recreate a qualified Plan, or execute the actions separately with
fresh balances between them. Separate Plan
executions, sequence steps and concurrent application requests have no shared
reservation: applications must coordinate those operations and refresh balances
between them. The SDK does not top up funds or reserve funds on chain.

:::game[Storage funding and fees]
The maximum is a **gross storage debit in native lamports**: the current rent
cost of adding 40 bytes to the Fleet. Existing surplus can reduce the actual
charge, and refunds can hide it in the net balance change; neither reduces the
quoted maximum. Network fees paid by the transaction fee payer are separate.
Unsupported layouts, noncanonical data, stale sequences or insufficient rent
funding make the estimate unavailable. These checks run before signing and do
not create an atomic on-chain spending cap: state can change before landing.
:::

## Arriving

A fleet that reaches its destination keeps reporting the move it was on.
Nothing rewrites that on a timer, so a fleet that arrived weeks ago still reads
as warping until something acts on it.

Movement and docking have their own program refresh paths. Mining after
completed subwarp needs explicit settlement; completed warp can refresh directly
into mining on the qualified PTR deployment. See the
[mining arrival rules](/guides/mining/#starting-mining); do not assume every
action completes an arrival automatically.

`planFleetSettleArrival` settles a journey after its scheduled duration has elapsed. It reads the
chain Clock and exact journey values during planning and again before signing.
Journeys whose scheduled duration has not elapsed and Idle Fleets fail locally. For Game version 2 subwarp, supply the
owning Profile's direct, unexpired authority:

```ts
import { planFleetSettleArrival } from '@aephia/atlas-kit/fleets/actions';

const settle = await planFleetSettleArrival(ctx, fleet, { authorization });
console.log(settle.describe());
```

The selected key must have Profile AUTH or SAGE moveFleet permission. The program
still decides whether its permissions suffice; the SDK never adds vault-drain
permission. Character, RegionTracker and reconciled Career XP data must
already exist. Certificates and delegated sub-Profiles are unsupported here.
The transaction fee payer is a separate assembly/execution choice.

Only Game version 2 subwarp is supported. Warp settlement, superseded Game
version 0, migration-sealed version 1 and unknown runtimes are rejected. The
funder-only settlement input is no longer accepted. Subwarp settlement charges fuel, awards XP on the supported current route,
and may refund released storage to the Profile. It does not clear warp
cooldown. Use `planFleetStopSubwarp` for intentional early stopping;
it uses the same existing Game-2 XP data.

**Settlement is not proof of reaching the destination.** A Fleet with insufficient
Fuel can settle Idle short of its destination, with no Fuel remaining. The Plan
warns when the fresh tank is below the recorded journey budget; this is a
planning-time observation, not a Fuel lock. Settlement remains available so an
underfunded Fleet can leave its travel state. Successful simulation or execution
alone does not prove arrival: inspect the refreshed Fleet state and location
before planning its next action or docking. Do not assume Fuel can be loaded in
open space; ordinary Fleet cargo transfer requires docking.

Automation can inspect a fresh Fleet without parsing Plan prose. When
`fleet.state.kind === 'subwarp'`, compare
`(fleet.fuel.amount << 56n) < fleet.state.totalFuelExpenditureRaw`.
A true result means the tank is below the stored journey budget. Both sides are
exact integers; the stored budget has 56 fractional bits. These fields describe
an already recorded journey, not a quote for a proposed departure. A shortfall
should trigger an explicit recovery decision, not an assumption that settlement
will reach the destination; settlement remains available.

Ensure enough Fuel **before departure**. Movement-start planners currently do not
calculate or guarantee the full journey's Fuel requirement. Intentional stopping
before the scheduled duration remains the separate `planFleetStopSubwarp` action.

Saved arrival Plans retain the exact journey and authority. Changed trips,
ownership, invalid XP state and expired authority require replanning. Old
unguarded settlement Plans also require recreation. Other Fleet updates and XP
accrual do not invalidate the Plan. A preceding action may not reference
the same Fleet; settle first, then compose an otherwise supported move/dock.
Current live qualification does not cover every composed path.

**A race remains:** these checks cannot atomically bind the action to the
checked journey. Another transaction could start a new trip before execution,
and the authorized settlement action could stop that new trip. There is
no automatic retry.

Movement planners accept stored warp and subwarp states and let the program
perform its normal refresh-to-idle step. Docking accepts a stored warp state but
refuses a subwarp state: the dock step refreshes the Fleet without its
Character and cannot settle a subwarp journey, so settle the arrival or stop the
subwarp first. All of them use the stored movement destination without guessing
from local time; a genuinely in-transit action is still rejected by chain
simulation.

Each planner produces the same inspectable Plan shape and uses the same
execution call.
The planner validates facts it can prove from current game state. The pure movement estimates below are separate from Plan validation.

`combinePlans` places the combined steps in one transaction; it inserts neither
elapsed time nor a confirmation boundary. It rejects conflicting subwarp,
coordinate warp, and lane warp starts for the same Fleet. If the Plans do not
contain enough information to identify a conflict, the SDK leaves their
compatibility unknown instead of assuming they are safe to combine.

For a journey that must move, wait/confirm, then move again, use the shipped
[`PlanSequence` coordinator](/guides/plan-sequences/). It preserves separate
confirmed transactions, lazy capability readiness, caller-owned checkpoints,
and fresh authorization for every reached Plan.

## Reference

- [`fleets/actions`](/reference/fleets/actions/) — every movement planner
- [`planning`](/reference/planning/) — Plan inspection, assembly, and execution
- [Plan sequences](/guides/plan-sequences/) — non-atomic multi-transaction journeys
- [Fleets](/guides/fleets/) — reading fleet state before and after a move

## Predicting the warp cooldown

`deriveFleetMovementCooldown` predicts the assigned cooldown timestamp, not
whether a movement transaction can succeed. Supply an already-known arrival
time for coordinate warp; this helper does not calculate travel time. Use
`deriveFleetTimers` to display an existing Fleet's observed timers.

```ts
import { deriveFleetMovementCooldown } from '@aephia/atlas-kit/fleets';

const expiresAt = deriveFleetMovementCooldown({
  kind: 'coordinateWarp',
  arrivesAtUnixSeconds: 1_800_000_000n,
  baseCooldownSeconds: 61,
  cooldownModifierRaw: 2_147_483_648n, // raw Character modifier: 0.5
});
console.log(expiresAt); // 1800000031n
```

Coordinate warp rounds the modified cooldown upward and adds it after arrival.
A zero modifier uses the unmodified base. For `warpLane`, supply
`departsAtUnixSeconds` and `existingCooldownExpiresAt`: a lane adds no cooldown
but keeps an active one, so the result is the later of the two. For `subwarp`,
supply `existingCooldownExpiresAt`: it is preserved unchanged.

Use the Fleet's `stats.movement.warpCoolDownSeconds` and the Character's
`modifiers.values.warpCooldownModifierRaw` value. Base seconds must be an integer from 0 to 65535. New timestamps and raw modifiers must be nonnegative signed-i64 values;
results outside signed i64 are rejected. An existing expiry may retain
negative sentinels. The result is bigint Unix seconds, not a JavaScript Date.

This calculation is empirically qualified against PTR deployment slot 54356023. It checks no active cooldown, state, permissions, Fuel, range or lane
eligibility. Refresh and simulate the intended action before signing.

## Estimating a subwarp journey

`deriveFleetSubwarpEstimate` predicts direct subwarp duration and fractional
Fuel expenditure for an Idle Fleet. It also returns `fuelBudget`, rounded up to
whole Fuel units. Compare that budget with `fleet.fuel.amount`: subwarp can
start without enough Fuel, so a successful start does not promise arrival.

This standalone example is runnable and needs no RPC or wallet:

```ts
import { deriveFleetSubwarpEstimate } from '@aephia/atlas-kit/fleets';

const estimate = deriveFleetSubwarpEstimate({
  fromRaw: { x: 0n, y: 0n },
  toRaw: { x: 1n << 56n, y: 0n }, // one AU east, exact I8F56
  speedRaw: 65_536n, // one AU/second, U16F16
  fuelRateRaw: 64n, // one Fuel/AU, U26F6
  speedModifierRaw: 4_294_967_296n, // neutral I32F32
});
console.log(estimate.durationSeconds, estimate.fuelBudget); // 1, 1n
```

For real inputs, use `fleet.locationRaw`,
`fleet.stats.movement.subwarpSpeed.raw`,
`fleet.stats.movement.subwarpFuelConsumptionRate.raw`, and
`character.modifiers.values.subwarpSpeedModifierRaw`. Use the exact encoded
raw destination too; do not recover a Fleet's raw position by multiplying its
approximate `location` display numbers. The stored position is not a moving
Fleet's interpolated position. Values ending in `Raw` on the result retain 56
fractional bits; display numbers are approximate, and JSON serializes bigint as
decimal strings.

The model is qualified against PTR deployment slot 54356023. A zero modifier
means neutral. Inputs must fit their documented fixed-point ranges; zero speed,
negative modifiers, identical endpoints and unsupported coordinate differences
or distances are rejected. So is a route shorter than the program can measure
(a displacement below 2^28 raw quanta on both axes), because the program refuses
to start such a subwarp; `planFleetSubwarp` refuses it too. This helper predicts neither planet departure nor warp,
lane travel, XP, permissions or journey completion. The budget is not a
measured settlement debit. Refresh and simulate the actual Plan before signing.

## Estimating a coordinate warp

`deriveFleetCoordinateWarpEstimate` predicts whole-second duration including
spool time, the Fuel debit at departure, and whether the computed distance is
within the Fleet's maximum range. It uses coordinate-warp stats, not lane stats.
This standalone example is runnable without a wallet or RPC:

```ts
import { deriveFleetCoordinateWarpEstimate } from '@aephia/atlas-kit/fleets';

const warp = deriveFleetCoordinateWarpEstimate({
  fromRaw: { x: 0n, y: 0n },
  toRaw: { x: 1n << 56n, y: 0n },
  speedRaw: 65_536n,
  fuelRateRaw: 64n,
  speedModifierRaw: 4_294_967_296n,
  spoolSeconds: 3,
  maxWarpDistanceRaw: 256n, // one AU, U8F8
});
console.log(warp.durationSeconds, warp.fuelDebit, warp.withinRange); // 4n, 1n, true
```

For actual inputs, use the same exact coordinate rules as subwarp, with
`fleet.stats.movement.warpSpeed.raw`,
`fleet.stats.movement.warpFuelConsumptionRate.raw`,
`fleet.stats.movement.maxWarpDistance.raw`,
`fleet.stats.combat.warpSpoolTimeSeconds`, and
`character.modifiers.values.warpSpeedModifierRaw`.

Duration includes spool; Fuel and duration round upward after the measured
fixed-point intermediates. Exact maximum range is inclusive. `withinRange`
compares only distance: it does not check cooldown, crew, permissions, region
rules or Fuel in the tank. Out-of-range requests still return estimates with
`withinRange: false`. Use the cooldown helper separately. This deployment-pinned
model covers Idle departures, not planets or lanes; simulate before signing.

## Estimating warp-lane travel

`deriveFleetWarpLaneEstimate` calculates travel time from exact System
coordinates and Fleet lane speed. Current qualified lanes debit zero Fuel;
Character warp-speed modifiers and spool time do not apply. This runnable
standalone example needs no RPC or wallet:

```ts
import { deriveFleetWarpLaneEstimate } from '@aephia/atlas-kit/fleets';

const lane = deriveFleetWarpLaneEstimate({
  fromRaw: { x: 0n, y: 0n },
  toRaw: { x: 1n << 56n, y: 0n },
  speedRaw: 65_536n,
});
console.log(lane.durationSeconds, lane.fuelDebit); // 1n, 0n
```

For real inputs, use each System's `coordinatesRaw` and the Fleet's
`stats.movement.warpLaneSpeed.raw`. Display `coordinates` can lose bits, so do
not reconstruct raw values by multiplying display numbers. Duration rounds up
after the measured fixed-point intermediates. Identical and zero-quantized
paths are rejected; coordinates, axis differences and distance must fit signed
I8F56, and lane speed must be positive u32 raw.

This model is qualified against PTR deployment slot 54356023, not every future
version of the game. It does not establish a lane connection, faction/risk
permission, ATLAS toll, affordability or Fleet readiness. Use the cooldown helper
separately, and refresh and simulate the actual Plan before signing.

## Estimating a warp-lane toll

`deriveFleetWarpLaneToll` predicts the **Character's in-game ATLAS debit**, not
a wallet charge. Use the source System's connection entry for the destination,
the source Starbase level, and the Fleet-to-destination-owner relationship.
This runnable example shows a CSS departure with a 0.75 fee modifier and an
allied foreign destination:

```ts
import { deriveFleetWarpLaneToll } from '@aephia/atlas-kit/fleets';

const toll = deriveFleetWarpLaneToll({
  costs: {
    level1Raw: 100000n,
    level2Raw: 200000n,
    level3Raw: 300000n,
    level4Raw: 400000n,
    level5Raw: 500000n,
    cssRaw: 600000n,
  },
  sourceLevel: 'css',
  fleetFaction: 'oni',
  sourceOwner: 'oni',
  destinationOwner: 'mud',
  destinationRelationship: 'allied',
  feeModifierRaw: 3221225472n,
  alliedTollBps: 5000,
  neutralTollBps: 20000,
});
console.log(toll.modifiedBaseRaw, toll.surchargeRaw, toll.totalRaw);
// 450000n, 300000n, 750000n raw ATLAS
```

Pass the connection's `costs`, source Starbase's `level` and owners, Character's
`modifiers.values.warpLaneFeeModifierRaw`, and current relationship tracker bps.
The modified base rounds upward. If either endpoint is foreign to the Fleet,
the surcharge uses the unmodified base and rounds downward. Allied uses allied
bps; other relationships use neutral bps, as does a foreign-source trip back to
the Fleet's own faction. A zero fee modifier removes only the modified base.

All amounts returned are exact raw ATLAS (Floyds), serialized as decimal strings
in JSON. Only playable factions, usable source levels, unsigned u64 base/total,
nonnegative signed-i64 modifier and u16 bps are supported. The qualified model
does not check the cross-faction enable flag, destination risk, connection,
available ATLAS or transaction eligibility. Refresh and simulate the Plan.

## Checking warp-lane policy

`deriveFleetWarpLanePolicy` reports a bounded set of policy failures. A passing
result does **not** mean the transaction will succeed. Supply current endpoint
ownership/levels, the cross-faction flag, Character security access, and the
region that contains the destination **coordinates**. This runnable standalone
example assumes that region was already resolved:

```ts
import { deriveFleetWarpLanePolicy } from '@aephia/atlas-kit/fleets';

const policy = deriveFleetWarpLanePolicy({
  fleetFaction: 'oni',
  sourceOwner: 'oni',
  destinationOwner: 'oni',
  sourceLevel: 'css',
  destinationLevel: 'level-1',
  crossFactionEconomicsEnabled: false,
  securityZoneAccess: 'low',
  enemyFactionZoneAccess: false,
  destinationRegion: {
    owner: 'oni',
    safety: 'safe',
    risk: { kind: 'low', faction: 'oni' },
  },
});
console.log(policy.passesPolicy, policy.failures); // true, []
```

The helper does not perform spatial lookup. Do not infer a different risk region
merely by changing a System's stored region ID or tracker membership: the
program's observed check follows destination position. Pass a region you have
resolved from those coordinates, plus Character
`modifiers.values.securityZoneAccess` and
`modifiers.values.enemyFactionZoneAccess` and the current tracker flag.

| Failure                  | Meaning                                                                                                                 |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `source-level-zero`      | The departure Starbase has no usable lane level.                                                                        |
| `destination-level-zero` | The destination Starbase has no usable lane level.                                                                      |
| `cross-faction-disabled` | At least one endpoint is foreign to the Fleet while cross-faction economics is disabled.                                |
| `foreign-low-risk`       | The destination's effective low-risk faction differs from the Fleet's. Allies are still rejected.                       |
| `security-zone-access`   | Character access is lower than the destination's effective risk tier.                                                   |
| `region-owner-access`    | A medium/high destination region belongs to another playable faction and the Character lacks enemy-faction-zone access. |

Stored low risk retains its embedded faction. Safe medium/high regions owned by
a playable faction resolve to low risk of their owner; Border regions and
Unaligned-owned regions retain their stored tier. The result exposes that
`effectiveRisk`. Only playable factions (an Unaligned destination region is
allowed) and known tiers/levels are qualified; unsupported values throw a typed SDK error rather than guessing NPC
policy. Relationship/combat/docking rules are not substituted for lane policy.

This deployment-pinned calculation does not check connectivity, permissions,
Fleet state, cooldown, crew, XP, tolls or balances. It also cannot bind a later
transaction to the checked state. Refresh and simulate the actual Plan.

## Comparing ships: a reference travel profile

`deriveFleetTravelProfile` turns movement stats into numbers you can compare
across ships: seconds and Fuel per AU of subwarp, how far a full tank reaches,
what one maximum-range warp costs, and how fast chained warps travel once each
cooldown is waited out. It composes the subwarp, coordinate-warp, lane and
cooldown estimates above over a one-AU trip and a maximum-range jump. This
runnable standalone example needs no RPC or wallet:

```ts
import { deriveFleetTravelProfile } from '@aephia/atlas-kit/fleets';

const profile = deriveFleetTravelProfile({
  stats: {
    movement: {
      subwarpSpeed: { raw: 65_536n, value: 1 }, // one AU per second
      subwarpFuelConsumptionRate: { raw: 64n, value: 1 }, // one Fuel per AU
      warpSpeed: { raw: 65_536n, value: 1 },
      warpFuelConsumptionRate: { raw: 128n, value: 2 },
      maxWarpDistance: { raw: 512n, value: 2 }, // two AU
      warpCoolDownSeconds: 6,
      warpLaneSpeed: { raw: 131_072n, value: 2 },
    },
    cargo: { fuelCapacity: 100n },
    combat: { warpSpoolTimeSeconds: 2 },
  },
});
console.log(profile.subwarp?.fullTankRangeAu); // 100
console.log(profile.coordinateWarp?.maxJumpFuel); // 4n
console.log(profile.coordinateWarp?.cycleSeconds); // 10n: 2 spool + 2 travel + 6 cooldown
console.log(profile.coordinateWarp?.sustainedAuPerHour); // 720
```

Pass `fleet.stats` for a Fleet, or a ship configuration's `stats` from
`listShipConfigurations` for one ship of that configuration; a one-ship Fleet
has the same stats. Character modifiers are optional raw I32F32 values
(`subwarpSpeedModifierRaw`, `warpSpeedModifierRaw`, `warpCooldownModifierRaw`
from `character.modifiers.values`); omit them for base values.

Full-tank range divides the tank by the Fuel rate. Sustained warp speed assumes
each maximum-range warp departs when the cooldown assigned at arrival expires.
Jumps per tank counts warp debits that fit in a full tank; whether a warp may
depart with less Fuel than its debit is not established. A section is `null`
when its speed or range is zero, and a full-tank value is `null` when that mode
burns no Fuel.

These are reference values from Idle, not a route: planet departure, region
policy, lane connections and tolls, crew, XP, active timers and current Fuel are
not considered. Fleets mixing configurations have not been qualified. See the
[reference profile evidence](https://github.com/Aephia/atlas-kit/blob/develop/docs/evidence/ship-reference-profiles.md).
