An eighth of a fishing fleet stops fishing within twenty minutes, silently #247

Closed
opened 2026-08-19 10:54:22 +00:00 by sickday · 1 comment
Owner

A 75-bot fishing fleet — three bots on each of the 25 derived grounds — was
watched for eighteen minutes, sampled every five seconds. Nine of them
(12%) never banked once.
They fill a pack, set off for the bank, fail the
leg, and the brain re-initialises the goal — nine to fourteen times each —
which walks them back to the water, where a full pack means there is nothing
to do but set off for the bank again.

The loop an operator sees is therefore: run to the spot, do nothing, go back
towards the bank, turn around, come back to the spot.
Over and over. Across
the nine, opening and depositing were reached zero times in eighteen
minutes; they spent 10-17% of their samples working and the rest walking.

Nothing in a log says so. The bots are walking, the brain reports its failures
at debug, and from the chair they look like bots on their way somewhere.

That silhouette has had one cause fixed already — a booth whose derived
stand could not press it, which produced the identical shuttle for the same
reason (a pack that never empties). This is a second cause with the same
appearance, and it is why the first fix looked complete: the ground it was
verified on has a 57-tile bank leg and works, while the three that fail have
the longest legs in the world.

It is whole grounds, not scattered bots

g16 {0, 2874, 3341}  bank leg 71 tiles (Falador)     3 of 3 bots dead
g17 {0, 2875, 3332}  bank leg 70 tiles (Falador)     3 of 3 bots dead
g22 {0, 3239, 3251}  bank leg 83 tiles (Al Kharid)   3 of 3 bots dead

Every other ground's bots were still working at the end — the Barbarian
Village pair, for contrast, ran 70-78% working with deposits landing and
zero goal re-inits. The three that died
are ranked 1st, 3rd and 4th of 25 by the length of their bank leg, which
is the shape of a long-leg routing failure rather than anything about fishing:

g22  83  <-- died        g10  77                g16  71  <-- died
g17  70  <-- died        g9   68                g19  65
...                      g13  12                g18  12

It is a risk factor rather than a threshold — g10 at 77 tiles and g9 at 68
were fine — which is consistent with a straight-line hop that wedges on
whatever happens to be in the way rather than on distance as such.

Both directions do route: World.Movement.route/3 answers for all three
pairs. For the two Falador ones it answers with what looks like the
closest-reachable fallback rather than a path — 4 steps out and 31 back
for a 71-tile trip — so the search is not completing.

Why the goal never escapes

Bot.Brain recovers by re-initialising the goal and relocating to the bank
nearest the anchor, and choosing_ground then picks by distance among the
three nearest workable grounds. Nothing in that loop knows the last attempt
failed, so it re-picks the same unreachable ground and fails the same way. The
bot is not wedged on a tile — it is wedged in a decision.

Two things would each break the cycle on their own: a choosing_ground that
remembers which grounds have just failed and takes the next candidate, or a
Bot.Travel long leg that does not need the hop. The first is cheap and
local; the second is the real one.

Same family as the pairing question in the reachability issue — a bank chosen
by distance that the bot cannot round-trip — but a distinct symptom worth its
own measurement: there the bot recovers after ninety seconds, here it never
does, and the fleet loses an eighth of itself inside twenty minutes with no
sign of it anywhere.

The fleet is otherwise healthy: 75% of all samples were spent working, 14%
travel_to_ground, 4% travel_to_bank, and no bot spent even two minutes
frozen on a single tile.

A 75-bot fishing fleet — three bots on each of the 25 derived grounds — was watched for eighteen minutes, sampled every five seconds. **Nine of them (12%) never banked once.** They fill a pack, set off for the bank, fail the leg, and the brain re-initialises the goal — nine to fourteen times each — which walks them back to the water, where a full pack means there is nothing to do but set off for the bank again. The loop an operator sees is therefore: *run to the spot, do nothing, go back towards the bank, turn around, come back to the spot.* Over and over. Across the nine, `opening` and `depositing` were reached **zero** times in eighteen minutes; they spent 10-17% of their samples `working` and the rest walking. Nothing in a log says so. The bots are walking, the brain reports its failures at `debug`, and from the chair they look like bots on their way somewhere. **That silhouette has had one cause fixed already** — a booth whose derived stand could not press it, which produced the identical shuttle for the same reason (a pack that never empties). This is a second cause with the same appearance, and it is why the first fix looked complete: the ground it was verified on has a 57-tile bank leg and works, while the three that fail have the longest legs in the world. ## It is whole grounds, not scattered bots g16 {0, 2874, 3341} bank leg 71 tiles (Falador) 3 of 3 bots dead g17 {0, 2875, 3332} bank leg 70 tiles (Falador) 3 of 3 bots dead g22 {0, 3239, 3251} bank leg 83 tiles (Al Kharid) 3 of 3 bots dead Every other ground's bots were still working at the end — the Barbarian Village pair, for contrast, ran 70-78% `working` with deposits landing and **zero** goal re-inits. The three that died are ranked **1st, 3rd and 4th of 25** by the length of their bank leg, which is the shape of a long-leg routing failure rather than anything about fishing: g22 83 <-- died g10 77 g16 71 <-- died g17 70 <-- died g9 68 g19 65 ... g13 12 g18 12 It is a risk factor rather than a threshold — g10 at 77 tiles and g9 at 68 were fine — which is consistent with a straight-line hop that wedges on whatever happens to be in the way rather than on distance as such. Both directions do route: `World.Movement.route/3` answers for all three pairs. For the two Falador ones it answers with what looks like the closest-reachable **fallback** rather than a path — 4 steps out and 31 back for a 71-tile trip — so the search is not completing. ## Why the goal never escapes `Bot.Brain` recovers by re-initialising the goal and relocating to the bank nearest the anchor, and `choosing_ground` then picks by distance among the three nearest workable grounds. Nothing in that loop knows the last attempt failed, so it re-picks the same unreachable ground and fails the same way. The bot is not wedged on a tile — it is wedged in a decision. Two things would each break the cycle on their own: a `choosing_ground` that remembers which grounds have just failed and takes the next candidate, or a `Bot.Travel` long leg that does not need the hop. The first is cheap and local; the second is the real one. ## Related Same family as the pairing question in the reachability issue — a bank chosen by distance that the bot cannot round-trip — but a distinct symptom worth its own measurement: there the bot recovers after ninety seconds, here it never does, and the fleet loses an eighth of itself inside twenty minutes with no sign of it anywhere. The fleet is otherwise healthy: 75% of all samples were spent `working`, 14% `travel_to_ground`, 4% `travel_to_bank`, and no bot spent even two minutes frozen on a single tile.
Author
Owner

Fixed on fix/long-legs-and-unreachable-banks (df2d46b), after c01f507 closed the cheap half. Asked of the world rather than of the log, the three grounds turn out to be two entirely different faults.

{0, 2874, 3341} -> Falador west  :closed at 2,478 tiles   5ms
{0, 2875, 3332} -> Falador west  :closed at 2,478 tiles   5ms
{0, 3239, 3251} -> Al Kharid     found, 267 steps        64ms
{0, 3239, 3145} -> Al Kharid     found, 384 steps        81ms   <- a fourth, not in the issue

The Falador pair are on an island. Their connected component is 2,478 tiles and no bank is on it, so the pairing was across open sea and no pathfinder was ever going to help — which is why c01f507 left them roaming: every ground they could spurn was equally unreachable.

@max_search is a budget, not a reach, and that is the other fault whole. World.Movement.route/4 stops at 104 * 104 visited tiles, justified as the client's 104-tile scene — and a breadth-first sweep visiting N tiles reaches sqrt(N), so the real reach is 52. Both halves of that reasoning are true and the conclusion is wrong. Worse than refusing, it falls back to the closest tile to the target it happened to see, which is greedy. Simulating the obvious fix — re-issue to the true destination and walk however far you get:

request 1   {3239,3251} -> 94 steps, ending {3249,3187}
request 2   {3249,3187} -> []

Empty, because from there every reachable tile was further from the bank by straight line than the one the body stood on. Progressive re-routing does not converge; it parks one request in.

What landed

Bot.Route — A* over the collision map. Chebyshev is the exact remaining step count on this grid rather than merely admissible, so it is very directed: 18,336 visits against a plain sweep's 62,498 for the same 267-step path, 23,593 against 81,069 for the 384. An ordinary leg is 1–3k visits and 2ms. Three answers: :unreachable (a fact about the world), :too_far (a fact about the budget), {:ok, path}.

Bot.Travel charts once per leg and walks waypoints/2, cut every forty steps of route rather than of map — that is what keeps each click inside one engine request. The blind straight-line hop is gone; it was clicking at the water. The leg's timeout is now sized by the walk (@max_steps + steps / 2) instead of a constant. Only :unreachable gives up, and immediately; :off_plane walks uncharted on purpose, because a destination up a staircase is a leg only Bot.Obstacle's climb/2 can finish and that is reached through the stuck ladder.

Bot.Sites now pairs a site with a bank it can walk to. It had half the rule already — enclosure/1 fills from each bank, so a bank in a pocket only pairs inside it — but the mirror image was open: a bank in the open world pairs with anything in range, island included. Drops 148 tree sites, 8 rock sites and 2 fishing grounds of ~700. Derivation 606ms → ~5.7s, concurrent, once at boot.

An unrelated hole this turned up

Moving the search origin from the cluster anchor to standable/1's tile added a site, which is the opposite of what a stricter rule should do. site/2 asked the enclosure MapSet.member? of the anchor — and a tree is a solid loc, so its tile is in no flood fill anywhere. Every enclosed bank had been excluding every gather site unconditionally for the table's whole life. fishing_ground/3 passed the stand and was always right, which is why the Fishing Guild case the rule was written for worked and nobody looked.

The witness was already in the suite: a test asserting arctic pine derives no site "because Neitiznot's bank is shut in". It is not — the pines walk to it in 26 steps, and World.Movement.route/4 finds the same leg in 29.

The sitting

Six fishers, three grounds, fourteen minutes. Deposits: catherby1 5, catherby2 4, lumbridge1 4, lumbridge2 1, desert1 2, desert2 3. spurned=0 and fails=0 for every bot for the whole run, nobody shuttling. The comparison is this issue's own measurement: on these grounds, nine bots banked nothing in eighteen minutes and reached opening zero times between them.

desert1 walks its 383-step leg northward first, up to the Lumbridge bridge before turning south for Al Kharid — the charted route rather than the straight line the hop drew, which is the visible difference between the two.

Left open

  • A shut door reads as rock to every search here, Bot.Route included, so a site behind one is now dropped rather than paired-and-wedged-at. Nothing is lost to it yet (the same door stops the walk), but a door-aware planner would recover them, and it is the same boundary index the Wilderness-wall gates want.
  • Nothing caps a bank leg by walking distance. @max_bank_range is 90 in chebyshev and the two desert grounds pass it at 267 and 384 steps — four minutes of walking for 28 fish. They work; whether they should be offered is a separate question. The Lumbridge one is only long because the Lumbridge bank is plane 2 and same_plane_bank/2 never looks a storey up.
  • #138 is now one call from being right: Bot.Route.reachable?/3 is the question recover/2 and nearest_bank/1 never asked.
Fixed on `fix/long-legs-and-unreachable-banks` (`df2d46b`), after `c01f507` closed the cheap half. Asked of the world rather than of the log, the three grounds turn out to be **two entirely different faults**. ``` {0, 2874, 3341} -> Falador west :closed at 2,478 tiles 5ms {0, 2875, 3332} -> Falador west :closed at 2,478 tiles 5ms {0, 3239, 3251} -> Al Kharid found, 267 steps 64ms {0, 3239, 3145} -> Al Kharid found, 384 steps 81ms <- a fourth, not in the issue ``` **The Falador pair are on an island.** Their connected component is 2,478 tiles and no bank is on it, so the pairing was across open sea and no pathfinder was ever going to help — which is why `c01f507` left them roaming: every ground they could spurn was equally unreachable. **`@max_search` is a budget, not a reach**, and that is the other fault whole. `World.Movement.route/4` stops at `104 * 104` visited tiles, justified as the client's 104-tile scene — and a breadth-first sweep visiting N tiles reaches `sqrt(N)`, so the real reach is **52**. Both halves of that reasoning are true and the conclusion is wrong. Worse than refusing, it falls back to the closest tile to the target it happened to see, which is greedy. Simulating the obvious fix — re-issue to the true destination and walk however far you get: ``` request 1 {3239,3251} -> 94 steps, ending {3249,3187} request 2 {3249,3187} -> [] ``` Empty, because from there every reachable tile was *further* from the bank by straight line than the one the body stood on. Progressive re-routing does not converge; it parks one request in. ## What landed **`Bot.Route`** — A* over the collision map. Chebyshev is the *exact* remaining step count on this grid rather than merely admissible, so it is very directed: 18,336 visits against a plain sweep's 62,498 for the same 267-step path, 23,593 against 81,069 for the 384. An ordinary leg is 1–3k visits and 2ms. Three answers: `:unreachable` (a fact about the world), `:too_far` (a fact about the budget), `{:ok, path}`. **`Bot.Travel`** charts once per leg and walks `waypoints/2`, cut every forty steps **of route** rather than of map — that is what keeps each click inside one engine request. The blind straight-line hop is gone; it was clicking at the water. The leg's timeout is now sized by the walk (`@max_steps + steps / 2`) instead of a constant. Only `:unreachable` gives up, and immediately; `:off_plane` walks uncharted on purpose, because a destination up a staircase is a leg only `Bot.Obstacle`'s `climb/2` can finish and that is reached through the stuck ladder. **`Bot.Sites`** now pairs a site with a bank it can **walk** to. It had half the rule already — `enclosure/1` fills from each bank, so a bank in a pocket only pairs inside it — but the mirror image was open: a bank in the open world pairs with anything in range, island included. Drops 148 tree sites, 8 rock sites and 2 fishing grounds of ~700. Derivation 606ms → ~5.7s, concurrent, once at boot. ## An unrelated hole this turned up Moving the search origin from the cluster anchor to `standable/1`'s tile *added* a site, which is the opposite of what a stricter rule should do. `site/2` asked the enclosure `MapSet.member?` of the **anchor** — and a tree is a solid loc, so its tile is in no flood fill anywhere. **Every enclosed bank had been excluding every gather site unconditionally for the table's whole life.** `fishing_ground/3` passed the stand and was always right, which is why the Fishing Guild case the rule was written for worked and nobody looked. The witness was already in the suite: a test asserting arctic pine derives no site *"because Neitiznot's bank is shut in"*. It is not — the pines walk to it in **26 steps**, and `World.Movement.route/4` finds the same leg in 29. ## The sitting Six fishers, three grounds, fourteen minutes. Deposits: `catherby1 5, catherby2 4, lumbridge1 4, lumbridge2 1, desert1 2, desert2 3`. `spurned=0` and `fails=0` for every bot for the whole run, nobody shuttling. The comparison is this issue's own measurement: on these grounds, nine bots banked **nothing** in eighteen minutes and reached `opening` zero times between them. `desert1` walks its 383-step leg *northward* first, up to the Lumbridge bridge before turning south for Al Kharid — the charted route rather than the straight line the hop drew, which is the visible difference between the two. ## Left open - **A shut door reads as rock** to every search here, `Bot.Route` included, so a site behind one is now dropped rather than paired-and-wedged-at. Nothing is lost to it yet (the same door stops the walk), but a **door-aware planner** would recover them, and it is the same boundary index the Wilderness-wall gates want. - **Nothing caps a bank leg by walking distance.** `@max_bank_range` is 90 in chebyshev and the two desert grounds pass it at 267 and 384 steps — four minutes of walking for 28 fish. They work; whether they *should* be offered is a separate question. The Lumbridge one is only long because the Lumbridge bank is plane 2 and `same_plane_bank/2` never looks a storey up. - **#138** is now one call from being right: `Bot.Route.reachable?/3` is the question `recover/2` and `nearest_bank/1` never asked.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Revenant/Server#247
No description provided.