Skip to content
cadrien1 edited this page Sep 12, 2026 · 3 revisions

Welcome to the poncelet wiki. This is a growing FAQ seeded from common questions — if yours isn't answered below, open an issue and we'll add it here.

For the full API and guarantees, start with README.md, docs/guide.md, and the cookbook for copy-pasteable recipes. This wiki is for the questions those don't answer directly.

The BitExact FMA bug hunt a real cross-platform determinism bug hunt: a "fixed" digest that turned out to be proof of the wrong fix, and an Apple-Clang -O0 gap that ignored -ffp-contract=off.


Which engine binding should I use?

bindings/ has four starting points:

  • Godot 4 — a GDExtension over the C++ API. Pick this if you're on Godot; positions/directions pass straight through (both use SI metres, Y-up, right-handed).
  • Unity — a native plugin, C# P/Invoke over the C ABI. Same frame convention as Godot, straight pass-through.
  • Unreal Engine — the odd one out: UE is centimetres, Z-up. The plugin does a real coordinate conversion (see the mirrored-spin gotcha below) — don't try to reuse the Godot/Unity pattern of "just pass the vector through" here.
  • raylib — not an engine, just a rendering/windowing library, so this is the smallest possible sample: no plugin system, no editor, a single main.cpp calling pon::preview_arc directly. Good starting point if you're not using any of the above three, or just want the leanest possible reference for wiring poncelet into your own loop.

None of the four bindings pull in collision/geometry — that's always your engine's job; poncelet's World callback is what you feed real hit detection through.

What's the difference between Loose, PlatformStable, and BitExact — which do I want?

  • Loose — no determinism guarantees at all, fastest. Fine for a single-player game where nobody's comparing two runs.
  • PlatformStable (the default) — the same platform + compiler + inputs produce an identical trajectory every time. Enough for local replay/demo recording, but two different machines (say, a Windows client and a Linux dedicated server) are not guaranteed to agree bit-for-bit.
  • BitExact — the integrator runs on a Q32.32 fixed-point core instead of double, so the result is identical on every OS, compiler, and optimization level. This is the one you want for rollback netcode or server-authoritative hit validation, where a client and server (or two peers) silently disagreeing about a bullet's position means silently disagreeing about who died.

BitExact costs a small amount of accuracy (sub-millimetre over a transonic kilometre flight in testing) and a small amount of performance versus the double path — worth it only when cross-machine agreement is actually load-bearing for your game.

Why isn't my guided-munition (proportional-nav) shot bit-identical across platforms under BitExact?

Known, documented gap: the core integrator (drag, gravity, 6-DOF, spin drift, Coriolis, the AdaptiveRKF45 step-size controller) is fully on the fixed-point path now, but the guidance law's steering computation (GuidanceDesc/compute_guidance) is still plain double. It's per-platform-deterministic (same machine, same result every time) but not yet guaranteed identical across different platforms. This is tracked as future work — if you need bit-exact guided munitions specifically, open an issue so it can be prioritized.

My spin/curve effect looks mirrored / backwards in Unreal

This is the one thing about the Unreal binding that can't be caught by an automated test: converting between poncelet's right-handed metre frame and UE's left-handed centimetre frame is an axis swap, which flips chirality. Position and velocity round-trip exactly (that part is tested), but a chirality-dependent effect computed inside poncelet's own frame — Magnus lift direction (curveball curve), gyroscopic spin drift — can come out mirrored relative to what looks intuitive from UE's side. If it looks backwards: negate the converted spin axis's Y (poncelet-space) component before spawning. See PonceletConversion.h in the Unreal binding for the full explanation.

My raylib (or other non-engine) demo's trajectory arc is a single point / has zero length

Check your launch height against your groundY. preview_arc stops once the projectile's height reaches groundY — if you launch from exactly y = 0 with groundY = 0.0, that check can trip on the very first sample since you start exactly at it. Launch from a real height instead (a standing shooter is usually ~1.5-1.7 m) the way every cookbook recipe and example does.

How do I zero a rifle / build a drop table?

No built-in zeroing function — it's a few lines of caller code around preview_arc (bisect the launch pitch until point-of-impact matches line-of-sight height at your zero range). Full worked example: cookbook recipe 1 ("Real-dope sniper rifle").

Does poncelet handle collision / hit detection?

No — poncelet never owns geometry. You supply a World (a raycast/medium/ material callback), and poncelet's integrator does the swept query against it every sub-step. docs/examples/custom_world.cpp and include/poncelet/worlds.hpp's stock primitives (PlaneWorld, SphereWorld, AabbWorld, SlabStackWorld, CompositeWorld) are the starting points depending on how much you want to write yourself.


Don't see your question here? Open an issue and it'll get added.