The webhook scrub rewrote the 18 stable-only commits, so the hashes quoted in the
post-mortem no longer resolved. Updated, and the rewrite itself is noted inline so the
change of hashes is not a mystery later.
Co-Authored-By: Claude Opus 5 <[email protected]>
main carried 19 commits of separate feature work (baal_xp, cold_plains, melee_hunt,
launch/tools) while stable carried the run_pindle repair. One real conflict, in
_run_wrapper, where both branches added a line at the same point:
stable self.tl("run", "approach", "start") <- run timeline
origin/main self._current_run = run_obj <- baal_xp run tracking
Independent, so both are kept. config/params.ini and src/config.py auto-merged.
Co-Authored-By: Claude Opus 5 <[email protected]>
Narrative record of the incident: failure rate 100% -> ~8%, character 32 -> 69, cycle time
185-250s -> ~52s across 17 commits. CLAUDE.md already carries the per-bug detail as Bugs
23-30; this is the part that does not fit a bug entry — the order things happened in, why
the silent failures cost far more than the loud ones, and the four times my own obvious
answer was wrong.
Two files:
docs/postmortem_pindle_2026-08-27.md canonical, diffable
docs/postmortems/pindle-2026-08-27.html source of the published artifact, kept in-repo
so it survives the scratchpad and stays editable
The corrections section is the reason this is worth committing rather than leaving as a
list of fixes. Each wrong turn looked correct in isolation:
- the red-portal guard: the portal renders on BOTH sides, so "portal visible => still in
town" blocked every genuine entry
- XP as proof of a boss kill: it only proves something died (minions, merc kills)
- "the template is degenerate": the Qual-Kehk asset scored 1.000 against a frame where
the tag actually renders; the THRESHOLD was wrong
- selling the charms: 673 blocked-sell lines were the guard working, not a clog, and
disabling it vendored five resistance charms
Also records the durable lesson: every one of these cost hours because the log held events
rather than structure. That is what the TL>/FAIL>/digest instrumentation exists to fix.
Co-Authored-By: Claude Opus 5 <[email protected]>
D2R UI hitboxes are tight; a 1px offset on the Join Game tab click was
enough to miss the button. Final SetCursorPos pass after the existing
verification check.
- 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
Adds Messenger.send_error across the Discord (red embed with screenshot attached)
and generic (text-only) APIs. Bot._save_error_screenshot now also pushes the
failure to the configured messenger after saving the screenshot to disk, so each
approach/battle/exception failure is reviewable in Discord with the visual.
Gated by new config discord_log_errors ([general], default 1) and the
[discord_events] error toggle. Both optional/backward-compatible. Verified wiring
and the suppression path via a stubbed messenger.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
When every run has been auto-disabled there are no routes left to run, so save a
session report and shut down via safe_exit() instead of calling restart_or_exit
(which would needlessly restart D2R into empty games when restart_d2r_when_stuck
is enabled).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Recovery: each run now has its own consecutive-failure counter. After
disable_run_after_failures (default 5) consecutive failures, that single run is
disabled for the rest of the session and the bot keeps doing the other runs
instead of stopping. A success resets the counter. If every run is disabled the
bot stops for investigation. Disable is in-memory only (restart re-enables).
Diagnostics: on every run failure (approach, battle, or exception) the bot saves
a timestamped screenshot to log/screenshots/error/ named with the run, reason,
and game/run counters so logs and visuals can be cross-referenced. Gated by the
new error_screenshots config (falls back to info_screenshots). The error/ dir is
routed through log rotation.
Adds config keys error_screenshots and disable_run_after_failures (both optional,
backward compatible) and docs/recovery_and_error_logging.md. Verified bot startup,
config parsing, Bot construction, the disable/reset logic, and screenshot writing.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
- i_char.py: rewrite _pre_buff_cta() to verify each weapon switch via BC
skill-bar template; detects wrong slot on game start (leftover from
interrupted buff cycle), retries failed switches once, logs clearly
when stuck on CTA slot to prevent dying with wrong weapon in combat
- personal.py: protect needed consumables from sell/drop in inspect_items;
check get_needs() before marking a pot for discard — if the belt needs
that pot type, skip it so fill_up_belt_from_inventory can restock later
- bot.py: add fill_up_belt_from_inventory + update_pot_needs after
buy_consumables so inventory pots reach the belt even when out of gold;
add town-heal loop at start of on_maintenance to drink health/rejuv pots
until HP >= 95% before the next run (health manager is paused in town)
- game_stats.py: add rolling 10-game pickup health check; warns in log and
sends Discord alert when zero item pickups occur across 10 games while
chickens or merc deaths are present
- params.ini: fix show_belt=n -> show_belt=k (belt key was wrong, causing
1.5s wasted recovery attempt every first game in a session)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Updated bot behavior to end the game when no TP charges are left, instead of attempting to walk back to town.
- Refined item processing logic in `processing_helpers.py` to use identity checks for item comparisons and improved item removal methods.
- Enhanced game stats logging to include failure reasons and send Discord notifications for failed games.
- Improved health management logic to trigger chicken behavior based on health percentage after rapid rejuvination.
- Added error handling for item pickup failures in `pickit.py` to blacklist items that cannot be picked up.
- Introduced walking fallback nodes for Countess, Andariel, Mephisto, and Baal runs in `pather.py`.
- Implemented path data validation in run scripts for Countess, Andariel, Mephisto, and Baal to ensure paths are correctly defined.
- Enhanced item identification and stash processes in `diablo.py` with retry logic for failures.
- Updated town management to handle identification failures gracefully and log appropriate warnings.
- Improved character selection logic to provide clearer logging messages.
- Modified game restart logic to launch the game via PowerShell for better session handling.
- Added documentation for recording paths for the Countess run and created a PowerShell script for scheduled task management.