Skip to content

Repository files navigation

LootLint

简体中文

CI Security Release License: MIT

Make random rewards reproducible, reviewable, and safe to ship.

LootLint is an engine-agnostic loot-table runtime, simulator, and CI regression gate for game teams. It executes real nested tables and bad-luck protection, measures what unlucky players experience, finds counterexample seeds, and replays exact selection traces. It has no runtime dependencies, backend, account, telemetry, or uploaded game data.

LootLint real simulation report

This is not a loot-table mockup. The CLI and Python API use the same deterministic runtime:

  • weighted item, nested-table, and explicit no-drop entries;
  • multiple draws, quantity ranges, and sampling with or without replacement;
  • soft pity through additive weights and hard pity through a guaranteed roll;
  • independent player streams from a documented stable PRNG;
  • per-roll item rates, quantities, Wilson 95% intervals, first-hit p50/p90/p95/p99, unreached-player rate, and dry-streak tails;
  • project-owned acceptance budgets that return a failing process exit code;
  • paired-seed comparison between a baseline and candidate;
  • counterexample seed search plus byte-stable JSON replay receipts;
  • console, JSON, Markdown, self-contained HTML, and SARIF output.

One-minute proof

From a source checkout with uv:

uv sync --all-groups
uv run lootlint validate examples/roguelike/loot.toml
uv run lootlint audit examples/roguelike/loot.toml
uv run lootlint hunt examples/broken-pity/loot.toml \
  --target tag:legendary --min-misses 60 --seeds 200 --rolls 80 \
  --output reports/broken-seed.json
uv run lootlint replay examples/broken-pity/loot.toml reports/broken-seed.json

The healthy roguelike audit exits 0. The intentionally broken fixture exits 1, and the receipt replay exits 0 only while the specification and runtime still reproduce every weighted choice.

To generate the committed demo evidence and run the complete release gate:

uv run python scripts/demo.py
uv run python scripts/verify.py
uv run python scripts/package_release.py
uv run python scripts/release_check.py

See acceptance commands and the repair playbook for expected output and failure-specific recovery.

Install

Install the wheel from the latest GitHub Release:

pipx install https://github.com/KanadeK/lootlint/releases/download/v0.1.0/lootlint-0.1.0-py3-none-any.whl
lootlint --version

Or install an extracted source release into an isolated environment:

python -m venv .venv
.venv/Scripts/python -m pip install .

On macOS/Linux, use .venv/bin/python in the second command.

Start a specification

lootlint init loot.toml --preset roguelike
lootlint audit loot.toml --format html --output loot-report.html

The starter is not a placeholder: it includes five items, three nested pools, an explicit no-drop outcome, soft and hard pity, a deterministic workload, and a tail budget.

version = 1
root = "boss_chest"

[simulation]
players = 2000
rolls_per_player = 120
seed = 20260730

[items.starforged_blade]
label = "Starforged Blade"
tags = ["equipment", "legendary"]

[tables.boss_chest]
draws = 1
replacement = true

[[tables.boss_chest.entries]]
id = "legendary"
item = "starforged_blade"
weight = 5

[[tables.boss_chest.entries]]
id = "empty"
nothing = true
weight = 95

[pity.legendary_bad_luck_protection]
table = "boss_chest"
entry = "legendary"
starts_after = 12
add_weight_per_miss = 5
guarantee_at = 30

[[expectations]]
id = "legendary-access"
tag = "legendary"
max_p99_first_hit = 30
max_dry_streak = 29
hard_pity_required = true

The full format, including nested table quantities and JSON equivalents, is documented in the v1 specification. The bundled JSON Schema is available without a network request:

lootlint schema --output lootlint-v1.schema.json

Commands

Command Real behavior CI exit rule
validate Checks references, cycles, entry IDs, weights, quantities, pity targets, unreachable content, and expectation selectors non-zero on structural errors
simulate Executes independent players and reports item rates plus configured tail metrics non-zero on invalid input
audit Runs validate and simulate, then enforces only budgets declared in the spec non-zero on any violated budget
compare Uses identical player seeds for baseline and candidate and measures item-rate and p99 drift non-zero when a supplied delta limit is exceeded
hunt Searches seeds for a requested miss streak and writes the exact trace non-zero when no searched seed meets the threshold
replay Re-runs a receipt and compares every drop, ticket, adjusted weight, and pity transition non-zero on fingerprint or outcome divergence
init Writes a complete roguelike or gacha starter refuses to overwrite unless --force
schema Prints the packaged v1 JSON Schema non-zero on write failure

Run lootlint <command> --help for every option. Simulation is bounded to 50 million root rolls per invocation and seed hunting to 10 million, preventing an accidental CI resource spike.

CI in three lines

The repository includes a reusable composite action:

- uses: KanadeK/lootlint@v0.1.0
  with:
    spec: game-data/loot.toml

Or use the CLI directly:

- run: pip install lootlint-0.1.0-py3-none-any.whl
- run: lootlint audit game-data/loot.toml --format sarif --output lootlint.sarif

SARIF preserves stable rule IDs such as LL1010 (nested cycles) and LL2003 (p99 budget). See the rule catalog.

Why a standard format instead of adapting every engine?

A generic validator layered over an arbitrary Unity, Godot, or custom schema usually costs more to integrate than it saves. LootLint makes the data contract part of the tool: designers can edit TOML, engines can consume canonical JSON, CI can execute the reference Python runtime, and QA can attach one replay receipt to a bug. This keeps selection semantics, pity state, and tests aligned.

The v1 contract deliberately does not claim to model an entire game economy or prove that a design is fun. It answers narrower questions with reproducible evidence:

  • Can every referenced reward be reached?
  • Does the implementation enforce the promised hard ceiling?
  • What do the least-lucky simulated players see?
  • Did this content change silently move a drop rate or first-hit tail?
  • Which exact seed reproduces the reported streak?

Project owners define the budgets. LootLint does not label monetization, rarity, or a particular probability as ethical or balanced on their behalf.

Determinism contract

LootLint uses SplitMix64 streams derived per player, integer tickets, declared entry order, and no wall-clock fields in reports. A receipt stores the specification SHA-256 plus every selected table entry, ticket, adjusted weight, and pity transition. The same LootLint version and specification must replay it exactly.

Changing any source field intentionally changes the fingerprint. Cross-version replay compatibility is tested with committed golden vectors. Read determinism and limits before using receipts as long-lived test fixtures.

Architecture

flowchart LR
    A["TOML or JSON spec"] --> B["Parser + structural rules"]
    B --> C["Deterministic runtime"]
    C --> D["Independent-player simulation"]
    C --> E["Counterexample hunt"]
    D --> F["Project budgets"]
    D --> G["Paired-seed diff"]
    E --> H["Replay receipt"]
    F --> I["Console / JSON / HTML / SARIF"]
    G --> I
Loading

The core is library-first: load_spec, LootEngine, simulation, auditing, comparison, and replay are ordinary Python functions. The CLI is an adapter, not a second implementation. See architecture.

Research and differentiation

The opportunity scan compared local projects, public GitHub repositories, game-development discussions, hosted calculators, engine-specific assets, and current English/Chinese material. Existing open-source repositories mainly provide engine-specific inventory systems or weighted selection libraries; hosted tools emphasize visual table construction. LootLint is differentiated by a versioned cross-engine contract, a reference runtime, project-owned tail gates, paired regression, SARIF, and exact counterexample replay. Sources and rejection notes are in research.

Security and privacy

  • Specifications and reports stay on the local machine.
  • The parser accepts only TOML/JSON and caps source size at 5 MiB.
  • There is no expression evaluator, plug-in execution, network request, analytics, or remote asset.
  • HTML reports escape source-controlled labels and embed no executable JavaScript.
  • Counterexample receipts can reveal item IDs and table structure; review them before attaching to a public issue.

Report vulnerabilities privately using SECURITY.md.

Contributing

Issues with a small, sanitized loot table and the expected selection semantics are especially useful. Run uv run python scripts/verify.py before opening a pull request. The contribution guide, code of conduct, and roadmap describe the project boundaries.

LootLint is available under the MIT License.

About

Deterministic loot-table runtime, simulator, CI regression gates, and exact counterexample replay for game teams

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages