diff --git a/TODO.md b/TODO.md index afa467b..dc0e070 100644 --- a/TODO.md +++ b/TODO.md @@ -1,72 +1,49 @@ -# lockstep artillery - build roadmap +# lofivor - build roadmap -## phase 1: foundation +survivor-like optimized for weak hardware. finding the performance ceiling first, then building the game. -- [x] set up build.zig with raylib-zig dependency -- [x] create fixed-point math module (Fixed struct, add/sub/mul/div) -- [x] create trig lookup tables (sin/cos at comptime) -- [x] basic window opens with raylib +## phase 1: sandbox stress test -## phase 2: single-player simulation +- [ ] create sandbox.zig (separate from existing game code) +- [ ] entity struct (x, y, vx, vy, color) +- [ ] flat array storage for entities +- [ ] spawn entities at random screen edges +- [ ] update loop: move toward center, respawn on arrival +- [ ] render: filled circles (4px radius, cyan) +- [ ] metrics overlay (entity count, frame time, update time, render time) +- [ ] controls: +/- 100, shift +/- 1000, space pause, r reset -- [x] define GameState, Player, Projectile structs -- [x] terrain generation (fixed pattern for now) -- [x] cannon aiming (angle adjustment with keys) -- [x] power adjustment -- [x] fire projectile -- [x] projectile physics (gravity, movement) -- [x] terrain collision detection -- [x] player hit detection -- [x] turn switching after shot resolves +## phase 2: find the ceiling -## phase 3: rendering +- [ ] test on i5-6500T / HD 530 @ 1280x1024 +- [ ] record entity count where 60fps breaks +- [ ] identify bottleneck (CPU update vs GPU render) +- [ ] document findings -- [x] draw terrain as connected line segments -- [x] draw players as geometric shapes -- [x] draw cannon angle indicator -- [x] draw power meter -- [x] draw projectile with trail (last N positions) -- [x] implement bloom shader (blur.fs) -- [x] render-to-texture pipeline for glow effect -- [x] explosion effect (expanding circle) +## phase 3: optimization experiments -## phase 4: local two-player +based on phase 2 results: -- [ ] split keyboard input (player 1: wasd+space, player 2: arrows+enter) -- [ ] verify determinism by running two simulations side-by-side -- [ ] add checksum verification +- [ ] if render-bound: batch rendering, instancing +- [ ] if cpu-bound: SIMD, struct-of-arrays, multithreading +- [ ] re-test after each change -## phase 5: networking +## phase 4: add collision -- [ ] UDP socket wrapper (bind, send, receive) -- [ ] define packet format (INPUT, SYNC, PING, PONG) -- [ ] host mode: listen for connection, send initial SYNC -- [ ] guest mode: connect, receive SYNC, start simulation -- [ ] input exchange each frame -- [ ] handle packet loss (resend on timeout) -- [ ] checksum exchange and desync detection -- [ ] latency display +- [ ] spatial partitioning (grid or quadtree) +- [ ] projectile-to-enemy collision +- [ ] measure new ceiling with collision enabled -## phase 6: polish +## phase 5: game loop -- [ ] wind indicator -- [ ] health bars -- [ ] win/lose screen -- [ ] rematch option -- [ ] sound effects (optional, breaks no-dependency purity) +- [ ] player entity (keyboard controlled) +- [ ] enemy spawning waves +- [ ] player attacks / projectiles +- [ ] enemy death on hit +- [ ] basic game feel -## known pitfalls to watch +## future -- [ ] don't use floats in simulation -- [ ] don't iterate hashmaps -- [ ] don't use @sin/@cos - use lookup tables -- [ ] always process inputs in same order (player 0 then player 1) -- [ ] serialize terrain heights as fixed-point, not float - -## stretch goals - -- [ ] destructible terrain (explosion removes pixels) -- [ ] multiple weapon types -- [ ] rollback netcode (predict, rewind, replay on correction) -- [ ] replay file save/load -- [ ] web build via emscripten +- [ ] different enemy types +- [ ] player upgrades +- [ ] actual game design (after we know the constraints) diff --git a/docs/plans/2025-12-14-sandbox-design.md b/docs/plans/2025-12-14-sandbox-design.md new file mode 100644 index 0000000..b54a141 --- /dev/null +++ b/docs/plans/2025-12-14-sandbox-design.md @@ -0,0 +1,115 @@ +# lofivor sandbox - stress test design + +a minimal harness for finding entity count ceilings on weak hardware before designing game systems. + +## goals + +**purpose:** answer "how many simple entities can we update and render at 60fps?" + +**target hardware:** +- primary: intel i5-6500T + HD 530 (thinkcentre m900) @ 1280x1024 +- secondary: windows laptop for dev iteration + +**what it does:** +- spawns colored circles that drift toward screen center +- manual controls to add/remove entities in real-time +- on-screen metrics: entity count, frame time (ms), update time, render time +- circles respawn at random edge when reaching center + +**what it doesn't do (yet):** +- no collision detection +- no player input beyond entity count controls +- no game logic, damage, spawning waves +- no particles or visual effects + +**success criteria:** +- locked 60fps with some meaningful entity count (finding that number is the goal) +- clear breakdown of where frame time goes (CPU update vs GPU render) +- stable enough to leave running while tweaking counts + +## data structures + +```zig +const Entity = struct { + x: f32, + y: f32, + vx: f32, + vy: f32, + color: u32, +}; +``` + +simple flat array of entities. no ECS, no spatial partitioning, no indirection. measuring the baseline - fancy structures come later. + +**memory budget:** 20 bytes per entity. 10k entities = 200KB (fits in L2 cache on skylake). + +## update loop + +``` +for each entity: + x += vx + y += vy + if distance_to_center < threshold: + respawn at random edge + recalculate vx, vy toward center +``` + +~5 float ops per entity per frame. no collision, no branching beyond respawn check. + +**velocity:** on spawn, compute normalized direction to center, multiply by constant speed, store vx/vy. + +## rendering + +each frame: +1. clear screen (dark background #0a0a12) +2. draw all entities as filled circles (4px radius) +3. draw metrics overlay + +**entity color:** cyan (#00ffff) - bright against dark background + +**metrics overlay (top-left):** +``` +entities: 5000 +frame: 12.4ms +update: 8.2ms +render: 4.1ms +``` + +## controls + +| key | action | +|-----|--------| +| `=` / `+` | add 100 entities | +| `-` | remove 100 entities | +| `shift + =` | add 1000 entities | +| `shift + -` | remove 1000 entities | +| `space` | pause update loop (render continues) | +| `r` | reset to 0 entities | + +pause isolates render cost from update cost. + +## what we're measuring + +**key questions:** +1. what's the entity ceiling at 60fps? (where frame time crosses 16.6ms) +2. is it CPU-bound or GPU-bound? (compare update vs render time) +3. where does it fall apart? (gradual degradation or sudden cliff?) + +**hypotheses to test:** +- HD 530 might struggle with thousands of individual draw calls (GPU-bound) +- if CPU-bound, update loop needs SIMD or better memory access +- skylake's 4 cores are untouched - parallelism is a future lever + +## next steps based on results + +| bottleneck | next experiment | +|------------|-----------------| +| render (draw calls) | batch rendering - instancing, sprite batches | +| update (CPU) | SIMD, struct-of-arrays layout, multithreading | +| both equally | optimize either for gains | + +## stretch measurements + +- memory bandwidth (cache-bound?) +- draw call count vs batched draws +- fixed-point vs float update cost comparison