Files
forgotten_caves/notes/roadmap.md
T
2026-07-17 22:58:00 +02:00

7.2 KiB
Executable File
Raw Blame History

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_rle in client/src/assets.rs
  • 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
  • Input system: GameAction, InputMap, InputState in client/src/input.rs (over pbio::Key)
  • Protocol spec: notes/protocol.md — Header, State, Action, Chunk, Entity, EntityQuery, EntityDetail, Ping/Pong; reliability model
  • Shared crate: packet structs as bytemuck::Pod with compile-time size assertions, player_action / packet_type constants, chunk_id / chunk_coords
  • 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
  • Client net: NetClient in client/src/net.rs, non-blocking UDP recv, StatePacket / EntityPacket / ChunkPacket / PongPacket dispatch, 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 ChunkPacket data (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 in entities.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 Disconnected after 10 s without a StatePacket; server evicts clients unseen for 10 s
  • 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
  • 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.
  • 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_phase in server/src/net.rs)
  • 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.
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 (camera follow done, entity interpolation open)

Sprite rendering already landed (see Achieved). Camera status:

  • Smooth camera follow: the camera is a float pixel position (cam in game.rs) that tracks the player linearly at ~64 px/s, snapping only to whole pixels at render time (and outright on teleport-sized corrections). The viewport renders with sub-tile offsets (31×31 tile pass + right-strip clip).
  • Entity interpolation: per-id lerp table (EntityLerp in game.rs) beside the entity list — previous/current tile plus a clock, rendered as a pixel lerp over one movement interval. Jumps of more than one tile (Chebyshev) snap. Purely cosmetic; game logic keeps using authoritative tile positions.

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)