This document provides guidance for AI code assistants working in this repository.
These rules MUST be followed in EVERY session without exception:
- NEVER add comments to code - use self-documenting method names instead
- This includes
//,/* */, and any explanatory text in code - Remove any comments you see during refactoring
- NEVER add AI attribution to commit messages
- This means NO:
Generated with [Claude Code]Co-Authored-By: Claude <noreply@anthropic.com>- Any similar AI tool attribution lines
- Commits should appear as if written by a human developer
- Always free to write/edit existing files and create new files - no confirmation needed
- NEVER commit without explicit user request - user must say "commit" or similar
- NEVER push without explicit user request - user must say "push" or similar
- NEVER proactively commit and push together - wait for each to be requested separately
- Always use double quotes
"not single quotes'for strings - No "get" prefixes on methods (use
user()notgetUser()) - Follow existing patterns - don't introduce new approaches
- Use null instead of undefined: Always use
nullfor absence of value, neverundefined
- EVERY request implicitly requires DRY abstractions for maintainability
- ALWAYS reuse existing components, hooks, and utilities - never duplicate functionality
- Before implementing ANY feature:
- Search the codebase for existing implementations
- Check for similar components, hooks, or utilities
- If found, reuse and enhance rather than duplicate
- If similar functionality exists in multiple places, refactor to a shared abstraction first
- When making changes:
- Ask: "Where else is this functionality used?"
- Update all instances, or better yet, consolidate to a single reusable implementation
- Look for wrapper components that might be duplicating logic
- NEVER use
console.log()for debugging - Use the existing
logutility from@/lib/utils(provideslog.debug,log.info,log.warn,log.error) - Debug logging is only visible in development mode
- Remove any temporary debug statements before completing work
- Keep related interfaces together in appropriate files
- Store types are defined in their respective store files (e.g.,
game-store.ts) - API types are defined near tRPC routers or in shared type files
- Component prop types can be defined inline for simple cases or extracted for complex/shared types
- A Claude Swarm orchestrator manages all builds and dev servers — agents must NEVER run build or dev commands
- NEVER run:
pnpm run build,next build,pnpm run dev,pnpm dev,npm run build,npm run dev, or equivalent - NEVER run:
./run-dev.sh,./kill-dev.sh, or any swarm lifecycle scripts - Running
next buildwhile the dev server is active corrupts the.nextcache and breaks the app - The swarm automatically rebuilds on file changes — just edit files and check the logs
- To verify your changes compiled: read the swarm logs at
logs/dev.logorlogs/oscillation.log - To check swarm status: read
.claude-swarm/registry.json - Type checking only (
npx tsc --noEmit) is safe — it doesn't write to.next
- No empty catches: Never add
catch {}orcatch (e) {}blocks without at least one of:- Logging a meaningful message through the log utility, or
- Returning/falling back to a safe default value
- Prefer small, targeted try/catch blocks close to the failing operation
Oscillation is a multiplayer board game played on real OS Maps of Britain. Players roll dice, move cars along A/B roads on a 1km grid, visit POIs (pubs, churches, phone boxes, schools), and draw cards.
- Repository: https://github.com/nbarrett/oscillation
- Hosting: Fly.io (https://oscillation.fly.dev)
- Dev port: 3002
| Component | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| API Layer | tRPC (via @trpc/server + @trpc/react-query) |
| ORM | Prisma |
| Database | PostgreSQL (Neon) |
| Auth | NextAuth.js (credentials provider, optional) |
| State Management | Zustand (with persist middleware using sessionStorage) |
| UI | Tailwind CSS + shadcn/ui components |
| Maps | Leaflet with OS Maps API tiles |
| Package Manager | pnpm |
/oscillation
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx # Root layout with providers
│ │ ├── page.tsx # Main game page
│ │ ├── providers.tsx # tRPC + React Query providers
│ │ └── api/trpc/[trpc]/route.ts # tRPC API handler
│ ├── components/ # React components
│ │ ├── ui/ # shadcn/ui primitives (button, input, etc.)
│ │ ├── DiceRoller.tsx # Dice rolling + turn management
│ │ ├── SelectGridSquares.tsx # Map click handling + path preview
│ │ ├── GameSync.tsx # Server polling + state sync
│ │ ├── JoinGame.tsx # Game creation/joining stepper
│ │ ├── GameLobby.tsx # Pre-game lobby
│ │ └── PoiPicker.tsx # POI selection during picking phase
│ ├── server/
│ │ ├── db/
│ │ │ └── index.ts # Prisma client
│ │ ├── api/
│ │ │ ├── routers/ # tRPC routers (game.ts, locations.ts, auth.ts)
│ │ │ ├── root.ts # Root router
│ │ │ └── trpc.ts # tRPC setup
│ │ └── overpass.ts # Overpass API for POI data
│ ├── lib/
│ │ ├── trpc/client.ts # tRPC client setup
│ │ ├── road-data.ts # Road grid BFS, pathfinding, grid utilities
│ │ ├── poi-categories.ts # POI type definitions + validation
│ │ ├── card-decks.ts # Chance/Edge/Motorway card definitions
│ │ ├── area-size.ts # Game area bounds calculation
│ │ ├── cn.ts # Tailwind class merging utility
│ │ └── utils.ts # Shared utilities (log, formatting)
│ └── stores/ # Zustand stores
│ ├── game-store.ts # Main game state (players, turns, movement)
│ ├── deck-store.ts # Card deck state
│ ├── car-store.ts # Car icon styles
│ ├── pub-store.ts # Pub POI data
│ ├── church-store.ts # Church spire/tower POI data
│ ├── phone-store.ts # Phone box POI data
│ ├── school-store.ts # School POI data
│ ├── error-store.ts # Error notification state
│ └── notification-store.ts # Toast notifications
├── prisma/
│ └── schema.prisma # Prisma schema (GameSession, GamePlayer, etc.)
├── public/ # Static assets (car images, icons)
└── package.json
- Game state persists to sessionStorage (per-tab isolation for multiplayer testing)
- Access store outside React:
useGameStore.getState() - Subscribe to changes:
useGameStore.subscribe(callback)
import { useGameStore } from "@/stores/game-store";
function MyComponent() {
const players = useGameStore((s) => s.players);
const setPlayers = useGameStore((s) => s.setPlayers);
}import { trpc } from "@/lib/trpc/client";
function MyComponent() {
const { data } = trpc.game.state.useQuery({ sessionId });
const mutation = trpc.game.rollDice.useMutation();
}- Projection: British National Grid (EPSG:27700) via proj4
- Grid resolution: 1km squares (keys like
"549000-217000") - Road data: A/B roads loaded via tRPC, stored in
roadDataCache - Pathfinding: BFS through road-connected grid adjacency
- Movement: Player clicks grid →
shortestPath()→movementPathin store - Leaflet SSR: Components use
dynamic(() => import(...), { ssr: false })
- JoinGame (4-step stepper): Player → Location → Settings → Review
- GameLobby: Wait for players, start game
- PoiPicker: Each player picks POIs to visit
- Playing: Roll dice → preview paths → click to move → end turn
- Cards: Chance (doubles), Edge/Motorway (triggered mid-movement)
ROLL_DICE → DICE_ROLLED → (player moves on map) → END_TURN → ROLL_DICE
Managed by GameTurnState enum in game-store.ts.
import { log } from "@/lib/utils";
log.debug("Debug message", someData);
log.info("Info message");
log.warn("Warning message");
log.error("Error message", error);Use Conventional Commits format:
Format: <type>(<scope>): <description> (ref: #<issue>)
Types:
feat- New features or enhancementsfix- Bug fixesrefactor- Code restructuring without behavior changestest- Adding or updating testsdocs- Documentation changesstyle- Code formatting changesbuild- Build system or dependency changes
Scopes:
game,map,dice,movement- Feature areasui,store,api- Technical componentsdb,trpc- Backend areas
Issue Reference:
- Always include the issue reference at the end of the first line:
(ref: #4)
Examples:
feat(game): add player turn rotation (ref: #12)
fix(map): resolve grid selection on mobile (ref: #8)
refactor(store): consolidate player state updates (ref: #15)
- NEVER commit without explicit user request
- NEVER push without explicit user request
- NEVER combine commit and push unless explicitly requested
- NEVER force push to main - this is not allowed under any circumstances
- Writing/editing files is allowed without permission, but git operations require explicit approval
When working on this project:
- No comments, no AI attribution in code or commits
- Double quotes, no
getprefixes,nullnotundefined - Use
logutility from@/lib/utils— neverconsole.log() - Never build or run dev — the swarm handles it, use
npx tsc --noEmitfor type checks - Embrace the stack: tRPC for APIs, Zustand for state, Prisma for database, shadcn for UI
- SSR safety: Leaflet components must use
dynamic(() => import(...), { ssr: false }) - Search before creating — reuse existing components, hooks, and utilities