Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
b5e3efc
fix(microbot): requeue error batch when submit throws; close response…
chsami Oct 7, 2026
8d62ef2
Merge main (2.6.30) back into development
chsami Oct 7, 2026
6dbe3c4
fix(player): guard Rs2Player accessors against a null local player
chsami Oct 8, 2026
23c6f35
fix(actor): return null positions when Rs2ActorModel wraps no actor
chsami Oct 8, 2026
e2f7b51
fix(shortestpath): skip minimap path overlay when the minimap clip is…
chsami Oct 8, 2026
f86f014
fix(gameobject): return empty when the scene query anchor is null
chsami Oct 8, 2026
e8453bf
fix(antiban): default the play style when action cooldown runs withou…
chsami Oct 8, 2026
2c1216b
fix(walker): yield instead of NPE when player location is missing at …
chsami Oct 8, 2026
5618c97
fix(shortestpath): capture refresh boosted-levels array locally
chsami Oct 8, 2026
4f1e6c1
fix(tileitem): return null location when ground item tile lost its wo…
chsami Oct 8, 2026
1572718
docs(entity-guides): show the Optional fallback in the null local pla…
chsami Oct 8, 2026
192f5af
Merge pull request #1904 from chsami/claude/e01-rs2player-null-local-…
chsami Oct 8, 2026
05cd716
Merge remote-tracking branch 'origin/development' into claude/e02-act…
chsami Oct 8, 2026
dc5909d
docs(entity-guides): cover Rs2ActorModel position accessors in the nu…
chsami Oct 8, 2026
ca16430
Merge pull request #1905 from chsami/claude/e02-actor-model-null-actor
chsami Oct 8, 2026
999aacc
Merge remote-tracking branch 'origin/development' into claude/e05-gam…
chsami Oct 8, 2026
107a05f
fix(gameobject): return null from single-object queries when the anch…
chsami Oct 8, 2026
d580f01
Merge pull request #1907 from chsami/claude/e05-gameobject-null-anchor
chsami Oct 8, 2026
2afe88d
Merge remote-tracking branch 'origin/development' into claude/e09-wal…
chsami Oct 8, 2026
8649f79
fix(walker): guard remaining processWalk player-location reads agains…
chsami Oct 8, 2026
f1c4890
Merge pull request #1909 from chsami/claude/e09-walker-null-player-loc
chsami Oct 8, 2026
e48353b
Merge origin/development into claude/e03-path-minimap-null-clip
chsami Oct 8, 2026
30c5321
Merge pull request #1906 from chsami/claude/e03-path-minimap-null-clip
chsami Oct 8, 2026
35c4131
Merge remote-tracking branch 'origin/development' into claude/e08-ant…
chsami Oct 8, 2026
985bf8e
Merge pull request #1908 from chsami/claude/e08-antiban-null-playstyle
chsami Oct 8, 2026
4d1e1aa
Merge remote-tracking branch 'origin/development' into claude/e12-pat…
chsami Oct 8, 2026
7ccd7a2
Merge pull request #1910 from chsami/claude/e12-pathfinder-boosted-le…
chsami Oct 8, 2026
1d70e4d
Merge remote-tracking branch 'origin/development' into claude/e13-til…
chsami Oct 8, 2026
bdd38cf
fix(queryable): skip entities without a location in within()
chsami Oct 8, 2026
2fef406
Merge pull request #1911 from chsami/claude/e13-tileitem-null-worldview
chsami Oct 8, 2026
7d9838a
fix(client-thread): log client-thread timeouts as rate-limited warning
chsami Oct 8, 2026
4cbe7cc
fix(microbot): treat interrupted waits as cancellation, not errors
chsami Oct 8, 2026
403db97
fix(questhelper): initialise requirements before skill filtering
chsami Oct 8, 2026
e8822fa
fix(plugins): don't register hub plugins whose injector failed to build
chsami Oct 8, 2026
1593791
test(plugins): force injector failure regardless of global injector s…
chsami Oct 8, 2026
42747bd
fix(coords): treat a null wrapped WorldPoint as unreachable in Rs2Wor…
chsami Oct 8, 2026
4e96475
fix(magic): fail cast instead of throwing when spell widget is missing
chsami Oct 8, 2026
e4aa68c
fix(skillcalculator): exclude zero-xp magic actions from calculator
chsami Oct 8, 2026
d4f3764
fix(kourendlibrary): read player location on client thread when toggl…
chsami Oct 8, 2026
411da77
fix(world-selector): sort a copy of the cached world list
chsami Oct 8, 2026
4f95f08
chore: remove example plugin and clean up dead code
claude Oct 9, 2026
6c768e0
chore: remove dead loot-selling chain and NotImplemented stub
claude Oct 9, 2026
969dfbf
Merge origin/development into backmerge 2.6.30
chsami Oct 9, 2026
6d5b5dc
Merge pull request #1903 from chsami/claude/backmerge-main-2.6.30
chsami Oct 9, 2026
e63a524
Merge remote-tracking branch 'origin/development' into review-1902
chsami Oct 9, 2026
e679d71
Merge pull request #1902 from chsami/claude/error-telemetry-followups
chsami Oct 9, 2026
ec5c82a
Merge remote-tracking branch 'origin/development' into claude/e14-cli…
chsami Oct 9, 2026
8a64b1e
Merge pull request #1912 from chsami/claude/e14-clientthread-timeout
chsami Oct 9, 2026
9ec5077
Merge remote-tracking branch 'origin/development' into claude/e15-cli…
chsami Oct 9, 2026
672ff09
Merge pull request #1913 from chsami/claude/e15-client-thread-interrupt
chsami Oct 9, 2026
26f18c6
Merge remote-tracking branch 'origin/development' into claude/e16-cur…
chsami Oct 9, 2026
fa5dbc1
Merge pull request #1914 from chsami/claude/e16-current-affairs-npe
chsami Oct 9, 2026
3105cf8
Merge remote-tracking branch 'origin/development' into claude/e17-plu…
chsami Oct 9, 2026
fe8f08c
fix(plugins): keep loading other plugins when one throws a linkage error
chsami Oct 9, 2026
1f95f7c
Merge pull request #1915 from chsami/claude/e17-plugin-null-injector
chsami Oct 9, 2026
6805d85
Merge remote-tracking branch 'origin/development' into claude/e20-rs2…
chsami Oct 9, 2026
e65d5a8
Merge pull request #1916 from chsami/claude/e20-rs2worldpoint-null-point
chsami Oct 9, 2026
ebf27cb
Merge remote-tracking branch 'origin/development' into claude/e21-mag…
chsami Oct 9, 2026
c46a74a
fix(magic): fall back to the sprite lookup canCast uses when the spel…
chsami Oct 9, 2026
5d403a6
Merge pull request #1917 from chsami/claude/e21-magic-cast-null-widget
chsami Oct 9, 2026
0c04337
Merge remote-tracking branch 'origin/development' into claude/e23-ski…
chsami Oct 9, 2026
e0a24ec
Merge pull request #1918 from chsami/claude/e23-skill-calc-zero-xp
chsami Oct 9, 2026
7122d37
Merge remote-tracking branch 'origin/development' into claude/e24-kou…
chsami Oct 9, 2026
31176de
fix(kourendlibrary): read player location on client thread when toggl…
chsami Oct 9, 2026
6597b1b
Merge pull request #1919 from chsami/claude/e24-kourend-library-clien…
chsami Oct 9, 2026
4bd6552
Merge remote-tracking branch 'origin/development' into claude/e26-wor…
chsami Oct 9, 2026
40811af
Merge pull request #1920 from chsami/claude/e26-world-selector-copy-w…
chsami Oct 9, 2026
26c4342
fix(magic): account for blighted ice sacks in rune checks
droplugins Oct 10, 2026
bc2c338
fix(autologin): stay logged out after terminal supply failure
droplugins Oct 10, 2026
557385f
Merge pull request #1922 from droplugins/fix-blighted-ice-sack-rune-c…
chsami Oct 10, 2026
6bb7e87
fix: keep deprecated loot API shims used by Microbot-Hub
chsami Oct 10, 2026
555a871
fix(autologin): regenerate guardrail baseline and drop unused logout …
chsami Oct 10, 2026
ad23662
Remove stale logoutWithoutAutoLoginStep reference from items guide
chsami Oct 10, 2026
e5fb3cf
Merge pull request #1921 from chsami/claude/reduce-bloat-simplify-RnomB
chsami Oct 10, 2026
ada7976
Expose Hunter plugin instance for AutoHunter dependency
JogOnJohn Oct 10, 2026
3ba2f87
Align Hunter getPublicModule with repo convention
chsami Oct 10, 2026
657f840
fix(dialogue): guard getQuestion against dialog closing between check…
chsami Oct 10, 2026
7c4533c
fix(widget): guard null root widget in Rs2Widget.clickWidget and sear…
chsami Oct 10, 2026
55e86fd
fix(walker): read interacting actor location on the client thread
chsami Oct 10, 2026
4135fbd
fix(bank): guard null player location in nearest-bank and distance he…
chsami Oct 10, 2026
8a0aef5
fix(agentserver): serialize JDK immutable collections in agent responses
chsami Oct 10, 2026
7bcba87
Merge pull request #1929 from chsami/fix/telemetry-agent-widget-descr…
chsami Oct 10, 2026
5828a09
fix(walker): strike out refused transports so replans stop reselectin…
chsami Oct 10, 2026
8d6bf49
fix(walker): count recovery transport refusals only after a real click
chsami Oct 10, 2026
90881ca
fix(autologin): opt in to supply logout and expire pending suppression
droplugins Oct 10, 2026
474c2a7
fix(shortestpath): make farmed spirit tree destinations opt-in
chsami Oct 10, 2026
5a763ec
fix(shortestpath): flag Port Sarim rat-pit manhole and ladder as memb…
chsami Oct 10, 2026
c173f7d
fix(walker): strike transports on first interaction and on repeated r…
chsami Oct 10, 2026
7b7b298
Merge pull request #1932 from chsami/fix/discord-port-sarim-manhole-m…
chsami Oct 10, 2026
31513a1
Merge pull request #1931 from chsami/fix/discord-farmed-spirit-tree-u…
chsami Oct 10, 2026
695a75d
Merge pull request #1930 from chsami/fix/discord-walker-refused-trans…
chsami Oct 10, 2026
e9d9a9e
Merge pull request #1928 from chsami/fix/telemetry-rs2bank-null-playe…
chsami Oct 10, 2026
6fd4e69
Merge pull request #1927 from chsami/fix/telemetry-walker-interacting…
chsami Oct 10, 2026
21b9b7b
Merge pull request #1926 from chsami/fix/telemetry-clickwidget-null-root
chsami Oct 10, 2026
905f64f
Merge pull request #1925 from chsami/fix/telemetry-dialogue-question-npe
chsami Oct 10, 2026
d33e9b4
Merge pull request #1924 from JogOnJohn/codex/hunter-public-module
chsami Oct 10, 2026
e5863ae
Merge pull request #1923 from droplugins/fix-autologin-terminal-suppl…
chsami Oct 10, 2026
f50629a
test(release): add live release E2E smoke harness
chsami Oct 10, 2026
c247cb4
fix(release-e2e): exit on login failure and dismiss welcome screen in…
chsami Oct 10, 2026
33df8b2
fix(security): stop logging profile credentials to stdout
chsami Oct 11, 2026
6dac4a9
fix(security): log profile ids instead of login names
chsami Oct 11, 2026
b1ce812
fix(security): drop export path and IO message from logs
chsami Oct 11, 2026
4e459b4
fix(world-hop): wait for the world switcher to load before hopping
chsami Oct 11, 2026
f0d45b3
fix(world-hop): reopen the world switcher while waiting for it to load
chsami Oct 11, 2026
cc98074
fix(statemachine): re-initialise StateMachineScript on restart
chsami Oct 11, 2026
a193266
fix(statemachine): keep an in-flight step from re-registering after stop
chsami Oct 11, 2026
5cc270c
Merge pull request #1934 from chsami/fix/credentials-stdout
chsami Oct 11, 2026
802a7b4
Merge pull request #1935 from chsami/fix/world-hop-first-attempt
chsami Oct 11, 2026
4e05326
Merge pull request #1936 from chsami/fix-script-restart
chsami Oct 11, 2026
b182a73
Merge pull request #1933 from chsami/feat/release-e2e-harness
chsami Oct 11, 2026
51a8f0c
fix(login): handle "Too many login attempts" lockout and stop AutoLog…
chsami Oct 11, 2026
24458da
fix(login): address review on lockout cooldown
chsami Oct 11, 2026
0d27fe4
Merge pull request #1937 from chsami/fix/login-lockout-breakhandler-v2
chsami Oct 11, 2026
c9670c4
fix(autologin): keep retry backoff across failed login attempts
chsami Oct 11, 2026
0f9400a
Merge pull request #1938 from chsami/claude/autologin-retry-backoff
chsami Oct 11, 2026
c556649
fix(telemetry): guard null actor animation, null click bounds, quest …
chsami Oct 12, 2026
6a50335
fix(telemetry): address review - reuse captured quest, read antiban c…
chsami Oct 12, 2026
9b2f28b
Merge pull request #1940 from chsami/fix/telemetry-oct12-guards
chsami Oct 12, 2026
bb9a7b8
Merge development into main for 2.6.31
chsami Oct 12, 2026
623c092
chore(release): bump microbot.version to 2.6.31
chsami Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ RuneLite fork with a hidden always-on plugin hosting automation scripts. Composi
- Offline client-thread lookup: `./microbot-cli ct <method>`.
- Test mode: `-Dmicrobot.test.mode=true -Dmicrobot.test.script=<PluginName>` → results in `~/.runelite/test-results/`. Protocol: `docs/AGENTIC_TESTING_LOOP.md`.

## Releases
- Before merging `development` -> `main` (which runs `release.yml`), run `scripts/run-release-e2e.sh full` (or `quick` at minimum) against the release candidate and paste the generated `summary.md` into the release PR. See `docs/RELEASE_E2E.md`.

## In-game settings
Use the settings search bar — tab indices shift on updates. Verify changes via `./microbot-cli varbit <id>`.

Expand Down
3 changes: 2 additions & 1 deletion docs/AGENTIC_TESTING_LOOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ java -jar microbot.jar \
| `microbot.test.script` | Plugin descriptor name to auto-enable | required |
| `microbot.test.timeout` | Max runtime in ms before forced exit | `120000` |
| `microbot.test.output` | Directory for results/screenshots | `~/.runelite/test-results` |
| `microbot.test.world` | Log in to this world with the active profile instead of enabling AutoLogin | unset |

### Components

Expand Down Expand Up @@ -419,7 +420,7 @@ Claude is multimodal. A screenshot of the game client showing a stuck character,

### ExampleScript as the reference test

The existing `ExampleScript` already validates all core APIs (caches, inventory, equipment, widgets, walker, dialogue, teleport, looting). It's the natural first target for the agentic loop. Future scripts can follow the same `check()` pattern and write `TestResult` output.
`ExampleScript` is no longer bundled with the client; it now comes from the Microbot-Hub example plugin (same package), which is an empty loop template. It is still a convenient first target for exercising the harness. Scripts that need real coverage should follow a `check()` pattern and write `TestResult` output.

---

Expand Down
2 changes: 2 additions & 0 deletions docs/AGENT_SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,8 @@ curl -X POST -H "Content-Type: application/json" \

### Script Lifecycle

The examples below use the Microbot-Hub `ExamplePlugin` (`net.runelite.client.plugins.microbot.example`); it is no longer bundled with the client.

#### GET /scripts

Lists all microbot plugins with their active/enabled status.
Expand Down
1 change: 1 addition & 0 deletions docs/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
- [Runtime CLI](MICROBOT_CLI.md)
- [Agent Server](AGENT_SERVER.md)
- [Agentic Testing Loop](AGENTIC_TESTING_LOOP.md)
- [Release E2E Smoke Test](RELEASE_E2E.md)
- [Entity Guides](entity-guides/README.md)
- [Gallery](gallery.md)
91 changes: 91 additions & 0 deletions docs/RELEASE_E2E.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Release E2E Smoke Test

A live smoke test that runs the release candidate client against a real logged-in account before `development` is merged into `main`. That merge triggers `release.yml`, which publishes the jar. The test needs a display and a logged-in game account, so it cannot run in GitHub CI. It is a local gate.

The test reuses the existing test-mode plumbing: `TestRunnerPlugin` logs the account in, starts the hidden `Release Smoke Harness` plugin, and the harness writes its results under `~/.runelite/test-results/`. That is the same flow the [F2P web walker harness](F2P_WEBWALKER_HARNESS.md) uses.

## Account requirements

- An F2P account that has finished Tutorial Island, saved as the active Microbot profile.
- A few coins and a few junk items in the inventory. The bank check deposits one item and withdraws it again. It looks for, in order: Burnt meat, Burnt fish, Burnt bread, Cowhide, Egg, Bones, Ashes, Bowl, Pot, Jug, Feather. It prefers an item that already has a stack in the bank, because a full F2P bank (400 stacks) refuses new item types.
- No bank PIN, or the PIN saved in the profile.
- Any F2P location is fine to start from. The harness walks to each start point itself, using Lumbridge castle and bank, the Lumbridge castle doors, Varrock west bank and the Grand Exchange.
- Log in to an F2P world. Set `MICROBOT_E2E_WORLD` to one, for example `383`. Otherwise AutoLogin uses its own world settings, and it defaults to members worlds.

All actions are reversible and safe. The harness does no trading, drops nothing, places no Grand Exchange offers, does no combat and stays out of the Wilderness. Each check registers cleanups that put back what it changed: doors, bank contents, the open tab, the world, and Hub plugins it installed. If a cleanup fails, the check is marked failed.

## Run

```bash
DISPLAY=:0 MICROBOT_E2E_WORLD=383 scripts/run-release-e2e.sh quick
DISPLAY=:0 MICROBOT_E2E_WORLD=383 scripts/run-release-e2e.sh full
DISPLAY=:0 MICROBOT_E2E_WORLD=383 scripts/run-release-e2e.sh quick bank npc-dialogue
DISPLAY=:0 scripts/run-release-e2e.sh full --jar /path/to/microbot-2.6.30.jar
scripts/run-release-e2e.sh --list
```

By default the script builds `:client:shadowJar` and runs that jar, so the test exercises the shaded artifact rather than the Gradle classpath. Use `--jar` or `MICROBOT_E2E_JAR` to test the exact release candidate jar. It exits non-zero when any check fails or does not run.

| Variable | Meaning |
|---|---|
| `MICROBOT_E2E_HOME` | `user.home` for the client, for an isolated runtime home that holds the saved profile |
| `MICROBOT_E2E_WORLD` | World to log in to. `TestRunnerPlugin` logs in with the active profile instead of enabling AutoLogin |
| `MICROBOT_E2E_EXPECTED_VERSION` | Version the client must report. Defaults to `microbot.version` from `gradle.properties`; set it empty to skip the comparison |
| `MICROBOT_E2E_TOTAL_TIMEOUT_MS` | Total budget for all checks. Defaults to 15 min (quick) and 35 min (full) |
| `MICROBOT_E2E_HOP_WORLD` | Target world for `world-hop`. Defaults to the least crowded plain F2P world in the same region |
| `MICROBOT_E2E_HUB_PLUGIN` | Hub plugin for `hub-plugins`. Defaults to `AttackRangesPlusPlugin`, which only draws an overlay |
| `MICROBOT_E2E_GRADLE` | Build command, for example `flock <lock> systemd-run --user --scope -p MemoryMax=6G ./gradlew` |
| `MICROBOT_E2E_OUTPUT_DIR` | Result directory. Defaults to `<home>/.runelite/test-results/release-e2e` |
| `--fail-fast` | Stop at the first failure. `logout` still runs |

The client runs with telemetry disabled, so test runs do not end up in the production error reports.

## Checks

| ID | Tier | Covers |
|---|---|---|
| `boot` | quick | `microbot.version` matches the expected version, every built-in always-on plugin is active, and `client.log` has no unexpected `ERROR` lines since startup |
| `login` | quick | `LOGGED_IN`, player location known, world known, login finished within `loginTimeoutMs` of JVM start |
| `state-caches` | quick | At the Lumbridge castle hall: all skills plausible; inventory cache equals the client container; cached varp 173 and a varbit equal the client values; NPC, object, ground item and player queryables return results |
| `widgets` | quick | Clicks the Skills and Inventory tab stones (widget click, verified with `Rs2Tab`) and finds the "Total level" text with `Rs2Widget.findWidget` |
| `walker-transport` | quick | Short walk in the castle courtyard; walk through a door the harness closes first (castle entrance tower), which must end with `ARRIVED` on the far side; walk up the castle stairs to the bank on plane 2 |
| `bank` | quick | Nearest bank, `openBank`, `depositOne` and `withdrawOne` on one junk item, `closeBank`, inventory identical to before |
| `npc-dialogue` | quick | Talks to a Lumbridge banker, reads the text, picks "What is this place?", continues, then walks away to close the dialogue. The bank must not open |
| `object-interaction` | quick | Opens and closes (or closes and opens) a Lumbridge castle door and confirms it is back in its original state |
| `agent-server` | quick | `/state`, `/inventory`, `/widgets/describe` and `/skills` with the token, plus the opaque 404 for a request without it. Starts the plugin for the check if it is off |
| `walker-medium` | full | Lumbridge courtyard to Varrock west bank. The walker may teleport, and `maxStepJump` records any jump |
| `grand-exchange` | full | Opens the Grand Exchange through the clerk and closes it. No offers |
| `world-hop` | full | `Microbot.hopToWorld` to another plain F2P world and back |
| `hub-plugins` | full | Plugin Hub manifest loaded with valid entries; install, start, stop and uninstall the Hub plugin (start and stop only if it is already installed) |
| `script-lifecycle` | full | Starts and stops `StateMachineExamplePlugin` twice through `Microbot.startPlugin`/`stopPlugin`: the loop runs, no thread is still running script code after stop, the pool stays at the `Script` core size, and the script is in `StateMachineScript.getRegistry()` while running |
| `logout` | quick, always runs | Stops AutoLogin, logs out with `Rs2Player.logout`, then scans the whole run's `client.log` for unexpected `ERROR` lines |

The quick tier runs checks 1-9 and `logout`. The full tier adds the rest. Check ids passed on the command line run regardless of tier, in catalog order.

## Reading results

The script prints `summary.md`, a Markdown table you can paste into the release PR, and leaves `result.json` next to it:

- `verdict`, `exitCode`, plus `passed`, `failed`, `skipped` and `notRun` counts.
- `results[]`: `id`, `description`, `tier`, `status` (`PASSED`, `FAILED`, `SKIPPED` or `NOT_RUN`), `durationMs`, `detail` (why it failed or was skipped), `details` (what the check observed, such as walk states, door ids, counts and dialogue transcripts) and `cleanupErrors`.
- `checks[]`, `errors` and `logTail` keep the generic `TestResult` format used by the other harnesses.

`SKIPPED` means the account cannot exercise the check, for example the bank is full and no junk item has a bank stack, the profile has no bank PIN, or the Grand Exchange clerk refuses this account. It is not a failure, but say so in the PR. `NOT_RUN` comes from `--fail-fast` or from running out of the total budget, and it fails the run.

When a check fails, look at `detail`, then at `details` in `result.json`, then at `<home>/.runelite/logs/client.log`. Lines from the harness are tagged `[ReleaseE2E]` and run on `ReleaseE2E-<check>` threads. To rerun one check, pass its id: `scripts/run-release-e2e.sh quick bank`.

## Log allowlist

`boot` and `logout` fail on any `ERROR` line in the current run's `client.log`, from the last `starting up, args:` banner onward. Known harmless lines are listed in `ClientLogScanner.DEFAULT_ALLOWLIST`, and each entry has a reason. Keep the list short. Add an entry only for a line that is expected and harmless during a healthy run, never to hide a real failure, and give it a unit test in `ClientLogScannerTest`.

## Adding a check

1. Add a method to `ReleaseSmokeChecks` taking a `CheckContext`. Walk to a fixed start point first with `walkToStart`. Use `context.require(condition, message)` for assertions, `context.skip(reason)` when the account cannot run it, and `context.detail(key, value)` to record what you observed.
2. Register a cleanup with `context.onCleanup(name, action)` before changing anything in game. Cleanups run in reverse order after the body, including after a timeout, and a cleanup that throws fails the check.
3. Add it to `all()` with `ReleaseCheck.quick(...)` or `ReleaseCheck.full(...)`, an id, a one-line description and a timeout long enough for its setup walk. Keep `logout` last.
4. Add the id to `CHECK_IDS` in `scripts/run-release-e2e.sh` and to the table above.
5. Follow the client-thread rules: game reads go through `Rs2*` helpers or `runOnClientThreadOptional`, queryables use the `*OnClientThread` terminals, and waits use `sleepUntil` with a timeout.

## Release checklist

Before merging the release PR (`development` -> `main`), build the release candidate, run `scripts/run-release-e2e.sh full` (or `quick` at minimum) against it, and paste `summary.md` into the release PR. A failing check blocks the release until it is fixed or written up in the PR as a known issue.
4 changes: 3 additions & 1 deletion docs/entity-guides/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Each guide lists known pitfalls when working with one specific game entity type.
| Items (inventory, bank, ground, equipment, shops) | [items.md](items.md) | Any code calling `Rs2Inventory`, `Rs2Bank`, `Rs2Equipment`, `Rs2GroundItem`, `Rs2Shop`, or `Rs2DepositBox` interaction helpers, or any helper that takes a list of item names and applies a single action to all of them |
| Movement (walker, minimap, pathing) | [movement.md](movement.md) | Any code calling or modifying `Rs2Walker`, `Rs2MiniMap`, shortest-path marker handling, or minimap/canvas walk-click logic |
| Death (graves, Death's Office, recovery) | [death.md](death.md) | Any code calling or modifying `Rs2Death`, `DeathRecoveryEvent`, `DeathEvent`, or handling graves, retrieval fees, and post-death item recovery |
| Worlds (selection, ping, hopping) | [worlds.md](worlds.md) | Any code measuring world latency, testing world reachability, or selecting worlds based on network availability |
| Worlds (selection, ping, hopping) | [worlds.md](worlds.md) | Any code measuring world latency, testing world reachability, selecting worlds based on network availability, or reading the local player while logging in or hopping |

Ground-item pickup: [propagate dispatch failures](items.md#11-propagate-ground-item-dispatch-failures) and [preserve explicit Take](items.md#12-preserve-an-explicit-ground-item-take-when-a-widget-is-selected).

Expand Down Expand Up @@ -50,3 +50,5 @@ Each entity guide is a numbered list of gotchas. Each entry follows this structu
## Adding a new gotcha to an existing guide

Append a new numbered entry. Do **not** delete or rewrite existing entries unless they are factually wrong — each entry corresponds to a real failure mode and removing it loses the institutional memory of why the rule exists.

Required-supply stops must suppress AutoLogin without blocking the next run: [items gotcha 15](items.md#15-suppress-autologin-only-for-confirmed-terminal-supply-stops).
11 changes: 11 additions & 0 deletions docs/entity-guides/items.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,14 @@ Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().
```

**Where this applies:** `Rs2GrandExchange`, `GrandExchangeWidget`, `Rs2Dialogue`, `Rs2Bank` X-amount prompts, and any helper reading chatbox prompts or GE offer state.


## 15. Suppress AutoLogin only for confirmed terminal supply stops

An ordinary logout can be a break or world hop, so it must keep normal AutoLogin behavior. After confirming a required supply is unavailable in a live bank mirror, call `Rs2Player.logoutWithoutAutoLogin()` off the client thread and cancel the owning run's scheduler. Existing Inventory Setup callers retain their global-pause behavior. Scripts that prefer terminal logout must opt in per loader with `withMissingSupplyPolicy(Rs2InventorySetup.MissingSupplyPolicy.LOGOUT_AND_STOP)`; do not globally change every plugin's supply policy. Partial-stack acceptance and optional supply fallbacks remain separate.

**Why this matters:** AutoLogin otherwise logs back in after supply exhaustion and the stopped bank loop retries indefinitely. Suppression clears on a new gameplay script run, a manual login after the logout, or an AutoLogin restart. Background AutoLogin and BreakHandler starts do not clear it.

**Pattern to follow:** The helper arms suppression before closing the bank and reports whether logout was observed or only requested. Uncompleted requests expire after 15 seconds using a monotonic clock, including when no game-state event occurs; observed terminal logouts do not expire. Ordinary `logout()` is unchanged; scripts that use it for terminal failure must explicitly adopt the new API.

**Defensive check:** Run `AutoLoginSuppressionTest`, `AutoLoginPluginSupplyStopTest`, `ScriptStartedTest`, `TerminalSupplyLogoutTest`, and `InventorySetupSupplyLogoutTest`. Verify live that a shortage stays logged out and restarting a gameplay script permits login.
29 changes: 29 additions & 0 deletions docs/entity-guides/worlds.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,32 @@ int ping = Ping.tcpPing(InetAddress.getByName(world.getAddress()));
**Where this applies:** `Ping`, `Rs2WorldUtil`, world selectors, login-world scoring, and any helper that tests world reachability.

**Defensive check:** Test fallback with local sockets so the first port refuses the connection and the second succeeds. Live endpoint probes are useful evidence, but must not become a permanent world-to-port mapping.

## 2. Treat the local player as absent during login, hop, and loading

`Client.getLocalPlayer()` returns `null` on the login screen, while hopping, and during some loading states. Scripts keep ticking through those states, so any helper that reads the local player must check for `null` and return the value its callers already treat as "no player".

**Why this matters:** `Rs2Player.getWorldLocation_Internal()` and `Rs2Player.getPoseAnimation()` dereferenced the local player directly. Telemetry for 2.6.30 recorded about 100 NullPointerExceptions from these lambdas in one day, many reached through `Rs2PlayerStateCache.refreshLocalPlayer()` from ordinary `Rs2Player.getWorldLocation()` calls.

**Pattern to follow:**

```java
// Wrong: throws on the client thread whenever the player is not loaded.
return Microbot.getClientThread().runOnClientThreadOptional(() ->
Microbot.getClient().getLocalPlayer().getPoseAnimation()
).orElse(-1);

// Right: read once, yield an empty Optional, and let orElse supply the "no player" value.
return Microbot.getClientThread().runOnClientThreadOptional(() -> {
Player localPlayer = Microbot.getClient().getLocalPlayer();
return localPlayer == null ? null : localPlayer.getPoseAnimation();
}).orElse(-1);
```

**Where this applies:** `Rs2Player` accessors (`getWorldLocation_Internal`, `getPoseAnimation`, `getAnimation`, `getCombatLevel`, `getLocalPlayer`, `getLocalLocation`, `getGraphicId`, `hasSpotAnimation`), the position accessors of `Rs2ActorModel` (`getWorldView`, `getWorldLocation`, `getLocalLocation`, `projectActorLocationToMainWorld`) and its `getAnimation` (returns -1), which `new Rs2PlayerModel()` reaches with a null actor, and any new helper that reads the local player.

**Defensive check:** Mock `Client.getLocalPlayer()` to return `null` and assert each accessor returns its fallback, as in `Rs2PlayerNullLocalPlayerTest` and `Rs2PlayerModelNullLocalPlayerTest`.

**Wrapped locations:** `Rs2Player.getRs2WorldPoint()` still returns an `Rs2WorldPoint`, but it can wrap a `null` `WorldPoint` in these states. `pathTo` returns `null` and `distanceToPath` returns `Integer.MAX_VALUE` for it; check `getWorldPoint() != null` before calling `getX()`, `getY()` or `getPlane()`, as in `Rs2WorldPointNullPointTest`.

**Location consumers:** `Rs2Player.getWorldLocation()` itself returns `null` in these states. Guard it before passing it to `Rs2RouteRequest.to`/`toAny` (which reject a null start) or `WorldPoint.distanceTo`. `Rs2Bank.getNearestBank`, `getNearestBankRoute` and `getPathToNearestBank` return no bank, `isNearBank` returns `false`, and `Rs2Walker.getDistanceBetween` returns `Integer.MAX_VALUE` for a null endpoint, as in `Rs2BankNullPlayerLocationTest`.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ project.build.version=1.13.1
runelite.injected-client.version=1.13.1

glslang.path=
microbot.version=2.6.30
microbot.version=2.6.31
microbot.commit.sha=nogit
microbot.repo.url=http://138.201.81.246:8081/repository/microbot-snapshot/
microbot.repo.username=
Expand Down
Loading
Loading