docs: operating handover — run, diagnose, and what is still outstanding
Written so the bot can be run without me: control commands and their gotchas, how to tell a normal break from a stuck bot, the break-length multiplier, the health-check greps, the temporary settings to revert, and the two things only the user can do (F7 rebind, fire resist). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
171
HANDOVER.md
Normal file
171
HANDOVER.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Handover — 2026-08-29
|
||||
|
||||
Everything you need to run this yourself. Written after a long debugging session;
|
||||
`CLAUDE.md` has the deep detail, this is the operating manual.
|
||||
|
||||
---
|
||||
|
||||
## Running the bot
|
||||
|
||||
```bash
|
||||
run_botty.bat # starts the process (idle)
|
||||
python scripts/hermes_bot_control.py start # begins playing
|
||||
python scripts/hermes_bot_control.py status # running=X paused=Y
|
||||
python scripts/hermes_bot_control.py stop # exits the process
|
||||
```
|
||||
|
||||
**`start` is idempotent** (fixed 2026-08-28) — pressing it twice is safe. `pause`
|
||||
and `toggle` are the toggle. Before the fix, a repeated `start` paused the bot
|
||||
and `status` still said `running=True`; that cost ~5 hours once.
|
||||
|
||||
**Always verify with `status`, not the "OK: command sent" reply.** And check the
|
||||
log actually moves — `=== BOT START ===` is the proof it began a game.
|
||||
|
||||
### Restarting after a code or config change
|
||||
|
||||
A running bot does **not** pick up edits. Python loads modules at process start.
|
||||
|
||||
```bash
|
||||
python scripts/hermes_bot_control.py stop
|
||||
# wait until nothing is listening:
|
||||
netstat -ano | grep 18899
|
||||
run_botty.bat
|
||||
python scripts/hermes_bot_control.py start
|
||||
```
|
||||
|
||||
If `stop` times out, retry it — the socket occasionally needs two attempts. Only
|
||||
force-kill as a last resort: killing mid-game leaves D2R in a state the bot
|
||||
cannot re-enter, and you then have to save+exit to the main menu by hand.
|
||||
|
||||
**Only ever run one instance.** Two both bind the control socket and fight over
|
||||
start/pause, and the logs become nonsense. Check with `tasklist | grep -i python`.
|
||||
|
||||
---
|
||||
|
||||
## Is it stuck, or just idling?
|
||||
|
||||
The bot sits at the D2R **character-select menu** during a normal break. The
|
||||
stuck case looks identical. Do not judge by the screen.
|
||||
|
||||
```bash
|
||||
LAST=$(grep -n "control socket listening" log/log.txt | tail -1 | cut -d: -f1)
|
||||
tail -n +$LAST log/log.txt | grep -cE "select_char|Restarting bot|Uncaught exception"
|
||||
```
|
||||
|
||||
| | Normal break | Stuck |
|
||||
|---|---|---|
|
||||
| `status` | `running=True paused=True` | the same |
|
||||
| `select_char` errors | none | present |
|
||||
| `Restarting bot` | none | every ~20s |
|
||||
| Log | quiet | new process repeatedly |
|
||||
|
||||
**The tell is the log filling with restart lines, not the menu.**
|
||||
|
||||
### Break lengths are longer than they look
|
||||
|
||||
`maybe_afk_break` calls `wait(m, m*1.5)` and `wait()` applies its own jitter (up
|
||||
to 1.44x). They compound:
|
||||
|
||||
| planned | actual |
|
||||
|---|---|
|
||||
| 3.9m | 7.1m |
|
||||
| 11.9m | 19.5m |
|
||||
| 20:56 | 25.5m |
|
||||
|
||||
So **multiply any break setting by 1.5 x 1.44** before deciding it is safe. A
|
||||
~25 minute idle is what left D2R unable to re-enter once. `afk_break_max_m` is
|
||||
capped at 7 for this reason (=> ~15m worst case).
|
||||
|
||||
---
|
||||
|
||||
## Health check
|
||||
|
||||
```bash
|
||||
LAST=$(grep -n "control socket listening" log/log.txt | tail -1 | cut -d: -f1)
|
||||
tail -n +$LAST log/log.txt > /tmp/c.log
|
||||
echo "games $(grep -c 'game | start' /tmp/c.log) | failed $(grep -c 'game | end .*fail' /tmp/c.log) | deaths $(grep -c 'You have died' /tmp/c.log) | crashes $(grep -c 'Uncaught exception' /tmp/c.log)"
|
||||
```
|
||||
|
||||
Useful greps:
|
||||
|
||||
| what | grep |
|
||||
|---|---|
|
||||
| Step-by-step timeline | `grep "TL>" log/log.txt` |
|
||||
| Failure records | `grep "FAIL>" log/log.txt` |
|
||||
| Stealth manifest at startup | `grep "STEALTH>" log/log.txt` |
|
||||
| Mana threshold crossings | `grep "MANA>" log/log.txt` |
|
||||
| Level / exp | `ls -t log/stats/mini_stats_*.json \| head -1` |
|
||||
|
||||
**`FAIL>` gives the whole story of a failed game** — reason, location, the three
|
||||
slowest steps, and a breadcrumb trail with `!` marking failures. Read the trail,
|
||||
not just the reason: the step that blew up is often not the one that caused it.
|
||||
|
||||
---
|
||||
|
||||
## Temporary settings to revert
|
||||
|
||||
| file | setting | now | should be |
|
||||
|---|---|---|---|
|
||||
| `config/params.ini` | `session_budget_h` | **20** | 8 |
|
||||
| `config/params.ini` | `difficulty` | hell | your call |
|
||||
|
||||
`session_budget_h = 20` was raised for a levelling push. It rolls to 13-27h,
|
||||
which largely disables the stop-for-the-day behaviour that the session-rhythm
|
||||
work exists to provide. **Put it back to 8 once you have the levels.** It is
|
||||
uncommitted, so `git checkout config/params.ini` reverts it.
|
||||
|
||||
---
|
||||
|
||||
## Outstanding
|
||||
|
||||
**PR #39 is open and NOT merged, and the fix is NOT live.**
|
||||
It makes the pather abort actually fire. The guard has now looked correct and
|
||||
done nothing twice — most recently `aborts 0, guesses 8` across 152 games. Merge
|
||||
it, then **restart** to load it, then confirm with:
|
||||
|
||||
```bash
|
||||
grep -c "aborting traverse" log/log.txt # should become non-zero
|
||||
grep -c "taking a random guess" log/log.txt # should stop growing
|
||||
```
|
||||
|
||||
**Two things only you can do:**
|
||||
|
||||
1. **Rebind F7 → Concentration** in game (hover it in the bind grid, press F7).
|
||||
Concentration is currently unbound; it is the party aura that buffs your
|
||||
merc. Then set `concentration=f7` in the profile.
|
||||
2. **Fire resist** — the chicken rate (~7%) is a gear number, not a config one.
|
||||
`chicken=0.40` is doing its job; lowering it trades chickens for deaths.
|
||||
|
||||
**Known-stale:** the `CONVICTION` preflight template scores 45.9% while the bind
|
||||
is provably correct, so every startup logs a false alarm. Cosmetic.
|
||||
|
||||
---
|
||||
|
||||
## Checking your binds after any gear change
|
||||
|
||||
```bash
|
||||
python tools/testbed.py spellbook --assets
|
||||
```
|
||||
|
||||
Hovers the whole bind grid, reads each skill from its tooltip and each hotkey
|
||||
from the icon corner, and prints which config keys disagree with the game. Exits
|
||||
1 on a mismatch. This exists because an Enigma put Teleport on F5 and displaced
|
||||
Conviction, and `conviction=f5` would have teleported the character mid-fight.
|
||||
|
||||
---
|
||||
|
||||
## The one habit worth keeping
|
||||
|
||||
Most of what went wrong here was **configured behaviour that never executed**,
|
||||
and nothing reported it. AFK breaks were 0-for-225 at a configured 5%. The
|
||||
manifest said `wired` throughout.
|
||||
|
||||
When something should be happening and you are not sure it is, **count it**:
|
||||
|
||||
```bash
|
||||
grep -c "<the thing>" log/log.txt
|
||||
```
|
||||
|
||||
A zero where you expected a number is the most informative result in this
|
||||
project. It found the AFK break, the loot-filter clicks, the Chronicle panel,
|
||||
and the pather abort — twice.
|
||||
Reference in New Issue
Block a user