Skip to main content
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.
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.

The two API surfaces

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

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

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