docs(series4): RESOLVED -- the 0x0C float is the PER-SAMPLE peak vector sum

Exact to 0.000% on all four bench waveforms, against a PVS recomputed from the
decoded samples.  The offset (Tran label - 12) is now established rather than
inferred: four independent confirmations plus a fifth on the other firmware
line.

RETRACTION.  The previous entry observed the float running 2-5% above
max(T,V,L) and concluded it was NOT the peak vector sum, "because computed PVS
runs higher than both".  That computed PVS was sqrt(T^2+V^2+L^2) from the three
REPORTED CHANNEL PEAKS, which is an upper bound on the real quantity rather
than the quantity -- the channel maxima do not occur at the same instant, so
combining them overstates the true peak.  Every row of the old table is in fact
consistent with max(T,V,L) <= stored <= sqrt(sum of squares).

I also got this wrong in the other direction today and want it recorded.  On
UM20147's event the stored float matched sqrt(sum of peak squares) to 0.048%,
and I called it the vector sum on that basis.  It is not -- that event's peaks
merely happen to be nearly coincident in time.  The six CB events separated the
two formulas immediately, with errors to -9.5%.  ONE SAMPLE AGREEING WITH A
FORMULA IS NOT EVIDENCE WHEN A SECOND FORMULA FITS IT EQUALLY WELL.

Why it is worth having: it is a FREE SELF-CHECK ON THE DECODER.  The device
computed this number from the same samples we decode, independently of our
codec, so a mismatch means the decode is wrong.  Given this codebase's history
of silent channel truncation -- walker bugs that shorten a channel and raise
nothing -- a per-event invariant costing one float comparison is cheap
insurance, and worth wiring into the download path.

Also added the full 0x0C field map from real UM20147 bytes: timestamp, the
record-type byte, sensor-location label, setup name, serial, the PVS float and
the four channel labels with their offsets.

Noted in passing: the setup name appears as "test2" in 0x0C, "test2.MMB" in
0x41 and "test2.mmb" in 0x40 -- three commands, three spellings of one file, in
one session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
This commit is contained in:
2026-09-30 14:27:31 -04:00
co-authored by Claude Opus 5
parent 7d1b033a05
commit a5237bb3b4
+74 -3
View File
@@ -1811,7 +1811,55 @@ given it, and `/db/import/idf_file` needs no change at all.
must carry it out of the chain walk. Losing it means losing the ability to
name the file correctly.
### ⚠ Unresolved: the `0x0C` peak float
### ✅ RESOLVED 2026-09-30: the `0x0C` float is the PER-SAMPLE peak vector sum
**`0x0C` content[`Tran` label − 12], float32 BE, = the peak vector sum computed
sample by sample.** Exact to 0.000% on all four bench waveforms, against a PVS
recomputed from the decoded samples:
| key | `0x0C` float | per-sample PVS | err | `sqrt(Σpeak²)` | `max(T,V,L)` |
|---|---|---|---|---|---|
| `…82` | 1.37198 | 1.37198 | **+0.000%** | 1.41746 | 1.37063 |
| `…83` | 2.35414 | 2.35414 | **+0.000%** | 2.37824 | 2.22274 |
| `…84` | 3.51515 | 3.51515 | **−0.000%** | 3.78731 | 3.44194 |
| `…85` | 0.42267 | 0.42267 | **−0.000%** | 0.46678 | 0.41985 |
> #### ⚠ Retraction — why this looked unresolved
>
> The previous entry (below, kept) observed the float running 2–5% above
> `max(T,V,L)` and concluded it was **not** the peak vector sum, because
> "computed PVS runs *higher* than both".
>
> That computed PVS was `sqrt(T² + V² + L²)` **from the three reported channel
> peaks** — and that is an *upper bound* on the real thing, not the real thing.
> The channel maxima do not occur at the same instant, so combining them
> overstates the true peak. Every measurement in the old table is consistent
> with the correct answer: `max(T,V,L) ≤ stored ≤ sqrt(Σpeak²)`, in all five rows.
>
> A near-miss worth recording: on UM20147's event the stored float matched
> `sqrt(Σpeak²)` to **0.048%**, which briefly looked like proof it *was* that
> quantity. It is not — the peaks on that event simply happen to be nearly
> coincident in time. **One sample agreeing with a formula is not evidence when
> a second formula fits it equally well;** the six CB events separated them
> immediately (errors to −9.5%).
**The offset is now established, not inferred** — four independent confirmations
at 0.000%, plus a fifth on the other firmware line.
**Why this is worth having: it is a free self-check on the decoder.** The device
computed this number from the same samples we decode, independently of our
codec. If a decoded event's per-sample PVS does not match the stored float, the
decode is wrong. Given this codebase's history of *silent* channel truncation —
walker bugs that shorten a channel and raise nothing — a per-event invariant
that costs one float comparison is cheap insurance. Worth wiring into the
download path.
⚠ It remains true that `max(T,V,L)` and `sqrt(Σpeak²)` are **not**
interchangeable with it, so a consumer wanting the vector sum must use this
field or decode the samples — it cannot be reconstructed from the three
per-channel peaks.
#### Superseded: the original "unresolved" entry
The float32 extracted from `0x0C` runs 2–5% above `max(Tran, Vert, Long)` from
the decoded samples:
@@ -1824,15 +1872,38 @@ the decoded samples:
| `…84` | 3.5152 | 3.4419 |
| `…85` | 0.4227 | 0.4198 |
It is not peak vector sum either (computed PVS runs *higher* than both). The
~~It is not peak vector sum either (computed PVS runs *higher* than both). The
field may not be the peak at all — its offset was inferred from a byte marker,
not established. **Do not rely on it** until it is pinned properly.
not established. **Do not rely on it** until it is pinned properly.~~
Worth noting the histogram (`…81`) and the loudest waveform (`…84`) report
*identical* peaks to four decimals, in both measures. That is self-consistent:
the histogram's single 1-minute interval spans the whole thumping session, so
its maximum should equal the loudest event in it.
### `0x0C` field map, from UM20147 (11.0BD, 2026-09-30)
Real bytes, 221 B response → 210 B content declared at `data[0] = 0xd2`:
```
content[ 0] day 0x1e = 30
content[ 1] month 0x09
content[ 2:4] year BE 0x07ea = 2026
content[ 4] ⚠ 0xb3 = 179 — unidentified, same slot as 0x1C's content[6]
content[ 5:8] h:m:s 13:27:33
content[11] RECORD TYPE 0x08 histogram / 0x07 waveform
content[12] "Location\0" sensor-location label
content[34] "test2\0" the setup file name, without its extension
content[76] "UM20147\0" serial
content[86] float32 BE the per-sample PEAK VECTOR SUM (= Tran − 12)
content[98] "Tran\0\0" + float32 BE at +6
content[112] "Vert" … content[126] "Long" … content[140] "Mic" …
```
⚠ The setup name here is `test2` — no extension — where `0x41` reported
`test2.MMB` and `0x40` reported `test2.mmb` in the same session. Three commands,
three spellings of one file. See *Setup-name case*.
## Event download, per-event delete, and ACH config (2026-09-25)
Three captures with operator-supplied ground truth, including Thor screenshots of