6.9 KiB
Executable File
6.9 KiB
Executable File
Roadmap
Workspace layout: shared/ (wire types), server/ (authoritative sim), client/ (render
terminal). The platform layer — window, GPU, input, RGB332 palette — lives in the external
pbio crate (git dependency), not in this repo.
Achieved milestones
- TGA loader:
Image,from_tga,to_tileset,decode_rleinclient/src/assets.rs - Pixel helpers:
set_pixel,extract,blit,blit_tileinclient/src/game/pixelhelper.rs— signedi32offsets, clip against all four framebuffer edges, index 0 = transparent - Input system:
GameAction,InputMap,InputStateinclient/src/input.rs(overpbio::Key) - Protocol spec:
notes/protocol.md— Header, State, Action, Chunk, Entity, EntityQuery, EntityDetail, Ping/Pong; reliability model - Shared crate: packet structs as
bytemuck::Podwith compile-time size assertions,player_action/packet_typeconstants,chunk_id/chunk_coords - Server binary: 24 Hz tick loop, 3×3-chunk test world (
build_world), wall collision, per-client action queue,ChunkPacketdispatch on cache miss, stale-client eviction - Client net:
NetClientinclient/src/net.rs, non-blocking UDP recv,StatePacket/EntityPacket/ChunkPacket/PongPacketdispatch, entity-checksum retransmit - End-to-end loop: player moves on server, position reflected in
EntityPacket, rendered on client - Tile rendering: chunk-based world rendering from received
ChunkPacketdata (6-bit unpack) - Tick-based movement: movement resolved in
entity_tick, currently every 4th tick (sim.rs) - Sprite-based entity rendering: entity
type_id→ tile inentities.tga, blitted with transparency - Phase-staggered broadcast: per-client
send_phase, flat outbound load across ticks - Ping/Pong RTT measurement (
PingPacket/PongPacket) - Connection timeout: client emits
Disconnectedafter 10 s without aStatePacket; server evicts clients unseen for 10 s - Map loader: Tiled-CSV parser in
server/src/map.rs(flip-flag masking, content-derived dimensions),load_worldslices it into chunks, invisible solid border at the map rim. Replaces the hardcodedbuild_world. Seenotes/07-map-loader.md - Client movement prediction: queued unconfirmed steps (
pathinclient/src/game.rs), paced at the server's 6 Hz movement cadence, reconciled againstEntityPacketpositions, dropped after idle timeout. Shared collision vocabularyshared::tile_collidable(currently empty — every tile walkable) keeps client prediction and server sim in lockstep. - Click-to-move: framebuffer click → world tile, A* over the chunk cache
(
client/src/game/pathfind.rs, unknown chunks count as blocked), route translated into cardinal actions one step per movement interval — the server only ever sees N/E/S/W and stays authoritative. Keyboard input cancels the route; each step is re-validated at send time and a blocked step voids the route. In-flight steps render bright blue, planned route dim blue.
Upcoming work
06 — Hybrid tick loop (partially done)
Design of record: notes/06-hybrid-tick-loop.md (24 Hz base tick + stride scheduling).
This supersedes the original 20/10/1 Hz three-tier sketch.
- 24 Hz base loop (
server/src/main.rs) - Phase-staggered broadcast at 12 Hz/client (
send_phaseinserver/src/net.rs) - Stride layout settled:
entity_tickat%4(6 Hz, sets movement rate), broadcast at%2(12 Hz/client). The06note now documents this and why. - Action-point model (
energy/speedper entity, act whenenergy >= ACTION_COST). Removed fromentity.rsfor now as premature — reintroduce when NPCs (08) actually need it. Thenentity_tickcan move to%2(12 Hz) andspeedsets the effective movement rate.
07 — Tiled map loader (done — see Achieved / notes/07-map-loader.md)
Remaining follow-ups when the need is concrete:
- Collision vocabulary:
tile_flagsinmap.rsis currently empty (all walkable). Fill in collidable / opaque tile ids as the level design requires. - Tile flipping: orientation is discarded on load; revisit with the sprite pass (09).
- Multiple / object layers (spawns, triggers) — not yet parsed.
08 — Basic NPC entity + AI budget
One dumb wandering enemy. Validates the simulation architecture before complexity accumulates. Depends on the action-point model from 06 being reintroduced.
- Add
EntityKind::Npcwith athink() -> u32method returning budget cost - Per sim-tick: distribute
think_budget = BASE / (clients + 1)across entities ordered by player proximity - Complex entities consume more budget; simple ones less. Loop breaks at zero — natural load shedding.
- No framework. No trait objects yet. A match on
EntityKindis fine.
let mut budget: u32 = BASE_BUDGET / (client_count + 1).max(1);
for entity in entities_by_player_proximity() {
if budget == 0 { break; }
budget = budget.saturating_sub(entity.think(&mut world));
}
09 — Client camera (sprite rendering already done)
Sprite rendering already landed (see Achieved). What remains is the camera:
- Smooth camera: lerp between last known and current server position; do not snap
(currently
player_possnaps hard to the server position ingame.rs) - Store previous + current position per entity, interpolate on render
- Camera math is architectural — affects how entity state is stored. Do it before UI.
Later — UI / HUD
- Bitmap font renderer: 8×8 glyph sheet TGA,
draw_text(frame, font, x, y, color, text)— seenotes/04 - UI primitives in
client/src/ui.rs:fill_rect,draw_rect,draw_hbar— seenotes/05 - HUD layout (320×240): HP bar + player name bottom 16px, message log right panel
- A real clip-rect on the blit primitives belongs here — once the viewport and HUD panels are two distinct regions, the abstraction earns its keep. Not before.
Later — Robustness + auth
- Reactive pathfinding: replan from the confirmed position toward the original goal when a route step is blocked or the server diverges from the prediction (currently the route is simply voided)
- Auth token handshake: replace source-address identity (
auth_tokenfield exists but is unused) - Multi-datagram
EntityPacket: server currently truncates at 66 entities (net.rsTODO) - Asset embedding:
include_bytes!for single-binary distribution
Later — World depth
Once the simulation architecture is stable and a real level exists:
- Doors, interactive objects (server-authoritative state)
- Combat: melee range check, HP, death, respawn
- Inventory: on-demand via
EntityQueryPacket/EntityDetailPacket - Day/night and weather on the slow tick — affect visibility, spawns
- Procedural dungeon generation (BSP or cellular automata feeding into the same tile format)