fix(assets): the stream must reach the end of the frame, not just say stop

Chose the CURSOR over a pixel-coverage count because the corpus cannot
choose -- 25 of 25 of the R761 delivery's RLE8 BMPs paint every pixel,
25 of 25 reach the end of the frame, 0 of 25 use a delta -- and an
independent decoder can: a delta and an end-of-line escape state their
skip, so every decoder agrees on the index-0 pixels they pass over, while
a pixel count would refuse both constructions the format defines.

`_bmp_rle8_rows` now refuses (`asset_samples_invalid`) when the terminator
arrives with the cursor short of the last row. Pillow reads 5 of the 8
streams in the table and refuses the same 3, one of them short by a
single pixel.

Both docstrings the round was sent to correct are rewritten: the test no
longer claims every pixel is decoded (it is not -- a stated skip keeps
index 0), and `_bmp_rle8_rows` no longer frames the delta argument as
read off the corpus, which it never was.

R761 rebuilt: bundle `diff -r`-identical to the build before this commit,
50 assets (29 JPEG + 21 PNG), 19 of 19 conversions, SHY 71, u = 0, d = 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 19:16:32 +02:00
commit 561b609a53
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 44 additions and 16 deletions

View file

@ -576,14 +576,18 @@ def test_a_truncated_rle8_stream_is_refused_with_a_published_code() -> None:
a stream cut to 90 % carried with 13 923 pixels wrong, to 50 % with 95 890,
to 10 % with 166 525 -- no code, no row, a partly blank PNG under a name
that says it holds the source's pixels. The uncompressed path already
refuses the same shape. NO PIXEL MAY BE GUESSED: either every one of them
is decoded from the stream, or the picture is refused.
refuses the same shape. NO PIXEL IS GUESSED THAT THE STREAM DID NOT SKIP
ON PURPOSE: a stream must say it is finished, and
`test_a_stream_that_stops_before_the_frame_is_refused` adds the other half
-- when it says so, the cursor must have reached the end of the frame.
Pixels a delta or an end-of-line escape passed over keep index 0, which is
the format's own value and what an independent decoder produces.
THE RULE IS THE TERMINATOR, and it is read off the corpus rather than
chosen: over the 19 real RLE8 assets of the frozen R761 delivery, 19 of 19
end at an explicit end-of-bitmap escape, that escape is the stream's LAST
two bytes on 19 of 19, and `biSizeImage` equals the available bytes on
19 of 19. So a stream that runs out before its terminator is refused, and
THIS GUARD IS THE TERMINATOR, and it is read off the corpus rather than
chosen: over the 25 RLE8 BMPs the frozen R761 delivery ships (24 distinct;
the bundle carries 19), 25 of 25 end at an explicit end-of-bitmap escape, that escape is the stream's LAST
two bytes on 25 of 25, and `biSizeImage` equals the available bytes on
25 of 25. So a stream that runs out before its terminator is refused, and
so is one that is whole but never states it is finished -- the two are the
same bytes from a reader's side, and the measurement says no real writer
here produces the second.
@ -675,8 +679,8 @@ def test_a_stream_that_stops_before_the_frame_is_refused() -> None:
not -- including one that is short by a single pixel.
A PIXEL coverage count would be a different rule and a wrong one: it
refuses the delta the format defines, and 0 of the 25 real RLE8 sources in
the R761 delivery would be affected either way (25 of 25 paint every
refuses the delta the format defines, and 0 of the 25 RLE8 BMPs
the R761 delivery ships would be affected either way (25 of 25 paint every
pixel, 25 of 25 reach the end of the frame, 0 of 25 use a delta), so the
corpus cannot choose between them. The independent decoder can.
"""