Skip to main content

osrlib

osrlib is a Python library that implements the classic 1981 B/X (Basic/Expert) fantasy adventure game rules for turn-based, grid-based dungeon crawlers in the style of the original Bard's Tale. The rules come from the Old-School Essentials System Reference Document, an Open Game Content restatement of the B/X rules. osrlib is the rules authority and game-state engine. Your game supplies presentation, input, and content.

The library is headless and sans-I/O: it never renders, prompts, sleeps, or touches the network. Every game it runs is deterministic. The same seed and the same commands always replay the same game. An adventure defines its own content and behavior (bundled items, gated doors, triggers, and quests), and the library includes the interpreter that plays the adventure through to a victory ending. osrlib is designed for four kinds of consumer: a web or mobile backend (FastAPI over HTTP), a terminal game (a local TUI crawler), an LLM referee or narrator driven by structured events and typed commands, and scripts or simulations that call the rules kernel with no session at all.

Status: released and published as osrlib on PyPI. The public API is frozen. The documentation site is the place to learn the library: quickstart, guides, front-end walk-throughs, and a full reference for every public symbol, command, event, rejection code, message code, RNG stream, and content id.

Installation

Requires Python ≥ 3.14. The only runtime dependency is pydantic.

uv add osrlib

or, with pip:

pip install osrlib

Quickstart

from osrlib.core.alignment import Alignment
from osrlib.core.character import CHARACTER_CREATION_STREAM, create_character
from osrlib.core.rng import RngStreams
from osrlib.core.ruleset import Ruleset
from osrlib.crawl.adventure import Adventure, TownSpec
from osrlib.crawl.commands import EnterDungeon, MoveParty, SessionMode
from osrlib.crawl.dungeon import Direction, DungeonSpec, Edge, EdgeKind, LevelSpec
from osrlib.crawl.party import Party
from osrlib.crawl.session import GameSession
from osrlib.messages import format_message
from osrlib.persistence import load_game, save_game

# Roll two 1st-level characters; every random draw comes from a named, seeded stream.
rules = Ruleset()
creation = RngStreams(master_seed=7).get(CHARACTER_CREATION_STREAM)
fighter = create_character(name="Hild", class_id="fighter", alignment=Alignment.LAWFUL, ruleset=rules, stream=creation)
cleric = create_character(name="Osric", class_id="cleric", alignment=Alignment.LAWFUL, ruleset=rules, stream=creation)
party = Party(members=[fighter.character, cleric.character])

# The smallest adventure: a town and a one-corridor dungeon, two cells joined west-east.
crypt = DungeonSpec(
    id="crypt",
    name="The Old Crypt",
    levels=(LevelSpec(number=1, width=2, height=1, entrance=(0, 0), edges={"1,0:west": Edge(kind=EdgeKind.OPEN)}),),
)
town = TownSpec(name="Threshold", travel_turns={"crypt": 1})
adventure = Adventure(name="A First Delve", town=town, dungeons=(crypt,))

# A session starts in town; entering the dungeon switches it to exploring.
session = GameSession.new(party, adventure, seed=7)
session.execute(EnterDungeon(dungeon_id="crypt"))
assert session.mode is SessionMode.EXPLORING

# Commands in, events out: every rules resolution is a typed event with a message code.
result = session.execute(MoveParty(direction=Direction.EAST))
assert result.accepted
lines = [format_message(event) for event in result.events]
assert lines  # every event formats to a default English line

# The whole session round-trips through JSON: same seed, same commands, same game.
document = save_game(session)
restored = load_game(document)
assert save_game(restored) == document

The documentation site walks you through this example step by step, then builds out from it: building an adventure, the session and event loop, gates, triggers, and quests, and front-end walk-throughs for the two example games in examples/.

Determinism

Determinism is a public API guarantee. All randomness flows through named PCG64 streams forked from a master seed. The same seed and the same key always produce the same stream, independent of any other stream:

from osrlib.core.dice import roll
from osrlib.core.rng import RngStreams

streams_a = RngStreams(master_seed=42)
streams_b = RngStreams(master_seed=42)

rolls_a = [roll("2d6×10", streams_a.get("treasure")).total for _ in range(3)]
rolls_b = [roll("2d6×10", streams_b.get("treasure")).total for _ in range(3)]
assert rolls_a == rolls_b  # the same seed and the same key give identical sequences

Successive rolls on one stream differ. The contract is that deriving a stream again, from the same seed and key, reproduces the same sequence. load_game restores a saved game from its serialized state alone, with no re-execution. replay_game rebuilds the same session by re-executing the seed and the command log from scratch. The determinism guarantee is that the two paths always agree, and a standing test checks that they do.

SRD data pipeline

The compiler in tools/srd_compile/ generates the game data in src/osrlib/data/ from the scraped SRD markdown in srd/. Don't edit the generated data by hand. To regenerate it, run:

uv run python -m tools.srd_compile

CI regenerates the data and fails on any diff, so srd/, the compiler, and the generated data can't drift apart. Parser corrections belong in tools/srd_compile/overrides/, never in the output. Every override includes a reason, and the compiler records each overridden field path in the output entry's overrides_applied provenance list. The adaptations register documents the rules interpretations and adaptations.

Contributing

Requires Python ≥ 3.14 and uv. Install from source and run the checks the way CI does:

git clone https://github.com/mmacy/osrlib-python.git
cd osrlib-python
uv sync
uv run ruff format --check
uv run ruff check
uv run pyright
uv run pytest
uv run mkdocs build --strict

For the design (architecture, contracts, rules scope, and the phased roadmap), see the specification.

Licensing

This repository contains two kinds of material under two licenses. The library code is licensed under the MIT license. The SRD-derived content (the scraped SRD text in srd/ and the compiled game data in src/osrlib/data/) is Open Game Content used under the Open Game License 1.0a, which includes the complete Section 15 copyright notice. The data package includes its own copy of the license, with the osrlib Section 15 entry, inside the built wheel.

osrlib is an independent project, not affiliated with or endorsed by Necrotic Gnome. "Old-School Essentials" is a trademark of Necrotic Gnome, used here only to identify the source document. osrlib makes no claim of compatibility.

Metadata

Release files for osrlib 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for osrlib 2.0.0
File Size Uploaded
osrlib-2.0.0.tar.gz 701.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for osrlib 2.0.0
File Interpreter ABI Platform
osrlib-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / osrlib-2.0.0.tar.gz

Download URL osrlib-2.0.0.tar.gz
Size 701.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d148c71dea7d7e6aaa700bebfb02d42fdcc8c223a7d1de9a7564b86cafd67137
BLAKE2b-256 checksum
How to use checksums
7035fbca4d55bff7caa411b29507d2e2aad45be3f41e2c54795409ff909ff8dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / osrlib-2.0.0-py3-none-any.whl

Download URL osrlib-2.0.0-py3-none-any.whl
Size 737.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c99418312416fd5240f40fc07d6b933c6253fdee80c7db89c29d02b9ec4b6524
BLAKE2b-256 checksum
How to use checksums
8f5755cb4413b7e98d3f03d651312a0ef6c6f8a005bdbc2e052c0b8996a925da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.8.0

2 release files

1.7.1

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page