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:
dotandtickare the holdable marks. Adotbecomes aMarkKind::Dot; atickbecomes aMarkKind::Hashwhose hold point is its own(x, y)anchor.textis a standalone label positioned in reticle coordinates. Following the schema’s recommended “option (b)”, each text is bound to the nearest hold-bearing mark withinDEFAULT_TEXT_BIND_MILand becomes that mark’slabel; 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° = UPThe 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§
- Ventum
Arc - One
circleelement 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. - Ventum
ArcPoint - A point on an imported arc, in the engine’s own mark coordinates.
- Ventum
Import Report - 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_typesuses for acirclethat carries a sweep. The format has noarcelement — an arc IS acirclewithstart/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 atextelement 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
VentumImportReportof everything the hold/decoration split left behind.