# Prediction

What the Standard and Physics models predicted for a group at the moment it was logged, and the dial the app suggested, to set against what was shot and observed.

## Generated table

**Record:** observation · **Covers:** `solver_prediction`, `solver_prediction_physics`, `suggested`

| Field | Type | Required | Limits in the schema | Solver reads | In a shared log |
|---|---|---|---|---|---|
| `solver_prediction.elev_mil` | number | yes | - | no | as stored |
| `solver_prediction.solver_version` | string | yes | - | no | as stored |
| `solver_prediction.wind_mil` | number | yes | - | no | as stored |
| `solver_prediction_physics.elev_mil` | number | yes, within | - | no | as stored |
| `solver_prediction_physics.solver_version` | string | yes, within | - | no | as stored |
| `solver_prediction_physics.wind_mil` | number | yes, within | - | no | as stored |
| `suggested.elev_mil` | number | yes, within | - | no | as stored |
| `suggested.wind_mil` | number | yes, within | - | no | as stored |

## What it holds

These fields keep what the solver said when a group was logged. `solver_prediction` is the come-up of the Standard model and `solver_prediction_physics` that of the Physics model, for the group's own range, air, angles, wind and measured velocity (for a group with wind by stretch, the wind is the best-guess flight of its stretches, see [target](target.md)). `suggested` is the dial the app showed for the profile's own model. It is a snapshot: nothing works it out again.

## What to know first

**The predictions are always Standard and Physics.** Whatever the profile's model, each is stored, so a Classic or Spin profile stores no figure from its own model. Only `suggested` follows the profile's model.

**Both use the group's mean velocity.** Not the profile's, and not its ammo-temperature table.

**They are dials, not true angles.** They include the fitted drag curve and divide by the tracking factors, so set them against the dial.

**`null` means the solver said "out of reach", not an error.** Usually the target range; it is also stored when the zero range, or a very low velocity, cannot be solved. Other failures save no group. An old group may have no Physics key.

**`suggested` is made before the shot,** from the profile's model and its velocity (the muzzle velocity, or the ammo-temperature table's value when an ammo temperature is typed), not the group's.

## The fields

**`solver_prediction`** and **`solver_prediction_physics`** The come-up for this group from the Standard model and from the Physics model. Each has three members: `solver_prediction.elev_mil` and `solver_prediction_physics.elev_mil` (up is plus), `solver_prediction.wind_mil` and `solver_prediction_physics.wind_mil` (plus is a dial to the right), both in mil, and `solver_prediction.solver_version` and `solver_prediction_physics.solver_version`, the solver's version at the time (for example `solver-11`). Both are dials: the true angle divided by the profile's elevation or windage tracking factor, and the windage is the answer with the calm drift at the zero range taken out, as the Calculator shows it. The windage is for the first wind speed only; a second wind speed is not stored. The profile's fitted drag curve, if it has one, is applied. The profile's Spin coefficient sets are not (neither model uses them). The Physics key is optional in a stored group: a group logged before it existed has the Standard figure only. The Standard one is required but may be `null`, which means this model said the shot is out of reach (see above). Examples: 11.43 and 0.65.

**`suggested`** The dial the app suggested when the group was logged: `suggested.elev_mil` and `suggested.wind_mil`, in mil, with the sign of the dial. The windage is for the first wind speed, like the predictions, or for the best-guess wind of the stretches when the group has them. It is the Calculator's answer for the group's range, air and angles with the profile's own model and velocity (its ammo-temperature table when one was typed), as the Session screen's "Calculated" line shows it. It is stored only when both numbers are there: the app leaves it out when it could not solve, and a group logged by the connector never has it. Example: 12.41 and 0.63.

**Limits.** None of these three is typed by anyone, so the limits of the profile and of the group form do not apply. The predictions are computed by the same function for the app and the connector from the inputs on the [group](group.md) and [target](target.md) pages. The server's schema asks only for numbers, a text for the version, and a whole `null` or a whole object for each prediction.

## Who writes it

**The app.** The Session screen's "Add group" builds the whole group with `buildObservation`, which solves twice, once with the Standard setup and once with the Physics setup, on a copy of the profile whose velocity is the group's mean. It also hands over the Calculated dial of the screen as `suggested`, or a blank if the screen has no answer. The copy of the profile the group is logged against is the one picked on the Session screen, and the group records its version.

**The connector.** `log_group` builds the group with the same function and gives no suggested dial, so a group logged by voice has no `suggested`. A voice log without a velocity uses the profile's muzzle velocity. Its reply says both predictions in words ("Predicted 11.43 (Standard) / 11.46 (Physics)", or "out of range" for `null`) and returns both objects. The tool has no input for any of these fields.

**Other paths.** A backup restored in the app brings the fields back as the file held them; the restore refuses a prediction whose numbers or version are not of the right type. Sync copies them unchanged. The fitter and the server never set them, and nothing changes them after the group is saved.

An unsolvable profile (a missing BC, say) saves no group at all, in the app and in the connector. Only a solve that says "out of reach" (the target range, or a zero range that needs a bore angle over 45 degrees) is stored as `null`.

## Who reads it

The solver does not read these fields; it wrote them. The fitter does not read them either. It solves each group again, with the profile's own model and the profile's present drag curve, so a calibration never depends on what was predicted at logging time. The Workbench, too, solves again.

**The app.** The Session screen's group list has two columns, "Pred" (Standard) and "Phys" (Physics), which show only the elevation, to two decimals and in the unit the dial is shown in (the scope's unit unless it was typed in another), beside "Dialed" and the centre. Its tooltip on the "Dialed" cell compares the aim (dial plus hold) with `suggested` in words ("0.20 up, 0.10 L" or "the same"). The CSV export writes `pred_elev_mil`, `pred_wind_mil`, `pred_phys_elev_mil`, `pred_phys_wind_mil`, `suggested_elev_mil` and `suggested_wind_mil` to two decimals, and one `solver_version` taken from the Standard figure (from the Physics one if there is none); on an MOA scope's rows it adds an MOA copy of each of the six (since 2026-10-16).

**Is it like with like?** The predictions and the dial are in the same unit, a turret reading, and the velocity is the group's, so a prediction set beside the dial is fair: the profile's tracking factors are on both. What the screen does not show is the correction the shot needed, so a reader must still work out the needed dial from the centre (see the [group](group.md) page). The CSV column `elev_mrad` for that is the aim minus the centre with no tracking factor, so it is a true come-up only when the scope tracks true. The predictions differ from `suggested` in two ways besides the model: the velocity (the group's against the profile's) and the model itself.

**The connector.** `log_group` says the predictions when it logs. `get_session` returns whole groups as stored, with these fields (on an MOA scope each angle also in MOA beside the mil one, since 2026-10-16), and no tool works them out again.

**The pooled file for admins.** It has no column for any of the three.

## Why it is stored

The two predictions are a record of how well each fixed model did on the day, without the profile's own model getting in the way: a group on a Spin profile can still be set against the plain Standard and Physics answers. They are kept as they were because the profile, the calibration and the solver all change afterwards, and the log should show what the app said then. The solver version says which solver said it. `suggested` is stored because a shooter often dials something other than what the screen suggested, and the difference is what the group says about the shooter's dope. The group's own fit is made again from the raw fields, so the stored predictions are for reading, not for fitting.

## Changing it

Only an edit of the group works its predictions out again: since 2026-10-13 the Session screen can edit a saved group, and "Save changes" stores both predictions afresh from the edited values, with the group's own profile version and the solver of that day. Nothing else does. A new solver, an edited profile, a new calibration or a changed group velocity leaves old predictions as they were, and `solver_version` is how to tell them apart. A hand edit has no effect on a calibration (`on_edit: none`). Deleting the group is a soft delete: the record stays, marked deleted, and the list, the CSV and the shared view leave it out.

## Sharing and privacy

Every row reads "as stored": a reader of a shared log sees both predictions and the suggested dial exactly as the owner does, whatever the location and notes switches say. They show which solver the shooter used (`solver_version`), how far the dial was from the model, and whether the shooter followed the app's suggestion.

---

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