Skip to content
Next — unreleased

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.

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.

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.

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):

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

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:

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.

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

const deposit = await getResourceDeposit(ctx, depositAddress);

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

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

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

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.

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.

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.

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

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.

  • mining — every export in this entry
  • cargo — where extracted resources end up
  • world — finding asteroids in a system