From 315ebe913880585c09f049ab0150097ee5781509 Mon Sep 17 00:00:00 2001 From: grom Date: Sun, 1 Nov 2026 12:59:29 -0700 Subject: [PATCH] ui: an with an empty src names no file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `src=""` was stored verbatim, so the region carried `texture: Some("")`, the resolve asked the size probe for a nameless file and the renderer searched the patch chain for one and logged `texture miss: ''` — a defect-shaped warning that can name neither a file nor an asker. `""` clears on every other texture setter here, as the reference's path arm treats it like nil (`0x770200`), and `a9ed091eb` closed the three that had stored one; this was the last route in the UI that still could. Found by sweeping the rest rather than by a new sighting, and two of the leads this list carried are now dead: the `CursorPayload` arm it called most likely is unreachable in a live session (`extract/mod.rs:1020` guards the whole block with `if capture.is_some()`), and `PortraitSource::File` cannot be empty — `temporary_portrait` always returns a `.blp`. The M2 and WMO sides filter at parse, and `texture_url("")` would read `'.blp'` rather than `''`, so the warning's own text says the UI sprite path is where it came from. Whether this closes the one line in grom's log is unproven: it sat in `benilla-2.log` at +20.717s, that run may predate `a9ed091eb`, and three measurement launches have since rotated his logs out. The test is the next sighting: with the filter removed it reports `texture: Some("")`, and in the fixture the nameless path also reserved the probe's 48 px of flow under the image — in game the probe answers nothing for a miss, so only the chain search and the warning were real. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn --- .../benilla-ui/src/script/simplehtml/mod.rs | 5 ++ .../benilla-ui/src/script/tests/simplehtml.rs | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/crates/benilla-ui/src/script/simplehtml/mod.rs b/crates/benilla-ui/src/script/simplehtml/mod.rs index 1a6513cf..1e3772d0 100644 --- a/crates/benilla-ui/src/script/simplehtml/mod.rs +++ b/crates/benilla-ui/src/script/simplehtml/mod.rs @@ -288,6 +288,11 @@ fn build(model: &mut Model, fh: FrameHandle, blocks: &[Block]) { let anchors = point .map(|p| vec![anchor_for(prev_block, frame_id, p, next_y)]) .unwrap_or_default(); + // `src=""` names no file, as `""` does on every texture setter (`0x770200`). + // Kept, it was the one path left in the UI that could put an empty string where a + // texture path goes, and the renderer then searched the patch chain for a nameless + // file and warned about a miss it could name neither the file nor the asker of. + let src = src.clone().filter(|p| !p.is_empty()); model.region_data.insert( rh, RegionData { diff --git a/crates/benilla-ui/src/script/tests/simplehtml.rs b/crates/benilla-ui/src/script/tests/simplehtml.rs index 0da4387e..ed8f49b4 100644 --- a/crates/benilla-ui/src/script/tests/simplehtml.rs +++ b/crates/benilla-ui/src/script/tests/simplehtml.rs @@ -552,6 +552,54 @@ fn the_books_unsized_crest_is_its_arts_texel_square_not_the_whole_page() { ); } +/// `` names no file. `""` clears on every texture setter here, as the reference's path +/// arm treats it like nil (`0x770200`), but the SimpleHTML image path stored the empty string: the +/// region then carried `texture: Some("")`, the resolve asked the size probe for a nameless file, +/// and the renderer searched the patch chain and logged `texture miss: ''` — a defect-shaped +/// warning naming neither a file nor an asker. It was the last unguarded route in the UI. +#[test] +fn an_img_with_an_empty_src_names_no_file() { + use std::cell::RefCell; + use std::rc::Rc; + + let asked: Rc>> = Rc::new(RefCell::new(Vec::new())); + let seen = Rc::clone(&asked); + let mut s = script(); + s.set_texture_size_probe(Box::new(move |path| { + seen.borrow_mut().push(path.to_string()); + Some((64, 48)) + })); + page(&s); + s.run( + r#"Page:SetText("

a

" .. + "" .. + "

b

")"#, + ) + .unwrap(); + + let b = blocks(&s, "Page"); + assert_eq!( + b.len(), + 3, + "the image block is still made, art or none: {b:?}" + ); + assert!( + b.iter().all(|x| x.texture.as_deref() != Some("")), + "a nameless file was stored as this image's texture: {b:?}" + ); + assert!( + !asked.borrow().iter().any(|p| p.is_empty()), + "the patch chain was searched for a nameless file: {:?}", + asked.borrow() + ); + // And nothing is reserved in the flow for art that does not exist. + assert_eq!( + b[2].anchor.unwrap(), + (Point::TopLeft, Point::BottomLeft, 0.0, 0.0, Rel::Block(0)), + "an image with no art reserved height anyway" + ); +} + /// An unfloated image reserves `GetHeight()` in the flow (`0x78ad07`), its art's texel height when /// it has no `height=`; a floated one reserves nothing, so text runs over it in the reference too. #[test]