Imported from oddgoo/bird-rpg (
AGENTS.md). Install upstream withnpx skills add oddgoo/bird-rpg. Copyright stays with the author.
AGENTS.md
This file provides guidance to coding agents when working with code in this repository.
Quick Playbook
- Mission: keep the bird-themed Discord RPG running smoothly and in-theme.
- Before changing gameplay logic or user-facing copy, read
README.mdfor setup/run basics and skimtemplates/help.htmlfor command behavior and tone. - Stack: Discord bot (
discord.pyslash commands incommands/) plus Flask web server (web/server.py) with templates intemplates/and assets instatic/. - Data: game state is persisted in Supabase via helpers in
data/storage.py; local reference/config data and backups still use files underconfig.config.DATA_PATH. - Runtime: create a venv,
pip install -r requirements.txt, set required env vars, and runpython bot.py(starts bot + web server). - Testing: run
pytest ./tests; prefer targeted tests near changed logic. - Web/UI: keep styles aligned with the established mystical/papyrus vibe in
templates/base.html. - Bot commands: add cogs in
commands/, register via the existing startup/sync pattern, and respect limits inconfig/config.py. - Data safety: when stored shapes change, add safe migrations and avoid breaking existing data.
- Library docs: use Context7 MCP docs for library-specific details.
Project Overview
Bird RPG is a bird-themed Discord bot game with an accompanying Flask web server. Players collect birds, tend gardens, and engage in community activities.
Commands
# Setup
python3 -m venv venv
source ./venv/bin/activate # Mac/Linux
pip install -r requirements.txt
# Run the bot (starts both Discord bot and Flask server on port 10000)
python bot.py
# Run tests (use venv python)
venv/bin/pytest ./tests
# Run a single test file
venv/bin/pytest ./tests/test_manifest.py
# Run a specific test
venv/bin/pytest ./tests/test_manifest.py::test_function_name
# Migrate JSON data to Supabase (one-time)
python scripts/migrate_to_supabase.py
Set DEBUG=true in .env to enable debug commands and template auto-reload.
Environment Variables
Required in .env:
DISCORD_TOKEN- Discord bot tokenSUPABASE_URL- Supabase project URL (e.g.https://your-project.supabase.co)SUPABASE_KEY- Supabase anon/public keyADMIN_PASSWORD- Password for web admin panelDEBUG- Set totruefor debug mode (optional)XENO_CANTO_API_KEY- Xeno-canto API key for bird song audio in/singcommands (optional, falls back to bundled MP3s)WEB_BASE_URL- Base URL for the web server (optional, defaults tohttp://localhost:10000). Used for generating decorator links.
Architecture
Dual-server design: bot.py starts both the Discord bot (discord.py with slash commands) and Flask web server in a separate thread.
Commands: Discord slash commands live in commands/ as cogs. Each cog class inherits from commands.Cog and registers via async def setup(bot). Add new commands by creating a cog file and adding it to the COGS list in bot.py.
Web: Flask routes in web/server.py, templates in templates/ (Jinja2), static assets in static/. templates/base.html provides the layout with a mystical/papyrus aesthetic. Route handlers are split into web/home.py, web/research.py, web/birdwatch.py, web/awards.py, web/admin.py, and web/decorator.py. Navigation menu is in templates/components/navbar.html.
Data storage: Supabase (PostgreSQL) via the supabase-py library. All game state is stored in ~20 normalized tables (players, player_birds, eggs, daily_actions, etc.).
data/db.py- Supabase client singleton (get_sync_client()for Flask,get_async_client()for Discord commands)data/storage.py- All database operations as async functions (with_syncsuffixed variants for Flask routes)data/models.py- Game logic functions (async) that call storage.py
Reference data files (read-only, bundled with code):
data/bird_species.json- Base bird species catalogdata/plant_species.json- Base plant species catalogdata/treasures.json- Treasure/sticker itemsdata/human_entities.json- Human spawner datadata/research_entities.json- Research system config (default event)data/research_entities_digraa2026.json- Research entities for digraa2026 eventdata/realm_lore.json- Realm narrative messages
Birdwatch: /birdwatch command accepts image attachments via discord.Attachment. Images are resized (max 1920px) and compressed to JPEG with Pillow before uploading to a Supabase Storage public bucket (birdwatch-images). Requires the bucket to be created manually in the Supabase dashboard.
Visual Decorator: /decorator command generates a temporary URL (1-hour expiry) that opens a web-based drag-and-drop sticker decorator. Players can visually place, rotate, resize, reorder, and remove stickers on birds, plants, or their nest. Token management is in web/decorator_tokens.py (in-memory, thread-safe). Routes are in web/decorator.py. The decorator template (templates/decorator.html) uses vanilla JS with Pointer Events for unified mouse/touch handling. Treasure tables (bird_treasures, plant_treasures, nest_treasures) include rotation (degrees), z_index (layering), and size (percentage, default 25%) columns.
Birdsong audio: /sing and /sing_repeat commands attach a short MP3 of a bird song from a random bird in the singer's collection. utils/birdsong_audio.py fetches audio from xeno-canto API v3 (requires XENO_CANTO_API_KEY), with an in-memory LRU cache (50 entries). Falls back to bundled MP3s in static/audio/birdsongs/ when the API is unavailable. Audio failure never blocks the text response.
Configuration: config/config.py holds limits (MAX_BIRDS_PER_NEST, MAX_GARDEN_SIZE), birdwatch image settings, Supabase connection config, and environment variables. Game constants in constants.py and data/constants.py.
Database Schema
Schema SQL is at scripts/schema.sql. Incremental SQL migrations for existing DBs live in scripts/migrations/. Key tables:
players- Player data (twigs, seeds, inspiration, etc.)player_birds/player_plants- Birds and plants owned by players (nullablegroup_namecolumn for user-defined grouping)common_nest- Shared community nest (singleton)eggs/egg_multipliers/egg_brooders- Egg incubation systemdaily_actions/daily_songs/daily_brooding- Daily activity trackingmanifested_birds/manifested_plants- Community-created speciesresearch_progress/exploration- Progression systemsbirdwatch_sightings- User-uploaded bird photos (stored in Supabase Storagebirdwatch-imagesbucket)game_settings- Key-value config (e.g.active_eventfor the event system,weather_locationfor configurable weather coordinates)
RPC functions provide atomic operations:
increment_common_nest/increment_player_field- Atomic field incrementsincrement_research_progress- Atomic research progress upsertincrement_exploration_points- Atomic exploration upsert (returns new total)upsert_released_bird_atomic- Atomic released bird count increment
Key Patterns
- Async/sync split: Discord commands use async DB functions; Flask routes use
_syncsuffixed variants - Atomic operations: Use
db.increment_player_field()/db.increment_common_nest()(RPC calls) instead of read-modify-write for numeric fields.db.increment_research(),db.increment_exploration(), anddb.upsert_released_bird()also use atomic RPCs. - Read-only player lookup: Use
db.get_player_sync(user_id)for web routes that should NOT auto-create players (returnsNoneif not found). Usedb.load_player_sync()only when auto-creation is intended. - First-action-of-day checks:
is_first_action_of_type(user_id, type)checks a single action type.is_first_action_of_any_type(user_id, types)checks multiple related types (e.g.["build", "build_common"]) to prevent bonuses firing twice for personal vs common nest actions. - Gardening pure helpers:
can_afford_plant(),has_garden_space(),calc_compost_refund()indata/models.pyare pure functions used by bothcommands/gardening.pyand tests. - Admin routes require POST: Destructive admin routes (
purge_old_actions,download_species_images) usemethods=['POST']to prevent CSRF via GET requests. - No load-everything pattern: Each command makes targeted DB calls (SELECT/INSERT/UPDATE) instead of loading all data
- Cached reference data: Read-only JSON files (
treasures.json,research_entities.json) are cached at module level. Usedata.models.load_treasures()anddata.storage.load_research_entities(event)instead of reading files directly.commands/foraging.py:load_treasures()delegates to the cached version.load_bird_species()/load_bird_species_sync()are also cached; callclear_bird_species_cache()after manifesting a new bird. - Group/ungroup commands:
/groupand/ungroupincommands/customisation.pylet players organize birds and plants into named groups. Groups display as subheadings on the user profile page (templates/user.html). The template uses Jinja2 macros (bird_card,plant_card) and data-attribute-based JS for image loading. - Batch singing operations:
_process_singing()incommands/singing.pyuses batch DB operations (db.get_sung_to_targets_today(),db.record_songs_batch()) andasyncio.gather()for concurrent bonus actions to minimize DB round-trips. Bird bonuses and inspiration are pre-computed once before the loop. - Batch brooding target discovery:
/brood_alland/brood_randomincommands/incubation.pynow prefilter with batch DB helpers (db.get_eggs_for_users(),db.get_bird_counts_for_users(),db.get_brooded_targets_today()) to avoid per-player query loops. The command resolves guild members from cache first (guild.get_member) and only falls back to bounded concurrentfetch_membercalls. - Event system: The
game_settingstable storesactive_event(default:"default"). Toggle events directly in Supabase.load_research_entities(event)loads event-specific JSON files (research_entities_{event}.json).load_all_research_entities()combines entities from all events for milestone bonus calculations. The/studydropdown shows 8 shuffled authors from the active event. - Weather location: The
game_settingstable storesweather_locationas a JSON string withlatitude,longitude,timezone, andnamefields. Defaults to Melbourne/Naarm if not set. Change it in Supabase when switching events to update/weatherand daily weather updates. - Research entity format: All research entity JSON files use
{"milestone": "+1 Bird Limit"}(single string). The milestone repeats for every threshold. Use_get_milestone_type(entity)helper indata/models.pyto read the milestone type. - Bulk-fetch helpers: Use
db.get_bird_treasures_for_birds_sync(bird_ids)for batch bird treasure lookups instead of per-bird queries - Slash commands auto-sync on bot startup
- Use
utils.logging.log_debugfor debug output - Time functions in
utils/time_utils.pyuse Australian timezone templates/help.htmlis the authoritative user-facing command guide - update when gameplay changes
Testing
Tests in tests/ use pytest with pytest-asyncio for async tests. Tests mock the Supabase client via unittest.mock.AsyncMock on data.storage functions. Prefer targeted tests near changed logic.
# Run all tests
venv/bin/pytest ./tests
# Run a single test file
venv/bin/pytest ./tests/test_birdsong_audio.py
# Run a specific test by name
venv/bin/pytest ./tests/test_manifest.py::test_function_name
# Verbose output
venv/bin/pytest ./tests -v
Workflow Rules
- Read this file before every task: Before starting any change, read
AGENTS.mdto align with current commands, patterns, and architecture notes. - Update this file after every task: After completing any task, review
AGENTS.mdand update it with any new relevant information (new commands, patterns, architecture changes, etc.) so it stays accurate. - Use MCP Context7 for documentation lookups: When you need documentation for any library or framework, always use the Context7 MCP tools (
resolve-library-idthenquery-docs) to get up-to-date docs instead of relying on training data.