> ## Documentation Index
> Fetch the complete documentation index at: https://runelite.zip/llms.txt
> Use this file to discover all available pages before exploring further.

# Core concepts

> The tick loop, observed state, the action surface, and the runtime gates every n3 plugin follows.

Every n3 plugin follows the same architecture. Learn it once and every playbook, plugin guide, and API page in this documentation reads the same way.

## The 600 ms server tick

The game server advances state on ticks of roughly 600 ms. RuneLite invokes each subscribed `onGameTick` handler once per tick, synchronously on the client thread.

```java theme={null}
@Subscribe
public void onGameTick(GameTick event) {
    // observe, decide, act — at most one meaningful action per tick
}
```

Three rules follow from this model:

1. **One meaningful action per tick.** Never assume dependent actions (withdraw, equip, close bank) complete in one callback. Dispatch one action, return, and observe the result on a later tick.
2. **React, never sleep.** Do not use `Thread.sleep()` or busy-wait loops. Return from the tick handler and let the next tick evaluate the new state.
3. **Observe before you act.** Query inventory, widgets, actors, and objects each tick instead of assuming your last click succeeded.

## Observed state over remembered state

Stateful scripts get stuck: a flag like `isChopping = true` stops matching reality the moment the player is attacked, walks away, or logs out. Query live state each tick and store workflow state only where a pipeline or state machine needs it, reconciled against the game before every action.

```java theme={null}
// Read state through query helpers
Widget bone = Inventory.search()
    .withName("Bones")
    .first()
    .orElse(null);

// Act through the action surface and inspect the result
if (bone != null) {
    InteractionResult result = InventoryActions.use(bone, "Bury");
    if (result.succeeded()) {
        lastFiredTick = currentTick;
    }
}
```

## The two API surfaces

| Surface             | Package                       | Purpose                                                                                                              |
| ------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Read-only queries   | `com.n3plugins.sdk.query.*`   | `Inventory`, `Bank`, `Widgets`, `Dialogue`, `Equipment`, `NPCs`, `TileObjects`, `GrandExchange`, `Shop`              |
| Result-aware writes | `com.n3plugins.Api.actions.*` | `InventoryActions`, `BankActions`, `NPCActions`, `WidgetActions`, `MagicActions`, and the rest of the action catalog |

Every mutating call returns an `InteractionResult` with a status of `CONFIRMED`, `ACCEPTED`, `PACED`, or `FAILED`. Inspect the status at the decision point instead of comparing messages:

```java theme={null}
InteractionResult result = InventoryActions.use("Bones", "Bury");
if (result.failed()) {
    log.debug("Inventory action failed: {}", result.getMessage());
    return;
}
```

A dispatched action is not proof the game state changed. Dispatch means the runtime accepted and routed the request; the transition itself must be observed.

## Choose the right workflow shape

```mermaid theme={null}
flowchart TD
    Start([New automation]) --> Q1{Shape of work?}
    Q1 -->|Single observed action| A["Api.actions call per tick"]
    Q1 -->|Fixed sequential steps| B[TaskPipeline]
    Q1 -->|Branching, loops, recovery| Q2{Standard domain flow?}
    Q2 -->|Yes| C["Domain workflow builder (bank, combat, production, enchanting)"]
    Q2 -->|No| D[TypesafeCarouselStateMachine]
```

* A single interaction needs no framework: check the condition, call one `Api.actions` method, inspect the result.
* A fixed sequence (open bank, withdraw, close) fits [TaskPipeline](/guides/automation-loop).
* Branching workflows with recovery and terminals fit the carousel state machine or a domain workflow builder.

## Runtime gates

The shared runtime, `PacketUtilsPlugin`, gates every action your plugin dispatches. It stays enabled, owns revision validation, menu dispatch, walker ticking, action pacing, and input locking, and it fails closed when the client, bundled evidence, and reflection shape disagree.

| Gate            | What it does                                                        | What your code sees                 |
| --------------- | ------------------------------------------------------------------- | ----------------------------------- |
| Revision health | Validates the client revision against bundled evidence              | Actions fail closed on a mismatch   |
| Action pacing   | Two-layer variance: a 1-3 tick gate plus bounded millisecond jitter | `PACED` result while cooling down   |
| Input lock      | Blocks dispatch while a lock is held                                | `INPUT_LOCKED` result               |
| Break handler   | Planned breaks pause active automations                             | `shouldBreak()` gate before actions |

Because pacing and locking are suite-level, plugins never implement their own cooldown timers for shared actions. When your plugin responds to transient state that already limits firing (a dialogue widget only exists while dialogue is open), a per-tick guard is enough:

```java theme={null}
private int lastFiredTick = -1;

@Subscribe
public void onGameTick(GameTick event) {
    int tick = client.getTickCount();
    if (tick == lastFiredTick) return;   // at most one per server tick
    // handle the transient widget, then:
    lastFiredTick = tick;
}
```

## Break handler contract

Plugins that run full automation register with the shared break handler:

* Register on startup, and call `startPlugin` only while active automation runs.
* Check `shouldBreak()` before game interactions; when a break is due, yield.
* Release input locks while paused, breaking, or stopped.
* Always-on helper plugins (Dialogue Helper, Upkeep) register no break handler and hold no input lock.

The [humanization and safety playbook](/guides/humanization-safety) covers the full lifecycle.

## IDs and widgets come from registries

Never hardcode item, NPC, object, or widget identifiers. Resolve them from:

1. `net.runelite.api.gameval.ItemID` for items
2. `IdMapRegistry` for revision-aware entity and item mappings
3. `WidgetCatalog` for revision-pinned widget discovery

## Runtime status and diagnostics

`Api.debug.SuiteRuntimeStatus.snapshot()` returns a read-only snapshot: the revision health log, expected and live client revisions, walker state, and pacer status. Use it to explain blocked states instead of guessing. The shared sidebar exposes the same information in the Suite status tab.
