Commit Graph
1229 Commits
Author SHA1 Message Date
FiskenPoul 737636b644 Make pickup-drought health check window configurable (pickup_drought_window)
Was hardcoded to 10 games; a strict pickit on a fast boss-only rush
route can legitimately go 10 games without a keep-worthy drop, making
the log warning noisy. Defaults to 10 (unchanged), override per-user
via profile.ini.
2026-07-11 22:39:38 +02:00
alexpolo1andClaude Opus 4.8 17bcb95c0a fix: make the Diablo (Chaos Sanctuary) run complete end-to-end
The run_diablo route was failing every game. Diagnosed and fixed live —
a full run now clears all three seals (Vizier, De Seis, Infector) and loots.

Pentagram navigation (was the #1 abort: "battle_failed", char stranded in
CS trash, pentagram never detected):
- _loop_pentagram now falls back to active node-602 navigation when the blind
  fixed-path teleport loop fails to surface the pentagram. Node 602 searches the
  PENT templates directly and teleports toward them with the pather's auto-
  recovery sweep — the same robust approach _cs_pentagram already uses. Applied
  in both diablo.py and vizier.py.
- Combined with the lowered _PENT_THRESHOLD (0.50), the pentagram now resolves:
  live reads were 57-96% where the old 0.83 threshold rejected them.

Seal layout check (next abort after the pentagram fix, at the Vizier seal):
- Added per-seal score logging (LC primary/confirm). This revealed the real
  cause is character-positioning variance, NOT template drift: the true layout
  reads 84-88% and the other 42-57% (clean separation) when well-positioned, but
  from a bad camera angle BOTH read ~55-66% and the check is ambiguous.
- So the fix is more re-approach attempts (max_attempts 2 -> 3), not lower
  thresholds — lowering a disambiguation threshold risks picking the WRONG seal
  from a bad-position read.
- Normalized seal-A threshold_confirmation 0.85 -> 0.80 (every other seal is
  0.80; safe given the 84-88% vs 42-57% separation).

Crash fix (game_recovery.py): go_to_hero_selection had been dedented to module
level while its body kept method indentation, so it fell out of the GameRecovery
class. Every post-chicken/death/failed-game recovery threw
AttributeError: 'GameRecovery' object has no attribute 'go_to_hero_selection'
and killed the run_bot thread. Re-indented into the class. Verified live: the
bot now recovers from failed games and auto-starts the next one.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-24 15:58:13 +02:00
alexpolo1 59b851b107 fix: stuck in town recovery improvements
- replace print() with Logger.debug() in misc.py to fix colorama OSError crash on restart
- try/except RuntimeError in template_finder ThreadPoolExecutor so interpreter shutdown falls back to sequential matching
- add last-resort direct WP scan in A5 open_wp after anchors fail
- extend go_to_hero_selection timeout 30s->45s, add ESC fallback after 15s if blocked by UI panel
- remove startup warning spam in screen.py
2026-06-24 10:47:34 +02:00
alexpolo1andClaude Opus 4.8 0e90b36e14 fix: bound A5 open_wp so a missed waypoint never strands the bot in town
The A5 waypoint stone matches reliably (65-91% when on screen). The real
failure mode is a stale curr_loc that lands the char off the stone, so
select_by_template("A5_WP") never matches. The old escalation (NPC anchors +
a 6-step directed sweep) then looped for 5+ minutes — the "stuck in town"
behavior seen in log/log.txt 2026-06-24 (08:28:48 -> 08:30 force-exit).

- Add a 45s hard wall-clock budget to open_wp; bail between anchors once past.
- Drop the directed sweep entirely: it never recovered in practice and was the
  main multi-minute time sink. A failure now returns fast so the caller falls
  back (buy at Malah / skip to stash) instead of stranding the bot.
- Also folds in the in-progress A5 repair-menu timing fix (wait_until_visible
  instead of a too-short 0.2-0.3s peek).
- Add test/town/a5_open_wp_test.py covering fast-fail, quick-mode, and budget.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-24 08:39:20 +02:00
alexpolo1 71109e9532 Merge fix stash branch into main 2026-06-21 19:04:54 +02:00
alexpolo1 6012934e66 Add chipped gem conversion runner 2026-06-21 16:06:58 +02:00
alexpolo1 48d8445c05 Bootstrap visual test harness and fix GEMS transmute flow 2026-06-21 15:04:12 +02:00
alexpolo1andClaude Sonnet 4.6 44794fba67 logs: hard size cap on log.txt + kill install-log progress-bar balloon
A single bot session produced a 22 GB log. The file logger used daily-only
rotation (TimedRotatingFileHandler when='midnight') with NO size cap, so a
long/spammy session grew log.txt unbounded within a day. Its archiver also
looked for .1/.2 backups that the timed handler never produced.

- logger.py: switch to size-based RotatingFileHandler — log.txt rotates at
  50 MB (override via BOTTY_LOG_MAX_MB), keeps 5 zipped backups, and prunes
  log/archive/ to 30 zips. Hard cap on both the live file and total disk.
  The .1/.2 naming now matches what the handler emits, so archiving works.
- install.bat: pip --progress-bar off. The progress bar redraws via \r;
  redirected to a file (run_install_capture.bat) those redraws became
  millions of lines — the other way an install log balloons to GBs.
- params.ini: document the log.txt cap + BOTTY_LOG_MAX_MB.

Verified: with a tiny cap, log.txt stayed under the limit while rotated
files zipped to archive; full suite 80 passed / 2 skipped.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 18:56:25 +02:00
alexpolo1andClaude Sonnet 4.6 aa78bab638 ci: harden coverage omit so config-3.py phantom never trips xml
Botty - CI / test (push) Canceled after 0s
Botty - CI / build (push) Canceled after 0s
The bare "config-3.py" omit never matched the phantom's absolute path
(D:\a\...\config-3.py), so coverage xml only survived via --ignore-errors
and still logged the alarming "No source for code" line. Use a glob
(*config-*.py) that matches the phantom at any path while keeping
src/config.py measured (verified via coverage GlobMatcher).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
v0.8.4
2026-06-20 11:30:16 +02:00
alexpolo1andClaude Sonnet 4.6 3d11947bf2 ocr: bundle Tesseract into release for click-and-run OCR
Make the standalone exe work with OCR out of the box — no separate
Tesseract install, no tesserocr DLL hell. Verified end-to-end: built the
exe, ran it frozen with the system Tesseract blinded, confirmed it
resolves the bundled binary and reads text ("CHAM RUNE").

- ocr.py: resolve an _APP_BASE (exe dir when frozen, else cwd) and prefer
  a bundled <exe_dir>/tesseract/tesseract.exe over PATH / Program Files.
  Resolve assets/tessdata to an absolute path so OCR no longer depends on
  the current working dir. Applies to both the tesserocr and pytesseract
  paths.
- build.py: copy a portable Tesseract (exe + DLLs) from TESSERACT_DIR
  (default C:\Program Files\Tesseract-OCR) into <release>/tesseract/. Our
  trained models in assets/tessdata are used via --tessdata-dir, so their
  tessdata is skipped. Warns (non-fatal) if Tesseract isn't present.
- ci.yml: choco install tesseract before the build so the bundle is
  reproducible on the runner; verify it landed in the release dir.
- test/conftest.py: apply the SSL cert-store workaround so pytest can be
  collected on Windows boxes with a corrupted cert store (no-op on CI).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 10:49:25 +02:00
alexpolo1andClaude Sonnet 4.6 f31c30f388 security: untrack cached d2jsp scrapes, parametrize cookie helper
- git rm --cached data/d2jsp_pages (102 files, already gitignored) — saved
  authenticated HTML embedded the live d2jsp msec session token
- apply_manual_cookies.py: read member_id/msec from env vars instead of
  hardcoding the real session token

History purge of these blobs follows in the same cleanup.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 10:33:50 +02:00
alexpolo1andClaude Sonnet 4.6 b9f6635f8f security: stop tracking cookies.txt, ignore secrets, drop personal paths
Pre-public cleanup:
- Remove cookies.txt from tracking (held live d2jsp session cookies
  member_id + msec) and delete the local copy
- .gitignore: cookies.txt, cookies_temp*, *.cookies, config/custom.ini
- fg_scrape_pipeline.sh: replace hardcoded /c/Users/alex/Downloads path
  and /c/Python313/python with a script-relative cd and $PYTHON from PATH

NOTE: cookies.txt still exists in git history (commit e3d6605) — a
history purge + cookie rotation is still required before going public.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 10:28:47 +02:00
alexpolo1andClaude Sonnet 4.6 73c4601e2b ci: tag-triggered build-and-release in one run
Switch from the upload-to-an-existing-release model (create release in
UI/CLI first, release:published triggers CI) to a tag-driven flow:

  git tag v0.8.5 && git push --tags
  -> CI builds + smoke-tests both exes
  -> softprops/action-gh-release creates the release and attaches the zip

Build fails => no release is ever created (no more empty/half-published
releases). Removes the release: trigger so the workflow can't double-fire
when the action publishes the release. Keeps permissions: contents: write.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 10:25:17 +02:00
alexpolo1andClaude Sonnet 4.6 5647f323a7 ci: grant contents:write so release upload step can attach the zip
The build job's "Upload to Release" step failed with HTTP 403
"Resource not accessible by integration" because the default
GITHUB_TOKEN is read-only. Add top-level permissions: contents: write.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 10:12:44 +02:00
alexpolo1andClaude Sonnet 4.6 3db44fe4e6 build: bundle conda native DLLs + fix shopper SSL crash
Verified by building locally and running both exes to their menus.

build.py: prepend the conda env's Library\bin, Library\lib and DLLs
dirs to PATH before invoking PyInstaller. PyInstaller resolves binary
deps via PATH (not --paths, which only affects Python imports), so
without this the frozen exe crashed at startup with
"DLL load failed while importing _ctypes" (missing ffi-8.dll, plus
liblzma/libbz2). Now ffi-8/lzma/bz2/leptonica/tesseract52 all bundle.

src/shopper.py: mirror main.py's startup header — add the
ssl.load_default_certs monkey-patch (corrupted Windows cert store made
aiohttp crash at import with ASN1 NOT_ENOUGH_DATA) and drop Library\bin
from os.add_dll_directory (it ships mismatched OpenSSL DLLs that break
_ssl). shopper.exe previously crashed on any machine with a bad cert
store; CI's clean runner masked it.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 09:47:41 +02:00
alexpolo1andClaude Sonnet 4.6 57073946ae ci: split test job into named steps, fix coverage xml phantom-file error
- Split monolithic 'Validate Botty' into 4 named steps so failures are
  visible per-step without log diving: Python version / Syntax check /
  Tests / Coverage report
- Add scripts/ to compileall so new scripts are syntax-checked too
- Add -v to pytest for per-test pass/fail in CI output
- Fix coverage xml exiting 1 on conda phantom config-3.py:
  - Move ignore_errors to [report] section (was wrongly in [xml])
  - Add --ignore-errors flag on coverage xml command (belt+suspenders)
  - Omit config-3.py and site-packages paths from [run] tracking

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 09:34:11 +02:00
Alex 5c687773b1 Click-and-go install: self-bootstrapping install.bat + tesserocr DLL fix (#2)
Merges fix/stash-full-guard-and-testbed into main. See PR #2 for full description.
2026-06-20 09:27:12 +02:00
alexpolo1andClaude Sonnet 4.6 c1997b438e build: use --conda_path arg instead of hardcoded user path
Fixes CI build job which passes --conda_path C:\Miniconda but
botty_env was hardcoded to C:\Users\alex\.conda\envs\botty.
Also replace os.system mkdir with os.makedirs and add error
detection so PyInstaller failures are not silently swallowed.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 00:59:20 +02:00
alexpolo1andClaude Sonnet 4.6 aa2f52f702 coverage: scope to src/, ignore missing sources in xml report
'coverage xml' was failing with 'No source for code: config-3.py' because
coverage was tracing conda internals (config-3.py from Miniconda's base env).
Add source=src to restrict coverage collection to our source tree, and
ignore_errors=True in [xml] as a safety net for any remaining phantom paths.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 00:49:18 +02:00
alexpolo1andClaude Sonnet 4.6 0fbe480f38 Fix two pre-existing CI test failures
- bnip/utils.py: standardise error code prefix NIP_0x23 -> BNIP_0x23 to match
  all other error codes in the bnip module (all use BNIP_ prefix)
- test_transpile: update syntax_test9 expected code 0x11 -> 0x23; parsing of
  '[idname] > ring' now hits the unique/set lookup (BNIP_0x23) before the
  logical-operator token check (BNIP_0x11) fires
- test_skill_preflight: test_unknown_build_has_no_checks used 'hammerdin' as
  the unknown build, but hammerdin now has skill checks defined; switch to
  'nonexistent_build' which has no checks

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 00:44:25 +02:00
alexpolo1andClaude Sonnet 4.6 de2943fe7a Add run_install_capture.bat — capture install output to install_log.txt
Wrapper for install.bat that redirects all output to install_log.txt so the
install can be run non-interactively and the log inspected after the fact.
Useful for debugging installs in CI or remote sessions.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 00:31:08 +02:00
alexpolo1andClaude Sonnet 4.6 41c230ad9c install.bat: fix discord import check for Windows SSL cert store bug
discord.py imports aiohttp which calls ssl.create_default_context() at import
time. A malformed cert in the Windows cert store raises ASN1 NOT_ENOUGH_DATA.
The bot patches this at runtime via _patch_ssl(); the install check now applies
the same workaround (null-patch load_default_certs) so discord: OK on all machines.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 00:19:24 +02:00
alexpolo1andClaude Sonnet 4.6 a4e3ff0e2f install.bat: fix Win11 detection, stale errorlevel in blocks, remove pause
- Echo with (no ABSOLUTE flag) had unescaped ) closing the if block early,
  causing BOTH Win11 and Win10 branches to run and wrong requirements used.
  Fix: escape the parens with ^( and ^).
- %errorlevel% inside ( ) blocks is expanded at parse time, not after the
  command that sets it. Affects winget install result check, tesseract winget
  check, pytesseract check, and dependency verify loop.
  Fix: use !errorlevel! (delayed expansion) in those four spots.
- Remove final pause so the CMD window closes automatically on success.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-20 00:14:17 +02:00
alexpolo1andClaude Sonnet 4.6 6625c1e1d5 install.bat: use winget as primary Miniforge install method
winget (available on Win10 1709+ and all Win11) handles registration,
PATH, and package integrity checks automatically — cleaner than the
NSIS /S fallback. Fall back to curl+NSIS if winget is absent or fails.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 23:54:51 +02:00
alexpolo1andClaude Sonnet 4.6 fc8ef97a7a install.bat: add %LOCALAPPDATA%\miniforge3 to conda search paths
Miniforge3's JustMe default install location changed from
%USERPROFILE%\miniforge3 to %LOCALAPPDATA%\miniforge3 in recent versions
(per robotology-superbuild docs and winget package metadata). Both the
initial scan and the post-bootstrap rescan now check %LOCALAPPDATA% first.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 23:51:41 +02:00
alexpolo1andClaude Sonnet 4.6 1a58006188 install.bat: fix Miniforge bootstrap — curl download + start /wait for installer
Two bugs found by studying oobabooga/text-generation-webui (reference impl):

1. PowerShell Invoke-WebRequest without $ProgressPreference='SilentlyContinue'
   renders an ASCII progress bar that drops download speed from ~50 MB/s to
   ~1 MB/s. Switch primary download to curl (built into Win10/11, fast, clean
   progress bar). PowerShell is kept as a fallback with the flag set.

2. Running the NSIS installer directly ("installer.exe /S") can return before
   the install finishes. The correct pattern is "start /wait "" installer.exe"
   which blocks until the installer process truly exits. Also adds /NoShortcuts
   and /NoRegistry flags (standard for embedded/self-contained installs).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 23:46:24 +02:00
alexpolo1andClaude Sonnet 4.6 e18b81200f install.bat: fix Miniforge silent install — drop /D= to avoid spaces-in-path failure
NSIS /D= does not support paths with spaces. %USERPROFILE% often contains
spaces (e.g. C:\Users\John Smith). Omitting /D= lets Miniforge install to
its own default location (%USERPROFILE%\miniforge3) which it handles safely.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 22:50:16 +02:00
alexpolo1andClaude Sonnet 4.6 03da8783b6 install.bat: auto-download and install Miniforge3 if conda not found
Users no longer need to pre-install conda. If the first scan finds no
conda.exe, install.bat downloads Miniforge3-Windows-x86_64.exe via
PowerShell (available on all Win10/Win11), installs it silently to
%USERPROFILE%\miniforge3 with no PATH changes, then re-scans and
continues. On failure it prints a manual fallback URL. Users with an
existing conda install are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 22:44:31 +02:00
alexpolo1andClaude Sonnet 4.6 26ecb826ec fix tesserocr: bundle MSVC tesseract52.dll + conda-forge .pyd instead of broken DLL name aliases
The old install.bat copied tesseract41.dll as tesseract.dll (wrong name — wheel
needs tesseract51.dll) and the bundled .pyd used MSVC C++ ABI that the MinGW conda
DLLs can't satisfy. Both tesserocr imports now work:

  tesseract52.dll        — MSVC tesseract 5.2.0 from conda-forge (compiled vs2019)
  tesserocr.cp310-...pyd — conda-forge 2.5.2 build linked against tesseract52.dll

The MSVC leptonica-1.78.0.dll comes from the existing conda tesseract=4.x install.
ocr.py already registers Library/bin via os.add_dll_directory so both backends
(tesserocr fast path, pytesseract reliable fallback) load correctly at bot startup.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 21:56:48 +02:00
alexpolo1andClaude Sonnet 4.6 3ed837e1ee testbed: add count to help docstring
count scenario was registered in the scenarios dict but missing from
the module docstring — running testbed.py with no args hid it.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 21:34:59 +02:00
alexpolo1andClaude Sonnet 4.6 175f8e342b install.bat: dual-backend OCR that works on Win10 and Win11 out of the box
Architecture: two backends, first one that works wins at runtime (ocr.py).
  1. tesserocr  — C extension via conda tesseract 4.x DLLs + bundled wheel (fast)
  2. pytesseract — subprocess via winget tesseract.exe (always works if exe exists)

Install changes:
- tesserocr setup: best-effort, never fatal. Wheel install failure or DLL mismatch
  just falls through to pytesseract. Removes the hard-exit on missing wheel.
- pytesseract setup: winget install with a manual download link fallback for Win10
  users where winget is unavailable. pytesseract is now in requirements.txt so it
  is always importable.
- OCR verification: tests BOTH backends after setup, reports which is active.
  Warns clearly if neither works rather than silently leaving OCR broken.
  Sets ALL_OK=0 so the installer ends with a visible failure state.
- Dependency loop: removes tesserocr from the import check — OCR is now verified
  end-to-end by the backend test above, not by a bare import.
- Removes the broken pytesseract source-patch block (syntax error, never ran).
- TESS_PATH hoisted to :env_found so it is available for the OCR check.

requirements.txt: add pytesseract (pure Python, no native deps).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 21:29:03 +02:00
alexpolo1andClaude Sonnet 4.6 c566519cd8 Revert misguided install.bat OCR commits
Reverts 63d0a33 and e712076. Both commits got the OCR architecture backwards:
- Made winget "optional" but DEPENDENCIES.md shows pytesseract is the working
  backend on the actual machine (tesserocr is DLL-broken there)
- Added a tesserocr smoke test that would always fail on this machine
- Added pytesseract to verification loop but it's not in requirements.txt

Starting fresh with a correct understanding of the OCR stack.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 21:27:17 +02:00
alexpolo1andClaude Sonnet 4.6 e712076f08 install.bat: make OCR setup work on Win10 and Win11
tesserocr (conda DLLs + bundled wheel) is the primary OCR backend and works
identically on both platforms. pytesseract is an optional fallback that needs
a working tesseract.exe, which winget can install on Win11 but not reliably
on Win10.

Changes:
- Winget step: reframed as optional/best-effort; failure is informational only,
  not a warning that "OCR will not work" (tesserocr handles all OCR)
- Remove pytesseract from the verification loop — it is not in requirements.txt
  and would always fail on Win10 without winget; adding it in the previous commit
  was wrong
- Remove pytesseract-specific smoke test block
- Add tesserocr end-to-end smoke test after the verification loop: opens a
  PyTessBaseAPI against the real tessdata model so DLL resolution + tessdata
  access are both exercised; sets ALL_OK=0 on failure so the installer reports
  the right thing

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 21:10:33 +02:00
alexpolo1andClaude Sonnet 4.6 63d0a334cb install.bat: fix OCR setup — verify tesseract exe, drop broken patch, add pytesseract check
Three fixes:
1. After winget tesseract install, check C:\Program Files\Tesseract-OCR\tesseract.exe
   actually exists and print a download link if not — previously silent failure.
2. Remove the broken pytesseract source-patch block (Python syntax error on the
   os.path.join call — it has never run). ocr.py already handles cmd discovery at
   import time via PYTESSERACT_TESSERACT_CMD (Bug 14 fix), so patching is moot.
   Replace with a pytesseract.get_tesseract_version() smoke test that exercises the
   full exe chain and fails clearly if tesseract is missing.
3. Add pytesseract to the dependency verification loop alongside tesserocr.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 21:02:36 +02:00
alexpolo1andClaude Sonnet 4.6 0facc84661 Add scripts/convert_chipped.py — inventory chipped-gem right-click converter
Standalone script: scans loot columns, tooltips every occupied slot to
identify chipped gems, then right-clicks each to convert in-place.
Uses the same screen/input_layer stack as the main bot (no D2R restart needed).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 20:40:25 +02:00
alexpolo1andClaude Sonnet 4.6 c3a2edb23c FG market: fix URL regex, expand to 60-day window, refresh estimates
fg_market_scraper.py: broaden user.php href regex to allow a path prefix
before user.php (some topic pages use relative paths like ../user.php).

fg_scrape_pipeline.sh: --days 21 -> --days 60 for a fuller price history.

improve_fg_estimates.py: hoist min_valid_price constant out of the per-file
loop (was a constant being re-assigned every iteration).

fg_daily_estimates.json: refreshed from 853 topics over 60 days (was 21).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 20:40:20 +02:00
alexpolo1andClaude Sonnet 4.6 a4498d2244 testbed: add count scenario, gem filter args, fix stash auto-open, dead return
- New 'count' scenario: read gem counts from GEMS tab via OCR, print plan
- gems_all: accept tier and gem-type filters + --max cap (e.g. 'gems_all flawless diamond')
- gems_all: fast-path ESC + stash-chest click before launching full TownManager
- gems_all: leave stash open after run so re-invocations skip navigation
- stash: stash chest auto-walk if stash not open
- Remove duplicate 'return 0' at end of _gems_all that made dead code

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 20:40:12 +02:00
alexpolo1andClaude Sonnet 4.6 0facc38613 Fix OCR digits whitelist syntax; broaden A5 stash-open detection
ocr.py: use -c tessedit_char_whitelist=0123456789 (tesseract 4+ style)
instead of the legacy blacklist+whitelist pair that was silently ignored.

a5.py: include left_inventory_ready() in the stash_is_open_func poll so
the open_stash success check fires even when GoldBtn templates miss at
current D2R rendering settings.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 20:40:06 +02:00
alexpolo1andClaude Sonnet 4.6 4bb460db2a Gem transmute v3: OCR counting, GEMS-tab cube flow, cube safety helpers
New production flow for convert_all_gems_to_perfect:
- Count gem stacks via OCR badge on each GEMS-tab slot (Hermes as fallback)
- Open cube from PERSONAL stash per-transmute, return to GEMS tab while cube stays open
- ctrl+shift+right-click x3 loads gems directly into cube from GEMS tab
- ctrl+shift+left-click moves result from cube back to GEMS tab
- Pre-flight: clear any leftover items in cube before planning
- Stash reopen guard at every iteration in case ESC closed the stash
- _ensure_cube_available / _locate_cube / _ensure_cube_in_stash safety chain
- _empty_cube_to_gems_tab: blindly ctrl+shift+left-click all 12 slots to clear

Also adds tier_filter, gem_filter, max_transmutes params to convert_all_gems_to_perfect
and wires the new flow into run_transmutes when stash_tabs > 4.

Includes 8 pytest unit tests covering OCR plan, gem/tier filters,
cube-location logic and _open_available_cube_for_gems_tab edge cases.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-19 20:40:00 +02:00
alexpolo1andClaude Sonnet 4.6 23f9488853 testbed gems_all: fall back to A5 when act detection fails
Avoids blocking on town-marker detection failure when character is
in A5 but detect_current_act() can't find templates (e.g. stash was
just closed, unusual camera angle). Assumes A5_TOWN_START so open_stash
navigates to Harrogath stash without triggering Cain.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 07:58:23 +02:00
alexpolo1andClaude Sonnet 4.6 e79cf0470d Fix gem transmute: open cube from inventory + ctrl+shift from GEMS tab into cube
- convert_all_gems_to_perfect: revert to _open_cube_from_inventory() — cube lives
  in character inventory, not stash PERSONAL tab
- After opening cube, switch to GEMS tab; ctrl+shift+click sends gems directly from
  GEMS tab into the open cube without an inventory roundtrip
- testbed _gems_all: add left_inventory_ready() fallback so stash-open detection
  works with new D2R UI (GoldBtnStash template doesn't match new UI)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 07:55:46 +02:00
alexpolo1andClaude Sonnet 4.6 fffc0a4b56 Gem transmute: use cube from stash (PERSONAL tab) + ctrl+shift to send gems to cube
- _open_cube_from_stash(): switch to PERSONAL tab, right-click cube from stash
- _ctrl_shift_click_monitor(): ctrl+shift+click sends item from active stash tab
  directly into the open cube (no inventory roundtrip)
- convert_all_gems_to_perfect: open cube once per gem type from stash, switch to
  GEMS tab, ctrl+shift+click gem stack 3x -> into cube, transmute, ctrl+click
  result -> back to GEMS tab; close cube when type is done
- Keeps _open_cube_from_inventory as fallback for other callers

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 07:45:47 +02:00
alexpolo1andClaude Sonnet 4.6 0e9c18e3e7 Fix gem transmute: stacked-slot clicks, search_all for inventory, Hermes key mapping
- _gems_tab_pull_3: click same stack position 3x (stacked slots = 1 match, not 3)
- convert_all_gems_to_perfect execute loop: same fix (was checking len(matches)<3)
- Replace inspect_inventory_area with search_all on right_inventory ROI in both
  old _run_gem_transmutes_new_ui and new convert_all_gems_to_perfect - slot-by-slot
  scan at 0.91 threshold missed gems that search_all finds at 0.80
- _plan_transmutes: handle both Hermes short keys (ruby_flawless) and template
  fallback long keys (inventory_ruby_flawless) via dual-key lookup
- _count_gems_by_template: return 999 instead of slot count - stacked slots show
  1 match per type regardless of how many gems are in the stack; execution loop
  stops naturally when the stack empties

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 07:31:14 +02:00
alexpolo1andClaude Sonnet 4.6 87c8dc5e16 convert_all_gems_to_perfect: use search_all for new GEMS tab UI
The new D2R stash has named tabs (PERSONAL/SHARED/GEMS/MATERIALS/RUNES)
where gems are stacked (one slot per type/tier) instead of individual
inventory grid positions. The old slot-by-slot inspect_area approach
found 0 matches because gem icons don't align to the 38x38 grid.

Fix: _gems_tab_pull_3() uses template_finder.search_all() to find gem
positions anywhere in the left panel, then ctrl+clicks the 3 best hits.
Cube must now be in the character's right inventory (not personal stash).

Verified: 101 flawless->perfect transmutes in one run. Batch cap 100->500.

Also adds _switch_to_gems_tab() and _open_cube_from_inventory() helpers.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 06:40:49 +02:00
alexpolo1andClaude Sonnet 4.6 b84bdc8f4b Add convert_all_gems_to_perfect + fix hermes encoding
transmute.py: convert_all_gems_to_perfect() chains all four gem tiers
(chipped->flawed->standard->flawless->perfect) by calling the existing
_run_gem_transmutes_lod for each tier in order. Result gems from each
pass land back in stash and feed the next pass.

testbed.py: gems_all scenario triggers convert_all_gems_to_perfect with
stash open; also adds missing return 0 to _gems().

hermes.py: fix UnicodeEncodeError on cp1252 terminals when the LLM
response contains non-ASCII characters (arrows, bullets, etc.).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 06:04:03 +02:00
alexpolo1andClaude Sonnet 4.6 8ca96241bf Hermes: disable Qwen3 thinking mode, add repetition_penalty
thinking mode caused the model to loop on uncertain items (e.g. weapon
slot identification). disable_thinking=False + repetition_penalty=1.15
gives clean, non-repetitive responses in ~5s.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-17 05:34:12 +02:00
alexpolo1andClaude Sonnet 4.6 f42637a5d8 Add Hermes: vision-LLM agent for live D2R game analysis
tools/hermes.py grabs the current D2R window (same grab() path the bot
uses), sends it to the local Qwen vision model at 192.168.1.98:8010,
and runs an interactive REPL. The system prompt gives Hermes full context
on coordinate systems, template matching, input layer, and bot patterns
so it can reason about what's on screen and suggest feature code.

  <botty-env-python> tools/hermes.py [initial prompt]
  <botty-env-python> tools/testbed.py hermes [initial prompt]

Also wired into testbed.py as the 'hermes' scenario.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-16 20:44:51 +02:00
alexpolo1andClaude Sonnet 4.6 cbc797c14a Stash scanner: fix multi-slot dedup, add periodic bot hook + trade CSV
- scripts/stash_inventory.py: fix BaseItem["dimensions"] being a list
  not a dict — use dims[0] for height so the claimed-slot dedup works
  and multi-slot items (Large/Grand Charms, 2h weapons) are no longer
  double-counted
- scripts/make_stash_csv.py: new — convert stash_inventory.json to a
  deduplicated stash_list.csv (name, page, stats) for trade reference
- stash_list.csv: current stash export (71 unique items)
- config/params.ini: add stash_scan_interval (default 0/off)
- src/config.py: parse stash_scan_interval from [general]
- src/bot.py: after stash+transmute, trigger scan every N runs when
  stash_scan_interval > 0
- src/inventory/personal.py: reset transfer_failures per stash tab so
  each tab gets its own 2-failure budget before moving to the next
- tools/testbed.py: PASS/FAIL fix for `stash all` — equipped-area cols
  (≥4) correctly left in inventory is a PASS not a FAIL (Bug 19 guard)
- CLAUDE.md: document stash scanner scripts in File Quick Reference

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-16 20:40:05 +02:00
alexpolo1andClaude Opus 4.8 3522dedcee Stash: guard against transfer failure being mistaken for full stash
stash_all_items() treated any keep item remaining after transfer_items()
as "this tab is full", paged through every stash tab, and on the last page
called stash_full() -> taskkill D2R + a false Discord "stash full" alert.
A transfer can fail for non-fullness reasons (equipped-area click guard,
transient UI), so this could kill the game on a stash that has free slots.

Fix: before declaring a tab full, check EmptyStashSlot. If a slot is free
but the transfer still failed, count it as a transfer failure (cap 2) and
bail, leaving items in inventory -- never advance tabs / call stash_full()
on a non-full page. Also fix a leftover >3 page bound (-> >5) in the same loop.

Add a `stash` scenario to tools/testbed.py (self-bootstraps: main menu ->
create game -> walk -> open stash -> run stashing; `all` widens the scan to
all 10 columns to exercise the keep-item branch) and extract the conda-env
SSL cert workaround into a shared _patch_ssl() helper. Document as Bug 19.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 04:05:01 +02:00
alexpolo1 6a8f1ed5d9 add fiskenersej character profile + pickit profile cycler (F10) 2026-06-12 12:08:21 +02:00