codec-re: crack Tran channel codec with high-amplitude May 11 bundle
User uploaded 3 high-amplitude events (PPV 6-7 in/s — shook the geophone
hard) to decode-re/5-11-26/. These cracked the Tran codec:
- Preamble bytes [3:5] and [5:7] = Tran[0] and Tran[1] as int16 BE
in 16-count units (LSB = 0.005 in/s). Confirmed across all 7
fixtures.
- First data block carries Tran deltas from sample 2 onward:
* 10 NN block: NN/2 bytes of payload, each byte = two 4-bit signed
nibble deltas (high nibble first)
* 20 NN block: NN int8 signed deltas
Verified 22+42+46 = 110 Tran samples across SP0/SS0/SV0 with 0 errors
against BW's ASCII export.
Why the earlier 96-combination brute force failed: the quiet 5-8
events all had T[0] = T[1] ≈ 0 so the preamble's per-channel encoding
was undetectable. Loud events made the encoding obvious.
What's solved:
- minimateplus.waveform_codec.decode_tran_initial: returns first
N Tran samples in 16-count units for any body.
- Walker length formula for in-data 30 NN blocks (NN*2 instead of NN*4).
- Walker now handles bodies that start with 20 NN (in addition to 10 NN).
What's still open:
- Tran past the first data block (multi-block channel switching).
- Vert / Long / MicL channel encodings.
- Walker correctness past offset ~427 in event-b.
Tests: 36 pass. decode_waveform_v2 still returns None — the full
multi-channel decoder is not wired up. decode_tran_initial is the
new verified entry point.
Files: minimateplus/waveform_codec.py, tests/test_waveform_codec.py
(adds 5-11-26 fixtures + decode_tran_initial tests), and
docs/instantel_protocol_reference.md §7.6.1 (Tran codec spec).
This commit is contained in:
@@ -14,11 +14,12 @@ import pytest
|
||||
|
||||
from minimateplus.waveform_codec import (
|
||||
WaveformBlock,
|
||||
decode_tran_initial,
|
||||
decode_waveform_v2,
|
||||
find_data_start,
|
||||
parse_segment_header,
|
||||
split_segments,
|
||||
walk_body,
|
||||
decode_waveform_v2,
|
||||
)
|
||||
|
||||
|
||||
@@ -238,7 +239,7 @@ def test_segment_counter_increments():
|
||||
@pytest.mark.parametrize("event_name", list(FIXTURES_INFO.keys()))
|
||||
def test_decode_waveform_v2_returns_none_until_verified(event_name):
|
||||
"""
|
||||
The verified per-byte sample decoder is not yet wired up.
|
||||
The full per-channel decoder is not yet wired up.
|
||||
|
||||
This test ensures decode_waveform_v2 returns ``None`` so callers know
|
||||
to keep using the legacy decoder. When a verified decoder lands,
|
||||
@@ -250,3 +251,64 @@ def test_decode_waveform_v2_returns_none_until_verified(event_name):
|
||||
pytest.skip(f"fixture missing: {path}")
|
||||
body = _bw_body(path)
|
||||
assert decode_waveform_v2(body) is None
|
||||
|
||||
|
||||
# ── decode_tran_initial: confirmed correct against ground truth ──────────────
|
||||
|
||||
# Bundled fixtures for the high-amplitude 5-11-26 events (PPV ~6-7 in/s).
|
||||
# These cracked the Tran codec — see waveform_codec module docstring.
|
||||
TRAN_INITIAL_FIXTURES = [
|
||||
# (path, expected first N Tran samples in 16-count units, # of samples to verify)
|
||||
(
|
||||
os.path.join(os.path.dirname(__file__), "fixtures", "5-11-26", "M529LL1A.SP0"),
|
||||
[4, 4, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 0, 1, 0],
|
||||
22,
|
||||
),
|
||||
(
|
||||
os.path.join(os.path.dirname(__file__), "fixtures", "5-11-26", "M529LL1A.SS0"),
|
||||
[-89, -89, -91, -91, -92, -93, -94, -94, -94, -94],
|
||||
42,
|
||||
),
|
||||
(
|
||||
os.path.join(os.path.dirname(__file__), "fixtures", "5-11-26", "M529LL1A.SV0"),
|
||||
[-745, -762, -771, -774, -779, -794, -808, -811, -811, -819],
|
||||
46,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path,expected,n_required", TRAN_INITIAL_FIXTURES)
|
||||
def test_decode_tran_initial_matches_ground_truth(path, expected, n_required):
|
||||
"""The Tran initial decoder produces values matching the BW ASCII export exactly."""
|
||||
if not os.path.exists(path):
|
||||
pytest.skip(f"fixture missing: {path}")
|
||||
with open(path, "rb") as f:
|
||||
raw = f.read()
|
||||
body = raw[43:-26]
|
||||
decoded = decode_tran_initial(body)
|
||||
assert decoded is not None
|
||||
# Check first len(expected) samples match exactly.
|
||||
for i in range(len(expected)):
|
||||
assert decoded[i] == expected[i], (
|
||||
f"sample {i}: decoded={decoded[i]} expected={expected[i]}"
|
||||
)
|
||||
# And we got at least n_required samples decoded.
|
||||
assert len(decoded) >= n_required, (
|
||||
f"decoded only {len(decoded)} samples, expected at least {n_required}"
|
||||
)
|
||||
|
||||
|
||||
def test_decode_tran_initial_handles_empty():
|
||||
assert decode_tran_initial(b"") is None
|
||||
assert decode_tran_initial(b"not a body") is None
|
||||
|
||||
|
||||
def test_decode_tran_initial_synthetic_body():
|
||||
"""A synthetic body with preamble + one 10 04 block decodes correctly."""
|
||||
# Magic + T[0]=10 + T[1]=20 in 16-count units.
|
||||
# Then 10 04 block with 4 nibbles: (+1, -1, +2, -2)
|
||||
# Encoded high-nibble first: 0x1F = (1, -1), 0x2E = (2, -2)
|
||||
body = b"\x00\x02\x00\x00\x0a\x00\x14" + b"\x10\x04" + b"\x1f\x2e"
|
||||
decoded = decode_tran_initial(body)
|
||||
# T[0]=10, T[1]=20, then deltas (+1, -1, +2, -2) from T[1]=20
|
||||
assert decoded == [10, 20, 21, 20, 22, 20]
|
||||
|
||||
Reference in New Issue
Block a user