Fix complex packing bit-padding bug corrupting decoded values

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.
This commit is contained in:
Graham McIntire 2026-03-30 09:54:10 -05:00
parent 4ef755f0cf
commit fdc2a31c77
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 21 additions and 2 deletions

View file

@ -132,8 +132,8 @@ defmodule Microwaveprop.Weather.Grib2.ComplexPacking do
<<chunk::binary-size(total_bytes), rest::binary>> = data
vals = consume_bits_simple(<<chunk::binary>>, 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

View file

@ -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 ---