Metadata-Version: 2.4
Name: nhemo
Version: 0.1.0
Summary: NetHack Expert Machine Operator — automated NetHack player
Author-email: "Javier Novoa C." <jstitch@gmail.com>
License: GPL-3.0-or-later
Project-URL: Homepage, https://gitlab.com/jstitch/nhemo-nethack-expert-machine-operator
Project-URL: Repository, https://gitlab.com/jstitch/nhemo-nethack-expert-machine-operator
Project-URL: Bug Tracker, https://gitlab.com/jstitch/nhemo-nethack-expert-machine-operator/-/issues
Keywords: nethack,roguelike,bot,automation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Games/Entertainment
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: pexpect>=4.9
Requires-Dist: pyte>=0.8
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.9; extra == "dev"
Requires-Dist: ty>=0.0.29; extra == "dev"
Dynamic: license-file

# NHEMO — NetHack Expert Machine Operator

An automated [NetHack](https://www.nethack.org/) 3.6.x player that combines a
rule-based behavior tree with optional LLM consultation to play the game from
screen-reading to strategic decision-making.

**MVP goal**: a bot that consistently reaches [Minetown](https://nethackwiki.com/wiki/Minetown)
as a dwarven Valkyrie.

---

## What is NHEMO?

NetHack is a notoriously complex roguelike — decades of players have failed to
ascend, and the game is famously hostile to automation. NHEMO approaches the
problem in layers:

1. **Screen reader** — connects to a live NetHack process via a PTY and parses
   the terminal output using a virtual terminal emulator ([pyte](https://github.com/selectel/pyte)).
2. **Parser** — translates the raw screen into structured game state: map tiles,
   player vitals, messages, menus, dungeon memory.
3. **Decision engine** — a priority-ordered behavior tree (survival > tactical >
   strategic > explore) chooses an action each turn.
4. **LLM layer** (future) — consults an LLM for novel situations the rule base
   doesn't cover.

The bot never sees raw NetHack internals. It reads only what a human player
would see on a terminal — making it work with any standard NetHack installation.

---

## Current Status

NHEMO is under active development. Completed slices:

| Slice | Description | Status |
|-------|-------------|--------|
| V1 | Spawn NetHack via PTY, read screen, send keystrokes | done |
| V2 | Parse map, find player `@`, move without walking into walls | done |
| V3 | Parse status bars and messages; detect hunger and low HP; eat and heal | done |
| V4 | A\* pathfinding, room-first exploration, doors, boulders, stairs, multi-level descent | done |
| V5 | Combat: detect monsters, melee, flee, pick up items | planned |
| V6 | SQLite persistence: dungeon memory, stash locations, session resume | planned |
| V7 | Knowledge base: auto-generated from NetHack source + hand-authored overlays | planned |
| V8 | LLM integration: abstract provider, context assembly, Claude + OpenAI backends | planned |
| V9 | Full behavior tree + goal system + main game loop | planned |
| V10 | MVP integration: consistent Minetown arrival as dwarven Valkyrie | planned |

See [`docs/TASKS.md`](docs/TASKS.md) for the granular task list with checkbox progress.

---

## Requirements

- Python 3.12+
- [uv](https://docs.astral.sh/uv/) (package manager)
- NetHack 3.6.x installed — the real binary, not a wrapper script.
  On many distros: `/usr/lib/nethack/nethack`

> **Note**: NHEMO uses the setgid NetHack binary directly. Do **not** point it
> at shell wrappers like `/usr/bin/nethack` — those override `NETHACKOPTIONS`
> and break the custom configuration.

**For development / contributing** (additional):

- [just](https://github.com/casey/just) (command runner) — `cargo install just`
  or via your package manager (`apt install just`, `brew install just`, etc.)

---

## Installation

```bash
git clone https://gitlab.com/your-username/nhemo.git
cd nhemo
uv sync
```

### Development setup

```bash
# Install dev dependencies (pytest, ruff, ty)
uv sync --extra dev

# Install the pre-commit lint hook (run once after cloning)
just install-hooks

# Verify your setup — lists all available dev tasks
just
```

The pre-commit hook runs `just lint` (ruff + ty) automatically before every
commit. If it fails, run `just lint-fix` to auto-fix and retry.

---

## Quick Start

```bash
# 30-turn smoke test (default)
uv run python -m nhemo

# Watch the bot play live
uv run python -m nhemo --turns 500 --watch

# Slower watch mode, easier to follow
uv run python -m nhemo --turns 500 --watch --delay 0.5

# Verbose per-turn logging to stderr (safe to combine with --watch)
uv run python -m nhemo --turns 200 --watch --log-level DEBUG

# Capture log to file
uv run python -m nhemo --turns 500 --log-level DEBUG 2>nhemo_debug.log
```

### Configuration

Copy and edit `config.yaml` to change the NetHack binary path, character
selection, or LLM settings:

```yaml
nethack:
  binary_path: "/usr/lib/nethack/nethack"   # adjust if needed

player:
  role: "Valkyrie"
  race: "Dwarf"
  alignment: "Neutral"

llm:
  mode: "offline"    # offline | complex_only | full
```

A recommended `.nethackrc` (parsing-friendly options, vi-key movement) is
included and loaded automatically.

---

## Observability

### Logging

```bash
uv run python -m nhemo --turns 200 --log-level DEBUG
```

| Level | What you see |
|-------|-------------|
| `WARNING` (default) | Parser failures (player/map not found), bot stuck and giving up on a position |
| `INFO` | Level transitions, doors opened/kicked, stairs descended, food eaten, monsters killed |
| `DEBUG` | Every turn: position, visited tile count, stuck state, chosen action and phase |

### Screen recording

Record a session and replay it against a modified parser to catch regressions
without running a live game:

```bash
# Record
uv run python -m nhemo --turns 200 --record /tmp/game.nhrec

# Replay through the current parser
uv run python tools/screen_recorder.py replay /tmp/game.nhrec

# Inspect a specific turn's raw screen
uv run python tools/screen_recorder.py show /tmp/game.nhrec --turn 42

# Diff two recordings
uv run python tools/screen_recorder.py diff /tmp/before.nhrec /tmp/after.nhrec
```

### Decision event log

```bash
uv run python -m nhemo --turns 200 --log-events /tmp/events.jsonl
```

Each turn emits one JSON line capturing what the bot decided and why:

```json
{"turn":42,"screen_mode":"NORMAL","action":"MOVE_E","phase":"pathfind","pos":[12,40],"hp":15,"max_hp":19,"dlvl":"Dlvl:3"}
```

All flags can be combined freely — they write to independent streams:

```bash
uv run python -m nhemo --turns 500 --watch --log-events /tmp/events.jsonl --log-level DEBUG 2>nhemo_debug.log
```

---

### Watch mode display

Running with `--watch` renders the NetHack screen live, followed by NHEMO's own
status line and a persistent message log:

```
This door is locked.                                    ← row 0: game message
                                                        ← rows 1-21: dungeon map
             --------
             |....#.|
             |......|
             |......|
             |......|
             ----.---          +
                ##             @
                #             ##
             ...
    -------.--------    #----.-
    |..............|    #|....|
    |......$........#####|"....###          ----------
    |..............|    #.....|#############..$$.....|
    |..............|`####------   #####     |........|
    ----------------                  ######|.<......|
                                            ----------
                                                       ← row 21: blank separator
NHEMO the Stripling  St:16 Dx:14 Co:17 In:8 Wi:12 Ch:8 Lawful  ← status bar 1
Dlvl:1 $:0 HP:18(18) Pw:1(1) AC:6 Xp:1/0 T:118        ← status bar 2
 NHEMO turn 321  action: ---                            ← NHEMO status line
── message log ──────────────────────────────────────   ← last 8 messages
  t 314: This door is locked.
  t 315: This door is locked.
  t 316: This door is locked.
  t 317: This door is locked.
  t 318: This door is locked.
  t 319: This door is locked.
  t 320: This door is locked.
> t 321: This door is locked.                          ← most recent (bold >)
```

**NetHack screen (rows 0–23)**

| Area | What it shows |
|------|---------------|
| Row 0 | Game message — combat results, door status, hunger warnings, etc. |
| Rows 1–21 | Dungeon map — `@` is the player, `+` closed door, `#` corridor, `.` floor, `$` gold, `<`/`>` stairs, `f` monster, etc. |
| Row 22 | Status bar 1 — name, title, attribute scores, alignment |
| Row 23 | Status bar 2 — depth (`Dlvl:`), gold (`$`), HP, power (`Pw`), armour class (`AC`), experience (`Xp`), game turn (`T:`) |

**NHEMO status line**

- `NHEMO turn N` — NHEMO's loop iteration counter (see below).
- `action:` — the last action chosen: an `Action` enum name (`MOVE_E`,
  `WAIT`, `MOVE_DOWN`, …) or `---` when a `CommandSequence` ran (open/kick
  door, eat item) rather than a single keystroke.

**Message log**

The last 8 game messages with their NHEMO turn number. The most recent line is
prefixed with `>`. Messages are deduplicated — if the same text appears on
consecutive turns it is only appended once.

#### NHEMO turn vs. game turn (`T:`)

The `NHEMO turn` counter and NetHack's `T:` counter are **not the same**.

`T:` advances once per in-game timed action. NHEMO's turn counter advances once
per main-loop iteration, including iterations that send a free (zero-time) key
or no key at all:

| Situation | NHEMO turn +1? | T: +1? |
|-----------|:--------------:|:------:|
| Normal movement or combat | yes | yes |
| Waiting in place (`WAIT`) | yes | yes |
| Opening/kicking a door | yes | yes |
| Dismissing `--More--` (space) | yes | **no** |
| Dismissing a menu or overlay | yes | **no** |
| Replying to character-creation prompts (`y`) | yes | **no** |
| `LOOK` after a kill (`:` command) | yes | **no** |

So NHEMO turns always run ahead of `T:`, and the gap grows whenever there is
heavy `--More--` output (long combat logs, intro text) or many kills (each
triggers a free `LOOK` to detect dropped items). This is expected and correct.

---

## Running Tests

```bash
uv run pytest                        # unit tests only
uv run pytest -m integration         # requires a live NetHack binary
uv run pytest --cov=src/nhemo        # with coverage
```

---

## Architecture (for collaborators)

### Component overview

```
┌─────────────────────────────────────────────────────────┐
│                        __main__.py                      │
│                    (game loop / CLI)                    │
└────────────────────────────┬────────────────────────────┘
                             │
          ┌──────────────────┼──────────────────┐
          ▼                  ▼                  ▼
   ┌────────────┐   ┌──────────────┐   ┌──────────────┐
   │  Interface │   │   Parser     │   │  Decision    │
   │  (PTY/pyte)│   │  (screen →   │   │  (behavior   │
   │            │   │  game state) │   │   tree)      │
   └────────────┘   └──────────────┘   └──────────────┘
                             │                  │
                    ┌────────┴────────┐         │
                    ▼                 ▼         ▼
             ┌──────────┐   ┌──────────────┐  ┌────────┐
             │  State   │   │  Knowledge   │  │  LLM   │
             │(game/map/│   │  Base (YAML) │  │ layer  │
             │ dungeon) │   └──────────────┘  └────────┘
             └──────────┘
                    │
             ┌──────▼──────┐
             │ Persistence │
             │  (SQLite)   │
             └─────────────┘
```

### Key modules

| Package | Description |
|---------|-------------|
| `src/nhemo/interface/` | Abstract interface protocol + PTY backend (`pexpect` + `pyte`) |
| `src/nhemo/parser/` | Screen → structured state: map tiles, status bars, messages, menus |
| `src/nhemo/state/` | Game state model: player, dungeon memory, level tiles, inventory |
| `src/nhemo/decision/` | Behavior tree framework, node types, YAML loader (V9) |
| `src/nhemo/knowledge/` | YAML knowledge base loader and query interface (V7) |
| `src/nhemo/llm/` | Abstract LLM provider + concrete backends (V8) |
| `src/nhemo/persistence/` | SQLite schema and data access layer (V6) |
| `src/nhemo/recording.py` | Screen recording and event log infrastructure |

### Three-level action system

NHEMO's decision engine produces actions at three granularities:

- **Level 1 — GameAction**: a single keystroke (move north, wait, search)
- **Level 2 — CommandSequence**: multi-keystroke interaction with screen checks
  between steps (eat item, open door). Aborts on unexpected screen state.
- **Level 3 — TacticalPlan**: multi-command plan toward a mini-goal (e.g.,
  clear a room). Interruptible by survival priorities — plans suspend and resume.

### Data-driven behavior tree

The tree structure is defined in `data/default_bt.yaml` and loaded at startup
via a node registry (decorator-registered conditions and actions). You can
experiment with strategies without touching Python code.

Priority order: **Survival → Tactical → Strategic → Explore**

### Design documents

- [`docs/PLAN.md`](docs/PLAN.md) — architecture decisions, slice breakdown, post-MVP roadmap
- [`docs/SPEC.md`](docs/SPEC.md) — detailed design: class diagrams, APIs, schemas, behavior tree
- [`docs/TASKS.md`](docs/TASKS.md) — granular implementation tasks with completion status

---

## Roadmap

Beyond the V10 MVP (consistent Minetown arrival):

- **Phase 2 (V11–V17)**: Mines End, Sokoban, shop interaction, altar use,
  item identification, quest completion
- **Phase 3 (V18–V21)**: Gehennom, Vlad's Tower, Sanctum
- **Phase 4 (V22–V24)**: Ascension run through the elemental planes
- **Phase 5 (V25–V32)**: Telnet/SSH backend, wiki knowledge ingestion,
  ttyrec learning, multi-role support, BT A/B testing, LLM-guided tree evolution

---

## Contributing

Contributions are welcome. The project uses a structured implementation workflow
— each unit of work is a single task in `docs/TASKS.md` tied to a section of
`docs/SPEC.md`. Reading PLAN → SPEC → TASKS in that order gives you the full
picture before touching code.

A few conventions:
- Python 3.12, type hints on all public APIs
- One task → one commit, descriptive message
- Every task ships with its own tests (`uv run pytest`)
- No over-engineering: implement exactly what the task specifies

---

## License

Copyright (C) 2025 Javier Novoa C.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

See [`LICENSE`](LICENSE) for the full text.
