<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TinyComputers.io (Posts about Software)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/cat_software.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 A.C. Jokela 
&lt;!-- div style="width: 100%" --&gt;
&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /&gt; Creative Commons Attribution-ShareAlike&lt;/a&gt;&amp;nbsp;|&amp;nbsp;
&lt;!-- /div --&gt;
</copyright><lastBuildDate>Sun, 02 Aug 2026 01:31:23 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>The Bullet Doesn't Go Where You Point It</title><link>https://tinycomputers.io/posts/the-bullet-doesnt-go-where-you-point-it.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/the-bullet-doesnt-go-where-you-point-it_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;20 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;A bullet fired at a target a thousand yards away does not go where you pointed it. It cannot. In the two-and-a-bit seconds it spends in the air, gravity pulls it something like thirty feet below the line you aimed along, so the barrel has to be tilted up to throw it in an arc. A crosswind you can barely feel on your face moves it a couple of feet sideways. Its own spin — the rifling that keeps it nose-forward is turning it some two hundred thousand times a minute — steers it several inches to the right, all on its own, in still air. And the Earth rotates underneath the whole affair, which sounds like a physicist's joke until you work out that at that distance it is worth a few inches too, in a direction that depends on which way you are facing.&lt;/p&gt;
&lt;p&gt;So a rifle is not a pointing device. It is a device you &lt;em&gt;solve&lt;/em&gt;. Everything past the muzzle is a computation, and the computation is interesting enough that I have been writing one for a while now, in Rust, and giving it away.&lt;/p&gt;
&lt;h3&gt;The forward problem&lt;/h3&gt;
&lt;p&gt;The polite name for this is &lt;em&gt;external ballistics&lt;/em&gt;: what happens to a projectile after it leaves the barrel and before it arrives somewhere. Strip away the domain vocabulary and it is an initial-value problem of the sort that turns up everywhere. You know the state of a thing at t=0 — position, velocity, spin — you know the forces acting on it as a function of that state, and you want the trajectory. Write down the derivatives, hand them to an integrator, march forward in small steps. The engine uses fourth-order Runge–Kutta, and there is nothing clever about that choice; it is the boring correct default.&lt;/p&gt;
&lt;p&gt;All the interest lives in the derivative function, because the forces are where the physics hides — drag that changes character as the bullet slows back through the sound barrier, gyroscopic spin drift, the Magnus force, aerodynamic jump, pitch damping, Coriolis. I have written that deep-dive already and will not repeat it here; if you want the physics term by term, &lt;a href="https://tinycomputers.io/posts/building-a-professional-grade-ballistics-calculator-a-deep-dive-into-physics-performance-and-modern-software-architecture.html"&gt;the professional-grade calculator post&lt;/a&gt; walks through each effect and &lt;a href="https://tinycomputers.io/posts/open-sourcing-a-high-performance-rust-based-ballistics-engine.html"&gt;the open-sourcing post&lt;/a&gt; covers how the Rust core came out of the platform it grew in. The short version is that the effects live behind individual switches, all off by default, because they belong to a strict hierarchy of mattering: get drag and wind wrong and no amount of Magnus force will save you.&lt;/p&gt;
&lt;p&gt;That whole forward solver is free and open. &lt;code&gt;cargo add ballistics-engine&lt;/code&gt; puts the library in your project; &lt;code&gt;cargo install ballistics-engine&lt;/code&gt; gives you a CLI called &lt;code&gt;ballistics&lt;/code&gt; that solves trajectories, runs Monte Carlo dispersion, computes the sight-in that zeroes a rifle, backs a true muzzle velocity out of drops you actually measured at the range, and builds reticle and BDC hold tables for a scope. It compiles to WebAssembly, which is what runs the browser calculators at &lt;a href="https://ballistics.rs/"&gt;ballistics.rs&lt;/a&gt;. There are Python and Ruby bindings. It will speak JSON over stdin and stdout, and it will speak Model Context Protocol, so a language model can ask it questions without anyone writing glue.&lt;/p&gt;
&lt;h3&gt;The bugs were never in the physics&lt;/h3&gt;
&lt;p&gt;Here is the thing I did not expect when I started, and which I suspect generalizes far past ballistics: in something like ninety thousand lines of Rust, almost none of the bugs worth remembering have been physics bugs. The equations are in textbooks. They have been in textbooks for decades, they were checked by people better at this than me, and when I have implemented one incorrectly the error was usually so large that it announced itself immediately.&lt;/p&gt;
&lt;p&gt;The bugs are in the &lt;em&gt;conventions&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Consider a wind direction. A number of degrees — obviously. Except: is that the direction the wind is blowing &lt;em&gt;toward&lt;/em&gt;, or the direction it is coming &lt;em&gt;from&lt;/em&gt;? Meteorology says from. Is zero degrees a headwind, a tailwind, or due north? Is it measured relative to the direction you are shooting, or is it a compass bearing that has to be resolved against your shot azimuth before it means anything? Every one of those is a defensible choice, several of them are in active use by real tools, and each pairing produces a number that looks completely reasonable and is wrong by up to a hundred percent of the wind effect. The engine's answer is wind-&lt;em&gt;from&lt;/em&gt;, zero degrees is a headwind — and that sentence is now welded into the help text, the API docs, and a shared constant, because the one time the convention drifted between two subcommands, both of them looked right in isolation.&lt;/p&gt;
&lt;p&gt;Units are the same disease. Inches or millimeters for the height of the scope above the bore. Feet per second or meters per second. Grains — a unit of mass equal to one seven-thousandth of a pound, used for bullets and gunpowder and essentially nothing else on Earth. Minutes of angle or milliradians for the correction you dial into your scope, where a minute of angle is &lt;em&gt;approximately&lt;/em&gt; an inch at a hundred yards, and the approximation has cost people their afternoons. None of this is intellectually difficult. All of it is a place where a plausible number comes out and nobody catches it, which is precisely the definition of the expensive kind of bug.&lt;/p&gt;
&lt;h3&gt;Testing physics you cannot check by hand&lt;/h3&gt;
&lt;p&gt;Which raises the real problem: how do you test this? For a normal library you assert that the function returns the right answer. Here, nobody knows the right answer. The whole point of the engine is to compute a number I cannot compute another way — and if I could hand-verify the output, I would not have needed to write it.&lt;/p&gt;
&lt;p&gt;The usual answer is a pile of regression tests, and there is one: roughly fourteen hundred of them. But a regression test only tells you that today's answer matches the answer you wrote down on the day you wrote the test, which may have been wrong. What I have found much more valuable is a set of ten fuzzing harnesses that do not look for crashes so much as for &lt;em&gt;lies&lt;/em&gt; — properties that must hold for any input at all, checked against millions of inputs nobody thought to write down.&lt;/p&gt;
&lt;p&gt;Some of them have an actual oracle. Turn the drag off — set the ballistic coefficient absurdly high so air resistance becomes negligible — and a projectile is just a parabola, with a closed-form solution any physics undergraduate can produce: the drop at distance &lt;em&gt;x&lt;/em&gt; is exactly ½g(x/v)². So one harness generates arbitrary velocities, runs the full production solver, and demands that its answer match the exact analytic parabola. If the integrator, the step size, or the coordinate handling is subtly wrong, no amount of physics-modeling opinion can hide it, because for that one degenerate case the truth is known.&lt;/p&gt;
&lt;p&gt;Others check symmetry. Switch off spin drift, Magnus, and Coriolis so that the only thing pushing the bullet sideways is wind, then mirror the wind: the drift must mirror too, exactly. Any asymmetry left over is a bug, because there is nothing left in the model that could physically prefer one side. Others check monotonicity — a claim like "at a fixed distance, a bullet with a higher ballistic coefficient must not drop &lt;em&gt;more&lt;/em&gt;." No exact answer required; only a direction of change, which is often the strongest statement you can actually make about a system this tangled.&lt;/p&gt;
&lt;p&gt;And my favorite links the &lt;em&gt;previous released version of the engine&lt;/em&gt; into the same binary as the current one and runs both. Not to demand identical output — the numbers are supposed to change when the physics improves — but to compare their qualitative responses, so that if a refactor quietly inverts how drop responds to a change in ballistic coefficient, the difference surfaces against the version that shipped rather than against my expectations. Which, being the person who wrote the refactor, are the least trustworthy oracle in the building. This trick has one delightful practical wrinkle: linking two versions of the same Rust crate into one executable works fine right up until both of them export identical &lt;code&gt;#[no_mangle]&lt;/code&gt; C symbols for the FFI layer and the linker gives up. The fix is a feature flag that turns the C ABI off on one of the two edges — a small ugly accommodation for a technique that has more than paid for it.&lt;/p&gt;
&lt;h3&gt;Thirteen platforms, because I am like this&lt;/h3&gt;
&lt;p&gt;One more testing note disguised as a shipping note. Each release goes out as native binaries for thirteen platforms — Linux on x86-64, Arm64, RISC-V and 64-bit MIPS; macOS on Intel and Apple Silicon; Windows; and FreeBSD, OpenBSD and NetBSD each on both x86-64 and Arm64 — built by &lt;a href="https://tinycomputers.io/posts/multi-operating-system-and-multi-architecture-build-orchestration-system.html"&gt;a small fleet of single-board computers&lt;/a&gt; that boots a real BSD guest for the length of one compile and then puts it away again. There is no business case for most of that; there is not, so far as I know, a single OpenBSD/Arm64 user waiting on a ballistics calculator.&lt;/p&gt;
&lt;p&gt;But it earns its place in this particular story, because portability is a bug detector. An engine that produces the same numbers on big-endian 64-bit MIPS as it does on Apple Silicon is an engine carrying fewer accidental assumptions about how its floats are laid out and how its integers are ordered. Thirteen platforms is thirteen independent opportunities to be told I assumed something I had not noticed assuming.&lt;/p&gt;
&lt;h3&gt;Solving it backwards&lt;/h3&gt;
&lt;p&gt;Everything so far is the &lt;em&gt;forward&lt;/em&gt; problem: here is the load, tell me where it hits. That is the well-posed direction, and honestly, it is a solved problem — there are good calculators, mine included, and they mostly agree.&lt;/p&gt;
&lt;p&gt;It is also not the question anybody actually has. Nobody at a loading bench wonders where their known load will land. They wonder the other way around: &lt;em&gt;I want it to land here — what gets me there?&lt;/em&gt; Which powder, and how many grains of it, produces the velocity I want? What barrel twist rate will stabilize this bullet? How long should I seat this cartridge? What is this bullet's ballistic coefficient really, derived from its measured dimensions, rather than the optimistic number on the box?&lt;/p&gt;
&lt;p&gt;Those are inverse problems, and inverse problems are where it stops being a matter of running the textbook forward. They generally have no closed form. You solve them by searching — running the forward model over and over inside a root-find or an optimizer, hunting for the input that produces the output that was asked for — and the search is only as trustworthy as the model inside its loop, which is the entire ballgame. A backward solver built on a mediocre forward model does not fail loudly. It produces a confident, specific, plausible number that is wrong, which is worse than useless in a domain where people act on the answer.&lt;/p&gt;
&lt;p&gt;So that is where the real work went: the drag model those searches lean on is calibrated against Doppler radar measurements of how actual bullets actually decelerate — including through the transonic region, where the classical scale-a-reference-curve assumption is at its weakest — rather than assuming every projectile behaves like a reference shape from a century ago.&lt;/p&gt;
&lt;h3&gt;The split, stated plainly&lt;/h3&gt;
&lt;p&gt;I would rather just tell you the arrangement than have you discover it.&lt;/p&gt;
&lt;p&gt;The engine is free and open: the library, the CLI, the WebAssembly build, the Python and Ruby bindings, the whole forward solver, the fuzzing harnesses, all of it. Compute trajectories, run dispersions, true your velocity against real measured drops, build hold tables, ship it inside something of your own. No account, no key, no telemetry. That is &lt;a href="https://ballistics.rs/"&gt;ballistics.rs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The four inverse solvers are the one piece behind a subscription, at &lt;a href="https://ballisticsinsight.com/"&gt;Ballistics Insight&lt;/a&gt;. They run as a service — in a browser, or from the same CLI once you have logged in:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;ballistics login
ballistics recommend-powder --cartridge "308 Winchester" \
    --bullet-weight 175 --desired-velocity 2600
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;They are the paid piece because they are the part with real ongoing cost behind them — the measured drag data the searches depend on, and the compute to run them — and because a project like this is more likely to still exist in five years if some part of it pays for itself. If you shoot, they may be worth it to you. If you are here for the numerical methods and the fuzzing and the pile of BSD binaries, that is all free and I am glad you came.&lt;/p&gt;
&lt;p&gt;One honest note if you do use the powder solver: what it returns is a &lt;em&gt;starting&lt;/em&gt; point, not a recipe. It is where to begin working a load up, carefully, against published load data, over a chronograph, in the slow and boring and safe way. The physics is good. The physics is not your reloading manual and will not pretend to be.&lt;/p&gt;
&lt;p&gt;The bullet still does not go where you point it. But it goes somewhere entirely predictable, and I find that a much more interesting fact than it first appears.&lt;/p&gt;</description><category>ballistics</category><category>cli</category><category>cross-compilation</category><category>external ballistics</category><category>fuzzing</category><category>numerical methods</category><category>ode solver</category><category>physics</category><category>property testing</category><category>rust</category><category>wasm</category><guid>https://tinycomputers.io/posts/the-bullet-doesnt-go-where-you-point-it.html</guid><pubDate>Sat, 01 Aug 2026 17:00:00 GMT</pubDate></item></channel></rss>