# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Multiplayer Tetris Battle Royale game with 2-8 player real-time battles via WebSocket. Players clear rows to send garbage to opponents; last player standing wins.

## Commands

```bash
# Install dependencies
cd server && npm install

# Start server
cd server && npm start
# or
cd server && node index.js

# Run tests
cd server && npm test

# Run tests with coverage
cd server && npm run test:coverage

# Server runs on http://localhost:3000 (or PORT env variable)
```

## Architecture

### Server-Client Model

- **Server** (`server/`): Express + Socket.io handles all game logic authoritatively
  - `index.js`: Main server with socket.io event handlers
  - `game-logic.js`: Pure game logic functions (testable, no socket dependencies)
  - `__tests__/`: Jest test suite with 57 tests
  - Manages single global lobby with `lobby.players` Map
  - Game tick runs at 50ms intervals via `gameTick()`
  - Broadcasts state updates to all connected clients via `broadcastState()`

- **Client** (`public/js/`): Vanilla JavaScript with module pattern
  - `network.js`: Socket.io client wrapper, manages player/game state caching
  - `game.js`: `TetrisGame` class for local game state (mirrors server)
  - `renderer.js`: `TetrisRenderer` class - Canvas rendering for all player boards
  - `ui.js`: `UIManager` class - screen transitions, DOM manipulation
  - `app.js`: Entry point, ties modules together, game loop via `requestAnimationFrame`

### Game Flow

1. **Lobby**: Players join global lobby via `join-lobby` socket event
2. **Ready**: All players click READY; game starts when all ready (2-8 players)
3. **Game**: Server authoritative - client inputs sent via socket, server broadcasts state
4. **Elimination**: Player eliminated when piece locks at top or garbage fills board
5. **Victory**: Game ends when 1 active player remains

### Key Constants

- `BOARD_WIDTH = 10`, `BOARD_HEIGHT = 20`, `CELL_SIZE = 24`
- `LOBBY_ROOM = 'global-lobby'` - Socket.io room for all players
- Garbage rules: 2 lines cleared -> 1 garbage row, 3 -> 2, 4 (Tetris) -> 4 rows

### Piece Spawn & Negative Y Overflow

Pieces spawn at `y = 0` (top of visible board). The coordinate system allows **negative Y values** as intentional overflow space:

- **Negative Y allowed**: `isValidPosition()` only checks `newY >= BOARD_HEIGHT`, not `newY < 0`
- **Purpose**: When garbage is received, pieces are pushed up (y decreases) into negative territory
- **Player survival**: Player is NOT eliminated when piece goes negative — only when piece LOCKS with blocks in rows 0-1
- **Recovery**: Piece drops naturally back into visible area; player can continue playing

This design gives players a buffer zone to recover from garbage attacks before elimination.

### Socket Events

| Event | Direction | Payload |
|-------|-----------|---------|
| `join-lobby` | Client->Server | `{ playerName }` |
| `ready` | Client->Server | - |
| `player-move` | Client->Server | `{ playerId, direction }` |
| `player-rotate` | Client->Server | `{ playerId }` |
| `player-drop` | Client->Server | `{ playerId, hard }` |
| `player-hold` | Client->Server | `{ playerId }` |
| `zone-activate` | Client->Server | `{ playerId }` |
| `player-joined` | Server->Client | `{ player, players }` |
| `player-left` | Server->Client | `{ playerId, players }` |
| `game-started` | Server->Client | `{ players, states }` |
| `state-update` | Server->Client | `states[]` |
| `game-over` | Server->Client | `{ states }` |

### Zone Mechanic (Tetris Effect)

Zone is a time-based attack mechanic imported from Tetris Effect. When players clear lines, their **Zone meter** fills:
- **1 line** = +15 Zone meter
- **2 lines** = +25 Zone meter
- **3 lines** = +35 Zone meter
- **4 lines (Tetris)** = +50 Zone meter

**When Zone meter reaches 100:**
- Press **Z key** or **ZONE button** to activate Zone
- Gravity pauses for **20 seconds**
- Players control pieces manually but gravity is frozen
- Cleared lines **cascade to bottom** instead of disappearing
- After 20 seconds, Zone ends and sends garbage to opponents

**Zone Attack Calculation:**
- Lines cleared during Zone × 1.5 = garbage rows sent
- Example: 4 Zone lines = 6 garbage rows, 8 Zone lines = 12 garbage rows
- Garbage is distributed randomly among remaining opponents

Zone provides a high-risk, high-reward tactic for eliminating opponents with massive attacks.

### Rendering

- Each player gets a dynamically created board with canvas + info divs
- `renderer.setActivePlayer()` marks current player's board as `.main`, others as `.spectator`
- Zone meter shown below each player board with fill bar and status indicator
- Battle grid layout classes: `.single-player`, `.two-players`, `.multi-player`
