# Crafting
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 recipes, habs, and processes, then plan process starts, completion, and cancellation.

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

Crafting has three moving parts, and separating them makes the API obvious:

- a **recipe** is the rule: these inputs become that output, taking this long;
- a **hab** is the place: a plot at a starbase where crafting can happen;
- a **process** is the run: one recipe executing in one hab, right now.

This guide covers recipes and processes. The hab as a _facility_ — its
buildings, modifiers, capacity, and rent — has
[its own guide](/guides/crafting-habs/).

:::game[Recipes run inside Crafting Habs]
A recipe is the plan for making something: it lists the materials needed, what
the job produces, and how long it normally takes. A Crafting Hab is the
workshop where that recipe runs. Its size and quality affect how much work it
can handle and how efficiently it works. While a job is running, its materials
and workshop space are tied up until the craft finishes or is cancelled.
:::

## Recipes

Recipes are separate accounts belonging to the configured Game. Read one by its
Game-scoped id:

```ts
const recipe = await sage.recipes.byId(recipeId);
```

To discover the catalog without knowing ids, call
`sage.recipes.list({ policy: 'no-store' })`.

The functional equivalent is `listRecipes(ctx, options?)` from
`@aephia/atlas-kit/crafting`. Both return a frozen readonly array sorted by
ascending numeric Recipe id, including **active and deactivated** Recipes.
Ingredients and outputs resolve cargo ids and names; `quantityRaw` stays an exact
`bigint`, and `JSON.stringify(recipes)` emits decimal strings. Empty results are
frozen arrays too, with discovery metadata available through `readMeta`.

Discovery uses only the verified Recipe discriminator and configured Game
filters, never id probing or an unfiltered scan. A configured indexer takes
precedence: it must support the full query, supplies address hints, and every
present candidate is validated directly. Duplicates are collapsed, missing stale
hints are omitted, and an empty indexer result does not fall back to RPC. Invalid
present accounts, unresolved cargo, reported provider/indexer failures and
resource-limit violations reject the whole call; the SDK does not truncate the
catalog or return a successful prefix.

### Daily snapshots and freshness

Each completed listing reconsults discovery; compatible concurrent requests may
share in-flight work. A retained context can observe changed Recipes while
**reusing cargo definitions** from its separate registry. `policy: 'no-store'`
prevents discovered Recipe cache writes, not registry retention. Per-call
`commitment` governs Recipe discovery/direct validation; cargo definitions use
the context's commitment. `signal` cancels only that caller's wait, including
while definitions load. Context disposal cancels its pending callers.

For a daily snapshot, create a fresh context with the desired commitment and
**no restored registry snapshot store**, use a provider (or configured indexer)
that can answer the complete query, and dispose the context afterwards. The runnable
[daily snapshot example](https://github.com/Aephia/atlas-kit/blob/develop/examples/contracts/daily-recipes.ts)
uses only public APIs, serializes exact quantities as decimal strings and accepts
your configured read-only RPC. Its call shapes compile in the offline gate;
Recipe discovery behavior is tested against generated offline fixtures, not a
new live catalog capture.

Finder and per-Recipe `readMeta` record strategy, filters, source, commitment and
slots; they do **not** prove a single atomic chain slot or latest state. Provider
interfaces expose no completeness token, so the SDK cannot detect a provider
that silently omits accounts. A fresh context avoids retained definitions but
does not establish that the provider itself is current or complete.

> **Runnable example — Read a recipe and crafting activity.** One recipe by id, plus the habs and processes a character runs. Run it in the browser at https://develop.atlas-kit-docs.pages.dev/guides/crafting/.

## Habs and processes

Both hang off whoever owns them, which is usually a character or their state at
a starbase:

```ts
const habs = await sage.craftingHabs.byCharacter(characterAddress);
const running = await sage.craftingProcesses.byCharacter(characterAddress);
```

A process is a lifecycle, not a boolean. It has a start, a duration derived
from its recipe, and an end — which is why crafting occupies a hab for a
period rather than completing instantly.

## Why processes need discovery

Crafting processes have **no derivable address**. You cannot compute where one
lives from the character or the recipe; it is assigned when the process is
created.

That is why the reads above are `byCharacter`, `byProfile`, and
`byStarbasePlayer` rather than a single `get(address)` — each encodes a
verified way of _finding_ processes, rather than pretending you can calculate
where they are. See [the bindings escape hatch](/concepts/bindings/) for the
broader pattern of accounts without derivable addresses.

## Running a process

The lifecycle above is also what you write against: starting a process,
collecting a finished one, and cancelling one that should stop are each one
planned transaction. If this would be your first write, read
[How Atlas Kit changes the game](/start-here/how-it-acts/) first.

Start and completion Plans resolve current history and fee routing. Their
descriptions show the applicable recipe-fee multiplier (10,000 basis points =
1×), rather than predicting a debit from an unevidenced formula. Completion
uses the fee snapshot and governance captured by a version-1 Process, even if
the Starbase now has another controller. Version-0 Processes retain their
legacy layout and have no captured fee metadata; Atlas Kit does not invent a
zero-fee guarantee.

On Game version 2, completion requires an existing reconciled Character XP
budget and XP configuration; Hab start and completion also require their
existing structure XP ledger. The SDK validates these accounts without creating,
funding or migrating them. Unsupported runtimes and missing accounts fail with
a local remedy. Relevant policy and runtime state are checked again before
signing, while shared balances may change; the transaction can still race later
changes. These routes are verified offline, not live-qualified. Recreate older
saved start/completion Plans.

**Starting** commits everything up front: the recipe, how many runs, the
inputs from your starbase storage, and the crew who will work it. Because a
process has no derivable address, starting one also names the fresh address
the new process will live at — you generate it, and its key signs once at
creation alongside your wallet:

```ts
import { generateKeyPairSigner } from '@solana/kit';
import type { PlanAuthorization } from '@aephia/atlas-kit/crafting/actions';
import { planStartCraftingProcess } from '@aephia/atlas-kit/crafting/actions';
import { executePlan } from '@aephia/atlas-kit/planning';

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

const processKey = await generateKeyPairSigner();
const recipe = await sage.recipes.byId(recipeId);
const plan = await planStartCraftingProcess(ctx, recipe, base, {
  authorization,
  craftingProcess: processKey.address,
  quantity: 1n,
  numCrew: 2,
});

const result = await executePlan(ctx, plan, {
  feePayer: authoritySigner,
  signers: [processKey],
});
```

Keep `processKey` until the Plan has executed: the fresh key signs the
creation alongside your wallet, so it goes into `signers` — execution fails
without it. Afterwards the key has no further role; the process is found
again by discovery, never by re-deriving that address.

The start planner checks the Recipe's fresh usage counters during planning and
again before signing. Each start consumes exactly one use, whatever its
quantity, so it needs `usageCountRaw` below `usageLimitRaw`; a stored limit of
`0` is never usable. Cancelling a process returns its use. Every Recipe on z.ink
PTR currently stores `9007199254740991` (2^53 − 1) as its limit. See the
[usage-limit evidence](https://github.com/Aephia/atlas-kit/blob/develop/docs/evidence/crafting-zero-usage-limit.md).

Concurrent Recipe usage by other players does not invalidate a Plan while at
least one use remains. Completion and cancellation do not
reserve new uses. All three planners still reject changes to the Recipe's other
encoded fields, including fees, status, ingredients, outputs and Hab policy.
Recreate previously prepared Plans to use this guard. The check is not a
reservation: another transaction can consume remaining uses after the pre-sign
read and before yours lands.

A Process started without a Crafting Hab omits the `craftingHab` property.
Completion and cancellation planners accept it without providing a Hab.
Completion has passed unsigned PTR simulation. Cancellation is currently
offline-tested only: when the no-Hab evidence was recorded, the planner still
refused finished Processes, and the only available PTR Process had already
finished. The game itself lets a finished Process be cancelled. See the [no-Hab evidence](https://github.com/Aephia/atlas-kit/blob/develop/docs/research/crafting-no-hab-806.md).

For a Process that references a Hab, completion requires that same Hab to exist
and be active. Cancellation passes the stored Hab without reading it, because
the game lets you cancel whatever state the Hab is in, including after it has
been deactivated, evicted or deconstructed.

**Completing** collects a process that has reached the end of its duration.
Derive the lifecycle state first — the planner refuses a process that is not
actually complete at the timestamp you give it:

```ts
import { deriveCraftingProcessState } from '@aephia/atlas-kit/crafting';
import type { PlanAuthorization } from '@aephia/atlas-kit/crafting/actions';
import { planCompleteCraftingProcess } from '@aephia/atlas-kit/crafting/actions';

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

const processes = await sage.craftingProcesses.byCharacter(characterAddress);
for (const process of processes) {
  if (deriveCraftingProcessState(process, nowUnixSeconds).kind !== 'complete')
    continue;
  const plan = await planCompleteCraftingProcess(ctx, process, {
    authorization,
    atUnixSeconds: nowUnixSeconds,
  });
  console.log(plan.summary);
  break;
}
```

**Cancelling** is the same shape as completing, without a timestamp:
`planCancelCraftingProcess(ctx, process, { authorization })` stops the run, and
what comes back — inputs, crew — follows the game's rules, decided at execution
rather than promised by the planner. The game has no timing rule for
cancellation, so a finished Process can still be cancelled; that returns its
inputs instead of producing outputs, so complete it instead if you want the
outputs.

Unrelated Starbase activity, such as shield changes, does not invalidate a
cancellation Plan. Preparation and execution verify the same canonical System,
Game, Starbase generation and active Starbase presence. Changes to the personal
Process, inventory, Character or Hab still require a fresh Plan. Recreate saved
cancellation Plans prepared before this safeguard was added; caller-added exact
state checks remain strict. These checks run before signing, so the game still
has the final say if state changes afterward.

The timestamp you pass to completion is used only for the local lifecycle
check; the game remains the authority on timing, capacity, and returns when the
Plan executes. Signing and executing works exactly as in [Warp &
subwarp](/guides/moving-a-fleet/#sign-and-execute-once).

## Gotchas

**The analyzed Food batch spent only its escrow.** At SAGE deployment slot
58,713,616, the rent-funded no-Hab batch of ten Food with one crew, 20 Biomass,
10,000 raw ATLAS escrowed and an existing Food output row required no additional
ATLAS, Profile lamports or cargo. Other recipes, quantities, crew counts, Hab
processes and new output rows were not analyzed. This is not a current spending
cap. See the [research and limits](https://github.com/Aephia/atlas-kit/blob/develop/docs/research/debit-bound-disposition.md).

**A finished process still exists.** Completion is a state, not a deletion. Read
the lifecycle rather than assuming presence means "in progress".

**Completing is not automatic.** A process that reaches its duration sits
complete until someone collects it — [running a process](#running-a-process)
covers planning that collection.

**Hab capacity is finite.** A hab holds a limited number of concurrent
processes, so "can this character craft" is not answerable from the recipe
alone — [crafting habs](/guides/crafting-habs/) covers reading the free
slots.

**Recipe inputs are raw amounts.** Like all quantities, they are `bigint` and
need the cargo definitions to render as names and decimals.

## Reference

- [`crafting`](/reference/crafting/) — every export in this entry
- [`crafting/actions`](/reference/crafting/actions/) — the lifecycle planners
- [Crafting habs](/guides/crafting-habs/) — the facility itself
- [`starbases`](/guides/starbases/) — where habs live
- [`cargo`](/guides/cargo/) — the inputs and outputs
