# `Sidereon.GNSS.RTK.MovingBaseline`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/gnss/rtk/moving_baseline.ex#L1)

Moving-baseline RTK: both receivers move each epoch (RTKLIB "moving-base").

Standard relative RTK positions a moving rover against a base whose ECEF
coordinates are fixed for the whole arc. The double-difference cancellation, the
iterated float baseline least squares, and the LAMBDA integer fix are all
unchanged when the base also moves, so the only difference is that the base ECEF
position is supplied **per epoch** (typically the base receiver's own navigation
fix). This module is a thin primitive over
`sidereon_core::rtk_filter::moving_baseline`: it marshals already-prepared
double-difference epochs into the core solvers and decodes the per-epoch
baseline, its length, the integer-fix verdict, and float-solve geometry
diagnostics.

This is a traceable primitive, not the high-level `Sidereon.GNSS.RTK` API: the
caller supplies the reference/non-reference satellite measurements and the
ambiguity set directly, so no reference selection or cycle-slip preparation
happens here.

## Epoch shape

    %{
      base_position_m: {x, y, z},
      references: [sat_meas],          # one reference per constellation
      nonref: [sat_meas],
      velocity_mps: {x, y, z} | nil,   # optional
      dt_s: 0.0,                        # optional
      ambiguity_ids: ["G02", ...],
      ambiguity_satellites: %{"G02" => "G02", ...},
      wavelengths_m: %{"G02" => 0.19, ...},
      offsets_m: %{"G02" => 0.0, ...},   # optional, default 0 per id
      float_only_systems: []             # optional
    }

where each `sat_meas` is

    %{
      sat: "G01", sd_ambiguity_id: "G01",
      base_code_m: _, base_phase_m: _, rover_code_m: _, rover_phase_m: _,
      base_tx_pos: {x, y, z}, rover_tx_pos: {x, y, z}, pos: {x, y, z}
    }

## Options

    %{
      model: %{code_sigma_m: 0.3, phase_sigma_m: 0.003,
               stochastic_model: :simple, elevation_weighting: false, sagnac: false},
      float: %{position_tol_m: 1.0e-4, ambiguity_tol_m: 1.0e-4, max_iterations: 10},
      fixed: %{position_tol_m: 1.0e-4, ambiguity_tol_m: 1.0e-4, max_iterations: 10,
               ratio_threshold: 3.0, partial_ambiguity_resolution: false,
               partial_min_ambiguities: 4},
      initial_baseline_m: {0.0, 0.0, 0.0},
      warm_start: true
    }

# `solution`

```elixir
@type solution() :: %{
  base_position_m: vec3(),
  baseline_m: vec3(),
  baseline_length_m: float(),
  status: :fixed | :float,
  float: map(),
  fixed: map()
}
```

# `vec3`

```elixir
@type vec3() :: {number(), number(), number()}
```

# `solve_epochs`

```elixir
@spec solve_epochs([map()], map()) :: {:ok, [solution()]} | {:error, term()}
```

Solve a sequence of moving-baseline epochs, each against its own base position.

With `warm_start: true` each solved baseline seeds the next epoch's float
linearization point. Returns `{:ok, [solution]}` or
`{:error, {epoch_index, reason}}` for the first failing epoch.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
