ui: an <IMG> with an empty src names no file

`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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-11-01 12:59:29 -07:00
co-authored by Claude Opus 5
parent fb0d37e3c8
commit 315ebe9138
2 changed files with 53 additions and 0 deletions
@@ -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 {
@@ -552,6 +552,54 @@ fn the_books_unsized_crest_is_its_arts_texel_square_not_the_whole_page() {
);
}
/// `<IMG src="">` 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<RefCell<Vec<String>>> = 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("<HTML><BODY><P>a</P>" ..
"<IMG src=\"\"/>" ..
"<P>b</P></BODY></HTML>")"#,
)
.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]