Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ repositories {

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
if (microbotClientPath) {
def clientFile = file(microbotClientPath)
if (!clientFile.exists()) {
Expand Down
19 changes: 19 additions & 0 deletions docs/pest-control/CODEX-TASK-PROMPT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Pest Control Codex Task Prompt

Continue live validation of `chsami/Microbot-Hub#522` on
`fix/pest-control-adaptive-strategy` in `JogOnJohn/Microbot-Hub`.

Read the operator root `AGENTS.md`,
`F:\vmware boxs\MBOT\operator-work\plugins\pest-control.psd1`, this directory's
`DEVELOPMENT-HANDOFF.md`, the Pest Control package `AGENTS.md`, and the full
local handoff before acting.

Use `F:\vmware boxs\MBOT\repos\Microbot-Hub-pest-control` for source. Use only
the newer `Bizza 12345` VM (`clanker\vmadmin2`) for runtime validation. Agent
Server is guest-local on port 8081 and requires `X-Agent-Token`; never expose
the token.

Start read-only. Confirm the current PR head/checks, both Git statuses, guest
checkout path, client/plugin state and installed JAR hash. Preserve the known-good
`2.5.4` tribrid artifact. Do not edit, build, install or alter client lifecycle
state until instructed.
62 changes: 62 additions & 0 deletions docs/pest-control/DEVELOPMENT-HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Pest Control PR Validation Handoff

Last refreshed: 2026-08-28 (Australia/Sydney)

## Source

- Upstream PR: `chsami/Microbot-Hub#522`
- PR title: `feat: add adaptive Pest Control strategy`
- Repository/branch: `JogOnJohn/Microbot-Hub`, `fix/pest-control-adaptive-strategy`
- Checkpoint before this documentation package: `d5edead2a9012efcb7592f4960ec65bc0cd09b60`
- Plugin version: `2.5.7`
- Minimum Microbot client: `2.6.15`
- Host worktree: `F:\vmware boxs\MBOT\repos\Microbot-Hub-pest-control`
- Operator manifest: `F:\vmware boxs\MBOT\operator-work\plugins\pest-control.psd1`
- Full local handoff: `F:\vmware boxs\MBOT\docs\handoffs\pest-control\PEST-CONTROL-HANDOFF.md`

The older `fix/pest-control-strategy` branch contains the long development
history but is not the PR head. Test and patch this branch only.

## Preserved Rollbacks

```text
2.5.4 known-good tribrid:
AD3532925A0A95941D4D219447C4BC0E26BADC7AEE35E0C15C357F88D0793E5B

2.4.34 known-good ranged:
213B84CEB2B496D733C062CFF6B287C3A8138561749588003A9E2607B35054AC
```

Do not delete or overwrite those archived artifacts. No exact `2.5.7` installed
hash was verified when this handoff was written.

## Validation Status

PR #522 records successful packaging against Microbot `2.6.15`, a packaged and
smoked `2.5.6`, and one complete `2.5.7` round on Microbot `2.6.16`. That is a
smoke checkpoint, not sufficient acceptance for the state-machine breadth.

On 2026-08-28, this branch was rebased onto `upstream/development` at
`64d1d0f` and source-validated with
`gradlew.bat test PestControlPluginJar -PpluginList=PestControlPlugin`. The
JUnit XML report recorded 17 Pest Control tests, with zero failures or errors.
This includes the focused combat-tab resolver coverage: configured Crush accepts
the visible `Crush`, `Pummel`, and `Smash` labels, while Stab, Slash, and Rapid
continue to require exact labels. The built `PestControlPlugin-2.5.7.jar` hash
was `594DB36A8A2FF2F7AEBD81EBACBC8369D7B17D72B1A79C728B69074562E5D886`.
This is build/test evidence only; it does not establish a loaded or live-tested
artifact.

Further live evidence should cover:

- several ranged paid rounds;
- paid win, unrewarded team win and Void Knight loss accounting;
- delayed result evidence and sub-five-second requeue;
- intact, open and destroyed gates;
- activity recovery, Brawler flank/fallback, and portal/Spinner flow;
- Ranged, Magic, Yellow Stab/Slash, Red Crush and tribrid profiles;
- one-hand/off-hand, two-handed, full-Void and non-Void setups; and
- portal-only specials with energy and duplicate-toggle checks.

Record exact artifact identity and per-round evidence. Patch only reproduced
failures. Preserve current client lifecycle unless explicitly authorized.
300 changes: 300 additions & 0 deletions docs/pest-control/validation/2026-08-28-30-minute-live-smoke.log

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Pest Control Plugin Rules

## Strategy Invariants

- Vulnerable portal pressure is primary. A Spinner healing the selected portal preempts the portal attack.
- Activity recovery begins at the configured low threshold and remains committed until the recovery target is reached.
- Brawlers are obstruction targets only: flank first, then attack only when the route remains blocked.
- Never path repeatedly through closed perimeter gates or the southwest no-go grid.
- Treat destroyed gate object `14245` as passable even when it advertises `Open`; never click that action repeatedly.
- Keep a selected vulnerable portal stable unless stronger game evidence invalidates it.
- Do not chase the opposite opening flank when the first portal drop would sacrifice the round's activity.
- Pre-position at the last shielded portal without abandoning active local combat prematurely.

## Combat Invariants

- Style 1 is mandatory and is the fallback for incomplete optional profiles.
- Verify weapon, off-hand, optional Void helmet and combat mode before the final attack interaction.
- A blank or `None` off-hand means the shield slot must be empty.
- Complete enabled-style Void helmet sets may switch automatically; incomplete/non-Void setups leave the head slot untouched.
- Magic autocast is prepared once per configured weapon and then treated as remembered.
- Specials are portal-only and require enough current energy for that weapon's special.
- Do not switch away from ongoing portal combat solely to prepare a later target.

## Result Invariants

- Finalize each round exactly once.
- Reward chat or a reliable positive total-points delta is authoritative paid-win evidence.
- Portal destruction count and team outcome are diagnostic, not authoritative reward evidence.
- Reconcile counters so `roundsPlayed == roundsWon + roundsLost` after every finalization.
- Deduplicate point-award messages and preserve evidence across a rapid boat transition.

## Runtime Diagnosis

Correlate state with tile, movement, animation, interaction, destination,
activity, portal vulnerability, gate variant, target NPC, loadout, combat mode,
special energy, point delta and timestamps. One delayed scheduler pass is not a
stall.

Prioritize these logs:

- `Pest Control state:`
- `Pest Control round ended`
- `Pest Control round accounted:`
- `Pest Control session:`
- `Pest Control activity recovery started`
- `Pest Control activity recovered`
- `Pest Control bypassing destroyed`
- `Pest Control gate diagnostic`
- `Pest Control watchdog recovery`
- `Pest Control command guard`
- `Pest Control combat config:`

Log transitions and bounded retries, not every scheduler iteration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package net.runelite.client.plugins.microbot.pestcontrol;

import net.runelite.client.plugins.pestcontrol.Portal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static net.runelite.client.plugins.pestcontrol.Portal.BLUE;
import static net.runelite.client.plugins.pestcontrol.Portal.PURPLE;
import static net.runelite.client.plugins.pestcontrol.Portal.RED;
import static net.runelite.client.plugins.pestcontrol.Portal.YELLOW;

final class PestControlCombatPlan {
private final PestControlConfig config;
private final List<PestControlCombatStyle> enabledStyles;
private final List<String> validationMessages;

PestControlCombatPlan(PestControlConfig config) {
this.config = config;
List<PestControlCombatStyle> styles = new ArrayList<>();
List<String> messages = new ArrayList<>();
PestControlCombatStyle primary = config.primaryCombatStyle() == null
? PestControlCombatStyle.RANGED
: config.primaryCombatStyle();
styles.add(primary);
addOptionalStyle(styles, messages, config.secondaryCombatStyle(), "Style 2");
addOptionalStyle(styles, messages, config.tertiaryCombatStyle(), "Style 3");
this.enabledStyles = Collections.unmodifiableList(styles);

for (int index = 0; index < styles.size(); index++) {
PestControlCombatStyle style = styles.get(index);
if (!loadoutForStyle(style).isConfigured()) {
messages.add("Style " + (index + 1) + " " + style + " weapon is not configured");
}
}
this.validationMessages = Collections.unmodifiableList(messages);
}

List<PestControlCombatStyle> enabledStyles() {
return enabledStyles;
}

List<String> validationMessages() {
return validationMessages;
}

boolean supports(PestControlCombatStyle style) {
return enabledStyles.contains(style);
}

PestControlLoadout primaryLoadout() {
return loadoutForStyle(enabledStyles.get(0));
}

PestControlLoadout magicLoadout() {
return loadoutForStyle(PestControlCombatStyle.MAGIC);
}

PestControlLoadout loadoutForPortal(Portal portal) {
PestControlCombatStyle desiredStyle = styleForPortal(portal);
if (!supports(desiredStyle)) {
return primaryLoadout();
}

PestControlLoadout desired;
if (desiredStyle == PestControlCombatStyle.MELEE) {
desired = portal == RED ? redMeleeLoadout() : yellowMeleeLoadout();
} else {
desired = loadoutForStyle(desiredStyle);
}
return desired.isConfigured() ? desired : primaryLoadout();
}

Portal openingPortal(int randomValue) {
PestControlOpeningMode mode = config.openingMode() == null
? PestControlOpeningMode.WEIGHTED_RANDOM
: config.openingMode();
switch (mode) {
case MAIN_STYLE:
return openingPortalForStyle(enabledStyles.get(0));
case EVEN_RANDOM:
return openingPortalByIndex(randomValue, 1, 1, 1);
case WEIGHTED_RANDOM:
default:
return openingPortalByIndex(
randomValue,
clampedWeight(config.rangedOpeningWeight()),
clampedWeight(config.magicOpeningWeight()),
clampedWeight(config.meleeOpeningWeight()));
}
}

private PestControlLoadout loadoutForStyle(PestControlCombatStyle style) {
switch (style) {
case MAGIC:
return PestControlLoadout.magic(config.magicWeapon(), config.magicOffhand());
case MELEE:
return yellowMeleeLoadout();
case RANGED:
default:
return PestControlLoadout.ranged(config.rangedWeapon(), config.rangedOffhand());
}
}

private PestControlLoadout yellowMeleeLoadout() {
PestControlLoadout yellow = configuredYellowMeleeLoadout();
return yellow.isConfigured() ? yellow : configuredRedMeleeLoadout();
}

private PestControlLoadout configuredYellowMeleeLoadout() {
PestControlYellowAttackStyle style = config.yellowMeleeStyle() == null
? PestControlYellowAttackStyle.SLASH
: config.yellowMeleeStyle();
return PestControlLoadout.melee(
style.meleeStyle(),
config.slashStabWeapon(),
config.slashOffhand());
}

private PestControlLoadout redMeleeLoadout() {
PestControlLoadout red = configuredRedMeleeLoadout();
return red.isConfigured() ? red : configuredYellowMeleeLoadout();
}

private PestControlLoadout configuredRedMeleeLoadout() {
return PestControlLoadout.melee(
PestControlMeleeStyle.CRUSH,
config.crushWeapon(),
config.crushOffhand());
}

private static void addOptionalStyle(
List<PestControlCombatStyle> styles,
List<String> messages,
PestControlOptionalCombatStyle optionalStyle,
String label) {
if (optionalStyle == null || optionalStyle.combatStyle() == null) {
return;
}
PestControlCombatStyle style = optionalStyle.combatStyle();
if (styles.contains(style)) {
messages.add(label + " duplicates " + style + " and will be ignored");
return;
}
styles.add(style);
}

private static PestControlCombatStyle styleForPortal(Portal portal) {
if (portal == PURPLE) {
return PestControlCombatStyle.RANGED;
}
if (portal == BLUE) {
return PestControlCombatStyle.MAGIC;
}
return PestControlCombatStyle.MELEE;
}

private static Portal openingPortalForStyle(PestControlCombatStyle style) {
switch (style) {
case MAGIC:
return BLUE;
case MELEE:
return YELLOW;
case RANGED:
default:
return PURPLE;
}
}

private static Portal openingPortalByIndex(
int randomValue,
int rangedWeight,
int magicWeight,
int meleeWeight) {
int total = rangedWeight + magicWeight + meleeWeight;
if (total <= 0) {
rangedWeight = 1;
magicWeight = 1;
meleeWeight = 1;
total = 3;
}
int roll = Math.floorMod(randomValue, total);
if (roll < rangedWeight) {
return PURPLE;
}
if (roll < rangedWeight + magicWeight) {
return BLUE;
}
return YELLOW;
}

private static int clampedWeight(int weight) {
return Math.max(0, Math.min(100, weight));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.runelite.client.plugins.microbot.pestcontrol;

public enum PestControlCombatStyle {
RANGED,
MELEE,
MAGIC
}
Loading
Loading