Follow-up to the 256×256-tiling reversal — the residual boundary seams on
≥2×2-tile textures are gone. Decoding the file header revealed the exact
layout (no oracle capture needed):
0x00 44 base header
0x1c 4 tile count = ceil(w/256) * ceil(h/256)
0x2c tiles*4 BE-u32 absolute offset of each row-major 256×256 tile
<off> 16 per-tile header, then tile_w*tile_h*4 A8R8G8B8 pixels
The old seams came from ignoring the offset table and the 16-byte per-tile
header (contiguous-packing drifted 16 bytes per tile). Small textures decoded
before only by luck: one tile puts pixels at 44+4+16 = 64, the old type-1
"header size". Now the 8AX title background, the prselect_win1 window frame,
and preff04 all decode pixel-perfect (verified against the title screen).
- t8ad.rs: parse() walks the offset table; dropped detile_256 + the
header-size-by-type table. Non-tilecount entries (field != ceil*ceil) return
None (likely DXT/other, deferred). New multi-tile round-trip test.
- sylpheed-cli: pak textures decodes via parse(); XDUMPHDR=1 dumps the base
header + offset table for RE. Removed the now-obsolete XTILE/XSCORE/XDESTRIP
experiment knobs and the reconstruct_tiles/TV-sweep helpers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified against the running game (freeze-fixed Canary title screen) that
T8aD colours are correct (no R↔B swap) and that wide textures were garbled
by an unreversed tile layout, not a colour bug.
Reversed the layout: large T8aD are stored as 256×256 raster tiles in
row-major order, each tile raster internally, edge tiles clipped to the
image (payload is exactly w*h*4, no padding). Surfaces ≤256px wide are a
single tile column, identical to plain linear — which is why small UI
textures always decoded correctly.
- t8ad.rs: add detile_256() + apply in parse(). Single tile-row / single
tile-column textures now decode exactly (ptcopyright, ptbtn, ptlogo1 =
clean "PROJECT" logo, previously pure noise). Known residual: ≥2×2-tile
textures (>256 in both dims, e.g. the 8AX background) come out coherent
but with boundary seams — the multi-tile order is a subtle swizzle, TBD.
- sylpheed-cli: new `pak textures <pak> <out>` command — decodes every
T8aD (direct, RATC-nested, LSTA frames) to PNG for A/B against the game.
Env debug knobs for layout RE: XTILE/XDESTRIP/XDETILE_W/XREINTERPRET_PITCH
/XSCORE (TV-ranked tile sweep), plus --verbose size classification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add three static PAK-format decoders in the Bevy-free formats crate and
present them in the explorer:
- t8ad.rs: linear 32bpp A8R8G8B8 2D texture (UI/HUD art). Dims at
0x14/0x18, header size keyed by the type field @0x1c (container-safe).
Decodes the ~85% RGBA variants; defers the rest (likely DXT) rather
than misdecoding.
- ratc.rs: nested resource bundle — lists named children by magic scan.
- lsta.rs: sprite list — walks the inline T8aD frames.
Viewer: PakContent gains T8ad/Lsta/Ratc, decoded off-thread and bounded
by an 8M-texel per-entry cap; detail views show the texture, a sprite
grid, and a child list with thumbnails. Decoded images carry a
"colours unverified" note — channel-order/sRGB stays on the dynamic-RE
backlog.
Tests: 12 new unit + 3 real-disc (T8aD >=70% decode over the hangar
pack, LSTA frames, RATC named children incl. a decodable T8aD).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>