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_-](else400 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;
inforeports the computed percentage per item. - Draws replace by default - the same item can appear twice in one
draw.
uniqueforbids repeats within a single call (not across calls), andremoveconsumes 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.
addupserts - re-adding an existingitem_idreplaces its weight and returns the old one inprevious_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 tablesWeights 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 1The 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 --forceBoth 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