Skip to content
Merged
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
90 changes: 90 additions & 0 deletions .github/workflows/shortest-path-upstream-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Shortest Path upstream drift

on:
schedule:
- cron: "17 6 * * 1"
workflow_dispatch:
pull_request:
paths:
- ".github/workflows/shortest-path-upstream-drift.yml"
- "scripts/check-shortest-path-upstream.py"
- "scripts/check-shortest-path-boundary.py"
- "scripts/check-shortest-path-vendored-core.py"
- "scripts/compare-shortest-path-transports.py"
- "scripts/compare-shortest-path-planners.py"
- "scripts/evaluate-walker-shadow-evidence.py"
- "scripts/evaluate-walker-rollout-evidence.py"
- "scripts/report-shortest-path-planner-performance.py"
- "scripts/shortest-path-planner-corpus.json"
- "scripts/shortest-path-planner-harness/**"
- "scripts/shortest-path-upstream-baseline.json"
- "scripts/shortest-path-vendored-core-baseline.json"
- "scripts/shortest-path-transport-baseline.json"
- "scripts/tests/test_compare_shortest_path_transports.py"
- "scripts/tests/test_compare_shortest_path_planners.py"
- "scripts/tests/test_evaluate_walker_shadow_evidence.py"
- "scripts/tests/test_evaluate_walker_rollout_evidence.py"
- "scripts/tests/test_report_shortest_path_planner_performance.py"
- "scripts/tests/test_check_shortest_path_boundary.py"
- "scripts/tests/test_check_shortest_path_vendored_core.py"
- "runelite-client/src/upstreamPlanner/**"
- "runelite-client/src/main/java/net/runelite/client/plugins/microbot/**"
- "runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/**"
- "docs/evidence/walker/**"

permissions:
contents: read

jobs:
check-upstream:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
cache: gradle
- name: Test semantic transport comparison
run: python3 -m unittest scripts/tests/test_compare_shortest_path_transports.py
- name: Test planner comparison orchestration
run: python3 -m unittest scripts/tests/test_compare_shortest_path_planners.py
- name: Test live planner shadow evidence evaluation
run: python3 -m unittest scripts/tests/test_evaluate_walker_shadow_evidence.py
- name: Test planner rollout evidence evaluation
run: python3 -m unittest scripts/tests/test_evaluate_walker_rollout_evidence.py
- name: Test planner performance evidence evaluation
run: python3 -m unittest scripts/tests/test_report_shortest_path_planner_performance.py
- name: Test vendored planner pin verification
run: python3 -m unittest scripts/tests/test_check_shortest_path_vendored_core.py
- name: Enforce vendored planner pin and patch surface
run: scripts/check-shortest-path-vendored-core.py
- name: Compare local and reviewed upstream planners
run: scripts/compare-shortest-path-planners.py --require-all
- name: Test shortest-path boundary check
run: python3 -m unittest scripts/tests/test_check_shortest_path_boundary.py
- name: Enforce shortest-path plugin-state boundary
run: python3 scripts/check-shortest-path-boundary.py
- name: Compare reviewed Shortest Path baseline with upstream
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: scripts/check-shortest-path-upstream.py
- name: Enforce reviewed semantic transport baseline
run: scripts/compare-shortest-path-transports.py --check-baseline --limit 20
- name: Verify tracked live-shadow evidence
run: |
scripts/evaluate-walker-shadow-evidence.py \
docs/evidence/walker/2026-08-05/shadow-input-*.json \
--json-output /tmp/walker-shadow-evidence.json \
--markdown-output /tmp/walker-shadow-evidence.md
cmp /tmp/walker-shadow-evidence.json \
docs/evidence/walker/2026-08-05/shadow-evidence.json
- name: Verify tracked F2P selection and rollback evidence
run: |
scripts/evaluate-walker-rollout-evidence.py \
docs/evidence/walker/2026-08-05/rollout-normal-input.json \
docs/evidence/walker/2026-08-05/rollout-rollback-input.json \
--json-output /tmp/walker-rollout-evidence.json \
--markdown-output /tmp/walker-rollout-evidence.md
cmp /tmp/walker-rollout-evidence.json \
docs/evidence/walker/2026-08-05/rollout-evidence.json
172 changes: 171 additions & 1 deletion docs/F2P_WEBWALKER_HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,164 @@ scripts/run-f2p-webwalker-harness.sh F2P-15

The runner forwards route settings through `microbot.test.webwalker.*` system properties because the Gradle `runTest` task only propagates `microbot.test.*` properties into the launched client JVM.

`TestRunnerPlugin` starts before ordinary plugins and clears the persisted enabled flag for the selected
test target. It starts that target only after a game tick reports `LOGGED_IN`, a local player is present and
the welcome-screen Play widget is no longer visible, then clears the enabled flag again while leaving the
target active for the current process. This readiness contract is intentional: RuneLite can report
`LOGGED_IN` and expose a local player before the welcome overlay has stopped blocking interaction, and a
persisted harness flag must not start its private route timeout during the next client's login sequence.

## Planner modes and evidence

Planner selection is explicit and defaults to `LOCAL`:

- `LOCAL` runs only the local planner;
- `SHADOW` executes local and compares upstream asynchronously;
- `UPSTREAM_F2P_CANARY` calculates both candidates for non-members policy and selects upstream only after a
semantic match. Members-policy requests remain local.

Enable shadow evidence for a harness run with:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=SHADOW \
scripts/run-f2p-webwalker-harness.sh F2P-17
```

`MICROBOT_WEBWALKER_UPSTREAM_PLANNER_SHADOW=true` remains a harness compatibility alias for `SHADOW`; new
automation should set the mode directly.

The harness waits up to two minutes for the bounded shadow worker to settle and embeds the same
coordinate-free schema-v2 object served by `/walker/shadow` under `shadowEvidence` in `result.json`. An
enabled run fails if it submits no comparison, leaves work pending, or observes a semantic divergence or
planner failure. Extract and run the full coverage evaluator with:

```bash
jq '.shadowEvidence' ~/.runelite/test-results/f2p-webwalker/result.json \
> build/walker-shadow-snapshot.json
scripts/evaluate-walker-shadow-evidence.py \
build/walker-shadow-snapshot.json \
--json-output build/walker-shadow-evidence.json \
--markdown-output build/walker-shadow-evidence.md
```

A single route can prove that evidence collection works but cannot satisfy the production selection gate's
cross-category minimums. Run the representative route mix described in `docs/walker-planner-selection-gate.md`.

The accepted 2026-08-05 aggregate combines 12 independently validated fresh-client snapshots and passes the
full F2P live gate: 141/141 semantic matches, 75 active routes, 15 active replans, 11 recovery replans, 39
underground comparisons, 18 walking-only cave selections and 71 exact arrivals, with no divergence, planner
failure, pending/discarded work, unreachable outcome or exit. Keep the inputs separate and use the evaluator;
do not treat one long process or hand-added counters as equivalent evidence.

Run the opt-in F2P selection canary separately:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=UPSTREAM_F2P_CANARY \
MICROBOT_WEBWALKER_OUTPUT_DIR=/tmp/microbot-f2p-canary \
scripts/run-f2p-webwalker-harness.sh F2P-17
```

The canary keeps the active route calculating until both planners have completed, then atomically exposes one
selected route. It fails evidence collection if an ordinary canary run records a semantic divergence, planner
failure or no upstream selection. The accepted 2026-08-05 underground run completed five repetitions, made
ten upstream selections and recorded ten arrivals with no divergence or failure.

The test-only forced-failure mode proves the release-independent local fallback without changing production
failure handling:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=UPSTREAM_F2P_CANARY \
MICROBOT_WEBWALKER_FORCE_UPSTREAM_FAILURE=true \
MICROBOT_WEBWALKER_EXPECT_LOCAL_FALLBACK=true \
MICROBOT_WEBWALKER_OUTPUT_DIR=/tmp/microbot-f2p-rollback \
scripts/run-f2p-webwalker-harness.sh F2P-17
```

The failure hook is honored only in test mode. An accepted rollback run requires planner failures and local
failure fallbacks, requires zero upstream selections, and still requires every live route arrival. The
2026-08-05 run recorded ten injected failures, ten local fallbacks and ten arrivals. Terminal outcomes are
bound to the generation-matched ready route, so `LOCAL` walks and members-policy walks under the F2P canary
cannot inflate these counts; the normal and forced-failure F2P-17 runs each retained all ten eligible arrivals.

Evaluate the two fresh-client artifacts as one release decision instead of reviewing their embedded checks
independently:

```bash
scripts/evaluate-walker-rollout-evidence.py \
/tmp/microbot-f2p-canary/result.json \
/tmp/microbot-f2p-rollback/result.json \
--json-output build/walker-f2p-rollout-evidence.json \
--markdown-output build/walker-f2p-rollout-evidence.md
```

The paired evaluator requires the pinned candidate, distinct client sessions, the same required route set,
settled accounting, ten normal upstream selections and arrivals, and ten forced failure fallbacks and
arrivals. It also requires one coordinate-free canary-readiness timing sample per completed comparison,
rejects a submission-to-ready maximum above `2,000 ms`, and rejects average non-search overhead above `250 ms`
per decision after subtracting both measured searches. Readiness includes executor queueing, both searches,
semantic comparison, selection/fallback and route materialization. Its report is coordinate-free and rejects
exception-message exposure. The fresh 2026-08-05 pair passes with no failure, shortfall or warning: normal
readiness averaged `304.3 ms`, peaked at `645.7 ms` and averaged `172.0 ms` of non-search overhead; forced
rollback averaged `260.8 ms`, peaked at `675.1 ms` and averaged `123.4 ms` of non-search overhead.

Prerequisite-bearing selection-gate routes are intentionally excluded from the default fresh-account suite.
Run them explicitly on a suitable profile:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=SHADOW \
scripts/run-f2p-webwalker-harness.sh F2P-18
```

It first performs ten real `compareRoutes` calls and requires all ten explicit bank-to-target shadow legs to
settle and select an item-gated transport. It then performs three Lumbridge-to-Champions' Guild repetitions,
disables agility shortcuts and teleports, enables canoes, and requires at least five completed `CANOE` shadow
selections in addition to exact arrivals. The accepted 2026-08-05 rerun produced 49/49 matching comparisons,
10/10 matching item-gated bank legs and six exact terminal arrivals.

The representative terminal-travel slice requires at least 90 coins for two outbound journeys and one reverse
setup journey:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=SHADOW \
scripts/run-f2p-webwalker-harness.sh F2P-19
```

It disables agility shortcuts and teleports, enables ships, and requires at least three completed
`TERMINAL_TRAVEL` shadow selections in addition to exact arrivals.

The ordinary-replan slice injects twelve replans only while a long surface route is active and requires each
one to finish in the upstream shadow before accepting the final arrival:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=SHADOW \
scripts/run-f2p-webwalker-harness.sh F2P-20
```

The recovery slice queues its replans through a test-only hook that is consumed by the walker thread. This
exercises the same recovery evidence context as a real stall without manufacturing a client-thread sleep or
calling the recovery helper from the harness thread. Three repetitions produce at least five alternating Port
Sarim / Rimmington legs; each sufficiently long outbound or reverse setup leg attempts two progress-gated
recovery replans. The verifier uses observed results rather than requested injections and requires ten
completed comparisons plus five recovered arrivals. The accepted 2026-08-05 session produced 11/11 matching
recovery comparisons and six recovered arrivals with no exit or unreachable outcome:

```bash
MICROBOT_WEBWALKER_PLANNER_MODE=SHADOW \
scripts/run-f2p-webwalker-harness.sh F2P-21
```

The existing spell-teleport stress harness can capture a separate fresh-session slice:

```bash
MICROBOT_GE_LUMBRIDGE_ITERATIONS=3 \
MICROBOT_GE_LUMBRIDGE_UPSTREAM_PLANNER_SHADOW=true \
scripts/run-ge-lumbridge-teleport-harness.sh
```

Extract its `shadowEvidence` as a second file and pass both snapshots to the evaluator. It validates every
session before aggregation and rejects duplicate session start identities. Do not concatenate JSON or add
counters by hand.

## Agent Loop

1. Run the full suite.
Expand Down Expand Up @@ -64,4 +222,16 @@ The runner forwards route settings through `microbot.test.webwalker.*` system pr
| F2P-14 | `3092,3245,0` | `3109,3341,0` | Draynor Manor approach |
| F2P-15 | `3109,3341,0` | `3106,3363,0` | Draynor Manor door/object handling |
| F2P-16 | `3106,3363,0` | `3092,3245,0` | Reverse manor exit behavior |
| F2P-17 | current live player tile | `3237,9858,0` | Captures current origin, then walks to Varrock Sewers 5 times on a F2P world with agility shortcuts and teleports disabled |
| F2P-17 | `3236,3458,0` | `3237,9858,0` | Walks from the fixed Varrock surface manhole to the sewers 5 times on a F2P world with agility shortcuts and teleports disabled; setup climbs out before every repetition, so a prior run ending underground cannot turn the case into a no-op |

## Selection-gate routes

These prerequisite-bearing routes are available by explicit ID and are not included by the default `all`
filter.

| ID | From | To | Coverage |
|---|---:|---:|---|
| F2P-18 | `3243,3237,0` | `3199,3344,0` | Ten explicit item-gated bank-to-target comparisons plus three River Lum canoe repetitions and five or more `CANOE` planner selections |
| F2P-19 | `3029,3217,0` | `2956,3146,0` | Two Port Sarim-to-Musa Point repetitions with three or more fare-gated `TERMINAL_TRAVEL` planner selections |
| F2P-20 | `3029,3217,0` | `2946,3368,0` | Long surface walk with twelve deliberately injected and settled `ACTIVE_REPLAN` comparisons |
| F2P-21 | `3029,3217,0` | `2957,3214,0` | Five alternating surface walks with two walker-thread `RECOVERY_REPLAN` comparisons each and five recovered arrivals |
58 changes: 58 additions & 0 deletions docs/decisions/adr-0005-walker-transport-execution-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# ADR 0005: Separate Transport Description from Execution Capability

- Status: Accepted (2026-08-05)

## Context
Shortest Path transport data describes where an edge goes and what it requires, while Microbot also
needs to execute the edge against a live game client. Those responsibilities cannot be treated as the
same contract: imported data can describe a route that Microbot does not yet know how to operate, and
some local transports carry behavior that a flattened data value cannot preserve. In particular,
`PohTransport` owns an executable POH action and seasonal handlers are a pluggable runtime API.

Converting every interaction handler directly from the local `Transport` class to the immutable
`Rs2TransportEdge` value would remove that behavior or recreate it through type switches and legacy
adapters. Retaining an unrestricted concrete planner object as the public route contract would instead
prevent a future upstream planner adapter.

## Decision
Represent every selected route transport with:

- an immutable, planner-independent `Rs2TransportEdge` description;
- an explicit `Rs2TransportExecutor` capability naming the Microbot runtime branch that owns it; and
- for terminal `SHIP`, `NPC` and `BOAT` travel, an explicit planner-independent interaction mode;
- only for the local engine, an opaque package-private payload containing the exact selected local
transport needed by the existing handlers.

The pure planner-side `TransportExecutionRegistry` is authoritative for whether a local catalog row is
executable. Unregistered rows fail closed during transport refresh and cannot be selected merely because
their data was imported. Runtime dispatch also rejects `UNSUPPORTED` as a defensive invariant. The local
payload is never recovered by matching endpoints or rescanning the mutable catalog.

Terminal catalog families describe a journey rather than the live target kind: rows may point at an NPC
or a scene object. The runtime resolves that kind from the configured semantic name/action near the exact
selected origin. The registry still owns the interaction sequence. Direct travel and a known dialogue
destination flow are separate modes; rows needing an unimplemented destination selection remain
unregistered even when another row in the same catalog family is executable.

## Consequences
- Upstream catalog convergence cannot silently create routes that stall at an unimplemented interaction.
- An upstream planner adapter can emit the same immutable edge and executor capability without exposing
its model classes.
- POH and other behavior-bearing transports remain correct while their handlers are gradually moved
behind Microbot-owned execution interfaces.
- The concrete local payload remains an internal implementation detail rather than a migration target by
itself; replacing it requires an equivalent executable capability, not a blanket signature rewrite.
- Missing executor families become explicit delivery work. At adoption, the non-Lumbridge home teleports
and hot-air-balloon network were deliberately fail-closed; the shared exact-name home-teleport executor
subsequently closed the former gap. The balloon network now has an exact six-destination map executor
and observed-landing contract; its static, dual-engine and locked-account fail-closed evidence is complete,
while a successful flight remains pending on an account with an unlocked station.
- Terminal-travel coverage cannot be inferred from `TransportType`. The current audit deliberately keeps
41 multi-step rows fail-closed: 30 `Board` rows for the multi-destination Boat/Boaty networks, six
destination-selecting Rowboat rows and five unimplemented `Talk-to` rows. Their exact interaction
groups are pinned by the catalog capability test.
- Inventory-item interactions are capabilities too. The Barrows dig executor is registered only for the
six reviewed mound-to-individual-crypt pairs with an exact one-spade requirement; arbitrary object-less
`Dig` rows remain unregistered. Individual-crypt stairs use ordinary object execution and a representative
surface-mound anchor because the live exit spawn can vary. Randomized sarcophagus-to-tunnel entry remains
outside the static catalog until the executor can consume observed run state.
Loading
Loading