# Architecture

Technical architecture for the Roguelite Platformer game.

---

## Project Structure

```
src/
├── main.ts                      # Entry point, Phaser config
├── types/                       # TypeScript interfaces
│   ├── index.ts                 # Re-exports
│   ├── game-state.ts            # RunState, MetaState
│   ├── entities.ts              # EnemyConfig, HazardConfig
│   ├── upgrades.ts              # Upgrade, UpgradeEffect
│   └── level-gen.ts             # RoomTemplate, SpawnPoint
│
├── config/                      # Configuration
│   ├── game.config.ts           # Phaser config
│   ├── physics.config.ts        # Arcade physics settings
│   ├── controls.config.ts       # Key bindings
│   ├── balance.config.ts        # Balance (speeds, timings)
│   └── upgrades.config.ts       # All shop upgrades
│
├── scenes/                      # Phaser scenes
│   ├── BootScene.ts             # Initialization
│   ├── PreloadScene.ts          # Asset loading
│   ├── MenuScene.ts             # Main menu
│   ├── GameScene.ts             # Core gameplay
│   ├── UIScene.ts               # HUD (parallel with GameScene)
│   ├── PauseScene.ts            # Pause menu
│   ├── ShopScene.ts             # Upgrade shop
│   └── GameOverScene.ts         # Death screen
│
├── entities/                    # Game objects
│   ├── Player.ts                # Player entity
│   ├── PlayerController.ts      # Controls + game feel
│   ├── enemies/                 # Enemy types
│   │   ├── Enemy.ts             # Base class
│   │   ├── Patroller.ts         # Patrol enemy
│   │   ├── Jumper.ts            # Jumping enemy
│   │   ├── Flyer.ts             # Flying enemy
│   │   ├── Chaser.ts            # Chasing enemy
│   │   └── Sprinter.ts          # Fast patrol enemy
│   ├── Projectile.ts            # Player projectile
│   ├── Coin.ts                  # Collectible coin
│   ├── PowerUp.ts               # Power-up item
│   └── hazards/                 # Hazard types
│       ├── Spikes.ts            # Static spikes
│       ├── FallingPlatform.ts   # Crumbling platform
│       ├── Saw.ts               # Moving saw
│       ├── Turret.ts            # Shooting turret
│       └── Laser.ts             # Toggle laser
│
├── systems/                     # Managers/Systems
│   ├── InputManager.ts          # Input buffering, abstraction
│   ├── AudioManager.ts          # Sound and music
│   ├── SaveManager.ts           # localStorage persistence
│   ├── GameStateManager.ts      # Run state + Meta state
│   ├── UpgradeManager.ts        # Upgrade application
│   ├── ScoreManager.ts          # Score and combo
│   ├── ParticleManager.ts       # Particle effects
│   └── CameraManager.ts         # Screen shake
│
├── level-generation/            # Procedural generation
│   ├── LevelGenerator.ts        # Main generator
│   ├── RoomPlacer.ts            # Room placement
│   ├── PathValidator.ts         # Path validation
│   └── templates/               # JSON room templates
│
├── ui/                          # UI components
│   ├── HUD.ts                   # Heads-up display
│   ├── Button.ts                # Reusable button
│   ├── ProgressBar.ts           # Progress/health bar
│   └── ComboDisplay.ts          # Combo counter
│
└── utils/                       # Utilities
    ├── math.ts                  # Math helpers
    └── SeededRandom.ts          # Seed-based RNG
```

---

## Scene Architecture

### Scene Flow

```
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  BootScene → PreloadScene → MenuScene ─┬→ GameScene ←──┐    │
│                                        │       │       │    │
│                                        │       ▼       │    │
│                                        │   UIScene     │    │
│                                        │   (parallel)  │    │
│                                        │       │       │    │
│                                        │       ▼       │    │
│                                        │  PauseScene   │    │
│                                        │       │       │    │
│                                        │       ▼       │    │
│                                        │  GameOverScene│    │
│                                        │       │       │    │
│                                        │       ▼       │    │
│                                        └→ ShopScene ───┘    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Scene Responsibilities

| Scene | Responsibility |
|-------|---------------|
| **BootScene** | Initialize game settings, configure physics |
| **PreloadScene** | Load all assets (sprites, audio, tilemaps) |
| **MenuScene** | Main menu, navigation to other scenes |
| **GameScene** | Core gameplay loop, entity management |
| **UIScene** | HUD overlay, runs parallel with GameScene |
| **PauseScene** | Pause menu, settings access during gameplay |
| **ShopScene** | Meta-progression shop, upgrade purchases |
| **GameOverScene** | Death screen, stats, navigation |

### Scene Communication

Scenes communicate through:

1. **Registry** (`this.registry`) - shared data store
2. **Events** (`this.events`) - event-based messaging
3. **Scene Manager** - scene transitions

```typescript
// Example: GameScene emits coin collection
this.events.emit('coin-collected', { amount: 1, total: this.coins });

// UIScene listens and updates display
this.scene.get('GameScene').events.on('coin-collected', this.updateCoinDisplay, this);
```

---

## State Management

### Dual-State Pattern

The game uses two separate state objects:

```typescript
// RunState - reset every run
interface RunState {
  currentLevel: number;
  coins: number;
  score: number;
  combo: number;
  projectilesAvailable: number;
  activePowerUp: PowerUpType | null;
  powerUpTimer: number;
  hp: number;
  hasUsedSecondChance: boolean;
  enemiesStunned: number;
  timeElapsed: number;
  levelSeed: string;
}

// MetaState - persists across runs
interface MetaState {
  gasCoins: number;
  purchasedUpgrades: string[];
  activeUpgrades: string[];
  achievements: string[];
  totalCoinsCollected: number;
  totalEnemiesStunned: number;
  totalDeaths: number;
  highScores: HighScore[];
  settings: GameSettings;
}
```

### State Flow

```
┌────────────────────────────────────────────────────────────┐
│                     GameStateManager                        │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  ┌─────────────┐              ┌─────────────────────────┐  │
│  │  RunState   │              │       MetaState         │  │
│  │  (memory)   │              │    (localStorage)       │  │
│  ├─────────────┤              ├─────────────────────────┤  │
│  │ level: 1    │  ──death──▶  │ gasCoins += runCoins    │  │
│  │ coins: 42   │              │ totalDeaths++           │  │
│  │ score: 1200 │              │ highScores.push(...)    │  │
│  │ ...         │              │                         │  │
│  └─────────────┘              └─────────────────────────┘  │
│         │                              │                   │
│         │ reset on new run             │ load on boot      │
│         ▼                              ▼ save on change    │
│                                                            │
└────────────────────────────────────────────────────────────┘
```

---

## Entity System

### Entity Hierarchy

```
                    Phaser.GameObjects.Sprite
                              │
                              │
              ┌───────────────┼───────────────┐
              │               │               │
           Player          Enemy           Hazard
              │               │               │
              │       ┌───────┼───────┐       │
              │       │       │       │       │
              │   Patroller Flyer  Chaser     │
              │       │               │       │
              │   Jumper          Sprinter    │
              │                               │
              │                       ┌───────┼───────┐
              │                       │       │       │
              │                    Spikes    Saw    Laser
              │                       │
              │                  FallingPlatform
              │                       │
              │                    Turret
              │
      PlayerController
```

### Enemy States

```typescript
enum EnemyState {
  ACTIVE = 'active',      // Dangerous, kills on contact
  STUNNED = 'stunned',    // Safe, can pass through
  RECOVERING = 'recovering' // Brief transition state
}
```

State machine for enemies:

```
┌─────────┐  hit by projectile  ┌─────────┐  timer expires  ┌────────────┐
│ ACTIVE  │ ─────────────────▶  │ STUNNED │ ──────────────▶ │ RECOVERING │
└─────────┘                     └─────────┘                 └────────────┘
     ▲                                                            │
     │                        recovery complete                   │
     └────────────────────────────────────────────────────────────┘
```

---

## Physics Configuration

### Arcade Physics Settings

```typescript
// physics.config.ts
export const PHYSICS_CONFIG = {
  gravity: { y: 1200 },

  player: {
    speed: 300,
    jumpVelocity: -500,
    acceleration: 2000,
    drag: 1500,
  },

  projectile: {
    speed: 600,
    lifetime: 1000, // ms
  },

  enemies: {
    patroller: { speed: 100 },
    jumper: { speed: 80, jumpForce: -400 },
    flyer: { speed: 120 },
    chaser: { speed: 60 },
    sprinter: { speed: 200 },
  },
};
```

### Collision Groups

```
Player  ←→  Enemy (active)    = Death
Player  ←→  Enemy (stunned)   = Pass through
Player  ←→  Hazard            = Death
Player  ←→  Coin              = Collect
Player  ←→  PowerUp           = Collect
Player  ←→  Platform          = Land
Projectile ←→ Enemy           = Stun enemy
Projectile ←→ Wall            = Destroy (or ricochet)
```

---

## Game Feel Implementation

### Coyote Time

```typescript
// PlayerController.ts
private coyoteTime = 100; // ms
private coyoteTimer = 0;
private wasGrounded = false;

update(delta: number) {
  if (this.body.onFloor()) {
    this.coyoteTimer = this.coyoteTime;
    this.wasGrounded = true;
  } else if (this.wasGrounded) {
    this.coyoteTimer -= delta;
    if (this.coyoteTimer <= 0) {
      this.wasGrounded = false;
    }
  }
}

canJump(): boolean {
  return this.body.onFloor() || this.coyoteTimer > 0;
}
```

### Input Buffering

```typescript
// InputManager.ts
private jumpBufferTime = 100; // ms
private jumpBufferTimer = 0;

update(delta: number) {
  if (this.jumpPressed) {
    this.jumpBufferTimer = this.jumpBufferTime;
  } else {
    this.jumpBufferTimer = Math.max(0, this.jumpBufferTimer - delta);
  }
}

hasBufferedJump(): boolean {
  return this.jumpBufferTimer > 0;
}

consumeJumpBuffer() {
  this.jumpBufferTimer = 0;
}
```

### Variable Jump Height

```typescript
// PlayerController.ts
private minJumpVelocity = -250;
private maxJumpVelocity = -500;

onJumpPressed() {
  if (this.canJump()) {
    this.body.setVelocityY(this.maxJumpVelocity);
    this.isJumping = true;
  }
}

onJumpReleased() {
  if (this.isJumping && this.body.velocity.y < this.minJumpVelocity) {
    this.body.setVelocityY(this.minJumpVelocity);
  }
  this.isJumping = false;
}
```

### Edge Correction

```typescript
// PlayerController.ts
private edgeCorrectionDistance = 4; // pixels

handleCollision(tile: Phaser.Tilemaps.Tile) {
  if (this.body.velocity.y < 0) { // Moving upward
    const overlap = this.getHorizontalOverlap(tile);
    if (overlap > 0 && overlap <= this.edgeCorrectionDistance) {
      this.x += overlap * this.getOverlapDirection(tile);
    }
  }
}
```

---

## Level Generation

### Spelunky-Style Room Grid

```
┌─────┬─────┬─────┬─────┐
│  S  │     │     │     │   S = Start
├─────┼─────┼─────┼─────┤   E = Exit
│  ↓  │  ←──┤     │     │   ↓ → = Path direction
├─────┼─────┼─────┼─────┤
│     │  ↓  │     │     │
├─────┼─────┼─────┼─────┤
│     │  E  │     │     │
└─────┴─────┴─────┴─────┘
```

### Generation Algorithm

```typescript
// LevelGenerator.ts
class LevelGenerator {
  generateLevel(level: number, seed: string): Level {
    const rng = new SeededRandom(seed);
    const config = this.getLevelConfig(level);

    // 1. Create room grid
    const grid = this.createGrid(config.gridSize);

    // 2. Generate guaranteed path
    const path = this.generatePath(grid, rng);

    // 3. Place room templates along path
    this.placeRooms(grid, path, config.difficulty, rng);

    // 4. Fill remaining cells with optional rooms
    this.fillOptionalRooms(grid, rng);

    // 5. Place entities
    this.placeCoins(grid, config.coinCount, rng);
    this.placeEnemies(grid, config.enemyCount, level, rng);
    this.placeHazards(grid, config.hazardCount, level, rng);
    this.placePowerUp(grid, rng); // 10% chance

    // 6. Validate
    if (!this.validatePath(grid)) {
      return this.generateLevel(level, seed + '_retry');
    }

    return this.buildLevel(grid);
  }
}
```

### Room Templates

```typescript
// types/level-gen.ts
interface RoomTemplate {
  id: string;
  width: number;          // tiles
  height: number;         // tiles
  difficulty: number;     // 1-10
  exits: {
    top: boolean;
    bottom: boolean;
    left: boolean;
    right: boolean;
  };
  tiles: number[][];      // tile IDs
  spawnPoints: SpawnPoint[];
}

interface SpawnPoint {
  type: 'coin' | 'enemy' | 'hazard' | 'powerup';
  x: number;
  y: number;
  probability?: number;
}
```

---

## Upgrade System

### Upgrade Configuration

```typescript
// config/upgrades.config.ts
export const UPGRADES: Upgrade[] = [
  {
    id: 'light_boots',
    name: 'Light Boots',
    description: '+10% movement speed',
    category: 'character',
    price: 100,
    effects: [{ type: 'speed_multiplier', value: 1.1 }],
    unlockRequirement: null,
  },
  {
    id: 'heavy_armor',
    name: 'Heavy Armor',
    description: '+1 HP per run, -15% speed',
    category: 'character',
    price: 500,
    effects: [
      { type: 'extra_hp', value: 1 },
      { type: 'speed_multiplier', value: 0.85 },
    ],
    unlockRequirement: null,
  },
  // ... more upgrades
];
```

### Upgrade Application

```typescript
// UpgradeManager.ts
class UpgradeManager {
  applyUpgrades(player: Player, activeUpgrades: string[]) {
    const stats = { ...DEFAULT_PLAYER_STATS };

    for (const upgradeId of activeUpgrades) {
      const upgrade = this.getUpgrade(upgradeId);
      for (const effect of upgrade.effects) {
        this.applyEffect(stats, effect);
      }
    }

    player.setStats(stats);
  }

  private applyEffect(stats: PlayerStats, effect: UpgradeEffect) {
    switch (effect.type) {
      case 'speed_multiplier':
        stats.speed *= effect.value;
        break;
      case 'jump_multiplier':
        stats.jumpVelocity *= effect.value;
        break;
      case 'extra_hp':
        stats.maxHp += effect.value;
        break;
      case 'extra_projectile':
        stats.maxProjectiles += effect.value;
        break;
      // ... more effect types
    }
  }
}
```

---

## Audio System

### Audio Manager

```typescript
// AudioManager.ts
class AudioManager {
  private music: Phaser.Sound.BaseSound | null = null;
  private sfxVolume: number = 1;
  private musicVolume: number = 0.5;

  playMusic(key: string, loop = true) {
    if (this.music) this.music.stop();
    this.music = this.scene.sound.add(key, {
      loop,
      volume: this.musicVolume
    });
    this.music.play();
  }

  playSFX(key: string, config?: Phaser.Types.Sound.SoundConfig) {
    this.scene.sound.play(key, {
      volume: this.sfxVolume,
      ...config
    });
  }

  setMusicVolume(volume: number) {
    this.musicVolume = volume;
    if (this.music) this.music.setVolume(volume);
  }

  setSFXVolume(volume: number) {
    this.sfxVolume = volume;
  }
}
```

### Sound Effects List

| Event | Sound Key |
|-------|-----------|
| Jump | `sfx_jump` |
| Land | `sfx_land` |
| Shoot | `sfx_shoot` |
| Enemy stunned | `sfx_stun` |
| Coin collected | `sfx_coin` |
| PowerUp collected | `sfx_powerup` |
| Player death | `sfx_death` |
| Level complete | `sfx_level_complete` |
| Menu select | `sfx_menu_select` |
| Purchase | `sfx_purchase` |

---

## Save System

### SaveManager

```typescript
// SaveManager.ts
class SaveManager {
  private readonly SAVE_KEY = 'roguelite_platformer_save';

  save(metaState: MetaState): void {
    const data = JSON.stringify(metaState);
    localStorage.setItem(this.SAVE_KEY, data);
  }

  load(): MetaState | null {
    const data = localStorage.getItem(this.SAVE_KEY);
    if (!data) return null;

    try {
      return JSON.parse(data) as MetaState;
    } catch {
      console.error('Failed to parse save data');
      return null;
    }
  }

  reset(): void {
    localStorage.removeItem(this.SAVE_KEY);
  }

  exists(): boolean {
    return localStorage.getItem(this.SAVE_KEY) !== null;
  }
}
```

### Data Stored

```typescript
interface SaveData {
  version: string;           // For migration
  metaState: MetaState;
  lastPlayed: number;        // Timestamp
}
```

---

## TypeScript Interfaces

### Core Types

```typescript
// types/game-state.ts
interface RunState {
  currentLevel: number;
  coins: number;
  totalCoinsOnLevel: number;
  score: number;
  combo: number;
  comboTimer: number;
  projectilesAvailable: number;
  projectileCooldowns: number[];
  activePowerUp: PowerUpType | null;
  powerUpTimer: number;
  hp: number;
  maxHp: number;
  hasUsedSecondChance: boolean;
  enemiesStunned: number;
  timeElapsed: number;
  levelSeed: string;
  isPaused: boolean;
}

interface MetaState {
  gasCoins: number;
  purchasedUpgrades: string[];
  activeUpgrades: string[];
  achievements: Achievement[];
  stats: GlobalStats;
  highScores: HighScore[];
  settings: GameSettings;
}

interface GlobalStats {
  totalCoinsCollected: number;
  totalEnemiesStunned: number;
  totalDeaths: number;
  totalLevelsCompleted: number;
  totalPlayTime: number;
  highestLevel: number;
  highestCombo: number;
}

interface HighScore {
  score: number;
  level: number;
  date: number;
  seed?: string;
}

interface GameSettings {
  musicVolume: number;
  sfxVolume: number;
  screenShake: boolean;
  showFPS: boolean;
}
```

### Entity Types

```typescript
// types/entities.ts
interface EnemyConfig {
  type: EnemyType;
  speed: number;
  patrolDistance?: number;
  jumpInterval?: number;
  flightPattern?: FlightPattern;
  detectionRange?: number;
}

type EnemyType = 'patroller' | 'jumper' | 'flyer' | 'chaser' | 'sprinter';

interface HazardConfig {
  type: HazardType;
  damage: number;
  interval?: number;      // For turrets, lasers
  path?: Phaser.Math.Vector2[];  // For saws
}

type HazardType = 'spikes' | 'falling_platform' | 'saw' | 'turret' | 'laser';

type PowerUpType = 'shield' | 'magnet' | 'clock' | 'coffee' | 'infinity' | 'ghost';

interface PowerUpConfig {
  type: PowerUpType;
  duration: number;
  effect: () => void;
}
```

### Upgrade Types

```typescript
// types/upgrades.ts
interface Upgrade {
  id: string;
  name: string;
  description: string;
  category: UpgradeCategory;
  price: number;
  effects: UpgradeEffect[];
  unlockRequirement: UnlockRequirement | null;
  tradeoff?: string;  // Description of downside
}

type UpgradeCategory =
  | 'character'
  | 'weapon'
  | 'survival'
  | 'risk_reward'
  | 'cosmetic';

interface UpgradeEffect {
  type: EffectType;
  value: number;
}

type EffectType =
  | 'speed_multiplier'
  | 'jump_multiplier'
  | 'extra_hp'
  | 'extra_projectile'
  | 'projectile_cooldown_multiplier'
  | 'projectile_range_multiplier'
  | 'stun_duration_multiplier'
  | 'coin_multiplier'
  | 'score_multiplier'
  | 'hitbox_multiplier'
  | 'enable_double_jump'
  | 'enable_dash'
  | 'enable_ricochet'
  | 'enable_explosive';

interface UnlockRequirement {
  type: 'level_reached' | 'coins_collected' | 'deaths' | 'enemies_stunned' | 'achievement';
  value: number | string;
}
```

---

## Visual Effects

### Particle System

```typescript
// ParticleManager.ts
class ParticleManager {
  private emitters: Map<string, Phaser.GameObjects.Particles.ParticleEmitter>;

  createDustEffect(x: number, y: number) {
    this.emitters.get('dust')?.explode(5, x, y);
  }

  createStunEffect(x: number, y: number) {
    this.emitters.get('stars')?.explode(8, x, y);
  }

  createCoinSparkle(x: number, y: number) {
    this.emitters.get('sparkle')?.explode(3, x, y);
  }

  createDeathEffect(x: number, y: number) {
    this.emitters.get('explosion')?.explode(20, x, y);
  }
}
```

### Screen Shake

```typescript
// CameraManager.ts
class CameraManager {
  private camera: Phaser.Cameras.Scene2D.Camera;
  private shakeEnabled: boolean = true;

  shake(intensity: number, duration: number) {
    if (!this.shakeEnabled) return;
    this.camera.shake(duration, intensity);
  }

  microShake() {
    this.shake(0.002, 50);
  }

  deathShake() {
    this.shake(0.01, 200);
  }

  setShakeEnabled(enabled: boolean) {
    this.shakeEnabled = enabled;
  }
}
```

### Squash & Stretch

```typescript
// Player.ts
jumpSquash() {
  this.setScale(0.8, 1.2);
  this.scene.tweens.add({
    targets: this,
    scaleX: 1,
    scaleY: 1,
    duration: 150,
    ease: 'Back.out',
  });
}

landSquash() {
  this.setScale(1.2, 0.8);
  this.scene.tweens.add({
    targets: this,
    scaleX: 1,
    scaleY: 1,
    duration: 100,
    ease: 'Back.out',
  });
}
```

### Hitstop

```typescript
// GameScene.ts
hitStop(duration: number = 50) {
  this.physics.pause();
  this.time.delayedCall(duration, () => {
    this.physics.resume();
  });
}
```

---

## Performance Considerations

### Object Pooling

```typescript
// Projectile pooling
class ProjectilePool {
  private pool: Phaser.GameObjects.Group;

  get(): Projectile {
    const projectile = this.pool.getFirstDead(true);
    if (projectile) {
      projectile.setActive(true).setVisible(true);
      return projectile;
    }
    return this.pool.add(new Projectile(this.scene));
  }

  release(projectile: Projectile) {
    projectile.setActive(false).setVisible(false);
    projectile.body.stop();
  }
}
```

### Culling

- Entities outside viewport are deactivated
- Physics calculations only for active entities
- Particle systems use burst mode, not continuous

### Asset Loading

- Sprites loaded as atlases
- Audio preloaded in PreloadScene
- Level templates loaded on demand

---

## Testing Strategy

### Unit Tests
- State management logic
- Level generation algorithms
- Upgrade calculations
- Collision detection helpers

### Integration Tests
- Scene transitions
- Save/load functionality
- Input handling

### Manual Testing
- Game feel tuning
- Balance verification
- Performance profiling
