README.md

Tatakae #

A Pokémon-style battle arena for the AT Protocol / Bluesky. Log in with your Atmosphere account, enter any public handle, and watch the two accounts fight it out with stats, moves, and procedural pixel-art sprites.

Features #

  • Account analysis via DeepInfra (Llama 3.3 70B): type, six stats, four moves.
  • Build stat derived from the number of distinct AT Protocol record collections the account publishes to (posts, likes, follows, custom lexicons, etc.).
  • Procedural pixel-art sprites generated deterministically from handle + type.
  • Turn-based battle engine with type effectiveness, STAB, accuracy, and RNG.
  • AT Protocol OAuth login required to battle.
  • Battle records written to the player's PDS using the custom rodeo.vibecode.tatakae.battle.record lexicon.
  • Leaderboard and battle history tracked in a local SQLite database.
  • SQLite cache for generated fighters so repeated battles are cheap.

Tech stack #

  • Python 3.10+
  • FastAPI + Jinja2 + vanilla JS
  • atproto SDK for handle/DID resolution
  • Custom OAuth 2.0 / DPoP implementation for login and PDS writes
  • DeepInfra OpenAI-compatible API for LLM analysis
  • SQLite for caching, sessions, and leaderboard

Local setup #

cd tatakae
python3 -m venv venv
./venv/bin/pip install -U pip
./venv/bin/pip install -r requirements.txt

# Copy and fill in the environment variables.
cp .env.example .env
# Edit .env and set DEEPINFRA_API_KEY, APP_URL, and SECRET_KEY.

# Run the server.
./venv/bin/uvicorn main:app --reload

Open http://localhost:8000.

Environment variables #

Variable Required Description
DEEPINFRA_API_KEY Yes API key for DeepInfra.
CHAT_MODEL No Default: meta-llama/Llama-3.3-70B-Instruct.
APP_URL No Default: http://localhost:8000. Must be HTTPS in production.
PORT No Default: 8000.
SECRET_KEY Yes Secret for signing session cookies.

Project structure #

tatakae/
├── main.py              # FastAPI app and routes
├── config.py            # Environment + type chart + tunables
├── db.py                # SQLite schema and cache/session/helpers
├── bsky.py              # Public AT Protocol data fetching
├── oauth.py             # AT Protocol OAuth + DPoP + PDS writes
├── llm.py               # DeepInfra chat client
├── analyze.py           # Fighter analysis pipeline
├── battle.py            # Turn-based battle engine
├── sprite.py            # Procedural SVG sprite generator
├── templates/           # Jinja2 templates
├── static/              # CSS and JS
├── lexicons/            # Custom AT Protocol lexicons
├── deploy/              # systemd, nginx, install/deploy scripts
├── requirements.txt
├── .env.example
└── .gitignore

Type chart #

Ten types and their effectiveness. Each type is strong against three and weak against three.

Type Strong against Weak against
Dunk Thread, Vibe, Art Meme, Reply, Music
Meme Dunk, Vibe, News Thread, Chaos, Code
Thread Meme, Chaos, Code Dunk, Reply, Music
Vibe Dunk, Chaos, Art Meme, Reply, News
Chaos Meme, Reply, News Thread, Vibe, Music
Reply Dunk, Thread, Music Vibe, Chaos, Code
Code Meme, Art, News Thread, Reply, Music
Art Vibe, Code, Music Dunk, Chaos, News
News Code, Art, Music Meme, Chaos, Vibe
Music Dunk, Chaos, Vibe Thread, Art, Code

Notes #

  • Battles require OAuth login. The opponent handle can be any public account.
  • After each battle, a record is written to the player's PDS under the rodeo.vibecode.tatakae.battle.record collection, and the result is stored in the local leaderboard database.
  • .env, venv/, *.db, *.db-*, and __pycache__/ are gitignored.

Deployment #

Tatakae is designed to run on an Ubuntu server behind nginx with Let's Encrypt. Deploy files are in deploy/.

Production target #

  • Domain: tatakae.vibecode.rodeo
  • Server user: tatakae
  • App directory: /opt/tatakae
  • Git remote: Tangled (git@tangled.org:did:plc:lmzx5mbszz2vrs7fe2k6sjt5)

First-time install #

Prerequisites:

  • DNS A record for tatakae.vibecode.rodeo pointing to the droplet IP.
  • (Optional) DNS TXT record _lexicon.tatakae.vibecode.rodeo for lexicon authority.

Get the code onto the server. Either clone from Tangled (if the server has a deploy key), or rsync from a local checkout:

rsync -avz --exclude='.env' --exclude='venv' --exclude='*.db' --exclude='*.db-*' \
    --exclude='__pycache__' --exclude='.git' \
    -e ssh ./tatakae/ root@<droplet-ip>:/opt/tatakae/

Then run the install script as root on the droplet:

scp tatakae/deploy/install.sh root@<droplet-ip>:/root/
ssh root@<droplet-ip> bash /root/install.sh

The install script:

  1. Installs Python, nginx, and certbot.
  2. Creates the tatakae user and /opt/tatakae directory.
  3. Creates a .env file (you must edit DEEPINFRA_API_KEY afterwards).
  4. Installs Python dependencies.
  5. Installs the systemd service.
  6. Obtains a TLS certificate from Let's Encrypt via webroot.
  7. Installs the nginx HTTPS config and starts the service.

Redeploy after code changes #

Push to Tangled, then run the deploy script on the server:

ssh root@<droplet-ip> /opt/tatakae/deploy/deploy.sh

Or redeploy directly from a local checkout:

rsync -avz --exclude='.env' --exclude='venv' --exclude='*.db' --exclude='*.db-*' \
    --exclude='__pycache__' --exclude='.git' \
    -e ssh ./tatakae/ root@<droplet-ip>:/opt/tatakae/
ssh root@<droplet-ip> 'systemctl restart tatakae'

DNS / lexicon authority #

For the custom lexicon to be publicly resolvable, create a DNS TXT record:

Name:  _lexicon.tatakae.vibecode.rodeo
Value: did=did:plc:YOUR-TATAKAE-DID

Then publish the schema record. See deploy/LEXICONS.md for details.

The app serves its own OAuth client metadata at https://tatakae.vibecode.rodeo/oauth-client-metadata.json, which Bluesky OAuth requires.

Operations #

systemctl status tatakae          # service state
journalctl -u tatakae -f -o cat   # follow logs
systemctl restart tatakae         # restart