diff --git a/Cargo.lock b/Cargo.lock
index 1b03558c..fd91f862 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -536,6 +536,7 @@ dependencies = [
"objc2-foundation 0.2.2",
"rand 0.8.6",
"raw-window-handle",
+ "regex",
"rtrb",
"serde",
"smithay-clipboard",
diff --git a/crates/benilla-app/Cargo.toml b/crates/benilla-app/Cargo.toml
index dba96a5c..b8f530f9 100644
--- a/crates/benilla-app/Cargo.toml
+++ b/crates/benilla-app/Cargo.toml
@@ -58,6 +58,11 @@ half = "2"
# macro plus a no-dependency runtime shim — it adds nothing to the graph at runtime, and it is the
# crate everyone uses for this. `assets/ui` is OUR content (MIT/Apache), not Blizzard's.
include_dir = "0.7"
+# The 1.12 chat filters are PCRE over shipped DBCs (decision 2077; `text_filter`): 2289 profanity
+# expressions and 28 spam ones, compiled once at startup and run over every chat line. `RegexSet` is
+# exactly the shape that needs — one automaton over the whole list for the "can anything match at
+# all" pass — and the crate is already in the tree transitively.
+regex = "1"
# `getrusage` — whole-process CPU time (all threads), the perf probes' `cpu_ms`/`cpu_pct` metric
# (`perf::process_cpu_secs`). Wall-clock fps is unusable as a regression instrument on a machine
# running parallel sessions' builds; CPU-per-frame is, and it is also the number the Mac report
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index e1544e23..f5dd916f 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -295,6 +295,31 @@
end
end
+ -- ── Show Tutorials' SETTER: the reference's own Save arm (decision 2077) ────────────
+ -- UIOptionsFrame.lua l.318-327, verbatim in behaviour. Its CheckButtons row is a bare
+ -- `{ index = 28 }` with no cvar and no uvar, which is why decision 1140's census filed it
+ -- as an "index-only entry with no store at all" and left it out; that reading is wrong.
+ -- The store is not in the TABLE, it is in the three special-case arms `UIOptionsFrame_Load`
+ -- (l.239), `_Save` (l.318) and `_SetDefaults` (l.645) keep for this key — the getter is
+ -- `TutorialsEnabled()` and the setter is the Reset/Clear pair, because "disable tutorials"
+ -- in 1.12 literally means "mark every bit of the acknowledged bank", not a CVar (1976;
+ -- wow-re censused all 210 `CVar::Register` sites and there is no tutorial CVar).
+ --
+ -- The `~=` guard is the reference's and it matters: `TutorialsEnabled()` returns the NUMBER
+ -- 1 or nil, so re-ticking an already-on row does not re-`ResetTutorials()` — which would
+ -- re-arm every tutorial the player has already dismissed.
+ function OptionsFrame_SetTutorialsEnabled(value)
+ if ( tonumber(value) ~= TutorialsEnabled() ) then
+ if ( value == "1" ) then
+ ResetTutorials()
+ TutorialFrameCheckButton:SetChecked(1)
+ else
+ ClearTutorials()
+ end
+ TutorialFrame_HideAllAlerts()
+ end
+ end
+
UIOptionsFrameCheckButtons = { };
------------------- Basic Options -------------------
-- Controls
@@ -927,7 +952,12 @@
-- reference's own on-screen order. Its Basic Options Display box is three COLUMNS read
-- left to right: A gives Status Text (CheckButton2), B gives Show Cloak → Show Helm
-- (31 → 20, sitting under the name toggles that live on our Nameplates page), C gives
- -- the 42 → 39 → 66 chain. Help (29) closes the page, as before.
+ -- the 42 → 39 → 66 chain. The **Help** box closes the page, and since 2077 it is
+ -- the box's own order rather than its one shippable row: 28 (Show Tutorials), 44
+ -- (Show Loading Screen Tips, anchored under 28 there) then 29 (Show Detailed
+ -- Tooltips). 28 was left out by 1140's census as an "index-only entry
+ -- with no store at all"; that reading missed the three special-case arms
+ -- `UIOptionsFrame_Load`/`_Save`/`_SetDefaults` keep for the key, which are its store.
-- The target-of-target pair (decision 1576) breaks that left-to-right reading, and
-- it has to: 1.12 keeps it in a box this window has no page for — Advanced Options'
-- "Raid & Party" — so there is no column order to preserve, only a neighbour to pick.
@@ -936,7 +966,8 @@
"RowStatusText", "RowTargetOfTarget", "RowTargetOfTargetMode",
"RowShowCloak", "RowShowHelm",
"RowInstantQuestText", "RowBuffDurations", "RowAutoQuestWatch",
- "RowNewbieTips",
+ "RowProfanityFilter",
+ "RowShowTutorials", "RowTipOfTheDay", "RowNewbieTips",
},
-- 1.12's Advanced Options **Chat** box (decision 1589, B246 — "no chat section in
-- options"). The page was called `Social` and held exactly the two chat-bubble
@@ -954,17 +985,17 @@
-- · 51 Auto-join Guild Recruitment Channel — carries **no cvar, uvar or func in the
-- reference either** (UIOptionsFrame.lua l.51 is a bare `{ index = 51 }`), so
-- it is inert there; a working row here would be an invention, not a fix
- -- · 68 Disable Spam Filter — benilla filters no chat text. Unlike 51 this is a REAL
- -- omission and not an inert row: `spamFilter` is registered "1" at `0x402e8e`
- -- with a change callback, and its one reader is the chat chokepoint `0x49a870`,
- -- which drops a `CHAT_MSG_FILTERED` line on a word-list hit unless the type is
- -- whisper (§5, wow-re `system/ui/scratch/spam-filter-cvar.md`). The row lands
- -- with the filter.
+ -- Row 68, Disable Spam Filter, **shipped by 2077** — and the note it replaces was
+ -- wrong twice over, both corrected at the bytes by wow-re's `text-filter-law.md`.
+ -- The filter is not a word list, it is 28 compiled PCRE patterns from
+ -- `SpamMessages.dbc`; and it does not drop `CHAT_MSG_FILTERED` lines, it is armed for
+ -- every type EXCEPT that one (`0x49aad2`'s `jne` skips the disarm, which the older
+ -- note read backwards). It drops a matching line silently, with nothing in its place.
-- 1139's note about the 37 → 46 → 38 gap is closed by this record: 46 is Loot Spam,
-- and it has a knob now.
Chat = {
"RowGuildMemberAlert", "RowRemoveChatDelay", "RowChatBubbles", "RowLootSpam",
- "RowPartyChatBubbles",
+ "RowPartyChatBubbles", "RowSpamFilter",
},
-- 1.12's Advanced Options "Action Bars" group, whole (decision 1500 completing 1136).
-- Its six rows, the four bar toggles then the grid switch then the lock. That is the
@@ -3367,11 +3398,74 @@
"Automatic Quest Tracking", "OPTION_TOOLTIP_AUTO_QUEST_WATCH")
-
+
+
+ OptionsRow_OnLoad(self, "profanityFilter", "Profanity Filter",
+ "OPTION_TOOLTIP_PROFANITY_FILTER")
+
+
+
+
+
+
+
+
+
+ OptionsFuncRow_OnLoad(self, TutorialsEnabled,
+ OptionsFrame_SetTutorialsEnabled, "1",
+ "Show Tutorials", "OPTION_TOOLTIP_SHOW_TUTORIALS")
+
+
+
+
+
+
+
+
+ OptionsRow_OnLoad(self, "showGameTips",
+ "Show Loading Screen Tips",
+ "OPTION_TOOLTIP_SHOW_TIPOFTHEDAY")
+
+
+
+
+
+
OptionsGlobalRow_OnLoad(self, "SHOW_NEWBIE_TIPS",
"Show Detailed Tooltips", "OPTION_TOOLTIP_SHOW_NEWBIE_TIPS")
@@ -3481,6 +3575,34 @@
"Show Party Chat Bubbles", "OPTION_TOOLTIP_PARTY_CHAT_BUBBLES")
+
+
+
+
+
+
+
+ OptionsRow_OnLoad(self, "spamFilter", "Disable Spam Filter",
+ "OPTION_TOOLTIP_DISABLE_SPAM_FILTER")
+ self.invert = 1
+
+