Move chunk, entity, world, map, sim and load_world into a new sim library crate together with the game rules from shared (player_action, action_delta, delta_action, step_allowed, tile_collidable, tick constants). shared keeps only the wire types and re-exports the rules, so server and client compile unchanged. First step toward a local single-player build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
4.5 KiB
Rust
Executable File
140 lines
4.5 KiB
Rust
Executable File
use bytemuck::{Pod, Zeroable};
|
|
|
|
// Game rules and tick constants live in the `sim` crate; re-exported here so the
|
|
// wire layer and both binaries share one vocabulary.
|
|
pub use sim::{player_action, action_delta, delta_action, step_allowed, tile_collidable,
|
|
TICK_HZ, TICKS_PER_MOVE, ACTION_WINDOW_HORIZON};
|
|
|
|
pub const MAGIC: u16 = 0x524C;
|
|
pub const VERSION: u8 = 1;
|
|
|
|
pub mod packet_type {
|
|
pub const STATE: u8 = 0;
|
|
pub const ACTION: u8 = 1;
|
|
pub const CHUNK: u8 = 2;
|
|
pub const ENTITY: u8 = 3;
|
|
pub const ENTITY_QUERY: u8 = 4;
|
|
pub const ENTITY_DETAIL: u8 = 5;
|
|
pub const PING: u8 = 6;
|
|
pub const PONG: u8 = 7;
|
|
}
|
|
|
|
/// Encodes signed chunk grid coordinates into the wire chunk_id format.
|
|
pub fn chunk_id(cx: i16, cy: i16) -> u32 {
|
|
(cx as u16 as u32) | ((cy as u16 as u32) << 16)
|
|
}
|
|
|
|
/// Decodes a wire chunk_id back to signed chunk grid coordinates.
|
|
pub fn chunk_coords(id: u32) -> (i16, i16) {
|
|
(id as u16 as i16, (id >> 16) as u16 as i16)
|
|
}
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct Header {
|
|
pub magic: u16,
|
|
pub version: u8,
|
|
pub packet_type: u8,
|
|
pub client_type: u16,
|
|
}
|
|
|
|
impl Header {
|
|
pub fn new(packet_type: u8) -> Self {
|
|
Self { magic: MAGIC, version: VERSION, packet_type, client_type: 0 }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct ChunkEntry {
|
|
pub chunk_id: u32,
|
|
pub version: u16,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct ActionPacket {
|
|
pub header: Header,
|
|
pub auth_token: u64,
|
|
/// The server tick this action is scheduled for: it executes in the movement window
|
|
/// covering that tick, or — if it arrives late — in the next window *if that slot is
|
|
/// still empty* (late actions fill gaps, they never override newer intent). A second
|
|
/// action addressed to the same window replaces the first, so a scheduled step can be
|
|
/// retracted (NOOP) or changed until its window executes. `0` means "no scheduling
|
|
/// intent": pure keep-alive / cache-ack packets that must never touch the queue.
|
|
pub target_tick: u32,
|
|
/// Slot index = (dy+1)*3 + (dx+1), dx/dy ∈ {-1, 0, 1}.
|
|
/// Slot 4 is always the player's current chunk.
|
|
pub cache: [ChunkEntry; 9],
|
|
pub player_action: u16,
|
|
}
|
|
|
|
const _: () = assert!(std::mem::size_of::<ChunkEntry>() == 6);
|
|
const _: () = assert!(std::mem::size_of::<ActionPacket>() == 74);
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct StatePacket {
|
|
pub header: Header, // 6 (offset 0)
|
|
pub tick: u32, // 4 (offset 6)
|
|
/// Slot index = (dy+1)*3 + (dx+1), dx/dy ∈ {-1, 0, 1}.
|
|
/// Slot 4 is always the player's current chunk.
|
|
pub chunks: [ChunkEntry; 9], // 54 (offset 10)
|
|
pub player_entity_id: u32, // 4 (offset 64)
|
|
pub entity_checksum: u32, // 4 (offset 68)
|
|
} // total: 72 bytes
|
|
|
|
const _: () = assert!(std::mem::size_of::<StatePacket>() == 72);
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct PingPacket {
|
|
pub header: Header,
|
|
pub timestamp_ms: u64,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct PongPacket {
|
|
pub header: Header,
|
|
pub timestamp_ms: u64,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
#[repr(C, packed)]
|
|
pub struct ChunkPacket {
|
|
pub header: Header, // 6 bytes
|
|
pub chunk: ChunkEntry, // 6 bytes
|
|
pub pal_count: u8, // 1 byte
|
|
pub palette: [u16; 64], // 128 bytes
|
|
pub tiles: [u8; 768], // 768 bytes — 1024 tiles, 6-bit packed
|
|
} // total: 909 bytes
|
|
|
|
const _: () = assert!(std::mem::size_of::<ChunkPacket>() == 909);
|
|
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
pub struct EntityEntry {
|
|
pub id: u32,
|
|
pub type_id: u16,
|
|
pub pos_x: i16,
|
|
pub pos_y: i16,
|
|
pub hp: u16,
|
|
pub hp_max: u16,
|
|
pub elo: u16,
|
|
pub entity_flags: u8,
|
|
pub meta_len: u8,
|
|
}
|
|
const _: () = assert!(std::mem::size_of::<EntityEntry>() == 18);
|
|
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
pub struct EntityPacket {
|
|
pub header: Header,
|
|
pub tick: u32,
|
|
pub entity_count: u8,
|
|
pub packet_flags: u8,
|
|
pub entities: [EntityEntry; 66],
|
|
}
|
|
const _: () = assert!(std::mem::size_of::<EntityPacket>() == 1200);
|