Old template was too large and included background elements that changed
between D2R versions, causing template matching to fail at all thresholds.
New crop isolates just the waypoint stone itself.
- Restore FIRST-RUN.md onboarding guide
- Pin environment.yml to requirements.txt (was loose 24 packages)
- Remove debug screenshots from git (debug_*.png -> .gitignore)
- Remove .hermes/ dev plans from git (already in .gitignore)
- Archive botty_next/ test harness to alexpolo1/my-botty-tools
- Archive docs/legacy-go/ to alexpolo1/my-botty-tools
- Move 14 dev tools from repo root to tools/ directory
- Extend .gitignore to prevent dev artifacts re-entering root
- Promote stable branch with all bug fixes from main
- Bump version 0.8.4 -> 0.9.1
Pindle (src/run/pindle.py):
- Pre-check if already in Pindle area before portal click
- Extended verify timeout to max(5s) for temple marker detection
A5 waypoint (src/town/a5.py):
- Two-tier WP scan: 0.45 threshold first, then 0.55
- Progressive stash thresholds (0.60 -> 0.50 -> 0.40) with direct click fallback
NPC detection (src/npc_manager.py):
- Close waypoint panel before NPC search (prevents WP UI blocking template match)
Diablo battle (src/char/paladin/hammerdin.py):
- Extended spawn wait from 15s to 20s
- Mid-fight target re-verification and repositioning
- Extra redemption burst to ensure kill
Auto-fixer (test/auto/auto_fixer.py):
- Updated to detect and verify all applied fixes
CI (.github/workflows/ci.yml):
- Added log analyzer tests to CI pipeline
- Excluded self-healing orchestrator (requires live D2R) and broken tests
New modules:
- test/auto/log_analyzer.py: parses bot logs and event JSONL files,
categorizes failures (approach, maintenance, battle, chicken, timeout,
crash, OCR) into structured BotFailure objects
- test/auto/auto_fixer.py: maps failure patterns to targeted code fixes,
checks if fixes are already applied, reports what needs work
- test/auto/test_self_healing.py: orchestrator that launches the bot,
monitors for failures in real-time, analyzes logs, applies fixes,
and retries up to 3 rounds
Supporting changes:
- test/auto/test_log_analyzer.py: 8 tests against historical run data
- pytest.ini: added repo root to pythonpath for test package imports
- test/__init__.py: new, enables test/ as importable package
All 95 existing tests pass. 8 new auto-test tests pass.
GetAsyncKeyState only detects keys when the bot process has focus.
When D2R is focused, F11/F12/End hotkeys were silently ignored.
Replaced polling loop with SetWindowsHookEx WH_KEYBOARD_LL which
intercepts all keystrokes globally before they reach any app.
Keeps polling as fallback if hook installation fails.
Also:
- Removed if-gate on enforce_d2r_window in game_controller.start()
- Added try/except pywintypes.error around all SetWindowPos calls
- Added pywintypes import to misc.py
- Added stop_hotkeys() cleanup in on_exit
enforce_d2r_window() now returns False when D2R is elevated, but the
if-gate caused all subsequent setup (window position, health manager,
death manager, game thread) to be skipped. Removed the if-gate so
startup continues regardless of window move result.
Wraps SetWindowPos in try/except pywintypes.error in move_d2r_window,
set_d2r_always_on_top, and restore_d2r_window_visibility. When D2R runs
as Administrator, Windows denies the call. Bot now continues gracefully
instead of crashing.
Wrapping SetWindowPos in try/except pywintypes.error in both
set_d2r_always_on_top() and restore_d2r_window_visibility(). When
D2R runs as Administrator and the bot runs as standard user,
Windows denies the call, causing a crash. Now it logs a debug
message and continues gracefully.
After Pindle fast-save/exit, the character spawns near the Nihlathak
portal area. During maintenance, identify() often fails and resets
_curr_loc to A5_TOWN_START, causing the pather to navigate from the
wrong starting position. The WP stone is often already visible on
screen, but the bot wasted 45s trying node-based pathing first.
Add an immediate full-screen WP scan as step 0 before any pathing.
This finds the WP directly when it's visible, bypassing stale
location tracking entirely.
Every fatal message told the user THAT something failed but not what to do
about it. The worst was "conda env create failed. See output above." --
useless when run_install_capture.bat redirects that output to a 56 KB log.
Each error now names the likely cause and the concrete next step:
- download failed -> the URL tried, firewall/proxy hint, manual-install
fallback that install.bat will detect on re-run
- truncated download -> got N bytes vs expected ~78 MB, bad file deleted
- installer failed -> antivirus/UAC hint, how to run it by hand
- conda found but dead -> the exact command to reproduce the real error
- env create failed -> disk/network/antivirus causes, plus the
"env remove -n botty -y" recovery for a half
finished install
- pip install failed -> notes the env itself is fine and a re-run resumes
- python.exe missing -> explains partial env, gives the recovery commands
- find_python.bat -> distinguishes "never installed" from "install.bat
did not finish", pointing at the capture log
Added a shared ":fail" exit so every fatal path ends with how to produce a
full log for a bug report, and states that nothing else was changed.
Also added a disk-space pre-flight before env creation: under 3 GB now
fails immediately with a clear message instead of letting conda die halfway
through with an opaque error; 3-6 GB warns. The environment needs ~4 GB
plus ~1 GB of downloads.
Verified: install.bat still completes with exit 0; the pre-flight was
exercised at real, simulated-2 GB and simulated-5 GB levels and all three
branches render and exit correctly; find_python.bat still resolves; 140
tests pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
install.bat had 9 pause statements on failure paths and none on the
success path. The README tells users to double-click install.bat, so on a
successful install the console vanished the moment it finished -- a new
user never saw "Installation complete", the OCR verdict, or the dependency
verification, and had no way to tell whether it had worked.
Added pause to the success path.
run_install_capture.bat redirects stdout to install_log.txt, so that new
pause would have blocked behind the redirect: an empty window silently
waiting on a keypress the user cannot see. It now feeds stdin from nul,
reports success/failure with the log path, and pauses itself.
Verified non-interactive: run_install_capture.bat completes in ~28s with
exit 0 and no hang.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
test_setup_bat_files.py asserted that run_asset_extractor.bat and
run_quest_debug.bat exist in the repo root. Those are developer tools that
the end-user `stable` branch deliberately strips, so a fresh clone of
stable shipped 4 failing tests even though the bot was fine.
Split the list into CORE_BATS (install/find_python/run_botty -- required on
every branch) and OPTIONAL_BATS (dev tooling -- validated only when
present). The username, absolute-path and find_python checks now iterate
over the files that actually exist rather than a hardcoded list.
Found by cloning stable from GitHub onto a clean machine and running the
suite as a new user would.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Includes the pefile import-chain technique that found it, since WinError
126 names the importing DLL and never the missing dependency.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
tesserocr never loaded -- install.bat always reported "tesserocr: not
available (DLL issue)" and the bot ran on the pytesseract fallback, which
shells out to tesseract.exe per OCR call instead of using the in-process
C++ API.
Root cause, found by walking the import table with pefile:
tesserocr.pyd -> tesseract52.dll -> leptonica-1.78.0.dll -> tiff.dll
-> libdeflate.dll <- MISSING
Current conda-forge libdeflate (>=1.20) installs the library as
"deflate.dll", but the older tiff.dll from the tesseract=4.x stack still
imports the previous name "libdeflate.dll". Nothing provided that name, so
tiff.dll failed to load and every DLL above it failed with WinError 126
("The specified module could not be found") -- which is why the error
looked like a missing module even though every file was present.
Fix: install libdeflate explicitly alongside tesseract=4.*, then copy
deflate.dll to the legacy name libdeflate.dll when that name is absent.
Same library, same exports.
Verified: removing the alias reproduces the failure exactly; running
install.bat recreates it and the installer now reports "tesserocr: OK
(fast path)". tesserocr initialises and performs real OCR with both bundled
models, and the bot logs "OCR backend: tesserocr (primary)" at startup.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
run_install_capture.bat writes install_log.txt into the repo root. It was
untracked but not ignored, so it showed up as noise in git status and was
easy to commit by accident.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Both were found by running install.bat under simulated clean-machine
conditions (conda removed, winget stripped from PATH, Tesseract hidden).
Bug 20: an unescaped ")" in an echo inside a parenthesised block aborted
the script at parse time, killing the conda direct-download path -- the
only path available without winget.
Bug 21: winget defaulted to machine scope, so the installer needed admin
and failed silently on a normal double-click.
Also documents the two recurring batch pitfalls with an awk audit command,
and the measured limitation that Tesseract has no per-user install path.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Testing the direct-download fallback with Tesseract absent and winget
unavailable showed the official installer self-elevates and its elevated
relaunch discards /D=, so it always installs machine-wide to
"C:\Program Files\Tesseract-OCR" regardless of TS_DEST.
The previous comment claimed this path gave a per-user install needing no
admin rights, which is not true: there is no per-user install path with
the official Tesseract installer, and it requires admin/UAC. Corrected the
comment rather than the code -- /D= is harmless as best-effort, and both
find_tesseract and src\d2r_image\ocr.py already search the machine-wide
and per-user locations, so either outcome works at runtime.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The Miniforge direct-download fallback -- the only path available on a
clean machine without winget -- could never complete. install.bat aborted
with ". was unexpected at this time." immediately after running the
Miniforge installer, so conda was installed but the botty env was never
created and the bot was unusable.
Cause: line 133 echoed "(exit code %errorlevel%)" inside a parenthesised
if-block. An unescaped ")" inside a block terminates the block, leaving
"." as a stray token. cmd parses the entire if-block when it reaches it,
so this fired even when the installer SUCCEEDED and the block body was
never meant to run -- verified with a minimal repro: the unescaped form
exits 255 on a false condition, the escaped form exits 0.
Fix: escape as ^(exit code %errorlevel%^), matching the convention the
rest of the file already uses ("^(fast path^)"). Audited every echo
inside a block; this was the only remaining unescaped instance.
Found by running install.bat with winget removed from PATH to simulate a
clean Windows 10 machine.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Live clean-install test caught a syntax bug in the new Tesseract setup
block: install.bat died with "so was unexpected at this time." right
after "Setting up OCR...", so OCR setup and the whole dependency
verification stage never ran.
Cause: "::" comment lines placed INSIDE parenthesised if-blocks. Two
problems compound there -- a "::" line inside a ( ) block is itself a
parse error, and any parenthesis in the comment text closes the block
early. The text "(non-zero when already installed), so after each" left
"so" as a stray token.
Fix: move every comment out of the parenthesised blocks, in both the
Tesseract block and the conda winget block added earlier. The conda one
had survived only because its text happened to contain no parentheses.
Verified: install.bat now runs to completion with exit 0 --
Tesseract: C:\Program Files\Tesseract-OCR\tesseract.exe
pytesseract: OK (tesserocr: DLL issue, expected)
cv2/mss/numpy/transitions/rapidfuzz/pydantic/pytesseract/yaml/discord: OK
All dependencies verified.
140 tests pass against the freshly created env.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Follow-up to the conda scope fix: the same admin/winget assumptions broke
the OCR backend, which is what actually carries item text reading since
tesserocr's MSVC DLL chain commonly fails to load.
- install.bat installed Tesseract via `winget install` with no --scope,
i.e. machine-wide into "C:\Program Files", which requires admin. On a
clean non-admin box this failed and left NO working OCR backend at all
(tesserocr already fails), so OCR_READY=0 and item reading was dead.
Now: winget machine scope -> winget --scope user -> direct download of
the official NSIS installer with a per-user /D= target. Also stops
trusting winget's exit code (non-zero when already installed) and
re-resolves tesseract.exe after each attempt.
- The downloaded installer is size-checked (~50 MB; <20 MB = failed
download) before being executed, matching the Miniforge handling.
- Added a :find_tesseract subroutine that resolves tesseract.exe from
Program Files, Program Files (x86), %LOCALAPPDATA%\Programs,
%ProgramData% and PATH. Verification now uses the resolved path instead
of the hardcoded "C:\Program Files" one.
- ocr.py: added Program Files (x86) and the per-user
%LOCALAPPDATA%\Programs\Tesseract-OCR location to the runtime search
order, since per-user installs are not on PATH.
- run_botty.bat: only export PYTESSERACT_TESSERACT_CMD when the file
exists, falling back to the per-user path, so a stale machine-wide
value cannot shadow a valid per-user install.
Verified on this machine: all install.bat dependency imports OK
(cv2/mss/numpy/transitions/rapidfuzz/pydantic/pytesseract/yaml/discord),
pytesseract resolves tesseract 5.5.0, osdetect reports the win11 profile,
config loads, 140 tests pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
install.bat could fail to auto-install conda on a fresh, non-admin
machine:
- winget install used the default (machine) scope, landing conda in
%ProgramData% and requiring elevation. A normal double-click without
admin failed silently and conda never installed. Add --scope user so
it installs to %USERPROFILE%\miniforge3 with no admin needed.
- winget returns non-zero when the package is already present, so its
exit code was unreliable. Rescan for conda.exe after winget and only
fall through to the direct download when it is genuinely missing.
- The GitHub (git) download fallback never validated the file before
running it: a truncated download or an HTML error page served with a
200 would be launched as the "installer" and silently do nothing. Add
a size check (<40 MB => failed download, clear error + bail) plus a
pre-download cleanup of any stale temp file.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
rapidfuzz 3.x moved levenshtein out of rapidfuzz.string_metric (removed)
into rapidfuzz.distance.Levenshtein, but Levenshtein is now a module, not
a function. The fallback alias pointed at the module — fix it to bind the
.distance method directly.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
environment-win11.yml installs only from requirements.txt, which was missing
pyinstaller. build.py constructs the full path to pyinstaller.exe so the
install is sufficient; no PATH change needed.
Verified locally: 99 passed, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
environment-win11.yml pulls only requirements.txt (not environment.yml), so
coverage, pytest, pytest-env, pytest-mock, and pytest-pythonpath were missing
from the CI conda env. Pinned to versions matching the local botty env.
Verified locally: 99 passed, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
conda activate does not add Scripts/ to PowerShell PATH in the GitHub Actions
runner. Switching to 'python -m coverage' works regardless of PATH state.
Verified locally: 99 passed, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
CI was failing at conda env setup: pip could not find async-timeout==5.1.0.
Latest available version is 5.0.1.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
time.time() on Windows has ~15 ms resolution; a 0.0-second budget produced a
deadline equal to the current tick, so the anchor-loop check never fired and
traverse_calls reached 5 instead of 1. Using -1.0 puts the deadline one second
in the past — guaranteed expired on any hardware.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
A "999" gem count (OCR couldn't read the digit, code assumes "convert
until depleted") relied on the loop noticing an empty stack and
breaking — but that check only ran when _gems_stack_monitor_for
returned None. For any registered gem type it always returns a static
screen coordinate, so the depletion check was dead code: the loop
just kept blindly clicking the same fixed position forever.
Observed in the wild: stuck on Topaz Flawless for 30+ minutes and 177
iterations (of a fake "999" target, ~2.7h worst case) before being
manually force-exited, repeatedly clicking fixed convert-panel/GEMS
coordinates with nothing real there — the likely cause of it also
grabbing and re-placing unrelated stash items during that time.
Now always does a live template search before clicking, breaking
immediately once the stack is genuinely gone, on every gem type.
Verified end-to-end with a mocked run: a fake depleted "999" stack now
stops instantly instead of looping, and a real gem right after it
still converts correctly.