Skip to content

Latest commit

 

History

History
66 lines (44 loc) · 7.47 KB

AGENTS.md

File metadata and controls

66 lines (44 loc) · 7.47 KB

Agent guide for osrlib

osrlib is a Python library implementing 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 are sourced from the Old-School Essentials SRD, an Open Game Content restatement of B/X — name the ruleset B/X and reserve the trademark for attribution, as README.md does.

Start here

  • docs/spec.md is the single source of truth. Read it before any implementation work. It is decision-complete: architecture, rules scope, contracts, and a phased roadmap. Implement in phase order.
  • srd/ is the scraped OSE SRD (412 markdown files, URL-encoded filenames). It is read-only reference material and the input to the data pipeline. Never edit these files by hand — parser corrections belong in tools/srd_compile/overrides/.
  • osrlib/data/ is generated by tools/srd_compile/ and is never hand-edited. CI regenerates and fails on diff.
  • When a rules question comes up, the SRD text in srd/ is the authority. Quote it in review discussions rather than working from memory.

Working method

Work is divided into chunks that the lead role packets to coder roles, with acceptance tests written before the implementation by a different agent and committed marked expected-to-fail. The method, the roles, the packet, and the worktree rules are in the suite guide at ../CLAUDE.md, and the role definitions are in ../.claude/agents/. Each roadmap phase in docs/spec.md is a batch of chunks. The plans under docs/phase-N-plan.md are the record of phases already built.

Toolchain

  • Python ≥ 3.14. Package management with uv exclusively (uv add, uv sync, uv run) — never pip.
  • Format with ruff format, lint with ruff check, test with pytest (not unittest).
  • Type hints use built-in generics (list[str], dict[str, int]). Do not import List/Dict/Tuple from typing and do not use from __future__ import annotations.
  • Docstrings are Google style, written in Markdown. Maximum line length 120. That limit is a Python source rule enforced by ruff, and it has never applied to the .md files under docs/.

Greenfield discipline

osrlib is released: the public API is frozen and semantic versioning governs what a release may change, so breaking the public surface is a major-version event, not a refactor. Behind that surface, code-level backward compatibility is an anti-pattern here, not a virtue. When a better internal factoring appears, refactor to it outright and update every call site — the tests and CI are the safety net. Do the right thing even when it's the hard thing, and question existing code and the decisions behind it as the project matures — what was right then may not be right now. Specifically:

  • No re-exports, aliases, or shims whose purpose is keeping an old import path working. One home per symbol; move it and fix the imports. (Precedent: the Alignment re-export from character.py was cut in PR #8 review as exactly this.)
  • No deprecation paths, _v2-style parallel names, or code kept "just in case" a removed path is wanted back — git history is the archive.
  • No parameters, flags, or branches whose only purpose is preserving a behavior nothing in the repo still uses.
  • Prose that justifies a design by "so the old path still works" is the tell: if the sentence names no current consumer, the accommodation should not exist.

Do not overcorrect into breaking the compatibility contracts that are real: the public API surface (semantic versioning, promised in CHANGELOG.md), schema_version for serialized artifacts (saves, commands, events — additive-only within a version), and the determinism contract governing draw sequences and golden files. Honor those; everything else is freely refactorable.

Invariants the spec imposes

These are contracts, not suggestions — see the corresponding spec sections before touching related code:

  • Determinism. All randomness flows through named PCG64 streams from core/rng.py. Never use the random module directly, never iterate sets or other order-unstable structures on an event-emitting path, and never use UUIDs for entity IDs (they are session-scoped and monotonic).
  • Layering. osrlib.core must never import from osrlib.crawl.
  • Events. Structured fields plus message codes only — never baked English prose in event models. Respect player/referee visibility.
  • Commands. Validation is a pure pre-phase: a rejected command consumes no RNG draws, no clock time, and mutates nothing.
  • Kernel arguments. A kernel function takes the same stance towards its own arguments: it checks them before its first draw and raises ValueError from the call, so a refused call costs no draws, no time, and no state, and never returns a plausible-looking result built on a bad argument.
  • Schema discipline. Serialized models follow the schema_version rules: additive-only within a version; renames, removals, and semantic changes bump it.
  • Frozen data. SRD data models are frozen; play spawns mutable instances from templates.

Documentation conventions

  • No numeric surface counts in prose. Never write "all 44 commands" or "68 events" — the registries and the generated reference pages carry the numbers, and prose counts drift the moment a surface grows.
  • The one-way bridge. Common, jobs-to-be-done language locates a concept (navigation labels, headings, opening sentences); the project term is introduced once, job first ("osrlib calls this a gate"), and from that sentence on the docs commit to the term. No page maintains a parallel vocabulary.
  • The voice gradient. The funnel top — README, index, quickstart, guide openings — is written plain, every sentence doing instruction; the register stays rich in guide interiors, walkthroughs, and reference prose, where the reader has bought in.
  • One paragraph per line. Markdown prose is never hard-wrapped: a paragraph, a list item, or a table row is one line however long it runs, and the editor soft-wraps it. Reflowed prose turns a one-word edit into a paragraph-sized diff, which buries the change under the rewrap. The 120-character limit in the toolchain section governs Python source, not these files.
  • Transcripts are captured, never composed. Any quoted program output — TUI transcripts, command output — is re-captured from a real run after every change that could affect it, and never hand-edited.

Testing expectations

  • Table fidelity tests assert against SRD values directly; golden-seed scenario tests are scoped per RNG stream.
  • Run pytest before committing. A change that alters golden files must explain why in the commit message.

Releasing

The version lives in pyproject.toml alone and the bump procedure is: edit the version, run uv lock, and nothing else. The golden comparisons normalize the engine_version stamp, so regenerating a golden file for a version bump is a defect by definition — the goldens keep the stamp of the engine that produced them. A PR that changes user-visible behavior adds its CHANGELOG.md bullet in the same PR. The full runbook — the annotated tag that drives release.yml, the local dry run, recovery, and the versioned-docs adoption trigger — is .claude/skills/release/SKILL.md.

Licensing

  • SRD-derived content is Open Game Content under OGL 1.0a (Necrotic Gnome); keep the OGL text and Section 15 notice intact and attribute new SRD-derived data accordingly.
  • Library code is MIT. Do not mix the two: code under MIT, compiled SRD data under OGL.