Walker fix - #11
Merged
Merged
Conversation
Fix the walker
Adds util/death/Rs2Death, a static facade for handling a normal death: locate the grave (an NPC, ids 9856-10367), loot free and paid items, and optionally fall back to Death's Office once the grave expires. Death's Office recovery is opt-in — its fee is unreadable before it is charged, so it is never entered without an explicit flag. Scripts poll hasDeathToHandle() and drive recoverItems(budget[, office]), or compose the primitives directly. No automatic behaviour and no config coupling: callers pass plain scalars, matching Rs2Bank/Rs2Walker. Also reads the "Items Kept on Death" panel for the game's own numbers (getPredictedGraveFee, getRiskValue, getItemsKeptOnDeath) and estimates the office fee from wiki prices per the confirmed per-unit 100k rule. Verified against a live client: grave/office interface groups and components, the entrance object and reclaim dialogue, the GRAVESTONE_* varbit encodings (VISIBLE is non-zero not boolean; DURATION is ticks), and that both grave and office charge on per-unit value, not stack or cumulative. Details and footguns in docs/entity-guides/death.md. Wires onActorDeath/onVarbitChanged in MicrobotPlugin and regenerates the client-thread guardrail baseline for the two event handlers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… low Checked the recovery rules against the OSRS wiki's own tables. Both schedules are confirmed as implemented: a grave charges flat coin amounts per item by tier (1k / 10k / 100k for 100k-1m / 1m-10m / 10m+) capped at 500k, and Death's Office charges a flat 5% on items worth 100k or more, uncapped; ironmen get 50% off both. The wiki says "each reclaimed item", matching the per-unit behaviour observed in game (862 coal at 146 each reclaimed free from both grave and office). It also documents exceptions "to which the above rules do not neatly apply" — notably that stacks of amulet of glory (6) worth over 100,000 are charged 10% at Death's Office, double the rate and assessed on the stack's value rather than per unit. estimateReclaimFee applies the per-unit rule, so it predicts free for such a stack and reads LOW — the one direction a ceiling must not fail, since reclaimAll(maxEstimatedFee) spends real gold against it. Deliberately not special-cased: hardcoding glory would imply the exception list is complete, and the wiki states it is not. Instead estimateReclaimFee and reclaimAll(int) now state plainly that the estimate can read low, name glory as the known case, and tell callers to leave real headroom rather than treat the ceiling as a guarantee. Also removes a stale "biased high" claim and a duplicated javadoc block. Still unobserved in game: an actual non-zero charge. Only the free-below-100k case has been watched happen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes estimateReclaimFee() and reclaimAll(int) — about 80 lines, over half of it caveats, with no callers outside the class. The office never publishes its fee before charging, so any estimate is a guess, and this one guessed LOW on documented exceptions: a stack of amulet of glory (6) over 100,000 is charged 10% on the stack rather than 5% per unit, so it is billed where the per-unit rule predicts free. A ceiling that can be quietly exceeded is worse than no ceiling, because reclaimAll(maxEstimatedFee) spent real gold against it. Special- casing glory was rejected: the wiki states the exception list is not exhaustive, so hardcoding one entry would imply a completeness that cannot be verified. What remains covers the same ground honestly: getPredictedGraveFee() reads the figure the game itself computed on the Items Kept on Death panel, reclaimAll() is unbounded and says so, and walk/enter/open plus closeInterfaces() let a script inspect the office and decline without paying — the trip is free, only the reclaim costs. A script that insists on its own cap can price the contents itself and owns that assumption. The fee schedules move from dead constants into the class javadoc as reference, since nothing computes them any more. Also corrects DeathsOfficeLocation's provenance note using the wiki's map data: every x matches exactly and every y sits a constant two tiles south of the wiki figure (four at Lumbridge, the one entry verified in-game against the real object). A uniform offset on the entry with known ground truth indicates the wiki centres its map north of the object, so these coordinates are the better estimate. Immaterial either way — enterDeathsOffice() resolves the entrance by id, never by coordinate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every looting path was Take-All: lootGraveFreeItems/lootGravePaidItems click the grave's section buttons and reclaimAll clicks the office's, so a script that only wanted its gear back had to take everything or nothing. The interfaces support picking individual slots; the API did not expose it. Adds: - getGraveFreeItems / getGravePaidItems / getDeathsOfficeItems — read what is waiting, with slot indices preserved (the slot is the param0 needed to click it). - lootGraveItems(Predicate) — takes matching slots from both halves via the per-slot Take action. - reclaimItems(Predicate) — takes matching slots from the office. The office selects first and only then reveals its quantity buttons, so each slot is a two-step click: Select, wait for ALL to become visible, then ALL. Slots are clicked highest-index first, because taking one re-packs the container and would otherwise invalidate the indices still to come. Both paths stop when the inventory fills rather than clicking into a full backpack. Also makes the Take-All paths report what they left behind: the office holds up to 120 stacks against 28 inventory slots, so a full reclaim can simply not fit. Nothing is lost there — Death keeps the remainder indefinitely — but a grave expires, so the grave warning includes the time left on the timer. Note the asymmetry, documented on the methods: the office charges per item reclaimed so taking less costs less, whereas a grave's fee covers its whole paid half at once. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three of the four findings were valid: 1. getNearest used WorldPoint.distanceTo, which returns Integer.MAX_VALUE across planes. Every entrance is on plane 0, so a player on any upper floor scored MAX_VALUE for all eight and min() silently returned the first constant — Lumbridge — however far away it was. Switched to distanceTo2D. 2. Two Javadoc blocks were orphaned when the item-reader methods were inserted ahead of the methods they described: the grave "claims the half that costs nothing" block landed on getGraveFreeItems, and the detailed reclaimAll block (spending-limit rationale and @return) landed on getDeathsOfficeItems. Both moved to the methods they document; no implementation change. 3. The Death's Office example in the guide called reclaimAll() unconditionally under a comment about pricing the office first — stale since the fee estimator was removed. It now reads the contents, leaves the decision to the caller, and shows closeInterfaces() as the free way to decline. The fourth — make reclaimItems resolve container and quantity buttons per retrieval variant, mirroring reclaimAll — is not implementable as described. Confirmed against the game cache (iftypes): death_office (669) has 1/5/x/all/takeall, while gravestone_retrieval (602) has no quantity controls at all, only button / button_bank / discard. There is nothing to resolve to. The real defect underneath it was that reclaimItems read the DeathOffice container unconditionally even though isDeathsOfficeOpen accepts either variant, so on 602 it would read an empty container and report "took nothing". It now detects the variant and fails loudly, pointing the caller at reclaimAll(). Both interfaces' component lists are documented in the guide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getDeathsOfficeItems always read InterfaceID.DeathOffice.ITEMS, but isDeathsOfficeOpen accepts either retrieval variant. With the GravestoneRetrieval variant up it returned an empty list, so an office still holding items looked empty — both to callers inspecting it and to reclaimAll's inventory-full warning, which would report "still holds 0 item(s)" while items remained. It now resolves the container from whichever interface is visible, matching how reclaimAll already picks between takeall and button. reclaimItems is unaffected: its guard has already established that the DeathOffice variant is the open one before it reads anything. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…"no requirement"
The Draynor underwall tunnel (42 Agility) was usable at any Agility level.
Two rows in agility_shortcuts.tsv carried their Duration value separated by
SPACES instead of a tab:
3070 3257 0 3066 3257 0 Climb-into;Underwall tunnel;19036 [42 Agility 7]
3066 3257 0 3070 3257 0 Climb-into;Underwall tunnel;19032 [42 Agility 7]
split("\s+", 2) then read the skill name as "Agility 7", which matches no
Skill, so skillLevels was never written. Zero is how "no requirement" is encoded,
so the requirement did not merely fail — it disappeared.
Worse than permissive. blocksWalkingEdgeWhenUnavailable blocks the walking edge a
shortcut spans when the shortcut is unusable, precisely so the planner routes
around it. With the gate erased the edge stays open and the shortcut looks free,
so the planner PREFERS it as the shortest route and sends the walker back to a
wall it cannot climb. The same tunnel's other approaches (lines 54-59) were well
formed, which is why this only bit from the y=3257 side.
Three parts:
- the two rows repaired and normalised to the header's 9 columns
- the parser now warns when a requirement names no known skill, instead of
dropping it silently; an unreadable requirement and an absent one were
indistinguishable in the logs
- a test over the shipped TSVs asserting every Skills entry resolves. Verified it
fails on the pre-fix data naming both rows, so it pins the class and not just
this instance
The row looks correct in an editor — the 7 sits where it belongs visually. That is
why this needed a test rather than review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ht-clicking
Veos opened the conversation, reached the Port Piscarilius / Land's End menu, and
the walker walked away leaving it on screen.
Terminal travel decides how to pick a destination from a static name whitelist:
if ("Mountain Guide".equalsIgnoreCase(transport.getName())) -> DIALOGUE_DESTINATION
return DIRECT;
DIRECT means "the right-click chose the destination, nothing more to do", and
selectTerminalTravelDialogueDestination returns immediately on it. Veos's
ships.tsv rows name a destination as the ACTION (Port Piscarilius, Land's End,
Port Sarim) exactly like Cabin Boy Herbert or Captain Barnaby, so it resolved to
DIRECT — but NPC 10724 (veos_visible_travel_amulet) does not offer those as
right-click options any more; it asks in conversation.
Right-clicking a destination is genuinely better than talking, so that stays the
preferred path. The walker just has to notice when it did not get it:
resolveTerminalNpcInteractionAction already reports which action the NPC actually
offered, and already LOGS the fallback to a generic "Travel" — it simply did not
act on it. When the configured destination-named action is unavailable, the
destination was not chosen by the click, so it must be chosen in the dialogue
regardless of the static mode.
Also: the destination is not always in the first menu. If it is absent,
try a menu-opening option ("Can you take me somewhere?") and look again, rather
than reporting the option missing while it sits one click away.
Runtime-detected rather than whitelisted, so the next ferryman Jagex moves into
dialogue does not need a code change. The dead Veos and Captain Magoro branches
gated on action=="Talk-to" — which their rows never carry — can now go, but that
is deliberately left for a follow-up rather than bundled here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ot just distance
Reported as "it says it succeeded before reaching the destination, if the
destination was near interactable objects". Confirmed — one branch:
if (reachableTileCheck || (!walkableCheck && distToTarget <= distance))
return WalkerState.ARRIVED;
An unwalkable target is normal: you cannot stand ON a door, chest or bank booth,
so the walk must finish beside it. But distanceTo is straight-line and knows
nothing about walls, so being within distance of an object counted as arrival
even with a wall in between. The caller then interacted from the wrong side and
failed, while the walker reported success — wrong success, which is worse than a
visible stall because the script blames itself.
Arrival at an unwalkable target now also requires a reachable tile ADJACENT to
it: somewhere we could actually stand to use it. That is the difference between
"close to the object" and "able to use the object", and it is exactly what
straight-line distance cannot express.
Deliberately falls back to the old distance-only answer when the reachability BFS
returns nothing, so a reachability hiccup cannot convert an arrival into a walk
that never terminates. Declining an arrival that would previously have been
granted logs arrival_declined_unreachable, so if this does cost a termination the
line says so rather than the walk just hanging.
The BFS was already being computed for the walkable case; this reuses it rather
than adding a second one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ally paying off
collision_conflict compares live against STATIC, so it answers "how wrong is the
shipped map here" — the disease, not the treatment. It reads identically whether
or not the persistent store works, which is why a whole evening of那 numbers said
nothing about whether persistence was earning its keep. Every conclusion drawn
from them was about the map, and none about the store.
Adds a coverage counter taken against the overlay as it stood BEFORE the capture
was merged in:
overlayKnew=NN% (known=N new=N changed=N)
known static was wrong and we already had the right answer — a previous
visit spared us the blind one, which is the entire point of the store
new static was wrong and we had nothing — the blind first visit
changed the overlay disagreed with this capture: world changed, or stale
learning worth knowing about separately
The prior view is pinned before overlay.set(); mergeScene replaces regions rather
than mutating them, so it stays a true "before" rather than seeing the capture it
is meant to be compared against.
Expected shape: high "new" on first exploration, climbing "known" on repeat
routes. If "known" stays near zero on ground walked before, persistence is not
working and no amount of static-map regeneration will help — which is exactly the
distinction the existing metric could not make.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doorOther is the residual after the probe and both waits — ~790ms of a 3181ms
scan — and it is the only part of door handling that is neither the player
walking (2250ms, irreducible) nor scanning (60ms). It is therefore the only place
a fix could plausibly come from, and it is currently one undifferentiated number.
Two candidates, now timed separately rather than argued about:
doorInteract Rs2GameObject.interact: composition resolve, menu entry
construction, mouse click
doorVerify doorStillHasAction: a radius-13 rescan that resolves a
composition PER CANDIDATE outside the scan-scoped memo, plus nine
transport-map lookups each. Only runs when traversal failed —
but that is exactly the path a stuck door repeats
The reason for measuring rather than fixing: the sampled 3181ms scan released by
progress, so traversal SUCCEEDED and doorStillHasAction cannot have run in it.
Whatever consumed 790ms there was the click path. Reading the code makes the
rescan look like the expensive one, which is precisely the kind of plausible
inference that has been wrong repeatedly today — doorProbe turned out to be waits
rather than geometry, and the item fingerprint turned out to be loading
compositions on the client thread.
No behaviour change. Next door-heavy scan says which one to attack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd replan
Refusing a walled route click was always correct but was never a recovery. The
planner keeps producing the same route, the net keeps refusing it, and the walker
oscillates with no escape — reported as "the walker just became unrecoverable, it
clicked back and forwards but was stuck in a loop".
Root cause at the reported spot is map data, not logic. Probed every tile the
walker kept clicking at Sinclair Mansion:
2736,3460 n=true s=true e=true w=true
2740,3463 n=true s=true e=true w=true
2737,3461 n=true s=true e=true w=true
All four edges open on every one — the shipped map has no walls for that building
at all, while the live capture reported liveBlocksStatic=330 in the same scene
(every other reading this session has been single or low double digits). So the
pathfinder plans through the walls, the player-origin BFS proves the tile is
behind one, and route_click_walled fires forever. anchorIdx=-1 on every refusal is
the signature of that deadlock.
A refusal carries information nothing was using: the route crosses from reachable
to unreachable at a specific edge, and that edge is impassable whatever the map
says. Learning it makes the next plan route around. The first strike blocks the
edge for THIS session, so the replan takes effect immediately; persistence across
sessions still needs an independent second strike, which is what keeps a transient
refusal from poisoning the store. learnBlockedEdge returns false for an edge
already known, so the replan fires once per edge, not once per refusal.
Only edges with BOTH ends inside the BFS budget are learned — beyond it
"unreachable" means far away and the edge is innocent. That guard is the whole
correctness argument, so it is a decision table rather than a comment.
firstWalledRawEdge is deliberately pure: the obvious getClosestTileIndex start
hint reads the scene on the client thread and made the tests hang.
Does not fix the map data; makes the walker survive it. Fourth data gap tonight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit e93d3a5)
…re through it Doors could not chain. Every release condition in the traversal wait observes the PLAYER, not the door: isDoorEdgeResolved is "within 1 tile of the far side", "moved closer to the far side", or "stationary, near, and reachable". Despite the name it never looks at the door. So "resolved" means we already walked through, and nothing could consider the next door until we were physically past this one. That is the wrong budget by an order of magnitude. An unlocked door opens within one game tick of the click landing — 0.6s at most. The traversal wait caps at 2200ms, ~3.7 ticks, and it was being spent walking, not opening. Measured cost was doorWait=2250ms per raw scan against doorFind=60ms of actual scanning: the waits, not the work. Observing the door instead releases us the moment it is open, while the server keeps walking us through it — which is the window in which the next door on the route can be clicked. One click per door, at range, chained, which is what a player does. doorStillHasAction already existed for after-the-fact verification and was never a release condition; it is now, via doorObservedOpen. TRANSPORT DOORS (the moves-you class) were the risk worth checking, since the door-scan exclusion is `isCatalogTransportObject && !isDoorLikeSceneObject` — a transport door that is also door-like stays on this path. They keep their action after relocating us, so the new condition stays false and the positional conditions release the wait, which they do at once because being moved is precisely what they detect. The guard in doorObservedOpen is the correctness argument: doorStillHasAction cannot distinguish "the action is gone" from "no object matched", and the second reading happens whenever the probe leaves the scan radius — which would report a shut door as open the moment we drifted. Requiring the door to be observable before trusting the reading removes that. Polling is rationed: nothing to see before the first tick, and the observation is a scene scan rather than a field read, so it runs on an interval instead of every poll. Guardrail baseline regenerated: 20 lines, verified as pure synthetic-lambda renumbering (index-agnostic sets identical, zero non-lambda lines, no violation naming the new code). Needs a live door-heavy run to confirm; door_await now reports releasedBy=door-opened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y is shut The first live run produced zero releasedBy=door-opened, so the previous commit did nothing. The build was loaded — the classes carry the symbols and the client started after them — and the config defaults on, so the observation ran and always answered "still closed". It delegated to doorStillHasAction, whose predicate accepts any door-like object within TWO tiles of the probe. That is right for its own job (verify, then retry) and wrong as a release condition: in a door-heavy area a neighbouring shut door answers for the one we clicked, and the answer never changes. Measured on the second door of the run — released by progress at 1422ms after roughly five polls, on a door that was open by the first of them. Matching the probe tile itself, or the geometry of the edge being crossed, asks about the door the click was aimed at. Threaded as a flag through the existing predicate rather than a second near-identical one: the guardrail delta is then the same accepted violation with one more parameter, not a new entry to grandfather. The walker has 75 door methods already; another copy of this one helps nobody. door_await now carries openPolls, because a release that is not door-opened was ambiguous between "the observation never ran" and "it ran and the door was shut", and that ambiguity cost the run. Not yet fixed, from the same log: the FIRST door never opened at all (releasedBy=timeout, traversalWaitMs=2683, no progress) — the click did not land, and the recovery/interim clicks that followed are downstream of that stall, not an independent scheduler race. Guardrail baseline: one line, the signature change, verified as the same method and same target with no new violation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two live runs have ended without a single releasedBy=door-opened. openPolls proved the check RUNS (2-3 polls per door) but not what it read, so the tighten to the probe tile could not be told apart from the check being structurally unable to see an open door. Inferring the difference has now failed three times — classifier, impostor resolution and neighbour radius all read as correct on inspection. door_await gains saw=, evaluated once and only when the release was not door-opened: strict=false the check said OPEN, so a non-door-opened release is plumbing, not observation strict=true the door on this very tile still offers the opening action strict!=loose the tighten worked and a neighbouring door had been answering Deliberately built from the two existing readings rather than by enumerating scene objects: the enumeration version called TileObject#getWorldLocation() in a new method and would have added guardrail entries for a temporary diagnostic. This adds none — the baseline delta is 20 lines of pure synthetic-lambda renumbering, verified. Costs one extra scan per slow door await, and only on the awaits that already log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…menu The walker had no direct reading of "this door is open". Every release condition in the traversal wait observes the PLAYER, and isDoorEdgeResolved — despite its name — is position only: within a tile of the far side, closer to the far side than the near one, or stationary-and-reachable. It learns a door opened by having already walked through it, which is exactly why doors cannot chain. The client's own collision data answers this directly. It is server-driven, so a door that opens clears its movement-block flag on that tick, and Rs2Tile already reads those flags for reachability. Rs2Tile.isEdgePassable asks about the one from->to step. Deliberately NOT isTileReachable, which was the existing (and only) collision-flavoured condition. That runs a BFS, so a still-shut door with a long way round reports the far tile as reachable and would release the wait having gone nowhere near the door; it also costs a scene search per call. One flag read has neither problem. This supersedes the object-action check as the primary signal. Two live runs produced no releasedBy=door-opened at all, and three rounds of inspecting the classifier, the impostor resolution and the match radius explained none of it. Collision does not care whether a door's menu text changes when it opens, which is the assumption that kept failing. The action check stays as a fallback rather than being removed, so nothing regresses where it did work. Unknowns answer false — off-scene, wrong plane, or an instance, where raw coordinates make the scene conversion unreliable. Callers release early on a true, so an unknown must never read as open; those cases fall through to the positional conditions as before. The collision rule is split into a pure isStepAllowed and covered by a decision table: per-direction flags (a door blocking north must not read as blocking east), stepping into a fully blocked tile, and diagonals not cutting corners. Guardrail baseline: 6 entries for isEdgePassableInternal plus one lambda renumber. Same scanner limitation as the already-baselined isTileReachableInternal beside it — the body is only reached through runClientReadBoolean, which runs on the client thread or hops via getClientThread().invoke(), verified before accepting the entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t decides The collision edge check never released a door either — openPolls 1, 5 and 4 across three doors, no door-edge-open. A bare false from isEdgePassable is ambiguous between "the door is shut" and "this could not be decided" (instance, plane not loaded, off-scene), and those want opposite responses: the first means wait, the second means the signal is unavailable here and the whole approach needs rethinking. isEdgePassable now records its decision — open / blocked / instance / plane-not-loaded / off-scene / no-flags / not-adjacent — and door_await carries it as edge=. Also fixes a flaw in the previous diagnostic. saw= was evaluated when the log printed, which is AFTER the wait released, so it described the wrong instant: one run reported strict=true loose=false, a pair the predicate cannot produce, because the two scans happened either side of the door changing. The edge decision is captured at poll time. No new guardrail entries: the recording sits inside the existing isEdgePassableInternal rather than in a new method with its own client reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n a door outcome
edge= data closed the case: every "still shut" reading during a ranged click's wait was
CORRECT. A door's 0-1 tick opening is measured from the interaction, not the click — when
the click is issued from range the server first walks us over, and the door is genuinely
shut for that entire approach. There was never a broken observation; there was a wait
whose shape assumed the click happened from adjacent.
Two concrete failures follow from that shape, both measured live:
- The flat 2200ms cap expired mid-approach (releasedBy=timeout at 11 tiles with the
player still walking), handing the recovery machinery its window — the competing
replan/interim clicks the user called "a competing race for recovery".
- The positional conditions release BEFORE the door opens: "progress" at Chebyshev 2
mid-approach, "edge-resolved" on reaching the near side. The release fails
verification (door still shut), and the door is interacted a second time from
adjacent — two full interactions per ranged door (1139ms + 2939ms on the same door
in one run).
A ranged click's wait is now an APPROACH: the budget is sized by the click distance at
walking pace (capped 8s), and the positional conditions are disabled — it releases only
on a DOOR outcome (collision edge open, opening action gone, conversation) or on a stall
(idle-accept, unchanged) / walk cancellation (new, since budgets can now reach seconds
where the flat cap bounded a stale hold at 2.2s). Adjacent clicks keep today's conditions
and budget exactly.
The click distance comes from the await ticket's before-position, so no call site
changes. A "blocked" edge reading now also skips the scene-scan fallback — it is
definitive, and the fallback only earns its scan when the edge cannot be decided.
On release the door is open with the player beside it: verification passes on the first
attempt, the cross-nudge issues the follow-through click, and the next door on the route
is immediately clickable. That is the click-walk-click chain, with the double-interaction
serialization deleted.
Budget rule is a decision table (adjacent unchanged / approach-time scaling / hard cap).
Guardrail baseline: pure lambda renumbering, verified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… tile After a door opens the player stands on its near side with the click consumed — opening does not walk you through. The follow-through was a canvas click on the single far-side tile, which cost ~1.5s of nudge machinery per door and then ANOTHER click to resume the route. A player clicks once, somewhere ahead. tryDoorEdgeCrossNudge now selects the furthest REACHABLE route point past the opened door (selectPostDoorRouteTarget) and clicks that: crossing the edge is still crossing it when the destination is further along, so the nudge and the route-resume click fold into one. The next door on the route then comes into ranged-click reach while we are already moving toward it. The reachability gate is the safety argument, learned the hard way: the reverted 3d03ed1 clicked a tile the walled-route net had just REFUSED, because it selected without the gate. Every candidate here must be in the player-origin BFS — one BFS per nudge, map lookups per candidate, not the per-candidate reachability probe that froze MLM's loop (tryPostDoorFastMinimapClick still pays that; not touched here). The BFS runs after the door opened, so it sees through the doorway. The success test (isDoorEdgeNudgeResolved) is unchanged, and with no route or no qualifying candidate the single-tile nudge behaves exactly as before. The edge must be found ON the route — a route that merely folds past the door proves nothing about what lies beyond it. Decision table covers furthest-reachable selection, skipping tiles the BFS cannot vouch for, the null fallback, the off-route edge, the Euclidean cap and the plane break. Route threading: handleDoors passes its raw path through tryHandleDoorObject and into the nudge; the tail-loop recent-attempt re-nudge passes rawPath. Recovery-path callers without a route keep the old behaviour via the delegating overloads. Guardrail baseline: one line, the tryHandleDoorObject signature gaining the route parameter — same method, same accepted target, verified. Note for the record: one full-suite run flagged RouteClickTargetRegressionTest, which is pure path-generation and untouched by this change; it passed in isolation and in the final full suite. The failing run coincided with the machine killing the next Gradle daemon (exit 137) — the test's 10s pathfinder cutoff under memory pressure yields a partial path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the post-door click now aimed past the door, success looks like "went through and kept moving" — and the resolution test could not see it. It accepted only arrival on or beside the far-side tile, which has a second, older blind spot the live log exposed: a nudge starts on fromWp, so beforeTo=1 and "afterTo < beforeTo" can only fire on exactly toWp — and a RUNNING player covers two tiles a tick and may never be observed there. Observed as 3369 -> 3367 -> 3365 with every poll reading unresolved, then door_edge_nudge_unresolved for a crossing that had plainly succeeded. The fact being tested is "did we cross the door's edge", so test it directly: the component of the player's displacement along the edge's own axis reaches the far side. Door edges are cardinal; anything else keeps the strict near-toWp rule alone (the wrong-neighbour diagonal case still answers false). A false "failed" is not cosmetic: the caller skips markNearbyDoorFamilyOpened and reports the door unhandled, inviting a re-probe of a door already behind us. Decision table covers the exact live case, the skipped-tile running case, the east-door variant, and walking parallel along the NEAR side (not a crossing, however far it gets). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oors chain Two inter-door costs measured on the 3-door Lumbridge gauntlet (33s total, ~13s of it between doors while standing still): STALE NUDGE (~2s at walk start). The recent-door-attempt edge survives across walk sessions and its 6s window comfortably spans a script's walk-to-walk gap, so a fresh walk re-nudged the PREVIOUS walk's door — observed as first_door_edge_nudge pointing BACKWARD at walk start (before=after: the click did nothing but burn the 1.2s wait). Cleared at markWalkSessionStart, exactly like the interim target it sits next to, which existed for the same reason. GLOBAL COOLDOWN SERIALISES CHAINS. The 1800ms window is anti-hammer for ONE door — re-clicking the same edge before the world catches up. A DIFFERENT door immediately after a successful open is chaining, not hammering; holding it for the full window cost up to 1.8s per adjacent-door pair. The throttle is now edge-scoped: the same edge keeps the full window, a different edge owes one game tick (600ms), and the dialogue defer stays unconditional. Pure decision in Rs2DoorHandler with a table; the unused no-arg wrapper is deleted rather than left as a trap. For the record, twice tonight the full suite flagged RouteClickTargetRegressionTest — pure path-generation, not loaded by this diff. It then failed once and passed twice in isolation with identical code: its 10s pathfinder cutoff under machine load returns a partial path. Flagged separately to be made hermetic (it also reads the developer's real learned-blocked-edges file via defaultFile()). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The post-door verification decides whether the interaction opened the door — and it asked the loose question, any door-like object within two tiles still offering "Open". Beside a doubled door that reads the NEIGHBOUR: measured live as saw=strict=false loose=true — this door open, the one beside it shut — reported as "did not traverse, action still present", which suppressed markStationaryDoorOpened and the post-door route click entirely. The walker stood still ~2s per door until the generic click machinery caught up, in exactly the door-dense places chaining matters. The same neighbour-answers-for-it bug as the open observation, one call site further down. Both verify sites now use the strict per-tile/per-edge match. The loose reading survives only in the saw= diagnostic, which deliberately reports both side by side, and the loose delegate is deleted so nothing reaches for it again by accident. Confirmed working in the wild within the hour: a genuinely-shut gate quick-failed verification honestly (doorVerify=80ms) instead of burning a wait. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 7s timeout at the (3115,3449) door decomposed into three gaps, each now closed: PASSED THE DOOR. Ranged holds had every positional release disabled, because near-side proximity fired mid-approach. But "past" is not "near": the player ended standing ON the far-side tile with the door still shut — the server had walked them there through another opening — and the hold ran its full budget anyway. Standing on toWp, or beyond the door along its own axis, is unambiguous and now releases (passed-door). The axis reading is the same one the nudge-resolution fix introduced, moved to Rs2DoorGeometry and shared instead of duplicated. REPLANNED UNDERNEATH. Live collision saw the awaited edge blocked, recalculated, and routed around via the trapdoor — one second before the timeout — while the hold kept waiting on the old plan's door. The cancel supplier now also releases when the Pathfinder instance changes (the replan signal), labelled cancelled-or-replanned. The walk-target check stays for the cancel/retarget case. WHY WAS IDLE-ACCEPT SILENT. Unanswerable from the log: silence is correct if the player walked the whole budget and a bug if the pose-based isMoving trap held it. door_await now tallies polls/movingPolls/animPolls so the next timeout answers it from one line. Correction for the record: the earlier analysis suggested the route never crossed that door's edge. Wrong — handleDoors derives the edge from consecutive raw route tiles by construction, so a route-crossing precondition is an invariant that already holds, and no such check was added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ded it Reported: the walker locks the minimap at max zoom and re-zooms it the moment the user changes it. Both walkMiniMap AND isMiniMapClickable forced setMinimapZoom(5) on every call — including the probe, so merely ASKING whether a tile was clickable moved the user's zoom. There is no correctness reason for any of it. Perspective.localToMinimap reads the LIVE zoom (s = 4d / client.getMinimapZoom()) and scales its range with it, so the conversion is exact at every setting. Zoom only moves a trade-off: zoomed IN shrinks clickable range (~16 tiles at zoom 5, ~40 zoomed out — the forcing was costing reach, not buying it), zoomed out shrinks pixels-per-tile. A far point that does not convert already degrades through the existing fallbacks to a nearer route point — which is what a human at that zoom does — and the tile-exact clicks near walls use the canvas path, which is pixel-precise at any zoom. walkMiniMap now clicks at whatever zoom the user has; the probe is side-effect free. The explicit-zoom overload keeps its contract for external callers that genuinely want a particular zoom, but nothing in the walker calls it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…click Scripts call walkTo for five-tile hops — spot changes, bank-to-spot shuffles — and every call paid the pipeline's fixed head: transport refresh, pathfinder, session setup, the startup handler pass. Measured at 1.3-1.5s before the FIRST click for moves a human does in one click. The existing short-circuit (tryDirectShortWalk) sits INSIDE the pipeline, takes the planned path as an argument, and can only ever save the tail. With hundreds of Hub scripts funnelling through this one entry point, the entry point is the fix. The gate is a reachability proof, not a distance guess: a target within 12 tiles that is BFS-reachable on the client's live collision flags needs no door, no transport and no plan — a shut door on the way reads as blocked and fails the gate, so anything that needs the pipeline still gets it. Deliberately strict: the target TILE itself must be reachable. Walk-beside-an-object calls (booths, trees) decline and take the full pipeline, because "within distance" with a wall between is exactly the false arrival the pipeline's richer checks exist to refuse — coverage traded for correctness on the hottest path in the client. Canvas click first, minimap at the user's own zoom second. The wait is bounded by walking pace plus slack (decision table), stall detection is position-diffed rather than isMoving() (the pose-based read stays true while turning on the spot), and walkUntil completion conditions are polled in the wait. Every non-arrival outcome — no click landed, stall, budget, interrupt — falls through to the full pipeline exactly as if the fast path had never existed: the degraded case is yesterday's behaviour, never a new failure mode. Placed inside the walker lock ahead of the banked-transports branch, so both walk modes benefit and concurrency semantics are unchanged. One short_walk log line per fast walk (result=arrived/completion/handoff) so live runs show it engaging and winning. Guardrail baseline: 23 lines, verified pure synthetic-lambda renumbering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observed twice, identical fingerprint: the walker stands at a door working it, and
"[LiveCollision] route step A -> B now blocked; recalculating" yanks the route out from
under it — the Edgeville 3115,3449 hold-to-timeout, and tonight's 2585,3141 mid-handling
reroute the user called out ("it went out the door and then it rerouted for no reason").
Both doors are CATALOG TRANSPORT doors, which is the fact that explains it.
LiveRouteValidator recognises transports by shape — cross-plane or non-adjacent steps —
but a door transport joins two ADJACENT SAME-PLANE tiles, so its step is
indistinguishable from walking. While the door is shut the live collision edge honestly
reads blocked. That is the door's NORMAL state, not an obstruction: the pathfinder
planned through it as a transport edge and the runtime executor opens it on contact.
Recalculating on it is always wrong.
The validator now takes a transport-step predicate and skips planned catalog-transport
edges; the plugin supplies it from the transports-by-origin map (one map lookup per
validated step). Scanning continues past the skipped edge, so a genuine obstruction
further along still triggers the recalc this validator exists for.
The validator's class doc claimed openable doors read as passable in the overlay via the
door mask — true for scene doors the mask catches, demonstrably not for this class.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…imed, not just catalog ones The transport-catalog predicate from 5455512 was the right fix for the wrong subclass. The door that kept triggering "route step now blocked; recalculating" in both directions is object 81 — fightarena_door1, the Fight Arena quest door — identified live off the agent server. It is in NO transport catalog (grep of every shipped TSV: nothing at 2584/2585,3141), so a catalog lookup can never exempt it, and the user's report held: cross it westbound, recalc; walk back eastbound, recalc again, straight into a script-level retarget. Quest doors are also exactly the class the live capture's door mask is least reliable for (impostor-varbit compositions), so "the overlay reads door edges as passable" cannot be assumed for them either. The walker already stamps every door attempt with its edge before clicking. That stamp is the authoritative "the executor owns this edge" signal, catalog or not: Rs2Walker.isActiveDoorEdge(a, b) answers it for either direction within a 10s claim window, and the validator's skip predicate now consults it ahead of the catalog lookup. A door the walker is actively working can no longer have the route recalculated out from under it, whatever kind of door it is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…shut itself behind them Watched live at the Fight Arena doors (agent server, position sampled every 1.2s): the character crosses the door east, and one tick later these quest doors close themselves. "Shut door on my route" is then TRUE again for an edge the player is already past, and the door machinery re-engaged it — the character stepped BACK through the door it had just crossed, then oscillated: through, back, sidestep, through. The walker also issued a route click one tile BACKWARD (to=2583 with the goal 9 tiles east) because with the door shut again, the only reachable route tile was behind the player. The invariant that ends it: a route door edge whose axis the player has already crossed, in route direction, is RESOLVED for this walk — whatever the door reads right now. handleDoors gates on it at entry (covering the segment loop, the raw scans and the recovery probes, which all funnel through it), and tryDoorEdgeCrossNudge treats at-or-past the far side as success instead of aiming its fallback click at toWp — one tile backward — for a player standing beyond it. Directionality keeps the guard honest: from/to derive from consecutive route tiles, so a walk genuinely routed back the other way carries the reversed edge and is unaffected. crossedDoorAxis is the shared, decision-table-tested primitive from the nudge-resolution fix. Also for the record from the same live watch: quest automation was OFF — the larger reversals in the trace were the user's own manual play between walker targets, and the zero "now blocked; recalculating" lines confirm the two validator fixes hold on a build that includes them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hat the BFS can vouch for Un-pinning the minimap zoom made the question live: zoomed out, the minimap shows ~38 tiles of radius while every stride stayed capped at the flat 11 tuned for the pinned zoom-5 window. A zoomed-out player clicks big strides — that is half the point of zooming out — so reach now follows the visible radius (20*4/zoom, the exact scale Perspective.localToMinimap uses), minus two tiles so a click never lands on the rim. Floored at 11: a fully zoomed-IN minimap behaves exactly as before. Capped at 18, and the cap is NOT the minimap's limit but the walled-click net's: every stride target must sit inside the player-origin reachability BFS (20-step budget) or a wall between could not be detected, which is the Clock Tower click-through-the-wall class. 18 leaves two steps of path-vs-Euclidean slack inside that budget. Raising it further means growing a client-thread BFS and gets measured first. At the default zoom (4) strides go 11 -> 18; fully zoomed out likewise 18. Wired at all five stride sites — the tail-loop stride, walkStep, the route-backed final click, the interim continuation click, and the reachability sample radius that must cover whatever the stride can reach. Pure decision (zoomAwareMinimapReach) with a decision table; degenerate zoom readings fall back to the old flat reach. Amended: the first cut of this commit accidentally swept an unrelated in-progress QuestingScript.java change from the working tree (git add -A); this one carries only the walker change, and the WIP is back in the tree untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit b949273a83dd24f1e6648d075ff4d3fd3a57d3ed)
…f flooding a million nodes 37 SEARCH_EXHAUSTED terminations in one evening's log, each expanding ~1.1M nodes over 1.2-3.8s of CPU — mostly for destinations TWO TILES away. An unreachable target made the forward search flood the entire connected world component before admitting it, and retries hammered the same flood. A bounded reverse flood from the target answers first. It reuses the bidirectional machinery's getReverseNeighbors with the incoming-transports index, so a room entered by a staircase or door transport grows past its walls and reads reachable — an upstairs destination is never falsely sealed — and anywhere-teleports (null origin, absent from that index) are checked per component tile. Only a frontier that drains under the 1024-node budget without touching start proves anything; big components are INCONCLUSIVE and the full search runs exactly as before. A sealed verdict does not abandon the caller: the search retargets the component's walkable rim, sorted nearest to START — the reachable rim is on the approach side, and goal-side ordering burned the whole substitute budget on best-effort (measured 50k nodes at Shantay Pass vs a direct walk to the near-side rim). The walk still ends beside the sealed area, which is all the old flood's path ever bought, and the termination stays SEARCH_EXHAUSTED because reaching a substitute is not reaching the caller's target. The substitute pass carries both a 2s leash and a 50k node budget: its targets can themselves prove unreachable (a moat tile whose rim is an unreachable pocket), and the time leash alone WAS the flood. The probe is failure-proof — any exception degrades to the full search, never a failed run. Measured on the pinned corpus: Shantay no-ticket 1.1M nodes/multi-second -> path to the gate's north side in 185ms goal-sorted, hundreds of nodes start-sorted; sealed courtyard tile -> approach path beside it; void tile -> instant empty result. The Shantay no-ticket corpus assertion needed its proxy tightened: "visits within 2 of the gate" also condemned a route that walks UP TO the gate and stops — which is what the fast path now correctly produces, and what a coinless player does. It now measures the crossing itself (a tile strictly south of the gate line at the pass) plus a must-not-arrive check, ending the proxy games its own comment history documents. For the record: one full-suite run flagged RouteClickTargetRegressionTest, which then passed 3/3 in isolation on identical code — the known tiebreaker-lottery flake, being made hermetic separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit f9cfb3e1f078ad7a4d4d4735b947c3f60e58480c)
…es.tsv is the authority Policy decision: the two-strike persistent learned-edge store is gone. What stays is the part that was always right — the observing session blocks a failed edge immediately, because it just watched the failure and anything less loops the walker into the same obstacle (the Sinclair deadlock escape). What goes is everything that existed to manage persistence: the file under ~/.runelite, the constructor load, the strike counting, the independence window, the probation semantics, and the test seam for simulating restarts. The store's history argued for this. Its probation machinery existed to self-heal its own poisonings (the Wydin door needed a hand-edit before two-strike, and two-strike existed to prevent the next one). Its default file leaked developer state into every test that constructed a PathfinderConfig — the hermeticity exposure flagged on RouteClickTargetRegressionTest. And in months of use it never accumulated a single confirmed row: the user's live file holds four probation entries, nothing enforced. An edge worth remembering across sessions is worth a reviewed row in blocked_edges.tsv, which ships with the client, survives the live-collision override, and is already the home of the Hemenster/Al Kharid/Varrock permanent blocks. learnBlockedEdge keeps its signature and return semantics (true = newly blocked this session), so Rs2Walker's walled-route learning and wrong-traversal callers are untouched. LearnedBlockedEdges and its parser/strike tests are deleted; the session semantics get their own decision table (block-once, direction-scoped, nothing survives a fresh config, null-safe). The suite run flagged only RouteClickTargetRegressionTest, the known tiebreaker-lottery flake, green in isolation immediately after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 68fccb17de95c2fa527233c2ce211385950cae2a)
The 1158ms post-login client-thread freeze hides in code the stage timers never covered.
The outer wrapper read transports=1154ms while the inner stages (merge/cache/filter/
similar) summed to ~30ms on every instrumented run — and the slow-stage breakdown line
never fired for the slow case, because its threshold summed only the measured stages.
Three regions were dark:
entry the quest-state and item gates (four getQuestState calls — quest state can
run a clientscript — plus fairy-ring inventory/equipment/bank checks and the
gnome glider/spirit tree/quetzal gates)
key leagues context + the cache-key fingerprint
verify/ condition encoding, verification hashing, and the snapshot capture's deep
capture copy of ~5k transport sets
Each now has a timer, carried on the stage log AND the slow log, and the slow threshold
sums all eight stages so the breakdown actually prints when the freeze happens. The next
slow login names its stage instead of hiding it; the fix targets whatever it names.
The only suite failure was RouteClickTargetRegressionTest, the known tiebreaker-lottery
flake, green in isolation immediately after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit a356c1b3795e8311b6f8d023323e14e1948b5386)
… instead of stalling forever User-reported as widespread, and it is: scripts click NPCs and objects with a shut door between, the server walks the player to the door and prints "I can't reach that!", and nothing recovers — observed live as the Kudos museum script blocked on three separate interactions in a building made of gates. The interaction layer's ONLY reachability recovery was dead code. The chat listener has set cantReachTarget on every can't-reach message all along, but the flag that gates the recovery (isCantReachTargetDetectionEnabled) defaulted to false and NOTHING in the repo ever set it true. Even enabled, the recovery was wrong twice over: the NPC path walked to a nearest-line-of-sight tile — which for an NPC behind a door selects tiles on the unreachable side — and its LOS "all clear" branch cleared the flag without walking, so a through-window NPC re-clicked forever without ever escalating. The object layer had no reactive handling at all, and its opt-in checkCanReach variant gated on line-of-sight — which solid objects fail from everywhere (docs/entity-guides) — before falling back to a raw canvas click that opens no door either. Detection now defaults ON, and both layers recover the same way: on the game's own can't-reach verdict, walk to the target with Rs2Walker.walkTo(loc, 2) — the walker opens doors en route, and its arrival semantics require a standable tile BESIDE an unwalkable target, which is precisely the reachability proof the follow-up click needs — then clear the flag and click from beside it. The retry escalation (pause + operator message after 3-5 failed rounds) is preserved on both paths. The trigger stays the game's own message, which is what keeps this safe: a ranged attack through a fence or a shouted conversation across a chasm never prints can't-reach, so legitimate at-range interactions are untouched. LOS is consulted nowhere. Amended: the first cut of this commit accidentally swept the user's staged Kudos and Varrock-cleaner plugin work in via the shared index; this one carries only the interaction-layer change and their staging is preserved untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 4ec3ab817114cdd5838c0bec6cff708922feb7e3)
…its own doorways The Kudos museum dead-end had a second cause under the interaction-layer one: every route to a museum-interior target was unplannable, so even the new can't-reach recovery had nothing to work with — walkTo cannot open a door the PLANNER refuses to route through. The proposed fix was a transports.tsv row for interior gate 24536 at (3261,3446)->(3261,3447). Verification found the actual gap one level deeper: restrictions.tsv has carried an unconditional "# Varrock museum" block since a Feb 2025 bulk upstream commit (no stated rationale) restricting the south double-door tiles (3264/3265,3442) and the gate tile (3261,3446). Restricted points are skipped as graph nodes entirely, so a transport row could never have helped — and none is needed: with the restrictions lifted, the corpus proves the interior routable immediately, meaning the static map already holds these as ordinary passable door edges. The runtime door machinery opens them on contact — today's live log shows exactly that for the south door at (3265,3442), which the walker opened in 1.2s on a walk that STARTED inside the restricted tile. The likely original motive — the pre-rewrite executor could not survive these doors, so the area was banned — is obsolete: doors chain now, and a museum through-route is at worst a valid shortcut. Pinned by a corpus route from outside the south door across the gate line to a display-pen tile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 040c6e9963287240e584340158d6ae36f343f7e3)
…t, not a door
User was right and my previous commit was half a fix. Lifting the restrictions let the
PLANNER route through the museum doorways, but gate 24536 (vm_barrier_guard_gate) is the
moves-you class, so the EXECUTOR still had nothing it could do with it.
Measured through the agent server rather than inferred, standing beside it:
player (3261,3447) -> click Open -> (3261,3446)
player (3261,3446) -> click Open -> (3261,3447)
player (3261,3447) -> click Open -> (3261,3446)
One click relocates the player across the barrier every time, and the gate's composition
never changes (transformedId stays 24536, action stays "Open" — it never enters an open
state). That is the Al Kharid toll gate / Wydin back room signature: the door pipeline
waits forever for an "open" that cannot happen, so the edge needs catalog rows. Added as
a bidirectional pair beside the Wydin rows that document the same class.
Note the trap this hid behind: the static map already reports every edge at both tiles
passable (probed n/s/e/w all true), so the planner was happy to route through and only
the executor failed — exactly the "collision says yes, executor can't" shape recorded in
the transport-doors notes. Collision evidence alone would have said there was nothing
wrong here.
The corpus test from the previous commit was too weak to have caught any of this: it
asserted the path passed within one tile of the gate, which any route merely reaching the
doorway satisfies. It now asserts the route SELECTS transport 24536.
Not claimed: an exhaustive audit of every museum interior door. The two south doors
(24565/24567) are ordinary — today's live log shows the walker opening one and stepping
through in 1.2s — but the agent server's /objects endpoint refused radii past ~6 tiles,
so the interior beyond the barrier is unsurveyed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 24a013b9c0ef50760243eb374e9066dc07d1e433)
…rings processWalk carried its control flow in a String. Forty-seven assignment sites produced forty-three distinct values, and eight places downstream matched them by equality or startsWith to decide three things: whether the iteration counted as route progress (partial-retry budget), whether it was exempt from the tail-iteration cap, and whether a post-door canvas nudge was owed. A value no branch had classified simply fell through to the default in each, silently. Two of the reasons are produced inside a ternary and never appear in a search for `exitReason = "..."`, so the set could not even be recovered by reading the code. Replaces it with WalkExit: the reasons are now enumerable, the three behaviours are flags on the constant, and a new value cannot be added without deciding what it means. Wire names are preserved exactly, because live walker debugging here is log-driven and renaming a reason would blind the one diagnostic that works. This commit is deliberately inert. WalkExitTest asserts, for every constant, that each flag agrees with the legacy string predicate evaluated on that constant's wire name, so the classification is provably unchanged. The legacy predicates stay (deprecated, unused by production) purely to hold that characterization up; the follow-up that corrects the classification moves the expectations in that test, making the test diff the record of exactly what behaviour changed. The architecture guard caught the three lines this added and is ratcheted DOWN 1647 -> 1646 rather than raised. It was raised once already to absorb a merge; doing that again would make it a rubber stamp. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cing
On a partial path — a route the pathfinder could not run all the way to the
goal, which is every long or awkward walk — each iteration that is not
classified as route progress spends one of three retries and forces a full
replan. Three of them and processWalk returns UNREACHABLE and the walk is
abandoned.
Fourteen reasons were misclassified. Every one of them means the walker
either just advanced the route or is waiting on movement it issued itself:
- recovery took a transport or mined the obstacle on the blocked edge
(transport-handled-local-reachability, frontier-obstacle-handled)
- a click was issued and the player is walking (local-recovery-click,
door-suppressed-approach-click, recent-door-edge-nudge,
route-move-in-flight)
- the door actually opened (the three door-edge-resolved-* reasons)
- we are waiting on an action we issued (door/transport settle yields,
door-traversal-pending, recovery-click-preempted-by-action)
- the pass was abandoned because the player MOVED
(recovery-position-stale)
So three settle windows at one ordinary door, on a partial route, could
exhaust the budget and abort a walk that was working exactly as designed.
The predicate this replaces documented this very failure mode in its own
javadoc and then covered about half the cases. It could not cover the rest,
because its list was hand-maintained against a set of forty-three strings
that no one could enumerate: seven were matched by a startsWith("door-handled")
prefix, which silently excluded door-edge-resolved-* and
door-suppressed-approach-click precisely because they read like door-handled
reasons without matching the prefix.
The divergence from the old classification is pinned as an explicit list in
WalkExitTest, asserted in both directions: an unlisted reason that changes
meaning fails, and a listed one that reverts fails too. The reasons that
genuinely mean "not advancing" are pinned separately, so the budget still
drains and a truly unreachable goal still terminates.
Follow-up: the deprecated string predicates in Rs2Walker are now referenced
only by that divergence test. They can be deleted once the historical
baseline moves into the test as data.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
For fifteen seconds after taking a transport the walker deliberately runs degraded: it skips the raw scene scan, skips the per-segment door, rockfall and transport handlers, disables ranged door dispatch for the whole pass (one skipped segment sets segmentSkippedThisPass, which withdraws the nearest-obstacle guarantee that ranged dispatch depends on), and bypasses the off-path recalc entirely. That is right for the seconds after a landing and badly wrong for a walk that has only just started: a fresh walk would ignore the door in front of it and never replan when it drifted off route. setTarget(null) already cleared the handoff on the normal completion path, with a comment saying exactly why. Walk-session start did not agree: it nulled the three location fields and left lastTransportHandledAtMs, which is the field every window check actually reads. So the window stayed armed for its full duration while the destination it describes was already null. The gap showed up on any walk that ended WITHOUT clearing its target — an exception, the tail cap tripping, or an external cancellation, the last of which is routine when a quest script interrupts a walk mid-route. Clearing at session start is sufficient on its own: walkWithStateInternal is the only caller of markWalkSessionStart and the only route into processWalk, banked walks included. So the walk-ending paths do not each need their own clear, and nothing is added to processWalk. Also drops lastTransportHandledAtLocation, which was written on every transport handoff and never read by anything. Removing it takes a Rs2Player.getWorldLocation() client-thread hop off the transport path. The state reset is split out of markWalkSessionStart as resetWalkSessionState so it performs no game reads and these staleness invariants can be unit-tested rather than re-discovered live. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… regression calculationCutoffMillis is a NO-PROGRESS guard, not a runtime budget. Under CPU contention — a full-suite run, or the client running alongside the build, which is the normal state of a dev machine here — the search gets starved and returns a best-effort PARTIAL path. A partial path wanders through tiles this test requires to be absent, so it fails in exactly the shape of a real routing change. That is not theoretical. This test going red was read as a route-data regression specific to one branch, and sent an investigation off bisecting for a change that did not exist. The apparent "green on one branch, red on the other" split was an artifact of isolated versus contended runs: a clean worktree at the same commit passes. The route is now verified to actually reach the goal before any of its content is asserted, with one retry and then an explicit "pathfinder starved — INCONCLUSIVE, not a route regression" failure naming where the search stopped. The no-progress cutoff goes 10s to 30s, which costs nothing on a healthy run because the guard resets on every heuristic improvement. Deliberately not addressed: the pathfinder's per-node random tiebreaker can vary equal-cost routes, which is a separate source of flakiness here. A hermetic rework of this test exists on another branch and should not be duplicated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…and see the livelock Two things end a processWalk iteration: the partial-retry accounting and the tail-iteration exemption. Both were inline, both are load-bearing, and neither could be tested — which is how a partial route came to report UNREACHABLE while the player was still advancing. TailDecision now owns the decision and is pure: given "did we arrive", "is this a partial route", the exit reason and the budget, it returns the action. The caller still does the work — replanning, telemetry, clearing the target. Thirteen decision-table cases pin the interactions, including the budget-refill rule, which is subtle enough to have needed a paragraph of comment to survive: the route-progress timestamp is also bumped by a mere replan, and every retry replans, so refilling on the timestamp alone would let a retry refill the budget it just spent. Also makes the loop's real termination behaviour visible. The iteration cap is not a bound: several exit reasons decrement the counter, so a walk that keeps producing one of them goes round forever, and nothing else in the call chain imposes a time limit. Two observations now say so out loud — a wall-clock budget, and a cap on uninterrupted tail-exempt iterations, which is the state the iteration cap structurally cannot see because those iterations refund their own charge. Both are OBSERVE-ONLY: they log and do not abort. A budget that kills a working long walk would be a worse bug than the livelock it guards against, and a banked walk across several transports is legitimately minutes. Decide enforcement from live logs, once we know they fire on real livelocks and never on healthy walks. processWalk 1646 -> 1641 lines; the guard ratchets down again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both of these come from one Ardougne-to-Catherby-to-Ardougne run that succeeded end to end. A walk arriving is not evidence that it was right. 1. Stop learning a blocked edge from the BFS frontier. The walled-route net refuses a click when the selected tile is Chebyshev-near yet absent from the player-origin BFS, and then LEARNS the first route edge that leaves that BFS as blocked. But the proximity guard is Chebyshev while the BFS budget counts steps, and those are not the same thing: a tile thirteen tiles away as the crow flies can be thirty steps away around a building, and it is then missing from the BFS for want of budget rather than because anything blocks it. The log caught it outright at the Port Sarim / Land's End docks. A click to (2760,3238) was refused as walled and the edge (2759,3230)->(2759,3231) learned as blocked. Nine seconds later the walker was standing on (2760,3238), having simply walked there. Refusing the click on that evidence is merely conservative and has fallbacks. Writing it into the learned-blocked-edge store is not: routing believes it for the rest of the session. So the edge is now only convicted when its near end is strictly INSIDE the frontier — the BFS expands every tile below its budget, so an interior tile whose neighbour is still missing proves the neighbour unreachable, whereas a tile sitting AT the budget never had its neighbours enumerated and proves nothing. This also removes the replan that followed each false conviction, which was on the critical path of the first click: two of the three walks in the log took ~5.4s to their first click against ~1.1s for the one that did not trigger it. 2. The exempt-run bound counted yields, and walking is mostly yields. Shipped this morning at 24 consecutive tail-exempt iterations. The log shows a completely healthy Catherby-to-Ardougne leg yielding interim-in-flight 28 times in a row while steadily covering ground, because that is simply what travelling between minimap clicks looks like. It would have fired a false livelock warning on a working walk. A bound on yields is a bound on walking. The state worth reporting is yielding while STATIONARY, so the run now resets whenever the player tile changes. Being observe-only is what made this cost a log line instead of an aborted walk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wning it The door cascade's completion contract is "the blocked edge became passable": it clicks, then waits for the edge to open. A stile never opens. You climb over it and arrive on the far side, so that wait can only ever time out. A catalog transport was classified door-like on its NAME, and "stile" is in the door-name fragments, so a Stile at (2637,3350) with action Climb-over was handed to the door cascade. Measured near Ardougne, from first contact to actually crossing: twenty seconds. The handler clicked it, logged door_edge_post_unresolved because the edge never opened, and the walk then spent six refused route clicks, a recovery click onto the far side of the fence, a stall, a replan and an idle nudge before the transport handler got the same object and crossed it in one action. So the action now wins over the name: a catalog row whose action moves the player ACROSS the obstacle — Climb-over, Climb-through, Squeeze-through, Cross — is not door-like, whatever it is called, and shouldDeferDoorHandlingToTransport hands it to the transport handler that can actually complete it. Opening actions are untouched: a named gate you Open is still the door cascade's job, because the door cascade is what knows how to open things. This is the third obstacle in this class to need the same correction (the Varrock museum guard barrier and the Port Sarim back-room door were both fixed as individual data rows). Deciding on the action generalises it: moves-you obstacles are their own class, and the class now has a rule instead of a growing list of coordinates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two live logs have now shown a stretch of walk startup with NO output at all, heartbeat included. By the heartbeat's own contract that means the thread is blocked inside a wait rather than spinning, but nothing says inside what, and the last two attempts to answer that from the log alone were both guesses. Diagnostics only; no behaviour changes. Startup marks are deduped per phase per walk, so a startup that REPLANS goes silent for the whole of its second pass: pf_wait_retry, pf_ready and path_snapshot have all been logged already and never fire again. That is precisely the window a walled-click replan lands in, which is why the slowest starts are the least visible ones — a four-second gap containing nothing but the replan that caused it. They now re-arm on a replan taken before the first movement click, so each startup attempt narrates its own. The heartbeat also carries the player-origin BFS cost now. Every getClosestTileIndex runs one, and the loop asks for a route index many times per iteration — route progress, interim tracking, near-path checks, click selection, each recovery probe. There are thirty call sites. Each is a fresh breadth-first search executed on the CLIENT thread, so the cost is a round trip rather than arithmetic, and it appears in no existing timing line. That makes it the leading candidate for the missing seconds, and it is a candidate precisely because nothing has ever measured it. Deliberately measuring before optimising. The obvious fix — memoise the BFS per tick — trades correctness for speed in the one place the walker can least afford it: the BFS reflects live collision, so a memo held across a door opening answers with the world as it was. That trade is only worth making against a number, and there is no number yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The blocked-frontier recovery tries, in order: door handlers, then a suppression branch for "an unresolved door is near the route", then the transport on the blocked edge. The suppression branch breaks out of recovery, so whenever it fired the transport never got its turn — even when the transport WAS the thing blocking us and the only handler able to cross it. Measured near Draynor on a catalog transport at (3064,3282): the click to it was refused as walled, the door handlers declined it (non-standard-door-action), the path-adjacent scan found no candidates, and recovery then suppressed itself for a "nearby route door" which was this very transport. Four seconds later the raw scene scan dispatched it from range and crossed in a single action. Ten seconds at a gate, and every step of it was the walker asking the wrong handler. The transport attempt now runs before suppression. Suppression is unchanged and still guards the generic recovery click below it — the Clock Tower failure it exists for is untouched. It simply no longer outranks the handler that can resolve the edge. Mechanically this is a move, not new logic, and the block it moves above already ran on every path where suppression declined. processWalk stays at 1641 lines; the comment was trimmed to fit rather than raising the guard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Whether a route segment's obstacle handlers run was inline boolean soup: two independent skip reasons — the window after a transport, and startup before the first movement click — with the logged reason derived from a ternary over which one fired. The pair matters more than it looks. A skipped segment was never EXAMINED, so an obstacle on it is neither resolved nor ruled out, which is why a skip silently withdraws the right to click a door at range. That coupling is what produced the Falador U-turn: segments 11 and 12 skipped with no_nearby_planned_transport, the door at (2985,3341) then clicked from range while the door at (2981,3340) was still shut between us and it, the server routed around the building, and a traversal wait that could never be satisfied timed out. Ten seconds and a U-turn, out of two booleans that never appeared in the same expression. SegmentGate now owns the decision as one enum-returning function, with the log reason carried by the constant rather than reconstructed at the call site, and mayDispatchDoorAtRange named for the invariant it protects. Twelve decision-table cases pin it, including the ones that must NOT skip: a planned transport nearby, an unreachable segment tile, a door attempt or settle or recovery in flight, an immediate transport step at startup, and the precedence when both skips apply. Behaviour-preserving: same conditions, same precedence, same wire strings. The two startup-preclick cases move from Rs2WalkerUnitTest into the new table, where the rest of their family now lives. processWalk 1641 -> 1630; the guard ratchets down a third time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rs2Player.isMoving() compares the pose animation against the idle pose, so it reads TRUE while the player merely TURNS ON THE SPOT. Stall accounting credited that as progress and refreshed the clock, so a player wedged against a wall or a door who kept re-facing it could never be declared stuck — the one state the stall detector exists to catch. The walker would sit there indefinitely, because its own sensor kept insisting it was walking. Requiring an actual tile change instead would be worse. A walking step takes ~600ms and this check samples faster than that, so "same tile as the last sample" is the normal state of a healthy walk; demanding a delta every sample would declare every walk stalled. That is presumably why the pose flag was used in the first place. The right question is not whether the tile changed since the last sample but whether it has changed at all RECENTLY. Walking refreshes that continuously; spinning never does. So the pose flag is now credited only when a real tile change happened within 2.5s — several walking steps of slack, and no help at all to a player who is only rotating. Tracked on its own field rather than reusing lastMovedTimeMs, which several places deliberately refresh to buy grace and therefore cannot answer "is the player really covering ground". Seeded at walk start, because an unknown tile-change time credits the pose and would otherwise hand a spinning player the benefit of the doubt for the whole first stall window. Scoped to stall accounting on purpose. Rs2Player.isMoving() has 65 call sites in the walker alone and more across every other plugin; changing its meaning globally is an unrelated blast radius. This changes who is allowed to believe it, not what it says. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…re the legacy predicates
Three separate places in the walk loop yield to a live interim waypoint,
and all three logged the same word. A line reading interim-in-flight could
mean the route-level yield, the blocked-frontier recovery deferring to a
click it already made, or click selection finding the player still
travelling. That ambiguity has now blocked two stall diagnoses: the log
says the walker is waiting, and nothing says which wait.
Each site gets its own constant, wire names suffixed :route, :recovery and
:click. The shared interim-in-flight prefix keeps one grep matching all
three, and the flags are identical, so nothing about the behaviour changes
— only what the log can tell you afterwards.
This also discharges the follow-up left by the classification fix. The
string predicates the enum replaced survived only to hold up the
characterization test that proved that refactor inert; they have done that
job, and keeping dead production code alive to serve a test is the kind of
accretion this whole effort exists to reverse. They are deleted, and their
classification moves into WalkExitTest as three explicit sets, checked
exhaustively against every constant.
The sets are strictly better than the predicates were. A reason cannot
change meaning without changing the list, a new constant cannot be added
without being classified, and the lists read as documentation of what the
walker considers progress rather than as a startsWith("door-handled")
prefix rule that silently excluded half the door reasons.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… 15s Two things padded the stall clock, and the first was papering over the sensor that has since been fixed. Every pass refreshed the clock outright for 12 seconds after any successful minimap click, on the reasoning that a click can outrun the tile delta. It cannot matter when it is right: while the player is walking, checkIfStuck already refreshes the clock on every real tile change. The blanket only ever had an effect while the player was NOT moving — precisely the state the clock exists to measure. It is deleted, not shortened, because there is no residue left for it to cover. The interim multiplier was the same mistake in smaller print. A sticky waypoint bought a 1.75x threshold in case a long segment outlasted the base stall, but a player walking toward an interim refreshes the clock the whole way, so the multiplier only ever bound the stationary-with-interim case — which the idle nudge already rescues within a second or two, long before any stall threshold comes into view. Now 1.25, for the tick or two between issuing a click and the first step. The base stays at 12s and should stay there. The longest LEGITIMATE motionless stretch measured across four live farm runs is ~7.1s, waiting out a transport handoff with nothing wrong. Cutting the base is the obvious way to make recovery snappier and the wrong one: it buys a walker that interrupts its own ships. Resulting budget, now pinned as wall-clock seconds rather than as multipliers, because seconds are the thing anyone actually cares about: 12s plain, 15s with an interim live (the common case for most of a walk), 24s worst case with everything applying at once. Was 12s + up to 24s = 36s. processWalk 1630 -> 1623; the guard ratchets down a fourth time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t (B2 slice 1) The reachable-tile set is captured once at the top of each pass, and then the obstacle handlers run: opening a door, waiting out a transport, each blocking for seconds inside interaction awaits. By the time the recovery cascade reads the verdict the player can be standing somewhere else entirely, and reachability computed from where we USED to be is not evidence about where we are. reachableTilesCacheOrigin has been declared, assigned twice and read never since it was introduced. Reading it is the fix: recapture whenever the player is no longer standing where the set was built. Two bugs fall out of the recapture that was already there. It ran at a SMALLER radius than the original capture — 18 steps against 39 — so it could answer "unreachable" for a tile the wider map had already reached. A double-check that manufactures the verdict it exists to question is worse than no double-check. And it was gated on the tile being within ~15 tiles of the player rather than on anything having changed, so it rebuilt the map when nothing had moved and left it stale when everything had. Precisely inverted. Scope note: the audit proposed also moving the recovery_position_stale guard to the top of this branch. On reading it that is wrong, and the guard should stay where it is. It does not duplicate this fix — it covers a different window, the seconds the door cascade itself spends between the verdict and the recovery click. Moving it earlier would defeat its purpose, the same way B1's suggested exit-path enumeration turned out unnecessary once the single entry point was found. The remaining WalkTick slices (one snapshot threaded through the segment, frontier and click-selection reads) are untouched; this is the behaviour half of B2, not the structural half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e read from Two things walker-fix was owed, hand-applied against its own copy rather than cherry-picked — the earlier scripted pick silently dropped a slice's tests. Drops the two changes that made things worse in live runs: the short-walk fast path (which broke `distance = 0` — the walker stopped wanting to end on the goal tile) and the zoom-aware minimap stride. Every call site is back on NORMAL_MINIMAP_REACH_EUCLIDEAN and both helpers are gone. Then lifts the frontier cascade's judgement out of processWalk and into FrontierDecision, where it can be stated as a table instead of inferred from 1600 lines of control flow: which route index is the earliest blocked one, which edge the frontier sits on, what a door wait means once it returns, when to yield before touching a door at all, how far back a recovery index may be clamped, which of three candidate targets wins and what happens when the winner is dangerous, and whether a scene click is worth trying at the tail. 41 rows pin it, seeded from incidents rather than invented: the Clock Tower rewind, the stepping-stone origin precedence, the fall-through door wait, and the hazard asymmetry between the raw-gated target and the shortcut origin. Four latent issues surfaced writing them that reading the cascade had not. processWalk 1623 -> 1599. Interactions stay in the shell; nothing here touches the game. Guardrail baseline regenerated for this branch — the delta is pure lambda renumbering, 0 non-lambda lines. Full suite green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…orward-only recovery, minecart 947, zoom strides, B2 slice 2 Batched sync of six PluginTesting commits (8b49bdba02, 8aabe7cceb, 160a5fbe4f, ed7d5333e8, 2ea5c902e6 and the raw watermark), applied as a three-way content merge per the branch policy — plus three OLDER fixes the merge exposed as never having reached this branch at all: the Dwarf Cannon catalog-transport guard in learnWalledRouteEdge, and the adventure-log letter-prefix / null-safety / keyboard-shortcut fixes in interactWithAdventureLog. The batch, live-verified on PluginTesting before travelling: - Door strike-out: three concluded-but-uncrossed attempts session-block the edge WALK-scoped (withdrawn before the next walk's first plan) and replan. Ends the Tithe Farm seed-gate class in ~25s instead of 4+ minutes. - Route stagnation bound, ENFORCED: 60s without raw-index advance replans (twice), then honest UNREACHABLE. Fed by the raw watermark, which advances tile-by-tile on healthy walks and refuses oscillation. - Recovery may only attack the obstacle AHEAD: the scan anchor is forward-corrected past raw-passed route tiles, and a wall door whose face the player is already beyond is never clicked (door_skip_crossed) — the Stronghold of Security paired-gate bounce, both organs. - Tail dither: no re-clicks while moving inside the final band; canvas precision for the finish. - Minecart destination selection: the Lovakengj menu is interface 947, not the adventure log; select by verbatim tsv displayInfo text. - Zoom-aware minimap strides, both directions: reach follows the visible radius, 8 tiles fully zoomed in to the 18-tile BFS-vouched cap. - B2 slice 2: WalkLoopSnapshot carries moving/animating/interacting, states its re-capture contract, and the stuck-sidestep stale-position read is fixed by re-capturing after its sleep. Residual 55-line divergence from PluginTesting's copy is this branch's own (door-wait local naming, WALK_TO_ORIGIN comment placement, no isWalkSuperseded) and is deliberate. Guardrail baseline regenerated here — delta is one pure lambda renumber. Full suite green on this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…es 1-3, Stronghold latency, goal-object and wing guards, fold stall Batched sync of PluginTesting 2ea5c902e6..a46b2a5768 (walker/API scope), all live-verified on the Stronghold of Security corridor: - B2 slices 3a-3c: the walk pass reads one world snapshot, re-captured at every blocking branch and per segment iteration - Door-attempt ledger slices 1-3: DoorAttemptLedger owns ATTEMPTED (cooldowns + latest claim), REFUSED (strikes + walk-scoped blocks) and the tile facets (opened-suppression + session blacklist); Rs2DoorHandler reduced to the key builder and global-cooldown pair - Stronghold gate latency: door-leg stage instrumentation, crossed-face release in the await, dialogue-or-crossing release with distance-scaled budget in handleStrongholdOfSecurityAnswer (5.4s/gate constant -> 1.5-3s) - A crossed door satisfies nothing (conquered-door fall-through) and an unreachable fallback click arms nothing - Goal-object rule: an object on the goal tile is the destination, not an obstacle (door_skip_goal_object) - Walled-net learning defers to doors ADJACENT to the edge (double-gate wing) - Fold stall ended: scan past behind/branch tiles, conquered doors resolve in both route-door classifiers Method: three-way merge-file against base 2ea5c902e6 (Rs2Walker merged clean, walker-fix's three recovered fixes preserved); guardrail baseline regenerated on this branch, delta confined to walker entries. Full suite green here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… classification, fold stall, E1 extraction Batched sync of PluginTesting a46b2a5768..da951b48d5 (walker/API scope; tithefarming and Rs2Farming stay home per policy): - D3 slice 4: ALL TEN door-state stores folded — the walk-runtime quartet (pass budget, settle window, global cooldown, raw-scan focus) joins DoorAttemptLedger; WalkerRouteState loses seven fields, door signatures lose their threaded Map parameter - Requirement #3: one route-door classification for all ten sites (Rs2DoorClassifier.isRouteDoorObject — a chest is never a door; GameObject gates by name, tollgates by traversal verb; wall semantics unchanged) - Fold stall ended live-verified; goal-object and double-gate wing guards live-verified - keyDetail sub-timers for the cold-login transport refresh (PathfinderConfig) - E1: the transport component (dispatcher + ~90 handlers, 3,090 lines) moves to Rs2WalkerTransports; Rs2Walker 14.1k -> 11.2k lines; baseline verified an exact multiset re-home on PluginTesting Method: three-way merge-file against base a46b2a5768 (clean; walker-fix's deliberate local lines preserved), baseline regenerated on this branch. Full suite green here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.