# Drag

Which ballistic coefficient is live, the fitted drag correction (curve, factor, drop table) and the slowest speed it covers.

## Generated table

**Record:** profile · **Covers:** `ammo.bc_g1`, `ammo.bc_g7`, `ammo.drag`, `ammo.dsf`

| Field | Type | Required | Limits in the schema | Solver reads | In a shared log |
|---|---|---|---|---|---|
| `ammo.bc_g1` | number | no | min 0.02; max 2 | yes | as stored |
| `ammo.bc_g7` | number | no | min 0.02; max 2 | yes | as stored |
| `ammo.drag` | object | yes | - | yes | as stored |
| `ammo.drag.beyond_calibration` | `'book'` or `'fit'` | no | - | yes | as stored |
| `ammo.drag.cdf` | number | no | - | yes | as stored |
| `ammo.drag.knots` | array of object | no | - | yes | as stored |
| `ammo.drag.knots[].k` | number | yes, within | - | yes | as stored |
| `ammo.drag.knots[].mach` | number | yes, within | - | yes | as stored |
| `ammo.drag.min_mach` | number | no | - | yes | as stored |
| `ammo.drag.model` | `'G1'` or `'G7'` | yes | - | yes | as stored |
| `ammo.dsf` | array of object | no | - | yes | as stored |
| `ammo.dsf[].factor` | number | yes, within | - | yes | as stored |
| `ammo.dsf[].mach` | number | yes, within | - | yes | as stored |

## What it holds

These fields of a profile version describe how hard the air slows the bullet: the two ballistic coefficients (BCs), which of them is live, and the correction a calibration fits to the shooter's own logged groups. A profile with no calibration holds only the drag model and a BC, and flies on the published curve. A calibrated one adds some of a drag curve, a drag factor and a drop table, a speed floor for them, and a choice of what to do below that floor. Muzzle velocity is a [velocity](velocity.md) field, and the bullet's size and catalog link are [bullet](bullet.md) fields.

## What to know first

**One BC is live, the other is only kept.** `ammo.drag.model` (G1 or G7) picks which BC the solver reads. Switching it does not convert the BC. A missing or zero live BC makes the profile unusable; a missing other BC changes nothing.

**The fitted correction sits on top of the live BC.** The curve and factor scale the drag; the drop table scales the elevation. It was fitted against the BC and model in force then, so a changed BC or model with the correction kept counts the same drag twice.

**Slower than the floor, the answer leaves the fit.** `ammo.drag.min_mach` is the slowest Mach the fit saw. Below it the default is the published curve, blended in over 0.1 Mach; `ammo.drag.beyond_calibration` `'fit'` keeps the fit. A floor alone does nothing; a curve with no floor applies everywhere.

**Only a calibration writes the correction.** The connector cannot set the curve, factor, drop table or floor, and the profile screen has no field for them. Both can set the BCs and model; only the profile screen's Model section sets `ammo.drag.beyond_calibration`. A reset removes the correction and that choice.

**A Spin coefficient set with its own drag curve ignores both BCs and the model.** The fitted correction still multiplies on top. Do not tell a shooter that a BC matters there.

## The fields

**`ammo.bc_g7`** The bullet's G7 ballistic coefficient, a number with no unit; higher means less drag. Optional in the record, but needed to solve when G7 is the model. Example: 0.312. A profile started in the app has 0.25. Guided setup lets the shooter mark it "Don't know yet". Picking a bullet from the catalog fills it where the catalog has one; changing it by hand afterwards ends the catalog link.

**`ammo.bc_g1`** The same for the G1 curve. Example: 0.64 for a bullet with a G7 of 0.312. G1 values are usually larger than G7 values for one bullet. Absent is normal: a profile started in the app has none. The app checks a stored value even when G7 is the model.

**`ammo.drag`** The object that holds the model and the whole fitted correction except the drop table. Always present.

**`ammo.drag.model`** `'G1'` or `'G7'`: which published curve the live BC belongs to. A catalog pick sets G7 when the bullet has a G7 BC and G1 when it has only a G1. The profile's solver model (Standard, Physics, Classic, Spin) is a different field, on the [model](model.md) page.

**`ammo.drag.knots`** The fitted drag curve: a list of points, each `ammo.drag.knots[].mach` (the bullet's Mach number) and `ammo.drag.knots[].k` (a multiplier on the published drag at that Mach; 1 changes nothing). The solver joins the points with straight lines and holds the first and last value flat beyond them. The list must run from slow to fast, with each Mach and multiplier above 0: since 2026-10-24 the conversion refuses a list that does not, with a message, rather than give wrong multipliers. Absent or empty means no curve.

**`ammo.drag.cdf`** The fitted drag factor: one multiplier on all drag. Above 1 is more drag. Absent means no factor, the same as 1.

**`ammo.dsf`** The fitted drop table: a list of `ammo.dsf[].mach` (the bullet's Mach at the target) and `ammo.dsf[].factor` (a multiplier on the elevation). The solver sorts the points, joins them with straight lines and holds the end values flat. It changes elevation only, never windage. Absent or empty means no table. It sits beside `ammo.drag`, not inside it.

**`ammo.drag.min_mach`** The slowest Mach the saved correction saw. It belongs to the curve and survives a hand edit that drops only the calibration record. Absent means the curve has no floor of its own; the floor of the top calibration record, when that is a drag-shape fit, then stands in for it, and with neither the fit applies at every speed.

**`ammo.drag.beyond_calibration`** `'book'` or `'fit'`: what to use at speeds below the floor. Absent means `'book'`. The editor offers it only on a profile that has a curve, a factor or a table.

**Limits, by writer.** App: the save check the profile screen's sections share. Connector: the input of `edit_profile` and `create_profile`. Conversion: the step every solve goes through first; a profile that fails it cannot be solved, and the connector refuses to save it. Solve: what the solver itself refuses.

| Field | App | Connector | Conversion | Solve |
|---|---|---|---|---|
| `ammo.bc_g7` | 0.02 to 2 | 0.02 to 2 | > 0 | > 0 |
| `ammo.bc_g1` | 0.02 to 2 | 0.02 to 2 | none | > 0 |
| `ammo.drag.cdf` | n/a | not settable | > 0 | > 0 |
| `ammo.drag.min_mach` | n/a | not settable | > 0 | none |

The cells are bounds on the value alone, read with G7 as the model. The conversion checks the BC of the live model only, so a G1 BC of 0 passes it while G7 is the model (the `none`) and fails it when G1 is. The solver is handed only the live BC. Since 2026-10-15 the app and the connector hold either BC to 0.02 to 2, and so does the record schema; the app also refuses a missing live one. The conversion and the solver have no upper limit (a BC of 50 solves), so a profile stored before the bound still solves. The connector's `what_if` takes a BC above 0 and up to 2, for the live model only. The app and the connector write no correction field: the app writes only the model and the choice, each with two values. The conversion passes the correction as stored. For the array members the solver refuses an `ammo.dsf[].factor` of 0 or less and an `ammo.dsf[].mach` that is not a number; it does not check `ammo.drag.knots[].k` or `ammo.drag.knots[].mach`.

## Who writes it

**The app.** The profile screen's Load and bullet section has "Drag model", "BC G7" and "BC G1", typed or filled from the catalog; a blank BC removes the key. "Past the calibrated Mach" is in the Model section, only on a profile that has a curve, a factor or a table, and it writes the key only when the shooter picks "Use the fit" or the profile already has one. Every section saves through one check.

**The connector.** `edit_profile` and `create_profile` take `bc_g1`, `bc_g7` and `drag_model`, and no input for the rest. An edit that makes the model one whose BC is missing is refused with the solver's reason. `create_profile` needs at least one BC; unless `drag_model` is given it uses G7 when it has a G7 BC and G1 otherwise.

**The fitter.** A calibration of the drag writes a new profile version with the fitted fields. The drag-factor choice writes `ammo.drag.cdf`, the drop-table choice writes `ammo.dsf`, and the whole-curve choice writes `ammo.drag.knots` and removes the factor and the table. Each of the three also sets `ammo.drag.min_mach` to the slowest Mach among the groups it used. A fit keeps the fields it does not write. The true-muzzle-velocity calibration writes none of these and keeps the floor it finds. The Calibrate screen, its "Use this one again" button, an accepted suggestion from a helper and the connector's `calibrate_profile` all save this way.

**Copies and restores.** Copying a shared profile (in the app or with `copy_shared_profile`) writes these fields as the sharer's record held them, and restoring a deleted profile writes them as the deleted version held them; neither applies the limits above. The [origin](origin.md) and [versions](versions.md) pages describe the two.

The server sets none of them.

## Who reads it

**The solver.** The conversion passes the live BC and the model always, and the curve, the factor and the table when they are present. It passes the floor from `ammo.drag.min_mach`, or, for a correction saved without one, the floor on a drag-shape calibration record (the profile's top record only, never one in its history); it passes `ammo.drag.beyond_calibration` when set. The [calibration](calibration.md) page says what the record holds.

**The math, in the order the solver applies it.**

```
drag coefficient  c(M) = table(M) x [Standard-model adjustment, G7 only] x k(M)
deceleration      proportional to  c(M) x cdf / BC
elevation         (true_mil(M_target) - jump) x dsf(M_target), then divided by the scope's tracking factor
```

`table` is the published curve of `ammo.drag.model`, `k(M)` the curve (1 with none), `cdf` the factor (1 with none) and `dsf` the table (1 with none). `M` is the bullet's Mach number along the flight and `M_target` its Mach at the target. The rifle's zero is flown with the same drag, so a curve or factor changes the zero's bore angle too; the drop table is applied after the zero and does not move it. The [dragTable](../options/dragTable.md) option decides the first line: the Standard model reads the live BC against its G7 table with a small adjustment near the speed of sound, and Physics, Classic and Spin read the plain published table; that page has the math and its numbers.

**Below the floor.** `solve` and `rangeCard` first fly the calibrated profile. If that flight is slower than the floor at the target, they also fly the profile with the curve, the factor, the table and the floor removed, and blend: all fit at the floor, all published curve 0.1 Mach below it, a straight line between. `ammo.drag.beyond_calibration: 'fit'` skips this. The test uses the Mach of the calibrated flight, which is lower than the Mach the blended answer shows. A floor with no curve, factor or table is ignored. Muzzle velocity, the scope factors and the velocity table stay in the published flight.

**The Spin model.** A stored coefficient set can carry its own drag curve. Then the flight uses that curve in place of the live BC, the model and the choice in [dragTable](../options/dragTable.md); `ammo.drag.cdf` and `ammo.drag.knots` still multiply on top, the table and the floor still apply, and the conversion still needs a valid live BC. A set without its own curve, and the estimate used when there is no set, read the BC as the other models do. See [dynamics](../options/dynamics.md).

**The fitter.** A fit starts from the profile's factor and curve, and flies the plain fitted curve without the floor or the choice below it. It does not compare the BC or the model of a group's version with the newest one's, so groups shot under an earlier BC stay usable. See "Changing it".

**The app.** Every answer goes through the same conversion. The Calculator shows "Past calibration" when the calibrated flight's Mach at the target is below the floor (as the blend tests it) and says which curve it uses, and shows a notice when a correction has no floor at all. The editor shows the fields.

**The connector.** Every tool that solves reads these fields through the conversion.

- `solve` and `range_card` take a `what_if` BC for the live model only, saved nowhere, and add a `past_calibration` value and a spoken sentence when a shot is slower than the floor.
- `sensitivity` reports what 1 percent of BC is worth.
- `explain_dope` tries a different BC among the explanations for confirmed come-ups, alone or with the muzzle velocity.
- `check_profile` notes a live BC that differs by more than 3 percent from the catalog's for the matched bullet.
- `calibrate_profile` previews and saves a fit, and warns when the fit saw nothing slower than Mach 1.

**The server.** A rough Mach for each logged group of a shared log (for the transonic and subsonic badges on Community) uses the live BC and model, or the other BC when the live one is missing, and ignores the correction. Community lists both BCs of each public share beside its bullet. The admins' pooled export lists the model and both BCs of each profile version.

## Why it is stored

**Both BCs and the model.** A maker may publish a G1 and a G7 value and the shooter may not know which is better. Keeping both lets the shooter switch without retyping, and keeps the catalog's numbers.

**The curve, the factor and the table.** A published curve describes an average bullet; a rifle's own groups can show that its bullet differs. The three parts are three ways a calibration can say so: by one factor everywhere, by a curve per speed, or on the elevation at the target.

**The floor and the choice.** A fit to groups that stayed fast says nothing about the slow end, and a fit extended there can be worse than the published curve. The floor records how slow the groups got, so the solver can fall back below it. The choice lets the shooter extend the fit anyway.

## Changing it

An edit saves a new version and each logged group keeps the version it was shot with; the [versions](versions.md) page explains that once for every field.

**The edit category is "shape".** A change to the drag model, or to the BC the model names, defaults to reset: the app asks keep or reset with "reset" already chosen, and the connector will not save until the caller passes `calibration` as `keep` or `reset`. It asks only when there is something to drop (the app also counts a velocity table or a scope factor; the connector, a curve, factor, table, floor, the choice or a record). Changing the BC the model does not name is not a change that affects a calibration: it saves without a question. Changing `ammo.drag.beyond_calibration` alone is also saved without one. A reset removes the curve, the factor, the table, the floor, the choice and the calibration record; the [calibration](calibration.md) page lists what else it can remove. A kept correction stays on the new version.

**Older groups.** A fit is always of the newest version, and it does not compare the BC or the model of the version a group was shot with. After a BC change with the correction kept, a new fit still uses every earlier group; it solves them with the new BC and fits a new correction against it. How a group records its version is on the [observation](../log/observation.md) page.

## Sharing and privacy

Every field on this page reads "as stored" in the table: a reader of a shared log sees both BCs, the model and the whole fitted correction exactly as the owner does, on a share with one person, with the public or with the admins, and whether or not location and notes are hidden. The floor shows roughly how slow the bullet was at the owner's farthest fitted group, and with the muzzle velocity and the air that hints at that range. On a public share, Community also lists both BCs beside the bullet.

---

[All reference pages](../index.md)
