From e61c60029a8b6b406dae60f69121fc1356128884 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Mon, 31 Aug 2026 06:06:30 +0000 Subject: [PATCH] re: the UI blend mode is MEASURED -- the frames are drawn ADDITIVE Closes the one route t32-blend-mode-not-on-disc.md left open: the executable's draw path. Canary's UI draw capture now logs RB_BLENDCONTROL0 per draw, and the game was driven to the main menu and to EXTRAS with F10 at each. The title-side UI uses two blend states and ONE pixel shader: 0x07010701 src=ONE dst=1-SRC_ALPHA alpha-over (premultiplied) ptbase, pteff05, the fade quad, ptmsg, ptmsg2, pttitle, buttons 0x01010101 src=ONE dst=ONE ADDITIVE ptframe1, ptframe2, ptframe3, pteff20, both rotated sweep strips Two controls, both run before the result was read: * the NDC->pixel conversion that identifies a draw by its quad size reproduces 1134 and 1303 px for the two rotated sweep strips -- numbers measured by a different tool in a different session -- on BOTH screens. The tool prints PASS/FAIL and disclaims its own output on FAIL. * pixel shader 0xE59B2B3DA4AA9008 is used with BOTH states, 12 draws additive and 18 alpha-over. ptframe1 and ptbase run the same shader; only the blend register differs. So this is a blend result, not a shader result. This confirms the port's independent measurement -- it solved the composite per pixel from two backgrounds and found additive halves alpha-over's error on both frames -- by a route with nothing in common with it. So the blend is no longer authored: 'any blend you choose is authored' was true of the disc and is not true of the game. What is still unknown is which field selects it; elements sharing a mode are batched into one draw call, so the selection happens before the draw. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- .../ui-draws/blend-extras-2026-08-31.log | 43 +++++++ .../ui-draws/blend-main-menu-2026-08-31.log | 70 +++++++++++ docs/re/data/ui-blend-mode-measured.txt | 109 ++++++++++++++++++ docs/re/structures/ui-blend-mode-measured.md | 105 +++++++++++++++++ tools/re-capture/ui_blend_map.py | 17 ++- 5 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 docs/re/captures/ui-draws/blend-extras-2026-08-31.log create mode 100644 docs/re/captures/ui-draws/blend-main-menu-2026-08-31.log create mode 100644 docs/re/data/ui-blend-mode-measured.txt create mode 100644 docs/re/structures/ui-blend-mode-measured.md diff --git a/docs/re/captures/ui-draws/blend-extras-2026-08-31.log b/docs/re/captures/ui-draws/blend-extras-2026-08-31.log new file mode 100644 index 00000000..d059f291 --- /dev/null +++ b/docs/re/captures/ui-draws/blend-extras-2026-08-31.log @@ -0,0 +1,43 @@ +# every draw in SUBMISSION ORDER, undeduplicated, frames 1..4 +# tex dimensions identify the sprite; base is the guest address + 0 prim=8 indices=3 vs=0x0A6D1DD7767FDF27 ps=0x2E372EA28CC404B7 blend=0x00010001[c:src=1 op=0 dst=0 a:src=1 op=0 dst=0] cc=0x00000000 mask=0xFFFF + vb=0x150388F8 stride=28 attrs=[57@0 38@12 ] fmt0=57 v: [-0.50,-0.50,z=0.00000] [1279.50,-0.50,z=0.00000] [1279.50,719.50,z=0.00000] + 1 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x15038950 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 2 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x150389B0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [1.29,1.58,z=0.00000,col=E2FFFFFF] [1.83,1.02,z=0.00000,col=E2FFFFFF] [0.99,-1.57,z=0.00000,col=E2FFFFFF] [0.45,-1.02,z=0.00000,col=E2FFFFFF] + 3 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BFE0000 1280x768 fmt=6] + vb=0x15038A10 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.89,1.02,z=0.00000,col=D0FFFFFF] [-1.45,1.81,z=0.00000,col=D0FFFFFF] [0.14,-1.02,z=0.00000,col=D0FFFFFF] [-0.30,-1.81,z=0.00000,col=D0FFFFFF] + 4 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0C3B0000 1280x768 fmt=6] + vb=0x15038A70 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 5 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0x5773DC18083C4C20 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF + vb=0x15038AD0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=40000000] [1.00,1.00,z=0.00000,col=40000000] [1.00,-1.00,z=0.00000,col=40000000] [-1.00,-1.00,z=0.00000,col=40000000] + 6 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BFE0000 1280x768 fmt=6] + vb=0x15038B30 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.64,1.00,z=0.00000,col=FFFFFFFF] [0.64,1.00,z=0.00000,col=FFFFFFFF] [0.64,-1.00,z=0.00000,col=FFFFFFFF] [-0.64,-1.00,z=0.00000,col=FFFFFFFF] + 7 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x15038B90 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.48,0.67,z=0.00000,col=80FFFFFF] [0.48,0.67,z=0.00000,col=80FFFFFF] [0.48,-0.77,z=0.00000,col=80FFFFFF] [-0.48,-0.77,z=0.00000,col=80FFFFFF] [-0.31,0.36,z=0.00000,col=FFFFFFFF] [0.07,0.36,z=0.00000,col=FFFFFFFF] [0.07,-0.25,z=0.00000,col=FFFFFFFF] [-0.31,-0.25,z=0.00000,col=FFFFFFFF] + 8 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x15038DD0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.27,-0.51,z=0.00000,col=FFFFFFFF] [0.28,-0.51,z=0.00000,col=FFFFFFFF] [0.28,-0.62,z=0.00000,col=FFFFFFFF] [-0.27,-0.62,z=0.00000,col=FFFFFFFF] [-0.14,0.72,z=0.00000,col=FFFFFFFF] [0.14,0.72,z=0.00000,col=FFFFFFFF] [0.14,0.63,z=0.00000,col=FFFFFFFF] [-0.14,0.63,z=0.00000,col=FFFFFFFF] + 9 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] + 10 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] +--- frame 3 --- + 11 prim=8 indices=3 vs=0x0A6D1DD7767FDF27 ps=0x2E372EA28CC404B7 blend=0x00010001[c:src=1 op=0 dst=0 a:src=1 op=0 dst=0] cc=0x00000000 mask=0xFFFF + vb=0x15079138 stride=28 attrs=[57@0 38@12 ] fmt0=57 v: [-0.50,-0.50,z=0.00000] [1279.50,-0.50,z=0.00000] [1279.50,719.50,z=0.00000] + 12 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x15079190 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 13 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x150791F0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [1.33,1.58,z=0.00000,col=E4FFFFFF] [1.87,1.02,z=0.00000,col=E4FFFFFF] [1.02,-1.57,z=0.00000,col=E4FFFFFF] [0.48,-1.02,z=0.00000,col=E4FFFFFF] + 14 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BFE0000 1280x768 fmt=6] + vb=0x15079250 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.92,1.02,z=0.00000,col=D1FFFFFF] [-1.48,1.81,z=0.00000,col=D1FFFFFF] [0.11,-1.02,z=0.00000,col=D1FFFFFF] [-0.33,-1.81,z=0.00000,col=D1FFFFFF] + 15 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0C3B0000 1280x768 fmt=6] + vb=0x150792B0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 16 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0x5773DC18083C4C20 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF + vb=0x15079310 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=40000000] [1.00,1.00,z=0.00000,col=40000000] [1.00,-1.00,z=0.00000,col=40000000] [-1.00,-1.00,z=0.00000,col=40000000] + 17 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BFE0000 1280x768 fmt=6] + vb=0x15079370 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.64,1.00,z=0.00000,col=FFFFFFFF] [0.64,1.00,z=0.00000,col=FFFFFFFF] [0.64,-1.00,z=0.00000,col=FFFFFFFF] [-0.64,-1.00,z=0.00000,col=FFFFFFFF] + 18 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x150793D0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.48,0.67,z=0.00000,col=80FFFFFF] [0.48,0.67,z=0.00000,col=80FFFFFF] [0.48,-0.77,z=0.00000,col=80FFFFFF] [-0.48,-0.77,z=0.00000,col=80FFFFFF] [-0.31,0.36,z=0.00000,col=FFFFFFFF] [0.07,0.36,z=0.00000,col=FFFFFFFF] [0.07,-0.25,z=0.00000,col=FFFFFFFF] [-0.31,-0.25,z=0.00000,col=FFFFFFFF] + 19 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x15079610 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.27,-0.51,z=0.00000,col=FFFFFFFF] [0.28,-0.51,z=0.00000,col=FFFFFFFF] [0.28,-0.62,z=0.00000,col=FFFFFFFF] [-0.27,-0.62,z=0.00000,col=FFFFFFFF] [-0.14,0.72,z=0.00000,col=FFFFFFFF] [0.14,0.72,z=0.00000,col=FFFFFFFF] [0.14,0.63,z=0.00000,col=FFFFFFFF] [-0.14,0.63,z=0.00000,col=FFFFFFFF] + 20 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] + 21 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] diff --git a/docs/re/captures/ui-draws/blend-main-menu-2026-08-31.log b/docs/re/captures/ui-draws/blend-main-menu-2026-08-31.log new file mode 100644 index 00000000..fdd42162 --- /dev/null +++ b/docs/re/captures/ui-draws/blend-main-menu-2026-08-31.log @@ -0,0 +1,70 @@ +# every draw in SUBMISSION ORDER, undeduplicated, frames 2..5 +# tex dimensions identify the sprite; base is the guest address + 0 prim=8 indices=3 vs=0x0A6D1DD7767FDF27 ps=0x2E372EA28CC404B7 blend=0x00010001[c:src=1 op=0 dst=0 a:src=1 op=0 dst=0] cc=0x00000000 mask=0xFFFF + vb=0x14CF00B8 stride=28 attrs=[57@0 38@12 ] fmt0=57 v: [-0.50,-0.50,z=0.00000] [1279.50,-0.50,z=0.00000] [1279.50,719.50,z=0.00000] + 1 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14CF0110 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 2 prim=13 indices=8 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14CF0170 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [1.54,1.58,z=0.00000,col=EFFFFFFF] [2.08,1.02,z=0.00000,col=EFFFFFFF] [1.23,-1.57,z=0.00000,col=EFFFFFFF] [0.69,-1.02,z=0.00000,col=EFFFFFFF] [-0.61,1.02,z=0.00000,col=9AFFFFFF] [-0.17,1.81,z=0.00000,col=9AFFFFFF] [1.42,-1.02,z=0.00000,col=9AFFFFFF] [0.98,-1.81,z=0.00000,col=9AFFFFFF] + 3 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0AEF0000 1280x768 fmt=6] + vb=0x14CF0230 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 4 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0x5773DC18083C4C20 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF + vb=0x14CF0290 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=40000000] [1.00,1.00,z=0.00000,col=40000000] [1.00,-1.00,z=0.00000,col=40000000] [-1.00,-1.00,z=0.00000,col=40000000] + 5 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14CF02F0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.64,1.00,z=0.00000,col=C0FFFFFF] [0.64,1.00,z=0.00000,col=C0FFFFFF] [0.64,-1.00,z=0.00000,col=C0FFFFFF] [-0.64,-1.00,z=0.00000,col=C0FFFFFF] + 6 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BA60000 1280x768 fmt=6] + vb=0x14CF0350 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.54,1.00,z=0.00000,col=FFFFFFFF] [0.54,1.00,z=0.00000,col=FFFFFFFF] [0.54,-1.00,z=0.00000,col=FFFFFFFF] [-0.54,-1.00,z=0.00000,col=FFFFFFFF] + 7 prim=13 indices=8 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14CF03B0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.31,0.70,z=0.00000,col=FFFFFFFF] [0.07,0.70,z=0.00000,col=FFFFFFFF] [0.07,-0.08,z=0.00000,col=FFFFFFFF] [-0.31,-0.08,z=0.00000,col=FFFFFFFF] [-0.09,0.26,z=0.00000,col=FFFFFFFF] [0.31,0.26,z=0.00000,col=FFFFFFFF] [0.31,-0.60,z=0.00000,col=FFFFFFFF] [-0.09,-0.60,z=0.00000,col=FFFFFFFF] + 8 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0B2C0000 1280x768 fmt=6] + vb=0x14CF0470 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.18,-0.65,z=0.00000,col=FFFFFFFF] [0.17,-0.65,z=0.00000,col=FFFFFFFF] [0.17,-0.76,z=0.00000,col=FFFFFFFF] [-0.18,-0.76,z=0.00000,col=FFFFFFFF] + 9 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x14CF04D0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.23,0.45,z=0.00000,col=FFFFFFFF] [-0.22,0.56,z=0.00000,col=FFFFFFFF] [-0.15,0.55,z=0.00000,col=FFFFFFFF] [-0.16,0.43,z=0.00000,col=FFFFFFFF] [-0.16,0.57,z=0.00000,col=FFFFFFFF] [0.17,0.57,z=0.00000,col=FFFFFFFF] [0.17,0.42,z=0.00000,col=FFFFFFFF] [-0.16,0.42,z=0.00000,col=FFFFFFFF] + 10 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] + 11 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] +--- frame 4 --- + 12 prim=8 indices=3 vs=0x0A6D1DD7767FDF27 ps=0x2E372EA28CC404B7 blend=0x00010001[c:src=1 op=0 dst=0 a:src=1 op=0 dst=0] cc=0x00000000 mask=0xFFFF + vb=0x14D30838 stride=28 attrs=[57@0 38@12 ] fmt0=57 v: [-0.50,-0.50,z=0.00000] [1279.50,-0.50,z=0.00000] [1279.50,719.50,z=0.00000] + 13 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D30890 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 14 prim=13 indices=8 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D308F0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [1.57,1.58,z=0.00000,col=F0FFFFFF] [2.11,1.02,z=0.00000,col=F0FFFFFF] [1.26,-1.57,z=0.00000,col=F0FFFFFF] [0.72,-1.02,z=0.00000,col=F0FFFFFF] [-0.64,1.02,z=0.00000,col=9CFFFFFF] [-0.20,1.81,z=0.00000,col=9CFFFFFF] [1.39,-1.02,z=0.00000,col=9CFFFFFF] [0.95,-1.81,z=0.00000,col=9CFFFFFF] + 15 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0AEF0000 1280x768 fmt=6] + vb=0x14D309B0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 16 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0x5773DC18083C4C20 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF + vb=0x14D30A10 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=40000000] [1.00,1.00,z=0.00000,col=40000000] [1.00,-1.00,z=0.00000,col=40000000] [-1.00,-1.00,z=0.00000,col=40000000] + 17 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D30A70 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.64,1.00,z=0.00000,col=C0FFFFFF] [0.64,1.00,z=0.00000,col=C0FFFFFF] [0.64,-1.00,z=0.00000,col=C0FFFFFF] [-0.64,-1.00,z=0.00000,col=C0FFFFFF] + 18 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BA60000 1280x768 fmt=6] + vb=0x14D30AD0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.54,1.00,z=0.00000,col=FFFFFFFF] [0.54,1.00,z=0.00000,col=FFFFFFFF] [0.54,-1.00,z=0.00000,col=FFFFFFFF] [-0.54,-1.00,z=0.00000,col=FFFFFFFF] + 19 prim=13 indices=8 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D30B30 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.31,0.70,z=0.00000,col=FFFFFFFF] [0.07,0.70,z=0.00000,col=FFFFFFFF] [0.07,-0.08,z=0.00000,col=FFFFFFFF] [-0.31,-0.08,z=0.00000,col=FFFFFFFF] [-0.09,0.26,z=0.00000,col=FFFFFFFF] [0.31,0.26,z=0.00000,col=FFFFFFFF] [0.31,-0.60,z=0.00000,col=FFFFFFFF] [-0.09,-0.60,z=0.00000,col=FFFFFFFF] + 20 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0B2C0000 1280x768 fmt=6] + vb=0x14D30BF0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.18,-0.65,z=0.00000,col=FFFFFFFF] [0.17,-0.65,z=0.00000,col=FFFFFFFF] [0.17,-0.76,z=0.00000,col=FFFFFFFF] [-0.18,-0.76,z=0.00000,col=FFFFFFFF] + 21 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x14D30C50 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.24,0.49,z=0.00000,col=FFFFFFFF] [-0.20,0.59,z=0.00000,col=FFFFFFFF] [-0.14,0.51,z=0.00000,col=FFFFFFFF] [-0.18,0.42,z=0.00000,col=FFFFFFFF] [-0.16,0.57,z=0.00000,col=FFFFFFFF] [0.17,0.57,z=0.00000,col=FFFFFFFF] [0.17,0.42,z=0.00000,col=FFFFFFFF] [-0.16,0.42,z=0.00000,col=FFFFFFFF] + 22 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] + 23 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] +--- frame 5 --- + 24 prim=8 indices=3 vs=0x0A6D1DD7767FDF27 ps=0x2E372EA28CC404B7 blend=0x00010001[c:src=1 op=0 dst=0 a:src=1 op=0 dst=0] cc=0x00000000 mask=0xFFFF + vb=0x14D50F38 stride=28 attrs=[57@0 38@12 ] fmt0=57 v: [-0.50,-0.50,z=0.00000] [1279.50,-0.50,z=0.00000] [1279.50,719.50,z=0.00000] + 25 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D50F90 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 26 prim=13 indices=8 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D50FF0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [1.61,1.58,z=0.00000,col=F2FFFFFF] [2.15,1.02,z=0.00000,col=F2FFFFFF] [1.30,-1.57,z=0.00000,col=F2FFFFFF] [0.76,-1.02,z=0.00000,col=F2FFFFFF] [-0.68,1.02,z=0.00000,col=9DFFFFFF] [-0.24,1.81,z=0.00000,col=9DFFFFFF] [1.35,-1.02,z=0.00000,col=9DFFFFFF] [0.91,-1.81,z=0.00000,col=9DFFFFFF] + 27 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0AEF0000 1280x768 fmt=6] + vb=0x14D510B0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,1.00,z=0.00000,col=FFFFFFFF] [1.00,-1.00,z=0.00000,col=FFFFFFFF] [-1.00,-1.00,z=0.00000,col=FFFFFFFF] + 28 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0x5773DC18083C4C20 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF + vb=0x14D51110 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-1.00,1.00,z=0.00000,col=40000000] [1.00,1.00,z=0.00000,col=40000000] [1.00,-1.00,z=0.00000,col=40000000] [-1.00,-1.00,z=0.00000,col=40000000] + 29 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D51170 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.64,1.00,z=0.00000,col=C0FFFFFF] [0.64,1.00,z=0.00000,col=C0FFFFFF] [0.64,-1.00,z=0.00000,col=C0FFFFFF] [-0.64,-1.00,z=0.00000,col=C0FFFFFF] + 30 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0BA60000 1280x768 fmt=6] + vb=0x14D511D0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.54,1.00,z=0.00000,col=FFFFFFFF] [0.54,1.00,z=0.00000,col=FFFFFFFF] [0.54,-1.00,z=0.00000,col=FFFFFFFF] [-0.54,-1.00,z=0.00000,col=FFFFFFFF] + 31 prim=13 indices=8 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x01010101[c:src=1 op=0 dst=1 a:src=1 op=0 dst=1] cc=0x8700000C mask=0xF tex[base=0x0C340000 1280x768 fmt=6] + vb=0x14D51230 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.31,0.70,z=0.00000,col=FFFFFFFF] [0.07,0.70,z=0.00000,col=FFFFFFFF] [0.07,-0.08,z=0.00000,col=FFFFFFFF] [-0.31,-0.08,z=0.00000,col=FFFFFFFF] [-0.09,0.26,z=0.00000,col=FFFFFFFF] [0.31,0.26,z=0.00000,col=FFFFFFFF] [0.31,-0.60,z=0.00000,col=FFFFFFFF] [-0.09,-0.60,z=0.00000,col=FFFFFFFF] + 32 prim=13 indices=4 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x0B2C0000 1280x768 fmt=6] + vb=0x14D512F0 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.18,-0.65,z=0.00000,col=FFFFFFFF] [0.17,-0.65,z=0.00000,col=FFFFFFFF] [0.17,-0.76,z=0.00000,col=FFFFFFFF] [-0.18,-0.76,z=0.00000,col=FFFFFFFF] + 33 prim=13 indices=24 vs=0xE0BAFB4F520FE441 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x11C30000 1280x768 fmt=6] + vb=0x14D51350 stride=24 attrs=[57@0 6@12 37@16 ] fmt0=57 v: [-0.23,0.55,z=0.00000,col=FFFFFFFF] [-0.17,0.58,z=0.00000,col=FFFFFFFF] [-0.15,0.46,z=0.00000,col=FFFFFFFF] [-0.21,0.43,z=0.00000,col=FFFFFFFF] [-0.16,0.57,z=0.00000,col=FFFFFFFF] [0.17,0.57,z=0.00000,col=FFFFFFFF] [0.17,0.42,z=0.00000,col=FFFFFFFF] [-0.16,0.42,z=0.00000,col=FFFFFFFF] + 34 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] + 35 prim=8 indices=3 vs=0x72CBCAA6A7984111 ps=0xE59B2B3DA4AA9008 blend=0x07010701[c:src=1 op=0 dst=7 a:src=1 op=0 dst=7] cc=0x8700000C mask=0xF tex[base=0x10000000 1x1 fmt=26] diff --git a/docs/re/data/ui-blend-mode-measured.txt b/docs/re/data/ui-blend-mode-measured.txt new file mode 100644 index 00000000..d27c20a6 --- /dev/null +++ b/docs/re/data/ui-blend-mode-measured.txt @@ -0,0 +1,109 @@ +# The UI blend mode, MEASURED from the running game. 2026-08-31. +# +# Question: the port's renderer composites every UI element with straight +# alpha-over, and four elements come out too dark against the capture -- +# ptframe1/ptframe2 on the main menu and ptframe3/ptframe4 on EXTRAS -- with the +# shortfall correlating with the BACKGROUND. Nothing on the disc selects a +# per-element mode (docs/re/structures/t32-blend-mode-not-on-disc.md, reach: +# declaration entry, T8aD header word- and bit-wise, keyframe record). +# +# Method: Canary's CaptureUiDrawForRE was extended to log RB_BLENDCONTROL0, +# RB_COLORCONTROL and RB_COLOR_MASK per draw. One emulator, driven to the main +# menu and then to EXTRAS, F10 at each. Elements are identified by the PIXEL SIZE +# of their quad (NDC extents x the 1280x720 surface) against sprite dimensions +# read off the disc -- the log names no elements. +# +# Xenos enum (xenos.h): BlendFactor kZero=0 kOne=1 kSrcAlpha=6 kOneMinusSrcAlpha=7; +# BlendOp kAdd=0. RB_BLENDCONTROL0 = src | op<<5 | dst<<8, colour in the low half +# and alpha in the high half. +# +# 0x00010001 src=ONE dst=ZERO opaque -- blending effectively off +# 0x07010701 src=ONE dst=ONE_MINUS_SRC_A alpha-over, PREMULTIPLIED (src is ONE) +# 0x01010101 src=ONE dst=ONE ADDITIVE +# +# CONTROL, run before reading anything below: the two rotated sweep strips were +# measured independently at 1134 and 1303 px tall +# (docs/re/data/title-sweep-drawn-at-rest.txt). The NDC->pixel conversion here +# reproduces BOTH, on BOTH screens. The tool prints PASS/FAIL and refuses to be +# trusted otherwise. +# +# SECOND CONTROL, and it is the one that makes this a blend result rather than a +# shader result: ONE pixel shader, 0xE59B2B3DA4AA9008, is used with BOTH states +# on the main menu -- 12 draws additive and 18 alpha-over. The frames and ptbase +# run the same shader. Only the blend register differs. + +=== main menu (GP_TITLE build 5), 36 draws over 5 frames === +draw prim idx blend state quad px (w x h) best name match + 1 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 2 13 8 0x01010101 ADDITIVE 889.6 x 1134.0 (no match, nearest pteff12.t32 @2x off 610) + 2 13 8 0x01010101 ADDITIVE 1299.2 x 1303.2 (no match, nearest ptbase.t32 @2x off 602) + 3 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 4 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 5 13 4 0x01010101 ADDITIVE 819.2 x 720.0 (no match, nearest pteff12.t32 @2x off 125) + 6 13 4 0x01010101 ADDITIVE 691.2 x 720.0 pteff12.t32 @2x + 7 13 8 0x01010101 ADDITIVE 243.2 x 280.8 ptframe1.t32 @1x + 7 13 8 0x01010101 ADDITIVE 256.0 x 309.6 ptframe2.t32 @1x + 8 13 4 0x07010701 alpha-over(premul) 224.0 x 39.6 ptmsg.t32 @1x + 9 13 24 0x07010701 alpha-over(premul) 51.2 x 46.8 (no match, nearest ptbtneff01.t32 @1x off 10) + 9 13 24 0x07010701 alpha-over(premul) 211.2 x 54.0 ptbtn01f.t32 @1x + 13 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 14 13 8 0x01010101 ADDITIVE 889.6 x 1134.0 (no match, nearest pteff12.t32 @2x off 610) + 14 13 8 0x01010101 ADDITIVE 1299.2 x 1303.2 (no match, nearest ptbase.t32 @2x off 602) + 15 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 16 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 17 13 4 0x01010101 ADDITIVE 819.2 x 720.0 (no match, nearest pteff12.t32 @2x off 125) + 18 13 4 0x01010101 ADDITIVE 691.2 x 720.0 pteff12.t32 @2x + 19 13 8 0x01010101 ADDITIVE 243.2 x 280.8 ptframe1.t32 @1x + 19 13 8 0x01010101 ADDITIVE 256.0 x 309.6 ptframe2.t32 @1x + 20 13 4 0x07010701 alpha-over(premul) 224.0 x 39.6 ptmsg.t32 @1x + 21 13 24 0x07010701 alpha-over(premul) 64.0 x 61.2 (no match, nearest ptbtneff01.t32 @1x off 37) + 21 13 24 0x07010701 alpha-over(premul) 211.2 x 54.0 ptbtn01f.t32 @1x + 25 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 26 13 8 0x01010101 ADDITIVE 889.6 x 1134.0 (no match, nearest pteff12.t32 @2x off 610) + 26 13 8 0x01010101 ADDITIVE 1299.2 x 1303.2 (no match, nearest ptbase.t32 @2x off 602) + 27 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 28 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 29 13 4 0x01010101 ADDITIVE 819.2 x 720.0 (no match, nearest pteff12.t32 @2x off 125) + 30 13 4 0x01010101 ADDITIVE 691.2 x 720.0 pteff12.t32 @2x + 31 13 8 0x01010101 ADDITIVE 243.2 x 280.8 ptframe1.t32 @1x + 31 13 8 0x01010101 ADDITIVE 256.0 x 309.6 ptframe2.t32 @1x + 32 13 4 0x07010701 alpha-over(premul) 224.0 x 39.6 ptmsg.t32 @1x + 33 13 24 0x07010701 alpha-over(premul) 51.2 x 54.0 (no match, nearest ptbtneff01.t32 @1x off 17) + 33 13 24 0x07010701 alpha-over(premul) 211.2 x 54.0 ptbtn01f.t32 @1x + +CONTROL — the two rotated sweep strips measured independently at 1134 and 1303 px: + tall quads found: ['1134', '1303'] + PASS + +=== EXTRAS (GP_TITLE build 6), 22 draws over 4 frames === +draw prim idx blend state quad px (w x h) best name match + 1 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 2 13 4 0x01010101 ADDITIVE 883.2 x 1134.0 (no match, nearest ptbase.t32 @2x off 811) + 3 13 4 0x01010101 ADDITIVE 1299.2 x 1303.2 (no match, nearest ptbase.t32 @2x off 602) + 4 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 5 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 6 13 4 0x01010101 ADDITIVE 819.2 x 720.0 (no match, nearest pteff03.t32 @2x off 381) + 7 13 24 0x01010101 ADDITIVE 614.4 x 518.4 pteff20.t32 @2x + 7 13 24 0x01010101 ADDITIVE 243.2 x 219.6 ptframe3.t32 @1x + 8 13 24 0x07010701 alpha-over(premul) 352.0 x 39.6 ptmsg2.t32 @1x + 8 13 24 0x07010701 alpha-over(premul) 179.2 x 32.4 pttitle.t32 @1x + 12 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 13 13 4 0x01010101 ADDITIVE 889.6 x 1134.0 (no match, nearest ptbase.t32 @2x off 804) + 14 13 4 0x01010101 ADDITIVE 1299.2 x 1303.2 (no match, nearest ptbase.t32 @2x off 602) + 15 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 16 13 4 0x07010701 alpha-over(premul) 1280.0 x 720.0 ptbase.t32 @2x + 17 13 4 0x01010101 ADDITIVE 819.2 x 720.0 (no match, nearest pteff03.t32 @2x off 381) + 18 13 24 0x01010101 ADDITIVE 614.4 x 518.4 pteff20.t32 @2x + 18 13 24 0x01010101 ADDITIVE 243.2 x 219.6 ptframe3.t32 @1x + 19 13 24 0x07010701 alpha-over(premul) 352.0 x 39.6 ptmsg2.t32 @1x + 19 13 24 0x07010701 alpha-over(premul) 179.2 x 32.4 pttitle.t32 @1x + +CONTROL — the two rotated sweep strips measured independently at 1134 and 1303 px: + tall quads found: ['1134', '1303'] + PASS + +=== shader x blend cross-tab, main menu === + 3 ps=0x2E372EA28CC404B7 blend=0x00010001 + 3 ps=0x5773DC18083C4C20 blend=0x07010701 + 12 ps=0xE59B2B3DA4AA9008 blend=0x01010101 + 18 ps=0xE59B2B3DA4AA9008 blend=0x07010701 diff --git a/docs/re/structures/ui-blend-mode-measured.md b/docs/re/structures/ui-blend-mode-measured.md new file mode 100644 index 00000000..0fc7a905 --- /dev/null +++ b/docs/re/structures/ui-blend-mode-measured.md @@ -0,0 +1,105 @@ +# The UI blend mode — ✅ **MEASURED**: the frames are drawn **ADDITIVE** + +**Status:** ✅ `CONFIRMED`, classification **measured** — nothing on the disc +selects it, and this is what the running game tells the GPU, per draw, on two +screens. 2026-08-31. + +Closes the one route +[`t32-blend-mode-not-on-disc.md`](t32-blend-mode-not-on-disc.md) left open: *"the +executable's draw path, which is where a mode selected in code rather than data +would live. That is a route, and it is the one left."* + +## The answer + +The title-side UI uses **two blend states, and one pixel shader**: + +| `RB_BLENDCONTROL0` | src | op | dst | what it is | drawn this way | +|---|---|---|---|---|---| +| `0x07010701` | `ONE` | ADD | `1−SRC_ALPHA` | **alpha-over, premultiplied** | `ptbase`, `pteff05`, the fade quad, `ptmsg`, `ptmsg2`, `pttitle`, every button | +| `0x01010101` | `ONE` | ADD | `ONE` | **ADDITIVE** | **`ptframe1`, `ptframe2`, `ptframe3`**, `pteff20`, both rotated sweep strips | +| `0x00010001` | `ONE` | ADD | `ZERO` | opaque, blending off | the one non-UI blit that opens the frame | + +Reference data and both raw logs: +[`data/ui-blend-mode-measured.txt`](../data/ui-blend-mode-measured.txt) · +[`captures/ui-draws/blend-main-menu-2026-08-31.log`](../captures/ui-draws/blend-main-menu-2026-08-31.log) · +[`captures/ui-draws/blend-extras-2026-08-31.log`](../captures/ui-draws/blend-extras-2026-08-31.log) + +## How it was measured + +Canary's `CaptureUiDrawForRE` already dumped every UI draw in submission order +with its shaders and bound texture. It was extended to log `RB_BLENDCONTROL0`, +`RB_COLORCONTROL` and `RB_COLOR_MASK` as well — **raw and decoded**, so a decode +bug here cannot quietly become the answer. One emulator, driven to the main menu +and then into `EXTRAS`, F10 at each. + +The log names no elements, so a draw is identified by the **pixel size of its +quad**: NDC extents × the 1280×720 surface, matched against sprite dimensions +read straight off the disc ([`tools/re-capture/ui_blend_map.py`](../../../tools/re-capture/ui_blend_map.py)). + +### The controls, both run before the result was read + +**1. The size conversion reproduces two independently measured numbers.** The +title's two rotated sweep strips were measured at **1134** and **1303** px tall +in [`data/title-sweep-drawn-at-rest.txt`](../data/title-sweep-drawn-at-rest.txt), +by a different tool in a different session. This conversion produces `1134.0` and +`1303.2` — **on both screens**. The tool prints `PASS`/`FAIL` and says outright +that its sizes are worthless if the check fails. + +**2. It is the blend register, not a different shader.** Pixel shader +`0xE59B2B3DA4AA9008` is used with **both** states on the main menu — 12 draws +additive, 18 alpha-over. `ptframe1` and `ptbase` run the *same shader*; only the +blend differs. Without this the result could have been "the frames use a +different shader", which is a different finding. + +## The identification, from the artefact + +| draw | quad px | disc sprite | blend | +|---|---|---|---| +| menu 7 | 243.2 × 280.8 | `ptframe1.t32` **247×281** | **ADDITIVE** | +| menu 7 | 256.0 × 309.6 | `ptframe2.t32` **257×311** | **ADDITIVE** | +| menu 8 | 224.0 × 39.6 | `ptmsg.t32` **223×38** | alpha-over | +| menu 9 | 211.2 × 54.0 | `ptbtn01f.t32` **216×56** | alpha-over | +| extras 7 | 243.2 × 219.6 | `ptframe3.t32` **246×220** | **ADDITIVE** | +| extras 7 | 614.4 × 518.4 | `pteff20.t32` **309×259** @2× | **ADDITIVE** | +| extras 8 | 352.0 × 39.6 | `ptmsg2.t32` **354×38** | alpha-over | +| extras 8 | 179.2 × 32.4 | `pttitle.t32` **182×34** | alpha-over | + +`ptframe1` and `ptframe2` are in **one draw call**; so are `pteff20` and +`ptframe3`. A draw call carries one blend state, so elements that share a mode +are **batched together** — which is also why `ptframe4` needs no separate +argument: it is inside `EXTRAS`' 24-index additive batch with `ptframe3`. + +## What this settles, and what it does not + +✅ **The port's independent measurement is confirmed by the oracle.** It solved +the composite per pixel from two backgrounds and found additive halves +alpha-over's error on both frames (34.305/28.948 against 65.046/71.299). Two +methods with nothing in common — a solved composite against a capture, and the +register the GPU was handed — give the same answer. + +✅ **This is no longer authored.** The [not-on-disc page](t32-blend-mode-not-on-disc.md) +concluded *"any blend you choose is authored and must carry that label"*. That was +true of the **disc**; it is not true any more of the **game**. Additive is +measured, and the port can transcribe it. + +⚠️ **`src = ONE`, not `SRC_ALPHA`, in BOTH states.** The fixed-function blend +multiplies the pixel shader's output by 1, so whatever alpha weighting happens, +the *shader* does it. This says nothing about whether the `.t32` texels are +stored premultiplied — that is a separate question, and the shader is unread. + +❔ **Where the mode is selected is still unknown.** This measures *what* the GPU +was told, not *which field* decided it. The disc-side search found nothing +([reach here](t32-blend-mode-not-on-disc.md)), and the batching means the +selection happens before the draw, in whatever sorts the elements. So the port +should read this table as a per-element fact it can transcribe, **not** as a rule +it can extend to elements not in it. + +❔ **Two additive draws on the menu are unidentified** — 819.2×720 and 691.2×720. +The second is within 8 px of `pteff12` @2×; the first matches nothing on the +screen at either scale. Both are additive, so they do not change the answer, and +neither is guessed at here. + +⚠️ **Reach.** Two screens, one session, one emulator. Every element on the two +screens the port ships is in the table except the two above and `pteff10`, which +did not appear as an identifiable quad. The title screen (build 4) was **not** +captured. diff --git a/tools/re-capture/ui_blend_map.py b/tools/re-capture/ui_blend_map.py index bb97c2ba..3285698c 100755 --- a/tools/re-capture/ui_blend_map.py +++ b/tools/re-capture/ui_blend_map.py @@ -92,12 +92,19 @@ def main(): w = (max(xs) - min(xs)) / 2 * W h = (max(ys) - min(ys)) / 2 * H heights.append(h) - best, bd = "-", 1e9 + # A sprite is drawn at its native size or at 2x: the screen is + # 1280x720 and the design space is 640x360, so `ptbase` (640x360) + # covers the screen while `pteff05` (1280x720) is 1:1. Both are + # tried and the scale is reported, because guessing one would + # silently mis-name half the draws. + best, bd, bs = "-", 1e9, 1 for n, (sw, sh) in sizes.items(): - d = abs(sw - w) + abs(sh - h) - if d < bd: - best, bd = n, d - tag = best if bd <= 6 else "(no match, nearest %s off %.0f)" % (best, bd) + for sc in (1, 2): + d = abs(sw * sc - w) + abs(sh * sc - h) + if d < bd: + best, bd, bs = n, d, sc + tag = ("%s @%dx" % (best, bs)) if bd <= 8 else \ + "(no match, nearest %s @%dx off %.0f)" % (best, bs, bd) print("%4d %4d %4d 0x%08X %-20s %7.1f x %7.1f %s" % (idx, prim, nidx, raw, blend_name(raw), w, h, tag)) tall = sorted(h for h in heights if h > 900)