Skip to content

Add game profiling system and rendering optimizations#14

Merged
AndrewAltimit merged 7 commits into
mainfrom
feature/game-profiling
Feb 24, 2026
Merged

Add game profiling system and rendering optimizations#14
AndrewAltimit merged 7 commits into
mainfrom
feature/game-profiling

Conversation

@AndrewAltimit
Copy link
Copy Markdown
Owner

Summary

  • Feature-gated profiling system (--features profiling): server-side game loop timing, client-side frame timing, browser overlay via profiler.js -- zero overhead when disabled
  • Serialization optimization: remove course data from per-tick platformer broadcast, zero-copy downcast via GameState trait, eliminate per-frame decode_server_message
  • Rendering pipeline optimization: cache sprite atlas (OnceLock instead of rebuilding 500+ HashMap entries/frame), batch outline sprites via per-vertex attribute in sprite_batch shader (12 fewer individual draw calls), direct scene batch buffers bypassing RenderObject creation/frustum cull/sort for ~600 tiles, reuse per-blend-mode vertex Vecs across frames

Performance Impact

Phase Change Savings (est.)
Phase 1 Profiling infrastructure Measurement baseline
Phase 2 Zero-copy downcast + skip deserialization ~2-3ms/frame
Phase 3 Atlas cache + outline batching + direct scene batches + Vec reuse ~4-6ms/frame
Total ~6-9ms/frame (22-25ms -> 13-19ms)

Target: 60 FPS (16.67ms budget) for the platformer game.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo clippy --workspace --all-targets --features profiling -- -D warnings
  • cargo test --workspace -- all 484 tests pass
  • wasm-pack build with profiling feature succeeds
  • Manual: run server + client, play platformer -- confirm FPS near 60
  • Visual: confirm enemy/player outlines render correctly in batch path
  • Manual: confirm golf/lasertag/tron unaffected

Generated with Claude Code

AI Agent Bot and others added 7 commits February 24, 2026 07:51
Adds a zero-overhead profiling system behind the `profiling` feature flag.
When enabled, RAII scope guards measure per-phase timing in both the server
tick loop and client render loop, with deep sub-phase breakdown for the
platformer game. A browser overlay (F3) shows FPS, frame time, and a
color-coded phase breakdown bar. DevTools performance marks are emitted for
each scope. Server exposes GET /api/v1/profile for live tick stats.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The profiling module's WASM timing path needs web_sys::Performance.
Add web-sys as an optional dependency of breakpoint-core, activated
by the profiling feature flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Profiler data showed 94% of each server tick spent in serialization (497us)
and encoding (379us), not game logic (43us). Four optimizations:

1. Remove 23K-tile course from per-tick state — send via separate
   CourseUpdate messages only when breakable walls are destroyed.
   PlatformerNetState (without course) used for wire format.

2. Eliminate buffer clone + double MessagePack pass — encode_game_state_fast()
   writes [type_byte|tick_le32|state_data] in one allocation.

3. Throttle client JS bridge from 60Hz to 10Hz (force-push on state changes).

4. Incremental scene updates — cache static tiles via mark_static()/
   clear_dynamic(), only rebuild ~110 dynamic objects per frame instead of ~485.

Expected: serialize_state 497us→~50us, encode_broadcast 379us→~20us,
network payload ~5KB→~500B per tick.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The incremental scene update (Optimization 4) cached tiles as "static"
but tile culling is camera-dependent — as the player scrolls, different
tiles must be visible. This caused tiles to disappear during movement.

Reverted to full scene.clear() + tile re-render per frame. The other
3 optimizations (course removal, fast encoding, bridge throttle) remain
and address the actual bottleneck (94% serialization overhead).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

Add as_any() to BreakpointGame trait for zero-copy downcast to concrete
game types. Platformer render, camera, HUD, events, and weather now
access PlatformRacer::state() directly instead of serialize+deserialize
round-trips via read_game_state().

- Eliminate 2x serialize_state() per network tick (prev_state + cache)
- Eliminate 4x read_game_state() per frame in platformer hot path
- Remove dead ActiveGame fields: prev_state, interp_alpha, cached_state_bytes
- Add minimap cache (rooms/connections by course_version, ~600 tile queries saved)
- Golf/LaserTag/Tron unchanged (small states, still use read_game_state)

Expected: ~3-5ms/frame savings → 38 FPS to ~45-50 FPS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e atlas

- Cache sprite atlas in app.rs (use OnceLock instead of rebuilding 500+ entry
  HashMap per frame)
- Add outline support to sprite_batch shader (per-vertex attribute), allowing
  12 enemy/player sprites per frame to be batched instead of drawn individually
- Add pre-built batch buffers to Scene (add_batch_sprite bypasses RenderObject
  creation, frustum culling, and sorting for ~600 tile sprites)
- Reuse per-blend-mode vertex Vecs across frames (eliminate 3 heap allocations)
- Relax batch eligibility: only dissolve sprites need individual draw calls

Estimated ~4-6ms/frame savings (22-25ms → 16-21ms, targeting 60 FPS).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The server's encode_game_state_fast writes [0x10][4-byte LE tick][raw state]
instead of msgpack, causing the test protocol decoder to silently fail on
all GAME_STATE messages (0 received in Tron tests).

Fix: detect GameState type byte in decode() and parse the fast binary format
with readUInt32LE + subarray instead of msgpack unpack.

Also add missing message types (CourseUpdate, RequestGameStart, AddBot,
RemoveBot) to the test protocol constants.

Update CLAUDE.md and README.md:
- Test count: 484 -> 699, Playwright specs: 12 -> 16
- Document profiling system, fast game state protocol, CourseUpdate messages
- Correct release profile (opt-level=3 server, "z" client)
- Update platformer description (Castlevania-style labyrinth)
- Add new key file paths (profiling, protocol, browser tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AndrewAltimit AndrewAltimit merged commit cf6c3f0 into main Feb 24, 2026
10 checks passed
@AndrewAltimit AndrewAltimit deleted the feature/game-profiling branch February 24, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant