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:
- current RuneLite constants/source,
- repository mappings,
- 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.