Add long-term design consideration for forking the RPG campaign as a standalone offline experience. ARCHITECTURE.md: - Add 'Offline Standalone Fork' section explaining: - Why offline support matters (single-player RPG focus) - Architecture principles for fork compatibility - Core engine independence requirements - Potential package structures and distribution options - What stays vs what goes in a fork AGENTS.md: - Add 'Core Engine Independence' section with: - Rules for keeping app/core/ decoupled - Import boundary examples (allowed vs forbidden) - Link to full architecture docs This ensures all contributors understand the design constraint: the game engine must remain completely independent of network, database, and authentication concerns.
8.8 KiB
AGENTS.md - Mantimon TCG
Guidelines for agentic coding agents working on this codebase.
Quick Commands
Development Servers
cd frontend && npm run dev # Frontend dev server
cd backend && uv run uvicorn app.main:app --reload # Backend dev server
Testing
# Frontend (Vitest)
cd frontend && npm run test # Run all tests
cd frontend && npm run test -- path/to/file # Run single test file
cd frontend && npm run test -- -t "test name" # Run by test name
# Backend (pytest with uv)
cd backend && uv run pytest # Run all tests
cd backend && uv run pytest tests/test_file.py # Single file
cd backend && uv run pytest tests/test_file.py::test_fn # Single test
cd backend && uv run pytest -k "test_name" # By name pattern
cd backend && uv run pytest -x # Stop on first failure
cd backend && uv run pytest --cov=app # With coverage
Linting & Formatting
# Frontend
cd frontend && npm run lint # ESLint
cd frontend && npm run typecheck # TypeScript check
# Backend (uses uv for all commands)
cd backend && uv run black app tests # Format with Black
cd backend && uv run black --check . # Check formatting (CI)
cd backend && uv run ruff check . # Lint with Ruff
cd backend && uv run ruff check --fix . # Auto-fix lint issues
cd backend && uv run mypy app # Type check
Dependency Management (Backend)
cd backend && uv add <package> # Add runtime dependency
cd backend && uv add --dev <package> # Add dev dependency
cd backend && uv sync # Install all dependencies
cd backend && uv lock # Update lock file
Code Style
General Rules
- Line length: 100 characters max
- Indentation: 2 spaces (frontend), 4 spaces (backend)
- Trailing commas in multi-line structures
- Explicit over implicit
TypeScript/Vue
Import order (separated by blank lines):
- Standard library / Vue core
- Third-party packages
- Local imports (use
@/alias)
import { ref, computed } from 'vue'
import { useGameStore } from '@/stores/game'
import type { Card, GameState } from '@/types'
// Always use `import type` for type-only imports
import type { Player } from '@/types/player'
// Prefer const over let
const cards = ref<Card[]>([])
Naming conventions:
| Type | Convention | Example |
|---|---|---|
| Vue components | PascalCase | CardHand.vue, GameBoard.vue |
| Phaser scenes | PascalCase | MatchScene.ts, PackOpeningScene.ts |
| TypeScript files | camelCase | useWebSocket.ts, cardUtils.ts |
| Constants | UPPER_SNAKE_CASE | MAX_HAND_SIZE, PRIZE_COUNT |
Python
Import order (separated by blank lines):
- Standard library
- Third-party packages
- Local imports
from typing import Optional
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import Card, Player
from app.services.game_service import GameService
Type hints required for all function signatures:
async def get_card(card_id: int, db: AsyncSession = Depends(get_db)) -> Card:
...
Naming conventions:
| Type | Convention | Example |
|---|---|---|
| Modules | snake_case | game_engine.py, card_service.py |
| Database tables | snake_case | user_collections, match_history |
| Constants | UPPER_SNAKE_CASE | MAX_HAND_SIZE, PRIZE_COUNT |
Architecture Patterns
Frontend: Vue + Phaser Integration
Phaser mounts as a Vue component. Communication via event bridge:
// Vue -> Phaser
phaserGame.value?.events.emit('card:play', { cardId, targetId })
// Phaser -> Vue
phaserGame.value?.events.on('animation:complete', handleAnimationComplete)
Frontend: State Management (Pinia)
export const useGameStore = defineStore('game', () => {
const gameState = ref<GameState | null>(null)
const myHand = computed(() => gameState.value?.myHand ?? [])
return { gameState, myHand }
})
Backend: Service Layer
Never bypass services for business logic:
# CORRECT
card = await card_service.get_card(card_id)
result = await game_service.play_card(game_id, player_id, card_id)
# WRONG - direct DB access in endpoint
card = await db.execute(select(Card).where(Card.id == card_id))
Backend: Async by Default
All I/O operations use async/await:
async def resolve_attack(attacker: Card, defender: Card) -> AttackResult:
...
Testing Guidelines
Test Docstrings Required
Every test must include a docstring explaining "what" and "why":
@pytest.mark.asyncio
async def test_draw_card():
"""
Test that drawing a card moves it from deck to hand.
Verifies the fundamental draw mechanic works correctly
and updates both zones appropriately.
"""
# test implementation
Frontend Tests (Vitest)
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
describe('CardHand', () => {
it('renders cards in hand', () => {
const wrapper = mount(CardHand, {
props: { cards: [{ id: '1', name: 'Pikachu' }] }
})
expect(wrapper.text()).toContain('Pikachu')
})
})
Critical Security Rules
Hidden Information
Never expose to clients:
- Deck order (either player)
- Opponent's hand contents
- Unrevealed prize cards
- RNG seeds or future random results
# Correct: Only send counts for opponent's hidden zones
opponent_hand_count=len(opponent.hand) # ONLY count, not contents
opponent_deck_count=len(opponent.deck) # ONLY count
Server Authority
- All game logic runs server-side
- Client sends intentions, server validates and executes
- Never trust client-provided game state
Critical Rules Summary
- Git: Never commit directly to
main. Create feature branches. - Commits: Do not commit without user approval.
- Hidden Info: Never send deck contents, opponent hand, or unrevealed prizes to client.
- Validation: Always validate actions server-side. Never trust client.
- Tests: Include docstrings explaining "what" and "why" for each test.
- Phaser in Vue: Keep Phaser scenes focused on rendering. Game logic lives in backend.
- Services: Never bypass the service layer for business logic.
- Core Engine Independence: Keep
app/core/decoupled from DB/network (see below).
Core Engine Independence (Offline Fork Support)
Long-term goal: The
backend/app/core/module should remain forkable as a standalone offline game.
The game engine must stay completely decoupled from network and database concerns to enable a future offline/standalone version of the RPG campaign mode.
Rules for app/core/ Module
| DO | DON'T |
|---|---|
Accept CardDefinition objects as parameters |
Import from app.services or app.api |
Use RandomProvider protocol for RNG |
Import database session types |
Keep state self-contained in GameState |
Make network calls or database queries |
| Use sync logic (async wrappers at service layer) | Require authentication or user sessions |
Load configuration from RulesConfig objects |
Hard-code database connection strings |
Import Boundaries
# ALLOWED in app/core/
from app.core.models import CardDefinition, GameState
from app.core.config import RulesConfig
from app.core.rng import RandomProvider
# FORBIDDEN in app/core/
from app.services.card_service import CardService # NO - DB dependency
from app.api.deps import get_current_user # NO - Auth dependency
from sqlalchemy.ext.asyncio import AsyncSession # NO - DB dependency
Why This Matters
See docs/ARCHITECTURE.md#offline-standalone-fork for full details. The core engine should be directly copyable to a standalone Python application with:
- Card definitions loaded from JSON files
- Save data stored locally
- No network or authentication requirements
Project Structure
mantimon-tcg/
├── frontend/ # Vue 3 + Phaser 3
│ ├── src/
│ │ ├── components/ # Vue components
│ │ ├── pages/ # Route pages
│ │ ├── stores/ # Pinia stores
│ │ ├── game/ # Phaser scenes and game objects
│ │ └── composables/ # Vue composables
│ └── package.json
├── backend/ # FastAPI + PostgreSQL
│ ├── app/
│ │ ├── api/ # REST endpoints
│ │ ├── core/ # Game engine
│ │ ├── models/ # Pydantic + SQLAlchemy models
│ │ ├── services/ # Business logic
│ │ └── websocket/ # Socket.io handlers
│ └── pyproject.toml
└── shared/ # Shared types/schemas