From e42d9c25c8d6c06c7ce4653ab3bdf518b906d29d Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Mon, 14 Sep 2026 04:57:15 -0700 Subject: [PATCH] Docs: one layer, one table on the API overview The API overview rendered every module twice: once in the package docstring's three layer lists, then again in the lists the page generator appended under the same three headings. The generator now renders the package docstring alone, and the docstring gives each layer a heading, a short paragraph on what the layer is for, and a table of its modules in the order a reader meets them. A new docs gate fails when an exporting module is missing from those tables, so the overview cannot drift from the import surface. Claude-Session: https://claude.ai/code/session_01GL26QnA6dCrvUc3WmhzFSa --- src/osrlib/__init__.py | 123 +++++++++++++++++++------------- tests/test_docs_gates.py | 18 +++++ tools/docs/gen_api_reference.py | 16 ++--- 3 files changed, 97 insertions(+), 60 deletions(-) diff --git a/src/osrlib/__init__.py b/src/osrlib/__init__.py index ba27d94..7e422e6 100644 --- a/src/osrlib/__init__.py +++ b/src/osrlib/__init__.py @@ -7,61 +7,82 @@ the same seed and the same commands replay the same game. Every name has one import home and the package root re-exports nothing, so you import from -the module that defines the symbol. The modules fall into three layers. +the module that defines the symbol. The modules fall into three layers, and each table below +lists one layer's modules in the order you meet them. + +## The core kernel The kernel, under `osrlib.core`, is the rules on their own: no session, no dungeon, no -adventure. Use it directly to roll a character, resolve an attack, or price a sword with no -game running. - -- [`osrlib.core.rng`][osrlib.core.rng]: a master seed in, one named stream per subsystem out. -- [`osrlib.core.ruleset`][osrlib.core.ruleset]: the optional rules you switch on, in one model the kernel reads. -- [`osrlib.core.dice`][osrlib.core.dice]: a dice expression in, a parsed expression or a roll and its own dice out. -- [`osrlib.core.alignment`][osrlib.core.alignment]: the three alignments, shared by characters and monsters. -- [`osrlib.core.abilities`][osrlib.core.abilities]: an ability score in, the modifier or chance the tables grant it out. -- [`osrlib.core.classes`][osrlib.core.classes]: a class definition and a character in, titles, XP, advancement out. -- [`osrlib.core.character`][osrlib.core.character]: creation choices and a stream in, a character or refusals out. -- [`osrlib.core.items`][osrlib.core.items]: templates and an inventory in, purchases, equipment, and encumbrance out. -- [`osrlib.core.spells`][osrlib.core.spells]: a caster and a spell in, memorization, casting, and turning undead out. -- [`osrlib.core.monsters`][osrlib.core.monsters]: a monster template in, a spawned instance with its own hit points out. -- [`osrlib.core.combat`][osrlib.core.combat]: combatants and a stream in, initiative, attacks, damage, and saves out. -- [`osrlib.core.effects`][osrlib.core.effects]: a condition or effect in, a ledger that ticks and expires it out. -- [`osrlib.core.treasure`][osrlib.core.treasure]: a treasure type and a stream in, coins, valuables, magic items out. -- [`osrlib.core.tables`][osrlib.core.tables]: hit dice or an armour class in, the printed row for it out. -- [`osrlib.core.npc`][osrlib.core.npc]: a party level and a stream in, a generated NPC adventuring party out. -- [`osrlib.core.clock`][osrlib.core.clock]: rounds in, turns and days out, with the boundaries each crossing reports. -- [`osrlib.core.events`][osrlib.core.events]: the base class every event inherits, and the contract its code follows. -- [`osrlib.core.validation`][osrlib.core.validation]: the refusal value a rules check hands back instead of raising. +adventure. You hand a function the inputs a rule needs, plus a seeded stream and a ruleset, +and it returns the outcome and the events that describe it. Call it directly to roll a +character, resolve an attack, or price a sword with no game running. Kernel modules never +import from `osrlib.crawl`, so what you build on the kernel keeps working whatever the crawl +framework does above it. + +| Module | What it's for | +| --- | --- | +| [`osrlib.core.rng`][] | A master seed in, one named stream per subsystem out. | +| [`osrlib.core.ruleset`][] | The optional rules you switch on, in one model the kernel reads. | +| [`osrlib.core.dice`][] | A dice expression in, a parsed expression or a roll and its own dice out. | +| [`osrlib.core.alignment`][] | The three alignments, shared by characters and monsters. | +| [`osrlib.core.abilities`][] | An ability score in, the modifier or chance the tables grant it out. | +| [`osrlib.core.classes`][] | A class definition and a character in, titles, XP, and advancement out. | +| [`osrlib.core.character`][] | Creation choices and a stream in, a character or refusals out. | +| [`osrlib.core.items`][] | Templates and an inventory in, purchases, equipment, and encumbrance out. | +| [`osrlib.core.spells`][] | A caster and a spell in, memorization, casting, and turning undead out. | +| [`osrlib.core.monsters`][] | A monster template in, a spawned instance with its own hit points out. | +| [`osrlib.core.combat`][] | Combatants and a stream in, initiative, attacks, damage, and saves out. | +| [`osrlib.core.effects`][] | A condition or effect in, a ledger that ticks and expires it out. | +| [`osrlib.core.treasure`][] | A treasure type and a stream in, coins, valuables, and magic items out. | +| [`osrlib.core.tables`][] | Hit dice or an armour class in, the printed row for it out. | +| [`osrlib.core.npc`][] | A party level and a stream in, a generated NPC adventuring party out. | +| [`osrlib.core.clock`][] | Rounds in, turns and days out, with the boundaries each crossing reports. | +| [`osrlib.core.events`][] | The base class every event inherits, and the contract its code follows. | +| [`osrlib.core.validation`][] | The refusal value a rules check returns instead of raising. | + +## The crawl framework The crawl framework, under `osrlib.crawl`, is the game around those rules: a party in a -mapped dungeon, driven by commands. Start at the session and work outwards. - -- [`osrlib.crawl.dungeon`][osrlib.crawl.dungeon]: cells, edges, doors, areas, and traps in, a mapped dungeon out. -- [`osrlib.crawl.adventure`][osrlib.crawl.adventure]: dungeons and a town in, one adventure a session can play out. -- [`osrlib.crawl.party`][osrlib.crawl.party]: characters in, marching order, group movement, and combat ranks out. -- [`osrlib.crawl.session`][osrlib.crawl.session]: a party, an adventure, and a seed in, a game taking commands out. -- [`osrlib.crawl.commands`][osrlib.crawl.commands]: every command you can execute, each with the modes it is legal in. -- [`osrlib.crawl.events`][osrlib.crawl.events]: every event a command can emit, and the parser that reads one back. -- [`osrlib.crawl.views`][osrlib.crawl.views]: a session in, what a player may see or what a referee may see out. -- [`osrlib.crawl.exploration`][osrlib.crawl.exploration]: movement, doors, searching, light, rest, and wandering checks. -- [`osrlib.crawl.encounter`][osrlib.crawl.encounter]: a meeting in, surprise, distance, reaction, evasion, pursuit out. -- [`osrlib.crawl.battle`][osrlib.crawl.battle]: an encounter that came to blows in, a round-by-round battle out. -- [`osrlib.crawl.stocking`][osrlib.crawl.stocking]: an empty area and a stream in, its monsters and treasure out. -- [`osrlib.crawl.gates`][osrlib.crawl.gates]: a condition and a session in, whether the way opens out. -- [`osrlib.crawl.triggers`][osrlib.crawl.triggers]: an event pattern in, a match against what just happened out. -- [`osrlib.crawl.quests`][osrlib.crawl.quests]: objectives and the clauses that complete them, as authored content. -- [`osrlib.crawl.narrative`][osrlib.crawl.narrative]: the authored text on a mechanical object, one block per audience. -- [`osrlib.crawl.interpreter`][osrlib.crawl.interpreter]: a listener you register in, an adventure playing itself out. -- [`osrlib.crawl.content_pack`][osrlib.crawl.content_pack]: keyed room content out of one adventure and into another. - -The shared services sit at the top level and serve both layers. - -- [`osrlib.data`][osrlib.data]: a content id in, the frozen rules entry behind it out. -- [`osrlib.errors`][osrlib.errors]: the exceptions the library raises, and which failure each one stands for. -- [`osrlib.messages`][osrlib.messages]: an event in, a line of default English out. -- [`osrlib.persistence`][osrlib.persistence]: a session in, a save document out, and back again by loading or replaying. -- [`osrlib.versioning`][osrlib.versioning]: the two version stamps on every document, and the envelope for them. - -The quickstart below runs the whole loop: characters, party, adventure, session, commands, +mapped dungeon, driven by commands. You author the content, start a session, and hand it one +command per player action. The session runs the exploration, encounter, and battle procedures +for you and returns the events. Start at the session and work outwards. + +| Module | What it's for | +| --- | --- | +| [`osrlib.crawl.dungeon`][] | Cells, edges, doors, areas, and traps in, a mapped dungeon out. | +| [`osrlib.crawl.adventure`][] | Dungeons and a town in, one adventure a session can play out. | +| [`osrlib.crawl.party`][] | Characters in, marching order, group movement, and combat ranks out. | +| [`osrlib.crawl.session`][] | A party, an adventure, and a seed in, a game taking commands out. | +| [`osrlib.crawl.commands`][] | Every command you can execute, each with the modes it's legal in. | +| [`osrlib.crawl.events`][] | Every event a command can emit, and the parser that reads one back. | +| [`osrlib.crawl.views`][] | A session in, what a player may see or what a referee may see out. | +| [`osrlib.crawl.exploration`][] | Movement, doors, searching, light, rest, and wandering checks. | +| [`osrlib.crawl.encounter`][] | A meeting in, surprise, distance, reaction, evasion, and pursuit out. | +| [`osrlib.crawl.battle`][] | An encounter that came to blows in, a round-by-round battle out. | +| [`osrlib.crawl.stocking`][] | An empty area and a stream in, its monsters and treasure out. | +| [`osrlib.crawl.gates`][] | A gate's condition and a session in, whether the gate opens out. | +| [`osrlib.crawl.triggers`][] | An event pattern in, a match against what just happened out. | +| [`osrlib.crawl.quests`][] | Objectives and the clauses that complete them, as authored content. | +| [`osrlib.crawl.narrative`][] | The authored text on a mechanical object, one block per audience. | +| [`osrlib.crawl.interpreter`][] | The listener you register on a session to play the adventure's triggers and quests. | +| [`osrlib.crawl.content_pack`][] | Keyed room content out of one adventure and into another. | + +## Shared services + +The shared services sit at the top level and serve both layers: the compiled rules content, +the exceptions, the message formatter, and the save and version documents. + +| Module | What it's for | +| --- | --- | +| [`osrlib.data`][] | A content id in, the frozen rules entry behind it out. | +| [`osrlib.errors`][] | The exceptions the library raises, and which failure each one stands for. | +| [`osrlib.messages`][] | An event in, a line of default English out. | +| [`osrlib.persistence`][] | A session in, a save document out, and back again by loading or replaying. | +| [`osrlib.versioning`][] | The two version stamps on every document, and the envelope for them. | + +## Quickstart + +The quickstart runs the whole loop: characters, party, adventure, session, commands, events, save, and load. For the documentation, including a stepwise walk through this example, see https://mmacy.github.io/osrlib-python/ diff --git a/tests/test_docs_gates.py b/tests/test_docs_gates.py index be7ed9a..78f5650 100644 --- a/tests/test_docs_gates.py +++ b/tests/test_docs_gates.py @@ -212,3 +212,21 @@ def test_the_stream_page_names_every_stream_constant(self): if name.endswith("_STREAM") and f"`{name}`" not in page: missing.append(f"{info.name}.{name}") assert not missing, f"stream constants absent from docs/reference/rng-streams.md: {missing}" + + def test_the_api_overview_tables_every_exporting_module_once(self): + import importlib + import pkgutil + + import osrlib + + overview = osrlib.__doc__ or "" + rows = re.findall(r"^\| \[`(osrlib\.[\w.]+)`\]\[\] \|", overview, re.M) + exporting = sorted( + info.name + for info in pkgutil.walk_packages(osrlib.__path__, "osrlib.") + if getattr(importlib.import_module(info.name), "__all__", None) + ) + assert sorted(rows) == exporting, ( + f"missing from the package docstring tables: {sorted(set(exporting) - set(rows))}; " + f"tabled but not exporting or duplicated: {sorted(set(rows) - set(exporting))}" + ) diff --git a/tools/docs/gen_api_reference.py b/tools/docs/gen_api_reference.py index 3b67242..42106cd 100644 --- a/tools/docs/gen_api_reference.py +++ b/tools/docs/gen_api_reference.py @@ -52,17 +52,15 @@ def _layer(name: str) -> str: for symbol in exported: page.write(f" - {symbol}\n") +# The overview page is the package docstring: it introduces each layer and tables its +# modules in the order a reader meets them, with a link to every module page. with mkdocs_gen_files.open("reference/api/index.md", "w") as index: index.write("# API reference\n\n") - index.write("::: osrlib\n options:\n members: false\n\n") - index.write("One page per module, each rendering that module's public (importable) surface:\n\n") - for layer_title, _ in _LAYERS: - index.write(f"\n## {layer_title}\n\n") - for name, exported in modules: - if _layer(name) == layer_title: - path = name.replace(".", "/") + ".md" - summary = importlib.import_module(name).__doc__.strip().splitlines()[0].rstrip(".") - index.write(f"- [`{name}`]({path}) — {summary} ({len(exported)} symbols)\n") + index.write("::: osrlib\n") + index.write(" options:\n") + index.write(" members: false\n") + index.write(" heading_level: 1\n") + index.write(" show_root_toc_entry: false\n") with mkdocs_gen_files.open("reference/api/SUMMARY.md", "w") as summary: summary.write("\n".join(summary_lines) + "\n")