a cartridge with no chr rom keeps its patterns in ram and builds them as it runs, so there is nothing in the file to show. those are refused rather than drawn as an empty square.
detect only: there is no jpeg decoder, so this answers "jpeg yuv420" and then Decode refuses. that refusal naming the format is the point, and it is what let describeForeignMagic go away.
palette (colour type 3) and gray+alpha (type 4) are not recognised at all, because neither has a Color to name it. before this refactor wuffs expanded both to rgba and they simply worked, so this is a regression until Color grows a PALETTE8 and a GRAY_ALPHA – and palette drags a colour table along with it.
a terminal that cannot do 24 bit colour renders this as noise. detecting that means reading COLORTERM, and falling back means quantising to the 256 colour cube.
the mirror of the rgba8888 to rgb888 pack, and just as hot, but left scalar: the 3 to 4 unpack needs a load that straddles the shuffle window, so it is a real port rather than the same mask backwards.
everything funnels through rgba8888, so ktx2(etc1) -> ktx2 decompresses and recompresses for nothing. keeping the payload needs a typed path, which is exactly what Codecs::Decode<Format, Color> already offers; only this runtime bridge flattens it.
the output is pinned to rgba8888 because a pipeline is typed and the input is not. a jpeg is natively yuv, so asking for y4m through here would go through rgb for nothing; Codecs::Decode<Format::JPEG, Color::YUV420> is the way around it.
everything travels as rgba8888 here, so a pipeline with no transforms still decompresses a gpu payload and recompresses it. the typed pipe in haio_pipe.hpp keeps the colour; only this runtime path flattens it.