← BACK TO CRITIQUES
METHODOLOGY ERROR ACOUSTIC TOOL CRITIQUE

calcrack — A Fit-Checker Presented as a Solver

Jordan Henshaw's calcrack (github.com/jordanhenshaw/calcrack, @jordanhenshawhq) is a Blender add-on for gunshot crack-thump analysis. Unlike Bray's work, the physics it implements is correctly derived — this is not a case of broken math. The problem is structural: the tool can't produce a shooter position without you assuming most of the answer first, it reports no uncertainty, and its headline "more accurate" feature does nothing in the current code.

SUMMARY

calcrack lets the user place a rifle and a target, place microphones with a measured crack-thump delay on each, then predicts the per-mic delay and scores it against the measured values. Reading the actual source establishes:

  • By its own README, it does not solve for position — the user supplies candidate rifle/target positions and the tool only scores the fit (a forward model, not an inverse solver)
  • Its only output is an aggregated and mean delay error in seconds — no confidence interval, no Monte Carlo, no residual analysis
  • It assumes a single constant bullet velocity; the "Consider Air Drag" mode is an unimplemented stub that still computes constant-velocity motion
  • The user supplies the trajectory and hand-reads crack-vs-thump from each recording — the assumptions, not the data, drive the result

What calcrack Is

calcrack is a Blender add-on (Python, GPL-3.0). The workflow: position a rifle and a target object — which together define the bullet's trajectory — then add microphones (Blender cameras), each carrying a measured crack-thump delay (the delta_t read by hand from that recording). It offers two methods:

FIRE — CLOSED FORM
Computes predicted crack-thump delay per mic analytically and scores it.
SIMULATE — VISUAL
Spawns a Mach cone and expanding "bang spheres"; the user clicks the frame where each hits a mic.

The README is upfront about what this is: "Relies on user supplying candidate solutions, does not itself solve for a solution." That single sentence is the whole story — everything below follows from it.

First, Credit Where Due // The Math Is Correct

It would be easy to wave this off as another broken script. It isn't. The closed-form crack-thump calculation in algorithm/math.py is derived correctly:

algorithm/math.py
def calculate(x, r, R_mag, v, c):
    t_thump = R_mag / c
    if v <= c:
        return 0.0
    cot_theta = math.sqrt(v*v - c*c) / c
    tan_theta = c / math.sqrt(v*v - c*c)
    if x < r * tan_theta:
        return 0.0
    t_crack = (x + r * cot_theta) / v
    return t_thump - t_crack

With Mach angle μ where sin μ = c/v, the first-arrival shock at a mic at along-path distance x and perpendicular miss r is t_crack = (x + r · cot μ) / v, and the muzzle blast is t_thump = R/c. The code's cot_theta is exactly cot μ, and the x < r·tan θ gate correctly rejects mics the shock cone can't reach. The speed-of-sound model (c = 331.3·√(T/273.15)) is the standard relation.

The problem with calcrack is not the algebra. It's everything the algebra has to assume before it can run.

Problem 1 // Assumptions It Can't Escape

A crack-thump delay is one number per mic. To predict it, the model needs a trajectory, a velocity, and a clean read of the waveform. The question that matters is which of those you can actually pin down — and which you're forced to assume.

The trajectory — you supply it

The bullet path is defined as rifle → target, both placed by the user (aim_target). The crack timing depends on that direction. So the geometry you'd be claiming to recover is the geometry you typed in. To "find" a shooter you would manually sweep the rifle and target until the error looks small — which is searching the input space, not solving.

The velocity profile — one constant for the whole flight

v is a single constant, used for both the Mach angle and the crack timing. A real round decelerates: the cone widens downrange and the crack disappears entirely once it drops subsonic. The model never does this, and the path is treated as infinite — a far mic still "hears" a crack from a bullet that physically stopped at the target. Unlike speed of sound, you can't fix this by entering a better number; one scalar can't represent a decelerating bullet.

Crack vs thump — hand-read out of the echoes

Each mic's delta_t is measured by a human picking the crack and the thump out of a real recording full of reflections and reverberation. That is the same error class — confusing echoes with separate events — already catalogued on this site. The visual "Simulate" mode is no better: it's manual frame-clicking, quantized to 1–5 ms.

In fairness — one thing that is not a weakness: speed of sound. calcrack derives it from temperature only (no humidity or wind), but that scalar is computable to a fraction of a percent, and you can simply enter a corrected value. We flag this explicitly so the rest of the list reads as what it is: the assumptions you genuinely can't escape, not padding.

Problem 2 // No Solve, No Uncertainty

Because the user supplies the candidate, the entire output is a measure of how well that candidate fits. algorithm/compare.py computes, per mic, error = abs(predicted - observed), then sums and averages them into two numbers: aggregated error and mean error, in seconds.

  • No confidence intervals, no uncertainty propagation, no Monte Carlo over input noise — nothing that tells you whether a small error is significant or coincidental
  • An error_margin setting rounds any per-mic error at or below the threshold down to 0.00 (default 0, so honest out of the box — but available)
  • The UI invites a per-mic confidence rating (1–3) — which the code gathers and then never uses; it has zero effect on the result

A low residual from a position you chose yourself, reported with no error bars, is not a location estimate. It only says your inputs were mutually consistent.

Problem 3 // The "Air Drag" Mode Doesn't Apply Drag

The constant-velocity limitation above would be excusable if the tool's own answer to it worked. It exposes a "Consider Air Drag" toggle — described as a "more accurate and resource-intensive simulation" — and switches to an advanced code path when it's on. Here is that path's drag module in full:

simulate_advanced/air_drag.py
BALLISTIC_COEFF = 0.45
BULLET_MASS_GRAINS = 150.0
BULLET_DIAMETER_INCH = 0.308
AIR_DENSITY_KG_M3 = 1.225
...
def distance_at_time(t_seconds, muzzle_velocity_mps):
    return muzzle_velocity_mps * t_seconds

distance_at_time is pure v · t — there is no drag. The ballistic coefficient, bullet mass, diameter, and air density are defined but never referenced anywhere in the module. The drag model is unbuilt scaffolding.

The practical consequence: every mode in the tool currently uses constant velocity. The deceleration limitation isn't a corner case you can opt out of — it applies universally, and the "more accurate" option the interface advertises is not doing anything yet. (The projectile's muzzle velocity is a user input — what's fixed in code are the drag model's physical constants in air_drag.py: bullet mass, diameter, and ballistic coefficient, hardcoded to a .308 / 150 gr round.)

Conclusions

calcrack is a different animal from Bray's analysis, and it deserves a different verdict. Bray's TDOA method is sound in principle but was executed badly — frame-pick synchronization, a hardcoded answer, cameras dropped without justification. calcrack is honestly documented and its physics is correct, but it is structurally incapable of producing a location.

  1. 1 It evaluates a hypothesis; it does not solve. By the README's own admission it requires the user to supply candidate solutions. The trajectory — the bulk of the answer — is an input, not an output.
  2. 2 It reports no uncertainty. The output is two scalar error figures. There is no way to distinguish a meaningful fit from a coincidental one, and a per-mic confidence input is collected but ignored.
  3. 3 Its accuracy headline is unimplemented. The constant-velocity model is the only model; the "Consider Air Drag" path computes the same v · t motion with the drag constants unused.
  4. 4 Used as advertised, it's fine. Cited as proof, it isn't. As a sanity check that a hypothesized geometry is roughly consistent with observed crack-thump delays — which is all the README claims — calcrack is reasonable. As forensic localization or proof of a shooter position, it is not better than Bray; it simply relocates the assumptions from the synchronization into the inputs.

For why crack-to-bang ranging yields a distance — a circle around the microphone — and never an independent shooter location, see our crack-to-bang explainer.

This critique is based on a direct reading of the public source at github.com/jordanhenshaw/calcrack — the closed-form engine (algorithm/), the scoring (compare.py), the exposed properties, and both simulation paths. Code excerpts are quoted verbatim.

// Source reviewed: algorithm/math.py, wrap.py, compare.py, speed_sound.py, manager_properties.py, simulate_advanced/air_drag.py