re: primitive blend mode -- undecodable with reach, and no longer a risk
Looked in four places. The bundle has no field: a primitive has no RATC child at all and the declaration words are constant across every element of three measured screens -- the two grounds ui-prm-primitives.md already used to refute a bundle-side LAYER key, and they apply identically to blend. The colour census: every full-screen *eff00* primitive on the disc carries pure black at its various alphas, and the only non-black primitive anywhere is pbafc.prm, RGB 00e8e0 cyan. The occlusion constraint cannot reach that one. pbafc.prm looked alarming at a declared 844x600 and alpha ff; it is a small moving glint. It strobes between alpha 255 and 124 every 2 units, travels from x=178 to x=291, and is scaled 2%x3%, so it draws about 17x18 pixels. At that size it occludes essentially nothing. The oracle is unavailable: GP_READY_ROOM is a recorded no-go and gameplay needs the A press that faults the guest in this container. But the consequence closes even though the question does not. For a BLACK quad -- which is every primitive forced_backdrop touches -- the hypotheses differ only in whether it hides what is beneath. Drawn first it is correct under both; drawn last it is correct only under additive. So the rule's verdict is robust to the open question, and the port's original "layerless sorts last" was wrong under alpha-over and merely pointless under additive. This is explicitly NOT evidence for alpha-over. The investigation also found forced_backdrop judging coverage from the pivot alone, ignoring scale -- pbafc.prm is the disc's own proof that a nominally 844x600 element can draw at 2%. Checked before changing anything: all 80 forced instances are at scale 100% on every opaque instant, so no verdict moved. The guard now requires scale >= 100 at the instants it counts as opaque. Defensive, not a fix. 4 + 13 disc tests green either side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
39
crates/sylpheed-formats/examples/_pbafc.rs
Normal file
39
crates/sylpheed-formats/examples/_pbafc.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||
fn main(){
|
||||
let root=std::env::var("SYLPHEED_DISC").unwrap_or_else(|_|"/disc".into());
|
||||
let ar=pak::PakArchive::open(format!("{root}/dat/GP_READY_ROOM.pak")).unwrap();
|
||||
for (i,e) in ar.entries().iter().enumerate() {
|
||||
let Ok(by)=ar.read(e) else { continue };
|
||||
if !ratc::is_ratc(&by) { continue }
|
||||
let Some(b)=ui_layout::parse_build(&by) else { continue };
|
||||
let Some(p)=b.elements.iter().find(|el|el.name=="pbafc.prm") else { continue };
|
||||
println!("=== entry {i}: {} elements ===", b.elements.len());
|
||||
println!(" pbafc.prm pivot=({},{}) -> {}x{}", p.pivot_x,p.pivot_y,p.pivot_x*2,p.pivot_y*2);
|
||||
for k in &p.keyframes {
|
||||
println!(" t={:<5} fade={:08x} a={:<4} xy=({},{}) s={}/{}",
|
||||
k.time.map(|v|v as i64).unwrap_or(-1), k.fade, k.fade>>24, k.x,k.y,k.scale_x,k.scale_y);
|
||||
}
|
||||
// what does it cover, and is anything visible while it is opaque?
|
||||
let rest=p.rest().unwrap();
|
||||
let (px,py,pw,ph)=(rest.x, rest.y, (p.pivot_x*2) as i32, (p.pivot_y*2) as i32);
|
||||
println!(" its rect at rest: ({px},{py}) {pw}x{ph}");
|
||||
let tmax=b.elements.iter().flat_map(|e|e.keyframes.iter().filter_map(|k|k.time)).max().unwrap_or(0);
|
||||
let op:Vec<u32>=(0..=tmax).filter(|&t|p.pose_at(t).map(|k|k.fade>>24)==Some(255)).collect();
|
||||
println!(" opaque at {} instants (t={:?}..{:?}) of 0..{tmax}", op.len(), op.first(), op.last());
|
||||
let mut cov=0; let mut vis=0;
|
||||
for o in &b.elements {
|
||||
if o.index==p.index { continue }
|
||||
let Some(ok)=o.rest() else { continue };
|
||||
let (ow,oh)=((o.pivot_x*2) as i32,(o.pivot_y*2) as i32);
|
||||
let overlap=(px+pw).min(ok.x+ow)-px.max(ok.x)>0 && (py+ph).min(ok.y+oh)-py.max(ok.y)>0;
|
||||
if !overlap { continue }
|
||||
cov+=1;
|
||||
if op.iter().any(|&t| o.pose_at(t).map(|k|k.fade>>24).unwrap_or(0)>0) {
|
||||
vis+=1;
|
||||
if vis<=6 { println!(" covered AND visible while opaque: {}", o.name); }
|
||||
}
|
||||
}
|
||||
println!(" elements its rect covers: {cov}; visible while it is opaque: {vis}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -960,6 +960,12 @@ pub fn forced_backdrop(build: &UiBuild, el: &Element) -> bool {
|
||||
if el.sprite.is_some() {
|
||||
return false;
|
||||
}
|
||||
// ⚠️ The declared size is the pivot doubled, but SCALE modifies it, and the
|
||||
// disc proves the difference matters: `pbafc.prm` has a 844x600 pivot and a
|
||||
// scale of 2 %/3 %, so it draws about 17x18 px. Checking the pivot alone
|
||||
// would call it full-screen. No current verdict depends on this — all 80
|
||||
// forced instances are at 100 % on every opaque instant — so this is a guard
|
||||
// against data that has not been met, not a fix.
|
||||
if (el.pivot_x * 2) < build.design_w as u32 || (el.pivot_y * 2) < build.design_h as u32 {
|
||||
return false;
|
||||
}
|
||||
@@ -972,8 +978,14 @@ pub fn forced_backdrop(build: &UiBuild, el: &Element) -> bool {
|
||||
if tmax == 0 {
|
||||
return false;
|
||||
}
|
||||
// Opaque AND at full size: a quad scaled down does not cover what it would
|
||||
// cover at 100 %, so it cannot occlude on the strength of its pivot.
|
||||
let opaque: Vec<u32> = (0..=tmax)
|
||||
.filter(|&t| el.pose_at(t).map(|k| k.fade >> 24) == Some(255))
|
||||
.filter(|&t| {
|
||||
el.pose_at(t).map_or(false, |k| {
|
||||
k.fade >> 24 == 255 && k.scale_x >= 100 && k.scale_y >= 100
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if opaque.is_empty() {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user