The psk antenna-settle trim open-coded the save/restore pair: save_bufferS32
plus g_GridOffset on the way in, and restore_bufferS32's return assigned to
g_GraphTraceLen on the way out. graph.c now has save_graphbuffer and
restore_graphbuffer, which hold contents, length and grid offset together, so
call those and drop the local copy of the length contract.
No behaviour change. `lf t55xx detect -1` on the in-tree psk traces leaves the
graph at 20000 samples before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test_scan and t55xx_fallback_try drop 160 samples off the head of the capture
before demodulating psk, then restore it. restore_bufferS32 copies the samples
back and returns the length it put back, but no caller assigned that return, so
g_GraphTraceLen stayed at the trimmed length for the rest of detect.
That left the sample-space anchoring unable to engage. t55xx_demod_offset only
resolves when config.anchor_tracelen equals g_GraphTraceLen; detect recorded
11840 while every block read acquires 12000, so the guard never matched and a
dump fell back to config.offset - a bit index into a demod buffer that no longer
exists, which is the case its own comment warns about. Over six instrumented
dumps of a PSK1 RF/128 tag, one resolved and five did not; the one that resolved
read block 0 correctly on the first try.
A candidate found inside the trim was also measured 160 samples left of where a
block read measures, so those anchors were out of frame even when the length
happened to match. 160 samples is round(160 / clk) bits - 5 at RF/32, 1 at
RF/128 - and a rotation of that size is what read-back was showing.
Pair the trim with its undo so a third call site cannot pick up half of it, hold
a bias while the capture is trimmed so a recorded anchor still names an
untrimmed sample, and give the 160 one home.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A block read repeats one 32 bit word, so every offset yields a rotation and more
than one can pass the structural checks. A T5577 in direct/nrz carried both
00080040, the word on the tag, and 00080001, its ror19 - both master key 0,
reserved 0, RF/32, direct. test() answers with the first hit from a fixed floor,
so which one detect reported was scan order rather than evidence.
No structural check separates them: every bit of block 0 is a real field, and
the three the scan skips are otp, fast write and inverse data, all settable. The
tag can, though - maxblock has to account for what regular read mode cycles
through. So measure the broadcast period and keep the rotation that agrees,
reusing the constraint printConfiguration already applies to the psk2/psk3
pre-images. Costs one acquisition, and only when the reading is ambiguous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The emitted bit is the running phase, so a shift accepted before the tag answers
toggles curPhase once too often and the rest of the word comes back complemented.
pskFindFirstPhaseShift judged its first measured length, but waveStart starts as
wherever the caller began looking - that length is part of a wave, not a wave,
and any gap beating fc was taken as a shift. Baseline on the first peak instead.
pskRawDemod_ext separately trusted a shift under one bit period in; the clock is
known by then, so look again from a bit period in.
Both are needed - dropping either leaves one of the tags measured inverting on
most reads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
aggregate_bits sized each run of like waves in bits and then forced a zero to
one. That is right mid-stream, where a run rounding to nothing would drop a bit
the tag did send. On the leading run it is wrong: a single subcarrier wave is
about fchigh samples against a bit period of clk, so it rounds to no bits and
was made into one anyway - a bit the tag never sent, shifting every bit after it
and dragging startIdx back by most of a bit period.
Skip a leading run that rounds to nothing and let the next transition be first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The samples before the first level change were counted as bits, and where no
edge arrives inside ten clocks the long-run flush fired first and invented ten
of them out of the quiet lead-in. The count sat on a rounding boundary, so one
sample of jitter in that edge added or dropped a leading bit and rotated the
rest of the word - while startIdx, derived from the same count, held i % clk and
could name no sample.
A level change can only fall on a bit boundary, so start there and report it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One read cannot see the fault this suite is most useful for. A block that comes
back silently wrong on some reads and right on others scores the same as one
that is simply wrong, and the same as one that is fine. Read each block three
times and mark disagreement between them as `unstable`.
Where a block is wrong the same way every time, name it: `rol1`, `ror5`,
`inverted`, `inv+ror2`, `shr1`. The kind matters. A rotation says the word
boundary moved, an inversion says psk picked the opposite phase, and a shift
says a demodulation opened a bit early and padded with zero - three different
faults that want three different fixes, and all of them previously reported as
"block 3,4 bad".
Measured on a T5577 over the 21 PSK1 configurations, this turns 56 fields of
"bad" into 56 marked unstable, and on a build that has since fixed part of it,
into 24 rotations whose size tracks the bit rate - ror5 at RF/32, ror4 at RF/40,
ror2 at RF/100 - which is a fixed sample offset rather than noise, and says
where to look.
Block 1 is 00000000, which equals all of its own rotations, so a rotated read of
it cannot be told from a correct one. Noted in the header rather than changed,
since the payload is the suite's own choice.
Also corrects the header, which described block 2 as ffffffff where the script
writes aa5500ff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
restore_bufferS32() returns the length it put back and every graph-buffer
caller dropped it, so g_GraphTraceLen kept whatever value the code between
save and restore had left.
In try_detect_modulation() that is the 160 sample psk antenna-settle trim:
`data modulation` gave the buffer's samples back but left it 160 short, and
a following demod lost round(160 / clk) bits off the end. check_chiptype()
loses the same way through the acquisitions its detections make, on the
`lf search` path, and for g_DemodBuffer too. demodTI() loses convLen + 16
on its error path.
save_graphbuffer() / restore_graphbuffer() pair the length and grid offset
with the contents so no call site has to remember them. The visa2000 and
zx8211 sites do not change the length, so converting them is a no-op today
and keeps the next one from being a bug.
Regression test in tools/pm3_tests.sh: `data modulation` on a saved psk1
trace must leave all 20000 samples.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both psk3 entries run the same demodulation as the psk2 entry beside them and
differ only in the constant given to test(). test() accepts a word only when
that word's own modulation field matches the constant, so the psk3 entry needs
a recovered word whose field reads 3.
Field 3 is 00011, so it needs two adjacent 1's. What this demodulation recovers
is the data's rising edges, and a rising edge needs a 0 before the 1, so no two
of them are ever adjacent. The words it produces can never carry field 3, and so
the psk3 entries can never match the case they were written for.
What they can do, however, is match on a demodulation error, and then detect
names psk3 and a block 0 word the tag does not hold.
psk3 is still reached, by ruling psk2 out from the broadcast period rather
than by demodulating for it - see t55xx_psk3_resolve().
Tags that only these branches matched now read as psk2, or as undetected
where no offset yields a plausible psk2 word, on the basis that a wrong
answer is worse than none if nothing about it tells you it is wrong.
Edited by a human.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The client demodulates psk2 and psk3 the same way, and against a psk3 tag
that demodulation keeps only the leading bit of every run of ones. A psk3
config word therefore always reads back as its psk2 neighbour, one bit out,
and detect structurally cannot tell the two apart from the waveform alone.
It said psk2 anyway. Worse, the data-block probe meant to settle it
confidently answered psk3 even for a freshly wiped psk2 tag, where a page
of zeroes has no adjacent ones under either modulation.
Report what is actually known:
- print "PSK2 or PSK3 ( ambiguous )" when the read fits both
- list the words block 0 could be, rather than printing one and relegating
the rest to a note
- weight the probe's evidence, so empty blocks settle nothing
- narrow that list with things the tag cannot hide - the subcarrier it
transmits on, and how many blocks it broadcasts, which constrains MAXBLK and
also rules out the sequence terminator
Where that leaves one word, block 0 reports it. Where it does not, detect
says so rather than choosing.
Edited by a human.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>