Skip to content

Tempoross: permit-max rework (auto-equip, reward collection, instance-safe navigation) - #534

Merged
chsami merged 69 commits into
chsami:developmentfrom
infuse21:Tempoross
Sep 3, 2026
Merged

Tempoross: permit-max rework (auto-equip, reward collection, instance-safe navigation)#534
chsami merged 69 commits into
chsami:developmentfrom
infuse21:Tempoross

Conversation

@infuse21

Copy link
Copy Markdown
Contributor

What this is

A ground-up rework of the Tempoross plugin (v2.0.0 -> v2.20.1) built around one goal: maximum reward permits per game on mass worlds, using the "cook everything" strategy. Points, not XP.

Gameplay changes

  • Permit-max strategy: opening catch of 7 (9 at 85+ Fishing), cook everything, load cooked (65 pts vs 20 raw), adaptive catch cutoff from a live energy-drain estimate (fixed ~49% line kept as a floor), stage at the spirit pool ~5%, harpoon it to 97-98%
  • Reward automation: permit tracking (varbit 11936), collection at the reward pool via Big-search once a configurable threshold is met, drain-to-zero with bank trips between full inventories, min-Fishing-level hold option (rewards roll at collection time)
  • Auto-equip: bank-first flow that strips, deposits, then dresses from the bank - Spirit Angler > Angler per slot, Imcando off-hand hammer, and the best owned harpoon (wiki-verified for max permits: infernal > crystal > dragon > barb-tail; the infernal cooks harpoonfish in-place inside the minigame). Wield vs carry decided by actual Attack/Agility/Fishing levels. Re-runs after each collection so fresh outfit drops go straight on. The harpoon dropdown is gone - one "Fish bare-handed" toggle remains
  • Fire/cloud handling: measured telegraphs (41006 shadows live 16 ticks, strikes land in synchronized batches; 41007 ~2 ticks) drive timed dodges instead of flee-on-sight, plus douse-first routing and a one-pass hold while strikes materialize
  • World hopper (default 422), harpoon spec fired at fish spots (does nothing at the pool), post-run dialogue dismissal

Reliability fixes (all reproduced live before fixing)

  • Instance coordinate spaces: Rs2Player.getWorldLocation() returns template space, entity locations return raw scene space - mixing them broke fish-spot choice, repairs, walking. All internal logic now stays in one space; docs/INSTANCE_COORDINATE_SPACES.md documents the trap for other plugins
  • Per-side entity tables (TemporossSide): both arena halves are in the scene with distinct ids; side is resolved from the exit-NPC id, never from geometry (measured 1-tile margin made proximity rules flip sides)
  • Walk safety: no global pathfinder on raw instance coords, no canvas clicks on off-screen tiles, camera ladder (yaw/pitch/zoom) instead of stalling, totem staging for out-of-scene targets
  • Depleted-spot detection, wave tether locking, pool-side disambiguation by dock mark (both pools measure identical exit distance), stale pool-phase latch, end-of-game NPE race

Reviewer notes

  • minClientVersion raised to 2.6.16 - the rework uses the newer cache/query APIs (Rs2TileObjectCache, NPC cache queries, hopToWorld)
  • Entity ids were captured from live games via the agent server across three instances, not transcribed from the cache dumps
  • Adds PluginConstants.INFUSE prefix constant (unused by this plugin, available for future ones)

Co-authored with Claude (Anthropic) during live debugging sessions.

🤖 Generated with Claude Code

infuse21 and others added 30 commits July 30, 2026 20:16
PLUGIN_DEBUGGING_NOTES section 2 documented the two getWorldLocation()
accessors the wrong way round. Rs2Player.getWorldLocation() translates via
WorldPoint.fromLocalInstance, so it returns the OVERWORLD coordinate the
instance was copied from; the raw client.getLocalPlayer() call does not
translate and stays on the instance side. Measured in Tempoross:
Rs2Player.getWorldLocation() = (3035, 2853) in region 12076 while NPCs
reported (10556, 5892) in the same tick.

The bullets above that code block were already correct - only the labels
contradicted them - but the swapped labels plausibly produced the bugs now
recorded in the new doc.

Adds INSTANCE_COORDINATE_SPACES.md: the rule for which comparisons are safe
inside an instance, the plugins currently mixing the two spaces (RoyalTitans
danger-tile dodge, pestcontrol brawler proximity, mahoganyhomez object
sorting), the plugins verified consistent so they are not "fixed" by mistake,
and how to confirm at runtime before changing combat code.

Plugin fixes are deliberately not included - the reference implementation is
being proven in the Tempoross plugin first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Live Tempoross runs showed only the cloud dodge was the space-mixing bug;
the ammo-crate and fish-spot symptoms had different causes (NPC render
distance, per-game chunk rotation). Record those alongside the walkFastLocal
canvas-projection footgun so they are not conflated with the split again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…9.1)

Brings over ~30 fixes and the permit-max strategy developed and live-tested
across many rounds in the sibling Microbot repo. Highlights:

Coordinate/instance correctness
- entity vs Rs2Player.getWorldLocation() coordinate-space mixing fixed
  throughout (cloud dodge, tether logging, fish-spot selection); see
  docs/INSTANCE_COORDINATE_SPACES.md
- side containment via the two per-side exit NPCs (ship + totem anchors);
  cross-ship watchdog cancels fall-through clicks and walks
- spirit pool selected by the spiritPoolPoint mark - both sides' pools tie
  at exactly 10 tiles from the exit NPC, so exit distance cannot pick

Behaviour
- permit-max strategy: 7/9-fish opening, cook-everything, 49% load cutoff
  (needs 4+ fish), stage at the pool mark at ~5% energy, harpoon to 97-98%
- pool phase detected by energy, not pool-NPC visibility (render distance);
  poolPhaseActive flag stops idling at the mark after the final load
- fires: douse en route (triangle-inequality detour cap), sidestep when out
  of water, douse when standing in one; walkable-tile checks on all dodges
- repairs walk to the damaged mast/totem (10-tile cap), rope refetched from
  any state, harpoon fallback no longer rewrites user config
- world hop to a configurable world (default 422) at script start

Robustness
- isInMinigame debounced (scene-reload LOADING blips reset the game state
  mid-round); work area stashed and restored after spurious resets;
  finishGame resets only after departure is confirmed
- depleted fish spots released immediately; camera turns only when the
  target is off-screen; thresholds randomized per game

Descriptor keeps the Hub's PluginConstants fields (isExternal/enabledByDefault).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…entVersion

- docs/README.md for the hub listing: strategy, setup, config table, known
  limitations (including the intentional forfeit behaviour)
- authors = See1Duck (original plugin author)
- minClientVersion 2.0.13 -> 2.6.16: the rework uses tile-object cache
  queries, Rs2Tile.isWalkable(LocalPoint) and hopToWorld, none of which
  exist in 2.0.13; 2.6.16 is the version the build verifies against

No icon/card assets yet, so iconUrl/cardUrl stay empty rather than pointing
at files that do not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e is up

THIRD_CATCH previously ran until the bag was full, so the third phase was one
long catch followed by one long cook. The strategy is batches: catch 7, cook
them, load, repeat - and only stay out to fill the bag while a double spot is
available, since that is when catching is fast enough to be worth it.

The predicate now short-circuits in order: bag full (or the solo essence
target) -> done; solo -> unchanged; 49% energy load cutoff with 4+ fish ->
done; otherwise 7 fish AND no double spot up. The existing cook interrupt in
handleStateLoop already pulls us back out of THIRD_COOK when a double appears
mid-cook, so the two rules together produce the alternating pattern.

Also consolidates three slightly different "is a double available" checks into
TemporossScript.hasDoubleSpot() - cloud-filtered, since a spot we cannot stand
at is no reason to keep catching. The catch cutoff, the cook interrupt and the
abandon-a-single rule now agree by construction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Captured live from the agent server across three games: almost everything in the
arena is duplicated per side with DISTINCT ids, so the side is a lookup, not a
geometry problem. All four exit NPCs appear in one instance and pair fixedly:

  A: Captain Dudi 10587 (west ship) + First Mate Deri 10593 (north totem)
     spots 10565, mast 41352, totem 41354, crates 10576/10577
  B: Captain Pudi 10585 (east ship) + First Mate Peri 10596 (south totem)
     spots 10568, mast 41353, totem 41355, crates 10578/10579

Measured fishing clusters sat 40 tiles apart with no overlap. New TemporossSide
enum holds the table; the nearest exit NPC's id selects it at setup.

This removes two proven-unsafe distance rules:
- pairing our two exit NPCs by proximity: the west ship host measured 17 tiles
  from its own totem host and 18 from the other side's, a one-tile margin
- selecting the totem object by radius: the other side's sat 19 tiles away,
  inside the 30 query radius, rejected only by isOnOurSide's 18-tile test

Fishing spots, masts, totems, damaged variants and ammo crates are now selected
by our side's id alone. Still geometric, because both sides share the id: the
shrine, water pump, supply crates, spirit pool, and the double spot 10569 -
which the capture confirmed spawns on both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dodge has been firing early. Two candidate causes, both addressed:

1. inCloud(point, 0) computes (radius+1) tiles, so "radius 0" actually meant
   "within one tile" - it dodged while merely ADJACENT to a shadow.
2. 41007 (lightning_shadow_short) was never tracked at all. It may be the
   imminent-strike marker while 41006 is the earlier warning. Unproven.

Now tiered:
- IMMINENT (standing on a shadow, or on/beside a 41007): move regardless of
  what we are doing, including a pool harpoon - the hit is never worth it.
- WARNING (adjacent to a 41006 only): still step out, but not worth abandoning
  a pool harpoon for.

41007 is tracked and logged rather than trusted. A throttled CLOUDS: line
reports the 41006/41007 split, whether we are on-tile / adjacent / imminent,
and current HP, so one game shows whether 41007 appears at all and whether
damage follows it. If it never appears, tier 1 degrades to the on-tile test,
which is the off-by-one fix on its own.

Rollback is this commit alone; the side-lock work is separate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two fire gaps, both about not eating avoidable damage.

1. Douse the fire a strike leaves behind. In mass mode nothing covered this:
   handleFires() is solo-only, and fightFiresInPath only clears fires that lie
   on a route we happen to be walking, so a fresh fire beside us burned
   untouched. handleNearbyFire() douses anything within 3 tiles when we have
   water. Bounded tight so it can never turn into a trip, and it yields to a
   pool harpoon.

2. Never walk through a fire that is in the way. detourAroundFires used to run
   only when we had ZERO water, on the assumption that dousing covered the
   route otherwise. It does not: fightFiresInPath only clears as many fires as
   we have full buckets, so with one bucket and three fires on the line it
   doused one and walked through the other two. The sidestep now runs
   regardless, after dousing, on whatever is still burning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dousing means walking to the fire - there is no ranged douse - so fightFiresInPath
targeting fires up to 10 tiles away with a 4 tile detour budget was a genuine trip.
Cut to 5 and 2, so it only douses fires it is effectively passing.

Fires are a hazard to avoid, not a points source: the permit route scores on fish.
Anything too far to douse in passing is walked around by detourAroundFires instead,
which costs nothing and already guarantees we never walk through one.

handleNearbyFire (3 tiles) was already local and is unchanged. Solo mode's
continuous fire-fighting is untouched - clearing fires aggressively is the point
there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Root cause of the observed run-off. Rs2Walker.walkFastLocal builds its menu click
from Perspective.localToCanvas and does not null-check it: a tile beyond draw
distance or off-screen is dispatched as (-1,-1) and the client resolves that to an
arbitrary destination.

Measured 2026-08-05 23:21: the pool-phase trigger fired while we were at the range,
walkToSpiritPool aimed at the mark ~25 tiles away, and the resulting destination was
(10512,8607) - 44 tiles from our exit NPC. The wrong-side watchdog caught and
cancelled it, and the bot recovered to the pool 12s later, but the walk should never
have been issued.

walkLocalSafe() now guards every work-area walk: turn the camera to the target, and
if it is still off-screen walk a fraction of the way along the same line to a tile
that IS clickable and walkable. A far target still makes progress, one hop at a time.

The bug is in the shared walker, which ships in the client jar and cannot be patched
from the Hub, so it is avoided at the call site instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…walkTo fallback

The run-off past the range was Rs2Walker.walkTo, not the canvas click. When
LocalPoint.fromWorld returned null, walkToWorkAreaPoint fell back to the global
pathfinder - but our points are raw instance coordinates and the shortest-path
plugin reads them as real-world ones. Hence shortest-path-script:trigger-null in
the log, ten seconds of running, and recovery only once the pool rendered and the
watchdog cancelled the destination.

Both unreachable paths now stage via the totem, the mid-side anchor:
- target outside the loaded scene (was walkTo)
- target loaded but with no canvas point (was a straight-line partway step)

The totem is used rather than interpolating along the straight line because the
direct line from the range to the dock can cross water; the totem never does.
Everything on our side is reachable from there, and the real target usually renders
on the way, so the second leg is issued before we arrive.

Staging is suppressed when the totem is itself the target, so it cannot recurse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Permits are varbit 11936 (verified live via the agent server - read 26 while
standing in the lobby). In-game points are varbit 11897.

Collection runs between games, in the lobby:
- take a small fishing net (303) from the Spirit Angler, NPC 10605, "Take-net"
- Big-search the reward pool, object 41300 in the menu / 41356 in the object
  cache (multiloc, so both ids are matched)

Two config options, both driven by how the reward table actually works. Per the
wiki: rewards are rolled from BASE Fishing level AT THE MOMENT OF COLLECTION -
not when the permits were earned - boosts do not count, and up to 8000 rolls can
be stored. So holding permits until a higher level is strictly better and there
is no rush:
- "Permits before collecting" (default 50)
- "Min Fishing level to collect" (default 1 = collect regardless)

Also logs points and permits gained when a game ends, and shows permits plus the
current game's gain on the progression overlay.

Known limit: collection stops with 3 free slots left rather than banking, so it
will not strand the next game with a full inventory. Banking is deliberately not
included - a blind deposit-all would bank the harpoon, hammer and rope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…laim

I described the pool as a multiloc on the strength of the object cache returning
41356 while the in-game menu reported 41300. That characterisation was not
established - correction from the user.

What the game data does show: ten ids exist - base 41356 tempoross_rewardpool plus
41296-41304 tempoross_rewardpool_0.._8 - and the wiki states the pool's appearance
changes as more permits are stored. At 26 permits the menu reported 41300 (_4).

Whatever drives that, matching only {41300, 41356} was fragile: a different permit
count could present a different variant and the lookup would find nothing. All ten
are now matched, which is correct under either explanation and costs nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pool presents one of ten ids and the id tracks stored permits, but the
thresholds are unverified - 26 permits presenting as 41300 (_4) is the only
pairing actually measured. Logging the resolved id on every collection means the
mapping accumulates from normal play instead of being argued about.

No behaviour change: all ten ids are already matched, which is correct whatever
the thresholds turn out to be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… not HP

Banking. Collection used to stall at 3 free slots. It now opens the lobby bank
chest (41315) and deposits everything except what the next game needs: the
configured harpoon, buckets (empty and full), a rope and a hammer. The small net
is deliberately NOT kept - the Spirit Angler hands out a fresh one each cycle.

The cloud diagnostic was measuring the wrong thing. Tempoross fire deals no
hitpoint damage at all: it destroys fish and supplies. HP therefore never moved,
which is why last game's log showed a flat 48 throughout and proved nothing. The
CLOUDS: line now reports fish (total and cooked), water, rope and hammer, so a
loss event is visible and can be attributed to 41006 or 41007.

That also raises the stakes on the dodge: destroyed cooked fish is lost points,
and points are permits, which is the whole objective.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Banking the net would stockpile hundreds of them - the Spirit Angler hands out a
fresh one every cycle - and carrying one into a game wastes an inventory slot that
should be holding fish.

The net is now kept across banking DURING a collection session, so mid-session
banking does not force a trip back to the Angler, and dropped as soon as
collecting stops (permits below threshold, or holding for a Fishing level).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ing it

Big-search is one continuous animation that drains permits until they hit zero or
the bag fills - not a per-click action. Two bugs against that:

1. The permit threshold was re-checked every loop, so with a threshold of 50 the
   FIRST permit spent dropped us to 49, collection returned false, and the next
   loop boarded the boat mid-search. The threshold now gates starting only; a
   collectingRewards flag keeps it draining to zero once begun.

2. After clicking we waited for permits to tick down, which returned after the
   first one and led straight back into a re-click, restarting the interaction.
   Now we only wait for the animation to START - the existing isAnimating guard
   lets it run untouched from there.

Collection ends when permits reach zero: flag cleared, net dropped. A bag that
fills mid-search still breaks out to bank and resumes afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reads the wiki's Tempoross/Strategies recommended equipment table into
TemporossGear and equips the best owned tier per slot, once per start, from the
lobby bank chest:

  head  Spirit angler headband -> Angler hat        (25592 -> 13258)
  body  Spirit angler top      -> Angler top        (25594 -> 13259)
  legs  Spirit angler waders   -> Angler waders     (25596 -> 13260)
  boots Spirit angler boots    -> Angler boots      (25598 -> 13261)
  off   Imcando hammer (off-hand)                   (29775)

Slots resolve independently because the wiki notes a Spirit Angler piece is
interchangeable with an Angler one without the full set, so there is no reason to
hold out for a matching set. This is on-objective rather than cosmetic: Angler
pieces raise points, and points are permits.

The harpoon defers to the configured type - that is the user's stated intent - and
only falls back down the wiki tier list when they do not own it. A plain harpoon is
left to the existing crate pickup, which already covers owning none at all.

Worth recording from the wiki footnotes: the top weapon tier splits by goal -
Crystal harpoon "if maximum fishing experience is desired", Infernal harpoon "if
maximum reward permits are desired". Infernal is correct for this plugin, and is
already the config default.

Rings (Lightbearer, Elven/Celestial signet) and Ghommal's lucky penny are left out:
all are conditional on special-attack usage or on being under the catch-rate level,
and Ghommal's penny has no matching ItemID constant to bind to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g slot

Ghommal's lucky penny is absent from RuneLite's ItemID and from the OSRS objtypes
dump (searched lucky_penny and ghommal; only infernal defenders returned), so it
most likely postdates that data. Ring ids recorded inline in case the conditional
cases ever become worth handling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ammer

An Imcando hammer (off-hand, 29775) repairs from the equipment slot, so carrying a
normal hammer as well is a wasted inventory slot - and that slot holds a fish,
which is points, which is permits.

Overridden in all four places the hammer config reached:
- areItemsMissing / fetchMissingItems: no longer treats a missing inventory hammer
  as missing when one is worn, so no pointless trip to the hammer crate
- handleRepairs: gates on canRepair() (worn OR carried) rather than the inventory
  alone, so repairs still happen with only the off-hand equipped
- bank keep-list: only keeps a hammer when one is actually needed

The CLOUDS: diagnostic now reports canRepair rather than an inventory-only count,
so it stays truthful for either source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…deciding

Two bugs, either sufficient to produce "does not seem to run":

1. Chicken-and-egg. The "anything to do?" check ran before the bank was opened,
   but bank contents were only visible while it was open (isOpen() ? hasItem :
   false). Gear in the bank was therefore invisible, the check concluded "Gear
   already optimal", marked itself done, and the bank never opened. The feature
   could only ever act on gear already in the inventory.

2. autoEquipDone was never reset in run(). The script bean is a singleton, so
   the flag survived plugin restarts and every later start skipped auto-equip
   with no log at all. Now reset alongside the other per-start flags (plus
   collectingRewards / loggedHoldingPermits, which had the same latent issue).

Rewritten bank-first: equip anything already carried (inventory menus are not
reachable while the bank is up), open the chest, then one banked upgrade per
pass. On completion, displaced lower tiers are deposited so they do not ride
into the game as dead slots - harpoons only when one is worn, since with none
worn a carried harpoon is the fishing tool. A carried configured harpoon is now
wielded too: worn instead of carried is one more fish slot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ange

Auto-equip and reward banking both bailed with "Bank chest not in range" when
the script started away from the dock. Both now route through openLobbyBank():
head for the tile in front of the chest (3156, 2836 - real overworld
coordinates, the Unkah dock is not instanced so the global pathfinder is safe
here) and interact once within 10 tiles. The distance gate also covers the
chest being in scene but too far for a reliable canvas click.

Gives up after five failed pathing attempts so an unreachable start (wrong
area entirely) cannot wedge the loop; counter resets per script start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cache names settle what the two ids are: 41006 tempoross_lightning_shadow,
41007 tempoross_lightning_shadow_short, and NPC 10580 is the harmless hovering
tempoross_lightning_cloud. The shadows are the strike telegraphs and "short"
means less warning - but how many ticks each variant actually gives is the
number the dodge policy needs, and nobody has measured it.

Logs each shadow's spawn-to-despawn lifetime in game ticks (CLOUD-TELEGRAPH:
<id> lived N ticks). Pure telemetry, no behaviour change; every test game now
collects the data for free. Tracked from the unfiltered scene list so a shadow
leaving the 30-tile radius is not mistaken for a despawn, and lifetimes >= 100
ticks are discarded as cross-game garbage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A plain harpoon (and bare hands) cannot go in the weapon slot, but the harpoon
logic only excluded BAREHAND - with the config on a normal harpoon it offered
item 311 for wielding every pass, the equip silently failed, and the retry loop
never reached the bank.

Two guards:
- isWieldable(): only the four wiki weapon tiers (barb-tail/dragon/infernal/
  crystal) are ever offered to the weapon slot. A non-wieldable configured
  harpoon is a deliberate choice - auto-equip leaves it to the normal
  carry/crate supply logic entirely.
- equipRejected: any item whose equip attempt fails (stat requirements are
  invisible in item ids - dragon/infernal need 60 Attack, crystal 70) is
  blacklisted for the session instead of retried forever. Reset per script
  start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…w loadout

Per-slot diffing equipped the Angler outfit but left the displaced graceful
set riding along in the inventory into the game as dead fish slots. Redesigned
to the flow the user described: deposit worn items, deposit the whole
inventory, then withdraw exactly what the game needs.

Step machine, one step per pass, each capped at 8 passes so nothing wedges:
  0 strip (deposit worn items)      4 buckets - pre-filled water from the
  1 deposit inventory                 bank first, empty for the remainder
  2 best outfit piece per slot      5 rope (in-game rule: rope && !spirit
  3 harpoon - wield wieldable,        anglers; hints when the full set is on)
    carry plain                     6 hammer (skipped with Imcando off-hand)

A stat-gated wieldable harpoon (60/70 Attack) that fails to equip is carried
instead - it fishes fine from the inventory. Bank shortfalls fall through to
the in-game crates, same as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…session

Observed: after spending all permits it boarded the next game with the full
inventory of rewards still in the bag - the "all permits spent" exit never
banked, and those slots would have ridden into the game as dead fish slots.

Collection session reshaped to the described flow:
- opens with a deposit (keep-list only stays) so the whole bag is free before
  taking the net
- a full bag is always banked; in drain mode collection then resumes until
  permits hit zero
- EVERY exit path banks remaining loot first: drained, one-load done, or bank
  unreachable (which now also latches the session shut instead of restarting
  into the same dead end every pass)

New "Spend all permits" toggle (default on): off collects one inventory per
lobby visit, banks it, and returns to the game; the latch clears when the next
game starts. Keep-list extracted to rewardKeepList() and shared with the new
hasLootToBank(), which replaces the free-slot heuristic for "anything worth
banking".

Bank shortfalls of buckets/rope/hammer remain non-fatal throughout - the
in-game supply crates cover whatever the bank could not provide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… behaviour

User call: bank-when-full-and-go-back-for-more was the whole ask, and that is
what drain-to-zero already does, so the one-load mode was a config knob with no
audience. The rewardSessionDone latch stays for its other job - stopping a
collection whose bank went unreachable from restarting into the same dead end
every pass until a new game clears it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ions

permits < threshold passes when both are zero, so every lobby loop pass opened
a session that logged "Collecting 0 permits" and immediately "All permits
spent". Harmless but noisy, forever. Starting now also requires permits > 0,
which gives threshold 0 the sensible reading: collect whenever there is
anything to collect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…scipline

Three changes built on the first live telemetry (five 41006 shadows, all
exactly 16 ticks from spawn to strike):

Clouds - dodge on a timer, not on sight. A 41006 shadow only counts as a
threat inside its final 4 ticks, so standing on a fresh one keeps fishing
instead of throwing away up to 9 seconds per cloud (the "dodges early"
observation). 41007 stays flee-on-sight until measured; shadows first seen
mid-life count as imminent. The tracker now runs BEFORE the wave gate in
onGameTick so ages keep counting through waves. Strikes never land on the
pool point (observed live), so a pool harpoon is never abandoned.
CLOUDS diagnostic gains nextStrike=Nt.

States - the fixed ~49% catch cutoff becomes adaptive: an EMA of the energy
drain rate (sampled from widget changes, reset on any rise) projects ticks
until the pool phase, and catching stops when that is only just enough to
cook (~2t) and load (~1t) the raw fish held, plus walking overhead. Fast mass
worlds cut earlier, slow ones keep fishing past 49%. Falls back to the old
line until the rate is sampled.

Camera - once per game: high pitch + wide zoom (whole side on screen), and
walkLocalSafe now widens the view (pitch, then zoom steps) when a yaw turn
was not what the off-screen target needed. Observed live: 22 seconds of
"no on-screen approach" at game start with nothing ever changing the view.
Fewer arrow-key turns also means fewer of the 3x-speed camera spins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ding

Observed: attempt 1 fired while the welcome screen was still initializing
(getLocalPlayer null), a guaranteed failure that consumed one of the three
tries. Now waits for the player to exist before attempting, without counting
the wait against the attempt budget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
infuse21 and others added 13 commits August 24, 2026 21:21
A strike can drop a fire ON the player's tile with roughly one tick to
douse it before supplies burn. The script loop's 300ms cadence can eat most
of that budget, so the on-top-of-us case now bypasses the loop entirely:
an NpcSpawned subscriber recognises a Douse-action NPC within a tile of the
player and sends the Douse click the same client frame it spawned (water
permitting). One click per tick across a spawn batch; anything further away
stays with the hazards-first loop from the previous commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pre-review hardening: the client-thread snapshot fields were volatile, but
the older cross-thread statics were not - state, workArea, isFilling, the
entity lists (sortedFires/sortedClouds/fishSpots), and the per-tick fish
counters plus cachedInMinigame are all written on the client thread and
read on the script executor. All volatile now. The lists were already
replaced whole rather than mutated in place, so reference visibility is the
only thing that was missing.

Also audited the new instant-douse path for client-thread safety: the click
helper's walk branch only runs behind a latched cant-reach flag and the
walker no-ops on the client thread; runOnClientThreadOptional executes
directly when already there. Rs2Player.isMoving/isAnimating wrap
runOnClientThreadOptional internally, so their remaining uses are sound.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two live findings on 2.21.7:

Red mark, no movement. A clicked fish spot with no walkable path (fresh
double, path blocked) leaves the interaction set while the player stands
still - and the interaction-based busy check read that as "busy" forever.
Busy now requires PROGRESS: moving, or engaged with the fishing animation
playing. A stuck stand falls through to the fire/detour logic and a fresh
approach instead of waiting.

Parked at an empty pool from 30%. The recharge tops out around 97 against
an ATTACK completion threshold sampled at 98, so the phase could end with
the latch still set: the redirect-to-fishing required the latch clear, and
the clear required energy 92+, leaving the bot idling at the mark through
the whole climb. A pool that stays gone for ~6 passes (~2s) with energy
past the approach window now ends the phase on the spot. The pool query
already required the Harpoon action, so ghost pool NPCs were not the cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Observed: a fire douse stopped the character, and the re-walk to the crate
spammed "no on-screen approach" for 12+ seconds with the camera visibly not
turning. The camera ladder was an else-if: when one rung silently did
nothing (pitch smoothing no-op), it re-claimed its turn every pass and the
yaw rung never ran.

Two changes to walkLocalSafe:
- all camera rungs fire together each pass (redundant adjustments are
  cheap, stalls are not)
- a camera-independent last resort: a MINIMAP click needs no 3D projection.
  A bounded leg (11/8/5/3 tiles, first walkable and fire-free) along the
  line toward the target gets clicked on the minimap, repeating from closer
  next pass. Raw instance coordinates round-trip correctly because
  worldToMinimap converts back through LocalPoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Observed: dodged a cloud cleanly, then ran back through the fresh fires
twice. Two holes:

- the cloud-dodge escape predicate only avoided CLOUDS, so a dodge could
  legally land on or beside a fire; it now requires fire-free tiles too
- every fire check ran at CLICK time, and most handlers return early while
  already moving, so a fire spawning on a committed path was run through.
  New handleFireOnPath in the hazards chain: while moving, the nearest live
  fire near the remaining path is doused once within ~6 tiles (the Douse
  click redirects the walk); with no water the walk is broken off so the
  detour logic can route around it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
User strategy call: the boss does not normally survive two pool phases, so
essence at or under half after pool 1 means the next pool kills it, and
energy under 30 means that pool is a minute or two out. Both together now
trigger the emergency fill of everything held - raw and cooked - into the
cannon (previously essence <=20 with energy <=48, which barely ever fired
given essence only falls during pool phases). By arrival energy is lower
still; fish not loaded when the boss dies score nothing.

Both gates are named constants (ESSENCE_ENDGAME / ENERGY_ENDGAME) and
expressly tuned by trial and error; the log line now prints both readings
so rounds show how early the dump engages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
User strategy (wiki-endorsed): after the opening load, everything caught and
cooked before the FIRST pool phase stays in the bag, and the cannon trip
happens after the phase ends. Every early catch is one fewer to catch in the
short post-pool window - 170 kills of live data say the round is decided by
the second pool, so the pre-pool-1 hold is free (pool 1 cannot end the round,
essence starts full) and jump-starts the only load that matters. Saves a
crate trip too.

Mechanics:
- poolPhasesSeen counts phases from the energy widget itself - energy only
  recharges during a pool phase, so a rise out of the low band IS one; no
  reliance on our own staging flags. Reset per game and per start.
- while poolPhasesSeen == 0, the THIRD_COOK -> SECOND_FILL advance redirects
  back to catching, and the low-energy emergency fill is suppressed
- a full cooked bag pre-pool stands by instead of clicking spots that cannot
  pay out

Also corrected against the Reward pool wiki page: it drops NO outfit pieces
(the Spirit Angler set there is just the navbox); the post-collection
auto-equip re-run is kept for the pool's real equipable unique, the dragon
harpoon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Observed again: hop attempt 1 fired seconds after login and failed - the
player EXISTING is not enough, the welcome banner keeps initialising for
several seconds and the world switcher cannot open through it; attempt 2
then fired with zero cooldown.

The hop now settles ~5s (8 ticks) after the player first appears before the
first attempt, cools down ~6s (10 ticks) between attempts, and gets 4 tries
instead of 3. The mid-hop LootTracker NPE in the log is the stock RuneLite
plugin reacting to a chat message while the local player is null during the
hop - client-side, cosmetic, not reachable from the Hub.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
User call: banking and reward collection are world-agnostic (lobby chest and
pool exist everywhere, permits are an account varbit), only boarding wants
the mass world. Hopping last means the client has settled long past the
welcome-banner window that made login-time hops fail; the settle and
cooldown guards remain for the nothing-to-bank case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Observed: THIRD_CATCH left a live double to cook 4 fish. The adaptive/49%
cutoff fired on ">=4 fish + energy at the floor" with no regard for the
double - only the batch-of-7 rule respected it. With the endgame dump
backstopping at ENERGY_ENDGAME, deferring is safe: a double dies within
~23s (measured) which re-arms the cutoff, and anything the schedule cannot
cook is swept raw by the dump.

- THIRD_CATCH cutoff (floor and adaptive both) suspended while a double is
  up; bag-full still ends the catch
- the SECOND_COOK/THIRD_COOK double interrupts now yield down to the dump
  gate (30) instead of the load cutoff (48) - every tick above the dump
  with a double up belongs to the double

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… sweep

User flow, now the whole round shape: second cook -> opening load -> fish
(and cook) while waiting for pool 1, holding everything -> after the pool,
catch-and-cook batches continuously -> the emergency fill / endgame dump is
the SINGLE cannon trip of the cycle, sweeping cooked and raw together.

- the scheduled SECOND_FILL is never entered, in any cycle; loading happens
  via the opening INITIAL_FILL and EMERGENCY_FILL only
- the load cutoff (49% floor + adaptive projection) is gone from
  THIRD_CATCH: below the old line the batch just shrinks to 4 so the
  backlog stays cooked for the sweep (cooked deposits 65 against 20 raw),
  which also kills the cook-per-fish thrash the cutoff would have caused
  under the new flow
- a live double still suspends batching outright, bag-full still ends any
  catch, and the emergency/dump gates are unchanged (full bag under 49, or
  6+ fish under 33, or essence <=50 with energy <=30)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Root cause read out of Microbot.hopToWorld: it opens the world switcher and
requests the hop in ONE client-thread pass, but the panel opens
asynchronously, so the request is dropped before the switcher exists. That
made attempt 1 fail every single time, with attempt 2 working only because
attempt 1 had left the panel open. The welcome-banner and settle theories
were incidental.

The script now opens the switcher itself a few ticks before the first
attempt, so the hop request lands on an existing panel. Also, the retry
cooldown is armed AFTER the attempt returns - armed before it, the ~6s the
call itself takes had already burned the window and retries fired back to
back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Observed on a slow round: cooking finished with a full bag at ~50% energy,
and the bag-full sweep gate (<49) left the bot standing idle waiting for
the threshold. A full, fully-cooked bag has nothing left to do at any
energy - it now goes straight to the cannon (post-pool-1 as ever), freeing
the bag for more catching in the remaining window. Full with raw still
cooks first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@infuse21
infuse21 requested a review from chsami August 25, 2026 02:00
@infuse21

Copy link
Copy Markdown
Contributor Author

The plugin is now at ver 2.24.2 the plugin is stable. Tested running for ~1-2 hrs after the last commit.

The phases still need a bit of work but it's consistent permits.

@chsami chsami left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—the release version is now aligned at 2.24.2, the submitted CI is green, and a clean synthetic merge with current development (7a16229) passes ./gradlew build -PpluginList=TemporossPlugin. The 1–2 hour live run is also useful.

The threading migration on current head d375ea6 is still incomplete, though. The scheduled script executor continues to consume live client objects and perform client/world-view conversions outside GameTick, for example:

  • handleWrongSideClick() calls Rs2Player.getInteracting(), then reads the returned NPC's world location, and calls WorldPoint.fromLocal(Microbot.getClient(), dest).
  • handleFires(), the ATTACK_TEMPOROSS state, and handleNearbyFire() call Rs2Player.getInteracting() and then inspect the returned live actor/NPC on the executor.
  • localToWorld() calls both LocalPoint.fromWorld(Microbot.getClient(), ...) and WorldPoint.fromLocalInstance(Microbot.getClient(), ...); multiple executor paths also call LocalPoint.fromWorld(Microbot.getClient(), ...) directly. Those conversions consult the client's world view/plane and belong on the client thread or in the per-tick snapshot.

cachedInteractingIndex is captured correctly but currently used only for the fishing-spot check; the other interaction branches still retrieve live actors. Please finish the snapshot boundary by caching the plain interaction/location/coordinate data needed by the executor (or marshal each complete operation onto the client thread), rather than returning RuneLite objects and reading them later off-thread. After that, please rerun the focused multi-round live check. I am not changing this broad runtime rewrite from the maintainer side.

…thread

Addresses the second threading review on PR chsami#534. The executor no longer
touches any live RuneLite object or performs client conversions:

- interaction snapshot extended to plain data (cachedInteractingId and
  cachedInteractingWorld alongside the index), captured on GameTick. All
  five executor interaction sites now use it: handleWrongSideClick (side
  check via the cached world point), handleFires and handleNearbyFire
  ("already dousing" by index), and the ATTACK_TEMPOROSS busy-with-pool
  check (id + cached location against the pool mark). Zero
  Rs2Player.getInteracting() calls remain in the plugin.
- every LocalPoint.fromWorld / WorldPoint.fromLocal /
  WorldPoint.fromLocalInstance is marshalled through localFromWorld /
  worldFromLocal / templateFromLocal, each a complete operation on the
  client thread via getClientThread().invoke (which executes inline when
  already on it, so client-thread callers pay nothing). The three helper
  bodies are the only raw conversion sites left in the file.

A fresh focused multi-round live check on this build follows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@infuse21

Copy link
Copy Markdown
Contributor Author

Snapshot boundary completed in 9bac075 (v2.24.3), covering every site named plus one more of the same class:

  • Interaction data: the snapshot now carries plain cachedInteractingId and cachedInteractingWorld alongside the index, captured on GameTick. All executor interaction branches consume only those: handleWrongSideClick (side check via the cached world point), handleFires and handleNearbyFire ("already dousing" matched by index), and the ATTACK_TEMPOROSS busy-with-pool check (id + cached location against the pool mark — this one wasn't in the list but had the same pattern). grep Rs2Player.getInteracting() over the plugin now returns nothing.
  • Conversions: every LocalPoint.fromWorld / WorldPoint.fromLocal / WorldPoint.fromLocalInstance (including localToWorld() and the direct executor calls) is marshalled through three helpers that run the complete operation via getClientThread().invoke — inline when already on the client thread, so the GameTick-path callers pay nothing. The three helper bodies are the only raw conversion calls left in the file.

A fresh focused multi-round live check on this exact build is being run and will be reported here.

🤖 Addressed by Claude Code

@infuse21
infuse21 requested a review from chsami August 25, 2026 11:17
The 2.24.2 full-and-cooked arm sat behind the poolPhasesSeen gate, so
during the pre-pool-1 hold a bag that filled after a few batches stood idle
until the pool arrived. The hold means no SCHEDULED trips, not stranding a
full bag: loading it and refilling beats idling in every cycle. The
low-energy arms keep the pool-1 gate.

(Report that surfaced this came from a stale 2.24.0 sideload, but the idle
exists pre-pool on current head too - the fix stands on its own.)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@infuse21

infuse21 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Run for 1h. no issues within the minigame found. world hop now completes on the first try. roughly 4 permits per game with a basic harpoon and level ~70 fishing on mass worlds. could get more or less depending on the amount of players.

threading migration should be complete now

@chsami chsami left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interaction fields and scene conversions called out on the prior head are corrected, but the snapshot boundary on current head 2908490 is still incomplete.

The GameTick publisher replaces sortedFires, fishSpots, sortedClouds, and work-area references atomically, but those collections still contain live Rs2NpcModel, NPC, GameObject, and tile-object wrappers. The scheduled executor then dereferences them directly. Examples include fishSpots entries through getNpc().getLocalLocation() in the catch/fill paths, sortedFires entries through getNpc().getLocalLocation() in handleNearbyFire and fire-path handling, and sortedClouds GameObject locations in the dodge paths. The current model wrappers delegate those location reads to live actors/objects; replacing the list does not snapshot their fields.

Please publish the plain IDs/indices and LocalPoint/WorldPoint data needed by the executor from GameTick, or marshal each complete live-object operation to the client thread. Avoid retaining live RuneLite objects for later executor reads. Add focused automated coverage where practical and rerun the normal targeted build. The existing green CI and reported runtime results do not remove this client-thread race.

@infuse21

Copy link
Copy Markdown
Contributor Author

Addressed in c3ed416 (v2.24.5).

The executor-facing boundary now contains only immutable plain-data snapshots:

  • sortedFires, fishSpots, temporossPool, and ammo/exit/work-area NPC references carry only id, index, name, LocalPoint, and WorldPoint.
  • sortedClouds carries only id plus copied local/world coordinates; the overlay consumes the same snapshots rather than retaining GameObject/NPC wrappers.
  • Work-area tile objects and the locked tether are plain object snapshots. NPC/tile-object clicks re-resolve by identity and execute as one complete client-thread callback.
  • The remaining setup, exit/forfeit, lobby bank/reward, boarding, and fill-diagnostic cache operations were also audited and marshalled; live wrappers now remain local only to GameTick/event handlers or client-thread callbacks.
  • Cross-thread tick/event signals (ENERGY/INTENSITY/ESSENCE, wave/tether flags, estimator state, overlay publications) now have explicit volatile publication.

Added TemporossSnapshotBoundaryTest with four focused checks covering published snapshot types, final/plain snapshot fields, absence of retained live entity/model fields in executor owners, and volatile cross-thread signals.

Verification:

  • ./gradlew compileTemporossJava -PpluginList=TemporossPlugin against current client 2.6.21: passed.
  • ./gradlew build -PpluginList=TemporossPlugin -PmicrobotClientVersion=2.6.20: passed, including all four new tests and the Tempoross plugin JAR.
  • The unpinned full build currently resolves 2.6.21 and stops in unrelated existing FarmingContractScript code because that client no longer exposes Produce#getContractName; the focused Tempoross compile on 2.6.21 is green.

@infuse21

Copy link
Copy Markdown
Contributor Author

Follow-up on the failed build job: CI resolved Microbot 2.6.21, where Produce#getContractName() was removed. Commit 1152422 updates the unrelated Farming Contracts dialogue matching to use APIs shared by 2.6.20/2.6.21 while preserving legacy plural/tree/berry aliases, bumps that plugin to 0.2.3, and adds focused coverage.

Verification:

  • ./gradlew clean build against the default resolved client 2.6.21BUILD SUCCESSFUL (671 tasks, 5 tests)
  • ./gradlew compileFarmingContractJava -PpluginList=FarmingContractPlugin -PmicrobotClientVersion=2.6.20BUILD SUCCESSFUL

The Tempoross snapshot-boundary changes remain unchanged in c3ed416.

@infuse21
infuse21 requested a review from chsami August 28, 2026 19:46

@chsami chsami left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the synchronized head. The executor/client-thread boundary now publishes immutable plain snapshots, resolves live NPC/tile-object wrappers only inside client-thread operations, and keeps cross-thread signals volatile. The version is aligned at 2.24.5. A fresh synthetic-merge build produced the Tempoross and Farming Contracts JARs with all five added tests passing; updated-head CI is green.

@chsami
chsami merged commit fe5deb4 into chsami:development Sep 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants