codec-re: channel rotation CONFIRMED — full multi-channel decoder works
The segment-channel scoring analyzer (from scratch/next_experiment_skeleton.py) ran and immediately confirmed the rotation hypothesis: SP0 seg 0: best fit Vert 508/508 ✓ SP0 seg 1: best fit Long 508/508 ✓ SP0 seg 3: best fit Tran 508/508 ✓ (Tran continuation) SP0 seg 5: best fit Long 508/508 ✓ SP0 seg 9: best fit Long 508/508 ✓ V70 seg 0: best fit Vert 508/508 ✓ V70 seg 1: best fit Long 508/508 ✓ Channels rotate Tran → Vert → Long → MicL per 40 02 segment header. Also discovered the segment header has DOUBLE duty: bytes [14:18] anchor the NEW segment's channel (2 samples as int16 BE in 16-count units), AND bytes [0:4] extend the PREVIOUS channel by 2 more samples (2 deltas as int16 BE). This is the same "2 anchors + delta stream" structure as the body preamble for Tran. decode_waveform_v2 now returns full per-channel sample dicts. Byte-exact verified ranges: V70: Tran 512, Vert 512, Long 512 (all first segments) JQ0: Tran 512, Vert 258 SP0: Long 1536 (all 3 L segments) Still open: the 30 NN block format (high-amplitude packed deltas) — appears mid-segment when single-byte deltas can't carry the magnitude. 6 new tests bring the count to 46. All passing.
This commit is contained in:
@@ -263,29 +263,62 @@ def score_against_truth(
|
||||
def score_segment_against_all_channels(
|
||||
event: FixtureEvent,
|
||||
segment_index: int,
|
||||
) -> List[Tuple[str, str, int, int, int]]:
|
||||
"""For segment *segment_index* of *event*, try decoding it as each channel
|
||||
with each candidate anchor source.
|
||||
) -> List[Tuple[str, int, int, int]]:
|
||||
"""For segment *segment_index* of *event*, find the best (channel, start_sample)
|
||||
fit.
|
||||
|
||||
Returns rows of (channel_name, anchor_source_label, anchor_value, n_matches, n_compared)
|
||||
sorted by match count descending.
|
||||
For each candidate channel C and each candidate starting truth-sample index s,
|
||||
we pick the anchor that makes the FIRST decoded value match truth[C][s], then
|
||||
score the remaining decoded values against truth[C][s+1 : s+N].
|
||||
|
||||
Anchor source candidates to try:
|
||||
- "header[0:2]" int16 BE from segment header bytes [0:2]
|
||||
- "header[2:4]" int16 BE from segment header bytes [2:4]
|
||||
- "header[4:6]" int16 BE from segment header bytes [4:6]
|
||||
- "header[14:16]" int16 BE from segment header bytes [14:16]
|
||||
- "header[16:18]" int16 BE from segment header bytes [16:18]
|
||||
- "channel[0]" truth[channel][0] (= "this segment starts at sample 0 of this channel")
|
||||
- "channel[prev]" truth[channel][segment_sample_starts[segment_index] - 1]
|
||||
(= "this segment continues from sample N-1 of this channel")
|
||||
|
||||
For each combination of (channel, anchor source, "starts at sample X of channel"),
|
||||
decode the segment and score against truth.
|
||||
|
||||
TODO: implement this — it's the heart of the experiment.
|
||||
Returns rows of (channel_name, start_sample, n_matches, n_compared)
|
||||
sorted by match-count descending.
|
||||
"""
|
||||
raise NotImplementedError("This is the next experiment to run.")
|
||||
# Block range of this segment: from the segment header (inclusive) up to
|
||||
# the next segment header (exclusive), or end-of-blocks.
|
||||
seg_header_idx = event.segment_starts[segment_index]
|
||||
next_header_idx = (
|
||||
event.segment_starts[segment_index + 1]
|
||||
if segment_index + 1 < len(event.segment_starts)
|
||||
else len(event.blocks)
|
||||
)
|
||||
|
||||
# Decode the segment's data blocks (skip the segment-header block itself).
|
||||
# Use anchor=0 — we'll re-anchor when scoring against each channel.
|
||||
deltas_trajectory = decode_segment_as_channel(
|
||||
event.blocks, seg_header_idx + 1, next_header_idx, anchor=0
|
||||
)
|
||||
if not deltas_trajectory:
|
||||
return []
|
||||
|
||||
n = len(deltas_trajectory)
|
||||
results = []
|
||||
|
||||
for ch in ("Tran", "Vert", "Long"):
|
||||
truth = event.truth.get(ch)
|
||||
if not truth or len(truth) < n + 1:
|
||||
continue
|
||||
# For each candidate starting sample s in truth, check if applying
|
||||
# the deltas starting from truth[s] reproduces truth[s+1:s+n+1].
|
||||
best = (0, -1)
|
||||
for s in range(len(truth) - n):
|
||||
anchor = truth[s]
|
||||
offset = anchor - deltas_trajectory[0] + truth[s + 1] - anchor
|
||||
# Recompute: trajectory[i] = anchor + cumulative_delta_through_i
|
||||
# but we already have deltas_trajectory computed from anchor=0,
|
||||
# so trajectory_relative[i] = anchor + deltas_trajectory[i].
|
||||
matches = 0
|
||||
for i in range(n):
|
||||
if truth[s + i + 1] == anchor + deltas_trajectory[i]:
|
||||
matches += 1
|
||||
# Note: we could break early on first mismatch for "matches start",
|
||||
# but counting total matches gives a more robust score.
|
||||
if matches > best[0]:
|
||||
best = (matches, s)
|
||||
results.append((ch, best[1], best[0], n))
|
||||
|
||||
results.sort(key=lambda r: -r[2])
|
||||
return results
|
||||
|
||||
|
||||
# ── Driver ──────────────────────────────────────────────────────────────────
|
||||
@@ -310,11 +343,17 @@ def main():
|
||||
for si, sample_start in enumerate(event.segment_sample_starts):
|
||||
print(f" seg {si}: sample {sample_start}")
|
||||
|
||||
# When score_segment_against_all_channels is implemented:
|
||||
# for si in range(len(event.segment_starts)):
|
||||
# results = score_segment_against_all_channels(event, si)
|
||||
# best = results[0]
|
||||
# print(f" seg {si}: best fit = {best}")
|
||||
for si in range(len(event.segment_starts)):
|
||||
results = score_segment_against_all_channels(event, si)
|
||||
if not results:
|
||||
print(f" seg {si}: (no scorable data)")
|
||||
continue
|
||||
tag = "✓" if results[0][2] / max(results[0][3], 1) > 0.9 else " "
|
||||
top = results[0]
|
||||
print(f" seg {si}: best fit {tag} = {top[0]:<5} "
|
||||
f"starting at sample {top[1]:>5}, {top[2]:>4}/{top[3]:<4} match"
|
||||
+ (f" (next: {results[1][0]} @{results[1][1]} {results[1][2]}/{results[1][3]})"
|
||||
if len(results) > 1 else ""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user