diff --git a/crates/benilla-app/src/entities/display.rs b/crates/benilla-app/src/entities/display.rs index 66bc19ba..c096a160 100644 --- a/crates/benilla-app/src/entities/display.rs +++ b/crates/benilla-app/src/entities/display.rs @@ -227,6 +227,15 @@ pub(super) fn new_gameobject_display( asset_server: &AssetServer, ) -> DisplayModel { let Some(path) = catalog.model_path(display_id) else { + // One line per display id, since a display is built once and cached. A GameObject with no + // model draws nothing *and* cannot be hovered — the pick set is built from mesh parts + // (`GoPickPart`), so there is nothing for the ray to hit and no plate can follow. That is + // the shape "mousing over it gives no tooltip" takes for a server's own GameObject whose + // `GameObjectDisplayInfo` row this chain's DBC does not carry, a hardcore gravestone + // included. + info!( + "gameobject display {display_id} names no model — it draws nothing and cannot be picked" + ); return empty_display(); }; let handle = if path.to_ascii_lowercase().ends_with(".wmo") { diff --git a/crates/benilla-app/src/ui_tooltip/mod.rs b/crates/benilla-app/src/ui_tooltip/mod.rs index 70f335f7..c5c83636 100644 --- a/crates/benilla-app/src/ui_tooltip/mod.rs +++ b/crates/benilla-app/src/ui_tooltip/mod.rs @@ -569,6 +569,8 @@ pub(crate) struct HoverMemo<'s> { /// The last `(corpse guid, reason)` the corpse plate gave up on, so a hover that cannot draw /// one says why **once** instead of every frame it is held. corpse_miss: Local<'s, Option<(u64, &'static str)>>, + /// The last GameObject guid whose plate waited on a template, same rule. + go_miss: Local<'s, Option>, } /// Report a corpse plate this hover could not build, once per corpse and reason. @@ -803,7 +805,15 @@ fn drive_mouseover_tooltip( ); } let Some(template) = go_inputs.templates.get(guid).cloned() else { - // Template in flight: ask once, and show it when it lands. + // Template in flight: ask once, and show it when it lands. Said out loud once per + // object, because this is the plate's only silent exit and the one place a server's + // own GameObject — a hardcore gravestone, say — can go nameless: no + // `SMSG_GAMEOBJECT_QUERY_RESPONSE`, no plate, however well the pick landed. A line + // that never gets a sequel means the query went unanswered. + if *memo.go_miss != Some(guid) { + *memo.go_miss = Some(guid); + info!("go plate: {guid:#x} has no template yet — asked, drawing nothing"); + } go_inputs.templates.request(guid, &commands); return; };