pub fn evaluate(
req: &SolveRequestV1,
ranges_m: &[f64],
) -> Result<Vec<Observation>, KernelError>Expand description
Solve req once and read a checked observation at each of ranges_m, in the order given.
§Which TrajectoryResult path this uses, and why
This task’s brief assumed a SolveSuccessV1::trajectory_result_for_observation() accessor;
no such method exists. crate::solve_json::SolveSuccessV1 does not expose a raw
crate::TrajectoryResult at all – only the wire-shaped, already-downsampled
samples: Vec<TrajectorySampleV1> (the brief’s option (a), ruled out for that reason).
Instead this follows the brief’s fallback (option (b)), sharing solve_v1’s own building
blocks rather than re-implementing them: solve_v1::prepare_request resolves req
exactly as solve_v1 does, and solve_v1::build_zeroed_solver builds and zeroes a
TrajectorySolver exactly as solve_v1 does (both widened from module-private to
pub(crate) for this task). This function then calls TrajectorySolver::solve itself and
reads observations straight off the result: one solve per evaluate call, not two, and not
solve_v1 internally.
(Revision note: an earlier version of this function hand-duplicated solve_v1’s
zero-handling match here instead of sharing build_zeroed_solver, kept honest only by an
outboard cross-check test. Review judged that duplication risk too costly to keep – see
“Drop reference plane” below for a related place this function deliberately does NOT mirror
solve_v1 – so it was replaced with this structural share.)
§Drop reference plane
evaluate always reports drop_m perpendicular to the line of sight, regardless of the
request’s shot.drops_reference. solve_v1 applies the MBA-1403 target-plane transform
(drop_m /= shooting_angle_rad.cos()) as a wire-only rescaling at its own boundary
(src/solve_v1.rs, applied once to each sample after TrajectoryResult::sample_observations
runs); evaluate deliberately does not reproduce it, so the kernel always speaks one
geometry no matter which reference plane the original request asked for on output. This is a
decision, not an oversight – see evaluate_ignores_drops_reference_and_always_reports_los_drop
in this module’s tests, which pins it down. A caller that wants the target-plane number can
still derive it from drop_m / shooting_angle_rad.cos(), using the SAME shooting_angle_rad
this request resolved to (ResolvedShotV1::shooting_angle_rad) – and should do that
explicitly rather than have it baked silently into drop_m here, because a later task
differentiates observations with respect to taxonomy axes including ShootingAngle
(InputAxis::ShootingAngle, taxonomy.rs): folding the target-plane transform into drop_m
in this function would silently add an extra d/dtheta[sec(theta)] term to that derivative,
and only for target-referenced requests.
§Errors
KernelError::Solveif resolving or solvingreqfails – the same failure modessolve_v1itself reports (invalid or conflicting fields, a zero search that does not converge, a non-finite effective muzzle angle), uniformly converted fromSolveErrorEnvelopeV1regardless of which stage (prepare_request,build_zeroed_solver, orTrajectorySolver::solve) produced it.KernelError::Observationif any requested range cannot be read off the resulting trajectory – most notably a range outside[0, actual_range]– viacrate::TrajectoryResult::observation_at_range_checked, used specifically because it ERRORS on an out-of-range query instead of clamping to the nearest sample (the pattern the CLI card commands use,src/main.rs:17734).