Buy tickets (200 coins each, max 100/draw); one winner is drawn daily at 21:00
Tallinn time weighted by ticket count and credited the whole pot. Coin-conserving
by design: each ticket's cost is deducted at purchase and the winner is minted
exactly the sum of all ticket spend - no shared pot record, so no cross-period
race. Ticket state lives per-user keyed by draw period (full scan only at draw
time and for the pot view).
- New lottery.py: TICKET_COST/MAX_TICKETS/DRAW_HOUR, pure period_for, and
do_buy_ticket / get_lottery_state / do_lottery_draw. New lottery_tickets +
lottery_period schema fields (period added to _TEXT_FIELDS).
- /lottery [kogus] command (view or buy) with full failure handling.
- Scheduled lottery_draw_daily loop in bot.py (both profiles; each draws its own
collection), announcing to the optional LOTTERY_CHANNEL_ID (config + .env).
14 tests: period boundary, buy/accumulate/reset/caps/guards, pot state, draw
payout with coin conservation, and the more-tickets-wins-more weighting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
- /stats and /profile stats now show lootboxes opened and achievements unlocked
alongside the best streak.
- /adminview shows bank balance, achievements count, lootboxes opened, and any
pending (escrowed) wager.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
15 one-time achievements over the lifetime stat counters (work/wealth/gambling/
crime/heists/fishing/streaks/prestige), each paying a modest one-time coin reward
when unlocked. Detection is lazy - opening /achievements claims any newly earned
(like quests roll on view) - so no per-command hook is needed, and rewards are a
bounded, one-time coin source.
- New achievements.py: ACHIEVEMENTS table, pure newly_earned/achievements_view,
and locked do_check_achievements. New achievements_earned schema field.
- /achievements command (own view claims; viewing others is read-only) with
progress bars, db_error handling, and an "unlocked" banner.
8 tests: threshold detection, one-time claim (no double pay), multi-unlock,
view progress capping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
A strategic counterpart to /rob: coins moved to the bank are safe from /rob and
/heist (which only touch liquid balance) but earn no Bot Farm interest and can't
be spent, gambled or given until withdrawn - the deliberate trade-off against
keeping coins liquid.
- New bank.py (do_deposit/do_withdraw), bank_balance schema field.
- /bank (view), /deposit, /withdraw commands ('all' supported), with db_error
handling; /balance shows the vault when non-zero.
- Coins leaderboard and /status money-supply now count net worth
(balance + bank_balance), so banking never hides you from the board or the
supply metric.
- Season reset wipes bank_balance too (no cross-season wealth hiding).
10 tests: deposit/withdraw math, guards, coin conservation, rob cannot touch the
vault, and net-worth accounting on the leaderboard + stats.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
A pay-to-open box (1000 coins) with a weighted reward: coin tiers (usually a net
loss - the sink), a random 30-min earn/exp buff, or a rare jackpot. All rolling
lives in do_open_lootbox for testability; the command adds a short reveal.
- New core module lootbox.py; extracted consumables.grant_buff (reused by both
consumables and lootbox) to avoid duplicating the buff-stacking logic.
- New lootboxes_opened stat; pending schema syncs as a number automatically.
- Added /consumables, /lootbox, /vanity to the help embed (the first two were
previously missing from /help).
Tests cover charging, insufficient/banned, the coin and buff outcomes, the
net-vs-reward invariant, and that balance never goes negative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
Vanity shop (/vanity): cosmetic badges/titles as a pure whale coin sink -
purchases burn coins (not credited to the house) and equip a badge shown on
/profile. New vanity_owned/vanity_active fields, schema sync, and tests.
Money-safety and robustness fixes from a codebase review:
- _parse_amount now rejects negative amounts. Every bet/give/request flows
through it, so a negative value can no longer mint coins on a loss/transfer
path that trusts the caller's sign (all call sites already guarded <= 0;
this closes the source).
- do_blackjack_payout no longer raises on a DB failure. The stake was already
deducted in do_blackjack_bet, so it now logs critical with the owed amount
(for admin reconciliation) and returns db_error; all payout call sites render
a clear "payout failed" notice instead of crashing the interaction.
- Instant "kohv" consumable now cancels the pending reminder DMs for the
cooldowns it wipes (via new INSTANT_RESET_COMMANDS), so no stale/duplicate
reminders fire.
- Renamed the misleadingly-named _refund_user_safe -> _debit_house_safe (it
debits the house) and dropped its ignored first arg.
- Added __all__ to vanity.py and consumables.py so `import *` no longer leaks
incidental imports into the economy namespace.
- Documented Kõrvaklapid's +25 coin daily bonus in README and DEV_NOTES.
Tests: blackjack payout DB-failure safety and INSTANT_RESET_COMMANDS lockstep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
The economy had many coin faucets but almost no sinks: the /shop is
one-time ownership, and gambling/rob fines route to the house (which
players drain back via jackpots and heists), so they recirculate rather
than destroy coins. Result: steady inflation.
Add a consumables shop as a true recurring sink - buying destroys the
coins and grants a temporary boost, so there's always something to spend
on after gear is maxed:
- Energiajook XL (500) - 1h of 2x earnings on /work, /beg, /crime
- XP jook (500) - 1h of 2x EXP
- Kohv (300) - instantly clears all cooldowns
Timed buffs live in a new active_buffs field ({kind: expiry_iso}), pruned
on read; rebuying extends the timer. Effects hook where they belong:
earn_mult in income.do_work/do_beg/do_crime, exp_buff_mult in
levels.award_exp; kohv is self-contained. New /consumables command browses
the menu (with active buffs) or buys a boost. Covered by 9 tests.
Note: active_buffs is a new PocketBase field - run
scripts/sync_pb_schema.py before deploying or buffs won't persist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>