Skip to main content

n3Plugins RuneLite Development Contract

This document defines default implementation standards for n3Plugins RuneLite development.

Live repository architecture takes precedence when it differs.


1. Platform Baseline

n3Plugins is a Java 11 RuneLite external-plugin suite.

Requirements:

Java language/runtime target: 11
package root: com.n3plugins.*
native RuneLite plugin lifecycle
repository-owned shared SDK/interaction infrastructure

Do not introduce Java features requiring a newer target.


2. Plugin Structure

Follow adjacent plugins for exact conventions.

Typical plugin implementation uses:

  • Plugin,
  • @PluginDescriptor,
  • dependency injection,
  • config injection,
  • event subscriptions,
  • startUp(),
  • shutDown().

Do not invent custom lifecycle infrastructure when RuneLite and existing repository patterns provide the required behavior.


3. Plugin Dependencies

Use @PluginDependency only when the dependency is required by current architecture and matches adjacent patterns.

Do not add dependencies to gain access to unrelated helpers.

Centralize shared infrastructure ownership.

Check live source and the Source of Truth for current dependency relationships.


4. Registration

Use the repository's existing native plugin registration system.

When adding, removing, or renaming a plugin:

  • update the correct registration metadata,
  • keep class names under com.n3plugins.*,
  • preserve bundle naming conventions,
  • verify runtime discoverability.

Do not register the Packet Utils-owned n3DevTools runtime as a standalone plugin unless the current Source of Truth changes that policy.


5. Shared Infrastructure Ownership

Feature plugins must not duplicate shared low-level infrastructure.

Prefer the repository's existing:

SDK
Api.actions
walker
widget APIs
workflow/state helpers
packet infrastructure
shared client abstractions

Use Api.actions.* where that layer owns the required interaction.

Respect InteractionResult or the current live equivalent rather than assuming an attempted action succeeded.

Do not bypass:

  • action pacing,
  • readiness gates,
  • central packet ownership,
  • shared walker ticking,
  • SDK initialization.

6. Event-Driven Behavior

Use RuneLite events for state observation when appropriate.

Common examples include:

  • GameTick,
  • client state changes,
  • animation changes,
  • inventory/container changes,
  • widget events,
  • menu events.

Do not create polling loops that fight the RuneLite event model without a concrete reason.


7. Automation State Machines

For repeated automated behavior:

observe state
→ choose one meaningful next action
→ perform action
→ wait for evidence of transition
→ re-read live state

Do not:

  • spam repeated clicks,
  • spam menu actions,
  • queue multiple unverified interactions,
  • assume an interaction succeeded solely because a method returned or a click was attempted.

Keep state explicit.

Reset transient state when relevant on:

  • logout,
  • world hop,
  • task stop,
  • invalid target,
  • major game-state transition,
  • plugin shutdown.

8. One Meaningful Action per Tick/Step

Prefer one state-changing action per execution step.

Examples:

open bank

then verify it opened before:

withdraw item

Do not perform:

open bank
withdraw
equip
walk

as an optimistic chain without state confirmation.


9. Live State

Re-fetch state after actions.

Avoid stale references to:

  • NPCs,
  • tile objects,
  • widgets,
  • inventory items,
  • bank items,
  • menu entries.

Game state changes.

A reference that was valid before interaction may no longer be valid afterward.


10. Threading

Respect RuneLite client-thread requirements.

Do not perform game-client state mutation or unsafe reads from arbitrary worker threads.

Use existing repository threading helpers and adjacent patterns.

Do not solve sequencing problems by sprinkling uncontrolled sleeps across threads.


11. Configuration

Use RuneLite configuration patterns established by the repository.

Configuration should control user-facing behavior, not compensate for broken architecture.

Do not add toggles for speculative features.

Defaults must preserve safe existing behavior unless the task changes them.


12. IDs and Game Data

Do not invent:

  • item IDs,
  • NPC IDs,
  • object IDs,
  • widget IDs,
  • varbits,
  • varplayers,
  • script IDs,
  • packet IDs.

Resolve them from:

  1. current RuneLite constants/source,
  2. repository mappings,
  3. verified live/reference evidence.

IDs are revision-sensitive where applicable.

Native menu actions are revision-sensitive too. Route listener-backed widget operations through com.n3plugins.PacketUtils.reflection.MenuDispatcher; do not add calls to an injected Client.menuAction(...) method or create a second ASM resolver/cache. See docs/menu-action-dispatch.md.

Packet, buffer, mouse, packet-writer, packet-node, and Profiles reflection mappings are revision-sensitive. Keep their ASM-derived evidence in the shared PacketUtils/reflection/revision-mappings resource and validate it with the revision analyzer and artifact tests. Do not reintroduce Java constants for obfuscated owners, fields, methods, multipliers, transforms, or trailing arguments. Login-index writes must use the active client's public getLoginIndex() hook through the shared resolver. See docs/packet-debugging.md and docs/profiles.md.


13. Local Source References

External local source trees serve as design/reference input.

Do not make them runtime dependencies.

Do not:

import source from C:\Users\...
reference developer-local checkout paths
copy secrets
copy launcher hacks
copy generated caches
copy irrelevant client internals

Adapt useful concepts into n3Plugins architecture.


14. SDK-First Rule

Before adding feature-local helpers, inspect whether the n3 SDK owns the concept.

Preferred order:

existing SDK abstraction
→ extend existing SDK abstraction
→ small feature-local helper
→ new shared abstraction only when justified

Do not build parallel APIs for concepts the SDK already owns.