Start here
GIF image records can be partial rectangles. To inspect a displayed frame, decode the complete logical canvas and apply the preceding frame’s disposal. On the original control, simply stacking patches left green trails where disposal 3 should have restored red and dark pixels.
Run python3 disposal_diagnostic.py partial-disposal. The saved comparison places actual complete Pillow canvases above a deliberately wrong patch stack. The failure appears from frame 2 onward and is measured per pixel.
What you need
Use the downloadable original controlled project and its six-frame 8×6 GIF. You need an existing Python environment with Pillow 12.3.0; the recorded native run used Python 3.14.8 on macOS ARM64.
The diagnostic accepts only three named original cases. The wrong row is generated from their declared patch indices while honoring transparency and intentionally ignoring disposal. It is not output from a second decoder or a general GIF patch extractor.
Tested with: Python 3.14.8 and Pillow 12.3.0, native CLI on macOS ARM64; independent Node 24.22.0 PNG/literal verification. Actual retained native exports, no GUI or browser-decoder run.
1. Produce an explicit wrong-control comparison
Extract the package into your own directory and run:
python3 disposal_diagnostic.py partial-disposalUnder outputs/diagnostics/partial-disposal/, inspect correct.png, wrong.png, comparison.png and diagnostic.json. Existing differing or extra outputs are rejected; choose an empty owned directory with --out rather than overwriting unrelated files.
The original source rectangles are (0,0,8,6), (2,1,4,3), (0,0,3,4), (4,3,3,2), (0,4,2,2) and (6,0,2,2). All correct displayed frames are still 8×6. Saving each rectangle alone would lose carried pixels; stacking every rectangle forever would retain pixels that disposal should remove.
2. Locate the disposal failure in the pixels
Read each row left to right in source order. The top row is the actual Pillow full-canvas export; the bottom is the deliberately wrong control. These images are exported pixels, not a GUI capture.



The partial-disposal differences are [0, 0, 9, 19, 19, 19] pixels. Frame 1 uses disposal 2; its blue rectangle is cleared before frame 2. Frame 2 uses disposal 3; before frame 3, the prior canvas returns. In frame 3, (1,1) is red [236,64,76,255] and (0,0) is dark [24,32,48,255]. The wrong stack leaves both green [40,190,104,255].
The transparent and nonzero-background controls exercise separate declared backgrounds. Their measured mismatch vectors are [0, 0, 3, 9] and [0, 0, 4]. Do not replace every cleared area with transparent black without checking the source’s background and transparency declarations.
3. Verify full canvases before exporting a sequence or sheet
The actual correct rows match independent literal full-canvas references. A separate Node PNG reader checks their cells and both comparison rows; it does not invoke a GIF decoder or treat the wrong row as an oracle for valid output.
node generate.mjs
python3 native.test.py
node verify.mjs outputsThe Node verification command requires all ten sheet exports and all three diagnostics under outputs; follow the package README for the full corpus. The diagnostic CLI restricts its case argument to the three named controls. It is not an upload validator. The ten sheet controls cover 24 literal frames, but this finite result does not prove arbitrary GIF correctness, malformed-stream rejection, large-file budgets, cancellation or browser compositing.
Keep the original GIF. A PNG sequence loses frame delays and loop declarations. For a sheet, use the native sheet recipe and raw timing sidecar; for a native editor sequence, inspect the tested Aseprite route, including its zero-delay interpretation.