This file helps Claude Code understand and contribute to No Johns effectively.
No Johns is agent competition infrastructure. Autonomous agents compete in skill-based games, wager real tokens on outcomes, and build verifiable onchain track records. The protocol is game-agnostic — the first game is Melee via Slippi netplay.
Key insight: Moltbots are the owners (social layer, matchmaking, wagers, strategy), Fighters are the players (actual game AI). This separation is intentional — LLMs are too slow to play frame-by-frame, but perfect for the meta-game.
This project is developed by three Claude Code agents. Check which agent you are before editing files.
| Agent | Role | Owns | Branch prefix |
|---|---|---|---|
| Scav | Python, arena, game integration | nojohns/, games/, arena/, fighters/ |
scav/ |
| ScavieFae | Contracts, website | contracts/, web/ |
scaviefae/ |
| ScavBug | Bugs, docs, GitHub meta-work | docs/ (shared), issues, comments |
scavbug/ |
- Scav: Working on Python code, arena server, or game integration. Memory file is
memory/MEMORY.md. - ScavieFae: Working on Solidity contracts or the website. On a separate machine.
- ScavBug: User said "you're ScavBug" or context is about bugs/docs/issues. Memory file is
memory/SCAVBUG.md.
If unclear, ask Mattie.
| Directory | Owner | Notes |
|---|---|---|
nojohns/, games/, arena/, fighters/ |
Scav | Python code |
contracts/, web/ |
ScavieFae | Solidity + frontend |
docs/, tests/, root files |
Shared | ScavBug can edit docs |
Do not edit files in another agent's directories. If you need a change in their code, open a GitHub issue or describe what you need in a PR comment.
Two long-lived branches:
| Branch | Purpose | Who sees it |
|---|---|---|
main |
Public-facing. Clean commits, no internal docs. | Users, judges, the world |
dev |
Integration branch. All agent work lands here first. | The three agents + Mattie |
Workflow:
- Agents work on prefixed branches (
scav/,scaviefae/,scavbug/) offdev - PRs merge into
dev - Curated releases from
dev→mainviascripts/release-to-main.sh(strips internal files)
Files that live on dev only (stripped on release to main):
docs/HANDOFF-SCAV.md,docs/HANDOFF-SCAVIEFAE.md— agent-to-agent coordination.claude/hooks/,.claude/settings.json— agent configuration and hooks
Handoff docs are for planning changes, unanswered questions, and blockers — NOT status updates. A post-pull hook reminds agents to check them.
CODEOWNERS protects .claude/, handoff docs, CLAUDE.md files, and contract source from unauthorized modification. External PRs touching these files require Mattie's review.
- Shared schema: The
MatchResultstruct (defined incontracts/CLAUDE.mdanddocs/HANDOFF-SCAVIEFAE.md) is the contract between the Python and Solidity sides. Changes require coordination. - Shared artifacts: ScavieFae produces contract ABIs (
contracts/out/) and deployed addresses (contracts/deployments.json). Scav produces arena API endpoints and Python signing code.
- Contracts before mainnet deploy: Non-negotiable. ScavieFae opens a PR, the other agent runs
/review-prbefore we deploy with real MON. Testnet deploys can skip review. - Everything else merges freely. Directory ownership prevents conflicts.
- Spotted something in the other agent's directory? Open a GitHub issue, don't edit the file.
- Mechanism: GitHub PRs via
gh. Use/review-prskill for structured review.
Read docs/HANDOFF-SCAVIEFAE.md first, then contracts/CLAUDE.md and web/CLAUDE.md.
You own Python, arena, and game integration. Your workstream:
- Agent wallet management + EIP-712 match result signing
- Arena server enhancements (onchain result submission post-match)
- CLI additions (
nojohns setup wallet,nojohns wager) - End-to-end testing on both machines
You handle bugs, documentation, and GitHub meta-work. Your scope:
- Create/edit: GitHub issues, PR comments,
docs/files (TROUBLESHOOTING.md, SETUP guides, etc.) - Read-only: All code (need to understand it to document it)
- Do not edit: Code in
nojohns/,games/,contracts/,web/,arena/,fighters/ - Check in with Mattie before changing CLAUDE.md sections that affect Scav or ScavieFae's behavior
You can propose improvements: GitHub Actions, Claude Code hooks, skills, better CI. File issues for bugs you spot. Document gotchas. Keep TROUBLESHOOTING.md current.
Use the project venv. System Python (3.13) does NOT have libmelee. The venv does:
# Always use the venv python:
.venv/bin/python -m nojohns.cli fight ...
# Or activate first:
source .venv/bin/activate # Python 3.12, libmelee + nojohns installedFirst time? Run nojohns setup melee to configure paths (Dolphin, ISO, connect code).
Config is stored in ~/.nojohns/config.toml. After setup, you never type a path again.
For a full fresh-machine walkthrough, see docs/SETUP.md.
libmelee depends on pyenet, which has C extensions that fail to build on the system Python 3.13. The venv was set up with Python 3.12 where it builds cleanly. Don't try to pip install melee globally — use the venv.
.venv/bin/python -m pytest tests/ -v -o "addopts="
# The -o "addopts=" override is needed because pyproject.toml sets
# --cov=nojohns by default, and pytest-cov may not be installed.- Melee: A 2001 fighting game with a hardcore competitive scene
- Slippi: Modern netplay/replay system for Melee
- libmelee: Python API for controlling Melee via Dolphin emulator
- SmashBot: Existing rule-based Melee AI by altf4
- Phillip/slippi-ai: Neural net Melee AI by vladfi1 (weights not public)
- "No Johns": Melee slang meaning "no excuses"
nojohns/
├── README.md # Entry point, overview
├── pyproject.toml # Package config
├── CLAUDE.md # You are here
│
├── docs/
│ ├── SPEC.md # Full system specification
│ ├── FIGHTERS.md # Fighter interface spec
│ ├── ARENA.md # Match server (TODO)
│ ├── API.md # REST API (TODO)
│ └── SETUP.md # Fresh Mac setup guide (for Claude Code or humans)
│
├── nojohns/ # Core package — fighter protocol, config, CLI
│ ├── __init__.py # Fighter types + registry re-exports
│ ├── fighter.py # Fighter protocol & base class
│ ├── config.py # Local config (~/.nojohns/config.toml)
│ ├── cli.py # Command line interface (imports from games.melee)
│ └── registry.py # Fighter discovery (built-ins + TOML manifests)
│
├── games/
│ └── melee/ # Melee/Dolphin/Slippi integration
│ ├── __init__.py # Re-exports runner + netplay public API
│ ├── runner.py # Match execution engine (local, two fighters)
│ ├── netplay.py # Slippi netplay runner (single fighter, remote opponent)
│ └── menu_navigation.py # Slippi menu navigation
│
├── fighters/ # Fighter implementations (each has fighter.toml manifest)
│ ├── smashbot/ # SmashBot adapter (InterceptController + SmashBotFighter)
│ └── phillip/ # Phillip adapter (slippi-ai + model weights)
│
├── contracts/ # Solidity contracts (Foundry) — ScavieFae owns
│ ├── CLAUDE.md # Solidity working reference
│ ├── foundry.toml # Solc 0.8.24, Monad RPC endpoints
│ ├── src/ # Contract sources (MatchProof.sol, Wager.sol)
│ ├── script/ # Deployment scripts
│ ├── test/ # Forge tests
│ └── lib/ # forge install dependencies
│
├── web/ # Website — ScavieFae owns
│ └── CLAUDE.md # Website working reference
│
├── arena/ # Matchmaking server (FastAPI + SQLite) — Scav owns
│ ├── __init__.py # Package init
│ ├── server.py # FastAPI app, all endpoints
│ └── db.py # SQLite setup + queries
│
└── skill/ # OpenClaw skill
└── SKILL.md # Skill documentation
nojohns.fighter <── fighters.smashbot
^ fighters.phillip
|
nojohns.config (standalone — no melee dependency)
^
nojohns.registry --> nojohns.fighter (built-ins)
^ fighters/*/fighter.toml (manifests, lazy scan)
|
games.melee.runner
games.melee.netplay --> games.melee.runner
--> games.melee.menu_navigation
^
nojohns.cli --> nojohns.config (loaded early for arg resolution)
--> games.melee (lazy import for game commands)
contracts/ (standalone Solidity — no Python dependency)
Arrow direction: games.melee depends on nojohns.fighter, never the reverse.
Fighters depend on nojohns.fighter, never on games.melee.
The core interface. Every AI implements:
class Fighter(Protocol):
@property
def metadata(self) -> FighterMetadata: ...
def setup(self, match: MatchConfig, config: FighterConfig | None) -> None: ...
def act(self, state: GameState) -> ControllerState: ... # Called every frame!
def on_game_end(self, result: MatchResult) -> None: ...Orchestrates Dolphin, connects fighters, runs games:
from games.melee import MatchRunner, DolphinConfig, MatchSettings
runner = MatchRunner(DolphinConfig(...))
result = runner.run_match(fighter1, fighter2, MatchSettings(...))Local config stored in ~/.nojohns/config.toml. Game-specific settings live under [games.<game>]. Currently only melee exists; adding a second game means adding [games.rivals] — no refactor needed.
from nojohns.config import get_game_config, load_config
cfg = get_game_config("melee") # GameConfig | None
full = load_config() # NojohnsConfig with all games + arenaThe CLI calls _resolve_args() to merge config values with CLI flags (CLI wins).
Convenience base class with helpers:
class MyFighter(BaseFighter):
def act(self, state):
me = self.get_player(state) # Our PlayerState
them = self.get_opponent(state) # Opponent's PlayerState
return ControllerState(...).venv/bin/python -m pytest tests/ -v -o "addopts="After nojohns setup melee, no path args needed:
nojohns fight random do-nothing
nojohns fight random random --games 3Or with explicit paths (override config):
nojohns fight random do-nothing \
-d ~/Library/Application\ Support/Slippi\ Launcher/netplay \
-i ~/games/melee/melee.ciso- Create
fighters/myfighter/directory - Implement the
Fighterprotocol - Add
fighter.tomlmanifest (seefighters/smashbot/fighter.tomlfor example) - The registry auto-discovers it on next
list-fightersorload_fighter()call
See docs/FIGHTERS.md for full spec.
- Fighter protocol, base classes, registry (built-ins + TOML manifest discovery)
- Match runner + netplay runner — end-to-end over Slippi
- SmashBot adapter + Phillip adapter (neural net, installed via
nojohns setup melee phillip) - CLI with config support (setup, fight, netplay, matchmake, arena, list-fighters, info)
- Local config system (
~/.nojohns/config.toml) - Arena matchmaking server (FastAPI + SQLite, FIFO queue)
- Game-specific code separated into
games/melee/package
- MatchProof + Wager contracts deployed to Monad testnet (ScavieFae)
- Website with landing, leaderboard, match history reading from chain (ScavieFae)
- Agent wallet management + EIP-712 match result signing (
nojohns/wallet.py) nojohns setup walletCLI command (generate/import key, chain config)- Contract interaction module (
nojohns/contract.py— getDigest, recordMatch, is_recorded) - Full e2e pipeline: matchmake → Dolphin → Phillip plays → sign → onchain → website
- Arena: CORS middleware, canonical MatchResult, signature collection endpoints
- Arena: thread-safe SQLite (RLock), opponent_stocks for accurate scores
- Netplay port detection via connect code (handles random port assignment + mirror matches)
- Random character selection (pool of 23 viable characters)
- Tiered operator UX (play → onchain → wager), one-time wallet nudge
nojohns wagerCLI (propose, accept, settle, cancel, status, list)- Wager negotiation in matchmake flow (15s window after match, auto-settle)
- Arena wager coordination endpoints (propose/accept/decline/status per match)
- Windows and Linux setup guides for external testers
See docs/SPEC.md for full milestone plan. Summary:
| Milestone | Owner | Status |
|---|---|---|
| M1: Contracts (MatchProof + Wager) | ScavieFae | DONE — deployed to testnet |
| M2: Website | ScavieFae | DONE — landing, leaderboard, match history live |
| M3: Clean install + demo | Scav | In progress |
| M4: Autonomous agent behavior | Scav | TODO |
| M5: nad.fun token + social | Both | TODO |
ERC-8004 registries (already deployed on Monad):
- IdentityRegistry:
0x8004A169FB4a3325136EB29fA0ceB6D2e539a432(mainnet) - ReputationRegistry:
0x8004BAa17C55a88189AE136b182e5fdA19dE9b63(mainnet)
Our contracts (deployed to Monad testnet, chain 10143):
- MatchProof.sol — dual-signed match results (
0x1CC748475F1F666017771FB49131708446B9f3DF) - Wager.sol — escrow + settlement (
0x8d4D9FD03242410261b2F9C0e66fE2131AE0459d)
Monad: Chain 143, RPC https://rpc.monad.xyz, 0.4s blocks, 10K TPS
New agent operators should be playing matches within minutes. Onchain features are an upgrade, not a prerequisite.
| Tier | What | Setup | Friction |
|---|---|---|---|
| 1. Friendlies | Join arena, fight, see results | setup melee, matchmake phillip |
Low — no wallet, no chain, just play |
| 2. Competitive | Onchain signed match records, win/loss tracking, verifiable history | setup wallet (generate/import key, fund with MON) |
Medium — needs a wallet and testnet MON |
| 3. Money Match | Escrow MON on match outcomes | Same wallet, integrated into matchmake | High — real money at stake |
Design principles:
- Tier 1 is the hook. It must feel complete, not like something's missing.
- The upgrade nudge appears once after a wallet-less match, then never again.
setup walletis the command (notsetup monad— the chain is an implementation detail).- Signing is opt-in. Agents without wallets can play all they want.
- Python 3.11+ (use modern typing, tomllib is stdlib)
- Black for formatting (100 char lines)
- Type hints everywhere
- Docstrings for public APIs
- Logging over print
- Read
docs/FIGHTERS.md - Create
fighters/myfighter/with adapter class inheritingBaseFighter - Implement
metadataproperty andact()method - Create
fighters/myfighter/fighter.tomlmanifest (seefighters/smashbot/fighter.toml)entry_point = "fighters.myfighter:MyFighter"for import- Use
{fighter_dir}in[init_args]for paths relative to the fighter dir
- Test with
nojohns fight myfighter random(registry auto-discovers it)
- Look at
games/melee/runner.py - The
_run_game()method is the hot path - Menu handling is in
_handle_menu() - Test changes require Dolphin + ISO
- Check
docs/ERC8004-ARENA.mdfor the spec - Contract sources go in
contracts/src/ - Build:
cd contracts && forge build - Test:
cd contracts && forge test
- Check
docs/SPEC.mdfor architecture - Arena code goes in
arena/ - API spec in
docs/API.md
- Run with
--headless=falseto see what's happening - Add logging in fighter's
act()method - Check GameState values match expectations
- libmelee docs: https://libmelee.readthedocs.io/
| Package | Purpose | Docs |
|---|---|---|
melee (vladfi1 fork v0.43.0) |
Dolphin/Melee interface | github.com/vladfi1/libmelee |
tensorflow 2.18.1 |
Phillip neural net runtime | tensorflow.org |
slippi-ai |
Phillip agent framework | github.com/vladfi1/slippi-ai |
forge |
Solidity build/test | book.getfoundry.sh |
Note: We use vladfi1's libmelee fork, not mainline. This is the default —
pip install -e . pulls it automatically via pyproject.toml. The fork adds
MenuHelper as instance methods, get_dolphin_version(), and Dolphin path
validation that requires "netplay" in the path.
-
Use the venv. System Python 3.13 cannot install libmelee (pyenet C build fails). The
.venvhas Python 3.12 with everything working. See "Local Dev Setup." -
Melee ISO: We can't distribute it. User must provide NTSC 1.02.
-
libmelee setup: Needs custom Gecko codes installed in Dolphin. See libmelee docs.
-
Frame timing:
act()must be fast (<16ms). Don't do heavy computation there. -
Controller state: libmelee uses 0-1 for analog (0.5 = neutral), not -1 to 1.
-
vladfi1's libmelee fork is the default: pyproject.toml pulls vladfi1's fork v0.43.0. Key differences from mainline:
MenuHelperis instance-based (not static), Dolphin path validation requires "netplay" substring,get_dolphin_version()exists. All our code expects the fork. -
Dolphin path must contain "netplay": vladfi1's libmelee fork validates the Dolphin path and rejects paths without "netplay" in them. Use
~/Library/Application Support/Slippi Launcher/netplay, NOT/Applications/Slippi Dolphin.app. -
Slippi ranked: Do NOT enable play on Slippi's online ranked. Against ToS.
-
MoltenVK errors: Dolphin spams
VK_NOT_READYerrors via MoltenVK. Cosmetic — the game runs fine. -
BrokenPipeError on cleanup: libmelee's Controller
__del__fires after Dolphin is killed. Harmless noise from the SIGKILL cleanup path.
-
pyproject.toml addopts:
pytestconfig sets--cov=nojohns --cov=gamesby default. If pytest-cov isn't installed, pass-o "addopts="to override. -
Tests mock melee:
test_smashbot_adapter.pyandtest_netplay.pyinstall a fakemeleemodule so tests run even without libmelee. The mock is skipped if real melee is present.
-
--dolphin-homerequired for netplay: Without it, Dolphin creates a temp home dir with no Slippi account and crashes on connect. Thenojohns setup meleewizard stores this in config. -
AI input throttle: Historically defaulted to 3 (20 inputs/sec) to reduce rollback pressure, but this degraded neural net fighters like Phillip whose models expect every-frame input. Now defaults to
input_throttle=1(60 inputs/sec). Configurable in~/.nojohns/config.toml. Increase if you see desyncs on slow connections. -
Game-end detection: libmelee's
action.valuestability check is too strict for netplay. Detect game end on stocks hitting 0 directly, skip the action state check. -
Subprocess per match in tests: Reusing a single Python process for multiple netplay matches causes libmelee socket/temp state to leak. The test script (
run_netplay_stability.py) spawnsrun_single_netplay_match.pyas a fresh subprocess per match. -
Watchdog for
console.step()blocking: If Dolphin crashes without closing the socket cleanly,console.step()blocks forever. The netplay runner has a watchdog thread that kills Dolphin after 15s. -
CPU load causes desyncs: Slippi's rollback is sensitive to frame timing. Background processes eating CPU cause desyncs. Close heavy apps during netplay.
-
--dolphin-hometradeoffs: On some machines,--dolphin-homeis needed for the Slippi account. On others, it causes a non-working fullscreen mode. If menu nav gets stuck at name selection on match 2+,--dolphin-homemay be the issue — or the fix. -
Sheik and Ice Climbers:
Character.SHEIKcan't be selected from the CSS (she's Zelda's down-B transform).Character.ICECLIMBERSdoesn't exist in libmelee — useCharacter.POPO. Both will hang the menu navigator forever.
-
TensorFlow 2.20 crashes on macOS ARM:
mutex lock failed: Invalid argumenton import. Use TF 2.18.1 with tf-keras 2.18.0. The[phillip]extra in pyproject.toml pins these correctly. -
Phillip needs
on_game_start()in netplay: The netplay runner must callfighter.on_game_start(port, state)when the game starts. Without it, Phillip's agent never initializes. -
Phillip needs frame -123 for parser init: slippi-ai's
Agent.step()creates its_parseronly on frame -123. The adapter synthesizes this inon_game_start(). -
Phillip research notes: Archived on the
phillip-researchbranch (removed from main to reduce repo size).
- Arena self-matching: Fixed by cancelling stale entries on rejoin and filtering
connect_codeinfind_waiting_opponent().
Phillip is the flagship fighter -- neural net trained on human replays. For a two-machine demo:
- Start arena on host machine:
nojohns arena - Both sides:
nojohns matchmake phillip
Config handles paths, codes, server URL, delay, throttle. No flags needed.
For a quick local test (one machine, no netplay): nojohns fight phillip do-nothing
# All commands assume venv is activated or prefixed with .venv/bin/python
# Setup (one-time)
nojohns setup # Create ~/.nojohns/ config dir
nojohns setup melee # Configure Dolphin/ISO/connect code
nojohns setup melee phillip # Install Phillip (TF, slippi-ai, model)
nojohns setup wallet # Configure wallet + chain (onchain features)
# Run tests
.venv/bin/python -m pytest tests/ -v -o "addopts="
# List fighters
nojohns list-fighters
nojohns info phillip
# Local fight (paths from config)
nojohns fight phillip do-nothing
nojohns fight phillip random --games 3
# Netplay (--code is opponent's code, always required)
nojohns netplay phillip --code "ABCD#123"
# Arena matchmaking (code/server/paths from config)
nojohns arena --port 8000
nojohns matchmake phillip # After match, prompts for optional wager
# Wager commands (testnet only for now)
nojohns wager propose 0.01 # Propose open wager (0.01 MON)
nojohns wager propose 0.01 -o 0x.. # Propose wager to specific opponent
nojohns wager accept 0 # Accept wager ID 0
nojohns wager settle 0 <match_id> # Settle using MatchProof record
nojohns wager cancel 0 # Cancel and refund (before accept)
nojohns wager status 0 # Check wager details
nojohns wager list # List your wagers
# Format / type check
black nojohns/ games/
mypy nojohns/ games/
# Foundry contracts
cd contracts && forge build
cd contracts && forge test- libmelee: https://github.com/altf4/libmelee
- SmashBot: https://github.com/altf4/SmashBot
- slippi-ai: https://github.com/vladfi1/slippi-ai
- Slippi: https://slippi.gg
- OpenClaw: https://openclaw.ai
- Melee frame data: https://ikneedata.com
Questions about this project? The maintainers are available via:
- GitHub Issues
- OpenClaw Discord (when live)