Imported from mathiasfritsch/mistral-frogger (
AGENTS.md). Install upstream withnpx skills add mathiasfritsch/mistral-frogger. Copyright stays with the author.
AGENTS.md - Frogger Game Project (PixiJS + Vite + TypeScript)
This file provides project-specific guidance for Mistral Vibe when working on the Frogger game.
⚠️ ALWAYS USE LATEST PIXIJS v8+ API - See the Deprecated vs Latest Methods section below.
Project Overview
Project: Frogger game clone
Framework: PixiJS v8.8.1 - The HTML5 2D WebGL renderer
Bundler: Vite 6.2.0
Language: TypeScript 5.7.3
Linting: ESLint + Prettier
Working Directory: /frogger/ (all commands run from here)
Project Structure
frogger/
├── public/
│ ├── assets/ # Static assets (images, sprites)
│ │ └── logo.svg # Logo
│ ├── favicon.png
│ └── style.css # Global styles
├── src/
│ ├── main.ts # Entry point - PixiJS application (frog drawn via Graphics API)
│ └── vite-env.d.ts # Vite TypeScript declarations
├── index.html # HTML entry point
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── vite.config.ts # Vite configuration
└── eslint.config.mjs # ESLint configuration
Essential Commands
Development
# Install dependencies
npm install
# Start development server (port 8080, auto-opens browser)
npm run dev
# Or explicitly
npm start
Building
# Lint, type-check, and build for production
npm run build
# Just lint
npm run lint
PixiJS Quick Reference (v8+)
Core Imports
import { Application, Assets, Sprite, Container, Text, Graphics } from "pixi.js";
Basic Application Setup
const app = new Application();
await app.init({
background: "#1099bb",
resizeTo: window,
width: 800,
height: 600
});
document.getElementById("pixi-container")!.appendChild(app.canvas);
Loading Assets
// Single asset
const texture = await Assets.load("/assets/sprite.png");
// Multiple assets
const textures = await Assets.load([
"/assets/player.png",
"/assets/enemy.png"
]);
Creating Sprites
const sprite = new Sprite(texture);
sprite.pivot.set(0.5, 0.5); // Center pivot (v8: use pivot, not anchor)
sprite.position.set(x, y);
sprite.scale.set(0.5, 0.5);
app.stage.addChild(sprite);
Drawing with Graphics (v8+ chaining API)
const g = new Graphics()
.rect(0, 0, 100, 50)
.fill(0xFF0000)
.circle(50, 25, 20)
.fill(0x00FF00);
app.stage.addChild(g);
Game Loop (Animation)
app.ticker.add((delta) => {
// delta = time since last frame (1.0 = 60fps)
sprite.x += speed * delta;
});
Key Concepts for Frogger
- Application: Main renderer instance
- Assets: Load textures, sounds, JSON
- Sprite: Display and animate images
- Container: Group multiple display objects
- Graphics: Draw shapes programmatically
- Text: Render text
- Ticker: Animation loop (requestAnimationFrame)
PixiJS Documentation
- Official Guides - Step-by-step tutorials
- API Documentation - Full class reference
- GitHub Repository - Examples and plugins
- Playground - Experiment without local setup
Development Workflow
When Adding New Features
- Create a new TypeScript file in
src/for game components - Add assets to
public/assets/ - Import and use in
main.tsor your component - Test in browser:
npm run dev
When Modifying Game Logic
- Edit files in
src/ - Use
app.ticker.add()for frame updates - Use
Assets.load()for all asset loading
File Naming Conventions
- TypeScript:
camelCase.ts(e.g.,game.ts,player.ts,enemy.ts) - Assets:
lowercase-with-dashes.pngorsnake_case.png
Code Style
TypeScript
- Use
camelCasefor variables and functions - Use
PascalCasefor classes and types - Always use
constunless variable needs reassignment - Use type annotations for function parameters and returns
PixiJS Specific
- Name sprite variables descriptively:
playerSprite,enemyCar,roadTile - Group related objects in Containers:
const roadContainer = new Container() - Clean up sprites:
app.stage.removeChild(sprite)orsprite.destroy()
ESLint Rules (from project config)
- Prettier integration for formatting
- Run
npm run lintbefore committing
Testing Strategy
This is a visual/desktop project. Testing approach:
- Manual Testing: Run
npm run devand verify in browser - Visual Regression: Screenshot comparison for UI changes
- Unit Tests: Use
vitest(not yet configured - can be added)
Git Guidelines
- Use feature branches:
git checkout -b feature/frogger-logic - Commit messages: Use imperative mood (e.g., "Add player collision detection")
- Do not commit to
maindirectly - Push only after local testing
Common PixiJS Patterns for Frogger
Moving a Sprite
app.ticker.add((delta) => {
const speed = 200; // pixels per second
sprite.x += speed * delta;
});
Collision Detection
// Using getLocalBounds() (v8+ recommended)
function checkCollision(sprite1: Sprite, sprite2: Sprite): boolean {
const bounds1 = sprite1.getLocalBounds();
const bounds2 = sprite2.getLocalBounds();
return bounds1.x < bounds2.x + bounds2.width &&
bounds1.x + bounds1.width > bounds2.x &&
bounds1.y < bounds2.y + bounds2.height &&
bounds1.y + bounds1.height > bounds2.y;
}
Screen Boundary Clamping
// For a sprite with a given radius/half-width
const radius = 25; // Half of sprite width
sprite.x = Math.max(radius, Math.min(sprite.x, app.canvas.width - radius));
sprite.y = Math.max(radius, Math.min(sprite.y, app.canvas.height - radius));
Keyboard Input (WASD + Arrow Keys)
// Track keyboard state
const keys: Record<string, boolean> = {};
window.addEventListener("keydown", (e) => keys[e.key] = true);
window.addEventListener("keyup", (e) => keys[e.key] = false);
// In game loop with frame-independent movement
app.ticker.add((delta) => {
const speed = 200; // pixels per second
let vx = 0;
let vy = 0;
// WASD + Arrow keys for steering
if (keys["w"] || keys["ArrowUp"]) vy -= speed * delta;
if (keys["s"] || keys["ArrowDown"]) vy += speed * delta;
if (keys["a"] || keys["ArrowLeft"]) vx -= speed * delta;
if (keys["d"] || keys["ArrowRight"]) vx += speed * delta;
player.x += vx;
player.y += vy;
});
Tools & Permissions
Allowed Tools
- All default tools are permitted
- Web search for PixiJS documentation
- File modifications in
frogger/directory
Denied Tools
- None specific to this project
Troubleshooting
Common Issues
"Assets.load() returns undefined"
- Check asset path is correct (from
/public/folder) - Verify file exists in
public/assets/ - Use absolute paths starting with
/
"Sprite not visible"
- Check if added to stage:
app.stage.addChild(sprite) - Verify texture loaded successfully
- Check if sprite is off-screen
"TypeScript errors"
- Run
npm installto ensure types are available - Check
tsconfig.jsonfor module resolution settings
PixiJS v8 API: Deprecated vs Latest Methods
⚠️ AVOID (Deprecated in v8)
| Deprecated | Replacement |
|---|---|
new PIXI.Application() |
new Application() + await app.init() |
PIXI.loader.add() |
Assets.load() or Assets.loadBundle() |
PIXI.Sprite.fromImage() |
Assets.load() + new Sprite(texture) |
PIXI.Texture.fromImage() |
Assets.load() |
graphics.beginFill().drawCircle().endFill() |
Use chaining API (see below) |
loader.onComplete.add() |
Assets.load().then() |
PIXI.ticker.shared.add() |
app.ticker.add() |
texture.baseTexture.scaleMode |
texture.source.scaleMode |
✅ USE (PixiJS v8+)
Application Setup
// OLD (v7)
const app = new PIXI.Application({ width, height });
// NEW (v8+)
const app = new Application();
await app.init({ width, height, background: "#1099bb", resizeTo: window });
Loading Assets
// OLD (v7)
PIXI.Assets.load("image.png").then((texture) => {...});
// NEW (v8+)
const texture = await Assets.load("/assets/image.png");
Graphics (Chaining API)
// OLD (v7) - DEPRECATED
const g = new PIXI.Graphics();
g.beginFill(0xFF0000);
g.drawCircle(0, 0, 50);
g.endFill();
// NEW (v8+) - CHAINING API
const g = new Graphics()
.circle(0, 0, 50)
.fill(0xFF0000);
Sprites from Images
// OLD (v7)
const sprite = PIXI.Sprite.fromImage("image.png");
// NEW (v8+)
const texture = await Assets.load("image.png");
const sprite = new Sprite(texture);