Skip to content

Add SRD dungeon stocking and the KeyedEncounter.hoard gate (1.3.0)#30

Merged
mmacy merged 3 commits into
mainfrom
phase-7-stocking
Jul 24, 2026
Merged

Add SRD dungeon stocking and the KeyedEncounter.hoard gate (1.3.0)#30
mmacy merged 3 commits into
mainfrom
phase-7-stocking

Conversation

@mmacy

@mmacy mmacy commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Grows osrlib with the SRD dungeon-stocking procedure its own spec deferred with "the generator that consumes them comes later" — osr-editor's phase 7 is that first consumer — and cuts 1.3.0.

Added

  • osrlib.crawl.stocking.stock_area — the stocking procedure over the shipped tables. Given a dungeon level, an effective monster catalog, and one RngStream, it rolls one keyed area's contents and answers a frozen StockedArea of content models (a KeyedEncounter, an unguarded AreaTreasureSpec, or a StockedNpcParty report) an author can review, place, and edit. Every draw comes from the passed stream in a fixed order — contents d6, treasure d6 when the row's chance is non-zero, then on a monster room the d20 row, its count, and the variant roll or per-individual pool picks — so a stocked area is reproducible from the stream's state alone.
  • KeyedEncounter.hoard (default True) — gates _generate_lair_hoard at first spawn. The default preserves every existing document's play semantics; hoard=False expresses the treasure-absent keyed room the SRD stocking roll produces, which an unconditional lair hoard could not otherwise represent.

Shared resolution, not a parallel copy

select_encounter_individuals is extracted into core/tables.py as the single home for resolving a monster row's individuals (variant / pool / single) from a stream. Both wandering_check and stock_area consume it, so a stocked row can never drift from a wandering roll on that row — the byte-for-byte agreement is structural rather than a hand-kept parallel. The duplicated _dice_minimum is gone with it.

Boundaries

Traps and specials produce no models (B/X ships example lists as referee prose, not tables), and an NPC-party row has no authorable content model — the procedure reports the rolled kind and count and stops. Treasure generates exactly once at play: a stocked monster room's hoard comes from the encounter's gated lair cache, an empty or trap room's from its unguarded spec, never both.

Tests

tests/test_stocking.py — seeded goldens across all four contents kinds, the variant / pool / NPC-party rows, count-pinning, the catalog-resolution contract, per-address stream independence, the shared resolver's draw, and the hoard gate driven through a real session (same seed, gate flipped: exactly one cache vs. none).

Full gate green: ruff format, ruff check, pyright 0 errors, 1579 passed / 58 skipped.

https://claude.ai/code/session_01SN8gc3PnSwXgKqqgfVcA4x

mmacy added 3 commits July 24, 2026 08:02
…hoard gate

`osrlib.crawl.stocking.stock_area` consumes the shipped stocking tables to roll
one keyed area's contents deterministically from a single RngStream, answering a
frozen `StockedArea` of content models (a keyed encounter, an unguarded area
treasure, or an NPC-party report) an author can review, place, and edit. Every
draw comes from the passed stream in a fixed order (contents d6, treasure d6,
d20, count, then variant/pool), mirroring the crawl's wandering resolution so a
stocked row yields what a wandering encounter on that row would.

`KeyedEncounter.hoard` (default True) gates `_generate_lair_hoard` so the
treasure-absent keyed room — a monster room the stocking roll gave no treasure —
is expressible; the default preserves every existing document's play semantics.

Claude-Session: https://claude.ai/code/session_01SN8gc3PnSwXgKqqgfVcA4x
Extract `select_encounter_individuals` into core/tables.py as the single home for
resolving a monster row's individuals (variant/pool/single) from a stream, and
have both `wandering_check` and `stock_area` consume it. This removes the
copy-pasted resolution and the duplicated `_dice_minimum`, so a stocked row can
never drift from a wandering roll on that row — the byte-for-byte contract is now
structural, not a hand-kept parallel. Adds resolver unit tests pinning its draw.

Claude-Session: https://claude.ai/code/session_01SN8gc3PnSwXgKqqgfVcA4x
@mmacy
mmacy merged commit 0da9d0c into main Jul 24, 2026
5 checks passed
@mmacy
mmacy deleted the phase-7-stocking branch July 24, 2026 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant