Metadata-Version: 2.4
Name: hvbattle
Version: 0.2.1
Summary: Reusable battle-domain APIs for HentaiVerse.
Project-URL: Homepage, https://github.com/Kuan-Lun/hvbattle
Project-URL: Tracker, https://github.com/Kuan-Lun/hvbattle/issues
Author: Kuan-Lun Wang
License-Expression: AGPL-3.0-or-later
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.14
Requires-Python: <3.15,>=3.14
Requires-Dist: hv-bie<0.8,>=0.7.0
Requires-Dist: hvbrowser<0.2,>=0.1.1
Requires-Dist: ponychart-classifier<0.10,>=0.9.1
Requires-Dist: zendriver<0.16,>=0.15.5
Provides-Extra: dev
Requires-Dist: black>=26.5.1; extra == 'dev'
Requires-Dist: mypy>=2.1.0; extra == 'dev'
Requires-Dist: pymarkdownlnt>=0.9.38; extra == 'dev'
Requires-Dist: pytest>=9.1.1; extra == 'dev'
Requires-Dist: ruff>=0.15.20; extra == 'dev'
Description-Content-Type: text/markdown

# HVBattle

HVBattle provides reusable HentaiVerse battle-domain APIs. It builds on
`hvbrowser` for the authenticated Hentaiverse browser session and keeps the
private command-line runner in a separate application workspace.

The package exposes a policy-neutral `BattleSession`, atomic battle actions,
and a `BattleRunner` that runs exactly one already-active battle using a
client-supplied `BattleStrategy`. It never repairs equipment, recovers stamina,
or starts Arena/GrindFest on its own. Campaign policy and post-battle work belong
to the calling application.

`BattleSession` preloads the PonyChart classifier and ONNX model before opening
the browser, so a timed challenge never pays the first-load cost. The runner
checks for and resolves PonyChart before parsing an ordinary battle turn or
calling client strategy code.

```python
import asyncio

from hvbattle import BattleCompleted, BattleRunner, BattleSession, TurnDecision


class MyStrategy:
    async def take_turn(self, session: BattleSession, /) -> TurnDecision:
        if await session.go_next_floor():
            return TurnDecision.ACTED
        if not session.alive_monster_ids:
            return TurnDecision.IDLE
        if await session.attack_monster(session.alive_monster_ids[0]):
            return TurnDecision.ACTED
        return TurnDecision.IDLE


async def after_battle(session: BattleSession, completed: BattleCompleted) -> None:
    print(completed)


async def main() -> None:
    async with BattleSession(headless=True) as session:
        result = await BattleRunner(session, MyStrategy()).run_current()
        if isinstance(result, BattleCompleted):
            await after_battle(session, result)


asyncio.run(main())
```

The example requires credentials through the normal `EH_USERNAME` and
`EH_PASSWORD` indirection and an already-active server battle; otherwise
`run_current()` returns `None`. `TurnDecision.STOP` deliberately returns a
`BattleStopped` result. Leaving the battle page without positive final-round
completion evidence raises `BattleInterruptedError`, so callers cannot mistake
an expired login or unexpected navigation for a completed battle.

`BattleDriver` is only a transitional name alias for `BattleSession`, not an
API-compatible implementation of the old driver. Migrate constructor strategy
settings into a `BattleStrategy`, replace `driver.battle()` with
`BattleRunner(driver, strategy).run_current()`, and perform maintenance,
post-battle tasks, and next-battle selection after the returned
`BattleCompleted`. Arena choice follows the same boundary:
`list_arena_options()` returns data and `start_arena(option)` starts only the
option explicitly selected by the caller.
GrindFest uses the equivalent `list_grindfest_options()` and
`start_grindfest(option)` pair; the package does not silently choose the first
or last server option.

`BaseControlPanel`, `ControlPanel`, and `NullControlPanel` provide reusable
pause, skill-selection, and named-toggle mechanisms. The package does not
register campaign choices or choose their defaults: a calling application owns
the toggle names, labels, initial values, and the policy that reads their live
state. Importing `hvbattle` does not import Tk or start a GUI process.

## Development

Build a clean environment backed by PyPI releases:

```bash
bash scripts/rebuild-env.sh
```

For coordinated local development before all dependent releases are on PyPI,
overlay editable checkouts in dependency order:

```bash
uv pip install --python .venv/bin/python --reinstall --no-deps --editable \
  /Users/kuanlun_wang/Desktop/git-repo/hbrowser.clone
uv pip install --python .venv/bin/python --reinstall --no-deps --editable \
  /Users/kuanlun_wang/Desktop/git-repo/hvbrowser.clone
```

Commands that must preserve these editable overlays use `uv run --no-sync`.
