Shop Generator Technical Guide
Architecture, 5-axis level scaling, concrete instantiation, d100 math, and security boundaries.
Overview & Procedural Generation Pipeline
The TaliVault Shop Generator ("Click. New Shop.") is a procedural generation engine designed to create ready-to-run shops in a single click, without generic placeholders or level-inappropriate inventory leaks.
When you click to roll a shop, the generation pipeline executes in milliseconds through these stages:
- Seeded PRNG Initialization: A seed (timestamp or user-specified integer) creates a deterministic pseudorandom number generator (
createRngusing Mulberry32). Every sub-roll—from shopkeeper personality to stock d100 rolls—is reproducible. - Level Profile Resolution: Party level (1–20) is translated into a 5-axis mathematical level profile (
getLevelProfile) governing target rarity, bonus gating, scroll tiers, healing potions, and mundane armor. - Settlement Constraint Evaluation: The settlement size (
hamlet,village,town,city,metropolis,planar) establishes hard mundane item counts, magic item counts, default markup percentages, and an upper rarity ceiling (maxRarity). - Catalogue Preparation & Campaign Layering: The 1,937-item Price Guide catalogue is filtered through active Campaign Economy rules (Availability Dial, Category Multipliers, Rarity Multipliers, Banned Items, and Custom Price Overrides).
- Archetype Pool Filtering: Items are mapped against the chosen shop archetype (
blacksmith,apothecary,arcane,curiosities,general,temple,fletcher,shadow,jeweler,peddler, orcustom) using tag and category matchers. - Shelf Partitioning: Inventory is generated into distinct operational shelves: standard inventory, one Quirked Bargain, one Behind-the-Counter showpiece, and an adventuring gear/component shelf.
- Shopkeeper & Sensory Atmosphere Rolling: An NPC proprietor is generated with ancestry, archetype, disposition, Persuasion haggling DC, soft spot, dealbreaker, secret hook, sensory shop atmosphere, and a distinctive house policy.
The 5-Axis Exact-Level Appropriateness Engine
Most tabletop RPG generators divide games into four coarse tiers (Levels 1–4, 5–10, 11–16, 17–20). In actual 5e play, a Level 1 party has vastly different wealth and monster math than a Level 4 party, and a Level 5 party shouldn't encounter the same wares as a Level 10 party.
TaliVault evaluates five continuous mathematical axes for every party level from 1 to 20:
- Axis 1: Base Rarity Probability Target: Items receive a score weighted around the party's level sweet spot. Lower-tier items remain eligible at modest probabilities, but items above the level sweet spot face steep penalty curves.
- Axis 2: Generic Weapon & Armor Bonus Gating: Generic bonus items are hard-gated by party level. Generic
+1weapons and armor are blocked before Level 4;+2items are blocked before Level 8;+3items are blocked before Level 13. - Axis 3: Spell Scroll Level Clamping: The maximum spell scroll level appearing in shops is calculated as
min(9, ceil(partyLevel / 2) + 1). A Level 3 party sees scrolls up to 3rd-level spells, while 9th-level scrolls never appear before Level 15. - Axis 4: Healing Potion Progression: Apothecaries and Temples automatically stock exact-level healing potion staples (Regular at Levels 1–4, Greater at Levels 5–9, Superior at Levels 10–14, Supreme at Levels 15–20), clamped by the settlement's rarity ceiling so Greater Potions do not leak into hamlets.
- Axis 5: Mundane Armor Caps: Mundane equipment cost is capped at Level 1–2 (
maxMundaneArmorGp = 100) so Blacksmiths don't flood early-game parties with unaffordable Full Plate (1,500 gp) or Half Plate (750 gp). The cap expands to 750 gp at Level 3 and 1,500 gp at Level 5+.
Concrete Base Item & Spell Scroll Instantiation
In 5e rulebooks, many items are defined generically—such as +1 Weapon, Weapon of Warning, Flame Tongue, or Spell Scroll (3rd Level). Presenting these as generic lines forces DMs to improvise details at the table.
TaliVault's instantiation engine (lib/shops/instantiate-item.ts) resolves every generic entry into concrete reality:
- Base Weapon & Armor Resolution: Generic items match against
data/price-guide/base-items.json. A rolledFlame TonguebecomesFlame Tongue (Greatsword)orFlame Tongue (Rapier). - Accurate Gold Calculation: The base mundane item's gold cost is added directly to the magical asking price (
askingGp = magicPrice + baseItemCost), ensuring a+1 Plate Armorcorrectly reflects the 1,500 gp base plate cost plus the magic enchantment. - Interactive Variant Picker: Both the DM workbench and the player shopfront feature an inline variant picker. DMs can switch a
+1 Longswordto a+1 Warhammer, or set it to Player's Choice (Made to Order). - Curated SRD Spell Scrolls: Spell Scrolls resolve concrete spells from a curated 100% CC-BY-4.0 SRD catalog partitioned by spell level (Cantrips through 9th level) and tradition (Arcane vs Divine). Arcane scriberies favor Wizard/Sorcerer spells, while Temple reliquaries favor Cleric/Paladin spells.
Shelf Taxonomy, Quirked Bargains & Ample Stock
Every shop partitions its inventory into discrete shelf types, each governed by different availability and pricing behaviors:
- Standard Magic Shelf (`shelfSlot: "standard"`): The main magic inventory rolled for the shop, filtered by archetype and settlement constraints.
- Quirked Bargain Shelf (`shelfSlot: "bargain"`): Exactly one standard non-consumable magic item is converted into a bargain. The item receives a 15% to 25% discount and is stamped with a sensory or narrative quirk from
ITEM_QUIRKS(e.g., "Hums softly in dwarvish when near running water", "Frost forms on the scabbard in warm sunlight"). - Behind the Counter Showpiece (`shelfSlot: "counter"`): The proprietor keeps one rare "grail" item in a locked cabinet or back room. This item is rolled at one rarity tier above the settlement's normal cap (
nextRarityUp). This item is strictly restricted to DM-only views and is completely stripped from player-facing interfaces. - New Arrivals (`shelfSlot: "new_arrival"`): Wares introduced during a partial restock, highlighted with an amber pill.
- Broker Orders (`shelfSlot: "broker_order"`): Items procured through trade guild networks following a Near-Miss stock check.
- Mundane Adventuring Gear & Ample Stock: General equipment (torches, rations, rope, kits) has
quantity: null, indicating unlimited, ample stock. Only costly spell components (e.g., 100+ gp diamonds for Revivify or 1,000 gp chalices for Heroes' Feast) have finite quantities rolled by the engine.
d100 Stock Checks, Near-Miss Broker & Persistent Memory
When players ask "Do you have any Potions of Invisibility in the back?", DMs often guess or scramble. TaliVault formalizes this with an interactive d100 stock check drawer (executeD100StockCheck):
- Target Probability Calculation: The base target percentage is established by rarity odds (Common 85%, Uncommon 55%, Rare 25%, Very Rare 8%, Legendary 1%).
- Specialty Penalty: If players search for an item outside the shop's archetype (e.g., asking a blacksmith for a wand), the target percentage is penalized by 75% (
targetPct = round(targetPct * 0.25)). - Settlement Scaling: Target odds scale with settlement size (from 0.35× in a rural Hamlet to 2.0× in a Planar Bazaar).
- The Near-Miss Broker Mechanic (+10% Window): If the d100 roll exceeds the target percentage by 10 or less (
target < roll <= target + 10), the check does not fail. Instead, the merchant offers to broker the item through regional guild contacts for delivery in 1d4 days at a +20% finder's fee. - Persistent Roll Memory: Every d100 roll is recorded in
shop.stockCheckswith its roll value, target threshold, outcome, roller identity, and timestamp. If someone rolls for the same item a second time during the visit, the engine returns the cached roll, preventing players or DMs from re-rolling until they get a favorable result.
Campaign Economy Layering & Precedence
If a shop is rolled within or saved to a Campaign with Campaign Economy enabled, the pricing engine applies custom economic rules in a strict precedence hierarchy:
- Custom Price Overrides (Absolute Precedence): Explicit gold piece overrides defined for specific item slugs bypass all multipliers, markups, and formulas.
- Banned Item Purging: Banned item slugs are completely removed from candidate pools, procedural generation, and stock check search results.
- Availability Dial Scaling: The global campaign dial (0.5× abundant to 1.5× scarce) scales base catalogue prices.
- Category & Rarity Multipliers: Specific item categories (e.g., 2.0× on potions) and rarity tiers (e.g., 1.5× on very rare items) are applied multiplicatively.
- Settlement Markup & Rounding: The shop's local settlement markup (or custom DM markup) is applied on top of the campaign-adjusted base price, rounded cleanly to D&D copper/silver/gold increments using
niceGp. - Campaign Default Party Level: When a DM opens the Shop Generator within a campaign, the generator automatically defaults to the campaign's configured party level.
State Mutations: Locks, Single Rerolls & Partial Restock
Shops can be edited and simulated across in-game downtime without regenerating from scratch:
- Row Locking (`locked: true`): Pinned items are protected from all mass mutations, rerolls, and restocks.
- Single-Item In-Place Reroll (`rerollSingleShopItem`): Replaces a single row with a fresh item of matching shelf type and rarity constraints, preserving all other rows, locks, and seed determinism.
- Partial Restock (`restockShopPartial`): Simulates the passage of 1–2 weeks of in-game time. All locked items and Behind-the-Counter showpieces are preserved. Existing
new_arrivalitems convert tostandard. Approximately 30% of unlocked standard magic items rotate out, and 2–3 fresh items are added and flagged as New Arrival. - Markup Feathering (`recalculateShopMarkup`): When adjusting the shopwide markup percentage, the engine scales base and asking prices proportionally while preserving custom price notes and bargain discount ratios.
Security Boundary & Player Sanitization
TaliVault maintains a strict security boundary between DM tools and player-facing views:
- Database Layer: Every shop belongs to a campaign (
campaign_shops) with visibility set todraft,shared, orarchived. - Query Authorization: In lib/queries/shops.ts, non-DM players at a campaign's table can only fetch shops with
visibility = "shared". - Server-Side Sanitization (`sanitizeShopForPlayer`): Before a shop object is sent to a player client, the server strips all private DM data:
-
dmNotesis set toundefined. -shopkeeper.secretOrHookis cleared. -shopkeeper.haggling(Persuasion DC, soft spot, dealbreaker, maximum discount) is zeroed out. - AllshelfSlot === "counter"items are purged from the inventory array. - Player Inquiries: When players browse a shared shop, they can submit formal inquiries (such as requesting a custom broker item or asking if a sold-out item will return), which appear directly on the DM's shop workbench for resolution.
