Roadmap

RPG98 Roadmap Phase 3 — Combat & Classes

Phase 3 — Combat & Classes

Version: v0.3.0 Status: ✅ Complete


Goal

Build the foundational systems that make combat possible: a proper game state machine, a UDP network channel, a server tick loop, three playable classes, enemy AI, and a complete basic combat loop. By the end, the game has a real beginning, middle, and end — pick a class, fight enemies, extract or die.

Deliverable

A complete combat loop on real Miyoo hardware: title screen → class select → fight enemies → use your starter ability → die or extract. State transitions feel clean and the combat is responsive over WiFi.


Planned

1. State Machine (Client)

The client currently has one state: connect and render the dungeon. Phase 3 introduces a proper state machine:

State Description
Boot Load core assets, display title screen, auto-advance to Main Menu
Main Menu Login / connect, quit. Transitions to Class Select on success
Class Select Pick Warrior, Skirmisher, or Arcanist. Sends class to server
Loading Receive map data, show progress text ("The void stirs…")
Dungeon Active run — movement, combat, looting
Death Brief screen, return to Main Menu
Extraction Success Brief screen listing extracted items, return to Main Menu
System Menu Start button overlay — accessible from Dungeon state. Logout / quit

State transitions are server-driven for dungeon events (death, extraction). Menu navigation is purely client-side.

2. Networking: UDP Channel

Add a UDP socket alongside the existing TCP connection. Used for high-frequency, loss-tolerant messages:

  • Client → Server: PlayerInput (position, facing, action flags) sent every tick
  • Server → Client: WorldSnapshot (all entity states) broadcast every tick (~33ms at 30Hz)

TCP remains for reliable messages: connect, class select, loot pickup, extraction, death confirmation.

3. Server: 30Hz Tick Loop

The server runs a Tokio interval at 30Hz independently of client connections. Each tick:

  1. Process pending PlayerInput packets
  2. Run enemy AI (move, attack)
  3. Resolve hit detection and damage
  4. Broadcast WorldSnapshot via UDP to all clients in the dungeon

4. The Three Classes

Defined in shared/src/lib.rs. All classes are solo-viable — differences are in playstyle, not raw power.

Class Identity HP Starter Ability
Warrior Tanky frontline fighter High Second Wind — burst heal + brief damage reduction
Skirmisher Mobile damage dealer Medium Shadow Mend — quick self-heal + brief speed boost
Arcanist Ranged spellcaster Low Arcane Restore — mana refill + minor damage shield

Starter abilities are on Y, permanent, and never lost on death.

pub enum Class { Warrior, Skirmisher, Arcanist }

pub struct ClassStats {
    pub max_health:        f32,
    pub damage_multiplier: f32,
    pub move_speed:        f32,
}

pub struct EntityState {
    pub id:         u32,
    pub x:          f32,
    pub y:          f32,
    pub health:     f32,
    pub max_health: f32,
    pub class:      Option<Class>,
}

WorldSnapshot carries a Vec<EntityState> covering players and enemies.

5. Enemy AI

Enemies are seeded at dungeon generation time. Their state lives entirely on the server.

  • Patrol: wander within a small radius when no player is nearby
  • Chase: move toward nearest player when in detection range
  • Attack: melee hit when within attack range; brief back-off after each swing to prevent stunlocking
  • Enemy health, damage, and detection range scale with dungeon tier (placeholder values in v0.3.0)

6. Basic Combat

  • A (tap) — Basic attack. Sends PlayerInput with attack flag; server resolves hit detection
  • Y — Starter ability. Server validates cooldown and applies effect
  • Damage numbers already built (floating, alpha-fade) — wired to server-confirmed hits only
  • HP bars rendered above all entities (thin rect, green → red)
  • Player death: server sends Death message → client transitions to Death state
  • Enemy death: entity removed from next WorldSnapshot

7. Potions (Basic)

  • R1 (tap) — Use health potion. Server validates and applies heal
  • 3 potions per run (server-tracked count)
  • No vendor or UI yet — potions are granted automatically at run start as a placeholder
  • Full potion purchasing and quick-use menu comes in Phase 4

8. Client: Prediction & Reconciliation

  • Movement inputs play locally immediately (prediction)
  • Server sends authoritative WorldSnapshot every ~33ms
  • If server position disagrees, client lerps to correct position over 2–3 frames
  • No prediction on damage — damage numbers and HP changes only appear on server confirmation

Deferred to v0.3.x Patches

These are combat mechanics from the design doc that aren't needed for the basic loop but will be added before Phase 4:

  • Sprint (B hold) + stamina system
  • Charged / heavy attack (A long press)
  • Shield blocking (R1 hold) + shield-walk animations
  • Class special movement abilities (B quick-tap + hold)
  • Target cycling (L1)
  • Loading screen with progress bar and baked static background (Page 11 optimization)

Controls Reference (v0.3.0)

All input via evdev (key codes, not SDL2 scancodes). See client/src/input.rs for the authoritative mapping.

Button Dungeon Action Menu Action
D-pad Move (8-directional) Navigate
A Basic attack Confirm
B (reserved for sprint — v0.3.x) Back / Cancel
Y Starter ability
X (reserved for Ability Tome — Phase 4)
R1 Use health potion
Start System menu
Select (reserved for inventory — Phase 4)

Deploy & Test Loop

# Server
cargo run -p server

# Build + deploy to Miyoo Mini Plus
./deploy.sh plus

# Build + deploy to Miyoo Mini Flip
./deploy.sh flip

Key things to test on hardware:

  • Each class feels distinct in movement speed and survivability
  • Combat is responsive — no rubberbanding on LAN
  • Enemy AI chases and attacks without getting stuck
  • Starter ability cooldown is correct server-side
  • Death and extraction transitions are clean
  • 30+ FPS with 6+ enemies on screen

Completion Checklist (Planned)

State Machine

  • Boot → Main Menu → Class Select → Loading → Dungeon → Death / Extraction flow
  • System Menu overlay from Dungeon state (Start button)
  • Server-driven death and extraction triggers

Networking

  • UDP socket open alongside TCP
  • PlayerInput sent via UDP every tick
  • WorldSnapshot broadcast via UDP from server every tick
  • Client prediction + reconciliation for movement

Server

  • 30Hz Tokio tick loop
  • Enemy spawn at dungeon generation
  • Enemy patrol / chase / attack AI
  • Hit detection and damage calculation (server-side only)
  • Ability cooldown tracking per player
  • Potion count tracking per player (3 per run, server-authoritative)

Classes & Combat

  • Warrior, Skirmisher, Arcanist defined in shared crate with distinct base stats
  • Class transmitted to server at Class Select
  • Basic attack (A) wired to server hit detection
  • Starter ability (Y) for each class with cooldown
  • Potions (R1) with server-validated heal
  • HP bars on all entities
  • Floating damage / heal numbers on server-confirmed hits
  • Enemy sprites rendered from monster sprite sheet
  • Player death → Death state transition
  • Enemy death → removed from WorldSnapshot

Hardware

  • 30+ FPS on Miyoo Mini Plus with multiple enemies

Actual

What Was Built

  • State machine: Boot → MainMenu → ClassSelect → Loading → Dungeon → Death → ExtractionSuccess — confirmed working on Miyoo Mini Plus hardware
  • System Menu overlay (Start button) with grid/hitbox toggles and exit
  • Class selection screen (Warrior / Skirmisher / Arcanist) with D-pad navigation
  • UDP game channel: client sends PlayerInput every frame, server 30Hz tick broadcasts WorldSnapshot
  • Async server rewrite: #[tokio::main], GameWorld shared state, per-connection tasks, UDP recv loop
  • deploy.sh: unified deploy script for both Miyoo devices
  • Full combat loop: server-authoritative hit detection (A), damage calc by class, enemy melee on contact
  • Starter abilities (Y) for all 3 classes with distinct effects and server-side cooldown tracking
  • Potions (R1): 3 per run, 40% heal, server-validated
  • Enemy sprites rendered per kind+variant from assets/monster_sprites/
  • Player HUD: HP bar, ability state indicator, potion count
  • Tile-locked A* enemy chase AI with aggro/leash, reactive repathing, animation hysteresis
  • Enemy patrol state (wander in radius when no player in range)
  • Server sends PlayerDied / PlayerExtracted via TCP; client transitions to correct outcome screen
  • Extraction triggered server-side when player center enters portal tile AABB

Deviations & Discoveries

  • System Menu already existed from Phase 2; carried forward into the state machine as-is
  • UDP registration is implicit: server learns the client's UDP address from the first PlayerInput datagram received (no explicit UDP handshake needed)
  • Wall-adjacency A* inflation removed — caused pathfinding failure in corridors; tile-locked movement made it unnecessary

What Was Deferred

  • Client prediction + reconciliation — deferred to v0.3.x; position snapshots arrive but corrections aren't applied yet
  • Sprint, charged attack, shield blocking, target cycling — deferred to v0.3.x patches per plan

Completion Checklist (Actual)

  • Boot → Main Menu → Class Select → Loading → Dungeon → Death / Extraction flow
  • System Menu overlay from Dungeon state (Start button)
  • Server-driven death and extraction triggers
  • UDP socket open alongside TCP
  • PlayerInput sent via UDP every tick
  • WorldSnapshot broadcast via UDP from server every tick
  • Client prediction + reconciliation for movement (deferred to v0.3.x)
  • 30Hz Tokio tick loop
  • Enemy spawn at dungeon generation
  • Enemy patrol / chase / attack AI
  • Hit detection and damage calculation (server-side only)
  • Ability cooldown tracking per player
  • Potion count tracking per player
  • Warrior, Skirmisher, Arcanist defined in shared crate with distinct base stats
  • Class transmitted to server at Class Select
  • Basic attack (A) wired to server hit detection
  • Starter ability (Y) for each class with cooldown
  • Potions (R1) with server-validated heal
  • HP bars on all entities
  • Floating damage / heal numbers on server-confirmed hits
  • Enemy sprites rendered from monster sprite sheet
  • Player death → Death state transition
  • Enemy death → removed from WorldSnapshot
  • 30+ FPS on Miyoo Mini Plus with multiple enemies

Previous: Phase 2 — World & Movement Next: Phase 4 — Loot, Extraction & Hub