MuninCore-1.0 — shared foundation for the Munin addon suite.

Not a standalone addon. Vendor this whole folder (Libs/MuninCore/) into
each consuming addon, and list Libs/MuninCore/MuninCore.lua in that
addon's .toc BEFORE the addon's own Lua files.

Multiple Munin addons can each embed their own copy; a small version
registry (top of MuninCore.lua) makes sure only the newest loaded copy
stays active, so nothing breaks when several Munin addons are installed
together.

Usage in a consuming addon file:

    local Core = _G.MuninCoreRegistry["MuninCore-1.0"].lib

    MyAddonDB = MyAddonDB or {}
    local box = Core.CreateBox("MyAddonFrame", MyAddonDB, {
        width = 140,
        height = 36,
    })
    box:SetText("hello")
    Core.RegisterSlash(box, "MyAddon", "myaddon")

API surface:
    Core.CreateBox(name, db, opts) -> box
        opts: defaultPoint, defaultX, defaultY, width, height, textPadding,
              resizable, minWidth, minHeight, maxWidth, maxHeight
        box:SetText(str)
        box:SetLocked(bool) / box:ToggleLocked() / box:IsLocked()
        box.frame   -- underlying Frame, for tools that need custom children
    Core.RegisterSlash(box, addonName, token, extraHandlers)
        builds /token lock|unlock|toggle, plus any extra subcommands
    Core.MergeDefaults(db, defaults)
        recursive fill of missing keys, never overwrites existing values
    Core.Throttle(frame, interval, fn)
        calls fn() every ~interval seconds while frame is shown; use for
        polling values with no reliable event (speed, coords)

Every box from Core.CreateBox automatically hides during cinematics/movies
(CINEMATIC_START/PLAY_MOVIE) and restores afterward -- only for boxes that
were actually shown when it started, so an already-hidden box stays hidden.
No per-addon setup needed; this is suite-wide by default (added MINOR 2).

Instance-category visibility gating (opt-in, added MINOR 3):
    Core.CreateBox(name, db, { ..., visibilityGating = true })
        Seeds db.settings.visibility = { world, pvp, arena, party, raid,
        scenario } (all true by default) and auto-hides/shows the box as the
        player enters/leaves those instance categories (World/PvP/Arena/
        Dungeon/Raid/Scenario -- "party" is WoW's own instanceType key for
        Dungeon). Edge-triggered on real category CHANGES only, not a
        continuous poll, so the user can still manually show the box at any
        time (e.g. a minimap-icon click) and it stays open until the next
        real zone-category transition.
    Core.AddVisibilityMenuItems(rootDescription, db, frame)
        Adds a "Show In" submenu of six checkboxes to an existing MenuUtil
        rootDescription, bound to db.settings.visibility. Use inside your
        own gear-menu generator function alongside a Locked checkbox. Pass
        the box's frame (added MINOR 5) so toggling a checkbox takes effect
        immediately via Core.ReapplyInstanceVisibility, instead of waiting
        for the next zone transition; frame is optional for backwards compat
        but should always be passed for a box using visibilityGating.
    Core.GetCurrentInstanceCategory()
        Returns the current category as one of "world"/"pvp"/"arena"/
        "party"/"raid"/"scenario", independent of any box.

Blizzard Settings panel for visibility gating (added MINOR 4):
    Core.CreateVisibilitySettingsPanel(addonName, db, frame)
        Registers a category under Options > AddOns > addonName with the
        same six "Show In" checkboxes as Core.AddVisibilityMenuItems, bound
        to the same db.settings.visibility table (both surfaces always
        agree). Unlike the gear-menu version, this is always reachable via
        Escape/Options even if the box itself is currently hidden -- use
        this alongside (not instead of) the gear-menu version so hiding a
        box in every category isn't a dead end. Returns the category.
    Core.ReapplyInstanceVisibility(frame)
        Re-evaluates one registered frame's visibility against the CURRENT
        category right now, instead of waiting for the next zone
        transition. CreateVisibilitySettingsPanel already wires this up
        automatically; call it yourself only if you toggle
        db.settings.visibility some other way and want it to take effect
        immediately.
    Core.IsInstanceVisible(db) -> bool (added MINOR 6)
        Returns whether the CURRENT category is allowed by db's visibility
        settings. Guard any frame:Show() your own addon calls from a polling
        loop or a login-time saved-state restore with this, so it doesn't
        undo Core's own instance-hide (e.g. MoveSpeed/Coordinates re-check
        this before their throttled poll shows the box). Not needed for a
        Show() that's a direct response to user input -- those are meant to
        override the gate.

Bumping the version:
    Any change to CreateBox/RegisterSlash/MergeDefaults/Throttle behavior
    that existing consumers should pick up requires bumping MINOR in
    MuninCore.lua, then re-copying this folder into every addon that
    embeds it. MAJOR only changes on a breaking API change.
