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:
Claude
2026-05-11 18:30:56 +00:00
committed by serversdown
parent d3f77d1d96
commit 6ac126e05c
14 changed files with 10113 additions and 50 deletions
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
+64 -2
View File
@@ -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]