diff --git a/crates/benilla-app/src/nameplates.rs b/crates/benilla-app/src/nameplates.rs index 20359d2d..fc405925 100644 --- a/crates/benilla-app/src/nameplates.rs +++ b/crates/benilla-app/src/nameplates.rs @@ -74,12 +74,6 @@ const SCALE_FLOOR: f32 = 0.2; // [0x80679c] const SCALE_KNEE: f32 = 4.0; // [0x8112a8] const SCALE_RATE: f32 = 1.5; // [0x8112ac] -/// The plate cull's squared comparand — the reference's `[0xc4d988] = 400.0 = 20²` yd², written -/// once from `.rdata 0x80c448 = 20.0`, which no CVar registrar references: **hardcoded**, and -/// not a distance shrink either (plate size never scales with range — verified by exhaustive -/// absence). wow-re `nameplate-vkey.md` §5 / `nameplate-offscreen-cull.md` §2. -const NAMEPLATE_RANGE_SQ: f32 = 400.0; - /// benilla's nameplate config (the client's `0xce8720` cvar mask, reduced to what streams today), /// player-settable since 0992: 1.12's own `UnitNamePlayer`/`UnitNameNPC`/`UnitNameOwn` CVars over /// these gates (the Options window's Nameplates page, through the 0954 store — the arms live in @@ -371,9 +365,6 @@ pub(crate) fn drive_nameplates( Option<&InheritedVisibility>, )>, self_store: Query<&ObjectStore, With>, - // The 20-yard cap's anchor: the reference measures from the LOCAL PLAYER, not the camera - // (`0x60f600`'s 3-D squared distance). - self_tf: Query<&Transform, With>, // The show-gate inputs (one tuple param — Bevy's 16-param ceiling). gates: ( Res, @@ -468,23 +459,18 @@ pub(crate) fn drive_nameplates( if !drawn.is_none_or(|v| v.get()) { continue; } - // **The 20-yard cap** (wow-re `nameplate-offscreen-cull.md` §2, VERIFIED): `0x60f600` - // destroys the frame when the 3-D squared player→unit distance exceeds - // `[0xc4d988] = 400 = 20²` — hardcoded (`.rdata 0x80c448`, no CVar registrar ref), - // measured from the LOCAL PLAYER, never the camera, with no line-of-sight term. Beyond - // it the reference DESTROYS the plate rather than hiding it; skipping here keeps this - // unit out of `seen`, and the retain sweep below IS that destroy. The reference runs - // this gate LAST in its admission ladder (after the projection gate) — every rung is - // verdict-independent, so hoisting it changes no outcome, only how much of the ladder - // the far crowd pays. With no self body (a capture rig without a player) the gate - // stands down — the reference's "no local player ⇒ destroy" rung is the fixtures' call, - // not this one's (named residual, 1490). - if let Ok(anchor) = self_tf.single() { - if !is_self && tf.translation.distance_squared(anchor.translation) > NAMEPLATE_RANGE_SQ - { - continue; - } - } + // **There is NO distance cull here, and adding one is the mistake to not make twice.** + // The overhead NAME (`CGUnit+0xc7c`, a `PLAYERNAMEDESC`) and the V-key nameplate FRAME + // (`CGUnit+0xe60`) are two systems on the same unit, and only the FRAME carries the + // 20-yard cap (`0x60f600` vs `[0xc4d988] = 400`) that `vplates.rs` implements. This + // lane's own update/cull/build (`0x6c6d40`/`0x6c6e00`/`0x6c6e90`) holds **zero** distance + // compares — every early-out is identity/state-based, and the one FP compare is the + // height law above, not a depth term (wow-re `overhead-name.md` §Q5 + `playername.md`, + // both VERIFIED). A far name just gets small: it is a world billboard, apparent size ∝ + // scale/depth. The effective range is the server's interest management — no CGUnit, no + // desc. 1490 item 7 read the frame's law onto this lane and capped it at 20 yd; the + // director's Elwynn shot (named wolves up the hill) is the reference's own behaviour, and + // it outranked the mis-scoped citation. Superseded by 1492. // The ShouldShowName gate, in the client's own order (own-unit answers its cvar BEFORE // the rescue; everyone else: the current-TARGET bypass — `[0xb4e2d8]` is the selection, // not the mouseover — then the kind cvar). A unit carrying a V-key nameplate never also diff --git a/crates/benilla-app/src/ui_chat.rs b/crates/benilla-app/src/ui_chat.rs index b11b892b..a4a9d0e4 100644 --- a/crates/benilla-app/src/ui_chat.rs +++ b/crates/benilla-app/src/ui_chat.rs @@ -21,6 +21,8 @@ mod event; mod feed; mod frames; mod input; +/// The language gate — the exemptions and the fluency lookup behind the chat garble (B262). +mod language; #[cfg(test)] mod tests; @@ -41,6 +43,7 @@ impl Plugin for UiChatPlugin { .init_resource::() .init_resource::() .init_resource::() + .init_resource::() // `ChatChannels.dbc` — six rows, read once; the auto-join walk and every chat event's // arg7 both come out of it. **`.after(AssetSet::Open)` is load-bearing**: without it // this runs before the patch chain exists, takes its `assets: Option>` `None` @@ -60,6 +63,18 @@ impl Plugin for UiChatPlugin { .add_systems(PostStartup, commands::build_slash_commands) // Push before the input pass so a line is on screen the same frame it decodes (mirrors // the loot/merchant feeds). + // The language gate's two feeds, both upstream of the chat drain that reads them: the + // word pool loads once (it retries until the chain is up), and the fluency map + // rebuilds off the spell book + the self descriptors. + .add_systems( + Update, + ( + language::load_language_words, + language::feed_language_skills, + language::feed_default_language, + ) + .before(feed::feed_chat), + ) .add_systems(Update, feed::feed_chat.before(UiInput)) // A fresh VM gets the joined-channel mirror re-pushed once (decision 1291) — before // the feed, so the reload frame's first routed line already renders numbered. diff --git a/crates/benilla-app/src/ui_chat/event.rs b/crates/benilla-app/src/ui_chat/event.rs index b5e11762..2dda0e76 100644 --- a/crates/benilla-app/src/ui_chat/event.rs +++ b/crates/benilla-app/src/ui_chat/event.rs @@ -163,13 +163,14 @@ impl ChatEventKind { /// in the local list — see [`super::edit::ChannelState::stamp_channel`], which is the only place /// they are written. /// -/// **KNOWN GAP — arg1 should be GARBLED, and ours is not.** The reference garbles the body -/// client-side (`0x49b560` from `0x49aa7c`) and passes the wire text through only when the language -/// is Universal, there is no local player, or the player's skill in that language is ≥ 300 -/// (`0x49b599 cmp esi,0x12c`). benilla speaks Common and does not model language skill at all -/// (0288 §3 keeps garbling out with the language picker), so arg1 is always the ungarbled wire -/// text. An addon reading a foreign-language line therefore sees more than the reference would give -/// it. Also unmodeled: the profanity filter `0x4a1ca0` (`0x49ab23`), which in the reference can +/// **arg1 IS the garbled text** (B262, decision 1485). The reference fills `0x49a870`'s one buffer +/// exactly once — a plain `SStrCopy` at `0x49a9f0` or the garble `0x49b560` at `0x49aa7c` — and +/// never reads the raw wire pointer again, so every consumer downstream shares it: the chat line, +/// this event's arg1, and the bubble. **An addon receiving a foreign-language line cannot recover +/// the plaintext**, and no other slot in this ten-argument tuple carries the body. Ours now behaves +/// the same way ([`super::language`] owns the gate, [`benilla_formats::garble`] the substitution). +/// +/// Still unmodelled: the profanity filter `0x4a1ca0` (`0x49ab23`), which in the reference can /// suppress the whole event for non-whisper types. /// /// **Corrections on record.** This comment previously listed args 1-6, 8 and 9 only — arg7 and diff --git a/crates/benilla-app/src/ui_chat/feed.rs b/crates/benilla-app/src/ui_chat/feed.rs index f43d677b..f2415ec8 100644 --- a/crates/benilla-app/src/ui_chat/feed.rs +++ b/crates/benilla-app/src/ui_chat/feed.rs @@ -526,6 +526,19 @@ pub(super) fn deliver( } } +/// What the **speaker** does when a line lands — the over-the-head bubble and the talk/laugh +/// gesture, bundled because the reference arms both from the one display path (`ChatFrame.cpp`) and +/// because a Bevy system takes at most sixteen parameters, which [`feed_chat`] had already reached. +/// +/// They stay separate mechanisms inside the bundle: the bubble has a 20 yd range test and two CVars +/// ([`crate::chat_bubble`]), the gesture has neither (decision 1469). +#[derive(bevy::ecs::system::SystemParam)] +pub(super) struct SpeakerEffects<'w> { + bubbles: ResMut<'w, crate::chat_bubble::BubbleQueue>, + bubble_cfg: Res<'w, crate::chat_bubble::BubbleConfig>, + gestures: ResMut<'w, crate::creature_anim::GestureQueue>, +} + /// Drain [`ChatLog`]: resolve names (ask-once, bounded), build events, [`route`] them. Also ticks /// the whisper-chime throttle. #[allow(clippy::too_many_arguments)] // a Bevy system's param list IS its dependency set @@ -536,11 +549,7 @@ pub(super) fn feed_chat( mut edit: ResMut, mut channels: ResMut, mut names: ResMut, - mut bubbles: ResMut, - bubble_cfg: Res, - // The client-local talk gesture (decision 1469) — armed from this same display path, beside - // the bubble, exactly as the reference arms both from `ChatFrame.cpp`. - mut gestures: ResMut, + mut speaker: SpeakerEffects, commands: Res, time: Res