# ballistic-link

description: Construct BallisticLink solution URLs from conversational
shooting requests. Use when the user mentions a shot, range, wind,
DOPE, or references a saved load by nickname.

Base URL: `https://ballisticlink.pages.dev/solution`

## What you do

1. Maintain saved PROFILES as query-string templates in memory,
   keyed by nickname ("7mm", "the WSM", "elk rifle"). Profile params
   (`n c w b bc dm mv z sh u pv`) change ONLY when the user tells you
   in conversation; never tilde them.
2. When the user calls a shot, merge the called FIELD values
   (`y ws wd a t da ang`) into the template un-tilded. Uncalled fields
   keep their tilde. Never invent an un-tilded field value.
3. Reply with ONE markdown link: `[452 yd · 5 mph L → Larry's 7WSM](url)`
4. When the user pastes a URL back with "save this", update the
   template's FIELD defaults (re-tilde them); leave the profile as-is
   unless the user explicitly changes it ("bump mv to 2892" →
   edit `mv`, and ask whether `pv` should flip to `true` if it was `book`).

## Tilde rule (FIELD params only)

No tilde = the shooter called it this session.
Tilde = carried default or your inference. When in doubt, tilde it.

- FIELD values the user stated in this conversation, for this shot → no tilde.
- FIELD values carried from the saved template (typical range, home-range
  altitude) → tilde.
- FIELD values you guessed or inferred (altitude from the user's known
  location, temp from a weather lookup) → always tilde. If the user *asked*
  you to look it up ("grab current temp"), it counts as called → no tilde.
- Never put a tilde on a PROFILE param — the app treats that as a
  construction error and flags it.

## Parameter reference

### PROFILE params (agent-managed, page-immutable, never tilded)

| Param | Meaning | Type / units | Default | Example |
|---|---|---|---|---|
| `n` | Profile name (display label) | URL-encoded string ≤ 40 chars | "SOLUTION" | `n=Larrys%207WSM` |
| `c` | Cartridge label (display only) | string | "" | `c=7mm%20WSM` |
| `w` | Bullet weight | grains, int | 150 | `w=168` |
| `b` | Bullet name (display only) | string | "" | `b=Berger%20VLD` |
| `bc` | Ballistic coefficient | float | 0.493 | `bc=.617` |
| `dm` | Drag model | `g1` \| `g7` | `g1` | `dm=g7` |
| `mv` | Muzzle velocity | fps, int | 3130 | `mv=2887` |
| `z` | Zero range | yards, int | 100 | `z=200` |
| `sh` | Sight height | inches, float | 1.5 | `sh=1.8` |
| `u` | Output units | `moa` \| `mil` | `moa` | `u=mil` |
| `pv` | Profile verification level | `book` \| `true` | `book` | `pv=true` |

`pv=book` = published/manufacturer data, not yet validated by this shooter.
`pv=true` = the shooter has chrono'd velocity and/or trued the solution on steel.

### FIELD params (page-adjustable, tilde-eligible — the live layer)

| Param | Meaning | Type / units | Default | Example |
|---|---|---|---|---|
| `y` | Target range | yards, int, 25–2000 | 300 | `y=452` |
| `ws` | Wind speed | mph, int 0–40 | 0 | `ws=5` |
| `wd` | Wind direction | `l` \| `r`, or clock `1`–`12` | — | `wd=l` or `wd=9` |
| `a` | Altitude / elevation | feet, int | 0 | `a=~1200` |
| `t` | Temperature | °F, int | 59 | `t=38` |
| `da` | Density altitude (overrides `a`+`t` if present) | feet, int | — | `da=4300` |
| `ang` | Incline angle | degrees, ±60 | 0 | `ang=~0` |

Clock-position wind: `wd=9` = full-value left, `wd=3` = full-value right,
`wd=1`/`wd=11` = half-value, `wd=12`/`wd=6` = no value. `l`/`r` map to 9/3.

Reserved for future versions (ignore, do not emit): `p`, `rh`, `lat`, `az`, `tw`, `v`.

## Hard rules (failure-mode hardening)

- Never fabricate `mv` or `bc`; if no template exists, ask once, or emit
  book values **tilded on the FIELD side only** and label the profile
  `n=~<cartridge>%20(book)` with `pv=book`.
- One link per solution; don't dump the raw query string in prose.
- `y` is always yards; if the user speaks meters, convert (×1.0936) and
  note the conversion in your reply.
- Echo the live params in the link text so the user can sanity-check
  before tapping.
- Unknown or extra params are ignored by the app — never rely on them.

## Examples

**1. Saved template in your memory** ("Larry's 7WSM", profile trued, fields all carried):

```
/solution?n=Larrys%207WSM&c=7mm%20WSM&w=168&b=Berger%20VLD&bc=.617&dm=g1&mv=2887&z=200&sh=1.5&pv=true&y=~500&ws=~0&a=~900&t=~59
```

**2. User says: "7mm, 452 yards, wind 5 left"** → merge called fields un-tilded:

```
/solution?n=Larrys%207WSM&c=7mm%20WSM&w=168&b=Berger%20VLD&bc=.617&dm=g1&mv=2887&z=200&sh=1.5&pv=true&y=452&ws=5&wd=l&a=~900&t=~59
```

Reply: `[452 yd · 5 mph L → Larry's 7WSM](…)`

**3. Metric user: "elk rifle, 640 meters, wind 3 from the right"**
640 m × 1.0936 = 700 yd:

```
/solution?n=Elk%20Rifle&c=300%20Win%20Mag&w=200&bc=.588&dm=g1&mv=2930&z=200&sh=1.7&pv=book&y=700&ws=3&wd=r&a=~5200&t=~45
```

Reply: `[700 yd (640 m) · 3 mph R → Elk Rifle](…)` — note the meter conversion.

**4. Clock wind: "wind's about 8 at my 10 o'clock, target 850"**

```
…&y=850&ws=8&wd=10&a=~5200&t=~45
```

**5. "Save this" round trip.** User pastes back
`…&y=452&ws=5&wd=l&a=900&t=38…` and says *"save this as my 7WSM"* →
update the template's FIELD defaults and re-tilde them:

```
…&y=~452&ws=~5&wd=~l&a=~900&t=~38
```

Profile params are copied byte-identical. If the user has two rifles that
could match a nickname, ask which one — never guess between profiles.
