# 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 - [x] TGA loader: `Image`, `from_tga`, `to_tileset`, `decode_rle` in `client/src/assets.rs` - [x] Pixel helpers: `set_pixel`, `extract`, `blit`, `blit_tile` in `client/src/game/pixelhelper.rs` — signed `i32` offsets, clip against all four framebuffer edges, index 0 = transparent - [x] Input system: `GameAction`, `InputMap`, `InputState` in `client/src/input.rs` (over `pbio::Key`) - [x] Protocol spec: `notes/protocol.md` — Header, State, Action, Chunk, Entity, EntityQuery, EntityDetail, Ping/Pong; reliability model - [x] Shared crate: packet structs as `bytemuck::Pod` with compile-time size assertions, `player_action` / `packet_type` constants, `chunk_id` / `chunk_coords` - [x] Server binary: 24 Hz tick loop, 3×3-chunk test world (`build_world`), wall collision, per-client action queue, `ChunkPacket` dispatch on cache miss, stale-client eviction - [x] Client net: `NetClient` in `client/src/net.rs`, non-blocking UDP recv, `StatePacket` / `EntityPacket` / `ChunkPacket` / `PongPacket` dispatch, entity-checksum retransmit - [x] End-to-end loop: player moves on server, position reflected in `EntityPacket`, rendered on client - [x] Tile rendering: chunk-based world rendering from received `ChunkPacket` data (6-bit unpack) - [x] Tick-based movement: movement resolved in `entity_tick`, currently every 4th tick (`sim.rs`) - [x] Sprite-based entity rendering: entity `type_id` → tile in `entities.tga`, blitted with transparency - [x] Phase-staggered broadcast: per-client `send_phase`, flat outbound load across ticks - [x] Ping/Pong RTT measurement (`PingPacket` / `PongPacket`) - [x] Connection timeout: client emits `Disconnected` after 10 s without a `StatePacket`; server evicts clients unseen for 10 s - [x] Map loader: Tiled-CSV parser in `server/src/map.rs` (flip-flag masking, content-derived dimensions), `load_world` slices it into chunks, invisible solid border at the map rim. Replaces the hardcoded `build_world`. See `notes/07-map-loader.md` - [x] Client movement prediction: queued unconfirmed steps (`path` in `client/src/game.rs`), paced at the server's 6 Hz movement cadence, reconciled against `EntityPacket` positions, dropped after idle timeout. Shared collision vocabulary `shared::tile_collidable` (currently empty — every tile walkable) keeps client prediction and server sim in lockstep. - [x] 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. - [x] 24 Hz base loop (`server/src/main.rs`) - [x] Phase-staggered broadcast at 12 Hz/client (`send_phase` in `server/src/net.rs`) - [x] Stride layout settled: `entity_tick` at `%4` (6 Hz, sets movement rate), broadcast at `%2` (12 Hz/client). The `06` note now documents this and why. - [ ] Action-point model (`energy` / `speed` per entity, act when `energy >= ACTION_COST`). Removed from `entity.rs` for now as premature — reintroduce when NPCs (08) actually need it. Then `entity_tick` can move to `%2` (12 Hz) and `speed` sets 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_flags` in `map.rs` is 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::Npc` with a `think() -> u32` method 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 `EntityKind` is fine. ```rust 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_pos` snaps hard to the server position in `game.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)` — see `notes/04` - UI primitives in `client/src/ui.rs`: `fill_rect`, `draw_rect`, `draw_hbar` — see `notes/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_token` field exists but is unused) - Multi-datagram `EntityPacket`: server currently truncates at 66 entities (`net.rs` TODO) - 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) ---