With the implementation stable, it's time to stop tweaking and start measuring. This post covers the automated accuracy test suite I built to validate three things:
- Force correctness, does the integrator compute the right buoyant force?
- Torque correctness, does the corrected formula settle to the analytical equilibrium?
- Adaptive vs linear divergence, does the adaptive clipping path actually produce different results?
The test harness
I wrote a Unity MonoBehaviour called AccuracyTests that runs each test as a coroutine. For the static tests (force sweep, equilibrium tilt), it freezes the rigidbody, teleports it to a known position, waits for physics to tick, then reads the force/torque from the DLL via reflection. Everything outputs CSV-formatted [TEST] lines to the Unity console.
One gotcha, when the rigidbody is set to kinematic for the static sweep, Unity zeroes out the inertia tensor for constrained axes. The BuoyancyController divides angular momentum by inertia to get angular velocity, division by zero -> NaN -> Unity screams. Fixed with a simple guard: skip the division if the tensor component is below 1e-6f.
Force sweep, cube submersion
The simplest possible validation. A 1×1×1 cube, flat water (all wave steepnesses set to 0), swept from 0.6m above to 0.6m below the surface in 25 steps. At each step, compare the DLL's computed force against the analytical .

The top panel shows computed vs analytical force, they are literally on top of each other. The right panel shows relative error on a 1e-6 % scale. That's 32-bit floating point noise. The integrator is exact to machine precision on flat water, which makes sense, the closed-form equations were derived for a linear water surface, and flat water is the simplest case of that.
Equilibrium tilt, corrected vs paper formula
This is the test that matters most. A 1m³ cube with density ratio 0.75, constrained to rotate around one axis only (matching the 2D-bar setup in Hirae et al. Table 1). Drop it with a 5° initial tilt, let it settle, read the equilibrium angle.
Analytical answer:
The corrected formula: settles to 26.565° with residual torque of 2.4×10⁻⁵ N·m, essentially zero.
The paper's printed Eqs. 7–9: settles to 45.000° with a permanent residual torque of 215.9 N·m. The cube reaches force equilibrium () but the torque never vanishes because the auxiliary vector is wrong.

- Top (Tilt): Corrected formula converges smoothly to the analytical 26.565°. Paper formula overshoots and sticks at 45°.
- Middle (Force): Both reach force equilibrium at N, the force equation (Eq. 5) is correct in both versions. The typo is only in the torque (Eqs. 7/9).
- Bottom (Torque): Corrected torque drops 6 orders of magnitude to ~10⁻⁵. Paper torque flatlines at 215 N·m forever.
This is the smoking gun for the bug report I weill send to the authors. The factor-of-2 error in the and components of the auxiliary vector doesn't affect the force at all (force uses a different equation), but it produces a constant rotational bias that prevents correct equilibrium.
Adaptive vs linear clipping, convergence vs dense ground truth
The final accuracy question is, we know adaptive clipping produces different results than linear clipping, but does it produce better results?
To answer this, we need a ground truth. While there is no analytical solution for a bunny floating on Gerstner waves, we can use a dense mesh convergence argument. If we pass a 30,000-triangle mesh to the pure linear integrator, the triangles are so incredibly tiny relative to the wave period that the linear chord perfectly approximates the local water curve. We can treat this 30k "Dense Mesh" as the ground truth.
I set up three identical 250-triangle bunnies falling into well-formed waves alongside the 30k ground truth bunny:
- Linear Path: 250 triangles
- Adaptive Path (N=4): 250 triangles
- Adaptive Path (N=16): 250 triangles

The new error analysis plot tells a phenomenal story:
- Strictly Lower Error: Looking at the absolute error plot after (when the bunnies are fully interacting with the chaotic wave surface), the Adaptive paths consistently maintain a lower error margin than the Linear path. The linear path drifts up to ~6.5° of tilt error and ~10 cm of height error. The adaptive path pulls this down by a measurable margin.
- Proof of Mathematical Convergence: The N=4 and N=16 error lines completely overlap. They are identical. This proves that refinement samples is mathematically converged for this mesh! Calculating 16 points along the waterline yields zero additional accuracy.
- The Mesh Volume Limit: If N=16 is perfectly converged, why is there still a ~6° error compared to the ground truth? Because we are perfectly clipping the wrong geometry. A 250-triangle bunny has massive flat polygons where the 30k bunny has intricate curves (ears, paws). The algorithm is perfectly computing the curved waterline across a blocky volume. The remaining 6° error is the fundamental volume error of the low-poly mesh, which cannot be fixed by clipping alone.
- Adaptive vs Linear is Negligible: Most importantly, look at the gap between the Linear (orange) and Adaptive (blue) lines. It is practically non-existent. While Adaptive is slightly better in a few peaks (shaving off maybe 0.5° of error), it is completely swamped by the massive 6° mesh volume error.
The Final Verdict: Accuracy vs Performance
So we have mathematically proven that Adaptive Clipping works and converges. But does it matter for a game engine?
The short answer is absolutely not.
While the Adaptive path (N=4) successfully curves the waterline, the actual improvement in accuracy is negligible (less than 1° difference) compared to the pure Linear path. The error introduced by simply having a low-poly mesh fundamentally dominates the error caused by a flat waterline chord.
Worse, this negligible accuracy gain comes at an immense performance cost. As discovered during stress testing, calculating the exact adaptive curve across 100 bobbing objects requires over 12 million trigonometric functions per second. Because the adaptive sub-triangulation relies on dynamic arrays, it had to be implemented in standard C# rather than the highly-optimized Burst compiler. This drops frame rates from 300+ FPS (Linear) down to single digits (Adaptive), as you'll see in the next post, evaluation post.
The conclusion for the project report is that the Linear C++ DLL is the undisputed winner for real-time game development. It is a high-performance workhorse capable of simulating massive crowds of buoyant objects at 300+ FPS. The Adaptive algorithm is mathematically brilliant and perfect for proving that the flat-chord approximation is acceptable, but its microscopic accuracy gains are completely swallowed by mesh volume error, and its computational overhead makes it unviable for real-time games.