# Factions
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 player and NPC factions, then plan a Profile's first MUD, ONI, or Ustur enrollment.

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

Players enroll in **MUD, ONI or Ustur**. Read the faction that an existing
Profile joined with `getFactionForProfile`:

```ts
import { getFactionForProfile } from '@aephia/atlas-kit/factions';

const enrollment = await getFactionForProfile(ctx, profileAddress);
console.log(enrollment.faction, enrollment.factionId);
```

> **Runnable example — Read a Profile's faction.** Read the MUD, ONI or Ustur faction this Profile joined. Run it in the browser at https://develop.atlas-kit-docs.pages.dev/guides/factions/.

:::game[MUD, ONI, and Ustur are the three player factions]
Choosing a faction places a player on one of the three major sides in the
galaxy. The factions are not simple character classes with permanent bonuses.
What separates them in play is the territory and assets they control, the
resources and money available to them, and their current relationships with
the other factions. In future gameplay, a player's standing is intended to
record how that individual is doing with a faction and change as the player
contributes or takes actions.
:::

## Player enrollment and NPC factions

A Profile's enrollment and a Game's dynamic faction registry are different
things. Enrollment records the player's MUD/ONI/Ustur choice. The dynamic
registry describes factions used by other game systems, including NPCs such as
**Jorvik (ID 4)**. Inspecting an NPC faction does not make it a player-enrollment
option.

- `getFactionForProfile(ctx, profileAddress)` reads a player's enrollment.
- `getFaction(ctx, factionId)` reads a Game-scoped dynamic faction record.
- `getFleetCombatStatus(ctx, fleetAddress)` from the combat entry exposes
  `participant.kind`; an NPC participant retains its numeric `factionId`.
  This remains useful even when its dynamic registry record is unavailable.

Keep the NPC ID when inspecting Fleet state and combat activity. Do not infer
player membership from a Fleet's owner Profile, treat every faction ID as a
playable choice, or require a registry record merely to recognize an NPC.
NPC action planning remains subject to each action's documented authority and
preconditions; faction inspection grants no authority to act for that NPC.

## Optional enrollment

Use `maybeGetFactionForProfile(ctx, profileAddress)` when the Profile may not
have enrolled yet. It returns `undefined` only when its canonical enrollment is
absent. A present record exposes `profile`, `faction`, and `factionId`;
`faction` is `unaligned`, `mud`, `oni`, or `ustur`. Invalid owners, malformed
bytes, and wrong Profile relationships are errors, not missing enrollment.

Use `getFactionForProfile` when enrollment must already exist. After a confirmed
write, read it with `{ refresh: true }` and verify both the Profile and selected
faction. Reads never choose a faction or create an account.

## Dynamic registry, diplomacy and standing

The faction entry also covers dynamic faction identity, economics, diplomacy,
standing, treasuries, asset ownership and King-system ownership. These reads do
not replace player enrollment. `maybeGetFactionStanding` returns `undefined`
when no standing is recorded; `getFaction` asserts that a registry record exists
and reports `ACCOUNT_NOT_FOUND` if it does not.

**Recorded example — z.ink PTR, 2026-08-30.** The example below remains runnable
against the documentation's isolated historical faction scenario, using dynamic
faction ID 4. Its registry record was removed on 2026-09-09. It is not a current
PTR availability promise or a player-enrollment example. Live documentation
checks still execute this example as archived replay, separately from current
player reads. For your own deployment, provide an existing dynamic faction ID.

```ts
import {
  getFaction,
  getFactionRelations,
  maybeGetFactionStanding,
} from '@aephia/atlas-kit/factions';

const faction = await getFaction(ctx, factionId);
const relations = await getFactionRelations(ctx);
const standing = await maybeGetFactionStanding(ctx, profileAddress, factionId);
console.log(faction.factionId, relations, standing);
```

## First enrollment

`planChooseFaction` from `@aephia/atlas-kit/factions/actions` plans first
enrollment into `mud`, `oni`, or `ustur`. It rejects an existing enrollment
account, including an existing `unaligned` account. Reuse a valid enrollment;
faction switching and repair of existing accounts are outside this planner.

Illustrative: supply a configured context, an existing Profile address, and
its authorized wallet address. Use the actual Profile key index (shown as zero
here). The result is an inert Plan; this snippet does not sign or submit.

```ts
import { planChooseFaction } from '@aephia/atlas-kit/factions/actions';
import type { PlanAuthorization } from '@aephia/atlas-kit/factions/actions';

const authorization: PlanAuthorization = {
  profile: profileAddress,
  authority: walletAddress,
  keyIndex: 0,
};

const plan = await planChooseFaction(ctx, {
  faction: 'ustur',
  authorization,
});
console.log(plan.describe());
```

The Profile authority signs this instruction. There is no separate payer: the
program funds the new account from the **Profile account's own on-chain
balance**, preserving the Profile's rent reserve. A funded authority wallet
does not replace that balance. The planner validates the current Rent account
and observed Profile funding; it does not transfer funds or top up the Profile.
Transaction fees remain separate and execution requires an explicit fee payer.

Inspect the Plan's signers, writable accounts, funding description, and
preconditions, then simulate before requesting signatures. The account must
remain absent and validated prerequisite state must remain unchanged. Profile
permissions and certificate authorization ultimately remain chain-authoritative;
local validation is not proof that every delegated certificate path is accepted.
When delegation requires a certificate, its account must exist before the planner
returns a Plan. A missing account produces a remedy to select an existing
certificate or a matching direct Profile key. An unused certificate supplied
with valid direct authority remains ignored. Profile authority, Rent pricing and
certificate guards identify their respective roles and explain how to replan.

After confirmed execution, load the enrollment again with a refresh read.
A simulation or submitted signature alone does not establish enrollment. The
[PTR evidence](https://github.com/Aephia/atlas-kit/blob/develop/docs/evidence/faction-enrollment-ptr.md)
uses unsigned simulations, without live account creation or token movement.

## Region presentation metadata

**Classification: off-chain/editor-authored color is editor-local; live Region
presentation is chain-derived from owner and safety.** The official map
editor's inspected **2026-09-13**
[`createNewRegion` producer](https://ses.staratlas.com/SAGE%20Map%20Editor/js/ui-handlers.js?v=c4-membership-3)
reads a color picker and stores its value as `color` in `regionDefinitions`.
The [export code](https://ses.staratlas.com/SAGE%20Map%20Editor/js/file-operations.js)
serializes those definitions alongside the systems in `mapData`. This directly
establishes an editor source, not a canonical C4 palette or a chain-addressed
payload. The reported 69 Regions in the dated 2026-06-09 export are consumer
evidence, not a schema or a verified deployment mapping.

The checked **`@staratlas/dev-sage@0.52.0`** generated `Region` type and
`RegionTracker` account codecs contain ownership, safety/risk, XP, geometry,
connections, systems and research requirements, but no color or Region content
reference. `Region` is nested in `RegionTracker`, not a separately addressable
account. **`Faction.contentUriHash` belongs to `FactionAccount`**, whose generated
comment describes an off-chain manifest hash for cosmetics, blueprints and
dialogue; neither that hash nor its `homeRegion` field proves a Region palette
relationship. The checked **`@staratlas/dev-profile-faction@0.45.7`** `Faction`
type is an alignment enum, not another metadata account.

The deployed C4 client inspected **2026-09-13** uses `drawRegions` and
`getFactionColorFromOwner` to choose Region fill/stroke color from chain
`Region.owner`, using a fixed faction palette. Safety/risk status governs baseline
opacity; hover, selection and zoom add client-side styling. It draws borders
from the chain Region geometry, not the editor's authored palette.

For live-game-style presentation, use **`TerritoryRegionSummary.owner`** and
**`TerritoryRegionSummary.safety`**, which already project the governing chain
fields. Also use `TerritoryRegionSummary.risk` when matching the client's
low-risk styling: low-risk Regions, or safe aligned Regions, receive that style.
Choose presentation styles in your application; do not join editor IDs
to chain Region IDs to reproduce the live renderer. Editor timestamp string IDs
and chain u16 IDs are different namespaces. Custom editor palettes remain
application-owned authoring data, not SDK-owned canonical Region metadata.

There is **no proven Region-scoped content reference** supplying authored color.
The checked schema **does not prove universal on-chain absence**; it does expose
the owner/safety inputs used by the inspected live renderer. No separate implementation issue is needed:
those inputs are already exposed, and no new stored color or canonical payload
was discovered. `TerritoryRegionSummary` gains no color/content fields, and the
SDK does not fetch or vendor editor exports.

The dated [provenance record](https://github.com/Aephia/atlas-kit/blob/develop/docs/research/region-presentation-provenance.md)
contains the deployed bundle URL, SHA-256, renderer anchors, editor-source hashes
and codec boundaries. See also the [#436 chain-audit correction](https://github.com/Aephia/atlas-kit/blob/develop/docs/research/world-metadata-chain-audit.md):
editor-only evidence previously missed chain-derived visual semantics. Star and
Celestial Body findings remain separate from this Region conclusion.

## Gotchas

**Keep player alignment and NPC identity separate.** Enrollment ID 0 means
`unaligned`. For a Fleet, inspect its explicit participant kind and retain an
NPC participant’s numeric faction ID; do not infer NPC identity from a player
alignment value or its owner Profile.

**Standing is per profile, not per character.** It belongs to the wider Star
Atlas identity rather than to the SAGE character.

**Relations are between factions, not players.** Diplomacy describes the
factions' stances toward each other.

## Reference

- [`factions`](/reference/factions/) — every export in this entry
- [`factions/actions`](/reference/factions/actions/) — enrollment planner
- [`loyalty`](/guides/loyalty/) — contributing to a faction over an epoch
- [`identity`](/guides/identity/) — the profile standing attaches to
