# Target

The range, direction, slope and wind of one logged group, as the shooter entered them, and what each is used for when the group is solved again.

## Generated table

**Record:** observation · **Covers:** `target`

| Field | Type | Required | Limits in the schema | Solver reads | In a shared log |
|---|---|---|---|---|---|
| `target.assumed` | array of `'azimuth'` or `'inclination'` or `'wind'` or `'wind_from'` | no | - | no | as stored |
| `target.azimuth_true_deg` | number | yes | - | no | rounded to 10° when location is hidden |
| `target.inclination_deg` | number | yes | - | no | as stored |
| `target.range_source` | `'rangefinder'` | no | - | no | as stored |
| `target.range_yd` | number | yes | max 10000 | no | as stored |
| `target.target_id` | string | no | - | no | as stored |
| `target.target_speed_mph` | number | yes | - | no | as stored |
| `target.wind` | object | yes | - | no | as stored |
| `target.wind.from_deg` | number | yes | - | no | as stored |
| `target.wind.mode` | `'simple'` | yes | - | no | as stored |
| `target.wind.speed1_mph` | number | yes | - | no | as stored |
| `target.wind.speed2_mph` | number | yes | - | no | as stored |
| `target.wind.stretches` | array of object | no | at most 3 items | no | as stored |
| `target.wind.stretches[].from_deg` | number | no | min 0; max 360 | no | as stored |
| `target.wind.stretches[].from_yd` | number | yes, within | min 0; max 10000 | no | as stored |
| `target.wind.stretches[].given` | boolean | yes, within | - | no | as stored |
| `target.wind.stretches[].speed_hi_mph` | number | no | min 0; max 60 | no | as stored |
| `target.wind.stretches[].speed_mph` | number | no | min 0; max 60 | no | as stored |
| `target.wind.stretches[].switching` | `'true'` | no | - | no | as stored |
| `target.wind.stretches[].to_yd` | number | yes, within | min 0; max 10000 | no | as stored |

## What it holds

The `target` block of a logged group says where the group was shot and in what wind: the range, the direction the shot was fired, the slope of the line of sight, and the wind. The wind is either two speeds with one direction over the whole path, or, when the shooter used "Wind by stretch", three stretches of the path with a wind of their own. It is a copy made when the group was logged. It carries no place name and no position: a group's firing point and target position are separate, optional fields on the [positions](positions.md) page. When the group is solved again, for a calibration or a chart, these fields become the target of that solve and the group's [air](air.md) becomes its weather.

## What to know first

**The wind direction is a clock position, not a compass bearing.** `target.wind.from_deg` is the clock value times 30: 0 is a head wind, 90 (3 o'clock) comes from the shooter's right, 180 is a tail wind. The azimuth does not turn it.

**Wind 1 and wind 2 are two guesses at the whole path.** Both blow the full distance from the same direction. The app's prediction and every fit use wind 1; wind 2 only widens a group's uncertainty. They are not a near wind and a far wind.

**With `target.wind.stretches`, the wind is the stretches alone.** The three simple fields then hold 0, 0 and 90 and mean nothing; every reader uses the stretches. An older app reads such a group as calm.

**Azimuth is true north, and an uphill slope is positive.** The log form does not limit either. A group logged by the connector takes what the shooter did not say from the phone's Calculator, else defaults (azimuth 0, slope 0, no wind, 3 o'clock), each named in `target.assumed`.

**Hiding the location rounds the bearing.** A share with location hidden shows every group's range and slope, and its azimuth to the nearest 10 degrees.

## The fields

**`target.range_yd`** The distance to the target in yards, as the shooter measured it. Required. The log form takes any number; the schema allows up to 10000. Example: 1760.

**`target.range_source`** Where the range came from, only when a connected rangefinder gave it. Optional; its one value is `rangefinder`, written when a group is logged from the Calculator while the range is still the ranged value, and when the group is added with the range unchanged in the log form. Absent means typed (or picked from a planned target, or given by the connector), as every group from before the rangefinder link. Changing the range by hand, picking a planned target or discarding the draft clears the mark. The mark says nothing about which unit measured it: no serial number, address or name is stored. Nothing reads it; it is there for a later look at a group. Example: `rangefinder`.

**`target.assumed`** Which of this block's values nobody gave, so that a default was stored. Optional: a list of `azimuth` (stored 0), `inclination` (stored 0), `wind` (wind 1 stored as 0 mph) and `wind_from` (stored as 3 o'clock). The connector's `log_group` writes it: for a value that was neither said nor on the phone, and for a bearing, slope or wind of exactly 0 taken from the phone's Calculator (an untouched Calculator holds 0 there). Any other value taken from the phone is not marked. Since 2026-10-19 the app's group form writes it too, but only for a box it found blank and filled with the default (a restored or emptied form), and, since 2026-10-22, for an angle it fills with 0 when a planned target with no learned angles is picked; the 0 the form starts with is not marked. Absent means nothing is said: most groups from the app's form, and every group logged before 2026-10-13. The Session screen shows "Not given, a default is stored" under such a group, in your own log and in a shared one. Editing the group in the app drops the entry of each value that was changed. No fit reads it. Example: `["azimuth", "inclination"]`.

**`target.azimuth_true_deg`** The direction of fire in degrees from true north, clockwise: 0 is north, 90 is east. Required. Only the Coriolis choice reads it (see "Who reads it"). The camera aim fills it from the compass and a position fix, with magnetic declination applied, and leaves it alone when it has no fix. Example: 135.

**`target.inclination_deg`** The slope of the line of sight in degrees, positive for a target above the shooter, negative below. Required; 0 is level. Example: -6 for a target six degrees below.

**`target.wind`** The wind the shooter entered: one object holding the mode, two speeds and a direction, and, when the wind was given by stretch, the list of stretches. There is no field for where the wind was measured. A gust is only possible inside a stretch. The members are `target.wind.mode` (always `simple`), `target.wind.speed1_mph`, `target.wind.speed2_mph` (both in miles per hour), `target.wind.from_deg` (degrees, below) and `target.wind.stretches`. Without stretches the solver applies each speed along the whole flight from the muzzle to the target.

**`target.wind.speed1_mph`** The first wind speed in miles per hour. Required. It is the wind of the group's prediction and of every fit. The Calculator and the log form both call it "Wind 1". Example: 8.

**`target.wind.speed2_mph`** The second wind speed in miles per hour. Required; the form starts it at 0, and the connector sets it equal to wind 1 unless told otherwise. The solver works out a second windage for it, and the app shows that as "Wind 2". It never changes the elevation. Example: 12.

**`target.wind.from_deg`** The direction the wind blows from, in degrees clockwise from straight downrange: the form's "Wind from" clock value times 30, so 10:30 is 315. Required. Straight downrange can be stored as 0 (clock 0) or 360 (clock 12); the solver treats them the same. The stored record accepts any value, such as 450; the form (since 2026-10-13) and the connector accept a clock of 0 to 12. A group logged with no wind still carries a direction; the form starts at 3 o'clock, which is 90.

**`target.wind.stretches`** The wind by stretch, as entered. Optional, and a list of exactly three items: Near, Mid and Far, in that order, so that each starts where the one before it ends and the first starts at 0 yd. When it is present the three simple fields above hold 0 mph, 0 mph and 90 degrees and mean nothing. A stretch can be steady, gusty (a low and a high speed) or switching (it may equally come from the mirror direction across the line of fire). A stretch the shooter did not fill in is blank: it is calm and is shown as "calm (not given)". Only a row with every box empty and "Switching" not ticked is blank; a row with something in it but not its speed or its direction ("Switching" ticked alone, too) gives no solve and says what is missing, for example "Mid: give the wind speed". It never takes a neighbour's wind. Absent means the group has the simple wind. A group whose list is damaged (not three stretches, or numbers a stretch cannot have) is read as calm. Example: three stretches of 500 yd, calm, 5 to 10 mph from 3 o'clock, 8 mph from 10 o'clock and switching.

**`target.wind.stretches[].from_yd`** and **`target.wind.stretches[].to_yd`** Where the stretch starts and ends, in yards from the muzzle. Required inside each stretch, 0 to 10000. The app's default is the range cut in three equal parts; the shooter can move the two boundaries, each to at most 9,999 yd, so the last stretch ends at most at 10,000 yd. A stretch that starts past the target's range is not used by that shot. The last stretch is flown to the end of the path, however far its `to_yd` is. Example: 0 and 667.

**`target.wind.stretches[].given`** Whether the shooter filled this stretch in. Required inside each stretch. `false` is a blank stretch: calm, shown as "calm (not given)", with no other field set. `true` needs `speed_mph` and `from_deg`.

**`target.wind.stretches[].speed_mph`** The wind speed in miles per hour, 0 to 60, set when `given` is true. For a gusty stretch it is the low speed. Example: 5.

**`target.wind.stretches[].speed_hi_mph`** The high speed of a gusty stretch, 0 to 60, set only for a gust, and then it must be more than `speed_mph`. Example: 10.

**`target.wind.stretches[].from_deg`** The direction the wind blows from, in degrees clockwise from straight downrange, 0 to 360: the clock value times 30, as for `target.wind.from_deg`. Set when `given` is true. Example: 90 for 3 o'clock.

**`target.wind.stretches[].switching`** The one value `true`, set only when the stretch is marked as switching: the wind may equally come from the mirror direction on the other side of the line of fire (360 minus `from_deg`). Absent means steady in direction. Example: `true`.

**How the hold is formed.** The solver flies each stretch as a segment of the path. The one hold, the elevation and the time of flight come from a single flight in which each stretch has the average of its possible winds: a gust is flown at the middle of its two speeds, and a switching stretch keeps only the part of its wind along the line of fire, since its two sideways parts cancel. A blank stretch is calm. Under the hold the app shows a spread, "could be from ... to ...": up to two more flights (a flight equal to the best guess is reused), one with every stretch at whichever of its possible winds pushes the bullet most to the left, and one with every stretch at the one that pushes it most to the right. The spread is not stored; it is worked out again when it is shown.

**What this has been checked against.** The stretch wind is the solver's own physics, checked for consistency: three equal steady stretches give the same numbers as the simple wind, blank stretches give no wind, and the two-flight spread equals flying every combination of the possible winds. It has not been compared with any other calculator's wind that changes along the path, so no outside figure backs it.

**`target.target_id`** The id of the planned target (a [session target](session-targets.md)) the group was logged against. Optional; absent for a group logged without picking one, and always absent for a group logged by the connector. It is a link, not a copy: the range and angles in this block are the group's own.

**`target.target_speed_mph`** A speed for a moving target. Required, and 0 in every group the app or the connector writes. Nothing reads it except the CSV export, which writes it out.

**Limits, by writer.** The limits table of a profile page does not fit here, since a log field has no conversion step, so the limits are listed. The log form and the stored record check little; the connector checks more. Units are the field's own.

- App, the log form: each number must be readable as a number (a comma works as the decimal mark), and nothing else is checked. No range, azimuth, slope, wind or clock bound exists. A range of 0 or below is saved, with no prediction stored, since the solver refuses it.
- Stored record, checked when the group syncs to the server: `target.range_yd` at most 10000. A group over that is saved on the device and refused by the server, which lists it among the rejected records of the sync.
- Stretches: exactly three; `from_yd` and `to_yd` 0 to 10000; speeds 0 to 60 mph; `from_deg` 0 to 360; a gust's high speed above its low speed. The record schema holds the numbers; the rest (each stretch starting where the one before it ends, the first at 0 yd, no stretch ending past 10,000 yd, a given stretch having its speed and direction) is checked by the app and the connector before a group is saved, and a record that breaks it is read as calm.
- Connector, `log_group`: `range_yd` 1 to 5000, `wind_mph` and `wind2_mph` 0 to 60, `wind_from_clock` 0 to 12, `azimuth_deg` 0 to 360, `inclination_deg` -45 to 45. `wind_stretches` takes one to three parts, each with a speed 0 to 60, an optional gust 0 to 60 above it, a clock 0 to 12 and a switching mark, and `wind_bounds_yd` takes two distances of 1 to 5000. A value read from the phone for azimuth, slope, wind or clock must be inside the same bounds, or the tool refuses and names it. The phone's range is never used: `range_yd` is required.
- Solve: the range must be above 0 and reachable (a target needing more than 45 degrees of elevation is "out of range"). A wind speed too large for the bullet to reach the target gives the same answer. Any slope and azimuth are accepted.
- Fit: no extra bound. A group that cannot be solved stops the fit with "group at N yd is out of range for this profile".

## Who writes it

**The app.** The range log's group form has "Range", "Azimuth", "Inclination", "Wind 1", "Wind 2" and "Wind from" (o'clock), and "Add group" saves them through `buildObservation`, which multiplies the clock by 30 and stores 0 for the target speed. "Aim with camera" fills the azimuth and the slope (to one decimal), and "Use azimuth" fills the azimuth from the bearing between the two [positions](positions.md) when both are entered, and the target picker fills the range and, when the planned target has them, the slope and azimuth. A planned target with no angles yet fills each angle box with 0, marked as not given, unless the shooter typed one (until 2026-10-22 it blanked both, which kept "Add group" from saving); while a saved group is being edited, such a pick leaves the angles as they are. Only a measured angle is learned into the target: a slope or azimuth marked as not given is not. "Log this shot" in the Calculator hands over its range, azimuth, slope, both winds and the clock, which become a new unsaved group; when the Calculator's "Wind by stretch" switch is on, it hands over the three stretches instead, and the group form shows them as a read-only summary with "Use simple wind instead", which removes them and asks for a wind (nothing is filled in from them); when the switch is on but its rows do not read, no wind is handed over and the form fills the wind as for any blank box, marked as not given (the Calculator's hidden simple wind boxes are never used then, nor by "Log a group" on the Range log while the switch is on); when the Calculator's range was filled by tapping one of the active log's targets and has not changed since, the group is at that target (`target.target_id`); when the Calculator's range is still the value a connected rangefinder gave, the form says so ("Range is from the rangefinder") and the group is saved with `target.range_source`. After a group is added, the next form keeps the same azimuth, slope and wind, and clears the range and the target.

**The connector.** `log_group` takes `range_yd`, `wind_mph`, `wind2_mph`, `wind_from_clock`, `azimuth_deg` and `inclination_deg`. Anything not given is read from the phone's Calculator, then defaulted: azimuth 0, slope 0, wind 1 at 0, wind 2 equal to wind 1, clock 3. The notes of the group name each value that was not said, by its word only (for example "[default: bearing, slope]"; until 2026-10-15 with its value, "azimuth 0° (default)"), and `target.assumed` marks the defaults. It cannot set `target.target_id`. An input it does not know is not used: the group is logged and the reply names it ("I ignored: ..."). It also stores stretches: from `wind_stretches` (with `wind_bounds_yd`, or thirds of `range_yd`) when the shooter said them, else the phone's stretches when the Calculator's switch is on and no wind was said (when the phone's boundaries follow its range, cut again in thirds of `range_yd`, each stretch keeping its wind); a spoken simple wind wins over the phone's stretches. A group stored that way has the placeholder simple fields. Saying both stretches and a simple wind is refused.

**Copy and restore.** A shared log's viewer reads the target as the share shows it (the bearing rounded when location is hidden); nothing is written back. The [observation](observation.md) page covers what a copy or a restore of a group does.

**Never fitted.** No fit writes this block. Since 2026-10-13 the app can edit a saved group, this block included: a changed range drops `target.range_source`, and a changed value drops its entry in `target.assumed` (what an edit keeps and works out again is on the [group](group.md) page). A group with stretches shows them in the form as a read-only summary: the form cannot edit them, and "Use simple wind instead" replaces them with a typed wind, which drops the `wind` and `wind_from` entries. The connector has no way to change a saved group.

## Who reads it

**The fitter.** A calibration turns each logged group into a calibration group. The target becomes the solver's range, azimuth, slope, both wind speeds and the wind direction. For a group with stretches the target is instead the best-guess flight of its stretches (the hold's wind, above), in the app, the connector and the pooled fit alike: a head or tail wind and the crosswind jump of the near stretch both move elevation, which is what a fit reads, so the stand-in 0, 0 and 90 are never flown. Every solve of the fit then uses the group's own target with the profile's current model.

The fit leaves a group out only for two target reasons: the range is missing or is not a number, or the group has no wind block at all. Neither a steep slope nor a strong wind excludes a group. The guided calibration flags a group as "windy" when its larger wind speed is at least 5 mph and the wind dominates its uncertainty, and the chart panels weigh a group's wind through its larger speed. For a group with stretches that speed is the largest speed of its best-guess stretches. Wind 2 is read only there. The guide measures that uncertainty as the elevation change of 1 mph of head wind.

**The solver options.** Four options decide how these fields are used when a group is solved again:

- [coriolis](../options/coriolis.md): the azimuth enters only through the Earth's rotation. The Standard choice uses it for the east-west change in gravity, the integrated choice uses it with the slope, and Off ignores it, so with Off no azimuth matters.
- [angle](../options/angle.md): the slope tilts gravity in every choice. The Classic choice also changes how the sight height counts on a slope, which matters at short range.
- [windDrag](../options/windDrag.md): the wind speed and direction move the bullet in every choice. The Standard choice takes the drag from the ground speed, the others from the speed through the moving air, which differs for head and tail winds and hardly at all for a crosswind.

- [aeroJump](../options/aeroJump.md): wind 1 and the wind direction give the crosswind at the muzzle, which this option turns into a small change in elevation.

Another option reads the same fields without a choice about them: the [curvature](../options/curvature.md) option, off in every model, treats the slope as measured from map heights.

**The app.** The range log shows each group's range in its table, and, for a group with stretches, a line "Wind by stretch: ..." that names each stretch with its yards, its speed (or low and high speed), its clock direction and "switching", or "calm (not given)". The Workbench uses the winds in its windage and spin panels, and its calm-air panel picks groups by their larger wind speed. Guided calibration reads the newest group's air, wind and azimuth as the conditions for the next shot it suggests; for a group with stretches its current wind is the windiest best-guess stretch, with that stretch's direction. The windage residuals and the verdict predict a group with stretches from its best-guess flight. The CSV export writes the block as columns, with the wind direction as a clock again, and a `wind_stretches` column holding the summary line for a group with stretches (its simple wind columns are left empty).

**The connector.** `calibrate_profile` fits and previews through the same conversion, so a group's target decides whether it counts and how it is solved. `get_session` shows a group with stretches as a `wind_stretches` summary and leaves out its simple wind figures.

**The server.** The admins' pooled export lists each group's range, slope, azimuth, both wind speeds and the wind direction, and a `wind_stretches` column with the summary for a group that has them, and the Community and pool pages use the range for a bullet's longest range and a rough speed at the target.

## Why it is stored

**Range, azimuth, slope and the wind** are everything the solver needs to repeat the shot, besides the air, the muzzle velocity and the profile. They are saved with the group, not looked up later, because the shooter could not change them afterwards and the profile version already records the rest. Without them the group could be compared with nothing.

**Two wind speeds** because the Calculator gives two windages and the log records both entries, so the group can be looked at against either.

**Stretches, with the simple fields left at a stand-in,** because a made-up single wind that stands for three would show a wind the shooter never entered and would have no head or tail part. So the stretches are stored as entered, and every reader reads them in place of the simple wind.

**The wind mode and the target speed** were part of the first design of the record and have only ever held one value. Wind that changes along the path has since been built, as `target.wind.stretches`, and `mode` is still `simple`. A moving target has not been built.

**The target id** lets guided calibration compare a group with the planned target it was shot at, and flag one whose range or angles disagree.

## Changing it

The record is not versioned: a group is added, edited in place on the Session screen (since 2026-10-13), or deleted by setting its deleted mark. A deleted group is left out of every fit. Nothing about the target changes a calibration, so the edit category is "none".

**The planned target.** The group's block is a copy. The app cannot change a planned target's range or name: it only adds and removes targets. Removing one, or a list changed by sync or a restored backup, changes nothing in earlier groups. Logging a new group against a planned target writes that group's slope and azimuth into the planned target, replacing the earlier values, and the screen notes the change. The guided calibration then compares a group's range (more than 1%), slope (more than 0.5 degrees) or azimuth (more than 3 degrees) with the planned target's current values and flags a mismatch as advice. A group whose planned target was deleted is not flagged.

**Older groups.** Which groups a fit uses depends on the profile version each was shot with, not on the target; see the [observation](observation.md) page.

## Sharing and privacy

Every field on this page but one reads "as stored" in the table: a reader of a shared log sees each group's range, slope, wind and target id exactly as the owner does, and the range's source mark with the range: it is never hidden, because the range is not. That holds for a share with one person, with the public or with the admins, and whether notes are hidden or not. The target block holds no text, so hiding notes changes nothing here.

`target.azimuth_true_deg` is rounded to the nearest 10 degrees (0 to 350; 355 reads 0) when the share hides location; with location shown it is exact. The rounding moves a come-up at 2,000 yd by at most about 0.02 mil, through the vertical part of the earth's rotation, so a helper's fit or the pooled fit loses almost nothing. The location switch also removes the positions of a group, rounds the air's latitude to a whole degree and its pressure to 0.1 inHg, and empties the place name. A reader can still work out the distance of each shot, its rough direction, and the height difference between the firing point and the target (the range times the sine of the slope).

The planned targets of the same session, which a shared view also carries, keep their range and slope the same way; with location hidden they lose their position and their height `elevation_ft`, their bearing is rounded to 10 degrees, and their name becomes their range. They are described on the [session targets](session-targets.md) page. The id of a group's planned target is the owner's own id, the same in the group and in the shared session.

---

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