v0.103.0 — Treasure Manager
Shipped 2026-09-21 — built on version/0.103.0, twelve commits,
merged to main as db54d1a1, tagged v0.103.0. The thirteenth of the
releases the near-term roadmap sequences from the
August 2026 heuristic evaluation, and part two of
Campaign configuration; the roadmap
page is Treasure Manager, everything here
UI and rules over the Treasury's model that
v0.102.0 shipped. The help is
The Treasury. No module release; Action API 0.5.0.
A campaign's treasure now has a place to be seen and handled: a Treasury page and the same thing as a sidebar in Play, where the GM builds hauls, opens a defeated enemy's pockets or a chest without copying anything, hands a bucket out by a method the campaign allows, runs a merchant, and says who carries the party's treasure; and where a player claims what is theirs, rolls need or greed, buys and sells. Decisions TM-D1 (only the GM starts a session) and TM-D2 (a defeated enemy's loot lives on the enemy until taken) at their recommendations.
The surfaces
- The Treasury page (T.1) —
/campaign/:id/treasurein the campaign's nav: every bucket a section, or every item as one searchable, paged list with its holder; the transfer menu on every row (a stack asks how many); New bucket, rename, reveal or hide, delete; the treasury's own History. - In Play (T.2) — the same body as a left sidebar context opened from the Edit Campaign sidebar's header (the bar stays at five); on the phone a drawer of the Me tab.
- Hauls (T.3) — the add row under every bucket: an item by name (the system's catalogue suggesting, the sheet's own quick-add) or coins of a campaign currency (one stack per currency, merged); each row's Edit and Remove; every write a commit the History panel names.
Loot
- From enemies (T.4 — TM-D2) — a
lootbucket views a character's rows:treasury_buckets.character_idandtoken_id, the enemy's inventory folded into the Treasury's view without a copy. Loot on a downed token's menu (any linked token for the GM) opens it; a member only for a defeated non-party token whentreasure.lootVisibilitylets players see loot without the GM —always, oron-defeatwhile the token is at 0 hp (a linked token crossing 0 announces the treasury either way);on-revealis the GM's reveal. A take is the transfer from the enemy to one's own character, counted as a claim underdistribution-only. - From chests (T.5) — an object token holds items: Contents on its
Token Settings drawer,
POST …/tokens/:tokenId/items; its contents open as a bucket to the GM, or to a member whose PC stands beside it (footprints in grid cells touching or overlapping) when there is something in it; Contents on the token's menu.
Handing treasure out
- The session (T.6) —
treasury_distributions: one active per campaign — bucket, method (allowed bytreasure.distributionMethodsand built; DKP, GDKP and loot council are declared and refused), the item order and the participants fixed at the start, the awarded log; part of the treasury's history. Start from a bucket's menu (Hand out…), Stop from the panel, or it ends itself when the order is done. The Treasury view carries the session and every change announces it, so every member's view switches at once; a member's own transfer of a session item is refused. - GM arbitration (T.7) — drag an item onto a character's card, or give it from the card's menu; one transfer per drop.
- Need / greed (T.8) — Need, Greed or Pass per participant (for a character of one's own; the GM for anyone); when all have chosen the server rolls d100 for the non-passers, need beats greed, ties re-roll among the tied, the winner's transfer happens there, and the rolls post to the campaign channel as roll frames. Everyone passing leaves the item.
- Total value (T.9) — each participant's treasure value (holdings × value + the wallet, in the base currency), the lowest picks first, Take on the picker's row, the totals recomputed.
Merchants, party treasure, kits
- Merchants (T.10) — a
merchantbucket's rows carry a price and a stock (properties.unlimitedfor a shelf that never empties). Buy goes through the transfer with the wallet debited — base currency first, then the others at their rate, change back; Sell attreasure.sellRate × value, the merchant asking its value after. A merchant at a token: Open a shop here (the GM) and Shop on the token's menu. Stock from the catalogue puts the system's whole equipment list on the shelf (T.12). - Party treasure (T.11) — the party bucket's carrier and weight in its
header under
treasure.partyTreasure: carried; Hand over… — the GM to anyone or to nobody, the carrier's owner on; the carrier's sheet counts the party treasure's weight in the Inventory section's new Carrying line (a line, not a rule — encumbrance is the system's). - Earmarks and starting kits (T.12) — an item earmarked for a character of mine shows wherever it sits (a hidden GM-curated kit included) and is mine to claim from there: Claim on the row, and on the sheet's Inventory section, which lists what is set aside for the character.
Guards, help, the API
- The Treasury help page (T.13) — The Treasury; the Campaigns page's Rolls row says where a roll goes.
- Action API 0.5.0 — read-only
api.treasury.buckets(),holdings(characterId?),distribution(); one fetch per run. - The Rolls settings enforced — v0.102.0 declared three keys ahead of
their enforcement; now the post routes a roll frame: a member's to the
GM's Secret channel under
playerRollsVisibleTo: gm-only, an Editor's undergmRollsHidden, a/secretmessage underplayersMayRollSecretly(refused when off). The CC.18 guard names a file for every platform key. - Verification — harness suites per surface (items, loot, chests, the session and its three methods, merchants, hand-over, earmarks, the roll routing); contract tests per surface on the page, the context menu, the drawer and the sheet; the full gate green on every commit. Verified live on the dev campaign: a 5e catalogue quick-add, coins merged, an enemy's sheet items under its loot bucket, a chest beside a PC, a five-way need / greed roll-off with its frames in the channel, a potion bought for 60 of 100 gp, the party bucket carried, the catalogue stocked.
Traps met on the way
- The
systemLiteralsguard readsx.systemId === yas a system branch; write the comparison the other way round. - The transfer's conditional UPDATE must match a pre-holder row (
holder_idnull,character_idset) by itscharacter_id, or a legacy inventory can never be looted. - A module's content provider fetches over HTTP with the browser's auth, so the server cannot list a catalogue; the client lists and posts in bulk.
- The Secret channel is the campaign owner's alone, so a player's secret
roll is routed there by the server — the client's
playcheck is on the channel the sender asked for.