From 3e731c68ce16ed191b6edcfa8aeb156057d64b02 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sat, 29 Aug 2026 19:35:29 +0000 Subject: [PATCH] re: the paint-order tie-break costs one pixel, on one screen we do not ship Closes the open half of Q3. `ui-paint-order-derived-check.md` bounded WHERE a wrong tie-break could show -- overlapping same-key pairs -- and said outright that nobody had measured how many change a pixel. At the instant the player sees, the answer is: at most 1 px at max channel difference 1, on the JAPANESE title only (`ptlogo2` x `ptlogo_tm`, 5 px of shared ink). Exactly 0 px on all five port screens. The earlier 24-pair bound was counted at `rest()`, and 10 of the title's 11 overlapping tied pairs are between `ptlogo_back2eff1`..`eff5` -- the five transient flashes from the settle-time finding, transparent on the settled screen. A tie between two invisible elements cannot cost a pixel. Not a knife-edge. Sweeping every keyframe time and every midpoint between keyframe times, the live-pair count is flat across the ENTIRE settle window: 1 on the EN title, 2 on the JP title, 0 on all four loading bundles -- whose tie is live only at t17..t33, during the build-in, which matters because their settle windows are narrow enough to deserve little trust otherwise. Controls: every entry reporting zero also swaps an overlapping DIFFERENT-key pair, which must and does move pixels (25 310 / 268 698 / ~765 000 px). Zeros are explained by shared-ink counts rather than asserted -- the `ptframe` pairs overlap by bounding box and share 0 px of ink. Entries 0/1/12/15 have NO live control and their zeros rest on keyframe data rather than a render; recorded as the weaker claim it is. Refutation attempt on the corpus's "24 overlapping pairs": it SURVIVES as a rest-pose count -- an independent recount reproduces entry 7's 16 exactly. What is overturned is its interpretation as the risk surface. `tie_break_pixel_cost` gains a settle-time case and an alpha/scale filter on its rect test; `tie_cost_over_time` is new. Also strips 611 bytes of captured cargo warnings from the head of the committed tie census. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd --- .../examples/tie_break_pixel_cost.rs | 87 ++++++++--- .../examples/tie_cost_over_time.rs | 76 ++++++++++ docs/port/HANDOFF.md | 33 +++- docs/re/INDEX.md | 1 + docs/re/data/paint-order-ties-gp_title.txt | 18 --- .../tie-break-live-over-time-gp_title.txt | 22 +++ .../re/data/tie-break-pixel-cost-gp_title.txt | 142 ++++++++++++++++++ .../ui-paint-order-derived-check.md | 9 ++ .../structures/ui-tie-break-cost-at-settle.md | 118 +++++++++++++++ 9 files changed, 470 insertions(+), 36 deletions(-) create mode 100644 crates/sylpheed-formats/examples/tie_cost_over_time.rs create mode 100644 docs/re/data/tie-break-live-over-time-gp_title.txt create mode 100644 docs/re/data/tie-break-pixel-cost-gp_title.txt create mode 100644 docs/re/structures/ui-tie-break-cost-at-settle.md diff --git a/crates/sylpheed-formats/examples/tie_break_pixel_cost.rs b/crates/sylpheed-formats/examples/tie_break_pixel_cost.rs index a264b5be..4007960d 100644 --- a/crates/sylpheed-formats/examples/tie_break_pixel_cost.rs +++ b/crates/sylpheed-formats/examples/tie_break_pixel_cost.rs @@ -67,10 +67,21 @@ fn swapped(order: &[usize], a: usize, b: usize) -> Vec { o } -/// The element's on-screen rect at rest, the same approximation the tie census -/// uses: the declared pivot doubled, placed at the resting keyframe. -fn rect(e: &ui_layout::Element) -> Option<(i32, i32, i32, i32)> { - let kf = e.rest()?; +/// The element's on-screen rect, the same approximation the tie census uses: +/// the declared pivot doubled, placed at the keyframe. `at` selects the pose β€” +/// `None` is `rest()`, which is where the original census was computed. +/// +/// πŸ”΄ An element that is TRANSPARENT at the chosen pose gets no rect at all. A +/// tie involving something invisible cannot cost a pixel, and counting it as an +/// overlap is what made the original census an upper bound rather than a cost. +fn rect(e: &ui_layout::Element, at: Option) -> Option<(i32, i32, i32, i32)> { + let kf = match at { + Some(t) => e.pose_at(t)?, + None => e.rest()?.clone(), + }; + if kf.fade >> 24 == 0 || kf.scale_x == 0 || kf.scale_y == 0 { + return None; + } let (w, h) = ((e.pivot_x * 2) as i32, (e.pivot_y * 2) as i32); if w == 0 || h == 0 { return None; @@ -78,8 +89,8 @@ fn rect(e: &ui_layout::Element) -> Option<(i32, i32, i32, i32)> { Some((kf.x, kf.y, w, h)) } -fn overlaps(a: &ui_layout::Element, b: &ui_layout::Element) -> bool { - let (Some(ra), Some(rb)) = (rect(a), rect(b)) else { +fn overlaps(a: &ui_layout::Element, b: &ui_layout::Element, at: Option) -> bool { + let (Some(ra), Some(rb)) = (rect(a, at), rect(b, at)) else { return false; }; (ra.0 + ra.2).min(rb.0 + rb.2) - ra.0.max(rb.0) > 0 @@ -107,12 +118,27 @@ fn cases() -> Vec { include_animated: true, include_primitives: true, backdrop: [0, 0, 0, 255], + ..Default::default() + }, + }, + // πŸ”΄ The two cases above pose at `rest()`, which is each element's last + // hold picked independently β€” so they draw transients that the settled + // screen does not have (`docs/re/structures/ui-settle-time.md`). A tie + // between two elements that are transparent at the settle time cannot + // cost a pixel on the screen the player sees, however much their rects + // overlap at rest. `at` is filled in per entry. + Case { + name: "AT THE SETTLE TIME (what the player sees)", + opts: ComposeOptions { + backdrop: [0, 0, 0, 255], + at: Some(0), // replaced per entry + ..Default::default() }, }, ] } -fn tied_overlapping_pairs(build: &UiBuild, bytes: &[u8]) -> Vec<(usize, usize, u32)> { +fn tied_overlapping_pairs(build: &UiBuild, bytes: &[u8], at: Option) -> Vec<(usize, usize, u32)> { let keys: Vec = build .elements .iter() @@ -124,7 +150,7 @@ fn tied_overlapping_pairs(build: &UiBuild, bytes: &[u8]) -> Vec<(usize, usize, u if keys[a] != keys[b] || keys[a] == u32::MAX { continue; } - if overlaps(&build.elements[a], &build.elements[b]) { + if overlaps(&build.elements[a], &build.elements[b], at) { out.push((a, b, keys[a])); } } @@ -134,7 +160,7 @@ fn tied_overlapping_pairs(build: &UiBuild, bytes: &[u8]) -> Vec<(usize, usize, u /// A pair the game's own order DOES separate: overlapping, different keys. /// Used as the control β€” swapping it must move pixels. -fn control_pair(build: &UiBuild, bytes: &[u8]) -> Option<(usize, usize)> { +fn control_pair(build: &UiBuild, bytes: &[u8], at: Option) -> Option<(usize, usize)> { let keys: Vec = build .elements .iter() @@ -146,10 +172,10 @@ fn control_pair(build: &UiBuild, bytes: &[u8]) -> Option<(usize, usize)> { if keys[a] == keys[b] || keys[a] == u32::MAX || keys[b] == u32::MAX { continue; } - if !overlaps(&build.elements[a], &build.elements[b]) { + if !overlaps(&build.elements[a], &build.elements[b], at) { continue; } - let (ra, rb) = (rect(&build.elements[a])?, rect(&build.elements[b])?); + let (ra, rb) = (rect(&build.elements[a], at)?, rect(&build.elements[b], at)?); let ox = ((ra.0 + ra.2).min(rb.0 + rb.2) - ra.0.max(rb.0)) as i64; let oy = ((ra.1 + ra.3).min(rb.1 + rb.3) - ra.1.max(rb.1)) as i64; let area = ox * oy; @@ -174,22 +200,49 @@ fn main() { let Some(build) = ui_layout::parse_build(&bytes) else { continue; }; - let pairs = tied_overlapping_pairs(&build, &bytes); - if pairs.is_empty() { + // The census pairs are still computed at rest, so the report can say + // how many of THOSE survive posing at the settle time. + let pairs_at_rest = tied_overlapping_pairs(&build, &bytes, None); + if pairs_at_rest.is_empty() { continue; } + let settle = build.settle_time(); println!( - "entry {i:2} {} elements {} overlapping tied pair(s)", + "entry {i:2} {} elements {} overlapping tied pair(s) at rest{}", build.elements.len(), - pairs.len() + pairs_at_rest.len(), + match (settle, build.settle_window()) { + (Some(t), Some((lo, hi))) => format!(" settle t={t} (window {} units)", hi - lo), + _ => " NO SETTLE WINDOW".to_string(), + } ); let derived = ui_layout::derived_paint_order(&build, &bytes); - for c in cases() { + for mut c in cases() { + // The settle case is a no-op on a bundle that never settles. + if c.opts.at.is_some() { + match settle { + Some(t) => c.opts.at = Some(t), + None => { + println!(" [{}] SKIPPED: no settle window", c.name); + continue; + } + } + } + let pairs = tied_overlapping_pairs(&build, &bytes, c.opts.at); + if pairs.len() != pairs_at_rest.len() { + println!( + " [{}] πŸ”΄ {} of the {} tied pairs are GONE at this pose (an element is \ +transparent or collapsed there) β€” they cannot cost a pixel", + c.name, + pairs_at_rest.len() - pairs.len(), + pairs_at_rest.len() + ); + } let base = ui_layout::compose_with_order(&build, &bytes, c.opts, None, Some(&derived)); let drawn: std::collections::HashSet = base.drawn.iter().copied().collect(); // Control first. An instrument that cannot see a reorder it is // supposed to see makes every zero below meaningless. - let ctrl = match control_pair(&build, &bytes) { + let ctrl = match control_pair(&build, &bytes, c.opts.at) { Some((a, b)) if drawn.contains(&a) && drawn.contains(&b) => { let alt = ui_layout::compose_with_order( &build, diff --git a/crates/sylpheed-formats/examples/tie_cost_over_time.rs b/crates/sylpheed-formats/examples/tie_cost_over_time.rs new file mode 100644 index 00000000..78694e77 --- /dev/null +++ b/crates/sylpheed-formats/examples/tie_cost_over_time.rs @@ -0,0 +1,76 @@ +//! How many tied pairs can cost a pixel, as a function of TIME? +//! +//! `tie_break_pixel_cost` answers "at one pose". That leaves the answer looking +//! like it might be a knife-edge: pick a different instant and the count could +//! jump. This sweeps every keyframe time in the bundle and reports, per entry, +//! how many same-key pairs are simultaneously **opaque, non-collapsed and +//! overlapping** β€” the pairs whose order could possibly matter at that instant. +//! +//! The instrument's control is built in: the count at t=0 (nothing has faded in) +//! and the count at rest must bracket it, and an entry whose count is flat at +//! zero for the whole sweep would be suspicious rather than reassuring β€” so the +//! peak is printed too. +use sylpheed_formats::{pak, ui_layout}; +use ui_layout::UiBuild; + +fn rect(e: &ui_layout::Element, t: u32) -> Option<(i32, i32, i32, i32)> { + let kf = e.pose_at(t)?; + if kf.fade >> 24 == 0 || kf.scale_x == 0 || kf.scale_y == 0 { + return None; + } + let (w, h) = ((e.pivot_x * 2) as i32, (e.pivot_y * 2) as i32); + if w == 0 || h == 0 { return None; } + Some((kf.x, kf.y, w, h)) +} + +fn live_pairs(b: &UiBuild, bytes: &[u8], keys: &[u32], t: u32) -> usize { + let mut n = 0; + for a in 0..keys.len() { + for c in (a + 1)..keys.len() { + if keys[a] != keys[c] || keys[a] == u32::MAX { continue } + let (Some(ra), Some(rb)) = (rect(&b.elements[a], t), rect(&b.elements[c], t)) else { continue }; + if (ra.0 + ra.2).min(rb.0 + rb.2) - ra.0.max(rb.0) > 0 + && (ra.1 + ra.3).min(rb.1 + rb.3) - ra.1.max(rb.1) > 0 { n += 1 } + } + } + n +} + +fn main() { + let path = std::env::args().nth(1).expect("usage: tie_cost_over_time "); + let ar = pak::PakArchive::open(&path).expect("open pak"); + println!("# tied pairs that could cost a pixel, over time β€” {path}\n"); + for (i, e) in ar.entries().to_vec().iter().enumerate() { + let Ok(by) = ar.read(e) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + let keys: Vec = b.elements.iter() + .map(|el| ui_layout::sprite_layer_key(&b, &by, el).unwrap_or(u32::MAX)).collect(); + let mut ts: Vec = b.elements.iter() + .flat_map(|el| el.keyframes.iter().filter_map(|k| k.time)).collect(); + ts.sort_unstable(); ts.dedup(); + if ts.len() < 2 { continue } + let last = *ts.last().unwrap(); + // sample every keyframe time AND every midpoint between them + let mut samples: Vec = ts.clone(); + for w in ts.windows(2) { samples.push(w[0] + (w[1] - w[0]) / 2); } + samples.sort_unstable(); samples.dedup(); + let counts: Vec<(u32, usize)> = + samples.iter().map(|&t| (t, live_pairs(&b, &by, &keys, t))).collect(); + let peak = counts.iter().map(|&(_, n)| n).max().unwrap_or(0); + if peak == 0 { continue } + let Some((lo, hi)) = b.settle_window() else { continue }; + let st = b.settle_time().unwrap(); + let at_settle = live_pairs(&b, &by, &keys, st); + // the whole plateau, not just its midpoint + let plateau: Vec = + (lo..=hi).step_by(((hi - lo).max(1) / 8).max(1) as usize) + .map(|t| live_pairs(&b, &by, &keys, t)).collect(); + let pmax = plateau.iter().copied().max().unwrap_or(0); + println!("entry {i:2} peak {peak} live pair(s) over t=0..{last} \ +settle window [{lo},{hi}] at t={st}: {at_settle} ACROSS THE WHOLE WINDOW: max {pmax}"); + let busy: Vec = counts.iter().filter(|&&(_, n)| n > 0) + .map(|&(t, n)| format!("t{t}:{n}")).collect(); + if busy.len() <= 24 { println!(" live only at {}", busy.join(" ")); } + else { println!(" live at {} of {} sampled instants", busy.len(), counts.len()); } + } +} diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index f579d637..0974622e 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -1749,7 +1749,7 @@ here until 2026-08-28 and is now settled.) |---|---|---| | 🟑 | **cue NAME β†’ event binding** (Q8) | eventβ†’**wave** is measured for move/confirm/back; that the cursor's wave is the cue *named* `SE_UI_CURSOR` is still read off the authors' identifiers | | ❔ | **the other ~319 SE cues** (Q8) | located one at a time by triggering them; only the three the menu needs have been done | -| βœ…β†’πŸŸ‘ | **the paint-order tie-break** (Q3) | βœ… **Its COST is now measured and it is zero on all five port screens** (2026-08-29) β€” the tied `ptframe` pairs share no ink, and the worst tie-break effect anywhere in `GP_TITLE` is Ξ”3/255. What stays open is only *why* the game orders ties as it does. Eight candidates refuted. πŸ”΄ **The cost was understated and is corrected 2026-08-29** β€” the port challenged it and was right. Over all 16 `GP_TITLE` entries: 5 use a **measured** order and carry no tie risk; the other 11 fall back to the derived order and **7 of them have overlapping ties, 24 pairs in total** β€” 16 of those on the **Japanese title** (entry 7) alone, 2 each on `EXTRAS` EN/JP, 1 each on the four loading bundles. ⚠️ Overlap bounds *where* a wrong tie-break could show; nobody has measured how many actually change a pixel β€” [`ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md) Β· [census](../re/data/paint-order-ties-gp_title.txt) | +| βœ… | **the paint-order tie-break** (Q3) | βœ… **CLOSED 2026-08-29 β€” the cost is measured and it is one pixel.** At the instant the player sees, the tie-break changes **at most 1 px at Ξ”1**, on the **Japanese title only**; **exactly 0 px on all five screens you ship**. The previous 24-pair bound was a `rest()` count and survives as such (entry 7's 16 reproduces exactly), but 10 of the title's 11 tied pairs are between `ptlogo_back2eff1`…`eff5` β€” five transient flashes that are **transparent** on the settled screen. Not a knife-edge: the live-pair count is flat across the whole settle window, and the loading bundles' tie is live only during the build-in (t17–t33). Controls live on every entry reporting zero (a different-key swap moves 25 310 / 268 698 / ~765 000 px); ⚠️ except entries 0/1/12/15, whose zeros rest on keyframe data rather than a render. ❔ *Why* the game orders ties as it does is still unknown β€” and now costs one pixel β€” [`ui-tie-break-cost-at-settle.md`](../re/structures/ui-tie-break-cost-at-settle.md) Β· [cost run](../re/data/tie-break-pixel-cost-gp_title.txt) Β· [time sweep](../re/data/tie-break-live-over-time-gp_title.txt) | | 🟑 | **GamePart ids behind the buttons** (Q4) | the *screens* are measured; the ids are a name match onto the executable's class names | | 🟑 | **the boot transitions in code** (Q6) | both levels decoded β€” phase at `this+132` (`entryβ†’2`, `2β†’0`, `2β†’3`, `3β†’4`, `4β†’2`) and state at `this+136` inside phase 4. Phase 0 = splash (`LOGO`), phase 2 = title + `PRESS β’Ά`, phase 4 = menu. Unknown: what the event *numbers* mean | | 🟑 | **β’· leaving the main menu** (Q5) | **upgraded 2026-08-29 (later).** The idle half of this objection is **refuted**: the main menu does not self-return for **β‰₯ 60 s** untouched, and the ~8–10 s idle belongs to the **title**. β’· is delivered (Canary logs `vk=5801`) and is the only input in β‰₯ 100 s before the return, so the **ordering is measured**; the latency is not (a backlogged probe void). The footer point stands β€” the main menu is still the only screen not advertising β’· β€” [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-β“‘) | @@ -1839,3 +1839,34 @@ at that document's own pre-change tag and 14.07 today. Treat conclusions resting on it as unverified. Detail, controls and census: [`docs/re/structures/ui-settle-time.md`](../re/structures/ui-settle-time.md). + +## 2026-08-29 (later) β€” Q3's last open half is closed: the tie-break costs one pixel + +βœ… **You can stop worrying about the paint-order tie-break.** Its cost is now +measured rather than bounded, and on the five screens you ship it is **zero +pixels**. The single non-zero anywhere in `GP_TITLE` is **1 pixel at Ξ”1** on the +**Japanese** title, where `ptlogo2` and `ptlogo_tm` share 5 pixels of ink. + +**Why the earlier 24-pair figure looked alarming.** It was counted at `rest()`, +and 10 of the title's 11 overlapping tied pairs are between +`ptlogo_back2eff1`…`eff5` β€” the five transient flashes from the settle-time +finding, which are **transparent on the settled screen**. A tie between two +invisible elements cannot cost a pixel. The 24 itself is not wrong; it is a +rest-pose upper bound, and I reproduced its entry-7 component (16) exactly. + +**It does not hinge on picking one instant.** Sweeping every keyframe time and +every midpoint, the number of live tied pairs is **flat across the entire settle +window** β€” 1 on the EN title, 2 on the JP title, 0 on all four loading bundles, +whose tie is live only at t17–t33 during the build-in. + +⚠️ **One honest gap:** entries 0, 1, 12 and 15 report zero with **no live +control** β€” no overlapping different-key pair is drawn there, so nothing +demonstrates the renderer would notice a swap on those bundles. Their zeros come +from the keyframe data (no tied pair has both elements opaque at any instant in +the window), which is why I state them, but they are a weaker kind of zero than +the other six. + +❔ **Still unknown:** *why* the game orders ties as it does. Eight candidate rules +remain refuted. This finding does not answer it β€” it makes it cheap to get wrong. + +Detail, controls and reach: [`docs/re/structures/ui-tie-break-cost-at-settle.md`](../re/structures/ui-tie-break-cost-at-settle.md). diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 076c7466..82d65226 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -168,3 +168,4 @@ files, which is how the same ground got covered twice. | [`title-plate-delay-measured.md`](title-plate-delay-measured.md) | How long the boot title shows build 4 before the `PRESS β’Ά` plate | βœ… **decoded after a refutation**: build 2 and build 4 run on **one clock started together**, and the plate's own `ptbtn00` reaches `a=255` at `t=238`; the last build-in ramp ends at `t=118`, so the interval is a declared **120 units = 2.000 s**. πŸ”΄ The instruction that shipped first β€” "wait 2.13 s after build 4 settles" β€” was **refuted by the port** with disc arithmetic and is corrected in place; πŸ”΄ `rest.t` is **not** when a screen settles (it is the last hold keyframe before the exit: `ptlogo1` rests at `t=251` and stops moving at `t=42`). ⚠️ The wall-clock 2.13 s is 6.7 % long because Canary presents at **28.06 / 28.14 fps** against a nominal 30, matching the corpus's independent **28.5 fps**; author the 120 units. βœ… **measured**, two independent boots: **2.138 s** and **2.132 s** from the frame build 4 settles (glyph = its no-plate 154, motion β†’ 0). Agreeing to **6 ms**. So the boot title's end state is **not** plate-free and a compositor must draw **two builds at once**. ⚠️ Measure from *settled*, not from first pixels β€” "first drawn β†’ plate" is 3.78 s vs 4.26 s across the same two runs, because the build-in animation's own duration varies with emulator frame pacing. Plate pulse re-measured at 2.12/2.19/2.34/2.31 s (mean 2.24), replicating the corpus's β‰ˆ2.3 s. βœ… black hold between screens bracketed at **0.14–0.30 s**, consistent with the declared 12 units. πŸ”΄ the β’Άβ†’menu latency is still **not** available: both runs freeze one frame for ~1.4 s at surface mean **26.626** β€” agreeing between runs to 1e-6, and reproduced with stream restarts disabled β€” which is a guest **load stall**, not the capture path. Probe: 8.7 ms/frame, 7.97/7.98 fps against a requested 8, controls 9/9 + 4/4 | | [`menu-idle-and-b-2026-08-29.md`](menu-idle-and-b-2026-08-29.md) | The main menu does not idle back to the title β€” and four durations that were a pipeline | βœ… **refuted**: no self-return in **β‰₯ 60 s** untouched; the ~8–10 s idle belongs to the **title**. 🟑 β’·β†’title ordering measured, latency not. πŸ”΄ `classify_array` at **1503 ms/frame** drained an 8 fps stream at 0.64 fps and manufactured four latencies (24.66 s / 15.58 s / 25.60 s / 20.26 s) β€” all withdrawn; a backlog preserves ordering and destroys durations | | [`structures/ui-settle-time.md`](structures/ui-settle-time.md) | Which instant a "settled screen" composite depicts | βœ… **decoded**: a settled screen is **one instant every element is posed at**, and the disc names it β€” the midpoint of the **longest keyframe-free interval** in the build (`UiBuild::settle_time` / `settle_window`). πŸ”΄ `rest()` is *not* that: it picks each element's last hold **independently**, so a two-frame flash holds at its **peak** and burns forever. `GP_TITLE` build 4 has five staggered flashes (`ptlogo_back2eff1`…`eff5`, all extinguished by t110) that `rest()` draws simultaneously and permanently, saturating the light arc. Predicted t=198 from `[160,236]` **before scoring**: arc band **33.22 β†’ 11.79**, clipped pixels **8 581 β†’ 1 452** against the console's **1 459** (an unfitted statistic), whole frame 14.07 β†’ 12.06; controls at t=100 and t=358 are far worse, and a hand-picked visibility list reaches the identical 12.06/11.79/1 452. Controls: `at=None` byte-identical (`cmp`), pre- and post-rotation tags both 14.07, 13 paint-order tests green. ⚠️ **Reach**: of 1 758 bundles with β‰₯2 keyframe times only **30 %** have a window β‰₯ 30 units and **42 %** under 10 β€” mostly `loop*` fragments that never settle; check the width. πŸ”΄ Withdraws two claims in [`ui-rotation-implemented.md`](structures/ui-rotation-implemented.md) β€” its "Flat. No minimum." (`at` posed **leaves only**) and its "Reborn does not draw `ptlogo1`/`ptlogo2`" (both **are** drawn; only kind-`0x4` ghosts are skipped, and hiding the real ones makes the error *worse* by +5.20/+7.47). ❔ its **10.92** baseline is unreproducible β€” 14.07 at both tags | +| [`structures/ui-tie-break-cost-at-settle.md`](structures/ui-tie-break-cost-at-settle.md) | What the unknown paint-order tie-break costs, in pixels | βœ… **decoded**, closing the open half of Q3: at the settled instant the tie-break costs **at most 1 px at Ξ”1**, on the **Japanese title only** (`ptlogo2`Γ—`ptlogo_tm`, 5 px shared ink); **exactly 0 px on all five port screens**. The earlier 24-pair bound was a `rest()` count β€” and 10 of the title's 11 tied pairs are between `ptlogo_back2eff1`…`eff5`, five transient flashes that are **transparent** on the settled screen ([`ui-settle-time.md`](structures/ui-settle-time.md)). Live pairs at settle: entry 4 β†’ **1**, entry 7 β†’ **2**, the four loading bundles β†’ **0**. βœ… Not a knife-edge β€” sweeping every keyframe time and midpoint, the count is **flat across the whole settle window**, and the loading bundles' tie is live only at t17–t33. βœ… Controls: an overlapping *different*-key swap moves 25 310 / 268 698 / ~765 000 px on the entries reporting zero; zeros are explained by shared-ink counts (the `ptframe` pairs share **0 px** of ink). ⚠️ Entries 0/1/12/15 have **no live control** β€” their zeros rest on keyframe data, not a render. 🟑 Refutation attempt on the corpus's "24 pairs": **survives** as a rest-pose bound, 16/16 on entry 7. ❔ *Why* ties order as they do is still unknown β€” and now worth one pixel | diff --git a/docs/re/data/paint-order-ties-gp_title.txt b/docs/re/data/paint-order-ties-gp_title.txt index 59148b79..74689b73 100644 --- a/docs/re/data/paint-order-ties-gp_title.txt +++ b/docs/re/data/paint-order-ties-gp_title.txt @@ -1,21 +1,3 @@ -warning: unused variable: `decl` - --> crates/sylpheed-formats/src/mesh.rs:1682:9 - | -1682 | let decl = &decls[0]; - | ^^^^ help: if this is intentional, prefix it with an underscore: `_decl` - | - = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default - -warning: variable does not need to be mutable - --> crates/sylpheed-formats/src/ship_capture.rs:290:9 - | -290 | let mut flush = |base: u32, - | ----^^^^^ - | | - | help: remove this `mut` - | - = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default - entry 0 (no measured order) 7 elements, 2 tied pairs, 1 of them OVERLAPPING overlapping tie: [5] pgloading_eff01.t32 x [6] pgloading_eff02.t32 key 49408 rect (202, 528, 332, 144) / (74, 518, 188, 186) overlap 60x144 entry 1 (no measured order) 7 elements, 2 tied pairs, 1 of them OVERLAPPING diff --git a/docs/re/data/tie-break-live-over-time-gp_title.txt b/docs/re/data/tie-break-live-over-time-gp_title.txt new file mode 100644 index 00000000..6c27892d --- /dev/null +++ b/docs/re/data/tie-break-live-over-time-gp_title.txt @@ -0,0 +1,22 @@ +# tied pairs that could cost a pixel, over time β€” /disc/dat/GP_TITLE.pak + +entry 0 peak 1 live pair(s) over t=0..40 settle window [34,38] at t=36: 0 ACROSS THE WHOLE WINDOW: max 0 + live only at t17:1 t18:1 t19:1 t20:1 t21:1 t22:1 t23:1 t24:1 t25:1 t26:1 t27:1 t28:1 t30:1 t32:1 t33:1 +entry 1 peak 1 live pair(s) over t=0..40 settle window [34,38] at t=36: 0 ACROSS THE WHOLE WINDOW: max 0 + live only at t17:1 t18:1 t19:1 t20:1 t21:1 t22:1 t23:1 t24:1 t25:1 t26:1 t27:1 t28:1 t30:1 t32:1 t33:1 +entry 4 peak 6 live pair(s) over t=0..269 settle window [160,236] at t=198: 1 ACROSS THE WHOLE WINDOW: max 1 + live at 62 of 88 sampled instants +entry 5 peak 2 live pair(s) over t=0..80 settle window [44,56] at t=50: 2 ACROSS THE WHOLE WINDOW: max 2 + live at 47 of 47 sampled instants +entry 6 peak 2 live pair(s) over t=0..74 settle window [38,50] at t=44: 2 ACROSS THE WHOLE WINDOW: max 2 + live at 46 of 46 sampled instants +entry 7 peak 6 live pair(s) over t=0..269 settle window [190,236] at t=213: 2 ACROSS THE WHOLE WINDOW: max 2 + live at 92 of 117 sampled instants +entry 8 peak 2 live pair(s) over t=0..80 settle window [44,56] at t=50: 2 ACROSS THE WHOLE WINDOW: max 2 + live at 47 of 47 sampled instants +entry 9 peak 2 live pair(s) over t=0..74 settle window [38,50] at t=44: 2 ACROSS THE WHOLE WINDOW: max 2 + live at 46 of 46 sampled instants +entry 12 peak 1 live pair(s) over t=0..48 settle window [40,48] at t=44: 0 ACROSS THE WHOLE WINDOW: max 0 + live only at t17:1 t18:1 t19:1 t20:1 t21:1 t22:1 t23:1 t24:1 t25:1 t26:1 t27:1 t28:1 t30:1 t32:1 t33:1 +entry 15 peak 1 live pair(s) over t=0..48 settle window [40,48] at t=44: 0 ACROSS THE WHOLE WINDOW: max 0 + live only at t17:1 t18:1 t19:1 t20:1 t21:1 t22:1 t23:1 t24:1 t25:1 t26:1 t27:1 t28:1 t30:1 t32:1 t33:1 diff --git a/docs/re/data/tie-break-pixel-cost-gp_title.txt b/docs/re/data/tie-break-pixel-cost-gp_title.txt new file mode 100644 index 00000000..80dc236b --- /dev/null +++ b/docs/re/data/tie-break-pixel-cost-gp_title.txt @@ -0,0 +1,142 @@ +# tie-break pixel cost β€” /disc/dat/GP_TITLE.pak + +entry 0 7 elements 1 overlapping tied pair(s) at rest settle t=36 (window 4 units) + [default (what `screen render` draws)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + [default (what `screen render` draws)] [5] pgloading_eff01.t32 x [6] pgloading_eff02.t32 (key 49408): 1761 px differ (0.1911% of frame), max Ξ” 1 | ink 30864 / 21163 px, shared 3298 px + [everything on (focus+animated+primitives)] CONTROL ok: swapping [4] pgloading_loop4.rat x [6] pgloading_eff02.t32 moves 3654 px (max Ξ” 23) + [everything on (focus+animated+primitives)] [5] pgloading_eff01.t32 x [6] pgloading_eff02.t32 (key 49408): 1610 px differ (0.1747% of frame), max Ξ” 1 | ink 29173 / 21017 px, shared 3139 px + [AT THE SETTLE TIME (what the player sees)] πŸ”΄ 1 of the 1 tied pairs are GONE at this pose (an element is transparent or collapsed there) β€” they cannot cost a pixel + [AT THE SETTLE TIME (what the player sees)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + +entry 1 7 elements 1 overlapping tied pair(s) at rest settle t=36 (window 4 units) + [default (what `screen render` draws)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + [default (what `screen render` draws)] [5] pgloading_eff01.t32 x [6] pgloading_eff02.t32 (key 49408): 1761 px differ (0.1911% of frame), max Ξ” 1 | ink 30864 / 21163 px, shared 3298 px + [everything on (focus+animated+primitives)] CONTROL ok: swapping [4] pgloading_loop4.rat x [6] pgloading_eff02.t32 moves 3654 px (max Ξ” 23) + [everything on (focus+animated+primitives)] [5] pgloading_eff01.t32 x [6] pgloading_eff02.t32 (key 49408): 1610 px differ (0.1747% of frame), max Ξ” 1 | ink 29173 / 21017 px, shared 3139 px + [AT THE SETTLE TIME (what the player sees)] πŸ”΄ 1 of the 1 tied pairs are GONE at this pose (an element is transparent or collapsed there) β€” they cannot cost a pixel + [AT THE SETTLE TIME (what the player sees)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + +entry 4 24 elements 11 overlapping tied pair(s) at rest settle t=198 (window 76 units) + [default (what `screen render` draws)] CONTROL ok: swapping [10] pteff04.t32 x [18] ptlogo_back2eff5.t32 moves 36305 px (max Ξ” 254) + [default (what `screen render` draws)] [11] ptloop01.rat x [12] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [default (what `screen render` draws)] [14] ptlogo_back2eff1.t32 x [15] ptlogo_back2eff2.t32 (key 32899): 295 px differ (0.0320% of frame), max Ξ” 1 | ink 2483 / 6547 px, shared 2483 px + [default (what `screen render` draws)] [14] ptlogo_back2eff1.t32 x [16] ptlogo_back2eff3.t32 (key 32899): 861 px differ (0.0934% of frame), max Ξ” 2 | ink 2483 / 9698 px, shared 2483 px + [default (what `screen render` draws)] [14] ptlogo_back2eff1.t32 x [17] ptlogo_back2eff4.t32 (key 32899): 1555 px differ (0.1687% of frame), max Ξ” 2 | ink 2483 / 13926 px, shared 2483 px + [default (what `screen render` draws)] [14] ptlogo_back2eff1.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 6645 px differ (0.7210% of frame), max Ξ” 3 | ink 2483 / 22834 px, shared 2398 px + [default (what `screen render` draws)] [15] ptlogo_back2eff2.t32 x [16] ptlogo_back2eff3.t32 (key 32899): 420 px differ (0.0456% of frame), max Ξ” 1 | ink 6547 / 9698 px, shared 6547 px + [default (what `screen render` draws)] [15] ptlogo_back2eff2.t32 x [17] ptlogo_back2eff4.t32 (key 32899): 1209 px differ (0.1312% of frame), max Ξ” 2 | ink 6547 / 13926 px, shared 6547 px + [default (what `screen render` draws)] [15] ptlogo_back2eff2.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 6641 px differ (0.7206% of frame), max Ξ” 2 | ink 6547 / 22834 px, shared 6360 px + [default (what `screen render` draws)] [16] ptlogo_back2eff3.t32 x [17] ptlogo_back2eff4.t32 (key 32899): 584 px differ (0.0634% of frame), max Ξ” 1 | ink 9698 / 13926 px, shared 9698 px + [default (what `screen render` draws)] [16] ptlogo_back2eff3.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 6390 px differ (0.6934% of frame), max Ξ” 2 | ink 9698 / 22834 px, shared 9462 px + [default (what `screen render` draws)] [17] ptlogo_back2eff4.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 5516 px differ (0.5985% of frame), max Ξ” 1 | ink 13926 / 22834 px, shared 13480 px + [everything on (focus+animated+primitives)] CONTROL ok: swapping [10] pteff04.t32 x [18] ptlogo_back2eff5.t32 moves 860461 px (max Ξ” 254) + [everything on (focus+animated+primitives)] [11] ptloop01.rat x [12] ptloop02.rat (key 32784): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [everything on (focus+animated+primitives)] [14] ptlogo_back2eff1.t32 x [15] ptlogo_back2eff2.t32 (key 32899): 280 px differ (0.0304% of frame), max Ξ” 1 | ink 2516 / 6589 px, shared 2516 px + [everything on (focus+animated+primitives)] [14] ptlogo_back2eff1.t32 x [16] ptlogo_back2eff3.t32 (key 32899): 811 px differ (0.0880% of frame), max Ξ” 2 | ink 2516 / 9754 px, shared 2516 px + [everything on (focus+animated+primitives)] [14] ptlogo_back2eff1.t32 x [17] ptlogo_back2eff4.t32 (key 32899): 1527 px differ (0.1657% of frame), max Ξ” 2 | ink 2516 / 14072 px, shared 2516 px + [everything on (focus+animated+primitives)] [14] ptlogo_back2eff1.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 6586 px differ (0.7146% of frame), max Ξ” 3 | ink 2516 / 22970 px, shared 2419 px + [everything on (focus+animated+primitives)] [15] ptlogo_back2eff2.t32 x [16] ptlogo_back2eff3.t32 (key 32899): 395 px differ (0.0429% of frame), max Ξ” 1 | ink 6589 / 9754 px, shared 6589 px + [everything on (focus+animated+primitives)] [15] ptlogo_back2eff2.t32 x [17] ptlogo_back2eff4.t32 (key 32899): 1204 px differ (0.1306% of frame), max Ξ” 2 | ink 6589 / 14072 px, shared 6589 px + [everything on (focus+animated+primitives)] [15] ptlogo_back2eff2.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 6567 px differ (0.7126% of frame), max Ξ” 2 | ink 6589 / 22970 px, shared 6397 px + [everything on (focus+animated+primitives)] [16] ptlogo_back2eff3.t32 x [17] ptlogo_back2eff4.t32 (key 32899): 599 px differ (0.0650% of frame), max Ξ” 1 | ink 9754 / 14072 px, shared 9754 px + [everything on (focus+animated+primitives)] [16] ptlogo_back2eff3.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 6333 px differ (0.6872% of frame), max Ξ” 2 | ink 9754 / 22970 px, shared 9512 px + [everything on (focus+animated+primitives)] [17] ptlogo_back2eff4.t32 x [18] ptlogo_back2eff5.t32 (key 32899): 5427 px differ (0.5889% of frame), max Ξ” 1 | ink 14072 / 22970 px, shared 13620 px + [AT THE SETTLE TIME (what the player sees)] πŸ”΄ 10 of the 11 tied pairs are GONE at this pose (an element is transparent or collapsed there) β€” they cannot cost a pixel + [AT THE SETTLE TIME (what the player sees)] CONTROL ok: swapping [10] pteff04.t32 x [20] ptlogo_back2eff.t32 moves 25310 px (max Ξ” 243) + [AT THE SETTLE TIME (what the player sees)] [11] ptloop01.rat x [12] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + +entry 5 16 elements 2 overlapping tied pair(s) at rest settle t=50 (window 12 units) + [default (what `screen render` draws)] CONTROL ok: swapping [1] ptbase.t32 x [2] pteff05.t32 moves 764030 px (max Ξ” 67) + [default (what `screen render` draws)] [3] ptloop01.rat x [4] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [default (what `screen render` draws)] [6] ptframe1.t32 x [7] ptframe2.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 4783 / 5297 px, shared 0 px + [everything on (focus+animated+primitives)] CONTROL ok: swapping [1] ptbase.t32 x [2] pteff05.t32 moves 725164 px (max Ξ” 50) + [everything on (focus+animated+primitives)] [3] ptloop01.rat x [4] ptloop02.rat (key 32784): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [everything on (focus+animated+primitives)] [6] ptframe1.t32 x [7] ptframe2.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 4781 / 5305 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] CONTROL ok: swapping [1] ptbase.t32 x [2] pteff05.t32 moves 765778 px (max Ξ” 67) + [AT THE SETTLE TIME (what the player sees)] [3] ptloop01.rat x [4] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [AT THE SETTLE TIME (what the player sees)] [6] ptframe1.t32 x [7] ptframe2.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 4783 / 5297 px, shared 0 px + +entry 6 18 elements 2 overlapping tied pair(s) at rest settle t=44 (window 12 units) + [default (what `screen render` draws)] CONTROL ok: swapping [11] ptbase.t32 x [12] pteff05.t32 moves 761600 px (max Ξ” 67) + [default (what `screen render` draws)] [0] ptframe3.t32 x [1] ptframe4.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 3646 / 3584 px, shared 0 px + [default (what `screen render` draws)] [14] ptloop01.rat x [15] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [everything on (focus+animated+primitives)] CONTROL ok: swapping [11] ptbase.t32 x [12] pteff05.t32 moves 721144 px (max Ξ” 50) + [everything on (focus+animated+primitives)] [0] ptframe3.t32 x [1] ptframe4.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 3646 / 3584 px, shared 0 px + [everything on (focus+animated+primitives)] [14] ptloop01.rat x [15] ptloop02.rat (key 32784): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] CONTROL ok: swapping [11] ptbase.t32 x [12] pteff05.t32 moves 764104 px (max Ξ” 67) + [AT THE SETTLE TIME (what the player sees)] [0] ptframe3.t32 x [1] ptframe4.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 3646 / 3584 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] [14] ptloop01.rat x [15] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + +entry 7 30 elements 13 overlapping tied pair(s) at rest settle t=213 (window 46 units) + [default (what `screen render` draws)] CONTROL ok: swapping [14] pteff04.t32 x [22] ptlogo_back2eff5.t32 moves 278139 px (max Ξ” 248) + [default (what `screen render` draws)] [1] ptlogo2.t32 x [11] ptlogo_tm.t32 (key 32928): 1 px differ (0.0001% of frame), max Ξ” 1 | ink 58773 / 1062 px, shared 5 px + [default (what `screen render` draws)] [7] ptlogo_eff2.rat x [23] ptlogo_back2.t32 (key 32898): 1 px differ (0.0001% of frame), max Ξ” 1 | ink 47839 / 659 px, shared 3 px + [default (what `screen render` draws)] [15] ptloop01.rat x [16] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [default (what `screen render` draws)] [18] ptlogo_back2eff1.t32 x [19] ptlogo_back2eff2.t32 (key 32899): 211 px differ (0.0229% of frame), max Ξ” 1 | ink 2124 / 4909 px, shared 2124 px + [default (what `screen render` draws)] [18] ptlogo_back2eff1.t32 x [20] ptlogo_back2eff3.t32 (key 32899): 538 px differ (0.0584% of frame), max Ξ” 2 | ink 2124 / 7538 px, shared 2124 px + [default (what `screen render` draws)] [18] ptlogo_back2eff1.t32 x [21] ptlogo_back2eff4.t32 (key 32899): 941 px differ (0.1021% of frame), max Ξ” 2 | ink 2124 / 10876 px, shared 2124 px + [default (what `screen render` draws)] [18] ptlogo_back2eff1.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 1237 px differ (0.1342% of frame), max Ξ” 2 | ink 2124 / 17678 px, shared 2124 px + [default (what `screen render` draws)] [19] ptlogo_back2eff2.t32 x [20] ptlogo_back2eff3.t32 (key 32899): 216 px differ (0.0234% of frame), max Ξ” 1 | ink 4909 / 7538 px, shared 4909 px + [default (what `screen render` draws)] [19] ptlogo_back2eff2.t32 x [21] ptlogo_back2eff4.t32 (key 32899): 663 px differ (0.0719% of frame), max Ξ” 2 | ink 4909 / 10876 px, shared 4909 px + [default (what `screen render` draws)] [19] ptlogo_back2eff2.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 1023 px differ (0.1110% of frame), max Ξ” 2 | ink 4909 / 17678 px, shared 4909 px + [default (what `screen render` draws)] [20] ptlogo_back2eff3.t32 x [21] ptlogo_back2eff4.t32 (key 32899): 330 px differ (0.0358% of frame), max Ξ” 1 | ink 7538 / 10876 px, shared 7538 px + [default (what `screen render` draws)] [20] ptlogo_back2eff3.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 702 px differ (0.0762% of frame), max Ξ” 2 | ink 7538 / 17678 px, shared 7538 px + [default (what `screen render` draws)] [21] ptlogo_back2eff4.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 185 px differ (0.0201% of frame), max Ξ” 1 | ink 10876 / 17678 px, shared 10876 px + [everything on (focus+animated+primitives)] CONTROL ok: swapping [14] pteff04.t32 x [22] ptlogo_back2eff5.t32 moves 862138 px (max Ξ” 248) + [everything on (focus+animated+primitives)] [1] ptlogo2.t32 x [11] ptlogo_tm.t32 (key 32928): 3 px differ (0.0003% of frame), max Ξ” 1 | ink 58727 / 1062 px, shared 5 px + [everything on (focus+animated+primitives)] [7] ptlogo_eff2.rat x [23] ptlogo_back2.t32 (key 32898): 1 px differ (0.0001% of frame), max Ξ” 1 | ink 47217 / 664 px, shared 3 px + [everything on (focus+animated+primitives)] [15] ptloop01.rat x [16] ptloop02.rat (key 32784): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [everything on (focus+animated+primitives)] [18] ptlogo_back2eff1.t32 x [19] ptlogo_back2eff2.t32 (key 32899): 193 px differ (0.0209% of frame), max Ξ” 1 | ink 2137 / 4917 px, shared 2137 px + [everything on (focus+animated+primitives)] [18] ptlogo_back2eff1.t32 x [20] ptlogo_back2eff3.t32 (key 32899): 511 px differ (0.0554% of frame), max Ξ” 2 | ink 2137 / 7552 px, shared 2137 px + [everything on (focus+animated+primitives)] [18] ptlogo_back2eff1.t32 x [21] ptlogo_back2eff4.t32 (key 32899): 931 px differ (0.1010% of frame), max Ξ” 2 | ink 2137 / 10892 px, shared 2137 px + [everything on (focus+animated+primitives)] [18] ptlogo_back2eff1.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 1215 px differ (0.1318% of frame), max Ξ” 3 | ink 2137 / 17707 px, shared 2137 px + [everything on (focus+animated+primitives)] [19] ptlogo_back2eff2.t32 x [20] ptlogo_back2eff3.t32 (key 32899): 233 px differ (0.0253% of frame), max Ξ” 1 | ink 4917 / 7552 px, shared 4917 px + [everything on (focus+animated+primitives)] [19] ptlogo_back2eff2.t32 x [21] ptlogo_back2eff4.t32 (key 32899): 668 px differ (0.0725% of frame), max Ξ” 2 | ink 4917 / 10892 px, shared 4917 px + [everything on (focus+animated+primitives)] [19] ptlogo_back2eff2.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 1022 px differ (0.1109% of frame), max Ξ” 2 | ink 4917 / 17707 px, shared 4917 px + [everything on (focus+animated+primitives)] [20] ptlogo_back2eff3.t32 x [21] ptlogo_back2eff4.t32 (key 32899): 319 px differ (0.0346% of frame), max Ξ” 1 | ink 7552 / 10892 px, shared 7552 px + [everything on (focus+animated+primitives)] [20] ptlogo_back2eff3.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 720 px differ (0.0781% of frame), max Ξ” 2 | ink 7552 / 17707 px, shared 7552 px + [everything on (focus+animated+primitives)] [21] ptlogo_back2eff4.t32 x [22] ptlogo_back2eff5.t32 (key 32899): 187 px differ (0.0203% of frame), max Ξ” 1 | ink 10892 / 17707 px, shared 10892 px + [AT THE SETTLE TIME (what the player sees)] πŸ”΄ 11 of the 13 tied pairs are GONE at this pose (an element is transparent or collapsed there) β€” they cannot cost a pixel + [AT THE SETTLE TIME (what the player sees)] CONTROL ok: swapping [14] pteff04.t32 x [24] ptlogo_back2eff.t32 moves 268698 px (max Ξ” 247) + [AT THE SETTLE TIME (what the player sees)] [1] ptlogo2.t32 x [11] ptlogo_tm.t32 (key 32928): 1 px differ (0.0001% of frame), max Ξ” 1 | ink 58770 / 1062 px, shared 5 px + [AT THE SETTLE TIME (what the player sees)] [15] ptloop01.rat x [16] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + +entry 8 16 elements 2 overlapping tied pair(s) at rest settle t=50 (window 12 units) + [default (what `screen render` draws)] CONTROL ok: swapping [1] ptbase.t32 x [2] pteff05.t32 moves 771479 px (max Ξ” 66) + [default (what `screen render` draws)] [3] ptloop01.rat x [4] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [default (what `screen render` draws)] [6] ptframe1.t32 x [7] ptframe2.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 4778 / 5302 px, shared 0 px + [everything on (focus+animated+primitives)] CONTROL ok: swapping [1] ptbase.t32 x [2] pteff05.t32 moves 733320 px (max Ξ” 50) + [everything on (focus+animated+primitives)] [3] ptloop01.rat x [4] ptloop02.rat (key 32784): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [everything on (focus+animated+primitives)] [6] ptframe1.t32 x [7] ptframe2.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 4782 / 5309 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] CONTROL ok: swapping [1] ptbase.t32 x [2] pteff05.t32 moves 773431 px (max Ξ” 66) + [AT THE SETTLE TIME (what the player sees)] [3] ptloop01.rat x [4] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [AT THE SETTLE TIME (what the player sees)] [6] ptframe1.t32 x [7] ptframe2.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 4778 / 5302 px, shared 0 px + +entry 9 18 elements 2 overlapping tied pair(s) at rest settle t=44 (window 12 units) + [default (what `screen render` draws)] CONTROL ok: swapping [11] ptbase.t32 x [12] pteff05.t32 moves 768159 px (max Ξ” 66) + [default (what `screen render` draws)] [0] ptframe3.t32 x [1] ptframe4.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 3646 / 3584 px, shared 0 px + [default (what `screen render` draws)] [14] ptloop01.rat x [15] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + [everything on (focus+animated+primitives)] CONTROL ok: swapping [11] ptbase.t32 x [12] pteff05.t32 moves 729480 px (max Ξ” 50) + [everything on (focus+animated+primitives)] [0] ptframe3.t32 x [1] ptframe4.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 3646 / 3584 px, shared 0 px + [everything on (focus+animated+primitives)] [14] ptloop01.rat x [15] ptloop02.rat (key 32784): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] CONTROL ok: swapping [11] ptbase.t32 x [12] pteff05.t32 moves 771048 px (max Ξ” 66) + [AT THE SETTLE TIME (what the player sees)] [0] ptframe3.t32 x [1] ptframe4.t32 (key 32848): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 3646 / 3584 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] [14] ptloop01.rat x [15] ptloop02.rat (key 32784): NOT BOTH DRAWN β€” unreachable here + +entry 12 10 elements 1 overlapping tied pair(s) at rest settle t=44 (window 8 units) + [default (what `screen render` draws)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + [default (what `screen render` draws)] [8] pgloading_eff01.t32 x [9] pgloading_eff02.t32 (key 49408): 1761 px differ (0.1911% of frame), max Ξ” 1 | ink 30864 / 21163 px, shared 3298 px + [everything on (focus+animated+primitives)] CONTROL DEAD: swapping [6] pgloading_loop5.rat x [7] pgloading_baseeff.t32 changes NOTHING β€” zeros below are uninterpretable + [everything on (focus+animated+primitives)] [8] pgloading_eff01.t32 x [9] pgloading_eff02.t32 (key 49408): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] πŸ”΄ 1 of the 1 tied pairs are GONE at this pose (an element is transparent or collapsed there) β€” they cannot cost a pixel + [AT THE SETTLE TIME (what the player sees)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + +entry 15 10 elements 1 overlapping tied pair(s) at rest settle t=44 (window 8 units) + [default (what `screen render` draws)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + [default (what `screen render` draws)] [8] pgloading_eff01.t32 x [9] pgloading_eff02.t32 (key 49408): 1761 px differ (0.1911% of frame), max Ξ” 1 | ink 30864 / 21163 px, shared 3298 px + [everything on (focus+animated+primitives)] CONTROL DEAD: swapping [6] pgloading_loop5.rat x [7] pgloading_baseeff.t32 changes NOTHING β€” zeros below are uninterpretable + [everything on (focus+animated+primitives)] [8] pgloading_eff01.t32 x [9] pgloading_eff02.t32 (key 49408): 0 px differ (0.0000% of frame), max Ξ” 0 | ink 0 / 0 px, shared 0 px + [AT THE SETTLE TIME (what the player sees)] πŸ”΄ 1 of the 1 tied pairs are GONE at this pose (an element is transparent or collapsed there) β€” they cannot cost a pixel + [AT THE SETTLE TIME (what the player sees)] CONTROL UNAVAILABLE: no overlapping different-key pair is drawn + +default-options summary: 26 of 30 overlapping tied pairs change at least one pixel; 10 dead/unavailable controls diff --git a/docs/re/structures/ui-paint-order-derived-check.md b/docs/re/structures/ui-paint-order-derived-check.md index 2e0947c8..20d2ac08 100644 --- a/docs/re/structures/ui-paint-order-derived-check.md +++ b/docs/re/structures/ui-paint-order-derived-check.md @@ -1,5 +1,14 @@ # βœ… Does the derived paint order reproduce the measured ones? Mostly β€” and the gap is bounded +> βœ… **The open half of this page is closed (2026-08-29): the tie-break's pixel +> cost is measured.** At the instant the player sees, it is **at most one pixel at +> Ξ”1, on the Japanese title only, and exactly zero on all five port screens** β€” +> [`ui-tie-break-cost-at-settle.md`](ui-tie-break-cost-at-settle.md). The 24-pair +> bound below is a **`rest()` count** and survives as such (entry 7's 16 +> reproduces exactly), but 10 of the title's 11 pairs are between elements that +> are *transparent* on the settled screen. Why the game orders ties as it does is +> still unknown, and now costs one pixel. + **Status:** βœ… **checked, with numbers.** `compose` uses a paint order *measured from the running game* for the builds that have one and falls back to `derived_paint_order` β€” a sort on each sprite's layer key β€” everywhere else. The diff --git a/docs/re/structures/ui-tie-break-cost-at-settle.md b/docs/re/structures/ui-tie-break-cost-at-settle.md new file mode 100644 index 00000000..f1b5f630 --- /dev/null +++ b/docs/re/structures/ui-tie-break-cost-at-settle.md @@ -0,0 +1,118 @@ +# What the paint-order tie-break actually costs: one pixel, on one screen + +**Classification: decoded.** Both terms come from the disc β€” the pairs from the +keyframe table, the pixels from rendering the same bundle twice with two +elements swapped. No capture is involved, and none is needed: this measures how +much a *wrong* answer could cost, not which answer is right. + +## The question this closes + +[`ui-paint-order-derived-check.md`](ui-paint-order-derived-check.md) bounds +*where* a wrong tie-break could show β€” same-layer-key pairs whose rects overlap β€” +and says outright that **nobody has measured how many of them change a pixel**. +That was the last open half of Q3. + +## The answer + +**At the instant the player actually sees, the tie-break costs at most one pixel, +at a maximum channel difference of 1, and only on the Japanese title. On all five +port screens it is exactly zero.** + +| entry | screen | live tied pairs at settle | measured cost | +|---|---|---|---| +| 4 | title (EN) | 1 | **0 px** β€” the pair is `ptloop01`Γ—`ptloop02`, not both drawn | +| 5 | main menu | 2 | **0 px** β€” `ptframe1`Γ—`ptframe2` share **no ink**; the other is undrawn | +| 6 | submenu | 2 | **0 px** β€” same shape | +| 8 | submenu | 2 | **0 px** β€” same shape | +| 9 | submenu | 2 | **0 px** β€” same shape | +| 7 | title (JP) | 2 | **1 px, max Ξ” 1** β€” `ptlogo2`Γ—`ptlogo_tm`, 5 px of shared ink | +| 0, 1, 12, 15 | loading | **0** | no pair is live at all | + +Full run: [`tie-break-pixel-cost-gp_title.txt`](../data/tie-break-pixel-cost-gp_title.txt). + +## Why the number moved: `rest()` was counting elements that are not there + +The census that produced the earlier bound posed every element at `rest()`, its +own last hold keyframe. That draws **transients at their peak** +([`ui-settle-time.md`](ui-settle-time.md)), and the title's ties are almost +entirely *between transients*: `ptlogo_back2eff1`…`eff5` are five staggered +two-frame flashes, all extinguished by t110, and they account for 10 of the +title's 11 overlapping tied pairs. + +A tie between two elements that are transparent cannot cost a pixel however much +their rects overlap. Posing at the settle time instead: + +| | entry 4 | entry 7 | entries 0/1/12/15 | +|---|---|---|---| +| overlapping tied pairs at `rest()` | 11 | 13 | 1 each | +| **still live at the settle time** | **1** | **2** | **0** | + +## It is not a knife-edge + +The obvious objection is that "at the settle time" picks one instant, and a +different instant might give a different count. Sweeping every keyframe time and +every midpoint between keyframe times +([`tie-break-live-over-time-gp_title.txt`](../data/tie-break-live-over-time-gp_title.txt)): + +| entry | peak live pairs over the whole timeline | **max anywhere in the settle window** | +|---|---|---| +| 4 | 6 | **1** | +| 7 | 6 | **2** | +| 5 / 6 / 8 / 9 | 2 | 2 | +| 0 / 1 / 12 / 15 | 1 | **0** | + +The count is **flat across the entire window**, not just at its midpoint. The four +loading bundles are the sharpest case: their tie is live only at t17–t33 β€” during +the build-in β€” and dead everywhere else, which matters because their settle +windows are narrow (4 and 8 units) and would otherwise deserve little trust. + +## Controls + +* βœ… **A live control on every entry that reports a zero.** Each run also swaps an + overlapping pair with **different** keys β€” an order the game demonstrably does + care about β€” and requires it to move pixels. It moves 25 310 px on the title, + 268 698 px on the JP title, and ~765 000 px on the four menu screens. +* ⚠️ **Entries 0, 1, 12, 15 have no live control** ("no overlapping different-key + pair is drawn"). Their zeros rest on the *keyframe data* β€” no tied pair has both + elements opaque at any instant in the window β€” not on a render, so a dead + control does not undermine them. But nothing here demonstrates the renderer + would notice a swap on those bundles. +* βœ… **Shared-ink accounting.** A zero is only meaningful with an explanation. The + `ptframe` pairs overlap by bounding box and share **0 px** of actual ink; the + JP title's 1 px comes from 5 px of shared ink between `ptlogo2` and + `ptlogo_tm`. + +## 🟑 Refutation attempt: the corpus's "24 overlapping pairs" β€” it survives + +Recounted independently. Without the alpha/scale filter this document adds, the +per-entry counts are `e0:1 e1:1 e4:13 e5:2 e6:2 e7:16 e8:2 e9:2 e12:1 e15:1`, +total 41 β€” and **entry 7's 16 reproduces the corpus's figure exactly**. The 24 is +that population scoped to the entries without a measured order. Adding the filter +takes the total to 36. + +So the count stands as a **rest-pose upper bound**. What this document overturns +is its *interpretation*: 24 was being read as the surface on which a wrong +tie-break could show, and at the instant the player sees, that surface is one +pixel. + +## Reach and what is still open + +❔ **Why the game orders ties as it does is still unknown**, and this does not +touch it β€” eight candidate rules remain refuted in +[`ui-paint-order-derived-check.md`](ui-paint-order-derived-check.md). What changes +is that the question is no longer worth much: getting it wrong costs one pixel on +a screen the port does not ship. + +⚠️ **`GP_TITLE` only.** Other paks were not swept. The method is bundle-agnostic +but the numbers are not. + +⚠️ **Rect overlap is still an approximation** (pivot doubled, placed at the pose). +It is used only to *choose candidate pairs*; every reported cost is a real render +diff, so the approximation can add candidates but cannot invent a cost. + +## Reproducing + +```bash +cargo run -p sylpheed-formats --example tie_break_pixel_cost -- "$SYLPHEED_DISC/dat/GP_TITLE.pak" +cargo run -p sylpheed-formats --example tie_cost_over_time -- "$SYLPHEED_DISC/dat/GP_TITLE.pak" +```