Loading page...Загружаем страницу...正在加载页面...
AtlasRepo

Game Optimization Methodology, or How 3 Days of Building a Test Harness Gave Me More FPS Than a Month of Writing Code

Description and use

After building a proper test harness over three days, the author was able to objectively measure the effect of every change and achieve significant FPS improvements on the target hardware by eliminating physics-server-induced FPS spikes first, rather than relying on average FPS which masked the problem.

Problem

A mobile game developer needs to optimize a Godot-based game (Brotato) running on weak ARM handheld hardware (Anbernic RG34XXSP) where the game drops to 5–10 FPS from wave 20 onward due to collision processing bottlenecks and excessive draw calls, but initial optimization attempts yield no measurable improvement on Android.

Outcome

After building a proper test harness over three days, the author was able to objectively measure the effect of every change and achieve significant FPS improvements on the target hardware by eliminating physics-server-induced FPS spikes first, rather than relying on average FPS which masked the problem.

Workflow

  • Extract the Godot project files from the Brotato game for local modification without changing game scripts
  • Build a simple test harness: spawn potato on wave 100 with fixed seed, record frame time, draw calls, and collision pair counts per frame, write average values to file at end
  • Identify main bottlenecks: collision processing in Godot's physics server (huge number of collision pairs per tick) and excessive draw calls/GPU state changes from separate texture files breaking batching
  • Replace Godot's physics server with a lightweight hash-grid implementation using rebuilt-every-frame SoA position/size storage and a simplified solver, implemented first in GDScript then planned for C++ plugin
  • Merge all textures into a single atlas and bake fonts into bitmap format to allow GPU resource changes only on layer switches and enable draw call batching
  • Re-test optimizations on Samsung A51 via ADB; find no measurable improvement beyond measurement noise compared to original game
  • Analyze mistakes: no run metadata, no dry runs, using elapsed time instead of physics-tick count to control test duration, using only one test scenario, not saving raw run metrics, using arithmetic mean as main representation
  • Rebuild test harness over three days: build in-game testing system (scenario system, scenario runner, metric collection per frame, test HUD, Git commit plugin, recording system), external data processing script computing median/P95/P99/worst-per-run per metric with variance estimation and comparison mode
  • Connect Samsung A51 to PC, execute all tests automatically through ADB, pull resulting data back to PC for analysis
  • Eliminate FPS spikes caused by long physics-server work before improving median FPS

Evidence

On the RG34XXSP's rather weak hardware, the game dropped to 5–10 FPS from around wave 20 onward.

Source

The main bottleneck was collision processing inside Godot's physics server. This was visible from the huge number of collision pairs per tick, and I was able to confirm it by running the game at half the original physics TPS

Source

The secondary bottleneck was the number of draw calls and GPU state changes. Textures were stored in separate files, forcing resource switches between draws and breaking batching

Source

I decided to try again, but this time put most of the effort into a proper test harness rather than the optimizations themselves — something that would let me objectively observe the effect of every change.

Source

  • Record date: 2026-10-20T18:56:29.388Z