# Mining
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.

> Inspect deposits and active extraction, then plan Fleet mining starts and stops.

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

Mining is a fleet sitting at an asteroid, extracting selected resources over time. Two
things are worth reading: what a deposit holds, and what a fleet is currently
pulling out of it.

:::game[Mining is production over time]
Mining does not give a fleet a pile of resources instantly. The fleet stays at
an asteroid and slowly extracts material into its cargo hold. A better mining
fleet gathers resources faster, but it still needs time to do the work. The
operation also has to stop when the fleet leaves or runs out of room.
:::

## Is this fleet mining?

```ts
const mining = await fleet.mining.get();

if (mining === undefined) {
  // Not mining. This is a normal state, not an error.
}
```

`undefined` is the answer for a fleet that is docked, in transit, or idle. The
SDK returns it rather than throwing, because "not mining" is an ordinary fact
about a fleet rather than a failure to read one.

> **Runnable example — Read mining state and a deposit.** A fleet mining state - undefined is a normal answer - and a resource deposit. Run it in the browser at https://develop.atlas-kit-docs.pages.dev/guides/mining/.

## Rates, not totals

The Fleet's mining capacity is **shared equally among its selected resources**.
Each resource's nominal rate is the Fleet base rate divided by the number
selected, then multiplied by that resource's richness. Count only selected
resources, including any with zero richness, rather than every resource in the
deposit.

`mining.miningRate.unitsPerSecond` is the Fleet base rate.
`mining.outputs[i].unitsPerSecond` already includes this division and richness;
do not divide it again. The pure `deriveMiningOutputRates` helper uses the same
rule when given the complete selected resource list.

For example, a Fleet rate of 0.06201171875 units/second shared among four
resources with richness 1 gives 0.0155029296875 units/second each. Over 658
seconds, that projects to about **10.20 units each**, rather than 40.80.

`projectMiningOutputUnits` multiplies a rate by elapsed seconds without rounding.
These are nominal estimates, not guaranteed cargo gains: capacity, upkeep and
whole-unit settlement remain chain-authoritative. The snapshot's
`lastUpdatedAtUnixSeconds` is the Fleet's last update, not a guaranteed mining
start time. Refresh mining state and cargo after confirmed actions before
projecting further accrual.

:::game[The best asteroid depends on the job]
Asteroids do not all contain the same materials, and some deposits are richer
than others. A rich deposit lets a fleet pull out more material in the same
amount of time, but the fleet's own mining ability still matters. The richest
asteroid is not always the best choice if it is far away, dangerous, or costly
to reach. Players balance the resource they need against yield, travel, and
risk.
:::

## Estimating a mining session

`deriveMiningEstimate` models a requested duration with explicit balances and
one row per selected resource. It shares the Fleet rate equally, then applies
each row's richness and effective runtime multiplier. Different resources can
have different storage costs and multipliers.

Runnable offline example with hypothetical values (cargo IDs label the scenario;
this helper does not look up their definitions):

```ts
import { deriveMiningEstimate } from '@aephia/atlas-kit/mining';

const estimate = deriveMiningEstimate({
  fleetUnitsPerSecond: 12,
  durationSeconds: 10,
  resources: [
    { cargoId: 300, richness: 2, storageCost: 2, runtimeMultiplier: 1.5 },
    { cargoId: 301, richness: 0.5, storageCost: 4, runtimeMultiplier: 1 },
  ],
  cargo: { occupiedSpace: 100, capacity: 1000 },
  food: { availableUnits: 50, burnUnitsPerSecond: 1, storageCost: 2 },
  ammo: { availableUnits: 100, burnUnitsPerSecond: 2 },
});

console.log(estimate.outputs.map((output) => output.projectedUnits)); // [180, 30]
console.log(estimate.finalOccupiedSpace); // 560
console.log(estimate.limitingFactors); // ['duration']
```

Use `mining.miningRate.unitsPerSecond` for the Fleet base rate. Do not pass an
already richness-adjusted output rate. Ship-component effects already included
in that Fleet rate must not be applied again. Supply effective runtime
multipliers explicitly: `1` means you assume no additional bonus. This helper
does not derive crew or research stacking from raw Character modifiers.

Use translated cargo `storageCost` values, in cargo space per unit. Occupied
space includes the supplied Food; Ammo uses its separate magazine. Consumed
Food frees cargo space while mined resources fill it. The result clips the
requested duration at the earliest Food, Ammo or cargo limit and reports ties.
A `null` limit means that constraint has no finite exhaustion time. Starting
with a full hold produces a conservative zero-duration estimate; unload first.

The result's `basis` is `official-model-estimate`. Inputs remain constant over
the modeled interval, quantities are fractional estimates, and program rounding,
Fleet eligibility and changing chain state are not reproduced. Zero output is
reported without projecting consumable use. This is a planning estimate, not
proof that mining can start or a guarantee of settled cargo.

The [official SAGE Editor Suite mining model](https://ses.staratlas.com/SAGE%20Editor%20Suite/Mining/index.html)
explicitly excludes resource hardness in current C4. Its editor metadata is not
proof that a restriction is enforced on the deployed PTR; Planet richness and
Claim Stake extraction are separate from this Asteroid-mining estimate.

## Comparing ships as miners

`deriveMiningProfile` answers the questions a player asks before choosing a
ship to mine with: how much it mines per hour, how much it mines per Food and
Ammo burned, and how long one hold takes. It runs the same official model on
one resource at a richness you choose, with no crew or research multiplier.
Runnable offline example with hypothetical stats:

```ts
import { deriveMiningProfile } from '@aephia/atlas-kit/mining';

const profile = deriveMiningProfile({
  stats: {
    cargo: {
      miningRate: { raw: 16_384n, value: 1 }, // one unit per second
      foodConsumptionRate: { raw: 2_048n, value: 0.125 },
      ammoConsumptionRate: { raw: 1_024n, value: 0.0625 },
      cargoCapacity: 100n,
      ammoCapacity: 1_000n,
    },
  },
  richness: 2,
  resourceStorageCost: 1,
  foodStorageCost: 1,
});
console.log(profile.unitsPerHour); // 7200
console.log(profile.unitsPerFood); // 16
console.log(profile.fullHold); // 50 s, 100 mined, 6.25 Food, 3.125 Ammo, limited by cargo
```

Pass `fleet.stats` or a ship configuration's `stats`. The configuration's
`foodConsumptionRate` and `ammoConsumptionRate` are burned per second while
mining, as in the official scenario. Storage costs are translated values: a
cargo definition's encoded `storageCost` of 256 is 1.

The full hold starts empty, carries exactly the Food it will burn and a full
Ammo bank. Burning that Food frees the space it took, so the hold fills in
capacity ÷ (output × storage cost) seconds unless Ammo runs out first;
`limitedBy` says which. `fullHold` is `null` when nothing is mined, the hold has
no capacity, or Food outflow keeps pace with output. Like
`deriveMiningEstimate`, this is an `official-model-estimate`, not a settlement:
eligibility, regional research, planet-exit Fuel and rounding are excluded.

## Deposits

A resource deposit is the asteroid's side of the same relationship:

```ts
const deposit = await getResourceDeposit(ctx, depositAddress);
```

It describes what the asteroid holds and the properties that govern extraction
from it.

## Starting mining

`planFleetStartMining` from `@aephia/atlas-kit/mining/actions` prepares one
instruction for a loaded Fleet and Asteroid. Select a dense, non-empty list of
unique deposit cargo ids in ascending order. The planner validates canonical
Fleet, Character, System, Asteroid, and RegionTracker relationships and exact
coordinates. Idle uses the current coordinate; warp/subwarp uses the destination
without a local clock check.

Travel types differ on the qualified PTR deployment:

- **Idle:** plan mining directly at the Fleet's current System.
- **Completed warp:** plan mining directly; the mining instruction refreshes the
  completed warp. An unfinished warp still rejects.
- **Completed subwarp:** explicitly settle before mining. Direct mining still
  rejects with `character_required_for_move_subwarp`, even with the canonical
  Character supplied. Either settle and reload Idle, or use
  `combinePlans(settlement, mining)` to place the explicit
  `planFleetSettleArrival` Plan before the mining Plan in one transaction.

Current subwarp settlement requires Profile authorization and existing
reconciled XP accounts; see [arrival settlement](/guides/moving-a-fleet/#arriving).
The combined route was verified through the public SDK in an unsigned PTR
simulation with enough Fuel to reach the Asteroid System. Settlement can stop
short with insufficient Fuel, so it does not by itself prove that mining is in
range. Simulate the complete Plan before signing. State can change between
reading, simulation and landing; neither the planner nor simulation reserves it.
The mining planner inserts no settlement instruction and chooses no authority.

These are deployment-specific observations from October 12, 2026, not a claim
about every future program version. They supersede the older blanket Idle-only
warning; movement and docking retain their own instruction-specific rules.

This illustrative example requires an Idle Fleet at the selected Asteroid,
cargo 8 in that deposit, and caller-supplied context and authorization. It
prepares and simulates without signing or submitting.

```ts
import { planFleetStartMining } from '@aephia/atlas-kit/mining/actions';
import { simulatePlan } from '@aephia/atlas-kit/planning';
import type { PlanAuthorization } from '@aephia/atlas-kit/mining/actions';
import type { FleetSnapshot } from '@aephia/atlas-kit/fleets';
import type { ResourceDepositSnapshot } from '@aephia/atlas-kit/mining';
import type { Address, SageContext } from '@aephia/atlas-kit/client';

declare const ctx: SageContext;
declare const fleet: FleetSnapshot;
declare const asteroid: ResourceDepositSnapshot;
declare const authorization: PlanAuthorization;
declare const feePayer: Address;

const plan = await planFleetStartMining(ctx, fleet, {
  authorization,
  asteroid,
  resourceIds: [8],
});
console.log(plan.describe());
const simulation = await simulatePlan(ctx, plan, { feePayer });
console.log(simulation);
```

[Saved unsigned PTR evidence](https://github.com/Aephia/atlas-kit/blob/develop/docs/evidence/mining-refresh-332.md)
measured warp settlement plus mining on an earlier runtime. The funder-only
settlement route from that capture has been removed; it is historical evidence,
not a current instruction recipe. Permissions, range, fuel and eligibility
remain chain-authoritative. After confirmed success, refresh the Fleet and read
its mining state. This planner does not stop mining or estimate yields.

### Storage paid from your Profile

Mining state is stored on the Fleet account, so starting to mine grows an Idle
Fleet by 40 bytes plus 2 per selected resource. A Fleet that arrives by subwarp
is settled to Idle first, which returns its travel storage to your Profile, so
its mining start is priced from Idle. Stopping removes the mining state, but
each mined resource can add a cargo-hold line, so eight new resources can still
grow the Fleet. The game pays any growth from your Profile's SOL vault with the
signing key. Both planners state that amount in the Plan summary, apart from
transaction fees. When it is positive, the key must be a Profile AUTH key or
also hold `drainSolVault`, and your Profile needs that many lamports above its
own rent reserve. Execution checks again before signing.

## Stopping mining

The stop planner requires Game 2 and the canonical Character's existing,
reconciled Career XP budget and Game configuration. It finds this data,
fails locally with a repair remedy when the data is unusable, and rechecks it
before signing. It never sets up or pays for XP tracking; accrued XP may change.
Current instruction construction and saved-Plan checks have offline regression coverage.
No current mining Fleet was available for an unsigned PTR stop, so current XP
yield remains unmeasured; earlier-runtime captures below do not qualify it.

Use `planFleetStopMining(ctx, fleet, { authorization })` from
`@aephia/atlas-kit/mining/actions` with an owned mining Fleet. The planner takes
the Asteroid from Fleet state, so no caller-supplied deposit is needed. It returns
one inert Plan and requires only the authorization authority to sign. A fresh
canonical Fleet read verifies the Game, owning Profile, mining state and selected
Asteroid; execution checks these again before signing. Character
is derived from Profile and Game; optional FleetCrewBinding and RegionTracker
accounts are included only after validated canonical reads. Invalid accounts or
read failures reject planning instead of being treated as absence.

### Shared Fleet crew binding validation

Coordinate warp/subwarp, Fleet and Starbase attacks, Claim Stake Fleet transfer,
and stop mining share the same internal FleetCrewBinding identity contract.
A present binding must have the SAGE program owner, correct discriminator,
exact 403-byte layout and canonical PDA derived from the requested Fleet.
Only the evidenced exact neutral encoding—all 395 bytes after the discriminator
zero—waives stored Fleet/Profile equality. Populated bindings must match both
identities. Partial-zero identities and alternative empty encodings with nonzero
version, bump or lastActionEpoch remain unsupported; no future layout is assumed.
Absence and instruction optional-account sentinels are unchanged. This does not
establish live acceptance for the other actions or populated crew bonuses/XP.

Refresh the Fleet before preparing a stop Plan so its state and Asteroid reflect
the latest read. This illustrative example uses the refreshed snapshot and
explicit Profile authorization; it prepares a Plan without signing or submitting.

```ts
import { getFleet, type FleetSnapshot } from '@aephia/atlas-kit/fleets';
import {
  planFleetStopMining,
  type PlanAuthorization,
} from '@aephia/atlas-kit/mining/actions';
import type { SageContext } from '@aephia/atlas-kit/client';

declare const ctx: SageContext;
declare const fleet: FleetSnapshot;
declare const authorization: PlanAuthorization;

const refreshedFleet = await getFleet(ctx, fleet.address, { refresh: true });
if (refreshedFleet.state.kind === 'mining') {
  const plan = await planFleetStopMining(ctx, refreshedFleet, {
    authorization,
  });
  console.log(plan.describe());
}
```

Saved stop Plans use a finite mining-state guard. Old unguarded
stop instructions, including those inside mixed Plans, are rejected with a
recreate-Plan remedy. Resource accrual and unrelated Fleet updates do not
invalidate the guard. A preceding instruction referencing this Fleet cannot
share its guarded stop transaction; confirm that action first, refresh, then
prepare the stop. Unrelated instructions and different Fleets may still combine.

Refresh before planning and only stop a Fleet that is still mining. **Idle does
not mean a free no-op:** the measured PTR instruction consumed the Fleet's
planet-exit fuel amount even when called after the Fleet was already Idle.
Cargo/upkeep checks can also reject the call. Simulate immediately before
execution; a refresh does not prevent state changing afterward. DO_MINING
permission and mining results remain chain-authoritative. After confirmed
success, refresh the Fleet and Asteroid; planning itself does not sign or submit.

The [unsigned PTR stop experiment](https://github.com/Aephia/atlas-kit/blob/develop/docs/evidence/stop-mining-489.md#fuel-controls--2026-09-16)
succeeded after loading the measured **1,383 exit Fuel** into that Fleet's tank.
Both immediate start/stop and Idle-stop consumed it; this number belongs to that
fixture, not every Fleet. Both RegionTracker choices also succeeded with a
chain-created, empty CrewBinding on a second Fleet whose exit requirement was
**10 Fuel**. The SDK accepts this exact empty binding at its canonical PDA;
nonempty bindings must match the Fleet and owning Profile.
[Issue #489](https://github.com/Aephia/atlas-kit/issues/489) records the evidence.
Populated crew bonuses and elapsed mining accrual remain unverified.
Do not rely on an upstream fix: check state before planning and simulate before
execution, while recognizing that state can still change afterward.

## Gotchas

**Rates depend on the fleet, not just the asteroid.** What a fleet extracts per
unit time is a function of its ships and the resource's properties together.
Two fleets at the same asteroid do not mine at the same speed.

**Derived amounts are computed, not stored.** If you display an accumulating
total, you are computing it. Recompute it on a timer rather than reading it
once and treating it as current.

**A deposit can be read without a fleet.** Deposits and fleet-mining state are
separate reads. You do not need one to get the other.

## Reference

- [`mining`](/reference/mining/) — every export in this entry
- [`cargo`](/guides/cargo/) — where extracted resources end up
- [`world`](/guides/world/) — finding asteroids in a system
