Expand description
Transport-free card-generation services (come-ups, range table, wind card).
These replicate the CLI’s card commands exactly — same zero solve, same sampled
trajectory, same nearest-sample row selection, same adjustment/bias/CF/click
ordering via crate::adjustment — as versioned request/response types the
bridge can expose to embedded consumers. The CLI remains the reference
implementation; tests/card_bridge_golden.rs asserts row-for-row agreement.
§Unit convention
Unlike solve-json (explicit SI), card requests are denominated in the shooter’s own units, exactly like the CLI flags they mirror: imperial = fps, grains, inches, yards, °F, inHg, mph; metric = m/s, grams, mm, meters, °C, hPa, m/s. A DOPE card is a display artifact; its inputs and outputs share the shooter’s unit world, and this keeps the request shape identical to the documented CLI surface.
§Per-dimension units (MBA-1519)
Nobody shoots in one system. A Finnish shooter ranges in metres, dials MIL, and buys
bullets labelled in grains, because that is how the industry sells them; European makers
dual-print grams / grains rather than replacing one with the other, and mix within a
single table. So the denominating state on this request is a unit PER DIMENSION, and
CardUnits is a one-shot preset that fills in every dimension the request does not
state for itself — not a mode the card is “in”.
Concretely: an explicit per-dimension field wins; an absent one falls back to what
units implies. A request written before those fields existed states none of them, so
every dimension falls back and the card is byte-for-byte the card it always was
(tests/card_units_per_dimension.rs pins that against a stored document).
The response needed no new FIELD for any of this: CardUnitsBlockV1 has always carried
seven independent label fields, so a card whose distance is metres and whose velocity is
fps already had somewhere to say so. It did need the two angular labels to become
requestable: elevation_adjustment/windage_adjustment print "MIL"/"MOA"/"SMOA"/
"IPHY"/"CLICKS" and AdjustmentUnit deserialized lowercase only, so those two were
the only fields of the block a caller could not echo back. They take the uppercase
spellings as aliases now, which makes “echo the units block into the next request” true
of all seven fields rather than five of them.
Three candidates do NOT get a new field, and each for its own reason:
- Scope adjustment is already per-dimension, and was first:
adjustment_unitandwindage_unitare separate fields with separate click graduations, because a shooter with a MIL rifle and an MOA rifle has two scopes rather than a preference. SMOA (and itsiphyspelling) is already one of the values they take. - Clicks is not a units dimension. A click is meaningless without a graduation, and
this module already enforces that:
adjustment_unit == Clickswithout anelevation_click_valueis a refusal. The graduation belongs beside the turret’s MIL/MOA/SMOA marking, not in a units picker. - Twist rate is a dimension of the design but not of this request — a card request carries no twist, so there is nothing here for a twist unit to denominate.
And the linear-at-distance spellings are NOT units: “inches@100yd” IS SMOA — the same 1/3600 rad, the same printed number, no conversion — and “cm/100m” is 0.1 mrad, i.e. MIL with a factor-of-ten display. They are alternate HEADER TEXT for units this request already has, and adding them as values would give a caller two spellings that produce identical numbers.
§What the response does NOT tell a caller
Like every other default on this request — sight_height, temperature, pressure,
humidity, windage_unit — a per-dimension unit taken from the preset is applied
silently, and this surface has no notice channel at all (there is no assumptions array
on CardResponseV1, unlike solve-json’s SolveSuccessV1).
Be precise about what that costs, because a paragraph of prose can argue it away. The units
block names the unit of the SEVEN OUTPUT COLUMNS and nothing else. It says nothing about
the six dimensions that are input-only — mass, diameter, sight_height,
temperature, pressure, altitude — so for those a caller cannot read back off the
card which unit the engine settled on.
altitude is the sharp one, because it is the dimension whose fallback is deliberately
NOT the preset’s: an imperial card.range_table carrying altitude: 5000 and no
altitude_unit — which a US caller writing an imperial card means as feet — is solved at
5000 METRES, and no field of the response says so. On a .308 175 gr that is ~1.9 MIL of
elevation at 1000 yd against the same card with altitude_unit: "feet". The BEHAVIOUR
predates the per-dimension fields (altitude has always reached
AtmosphericConditions unconverted); what is new is that the request can now name the
dimension, so the gap is describable instead of merely present.
No notice was added here, and the reason is narrow rather than principled: any notice —
an assumptions array, or extra label fields on the units block — is a change to the
RESPONSE bytes of every card, including the stored documents the reprint path compares
label-for-label and the six goldens that are the whole evidence for this change being
additive on the wire. A response-schema change belongs to a ticket that can move those
goldens honestly. Until then the remedy is on the request: a caller that cares which
altitude it got states altitude_unit, and a card built by an app should state every
dimension rather than lean on a preset.
Structs§
- Card
BcLadder V1 - The BC ladder a card’s rows flew (MBA-1601):
CardResponseV1::bc_ladder. - Card
BcSegment V1 - One velocity band of an explicit velocity-keyed BC schedule
(
CardRequestV1::bc_segments).velocity_min/velocity_maxare in the request’svelocity_unitand must satisfyvelocity_min < velocity_max;bcis the BC (for the request’sdrag_model) that applies inside the band.bcmust lie in 0.0001..=100. Responses state the bands a card flew in the same shape, inCardBcLadderV1::segments(MBA-1601). - Card
Request V1 - Everything the three card surfaces share: load, zero, atmosphere, display axes.
- Card
Response V1 - Card
RowV1 - Card
Truncation V1 - The wire form of
CardTruncation: the rows a card could not print and why. Distances are in the response’s ownunits.distance. - Card
Units Block V1 - PdfCard
Options V1 - Presentation knobs for the PDF dope card: the header/footer labels the printed card
carries and the table’s font size. The ballistics axes it prints (elevation unit,
windage unit, click graduations, tracking CFs, zero-set biases) are NOT here — they are
already fields on
CardRequestV1, shared with the on-screen card, precisely so the two cannot disagree about what a row means. - PdfCard
V1 - A rendered PDF dope card: the document plus the facts a transport needs to describe it without re-parsing the bytes.
- Stored
Card BcLadder V1 - The
bc_ladderblock of a stored response (seeCardBcLadderV1, which this mirrors). Unknown fields are ignored, for the reasonStoredCardResponseV1gives. - Stored
Card BcSegment V1 - One band of a stored
bc_ladder(seeCardBcSegmentV1, which this mirrors). Unknown fields are ignored, for the reasonStoredCardResponseV1gives. - Stored
Card Response V1 - A stored
CardResponseV1, as a deserializable mirror. - Stored
Card RowV1 - One stored row (see
CardRowV1, which this mirrors). Onlyrangeis required; a column the stored card did not carry staysNoneand prints as an em-dash rather than a fabricated zero. Unknown fields are ignored, for the reasonStoredCardResponseV1gives. - Stored
Card Truncation V1 - The truncation block of a stored response (see
CardTruncationV1, which this mirrors). - Stored
Card Units Block V1 - The
unitsblock of a stored response (seeCardUnitsBlockV1, which this mirrors). Unknown fields are ignored, for the reasonStoredCardResponseV1gives. - Stored
Card V1 - A card whose rows already exist: print THESE numbers instead of solving.
Enums§
- Card
Altitude Unit - Site elevation. See
CardRequestV1::altitude_unitfor why this one dimension’s default does not come from the preset. - Card
Diameter Unit - Bullet diameter: inches or millimetres, the two units a calibre is ever quoted in.
- Card
Distance Unit - Range axis: what
zero_distance,start/end/stepand every row’srangeare in. - Card
Energy Unit - Terminal energy column.
- Card
Linear Unit - A small linear length. Shared by the request’s two TARGET-SIDE linear dimensions (sight-to-bore geometry and the linear drop column) because they take the same values, never because they are the same setting: they are separate fields and resolve independently.
- Card
Mass Unit - Bullet weight. Grains and grams differ by 15.43x, and on the BC path mass never enters
the drag denominator — it touches energy and stability only — so a grams-for-grains slip
yields a trajectory that looks entirely normal. Which is exactly why the unit has to be
stated per-dimension rather than inferred: the dual-printed
g / grbox a European shooter is holding is the normal case, not an edge one. - Card
Pressure Unit - Station pressure.
hpaandmbarare the same number, which is why mbar is an alias rather than a value of its own. - Card
Service Error - Card
Temperature Unit - Air temperature.
- Card
Units - The unit PRESET for a card request: a one-shot bulk action that fills in every per-dimension unit the request does not state for itself, not a mode the card is in.
- Card
Velocity Unit - Speed of the bullet:
muzzle_velocity,bc_segmentsband edges, thevelocitycolumn. - Card
Wind Speed Unit - Wind and crossing-target speed. Four values, not two: a European card reads km/h and a coastal one reads knots, and neither is expressible by picking a system.
- Drag
Model V1 - Drag model selector mirroring the CLI’s
--drag-modelstrings. - PdfRow
Source - Where the printed rows came from — reported so a caller can verify it got a reprint rather than a re-solve, which is otherwise indistinguishable from the document.
Constants§
- CARD_
SCHEMA_ VERSION_ V1 - Schema version of the card request/response contract.
- MAX_
PDF_ PAGES - Pages a printable card may run to. Bounds the one row set the row cap above cannot: few enough rows, but at a font scale that fits very few of them per page.
- MAX_
PDF_ ROWS - Rows a printable card may carry.
- PDF_
CARD_ KIND - The one card kind
card.pdfcan print, in thekindspellingCardResponseV1::kinduses.
Functions§
- come_
ups_ v1 - Come-ups: elevation dial per range plus the incremental come-up between rows.
Mirrors the CLI’s
come-upscommand row-for-row. - pdf_
card_ v1 - Render the printable PDF dope card for a card request.
- range_
table_ v1 - Range table: drop and wind on possibly different display axes.
Mirrors the CLI’s
range-tablecommand row-for-row (two solves: with wind for drift, without wind for the pure elevation axis). - wind_
card_ v1 - Wind card: drift matrix, ranges x wind speeds, per wind-FROM angle.
Mirrors the CLI’s
wind-cardcommand cell-for-cell. The windage axis usesadjustment_unit(the CLI’s wind-card has a single unit for the matrix).