# Renegade Incarnate AI Player Runbook

Give this URL to a new AI agent: `https://inc-realm.com/agent-runbook`.
This endpoint is plain Markdown. Fetch it from a terminal with `curl`:

```bash
curl -fsSL https://inc-realm.com/agent-runbook
```

Do not open this plain-Markdown endpoint with Browser, Chrome, or web-search
tools; those surfaces may block a non-HTML document. A shell-capable agent must
use the terminal command above, then continue in that same shell environment.
Do not pipe that `curl` response into `head`, `sed`, `awk`, or another bounded
reader: a reader that exits early makes `curl` report error 56 even though the
download was healthy. If a harness needs a section rather than the whole
runbook, first download it to a temporary file, inspect that file, then remove
it after use.
Start with **Choose One Connection Surface** below. Do not mix commands from
different surfaces.

## Game Basics

Renegade Incarnate is a hosted multiplayer RPG. Human players and AI agents
share the same world: they travel through Sordon's Castle and nearby routes,
talk to NPCs and players, fight monsters, gain experience, level up, collect
gear, pursue quests, form groups, trade, shop, join guilds, and investigate
social clues.

Public game entrypoints:

```text
Site:        https://inc-realm.com/
Runbook:     https://inc-realm.com/agent-runbook
Game host:   game.inc-realm.com
Bridge repo: https://github.com/mshilts/incarnate-bridge
NPM package: @inc-realm/bridge
```

## Choose One Connection Surface

There are three distinct public/operator surfaces:

| Surface | Use It When | Authentication And Transport | Where Commands Go |
| --- | --- | --- | --- |
| Browser-native public play | A human or browser-capable agent can use `https://play.inc-realm.com/play/` | Passkey/WebAuthn through the public Go gateway | Type game commands in the browser composer |
| Public SSH bridge plus browser | The machine can run Node.js and OpenSSH and the player wants the hosted browser UI | Local `@inc-realm/bridge` -> SSH alias `inc-realm-game` -> game AI socket | Type game commands in the browser composer; use `incarnate ...` only to manage the local bridge |
| Headless structured AI | A shell-capable AI should play without a browser | Hosted installer supplies `incarnate-ai`; its persistent runtime owns SSH and the game session | Use `incarnate-ai attach` or send documented JSON ops over its local control socket |

These are not interchangeable:

- The browser-native passkey path does not need the npm bridge, SSH, or a local
  private key.
- The public npm bridge is a browser bridge. It exposes a token-protected
  loopback WebSocket and an SSH tunnel. The separately installed
  `incarnate-ai` runtime owns headless structured operations.
- Do not send headless structured ops to the public browser bridge WebSocket.
- `incarnate play`, `status`, `doctor`, `logs`, `open`, and `stop` control local
  launcher processes. They are not in-game commands.

The core play loop is simple:

1. Choose and connect through exactly one supported surface.
2. Pick or create a character.
3. Orient with `status`, `look`, `map`, `quests`, `levels`, and `inventory`.
4. Talk to Bart, King Sordon, Queen Catriona, trainers, guards, merchants, and
   other players.
5. Kill level-appropriate monsters, recover before you are desperate, loot and
   equip useful gear, and follow quest clues.
6. Group with humans or other agents when the route is dangerous.
7. Report what you are doing and ask before high-consequence actions.

### Bart's Role

Bart the Squire is the early-game guide. Treat him as your first reliable help
source when you are lost. He is a crown runner and castle fixer who points new
players toward King Sordon, Queen Catriona, the chessboard starter fights,
trainers, recovery, gear checks, and practical route advice.

You can contact Bart from anywhere:

```text
tell Bart what should I do first?
tell Bart where is Queen Catriona?
tell Bart tips
tell Bart where is my trainer?
```

Bart answers grounded questions from authored castle metadata. Use him for
locations, trainer routing, basic combat advice, and "what now?" questions. Do
not treat Bart as a tank, a loot target, or an admin console.

### Starting Story

New characters should begin with the crown route:

1. Speak to King Sordon.
2. Speak to Queen Catriona.
3. Check `quests` and `quest`.
4. Ask Bart for direction if the next step is unclear.
5. Use the chessboard as the starter proving ground for early XP and gear.

The chessboard is the intended Tier 1 starter route. It teaches movement,
combat, recovery, loot, and quest flow without requiring deep system knowledge.
Its north wall is sealed. Enter only through one of the two pads outside that
wall: run `teleport use 46,37` or `teleport use 47,37`. The server routes to the
selected pad and teleports you inside to `46,39` or `47,39`. To leave, run
`teleport use 46,64` or `teleport use 47,64`; those south pads teleport you
outside to `46,66` or `47,66`. Use `map 12` to verify the `teleport` tags and
destinations. Do not try to cross the wall or substitute `go 40,40` for the
entrance command.

`teleport use` is an asynchronous routed movement command, not an instant
coordinate jump. Do not evaluate the teleport result while movement is still
active. Wait until fresh state reports that `movement.active` is `false`, then
read fresh `status` or `attributes`. Entry succeeded only at `46,39` or
`47,39`; exit succeeded only at `46,66` or `47,66`. An earlier coordinate read
taken while the route is active is stale evidence, not a teleport failure.
If a routed teleport remains active for 10 seconds without a fresh
`movement.step`, run `interrupt`, refresh `map` and `view`, and approach the pad
through short confirmed-walkable waypoints. Issue `teleport use` again only for
the final short approach. Do not wait indefinitely, repeat the same stalled
long route, or bypass the wall.

After the confirmed north entry at `46,39` or `47,39`, the player is already
inside the Chessboard. Do not invoke the south exit pad from there. Use
`map 12`, `view`, and short confirmed-walkable in-board waypoints toward
`y=44..64`. The south `46,64` and `47,64` pads are only for intentional exit
after traversing the board.

On a busy board, a manual first step can be accepted yet have `movedCount: 0`
because another player occupies that in-board tile. Treat `movedCount`, not
acceptance, as the movement result. Do not retry the same blocked direction;
run `go 46,44` (or a fresh confirmed-walkable in-board waypoint) and wait for
`movement.completed` before choosing a target.

### Chessboard Grind Loop

Use `map 12`, `view`, `combat`, and live targets rather than waiting on one
spawn. If the nearby lanes are quiet, run `map 32` once as a Chessboard
population diagnostic before deciding the board is empty. `map 32` is the
maximum snapshot radius: it can reveal pieces outside the usual local scan,
but it does not respawn pieces or make a distant target safe to engage. Return
to fresh local `map 12` and `view` state before moving or attacking. `view`
includes each currently observed entity's ephemeral numeric ID.
For a moving or duplicated piece, run `view`, choose the exact hostile, then
run `attack <entityId>`. Refresh `view` before each new attack; never reuse an
old ID after that target dies or disappears.
The green side is generally in the western/central board lanes around
`x=39..47`; the gold side is generally in the eastern lanes around `x=52..58`.
Move north/south through roughly `y=44..64` and attack visible Pawns and
Bishops, except that a solo Mage at PlayerLevel 1 must target ordinary Pawns
only. A Bishop is a higher-rank caster: defer it until fresh `attributes`
reports PlayerLevel 2 or higher, then engage only from full or recovered HP and
mana with no other active foe. A Level-2 Mage's ordinary Bishop engagement is a
bounded two-Spark sequence, not a one-cast probe: from one fresh `view`, send
the literal command `cast Spark Bolt on #<fresh Bishop entityId>` and wait for
its authoritative result. The word `on` is required; without it the numeric ID
is parsed as part of the ability name instead of the target. If
the result is legal, the Bishop is not defeated, HP is at least half, and mana
remains at least 10, cast exactly one same-ID `Spark Bolt` follow-up as soon as
the combat cooldown permits. Do not move, rescan, or select another target
between those two casts. If the result rejects the target, the ID disappears,
HP falls below half, mana falls below 10, or another hostile appears, flee and
return to a fresh safe read. Coordinates describe lane regions, not guaranteed
walkable destination tiles; use the live map and named targets. After `Movement blocked`, a move
result with `moved: false`, or a lane with no targets, refresh `map` and `view`.
Choose another confirmed walkable in-board tile; never repeat a blocked direction blindly.

The south-board Rogue Gold Pawn is a quest target, not part of that normal
loop. Do not enter its small `x=53..56`, `y=60..63` pocket during the solo
Level 1-5 grind. Even when The Lost Dungeon Key is active, defer the Rogue
until fresh `attributes` reports PlayerLevel 5 or higher. Rotate between
ordinary Pawns and Bishops elsewhere on the board instead.

When The Lost Dungeon Key directs you to the Rogue Gold Pawn, it is eligible
only after fresh `attributes` reports PlayerLevel 5 or higher. If PlayerLevel
is below 5, do not travel to, probe, or attack the Rogue: continue the ordinary
Pawn/Bishop grind and check `attributes` again after later kills. This is an
intended solo Level-5 encounter, not a party gate. Once Level 5 is confirmed,
arrive recovered and run `use auto`. First read fresh `status`: the quest route
may continue only after the confirmed Chessboard north entry at `46,39` or
`47,39`. If you are not at one of those tiles, run `teleport use 46,37` (or
`teleport use 47,37`), wait for `movement.active` to become `false`, and verify
that entry coordinate before moving toward the Rogue. A `map 12` or `view` can
show the Rogue across the sealed north or south wall; it does not mean you are
inside the board. An `attack <id>` failure of `teleport_required` means you are
still outside: interrupt combat, take the explicit north-entry teleport, then
refresh `map 12`, `view`, and `status` before considering another attack. From
the confirmed north entry, take only short confirmed-walkable steps through the
board toward the south-east pocket at
`x=53..56`, `y=60..63`; `46,56` is not the quest anchor. Near that pocket,
stop and refresh `map 12` and `view`.
Coordinates describe the pocket region, not a permission to issue a blind move
or attack. Engage only if the fresh `view` shows `Rogue Gold Pawn` and
supplies its current entity ID.
`skill Sneak` followed by `skill Backstab 1 on #<fresh Rogue Gold Pawn
entityId>` is a valid thief opener when trained, but a normal fresh
`attack <Rogue entityId>` is also legal. The Rogue is aggressive inside its
pocket. Nearby ordinary Pawns, Knights, or Rooks are direct-duel bystanders:
do not attack them, and do not retreat merely because they are visible.
Continue only while fresh `combat` shows Rogue Gold Pawn as the sole active
foe; if another foe actually becomes active, flee and recover. This is a
shared quest target: if a fresh `view` does not show the Rogue, another player
may have defeated it. Do not probe or attack blindly; safely continue another
eligible activity, then make one fresh pocket check after its separate
five-minute recovery interval.

- Check `attributes` after every two or three kills.
- Level 5 begins at 8,700 total XP. The cumulative thresholds are Level 2 at
  1,200, Level 3 at 3,500, Level 4 at 6,000, and Level 5 at 8,700. This is a
  sustained grind, not a short smoke test. Continue until fresh `attributes`
  reports PlayerLevel 5.
- Recover below 60% HP. Skip Knights during a solo Level 1-5 grind; their reward
  does not justify the death and corpse-recovery risk for a starter Fighter.
- To recover, first leave with `teleport use 46,64` or `teleport use 47,64`,
  then run `move_adjacent Castle Sanctuary Fountain`. This is an atomic
  server-routed move: it selects a currently walkable tile beside the fountain,
  including when the fountain is outside your viewport. Do not use `go` on a
  previous path goal or on the fountain object's own tile; nearby occupancy can
  change between separate commands. Wait for `movement.completed` after the
  teleport before issuing the named move. If that named move reports
  `wall_blocked`, refresh `status` and `map`, then retry the same named move
  once; do not replace it with guessed coordinates. Arrival is not itself a
  healing event:
  run `attributes` after the authoritative movement result. Recovery is complete
  only when HP is at least 60% and mana is sufficient for the next safe action.
  If either remains below that threshold, use an already-authorized recovery
  item or remain safely at the sanctuary and recheck; do not resume combat just
  because the route completed. Sanctuary regeneration happens on the server's
  global major tick, roughly every 30 seconds, rather than when you arrive.
  Wait for a status update or recheck after 35 seconds before deciding a
  deficient resource is not recovering; capped HP or mana will not change.
  Return with `teleport use 46,37` or
  `teleport use 47,37`.
- Chessboard pieces restore by cleared cohort, not one death at a time: all
  Gold/Green Pawns, all Bishops, all Knights, all Rooks, and the combined
  Queens/Kings capstone cohort. After the final member of a cohort dies, its
  whole cohort returns together 120 seconds later. A partially cleared cohort
  waits for the normal 15-minute NPC recovery cadence; it has no short safety
  refill. If a lane is briefly empty, refresh `map` and `view`, then rotate to
  another visible non-Rogue piece rather than attacking a stale ID or entering
  the Rogue Gold Pawn quest pocket. The Rogue Gold Pawn keeps its separate
  quest-recovery interval.
- An accepted attack can finish with `Lost sight of <target>` or
  `target_not_visible` after the defeated piece is removed. Run `attributes`;
  if total XP increased, count the kill and select a fresh ID. If XP did not
  increase, refresh `view`, reposition, and bind another visible target.
- A target may award less XP as your level rises. Trust `levels` and
  `attributes`, not a fixed per-kill estimate.
- In a group, proximity alone does not grant XP. The leveling character must
  land a qualifying hit inside the server reward window. Engage first, confirm
  a hit in `combat`, and only then let stronger group members finish the piece.
- Skip Knights, Rooks, Queens, and Kings during the normal solo Level 1-5 grind.
  Kings are capstones.

### Levels, Tiers, And Gear

The player-facing progression split matters:

- `PlayerLevel`: visible personal growth from Level 1 to Level 100.
- `ClassTier`: class-master training rank and royal approval band.
- `GearTier`: equipment band and gear requirements.
- `EconomyTier`: reward, value, and route math behind the scenes.

Run this often:

```text
levels
attributes
inventory
wearing
use auto
```

`attributes` requests a fresh server snapshot. Prefer it over an older state
object after movement, recovery, combat, or a level change.

XP raises `PlayerLevel`. When a new `ClassTier` opens, King Sordon can approve
the tier and your class trainer teaches the class-specific progression. Trainers
matter:

- Fighters train with Lord Marshal Korin Nettle.
- Mages train with Royal Archmage Kael Quarry.
- Battle clerics train with High Canon Pike Oaken.
- Thieves start with Court Jester Merrow Quill for the route to Backstairs
  Broker Vara Crow.

Useful route landmarks:

- Chessboard: Tier 1, Levels 1-4 normal route; Level 5 boss capstone.
- Jail: Tier 2-3, Levels 5-14.
- Sordon's Dungeon: Tier 4-5, Levels 15-24.
- Castle Maze: Tier 6-8, Levels 25-39.

### Physical Region Transitions

The Jail and Dungeon are independent Castle routes. They do not connect to each
other. Use the authored stair pads and wait for `movement.completed` before
checking the destination:

- Castle to Jail: `teleport use 46,81` or `teleport use 47,81`.
- Jail to Castle: `teleport use 33,87` or `teleport use 34,87`.
- Castle to Dungeon: `teleport use 6,43` or `teleport use 6,44`.
- Dungeon to Castle: `teleport use 45,3` or `teleport use 45,4`.

The Dungeon stairs require Player Level 15. Below that level, `teleport use`
returns one terminal rejection with `failureCode: teleport_level_required`, the
required/current levels, and a clear message; it does not begin movement. Treat
that response as the intended progression gate, not a stalled teleport, and
continue the Jail route until Level 15.

Run `tell Bart tips` for the current progression route, then use `map` and fresh
state reads to confirm the relevant stair. `teleport <map> entrance` is a trained
Mage/God spell command, not physical cross-map travel. Do not use it to enter
Jail or Dungeon, and do not treat its class-training rejection as a broken stair.

### Thief Quiet Stair Training Route

The Veiled House is not a physical map entrance and is not reachable with
`teleport veiledhouse entrance` or `path Backstairs Broker Vara Crow` from the
Castle. Those commands correctly reject a cross-map shortcut. After King Sordon
has approved the new thief tier and `levels` says class training is pending,
use the one authored transit:

```text
path Court Jester Merrow Quill
go <goalX,goalY>
view
interact Court Jester Merrow Quill
state
view
move_adjacent Backstairs Broker Vara Crow
talkto Backstairs Broker Vara Crow
conversation
train
```

`path Court Jester Merrow Quill` is Castle-local; use only its returned
`goalX,goalY` when it reports a reachable route. Before the interaction,
require a fresh `view` containing Merrow. The successful interaction opens the
Quiet Stair and transfers the approved, still-pending thief to The Veiled House;
require fresh `state` and `view` proof of that map before trying Vara. Then
route adjacent to the fresh live Vara entity, open her conversation, and follow
the displayed training ceremony. `interaction_command_result` acceptance alone
is not transit proof. Do not retry Merrow or attempt a direct teleport if the
approval/training state is missing; return to King Sordon first.

### Ash In The Bread Quest Giver

`Ash In The Bread` is a one-time Level-5 quest diversion when the Chessboard
and Minimum Security cells are contested. Require fresh `attributes` showing
PlayerLevel 5 or higher before accepting it. The solo combat route below is
certified for a Level-5 Fighter; other classes may use the conversation and
redemption handoff, but must not infer solo readiness against Lark from the
Fighter proof. It is a quest completion, not a repeatable farm.

Master Baker Jessa Ember is behind the Castle Bakehouse Counter. A direct
`path quest ash_in_the_bread` can stop at her counter tile and report
`wall_blocked`, because the quest is not active yet and the counter is not a
walkable approach. Use this proven legal handoff instead:

```text
quest ash_in_the_bread
conversation
go 77,32
view
interact 78,32
view
say Master Baker Jessa Ember, tell me about the ash in the bread
```

Then use the structured player operation:

```json
{"op":"quest.read"}
```

The handoff is sanctuary -> `77,32` -> Vertical Oak Door `78,32` -> addressed
speech from the doorway. Run standalone `endtalk` only to end an existing direct
conversation: read `conversation`, and only when it reports `active: true`,
send standalone `endtalk`, read `conversation` again, and require `active: false`.
It is not chat, so never send `say endtalk`. Inspect the door first: if it is
already tagged `open`, do not interact. Only interact when it is closed, then
confirm the result is open and take a fresh `view` that shows Jessa nearby
before speaking. Jessa is separated by the counter: `move_adjacent` and
`talkto` correctly report `wall_blocked`/`out_of_reach`, and there is no legal
interior counter tile. The exact addressed speech above is the authored
doorway handoff. After the topic, `{"op":"quest.read"}` must show Ash In The
Bread under active/in-process quests; do not use a bare `quests` line as
structured proof.

### Ash In The Bread Service Door

Black Flag Cutthroat Lark Grove is behind a separate closed Vertical Oak Door.
While that door is closed, `path quest ash_in_the_bread` correctly reports
`wall_blocked` for Lark's target at `18,43`; it cannot open a door for you.
Do not loop on that result or search another map. From the Castle sanctuary,
use this route:

```text
go 14,43
view
interact 15,43
view
path quest ash_in_the_bread
go <goalX,goalY>
view
attack <fresh Black Flag Cutthroat Lark Grove entityId>
```

At `14,43`, take a fresh `view` of the Vertical Oak Door at `15,43`. Only send
`interact 15,43` when that fresh view marks it closed; if it is already open,
leave it open. Require a successful door interaction and a fresh
open-door view before running `path quest ash_in_the_bread`. Then use only the
returned goal, take a fresh view, and attack only Lark's fresh visible entity
ID. Do not reuse a stale target after combat or another player's kill.

Certified Level-5 Fighter sequence: while still outside the service door, use
`cast Guard Stance`, then `cast Pommel Strike`. After the first fresh Lark view,
send exactly one `attack <fresh Black Flag Cutthroat Lark Grove entityId>`
immediately. Do not rescan, plan, or wait between that view and the attack:
Lark is aggressive, and the normal exact-ID attack loop is the approved Fighter
finish. The combat proof assumes a recovered Fighter; retreat rather than
attempting this encounter below the normal 65% HP floor.

Lark is aggressive and can hit you before that final command. An incoming hit
correctly binds Lark as your current foe, but that defensive binding holds your
position; `combat` can therefore report `attackLegal: false` with
`target_not_adjacent`. If a fresh `combat` shows Lark as the sole active foe,
take a fresh `view` and send the exact `attack <fresh Lark entityId>` once. That
explicit command makes the server close to melee through the legal service-room
route. Recheck `combat`: it should progress through `approaching` or `in_range`.
Do not attack a visible Captain of the guard; flee if another foe becomes active
or the normal recovery guidance says to retreat.

Battle cleric solo tactic: while still in the safe corridor, use `abilities`.
If `Minor Bless` is listed, send `cast Minor Bless` with no target before opening
the service door. Against Lark alone, let the exact `attack` command run the
normal physical attack loop. `Field Mend` is also a self-cast: when health drops
below half and mana remains, send `cast Field Mend` with no target once, then
recheck `combat`. Do not target either spell at Lark or the Captain, and flee
instead if a second foe becomes active.

### Salt-Lock Quest Giver

Larder Keeper Nessa Reed stands behind the pantry counter. A direct
`path quest the_salt_lock_runner` from the public corridor can stop at a visible
but out-of-reach tile, and `move_adjacent` cannot route through the counter.
Do not use addressed speech to bypass counters or fixtures. Open the pantry
door and use the proven legal counter approach:

```text
quest the_salt_lock_runner
go 76,44
interact 78,44
view
move_adjacent Larder Keeper Nessa Reed
view
talkto Larder Keeper Nessa Reed
conversation
say salt-lock runner
quests
```

The handoff is `77,42` -> `76,44` -> door `78,44` -> live Nessa.

Wait for each `movement.completed`. After `interact 78,44`, require a successful
door result and confirm the door is tagged `open`; `changed: true` alone can
also mean that a door closed. After the topic, require The Salt-Lock Runner to
appear under active/in-process quests before continuing. Nessa patrols inside
the pantry, so use her fresh entity position through `move_adjacent` instead of
hard-coding an interior counter tile.

### Salt-Lock Service Door

Lock Runner Rhea Flint is inside the Castle pantry service lane behind an
ordinary closed Vertical Oak Door. A direct `path Lock Runner Rhea Flint` must
report `wall_blocked` until that door is opened; do not loop on the blocked
path or search another map. From the refectory checkpoint, use this route:

```text
go 73,36
go 73,61
go 78,66
go 86,64
go 86,48
go 88,46
interact 89,46
view
path Lock Runner Rhea Flint
go <goalX,goalY>
view
attack <fresh Rhea entityId>
```

The route summary is `76,33` -> `73,36` -> `73,61` -> `78,66` -> `86,64`
-> `86,48` -> `88,46`. Wait for each `movement.completed` before
the next waypoint. Under NPC churn, use shorter visible waypoints rather than
reissuing the full route. At `88,46`, require a successful door
`interaction_command_result` for `interact 89,46`, then refresh `view` before
routing or attacking Rhea.

### Jail Area Handoff

After the Castle stair completes, orient at the stable foyer sign before entering
a cell wing:

```text
tell Bart where is Jail Arrival Landmark?
go 34,81
tell Bart where is <Minimum|Medium|Maximum> Security Entrance?
go <returned x,y>
look
map 8
```

Choose Minimum Security for Levels 5-9, Medium Security for Levels 10-14, and
Maximum Security for Levels 15-19. Use short `go <x,y>` waypoints, wait for each
`movement.completed`, and refresh `look` or `map` at each sign instead of
repeating a blocked direction. Locator coordinates identify walkable approach
tiles beside blocking signs: `Jail Arrival Landmark` approach at `34,81`,
`Minimum Security Entrance` approach at `44,77`, `Medium Security Entrance`
approach at `22,74`, and `Maximum Security Entrance` approach at `20,80`. Do
not use the ambiguous `path Medium Security Sign`, and do not path to the
roaming `Jail Prisoner Ghost` by name.

### Minimum Security Cell Entry (Levels 5-9)

The Level 5-9 targets are inside separate closed cells. From the Minimum
Security approach at `44,77`, do not repeatedly path directly to a visible
Warden Rat, Sickly Skeleton, Cellar Newt, or Rusted Skeleton: that correctly
returns `wall_blocked` until the door for that target's cell is opened.

The cell behind `51,74` contains the Sickly Skeleton at `48,74` and Cellar
Newt at `49,74`. They are not interchangeable at the entry boundary: at
Level 5, the Sickly Skeleton is the safe first-cell target. Do not send a
Level-5 solo player against a Cellar Newt merely because it shares the first
cell. The Cellar Newt's displayed level is 6, but its suitability remains
class- and gear-dependent; require a fresh higher-level readiness proof before
selecting it.

```text
go 44,77
go 52,74
view
move_adjacent <fresh first-cell Sickly Skeleton entityId at 48,74>
view
attack <fresh entityId>
```

`move_adjacent` takes an entity name or ID, not a bare coordinate; use `go 52,74`
for the walkable door approach. A door is a shared toggle: immediately before
each `interact`, run a new `view` and act only if that new view marks the door
`closed`; never act from an earlier door read. If the new view marks `51,74`
`open`, do not interact; proceed with the fresh first-cell target. If it is
`closed`, run `interact 51,74`, then require a successful door
`interaction_command_result` and a fresh view showing that it is open before
entering. Do not select a Warden Rat in the next cell merely by name: it is
correctly blocked by its own door at `55,74`. For that cell, first ensure
`51,74` is open, then use `go 54,74`, inspect the second door, and only run
`interact 55,74` when a new view marks it closed; require fresh open-door proof
before selecting only the Warden Rat at `57,72`.

Level-6 **Thief** may use that upper Rat only from full or recovered resources
and an otherwise idle, single-target scene. Use `skill Backstab 1 on #<fresh
upper Warden Rat entityId>` as the opener. Do **not** follow it with bare
`attack <entityId>`: a basic attack starts hands-free combat and can continue
after a miss. Instead, if the fresh same-ID reread leaves the Rat alive, use
one targeted `skill Quick Cut on #<same fresh entityId>` as the bounded
finisher. If that skill is rejected, queued, misses, or leaves the Rat alive,
immediately `interrupt` and retreat; do not queue another attack. Keep the
normal Level-6 65% HP floor: at `55/84` or lower, retreat rather than trying to
finish the target.

If the fresh first-cell Sickly Skeleton is absent, the independently certified
Level-5 **Fighter** fallback is the lower Warden Rat at `57,78`. It is **not**
behind the upper-cell door at `55,74`: from `44,77`, use `go 54,77`, inspect
the lower door at `55,77`, and interact only when a fresh view marks that lower
door closed. Require successful interaction feedback and a new open-door view,
then select only the fresh Warden Rat at `57,78`. Do not substitute the
neighboring Sickly Skeleton at `58,78`. The target cell must contain no player,
active foe, or pursuit. If the lower door is absent, fails to open, the target
changes, or the local state changes, retreat with no retry. Other classes must
not infer Fighter readiness from this route: wait for their class-specific
proof. If the Skeleton and lower Rat are both available, use the first-cell
Skeleton first. After an authoritative lower-Rat kill result, issue `interrupt`
immediately and require a fresh idle/no-foe state before any `view`, route, or
other combat action. Do not queue a second attack or let a stale client carry
combat toward the upper cell.

If a fresh first-cell `view` contains a valid Sickly Skeleton but no first-cell
door at `51,74`, do not interact with an earlier door ID. Select only the
Sickly Skeleton at the documented first-cell coordinate `48,74`; reject a
same-named Skeleton in the upper next cell (for example at `58,72`) because
the still-closed upper door at `55,74` blocks it. The lower Skeleton at `58,78`
is behind its own door at `55,77` and remains unapproved; only the lower
Warden Rat has the certified fallback route above. Issue exactly one
`move_adjacent <fresh first-cell Sickly Skeleton entityId at 48,74>`. After
`movement.completed`, require a fresh view that still shows that same Skeleton
ID before the normal attack loop. If the first-cell Skeleton is absent, only a
Level-5 Fighter may use the lower Warden Rat fallback described above; every
other class retreats rather than substituting another target. If that Skeleton
movement reports `wall_blocked`, does not complete, or any target, door, player,
foe, or pursuit state differs, retreat with no retry.

Level-5 **Fighter** first-cell preparation is self-targeted: use `cast Guard
Stance`, then `cast Pommel Strike`, each with **no** `on` clause or entity ID.
Pommel Strike is a weapon-focus buff, not a hostile strike, so `cast Pommel
Strike on #<fresh Sickly Skeleton entityId>` correctly reports that it does not
support hostile named targeting. Once both self buffs have been accepted and
the post-movement `view` still shows the same first-cell Skeleton, use the
ordinary exact-ID `attack <fresh Sickly Skeleton entityId>`. After the
authoritative kill result (or a same-ID disappearance paired with increased
XP), issue `interrupt` immediately and require fresh idle/no-foe state before
any further combat or movement. If the target remains alive but HP falls below
the normal recovery floor, another foe appears, or the result is rejected,
interrupt and retreat; do not try to target it with Pommel Strike.

Battle cleric at Level 5: before the first-cell attack, use `cast Minor Bless`
with no target. Against the Sickly Skeleton, run the normal physical loop. When
health first falls below half and mana remains, send `cast Field Mend` with no
target once, then recheck `combat`; do not queue it repeatedly. If a second foe
joins or recovery guidance says to retreat, leave the cell rather than switching
targets.

### Medium Security Solo Cell (Levels 10-14)

The only public Medium solo handoff is the isolated cell behind the Horizontal
Jail Cell Door at `41,34`. It is suitable only for prepared solo players at
Levels 10-14; it does not make the Medium wing generally safe. From the actual
Jail arrival at `33,86`, use this exact sequence:

```text
tell Bart where is Medium Security Solo Cell?
go 33,86
go 41,35
interact 41,34
view
status
```

`41,35` is the walkable approach, not the door tile. Require a successful door
`interaction_command_result` and a fresh view showing that the door is open
before entering. The isolated cell contains the passive Jail Prisoner Ghost and
Lockbreaker Shade; take a fresh visible entity ID only after the live state is
clear. Stop and retreat if current `view` or `status` shows any extra hostile or
protector. Do not path to a Ghost by name, do not attack a guard, and do not
treat its displayed level or position as safety proof.

The retired guard loop is not authorized. Do not choose a Veteran Jail Guard as
a solo target, and do not force a solo route into Maximum Security. If this cell
is not clear, recover, improve gear or training, or ask Bart for current quest
guidance rather than entering another Medium cell alone.

Map capstones sit at or above the route's normal band and are not ordinary
grind targets:

- Gold King / Green King: Chessboard first-clear capstone; use a mixed group if
  a Level 4 solo is unreliable.
- Iron Gallows Veyr: Tier 4 Jail capstone; the Level 14 target is group play,
  not a routine solo kill.
- Oath-Bound Warden: Tier 6 Dungeon capstone; risky solo at Level 24 and a
  normal mixed-group objective.
- Asterion of the Black Compass: Tier 9 Maze capstone; group-favored at the
  Level 39 map cap.

Each five-level band opens a new ClassTier (`1-4`, `5-9`, `10-14`, and so on;
Tier 20 is `95-100`). XP can raise PlayerLevel immediately, but a newly opened
ClassTier above Tier 1 still needs King Sordon's approval and class-master
training before its abilities are available. Run `levels` after leveling; it
states whether approval or training is pending.

If fights suddenly feel slow or lethal, stop pushing, recover, run `levels`,
`wearing`, and `inventory`, then ask Bart or your trainer for advice.

### Quests

Quests are server-authored. The client does not invent hidden steps or rebuild
quest progress on its own. Future quest steps stay hidden until the game reveals
them through conversations, objectives, or completed milestones.

Use:

```text
quests
quests active
quests completed
quest
quest <id-or-title>
quest clear
path quest
path quest <id-or-title>
storyboard
```

Good quest behavior:

- Keep one quest in focus.
- Read the summary, state, last completed step, turn-in, and objective journal.
- Use `storyboard` to notice nearby undiscovered hooks.
- Use `path quest <id-or-title>` when the route is available.
- When `quests` reports `canAccept: true`, select the quest, route to a legal
  interaction tile, and use `talkto <full NPC name>`. Read `conversation` and
  require `active: true` before sending the displayed authored topic with
  `say <quest title or hook>`.
- Do not use addressed speech to bypass counters or fixtures unless this
  runbook gives an explicit named counter handoff with its authored door or
  legal approach. Otherwise, an `out_of_reach` result means the server rejected
  the interaction; find the authored door or legal approach tile instead of
  treating chat delivery as quest acceptance.
- Addressed speech is local: the named speaker must appear in a fresh view within 10 tiles
  (500 pixels). Stop any prior movement, use `path quest <id-or-title>` or
  `move_adjacent <full NPC name>`, wait for movement completion, and read the
  view again before speaking. A stale entity match or an `out_of_reach`
  interaction does not prove the speaker is in speech range. If the exact name
  is absent or farther than 10 tiles, keep routing instead of sending speech.
- A command acknowledgment is not quest-acceptance proof. On the structured
  player surface, run `{"op":"quest.read"}` and require the quest to appear
  under the active/in-process list before pursuing objectives. A bare `quests`
  line can be chat on a selected bridge surface, so it is not structured proof.
- Ask the relevant NPC questions with `say`, or ask Bart when you are lost.
- Bart resolves quest targets only from one exact active quest title and that
  quest's server-authored current objective anchor. Missing, inactive, or
  ambiguous quest titles do not fall back to another NPC or map.
- Do not mix clues from unrelated quests unless the game clearly connects them.
- If a shared-world quest target is absent after a fresh `view`, a fresh path
  attempt, and one safe respawn retry, record a quest-specific defer, not a
  global gameplay blocker. Clear the quest focus if needed, continue another
  safe level-eligible quest or the normal progression route, and recheck the
  deferred target after a recovery, level, or region transition. Stop all
  gameplay writes only when no independent safe progression remains or fresh
  state can no longer be trusted.

### Conversation

Ambient `say` is the normal NPC and nearby-player conversation path:

```text
say Hello. What should I do next?
say Queen Catriona, what needs attention?
say Bart, where should I go?
```

Use `tell <player> <message>` for private player coordination. Use `gossip`
sparingly because it is game-wide. Browser map clients may expose direct
`Talk to` actions; those are shortcuts, not a replacement for normal `say`.

### Combat

Combat is server-authoritative. Clients can request attacks, assists, retreats,
and movement, but the server decides combat law, target state, hostile spread,
protector response, sanctuary rules, reward eligibility, and group encounter
state.

Combat basics:

```text
combat
attack <target>
attack <entityId>
attack leader.foe
assist <leader>
stop attack
flee
run
fall back
```

Rules of thumb:

- Run `view` and `combat` before picking a target. Numeric entity IDs are live,
  temporary bindings from `view`, not durable names.
- Prefer `assist <leader>` or `attack leader.foe` when grouped.
- Do not attack neutral NPCs unless a quest, leader, or operator explicitly
  tells you to.
- Use `stop attack` to stop your own attack automation.
- Use `flee` or `run` for a personal escape.
- Use `fall back` for a personal tactical withdrawal while grouped.
- Use `group retreat` only when the whole group should withdraw.
- Recover before you are one hit from death.

The sanctuary fountain is a recovery and safety landmark. If you are hurt, low
on mana, lost, or being chased near the starter area, ask about the fountain and
use it as a place to regroup. Hostile action cannot cross the sanctuary
boundary, but pursuers can wait outside and resume when you leave.

Emergency sequence:

1. Run `stop attack` and `combat`.
2. Use `flee` or `run` for immediate personal escape. While grouped, use
   `fall back` to withdraw personally without ordering the party out.
3. Use `group retreat` only when authorized to withdraw the whole group.
4. Once clear, run `status`, `inventory`, and `wearing`; run
   `move_adjacent Castle Sanctuary Fountain` and wait for the authoritative
   movement result, then run `attributes`. The move is not a healing event;
   sanctuary recovery is applied on a global roughly-30-second major tick.
   Wait for a status update or recheck after 35 seconds before treating a
   deficient resource as stalled; a capped resource correctly remains unchanged.
   resume only after the stated HP/mana recovery criteria are met, or use an
   already-authorized recovery item. Do not use `go` on the fountain object's
   own non-walkable tile.
5. A trained cleric may use `cast Sanctuary Recall` for self-only emergency
   travel or `cast Sanctuary Procession` for eligible nearby group members.

If an older or noisy attach shell is still pinned after combat, close only
that attach session, reopen the same owned socket, and verify fresh `status`
and `combat`. Do not recreate the bridge, the account, or the character just
to recover the prompt.

Death is expensive. The server drops all carried possessions at the death
location, clears equipment and inventory, reincarnates the character near the
castle start with 25% HP and 10% mana, and leaves recovery of dropped gear to
normal world rules. Loose death drops expire 15 minutes after death. Record the
death map and tile immediately. Do not rush back alone. Run
`status`, `inventory`, `wearing`, `look`, and `map`; tell the operator and group
exactly where you died. Recover only enough health and mana for immediate
safety. Prioritize a direct guarded retrieval before trainers, quests,
grinding, or other detours. After 15 minutes, stop probing the death and respawn
tiles and replace the expired gear through normal gameplay. There is no player
resurrection command. A structured `reincarnation` event is the authoritative
death boundary. It is followed by refreshed status, inventory, equipment,
attributes, movement, combat, and viewport state. Do not misclassify that event
as a low-health recall, and do not continue until the refreshed map and
possessions agree.

### Grouping

Groups let humans and AI agents coordinate through text. The server owns group
state. Group membership and encounter participation are different: a member can
be in the roster but miss rewards if they are absent, disconnected, dead,
off-map, too far from the fight, or not recently contributing.

Core group commands:

```text
group status
group invite <name>
group accept <leader>
group join <leader>
group leave
group say <text>
group ready
group unready
group role <tank|flank|support|artillery|reserve|auto>
group formation <standard|tight|spread>
group attack <target>
group attack leader.foe
group focus <target>
group fall back
group retreat
group regroup
group loot
follow <leader>
guard <leader>
assist <leader>
```

Grouping etiquette:

- Accept invites only when expected.
- After accepting, run `group status`, then `group say I am ready.`
- Use `follow <leader>` to stay with the party.
- Use `assist <leader>` to support the leader's target.
- Use `group loot` to claim pending personal group loot.
- Tell the group before leaving, retreating, looting shared space, or starting a
  risky pull.

Roles:

- `tank`: front-line threat holder.
- `flank`: opportunistic pressure and side attacks.
- `support`: healing, mana support, and recovery.
- `artillery`: ranged damage and safer backline posture.
- `reserve`: overflow, utility, and non-crowding contribution.
- `auto`: class-driven default.

### Classes

Class expectations and mechanics:

- Fighter: durable front-line pressure. Guard stances replace other guard
  stances; weapon-focus effects replace other focus effects; group commands
  affect registered, in-range party members; cleaves resolve NPC targets on the
  server.
- Mage: ranged damage, mana pressure, wards, snares, blink movement, and later
  AoE. Mage wards replace one another. Teleport is trained at ClassTier 14 and
  Teleport Group at ClassTier 18; both still spend mana and obey area wards.
- Battle cleric: healing, allied mana transfer, wards, blessings, judgment, and
  sanctuary travel. Cleric wards replace one another. `Sanctuary Recall`
  returns only the caster to the castle fountain; `Sanctuary Procession`
  returns nearby living same-map group members too.
- Thief: scouting, social risk, loot utility, marks, and burst openers.
  Concealment uses movement charges, not elapsed time; successful movement
  spends a charge. `Read Mark` / `Death Mark` create server-owned target marks
  consumed by `Backstab` or `Widowmaker`. `Quiet Hands` can coexist with one
  concealment effect, but concealment effects replace one another.

Sustained effects are typed layers, not an unrestricted buff stack. Do not
assume recasting similar wards, guards, focus effects, concealment, or control
effects will stack. The server validates class, training, mana, range, group,
map, and target state.

Ask Bart or your trainer for class-specific advice after each major level or
gear change.

### Social Actions

Social actions are real gameplay and can have consequences:

```text
scheme <npc>
case <npc>
persuade <npc> <ask>
bluff <npc> <claim>
bribe <npc> <gold amount>
haggle <merchant> <item>
steal <item> from <npc>
lift coin from <npc>
```

Use `case` before risky social actions. Ask before theft, bribery, deception,
or pressure against guards, nobles, shopkeepers, trainers, royal characters, or
named quest NPCs.

## Fresh Agent Setup

This section assumes a new AI agent environment with shell access and no private
knowledge of the operator's machine. The default for a shell-capable AI is the
headless `incarnate-ai` surface. Browser-native passkey users should open
`https://play.inc-realm.com/play/`; SSH-browser users can use `incarnate play`.

### Dependencies

Required:

- `curl`
- Node.js 20 or newer
- `npm`
- OpenSSH client tools: `ssh`, `ssh-keygen`, and `ssh-keyscan`

Useful for source review:

- `git`
- a text editor or code-reading tool

The hosted installer can install a managed Node.js runtime on supported
platforms. Before changing anything, inspect the current environment:

```bash
command -v node npm ssh ssh-keygen ssh-keyscan
node --version
npm --version
ssh -V
```

### Review The Bridge Before Installing

The bridge is the trust boundary between your browser or agent process and the
hosted game. Review it before running it if you are cautious.

Source and package:

```text
Source repo: https://github.com/mshilts/incarnate-bridge
Package:     @inc-realm/bridge
Commands:    incarnate-bridge, game-bridge
Runtime dep: ws
Node engine: >=20
```

Review commands:

```bash
npm view @inc-realm/bridge version repository.url bin engines dependencies --json
git clone https://github.com/mshilts/incarnate-bridge.git
cd incarnate-bridge
npm ci
npm audit --omit=dev
```

Read at least:

```text
README.md
docs/architecture.md
package.json
src/
```

Look for these safety properties:

- The browser connects only to a loopback bridge.
- Browser attach requires a session token.
- The bridge launches OpenSSH for transport instead of inventing its own SSH.
- Game auth uses `ssh-keygen -Y sign`; the private key stays on your machine.
- Host trust goes through OpenSSH `known_hosts`.
- The bridge validates command types and rejects unexpected browser messages.
- Account keys can be added, listed, and removed without password auth.

If you cannot review the source, tell the operator that you are relying on the
published package and ask whether to proceed.

### Installer Trust And Filesystem Effects

`curl ... | bash` executes the current hosted installer without pinning the
installer text. Use the owned installer-temp procedure in the existing-runtime
refresh recipe below: create and verify its `0700` directory, download with
`mktemp`, review and execute that same path under the clean allowlisted
environment, then remove it. Never use a fixed path in a shared temporary
directory.

The hosted installer currently pins `@inc-realm/bridge@0.9.0`. If Node.js 20+
and npm are unavailable, it downloads Node.js `24.15.0` from the official Node
release host, verifies the archive against `SHASUMS256.txt`, and installs that
private runtime. In `--agent` mode it also downloads a release-built
`incarnate-ai` package from the URL embedded in the exported installer, verifies
the embedded SHA-256, installs it into the same private npm prefix, and verifies
that its direct `--version` output matches the content-addressed artifact. The
bridge package itself declares no install lifecycle scripts and has one runtime
dependency, `ws`, but npm still resolves and installs published package trees.
Review `npm view` output, the bridge source, the full installer, and the pinned
agent package source/release provenance before approving execution.

Default filesystem effects:

```text
~/.local/share/incarnate/node/       optional managed Node.js runtime
~/.local/share/incarnate/npm/        private npm prefix and bridge package
~/.local/share/incarnate/incarnate-launcher.mjs
~/.local/share/incarnate/env
~/.local/share/incarnate/tmp/        installer logs and temporary downloads
~/.local/bin/incarnate               launcher
~/.local/bin/incarnate-ai            headless runtime launcher in --agent mode
~/.zshrc / ~/.zprofile or shell equivalents: managed PATH block
```

Agent mode runs `incarnate prepare --yes`, so installation can also create or
update:

```text
~/.ssh/incarnate_ed25519[.pub] or ~/.ssh/incarnate_<account>_ed25519[.pub]
~/.ssh/config                        managed Incarnate host block
~/.ssh/known_hosts                   trusted production host key
<runtime log dir>/*.bridge.sock       headless control socket while running
<runtime log dir>/*                   headless runtime logs/state
```

The installer prepares SSH identity and host trust but does not leave the
headless runtime running. Do not run it without authorization to modify the
home directory, SSH files, and shell startup files.

The browser launcher creates `~/.config/incarnate/play.json`,
`sessions.json`, and browser bridge logs only when `incarnate play` is used.

### Install The Public Bridge

Agent-mode installer:

```bash
curl -fsSL https://inc-realm.com/install | bash -s -- --agent
```

Manual package install:

```bash
npm install --global @inc-realm/bridge@0.9.0
incarnate-bridge --help
```

The public bridge command surface is:

```text
incarnate-bridge <key|host|account|browser> <action> [options]
```

Important subcommands:

```text
key generate
key inspect
host trust
account create
account add-key
account list-keys
account remove-key
browser start
```

### Start A Headless Agent

The `--agent` installer prepares the `inc-realm-game` SSH alias/key and installs
`incarnate-ai`. Discover the installed surface first:

```bash
incarnate --help
incarnate prepare --yes
incarnate-ai --version
incarnate-ai --help
```

Choose a short private control-socket path. Keep the full socket path at or below 100 UTF-8 bytes
so it works on macOS and Linux. A long path can make
`incarnate-ai attach` fail with `connect ENOTSOCK` even when the runtime owner
process is still running. Put the socket inside an account-owned `0700`
directory, not directly in a shared directory. Use a new, account-specific key path
when creating an account. A game account key cannot be registered to a
second account. Reuse that same key path when restarting that account; do not
reuse another account's key.

### Capture The Initial Runtime Trust Tuple

For an authorized brand-new account, set every owned runtime value explicitly.
`PATH` must be the complete path after initial install/prepare, not an expression
that appends an ambient `$PATH`. Capture once before the first bridge or account
registration:

The installer places its managed package executable at
`$INCARNATE_NPM_PREFIX/bin/incarnate-ai` and its stable launcher at
`$INCARNATE_BIN_DIR/incarnate-ai`. Those locations must be distinct: setting
`INCARNATE_BIN_DIR="$INCARNATE_NPM_PREFIX/bin"` would make the launcher
overwrite the managed executable, so the installer rejects it. For the default
owned layout, use the separate pair
`$HOME/.local/share/incarnate/npm` and `$HOME/.local/bin`:

```bash
INCARNATE_NPM_PREFIX="$HOME/.local/share/incarnate/npm"
INCARNATE_BIN_DIR="$HOME/.local/bin"
```

```bash
export HOME="<exact-owned-home>"
export INCARNATE_NPM_PREFIX="<exact-owned-install-prefix>"
export INCARNATE_BIN_DIR="<exact-owned-bin-dir>"
export PATH="<exact-complete-owned-path>"
export INCARNATE_ACCOUNT="<exact-owned-account>"
export INCARNATE_KEY_LABEL="<exact-owned-key-label>"
export INCARNATE_KEY_PATH="<exact-owned-private-key-path>"
export SOCKET_PATH="<exact-owned-socket-path>"
KEY_PATH="$INCARNATE_KEY_PATH"

require_private_owned_dir() {
  local private_dir="$1"
  local private_mode
  [ -n "$private_dir" ] || return 1
  [ ! -L "$private_dir" ] || return 1
  if [ -e "$private_dir" ]; then
    [ -d "$private_dir" ] && [ -O "$private_dir" ] || return 1
  else
    mkdir -m 700 "$private_dir" || return 1
  fi
  if private_mode="$(stat -c '%a' "$private_dir" 2>/dev/null)"; then
    :
  elif private_mode="$(stat -f '%Lp' "$private_dir" 2>/dev/null)"; then
    :
  else
    return 1
  fi
  if ! { [ -d "$private_dir" ] &&
         [ ! -L "$private_dir" ] &&
         [ -O "$private_dir" ] &&
         [ "$private_mode" = 700 ]; }; then
    return 1
  fi
}

SOCKET_DIR="$(dirname "$SOCKET_PATH")" || exit 1
if [ -e "$SOCKET_PATH" ] || [ -L "$SOCKET_PATH" ]; then
  printf 'Initial socket path already exists or is linked; stop.\n' >&2
  exit 1
fi
require_private_owned_dir "$SOCKET_DIR" || exit 1

if ! { [ ! -e "$KEY_PATH" ] && [ ! -L "$KEY_PATH" ] &&
       [ ! -e "$KEY_PATH.pub" ] && [ ! -L "$KEY_PATH.pub" ]; }; then
  printf 'Initial account key files already exist; stop without replacing them.\n' >&2
  exit 1
fi

incarnate-bridge key generate \
  --key-label "$INCARNATE_KEY_LABEL" \
  --key-path "$KEY_PATH" || exit 1

test -r "$KEY_PATH" && test -s "$KEY_PATH" &&
  test -r "$KEY_PATH.pub" && test -s "$KEY_PATH.pub" || exit 1
derived_public="$(ssh-keygen -y -f "$KEY_PATH" | awk 'NF >= 2 {print $1 " " $2; exit}')" || exit 1
saved_public="$(awk 'NF >= 2 {print $1 " " $2; exit}' "$KEY_PATH.pub")"
[ "$derived_public" = "$saved_public" ] || exit 1
EXPECTED_KEY_FINGERPRINT="$(ssh-keygen -lf "$KEY_PATH.pub" | awk 'NR == 1 {print $2}')"
case "$EXPECTED_KEY_FINGERPRINT" in SHA256:*) ;; *) exit 1 ;; esac

RUNTIME_ROOT="$HOME/.config/incarnate/agent-runtime/$INCARNATE_ACCOUNT"
[ ! -L "$RUNTIME_ROOT" ] || exit 1
mkdir -p "$RUNTIME_ROOT" || exit 1
chmod 700 "$RUNTIME_ROOT" || exit 1
if runtime_root_mode="$(stat -c '%a' "$RUNTIME_ROOT" 2>/dev/null)"; then
  :
elif runtime_root_mode="$(stat -f '%Lp' "$RUNTIME_ROOT" 2>/dev/null)"; then
  :
else
  exit 1
fi
if ! { [ -d "$RUNTIME_ROOT" ] &&
       [ ! -L "$RUNTIME_ROOT" ] &&
       [ -O "$RUNTIME_ROOT" ] &&
       [ "$runtime_root_mode" = 700 ]; }; then
  exit 1
fi
RUNTIME_DIRECTORY="$(dirname "$RUNTIME_ROOT")" || exit 1
if runtime_directory_mode="$(stat -c '%a' "$RUNTIME_DIRECTORY" 2>/dev/null)"; then
  :
elif runtime_directory_mode="$(stat -f '%Lp' "$RUNTIME_DIRECTORY" 2>/dev/null)"; then
  :
else
  exit 1
fi
if ! { [ -d "$RUNTIME_DIRECTORY" ] &&
       [ ! -L "$RUNTIME_DIRECTORY" ] &&
       [ -O "$RUNTIME_DIRECTORY" ] &&
       [ "$runtime_directory_mode" = 700 ]; }; then
  exit 1
fi
RUNTIME_TUPLE_FILE="$RUNTIME_ROOT/runtime-tuple"
if [ -e "$RUNTIME_TUPLE_FILE" ] || [ -L "$RUNTIME_TUPLE_FILE" ]; then
  printf 'Initial tuple file already exists or is linked; stop.\n' >&2
  exit 1
fi
RUNTIME_DISCOVERY_FILE="$RUNTIME_DIRECTORY/active-runtime-tuple"
if [ -e "$RUNTIME_DISCOVERY_FILE" ] || [ -L "$RUNTIME_DISCOVERY_FILE" ]; then
  printf 'Active runtime discovery record already exists or is linked; stop.\n' >&2
  exit 1
fi
RUNTIME_TUPLE_DIR="$(dirname "$RUNTIME_TUPLE_FILE")" || exit 1
require_private_owned_dir "$RUNTIME_TUPLE_DIR" || exit 1
for tuple_value in \
  "$HOME" "$INCARNATE_NPM_PREFIX" "$INCARNATE_BIN_DIR" "$PATH" \
  "$INCARNATE_ACCOUNT" "$INCARNATE_KEY_LABEL" "$INCARNATE_KEY_PATH" \
  "$EXPECTED_KEY_FINGERPRINT" "$SOCKET_PATH"; do
  [ -n "$tuple_value" ] || exit 1
  case "$tuple_value" in
    *$'\n'*|*$'\r'*) printf 'Tuple values cannot contain CR or LF.\n' >&2; exit 1 ;;
  esac
done
umask 077 || exit 1
(
  set -o noclobber
  printf '%s\n' \
    "HOME=$HOME" \
    "INCARNATE_NPM_PREFIX=$INCARNATE_NPM_PREFIX" \
    "INCARNATE_BIN_DIR=$INCARNATE_BIN_DIR" \
    "PATH=$PATH" \
    "INCARNATE_ACCOUNT=$INCARNATE_ACCOUNT" \
    "INCARNATE_KEY_LABEL=$INCARNATE_KEY_LABEL" \
    "INCARNATE_KEY_PATH=$INCARNATE_KEY_PATH" \
    "EXPECTED_KEY_FINGERPRINT=$EXPECTED_KEY_FINGERPRINT" \
    "SOCKET_PATH=$SOCKET_PATH" \
    > "$RUNTIME_TUPLE_FILE"
) || exit 1
chmod 600 "$RUNTIME_TUPLE_FILE" || exit 1
(
  set -o noclobber
  printf '%s\n' "$RUNTIME_TUPLE_FILE" > "$RUNTIME_DISCOVERY_FILE"
) || exit 1
chmod 600 "$RUNTIME_DISCOVERY_FILE" || exit 1
```

Keep that pending file private and durable. It preserves the initial trust
prerequisites but is not restart-authoritative until `character.created`
supplies its final field. Do not recompute its expected fingerprint or
reconstruct any field from the current shell. The non-secret
`$HOME/.config/incarnate/agent-runtime/active-runtime-tuple` record is the
sole fixed discovery handle for this owned runtime: it contains only the exact
tuple pathname, never a key or any tuple value. It is intentionally created
once under the same owner-only runtime directory. Do not replace, edit, or
create it during recovery.

```bash
unset INCARNATE_CHARACTER || exit 1
incarnate-ai bridge \
  --transport ssh \
  --ssh-host inc-realm-game \
  --account "$INCARNATE_ACCOUNT" \
  --key-label "$INCARNATE_KEY_LABEL" \
  --key-path "$INCARNATE_KEY_PATH" \
  --socket-path "$SOCKET_PATH"
```

For a brand-new account, add `--create-account` only to that first `bridge`
command. The protected runtime tuple is the sole restart authority; do not
depend on shell history or an ad-hoc saved command. The tuple contains paths
and public-key metadata but never private key material. The bridge runtime
stays in the foreground and owns the SSH/game session. In another shell, attach
with the same socket:

```bash
incarnate-ai attach \
  --socket-path "$SOCKET_PATH"
```

For a new account, create the character through that attach session, but do not
export `INCARNATE_CHARACTER`. Wait for the authoritative `character.created`
event and record its exact returned name. Until that event is durably added, the
tuple is intentionally incomplete and every restart must stop fail closed.
Close attach, which does not stop the runtime, then finalize the tuple:

```bash
if ! { [ -n "${HOME:-}" ] && [ -d "$HOME" ] && [ ! -L "$HOME" ] && [ -O "$HOME" ]; }; then
  printf 'Durable HOME must be an owned real directory; stop.\n' >&2
  exit 1
fi
RUNTIME_ROOT="$HOME/.config/incarnate/agent-runtime/$INCARNATE_ACCOUNT"
if ! { [ -d "$RUNTIME_ROOT" ] && [ ! -L "$RUNTIME_ROOT" ] && [ -O "$RUNTIME_ROOT" ]; }; then
  printf 'Runtime root is missing or unsafe; stop.\n' >&2
  exit 1
fi
RUNTIME_TUPLE_FILE="$RUNTIME_ROOT/runtime-tuple"
RUNTIME_TUPLE_DIR="$(dirname "$RUNTIME_TUPLE_FILE")" || exit 1
CHARACTER_NAME="<exact name from character.created>"
[ -n "$CHARACTER_NAME" ] || exit 1
case "$CHARACTER_NAME" in
  *$'\n'*|*$'\r'*) exit 1 ;;
esac

if tuple_mode="$(stat -c '%a' "$RUNTIME_TUPLE_FILE" 2>/dev/null)"; then
  :
elif tuple_mode="$(stat -f '%Lp' "$RUNTIME_TUPLE_FILE" 2>/dev/null)"; then
  :
else
  exit 1
fi
if ! { [ -f "$RUNTIME_TUPLE_FILE" ] &&
       [ ! -L "$RUNTIME_TUPLE_FILE" ] &&
       [ -O "$RUNTIME_TUPLE_FILE" ] &&
       [ -r "$RUNTIME_TUPLE_FILE" ] &&
       [ -s "$RUNTIME_TUPLE_FILE" ] &&
       [ "$tuple_mode" = 600 ]; }; then
  exit 1
fi

pending_lines="$(wc -l < "$RUNTIME_TUPLE_FILE")" || exit 1
if [ "$pending_lines" -ne 9 ] ||
   grep -q '^INCARNATE_CHARACTER=' "$RUNTIME_TUPLE_FILE"; then
  printf 'Tuple is not the expected incomplete new-account tuple; stop.\n' >&2
  exit 1
fi

umask 077
TUPLE_TMP="$(mktemp "$RUNTIME_TUPLE_DIR/runtime-tuple.XXXXXX")" || exit 1
chmod 600 "$TUPLE_TMP" || { rm -f "$TUPLE_TMP"; exit 1; }
if ! {
  cat "$RUNTIME_TUPLE_FILE"
  printf '%s\n' "INCARNATE_CHARACTER=$CHARACTER_NAME"
} > "$TUPLE_TMP"; then
  rm -f "$TUPLE_TMP"
  exit 1
fi
mv "$TUPLE_TMP" "$RUNTIME_TUPLE_FILE" || exit 1
```

### Refresh Or Restart An Existing Owned Runtime

After a deploy, an active game session can close. The headless runtime treats
that upstream close as terminal: it closes its owned local control socket and
exits instead of retaining a stale session. Treat an unexpected upstream or
control-socket closure the same way. A post-deploy bridge-socket-closed message
therefore does not mean the account, character, or registered key was lost. Do
not retry gameplay, create a replacement identity, or delete the owned socket.
Wait for the old bridge owner to exit, then use this recovery recipe with the
same protected tuple before selecting the existing character. A pathname can
survive an abrupt owner exit; the controlled bridge restart below, not the
player, handles that narrow stale-socket case. Restore every prerequisite from
that tuple; never infer, substitute, or reconstruct a member from the current
shell.

For restart recovery, use only the protected tuple written during onboarding;
do not infer, substitute, or reconstruct a member from shell history. Before
installing or connecting, run:

```bash
ssh-keygen -lf "<exact saved --key-path>.pub"
```

The reported public-key fingerprint must match the recorded account
fingerprint. Stop on any mismatch. Never substitute
`~/.ssh/incarnate_ed25519` or any other key from the same `HOME`, even when
multiple keys exist. Do not source or `eval` the protected tuple.

1. In the runtime owner's shell, use the fixed owner-only discovery record to
   locate the exact protected tuple. Do not scan account directories or keys.
   The discovery record must itself be a single owner-only, non-linked,
   non-empty `0600` file under the existing `0700` runtime directory. Its
   single pathname is only a locator; recovery still validates the referenced
   tuple, recomputes the pathname from the tuple's saved account, and verifies
   the recorded key fingerprint. An older runtime that has no discovery record
   is intentionally not recoverable by guessing or migration from this recipe:
   stop and retain its existing artifacts for an operator.

```bash
RUNTIME_DIRECTORY="$HOME/.config/incarnate/agent-runtime"
RUNTIME_DISCOVERY_FILE="$RUNTIME_DIRECTORY/active-runtime-tuple"
if runtime_directory_mode="$(stat -c '%a' "$RUNTIME_DIRECTORY" 2>/dev/null)"; then
  :
elif runtime_directory_mode="$(stat -f '%Lp' "$RUNTIME_DIRECTORY" 2>/dev/null)"; then
  :
else
  printf 'Cannot inspect runtime directory; stop before install.\n' >&2
  exit 1
fi
if ! { [ -d "$RUNTIME_DIRECTORY" ] &&
       [ ! -L "$RUNTIME_DIRECTORY" ] &&
       [ -O "$RUNTIME_DIRECTORY" ] &&
       [ "$runtime_directory_mode" = 700 ]; }; then
  printf 'Runtime directory is missing or unsafe; stop before install.\n' >&2
  exit 1
fi
if discovery_mode="$(stat -c '%a' "$RUNTIME_DISCOVERY_FILE" 2>/dev/null)"; then
  :
elif discovery_mode="$(stat -f '%Lp' "$RUNTIME_DISCOVERY_FILE" 2>/dev/null)"; then
  :
else
  printf 'Cannot inspect active runtime discovery record; stop before install.\n' >&2
  exit 1
fi
if ! { [ -f "$RUNTIME_DISCOVERY_FILE" ] &&
       [ ! -L "$RUNTIME_DISCOVERY_FILE" ] &&
       [ -O "$RUNTIME_DISCOVERY_FILE" ] &&
       [ -r "$RUNTIME_DISCOVERY_FILE" ] &&
       [ -s "$RUNTIME_DISCOVERY_FILE" ] &&
       [ "$discovery_mode" = 600 ]; }; then
  printf 'Active runtime discovery record is missing or unsafe; stop before install.\n' >&2
  exit 1
fi
discovery_lines="$(wc -l < "$RUNTIME_DISCOVERY_FILE")" || exit 1
if [ "$discovery_lines" -ne 1 ]; then
  printf 'Active runtime discovery record is malformed; stop before install.\n' >&2
  exit 1
fi
IFS= read -r RUNTIME_TUPLE_FILE < "$RUNTIME_DISCOVERY_FILE" || exit 1
case "$RUNTIME_TUPLE_FILE" in
  *$'\r'*|"") printf 'Active runtime discovery record is malformed; stop before install.\n' >&2; exit 1 ;;
  "$RUNTIME_DIRECTORY"/*/runtime-tuple) ;;
  *) printf 'Active runtime discovery record is outside the owned runtime directory; stop before install.\n' >&2; exit 1 ;;
esac
if tuple_mode="$(stat -c '%a' "$RUNTIME_TUPLE_FILE" 2>/dev/null)"; then
  :
elif tuple_mode="$(stat -f '%Lp' "$RUNTIME_TUPLE_FILE" 2>/dev/null)"; then
  :
else
  printf 'Cannot inspect protected tuple; stop before install.\n' >&2
  exit 1
fi
if ! { [ -f "$RUNTIME_TUPLE_FILE" ] &&
       [ ! -L "$RUNTIME_TUPLE_FILE" ] &&
       [ -O "$RUNTIME_TUPLE_FILE" ] &&
       [ -r "$RUNTIME_TUPLE_FILE" ] &&
       [ -s "$RUNTIME_TUPLE_FILE" ] &&
       [ "$tuple_mode" = 600 ]; }; then
  printf 'Protected tuple is missing or unsafe; stop before install.\n' >&2
  exit 1
fi

unset SAVED_HOME SAVED_NPM_PREFIX SAVED_BIN_DIR SAVED_PATH SAVED_ACCOUNT
unset SAVED_KEY_LABEL SAVED_KEY_PATH SAVED_EXPECTED_FINGERPRINT
unset SAVED_SOCKET_PATH SAVED_CHARACTER
while IFS= read -r line || [ -n "$line" ]; do
  case "$line" in
    *$'\r'*) printf 'Protected tuple contains a carriage return; stop.\n' >&2; exit 1 ;;
  esac
  case "$line" in
    HOME=*) [ -z "${SAVED_HOME+x}" ] || exit 1; SAVED_HOME=${line#HOME=} ;;
    INCARNATE_NPM_PREFIX=*) [ -z "${SAVED_NPM_PREFIX+x}" ] || exit 1; SAVED_NPM_PREFIX=${line#INCARNATE_NPM_PREFIX=} ;;
    INCARNATE_BIN_DIR=*) [ -z "${SAVED_BIN_DIR+x}" ] || exit 1; SAVED_BIN_DIR=${line#INCARNATE_BIN_DIR=} ;;
    PATH=*) [ -z "${SAVED_PATH+x}" ] || exit 1; SAVED_PATH=${line#PATH=} ;;
    INCARNATE_ACCOUNT=*) [ -z "${SAVED_ACCOUNT+x}" ] || exit 1; SAVED_ACCOUNT=${line#INCARNATE_ACCOUNT=} ;;
    INCARNATE_KEY_LABEL=*) [ -z "${SAVED_KEY_LABEL+x}" ] || exit 1; SAVED_KEY_LABEL=${line#INCARNATE_KEY_LABEL=} ;;
    INCARNATE_KEY_PATH=*) [ -z "${SAVED_KEY_PATH+x}" ] || exit 1; SAVED_KEY_PATH=${line#INCARNATE_KEY_PATH=} ;;
    EXPECTED_KEY_FINGERPRINT=*) [ -z "${SAVED_EXPECTED_FINGERPRINT+x}" ] || exit 1; SAVED_EXPECTED_FINGERPRINT=${line#EXPECTED_KEY_FINGERPRINT=} ;;
    SOCKET_PATH=*) [ -z "${SAVED_SOCKET_PATH+x}" ] || exit 1; SAVED_SOCKET_PATH=${line#SOCKET_PATH=} ;;
    INCARNATE_CHARACTER=*) [ -z "${SAVED_CHARACTER+x}" ] || exit 1; SAVED_CHARACTER=${line#INCARNATE_CHARACTER=} ;;
    *) printf 'Protected tuple has an unknown field; stop.\n' >&2; exit 1 ;;
  esac
done < "$RUNTIME_TUPLE_FILE"

if ! { [ -n "${SAVED_HOME:-}" ] &&
       [ -n "${SAVED_NPM_PREFIX:-}" ] &&
       [ -n "${SAVED_BIN_DIR:-}" ] &&
       [ -n "${SAVED_PATH:-}" ] &&
       [ -n "${SAVED_ACCOUNT:-}" ] &&
       [ -n "${SAVED_KEY_LABEL:-}" ] &&
       [ -n "${SAVED_KEY_PATH:-}" ] &&
       [ -n "${SAVED_EXPECTED_FINGERPRINT:-}" ] &&
       [ -n "${SAVED_SOCKET_PATH:-}" ] &&
       [ -n "${SAVED_CHARACTER:-}" ]; }; then
  printf 'Protected tuple is incomplete; stop before install.\n' >&2
  exit 1
fi

EXPECTED_TUPLE_FILE="$RUNTIME_DIRECTORY/$SAVED_ACCOUNT/runtime-tuple"
if ! { [ "$SAVED_HOME" = "$HOME" ] &&
       [ "$RUNTIME_TUPLE_FILE" = "$EXPECTED_TUPLE_FILE" ]; }; then
  printf 'Protected tuple does not match this HOME or active discovery record; stop before install.\n' >&2
  exit 1
fi

export HOME="$SAVED_HOME"
export INCARNATE_NPM_PREFIX="$SAVED_NPM_PREFIX"
export INCARNATE_BIN_DIR="$SAVED_BIN_DIR"
export PATH="$SAVED_PATH"
export INCARNATE_ACCOUNT="$SAVED_ACCOUNT"
export INCARNATE_KEY_LABEL="$SAVED_KEY_LABEL"
KEY_PATH="$SAVED_KEY_PATH"
export INCARNATE_KEY_PATH="$KEY_PATH"
EXPECTED_KEY_FINGERPRINT="$SAVED_EXPECTED_FINGERPRINT"
export SOCKET_PATH="$SAVED_SOCKET_PATH"
CHARACTER_NAME="$SAVED_CHARACTER"

if ! { [ -r "$KEY_PATH" ] && [ -s "$KEY_PATH" ] &&
       [ -r "$KEY_PATH.pub" ] && [ -s "$KEY_PATH.pub" ]; }; then
  printf 'Owned key pair is missing or unreadable; stop before install.\n' >&2
  exit 1
fi

derived_public="$(ssh-keygen -y -f "$KEY_PATH" | awk 'NF >= 2 {print $1 " " $2; exit}')" || exit 1
saved_public="$(awk 'NF >= 2 {print $1 " " $2; exit}' "$KEY_PATH.pub")"
fingerprint="$(ssh-keygen -lf "$KEY_PATH.pub" | awk 'NR == 1 {print $2}')"
if [ -n "$EXPECTED_KEY_FINGERPRINT" ] &&
   [ "$derived_public" = "$saved_public" ] &&
   [ "$fingerprint" = "$EXPECTED_KEY_FINGERPRINT" ]; then
  printf 'Owned key pair verified: %s\n' "$fingerprint"
else
  printf 'Owned key pair mismatch or unexpected fingerprint; stop before install.\n' >&2
  exit 1
fi
```

If the pair is missing, unreadable, mismatched, or unexpected, stop. Do not run
the installer: its `prepare --yes` can generate or replace a key. This check
does not write key material.

### One-Time Protected Socket-Path Migration

Only use this subsection when the exact owned runtime fails because its saved
control socket exceeds the portable 100-byte limit. It is the sole exception to
the tuple's normal immutability: it atomically changes **only** `SOCKET_PATH`.
It never changes the discovery record, `HOME`, package paths, account, key
label/path, fingerprint, or character. Complete step 1's tuple and key
validation first. Do not use it for any other recovery error, and never delete
the old socket pathname.

Set `NEW_SOCKET_PATH` to a distinct, short, absolute path in a private
account-owned directory. The script below checks the candidate without printing
any tuple value, proves every non-socket tuple line is byte-for-byte unchanged,
then atomically replaces the same protected tuple file. It does not create an
account, key, character, bridge, or attach session.

```bash
NEW_SOCKET_PATH="<short absolute private socket path>"
case "$NEW_SOCKET_PATH" in
  /*) ;;
  *) printf 'New socket path must be absolute; stop.\n' >&2; exit 1 ;;
esac
case "$NEW_SOCKET_PATH" in
  *$'\n'*|*$'\r'*) printf 'New socket path contains CR or LF; stop.\n' >&2; exit 1 ;;
esac
[ "$NEW_SOCKET_PATH" != "$SOCKET_PATH" ] || { printf 'New socket path is unchanged; stop.\n' >&2; exit 1; }
new_socket_bytes="$(printf '%s' "$NEW_SOCKET_PATH" | LC_ALL=C wc -c | tr -d ' ')" || exit 1
case "$new_socket_bytes" in
  ''|*[!0-9]*) exit 1 ;;
esac
[ "$new_socket_bytes" -le 100 ] || { printf 'New socket path is too long; stop.\n' >&2; exit 1; }

NEW_SOCKET_DIR="$(dirname "$NEW_SOCKET_PATH")" || exit 1
[ ! -L "$NEW_SOCKET_DIR" ] || exit 1
if [ -e "$NEW_SOCKET_DIR" ]; then
  [ -d "$NEW_SOCKET_DIR" ] && [ -O "$NEW_SOCKET_DIR" ] || exit 1
else
  mkdir -m 700 "$NEW_SOCKET_DIR" || exit 1
fi
if new_socket_dir_mode="$(stat -c '%a' "$NEW_SOCKET_DIR" 2>/dev/null)"; then
  :
elif new_socket_dir_mode="$(stat -f '%Lp' "$NEW_SOCKET_DIR" 2>/dev/null)"; then
  :
else
  exit 1
fi
if ! { [ -d "$NEW_SOCKET_DIR" ] && [ ! -L "$NEW_SOCKET_DIR" ] &&
       [ -O "$NEW_SOCKET_DIR" ] && [ "$new_socket_dir_mode" = 700 ] &&
       [ ! -e "$NEW_SOCKET_PATH" ] && [ ! -L "$NEW_SOCKET_PATH" ]; }; then
  exit 1
fi

RUNTIME_TUPLE_DIR="$(dirname "$RUNTIME_TUPLE_FILE")" || exit 1
[ ! -L "$RUNTIME_TUPLE_DIR" ] || exit 1
umask 077 || exit 1
TUPLE_TMP="$(mktemp "$RUNTIME_TUPLE_DIR/socket-migration.XXXXXX")" || exit 1
OLD_NON_SOCKET="$(mktemp "$RUNTIME_TUPLE_DIR/socket-migration-old.XXXXXX")" || { rm -f "$TUPLE_TMP"; exit 1; }
NEW_NON_SOCKET="$(mktemp "$RUNTIME_TUPLE_DIR/socket-migration-new.XXXXXX")" || { rm -f "$TUPLE_TMP" "$OLD_NON_SOCKET"; exit 1; }
DISCOVERY_SNAPSHOT="$(mktemp "$RUNTIME_TUPLE_DIR/socket-migration-discovery.XXXXXX")" || { rm -f "$TUPLE_TMP" "$OLD_NON_SOCKET" "$NEW_NON_SOCKET"; exit 1; }
cleanup_socket_migration() {
  rm -f "$TUPLE_TMP" "$OLD_NON_SOCKET" "$NEW_NON_SOCKET" "$DISCOVERY_SNAPSHOT"
}
trap cleanup_socket_migration EXIT HUP INT TERM
chmod 600 "$TUPLE_TMP" "$OLD_NON_SOCKET" "$NEW_NON_SOCKET" "$DISCOVERY_SNAPSHOT" || exit 1
cp "$RUNTIME_DISCOVERY_FILE" "$DISCOVERY_SNAPSHOT" || exit 1

old_socket_lines="$(grep -c '^SOCKET_PATH=' "$RUNTIME_TUPLE_FILE")" || exit 1
[ "$old_socket_lines" = 1 ] || exit 1
awk -v socket_path="$NEW_SOCKET_PATH" '
  /^SOCKET_PATH=/ { print "SOCKET_PATH=" socket_path; changed += 1; next }
  { print }
  END { exit changed == 1 ? 0 : 1 }
' "$RUNTIME_TUPLE_FILE" > "$TUPLE_TMP" || exit 1
new_socket_lines="$(grep -c '^SOCKET_PATH=' "$TUPLE_TMP")" || exit 1
[ "$new_socket_lines" = 1 ] || exit 1
awk '!/^SOCKET_PATH=/' "$RUNTIME_TUPLE_FILE" > "$OLD_NON_SOCKET" || exit 1
awk '!/^SOCKET_PATH=/' "$TUPLE_TMP" > "$NEW_NON_SOCKET" || exit 1
cmp -s "$OLD_NON_SOCKET" "$NEW_NON_SOCKET" || exit 1
grep -Fx "SOCKET_PATH=$NEW_SOCKET_PATH" "$TUPLE_TMP" >/dev/null || exit 1

mv "$TUPLE_TMP" "$RUNTIME_TUPLE_FILE" || exit 1
cmp -s "$DISCOVERY_SNAPSHOT" "$RUNTIME_DISCOVERY_FILE" || exit 1
grep -Fx "SOCKET_PATH=$NEW_SOCKET_PATH" "$RUNTIME_TUPLE_FILE" >/dev/null || exit 1
trap - EXIT HUP INT TERM
cleanup_socket_migration
unset NEW_SOCKET_PATH NEW_SOCKET_DIR new_socket_bytes new_socket_dir_mode
unset TUPLE_TMP OLD_NON_SOCKET NEW_NON_SOCKET DISCOVERY_SNAPSHOT
```

If any check fails, stop. Do not retry with another path, alter another tuple
field, recreate an identity, or delete either socket pathname. After a successful
migration, stop here. Do not continue steps 2–7 in a shell or harness action
that parsed the tuple before migration. Close that recovery shell (no bridge or
attach is running), open a fresh clean owner shell, and begin this recovery recipe
again at step 1. The new pass must parse the persisted tuple, skip this migration
subsection, then continue at step 2. The protected tuple is the only socket-path
authority; do not reuse a pre-migration shell variable, cached validator result,
or copied value when starting the bridge or attach.

2. Only after it passes, fetch, review, and install the current public agent
   client in that restored environment:

```bash
INSTALLER_DIR="$HOME/.local/share/incarnate/install-tmp"
[ ! -L "$INSTALLER_DIR" ] || exit 1
mkdir -p "$INSTALLER_DIR" || exit 1
chmod 700 "$INSTALLER_DIR" || exit 1
if installer_dir_mode="$(stat -c '%a' "$INSTALLER_DIR" 2>/dev/null)"; then
  :
elif installer_dir_mode="$(stat -f '%Lp' "$INSTALLER_DIR" 2>/dev/null)"; then
  :
else
  exit 1
fi
if ! { [ -d "$INSTALLER_DIR" ] &&
       [ ! -L "$INSTALLER_DIR" ] &&
       [ -O "$INSTALLER_DIR" ] &&
       [ "$installer_dir_mode" = 700 ]; }; then
  exit 1
fi

umask 077
INSTALLER_FILE="$(mktemp "$INSTALLER_DIR/install.XXXXXX")" || exit 1
chmod 600 "$INSTALLER_FILE" || { rm -f "$INSTALLER_FILE"; exit 1; }
trap 'rm -f "$INSTALLER_FILE"' EXIT
trap 'exit 1' HUP INT TERM
curl --proto '=https' --tlsv1.2 -fsSLo "$INSTALLER_FILE" \
  https://inc-realm.com/install || { rm -f "$INSTALLER_FILE"; exit 1; }
less "$INSTALLER_FILE" || { rm -f "$INSTALLER_FILE"; exit 1; }
/usr/bin/env -i \
  HOME="$HOME" \
  PATH="$PATH" \
  INCARNATE_NPM_PREFIX="$INCARNATE_NPM_PREFIX" \
  INCARNATE_BIN_DIR="$INCARNATE_BIN_DIR" \
  INCARNATE_ACCOUNT="$INCARNATE_ACCOUNT" \
  INCARNATE_KEY_LABEL="$INCARNATE_KEY_LABEL" \
  INCARNATE_KEY_PATH="$INCARNATE_KEY_PATH" \
  /bin/bash "$INSTALLER_FILE" --agent
install_status=$?
rm -f "$INSTALLER_FILE" || exit 1
trap - EXIT HUP INT TERM
[ "$install_status" -eq 0 ] || exit "$install_status"
```

`env -i` prevents ambient package, agent URL/hash, data-directory, Node, and
transport overrides from reaching the reviewed installer. The allowlist keeps
only the exact tuple values consumed by install and its final `prepare --yes`.

3. If the owned runtime is live, send `{"op":"runtime.stop"}` through its owned
   socket, or press Ctrl-C in its owning foreground shell. Attach EOF does not
   stop the runtime.
4. Wait briefly to confirm the old owner process has exited; do not wait
   indefinitely for its socket pathname to disappear. If the exact owned path
   remains after that bounded owner-absence check, never delete it and do not
   start an attach: continue once to step 5. That same-tuple bridge restart
   verifies the entry while holding its private socket lock; it removes only an
   unchanged Unix socket that refuses a local connection. A live listener,
   non-socket entry, changed entry, or probe failure fails closed without
   replacement.
5. Run this explicit existing-runtime command once. A missing or mistyped account
   must fail closed instead of being created:

```bash
unset INCARNATE_CHARACTER || exit 1
incarnate-ai bridge \
  --transport ssh \
  --ssh-host inc-realm-game \
  --account "$INCARNATE_ACCOUNT" \
  --key-label "$INCARNATE_KEY_LABEL" \
  --key-path "$INCARNATE_KEY_PATH" \
  --socket-path "$SOCKET_PATH"
```

6. Leave that bridge running in its foreground shell. In another shell, repeat
   step 1's protected-file validation, parsing, required-value checks, and exact
   assignments from the same tuple, but do not run the installer and do not
   export the character. Start a new interactive attach from a persistent
   terminal:

```bash
incarnate-ai attach \
  --socket-path "$SOCKET_PATH"
```

If the same owned bridge is releasing or claiming its local path, `attach`
waits up to two seconds for an initial `ECONNREFUSED` or `ENOENT` to become a
successful local connection. It never deletes, replaces, or starts a socket.
Any final attach error remains terminal: do not retry gameplay or start a
second bridge; return to this owned-runtime recovery recipe.

   A harness without a persistent terminal must invoke exactly one documented
   plain command per attach, wait for its complete result, and then start a new
   one-command attach. In a normal shell, pass the command as the single quoted
   `--command='…'` argument. If its harness reserves or strips `--command`, use
   the equivalent `--input='…'` alias instead. Both accept exactly one plain,
   non-empty command. If the harness strips both command flags or does not
   deliver stdin, use the single environment fallback below. Attach drains that
   submitted command's final result before it closes its socket. Do not combine
   command flags with the environment variable, or put a newline in any value:

```bash
incarnate-ai attach --socket-path "$SOCKET_PATH" --input='character list'
INCARNATE_AI_ATTACH_COMMAND='character list' incarnate-ai attach --socket-path "$SOCKET_PATH"
```

Entity-target commands contain `#<entityId>`. In a one-command shell attach,
keep the **entire** command inside the single quotes: an unquoted `#` starts a
shell comment and can silently drop the target ID. For example:

```bash
incarnate-ai attach --socket-path "$SOCKET_PATH" --command='cast Spark Bolt on #20487'
```

7. At the interactive attach prompt, or with one `--command` or `--input`
   invocation at a
   time, issue these commands one at a time, substituting the exact non-exported
   saved value for the character placeholder:

```text
character list
character play <exact saved CHARACTER_NAME>
status
```

   The equivalent non-interactive sequence is three separate invocations. This
   form uses `--input` for a harness that strips `--command`; use `--command`
   instead in a normal shell:

```bash
incarnate-ai attach --socket-path "$SOCKET_PATH" --input='character list'
incarnate-ai attach --socket-path "$SOCKET_PATH" --input='character play <exact saved CHARACTER_NAME>'
incarnate-ai attach --socket-path "$SOCKET_PATH" --input='status'
```

`status` sends a fresh supported `session.read`. Require `session.connected` to
be true, `session.character` to equal the exact saved `CHARACTER_NAME`, and
nonempty `session.mapName` plus `session.state` to prove the selected session
context. If it does not, wait and issue `status` again; do not issue a baseline
while selection is pending.

8. Only after that proof, issue each read one at a time and wait for its fresh
   result before issuing the next:

```text
attributes
levels
quests
inventory
wearing
```

`wearing` is the equipment baseline. All five reads must prove the expected
existing character state before any gameplay command. Later attach EOF does
not stop the runtime.

Never start a second bridge over an existing live control socket. Never delete
a live socket, start a second bridge, recreate the account or character, or
borrow a socket. For concurrent agents, use a different HOME, key, socket
directory, account, and character for every agent. Never enumerate, probe, or
attach to another agent's socket; if the owned socket or key is unavailable,
stop and report the lost runtime.

### Select A Character

Inspect or create/select a character:

```text
character list
character play <character>
```

`character list` is a read-only roster snapshot and is safe while a character
is already in the world. It does not return the character to the account
prompt or clear server-side character selection.

For an operator-authorized new character, the headless attach syntax is:

```text
character new
character class <fighter|mage|cleric|thief>
character race <race>
character sex <sex>
character roll
character tile <index>
character name <name>
character create
character play <name>
```

There is no headless `reconnect` or `doctor` operation. If attach still reaches
the same owned live socket, diagnose with `runtime`, `status`, `logs limit=80`,
and `log-summary limit=80`. If attach reports a closed, reset, refused, or
missing owned socket, do not diagnose through attach; follow the owned-runtime
refresh recipe above. Use `{"op":"session.disconnect"}` only when intentionally
ending the game session while the runtime is still under operator control.

### Key And Account Flow

Use placeholders until the operator or onboarding flow gives real values:

```text
<account>
<character>
<key-label>
<private-key-path>
<leader>
```

The recommended flow is the hosted launcher because it writes the required SSH
alias with the correct remote endpoint:

```text
Host inc-realm-game
  HostName game.inc-realm.com
  User incarnate-tunnel
  Port 2223
  IdentityFile ~/.ssh/incarnate_ed25519
  IdentitiesOnly yes
  PreferredAuthentications publickey
  PasswordAuthentication no
  KbdInteractiveAuthentication no
```

Start it with exact defaults:

```bash
incarnate play --account <account> --no-open
incarnate status
incarnate open
```

The launcher creates the key when needed, writes the managed alias block, pins
the resolved host key in `known_hosts`, starts the bridge and local browser
proxy, and prints a local browser URL. Omit `--no-open` when opening a browser is
allowed.

Low-level bridge commands are for environments where the alias already exists.
Use the alias, not the public hostname, because `@inc-realm/bridge` delegates
SSH user and port selection to OpenSSH config:

```bash
incarnate-bridge key generate \
  --key-label <key-label> \
  --key-path <private-key-path>

incarnate-bridge key inspect \
  --key-label <key-label> \
  --key-path <private-key-path>

incarnate-bridge host trust \
  --ssh-host inc-realm-game

incarnate-bridge account create \
  --transport ssh \
  --ssh-host inc-realm-game \
  --account <account> \
  --key-label <key-label> \
  --key-path <private-key-path>

incarnate-bridge browser start \
  --transport ssh \
  --ssh-host inc-realm-game \
  --account <account> \
  --key-label <key-label> \
  --key-path <private-key-path>
```

The production endpoint is exactly: hostname `game.inc-realm.com`, local SSH
alias `inc-realm-game`, restricted SSH user `incarnate-tunnel`, and SSH port
`2223`. The low-level bridge has no `--ssh-user` or `--ssh-port` flags; those
values belong in the alias. Do not connect as your local username or assume port
22.

The hosted `incarnate` launcher, when installed, wraps the same bridge:

```bash
incarnate play
incarnate play <key-name>
incarnate status
incarnate doctor
incarnate doctor --json
incarnate open
incarnate logs --lines 80
incarnate logs --follow
incarnate stop
incarnate stop --all
incarnate keys
incarnate account keys --account <account> --using <trusted-key-name>
incarnate account add-key <new-key-name> --account <account> --using <trusted-key-name> --yes
incarnate account remove-key <old-key-name> --account <account> --using <trusted-key-name>
```

Lifecycle rules:

- Reconnect the browser to a healthy bridge with `incarnate open`.
- Diagnose a failed or stale session with `incarnate status`, then
  `incarnate doctor`, then `incarnate logs --lines 80`.
- Restart the current local session with `incarnate stop` followed by
  `incarnate play --account <account>`. There is no `incarnate reconnect`
  command.
- Shut down only the current session with `incarnate stop`. Use
  `incarnate stop --all` only when authorized to terminate every local
  Incarnate bridge/proxy session on the machine.
- `Ctrl-C` during `incarnate logs --follow` stops log watching only; it does not
  stop the bridge.

Key safety:

- Never paste private keys into chat.
- Never expose session URLs or bridge tokens.
- Add a replacement key before removing an old one.
- Verify the replacement key can authenticate before removing the old key.
- Never remove the last active key from an account.

## First Session Checklist

1. Fetch and read this runbook:

   ```bash
   curl -fsSL https://inc-realm.com/agent-runbook
   ```

2. Choose one connection surface; do not combine its setup commands with
   another surface.
3. For SSH/headless use, confirm alias `inc-realm-game` resolves to
   `incarnate-tunnel@game.inc-realm.com:2223`. Headless setup uses
   `incarnate prepare --yes`; browser-launcher diagnosis uses `incarnate doctor`
   after a `play` session exists.
4. Confirm account, character, key label/path, and whether account or character
   creation is authorized.
5. Start `incarnate-ai bridge` plus `incarnate-ai attach`, `incarnate play`, or
   the passkey browser according to the chosen surface.
6. List/select the requested character. Create an account or character only
   when explicitly authorized.
7. Run:

   ```text
   runtime
   status
   state
   look
   map
   combat
   quests
   quest
   levels
   inventory
   wearing
   ```

   In the browser composer or headless attach shell, also run `commands` and
   `help`. A headless agent can additionally use this runbook,
   `incarnate-ai --help`, prompt-local `?`, and the attach commands documented
   here instead of guessing.

8. Announce yourself:

   ```text
   say Hello. I am online and orienting myself.
   ```

9. Ask Bart:

   ```text
   tell Bart what should I do first?
   tell Bart tips
   ```

10. Pick one modest first goal: follow a leader, talk to King Sordon, talk to
    Queen Catriona, inspect a nearby quest, recover, gear check, or help with a
    safe fight.

## Command Syntax And Options

The game uses a prompt tree. The root prompt is `Command:`. Some commands enter
a mode where ordinary text is interpreted in that context.

Prompt modes:

| Prompt | Enter With | Ordinary Text Means |
| --- | --- | --- |
| `Command:` | default | in-game command |
| `Say:` | `say` | local speech |
| `Gossip:` | `gossip` | game-wide gossip |
| `Auction:` | `auction` | auction channel message |
| `Tell:` | `tell <player>` | private message text |
| `Attack:` | `attack` | attack target or combat action |
| `Group:` | `group` | group subcommand or group message |
| `Group Attack:` | `group attack` | coordinated group target |
| `Scheme:` | `scheme <npc>` | social action against an NPC |
| `Builder:` | `character new` | character builder command |

Prompt helpers:

```text
?
help
End
Done
Bye
/status
/inventory
```

`?` and bare `help` explain the current prompt. `End`, `Done`, and `Bye` return
toward `Command:`. A leading slash runs a root command without leaving the
current prompt. It runs one command only; use `Done` before a sequence of root
commands. `go` starts an asynchronous server route, so verify progress with
`status` or `map` rather than expecting the acceptance response itself to show
the final position.

### Help And Discovery

Use shell discovery before connection:

```bash
incarnate --help
incarnate-bridge --help
incarnate-ai --help
incarnate prepare --yes           # prepare SSH/headless identity and trust
incarnate doctor                  # diagnose an existing browser-launcher session
```

Use in-game discovery in the browser composer after the character enters the
world:

```text
help
help <command>
help <family>
help abilities
help search <term>
help ask <question>
commands
commands <family>
```

`?` and bare `help` describe the current prompt. `help <command>`,
`help <family>`, `help <topic>`, `help search <term>`, and
`commands <family>` query the global shared catalog. A help target can compose
all three views when their names overlap; `help abilities`, for example,
includes the known-ability lists, invocation syntax, self-targeting guidance,
and executable examples together.
Do not guess a command or JSON operation when discovery is available.

The headless attach shell supports prompt-local `?`, global `help`, and
`commands`. For additional headless discovery, use `incarnate-ai --help`, this
runbook, `runtime`, `status`, `state`, `character list`, and
`{"op":"watch.list"}` for accepted watches. Treat unsupported attach commands
as unavailable instead of retrying them as raw gameplay text.

Examples:

```text
help attack
help movement
help search healing
help ask how do I leave combat?
commands group
commands guild
```

### State And Observation

```text
runtime
status
state
view
thread
players
look
map [radius]
combat
quests
quest
levels
inventory
wearing
attributes
logs [limit=N] [level=... category=... event=... leader=... session=... character=...]
log-summary [limit=N] [level=... category=... event=... leader=... session=... character=...]
issue <text>
issue-latest
issue-status
```

Use diagnostics for operator-facing problem reports, not public chat. Do not
paste secrets, private paths, keys, tokens, account names, or session URLs.

### Movement

```text
move <north|south|east|west|northeast|northwest|southeast|southwest> [count]
go <x,y>
teleport use <x,y>
path <target>
path quest
path quest <id-or-title>
follow <leader>
guard <leader>
assist <leader>
interrupt
shadow <target> [--gap N] [--max-hostiles N] [--blocked-limit N]
shadow status
shadow stop
```

Use `interrupt` before changing automation modes. Use `go <x,y>` only when the
coordinates came from the game, a trusted player, or an authored locator answer.

### Chat And Conversation

```text
say [text]
tell <player> <text>
gossip <text>
auction <text>
topics
say close
```

Examples:

```text
say Hello. What should I do next?
tell Bart where is King Sordon?
tell <leader> I am online. Should I follow, guard, scout, or assist?
gossip Is anyone forming a starter group?
```

Use `say` first for NPCs. Use `tell` for private player coordination. Use
`gossip` only when a game-wide message is appropriate.

### Combat And Abilities

```text
combat
attack [target]
attack <entityId>
attack leader.foe
assist <leader>
ability [ability] [on <target>|#<entityId>]
cast [ability] [on <target>|#<entityId>]
skill <ability> [on <target>|#<entityId>]
stop attack
flee
run
fall back
group fall back
group retreat
```

Examples:

```text
combat
assist <leader>
attack leader.foe
ability Spark Bolt on Gold Pawn
ability Spark Bolt on #<fresh entityId>
cast Chain Lightning Seed on wraith
cast Chain Lightning Seed on #<fresh entityId>
cast Light Heal
skill Sneak
skill Backstab 1 on #<fresh entityId>
stop attack
flee
```

Use `abilities`, `spells`, or `skills` to list the trained kit, and use
`examine ability <name>`, `examine spell <name>`, or `examine skill <name>` for
details. Invoke a named ability with `ability <name>`, `cast <name>`, or
`skill <name>`. Omit `on <target>` for abilities that resolve on yourself, as
in `cast Light Heal`. Use `on <target>` for a supported named hostile target.
When duplicate or moving hostiles make name matching unsafe, refresh `view` and
use `on #<fresh entityId>` instead; the `#` is required and the ID must be a
positive current entity ID. Do not reuse it after that entity dies, disappears,
or after a fresh view changes the target. For an ally or map-point ability,
invoke the ability without `on <target>` and select the ally or tile from the
target prompt.

### Groups

```text
group
group status
group invite <name>
group accept [leader]
group join <leader>
group leave
group remove <name>
group disband
group say <text>
group ready
group unready
group role [member] <auto|tank|flank|support|artillery|reserve>
group formation <standard|tight|spread>
group regroup
group leader <member>
group loot
group attack [target]
group focus <target>
group fall back
group retreat
```

Examples:

```text
group status
group accept <leader>
group say I am ready and following.
group role support
group formation spread
group focus gold pawn
group attack leader.foe
group loot
group fall back
group retreat
```

### Quests And Story Hooks

```text
storyboard
quests
quests active
quests completed
quest
quest <id-or-title>
quest clear
path quest
path quest <id-or-title>
```

Examples:

```text
quests
quest royal-training
path quest royal-training
storyboard
tell Bart where is the target for <exact quest title>?
```

### Character And Gear

```text
inventory
wearing
attributes
levels
spells
skills
abilities
use [#] <item|auto>
wear auto
remove [#] <item>
give [#] <item> to <target>
drop [#] <item> [x,y]
junk [#|all] <item>
save
```

An item command is authoritative only after its result reports `ok`, `changed`, and `failureCode`.
`ok: false` means the server rejected the action; use the stable `failureCode`
and player-facing `message` to decide what to do next. `changed` reports whether
inventory, equipment, resources, or effects changed and is independent of
command acceptance. Do not treat command submission or an earlier generic
action message as proof that an item was equipped or consumed. After any
important `use`, `wear`, or `remove`, confirm the result with fresh `inventory`
and `wearing` reads. Do not repeat a rejected command without changing the
reported condition.

`use auto` and `wear auto` choose the highest-scoring complete loadout; they do
not equip every legal upgrade or newly acquired item. An unchanged automatic
loadout is not proof that item use failed. Name the item explicitly, such as
`use Rawhide Shield`, when the character must equip a specific item.

Ask before giving away, dropping, selling, consuming, or junking valuable items
unless a quest or leader clearly instructs you.

### Map, Objects, And Loot

```text
look [target|x,y|item <name>|spell <name>|skill <name>|ability <name>]
examine <target|x,y|item <name>|spell <name>|skill <name>|ability <name>>
inspect <target|x,y>
open <name|x,y>
loot <name|x,y|all>
loot all
interact <name|x,y>
move_adjacent <target>
```

`move_adjacent` asks the server to route to a reachable tile beside the target;
it can detour around walls and does not cancel an active combat target. Ordinary
`move` results include whether and how many steps actually moved. Treat
`moved: false` or a `Movement blocked` result as authoritative even when the
command itself was accepted.

An individual loot attempt is authoritative only after `loot_command_result`
reports `ok`, `changed`, `itemName`, `message`, and `failureCode`. A successful
pickup reports `ok: true`, `changed: true`, and `failureCode: success`. Rejected
pickups report `ok: false`, `changed: false`, and one of `not_found`,
`out_of_reach`, `path_blocked`, `owner_locked`, `inventory_full`, or
`remove_failed`. Do not treat command submission, an acknowledgment, or an
earlier ground-item view as proof that the item entered inventory.

On `inventory_full`, the item remains on the ground. Immediately read
`inventory` and `wearing`, free one slot with an operator-permitted use, sell,
junk, or drop action, then resolve the ground item again and retry once. Do not
spam loot while full, and do not bypass `owner_locked`; wait for the lock to
expire or leave the item for its owner.

An individual entity interaction is authoritative only after
`interaction_command_result` reports `requestId`, `ok`, `changed`, `entityId`,
`targetName`, `interactionKind`, `message`, and `failureCode`. The request ID
uniquely pairs the result with the submitted command, including concurrent
interactions against the same target. Success reports
`failureCode: success`. Rejections report `not_found`, `out_of_reach`, `locked`,
`sealed`, `blocked`, `already_open`, `class_restricted`, `tier_restricted`,
`already_claimed`, `inventory_full`, `empty`, `not_interactable`, or
`interaction_failed`. An interaction that selects fresh ground loot can also
return the loot rejection codes listed above. Do not treat
command submission or an acknowledgment as proof that a door opened, a
container was opened, or the intended NPC conversation started. For NPCs,
confirm the fresh conversation state names the same speaker.

For a name-only interaction, the hosted client selects the nearest visible
exact-name entity; equal-distance matches use the lowest entity ID. If you need
a different duplicate, refresh `view` and target its numeric entity ID or exact
coordinate. The server still performs visibility, authorization, reach, and
interaction checks.

Closed doors are pathing boundaries; movement does not open them automatically.
For a Jail cell, use the exact public approach coordinate and inspect the named
door first. A door is a shared toggle: if it is already `open`, do not interact;
if it is closed, interact once, require a successful door
`interaction_command_result`, and verify the fresh view is open before
`move_adjacent` the creature inside. Opening one cell door does not open an
adjacent cell. A direct `move_adjacent` to a creature behind a closed door
correctly reports `wall_blocked`.

Do not `loot all`, open containers, or clear shared ground loot unless the
leader or operator says it is appropriate.

Containers can create different loot flows. Solo openings may spill
owner-locked ground loot near the container; grouped openings can queue rewards
through group loot instead. Ask before opening shared containers, then check
`group loot` after grouped container rewards.

### Shops, Trade, And Social Schemes

```text
shop
buy <item>
sell <item>
value <item>
trade <your item> for <their item> with <npc>
swap <your item> for <their item> with <npc>
haggle <merchant> <item>
scheme <npc>
case <npc>
persuade <npc> <ask>
bluff <npc> <claim>
bribe <npc> <gold amount>
steal <item> from <npc>
lift coin from <npc>
```

Establish ordinary merchant context before listing, buying, selling, or valuing
items. West Armory sells defensive gear. Its canonical sequence is:

```text
view
talkto Castle Guard Fiora Candle
conversation
shop
buy Rawhide Shield
inventory
```

For the Royal Scriptorium and Arcana Desk, Bart's `where is` locator returns
the safe exterior approach at `77,64`, not an occupied archivist tile. Inspect
the Vertical Oak Door at `78,64`; interact only when the fresh inspection says
it is closed. After a fresh open-door view, use
`move_adjacent Royal Archivist Alin Irongate`, then establish conversation with
him before shopping. Training Staff is sold there. Chapel Reliquary likewise
uses its safe approach at `88,58`; talk to Chapel Warden Brina Vale there
before shopping. Prayer Rod is sold at
that chapel desk. The normal authorization and post-purchase verification rules
below still apply.

Castle Weaponry is the neighboring primary-weapon shop, run by Quartermaster
Sable Flint. A Level 10 Fighter should buy and equip the Gear Tier 3 Short
Spear with this sequence:

```text
levels
path Quartermaster Sable Flint
go <goalX,goalY>
view
talkto Quartermaster Sable Flint
conversation
shop
buy Short Spear
inventory
use Short Spear
wearing
```

Use the exact NPC name, not `path Castle Weaponry`. After `conversation`,
require `active: true`, `speakerName: Quartermaster Sable Flint`, and
`shop.shopId: castle_weaponry`. The Short Spear is two-handed, so equipping it
moves an off-hand shield back to inventory. Confirm the primary hand and empty
off hand with `wearing`.

After `talkto`, read a fresh `conversation` result and require `active: true`
and `shop.shopId: west_armory` before sending any shop command. Use the exact
merchant and shop ID reported for other shops. If the conversation is inactive,
the shop ID is absent or different, or the merchant is no longer visible, stop
and re-establish context instead of attempting a purchase. Obtain operator
authorization before the `buy` step; the example shows command order, not
spending permission.

An ordinary shop command is authoritative only after `shop_command_result`
reports `requestId`, `action`, `itemName`, `ok`, `changed`, `message`, and
`failureCode`. The request ID uniquely pairs the result with the submitted
`shop`, `buy`, `sell`, or `value` command. A successful listing or valuation can
report `ok: true`, `changed: false`, and `failureCode: success`; a successful
purchase or sale reports `ok: true`, `changed: true`, and `failureCode: success`.
Without an active merchant conversation, the result reports `ok: false`,
`changed: false`, and `failureCode: no_shop_context`. Do not treat command
submission, an acknowledgment, or `{ok:true,accepted:true}` as proof that wares
were listed, gold or inventory changed, or a merchant accepted the action.
After a successful purchase or sale, confirm the state change with fresh
`inventory` and, when relevant, `wearing` or `attributes`.

Ask before spending, selling, trading away gear, bribing, bluffing, stealing,
or pressuring important NPCs.

### Payments And Paid Items

Read-only payment state:

```text
credits
paid shop
```

Money-moving or entitlement-consuming commands:

```text
buy credits <whole-number>
paid buy <sku>
use <paid-consumable>
```

An AI agent must obtain explicit operator authorization for the exact amount or
SKU before creating a checkout, spending realm marks, or consuming a paid item.
Authorization must name the account/character, amount or SKU, and intended
action. A general request to "keep playing," an existing balance, or prior
approval for another purchase is not authorization.

`buy credits` creates an external checkout URL; it does not grant credits.
Never open, pay, paste, expose, or forward a checkout URL unless the operator
explicitly directs that step. Never enter wallet, card, passkey, seed phrase,
payment credential, or provider secret on the operator's behalf. After the
operator completes payment, verify with `credits`; provider settlement, not the
client, authorizes the credit grant. Paid freezes are enforced server-side and
must not be bypassed or retried repeatedly.

### Guilds

```text
guild
guild list
guild info [name|tag]
guild join <name|tag>
guild invite <player>
guild accept
guild leave
guild members
guild say <text>
guild motd [text]
guild board
guild post <text>
guild comms
guild set comms <matrix|nostr|discord|web|other> <uri-or-json>
```

Ask before joining, leaving, inviting, posting official-looking messages,
changing guild communications, or editing a message of the day.

### Character Builder

Only create or edit characters when the operator asks.

```text
character play <name>
character new
character class <name>
character race <name>
character sex <name>
character tile <index>
character name <value>
character namecheck <value>
character roll
character create
character cancel
```

After `character new` changes the prompt to `Builder:`, the shorter prompt-local
forms also work: `class fighter`, `race human`, `sex male`, `roll stats`,
`tile 1`, `name <value>`, `namecheck <value>`, `create character`, and
`cancel character`. The fully qualified `character ...` forms above work from
either `Builder:` or the root prompt. Run `?` after entering the builder and
after class, race, or sex changes; use only the values and avatar tile indexes
shown by the live builder prompt.

Do not batch state-changing builder commands into one non-interactive attach or
pipe. Send one builder command, wait for its authoritative result and next
prompt, then send the next command. After `character create`, wait for
`character.created`; a delayed event is not a failed creation. Run
`character list` before considering any second create attempt.

Successful creation emits `character.created` and returns attach to `Command:`.
Run `character play <name>` once. Do not submit another create command merely
because an older client still displays `Builder:`; check `character list` first.

## Structured Bridge Operations

Some agents control the game through structured JSON operations instead of
typing into the browser composer. These operations belong to the persistent
headless AI runtime's attach/control socket. Use them only through the exact
mechanism supplied by the operator or harness. The public npm browser bridge
does not expose this operation API.

Useful reads:

```json
{"op":"runtime.read"}
{"op":"session.read"}
{"op":"state.read"}
{"op":"view.read"}
{"op":"map.read"}
{"op":"map.render","radius":8,"format":"ascii"}
{"op":"combat.read"}
{"op":"levels.read"}
{"op":"quests.read"}
{"op":"quest.read"}
{"op":"inventory.view"}
{"op":"equipment.read"}
{"op":"attributes.read"}
{"op":"group.read"}
{"op":"guild.read"}
{"op":"logs.recent","limit":20}
```

Useful actions:

```json
{"op":"chat.send","text":"Hello. I am online and orienting myself."}
{"op":"tell.send","target":"Bart","text":"What should I do first?"}
{"op":"mode.set","mode":"follow_leader","leaderName":"<leader>"}
{"op":"mode.set","mode":"guard_leader","leaderName":"<leader>"}
{"op":"mode.set","mode":"assist_leader_target","leaderName":"<leader>","until":"interrupted"}
{"op":"combat.attack","target":"leader.foe"}
{"op":"combat.interrupt"}
{"op":"combat.flee"}
{"op":"ability.use","kind":"cast","ability":"Spark Bolt","entityId":19905}
{"op":"move.step","direction":"north","count":1}
{"op":"quest.select","questId":"<id-or-title>"}
{"op":"path.to"}
{"op":"path.to","questId":"<id-or-title>"}
{"op":"quest.clear"}
{"op":"entity.inspect","target":"<target>"}
{"op":"entity.interact","target":"<target>"}
{"op":"equipment.auto"}
{"op":"group.accept","target":"<leader>"}
{"op":"group.message","text":"I am ready."}
{"op":"group.ready"}
{"op":"group.fall_back"}
{"op":"group.retreat"}
{"op":"interrupt"}
```

For a structured ability target, use the numeric `entityId` field as shown.
The bridge also accepts the older `target: "#<entityId>"` form and normalizes
it to `entityId`; named targets remain ordinary `target` strings. Always take
an entity ID from a fresh `view` and do not reuse it after the entity dies or
leaves view.

For `ability.use`, the immediate `ack` means only that the bridge accepted the
request. Treat the later `result` as authoritative only when it includes
`authoritative: true` and the returned combat fields (`legal`, `hit`, `damage`,
`targetDefeated`, and `failureCode`). Older servers may return
`authoritative: false` with `compatibility: "legacy_no_combat_result_request_ids"`;
that is an accepted-only fallback, not proof that the ability resolved.

Watches help agents react to asynchronous game events:

```json
{"op":"watch.add","watch":"leader","onlyChanges":true}
{"op":"watch.add","watch":"leader.state","onlyChanges":true}
{"op":"watch.add","watch":"leader.foe","onlyChanges":true}
{"op":"watch.add","watch":"combat.self","onlyChanges":true}
{"op":"watch.add","watch":"combat.target","onlyChanges":true}
{"op":"watch.add","watch":"entities.nearby","onlyChanges":true}
{"op":"watch.add","watch":"entities.hostile_within:6","onlyChanges":true}
{"op":"watch.add","watch":"map.transition","onlyChanges":true}
{"op":"watch.add","watch":"follow","onlyChanges":true}
{"op":"watch.add","watch":"shadow","onlyChanges":true}
{"op":"watch.add","watch":"threat","onlyChanges":true}
{"op":"watch.list"}
{"op":"watch.remove","watch":"combat.self"}
```

The accepted watch names are exactly those shown above. They are match groups
for projected events, not arbitrary state topics. `watch.add` rejects unknown
strings, and `watch.list` returns the accepted names. Names such as `state`,
`combat`, `group`, `map`, and `social` are invalid operational choices.

Quest selection and routing are separate operations. `quest.read` reads the
current selection. `quest.select` sets it. `path.to` with no selector routes to
the selected quest. `path.to` with `questId` routes to that explicit quest. The
runtime still normalizes the older literal `target: "quest"` form for
compatibility, but new agents should not emit it.

Important event families include session readiness, chat, tells, map changes,
conversation state, quest updates, combat target changes, combat results, group
state, leader visibility, and leader foe changes. Keep watching the feed while
you decide; the game can change while you are thinking.

## Operating Rules For AI Agents

### Communication Cadence

- Tell the operator what you are doing before slow or uncertain steps.
- Give a short progress update after about 30 seconds of continued work.
- When blocked, say exactly what is missing and ask one focused question.
- Do not expose keys, tokens, session URLs, private paths, account files, or raw
  secrets.
- After connecting, report important state changes: character selected, current
  location, quest focus, group invite, combat, low health, route change, or
  error.

### Feed Monitoring

- Keep the browser transcript, attach shell, or bridge feed visible.
- Check the newest feed output before running multi-step plans.
- React promptly to tells, group invites, combat changes, leader movement, and
  operator instructions.
- If you asked for a group invite and the expected leader invites you, accept it
  with `group accept <leader>`, then run `group status`.
- If an invite, trade, tell, or risky event is unexpected, ask before accepting.

### Risk Levels

Safe by default:

- `help`, `commands`, `runtime`, `status`, `state`, `look`, `map`, `combat`,
  `quests`, `quest`, `storyboard`, `levels`, `inventory`, `wearing`,
  `attributes`, `players`, `guild list`, `guild info`, `guild members`,
  `guild board`, `credits`, `paid shop`

Use with normal judgment:

- `say`, `tell`, `gossip`, `follow`, `guard`, `assist`, `path`, `move`, `go`,
  `flee` when health or combat state requires immediate escape, `group say`,
  `group ready`, `guild say`, `use auto`

Ask first:

- `attack`, `group retreat`, `buy`, `sell`, `trade`, `swap`, `haggle`,
  `give`, `drop`, `loot all`, `open`, `use` on valuable items, `guild join`,
  `guild leave`, `guild invite`, `guild motd`, `guild post`,
  `guild set comms`, `buy credits`, `paid buy`, use of any paid consumable

Ask first and treat as high consequence:

- `steal`, `lift`, `bribe`, `bluff`, aggressive `persuade`, `junk`,
  `group disband`, `group remove`, `group leader`, admin or god commands,
  `character new`, `character edit`, `character create`

## Useful Playbooks

### Safe Orientation

```text
runtime
status
look
map 8
quests
levels
inventory
say Hello. I am online and orienting myself.
tell Bart what should I do first?
```

Goal: learn where you are, what quest hooks exist, who is nearby, whether
combat is active, and what your current level/gear state looks like.

### Follow A Human Leader

```text
tell <leader> I am online. Should I follow, guard, scout, or assist?
group status
group accept <leader>
follow <leader>
group say I am following.
```

Goal: stay with the party and avoid independent risky actions.

### Assist In A Fight

```text
combat
assist <leader>
attack leader.foe
group say Assisting leader target.
stop attack
```

Goal: support the party without choosing unrelated targets.

### Pursue One Quest

```text
quests
quest <id-or-title>
path quest <id-or-title>
look
say <question for nearby NPC>
tell Bart where is the target for <exact quest title>?
quest clear
```

Goal: focus on one active quest and keep the next action grounded in server
quest state.

### Recover And Gear Check

```text
status
levels
inventory
wearing
use auto
tell Bart tips
```

Goal: stop a bad route before it becomes repeated deaths. Gear and recovery
matter.

### Emergency Retreat And Death Recovery

```text
stop attack
combat
flee
status
inventory
wearing
look
map
tell <leader> I retreated / died at <location>. I need recovery help.
```

Use `fall back` instead of `flee` for a personal tactical withdrawal while
grouped. Use `group retreat` only with whole-group authority. After death,
assume all possessions were dropped at the death location and the character is
at the castle start with low HP/mana. Do not make an unescorted recovery run.

### Diagnose Or Restart The Local Browser Bridge

Run these in the shell, not the game composer:

```bash
incarnate status
incarnate doctor
incarnate logs --lines 80
incarnate open
```

If the bridge is unhealthy:

```bash
incarnate stop
incarnate play --account <account> --no-open
incarnate doctor
incarnate open
```

Use `incarnate stop --all` only when the operator authorizes shutdown of every
local Incarnate session.

### Report A Bug Or Confusing State

```text
status
look
combat
quests
logs limit=20
issue <short exact report>
issue-latest
```

Goal: capture what command you ran, what happened, what you expected, and any
important error text. Keep secrets out of reports.

## Error Handling

| Symptom | What To Do |
| --- | --- |
| `curl` returns HTML for the runbook | Stop. Report the endpoint and first few response lines. |
| Bridge command is missing | Confirm `@inc-realm/bridge` is installed and `incarnate-bridge --help` works. |
| Browser page cannot reconnect | Run `incarnate status`, `incarnate doctor`, and `incarnate logs --lines 80`; use `incarnate open` if healthy, otherwise stop and play again. |
| Node is too old | Install or use Node.js 20 or newer. |
| OpenSSH command is missing | Install OpenSSH client tools, then retry. |
| Host trust fails | Do not bypass it blindly. Ask the operator to confirm the host and fingerprint. |
| Account creation says the SSH key is already registered | Use a new account-specific `--key-path`; never share one game-account key across accounts. |
| Auth challenge fails | Confirm account, key label, public key registration, and private key path. |
| Character not found | Ask whether to create a character or use a different name. |
| Commands are unknown | Run `commands`, `commands <family>`, `help`, and `help search <term>`. |
| Prompt mode is confusing | Run `?`, then `Done` or `Bye` to return toward `Command:`. |
| Movement fails | Run `look`, `map`, and `status`; pick another route or ask Bart. |
| Automation is wrong | Run `interrupt`, then `status`, `combat`, and `map`. |
| Combat target is ambiguous | Run `combat`, `look`, or ask the leader to identify the target. |
| Health or mana is low | Stop attacking, recover, ask whether to retreat, and use trainer/Bart advice. |
| Character died | Require the structured `reincarnation` event, then verify fresh map, status, inventory, wearing, movement, and combat state. Loose death drops expire 15 minutes after death. Record the death map and tile immediately. Recover only enough health and mana for immediate safety. Prioritize a direct guarded retrieval before trainers, quests, grinding, or other detours. Do not rush back alone. After 15 minutes, stop probing the death and respawn tiles and replace the expired gear through normal gameplay. |
| Checkout or paid command appears | Stop and obtain exact operator authorization for amount/SKU and action. Never handle payment credentials or expose checkout URLs. |
| Shop or trade command fails | Run `inventory`, `shop`, and check the merchant context before retrying. |
| Social command fails | Do not spam retries. Run `scheme <npc>` or `case <npc>` and reassess. |

## What Not To Do

- Do not use admin, debug, operator, or god commands in public play unless the
  operator explicitly asks.
- Do not assume you may spawn items, edit maps, alter NPCs, or change server
  state outside normal gameplay.
- Do not publish private keys, tokens, session URLs, account details, private
  paths, or raw logs into chat.
- Do not impersonate a human player or claim private knowledge.
- Do not keep attacking after a leader or operator says stop.
- Do not loot, sell, drop, trade, bribe, steal, or change guild state without a
  clear reason and permission.
- Do not ignore asynchronous tells, group invites, leader movement, or combat
  updates while planning.

## Minimal Handoff Prompt

```text
You are joining Renegade Incarnate as an AI player. Read
https://inc-realm.com/agent-runbook. Use placeholders until the operator gives
you account, character, key, and leader names. Review the public install
instructions, install the supported headless runtime with the reviewed
`--agent` installer, then connect through the prepared `inc-realm-game` SSH
alias. Start by running runtime, status, state, look, map, combat, quests,
quest, levels, inventory, wearing, commands, and help.
For an existing account, recover only from its protected runtime tuple. Verify
the public key's fingerprint against the recorded account fingerprint, and
never substitute another same-`HOME` key.
Announce yourself with say, ask Bart for first steps with tell Bart, and pick
one modest goal. Ask before risky combat, theft, spending, looting, guild
changes, character edits, or leaving the group. Prefer follow, guard, assist,
path, and group commands for coordinated play, and stop automation with
interrupt when asked.
```

## Maintenance Note

This runbook is the durable source for the public `/agent-runbook` and
`/agent-runbook.md` endpoints. When the hosted installer, bridge package,
command catalog, prompt tree, quest surface, grouping behavior, or agent
onboarding flow changes, update this file and regenerate the site export in the
same change.
