# Jump 'n Run - Game Design Document

## Concept

2D side-scrolling platformer with run-and-gun combat.
Inspired by **Jazz Jackrabbit 2**, **Metal Slug**, **Mega Man**, and classic 90s side-scrollers.

**Theme:** Sci-fi / space western (think *Cowboy Bebop*).
The player is a bounty hunter traveling between planets and space stations,
taking on jobs that play out as platformer levels.

---

## Game Structure

### Two Main Modes

**1. World Map (Spacecraft Navigation)**
- Top-down or side-view map showing planets, stations, asteroids
- Player pilots a spacecraft between locations
- Each location is a level (or hub with multiple levels)
- Map could be a simple node graph or free-flight between points
- Unlocking new areas as the story progresses
- Ship could have upgrades (fuel range, shields, scanner)

**2. Platformer Levels**
- Core gameplay: run, jump, shoot, explore
- Each level is a self-contained planet/station/ship with its own atmosphere
- Levels end with reaching an exit zone (or defeating a boss)
- Collectibles: currency (bounty credits), health pickups, weapon upgrades
- Optional objectives for bonus rewards

---

## Atmosphere System

Each level defines its own atmosphere, affecting gameplay feel:

| Property       | Effect                                      | Example Values          |
|----------------|---------------------------------------------|-------------------------|
| `GRAVITY`      | Fall speed and jump arc                     | 980 (earth), 400 (moon) |
| `WIND`         | Constant horizontal force on entities       | -50 to 50 px/s^2        |
| `STORM`        | Visual effect + periodic strong wind gusts  | 0 (calm) to 3 (severe)  |
| `DRAG`         | Air resistance (underwater, thick atmo)     | 0.0 (none) to 0.9       |
| `BG_COLOR`     | Background clear color                      | hex color               |
| `PARALLAX_FAR` | Far background image path                   | assets/bg/stars.png     |
| `PARALLAX_NEAR`| Near background image path                  | assets/bg/nebula.png    |
| `MUSIC`        | Level music track                           | assets/music/level1.ogg |
| `PALETTE`      | Color mood (warm, cold, toxic, void)        | tint/filter values      |

Already implemented: `GRAVITY`, `WIND`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR` (all per-level). Parallax backgrounds are procedurally generated (starfield + nebula) when no image path is specified.

---

## Player

- **Size:** 12x16 px hitbox, 16x16 sprite
- **Movement:** Run, jump (variable height, coyote time, jump buffer)
- **Dash:** C key, directional (horizontal, up, diagonal, down while airborne).
  Brief i-frames during dash. 0.15s duration, 0.4s cooldown.
- **Combat:** Shoot projectiles (X or Space), directional aiming with UP key
  (straight up, or diagonal when combined with LEFT/RIGHT)
- **Camera:** Holding UP while standing still pans the camera upward
- **Health:** 3 HP (expandable), invincibility frames + knockback on hit
- **Death / Respawn:** Death animation plays, then 1s delay before respawning at level spawn with full HP, charges, and brief invincibility. Falling off the level kills instantly with 0.3s delay.
- **Future abilities:**
  - Wall slide / wall jump
  - Weapon switching (multiple projectile types from the def system)
  - Double jump (upgrade)
  - Melee attack (close range, stronger)

---

## Enemies

### Implemented
- **Grunt** — Red spiky ground patrol. Walks back and forth, turns at edges/walls. 2 HP.
- **Flyer** — Purple bat-like. Bobs in air, chases player when close, shoots fireballs. 1 HP.
- **Turret** — Stationary, rotates to aim at player, fires periodically

- **Charger** — Ground patrol, detects player in 200 px horizontal LOS, ALERT → CHARGE (150 px/s) → STUNNED on wall hit. 2 HP.
- **Spawner** — Stationary, spawns grunts every 4.5 s (max 3 alive). 3 HP, destructible.
- **Laser Turret** — State machine (IDLE → CHARGING → FIRING → COOLDOWN). Per-pixel beam raycast. Fixed variant aims left; tracking variant rotates toward player at 1.5 rad/s.

### Planned
- **Robot** — Slow, heavy ground patrol. Sturdy (4 HP), armored appearance.
  Walks deliberately, doesn't flinch from knockback. Punishes careless
  approaches — player must keep distance or use high-damage weapons.
  Exclusive to space station levels.
- **Rocket Turret** — Stationary launcher, two-stage attack. Stage 1: rocket
  plops up out of the turret with a visible arc (telegraph, ~0.6 s hang time),
  giving the player time to react. Stage 2: rocket ignites boosters and tracks
  the player with homing guidance. Moderate turn rate so skilled players can
  dodge or bait it into walls. Destroyable in flight. Exclusive to space
  station levels.
- **Shielder** — Has a directional shield, must be hit from behind or above
- **Boss** — Large, multi-phase encounters. One per world area.

---

## Hazards & Mechanics

### Implemented
- **Flame Vent** — Floor-mounted grate, toggles on/off on a timer
- **Force Field** — Vertical energy barrier, toggled by switch/timer
- **Moving Platform** — Horizontal/vertical patrol between two points
- **Laser Turret** — See Enemies above (also functions as a hazard)

### Planned
- **Bouncer** — Launch pad that shoots the player into the air on contact.
  Two variants: straight (vertical impulse only) and angled (rotatable,
  placed at arbitrary angles to launch the player diagonally or sideways).
  Could also affect enemies and projectiles for puzzle potential.

---

## Weapons / Projectiles

Data-driven system: each weapon type is a `ProjectileDef` struct describing speed,
damage, lifetime, hitbox, behavior flags, and animations. Adding a new weapon =
adding a new def. See `src/game/projectile.h` for the full definition.

### Behavior flags
- `PROJ_PIERCING` — Passes through enemies (up to `pierce_count` times)
- `PROJ_BOUNCY` — Ricochets off walls (up to `bounce_count` times)
- `PROJ_GRAVITY` — Affected by level gravity (scaled by `gravity_scale`)
- `PROJ_HOMING` — Steers toward nearest valid target

### Implemented weapon defs
| Weapon          | Speed | Damage | Special                  | Owner  |
|-----------------|-------|--------|--------------------------|--------|
| `WEAPON_PLASMA` | 400   | 1      | Default player weapon    | Player |
| `WEAPON_SPREAD` | 350   | 1      | Short range, fan pattern | Player |
| `WEAPON_LASER`  | 600   | 1      | Pierces 3 enemies        | Player |
| `WEAPON_ROCKET` | 200   | 3      | Slight gravity drop      | Player |
| `WEAPON_BOUNCE` | 300   | 1      | 3 wall bounces + gravity | Player |
| `WEAPON_ENEMY_FIRE` | 180 | 1    | Enemy fireball           | Enemy  |

### Directional aiming
- Forward (default) — shoots horizontally in facing direction
- Up — hold UP to shoot straight up
- Diagonal up — hold UP + LEFT/RIGHT to shoot at 45 degrees

---

## Levels

### Format (.lvl)
Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `WIND`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR`, `TRANSITION_IN`, `TRANSITION_OUT`, `TILEDEF`, `ENTITY`, `EXIT`, `LAYER`

**Needed additions:**
- `STORM`, `DRAG` — Remaining atmosphere settings

### Level Ideas

0. **Intro Level (Moon)** - Low gravity, bright surface, spacey/no obstacles
1. **Mars Surface** - Low gravity, red surface, spacey, little to no obstacles, transition area is entry to base
2. **Mars Base** - Normal gravity, very vertical, narrow, 90 degree turns, lots of enemies 
3. **Derelict Station** — Low gravity, dark, flickering lights, abandoned corridors
4. **Desert Planet (Saturn)** — High gravity, sand storm wind, bright orange palette
5. **Gas Giant (Jupiter)** — Very low gravity, floating platforms, toxic atmosphere
6. **Asteroid Belt** — Zero-G sections, small disconnected platforms
7. **Space Freighter** — Normal gravity, tight corridors, turret enemies
8. **Ice World** — Normal gravity, strong winds, slippery surface

### Old Space Station Campaign

Abandoned orbital station overrun by malfunctioning security systems. Only
robotic enemies remain — no organic creatures. Cold, industrial aesthetic
with metal walls, exposed pipes, warning lights, and airlock doors.

**Enemy roster (station-exclusive):**
- Robots — slow, heavy patrols that absorb punishment
- Turrets — standard rotating turrets from earlier levels
- Rocket Turrets — two-stage homing rockets (plop-up telegraph → boost + track)

**Level sequence:**
- **station01.lvl** (Docking Bay) — Spacecraft lands at the station exterior.
  Normal gravity, wide open hangar area easing the player in. A few robots
  and a turret introduce the new enemy types. Exit leads inside.
- **station02.lvl** (Reactor Core) — Vertical level, tight corridors around
  a central reactor shaft. Rocket turrets cover long sightlines, robots
  block narrow passages. Elevator transition into generated levels.
- **generate:old_station** (Security Decks) — Procedurally generated
  interior levels (2-3 before the boss arena). Increasing density of
  robots, turrets, and rocket turrets. Tight rooms, low ceilings,
  interlocking corridors. Difficulty scales with depth.
- **station03.lvl** (Command Bridge / Boss Arena) — Final station level.
  Large arena with a boss encounter (station security chief or haywire
  defense mainframe). Heavy use of rocket turrets as stage hazards.

---

## World Map

### Structure
- Graph of nodes (planets/stations) connected by routes
- Player selects destination, spacecraft flies there (short animation or instant)
- Some routes may require fuel/upgrades to unlock
- Map reveals new nodes as levels are completed

### Implementation Notes
- Separate game state from the platformer (own update/render)
- Needs: node data structure, spacecraft position, route rendering
- Could start simple: linear level select, evolve into open map

---

## Technical TODO

### High Priority
- [x] Entity spawn directives in .lvl format (`ENTITY` directive)
- [x] Level exit zones and level transitions
- [x] Dash mechanic
- [x] Particle system (death puffs, landing dust, projectile impact sparks, wall slide dust)
- [x] Screen shake on damage / enemy kills
- [x] Sound effects (jump, shoot, hit, enemy death, dash)
- [x] Basic HUD (health hearts + jetpack charges)

### Medium Priority
- [x] In-game level editor (tile/entity placement, save/load, test play)
- [x] Wind atmosphere property (`WIND` directive, affects all entities/particles/projectiles)
- [ ] Drag atmosphere property
- [x] Parallax scrolling backgrounds (procedural stars + nebula, or from image files)
- [x] Per-level background color (`BG_COLOR` directive)
- [x] Music playback per level (`MUSIC` directive)
- [ ] Weapon switching system
- [x] Pickup entities (health, jetpack refill, drone companion)
- [ ] Better tileset art (space-themed)
- [ ] Player sprite polish (more animation frames)
- [x] Death / respawn system
- [x] Pause menu

### Low Priority (Future)
- [ ] World map mode
- [ ] Spacecraft navigation
- [ ] Boss encounters
- [ ] Dialogue / mission briefing system
- [ ] Save system
- [ ] Controller support (SDL_GameController is initialized)
- [ ] Multiple playable characters

---

## Art Direction

- Pixel art, 16x16 tile grid
- Logical resolution: 640x360 (rendered at 2x = 1280x720)
- Nearest-neighbor scaling for crisp pixels
- Dark, moody color palettes with bright projectile/effect accents
- Inspired by: Cowboy Bebop color grading, retro sci-fi, neon-on-dark

---

## Controls

| Action    | Key          | Status        |
|-----------|--------------|---------------|
| Move      | Arrow keys   | Implemented   |
| Jump      | Z / Space    | Implemented   |
| Shoot     | X            | Implemented   |
| Aim up    | UP (+ shoot) | Implemented   |
| Aim diag  | UP+LEFT/RIGHT (+ shoot) | Implemented |
| Dash      | C            | Implemented   |
| Look up   | UP (stand still) | Implemented |
| Pause     | Escape       | Implemented   |

---

## Game Analytics & Highscores

Track comprehensive play session analytics and submit them to a backend service
for leaderboards and gameplay insights. Data flows from C → JS (via EM_JS) →
backend API (via fetch). Desktop builds can write to a local file as fallback.

### Metrics to track (GameStats struct)

**Per-run (reset on new game / restart):**

| Metric                  | Type     | Notes                                       |
|-------------------------|----------|---------------------------------------------|
| `levels_completed`      | int      | Incremented on each level exit trigger       |
| `enemies_killed`        | int      | Total kills across all levels                |
| `kills_by_type[N]`     | int[]    | Kills broken down by enemy type              |
| `deaths`                | int      | Player death/respawn count                   |
| `time_elapsed_ms`       | uint32_t | Wall-clock play time (accumulate dt)         |
| `shots_fired`           | int      | Total projectiles spawned by player          |
| `shots_hit`             | int      | Player projectiles that connected with enemy |
| `damage_taken`          | int      | Total HP lost (before death resets)          |
| `damage_dealt`          | int      | Total HP dealt to enemies                    |
| `dashes_used`           | int      | Dash activations                             |
| `jumps`                 | int      | Jump count                                   |
| `distance_traveled`     | float    | Horizontal pixels traversed                  |
| `pickups_collected`     | int      | Health, jetpack, weapon pickups              |
| `longest_kill_streak`   | int      | Max kills without taking damage              |
| `current_kill_streak`   | int      | (internal, not submitted)                    |

**Per-level snapshot (ring buffer or array, flushed on level exit):**
- Level name / generator tag
- Time spent in level
- Kills in level
- Deaths in level
- Health remaining on exit

### Data flow

```
C (GameStats)                JS (shell.html)              Backend API
 │                            │                            │
 ├─ on victory/game-over ────→│                            │
 │  EM_JS: submit_run()       ├── POST /api/runs ────────→│
 │                            │   { stats JSON }           │── store in DB
 │                            │                            │
 ├─ on leaderboard open ─────→│                            │
 │  EM_JS: fetch_leaderboard()├── GET /api/leaderboard ──→│
 │                            │←─ JSON [ top N runs ] ─────│
 │←─ KEEPALIVE callback ──────│                            │
 │  write to C memory         │                            │
```

### Backend service

Small HTTP API deployed alongside the game on k3s. Receives run data as
JSON, stores in a lightweight DB (SQLite or Postgres), serves leaderboard
queries. Endpoints:

- `POST /api/runs` — submit a completed run (all metrics above)
- `GET  /api/leaderboard` — top N runs, sortable by score/time/kills
- `GET  /api/stats` — aggregate stats (total runs, total kills, avg time)

A composite score formula ranks runs for the leaderboard, e.g.:
`score = (enemies_killed * 100) + (levels_completed * 500) - (deaths * 200) - (time_elapsed_ms / 1000)`

### Integration points in C

- **GameStats struct** in `main.c` or a new `src/game/stats.h` module
- **Kill counting:** hook into entity death in `level.c` damage handling
- **Shot tracking:** increment in `player.c` shoot logic
- **Time tracking:** accumulate `dt` each frame in `MODE_PLAY`
- **Distance:** accumulate abs(vel.x * dt) each frame
- **Jumps/dashes:** increment in `player.c` at point of activation
- **Level snapshots:** capture on exit trigger before level_free
- **Submission:** call `EM_JS` function from the victory path in `main.c`

### Anti-tamper: HMAC signature

All submissions are signed client-side in C/WASM before reaching JS,
preventing casual forgery (console injection, proxy interception, modified
payloads). The signature lives in compiled WASM — not trivially visible
like plain JS — raising the effort required to cheat.

**Scheme: HMAC-SHA256**

1. A shared secret key is embedded in the C source (compiled into WASM).
   Not truly hidden from a determined reverse-engineer, but opaque to
   casual inspection.
2. On submission, the C code:
   - Serializes the `GameStats` struct to a canonical JSON string
   - Requests a one-time nonce from the backend (`GET /api/nonce`)
   - Computes `HMAC-SHA256(secret, nonce + json_payload)`
   - Passes the JSON, nonce, and hex signature to JS via `EM_JS`
3. JS sends all three to the backend in the POST body.
4. Backend recomputes the HMAC with its copy of the secret, verifies the
   signature matches, and checks the nonce hasn't been used before (replay
   protection).

**What this prevents:**
- Browser console `fetch("/api/runs", { body: fakeStats })` — no valid sig
- Proxy/MITM payload modification — signature won't match
- Replay attacks — nonce is single-use

**What this does NOT prevent:**
- Reverse-engineering the WASM binary to extract the key
- Memory editing during gameplay (modifying stats before signing)
- A fully custom WASM build that signs fabricated data

**Implementation:**
- SHA-256 and HMAC can be implemented in ~200 lines of C (no external
  dependency). Alternatively use a small embedded library like micro-ecc
  or TweetNaCl.
- The secret key should be split across multiple static arrays and
  reassembled at runtime to deter simple string scanning of the binary.
- Native builds (Linux/Windows) use the same HMAC logic, posting via
  libcurl or a lightweight HTTP client.

### Anti-tamper: server-side plausibility checks

The backend validates every submission against known game constraints.
This is independent of the HMAC and catches cheats that produce valid
signatures (memory edits, custom builds).

**Hard limits (reject immediately):**
- `levels_completed` cannot exceed the total campaign length
- `enemies_killed` cannot exceed max possible spawns per level chain
- `shots_hit` cannot exceed `shots_fired`
- `damage_dealt` cannot exceed `enemies_killed * max_enemy_hp`
- `accuracy` (`shots_hit / shots_fired`) above 99% is flagged
- `time_elapsed_ms` below a minimum threshold per level is impossible
  (speed-of-light check: level_width / max_player_speed)

**Soft limits (flag for review, don't reject):**
- Zero deaths across the entire campaign
- Kill streak equal to total kills (never took damage)
- Unusually low time relative to levels completed
- Distance traveled below expected minimum for the level chain

**Rate limiting:**
- Max 1 submission per IP per 60 seconds
- Max 10 submissions per IP per hour
- Duplicate payload detection (exact same stats = reject)

**Schema:**
```
runs table:
  id, player_name, submitted_at, ip_hash,
  signature_valid (bool), plausibility_flags (bitmask),
  levels_completed, enemies_killed, kills_by_type (json),
  deaths, time_elapsed_ms, shots_fired, shots_hit,
  damage_taken, damage_dealt, dashes_used, jumps,
  distance_traveled, pickups_collected, longest_kill_streak,
  level_snapshots (json), composite_score
```

Flagged runs are stored but excluded from the public leaderboard.

### Requirements before this works

1. The game needs an ending — either a final boss level with empty exit
   target, or a depth limit on the station generator
2. A `MODE_VICTORY` game state showing final stats + leaderboard
3. The backend service (container image + k8s manifests)
4. Player name input (simple text prompt in JS, or anonymous with a
   generated handle)
5. HMAC-SHA256 implementation in C (or vendored micro-library)
6. Nonce endpoint on the backend
7. Plausibility rule set derived from game constants (MAX_ENTITIES,
   level chain length, player speed, enemy HP values)

---

## Reference Games
- Jazz Jackrabbit 2 (movement feel, weapon variety, level design)
- Metal Slug (run-and-gun, enemy variety, visual flair)
- Mega Man X (dash, tight controls)
- Cowboy Bebop (aesthetic, tone, music style)
