# FOUR screen changes, per frame, from the running game. 2026-08-30. # tools/re-capture/fade_decompose.sh (WHERE=menu | title | extras) # # Captures 2 and 4 are the SAME transition, title -> menu via (A), run twice. # They are the hold-vs-load test: a load varies with cache and contention, # a deterministic hold does not. The pair carries its own control -- the # press-to-first-change latency DIFFERS by ~12 frames between the two runs, # so conditions demonstrably were not identical, while the black gap is # exactly 3 frames in both. # # quantity run 1 (cap 2) run 2 (cap 4) # outgoing quad rise 67-70: 63,127,191,255 64-67: 63,127,191,255 # frames black ([255] alone) 70,71,72 = 3 67,68,69 = 3 # incoming appears frame 73 frame 70 # incoming decay 255,-,127,84,63 255,169,127,84,42 # press -> first change ~25 frames ~10 frames ############ CAPTURE 1 -- menu -> title, via (B) ############ frame untextured full-screen textured (distinct) draws tex 28 [64] [142, 146, 192, 255] 12 10 29 [64] [143, 146, 192, 255] 12 10 30 [64] [143, 147, 192, 255] 12 10 31 [64] [143, 147, 192, 255] 12 10 32 [64] [144, 148, 192, 255] 12 10 34 [64, 255, 64] [144, 148, 169, 191, 254, 255] 15 12 35 [64, 223, 64] [84, 143, 145, 149, 191, 255] 15 12 36 [64, 207, 64] [42, 119, 145, 149, 159, 255] 15 12 37 [64, 175, 64] [71, 95, 145, 149, 191, 255] 14 11 39 [64, 95, 64] [146, 150, 255] 11 8 40 [64, 31, 64, 102] [146, 151, 255] 12 6 41 [64, 15, 64, 127] [146, 151, 255] 12 6 43 [64, 64, 255] [147, 152, 255] 12 6 44 [64, 64, 255] [147, 152, 255] 12 6 45 [64, 64, 255] [148, 152, 255] 12 6 46 [64] [255] 6 2 47 [64] [32, 255] 8 6 49 [64] [32, 96, 255] 8 6 50 [64] [48, 128, 255] 8 6 ############ CAPTURE 2 -- title -> menu, via (A), run 1 ############ frame untextured full-screen textured (distinct) draws tex 60 [] [42, 50, 55, 84, 255] 9 8 61 [] [16, 18, 255] 8 7 62 [] [235, 255] 7 6 63 [] [196, 255] 7 6 64 [] [156, 255] 7 6 65 [] [117, 255] 7 6 66 [] [78, 255] 7 6 67 [63] [19, 255] 8 4 68 [127] [255] 6 2 69 [191] [255] 6 2 70 [255] [255] 6 2 71 [255] [255] 6 2 72 [255] [255] 6 2 73 [64, 255] [255] 7 3 75 [64, 127] [2, 252, 255] 7 3 76 [64, 84] [3, 251, 255] 7 3 77 [64, 63] [3, 251, 255] 7 3 78 [64] [5, 249, 255] 7 3 79 [64] [5, 249, 255] 7 3 80 [64] [7, 32, 247, 255] 8 6 81 [64] [8, 96, 246, 255] 8 6 82 [64] [8, 160, 246, 255] 8 6 ############ CAPTURE 3 -- EXTRAS -> menu, via (B) ############ frame untextured full-screen textured (distinct) draws tex 28 [64] [128, 171, 181, 255] 11 9 29 [64] [128, 172, 182, 255] 11 9 30 [64] [111, 127, 172, 182, 185, 255] 11 9 31 [64] [42, 79, 139, 172, 183, 223, 255] 11 9 32 [64] [31, 69, 127, 173, 183, 255] 10 8 33 [64] [31, 173, 184, 255] 9 7 34 [64, 51] [174, 184, 255] 8 4 35 [64, 102] [174, 184, 255] 8 4 36 [64, 178] [174, 185, 255] 8 4 37 [64, 229] [174, 185, 255] 8 4 38 [64, 255] [175, 186, 255] 8 4 39 [] [] 3 0 40 [] [] 3 0 41 [64, 169] [1, 253, 255] 7 3 42 [64, 148] [2, 252, 255] 7 3 43 [64, 106] [2, 252, 255] 7 3 44 [64, 63] [3, 251, 255] 7 3 45 [64, 21] [4, 250, 255] 7 3 46 [64] [5, 249, 255] 7 3 47 [64] [6, 248, 255] 7 3 48 [64] [7, 32, 247, 255] 8 6 49 [64] [8, 96, 246, 255] 8 6 50 [64] [9, 192, 245, 255] 8 6 ############ CAPTURE 4 -- title -> menu, via (A), run 2 ############ frame untextured full-screen textured (distinct) draws tex 61 [] [137, 255] 7 6 62 [] [98, 255] 7 6 63 [] [78, 255] 7 6 64 [63] [19, 255] 8 4 65 [127] [255] 6 2 66 [191] [255] 6 2 67 [255] [255] 6 2 68 [255] [255] 6 2 69 [255] [255] 6 2 70 [64, 255] [255] 7 3 71 [64, 169] [1, 253, 255] 7 3 72 [64, 127] [2, 252, 255] 7 3 73 [64, 84] [3, 251, 255] 7 3 74 [64, 42] [4, 250, 255] 7 3 75 [64] [5, 249, 255] 7 3 76 [64] [6, 248, 255] 7 3 77 [64] [7, 32, 247, 255] 8 6 78 [64] [8, 96, 246, 255] 8 6 79 [64] [9, 192, 245, 255] 8 6 80 [64] [10, 244, 255] 8 6 ################################################################################ # A THIRD title -> menu REPLICATE, 2026-08-30 -- from a run that executed the # WRONG EXPERIMENT. The script was invoked as WHERE=menu2extras but a condition # edit had silently failed to apply, so it took the `title` branch instead. The # capture is well-formed and is of a different transition than intended -- the # same shape as the build-ordinal error, caught only because the log lacked the # navigation lines the intended branch prints. # # run outgoing ramp black frames incoming decay # 1 (fadecap2) 67-70: 63,127,191,255 70,71,72 = 3 73-77 # 2 (fadecap4) 64-67: 63,127,191,255 67,68,69 = 3 70-74 # 3 (m2e) 92-95: 63,127,191,255 95,96,97 = 3 98-103 # # ✅ THREE INDEPENDENT RUNS, gap = 3 frames every time, and the outgoing ramp is # byte-identical in all three (63, 127, 191, 255 -- steps of exactly 64). That # strengthens "the black gap is not a load" from two replicates to three, and the # ramp's exactness across runs makes the 4-frame outgoing duration as solid as # anything measured here. # # 🟡 STILL OPEN: menu -> EXTRAS, the reverse of the pair measured at a 2-frame # gap. The instrument is fixed (WHERE=menu2extras now reaches its branch) and the # run has not been taken. ################################################################################ # FIFTH TRANSITION: menu -> EXTRAS via (A). 2026-08-30. # The reverse of the pair already measured at a 2-frame gap, taken to test whether # the black gap is a property of the screen PAIR or of the DIRECTION. # # Navigation verified both ways before arming: extras margin 11.23, back on the # menu margin 11.60, against the discriminator's 9.9-11.7 control band. And the # run announced its effective configuration -- "branch taken = menu2extras: back # on the MENU with focus restored -> press (A)" -- which is the guard added after # the previous run silently took the wrong branch. # # frames 42-46 outgoing quad 51, 102, 153, [45 absent], 255 -- 5 frames, # matching build 5's declared close of 10 units = 5 frames # frame 49 EMPTY: 3 draws, 0 textured # frames 51+ EXTRAS building # # ⚠️ Frames 48 and 50 carry no `--- frame` header at all, so the gap is ONE logged # empty frame with two unlogged neighbours. Quoted as 1, not silently rounded to 3. # ################################################################################ # ALL FIVE, ORDERED BY OUTGOING SCREEN -- which is the first thing that orders them # # transition black gap outgoing outgoing close (disc) # menu -> title (B) 0 menu (build 5) 10 units = 5 frames # menu -> EXTRAS (A) 1 menu (build 5) 10 units = 5 frames # EXTRAS -> menu (B) 2 EXTRAS (build 6) 10 units = 5 frames # title -> menu (A) x3 3, 3, 3 title (build 4) 8 units = 4 frames # # 🟡 THE GAP TRACKS THE OUTGOING SCREEN, not the direction and not the button: the # two transitions leaving the MENU give 0 and 1, the one leaving EXTRAS gives 2, # and the three leaving the TITLE give 3 every time. Direction is ruled out -- # EXTRAS->menu (2) and menu->EXTRAS (1) are the same pair in both directions and # differ; button is ruled out -- (B) gives 0 and 2, (A) gives 1 and 3. # # 🔴 BUT THAT IS NOT YET A RULE. With three outgoing screens and one value each # (bar the title's three), "each outgoing screen has its own gap" only restates # the data; a rule would PREDICT. And nothing declared does: the outgoing close is # 5, 5, 5, 4 frames against gaps 0/1, 2, 3 -- if anything inverted, on three # points. Recorded as a narrowing of WHERE to look, not as a rule. ################################################################################ # SIXTH TRANSITION: menu -> a screen OUTSIDE GP_TITLE, via (A). 2026-08-30. # # Taken because sylpheed-port's BLOCKED row asks for a SECOND VALUE ON ONE # OUTGOING SCREEN -- the thing that would make "the gap tracks the outgoing # screen" predictive rather than a restatement. ⚠️ The menu is the only screen in # GP_TITLE that can supply it: the title's sole exit is (A) to the menu and # EXTRAS's sole exit is (B) to the menu, so neither has a second destination. # # ⚠️ CONFOUND NAMED IN ADVANCE, before the result was seen: this transition leaves # the ARCHIVE, so a pak load could inflate the gap for reasons having nothing to # do with the outgoing screen. # # frames 4-21 menu settled ([64] = pteff02.prm) # frames 24-28 outgoing quad 25, 127, 255 -- 4-5 frames, matching build 5's # declared close of 10 units = 5 frames # frame 30 EMPTY: 3 draws, 0 textured # frames 32+ the new screen builds ([127] primitive, not the menu's [64]) # # ✅ GAP = 1, the same as menu -> EXTRAS. So the confound is MEASURED ABSENT: # leaving the archive costs no extra black. That is worth having on its own. # ################################################################################ # SIX TRANSITIONS, GROUPED BY OUTGOING SCREEN # # outgoing screen gaps n # menu (build 5) 0, 1, 1 3 -> title, -> EXTRAS, -> another archive # EXTRAS (build 6) 2 1 -> menu # title (build 4) 3, 3, 3 3 -> menu (three runs) # # 🟡 The ordering menu {0,1} < EXTRAS {2} < title {3} now rests on 3 + 1 + 3 # measurements rather than 2 + 1 + 3, and the menu's three values agree to within # ONE frame across three different destinations, one of them in another pak. # # 🔴 Still not predictive. Nothing declared separates 0/1 from 2 from 3: the # outgoing closes are 5, 5, 4 frames for menu, EXTRAS, title against gaps of # {0,1}, {2}, {3}. And EXTRAS still has n=1 with no way to get a second value. ################################################################################ # SEVENTH TRANSITION: EXTRAS -> a screen outside GP_TITLE, via (A). 2026-08-30. # # 🔴 Taken because my own claim that EXTRAS had a SOLE EXIT was wrong. I recorded # its n=1 as STRUCTURAL; the disc refutes that -- build 6 declares three buttons, # ptbtn11/ptbtn12/ptbtn13, all kind 0x3002. The cap was an unverified assertion. # # frames 28-35 EXTRAS settled ([64]); textured content fades 31-35 # frames 36-40 outgoing quad 25, 178, 229, 255 -- 4-5 frames, matching # build 6's declared close of 10 units = 5 frames # frames 42,43,44 EMPTY: 3 draws, 0 textured -- THREE frames # frames 45+ a different archive builds (23-28 draws/frame against # GP_TITLE's 11-14) # # ✅ GAP = 3. So EXTRAS as outgoing screen gives {2, 3}. # ################################################################################ # SEVEN TRANSITIONS, BY OUTGOING SCREEN # # outgoing gaps n destinations # menu (build 5) 0, 1, 1 3 title, EXTRAS, another archive # EXTRAS (build 6) 2, 3 2 menu, another archive # title (build 4) 3, 3, 3 3 menu x3 # # 📌 A PAIRWISE CONTROL that holds the destination class constant: menu -> another # archive gives 1, EXTRAS -> another archive gives 3. Same kind of destination, # gap differs by the outgoing screen. That is the strongest support yet for the # outgoing-screen dependence, because it removes the destination as the variable. # # 🔴 BUT THE CLEAN ORDERING IS GONE. EXTRAS {2,3} and title {3,3,3} now OVERLAP at # 3, so "menu < EXTRAS < title" no longer separates them. What survives is weaker: # the outgoing screen constrains the gap to a 2-wide band, and different outgoing # screens have different bands that are not disjoint. Still not predictive, and # now not even cleanly ordered. ################################################################################ # EIGHTH TRANSITION: a SECOND EXTRAS -> menu, via (B). 2026-08-30. # Taken to verify the fixed effective-config guard on the one screen screen_id.py # cannot resolve, and to replicate the table's weakest cell at the same time. # # ✅ THE GUARD WORKS, verified in a run rather than asserted: # arming on = menu [screen_id: cannot separate menu/EXTRAS] # discriminator = extras rmse=18.94 (other main_menu 30.09, margin 11.15) # Before the fix this run would have announced "arming on = menu" while armed on # EXTRAS. The ambiguity is now visible instead of hidden. # # frames 29-31 outgoing quad 229, 255, 255 # frames 32, 33 EMPTY: 3 draws, 0 textured # frames 34-38 incoming menu's quad decaying 169, 148, 106, 63, 21 # GAP = 2, identical to the first EXTRAS -> menu. # ################################################################################ # 📌 EIGHT TRANSITIONS: THE GAP IS A PROPERTY OF THE ORDERED PAIR, NOT THE ORIGIN # # transition gaps n repeats agree? # title -> menu 3, 3, 3 3 YES # EXTRAS -> menu 2, 2 2 YES # menu -> title 0 1 - # menu -> EXTRAS 1 1 - # menu -> other 1 1 - # EXTRAS -> other 3 1 - # # EVERY repeated pair is identical -- 3/3 and 2/2, five replicates, no variation. # EVERY differing value comes from a DIFFERENT pair. And the same origin gives # different values to different destinations: menu 0 vs 1, EXTRAS 2 vs 3. # # 🔴 So "the outgoing screen determines the gap" is superseded a second time. The # origin CONSTRAINS it (menu {0,1}, EXTRAS {2,3}, title {3}); the ORDERED PAIR # determines it, reproducibly. Still nothing declared predicts which value a pair # gets, so this remains a description with five replicates behind it rather than a # rule. ################################################################################ # NINTH TRANSITION: menu -> a SECOND screen outside GP_TITLE, via (A). 2026-08-30. # # Taken because the ordered-pair claim needs a second distinct destination from # the SAME origin -- data, not a fit. sylpheed-port checked "nothing declared # predicts the gap" independently and confirmed it, and DECLINED to search # combinations on the grounds that four pairs against many candidate two-screen # functions fits by construction. This adds a pair instead. # # ⚠️ The button is not controlled -- there is no focus readout -- so the # destination is IDENTIFIED AFTERWARDS by its draw signature: # menu -> other (1st): incoming primitive [127], 12-13 draws/frame # menu -> other (2nd): incoming primitive [255], 7-9 draws/frame # Different screens. # # frames 21-34 menu settled ([64]); textured content fades 32-34 # frames 37-42 outgoing quad 25, 51, 102, 229, 255 # frame 44 the new screen is ALREADY drawing -- [255] plus textured # content. NO empty frame anywhere. # # ✅ GAP = 0. # ################################################################################ # NINE TRANSITIONS. The menu as origin now gives FOUR values across FOUR # destinations: title 0, EXTRAS 1, other-1 1, other-2 0. # # 📌 That is further evidence for the ordered pair over the origin: the same # origin gives both 0 and 1 depending on where it goes, and the two repeated pairs # remain internally identical (3,3,3 and 2,2). # # 🟡 AN OBSERVATION, EXPLICITLY NOT A RULE. The incoming screen's own full-screen # primitive differs between the two "other" destinations: [255] where the gap is 0, # [127] where it is 1. A screen that begins from opaque black would not need a # blank frame, since its own backdrop covers it -- which is a tempting mechanism # and it FAILS on menu -> EXTRAS: EXTRAS declares a black backdrop # (ui-forced-backdrop.md, 12 of 16) and still gives 1. # 🔴 So it is recorded as an observation with its counter-example, not fitted. Nine # transitions against many candidate two-screen functions is the same construction # sylpheed-port declined to search, and their reason applies unchanged to me. ################################################################################ # CAN THE TWO "other" DESTINATIONS BE NAMED? Attempted 2026-08-30, FAILED. # # sylpheed-port's caveat on the ninth pair: the destination identification is # after the fact by draw signature, which establishes THAT the two screens differ # ([255] at 7-9 draws/frame against [127] at 12-13) but not WHICH screen either # is. The gap value is attributed to a pair whose second member is known only as # "not the other one". Correct, and worth trying to remove. # # Both runs saved a screenshot of the destination. Scored against the archives the # menu's non-EXTRAS buttons plausibly lead to: # # m2o GP_OPTIONS 43.30 GP_MISSION_SELECT 49.18 GP_SYSTEM 54.64 # GP_SAVE_LOAD 54.79 GP_TUTORIAL 55.56 -> best margin 5.88 # m2o2 GP_SYSTEM 45.74 GP_MISSION_SELECT 48.02 GP_OPTIONS 49.44 # GP_TUTORIAL 56.54 GP_SAVE_LOAD 57.00 -> best margin 2.28 # # 🔴 REJECTED, against this corpus's own calibration. which_title_screen.py's # control puts a TRUE match at RMSE ~18-20 with a margin of ~10, and a "neither" # at ~34 with a margin under 1. These best fits are 43 and 46 -- roughly double a # real match -- with margins of 5.88 and 2.28. Accepting "m2o is GP_OPTIONS" on # 5.88 would be the same weak-margin acceptance a threshold was added to the # navigation search to prevent, three iterations ago. # # ⚠️ Reach of the negative: one build per archive was rendered (the default, which # is the largest), and the screen a button opens need not be the largest build. # So this does not refute those archives -- it fails to identify, which is # different. The port's caveat stands and the ninth pair keeps it.