flatland-protocol 0.1.0

Flatland3 wire protocol types and codecs
Documentation
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub type EntityId = u64;
pub type Tick = u64;
pub type Seq = u32;
pub type SessionId = u64;

/// World position `(x, y, z, w, t)` — `t` reserved at 0.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct WorldCoord {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub w: u32,
    pub t: u32,
}

impl WorldCoord {
    pub fn surface(x: f32, y: f32) -> Self {
        Self {
            x,
            y,
            z: 0.0,
            w: 0,
            t: 0,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Velocity2D {
    pub vx: f32,
    pub vy: f32,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Transform {
    pub position: WorldCoord,
    pub yaw: f32,
    pub velocity: Velocity2D,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct EntityState {
    pub id: EntityId,
    pub transform: Transform,
}

/// Client → server gameplay input (reliable, sequenced per entity).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Intent {
    Move {
        entity_id: EntityId,
        forward: f32,
        strafe: f32,
        seq: Seq,
    },
    Stop {
        entity_id: EntityId,
        seq: Seq,
    },
}

/// Server → client AOI-filtered entity updates for one sim tick.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TickDelta {
    pub tick: Tick,
    pub entities: Vec<EntityState>,
}

/// Full state on region enter or reconnect.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Snapshot {
    pub tick: Tick,
    pub chunk_rev: u64,
    pub entities: Vec<EntityState>,
}

/// Every on-wire payload is wrapped for versioning and codec uniformity.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Envelope<T> {
    pub protocol_version: u16,
    pub payload: T,
}

impl<T> Envelope<T> {
    pub fn new(payload: T) -> Self {
        Self {
            protocol_version: crate::PROTOCOL_VERSION,
            payload,
        }
    }
}

/// Session handshake after transport connect.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Hello {
    pub client_name: String,
    pub protocol_version: u16,
    #[serde(default)]
    pub auth: AuthCredential,
    /// Required for session auth; embedded in `ApiToken` variant otherwise.
    #[serde(default)]
    pub character_id: Option<Uuid>,
}

/// How the client authenticates to the game gateway.
/// Uses default serde enum encoding (postcard-compatible; not internally tagged).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthCredential {
    DevLocal,
    Session { token: String },
    ApiToken { token: String, character_id: Uuid },
}

impl Default for AuthCredential {
    fn default() -> Self {
        Self::DevLocal
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Welcome {
    pub session_id: SessionId,
    pub entity_id: EntityId,
    pub snapshot: Snapshot,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ServerMessage {
    Welcome(Welcome),
    Tick(TickDelta),
    IntentAck { entity_id: EntityId, seq: Seq, tick: Tick },
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ClientMessage {
    Hello(Hello),
    Intent(Intent),
    Disconnect,
}