///|
/// One observation at an explicit UTC instant; no clock or I/O is consulted.
pub(all) struct Observation {
  time : Instant
  sun : Position
  moon : MoonPosition
  illumination : Illumination
} derive(Eq, Debug)

///|
/// Fixed-step, bounded sampling. All timestamps are validated before allocation.
/// Step must be a positive whole number of milliseconds; count is 1..4096.
pub fn sample_observations(
  start : Instant,
  step_ms : Double,
  count : Int,
  site : Observer,
) -> Result[Array[Observation], InputError] {
  if count < 1 || count > 4096 {
    return Err(InvalidSampleCount)
  }
  if !(step_ms > 0.0 && step_ms <= 7920806400000.0) ||
    @math.trunc(step_ms) != step_ms {
    return Err(InvalidSamplingStep)
  }
  let last = start.ms + step_ms * (count - 1).to_double()
  match instant(last) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let samples : Array[Observation] = []
  for i = 0; i < count; i = i + 1 {
    let time = { ms: start.ms + step_ms * i.to_double(), }
    samples.push({
      time,
      sun: sun_position(time, site),
      moon: moon_position(time, site),
      illumination: moon_illumination(time),
    })
  }
  Ok(samples)
}