fix(eloot): v2.11.9 Hinterwilds sell run no longer walks you to Icemule for the pawnshop - #2473
Merged
mrhoribu merged 3 commits intoSep 14, 2026
Conversation
…or the pawnshop
The Hinterwilds town (Coldriver Village, uid 7503205) has only a gemshop, a
furrier and the locksmith pool. Sell.go_sell has always known this and skipped
pawnshop/collectibles/consignment/chronomage there, but the pawnshop retries
added in v2.11.7 do their own find_nearest_by_tag("pawnshop") lookup and call
go2 directly, bypassing that guard. find_nearest_by_tag searches the whole map
graph with no town or distance scoping, so the nearest pawnshop is Icemule
Trace's -- and anyone whose gemshop declined a jewelry-tagged item got walked
out of the Hinterwilds mid-hunt. Their only bail-out was pawn.nil?, which never
fires while any pawnshop is reachable anywhere.
Extracts go_sell's inline condition into ELoot.shop_unavailable_in_town? and
routes all three paths through it, so a future shop trip can't quietly
reintroduce the cross-continent walk. When it trips, the queued items are left
with your loot and the queue is cleared, matching the pre-2.11.7 behaviour.
Both retries are affected: retry_wrong_shop_jewelry_at_pawnshop always runs, so
it is the one being reported; recheck_refused_at_pawnshop has the same defect
behind the opt-in "Recheck gemshop-refused items at the pawnshop" setting.
Also makes go_sell nil-safe. The old inline check called Room[nil].uid when no
town could be found; the predicate returns false instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced 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 |
…retries Review caught that the first commit fixed the reported symptom and missed three sibling paths with the identical defect. Guarding call sites one at a time is what caused this bug in the first place -- v2.11.7 added a shop trip and forgot the check -- so fold the guard into the lookup instead. Adds ELoot.nearest_shop, which applies shop_unavailable_in_town? and then find_nearest_by_tag, returning nil for "don't go". A new sell path now gets the town scoping by construction rather than having to remember a separate check. Newly covered: - Loot.sell_box_contents (v2.11.8, the locksmith pool full-box recovery). Picks shops via its own lookup and sorts them by dijkstra distance, never passing through go_sell. The Hinterwilds has a locksmith pool, so this fires exactly where the bug lives: a pool box holding a pawnshop-only item walked the character to Icemule mid-hunt. - Sell.custom_type (;eloot type <x>). Dispatches to the shop methods directly, bypassing go_sell's filter entirely. - Sell.custom_list (;eloot sell <item>). Called go2 on every location check_items returned, with no filtering at all. - Sell.collectibles, which did its own unguarded lookup. Both callers already filter, so this was not reachable, but it is safe from any future caller now. Also returns instead of calling go2(nil) when nothing is reachable. The two pawnshop retries keep their two distinct outcomes, which an earlier pass in this branch had collapsed into one: no pawnshop in town clears the queue (it can never succeed here), while no reachable pawnshop leaves it queued for a later run, as it did before this branch. go_sell now expresses its skip through the same lookup rather than a separate guard line above it. Specs: 165 examples, 0 failures (134 on master). Mutation-checked both ways -- reverting nearest_shop's guard fails the helper specs, and reverting sell_box_contents to its old lookup fails three of the new box examples. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mrhoribu
requested changes
Sep 14, 2026
Use ||= so re-running the script does not log "already initialized constant" warnings (review feedback on elanthia-online#2473). The spec mirrors the constant from source, so its extraction pattern follows suit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mrhoribu
approved these changes
Sep 14, 2026
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.
Summary
Reported: a character hunting out of Coldriver Village (the Hinterwilds) is now taken to Icemule Trace to sell, where eloot used to just skip the pawnshop because the town doesn't have one.
The Hinterwilds town (uid
7503205) has exactly three shop tags across its 246 rooms: a gemshop, a furrier, and the locksmith pool.Sell.go_sellhas always known this and skippedpawnshop|collect|consignment|chronomagethere unless "Sell in FWI" is on — that's the skip being remembered, and it still works.The problem is that
go_sellis not the only way to reach a shop. Several paths find one with their ownfind_nearest_by_tag(shop)call, which searches the entire map graph with no town or distance scoping, and then callELoot.go2directly. From the Hinterwilds, the nearest pawnshop is Icemule's room 2464.Affected paths
Sell.retry_wrong_shop_jewelry_at_pawnshopSell.recheck_refused_at_pawnshopLoot.sell_box_contentsSell.custom_type;eloot type <x>Sell.custom_list;eloot sell <item>The first is the one being reported. Its trigger fits the location: the Hinterwilds gemshop is the only gem buyer in town, so any jewelry-tagged item the jeweler answers "that's not quite my field" to gets queued and dragged to Icemule. Before v2.11.7 the
jewelry+Pawnshopguard inSell.appraisestopped those items cold and they were simply stowed.sell_box_contentsis worth calling out: the Hinterwilds has a locksmith pool, so that recovery runs exactly where the bug lives. A pool box holding a pawnshop-only item walks you out of the area mid-hunt.What changed
Guarding call sites one at a time is what caused this in the first place — v2.11.7 added a shop trip and forgot the check — so the guard is folded into the lookup rather than bolted onto each caller:
ELoot.shop_unavailable_in_town?—go_sell's inline town check, extracted.ELoot.nearest_shop(shop)— applies that guard, thenfind_nearest_by_tag. Returnsnilfor "don't go". Every path that travels to a shop now finds it through here, so a new sell path gets the town scoping by construction instead of having to remember a separate check.The guard was deliberately not put inside
ELoot.go2. That method is also called with bare room ids for banks, start rooms and locksmith pools, so a check there would silently skip legitimate travel while callers carried on as if they'd arrived.Behaviour notes:
Sell.collectibleswas not reachable unguarded (both callers already filter), but it had its own unguarded lookup, so it is now safe from any future caller. It also returns instead of callinggo2(nil)when nothing is reachable.go_sellis now nil-safe. The old inline check evaluatedRoom[nil].uidwhen no town could be found.Test plan
ruby -c scripts/eloot.lic— Syntax OKrubocop scripts/eloot.lic spec/scripts/eloot_spec.rb— no offensesrspec spec/scripts/eloot_spec.rb— 165 examples, 0 failures (134 on master)nearest_shop's guard fails the helper specs, and revertingsell_box_contentsto its old lookup fails three of the new box examples. The new tests are not vacuous.gemshop,furrierandlocksmith pooltags, and that the nearestpawnshopis Icemule room 2464Loot.sell_box_contents, a separate code path from the sell run above, and it is the one the Hinterwilds actually triggers since the town has a locksmith pool.New coverage: the
shop_unavailable_in_town?predicate (each missing shop skipped, each present shop still routed, the FWI opt-in, no-town, argument handling), thenearest_shoplookup, four cases on the jewelry retry, and four onsell_box_contentsconfirming it no longer leaves town and still sells what the town can buy.🤖 Generated with Claude Code