ServicesLottery

Lottery

Weighted random selection over named tables: items carry relative weights, draws pick with or without replacement, seeds make results reproducible, and consumable draws remove what they return. Built for loot tables, raffles, A/B variant picks, and weighted rollouts.

When to reach for it: loot drops, raffle winners, weighted A/B test variant assignment, "spin the wheel" mechanics, any place you need "pick N items where weight sets the odds".

When not to: probabilistic vault reveals (loot boxes with rarity tiers, pity timers, and inventory) belong to Vault; allocating items by competitive bidding belongs to Auction.

Concepts

  • Tables are created implicitly - adding the first item creates the table. There is no separate create endpoint; a name is valid if it is 1-64 characters of [A-Za-z0-9_-] (else 400 invalid_table_name).
  • Weights are relative - an item with weight 60 is six times as likely as weight 10. Weights do not need to sum to anything; info reports the computed percentage per item.
  • Draws replace by default - the same item can appear twice in one draw. unique forbids repeats within a single call (not across calls), and remove consumes drawn items from the table.
  • Seeds make draws deterministic - the same seed against the same table state reproduces the same result, for testing and replay.
  • Tables are shared - they are not per-user. Verified live: a second user's token reads and draws from a table another user built.
  • add upserts - re-adding an existing item_id replaces its weight and returns the old one in previous_weight.

Build a table

snug lottery add -t loot-drops -i rare-sword -w 10 -m '{"rarity":"rare"}'
snug lottery add -t loot-drops -i common-potion -w 60
snug lottery add -t loot-drops -i scroll -w 30

snug lottery batch-add -t loot-drops -f items.json   # array of {item_id, weight, meta}
snug lottery info -t loot-drops                      # items, weights, percentages
snug lottery list                                    # all tables

Weights must be in 1..=1000000 - weight 0 fails with 400 invalid_weight. Batch add applies items in order and stops at the first error.

Over HTTP, adding an item is a POST to /api/v1/lottery/{table_name}:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"item_id": "gem", "weight": 5, "meta": {"rarity": "epic"}}' \
  http://localhost:4000/api/v1/lottery/loot-drops
{
  "status": 200,
  "msg": "OK",
  "data": { "item_id": "gem", "weight": 5, "previous_weight": null, "table_size": 4 }
}

Draws

snug --output json lottery draw -t loot-drops -c 2
{
  "items": [
    { "item_id": "scroll", "weight": 30, "meta": {} },
    { "item_id": "common-potion", "weight": 60, "meta": {} }
  ],
  "table_size": 3,
  "total_weight": 100
}

table_size and total_weight describe the table after the draw, which matters with --remove. Items added without metadata come back with an empty meta object.

Draw frequencies track the weights. Verified live: 300 draws against weights 60/30/10/5 landed 176/81/28/15 - within two points of the expected 57/29/10/5 percent split.

snug lottery draw -t loot-drops -c 4 --unique     # no repeats in this call
snug lottery draw -t loot-drops -c 3 --seed 42    # deterministic
snug lottery draw -t loot-drops -c 1 --remove     # consume the drawn item
  • Unique draws of count N need at least N items - asking for 5 from a 4-item table fails with 400 insufficient_items ("Requested 5 unique items but only 4 available").
  • Seeded draws were verified identical across repeated calls with the same seed, and different under another seed. A seed pins the randomness, not the table - it replays only against the same table state.
  • Consumable draws remove what they return. Asking for more than remains is not an error: the draw returns everything left and empties the table. Count is capped at 100 per draw by default.

Drawing from a table with no items - never created, or emptied by --remove - fails with 400 empty_table (not 404).

Adjusting weights

adjust applies a signed delta atomically, clamped to [--min-weight, --max-weight] (defaults 1 and 1000000):

snug lottery adjust -t loot-drops -i gem --increment 495     # 5 -> 500
snug lottery adjust -t loot-drops -i gem --increment=-10000  # clamps to 1

The response carries previous_weight and new_weight. Verified live: raising one item from 5 to 500 moved it from 5% of draws to 83% in a 200-draw sample. Adjusting an item that is not in the table fails with 404 item_not_found. Note the --increment=-N form - a bare -10000 after a space is parsed as a flag.

Deleting

snug lottery delete-item -t loot-drops -i scroll --force
snug lottery delete-table -t loot-drops --force

Both prompt for confirmation unless --force is given (required in JSON and quiet modes). Deleting an absent item is 200 with removed: false over HTTP; the CLI treats it as a failure and exits non-zero. Deleting a table returns items_removed; deleting it again is 404 table_not_found.

One asymmetry to know, reproduced live: a table emptied by consumable draws or delete-item is 404 table_not_found on info, but still appears in list with size: 0 until you delete-table it.

Finding tables

GET /api/v1/lottery speaks the shared search grammar: q matches table name and description, filter takes field:operator:value expressions on item_count and total_weight, and sort_by accepts created_at, item_count, or total_weight. Verified: ?filter=item_count:range:1, returns only non-empty tables.

Limits and configuration

Up to 10000 items per table, 100 items per draw, and 500 items per batch add by default - tunable, along with the maximum item weight, via the LOTTERY_* variables in the Lottery CONFIG reference.

Reference

  • Lottery API - every endpoint, callable
  • Related: Vault for loot boxes with rarity tiers and inventory, Auction for bidding-based allocation, Remote Config for rule-based (non-random) feature targeting

On this page