Module reticle_import

Module reticle_import 

Source
Expand description

MBA-1440: import Bero’s “Ventum” reticle spec into the engine’s ReticleDescription.

A reticle drawn in Bero’s Ventum tool is a single JSON object: reticle-level metadata (name, focal plane, calibration magnification, angular unit) plus a spec list of drawing elements. This module is the one-way transform from that authoring format into the engine’s own ReticleDescription, so a Ventum reticle can be hold-solved by the existing hold_point_in_reticle without any of the CLI/FFI/WASM surfaces having to learn a second schema. It is a converter, not new physics — every coordinate is carried straight through, only unit-normalized to milliradians and repeat-expanded.

§What is kept, what is dropped

The Ventum spec mixes hold-bearing marks with decoration:

  • dot and tick are the holdable marks. A dot becomes a MarkKind::Dot; a tick becomes a MarkKind::Hash whose hold point is its own (x, y) anchor.
  • text is a standalone label positioned in reticle coordinates. Following the schema’s recommended “option (b)”, each text is bound to the nearest hold-bearing mark within DEFAULT_TEXT_BIND_MIL and becomes that mark’s label; unattached text is dropped.
  • line, circle, rect, grid, and any unknown future element type are pure decoration and are dropped.

Nothing is dropped silently. import_ventum_reticle_with_report returns a VentumImportReport tallying every element that produced no hold point, so a caller can say “this document drew 6 things I cannot aim with” instead of handing back a reticle that is quietly missing hold points and looks merely sparse. import_ventum_reticle is that function with the report discarded; it exists for callers that genuinely do not care.

§Coordinate and unit conventions

Ventum uses +x = right, +y = down, origin at the reticle center — identical to the engine’s ReticleMark::right_mil / ReticleMark::down_mil, so no sign flips are needed. All coordinates are expressed in the reticle’s own unit; MOA input is converted to milliradians with 1 MOA = 0.2908882 mil (MOA_TO_MIL). Auto-numbered ladder labels are NOT unit-converted — a label is the reticle-unit value it names.

Confirmed by the format’s author (2026-08-01): +y is down because a reticle is numbered downward (a 4-unit holdover is y: 4; a stadia mark 5 up is y: -5), which is plain SVG screen convention and happens to match shooter intuition.

§Arc angles

Ventum draws arcs as a circle element carrying start/end angles. Those angles are measured from 3 o’clock (0° = +x, right) and sweep CLOCKWISE:

  0° = right      90° = down      180° = left      270° = UP

The one counterintuitive consequence, worth stating because it is the opposite of a compass: since +y is down, the TOP of the reticle is 270°, not 90°. A horseshoe opening downward is therefore start: 200, end: 340. This is the same system as the coordinates (+x right, +y down, 0° at 3 o’clock, clockwise), so the format never mixes conventions.

A point on an arc of radius r about the reticle center is therefore

  x = r * cos(θ)          y = r * sin(θ)          (θ in degrees, +y DOWN)

Note the PLUS on the sine: in a y-up math convention this term is negated, and that single sign is the whole trap. Verified against the author’s reference diagram — its 0/90/180/270 markers and the start: 200, end: 340 horseshoe endpoints reproduce exactly under the formula above, and the arc’s 140° clockwise sweep passes through 270° (the top), leaving the gap at the bottom.

§Why an arc still contributes no mark (MBA-1441)

A horseshoe’s apex and its two tips are real aiming references on Vortex-style reticles, so “does an arc carry a hold?” has no single answer — and the Ventum format gives the document no way to say. A circle with a sweep is spelled identically whether it is a ranging horseshoe or a decorative ring segment, and turning every one of them into three marks would do two bad things at once: fabricate aiming points a reticle may not have, and silently change what hold_point_in_reticle answers for every Ventum reticle already imported since 0.32.0, since the nearest mark it reports would start snapping to invented geometry. Inventing holds out of decoration is the same sin as dropping holds without saying so.

So an arc is still not a mark — but it is no longer a silence either. Each one whose geometry this module can read is resolved (using exactly the convention above, which is why the convention was recorded) into the three points a shooter could actually index on, and handed back on the report as a VentumArc: its two tips and its apex, in the engine’s own right_mil / down_mil. One whose radius, sweep or center the document wrote in a form this module cannot read is counted in VentumImportReport::arcs_unresolved instead — reporting points computed from a fallback value nobody wrote would be inventing the very holds the paragraph above refuses to invent. A caller who knows their horseshoe is hold-bearing turns the resolved ones into marks with three ReticleMark::new calls and never re-derives the clockwise/+y-down trap; a caller who does not, at least learns the document drew something they cannot aim with.

§Safety

The mark cap (crate::reticle::MAX_RETICLE_MARKS) is enforced during repeat expansion, so a hostile repeat.n (or a huge mirrored ladder) can never allocate an unbounded vector: expansion stops the instant the running instance count would exceed the cap, having materialized at most one instance past it. This is the same defense as the 0.31.0 reticle generator size guards.

Structs§

VentumArc
One circle element that carried a sweep — a horseshoe or other arc — resolved into the points a shooter could index on, but NOT imported as marks. See the module documentation for why the decision is left to the caller.
VentumArcPoint
A point on an imported arc, in the engine’s own mark coordinates.
VentumImportReport
What an import could not turn into a hold point, so a sparse reticle is explained rather than merely suspicious. Returned by import_ventum_reticle_with_report.

Constants§

ARC_TAG
The tag VentumImportReport::dropped_element_types uses for a circle that carries a sweep. The format has no arc element — an arc IS a circle with start/end — but the two are worth telling apart in a report, because only one of them might have been a hold.
DEFAULT_TEXT_BIND_MIL
Largest (x, y) distance (in milliradians) at which a text element binds to a hold-bearing mark and becomes its label. Text farther than this from every mark is treated as free-floating decoration and dropped.
MOA_TO_MIL
Milliradians per minute of angle (1 MOA = 0.2908882 mil). Ventum reticles authored in MOA are converted to the engine’s milliradian marks with this factor.

Functions§

import_ventum_reticle
Import a Ventum reticle spec into a ReticleDescription.
import_ventum_reticle_with_report
Import a Ventum reticle spec, also returning a VentumImportReport of everything the hold/decoration split left behind.