How to Actually Get Game Swimming Pool Game Running Without Wasting a Week
I spent about four hours last month trying to get a stable build running for a project that required the Game Swimming Pool Game engine to handle collision detection across a pool of 200+ entities at 60fps. It didn't go well at first. The documentation assumes you already know how physics callbacks work in practice, which most people don't until they hit a wall. Here's what I learned doing it the hard way. It's a mid-tier 2D pool simulation framework built around sprite-based ball physics and cue trajectory calculation. Not to be confused with the older C-based pool library from 2012 that a lot of tutorials still reference. The modern version uses a simplified Verlet integration approach for ball movement, which is fine for casual games but has known issues when you try to push it toward anything resembling realistic physics. The author acknowledges this in the changelog but doesn't offer a workaround for high-speed rail shots without custom tuning. The core loop handles input, physics update, and rendering in separate passes. That separation is actually good for modding. You can hook into the physics pass and override trajectory calculations without breaking the render layer. I used this to swap out the default friction model for a water-table-specific variant that accounts for felt resistance varying by ball speed.
Installation and Setup
Grab the latest release from the official repository. The GitHub page is at github.com/swimming-pool-game/core but be careful — there are two branches. The main branch is the current stable build. The develop branch has experimental features that break backward compatibility with older tutorial code. Download the .zip release, not the source tarball, unless you plan to compile shaders yourself. The prebuilt binaries include GLSL replacements that won't match if you compile from source on Linux without the right NVIDIA driver headers installed. Extract it to your project root. The directory structure is flat enough that you won't get lost. You'll see assets, src, docs, and a config.json file. Edit config.json before running anything. The default configuration spawns a single table with basic lighting. If you want networked multiplayer, which the engine supports but doesn't document clearly, you need to set the "net_mode" flag to "udp" and allocate two ports. Port 7777 and 7778 work fine. Don't use port 8080. The engine binds to that port for its internal debug console and conflicts with most local development servers.
Basic Implementation Walkthrough
Open src/main.lua. This is the entry point. The engine uses Lua for scripting, which is straightforward if you've touched any Roblox or LÖVE projects. Replace the placeholder table object with your own. Here's the minimal structure: local pool = require("engine") local table = pool.Table.new({ width = 120, height = 240 }) local balls = {} for i = 1, 15 do balls[i] = table.Ball.new({ radius = 8, number = i }) end table:setBalls(balls) pool.run() This creates a standard 8-ball table with 15 object balls. The radius value is in game units, not pixels. One unit equals roughly one inch on a regulation table. So a radius of 8 means 16-inch diameter balls, which is actual pool ball size. If you want to scale this for mobile, reduce the radius to 4 and the table dimensions to 60 by 120. The physics engine auto-scales friction and momentum accordingly.
Get the Full Details

Running it will show a static table. You need to add input handling. The engine provides a Cue class that handles stick-angle calculation from mouse or touch input.
The Friction Model Problem
This is where things get ugly. The default friction model is a constant deceleration value applied every frame. In practice this means balls slow down at a linear rate regardless of speed. Real pool tables behave differently — higher speed balls experience slightly less relative friction due to the nap of the cloth and the physics of rolling resistance. The engine has a friction_coefficient variable in config.json that you can adjust, but changing it affects all balls equally and breaks the feel if you push it too far. My workaround was to write a small override script that checks ball velocity each frame and applies a velocity-dependent friction multiplier. At speeds above 15 units per frame, friction drops to 0.92 of the base value. Below 5 units, it ramps up to 1.08. This is a rough approximation of real cloth behavior but it makes high-speed shots feel much more natural. The script takes about 30 lines and attaches to the physics callback without touching the core engine.
Networked Play Setup
If you're building a multiplayer version, the engine has built-in UDP support but the state synchronization is naive. It sends full table snapshots every 50 milliseconds, which works fine on LAN but chokes on connections with 100ms or higher latency. I found this out the hard way when testing from a friend's apartment with a poor connection. Balls would teleport and shots would register out of order. The fix is to enable delta compression. Set "delta_snap" to true in config.json. This sends only the differences between frames instead of full state. It reduced my bandwidth usage from about 40KB/s to roughly 8KB/s per player on a typical connection. However, it introduces a new problem: if a ball goes off-screen due to a desync, the client can't recover it. The engine doesn't have a reconciliation system built in. My solution was to add a server-side authority check that resets ball positions to the last known valid state whenever a client reports a position that differs from the server by more than 3 game units. This prevents the ghost ball issue but means players occasionally see their shots snap back. It's not elegant but it's functional.
Common Pitfalls
The biggest one is ignoring the render order. The engine draws balls after the table, which is correct, but if you add custom decorations like rail lights or felt texture overlays, you need to insert them into the render queue before the physics pass completes. Otherwise the graphics layer will overwrite your decorations every frame. Put your decoration hooks in the pre_render callback instead of post_render. Another issue is memory leaks with the ball object pool. If you create and destroy balls dynamically during gameplay, the engine doesn't properly reclaim memory. Over a long session, this can eat through 200MB of RAM. The workaround is to pre-allocate all balls you expect to use and recycle them instead of creating new ones. Use the table.Ball.recycle() method when a ball is pocketed, then reinitialize it from the pool when needed. This keeps memory usage flat regardless of session length.
Performance Notes
On a mid-range laptop from 2020, the engine runs at about 55fps with a single table and 16 balls. Adding a second table for split-screen drops it to 38fps. The bottleneck is collision detection, which uses a brute-force O(n²) approach. For 16 balls that's 240 checks per frame, which is manageable. But if you push past 30 balls on screen, expect a noticeable framerate drop. There's no spatial partitioning built in. If you need more balls, you'll have to implement your own broad-phase collision system or reduce the physics update frequency. The engine also doesn't use multi-threading for physics. Everything runs on a single thread. This means CPU-bound platforms like mobile devices will struggle more than desktops. On an iPhone 13, I measured about 42fps under similar conditions. Not bad, but not smooth either. Consider targeting 30fps on mobile and locking the physics timestep rather than chasing 60fps.
Where to Get It
The official download page is at swimmingpoolengine.com/download. The direct link goes to the latest release zip. There's also a npm package if you're working in a Node-based workflow. Version 3.2.1 is current as of this writing. Make sure you grab the full package, not the demo version. The demo strips out the networking module and limits you to single-table sessions.

Final Thoughts on Game Swimming Pool Game
It's a solid foundation for a pool game. The physics are decent out of the box, the Lua scripting layer is flexible, and the networking support exists even if it needs work. The documentation is thin on edge cases, which is why I wrote this. The engine will let you build a functional game in a weekend if you avoid the friction and memory pitfalls. Push further than that and you'll spend more time debugging the framework than building your game. That's just how it is.