- Python 76%
- Jinja-like 9%
- CSS 8%
- JavaScript 4%
- Shell 3%
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.recordlexicon. - 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
atprotoSDK 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.recordcollection, 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.rodeopointing to the droplet IP. - (Optional) DNS TXT record
_lexicon.tatakae.vibecode.rodeofor 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:
- Installs Python, nginx, and certbot.
- Creates the
tatakaeuser and/opt/tatakaedirectory. - Creates a
.envfile (you must editDEEPINFRA_API_KEYafterwards). - Installs Python dependencies.
- Installs the systemd service.
- Obtains a TLS certificate from Let's Encrypt via webroot.
- 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