1. Work
  2. Neural Network Game Physics Engine
  3. Docs

Neural Network Game Physics Engine

Bouncing-ball simulator in C++ whose collisions come from a neural net trained on analytic physics.

4 min read

Source on GitHub

A small neural net learns 2D elastic circle-circle collision physics from analytic ground truth, and a hand-written C++ forward pass uses that trained net to drive a real-time bouncing-ball simulation, in place of recomputing the analytic collision formula at each collision event.

What it does

  1. python/train.py generates ~12,000 synthetic collision samples using the real analytic 2D elastic-collision equations (conservation of momentum/energy along the line of centers), trains an sklearn.neural_network.MLPRegressor (12 inputs -> 4 outputs, two 128-unit ReLU hidden layers) to predict post-collision velocities from pre-collision state, reports held-out test error, and exports the trained weights, biases, architecture, and input/output scaling to data/weights.json.
  2. cpp/sim.cpp loads data/weights.json once at startup, and on every ball-ball collision event runs a hand-written ~30-line forward pass (matrix-vector multiply + ReLU per layer, no NN library) to get post-collision velocities. It also implements the analytic physics directly in C++, runs both a NN-driven world and an analytic-driven world side by side from identical initial conditions, and reports the numeric divergence between them over the run. Wall bounces use simple analytic reflection directly (trivial, not worth a learned model). Rendering is an ASCII grid printed to the terminal each frame.

Build & run (exact order)

# 1. Train the network and export weights.json (uses the shared project venv)
cd "05-nn-physics-engine/python"
../../.venv10projects/bin/python train.py
# -> prints held-out test MSE/RMSE and a few predicted-vs-analytic examples
# -> writes ../data/weights.json

# 2. Build the C++ simulator (uses nlohmann-json, installed via `brew install nlohmann-json`)
cd ../cpp
clang++ -std=c++17 -O2 -I/opt/homebrew/Cellar/nlohmann-json/3.12.0/include -o sim sim.cpp

# 3. Run it
./sim              # prints divergence stats every 20 steps + summary
./sim --render     # also prints ASCII frames every 40 steps

Architecture

python/train.py            analytic physics (numpy) -> dataset -> MLPRegressor
        |
        v
data/weights.json           {layer_sizes, activation, weights, biases,
                              input_mean/scale, output_mean/scale}
        |
        v
cpp/sim.cpp   NeuralNet::load() -> NeuralNet::forward()  (hand-written forward pass)
              analytic_collision()                        (hand-written ground truth)
              step()  drives two parallel Ball worlds each frame, ASCII render

weights.json is the contract between the two sides: sklearn’s .coefs_[i] is a (n_in, n_out) matrix and .intercepts_[i] is a (n_out,) vector per layer. The C++ NeuralNet::forward() indexes them the same way sklearn produces them, so no transposition or reshaping is needed on load.

Results from a real run

Held-out test set (2,400 samples, 20% split), MLPRegressor(128, 128), ReLU (the physics self-check for momentum and kinetic energy conservation passed first):

  • Test MSE: 0.0305 (post-collision velocity, 4 components)
  • Test RMSE: 0.175 (velocities in this dataset range roughly -8..8)

Sample predicted vs. analytic ground truth (test set):

analytic=[ 1.9641 -1.4199  0.6505  2.6295]  predicted=[ 1.8881 -1.3697  1.1114  2.7731]
analytic=[ 7.3865 -2.0447  3.87    2.0057]  predicted=[ 7.2223 -2.0562  4.0924  2.0556]

C++ simulation, 5 balls, 200 steps, NN-driven world vs. analytic-driven world from identical initial conditions:

step 0    mean position divergence: 0.0
step 20   mean position divergence: 0.171
step 60   mean position divergence: 0.558
step 100  mean position divergence: 4.876
step 180  mean position divergence: 8.830

RMSE of NN-vs-analytic position divergence over the run: 8.49
Max single-ball position divergence observed: 26.19

Early frames track closely (sub-0.5 units of divergence for the first ~15 collisions). Divergence grows superlinearly afterward. This is expected: bouncing balls in a box are a chaotic dynamical system, so the ~5-10% per-collision velocity error from the NN compounds: a small perturbation at collision N changes the trajectory into collision N+1, which changes the next collision geometry, etc. This is a real, honest limitation of using a learned approximator for a chaotic simulation loop, not a bug.

Simplified vs. full spec

  • 2D circle-circle collisions only, no walls-as-NN-input, no cloth/fluid/soft-body. Add rigid-body rotation/torque, polygon collision, or soft-body/cloth constraints when the portfolio piece needs more than bouncing circles.
  • sklearn MLPRegressor, not a physics-informed neural network (PINN). Add an energy-conservation penalty term to the loss (e.g. a custom PyTorch training loop) when prediction accuracy over long rollouts matters more than a quick MVP.
  • ASCII-grid terminal rendering, not a real game engine/GUI. Swap in SFML/raylib (or a browser canvas) for real graphics when this needs to look like a game instead of prove the physics loop.
  • Divergence measured only as summary statistics over one run/seed, no systematic accuracy benchmark across many initial conditions. Add a batch harness that runs N random seeds and reports divergence distribution when accuracy claims need to be more rigorous.

Support

Submit issues, questions, or bug reports to the GitHub issue tracker: https://github.com/ADM1SH/nn-physics-engine/issues

Roadmap

  • Done: Core architecture and baseline implementation.
  • Done: Functional verification and test coverage.
  • To do: Train on non-elastic collisions and friction coefficients
  • To do: Add real-time OpenGL particle visualization

Contributing

Contributions are welcome.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/improvement.
  3. Commit your changes: git commit -m "feat: enhance functionality".
  4. Push to the branch: git push origin feature/improvement.
  5. Open a Pull Request.

Authors and Acknowledgment

  • Adam Anwar (ADM1SH) - Lead architect and developer.
  • Architected by Adam Anwar. Explores surrogate neural network approximation of rigid body mechanics.

License

Licensed under the MIT License. See LICENSE for details.

Project Status

Functional simulation sandbox.

Back to top