diff --git a/crates/xenia-gpu/src/translator.rs b/crates/xenia-gpu/src/translator.rs index 2aa966b..0753771 100644 --- a/crates/xenia-gpu/src/translator.rs +++ b/crates/xenia-gpu/src/translator.rs @@ -250,12 +250,30 @@ impl EmitCtx { // iterate-3T: real export model. Xenos export index 62 = oPos; // indices 0..15 = interpolators. We hold position + 8 // interpolator vec4s; `emit_export` writes the right slot keyed - // on the export index. Seed interp0 to white so a VS that only - // exports position still yields a visible (non-zero) color. + // on the export index. + // + // iterate-3AE (WHITE-TRIANGLE ROOT): interpolators a VS does NOT + // export must default to ZERO, not white. The old `ointerp[0] = + // (1,1,1,1)` was an iterate-3T debug convenience ("so a VS that + // only exports position still yields a visible non-zero color") + // — but it is a FAKE: it injects white that no guest value backs. + // The transition/background draws use the position-only VS + // `0xd4c14f46` (one vfetch → oPos; it exports NO color) paired + // with PS `0xed732b5a` (`ocolor0 = interp0`). With the white + // seed, interp0 stayed (1,1,1,1) → the fullscreen fill rendered + // OPAQUE WHITE (the diagonal half-triangle artifact that flashed + // before each splash logo and persisted across the dev-logo + // transition). Canary shows a black background there because the + // un-exported interpolator carries no white. Default to + // (0,0,0,0): a position-only VS now contributes nothing visible + // under its real (opaque or premultiplied) blend, matching + // canary, while every VS that really exports interp0 (the logo + // `0x03b7b020`, the `0x36660986` color fill) overwrites this seed + // and is unaffected. RGB=0 → black fill; A=0 → premultiplied + // overlays stay transparent. self.push("var opos: vec4 = vec4(0.0, 0.0, 0.0, 1.0);"); self.push("var ointerp: array, 8>;"); - self.push("for (var i = 0u; i < 8u; i = i + 1u) { ointerp[i] = vec4(0.0, 0.0, 0.0, 1.0); }"); - self.push("ointerp[0] = vec4(1.0, 1.0, 1.0, 1.0);"); + self.push("for (var i = 0u; i < 8u; i = i + 1u) { ointerp[i] = vec4(0.0, 0.0, 0.0, 0.0); }"); } Stage::Pixel => { // iterate-3T: the PS reads interpolator i from general register diff --git a/crates/xenia-ui/src/app.rs b/crates/xenia-ui/src/app.rs index a80b89b..ef8b3a9 100644 --- a/crates/xenia-ui/src/app.rs +++ b/crates/xenia-ui/src/app.rs @@ -369,7 +369,25 @@ impl ApplicationHandler for App { .map(|s| s.frame_index) .unwrap_or(0); if frame_idx != self.last_xenos_swap_frame { - rs.clear_frontbuffer([0.04, 0.04, 0.06, 1.0]); + // iterate-3AE: clear to BLACK, matching canary's + // splash background. The old navy `[0.04,0.04,0.06]` + // was an iterate-3S debug placeholder never matched + // to the guest. The splash background-fill draw is a + // full-screen Xbox-360 RectangleList (3 verts → a HW + // rectangle covering the whole screen); the UI replay + // draws it as a single triangle (the 4th implied + // corner isn't synthesized), so only the diagonal + // half is covered. With a navy clear the uncovered + // half showed a navy diagonal in the brief + // pre/inter-logo transition frames (where that fill + // is the only coverage). Canary's background there is + // black, and the guest's fill itself resolves to + // black, so a black clear makes the uncovered half + // match — the transition is uniformly black like the + // oracle. (Full RectangleList→rectangle expansion is + // the deeper fix and a separate follow-up; under a + // black clear the half-coverage is invisible.) + rs.clear_frontbuffer([0.0, 0.0, 0.0, 1.0]); self.last_xenos_swap_frame = frame_idx; } let delta = (draws_total - already) as u32;