Say which check failed, and read a byte from either end
Their diagnosis came back and the radio end is faultless. Every burst is textbook: four sync pulses at 612 microseconds, then 216 and 403 microsecond data pulses with gaps that complete each bit period, six of them a second, sliced cleanly at the right lengths. The pulses this had been failing to read were being recovered perfectly all along and the fault is in the arithmetic after them. Two things follow from that, and neither is a guess about their sensors. The first is that "nothing framed" is not a diagnosis, it is the absence of one, and it was all this could say. A message whose checksum holds and whose parity fails is a different fault from one where neither holds, and both differ again from a burst that never lined up on a byte boundary -- three faults, three fixes, one message. So the diagnosis now reports the closest framing it found, which of its checks held, and the bytes themselves in hexadecimal, which is what any question about a format is actually about and saves asking somebody to read numbers off a screen. The second is that this still assumed something it had no business assuming: which end of a byte goes down the air first. Both orders are tried now and the checksums say which, like everything else here. That one has a fingerprint worth knowing and worth having said in the manual: reversing the bits of a byte does not change how many of them are set, so odd parity survives it and a checksum does not -- a message read from the wrong end shows every parity holding and every sum failing, on every copy, which is a signature rather than a coincidence. Also fixed, and found by building their burst from the timings they sent: a real transmitter closes the last bit with a terminating pulse, so a message of fifty-six bits arrives as sixty-one pulses rather than sixty. Nothing here had ever seen one, the simulator not sending it, and every test in this file was therefore one pulse short of what comes off the air. It happens to be handled correctly, which is luck rather than design, so it is now what the tests are written against. Full suite 2339 passed. Sixty seconds of receiver noise still yields nothing, and four hundred seconds of the invented garden still yields no sensor that is not there, both rechecked after adding the second byte order. Built as 2026-09-07_04. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
706c632f47
commit
dfea5cb2f2
7 changed files with 275 additions and 36 deletions
|
|
@ -941,3 +941,83 @@ def test_the_gate_still_keeps_noise_out_when_there_is_no_signal():
|
|||
peak = float(np.percentile(smooth, 99.99))
|
||||
over = (smooth > a._noise_gate(smooth, quiet, peak)).mean()
|
||||
assert over < 0.01, f"{over:.1%} of pure noise cleared the gate"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Which end of a byte goes first
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def reversed_bytes(bits):
|
||||
"""The same message with each byte sent the other way round."""
|
||||
return "".join(bits[i:i + 8][::-1] for i in range(0, len(bits), 8))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order", ["as written", "least significant first"])
|
||||
def test_a_message_is_read_from_whichever_end_of_a_byte_it_arrives(order):
|
||||
frame = a.tower_frame(0x1A2B, 21.5, 48, "A")
|
||||
sent = frame if order == "as written" else reversed_bytes(frame)
|
||||
iq = keyed_at(sent, pwm_pairs(sent))
|
||||
got = a.readings_from(iq, 250_000.0)
|
||||
assert [r.sensor for r in got] == ["1A2B"], f"{order}: heard {got}"
|
||||
|
||||
|
||||
def test_reading_a_byte_backwards_keeps_its_parity_and_breaks_its_sum():
|
||||
"""The signature that names this fault, which is why it is worth having.
|
||||
|
||||
Reversing the bits of a byte does not change how many are set, so odd
|
||||
parity survives it; a checksum does not. A message read from the wrong
|
||||
end therefore shows every parity holding and the sum failing, on every
|
||||
copy -- which is a fingerprint rather than a guess.
|
||||
"""
|
||||
frame = a.tower_frame(0x1A2B, 21.5, 48, "A")
|
||||
backwards = reversed_bytes(frame)
|
||||
plain = [int(backwards[i:i + 8], 2) for i in range(0, len(backwards), 8)]
|
||||
assert all(a.parity8(byte) == 1 for byte in plain[2:-1])
|
||||
assert (sum(plain[:-1]) & 0xFF) != plain[-1]
|
||||
|
||||
|
||||
def test_only_one_of_the_two_orders_is_ever_reported():
|
||||
"""Otherwise one message would corroborate itself and the rule that the
|
||||
thinly checked models depend on would protect nothing."""
|
||||
frame = a.frame_609(0x5C, 4.2, 80)
|
||||
found = a.candidates("0" + frame)
|
||||
assert len(found) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Saying which check failed, not that one did
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_the_closest_framing_says_which_check_held_and_which_did_not():
|
||||
frame = a.tower_frame(0x1A2B, 21.5, 48, "A")
|
||||
good = a.near_misses("1111" + frame)[0]
|
||||
assert good.read and good.score == len(good.checks)
|
||||
assert dict(good.checks)["sum"] and dict(good.checks)["parity"]
|
||||
assert good.hex.startswith("DA 2B")
|
||||
assert "7 bytes at bit 4" in good.describe()
|
||||
|
||||
|
||||
def test_a_broken_checksum_is_reported_as_a_broken_checksum():
|
||||
frame = list(a.tower_frame(0x1A2B, 21.5, 48, "A"))
|
||||
frame[-1] = "1" if frame[-1] == "0" else "0" # the checksum byte
|
||||
miss = a.near_misses("1111" + "".join(frame))[0]
|
||||
assert dict(miss.checks)["parity"] is True
|
||||
assert dict(miss.checks)["sum"] is False
|
||||
assert not miss.read
|
||||
|
||||
|
||||
def test_a_burst_of_nonsense_reports_the_checks_all_failing():
|
||||
rng = np.random.default_rng(3)
|
||||
bits = "".join(rng.integers(0, 2, 60).astype(str))
|
||||
misses = a.near_misses(bits)
|
||||
assert misses and all(not m.read for m in misses)
|
||||
|
||||
|
||||
def test_the_hex_of_what_was_read_is_reported_so_it_can_be_worked_out_by_hand():
|
||||
"""Because the alternative is asking somebody to send a photograph of a
|
||||
display, and because the bytes are what any question about a format is
|
||||
actually about."""
|
||||
frame = a.tower_frame(0x0C41, 3.2, 91, "B")
|
||||
miss = a.near_misses("1111" + frame)[0]
|
||||
assert len(miss.hex.split()) == 7
|
||||
assert all(len(byte) == 2 for byte in miss.hex.split())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue