446 lines
20 KiB
Markdown
446 lines
20 KiB
Markdown
# 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)
|