Files
forgotten_caves/notes/roadmap.md
T
irrlichtandClaude Opus 5 64a6e1d42d Rewrite notes for the single-player architecture
Drop protocol.md; rewrite the tick-loop note around the game-loop-driven sim
and the intent model; rewrite the roadmap (network items removed, sim/game
crate layout, save/load and NPC blocking added); fix crate paths in the
remaining notes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 23:00:19 +02:00

147 lines
7.3 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roadmap
Local single-player roguelike with a continuously simulated, tick-based world.
Workspace layout: `sim/` (headless world simulation — tiles, entities, movement rules,
tick loop; no platform dependencies, unit-testable) and `game/` (the executable — window,
rendering, input, pathfinding, camera; owns a `Sim` and drives it). The platform layer —
window, GPU, input, RGB332 palette — lives in the external `pbio` crate (git dependency),
not in this repo.
History: this fork started from a client/server multiplayer architecture (authoritative UDP
server, thin client with prediction). The scope was cut to single player in September 2026;
the sim kept its authoritative shape, everything network-related was removed. The `sim`
crate's `tick`/`set_action` interface is the seam a server could be wrapped around again.
---
## Achieved milestones
- [x] TGA loader: `Image`, `from_tga`, `to_tileset`, `decode_rle` in `game/src/assets.rs`
- [x] Pixel helpers: `set_pixel`, `extract`, `blit`, `blit_tile` in `game/src/game/pixelhelper.rs`
— signed `i32` offsets, clip against all four framebuffer edges, index 0 = transparent
- [x] Input system: `GameAction`, `InputMap`, `InputState` in `game/src/input.rs` (over `pbio::Key`)
- [x] World model: `World` of 32×32 palette-compressed `Chunk`s (64-entry `TileDef` palette,
6-bit tile indices), entities indexed per chunk, `entities_in_viewport`
- [x] Map loader: Tiled-CSV parser in `sim/src/map.rs` (flip-flag masking, content-derived
dimensions), `load_world` slices it into chunks, invisible solid border at the map rim.
See `notes/07-map-loader.md`
- [x] Tile rendering: chunk-based world rendering straight from `World` (`render_viewport`)
- [x] Sprite-based entity rendering: entity `type_id` → tile in `entities.tga`, blitted with transparency
- [x] Hybrid tick loop: 24 Hz base tick, movement in `entity_tick` every 4th tick (6 Hz);
the game loop drives the sim with a fixed-timestep accumulator. See `notes/06-hybrid-tick-loop.md`
- [x] Intent model: one scheduled action per entity per movement window (`Sim::set_action`),
replaced on re-set, consumed on execution. Headless tests in `sim/src/sim.rs`
- [x] 8-directional movement in chessboard geometry: world physics use the Chebyshev
metric — diagonal and cardinal steps are the same distance, a "circle" is a square
of tiles, matching the square viewport. The single-step rule lives in
`sim::step_allowed` (king move onto a free tile, diagonals additionally need both
orthogonal neighbors free — no corner cutting) and is the one function used by the
sim and the A*. Two held keys walk diagonally.
- [x] Collision vocabulary seeded with test values (`sim::tile_collidable`): id 146
(trees/rocks) and id 0 (empty / the invisible world border). A proper tile-data file
format replaces this table later.
- [x] Click-to-move: framebuffer click → world tile, A* over the world
(`game/src/game/pathfind.rs`, 8-connected, Chebyshev heuristic), route fed into the
sim one step per movement window. Keyboard input cancels the route; each step is
re-validated at scheduling time and a blocked step replans toward the persistent
goal. Click-and-hold steers continuously: while the button is held the route keeps
replanning toward the tile under the cursor (only when that tile changes — cursor
or camera movement), sweeping across blocked tiles keeps the current route.
- [x] 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).
- [x] Entity interpolation: per-id lerp table (`EntityLerp` in `game.rs`) — 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 the
sim's tile positions.
---
## Upcoming work
### 06 — Hybrid tick loop *(partially done)*
Design of record: **`notes/06-hybrid-tick-loop.md`**.
- [x] 24 Hz base loop driven from the game loop
- [x] Stride layout settled: `entity_tick` at `%4` (6 Hz, sets movement rate)
- [ ] 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: seeded with test values (0, 146) in `sim::tile_collidable`.
Decide on a proper file format for tile data (collision, opacity, …) instead of a
hardcoded match.
- Tile flipping: orientation is discarded on load; revisit with the sprite pass (09).
- Multiple / object layers (spawns, triggers) — not yet parsed. The player currently
spawns at (0, 0).
---
### 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. NPCs schedule intents through
the same `Sim::set_action` the player uses.
- Add `EntityKind::Npc` with a `think() -> u32` method returning budget cost
- Per sim-tick: distribute a fixed `think_budget` 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;
for entity in entities_by_player_proximity() {
if budget == 0 { break; }
budget = budget.saturating_sub(entity.think(&mut world));
}
```
- Entity-vs-entity blocking: `step_allowed` only checks tiles. Once NPCs exist, occupied
tiles need to block too — in the sim *and* in the pathfinder's `blocked` closure.
---
### 09 — Client camera *(done — see Achieved)*
Sprite rendering, camera follow and entity interpolation have all landed.
---
### 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 `game/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 — Distribution
- Asset embedding: `include_bytes!` for single-binary distribution (assets are currently
read relative to the working directory)
- Save / load: the world is a plain `Sim` value — serialize it. Roguelike convention is a
single save slot deleted on load
---
### Later — World depth
Once the simulation architecture is stable and a real level exists:
- Doors, interactive objects
- Combat: melee range check, HP, death, permadeath
- Inventory
- Day/night and weather on the slow tick (`% 192`) — affect visibility, spawns
- Procedural dungeon generation (BSP or cellular automata feeding into the same tile format)