Metadata-Version: 2.4
Name: umalite
Version: 0.1.0
Summary: A minimal Python library for Uma Musume turn evaluation
Author: UmaLite Contributors
License: MIT
Project-URL: Homepage, https://github.com/ReaVNaiL/UmaLite
Project-URL: Repository, https://github.com/ReaVNaiL/UmaLite
Project-URL: Issues, https://github.com/ReaVNaiL/UmaLite/issues
Keywords: umamusume,uma-musume,simulation,evaluation,python
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Dynamic: license-file

# UmaLite

<img width="1701" height="124" alt="image" src="https://github.com/user-attachments/assets/9089e8f3-e6dc-45f2-840b-672ad043c0ba" />

UmaLite is a small Python library for scoring **Uma Musume turn actions**.

You give it a snapshot of the current state, a run context, and a list of candidate actions.
It returns ranked results with a score breakdown for each action.

The library does not read the game or run automation.
It scores actions from what the caller knows right now.

The goal is to keep evaluation logic isolated and reusable so other tools can use the same scoring engine.

UmaLite is **snapshot-first**.
The more visible state the caller supplies, the more accurate scenario-specific scoring becomes.
Missing optional data should reduce precision, not be silently invented.

---

## Quick start

Most callers now use a scenario client.

The preferred entrypoint is:

```python
from umalite import UmaLiteClient

umalite = UmaLiteClient()
ura_client = umalite.ura
unity_client = umalite.unity
trackblazer_client = umalite.trackblazer
```

Each scenario client exposes:

- `.snapshots`
- `.contexts`
- `.profiles`
- `.actions`

For URA and Unity, the normal path is `evaluate_turn(...)`.
For Trackblazer, the normal path is `plan_turn(...)`.

```python
from umalite import CheckpointContext, CheckpointKind, MoodLevel, RaceGrade, StatType, UmaLiteClient

ura_client = UmaLiteClient().ura

snapshot = ura_client.snapshots.minimal(
    stats={
        StatType.SPEED: 420,
        StatType.STAMINA: 360,
        StatType.POWER: 340,
        StatType.GUTS: 250,
        StatType.WIT: 390,
    },
    energy=58,
    mood=MoodLevel.NORMAL,
    fans=42000,
)

context = ura_client.contexts.basic(
    checkpoint=CheckpointContext(
        kind=CheckpointKind.GOAL_RACE,
        turns_remaining=8,
    ),
)

career_profile = ura_client.profiles.basic(
    target_stats={
        StatType.SPEED: 1200,
        StatType.STAMINA: 800,
        StatType.POWER: 1000,
        StatType.GUTS: 400,
        StatType.WIT: 1100,
    },
    minimum_training_mood=MoodLevel.GOOD,
)

actions = [
    ura_client.actions.train(
        "speed_training",
        training_type=StatType.SPEED,
        stat_gains={StatType.SPEED: 18, StatType.POWER: 9},
        sp_gain=5,
        fail_rate=0.15,
    ),
    ura_client.actions.race(
        "g3_race",
        race_grade=RaceGrade.G3,
        fan_gain=5000,
    ),
    ura_client.actions.rest("rest", mood_change=1),
]

result = ura_client.evaluate_turn(
    snapshot=snapshot,
    context=context,
    actions=actions,
    career_profile=career_profile,
)

print(result.best_action_id)

for bid in result.bids:
    print(bid.action.action_id, bid.score)
```

---

## Reusing an evaluator

If you're evaluating many turns, construct an evaluator once.

```python
from umalite import Evaluator, ScenarioName

engine = Evaluator(scenario=ScenarioName.URA)

result = engine.evaluate(snapshot, context, actions, career_profile=career_profile)
```

---

## Scenario support

| Scenario       | Public entrypoint        | Status         | Notes                                                |
| -------------- | ------------------------ | -------------- | ---------------------------------------------------- |
| **URA Finale** | `UmaLiteClient().ura`    | Implemented    | Evaluator-first baseline model                       |
| **Unity**      | `UmaLiteClient().unity`  | Implemented    | Evaluator-first with optional Unity-specific terms   |
| **Trackblazer**       | `UmaLiteClient().trackblazer`   | Experimental   | Planner-first with preparation steps                 |

Scenarios are implemented as plugins so each mode can adjust reward projection and evaluation logic without modifying the core engine.
Scenario clients provide the recommended public surface.

---

## Core models

Evaluation uses three primary inputs.

- `TurnSnapshot` - current visible state of the run
- `RunContext` - checkpoint horizon, local urgency, risk settings
- `ActionOption` - candidate action and its payload

`CareerProfile` is the run-intent object.

It is used by both `evaluate_turn(...)` and `plan_turn(...)`.
It currently carries:

- final target stats
- milestone target stats
- minimum training mood
- year-specific training mood floors
- maximum training fail rate

If both `CareerProfile` and `RunContext.target_stats` are provided, the explicit context targets win.

The output is a ranked list of `ActionBid` results.

---

## Action contract

Action inputs follow the same rule across scenarios:

- the caller supplies visible facts
- UmaLite projects hidden outcomes when needed

Training is mostly observable.
Callers usually provide visible stat gains, visible SP gain, fail rate, and `training_type`.
`energy_delta` is optional.
If omitted, UmaLite falls back to a bootstrap default.
Wit is the only training lane whose shared fallback `energy_delta` is non-negative.
The default Wit fallback is `+5` energy.

Race is partly projected.
Callers usually provide `race_grade` and visible `fan_gain`.
If explicit race rewards are omitted, UmaLite projects a baseline reward bundle from the grade.

Rest is projected by default.
Callers do not need to hardcode a fixed recovery amount unless they want an explicit override.

Recreation remains an explicit reward action.

The raw action builders remain available:

- `train_action(...)`
- `race_action(...)`
- `rest_action(...)`
- `recreation_action(...)`

Scenario clients wrap the same builders behind `.actions`.

---

## Minimal snapshot

The smallest useful snapshot:

```python
from umalite import MoodLevel, ScenarioName
from umalite.models import TurnSnapshot

snapshot = TurnSnapshot.minimal(
    scenario=ScenarioName.URA,
    stats={
        StatType.SPEED: 420,
        StatType.STAMINA: 360,
        StatType.POWER: 340,
        StatType.GUTS: 250,
        StatType.WIT: 390,
    },
    energy=58,
    mood=MoodLevel.NORMAL,
    fans=42000,
)
```

Additional fields such as bonds, facilities, turn metadata, conditions, and scenario state are optional enrichment.

String values are also accepted for convenience, but enums are the preferred public API.

---

## Raw API

The raw API is still available:

```python
result = evaluate_turn(
    snapshot,
    context,
    actions,
    config=None,
    career_profile=None,
    milestone=None,
)
```

```python
plan = plan_turn(
    snapshot,
    context,
    actions,
    config=None,
    career_profile=None,
    milestone=None,
)
```

Use the raw API when you want direct control over snapshots, contexts, and action payloads.

---

## Precision model

UmaLite does not require perfect state.

Required core state should always be present.
Optional enrichment improves scoring quality.
If optional scenario data is missing, the evaluator should fall back to a reduced path instead of inventing hidden game state.

In practice this means:

- minimal snapshots are valid
- enriched snapshots score better
- scenario-specific terms activate only when the needed state exists

Projection defaults are bootstrap library approximations, not final scenario truth.

---

## Configuration

Most usage can rely on defaults.

If needed you can supply your own config:

```python
from umalite.config import EvaluationConfig, UtilityConfig

config = EvaluationConfig(
    utility=UtilityConfig(...),
)
```

Or attach the config to an evaluator instance.

Configuration is intended for **policy**, such as weights, urgency shaping, and risk tolerance.
Core game semantics should remain owned by the library.

---

## Examples

See the `examples/` directory for:

- minimal URA usage
- enriched URA usage
- reusable evaluator pattern
- minimal Unity usage
- enriched Unity usage
- minimal Trackblazer usage
- enriched Trackblazer usage

The scenario client examples are the recommended starting point.

---

## Developing locally

Clone the repo and install dependencies.

```bash
git clone https://github.com/yourusername/umalite.git
cd umalite
python -m venv .venv
source .venv/bin/activate  # or .venv\Scripts\activate on Windows
pip install -e ".[dev]"
```

Run tests.

```bash
pytest
```

Run linters and type checks.

```bash
ruff check .
ruff format --check .
mypy .
```

Install pre-commit hooks for automatic checks on commit.

```bash
pre-commit install
```

The project uses ruff for formatting and linting, mypy for type checking, and pytest for tests.


