Files
the-sunken-reach/docs/SEEDS.md
T
joshuaandClaude Opus 5.5 9c97d61ce5 V1.12.0: worlds from a code, shops and cash in the seeds, up-to-date packages
- A DM can start a campaign from a ready-made world by typing its
  six-character code: "+ New Campaign" on the Campaigns page, then "Or
  start from a ready-made world". The whole world is built in one go
  with them as its DM: locations, weapons, items, spells, monsters,
  NPCs, encounters and notes. ALDENF is Aldenfell, HOLLOW is Hollowcrown.
- A world is one file in /backend/seeds. Drop another in and its code
  works straight away, with no restart. "npm run seeds:check" lists
  every world, or what is wrong with one: a misspelt field, a name used
  twice, an encounter pointing at something the file does not have. A
  file with a mistake in it is never offered. docs/SEEDS.md has the
  format.
- A seed can now hold everything a campaign can: gear effects, an NPC's
  wares and exchange rate, things for sale at an encounter, and rewards
  in silver, copper and cash.
- The database seed creates the admin account and nothing else. The
  Aldenfell and Hollowcrown seed commands are gone: worlds come from
  codes.
- Hollowcrown follows the V3 book: 4 starting items and 3 things
  equipped, the book's effects on every weapon and item, armor as items
  (library, modern and magic), worn gear at the book's levels, damage
  types on monsters, ten shop scenes with prices and stock, five money
  changers, Meridian loot as cash and mixed hoards as gold, silver and
  copper.
- Aldenfell: the Shield and Leather Armor change AC, Mireille and the
  Founding Fair stalls sell things, and the book's missing scenes are in
  (E5, E7, E15, E16, E18, E23-24, E26, the Drowned Shrine, Sevrisk's
  Hollow, a wyvern pass). The locket is found with Investigation, as the
  book has it.
- Packages are up to date: Prisma 7 (with the pg driver adapter and a
  prisma.config.js that holds the database URL), Express 5, zod 4,
  dotenv 18, jest 30, bcrypt 6 and TypeScript 7. "npm audit" finds
  nothing. Install scripts are approved for Prisma and bcrypt. On a
  server: "npm install", then "npm run deploy".
- The frontend's five lint warnings are fixed; useAuth and usePagination
  have files of their own.
- All packages at version 1.12.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:03:16 +02:00

10 KiB
Raw Blame History

World Seeds

A world seed is a ready-made campaign: locations, weapons, items, spells, monsters, NPCs with their wares, encounters with their rewards, and notes. A DM starts a campaign from one by typing its code.

This describes the code as it stands. Related: ITEMS.md, WEAPONS.md, SPELLS.md, MONSTERS.md, NPCS.md, ENCOUNTERS.md.

Contents

  1. The basics
  2. Starting a campaign from a code
  3. Adding a world
  4. What a seed file holds
  5. Checking a seed
  6. The database seed
  7. The worlds that come with the app

1. The basics

  • Every .js file in /backend/seeds is one world. The file says what its code is: six capital letters or digits.
  • Anyone who is logged in and knows a code can create that world as a new campaign. They become its DM and owner, exactly as with a blank campaign.
  • The same code can be used any number of times. Each use makes a separate campaign with its own invite code; nothing is shared between copies.
  • A seed only says what a campaign starts as. Once created, the campaign is the DM's to change, and changing the seed file later does not touch campaigns already made from it.
  • The folder is read each time a code is entered, so a new file is live as soon as it is on the server. No restart, no migration.

2. Starting a campaign from a code

On the Campaigns page, click + New Campaign. Under the form for a blank campaign is Or start from a ready-made world: type the code and click Create From Code. Capitals and spaces do not matter (aldenf, ALDENF and ald-enf are the same code).

The whole world is created in one go, and you land on its campaign page. If anything in it fails, nothing is created.

Reply Meaning
"A world code is 6 letters or numbers" What was typed is not six characters long
"There is no world with that code" No valid seed file has that code

The API is POST /campaigns/from-seed with { "code": "ALDENF" }. It answers with the new campaign, as POST /campaigns does.

3. Adding a world

  1. Write the world as a file in /backend/seeds, for example /backend/seeds/my-world.js (section 4).
  2. Run npm run seeds:check (section 5) and fix whatever it reports.
  3. Put the file on the server. The code works straight away.

A file with a mistake in it is never offered: its code simply answers "There is no world with that code" until the file is fixed. Two files cannot share a code; the one whose file name comes first keeps it.

To retire a world, delete its file. Campaigns already made from it stay.

4. What a seed file holds

A seed file is a JavaScript module that exports one object. Only code and campaign.name are required; every list can be left out.

module.exports = {
  code: 'MYWRLD',
  campaign: { name: 'My World', description: '...', startingGold: 20, startingItemCount: 2, startingSpellCount: 2 },
  locations: [{ name: 'The Old Mill', description: '...' }],
  weapons: [
    'dagger',                                      // a copy from the site library
    { library: 'longsword', isStartingOption: true }, // a copy, with changes
    { name: 'Millstone Hammer', damageDice: '2d6', damageType: 'bludgeoning', attackAbility: 'STR', minLevel: 3 },
  ],
  items: [
    { library: 'shield', isStartingOption: true },
    { name: 'Miller’s Charm', description: '...', effects: [{ stat: 'SKILL_PERCEPTION', amount: 1 }] },
  ],
  spells: [{ library: 'fire-bolt', isStartingOption: true }, 'fireball'],
  monsters: ['goblin', { name: 'Mill Wraith', ac: 13, maxHp: 30, attackBonus: 4, damageDice: '1d8+2 necrotic' }],
  npcs: [
    {
      name: 'Old Hob', role: 'Miller', disposition: 'FRIENDLY', description: '...', dmNotes: '...',
      wares: [{ item: 'Shield', price: 10 }, { weapon: 'Dagger', price: 2, stock: 3 }],
    },
  ],
  encounters: [
    {
      name: 'The Mill at Night', mode: 'BATTLE', location: 'The Old Mill', description: '...', dmNotes: '...',
      monsters: [['Goblin', 3], ['Mill Wraith', 1, 'The Miller’s Wife', 40]],
      npcs: ['Old Hob'],
      rewards: [{ gold: 25 }, { silver: 40 }, { weapon: 'Millstone Hammer', note: 'Behind the wheel' }],
    },
  ],
  sharedNotes: ['Welcome to My World...'],
  dmNotes: ['The secret of the mill...'],
};

The file is ordinary JavaScript, so it can build its lists however it likes (the Hollowcrown seed adds the whole spell library with a map).

Top level

Field What it is
code Six capital letters or digits. What a DM types
campaign The campaign's settings: name, and optionally description, startingGold, startingItemCount, startingSpellCount, skipMotivationRoll, showMonsterHp, areaAttackAcPenalty, maxEquipped, maxEquippedWeapons. Anything left out takes the app's default
locations { name, description }
weapons, items, spells, monsters The pools. Each entry is a row or a library copy (below)
npcs NPC rows, each optionally with wares
encounters In the order they should list
sharedNotes, dmNotes Note bodies, in reading order: the first is shown at the top. Shared notes are for everyone, DM notes for the DM

Pool entries

An entry in weapons, items, spells or monsters is one of:

  • A library key such as 'longsword': a copy of that entry from the site's built-in library (src/game/data/*Library.js).
  • A library copy with changes: { library: 'longsword', isStartingOption: true, minLevel: 2 }. Any field of the row can be changed, including its name.
  • A full row, with the same fields the app's own forms have:
Pool Fields (required in bold)
Weapon name, damageDice, damageType, attackAbility (STR, DEX or FINESSE), properties, rangeNormal, rangeLong, damageScaling (1 to 3), minLevel (1 to 9), isStartingOption, effects, effectNote
Item name, description, damageDice, damageScaling, minLevel, isStartingOption, effects, effectNote
Spell name, level (0 to 9), damageDice, school, castingTime, range, duration, description, isStartingOption, revives
Monster name, ac, maxHp, speed, strScore ... chaScore, attackBonus, damageDice (may name its type: "1d6+2 necrotic"), challengeRating, specialTrait, notes

effects is a list of { stat, amount }, as described in ITEMS.md: armor is an item with an AC effect.

Within a pool a name can only be used once, because everything else in the file points at pool entries by name.

NPCs

name, role, disposition (FRIENDLY, NEUTRAL or HOSTILE), description (what players see), dmNotes, and for those who might fight ac, maxHp, attackBonus, damageDice.

  • A money changer has exchangeGold and exchangeCash together: exchangeGold: 1, exchangeCash: 50 is 1 gold for $50, either way.
  • A shopkeeper has wares, a list of things for sale (below).

Encounters

Field What it is
name
mode BATTLE (the default) or QUEST. Buying and money changing only happen in a quest
description The scene, shown to the players in it
dmNotes DM only
location A location's name
monsters [monster name, how many, display name, max HP]. The last two are optional; more than one gets "#1", "#2"...
npcs NPC names, or { name, hp } to set the hit points for this scene
rewards Lined up unawarded (below)
wares For sale at the scene itself, with nobody behind it: a market, a vending machine

A reward is exactly one of gold, silver, copper, cash (an amount), weapon or item (a name), with an optional note. A weapon or item can also have quantity and level.

A ware is { weapon | item, price } with optional currency (GOLD, the default, SILVER, COPPER or CASH), level and stock (left out: no limit).

A reward's or ware's level defaults to the entry's own starting level, and cannot be below it.

Limits

Text fields have the same length limits as the app's forms (for example 4,000 characters for an encounter's DM notes and 10,000 for a note), so that everything a seed creates can still be edited on screen. The checker says which field is over.

5. Checking a seed

npm run seeds:check

lists every file in /backend/seeds with its code and what it holds, or everything wrong with it:

✓ aldenfell.js — code ALDENF — Aldenfell
    24 weapons (16 starting), 31 items (10 starting), 45 spells (30 starting), 21 locations
    15 monsters, 13 NPCs (8 wares), 38 encounters (40 rewards, 9 wares), 23 notes
✗ my-world.js
    npcs.0.desciption: Unrecognized key: "desciption"
    Encounter "The Mill at Night": there is no monster called "Gobblin"

It checks the things that would otherwise only show up as a half-working campaign:

  • A field name that is not one of the fields above (a typo).
  • A library key that is not in the library.
  • The same name twice in one pool.
  • An encounter, reward or ware that names a location, monster, NPC, weapon or item the file does not have.
  • A reward that is not exactly one thing, or gear handed out below its starting level.
  • An exchange rate with only one half.

npm test runs the same check on every seed in the folder.

6. The database seed

npx prisma db seed creates the admin account if it is missing, and nothing else. It never adds a campaign: worlds come from codes, in the app. See the README for the account it creates and --reset.

7. The worlds that come with the app

Code World From
ALDENF Aldenfell The Aldenfell Campaign Book
HOLLOW Hollowcrown: The Rift Between Worlds The Hollowcrown Dungeon Master's Book (V3)

Change a world's code by editing code in its file.