A retired fishing spot leaves its Content.Fish timer running for good #242

Closed
opened 2026-08-19 02:08:58 +00:00 by sickday · 0 comments
Owner

When a fishing spot retires under a player, its World.Content.Fish timer is
left running on that player for good. The loop never fires again, the skilling
clock stays pinned at 0 — permanently due — nothing animates, and
Timers.has?(player.timers, World.Content.Fish) answers true forever.

Measured

A fly fisher at Barbarian Village, polled every four seconds:

:working slots=25 clock=0 feathers=977 anim=nil
         timers=[HealthRegen, Wilderness, Fish, SpecialRegen, Darkness,
                 StatRegen, StatBoost]
         delay=%World.Delay{ticks: 0, active?: false, moved: 0, skilling: 0}
         target={17914, 1}  npc=nil

World.TickSnapshot.npc(17914) is nil: World.FishSpots had retired that
spot and stood its replacement at another tile under a new index. The state
above then held, unchanged, for eight minutes until the sitting was
stopped.

Why it should not be possible

World.Content.Fish.pass/3's first clause is not standing?(action) -> halt(player), and halt/1 reaches stop/1, which is
Player.clear_timer(player, World.Content.Fish). A loop that fired even once
after the spot went would have cleared its own timer within a tick.

So the loop is not firing. The clock sitting at 0 says the same thing from
the other side: Contract.Skilling.due?/1 is clock == 0, so had pass/3
run it would have taken the due? branch, rolled, and either landed a fish or
been refused — and every refusal path halts. Nothing rolled, and nothing
animated, which resume/2 would have done.

The suspicion is that the timer is driven from the pending interaction and the
engine drops that interaction when the NPC is retired, leaving the timer with
nothing to run it. Not confirmed — that is the first thing to check.

Why it matters beyond fishing

skilling? on the bot snapshot is exactly
Timers.has?(player.timers, Content.Fish) and friends, so a stale timer is a
body that reports working while doing nothing. Bot.Goals.Fish and
Bot.Goals.GatherAndBank no longer depend on the answer — both now ask
whether the target is still there before believing skilling? — but that
is a consumer working around it, not a fix. Anything else that reads
skilling?, now or later, inherits the lie.

The same shape should be checked for World.Content.Chop and
World.Content.Mine, whose nodes go by changing id rather than by vanishing
from an index; it is not known whether their teardown has the same hole.

When a fishing spot retires under a player, its `World.Content.Fish` timer is left running on that player for good. The loop never fires again, the skilling clock stays pinned at `0` — permanently *due* — nothing animates, and `Timers.has?(player.timers, World.Content.Fish)` answers `true` forever. ## Measured A fly fisher at Barbarian Village, polled every four seconds: :working slots=25 clock=0 feathers=977 anim=nil timers=[HealthRegen, Wilderness, Fish, SpecialRegen, Darkness, StatRegen, StatBoost] delay=%World.Delay{ticks: 0, active?: false, moved: 0, skilling: 0} target={17914, 1} npc=nil `World.TickSnapshot.npc(17914)` is `nil`: `World.FishSpots` had retired that spot and stood its replacement at another tile under a new index. The state above then held, unchanged, for **eight minutes** until the sitting was stopped. ## Why it should not be possible `World.Content.Fish.pass/3`'s first clause is `not standing?(action) -> halt(player)`, and `halt/1` reaches `stop/1`, which is `Player.clear_timer(player, World.Content.Fish)`. A loop that fired even once after the spot went would have cleared its own timer within a tick. So the loop is not firing. The clock sitting at `0` says the same thing from the other side: `Contract.Skilling.due?/1` is `clock == 0`, so had `pass/3` run it would have taken the `due?` branch, rolled, and either landed a fish or been refused — and every refusal path halts. Nothing rolled, and nothing animated, which `resume/2` would have done. The suspicion is that the timer is driven from the pending interaction and the engine drops that interaction when the NPC is retired, leaving the timer with nothing to run it. Not confirmed — that is the first thing to check. ## Why it matters beyond fishing `skilling?` on the bot snapshot is exactly `Timers.has?(player.timers, Content.Fish)` and friends, so a stale timer is a body that reports *working* while doing nothing. `Bot.Goals.Fish` and `Bot.Goals.GatherAndBank` no longer depend on the answer — both now ask whether the target is still there **before** believing `skilling?` — but that is a consumer working around it, not a fix. Anything else that reads `skilling?`, now or later, inherits the lie. The same shape should be checked for `World.Content.Chop` and `World.Content.Mine`, whose nodes go by changing id rather than by vanishing from an index; it is not known whether their teardown has the same hole.
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#242
No description provided.