From fdc2a31c77cf2c0a428339bdb0766349ea5c15f7 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 30 Mar 2026 09:54:10 -0500 Subject: [PATCH] Fix complex packing bit-padding bug corrupting decoded values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extract_n_values_array used Enum.take(count) on a reversed list before reversing it, which included padding values from byte alignment and dropped actual values. When group count * bits wasn't a multiple of 8, the extra padding bits produced a phantom value that shifted the entire array by one position. This caused cascading errors in spatial differencing — values started correct but diverged exponentially (DPT decoded as 38 billion K instead of 275 K). Fix: reverse the list first, then take count, so padding values at the end are discarded instead of actual values at the start. --- .../weather/grib2/complex_packing.ex | 4 ++-- .../weather/grib2/extractor_test.exs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/microwaveprop/weather/grib2/complex_packing.ex b/lib/microwaveprop/weather/grib2/complex_packing.ex index abc5d909..978aadfc 100644 --- a/lib/microwaveprop/weather/grib2/complex_packing.ex +++ b/lib/microwaveprop/weather/grib2/complex_packing.ex @@ -132,8 +132,8 @@ defmodule Microwaveprop.Weather.Grib2.ComplexPacking do <> = data vals = consume_bits_simple(<>, nbits, []) - # consume_bits_simple returns reversed, take only count values - arr = vals |> Enum.take(count) |> Enum.reverse() |> :array.from_list() + # consume_bits_simple prepends (reversed); reverse first, then take count to discard padding + arr = vals |> Enum.reverse() |> Enum.take(count) |> :array.from_list() {arr, rest} end diff --git a/test/microwaveprop/weather/grib2/extractor_test.exs b/test/microwaveprop/weather/grib2/extractor_test.exs index 02ad9f97..6acf016e 100644 --- a/test/microwaveprop/weather/grib2/extractor_test.exs +++ b/test/microwaveprop/weather/grib2/extractor_test.exs @@ -118,6 +118,25 @@ defmodule Microwaveprop.Weather.Grib2.ExtractorTest do assert map_size(result) == 2 end end + + @tag :fixture + @tag timeout: 300_000 + test "DPT values are meteorologically reasonable (not corrupted by bit padding)" do + if File.exists?(@multi_fixture_path) do + fixture = File.read!(@multi_fixture_path) + + assert {:ok, result} = Extractor.extract_points(fixture, 32.78, -96.8) + + tmp_k = result["TMP:2 m above ground"] + dpt_k = result["DPT:2 m above ground"] + + assert tmp_k > 200.0 and tmp_k < 340.0, + "TMP #{tmp_k}K unreasonable" + + assert dpt_k > 200.0 and dpt_k < 340.0, + "DPT #{dpt_k}K unreasonable — likely bit-padding corruption" + end + end end # --- Synthetic GRIB2 builder ---