# APPLICATION SPECIFICATION

## SUMMARY
A deterministic, top-down roguelike survival game where the player automatically attacks enemies while moving for a fixed 20-minute duration. The core loop involves collecting experience gems to level up and choose weapon upgrades, with all randomness being seedable for reproducible runs. The game must maintain 60 FPS with 500 enemies while using fixed timestep physics and spatial hashing for collision detection.

## DATA MODELS

### Player
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| position | vec2 | Yes | World position clamped to map boundaries |
| speed | float | Yes | Movement speed multiplier |
| health | integer | Yes | Current hit points |
| max_health | integer | Yes | Maximum hit points |
| level | integer | Yes | Current player level |
| weapons | array | Yes | Equipped weapons list |
| passives | array | Yes | Equipped passive items list |

**Constraints**: Collision capsule radius of 0.5 units, position clamped to [MAP_BORDER, MAP_WIDTH - MAP_BORDER], invulnerability for 0.5 seconds after damage
**Source**: "Player collision capsule: radius 0.5 units (world space), used for both enemy collision and enemy targeting" and "Player cannot move outside map boundaries: position clamped to [MAP_BORDER, MAP_WIDTH - MAP_BORDER]"

### Enemy
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| position | vec2 | Yes | Current world position |
| velocity | vec2 | Yes | Current movement velocity |
| health | float | Yes | Current hit points |
| max_health | float | Yes | Maximum hit points |
| damage | float | Yes | Damage dealt on contact |
| speed | float | Yes | Movement speed |
| type_id | integer | Yes | Enemy type identifier |
| stuck_time | float | Yes | Time duration without significant movement |

**Constraints**: Must always target player position, anti-stuck teleport if stuck for 2 seconds
**Source**: "Enemies must always have a target: the player character. No other target priorities allowed" and "Anti-stuck guarantee: If enemy hasn't moved > 0.1 units in 2.0 seconds, teleport enemy 1.0 unit away from nearest enemy toward player"

### Weapon
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| level | integer | Yes | Current weapon level (1-8) |
| base_damage | float | Yes | Base damage value |
| cooldown | float | Yes | Time between attacks |
| last_fired_time | float | Yes | Timestamp of last fire |
| projectile_count | integer | Yes | Number of projectiles |
| pierce | integer | Yes | Number of enemies pierce count |
| range | float | Yes | Effective attack range |

**Constraints**: Max level 8, checks availability every frame, damage formula includes seeded randomness
**Source**: "Weapon level cap: max_level = 8 for all weapons" and "Each weapon performs an availability check every frame: if (game_time - last_fired_time >= cooldown) then attempt_fire()"

### GameState
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| rng_seed | u64 | Yes | Random number generator seed |
| game_frame | u64 | Yes | Current frame count |
| game_time | f64 | Yes | Total elapsed game time |
| player | Player | Yes | Player character state |
| enemies | HashMap | Yes | All active enemies |
| projectiles | HashMap | Yes | All active projectiles |
| gems | HashMap | Yes | All experience gems |
| is_simulation_paused | boolean | Yes | Pause state flag |

**Constraints**: Fixed update order ensures determinism, spatial hash updated every frame
**Source**: "Update order must be fixed: Player → Enemies → Projectiles → Gems → Spawns" and "Collision detection must use Spatial Hashing with cell size 8x8 units, updated every frame"

## BEHAVIORS

### PlayerMovement
- **Trigger**: Player input every frame
- **Input**: WASD/arrow keys or gamepad input direction
- **Output**: Updated player position
- **Steps**:
  1. Read raw input values
  2. Apply gamepad deadzone (0.15)
  3. Normalize input direction
  4. Apply fixed timestep physics
  5. Clamp to map boundaries
- **Constraints**: Must prevent diagonal speed advantage via normalization, fixed delta time of 1/60s
- **Required**: Yes
- **Source**: "Character must move with fixed timestep physics: position += input_direction * speed * fixed_delta_time where fixed_delta_time = 1/60s" and "Input direction must be normalized: input_direction = normalize(input_x, input_y) to prevent diagonal speed advantage"

### WeaponUpdate
- **Trigger**: Every frame update
- **Input**: Current time, weapon state, enemy positions
- **Output**: New projectiles if conditions met
- **Steps**:
  1. Check if cooldown elapsed
  2. Find nearest enemy in range
  3. Calculate damage with seeded RNG
  4. Spawn projectile if valid target
- **Constraints**: No caching of enemy positions, must iterate all enemies every frame
- **Required**: Yes
- **Source**: "Each weapon performs an availability check every frame: if (game_time - last_fired_time >= cooldown) then attempt_fire()" and "Weapon target acquisition: find_nearest_enemy_in_range(range) - must iterate all enemies every frame, no caching"

### EnemyAI
- **Trigger**: Every frame update
- **Input**: Player position, other enemy positions
- **Output**: New enemy velocity and position
- **Steps**:
  1. Calculate steering force toward player
  2. Add separation force from nearby enemies
  3. Add wall avoidance force
  4. Update position with fixed timestep
  5. Check for stuck condition
- **Constraints**: Must use behavior tree or utility AI, max 0.1ms per enemy, teleport if stuck
- **Required**: Yes
- **Source**: "Enemy AI runs every frame: steering_force = seek(player_position) + separation(other_enemies) + wall_avoidance()" and "Enemy AI must use Behavior Tree or Utility AI with max 0.1ms per enemy per frame"

### ExperienceCollection
- **Trigger**: Every frame during gameplay
- **Input**: Player position, gem positions
- **Output**: Collected gems, added experience
- **Steps**:
  1. Check manual pickup range (< 0.5 units)
  2. Check magnetic pickup range (< 4.0 units)
  3. Apply magnetic attraction if in range
  4. Add experience to player total
  5. Check for level up condition
- **Constraints**: Magnetic attraction begins immediately with no delay
- **Required**: Yes
- **Source**: "Experience gem collection uses two-phase detection: Manual pickup: distance < 0.5 units → instant collection, Magnetic pickup: distance < pickup_range (default 4.0 units) → gem moves toward player at lerp(current_pos, player_pos, 0.15) per frame" and "Magnetic attraction must have no delay: begins immediately when in range"

### LevelUp
- **Trigger**: When experience >= required threshold
- **Input**: Player level, owned weapons/passives
- **Output**: Pause simulation, show 3 upgrade options
- **Steps**:
  1. Set simulation paused flag
  2. Generate upgrade options list
  3. Shuffle and select 3 options
  4. Display UI to player
  5. Wait for player selection
- **Constraints**: Must pause simulation but continue rendering, always 3 options
- **Required**: Yes
- **Source**: "Level-up screen must pause simulation (is_simulation_paused = true) but continue rendering with time_scale = 0" and "return shuffle(options)[:3] # Always 3 options"

### SpawnSystem
- **Trigger**: Every spawn interval during gameplay
- **Input**: Game time, player position
- **Output**: New enemy spawn commands
- **Steps**:
  1. Calculate spawn rate based on game minutes
  2. Calculate wave size
  3. Generate positions outside camera view
  4. Select enemy type based on weighted RNG
  5. Schedule spawns across frame budget
- **Constraints**: Must spawn outside camera view, use exponential scaling
- **Required**: Yes
- **Source**: "Spawn rate formula: spawns_per_second = base_rate * (1.0 + game_minutes * 0.15) ^ 1.3 (exponential scaling)" and "Spawn position must be outside camera view: distance_to_player > camera_radius + spawn_margin"

## INTERACTIONS

### PlayerInput
- **Actor**: User
- **Action**: Press movement keys/gamepad joysticks
- **Response**: Character moves in normalized direction
- **Required**: Yes
- **Source**: "Player must be controllable via keyboard (WASD/arrows) and gamepad simultaneously"

### GemCollection
- **Actor**: System (triggered by proximity)
- **Action**: Player enters collection range of experience gem
- **Response**: Gem either instantly collected or magnetically attracted
- **Required**: Yes
- **Source**: "Experience gem collection uses two-phase detection: Manual pickup: distance < 0.5 units → instant collection, Magnetic pickup: distance < pickup_range (default 4.0 units)"

### LevelUpSelection
- **Actor**: User
- **Action**: Click/select one of 3 upgrade options
- **Response**: Apply upgrade, unpause simulation
- **Required**: Yes
- **Source**: "Random sample(passive_items, 4 - len(options)) return shuffle(options)[:3] # Always 3 options"

### MenuNavigation
- **Actor**: User
- **Action**: Navigate menu options with keyboard/gamepad
- **Response**: Highlight/select menu items, key repeat after 500ms initial delay
- **Required**: Yes
- **Source**: "Key repeat for menus: 500ms initial delay, 50ms repeat rate"

## CONSTRAINTS

| Constraint | Type | Description | Source |
|------------|------|-------------|--------|
| fixed_timestep | REQUIRED | Physics must use 1/60 second timestep | "Physics must use fixed timestep: physics_delta = 1/60, accumulate remainder" |
| 60_fps_target | REQUIRED | Must maintain 60 FPS with 500 enemies/200 projectiles | "Must maintain 60 FPS (16.67ms/frame) with: 500 active enemies, 200 projectiles" |
| seeded_rng | REQUIRED | All randomness must use seeded RNG for reproducibility | "All randomness must use seeded RNG: rng = seed + game_frame + entity_id" |
| deterministic_float | REQUIRED | Use f64 or fixed-point for cross-platform determinism | "Floating-point operations must be deterministic across platforms: use f64 or fixed-point math" |
| memory_limit_web | REQUIRED | Web build max 200MB RAM usage | "Memory limit: 200MB RAM usage (web), 500MB (desktop)" |
| spatial_hashing | REQUIRED | Collision detection must use 8x8 unit spatial hashing | "Collision detection must use Spatial Hashing with cell size 8x8 units, updated every frame" |
| game_duration | REQUIRED | Each session lasts exactly 20 minutes | "Game session duration: 20 minutes (1200 seconds)" |
| weapon_max_level | REQUIRED | All weapons cap at level 8 | "Weapon level cap: max_level = 8 for all weapons" |
| enemy_stuck_handling | REQUIRED | Must teleport enemy if stuck for 2 seconds | "Anti-stuck guarantee: If enemy hasn't moved > 0.1 units in 2.0 seconds, teleport enemy 1.0 unit away" |
| save_version | REQUIRED | JSON save format with schema version 2 | "Save format: JSON with schema version current_version = 2" |

## EXTERNAL DEPENDENCIES

| Dependency | Type | Purpose | Source |
|------------|------|---------|--------|
| localStorage | API | Web save storage location | "Web: localStorage key "vampire_survivors_save_v2"" |
| filesystem | File | Desktop save storage location | "Desktop: %APPDATA%/VampireSurvivors/save_v2.json" |
| input_devices | Hardware | Keyboard and gamepad input | "Player must be controllable via keyboard (WASD/arrows) and gamepad simultaneously" |

## PRESENTATION

- **Format**: Real-time rendered game with overlay UI
- **Layout**: Top-down gameplay view with fixed-position HUD elements
- **Elements**: Health bar, timer, gold counter, experience bar, weapon/passive icons, damage numbers, level-up overlay
- **Required**: Yes
- **Source**: "HUD (Heads-Up Display): Must display with pixel-perfect positioning: Top-left: Health bar (width 200px, height 20px), HP text current/max, Top-center: Timer MM:SS, Level Lv.X, Top-right: Gold count with icon"

## AMBIGUITIES

### SaveLocationFormatting
- **Issue**: The save path format uses Windows-style notation but must work cross-platform
- **Quotes**: "Desktop: %APPDATA%/VampireSurvivors/save_v2.json" vs requirements for "Windows/Linux/Web" compatibility
- **Interpretations**:
  1. Use platform-specific environment variables on each OS
  2. Use a cross-platform config directory standardization
- **Recommendation**: Use OS-agnostic config directory (e.g., ~/.config on Linux, ~/Library/Application Support on macOS) to ensure cross-platform compatibility while maintaining the specified structure

### FlexibleMenuOptions
- **Issue**: Document specifies 3 options for level-up but suggests flexibility elsewhere
- **Quotes**: "return shuffle(options)[:3] # Always 3 options" vs "Number of options can be 3-4, but must be consistent within a game"
- **Interpretations**:
  1. Exactly 3 options always
  2. Configurable between 3-4 at game start
- **Recommendation**: Implement exactly 3 options as this matches the core algorithm and simplifies testing

### BossSpawnTiming
- **Issue**: Boss spawns replace normal waves but could interfere with spawn rate formula
- **Quotes**: "Boss spawns at: game_time = 300, 600, 900, 1200 seconds" vs "Boss spawn replaces normal spawn wave (not additional)" and spawn rate formula
- **Interpretations**:
  1. Skip the normal spawn at those exact timestamps
  2. Temporarily interrupt the spawn rate schedule
- **Recommendation**: Skip the normal scheduled spawn at exactly those frame times to maintain determinism