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).

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.
