diff --git a/crates/sylpheed-formats/examples/forced_backdrop_ink_thresholds.rs b/crates/sylpheed-formats/examples/forced_backdrop_ink_thresholds.rs new file mode 100644 index 00000000..c8a14263 --- /dev/null +++ b/crates/sylpheed-formats/examples/forced_backdrop_ink_thresholds.rs @@ -0,0 +1,90 @@ +//! Reconcile two ink counts for one screen that were never counting the same pixels. +//! +//! The port agent double-witnessed the pixel-cost claim in Godot — a renderer +//! sharing no code with `compose` — and got `GP_TITLE` entry 12 at **59 530 px** +//! ink above threshold 0 and **48 368** above 1. This crate reported **49 771**. +//! Neither is wrong; the question is which convention each was using, and on a +//! mostly-dark frame the answer moves thousands of pixels. +//! +//! So: count the same composite every way, and print the family. Whichever row +//! the port's numbers land in is the convention, and then the two renderers can be +//! compared on purpose rather than by coincidence. +//! +//! cargo run -p sylpheed-formats --example forced_backdrop_ink_thresholds + +use std::path::PathBuf; + +use sylpheed_formats::{pak::PakArchive, ui_layout}; +use ui_layout::ComposeOptions; + +fn order_without_rule(build: &ui_layout::UiBuild, bundle: &[u8]) -> Vec { + let mut idx: Vec = (0..build.elements.len()).collect(); + idx.sort_by_key(|&i| { + let el = &build.elements[i]; + ( + ui_layout::sprite_layer_key(build, bundle, el) + .or_else(|| ui_layout::implied_layer_key(&el.name)) + .unwrap_or(u32::MAX), + i, + ) + }); + idx +} + +fn counts(rgba: &[u8], t: u8) -> (usize, usize) { + let rgb = rgba + .chunks_exact(4) + .filter(|p| p[0] > t || p[1] > t || p[2] > t) + .count(); + let alpha = rgba.chunks_exact(4).filter(|p| p[3] > t).count(); + (rgb, alpha) +} + +fn main() { + let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); + let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE.pak"); + + for entry in [12usize, 15] { + let by = ar.read(&ar.entries()[entry]).expect("entry"); + let b = ui_layout::parse_build(&by).expect("parse"); + for (label, opts) in [ + ( + "primitives on (what the cost run used)", + ComposeOptions { + include_primitives: true, + backdrop: [0, 0, 0, 255], + ..Default::default() + }, + ), + ( + "primitives+focus+animated", + ComposeOptions { + include_primitives: true, + include_focus: true, + include_animated: true, + backdrop: [0, 0, 0, 255], + ..Default::default() + }, + ), + ] { + let with = ui_layout::derived_paint_order(&b, &by); + let without = order_without_rule(&b, &by); + let a = ui_layout::compose_with_order(&b, &by, opts.clone(), None, Some(&with)); + let c = ui_layout::compose_with_order(&b, &by, opts, None, Some(&without)); + println!("\n== GP_TITLE entry {entry} — {label} ({}x{})", a.width, a.height); + println!(" threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT"); + for t in [0u8, 1, 2, 4, 8, 16] { + let (r1, a1) = counts(&a.rgba, t); + let (r0, a0) = counts(&c.rgba, t); + println!(" >{t:<8} | {r1:>16} | {a1:>14} | {r0:>13} | {a0:>11}"); + } + let changed = a + .rgba + .chunks_exact(4) + .zip(c.rgba.chunks_exact(4)) + .filter(|(x, y)| x != y) + .count(); + println!(" exact-RGBA changed pixels between the two orders: {changed}"); + } + } +} diff --git a/docs/re/data/forced-backdrop-ink-thresholds.txt b/docs/re/data/forced-backdrop-ink-thresholds.txt new file mode 100644 index 00000000..1933dc4a --- /dev/null +++ b/docs/re/data/forced-backdrop-ink-thresholds.txt @@ -0,0 +1,61 @@ +# Reconciling two ink counts for GP_TITLE entries 12/15 that were never +# counting the same pixels. +# +# Produced by: cargo run -p sylpheed-formats --example forced_backdrop_ink_thresholds +# 2026-08-30, SYLPHEED_DISC=/disc, 1280x720, black backdrop. +# +# The port agent's Godot second witness for entry 12 (build_12, --pose=rest): +# >0 59 530 px >1 48 368 px without the rule: 0 at both +# +# This crate, same entry, primitives-on (the convention the cost run used): +# >0 49 771 px >1 48 043 px without the rule: 0 at every threshold +# +# CONCLUSION: the >1 counts agree to 0.68 % (325 px). The >0 counts differ by +# 16 %. So the disagreement lives entirely in pixels whose value is exactly 1 -- +# a 1-LSB artefact of a different sampler, not a different set of inked pixels. +# >0 is NOT portable between these two renderers on a mostly-dark frame; >1 is. +# +# Note also: our reported 49 771 was never an ink THRESHOLD figure. It is exact +# RGBA inequality between the two paint orders, which over a black backdrop +# coincides with ink>0 -- so it belongs against the port's 59 530, not its 48 368. +# + +== GP_TITLE entry 12 — primitives on (what the cost run used) (1280x720) + threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT + >0 | 49771 | 921600 | 0 | 921600 + >1 | 48043 | 921600 | 0 | 921600 + >2 | 44884 | 921600 | 0 | 921600 + >4 | 41946 | 921600 | 0 | 921600 + >8 | 38409 | 921600 | 0 | 921600 + >16 | 32760 | 921600 | 0 | 921600 + exact-RGBA changed pixels between the two orders: 49771 + +== GP_TITLE entry 12 — primitives+focus+animated (1280x720) + threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT + >0 | 54968 | 921600 | 0 | 921600 + >1 | 52058 | 921600 | 0 | 921600 + >2 | 48171 | 921600 | 0 | 921600 + >4 | 44396 | 921600 | 0 | 921600 + >8 | 40064 | 921600 | 0 | 921600 + >16 | 33791 | 921600 | 0 | 921600 + exact-RGBA changed pixels between the two orders: 54968 + +== GP_TITLE entry 15 — primitives on (what the cost run used) (1280x720) + threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT + >0 | 49771 | 921600 | 0 | 921600 + >1 | 48043 | 921600 | 0 | 921600 + >2 | 44884 | 921600 | 0 | 921600 + >4 | 41946 | 921600 | 0 | 921600 + >8 | 38409 | 921600 | 0 | 921600 + >16 | 32760 | 921600 | 0 | 921600 + exact-RGBA changed pixels between the two orders: 49771 + +== GP_TITLE entry 15 — primitives+focus+animated (1280x720) + threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT + >0 | 54968 | 921600 | 0 | 921600 + >1 | 52058 | 921600 | 0 | 921600 + >2 | 48171 | 921600 | 0 | 921600 + >4 | 44396 | 921600 | 0 | 921600 + >8 | 40064 | 921600 | 0 | 921600 + >16 | 33791 | 921600 | 0 | 921600 + exact-RGBA changed pixels between the two orders: 54968 diff --git a/docs/re/structures/ui-forced-backdrop.md b/docs/re/structures/ui-forced-backdrop.md index 51040992..f1064463 100644 --- a/docs/re/structures/ui-forced-backdrop.md +++ b/docs/re/structures/ui-forced-backdrop.md @@ -289,6 +289,49 @@ builds across seven archives rather than argued on two. For the port's two: `GP_TITLE` entries 12 and 15 each move **49 771 px = 5.40 %** of the frame, which is their entire ink. +### ✅ Second witness (2026-08-30): the strong form holds in Godot too + +The port agent re-checked the 38-`.prm` result in **Godot**, which shares no code +with `compose`, applying this crate's fallback to its own element list and swapping +**only** `paint_order` on one screen file — renderer, textures and pose all held +fixed, so the order is the single variable. `GP_TITLE` entry 12 came out at +**59 530 px** ink with the rule and **exactly 0 without it**, at both of its +thresholds. + +That is the claim that needed a second renderer, because it is the strong form: not +"a large difference" but *the screen ceasing to exist*. Two renderers, and this +time genuinely two — unlike the necessity census, where the port's re-run was this +crate's code executed twice. + +#### ⚠️ …and the two ink figures were never counting the same pixels + +The port reported 59 530 (>0) and 48 368 (>1); this page said 49 771. Counting the +same composite every way +([`../data/forced-backdrop-ink-thresholds.txt`](../data/forced-backdrop-ink-thresholds.txt)): + +| threshold | this crate | Godot | gap | +|---|---|---|---| +| RGB > 0 | 49 771 | **59 530** | 16 % | +| RGB > 1 | **48 043** | **48 368** | **0.68 %** | + +**The whole disagreement lives in pixels whose value is exactly 1.** Above that the +two renderers agree to 325 px on a 921 600-px frame. So it is a 1-LSB sampling +artefact — a different sampler putting a faint non-zero where this one puts exact +zero — and not a different set of inked pixels. + +Two things to carry from that: + +* **`> 0` is not a portable ink convention between renderers on a mostly-dark + frame; `> 1` is.** Any future cross-renderer ink comparison should say which. +* **The 49 771 in this page was never a threshold figure at all.** It is exact RGBA + inequality between the two paint orders, which over a black backdrop coincides + with ink > 0 — so it belongs against the port's 59 530, not its 48 368. Reading it + as the `>1` number would have made the two renderers look like they agreed for the + wrong reason. + +✅ The *without-the-rule* column is **0 at every threshold** here as well, matching +Godot. The strong form is not threshold-sensitive in either renderer. + ### 🔴 The zero on the 24 `.tbm` builds is my instrument, not a finding **The control I wrote was the wrong control and it passed anyway.** It asked