<roblox version="4">
  <Item class="Folder" referent="0">
    <Properties>
      <string name="Name">RBLXE_Pack</string>
      <BinaryString name="AttributesSerialize">BAAAAAwAAABSQkxYRV9IYXNoZXMC3wUAACNyYmx4ZS1oYXNoZXMgdjEgZm52MWEzMiBsZgpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvQ29uZmlnCTlhMzY3Nzk0ClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9Db250cmFjdHMJMWVlZTkyNzMKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL0xvYWRlcgkyOWU1NWFmZQpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvTmV0CTgxZjhjMzIyClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9Ob3RpZnkJYjUxNDI1YWYKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1BhY2thZ2VzL1Ryb3ZlCWI1YTdlYWM5ClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9TRlgJODZjY2M2NDEKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1NjaGVtYQkwYTY4M2EzYwpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvU2lnbmFsCTRkZGRkZDJjClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9VSS9NZW51U3lzdGVtCTgzYmZiMGViClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9VSS9QYW5lbEFuaW1hdGlvbnMJODg0MTcxNjkKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1VJL1BhbmVsQW5pbWF0aW9ucy9Cb3VuY2UJNmE1MTJmMmIKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1VJL1BpZWNlcwk2OTFhNTA2ZgpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvVUkvUm9vdFBlcnNpc3RlbmNlCTE4NWMyNmUwClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9VSS9TaGVsbAlhOWVlZDUxMgpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvVmVyc2lvbgkwYzk3ZTBlZgpSZXBsaWNhdGVkU3RvcmFnZS9QYWNrcy9Db2Rlc1N5c3RlbS9DbGllbnQJYTBkYmFmNWQKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWVzc2FnZXMJYjRhODA3MDYKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9Gcm9tVjIJZWM2OTQ3NmEKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9JbnRlcmZhY2UJMDM2MjY0ZWMKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9LZWVwCTY4NmNlZjhmClJlcGxpY2F0ZWRTdG9yYWdlL1BhY2tzL0NvZGVzU3lzdGVtL1BpZWNlcwk3NzYyZDY4NApSZXBsaWNhdGVkU3RvcmFnZS9QYWNrcy9Db2Rlc1N5c3RlbS9TY2hlbWEJYzExYzhiZmQKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vbWFuaWZlc3QJYWMxYjkxMGUKU2VydmVyU2NyaXB0U2VydmljZS9LZXJuZWwJYWJiZmZmMmIKU2VydmVyU2NyaXB0U2VydmljZS9QYWNrcy9Db2Rlc1N5c3RlbQkwYzIzNTVkYgpTZXJ2ZXJTY3JpcHRTZXJ2aWNlL1BhY2tzL0NvZGVzU3lzdGVtL0NvZGVMaXN0CTNjOGY0NTJhClNlcnZlclNjcmlwdFNlcnZpY2UvUGFja3MvQ29kZXNTeXN0ZW0vQ29kZXMJMmZhMzMwYTgKU2VydmVyU2NyaXB0U2VydmljZS9QYWNrcy9Db2Rlc1N5c3RlbS9DdXN0b21SZXdhcmRzL0dyYW50UGV0CTBhZDg4NDlmClN0YXJ0ZXJQbGF5ZXJTY3JpcHRzL0tlcm5lbENsaWVudAlhMDA4ODJmMQgAAABSQkxYRV9JZAILAAAAQ29kZXNTeXN0ZW0TAAAAUkJMWEVfS2VybmVsVmVyc2lvbgIFAAAAMy4wLjANAAAAUkJMWEVfVmVyc2lvbgIFAAAAMy4wLjA=</BinaryString>
      <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
      <Color3 name="IconTint">
        <R>0</R>
        <G>0</G>
        <B>0</B>
      </Color3>
      <bool name="DefinesCapabilities">false</bool>
      <int64 name="SourceAssetId">-1</int64>
      <BinaryString name="Tags"></BinaryString>
    </Properties>
    <Item class="Folder" referent="1">
      <Properties>
        <string name="Name">ReplicatedStorage</string>
        <BinaryString name="AttributesSerialize"></BinaryString>
        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
        <Color3 name="IconTint">
          <R>0</R>
          <G>0</G>
          <B>0</B>
        </Color3>
        <bool name="DefinesCapabilities">false</bool>
        <int64 name="SourceAssetId">-1</int64>
        <BinaryString name="Tags"></BinaryString>
      </Properties>
      <Item class="Folder" referent="2">
        <Properties>
          <string name="Name">Kernel</string>
          <BinaryString name="AttributesSerialize">AQAAAAwAAABSQkxYRV9IYXNoZXMCNwMAACNyYmx4ZS1oYXNoZXMgdjEgZm52MWEzMiBsZgpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvQ29uZmlnCTlhMzY3Nzk0ClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9Db250cmFjdHMJMWVlZTkyNzMKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL0xvYWRlcgkyOWU1NWFmZQpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvTmV0CTgxZjhjMzIyClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9Ob3RpZnkJYjUxNDI1YWYKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1BhY2thZ2VzL1Ryb3ZlCWI1YTdlYWM5ClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9TRlgJODZjY2M2NDEKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1NjaGVtYQkwYTY4M2EzYwpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvU2lnbmFsCTRkZGRkZDJjClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9VSS9NZW51U3lzdGVtCTgzYmZiMGViClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9VSS9QYW5lbEFuaW1hdGlvbnMJODg0MTcxNjkKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1VJL1BhbmVsQW5pbWF0aW9ucy9Cb3VuY2UJNmE1MTJmMmIKUmVwbGljYXRlZFN0b3JhZ2UvS2VybmVsL1VJL1BpZWNlcwk2OTFhNTA2ZgpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvVUkvUm9vdFBlcnNpc3RlbmNlCTE4NWMyNmUwClJlcGxpY2F0ZWRTdG9yYWdlL0tlcm5lbC9VSS9TaGVsbAlhOWVlZDUxMgpSZXBsaWNhdGVkU3RvcmFnZS9LZXJuZWwvVmVyc2lvbgkwYzk3ZTBlZgpTZXJ2ZXJTY3JpcHRTZXJ2aWNlL0tlcm5lbAlhYmJmZmYyYgpTdGFydGVyUGxheWVyU2NyaXB0cy9LZXJuZWxDbGllbnQJYTAwODgyZjE=</BinaryString>
          <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
          <Color3 name="IconTint">
            <R>0</R>
            <G>0</G>
            <B>0</B>
          </Color3>
          <bool name="DefinesCapabilities">false</bool>
          <int64 name="SourceAssetId">-1</int64>
          <BinaryString name="Tags"></BinaryString>
        </Properties>
        <Item class="ModuleScript" referent="3">
          <Properties>
            <string name="Name">Config</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Config.lua - loads a pack's game-owned configuration (RBLX Essentials 3.0).
--
-- A pack describes its options in `Packs/<PackId>/Schema` (see Schema.lua).
-- The GAME owns the values, in modules no update ever replaces:
--   ReplicatedStorage.RBLXEssentials.Config.<PackId>     options clients may know
--   ServerScriptService.RBLXEssentials.Config.<PackId>   options marked server = true
-- Both return only what the game changes; the rest comes from the schema.
-- The merged value is validated and frozen. A pack whose configuration is
-- invalid does not boot: running with an unexpected price or product id is
-- worse than a clear error that names the option.
--
-- Pure: every Instance and the require function come in through `opts`.
local Config = {}

-- What a caller passed, for error text: an Instance's ClassName, or the Luau
-- type of anything else passed by mistake (a string, a table).
local function classOf(value)
	return if typeof(value) == "Instance" then value.ClassName else typeof(value)
end

local function loadModule(requireFn, module, label, errors)
	if module == nil then
		return nil
	end
	if typeof(module) ~= "Instance" or not module:IsA("ModuleScript") then
		table.insert(errors, ("%s must be a ModuleScript, got %s"):format(label, classOf(module)))
		return nil
	end
	local ok, value = pcall(requireFn, module)
	if not ok then
		table.insert(errors, ("%s errored: %s"):format(label, tostring(value)))
		return nil
	end
	if value ~= nil and type(value) ~= "table" then
		table.insert(errors, ("%s must return a table, got %s"):format(label, type(value)))
		return nil
	end
	return value
end

local function display(value)
	if type(value) == "table" then
		return "a table"
	end
	return type(value) == "string" and ("%q"):format(value) or tostring(value)
end

-- What Config.load calls on schemaLib: anything else is not the Schema module.
local SCHEMA_FUNCTIONS = {
	"check", "serverFields", "merge", "defaults", "stripServer", "validate", "formatErrors", "freeze",
}

local function isSchemaLib(S)
	if type(S) ~= "table" then
		return false
	end
	for _, name in ipairs(SCHEMA_FUNCTIONS) do
		if type(S[name]) ~= "function" then
			return false
		end
	end
	return true
end

function Config.load(opts)
	-- Tools calls this too. The opts a caller can get wrong (side, packId,
	-- schemaLib, requireFn and the three module arguments) come back as
	-- errors, never as a silent guess or a throw: a side typo used to mean
	-- "client" and skip the server module. A game Config that makes the
	-- merge itself throw (a table that contains itself) still throws, so
	-- callers keep a pcall, as the Loader does.
	if opts.side ~= "Server" and opts.side ~= "Client" then
		return nil, { ('side must be "Server" or "Client", got %s'):format(display(opts.side)) }
	end
	if type(opts.packId) ~= "string" or opts.packId == "" then
		return nil, { ("packId must be non-empty text, got %s"):format(display(opts.packId)) }
	end
	local S = opts.schemaLib
	if opts.schemaModule == nil then
		return nil, {}
	end
	if not isSchemaLib(S) then
		return nil, { ("schemaLib must be the Kernel Schema module, got %s"):format(typeof(S)) }
	end
	if type(opts.requireFn) ~= "function" then
		return nil, { "requireFn must be a function, got " .. typeof(opts.requireFn) }
	end
	if typeof(opts.schemaModule) ~= "Instance" or not opts.schemaModule:IsA("ModuleScript") then
		return nil, { ("Schema must be a ModuleScript, got %s"):format(classOf(opts.schemaModule)) }
	end

	local okFactory, factory = pcall(opts.requireFn, opts.schemaModule)
	if not okFactory then
		return nil, { ("Schema errored: %s"):format(tostring(factory)) }
	end
	if type(factory) ~= "function" then
		return nil, { "Schema must return function(S) ... end" }
	end
	local okRoot, root = pcall(factory, S)
	if not okRoot then
		return nil, { ("Schema errored: %s"):format(tostring(root)) }
	end
	local definitionErrors = S.check(root)
	if #definitionErrors > 0 then
		for index, message in ipairs(definitionErrors) do
			definitionErrors[index] = "Schema: " .. message
		end
		return nil, definitionErrors
	end

	local name = "Config." .. opts.packId
	local sharedLabel = "ReplicatedStorage.RBLXEssentials.Config." .. opts.packId
	local serverLabel = "ServerScriptService.RBLXEssentials.Config." .. opts.packId
	local errors = {}
	local shared = loadModule(opts.requireFn, opts.sharedModule, sharedLabel, errors)
	local server = nil
	if opts.side == "Server" then
		server = loadModule(opts.requireFn, opts.serverModule, serverLabel, errors)
	end
	if #errors > 0 then
		return nil, errors
	end

	local serverFields = S.serverFields(root)
	for key in pairs(shared or {}) do
		if serverFields[key] then
			table.insert(errors, ("%s.%s is server-only and must not reach clients: move it to %s"):format(name, tostring(key), serverLabel))
		end
	end
	for key in pairs(server or {}) do
		if root.fields[key] ~= nil and not serverFields[key] then
			table.insert(errors, ("%s.%s is not server-only: move it to %s"):format(name, tostring(key), sharedLabel))
		end
	end
	if #errors > 0 then
		table.sort(errors)
		return nil, errors
	end

	local value = S.merge(root, S.defaults(root), shared)
	if server ~= nil then
		value = S.merge(root, value, server)
	end
	local clientSide = opts.side == "Client"
	if clientSide then
		value = S.stripServer(root, value)
	end
	local problems = S.validate(root, value, name, { skipServer = clientSide })
	if #problems > 0 then
		return nil, S.formatErrors(problems)
	end
	return S.freeze(value), {}
end

return Config
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="ModuleScript" referent="4">
          <Properties>
            <string name="Name">Contracts</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Contracts.lua - interface piece contracts (RBLX Essentials 3.0).
--
-- A pack declares, in `Packs/<PackId>/Pieces`, every piece of interface it
-- drives and what that piece must contain. The declaration is plain data:
--
--   return {
--       CodesPanel = {
--           contract = 1,              -- bumped only by an incompatible change
--           kind = "Panel",            -- Panel | HudButton | HudWidget | Billboard | Layer
--           doc = "Panel for typing and redeeming codes.",
--           requires = { { path = "Container/Button", class = "GuiButton" } },
--           optional = { { path = "Container/ErrorText", class = "TextLabel" } },
--       },
--       CodesButton = { contract = 1, kind = "HudButton", doc = "...", opens = "CodesPanel" },
--   }
--
-- A piece root carries RBLXE_Piece = "<PackId>.<Name>" and RBLXE_Contract.
-- The same check runs in the Kernel (which copy to use) and in Tools (before
-- Play), so both always agree. Pure: Instances come in as arguments.
local Contracts = {}

-- Layer (plan B0): a ScreenGui that is itself the piece, such as a layer of
-- notification templates; like a Billboard piece it is its own entry in
-- Interface, never inside another ScreenGui.
Contracts.KINDS = {
	Panel = "GuiObject",
	HudButton = "GuiButton",
	HudWidget = "GuiObject",
	Billboard = "BillboardGui",
	Layer = "ScreenGui",
}
Contracts.PIECE_ATTRIBUTE = "RBLXE_Piece"
Contracts.CONTRACT_ATTRIBUTE = "RBLXE_Contract"

-- What MenuSystem needs to adopt and open a piece, checked on every piece of
-- that kind on top of its declared `requires`: a Panel is adopted through its
-- UI_Panel tag and registered under its PanelId; a HudButton is adopted
-- through its UI_Interactable tag and opens its panel with
-- Action = "TogglePanel" and TargetPanel. Without these a piece could win on
-- the server and still be unusable on every client. Tags are read with
-- Instance:HasTag, which @lune/roblox implements, so the check stays pure.
Contracts.IMPLICIT = {
	Panel = { tag = "UI_Panel", attributes = { PanelId = true } },
	HudButton = { tag = "UI_Interactable", attributes = { Action = "TogglePanel", TargetPanel = true } },
}

local function validateEntries(prefix, list, label, errors)
	if list == nil then
		return
	end
	if type(list) ~= "table" then
		table.insert(errors, ("%s.%s must be a list"):format(prefix, label))
		return
	end
	for index, entry in ipairs(list) do
		if type(entry) ~= "table" or type(entry.path) ~= "string" or entry.path == ""
			or type(entry.class) ~= "string" or entry.class == "" then
			table.insert(errors, ("%s.%s[%d] needs a non-empty path and class"):format(prefix, label, index))
		end
	end
end

function Contracts.validateSpecs(packId, specs)
	if type(specs) ~= "table" then
		return { ("%s.Pieces must return a table"):format(packId) }
	end
	local errors = {}
	for name, spec in pairs(specs) do
		local prefix = ("%s.Pieces.%s"):format(packId, tostring(name))
		if type(name) ~= "string" or not name:match("^[%a][%w_]*$") then
			table.insert(errors, ("%s: piece names must start with a letter and use letters, digits or _"):format(prefix))
		elseif type(spec) ~= "table" then
			table.insert(errors, ("%s must be a table"):format(prefix))
		else
			if type(spec.contract) ~= "number" or spec.contract < 1 or spec.contract % 1 ~= 0 then
				table.insert(errors, ("%s.contract must be a whole number >= 1"):format(prefix))
			end
			if Contracts.KINDS[spec.kind] == nil then
				table.insert(errors, ("%s.kind must be Panel, HudButton, HudWidget, Billboard or Layer"):format(prefix))
			end
			if type(spec.doc) ~= "string" or spec.doc == "" then
				table.insert(errors, ("%s.doc is required"):format(prefix))
			end
			validateEntries(prefix, spec.requires, "requires", errors)
			validateEntries(prefix, spec.optional, "optional", errors)
			if spec.opens ~= nil and (type(spec.opens) ~= "string" or specs[spec.opens] == nil) then
				table.insert(errors, ("%s.opens must name another piece of %s"):format(prefix, packId))
			end
		end
	end
	table.sort(errors)
	return errors
end

function Contracts.resolve(root, path)
	local node = root
	for segment in string.gmatch(path, "[^/]+") do
		node = node:FindFirstChild(segment)
		if node == nil then
			return nil
		end
	end
	return node
end

local function display(value)
	if type(value) == "string" then
		return ("%q"):format(value)
	elseif type(value) == "table" then
		return "a table"
	end
	return tostring(value)
end

-- What makes a spec unusable for check. validateSpecs already rejects all of
-- it for a pack's own Pieces module, but Tools also checks pieces against
-- specs nobody validated, and a check answers with problems, never a throw.
local function specProblems(spec)
	local function problem(name, expected, found)
		return { path = "(spec)", problem = "spec", name = name, expected = expected, found = display(found) }
	end
	if type(spec) ~= "table" then
		return { problem("spec", "a table", spec) }
	end
	local problems = {}
	if Contracts.KINDS[spec.kind] == nil then
		table.insert(problems, problem("kind", "Panel, HudButton, HudWidget, Billboard or Layer", spec.kind))
	end
	if type(spec.contract) ~= "number" or spec.contract < 1 or spec.contract % 1 ~= 0 then
		table.insert(problems, problem("contract", "a whole number >= 1", spec.contract))
	end
	for _, label in ipairs({ "requires", "optional" }) do
		local list = spec[label]
		if list ~= nil and type(list) ~= "table" then
			table.insert(problems, problem(label, "a list", list))
		elseif list ~= nil then
			for index, entry in ipairs(list) do
				if type(entry) ~= "table" or type(entry.path) ~= "string" or entry.path == ""
					or type(entry.class) ~= "string" or entry.class == "" then
					table.insert(problems, problem(("%s[%d]"):format(label, index), "a non-empty path and class", entry))
				end
			end
		end
	end
	return problems
end

function Contracts.check(root, spec)
	local problems = specProblems(spec)
	if typeof(root) ~= "Instance" then
		table.insert(problems, { path = "(root)", problem = "root", expected = "an Instance", found = display(root) })
	end
	if #problems > 0 then
		return problems
	end
	local rootClass = Contracts.KINDS[spec.kind]
	if not root:IsA(rootClass) then
		table.insert(problems, { path = "(root)", problem = "wrong class", expected = rootClass, found = root.ClassName })
	end
	local declared = root:GetAttribute(Contracts.CONTRACT_ATTRIBUTE)
	if declared ~= spec.contract then
		table.insert(problems, { path = "(root)", problem = "contract", expected = spec.contract, found = declared })
	end
	local implicit = Contracts.IMPLICIT[spec.kind]
	if implicit ~= nil then
		if not root:HasTag(implicit.tag) then
			table.insert(problems, { path = "(root)", problem = "tag", expected = implicit.tag })
		end
		local names = {}
		for name in pairs(implicit.attributes) do
			table.insert(names, name)
		end
		table.sort(names)
		for _, name in ipairs(names) do
			local want = implicit.attributes[name]
			local value = root:GetAttribute(name)
			if want == true then
				if type(value) ~= "string" or value == "" then
					table.insert(problems, { path = "(root)", problem = "attribute", name = name, expected = "non-empty text", found = value })
				end
			elseif value ~= want then
				table.insert(problems, { path = "(root)", problem = "attribute", name = name, expected = ("%q"):format(want), found = value })
			end
		end
	end
	for _, entry in ipairs(spec.requires or {}) do
		local found = Contracts.resolve(root, entry.path)
		if found == nil then
			table.insert(problems, { path = entry.path, problem = "missing", expected = entry.class })
		elseif not found:IsA(entry.class) then
			table.insert(problems, { path = entry.path, problem = "wrong class", expected = entry.class, found = found.ClassName })
		end
	end
	for _, entry in ipairs(spec.optional or {}) do
		local found = Contracts.resolve(root, entry.path)
		if found ~= nil and not found:IsA(entry.class) then
			table.insert(problems, { path = entry.path, problem = "wrong class", expected = entry.class, found = found.ClassName })
		end
	end
	return problems
end

function Contracts.formatProblems(problems)
	local parts = {}
	for _, p in ipairs(problems) do
		if p.problem == "missing" then
			table.insert(parts, ("missing %s (%s)"):format(p.path, p.expected))
		elseif p.problem == "contract" then
			table.insert(parts, ("contract version %s, expected %s"):format(tostring(p.found), tostring(p.expected)))
		elseif p.problem == "tag" then
			table.insert(parts, ("missing tag %s"):format(tostring(p.expected)))
		elseif p.problem == "attribute" then
			local found = type(p.found) == "string" and ("%q"):format(p.found) or tostring(p.found)
			table.insert(parts, ("attribute %s is %s, expected %s"):format(p.name, found, tostring(p.expected)))
		elseif p.problem == "spec" then
			table.insert(parts, ("invalid contract: %s is %s, expected %s"):format(p.name, p.found, p.expected))
		elseif p.problem == "root" then
			table.insert(parts, ("piece root is %s, expected %s"):format(p.found, p.expected))
		else
			table.insert(parts, ("%s is %s, expected %s"):format(p.path, tostring(p.found), tostring(p.expected)))
		end
	end
	return table.concat(parts, "; ")
end

function Contracts.splitId(pieceId)
	return string.match(pieceId, "^([^.]+)%.([^.]+)$")
end

return Contracts
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="ModuleScript" referent="5">
          <Properties>
            <string name="Name">Loader</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Boots installed packs in dependency order. Shared by Kernel (server) and
-- KernelClient. Pure: all roots come in via opts, so tests can point it at
-- fixture folders.
--
-- Plan B0 (phase B, before the Kernel 3.0.0 API freeze), in every ctx:
--   * companion(packId): another installed pack's frozen configuration for
--     this side and, once it booted, its API (handle:api()), or nil when
--     that pack is not installed. Optional by definition, like the Notify
--     and SFX facades: no manifest declaration, and never an instance path
--     into another pack;
--   * extension(name, defaults) / extensions(defaults): the game's own
--     modules for this pack, in <Service>.RBLXEssentials.Extensions.<PackId>
--     (a game zone no update or uninstall touches), before the pack's own
--     shipped ones in `defaults`;
--   * settings: only for a pack without a Schema (a game's own pack), with
--     a deprecation notice in report.notices; a pack with a Schema reads
--     ctx.config (and a notice says so when it still has a Settings folder).
local Loader = {}

-- RT-5: after its boot the server publishes which packs did not start, in
-- this attribute of ReplicatedStorage.RBLXEssentials (JSON { failed,
-- skipped }), so each client skips them instead of waiting on remotes a
-- server module never creates.
Loader.SERVER_REPORT_ATTRIBUTE = "RBLXE_ServerReport"

-- The report is attribute data, which Roblox does not store past a size
-- limit, while a reason can be long (a stack traceback, every configuration
-- error of a pack). The limit is PackFormat.HASHES_MAX_LENGTH's. A report
-- over it is encoded again with each reason cut to SERVER_REPORT_REASON_LENGTH
-- bytes, then with no reasons: clients need the ids, and the server output
-- keeps every reason whole.
Loader.SERVER_REPORT_MAX_LENGTH = 100000
Loader.SERVER_REPORT_REASON_LENGTH = 500

local DROPPED_REASON = "see the server output"

-- What clients read instead of a reason that may quote server-only values
-- (Loader.boot marks it in report.private): an invalid configuration names
-- the server Config's values (a repeated code, the text a check quoted), and
-- a server module's error can hold anything it read. The server output keeps
-- every reason whole; clients only need to know the pack did not start.
local PRIVATE_REASONS = {
	configuration = "invalid configuration (the server output names each problem; server-only values are never sent to clients)",
	error = "it errored while starting (the server output has the error; server-only values are never sent to clients)",
}

-- cutReason(reason) -> the reason, or its first SERVER_REPORT_REASON_LENGTH
-- bytes (never ending inside a UTF-8 character) and a note that it was cut.
local function cutReason(reason)
	local text = tostring(reason)
	local stop = Loader.SERVER_REPORT_REASON_LENGTH
	if #text <= stop then
		return text
	end
	-- step back over continuation bytes (0x80 to 0xBF) so the cut falls
	-- between two characters
	while stop > 0 and string.byte(text, stop + 1) >= 0x80 and string.byte(text, stop + 1) < 0xC0 do
		stop -= 1
	end
	return string.sub(text, 1, stop) .. " ... (cut; the server output has it whole)"
end

local function mapReasons(map, reasonFor)
	local out = {}
	for id, reason in pairs(map) do
		out[id] = reasonFor(reason)
	end
	return out
end

-- publicFailed(report) -> report.failed as clients may read it: each reason
-- report.private marks is replaced with its PRIVATE_REASONS text.
local function publicFailed(report)
	local private = if type(report.private) == "table" then report.private else {}
	local out = {}
	for id, reason in pairs(report.failed) do
		local kind = private[id]
		out[id] = if kind ~= nil then (PRIVATE_REASONS[kind] or DROPPED_REASON) else reason
	end
	return out
end

-- encodeServerReport(report, jsonEncode) -> JSON { failed, skipped } of at
-- most SERVER_REPORT_MAX_LENGTH bytes, for every client: a reason
-- report.private marks never leaves the server (publicFailed). Errors when
-- the ids alone do not fit: a client must never skip a shortened list.
function Loader.encodeServerReport(report, jsonEncode)
	report = { failed = publicFailed(report), skipped = report.skipped }
	local encoded = jsonEncode({ failed = report.failed, skipped = report.skipped })
	if #encoded <= Loader.SERVER_REPORT_MAX_LENGTH then
		return encoded
	end
	encoded = jsonEncode({ failed = mapReasons(report.failed, cutReason), skipped = mapReasons(report.skipped, cutReason) })
	if #encoded <= Loader.SERVER_REPORT_MAX_LENGTH then
		return encoded
	end
	local function dropped()
		return DROPPED_REASON
	end
	encoded = jsonEncode({ failed = mapReasons(report.failed, dropped), skipped = mapReasons(report.skipped, dropped) })
	if #encoded <= Loader.SERVER_REPORT_MAX_LENGTH then
		return encoded
	end
	error(string.format("the boot report is too long even without its reasons (%d bytes, limit %d)",
		#encoded, Loader.SERVER_REPORT_MAX_LENGTH), 0)
end

-- decodeServerReport(encoded, jsonDecode) -> { failed, skipped } or nil when
-- there is no usable report. HttpService writes an empty map as [], which
-- decodes to an empty table.
function Loader.decodeServerReport(encoded, jsonDecode)
	if type(encoded) ~= "string" then
		return nil
	end
	local ok, decoded = pcall(jsonDecode, encoded)
	if not ok or type(decoded) ~= "table" then
		return nil
	end
	return {
		failed = if type(decoded.failed) == "table" then decoded.failed else {},
		skipped = if type(decoded.skipped) == "table" then decoded.skipped else {},
	}
end

-- xpcall error handler: appends a stack traceback to the error message when
-- `debug.traceback` is available (real Roblox + the Lune test harness both
-- have it), falling back to a plain tostring() if some sandboxed environment
-- doesn't provide `debug`.
local function tracebackHandler(err)
	if debug and debug.traceback then
		return debug.traceback(tostring(err), 2)
	end
	return tostring(err)
end

local function readManifests(sharedPacks, requireImpl)
	local manifests, errors = {}, {}
	for _, packFolder in ipairs(sharedPacks:GetChildren()) do
		local manifestModule = packFolder:FindFirstChild("manifest")
		if not manifestModule or not manifestModule:IsA("ModuleScript") then
			errors[packFolder.Name] = "no manifest ModuleScript"
			continue
		end
		local ok, manifest = pcall(requireImpl, manifestModule)
		if not ok then
			errors[packFolder.Name] = "manifest error: " .. tostring(manifest)
			continue
		end
		if type(manifest) ~= "table" or type(manifest.id) ~= "string" then
			errors[packFolder.Name] = "manifest must return a table with a string id"
			continue
		end
		if manifest.id ~= packFolder.Name then
			errors[packFolder.Name] = string.format("manifest id %q does not match folder name", manifest.id)
			continue
		end
		manifest = table.clone(manifest)
		manifest.deps = manifest.deps or {}
		manifest.folder = packFolder
		manifests[manifest.id] = manifest
	end
	return manifests, errors
end

local function resolveOrder(manifests)
	local skipped = {}

	-- transitively drop packs whose declared deps are not installed
	local changed = true
	while changed do
		changed = false
		for id, m in pairs(manifests) do
			if not skipped[id] then
				for _, dep in ipairs(m.deps) do
					if not manifests[dep] or skipped[dep] then
						-- Whether dep is simply absent, or exists but was itself already
						-- skipped by an earlier fixed-point pass, the reason is the same:
						-- this pack can't run because dep isn't available. Cycle detection
						-- (and the "dependency cycle" reason) happens later, in the
						-- post-Kahn indegree sweep below -- never here.
						skipped[id] = "missing dependency: " .. dep
						changed = true
						break
					end
				end
			end
		end
	end

	-- Kahn topological sort (alphabetical tie-break for determinism)
	local order, indegree, dependents = {}, {}, {}
	for id in pairs(manifests) do
		if not skipped[id] then indegree[id] = 0 end
	end
	for id, m in pairs(manifests) do
		if not skipped[id] then
			for _, dep in ipairs(m.deps) do
				indegree[id] += 1
				dependents[dep] = dependents[dep] or {}
				table.insert(dependents[dep], id)
			end
		end
	end
	local queue = {}
	for id, deg in pairs(indegree) do
		if deg == 0 then table.insert(queue, id) end
	end
	table.sort(queue)
	while #queue > 0 do
		local id = table.remove(queue, 1)
		table.insert(order, id)
		for _, dependent in ipairs(dependents[id] or {}) do
			indegree[dependent] -= 1
			if indegree[dependent] == 0 then
				local pos = #queue + 1
				for i, q in ipairs(queue) do
					if dependent < q then pos = i break end
				end
				table.insert(queue, pos, dependent)
			end
		end
	end
	for id, deg in pairs(indegree) do
		if deg > 0 and not skipped[id] then
			skipped[id] = "dependency cycle"
		end
	end
	return order, skipped
end

-- moduleNamed(parent, name): the first ModuleScript child called `name`,
-- past any other child of that name (FindFirstChild would stop at a Folder).
local function moduleNamed(parent, name)
	for _, child in ipairs(parent and parent:GetChildren() or {}) do
		if child.Name == name and child:IsA("ModuleScript") then
			return child
		end
	end
	return nil
end

-- extension(root, packId, name, defaults?) -> (module?, from?): the game's
-- module <root>.<packId>.<name> ("game"), else defaults.<name> ("pack");
-- only a ModuleScript counts. `root` is the side's Extensions folder.
function Loader.extension(root, packId, name, defaults)
	local found = moduleNamed(root and root:FindFirstChild(packId), name)
	if found ~= nil then
		return found, "game"
	end
	local shipped = moduleNamed(defaults, name)
	if shipped ~= nil then
		return shipped, "pack"
	end
	return nil, nil
end

-- extensions(root, packId, defaults?) -> { { name, module, from } }: every
-- module of both, one per name: the pack's own in their order (the game's
-- module winning a name it shares), then the game's additions in theirs.
function Loader.extensions(root, packId, defaults)
	local list, taken = {}, {}
	local own = root and root:FindFirstChild(packId)
	for _, child in ipairs(defaults and defaults:GetChildren() or {}) do
		if child:IsA("ModuleScript") and not taken[child.Name] then
			taken[child.Name] = true
			local module, from = Loader.extension(root, packId, child.Name, defaults)
			table.insert(list, { name = child.Name, module = module, from = from })
		end
	end
	for _, child in ipairs(own and own:GetChildren() or {}) do
		if child:IsA("ModuleScript") and not taken[child.Name] then
			taken[child.Name] = true
			table.insert(list, { name = child.Name, module = child, from = "game" })
		end
	end
	return list
end

function Loader.boot(opts)
	local requireImpl = opts.requireFn or require
	-- report.private[id]: why report.failed[id] may quote server-only values
	-- ("configuration" or "error"); encodeServerReport never sends such a
	-- reason to clients, and the server output prints it whole.
	-- report.notices: one line per pack that still gets ctx.settings (plan
	-- B0), or whose Settings folder its Schema leaves unread, printed once
	-- by the Kernel scripts.
	local report = { booted = {}, failed = {}, skipped = {}, private = {}, notices = {} }
	local manifests, manifestErrors = readManifests(opts.sharedPacks, requireImpl)
	for name, err in pairs(manifestErrors) do
		report.failed[name] = err
	end
	local order, skipped = resolveOrder(manifests)
	for id, reason in pairs(skipped) do
		report.skipped[id] = reason
	end

	local apis = {}
	-- Each pack's configuration is loaded once, on its own turn below or
	-- earlier through a companion handle, and shared by both.
	local configs = {}
	local function loadConfig(packId)
		local cached = configs[packId]
		if cached ~= nil then
			return cached
		end
		local m = manifests[packId]
		local configRoots = opts.configRoots or {}
		local ok, value, errors = pcall(opts.configLib.load, {
			packId = packId,
			side = opts.side,
			requireFn = requireImpl,
			schemaLib = opts.schemaLib,
			schemaModule = m.folder:FindFirstChild("Schema"),
			sharedModule = configRoots.shared and configRoots.shared:FindFirstChild(packId) or nil,
			serverModule = configRoots.server and configRoots.server:FindFirstChild(packId) or nil,
		})
		cached = { ok = ok, value = value, errors = errors }
		configs[packId] = cached
		return cached
	end
	-- One frozen handle per companion (plan B0, ruling 4).
	local companions = {}
	local function companionOf(packId)
		local handle = companions[packId]
		if handle ~= nil then
			return handle
		end
		-- No config for a pack whose pieces failed on this side or, on a
		-- client, one the server failed or skipped (nothing it declares is in
		-- force there). Otherwise its config, even for a pack this side skips
		-- or that fails later: handle.api() says whether it booted. The
		-- handle is made once, at the first call.
		local stopped = (opts.pieceFailures ~= nil and opts.pieceFailures[packId] ~= nil)
			or (opts.serverReport ~= nil and (opts.serverReport.failed[packId] ~= nil or opts.serverReport.skipped[packId] ~= nil))
		local config = nil
		if opts.configLib and not stopped then
			local loaded = loadConfig(packId)
			if loaded.ok and type(loaded.errors) == "table" and #loaded.errors == 0 then
				config = loaded.value
			end
		end
		handle = table.freeze({
			id = packId,
			version = manifests[packId].version,
			config = config,
			api = function()
				return apis[packId]
			end,
		})
		companions[packId] = handle
		return handle
	end
	for _, id in ipairs(order) do
		local m = manifests[id]
		local blockedBy
		for _, dep in ipairs(m.deps) do
			if report.failed[dep] or (report.skipped[dep] and not apis[dep]) then
				blockedBy = dep
				break
			end
		end
		if blockedBy then
			report.skipped[id] = "dependency failed: " .. blockedBy
			continue
		end

		-- 3.0 (RT-6): a pack whose Pieces declaration is invalid does not
		-- boot: its pieces can never be resolved (Pieces.collectDeclarations).
		local pieceFailure = opts.pieceFailures and opts.pieceFailures[id]
		if pieceFailure ~= nil then
			report.failed[id] = pieceFailure
			continue
		end
		-- 3.0 (RT-5): on a client, a pack that did not start on the server
		-- does not start either: it would wait for remotes nobody creates.
		local serverReason = opts.serverReport and (opts.serverReport.failed[id] or opts.serverReport.skipped[id])
		if serverReason ~= nil then
			report.failed[id] = "did not start on the server: " .. tostring(serverReason)
			continue
		end

		-- 3.0: game-owned configuration (Config.lua). A pack whose configuration
		-- is invalid fails like a pack whose init errors, so its dependents are
		-- skipped instead of booting against a half-configured dependency.
		-- The load runs in its own pcall: a schema or a Config module that
		-- makes the load throw (a self-referencing table, anything Schema.check
		-- did not foresee) fails this pack alone, never the rest of the boot.
		-- A companion handle may have loaded it already (loadConfig).
		local config = nil
		if opts.configLib then
			local loaded = loadConfig(id)
			if not loaded.ok then
				report.failed[id] = "invalid configuration: " .. tostring(loaded.value)
				report.private[id] = "configuration"
				continue
			end
			if #loaded.errors > 0 then
				report.failed[id] = "invalid configuration:\n    " .. table.concat(loaded.errors, "\n    ")
				report.private[id] = "configuration"
				continue
			end
			config = loaded.value
		end

		local module
		if opts.side == "Server" then
			module = opts.serverPacks and opts.serverPacks:FindFirstChild(id)
		else
			module = m.folder:FindFirstChild("Client")
		end
		if not module or not module:IsA("ModuleScript") then
			-- pack ships no code for this side - still counts as booted
			apis[id] = {}
			table.insert(report.booted, id)
			continue
		end

		-- A pack with a Schema reads ctx.config and gets no ctx.settings; a
		-- pack without one (a game's own pack) keeps its 2.x Settings folder,
		-- and the report says once that this is deprecated (plan B0, ruling 5).
		-- A Settings folder next to a Schema (a hand copy: T.update never
		-- takes a 2.x pack to 3.x) is not read, and the report says so.
		local settings = nil
		if m.folder:FindFirstChild("Schema") == nil then
			settings = m.folder:FindFirstChild("Settings")
			if settings ~= nil then
				table.insert(report.notices, string.format(
					"%s reads its 2.x Settings through ctx.settings, which is deprecated: an official pack stops using it when it is updated to 3.x; a game's own pack should move to a Schema and ctx.config", id))
			end
		elseif m.folder:FindFirstChild("Settings") ~= nil then
			table.insert(report.notices, string.format(
				"%s has a Schema, so its Settings folder is not read: the pack runs on Config.%s. Set there any value the game changed in Settings, then delete that folder", id, id))
		end
		local ctx = {
			packId = id,
			version = m.version,
			folder = m.folder,
			serverFolder = (opts.side == "Server") and module or nil,
			settings = settings,
			config = config,
			-- Plan B0: another installed pack's config and API (nil when it is
			-- not installed), and the game's own modules for this pack.
			companion = function(otherId)
				if type(otherId) ~= "string" or otherId == id or manifests[otherId] == nil then
					return nil
				end
				return companionOf(otherId)
			end,
			extension = function(name, defaults)
				return Loader.extension(opts.extensions, id, name, defaults)
			end,
			extensions = function(defaults)
				return Loader.extensions(opts.extensions, id, defaults)
			end,
			kernel = opts.kernelApi,
			ui = opts.ui,
			-- 3.0: this pack's own interface piece, already resolved by the Kernel.
			getPiece = opts.ui and function(name, timeout)
				return opts.ui:getPiece(id .. "." .. name, timeout)
			end or nil,
			require = function(depId)
				local declared = false
				for _, dep in ipairs(m.deps) do
					if dep == depId then declared = true break end
				end
				assert(declared, string.format(
					"[RBLXE] %s tried to require %s without declaring it in manifest.deps", id, depId))
				return apis[depId]
			end,
		}

		local ok, initOrErr = xpcall(requireImpl, tracebackHandler, module)
		if ok and type(initOrErr) ~= "function" then
			ok, initOrErr = false, "module must return function(ctx)"
		end
		if ok then
			local api
			ok, api = xpcall(initOrErr, tracebackHandler, ctx)
			if ok then
				apis[id] = api or {}
				table.insert(report.booted, id)
			else
				report.failed[id] = tostring(api)
				report.private[id] = "error"
			end
		else
			report.failed[id] = tostring(initOrErr)
			report.private[id] = "error"
		end
	end
	return report
end

function Loader.formatReport(side, report)
	local function keys(map)
		local out = {}
		for k in pairs(map) do table.insert(out, k) end
		table.sort(out)
		return table.concat(out, ",")
	end
	return string.format("[RBLXE] %s booted: %s | failed: %s | skipped: %s",
		side, table.concat(report.booted, ","), keys(report.failed), keys(report.skipped))
end

return Loader
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="ModuleScript" referent="6">
          <Properties>
            <string name="Name">Net</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Per-pack remote namespaces under RS/RBLXEssentials/Kernel/Remotes/<PackId>/.
-- Server creates on demand; clients wait for the server to create.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Net = {}

local function kernelFolder()
	return ReplicatedStorage:WaitForChild("RBLXEssentials"):WaitForChild("Kernel")
end

local function serverGet(packId, className, name)
	local kernel = kernelFolder()
	local root = kernel:FindFirstChild("Remotes")
	if not root then
		root = Instance.new("Folder"); root.Name = "Remotes"; root.Parent = kernel
	end
	local packFolder = root:FindFirstChild(packId)
	if not packFolder then
		packFolder = Instance.new("Folder"); packFolder.Name = packId; packFolder.Parent = root
	end
	local remote = packFolder:FindFirstChild(name)
	if remote then
		if remote.ClassName ~= className then
			error(string.format("[RBLXE Net] %s/%s already exists as %s", packId, name, remote.ClassName))
		end
		return remote
	end
	remote = Instance.new(className)
	remote.Name = name
	remote.Parent = packFolder
	return remote
end

function Net.server(packId)
	assert(RunService:IsServer(), "[RBLXE Net] Net.server is server-only")
	return {
		event = function(_, name) return serverGet(packId, "RemoteEvent", name) end,
		func = function(_, name) return serverGet(packId, "RemoteFunction", name) end,
	}
end

function Net.client(packId)
	assert(RunService:IsClient(), "[RBLXE Net] Net.client is client-only")

	-- waitFor(parent, name, deadline) -> the child, or nil once `deadline`
	-- (an os.clock time) has passed. Waits in slices of at most 10 s and
	-- warns once when it has to wait longer than one slice, instead of
	-- yielding with no word. Without a deadline it waits as long as it
	-- takes, as 2.x packs expect.
	local function waitFor(parent, name, deadline)
		local child = parent:FindFirstChild(name)
		local warned = false
		while child == nil do
			local slice = 10
			if deadline ~= nil then
				local left = deadline - os.clock()
				if left <= 0 then
					return nil
				end
				slice = math.min(slice, left)
			end
			child = parent:WaitForChild(name, slice)
			if child == nil and not warned and (deadline == nil or os.clock() < deadline) then
				warned = true
				warn(string.format(
					"[RBLXE] Net.client(%q) still waiting for %s.%s. Is the pack's server module installed and started?",
					packId, parent:GetFullName(), name))
			end
		end
		return child
	end

	-- lookup(name, timeout?) -> the remote, or nil with a warning once
	-- `timeout` seconds have passed (RT-5): one deadline for the Remotes
	-- folder, the pack's folder and the remote itself.
	local function lookup(name, timeout)
		local deadline = if type(timeout) == "number" then os.clock() + timeout else nil
		local remotes = waitFor(kernelFolder(), "Remotes", deadline)
		local packFolder = remotes and waitFor(remotes, packId, deadline)
		local remote = packFolder and waitFor(packFolder, name, deadline)
		if remote == nil then
			warn(string.format(
				"[RBLXE] Net.client(%q): remote %q did not appear within %ss; the pack's server module did not start or never creates it",
				packId, name, tostring(timeout)))
		end
		return remote
	end
	return {
		event = function(_, name, timeout) return lookup(name, timeout) end,
		func = function(_, name, timeout) return lookup(name, timeout) end,
	}
end

return Net
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="ModuleScript" referent="7">
          <Properties>
            <string name="Name">Notify</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Notification facade. Packs call Notify:Toast(...) freely; it is a no-op
-- until a backend registers (Notifications System v2 calls setBackend from
-- its server and client inits).
--
-- WHY THIS IS A FACADE AND NOT A REQUIRE: every pack in the ecosystem wants
-- to announce something, and Notifications System is optional. Without this,
-- five packs would each carry the same twenty lines of FindFirstChild +
-- pcall + degrade - and a pack that declared it in manifest.deps would make
-- an optional companion mandatory, since the plugin refuses to install a
-- pack whose declared deps are missing. Same reasoning, same shape, as
-- SFX.lua, which solved this once already for sound.
--
-- Server methods take a `player`; the client backend's do not (a client only
-- ever notifies its own player), which is why a missing method has to be a
-- no-op rather than an error.
local Notify = {}
local backend = nil

-- A missing method behaves exactly like a missing backend: the client
-- backend has no ChatTo/ToastAll/BannerAll, and a pack must not have to know
-- which side it is on. pcall because a notification is fire-and-forget - a
-- bug in the renderer must never surface as an error inside the pack that
-- called it.
local function forward(method, ...)
	if not backend then
		return
	end
	local fn = backend[method]
	if type(fn) ~= "function" then
		return
	end
	local ok, err = pcall(fn, backend, ...)
	if not ok then
		warn("[RBLXE Notify] backend:" .. method .. " failed: " .. tostring(err))
	end
end

function Notify:Toast(...)
	forward("Toast", ...)
end

function Notify:ToastAll(...)
	forward("ToastAll", ...)
end

function Notify:Chat(...)
	forward("Chat", ...)
end

function Notify:ChatTo(...)
	forward("ChatTo", ...)
end

function Notify:Banner(...)
	forward("Banner", ...)
end

function Notify:BannerAll(...)
	forward("BannerAll", ...)
end

function Notify.setBackend(b)
	backend = b
end

return Notify
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="Folder" referent="8">
          <Properties>
            <string name="Name">Packages</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <Color3 name="IconTint">
              <R>0</R>
              <G>0</G>
              <B>0</B>
            </Color3>
            <bool name="DefinesCapabilities">false</bool>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
          <Item class="ModuleScript" referent="9">
            <Properties>
              <string name="Name">Trove</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[--!strict

local RunService = game:GetService("RunService")

export type Trove = {
	Extend: (self: Trove) -> Trove,
	Clone: <T>(self: Trove, instance: T & Instance) -> T,
	Construct: <T, A...>(self: Trove, class: Constructable<T, A...>, A...) -> T,
	Connect: (self: Trove, signal: SignalLike | RBXScriptSignal, fn: (...any) -> ...any) -> ConnectionLike,
	BindToRenderStep: (self: Trove, name: string, priority: number, fn: (dt: number) -> ()) -> (),
	AddPromise: <T>(self: Trove, promise: T & PromiseLike) -> T,
	Add: <T>(self: Trove, object: T & Trackable, cleanupMethod: string?) -> T,
	Remove: <T>(self: Trove, object: T & Trackable) -> boolean,
	Clean: (self: Trove) -> (),
	WrapClean: (self: Trove) -> () -> (),
	AttachToInstance: (self: Trove, instance: Instance) -> RBXScriptConnection,
	Destroy: (self: Trove) -> (),
}

type TroveInternal = Trove & {
	_objects: { any },
	_cleaning: boolean,
	_findAndRemoveFromObjects: (self: TroveInternal, object: any, cleanup: boolean) -> boolean,
	_cleanupObject: (self: TroveInternal, object: any, cleanupMethod: string?) -> (),
}

--[=[
	@within Trove
	@type Trackable Instance | RBXScriptConnection | ConnectionLike | PromiseLike | thread | ((...any) -> ...any) | Destroyable | DestroyableLowercase | Disconnectable | DisconnectableLowercase
	Represents all trackable objects by Trove.
]=]
export type Trackable =
	Instance
	| RBXScriptConnection
	| ConnectionLike
	| PromiseLike
	| thread
	| ((...any) -> ...any)
	| Destroyable
	| DestroyableLowercase
	| Disconnectable
	| DisconnectableLowercase

--[=[
	@within Trove
	@interface ConnectionLike
	.Connected boolean
	.Disconnect (self) -> ()
]=]
type ConnectionLike = {
	Connected: boolean,
	Disconnect: (self: ConnectionLike) -> (),
}

--[=[
	@within Trove
	@interface SignalLike
	.Connect (self, callback: (...any) -> ...any) -> ConnectionLike
	.Once (self, callback: (...any) -> ...any) -> ConnectionLike
]=]
type SignalLike = {
	Connect: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike,
	Once: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike,
}

--[=[
	@within Trove
	@interface PromiseLike
	.getStatus (self) -> string
	.finally (self, callback: (...any) -> ...any) -> PromiseLike
	.cancel (self) -> ()
]=]
type PromiseLike = {
	getStatus: (self: PromiseLike) -> string,
	finally: (self: PromiseLike, callback: (...any) -> ...any) -> PromiseLike,
	cancel: (self: PromiseLike) -> (),
}

--[=[
	@within Trove
	@type Constructable { new: (A...) -> T } | (A...) -> T
]=]
type Constructable<T, A...> = { new: (A...) -> T } | (A...) -> T

--[=[
	@within Trove
	@interface Destroyable
	.disconnect (self) -> ()
]=]
type Destroyable = {
	Destroy: (self: Destroyable) -> (),
}

--[=[
	@within Trove
	@interface DestroyableLowercase
	.disconnect (self) -> ()
]=]
type DestroyableLowercase = {
	destroy: (self: DestroyableLowercase) -> (),
}

--[=[
	@within Trove
	@interface Disconnectable
	.disconnect (self) -> ()
]=]
type Disconnectable = {
	Disconnect: (self: Disconnectable) -> (),
}

--[=[
	@within Trove
	@interface DisconnectableLowercase
	.disconnect (self) -> ()
]=]
type DisconnectableLowercase = {
	disconnect: (self: DisconnectableLowercase) -> (),
}

local FN_MARKER = newproxy()
local THREAD_MARKER = newproxy()
local GENERIC_OBJECT_CLEANUP_METHODS = table.freeze({ "Destroy", "Disconnect", "destroy", "disconnect" })

local function GetObjectCleanupFunction(object: any, cleanupMethod: string?)
	local t = typeof(object)

	if t == "function" then
		return FN_MARKER
	elseif t == "thread" then
		return THREAD_MARKER
	end

	if cleanupMethod then
		return cleanupMethod
	end

	if t == "Instance" then
		return "Destroy"
	elseif t == "RBXScriptConnection" then
		return "Disconnect"
	elseif t == "table" then
		for _, genericCleanupMethod in GENERIC_OBJECT_CLEANUP_METHODS do
			if typeof(object[genericCleanupMethod]) == "function" then
				return genericCleanupMethod
			end
		end
	end

	error(`failed to get cleanup function for object {t}: {object}`, 3)
end

local function AssertPromiseLike(object: any)
	if
		typeof(object) ~= "table"
		or typeof(object.getStatus) ~= "function"
		or typeof(object.finally) ~= "function"
		or typeof(object.cancel) ~= "function"
	then
		error("did not receive a promise as an argument", 3)
	end
end

--[=[
	@class Trove
	A Trove is helpful for tracking any sort of object during
	runtime that needs to get cleaned up at some point.
]=]
local Trove = {}
Trove.__index = Trove

--[=[
	@return Trove
	Constructs a Trove object.

	```lua
	local trove = Trove.new()
	```
]=]
function Trove.new(): Trove
	local self = setmetatable({}, Trove)

	self._objects = {}
	self._cleaning = false

	return (self :: any) :: Trove
end

--[=[
	@method Add
	@within Trove
	@param object any -- Object to track
	@param cleanupMethod string? -- Optional cleanup name override
	@return object: any
	Adds an object to the trove. Once the trove is cleaned or
	destroyed, the object will also be cleaned up.

	The following types are accepted (e.g. `typeof(object)`):

	| Type | Cleanup |
	| ---- | ------- |
	| `Instance` | `object:Destroy()` |
	| `RBXScriptConnection` | `object:Disconnect()` |
	| `function` | `object()` |
	| `thread` | `task.cancel(object)` |
	| `table` | `object:Destroy()` _or_ `object:Disconnect()` _or_ `object:destroy()` _or_ `object:disconnect()` |
	| `table` with `cleanupMethod` | `object:<cleanupMethod>()` |

	Returns the object added.

	```lua
	-- Add a part to the trove, then destroy the trove,
	-- which will also destroy the part:
	local part = Instance.new("Part")
	trove:Add(part)
	trove:Destroy()

	-- Add a function to the trove:
	trove:Add(function()
		print("Cleanup!")
	end)
	trove:Destroy()

	-- Standard cleanup from table:
	local tbl = {}
	function tbl:Destroy()
		print("Cleanup")
	end
	trove:Add(tbl)

	-- Custom cleanup from table:
	local tbl = {}
	function tbl:DoSomething()
		print("Do something on cleanup")
	end
	trove:Add(tbl, "DoSomething")
	```
]=]
function Trove.Add(self: TroveInternal, object: Trackable, cleanupMethod: string?): any
	if self._cleaning then
		error("cannot call trove:Add() while cleaning", 2)
	end

	local cleanup = GetObjectCleanupFunction(object, cleanupMethod)
	table.insert(self._objects, { object, cleanup })

	return object
end

--[=[
	@method Clone
	@within Trove
	@return Instance
	Clones the given instance and adds it to the trove. Shorthand for
	`trove:Add(instance:Clone())`.

	```lua
	local clonedPart = trove:Clone(somePart)
	```
]=]
function Trove.Clone(self: TroveInternal, instance: Instance): Instance
	if self._cleaning then
		error("cannot call trove:Clone() while cleaning", 2)
	end

	return self:Add(instance:Clone())
end

--[=[
	@method Construct
	@within Trove
	@param class { new(Args...) -> T } | (Args...) -> T
	@param ... Args...
	@return T
	Constructs a new object from either the
	table or function given.

	If a table is given, the table's `new`
	function will be called with the given
	arguments.

	If a function is given, the function will
	be called with the given arguments.

	The result from either of the two options
	will be added to the trove.

	This is shorthand for `trove:Add(SomeClass.new(...))`
	and `trove:Add(SomeFunction(...))`.

	```lua
	local Signal = require(somewhere.Signal)

	-- All of these are identical:
	local s = trove:Construct(Signal)
	local s = trove:Construct(Signal.new)
	local s = trove:Construct(function() return Signal.new() end)
	local s = trove:Add(Signal.new())

	-- Even Roblox instances can be created:
	local part = trove:Construct(Instance, "Part")
	```
]=]
function Trove.Construct<T, A...>(self: TroveInternal, class: Constructable<T, A...>, ...: A...)
	if self._cleaning then
		error("Cannot call trove:Construct() while cleaning", 2)
	end

	local object = nil
	local t = type(class)
	if t == "table" then
		object = (class :: any).new(...)
	elseif t == "function" then
		object = (class :: any)(...)
	end

	return self:Add(object)
end

--[=[
	@method Connect
	@within Trove
	@param signal RBXScriptSignal
	@param fn (...: any) -> ()
	@return RBXScriptConnection
	Connects the function to the signal, adds the connection
	to the trove, and then returns the connection.

	This is shorthand for `trove:Add(signal:Connect(fn))`.

	```lua
	trove:Connect(workspace.ChildAdded, function(instance)
		print(instance.Name .. " added to workspace")
	end)
	```
]=]
function Trove.Connect(self: TroveInternal, signal: SignalLike, fn: (...any) -> ...any)
	if self._cleaning then
		error("Cannot call trove:Connect() while cleaning", 2)
	end

	return self:Add(signal:Connect(fn))
end

--[=[
	@method BindToRenderStep
	@within Trove
	@param name string
	@param priority number
	@param fn (dt: number) -> ()
	Calls `RunService:BindToRenderStep` and registers a function in the
	trove that will call `RunService:UnbindFromRenderStep` on cleanup.

	```lua
	trove:BindToRenderStep("Test", Enum.RenderPriority.Last.Value, function(dt)
		-- Do something
	end)
	```
]=]
function Trove.BindToRenderStep(self: TroveInternal, name: string, priority: number, fn: (dt: number) -> ())
	if self._cleaning then
		error("cannot call trove:BindToRenderStep() while cleaning", 2)
	end

	RunService:BindToRenderStep(name, priority, fn)

	self:Add(function()
		RunService:UnbindFromRenderStep(name)
	end)
end

--[=[
	@method AddPromise
	@within Trove
	@param promise Promise
	@return Promise
	Gives the promise to the trove, which will cancel the promise if the trove is cleaned up or if the promise
	is removed. The exact promise is returned, thus allowing chaining.

	```lua
	trove:AddPromise(doSomethingThatReturnsAPromise())
		:andThen(function()
			print("Done")
		end)
	-- Will cancel the above promise (assuming it didn't resolve immediately)
	trove:Clean()

	local p = trove:AddPromise(doSomethingThatReturnsAPromise())
	-- Will also cancel the promise
	trove:Remove(p)
	```

	:::caution Promise v4 Only
	This is only compatible with the [roblox-lua-promise](https://eryn.io/roblox-lua-promise/) library, version 4.
	:::
]=]
function Trove.AddPromise(self: TroveInternal, promise: PromiseLike)
	if self._cleaning then
		error("cannot call trove:AddPromise() while cleaning", 2)
	end
	AssertPromiseLike(promise)

	if promise:getStatus() == "Started" then
		promise:finally(function()
			if self._cleaning then
				return
			end
			self:_findAndRemoveFromObjects(promise, false)
		end)

		self:Add(promise, "cancel")
	end

	return promise
end

--[=[
	@method Remove
	@within Trove
	@param object any
	Removes the object from the Trove and cleans it up.

	```lua
	local part = Instance.new("Part")
	trove:Add(part)
	trove:Remove(part)
	```
]=]
function Trove.Remove(self: TroveInternal, object: Trackable): boolean
	if self._cleaning then
		error("cannot call trove:Remove() while cleaning", 2)
	end

	return self:_findAndRemoveFromObjects(object, true)
end

--[=[
	@method Extend
	@within Trove
	@return Trove
	Creates and adds another trove to itself. This is just shorthand
	for `trove:Construct(Trove)`. This is useful for contexts where
	the trove object is present, but the class itself isn't.

	:::note
	This does _not_ clone the trove. In other words, the objects in the
	trove are not given to the new constructed trove. This is simply to
	construct a new Trove and add it as an object to track.
	:::

	```lua
	local trove = Trove.new()
	local subTrove = trove:Extend()

	trove:Clean() -- Cleans up the subTrove too
	```
]=]
function Trove.Extend(self: TroveInternal)
	if self._cleaning then
		error("cannot call trove:Extend() while cleaning", 2)
	end

	return self:Construct(Trove)
end

--[=[
	@method Clean
	@within Trove
	Cleans up all objects in the trove. This is
	similar to calling `Remove` on each object
	within the trove. The ordering of the objects
	removed is _not_ guaranteed.

	```lua
	trove:Clean()
	```
]=]
function Trove.Clean(self: TroveInternal)
	if self._cleaning then
		return
	end

	self._cleaning = true

	for _, obj in self._objects do
		self:_cleanupObject(obj[1], obj[2])
	end

	table.clear(self._objects)
	self._cleaning = false
end

--[=[
	@method WrapClean
	@within Trove
	Returns a function that wraps the trove's `Clean()`
	method. Calling the returned function will clean up
	the trove.

	This is often useful in contexts where functions
	are the primary mode for cleaning up an environment,
	such as in many "observer" patterns.

	```lua
	local cleanup = trove:WrapClean()

	-- Sometime later...
	cleanup()
	```

	```lua
	-- Common observer pattern example:
	someObserver(function()
		local trove = Trove.new()
		-- Foo
		return trove:WrapClean()
	end)
	```
]=]
function Trove.WrapClean(self: TroveInternal)
	return function()
		self:Clean()
	end
end

function Trove._findAndRemoveFromObjects(self: TroveInternal, object: any, cleanup: boolean): boolean
	local objects = self._objects

	for i, obj in objects do
		if obj[1] == object then
			local n = #objects
			objects[i] = objects[n]
			objects[n] = nil

			if cleanup then
				self:_cleanupObject(obj[1], obj[2])
			end

			return true
		end
	end

	return false
end

function Trove._cleanupObject(_self: TroveInternal, object: any, cleanupMethod: string?)
	if cleanupMethod == FN_MARKER then
		task.spawn(object)
	elseif cleanupMethod == THREAD_MARKER then
		pcall(task.cancel, object)
	else
		object[cleanupMethod](object)
	end
end

--[=[
	@method AttachToInstance
	@within Trove
	@param instance Instance
	@return RBXScriptConnection
	Attaches the trove to a Roblox instance. Once this
	instance is removed from the game (parent or ancestor's
	parent set to `nil`), the trove will automatically
	clean up.

	This inverses the ownership of the Trove object, and should
	only be used when necessary. In other words, the attached
	instance dictates when the trove is cleaned up, rather than
	the trove dictating the cleanup of the instance.

	:::caution
	Will throw an error if `instance` is not a descendant
	of the game hierarchy.
	:::

	```lua
	trove:AttachToInstance(somePart)
	trove:Add(function()
		print("Cleaned")
	end)

	-- Destroying the part will cause the trove to clean up, thus "Cleaned" printed:
	somePart:Destroy()
	```
]=]
function Trove.AttachToInstance(self: TroveInternal, instance: Instance)
	if self._cleaning then
		error("cannot call trove:AttachToInstance() while cleaning", 2)
	elseif not instance:IsDescendantOf(game) then
		error("instance is not a descendant of the game hierarchy", 2)
	end

	return self:Connect(instance.Destroying, function()
		self:Destroy()
	end)
end

--[=[
	@method Destroy
	@within Trove
	Alias for `trove:Clean()`.

	```lua
	trove:Destroy()
	```
]=]
function Trove.Destroy(self: TroveInternal)
	self:Clean()
end

return {
	new = Trove.new,
}
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
        </Item>
        <Item class="ModuleScript" referent="10">
          <Properties>
            <string name="Name">SFX</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Sound facade. Packs call SFX:Play(name) freely; it is a no-op until a sound
-- backend registers (Sound System v2 calls setBackend from its client init).
local SFX = {}
local backend = nil

function SFX:Play(name)
	if backend then
		backend:Play(name)
	end
end

function SFX:PlayOverlap(name)
	if backend then
		backend:PlayOverlap(name)
	end
end

function SFX.setBackend(b)
	backend = b
end

return SFX
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="ModuleScript" referent="11">
          <Properties>
            <string name="Name">Schema</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Schema.lua - declarative configuration schemas (RBLX Essentials 3.0).
--
-- A pack describes the options the GAME may set in `Packs/<PackId>/Schema`,
-- a ModuleScript returning `function(S) return S.config({ ... }) end`, where
-- `S` is this module. One schema serves three readers:
--   * the Kernel at boot: defaults plus validation of the game's Config modules;
--   * Tools in Studio: the same validation, before anyone presses Play;
--   * the reference generator: every field carries a `doc`.
--
-- Values are plain Luau data. The only merge rule: `config`, `record` and
-- `map` merge key by key; a `list` and every scalar are replaced whole (list
-- items still receive their own record defaults).
--
-- Plan A3:
--   * a `server = true` option, and everything under it, has no `default`:
--     this module sits in ReplicatedStorage, so a default would reach every
--     client (RT-10). Such an option is required or S.optional, and the game
--     sets it in the server Config module; a server list starts empty;
--   * any node may carry `check = function(value) -> { { field?, message } }`
--     with a `checkDoc` that states the rule: a constraint over several
--     fields ("Target is required when Type is Stat"), run by validate once
--     the value passed every type check below it.
--
-- Plan B0 (phase B, before the Kernel 3.0.0 API freeze):
--   * S.color: a Color3 (typeof) with finite channels, written in a Config
--     module as Color3.fromRGB(r, g, b);
--   * S.tree: a nested table of text keys whose values are finite numbers,
--     text, true/false or tables of the same kind (at most TREE_DEPTH
--     levels, never a table inside itself), such as a starting profile. It
--     is a value, not a group: a game's tree replaces the default whole;
--   * pinned = true on an option outside lists and maps (never the root):
--     T.migrate refuses a game module whose value there differs from the
--     migrated one, and force never lifts it (a DataStore version, where
--     another value starts every player on an empty profile). The Kernel
--     only checks the flag and lists it in describe.
--
-- Pure: no `game`, no Instances, no yields.
local Schema = {}

-- The kinds whose value has no default of its own when the schema gives
-- none (describe's `required`): every kind but the groups and collections.
local SCALAR = {
	number = true, string = true, boolean = true, enum = true,
	id = true, dataPath = true, utcDate = true, color = true, tree = true,
}
local KNOWN = {
	number = true, string = true, boolean = true, enum = true, id = true,
	dataPath = true, utcDate = true, color = true, tree = true, list = true,
	map = true, record = true, config = true, optional = true,
}
-- How deep an S.tree may nest (plan B0).
Schema.TREE_DEPTH = 32
local ID_NAMES = { asset = "an asset id", product = "a developer product id", gamePass = "a game pass id" }
local DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

-- ── constructors ─────────────────────────────────────────────────────

local function build(kind, opts, extra)
	local node = { kind = kind }
	if opts ~= nil then
		assert(type(opts) == "table", "schema options must be a table")
		for key, value in pairs(opts) do
			node[key] = value
		end
	end
	if extra ~= nil then
		for key, value in pairs(extra) do
			node[key] = value
		end
	end
	return node
end

function Schema.number(opts) return build("number", opts) end
function Schema.integer(opts) return build("number", opts, { integer = true }) end
function Schema.string(opts) return build("string", opts) end
function Schema.boolean(opts) return build("boolean", opts) end
function Schema.enum(values, opts) return build("enum", opts, { values = values }) end
function Schema.assetId(opts) return build("id", opts, { idKind = "asset" }) end
function Schema.productId(opts) return build("id", opts, { idKind = "product" }) end
function Schema.gamePassId(opts) return build("id", opts, { idKind = "gamePass" }) end
function Schema.dataPath(opts) return build("dataPath", opts) end
function Schema.utcDate(opts) return build("utcDate", opts) end
function Schema.color(opts) return build("color", opts) end
function Schema.tree(opts) return build("tree", opts) end
function Schema.list(item, opts) return build("list", opts, { item = item }) end
function Schema.map(value, opts) return build("map", opts, { value = value }) end
function Schema.record(fields, opts) return build("record", opts, { fields = fields }) end
function Schema.config(fields, opts) return build("config", opts, { fields = fields }) end
function Schema.optional(inner) return { kind = "optional", inner = inner } end

-- ── helpers ──────────────────────────────────────────────────────────

local function unwrap(node)
	if type(node) == "table" and node.kind == "optional" then
		return node.inner
	end
	return node
end

local function copy(value)
	if type(value) ~= "table" then
		return value
	end
	local out = {}
	for key, item in pairs(value) do
		out[key] = copy(item)
	end
	return out
end
Schema.copy = copy

local function sortedKeys(t)
	local keys = {}
	for key in pairs(t) do
		table.insert(keys, key)
	end
	table.sort(keys, function(a, b)
		return tostring(a) < tostring(b)
	end)
	return keys
end

local function childPath(path, key)
	if type(key) == "number" then
		return ("%s[%d]"):format(path, key)
	end
	if type(key) == "string" and key:match("^[%a_][%w_]*$") then
		return path == "" and key or (path .. "." .. key)
	end
	return ("%s[%q]"):format(path, tostring(key))
end
Schema.childPath = childPath

local function isArray(t)
	local count = 0
	for key in pairs(t) do
		if type(key) ~= "number" or key < 1 or key % 1 ~= 0 then
			return false
		end
		count += 1
	end
	for i = 1, count do
		if t[i] == nil then
			return false
		end
	end
	return true
end

local function isDataPath(text)
	if type(text) ~= "string" or text == "" then
		return false
	end
	for segment in (text .. "."):gmatch("(.-)%.") do
		if not segment:match("^[%a_][%w_]*$") then
			return false
		end
	end
	return true
end

function Schema.parseUtcDate(text)
	if type(text) ~= "string" then
		return nil
	end
	local y, m, d = text:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
	if y == nil then
		return nil
	end
	local year, month, day = tonumber(y), tonumber(m), tonumber(d)
	if month < 1 or month > 12 then
		return nil
	end
	local days = DAYS_IN_MONTH[month]
	if month == 2 and ((year % 4 == 0 and year % 100 ~= 0) or year % 400 == 0) then
		days = 29
	end
	if day < 1 or day > days then
		return nil
	end
	return year, month, day
end

local function distance(a, b)
	local la, lb = #a, #b
	if la == 0 then return lb end
	if lb == 0 then return la end
	local previous = {}
	for j = 0, lb do
		previous[j] = j
	end
	for i = 1, la do
		local current = { [0] = i }
		local ca = a:sub(i, i):lower()
		for j = 1, lb do
			local cost = (ca == b:sub(j, j):lower()) and 0 or 1
			current[j] = math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + cost)
		end
		previous = current
	end
	return previous[lb]
end

local function suggest(key, fields)
	local best, bestDistance = nil, nil
	for _, name in ipairs(sortedKeys(fields)) do
		local d = distance(key, name)
		if d <= 2 and (bestDistance == nil or d < bestDistance) then
			best, bestDistance = name, d
		end
	end
	return best
end

-- ── validation ───────────────────────────────────────────────────────

local function push(problems, path, message)
	table.insert(problems, { path = path, message = message })
end

-- validateTree(value, path, problems, seen, depth): S.tree's rule. Every
-- bad entry is reported at its own path; a key that is not text is shown
-- with tostring (childPath's %d would throw on 1.5).
local function validateTree(value, path, problems, seen, depth)
	if type(value) ~= "table" then
		return push(problems, path, "must be a table of text keys")
	end
	if seen[value] then
		return push(problems, path, "contains itself")
	end
	if depth > Schema.TREE_DEPTH then
		return push(problems, path, ("is nested deeper than %d levels"):format(Schema.TREE_DEPTH))
	end
	seen[value] = true
	for _, key in ipairs(sortedKeys(value)) do
		local item = value[key]
		local at = if type(key) == "string" then childPath(path, key) else ("%s[%s]"):format(path, tostring(key))
		if type(key) ~= "string" then
			push(problems, at, "keys must be text")
		elseif type(item) == "number" then
			if item ~= item or item == math.huge or item == -math.huge then
				push(problems, at, "must be a finite number")
			end
		elseif type(item) == "table" then
			validateTree(item, at, problems, seen, depth + 1)
		elseif type(item) ~= "string" and type(item) ~= "boolean" then
			push(problems, at, "must be a number, text, true/false or a table")
		end
	end
	seen[value] = nil
end

local validateNode

-- runCheck(node, value, path, problems): the node's own `check`. Each item it
-- returns is { field?, message }: `field` is a key, or a list of keys, under
-- the node's path. Inside a pcall, so a rule that throws is a problem.
local function runCheck(node, value, path, problems)
	local ok, found = pcall(node.check, value)
	if not ok then
		return push(problems, path, ("could not be checked: %s"):format(tostring(found)))
	end
	if type(found) ~= "table" then
		return
	end
	for _, item in ipairs(found) do
		if type(item) == "table" and type(item.message) == "string" then
			local at = path
			local field = item.field
			if type(field) == "table" then
				for _, key in ipairs(field) do
					at = childPath(at, key)
				end
			elseif field ~= nil then
				at = childPath(at, field)
			end
			push(problems, at, item.message)
		end
	end
end

local validateShape
validateNode = function(node, value, path, problems, opts)
	if node.kind == "optional" then
		if value == nil then
			return
		end
		return validateNode(node.inner, value, path, problems, opts)
	end
	-- A node's own rule runs only over a value that passed every type check
	-- below it: a rule over fields that are already wrong is noise.
	local before = #problems
	validateShape(node, value, path, problems, opts)
	if type(node.check) == "function" and #problems == before then
		runCheck(node, value, path, problems)
	end
end

validateShape = function(node, value, path, problems, opts)
	if value == nil then
		return push(problems, path, "is required")
	end
	local kind = node.kind
	if kind == "number" then
		if type(value) ~= "number" or value ~= value then
			return push(problems, path, "must be a number")
		end
		if node.integer and value % 1 ~= 0 then
			return push(problems, path, "must be a whole number")
		end
		if node.min ~= nil and value < node.min then
			return push(problems, path, ("must be >= %s"):format(tostring(node.min)))
		end
		if node.max ~= nil and value > node.max then
			return push(problems, path, ("must be <= %s"):format(tostring(node.max)))
		end
	elseif kind == "string" then
		if type(value) ~= "string" then
			return push(problems, path, "must be text")
		end
		if node.minLength ~= nil and #value < node.minLength then
			if node.minLength == 1 then
				return push(problems, path, "must not be empty")
			end
			return push(problems, path, ("must have at least %d characters"):format(node.minLength))
		end
		if node.maxLength ~= nil and #value > node.maxLength then
			return push(problems, path, ("must have at most %d characters"):format(node.maxLength))
		end
		if node.pattern ~= nil then
			-- pcall: validate can run on a schema Schema.check never saw, and a
			-- malformed pattern must be reported, never thrown.
			local ok, matched = pcall(string.match, value, node.pattern)
			if not ok then
				return push(problems, path, ("cannot be checked against pattern %s: %s"):format(tostring(node.pattern), tostring(matched)))
			end
			if matched == nil then
				return push(problems, path, ("must match %s"):format(node.patternDoc or node.pattern))
			end
		end
	elseif kind == "boolean" then
		if type(value) ~= "boolean" then
			return push(problems, path, "must be true or false")
		end
	elseif kind == "enum" then
		if type(node.values) ~= "table" or type(value) ~= "string" or table.find(node.values, value) == nil then
			return push(problems, path, ("must be one of: %s"):format(table.concat(node.values or {}, ", ")))
		end
	elseif kind == "id" then
		if type(value) ~= "number" or value % 1 ~= 0 or value < 0 then
			return push(problems, path, ("must be %s: a whole number, 0 for none"):format(ID_NAMES[node.idKind] or "an id"))
		end
	elseif kind == "dataPath" then
		if not isDataPath(value) then
			return push(problems, path, "must be a data path like Stats.Cash")
		end
	elseif kind == "utcDate" then
		if Schema.parseUtcDate(value) == nil then
			return push(problems, path, "must be a UTC date written YYYY-MM-DD")
		end
	elseif kind == "color" then
		if typeof(value) ~= "Color3" then
			return push(problems, path, "must be a Color3 (Color3.fromRGB(r, g, b))")
		end
		-- LuaText cannot write a NaN or infinite channel, so T.config and
		-- T.migrate would fail later on a value the Kernel took.
		for _, channel in ipairs({ value.R, value.G, value.B }) do
			if channel ~= channel or channel == math.huge or channel == -math.huge then
				return push(problems, path, "must have finite channels")
			end
		end
	elseif kind == "tree" then
		validateTree(value, path, problems, {}, 1)
	elseif kind == "list" then
		if type(value) ~= "table" or not isArray(value) then
			return push(problems, path, "must be a list")
		end
		if node.minItems ~= nil and #value < node.minItems then
			push(problems, path, ("must have at least %d entries"):format(node.minItems))
		end
		if node.maxItems ~= nil and #value > node.maxItems then
			push(problems, path, ("must have at most %d entries"):format(node.maxItems))
		end
		for index, item in ipairs(value) do
			validateNode(node.item, item, childPath(path, index), problems, opts)
		end
	elseif kind == "map" then
		if type(value) ~= "table" then
			return push(problems, path, "must be a table")
		end
		for _, key in ipairs(sortedKeys(value)) do
			if type(key) ~= "string" then
				push(problems, path, "keys must be text")
			else
				validateNode(node.value, value[key], childPath(path, key), problems, opts)
			end
		end
	elseif kind == "record" or kind == "config" then
		if type(value) ~= "table" then
			return push(problems, path, "must be a table")
		end
		for _, key in ipairs(sortedKeys(value)) do
			if type(key) ~= "string" or node.fields[key] == nil then
				local hint = type(key) == "string" and suggest(key, node.fields) or nil
				push(problems, childPath(path, key),
					hint and ("is not a known option (did you mean %s?)"):format(hint) or "is not a known option")
			end
		end
		for _, name in ipairs(sortedKeys(node.fields)) do
			local field = node.fields[name]
			if not (kind == "config" and opts.skipServer and unwrap(field).server == true) then
				validateNode(field, value[name], childPath(path, name), problems, opts)
			end
		end
	end
end

function Schema.validate(root, value, rootPath, opts)
	local problems = {}
	validateNode(root, value, rootPath or "", problems, opts or {})
	return problems
end

function Schema.formatErrors(problems)
	local out = {}
	for _, problem in ipairs(problems) do
		table.insert(out, ("%s: %s"):format(problem.path, problem.message))
	end
	return out
end

-- ── defaults and merge ───────────────────────────────────────────────

function Schema.defaults(node)
	local kind = node.kind
	if kind == "optional" then
		if node.inner.default ~= nil then
			return Schema.defaults(node.inner)
		end
		return nil
	elseif kind == "record" or kind == "config" then
		local out = {}
		for name, field in pairs(node.fields) do
			out[name] = Schema.defaults(field)
		end
		return out
	elseif kind == "list" or kind == "map" then
		return Schema.merge(node, nil, node.default or {})
	end
	return copy(node.default)
end

function Schema.merge(node, base, override)
	if override == nil then
		return copy(base)
	end
	local n = unwrap(node)
	if type(override) ~= "table" then
		return copy(override)
	end
	if n.kind == "list" then
		local out = {}
		for index, item in ipairs(override) do
			out[index] = Schema.merge(n.item, Schema.defaults(n.item), item)
		end
		for key, item in pairs(override) do
			if out[key] == nil then
				out[key] = copy(item) -- kept so validation can reject a non-list
			end
		end
		return out
	end
	if n.kind ~= "record" and n.kind ~= "config" and n.kind ~= "map" then
		return copy(override)
	end
	local out = type(base) == "table" and copy(base) or {}
	for key, item in pairs(override) do
		local child = if n.kind == "map" then n.value else n.fields[key]
		if child == nil then
			out[key] = copy(item) -- unknown option: kept so validation reports it
		else
			local current = out[key]
			if current == nil then
				current = Schema.defaults(child)
			end
			out[key] = Schema.merge(child, current, item)
		end
	end
	return out
end

-- ── server-only options ──────────────────────────────────────────────

function Schema.serverFields(root)
	local set = {}
	for name, field in pairs(root.fields) do
		if unwrap(field).server == true then
			set[name] = true
		end
	end
	return set
end

function Schema.stripServer(root, value)
	local out = copy(value)
	for name in pairs(Schema.serverFields(root)) do
		out[name] = nil
	end
	return out
end

-- ── patterns ─────────────────────────────────────────────────────────

-- Luau rejects a malformed pattern only when string.match reaches the bad
-- item, so a probe on one subject lets most of them through ("a[" never
-- fails on "") and they throw later, on the game's value. patternError
-- walks the whole pattern once with the matcher's own rules (lstrlib) and
-- returns the reason the first bad item gives, or nil when every item is
-- well formed.
local MAX_CAPTURES = 32 -- LUA_MAXCAPTURES

-- setEnd(pattern, i) -> the index after the ']' that closes a set whose
-- first character is at i, or nil. As in the matcher, the first character
-- after '[' or '[^' always belongs to the set, so "[]]" and "[^]]" are sets
-- holding ']', and a '%' inside a set escapes the character after it.
local function setEnd(pattern, i)
	local n = #pattern
	if string.sub(pattern, i, i) == "^" then
		i += 1
	end
	repeat
		if i > n then
			return nil
		end
		local c = string.sub(pattern, i, i)
		i += 1
		if c == "%" and i <= n then
			i += 1
		end
	until string.sub(pattern, i, i) == "]"
	return i + 1
end

function Schema.patternError(pattern)
	if type(pattern) ~= "string" then
		return "pattern must be text"
	end
	local n = #pattern
	local i = if string.sub(pattern, 1, 1) == "^" then 2 else 1
	local closed = {} -- closed[k] becomes true when capture k gets its ')'
	local open = {} -- captures still open, innermost last
	while i <= n do
		local c = string.sub(pattern, i, i)
		if c == "(" then
			table.insert(closed, false)
			if #closed > MAX_CAPTURES then
				return "too many captures"
			end
			table.insert(open, #closed)
			i += 1
		elseif c == ")" then
			local index = table.remove(open)
			if index == nil then
				return "invalid pattern capture"
			end
			closed[index] = true
			i += 1
		elseif c == "[" then
			local after = setEnd(pattern, i + 1)
			if after == nil then
				return "missing ']'"
			end
			i = after
		elseif c == "%" then
			if i == n then
				return "ends with '%'"
			end
			local nextChar = string.sub(pattern, i + 1, i + 1)
			if nextChar == "b" then
				if i + 3 > n then
					return "missing arguments to '%b'"
				end
				i += 4
			elseif nextChar == "f" then
				if string.sub(pattern, i + 2, i + 2) ~= "[" then
					return "missing '[' after '%f'"
				end
				local after = setEnd(pattern, i + 3)
				if after == nil then
					return "missing ']'"
				end
				i = after
			elseif string.find(nextChar, "^%d$") then
				if closed[tonumber(nextChar)] ~= true then
					return "invalid capture index %" .. nextChar
				end
				i += 2
			else
				i += 2
			end
		else
			i += 1
		end
	end
	if #open > 0 then
		return "unfinished capture"
	end
	return nil
end

-- ── schema definition check ──────────────────────────────────────────

local function isCount(value)
	return type(value) == "number" and value >= 0 and value % 1 == 0
end

-- The option values validation relies on. A wrong type here would make
-- validation misbehave (a number compared with a string, a boolean
-- concatenated into a message) and a malformed pattern would only fail on
-- the game's value, so the schema is rejected instead.
local function checkOptions(node, path, errors)
	for _, option in ipairs({ "min", "max" }) do
		if node[option] ~= nil and (type(node[option]) ~= "number" or node[option] ~= node[option]) then
			table.insert(errors, ("%s: %s must be a number"):format(path, option))
		end
	end
	for _, option in ipairs({ "minLength", "maxLength", "minItems", "maxItems" }) do
		if node[option] ~= nil and not isCount(node[option]) then
			table.insert(errors, ("%s: %s must be a whole number >= 0"):format(path, option))
		end
	end
	if node.pattern ~= nil then
		if type(node.pattern) ~= "string" then
			table.insert(errors, ("%s: pattern must be text"):format(path))
		else
			local reason = Schema.patternError(node.pattern)
			if reason ~= nil then
				table.insert(errors, ("%s: pattern is not a valid Luau pattern (%s)"):format(path, reason))
			end
		end
	end
	if node.kind == "enum" then
		if type(node.values) ~= "table" or not isArray(node.values) then
			table.insert(errors, ("%s: enum needs a list of text values"):format(path))
		elseif #node.values == 0 then
			table.insert(errors, ("%s: enum needs at least one value"):format(path))
		else
			for _, value in ipairs(node.values) do
				if type(value) ~= "string" then
					table.insert(errors, ("%s: enum values must be text"):format(path))
					break
				end
			end
		end
	end
	if node.check ~= nil then
		local where = if path == "" then "the schema" else path
		if type(node.check) ~= "function" then
			table.insert(errors, ("%s: check must be a function"):format(where))
		elseif type(node.checkDoc) ~= "string" or node.checkDoc == "" then
			table.insert(errors, ("%s: check needs a checkDoc that says its rule"):format(where))
		end
	end
	if node.pinned ~= nil and type(node.pinned) ~= "boolean" then
		table.insert(errors, ("%s: pinned must be true or false"):format(if path == "" then "the schema" else path))
	end
end

-- Validates what the node gives the game when the game leaves it out: its
-- default, with list items and map values filled in from their own
-- defaults, exactly as Config.load merges it. On a group (only reached for
-- an S.optional group, see checkNode) the default is the switch that makes
-- the group present, built by Schema.defaults from its fields' defaults and
-- never from the switch's values: the switch must be `{}`, since values in
-- it would be dropped without a word, and the group it switches on must be
-- valid. In a pcall, because a default the merge cannot copy (a table that
-- contains itself) must become an error in the list, not a throw.
local function checkDefault(node, path, errors)
	local problems = {}
	local ok, err = pcall(function()
		if node.kind == "record" or node.kind == "config" then
			if type(node.default) ~= "table" then
				validateNode(node, node.default, path, problems, {})
			else
				if next(node.default) ~= nil then
					push(problems, path, "an optional group's default only switches it on, its values are not used;"
						.. " give each field its own default and use default = {}")
				end
				validateNode(node, Schema.defaults(node), path, problems, {})
			end
		else
			validateNode(node, Schema.defaults(node), path, problems, {})
		end
	end)
	if not ok then
		table.insert(errors, ("default of %s: cannot be checked (%s)"):format(path, tostring(err)))
		return
	end
	local seen = {}
	for _, problem in ipairs(problems) do
		local line = ("default of %s: %s"):format(problem.path, problem.message)
		if not seen[line] then
			seen[line] = true
			table.insert(errors, line)
		end
	end
end

-- `server` is true under a server-only option: nothing there may carry a
-- default (RT-10). `collection` is true under a list item or a map value:
-- nothing there may be pinned (plan B0), since T.migrate compares a pinned
-- option at one path.
local function checkNode(node, path, errors, topLevel, optional, server, collection)
	if type(node) ~= "table" or type(node.kind) ~= "string" or not KNOWN[node.kind] then
		table.insert(errors, ("%s is not a schema node"):format(path))
		return
	end
	if node.kind == "optional" then
		return checkNode(node.inner, path, errors, topLevel, true, server, collection)
	end
	local kind = node.kind
	local before = #errors
	if node.server == true and not topLevel then
		table.insert(errors, ("%s: server = true is only allowed on top-level options"):format(path))
	end
	if node.pinned == true and collection then
		table.insert(errors, ("%s: pinned is only allowed outside lists and maps"):format(path))
	elseif node.pinned == true and path == "" then
		-- The root has no describe row, so T.migrate would never see the flag.
		table.insert(errors, "the schema: pinned is only allowed on options")
	end
	local serverOnly = server == true or (topLevel and node.server == true)
	checkOptions(node, path, errors)
	if kind == "list" then
		checkNode(node.item, path .. "[]", errors, false, nil, serverOnly, true)
	elseif kind == "map" then
		checkNode(node.value, path .. ".<key>", errors, false, nil, serverOnly, true)
	elseif kind == "record" or kind == "config" then
		if type(node.fields) ~= "table" then
			table.insert(errors, ("%s: needs a table of fields"):format(path))
		else
			for _, name in ipairs(sortedKeys(node.fields)) do
				local field = node.fields[name]
				local fieldPath = childPath(path, name)
				local inner = unwrap(field)
				if type(inner) ~= "table" or type(inner.doc) ~= "string" or inner.doc == "" then
					table.insert(errors, ("%s needs a doc"):format(fieldPath))
				end
				checkNode(field, fieldPath, errors, kind == "config", nil, serverOnly, collection)
			end
		end
	end
	if node.default == nil then
		return
	end
	if serverOnly then
		table.insert(errors, ("%s: a server-only option has no default: the Schema reaches every client, and so would its default. Leave it out (S.optional, or required) and set the value in the server Config module")
			:format(path))
		return
	end
	if (kind == "record" or kind == "config") and not optional then
		-- Schema.defaults builds a group from its fields and reads a group's
		-- default only when the group is S.optional, as the switch that makes
		-- it present. Anywhere else the default has no effect at all.
		table.insert(errors, ("%s: default has no effect on a group that is not optional, give each field its own default")
			:format(path == "" and "the schema" or path))
	elseif #errors == before then
		-- Only on a sound node: a bad option or child is exactly what would
		-- make validating the default throw or report noise.
		checkDefault(node, path, errors)
	end
end

function Schema.check(root)
	if type(root) ~= "table" or root.kind ~= "config" then
		return { "the schema must be S.config({...})" }
	end
	local errors = {}
	checkNode(root, "", errors, false)
	return errors
end

-- ── describe (reference documentation) ───────────────────────────────

local function typeName(node)
	local kind = node.kind
	if kind == "number" then
		local parts = { node.integer and "whole number" or "number" }
		if node.min ~= nil then table.insert(parts, ">= " .. tostring(node.min)) end
		if node.max ~= nil then table.insert(parts, "<= " .. tostring(node.max)) end
		return table.concat(parts, ", ")
	elseif kind == "string" then
		return "text"
	elseif kind == "boolean" then
		return "true/false"
	elseif kind == "enum" then
		return "one of " .. table.concat(node.values or {}, " | ")
	elseif kind == "id" then
		return ID_NAMES[node.idKind] or "an id"
	elseif kind == "dataPath" then
		return "data path (e.g. Stats.Cash)"
	elseif kind == "utcDate" then
		return "UTC date YYYY-MM-DD"
	elseif kind == "color" then
		return "color (Color3)"
	elseif kind == "tree" then
		return "tree (text keys; numbers, text, true/false or nested tables)"
	elseif kind == "record" then
		return "group"
	end
	return kind
end

-- LuaText's number rule: a whole number as %d, else the shortest %.<p>g
-- that reads back exactly (a float32 channel needs up to 17 digits).
local function exactNumber(value)
	if value == math.floor(value) and math.abs(value) < 2 ^ 53 then
		return ("%d"):format(value)
	end
	for precision = 1, 17 do
		local text = ("%." .. precision .. "g"):format(value)
		if tonumber(text) == value then
			return text
		end
	end
	return ("%.17g"):format(value)
end

local function show(value)
	if value == nil then
		return ""
	end
	if typeof(value) == "Color3" then
		-- LuaText's rule (plan B0, planning decision 9): fromRGB only when
		-- every channel is exactly a whole 1/255 step as fromRGB makes it (a
		-- float32), else Color3.new with each channel's exact value.
		local channels, steps = { value.R, value.G, value.B }, {}
		for index, channel in ipairs(channels) do
			local step = math.round(channel * 255)
			if steps ~= nil and step >= 0 and step <= 255 and string.unpack("<f", string.pack("<f", step / 255)) == channel then
				steps[index] = step
			else
				steps = nil
			end
		end
		if steps ~= nil then
			return ("Color3.fromRGB(%d, %d, %d)"):format(steps[1], steps[2], steps[3])
		end
		return ("Color3.new(%s, %s, %s)"):format(exactNumber(channels[1]), exactNumber(channels[2]), exactNumber(channels[3]))
	end
	if type(value) == "string" then
		return ("%q"):format(value)
	end
	if type(value) == "table" then
		local count = 0
		for _ in pairs(value) do
			count += 1
		end
		return count == 0 and "{}" or ("{%d entries}"):format(count)
	end
	return tostring(value)
end

-- The limits a reader needs that `typeName` does not already say: text
-- length and pattern, and list sizes. Worded like the validation messages.
local function plural(count, one, many)
	return ("%d %s"):format(count, count == 1 and one or many)
end

local function constraintsOf(node)
	local out = {}
	if node.kind == "string" then
		if node.minLength == 1 then
			table.insert(out, "not empty")
		elseif node.minLength ~= nil then
			table.insert(out, "at least " .. plural(node.minLength, "character", "characters"))
		end
		if node.maxLength ~= nil then
			table.insert(out, "at most " .. plural(node.maxLength, "character", "characters"))
		end
		if node.pattern ~= nil then
			table.insert(out, "matches " .. tostring(node.patternDoc or node.pattern))
		end
	elseif node.kind == "list" then
		if node.minItems ~= nil then
			table.insert(out, "at least " .. plural(node.minItems, "entry", "entries"))
		end
		if node.maxItems ~= nil then
			table.insert(out, "at most " .. plural(node.maxItems, "entry", "entries"))
		end
	end
	if type(node.checkDoc) == "string" and node.checkDoc ~= "" then
		table.insert(out, node.checkDoc)
	end
	return out
end

-- describe(root) -> rows, one per option, in path order:
--   { path, type, default, doc, server, required,     -- display fields
--     optional, value, example, constraints,          -- for templates and Tools
--     pinned }                                        -- plan B0, for T.migrate
-- `default` is the display text the reference prints. `value` is the
-- default itself, a fresh copy, with the defaults of nested fields filled in
-- (Schema.defaults), nil when there is none. `example` is a copy of the
-- option's `example`. `constraints` lists the limits `type` does not say.
function Schema.describe(root)
	local rows = {}
	-- A list item or a map value is a structural node, not required to carry
	-- its own doc (Schema.check does not demand one there). Its row falls
	-- back to the list/map's own doc; an explicit doc on the item still wins.
	local function walk(node, path, server, parentDoc)
		local optional = node.kind == "optional"
		local n = unwrap(node)
		local isServer = server or n.server == true
		if path ~= "" then
			local doc = n.doc
			if doc == nil or doc == "" then
				doc = parentDoc
			end
			table.insert(rows, {
				path = path,
				type = typeName(n),
				default = show(n.default),
				doc = doc or "",
				server = isServer,
				required = (not optional) and n.default == nil and SCALAR[n.kind] == true,
				optional = optional,
				value = Schema.defaults(node),
				example = copy(n.example),
				constraints = constraintsOf(n),
				pinned = n.pinned == true,
			})
		end
		if n.kind == "record" or n.kind == "config" then
			for _, name in ipairs(sortedKeys(n.fields)) do
				walk(n.fields[name], childPath(path, name), isServer, nil)
			end
		elseif n.kind == "list" then
			walk(n.item, path .. "[]", isServer, n.doc)
		elseif n.kind == "map" then
			walk(n.value, path .. ".<key>", isServer, n.doc)
		end
	end
	walk(root, "", false, nil)
	return rows
end

-- ── freeze ───────────────────────────────────────────────────────────

function Schema.freeze(value)
	if type(value) ~= "table" or table.isfrozen(value) then
		return value
	end
	for _, item in pairs(value) do
		Schema.freeze(item)
	end
	return table.freeze(value)
end

return Schema
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="ModuleScript" referent="12">
          <Properties>
            <string name="Name">Signal</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Minimal signal (connect/fire/once/wait). Handlers run via task.spawn so a
-- slow or erroring handler never blocks the firer.
local Signal = {}
Signal.__index = Signal

local Connection = {}
Connection.__index = Connection

function Connection:Disconnect()
	if not self.Connected then return end
	self.Connected = false
	local conns = self._signal._connections
	for i, c in ipairs(conns) do
		if c == self then table.remove(conns, i) break end
	end
end

function Signal.new()
	return setmetatable({ _connections = {} }, Signal)
end

function Signal:Connect(fn)
	local conn = setmetatable({ _signal = self, _fn = fn, Connected = true }, Connection)
	table.insert(self._connections, conn)
	return conn
end

function Signal:Once(fn)
	local conn
	conn = self:Connect(function(...)
		conn:Disconnect()
		fn(...)
	end)
	return conn
end

function Signal:Fire(...)
	for _, conn in ipairs(table.clone(self._connections)) do
		if conn.Connected then
			task.spawn(conn._fn, ...)
		end
	end
end

function Signal:Wait()
	local thread = coroutine.running()
	self:Once(function(...)
		task.spawn(thread, ...)
	end)
	return coroutine.yield()
end

function Signal:Destroy()
	for _, conn in ipairs(table.clone(self._connections)) do
		conn:Disconnect()
	end
end

return Signal
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
        <Item class="Folder" referent="13">
          <Properties>
            <string name="Name">UI</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <Color3 name="IconTint">
              <R>0</R>
              <G>0</G>
              <B>0</B>
            </Color3>
            <bool name="DefinesCapabilities">false</bool>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
          <Item class="ModuleScript" referent="14">
            <Properties>
              <string name="Name">MenuSystem</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- Runtime menu system. Replaces v1's install-time MenusGUI merge
-- (GUIManager + ToggleListener + GUIBridge) with runtime registration.
local CollectionService = game:GetService("CollectionService")
local ContentProvider = game:GetService("ContentProvider")
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")

local SFX = require(script.Parent.Parent.SFX)
local buildShell = require(script.Parent.Shell)
local PanelAnimations = require(script.Parent.PanelAnimations)

local TOOLTIP_OFFSET_X = 15
local TOOLTIP_OFFSET_Y = 78
local TOOLTIP_THROTTLE_INTERVAL = 0.016
local ICON_HOVER_SCALE = UDim2.new(1.05, 0, 1.05, 0)
local ICON_DEFAULT_SCALE = UDim2.new(1, 0, 1, 0)
local ICON_HOVER_TWEEN_TIME = 0.1
local ICON_LEAVE_TWEEN_TIME = 0.3
local TOGGLE_COLOR_ON = Color3.fromRGB(85, 255, 0)
local TOGGLE_COLOR_OFF = Color3.fromRGB(255, 50, 50)

-- AdoptPriority is buyer-editable (Studio attribute), so it may arrive as a
-- non-numeric type (e.g. a String) instead of the expected number. Both call
-- sites coerce through this helper rather than trusting the raw attribute -
-- a non-numeric value is treated as priority 0 instead of crashing the
-- table.sort comparator / the <= comparison in _adoptCandidate.
local function readAdoptPriority(inst)
	return tonumber(inst:GetAttribute("AdoptPriority")) or 0
end

-- Deterministic adoption order: highest AdoptPriority first, ties broken by
-- original GetTagged() encounter order (not Luau's unstable table.sort) so a
-- lower-priority duplicate is NEVER reparented during a sweep - it always
-- loses to a higher-priority sibling already processed first, regardless of
-- CollectionService's return order.
local function sortByAdoptPriority(list)
	local decorated = {}
	for i, inst in ipairs(list) do
		decorated[i] = { inst = inst, priority = readAdoptPriority(inst), index = i }
	end
	table.sort(decorated, function(a, b)
		if a.priority ~= b.priority then
			return a.priority > b.priority
		end
		return a.index < b.index
	end)
	local out = {}
	for i, d in ipairs(decorated) do
		out[i] = d.inst
	end
	return out
end

-- Async (task.spawn) image warm-up shared by both panel-acquisition paths:
-- registerPanel's clone (below) and the panel branch of adoption
-- (_adoptCandidate, "parity restoration" fix - adopted panels used to skip
-- this entirely, causing first-open texture pop-in for every StarterGui-
-- shipped panel post-edit-time-GUI-migration). Interactables are NOT
-- preloaded (registerPanel never did for addInteractable's clones either -
-- this only restores parity with registerPanel's own existing behavior, not
-- a new scope).
local function preloadPanelImages(panel)
	task.spawn(function()
		local images = {}
		for _, desc in ipairs(panel:GetDescendants()) do
			if desc:IsA("ImageLabel") or desc:IsA("ImageButton") then
				table.insert(images, desc)
			end
		end
		if #images > 0 then
			ContentProvider:PreloadAsync(images)
		end
	end)
end

local MenuSystem = {}
MenuSystem.__index = MenuSystem

function MenuSystem.new(playerGui, opts)
	local self = setmetatable({}, MenuSystem)
	self._playerGui = playerGui
	-- 3.0: the Kernel's piece runtime (UI/Pieces.lua). When present, every
	-- adoption candidate is offered to it first; nil keeps 2.x behavior.
	self._pieces = opts and opts.pieces or nil
	self._mouse = Players.LocalPlayer:GetMouse()
	self.gui = buildShell(playerGui)
	self._menus = self.gui.Menus
	self._interactables = self.gui.Interactables.Container
	self._tooltip = self.gui.Tooltip
	self._handlers = {}
	self._panels = {}
	self._panelPriority = {}
	self._panelScrollers = {}
	self._interactableRegistry = {}
	self._interactablePriority = {}
	self._animations = {}
	self._wired = {}
	self._toggled = {}

	for name, fn in pairs(PanelAnimations) do
		self._animations[name] = fn
	end

	for _, desc in ipairs(self.gui:GetDescendants()) do
		self:_wireDescendant(desc)
	end
	self.gui.DescendantAdded:Connect(function(desc)
		self:_wireDescendant(desc)
	end)

	for _, button in ipairs(CollectionService:GetTagged("UI_Toggle")) do
		self:_wireToggle(button)
	end
	CollectionService:GetInstanceAddedSignal("UI_Toggle"):Connect(function(button)
		self:_wireToggle(button)
	end)

	-- Edit-time GUI adoption (StarterGui-shipped panels/interactables). Must
	-- run after the shell + wiring above exist so reparenting into Menus /
	-- Interactables.Container flows through the DescendantAdded wiring path
	-- automatically (no separate wiring code needed for adopted instances).
	-- self:_safeAdopt pcall-wraps each candidate: MenuSystem.new runs BEFORE
	-- Loader.boot in KernelClient, so an uncaught error here would take down
	-- the whole client ecosystem, not just adoption - one bad tag must never
	-- do that.
	for _, inst in ipairs(sortByAdoptPriority(CollectionService:GetTagged("UI_Panel"))) do
		self:_safeAdopt(inst, "Panel")
	end
	CollectionService:GetInstanceAddedSignal("UI_Panel"):Connect(function(inst)
		self:_safeAdopt(inst, "Panel")
	end)

	for _, inst in ipairs(sortByAdoptPriority(CollectionService:GetTagged("UI_Interactable"))) do
		self:_safeAdopt(inst, "Interactable")
	end
	CollectionService:GetInstanceAddedSignal("UI_Interactable"):Connect(function(inst)
		self:_safeAdopt(inst, "Interactable")
	end)

	return self
end

-- ── handlers ─────────────────────────────────────────────────────────

function MenuSystem:registerHandlers(handlers)
	for action, fn in pairs(handlers) do
		self._handlers[action] = fn
	end
end

function MenuSystem:dispatch(action, button)
	local fn = self._handlers[action]
	if fn then
		fn(button)
	else
		warn("[RBLXE UI] No handler registered for action:", action)
	end
end

function MenuSystem:registerAnimation(name, fn)
	self._animations[name] = fn
end

-- ── panels ───────────────────────────────────────────────────────────

function MenuSystem:registerPanel(template)
	local panel = template:Clone()
	panel.Visible = false
	panel.Parent = self._menus
	self._panels[panel.Name] = panel
	local scrollers = {}
	for _, desc in ipairs(panel:GetDescendants()) do
		if desc:IsA("ScrollingFrame") then
			table.insert(scrollers, desc)
		end
	end
	self._panelScrollers[panel.Name] = scrollers
	preloadPanelImages(panel)
	return panel
end

function MenuSystem:getPanel(name)
	return self._panels[name]
end

--- The HUD button that actually opens `panelName` - the one this system
--- REGISTERED, not merely one that carries the attribute.
---
--- Packs need this to put a notification badge on their own button, and until
--- 2026-08-25 every one of them scanned the shell for the first GuiButton with
--- a matching `TargetPanel`. That is wrong whenever two packs ship a fallback
--- button for the same panel: the loser of the priority contest is hidden but
--- LEFT IN THE CONTAINER when it was adopted before the winner arrived (see
--- `_adoptCandidate`'s "deposed" branch), so the scan could - and did, for
--- Easy Pet System's Pets button - return an invisible button and light a
--- badge nobody could see. Which of the two a scan hits depends on sweep
--- order, so the packs that appeared to work were only lucky.
function MenuSystem:getInteractable(panelName)
	return self._interactableRegistry["TogglePanel:" .. tostring(panelName)]
end

--- A 3.0 interface piece, already resolved by the Kernel: the game's Interface
--- override when it satisfies its contract, otherwise the pack's default.
--- `pieceId` is "<PackId>.<Name>" (packs use ctx.getPiece("<Name>")). Yields
--- up to `timeout` seconds (default 10) for it to reach PlayerGui. A Panel or
--- HudButton piece (tagged UI_Panel / UI_Interactable) is only useful once
--- this menu adopted it; if it did not, the piece is still returned but a
--- warning says why the menu cannot open it, instead of failing silently.
function MenuSystem:getPiece(pieceId, timeout)
	if self._pieces == nil then
		return nil
	end
	local piece = self._pieces:get(pieceId, timeout)
	if piece ~= nil then
		local tag = (CollectionService:HasTag(piece, "UI_Panel") and "UI_Panel")
			or (CollectionService:HasTag(piece, "UI_Interactable") and "UI_Interactable")
			or nil
		-- M-d: the kind in the server's decision marks a menu piece even when
		-- it lacks its tag (a default that fails its contract).
		local kind = if self._pieces.kindOf ~= nil then self._pieces:kindOf(pieceId) else nil
		local menuPiece = tag ~= nil or kind == "Panel" or kind == "HudButton"
		if menuPiece and not self:_holds(piece) then
			-- A piece that has just arrived is adopted from its own tag signal:
			-- give that one frame before warning.
			task.wait()
			if not self:_holds(piece) then
				warn(("[RBLXE] UI: piece %s (%s) was not adopted by the menu; nothing can open it"):format(pieceId, tag or kind))
			end
		end
	end
	return piece
end

-- Whether `inst` holds a registry slot (an adopted or registered panel or
-- interactable), by identity.
function MenuSystem:_holds(inst)
	for _, registry in ipairs({ self._panels, self._interactableRegistry }) do
		for _, registered in pairs(registry) do
			if registered == inst then
				return true
			end
		end
	end
	return false
end

function MenuSystem:openPanel(name)
	local menu = self._panels[name]
	if not menu or menu.Visible then return end
	SFX:Play("Press")
	menu.Visible = true
	local animFn = self._animations[menu:GetAttribute("Animation") or "Bounce"]
	if animFn then animFn(menu) end
	for _, sf in ipairs(self._panelScrollers[name] or {}) do
		sf.CanvasPosition = Vector2.zero
	end
	if menu:GetAttribute("Exclusive") ~= false then
		for otherName, otherMenu in pairs(self._panels) do
			if otherName ~= name and otherMenu.Visible and otherMenu:GetAttribute("Exclusive") ~= false then
				otherMenu.Visible = false
			end
		end
	end
end

function MenuSystem:closePanel(name)
	local menu = self._panels[name]
	if menu then menu.Visible = false end
end

function MenuSystem:togglePanel(name)
	local menu = self._panels[name]
	if not menu then return end
	if menu.Visible then
		SFX:Play("Press")
		menu.Visible = false
	else
		self:openPanel(name)
	end
end

function MenuSystem:addInteractable(template)
	local button = template:Clone()
	button.Parent = self._interactables
	return button
end

-- ── adoption (edit-time GUI: StarterGui-shipped panels/interactables) ──

-- interactableKey(inst) -> the identity two packs' HUD buttons are deduped
-- by. A button that toggles a panel IS that panel's button, whatever the
-- instance happens to be called, so the key is the panel it opens.
--
-- It used to be `inst.Name`, which only ever worked by coincidence: General
-- UI Kit and Easy Pet System both shipped a fallback "Shop" button and both
-- happened to name it after the panel. Renaming General UI Kit's buttons to
-- "Shop Button" etc. (2026-08-21) broke that coincidence instantly - the two
-- stopped colliding, both got adopted, and the HUD would have shown two Shop
-- buttons side by side, each opening the same panel. Keying off what the
-- button DOES cannot be broken by a rename.
--
-- Anything that is not a panel toggle (a pack's own custom Action, or a
-- decorative interactable with none) falls back to the name: there is
-- nothing better to compare, and two of those are not obviously duplicates.
local function interactableKey(inst)
	if inst:GetAttribute("Action") == "TogglePanel" then
		local target = inst:GetAttribute("TargetPanel")
		if type(target) == "string" and target ~= "" then
			return "TogglePanel:" .. target
		end
	end
	return inst.Name
end

local ADOPT_KIND = {
	Panel = { tag = "UI_Panel", container = "_menus", registry = "_panels", priorities = "_panelPriority" },
	Interactable = { tag = "UI_Interactable", container = "_interactables", registry = "_interactableRegistry", priorities = "_interactablePriority" },
}

-- Entry point for both the sweep (GetTagged) and late-arrival
-- (GetInstanceAddedSignal) paths. Exposed (not local-only) so specs can
-- drive it directly where there is no natural way to simulate a scenario
-- through the tag-add signal (e.g. reentrancy guard).
function MenuSystem:_adoptCandidate(inst, kind)
	local cfg = ADOPT_KIND[kind]
	if not cfg then return end
	-- A UI_Panel/UI_Interactable tag on a non-GuiObject (Folder, ScreenGui,
	-- UIStroke, ...) is not a valid candidate - bail before touching any
	-- GuiObject-only property (Visible, below) that would error on it.
	if not inst:IsA("GuiObject") then return end
	-- Only adopt from the local player's own PlayerGui tree, and never
	-- re-adopt something already living inside our own shell (avoids
	-- re-registration loops for anything CollectionService reports again).
	if not inst:IsDescendantOf(self._playerGui) or inst:IsDescendantOf(self.gui) then
		return
	end

	-- 3.0 pieces: a copy the Kernel did not choose (the default behind a valid
	-- Interface override, or the reverse, or a clone a pack made) is never
	-- adopted. Instances without RBLXE_Piece are always admitted, so 2.x GUIs
	-- keep the priority path below.
	if self._pieces ~= nil and not self._pieces:admit(inst) then
		return
	end

	local registry = self[cfg.registry]
	local priorities = self[cfg.priorities]
	local name = (kind == "Panel" and inst:GetAttribute("PanelId")) or interactableKey(inst)
	local priority = readAdoptPriority(inst)
	local existingPriority = priorities[name]
	local incumbent = registry[name]
	local chosen = self._pieces ~= nil and self._pieces:chose(inst)

	if chosen then
		-- The Kernel already decided this piece: it takes the slot whatever
		-- the AdoptPriority of a 2.x copy, which is deposed (hidden) instead.
		-- Two chosen pieces on one key is a declaration conflict: the first
		-- keeps the slot and both are named.
		local pieceId = inst:GetAttribute("RBLXE_Piece")
		if incumbent ~= nil and incumbent ~= inst and self._pieces:chose(incumbent) then
			inst.Visible = false
			warn(("[RBLXE] UI: pieces %s and %s both use %s '%s'; %s keeps it, %s is left unadopted")
				:format(tostring(incumbent:GetAttribute("RBLXE_Piece")), tostring(pieceId), cfg.tag, tostring(name),
					tostring(incumbent:GetAttribute("RBLXE_Piece")), tostring(pieceId)))
			return
		end
		if incumbent ~= nil and incumbent ~= inst then
			warn(("[RBLXE] UI: %s '%s' %s deposed by the 3.0 piece %s")
				:format(cfg.tag, tostring(name), incumbent:GetFullName(), tostring(pieceId)))
		end
		priority = math.huge
	elseif incumbent ~= nil and self._pieces ~= nil and self._pieces:chose(incumbent) then
		-- A 2.x copy arriving after the piece: the slot stays with the piece.
		inst.Visible = false
		warn(("[RBLXE] UI: %s '%s' %s left unadopted: the slot belongs to the 3.0 piece %s")
			:format(cfg.tag, tostring(name), inst:GetFullName(), tostring(incumbent:GetAttribute("RBLXE_Piece"))))
		return
	elseif existingPriority ~= nil and priority <= existingPriority then
		-- Loses to the incumbent (strictly lower, or a tie): left in place,
		-- unadopted, hidden so a dangling duplicate is never visible/clickable.
		inst.Visible = false
		if incumbent ~= nil and incumbent.Parent == nil then
			warn(("[RBLXE] duplicate %s '%s' left unadopted (priority): the incumbent was destroyed"):format(cfg.tag, name))
		else
			warn(("[RBLXE] duplicate %s '%s' left unadopted (priority)"):format(cfg.tag, name))
		end
		return
	end

	-- A higher-priority late arrival swaps the registration: the previously
	-- registered instance (if any) is deposed - hide it so it doesn't stay
	-- visible/reachable (an open panel, or a still-clickable interactable)
	-- after losing its registry slot, symmetric with the sweep-loser path.
	local previous = registry[name]
	if previous and previous ~= inst then
		previous.Visible = false
	end

	inst.Parent = self[cfg.container]
	if kind == "Panel" then
		inst.Visible = false -- panels default closed; interactables stay visible
	end
	registry[name] = inst
	priorities[name] = priority

	if kind == "Panel" then
		local scrollers = {}
		for _, desc in ipairs(inst:GetDescendants()) do
			if desc:IsA("ScrollingFrame") then
				table.insert(scrollers, desc)
			end
		end
		self._panelScrollers[name] = scrollers
		-- Parity restoration (M3 review fix): registerPanel always warmed up
		-- its clone's images; adopted panels must get the identical treatment
		-- - otherwise every StarterGui-shipped panel (GUK's 13, EPS's own)
		-- loses this warm-up entirely post edit-time-GUI-migration, causing
		-- first-open texture pop-in that registerPanel-based panels never had.
		preloadPanelImages(inst)
	end
end

-- Safety net around _adoptCandidate: MenuSystem.new runs before Loader.boot,
-- so any uncaught error here would kill client boot entirely. A malformed
-- candidate (or anything else unexpected) is logged and skipped instead.
function MenuSystem:_safeAdopt(inst, kind)
	local ok, err = pcall(self._adoptCandidate, self, inst, kind)
	if not ok then
		warn(("[RBLXE] adoption failed for '%s': %s"):format(inst.Name, tostring(err)))
	end
end

function MenuSystem:_findAncestorPanel(instance)
	local current = instance.Parent
	while current and current ~= self.gui do
		-- Identity lookup, not a Name-keyed probe: adopted panels register
		-- under their PanelId attribute, which may differ from the
		-- instance's own Name - self._panels[current.Name] would miss those.
		for _, panel in pairs(self._panels) do
			if panel == current then
				return current
			end
		end
		current = current.Parent
	end
	return nil
end

-- Action routing. Real clicks call this from _wireButton; tests call it
-- directly because Activated cannot be fired programmatically.
function MenuSystem:_onButtonActivated(button)
	local action = button:GetAttribute("Action")
	if not action or action == "" then return end
	if action == "TogglePanel" then
		self:togglePanel(button:GetAttribute("TargetPanel") or "")
	elseif action == "ClosePanel" then
		local target = button:GetAttribute("TargetPanel") or ""
		if target ~= "" then
			self:closePanel(target)
		else
			local ancestor = self:_findAncestorPanel(button)
			if ancestor then ancestor.Visible = false end
		end
	else
		self:dispatch(action, button)
	end
end

-- ── wiring (Task 8 fills these in) ───────────────────────────────────

function MenuSystem:_wireDescendant(desc)
	if desc:IsA("GuiButton") then
		self:_wireButton(desc)
	elseif desc:IsA("GuiObject") then
		self:_wireDecoration(desc)
	end
end

function MenuSystem:_wireButton(button)
	if self._wired[button] then return end
	self._wired[button] = true
	if CollectionService:HasTag(button, "UI_Toggle") then return end
	-- A blocker is a GuiButton only so that it SINKS input - the invisible
	-- full-screen sheet a modal panel puts behind itself so clicks never
	-- reach what it covers. It is not something the player presses, so it
	-- gets no press sound, no hover sound, no icon tween and no tooltip.
	-- Reported by the user against Roulette System's Spin: "cada vez que
	-- clico el fondo del menu Spin suena el SFX de click". Declared with a
	-- tag rather than inferred: 100 GuiButtons across the shipped v2 assets
	-- carry neither a tag nor an Action (Teleport zones, Claim buttons,
	-- Equip/Merge/Delete, the music player's Next/Mute - all wired by pack
	-- code and all of them SHOULD click), so there is no property of a
	-- blocker to detect. It still sinks input exactly as before: absorbing
	-- clicks is what a GuiButton does, not something this wiring conferred.
	if CollectionService:HasTag(button, "UI_Blocker") then return end

	button.Activated:Connect(function()
		SFX:Play(button:GetAttribute("BuySound") and "Buy_1" or "Press")
		self:_onButtonActivated(button)
	end)

	local icon = button:FindFirstChild("Icon")
	button.MouseEnter:Connect(function()
		SFX:Play("Hover")
		if icon then
			icon:TweenSize(ICON_HOVER_SCALE, Enum.EasingDirection.Out, Enum.EasingStyle.Quint, ICON_HOVER_TWEEN_TIME, true)
		end
		if button:GetAttribute("ShowTooltip") then
			self._tooltip.Visible = true
			self._tooltip.ButtonName.Text = button:GetAttribute("TooltipText") or button.Name
		end
	end)
	local lastTooltipMove = 0
	button.MouseMoved:Connect(function()
		if button:GetAttribute("ShowTooltip") then
			local now = tick()
			if now - lastTooltipMove < TOOLTIP_THROTTLE_INTERVAL then return end
			lastTooltipMove = now
			self._tooltip.Position = UDim2.fromOffset(
				self._mouse.X + TOOLTIP_OFFSET_X, self._mouse.Y + TOOLTIP_OFFSET_Y)
		end
	end)
	button.MouseLeave:Connect(function()
		if icon then
			icon:TweenSize(ICON_DEFAULT_SCALE, Enum.EasingDirection.Out, Enum.EasingStyle.Quint, ICON_LEAVE_TWEEN_TIME, true)
		end
		if button:GetAttribute("ShowTooltip") then
			self._tooltip.Visible = false
		end
	end)
end

function MenuSystem:_wireDecoration(element)
	if self._wired[element] then return end
	local animation = element:GetAttribute("Animation")
	if not animation then return end
	if element.Parent == self._menus then return end -- panels use Animation for their open-anim
	self._wired[element] = true
	if animation == "Rotate" then
		local speed = element:GetAttribute("Speed") or 15
		TweenService:Create(element,
			TweenInfo.new(speed, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, -1),
			{ Rotation = 360 }):Play()
	elseif animation == "HoverBob" then
		local basePos = element.Position
		element.MouseEnter:Connect(function()
			element:TweenPosition(basePos - UDim2.fromScale(0, 0.03),
				Enum.EasingDirection.Out, Enum.EasingStyle.Quint, 0.2, true)
		end)
		element.MouseLeave:Connect(function()
			element:TweenPosition(basePos,
				Enum.EasingDirection.Out, Enum.EasingStyle.Quint, 0.2, true)
		end)
	end
end

function MenuSystem:_wireToggle(button)
	if not button:IsA("GuiButton") then return end
	if not button:IsDescendantOf(self._playerGui) then return end -- ignore RS/asset templates
	if self._toggled[button] then return end
	local state = { isEnabled = true }
	self._toggled[button] = state
	local function setVisual()
		button.BackgroundColor3 = state.isEnabled and TOGGLE_COLOR_ON or TOGGLE_COLOR_OFF
		local status = button:FindFirstChild("Status")
		if status then status.Text = state.isEnabled and "ON" or "OFF" end
	end
	setVisual()
	button.MouseButton1Click:Connect(function()
		state.isEnabled = not state.isEnabled
		setVisual()
		local action = button:GetAttribute("Action")
		if action then self:dispatch(action, button) end
	end)
end

return MenuSystem
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="15">
            <Properties>
              <string name="Name">PanelAnimations</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- PanelAnimations loader
-- Each child ModuleScript is an animation: its name maps to the Animation
-- attribute on UI_Panel, and it must return function(menu: GuiObject).
local Animations = {}

for _, child in ipairs(script:GetChildren()) do
	if child:IsA("ModuleScript") then
		local ok, fn = pcall(require, child)
		if ok and type(fn) == "function" then
			Animations[child.Name] = fn
		end
	end
end

return Animations
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
            <Item class="ModuleScript" referent="16">
              <Properties>
                <string name="Name">Bounce</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <ContentId name="LinkedSource">
                  <null>
                  </null>
                </ContentId>
                <bool name="DefinesCapabilities">false</bool>
                <string name="Source"><![CDATA[-- Bounce animation: drops from slightly above with Back easing and a small random rotation.
--
-- Works on ANY GuiObject. It used to bail out on anything that was not a
-- CanvasGroup - a guard with nothing behind it, since the animation only ever
-- touches Position and Rotation and never GroupTransparency. Two of General
-- UI Kit's thirteen shipped panels are plain Frames (TradeRequest, Spin), and
-- both ask for Bounce, so both opened with a hard cut and no animation at all
-- while every CanvasGroup panel bounced. Found 2026-08-21 via the plugin's
-- new MENUS Preview button; the guard is removed rather than the panels
-- converted, because a CanvasGroup rasterizes its descendants into a canvas
-- its own size and both panels have content that deliberately overhangs
-- (TradeRequest's Icon at x=-0.07/y=-0.22, Spin's CoinsEffect spawn line at
-- y=-0.1) - converting them would have cropped it.
local TweenService = game:GetService("TweenService")

local DURATION   = 0.5
local OFFSET_Y   = 0.02  -- how far above the resting position the panel starts

return function(menu: GuiObject)
	local origPos = menu.Position
	menu.Position = UDim2.new(origPos.X.Scale, origPos.X.Offset, origPos.Y.Scale - OFFSET_Y, origPos.Y.Offset)
	menu.Rotation = math.random(2) == 1 and 1 or -1

	menu:TweenPosition(
		origPos,
		Enum.EasingDirection.Out, Enum.EasingStyle.Back, DURATION, true
	)
	TweenService:Create(
		menu,
		TweenInfo.new(DURATION, Enum.EasingStyle.Quint, Enum.EasingDirection.Out),
		{ Rotation = 0 }
	):Play()
end
]]></string>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
          </Item>
          <Item class="ModuleScript" referent="17">
            <Properties>
              <string name="Name">Pieces</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- Pieces.lua - interface pieces (RBLX Essentials 3.0).
--
-- A pack declares the pieces of interface it drives in `Packs/<PackId>/Pieces`
-- (see Contracts.lua). Each piece has one DEFAULT copy, shipped by the pack
-- under StarterGui.RBLXEssentials.<PackId>, and may have one OVERRIDE, owned
-- by the game, under StarterGui.RBLXEssentials.Interface (a theme's piece or
-- a fork). Piece roots carry RBLXE_Piece = "<PackId>.<Name>".
--
-- The server decides once, at boot (`plan`), which copy each piece uses: a
-- valid override, else the default. Each decision is published as the
-- relative path of the chosen instance under the RBLXEssentials root (for
-- example "Interface/ThemeGui/Panel" or "Demo/DemoGui/Panel"), together with
-- the path of every other candidate copy the server saw for that id (the
-- refused ones), reaching every client in the RBLXE_PieceWinners attribute
-- (`encodeDecisions`/`decodeDecisions`). The client (`runtime`) admits only
-- the first instance of a piece whose own path under its nearest
-- RBLXEssentials ancestor matches the winner, and retires the copies that
-- arrive at a refused path or as a second copy at the winner path, so no pack
-- ever holds a copy that is later replaced (the 434 failure). Any other
-- instance carrying RBLXE_Piece (a clone a pack makes at runtime: a toast, a
-- billboard per adornee, a registerPanel copy) is not admitted but is left
-- alone: clones belong to the pack that made them.
--
-- The server makes StarterGui.RBLXEssentials survive character respawns
-- before `plan` runs (UI/RootPersistence.lua), so a piece admitted here
-- stays the same instance for the whole session.
--
-- Pure: every Instance, require function and scheduler comes in as an argument.
local Pieces = {}

Pieces.PIECE_ATTRIBUTE = "RBLXE_Piece"
Pieces.SOURCE_ATTRIBUTE = "RBLXE_Source"
Pieces.WINNERS_ATTRIBUTE = "RBLXE_PieceWinners"
Pieces.LEGACY = "legacy"
Pieces.INTERFACE = "Interface"
Pieces.ROOT = "RBLXEssentials"
-- StarterGui.RBLXEssentials.Interface.Custom holds what the 2.1 to 3.0
-- migration kept without managing it (T.migrate: retired panels and
-- buttons). Nothing there is a candidate for any piece, here or in Tools.
Pieces.CUSTOM = "Custom"
-- How long getPiece waits for PlayerGui.RBLXEssentials (it arrives with the
-- first character) before its own timeout starts counting (RT-4).
Pieces.ROOT_WAIT = 120

local function pieceIdOf(inst)
	local id = inst:GetAttribute(Pieces.PIECE_ATTRIBUTE)
	if type(id) == "string" and id ~= "" then
		return id
	end
	return nil
end

local function sortedKeys(t)
	local keys = {}
	for key in pairs(t) do
		table.insert(keys, key)
	end
	table.sort(keys)
	return keys
end

-- ── declarations ─────────────────────────────────────────────────────

-- collectDeclarations(...) -> (declarations, warnings, failures). `failures`
-- maps the id of a pack whose Pieces module errors or is invalid to the
-- reason its boot fails with (RT-6): its pieces can never be resolved, so
-- the Loader fails it instead of letting it drive an interface nobody sees.
function Pieces.collectDeclarations(packsFolder, requireFn, contracts)
	local declarations, warnings, failures = {}, {}, {}
	for _, packFolder in ipairs(packsFolder:GetChildren()) do
		local module = packFolder:FindFirstChild("Pieces")
		if module ~= nil and module:IsA("ModuleScript") then
			local ok, specs = pcall(requireFn, module)
			if not ok then
				local message = ("%s.Pieces errored: %s"):format(packFolder.Name, tostring(specs))
				table.insert(warnings, message)
				failures[packFolder.Name] = "invalid Pieces: " .. message
			else
				local errors = contracts.validateSpecs(packFolder.Name, specs)
				if #errors > 0 then
					for _, message in ipairs(errors) do
						table.insert(warnings, message)
					end
					failures[packFolder.Name] = "invalid Pieces:\n    " .. table.concat(errors, "\n    ")
				else
					declarations[packFolder.Name] = specs
				end
			end
		end
	end
	table.sort(warnings)
	return declarations, warnings, failures
end

-- ── server plan ──────────────────────────────────────────────────────

-- byId keeps the first copy of each id (sorted by full name); all keeps
-- every copy, so the plan can publish the paths of the ones it refuses.
local function index(list, origin, warnings, all)
	table.sort(list, function(a, b)
		return a:GetFullName() < b:GetFullName()
	end)
	local byId = {}
	for _, inst in ipairs(list) do
		local id = pieceIdOf(inst)
		all[id] = all[id] or {}
		table.insert(all[id], inst)
		if byId[id] ~= nil then
			table.insert(warnings, ("%s: more than one %s piece; using %s"):format(id, origin, byId[id]:GetFullName()))
		else
			byId[id] = inst
		end
	end
	return byId
end

-- The attribute that ties each piece kind to MenuSystem: a Panel is
-- registered under its PanelId, a HudButton opens its TargetPanel. An
-- Interface override must keep the default copy's value, or the button that
-- opens the panel (and every pack that asks for it) would miss it.
local IDENTITY = { Panel = "PanelId", HudButton = "TargetPanel" }

local function nonEmptyText(value)
	if type(value) == "string" and value ~= "" then
		return value
	end
	return nil
end

local function quoted(value)
	if type(value) == "string" then
		return ("%q"):format(value)
	end
	return tostring(value)
end

-- The path of `inst` under `root`, as the names of every node strictly
-- between them, joined by "/" (for example "Interface/ThemeGui/Panel").
local function relativePath(inst, root)
	local segments = {}
	local node = inst
	while node ~= nil and node ~= root do
		table.insert(segments, 1, node.Name)
		node = node.Parent
	end
	return table.concat(segments, "/")
end

-- winners maps each declared piece id to the relative path of the instance
-- chosen to satisfy it; a piece with nothing valid to use has no entry.
-- refused maps every id seen under the root, declared or not, to the paths
-- of its other copies (the default behind a valid override, a rejected
-- override, duplicates, copies of an id no pack declares): the client
-- retires exactly those, and nothing else.
function Pieces.plan(args)
	local contracts = args.contracts
	local root = args.root
	local interface = root and root:FindFirstChild(Pieces.INTERFACE)
	local custom = interface and interface:FindFirstChild(Pieces.CUSTOM)
	local defaultList, overrideList = {}, {}
	if root ~= nil then
		for _, inst in ipairs(root:GetDescendants()) do
			if pieceIdOf(inst) ~= nil and not (custom ~= nil and inst:IsDescendantOf(custom)) then
				if interface ~= nil and inst:IsDescendantOf(interface) then
					table.insert(overrideList, inst)
				else
					table.insert(defaultList, inst)
				end
			end
		end
	end

	local warnings, copies = {}, {}
	local defaults = index(defaultList, "default", warnings, copies)
	local overrides = index(overrideList, "Interface", warnings, copies)
	local declared, order, chosen, fromInterface, kinds = {}, {}, {}, {}, {}

	-- Pass 1: each piece's contract, and an override's identity (PanelId or
	-- TargetPanel) against its default.
	for _, packId in ipairs(sortedKeys(args.declarations)) do
		local specs = args.declarations[packId]
		for _, name in ipairs(sortedKeys(specs)) do
			local spec = specs[name]
			local id = packId .. "." .. name
			declared[id] = { packId = packId, spec = spec }
			kinds[id] = spec.kind
			table.insert(order, id)
			local default = defaults[id]
			if default ~= nil then
				local problems = contracts.check(default, spec)
				if #problems > 0 then
					table.insert(warnings, ("%s: default piece does not satisfy its contract: %s"):format(id, contracts.formatProblems(problems)))
				end
			end
			chosen[id] = default
			local override = overrides[id]
			if override ~= nil then
				local reason = nil
				local problems = contracts.check(override, spec)
				if #problems > 0 then
					reason = contracts.formatProblems(problems)
				else
					local attribute = IDENTITY[spec.kind]
					local want = attribute ~= nil and default ~= nil and nonEmptyText(default:GetAttribute(attribute)) or nil
					local got = attribute ~= nil and override:GetAttribute(attribute) or nil
					if want ~= nil and got ~= want then
						reason = ("%s is %s, the default's is %s"):format(attribute, quoted(got), quoted(want))
					end
				end
				if reason == nil then
					chosen[id] = override
					fromInterface[id] = true
				else
					table.insert(warnings, ("%s: Interface piece rejected, using the default: %s"):format(id, reason))
				end
			end
		end
	end

	-- Pass 2: `opens`. The chosen HudButton must target the PanelId of the
	-- chosen copy of the panel it opens, or it would open nothing.
	for _, id in ipairs(order) do
		local entry = declared[id]
		local opens = entry.spec.opens
		local panel = opens ~= nil and chosen[entry.packId .. "." .. opens] or nil
		if panel ~= nil and chosen[id] ~= nil then
			local panelId = panel:GetAttribute("PanelId")
			local function mismatch(button)
				local target = button:GetAttribute("TargetPanel")
				if target ~= panelId then
					return ("TargetPanel is %s but %s.%s has PanelId %s"):format(quoted(target), entry.packId, opens, quoted(panelId))
				end
				return nil
			end
			local reason = mismatch(chosen[id])
			if reason ~= nil and fromInterface[id] then
				table.insert(warnings, ("%s: Interface piece rejected, using the default: %s"):format(id, reason))
				fromInterface[id] = nil
				chosen[id] = defaults[id]
				reason = chosen[id] ~= nil and mismatch(chosen[id]) or nil
			end
			if reason ~= nil then
				table.insert(warnings, ("%s: default piece does not satisfy its contract: %s"):format(id, reason))
			end
		end
	end

	local winners, refused, detail = {}, {}, {}
	local total, interfaceCount = #order, 0
	for _, id in ipairs(order) do
		local inst = chosen[id]
		if inst == nil then
			table.insert(warnings, ("%s: no piece available (no valid Interface piece and no default)"):format(id))
		else
			winners[id] = relativePath(inst, root)
			if fromInterface[id] then
				interfaceCount += 1
				table.insert(detail, ("%s <- Interface (%s)"):format(id, tostring(inst:GetAttribute(Pieces.SOURCE_ATTRIBUTE) or "custom")))
			end
		end
	end
	for _, id in ipairs(sortedKeys(copies)) do
		local seen = {}
		for _, inst in ipairs(copies[id]) do
			local path = relativePath(inst, root)
			if inst ~= chosen[id] and path ~= winners[id] and not seen[path] then
				seen[path] = true
				refused[id] = refused[id] or {}
				table.insert(refused[id], path)
			end
		end
		if refused[id] ~= nil then
			table.sort(refused[id])
		end
	end
	for _, id in ipairs(sortedKeys(overrides)) do
		if declared[id] == nil then
			table.insert(warnings, ("%s: Interface piece ignored, no installed pack declares it (%s)"):format(id, overrides[id]:GetFullName()))
		end
	end
	table.sort(warnings)

	local lines = {}
	if total > 0 then
		table.insert(lines, ("%d pieces: %d from Interface, %d default"):format(total, interfaceCount, total - interfaceCount))
		for _, line in ipairs(detail) do
			table.insert(lines, line)
		end
	end
	return { winners = winners, refused = refused, kinds = kinds, lines = lines, warnings = warnings }
end

-- The RBLXE_PieceWinners value: the plan's winners and refused paths as JSON.
-- `jsonEncode` is HttpService:JSONEncode in the Kernel; the server calls this
-- inside the same pcall as the plan, so a value JSON cannot encode (an
-- instance name that is not valid UTF-8, for example) falls back to LEGACY
-- instead of stopping the server script.
function Pieces.encodeDecisions(plan, jsonEncode)
	return jsonEncode({ winners = plan.winners, refused = plan.refused or {}, kinds = plan.kinds or {} })
end

-- The client side of encodeDecisions. Returns { winners, refused }, or nil
-- when the server sent no usable decision (missing, LEGACY, not JSON, or not
-- the { winners, refused } shape): every piece is then adopted as in 2.x.
-- An empty winners map encodes as "[]" and decodes to an empty table.
function Pieces.decodeDecisions(encoded, jsonDecode)
	if type(encoded) ~= "string" or encoded == Pieces.LEGACY then
		return nil
	end
	local ok, decoded = pcall(jsonDecode, encoded)
	if not ok or type(decoded) ~= "table" or type(decoded.winners) ~= "table" then
		return nil
	end
	local refused = decoded.refused
	if type(refused) ~= "table" then
		refused = {}
	end
	local kinds = decoded.kinds
	if type(kinds) ~= "table" then
		kinds = {}
	end
	return { winners = decoded.winners, refused = refused, kinds = kinds }
end

-- strays(starterGui, root) -> warnings (M-c): every RBLXE_Piece instance in
-- StarterGui outside StarterGui.RBLXEssentials. Nothing resolves or retires
-- it, so it shows on every client as it is. The server says so once at boot
-- (Kernel.server.lua) and T.validate says it in Studio. A clone a pack makes
-- at runtime never reaches StarterGui, so it never warns.
function Pieces.strays(starterGui, root)
	local warnings = {}
	if starterGui == nil then
		return warnings
	end
	for _, inst in ipairs(starterGui:GetDescendants()) do
		local id = pieceIdOf(inst)
		if id ~= nil and not (root ~= nil and (inst == root or inst:IsDescendantOf(root))) then
			table.insert(warnings, ("%s: %s is outside StarterGui.%s: the Kernel never uses it, and it shows on screen as it is")
				:format(id, inst:GetFullName(), Pieces.ROOT))
		end
	end
	table.sort(warnings)
	return warnings
end

-- ── client runtime ───────────────────────────────────────────────────

function Pieces.originOf(inst)
	local node = inst.Parent
	while node ~= nil do
		if node.Name == Pieces.INTERFACE and node.Parent ~= nil and node.Parent.Name == Pieces.ROOT then
			return "Interface"
		end
		node = node.Parent
	end
	return "Default"
end

-- The path of `inst` under its nearest ancestor named Pieces.ROOT
-- ("RBLXEssentials"), matching what the server published in `plan`'s
-- winners. Nil when `inst` has no such ancestor at all.
local function pathUnderRoot(inst)
	local segments = {}
	local node = inst
	while node ~= nil do
		local parent = node.Parent
		if parent == nil then
			return nil
		end
		table.insert(segments, 1, node.Name)
		if parent.Name == Pieces.ROOT then
			return table.concat(segments, "/")
		end
		node = parent
	end
	return nil
end

local Runtime = {}
Runtime.__index = Runtime

-- decisions is what decodeDecisions returns (or a plan result): winners maps
-- a piece id to the relative path (relativePath/pathUnderRoot) of the
-- instance chosen to satisfy it, refused maps an id to the paths of the
-- copies the server refused. decisions == nil means the server sent no
-- decision at all: 2.x adoption for every piece.
function Pieces.runtime(decisions, opts)
	opts = opts or {}
	local self = setmetatable({}, Runtime)
	self._winners = decisions and decisions.winners or nil
	self._refused = {}
	for id, paths in pairs(decisions and decisions.refused or {}) do
		if type(paths) == "table" then
			local set = {}
			for _, path in ipairs(paths) do
				set[path] = true
			end
			self._refused[id] = set
		end
	end
	self._admitted = {}
	self._decided = setmetatable({}, { __mode = "k" })
	self._kinds = decisions and decisions.kinds or {}
	self._noDecision = {}
	-- RT-4: getPiece counts its timeout only once PlayerGui.RBLXEssentials
	-- exists. attach() records the PlayerGui; without one (a spec) the root
	-- counts as arrived.
	self._playerGui = opts.playerGui
	self._rootWait = opts.rootWait or Pieces.ROOT_WAIT
	self._wait = opts.wait or function(seconds)
		return seconds
	end
	self._warn = opts.warn or function() end
	self._defer = opts.defer or function(fn)
		fn()
	end
	return self
end

-- The declared kind of a piece id ("Panel", "HudButton", ...) as the
-- server's decision carries it, or nil. MenuSystem:getPiece warns from it
-- about a menu piece it never adopted, even one that lacks its tag (M-d).
function Runtime:kindOf(pieceId)
	local kind = self._kinds[pieceId]
	return if type(kind) == "string" then kind else nil
end

-- One warning per id the server's decision does not contain (T-G, M5): no
-- installed pack declares it, or no copy of it was usable.
function Runtime:_warnNoDecision(id, where)
	if self._noDecision[id] then
		return
	end
	self._noDecision[id] = true
	self._warn(("[RBLXE] UI: piece %s has no decision from the server (no installed pack declares it, or no copy of it is usable)%s")
		:format(id, if where ~= nil then ("; %s is not used"):format(where) else ""))
end

function Runtime:_retire(inst)
	pcall(function()
		if inst:IsA("GuiObject") then
			inst.Visible = false
		elseif inst:IsA("LayerCollector") then
			inst.Enabled = false
		end
	end)
	-- Deferred: the clone may still be mid-parenting when it is refused.
	self._defer(function()
		inst:Destroy()
	end)
end

-- Decides once per instance (the answer is cached, so a later move into the
-- MenuSystem shell cannot change it) and returns whether it may be used.
-- Only copies competing for the id are retired: one at a path the server
-- refused, or a second one at the winner path. Any other instance carrying
-- RBLXE_Piece (a clone a pack made) is refused but left alone.
function Runtime:admit(inst)
	local cached = self._decided[inst]
	if cached ~= nil then
		return cached
	end
	local id = pieceIdOf(inst)
	local admitted = true
	if id ~= nil then
		if self._winners == nil then
			self._admitted[id] = self._admitted[id] or inst
		else
			local winnerPath = self._winners[id]
			local path = pathUnderRoot(inst)
			if winnerPath == nil then
				admitted = false
				self:_warnNoDecision(id, inst:GetFullName())
				local refused = self._refused[id]
				if path ~= nil and refused ~= nil and refused[path] then
					self:_retire(inst)
				end
			elseif path ~= nil and path == winnerPath then
				local incumbent = self._admitted[id]
				if incumbent == nil then
					self._admitted[id] = inst
				else
					admitted = false
					if incumbent.Parent == nil then
						self._warn(("[RBLXE] UI: piece %s was destroyed; its new copy %s is refused (pieces are admitted once per session)")
							:format(id, inst:GetFullName()))
					else
						-- M-b: never removed without a word.
						self._warn(("[RBLXE] UI: piece %s: a second copy arrived at %s and was removed; the first copy keeps the piece")
							:format(id, inst:GetFullName()))
					end
					self:_retire(inst)
				end
			else
				admitted = false
				local refused = self._refused[id]
				if path ~= nil and refused ~= nil and refused[path] then
					self:_retire(inst)
				end
			end
		end
	end
	self._decided[inst] = admitted
	return admitted
end

-- Whether `inst` is the copy the server's decision chose for its id (always
-- false in 2.x legacy mode, where nothing was decided). MenuSystem adopts
-- such a piece over any 2.x panel or button with the same key.
function Runtime:chose(inst)
	if self._winners == nil then
		return false
	end
	local id = pieceIdOf(inst)
	return id ~= nil and self._admitted[id] == inst
end

function Runtime:attach(playerGui)
	self._playerGui = playerGui
	local function consider(inst)
		if pieceIdOf(inst) ~= nil then
			self:admit(inst)
		end
	end
	for _, inst in ipairs(playerGui:GetDescendants()) do
		consider(inst)
	end
	return playerGui.DescendantAdded:Connect(consider)
end

-- get(pieceId, timeout?) -> the admitted piece, or nil. An id the server's
-- decision does not contain returns at once (M5): no copy of it will ever be
-- admitted. Otherwise the timeout (default 10 s) counts only while
-- PlayerGui.RBLXEssentials exists (RT-4): the root arrives with the first
-- character, which a slow client or a pack like DatastorePurchaseSystem can
-- delay; waiting for the root is bounded by rootWait on its own.
function Runtime:get(pieceId, timeout)
	if self._winners ~= nil and self._winners[pieceId] == nil then
		self:_warnNoDecision(pieceId, nil)
		return nil
	end
	local limit = timeout or 10
	local elapsed, waitedForRoot = 0, 0
	while true do
		local found = self._admitted[pieceId]
		if found ~= nil and found.Parent ~= nil then
			return found
		end
		local rootHere = self._playerGui == nil or self._playerGui:FindFirstChild(Pieces.ROOT) ~= nil
		if rootHere and elapsed >= limit then
			break
		end
		if not rootHere and waitedForRoot >= self._rootWait then
			self._warn(("[RBLXE] UI: piece %s: PlayerGui.%s did not arrive within %ss (the player has no character yet?)")
				:format(pieceId, Pieces.ROOT, tostring(self._rootWait)))
			return nil
		end
		local step = self._wait(0.1)
		step = if type(step) == "number" and step > 0 then step else 0.1
		if rootHere then
			elapsed += step
		else
			waitedForRoot += step
		end
	end
	self._warn(("[RBLXE] UI: piece %s did not appear within %ss"):format(pieceId, tostring(limit)))
	return nil
end

return Pieces
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="18">
            <Properties>
              <string name="Name">RootPersistence</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- RootPersistence.lua - keeps StarterGui.RBLXEssentials alive across
-- character respawns (Kernel 2.1.1).
--
-- WHY: Roblox resets PlayerGui on every character spawn. Each StarterGui
-- child is destroyed and cloned again unless it is a top-level
-- LayerCollector with ResetOnSpawn = false. The installer creates
-- StarterGui.RBLXEssentials as a Folder, which has no ResetOnSpawn, so the
-- whole tree was destroyed on the server and cloned again on every respawn
-- (the ResetOnSpawn = false of each pack ScreenGui nested inside is
-- ignored: only top-level LayerCollectors count). That server-side Destroy
-- replicates to every instance the client had moved out of the tree,
-- including every panel and HUD button MenuSystem adopted into
-- RBLXEssentialsGUI, so after the first death the menus and the HUD buttons
-- were gone. Diagnosed natively on 2026-09-23.
--
-- The server calls persist once at boot, before packs load, to turn a
-- non-ScreenGui root into a ScreenGui with ResetOnSpawn = false. The root
-- instance itself is replaced (the old Folder is destroyed), but the new one
-- keeps its name, attributes, tags and children, so every
-- PlayerGui.RBLXEssentials.<PackId>.<Gui> path stays the same: look the root
-- up by name after boot. The holder is neutral (no insets, no safe-area
-- clipping), so it adds no layout of its own, and the pack ScreenGuis nested
-- inside keep rendering as independent layers.
--
-- Pure: no `game` access. `create` is Instance.new and `enums` is Enum,
-- both injected so Lune specs can pass @lune/roblox's versions.
local RootPersistence = {}

RootPersistence.ROOT = "RBLXEssentials"

-- The nearest ancestor of `inst` (not `inst` itself) that is a GuiObject or
-- a LayerCollector, or nil when there is none.
local function nearestGuiAncestor(inst)
	local node = inst.Parent
	while node ~= nil do
		if node:IsA("GuiObject") or node:IsA("LayerCollector") then
			return node
		end
		node = node.Parent
	end
	return nil
end

-- Reports every instance under the root whose behavior changes now that the
-- root persists instead of resetting on spawn:
--   * client code (a LocalScript, or a Script with RunContext Client) now
--     runs once per session instead of on every respawn;
--   * a nested LayerCollector that asked to reset on spawn no longer does;
--   * only when the old root was not a LayerCollector (a Folder, which
--     renders nothing): a GuiObject with no GuiObject or LayerCollector
--     between it and the root now renders. Only the top of each such subtree
--     is reported, so a Frame > Folder > Frame chain gives one warning. When
--     the old root was already a ScreenGui, those objects were already
--     rendering, so they are not reported.
local function audit(root, enums, warnings, rootWasLayerCollector)
	for _, inst in ipairs(root:GetDescendants()) do
		if inst:IsA("LocalScript") or (inst:IsA("Script") and inst.RunContext == enums.RunContext.Client) then
			table.insert(warnings, ("%s: now runs once per session instead of on every respawn"):format(inst:GetFullName()))
		elseif inst:IsA("LayerCollector") then
			if inst.ResetOnSpawn == true then
				table.insert(warnings, ("%s: no longer resets on spawn"):format(inst:GetFullName()))
			end
		elseif inst:IsA("GuiObject") then
			if not rootWasLayerCollector and nearestGuiAncestor(inst) == root then
				table.insert(warnings, ("%s: now renders (it has no ScreenGui of its own)"):format(inst:GetFullName()))
			end
		end
	end
end

-- Makes StarterGui.RBLXEssentials survive respawn. Returns (root, warnings);
-- root is nil only when StarterGui has no RBLXEssentials child.
function RootPersistence.persist(starterGui, create, enums)
	local warnings = {}
	local root = starterGui:FindFirstChild(RootPersistence.ROOT)

	local matches = 0
	for _, child in ipairs(starterGui:GetChildren()) do
		if child.Name == RootPersistence.ROOT then
			matches += 1
		end
	end
	if matches > 1 and root ~= nil then
		table.insert(warnings, ("more than one StarterGui child is named %s; only %s was made persistent")
			:format(RootPersistence.ROOT, root:GetFullName()))
	end

	if root == nil then
		return nil, warnings
	end

	if root:IsA("ScreenGui") then
		-- Already converted, or the root may already be a ScreenGui if the
		-- game replaced it by hand: only make sure it survives respawn, and
		-- audit only when that is a change. Its GuiObjects already rendered.
		if root.ResetOnSpawn then
			root.ResetOnSpawn = false
			audit(root, enums, warnings, true)
		end
		table.sort(warnings)
		return root, warnings
	end

	local holder = create("ScreenGui")
	holder.Name = RootPersistence.ROOT
	holder.ResetOnSpawn = false
	holder.ScreenInsets = enums.ScreenInsets.None
	holder.ClipToDeviceSafeArea = false
	holder.SafeAreaCompatibility = enums.SafeAreaCompatibility.None
	for key, value in pairs(root:GetAttributes()) do
		holder:SetAttribute(key, value)
	end
	for _, tag in ipairs(root:GetTags()) do
		holder:AddTag(tag)
	end

	local moved = {}
	local moveOk, moveErr = pcall(function()
		for _, child in ipairs(root:GetChildren()) do
			child.Parent = holder
			table.insert(moved, child)
		end
	end)
	if not moveOk then
		-- Put back whatever already moved, so nothing is left orphaned.
		local stranded = {}
		for _, child in ipairs(moved) do
			local backOk = pcall(function()
				child.Parent = root
			end)
			if not backOk then
				table.insert(stranded, child)
			end
		end
		if #stranded == 0 then
			holder:Destroy()
		else
			-- Destroying the holder would destroy these children with it.
			for _, child in ipairs(stranded) do
				table.insert(warnings, ("%s could not be returned to %s"):format(child.Name, RootPersistence.ROOT))
			end
		end
		table.insert(warnings, ("%s: root not made persistent; menus, HUD buttons and pack interfaces may stop working after a respawn (%s)")
			:format(RootPersistence.ROOT, tostring(moveErr)))
		table.sort(warnings)
		return root, warnings
	end

	-- Parented before the Folder is destroyed, with no yield in between, so
	-- the moved children always have a root.
	holder.Parent = starterGui
	root:Destroy()

	audit(holder, enums, warnings, false)
	table.sort(warnings)
	return holder, warnings
end

return RootPersistence
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="19">
            <Properties>
              <string name="Name">Shell</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- Builds the kernel's ScreenGui shell programmatically - the kernel ships no
-- Studio-only GUI assets. Packs contribute their own panels via registerPanel.
return function(playerGui)
	local gui = Instance.new("ScreenGui")
	gui.Name = "RBLXEssentialsGUI"
	gui.ResetOnSpawn = false
	gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	gui.DisplayOrder = 5
	-- Parity with v1 MenusGUI: ignore topbar inset to prevent coordinate
	-- shift of scale-positioned content and mouse-follow tooltip.
	gui.IgnoreGuiInset = true

	local menus = Instance.new("Frame")
	menus.Name = "Menus"
	menus.BackgroundTransparency = 1
	menus.Size = UDim2.fromScale(1, 1)
	-- Panels draw ABOVE the HUD. This frame and the interactables layer are
	-- effective render siblings under the ScreenGui - `Interactables` below
	-- is a Folder, not a GuiObject, so it creates no stacking context of its
	-- own and `Container` is compared straight against this frame. Both used
	-- to sit at the default ZIndex 1: a tie, broken only by the order the
	-- children happen to be parented in, which put the HUD on top. Any
	-- full-screen panel then rendered UNDER the nav icons at the left HUD
	-- spot - reported against Roulette System's Spin, whose panel dims the
	-- whole screen at BackgroundTransparency 0.5 and is the first v2 panel
	-- big enough to reach that strip at all. Explicit ranks remove the tie
	-- instead of relying on parenting order. Anything a pack clones in as a
	-- sibling group (GUK's Effects/InviteFriends) keeps the default 1 and so
	-- also stays below panels; the Tooltip (50) still wins over everything.
	menus.ZIndex = 10
	menus.Parent = gui

	-- Folder, not Frame: matches v1 (EC's designed MenusGUI.Interactables) -
	-- layout-transparent, so GuiObject children (Container, and any
	-- pack-cloned sibling groups) size/position relative to the ScreenGui
	-- instead of compressing into an invented strip. Folders have no
	-- Size/Position/AnchorPoint to set.
	local interactables = Instance.new("Folder")
	interactables.Name = "Interactables"
	interactables.Parent = gui

	-- Geometry below matches GUK v2's designed Container (EC lineage;
	-- General UI Kit v2/src/StarterGui/GeneralUIKit/GeneralUIKitGUI.rbxm ->
	-- Interactables.Container pre-M1-unwrap, verified directly by a Lune
	-- probe against `git show 8b0e864:"General UI Kit v2/src/
	-- ReplicatedStorage/GeneralUIKit/Assets/Interactables.rbxm"` - see
	-- task-M1-report.md's I2 fix section for the probe output) as the
	-- kernel default for ALL packs, designed or not: since Task M1, GUK
	-- ships its 10 nav buttons directly adopted into THIS Container (no
	-- pack ever overwrites this geometry anymore - the old "GUK v2
	-- overwrites this after cloning its layout" comment this replaced is
	-- stale post-M1).
	local container = Instance.new("Frame")
	container.Name = "Container"
	container.BackgroundTransparency = 1
	container.AnchorPoint = Vector2.new(0, 0.5)
	container.Position = UDim2.fromScale(0.01, 0.5)
	container.Size = UDim2.fromScale(0.2, 0.38)
	-- Explicit even though it equals the Frame default: this is the low half
	-- of the rank documented on `menus.ZIndex` above, and a silent default is
	-- exactly what produced the tie in the first place.
	container.ZIndex = 1
	container.Parent = interactables

	local grid = Instance.new("UIGridLayout")
	grid.CellSize = UDim2.fromScale(0.24, 0.24)
	grid.CellPadding = UDim2.fromScale(0.03, 0.01)
	grid.FillDirection = Enum.FillDirection.Vertical
	grid.HorizontalAlignment = Enum.HorizontalAlignment.Left
	grid.SortOrder = Enum.SortOrder.LayoutOrder
	grid.Parent = container

	-- The designed Container parents this directly to the FRAME, not to the
	-- UIGridLayout (a UIAspectRatioConstraint parented to a UIGridLayout has
	-- no effect on anything - verified via the probe above: it does nothing
	-- there, which is why this was a real, not merely cosmetic, v1-parity
	-- bug prior to this fix).
	local aspect = Instance.new("UIAspectRatioConstraint")
	aspect.AspectRatio = 0.9341463446617126
	aspect.AspectType = Enum.AspectType.FitWithinMaxSize
	aspect.DominantAxis = Enum.DominantAxis.Height
	aspect.Parent = container

	local tooltip = Instance.new("Frame")
	tooltip.Name = "Tooltip"
	tooltip:SetAttribute("TooltipType", "Button")
	tooltip.Visible = false
	tooltip.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
	tooltip.BackgroundTransparency = 0.15
	tooltip.BorderSizePixel = 0
	tooltip.Size = UDim2.fromOffset(140, 34)
	tooltip.ZIndex = 50
	tooltip.Parent = gui

	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 6)
	corner.Parent = tooltip

	local label = Instance.new("TextLabel")
	label.Name = "ButtonName"
	label.BackgroundTransparency = 1
	label.Size = UDim2.fromScale(1, 1)
	label.TextColor3 = Color3.fromRGB(255, 255, 255)
	label.TextScaled = true
	label.Font = Enum.Font.GothamBold
	label.Text = ""
	label.ZIndex = 51
	label.Parent = tooltip

	gui.Parent = playerGui
	return gui
end
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
        </Item>
        <Item class="ModuleScript" referent="20">
          <Properties>
            <string name="Name">Version</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[-- Kernel semver. Single source of truth - the server stamps it as the
-- RBLXE_KernelVersion attribute on ReplicatedStorage.RBLXEssentials at boot,
-- and the Dev Plugin's ExporterV2 reads it through
-- `PackFormat.parseKernelVersion` to stamp the same attribute on every
-- exported RBLXE_Pack container.
--
-- BUMP THIS WITH EVERY KERNEL CHANGE. The kernel unit is shipped inside each
-- pack, and `Installer.decideKernel` replaces an installed kernel ONLY when
-- the incoming version is strictly greater - an equal version is
-- "kept-existing". So a kernel fix that does not raise this number reaches
-- nobody: reinstalling the pack silently keeps the old modules. Learned the
-- hard way on 2026-08-24, when the panels-above-HUD and UI_Blocker fixes
-- both looked like they had not worked after a reinstall (user: "he
-- reinstalado el roulette system en el place de prueba y nada, se ve por
-- detrás el fondo aún").
--
-- 2.0.1 - panels draw above the HUD (Shell ZIndex ranks); UI_Blocker tag
--         skips wiring for a modal's click-eating sheet.
-- 2.0.2 - MenuSystem:getInteractable(panelName): the HUD button a pack's
--         notification badge belongs on. Additive, so an older pack keeps
--         working against this kernel unchanged - but a pack that ASKS for it
--         needs this version present, or it falls back to scanning.
-- 2.1.0 - ctx.kernel.Notify: the notification facade, shaped like SFX. A
--         no-op until Notifications System v2 registers a backend, so every
--         pack can announce unconditionally without declaring a dependency
--         on an optional pack. MINOR, not patch: this adds API that packs
--         built against it will call, and an older kernel does not have it.
-- 2.1.1 - menus and HUD buttons survive character respawn: the server makes
--         StarterGui.RBLXEssentials a ScreenGui with ResetOnSpawn = false at
--         boot (UI/RootPersistence.lua). Before, every respawn destroyed every
--         panel and HUD button MenuSystem had adopted. PATCH: no API change.
-- 3.0.0 - RBLX Essentials 3.0 foundation (design spec 2026-09-23):
--         game-owned configuration (ctx.config from each pack's Schema and
--         the game's Config modules; Schema.lua, Config.lua), interface piece
--         contracts (Contracts.lua) and piece resolution decided on the server
--         and enforced on clients (UI/Pieces.lua, RBLXE_PieceWinners,
--         MenuSystem pieces gate, getPiece, ctx.getPiece). The respawn fix
--         (StarterGui.RBLXEssentials made a persistent ScreenGui at boot, so
--         the pieces a client adopted survive respawns) is not new in 3.0: it
--         shipped in 2.1.1 (UI/RootPersistence.lua) and 3.0 keeps it. The
--         client now waits for game.Loaded
--         before booting. Final A1 fix wave (still 3.0.0, unreleased): a
--         config load that throws fails only its pack, Schema.check rejects
--         unusable option types, a chosen piece takes its MenuSystem slot
--         over any 2.x copy, contracts check what MenuSystem needs (tags,
--         PanelId, TargetPanel, `opens`), and clients retire only the
--         StarterGui copies the server refused, never a pack's own clones.
--         A2a hardening for Tools (still 3.0.0, unreleased): Schema.check
--         walks whole patterns and checks list, map and optional-group
--         defaults; Contracts.check reports unusable specs and roots as
--         problems; Config.load reports a wrong side, packId, schemaLib or
--         module argument as an error. Callers keep their pcall for the
--         rest (Loader.lua does).
--         A2b (still 3.0.0): Schema.describe rows also carry optional,
--         value, example and constraints, for config templates and Tools.
--         A3 (still 3.0.0): Schema refuses defaults under server-only
--         options (RT-10) and takes check/checkDoc rules; getPiece counts
--         from PlayerGui.RBLXEssentials' arrival and answers an undecided id
--         at once; the piece runtime warns where it was silent (T-G, M-b,
--         M-c, M-d); Interface/Custom is never a candidate; an invalid
--         Pieces declaration fails its pack (RT-6); clients skip the packs
--         the server could not start (RBLXE_ServerReport) and Net.client
--         waits can be bounded (RT-5).
--         B0 (still 3.0.0, then frozen): Schema kinds S.color and S.tree
--         and the pinned flag; ctx.companion, ctx.extension(s) (the game's
--         Extensions zone) and no ctx.settings for a pack with a Schema
--         (a notice for the others); the Layer piece kind.
--         MAJOR: official 2.x packs are replaced together
--         with it at release; during development 2.x packs keep ctx.settings
--         and 2.x adoption.
return "3.0.0"
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
        </Item>
      </Item>
      <Item class="Folder" referent="21">
        <Properties>
          <string name="Name">Packs</string>
          <BinaryString name="AttributesSerialize"></BinaryString>
          <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
          <Color3 name="IconTint">
            <R>0</R>
            <G>0</G>
            <B>0</B>
          </Color3>
          <bool name="DefinesCapabilities">false</bool>
          <int64 name="SourceAssetId">-1</int64>
          <BinaryString name="Tags"></BinaryString>
        </Properties>
        <Item class="Folder" referent="22">
          <Properties>
            <string name="Name">CodesSystem</string>
            <BinaryString name="AttributesSerialize">AQAAAAwAAABSQkxYRV9IYXNoZXMCwwIAACNyYmx4ZS1oYXNoZXMgdjEgZm52MWEzMiBsZgpSZXBsaWNhdGVkU3RvcmFnZS9QYWNrcy9Db2Rlc1N5c3RlbS9DbGllbnQJYTBkYmFmNWQKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWVzc2FnZXMJYjRhODA3MDYKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9Gcm9tVjIJZWM2OTQ3NmEKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9JbnRlcmZhY2UJMDM2MjY0ZWMKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9LZWVwCTY4NmNlZjhmClJlcGxpY2F0ZWRTdG9yYWdlL1BhY2tzL0NvZGVzU3lzdGVtL1BpZWNlcwk3NzYyZDY4NApSZXBsaWNhdGVkU3RvcmFnZS9QYWNrcy9Db2Rlc1N5c3RlbS9TY2hlbWEJYzExYzhiZmQKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vbWFuaWZlc3QJYWMxYjkxMGUKU2VydmVyU2NyaXB0U2VydmljZS9QYWNrcy9Db2Rlc1N5c3RlbQkwYzIzNTVkYgpTZXJ2ZXJTY3JpcHRTZXJ2aWNlL1BhY2tzL0NvZGVzU3lzdGVtL0NvZGVMaXN0CTNjOGY0NTJhClNlcnZlclNjcmlwdFNlcnZpY2UvUGFja3MvQ29kZXNTeXN0ZW0vQ29kZXMJMmZhMzMwYTgKU2VydmVyU2NyaXB0U2VydmljZS9QYWNrcy9Db2Rlc1N5c3RlbS9DdXN0b21SZXdhcmRzL0dyYW50UGV0CTBhZDg4NDlm</BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <Color3 name="IconTint">
              <R>0</R>
              <G>0</G>
              <B>0</B>
            </Color3>
            <bool name="DefinesCapabilities">false</bool>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
          <Item class="ModuleScript" referent="23">
            <Properties>
              <string name="Name">Client</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[--[[
	CodesSystem - client module.

	Drives the CodesPanel piece the Kernel chose (ctx.getPiece): a theme's
	copy or a fork in StarterGui.RBLXEssentials.Interface when it satisfies
	its contract (Pieces.lua), otherwise this pack's own. Everything is
	resolved BY NAME off that panel:

	  Container/Code/TextBox   what the player types
	  Container/Button         REDEEM
	  Container/ErrorText      refusals
	  Container/SuccessText    confirmations

	The Button is wired BOTH ways, and both are needed. It is connected
	directly, which is what works when the kernel adopted a panel it never
	bound the actions of; and it is ALSO registered as the `ClaimCode`
	handler, because the designed button carries `Action = "ClaimCode"` and
	`MenuSystem:dispatch` warns once per click for an action nobody claims.
	One click can therefore reach `submit` twice, which is harmless: the
	`busy` guard is set before the call yields on `InvokeServer`, so the
	second arrival is discarded rather than redeeming again.

	THE WORDING IS THE GAME'S: `Messages` in its Config module
	(ReplicatedStorage.RBLXEssentials.Config.CodesSystem), with the defaults
	in Schema.lua, reaching this side as ctx.config.Messages. The server
	still never carries player-facing
	English: it answers in machine reasons and this side names them. Every
	reason the server can return must have a key - five from the pure rules
	plus NoData, GrantFailed and TooFast from the server itself - because
	`MESSAGE[reason]` falling through to GrantFailed would tell the player
	something went wrong when nothing did.

	Boot follows the kernel's yield policy: nothing yields in the init, all of
	it is deferred into a `task.spawn` with a bounded wait and warn-and-degrade,
	and the remote is acquired last.
]]

local TAG = "[CodesSystem]"
local FADE_AFTER = 4
-- RT-5: the Redeem remote wait is bounded. The Kernel does not boot this
-- client when the server module did not start, so this only covers a server
-- that is very slow to create the remote.
local REMOTE_WAIT = 30

return function(ctx)
	local ui = ctx.ui
	local SFX = ctx.kernel.SFX
	local trove = ctx.kernel.Trove.new()

	-- Every word the player can be told: the game's Messages merged over the
	-- Schema defaults and validated by the Kernel (never an empty string).
	local Messages = require(ctx.folder.Messages)
	local MESSAGE = ctx.config.Messages

	-- The pack's own icon, for the redeem notification. Read from the
	-- manifest rather than written out again, so the art has one definition.
	-- `icon`, NOT `thumbnail`: the thumbnail is the wide store card and a
	-- notification plate is square, so it would arrive as a thin strip.
	local LOGO = require(ctx.folder.manifest).icon

	task.spawn(function()
		-- The piece the Kernel chose. getPiece counts its wait from the
		-- moment PlayerGui.RBLXEssentials arrives and warns on its own when
		-- the piece does not come.
		local panel = ctx.getPiece("CodesPanel")
		if not panel then
			warn(TAG .. " the CodesPanel piece did not appear, so the code UI is disabled this session")
			return
		end
		trove:AttachToInstance(panel)

		local container = panel:FindFirstChild("Container")
		local codeFrame = container and container:FindFirstChild("Code")
		local box = codeFrame and codeFrame:FindFirstChild("TextBox")
		local button = container and container:FindFirstChild("Button")
		local errorText = container and container:FindFirstChild("ErrorText")
		local successText = container and container:FindFirstChild("SuccessText")

		if not (box and box:IsA("TextBox")) or not (button and button:IsA("GuiButton")) then
			warn(("%s the live Codes panel (%s) has no Container/Code/TextBox or Container/Button, so the code UI is disabled this session"):format(
				TAG,
				panel:GetFullName()
			))
			return
		end
		if not (errorText and errorText:IsA("TextLabel")) or not (successText and successText:IsA("TextLabel")) then
			-- Degraded, not broken: redeeming still works, the player just
			-- gets no verdict. Worth a warning because a buyer who renamed
			-- these would otherwise never know why nothing is said.
			warn(("%s the live Codes panel (%s) has no ErrorText/SuccessText, so redemptions will be silent"):format(
				TAG,
				panel:GetFullName()
			))
		end

		-- Start clean: the designed panel ships these blank, but a place
		-- saved mid-play could carry a stale verdict into the next session.
		if errorText then
			errorText.Text = ""
		end
		if successText then
			successText.Text = ""
		end

		--- Writes one label and clears the other, then fades after a few
		--- seconds so the panel never keeps a stale verdict on screen. The
		--- token guards against a slow fade wiping a NEWER message.
		local lastMessage = nil
		local function say(label, other, text: string)
			if other then
				other.Text = ""
			end
			if not label then
				return
			end
			label.Text = text
			local token = {}
			lastMessage = token
			task.delay(FADE_AFTER, function()
				if lastMessage == token and label.Parent then
					label.Text = ""
				end
			end)
		end

		local net = ctx.kernel.Net.client(ctx.packId)
		local redeemRemote = net:func("Redeem", REMOTE_WAIT)
		if redeemRemote == nil then
			warn(TAG .. " the Redeem remote did not appear, so the code UI is disabled this session")
			return
		end

		local busy = false
		local function submit()
			if busy then
				return
			end
			busy = true
			button.Active = false
			button.AutoButtonColor = false

			local ok, response = pcall(function()
				return redeemRemote:InvokeServer(box.Text)
			end)

			busy = false
			button.Active = true
			button.AutoButtonColor = true

			if not ok or type(response) ~= "table" then
				say(errorText, successText, MESSAGE.GrantFailed)
				return
			end
			if response.Success then
				-- Success clears the field; a failure deliberately leaves it,
				-- so a typo can be corrected instead of retyped.
				box.Text = ""
				local code = tostring(response.Code)
				say(successText, errorText, Messages.format(MESSAGE.Redeemed, code))
				SFX:Play("Reward_Claim")
				-- Also announce it outside the panel: the verdict above is
				-- only visible while the Codes menu is open, and a player
				-- who closes it immediately would have nothing to show for
				-- the redemption. Silent no-op without Notifications System.
				-- The title is a LITERAL, not a setting (user, 2026-08-29):
				-- nothing renders it unless Notifications System is also
				-- installed, and a settings field that does nothing at all
				-- without a second, optional pack is a field that only
				-- confuses. The code itself carries the meaning anyway, and
				-- it is in the subtitle.
				--
				-- IMAGE TEMPLATE, with this pack's logo (user, 2026-08-31): a
				-- redeem toast arriving beside a hatch or a spin should say
				-- WHICH pack is talking without being read. If the buyer
				-- deleted ImageTitleSubtitle from their templates the
				-- renderer warns once and drops it, the same as any other
				-- name it cannot find -- this pack does not depend on that
				-- one and must not assume its shape.
				ctx.kernel.Notify:Toast("ImageTitleSubtitle", {
					Title = "Code redeemed!",
					Subtitle = code,
					Image = LOGO,
				})
			else
				say(errorText, successText, MESSAGE[response.Error] or MESSAGE.GrantFailed)
			end
		end

		-- UPPERCASE AS YOU TYPE (user, 2026-08-26). `Codes.normalize` already
		-- upper-cases and trims before matching, so a lowercase code has
		-- always redeemed fine -- this is about the box SHOWING what will
		-- actually be redeemed, instead of leaving the player to wonder
		-- whether case matters. Codes are usually authored uppercase.
		--
		-- THE CURSOR HAS TO BE PUT BACK. Assigning `.Text` sends the caret to
		-- the end, so typing a letter into the middle of a code would kick
		-- you to the end of it on every keystroke. Restoring the position
		-- works because `:upper()` only touches ASCII a-z and never changes
		-- the string's LENGTH -- an accented character keeps its byte count
		-- too, since Lua's upper is byte-wise and leaves it alone.
		--
		-- No re-entrancy guard is needed: assigning fires this again, and the
		-- second pass finds the text already upper-cased and stops, because
		-- upper(upper(x)) == upper(x).
		trove:Connect(box:GetPropertyChangedSignal("Text"), function()
			local upper = box.Text:upper()
			if upper == box.Text then
				return
			end
			local caret = box.CursorPosition
			box.Text = upper
			box.CursorPosition = caret
		end)

		trove:Connect(button.Activated, submit)
		-- …and as the action the designed button actually declares. Without
		-- this every redemption logs "No handler registered for action:
		-- ClaimCode" in the buyer's output, from a button that redeemed
		-- perfectly well. Registering it late is fine: handlers are looked up
		-- at dispatch time, and nothing can click this button before its
		-- panel exists.
		ui:registerHandlers({ ClaimCode = submit })
		-- Enter submits too: typing a code and pressing Return is what a
		-- player will try first.
		trove:Connect(box.FocusLost, function(enterPressed)
			if enterPressed then
				submit()
			end
		end)
	end)

	return {}
end
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="24">
            <Properties>
              <string name="Name">Messages</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[--[[
	Messages.lua - the `{code}` substitution for the Redeemed message.

	3.0: the words themselves are the game's configuration, `Messages` in
	ReplicatedStorage.RBLXEssentials.Config.CodesSystem, with the defaults
	in Schema.lua; the client reads them as ctx.config.Messages.

	`{code}` is a TOKEN, not a `%s`: string.format errors on a stray percent
	sign, and a message the game writes must never be able to break
	redemption. gsub with a FUNCTION, so a `%` in the redeemed code is not
	read as a capture reference either.
]]

local Messages = {}

function Messages.format(text: string, code: string): string
	local out = (text or ""):gsub("{code}", function()
		return code
	end)
	return out
end

return Messages
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="Folder" referent="25">
            <Properties>
              <string name="Name">Migrations</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <Color3 name="IconTint">
                <R>0</R>
                <G>0</G>
                <B>0</B>
              </Color3>
              <bool name="DefinesCapabilities">false</bool>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
            <Item class="ModuleScript" referent="26">
              <Properties>
                <string name="Name">FromV2</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <ContentId name="LinkedSource">
                  <null>
                  </null>
                </ContentId>
                <bool name="DefinesCapabilities">false</bool>
                <string name="Source"><![CDATA[-- Migrations/FromV2 - Codes System 2.x Settings to the 3.0 configuration
-- (design spec 8.1, step 3; plan A3, ruling 6). T.migrate reads the
-- installed 2.x Packs/CodesSystem/Settings folder into plain data and calls
-- this. Pure: data in, data out; no game, no Instances, no script.
--
-- tree, as T.migrate passes it: the Settings folder as
--   node = { name, class, value?, children = { node } }
-- where `value` is set for a StringValue, NumberValue, IntValue or BoolValue.
--
-- Returns (config, notes, sources). config = { Codes = { ... }, Messages =
-- { ... } } holds every code and message the 2.x game had live; T.migrate
-- writes only what differs from the Schema's defaults. notes = { { path,
-- message } } names each 2.x value that is not carried over, and says what
-- 2.x did with it. sources = { [option path] = 2.x setting }: where each
-- carried value came from, such as ["Codes[4]"] = "Settings.Codes.Code9"
-- (the list leaves out the slots 2.x disabled, so an index is not a slot
-- number) and ["Messages.NoSuchCode"] = "Settings.Messages.NoSuchCode";
-- T.migrate names a 2.x value by its setting, never by its index.
--
-- It mirrors Codes System 2.x exactly (Config.buildCodes and readSlot,
-- Messages.read); tests/fromv2.spec.luau runs the 2.1.1 modules on the same
-- trees and compares:
--   * a slot is a Folder child of Settings/Codes whose name ends in digits,
--     sorted by that number with the same sort 2.x used; other children are
--     ignored, as in 2.x;
--   * Code, Type, Target and Expires are read only from a StringValue and
--     Amount only from a NumberValue or IntValue; anything else reads as
--     missing: Type "Cash", Amount 0, the others "" (2.x's defaults);
--   * nothing is trimmed: each value is carried exactly as the game wrote it;
--   * a slot is left out only for the four reasons 2.x disabled it at
--     startup: a Code that is empty once trimmed, a Type that is not one of
--     the five, a Stat or Custom slot with an empty Target, and an Expires
--     that is not a YYYY-MM-DD date;
--   * duplicates are settled over every numbered slot, disabled ones
--     included, in slot order, as Codes.find settles them when a player
--     redeems: the first slot with a code owns it. A later slot with the
--     same code was never redeemable: 2.x disabled it at startup when the
--     owner was valid, and answered CodeDisabled for it when the owner was
--     disabled. Either way it is left out with a note that says which;
--   * a Target on Cash, Diamonds or Spins is left out: 2.x ignored it;
--   * a message is carried when it is a non-empty StringValue, as written;
--     an empty one meant the default in 2.x.
-- A Custom code whose module does not exist is carried: 2.x disabled it when
-- the server started, and 3.0's server does the same, with the same warning.
local TYPES = { Cash = true, Diamonds = true, Spins = true, Stat = true, Custom = true }
local MESSAGE_KEYS = { "Redeemed", "Empty", "NoSuchCode", "AlreadyRedeemed", "Expired", "CodeDisabled", "TooFast", "NoData", "GrantFailed" }
local DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

local function child(node, name)
	for _, item in ipairs(node and node.children or {}) do
		if item.name == name then
			return item
		end
	end
	return nil
end

-- str/num: 2.x's own readers (Config.lua), over the tree.
local function str(node, name, default)
	local item = child(node, name)
	if item ~= nil and item.class == "StringValue" and type(item.value) == "string" then
		return item.value
	end
	return default
end

local function num(node, name, default)
	local item = child(node, name)
	if item ~= nil and (item.class == "NumberValue" or item.class == "IntValue") and type(item.value) == "number" then
		return item.value
	end
	return default
end

-- Codes.normalize of 2.x.
local function normalize(text)
	if type(text) ~= "string" then
		return ""
	end
	return (text:match("^%s*(.-)%s*$") or ""):upper()
end

-- Codes.expiresAt of 2.x, as a verdict: "none" for "", "date" for a valid
-- date, "bad" for anything else.
local function expiry(spec)
	if spec == "" then
		return "none"
	end
	local ys, ms, ds = spec:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
	if ys == nil then
		return "bad"
	end
	local year, month, day = tonumber(ys), tonumber(ms), tonumber(ds)
	if month < 1 or month > 12 or day < 1 then
		return "bad"
	end
	local days = DAYS_IN_MONTH[month]
	if month == 2 and year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0) then
		days = 29
	end
	return if day > days then "bad" else "date"
end

return function(tree)
	local config, notes, sources = { Codes = {}, Messages = {} }, {}, {}
	local function note(path, message)
		table.insert(notes, { path = "Settings." .. path, message = message })
	end

	local slots = {}
	for _, item in ipairs(child(tree, "Codes") and child(tree, "Codes").children or {}) do
		if item.class ~= "Folder" then
			note("Codes." .. item.name, ("is not a Folder (it is a %s), so 2.x ignored it: not carried over"):format(item.class))
		else
			local index = tonumber(string.match(item.name, "(%d+)$"))
			if index == nil then
				note("Codes." .. item.name, "has no slot number, so 2.x skipped it at startup: not carried over")
			else
				table.insert(slots, {
					index = index,
					name = item.name,
					code = str(item, "Code", ""),
					kind = str(item, "Type", "Cash"),
					target = str(item, "Target", ""),
					amount = num(item, "Amount", 0),
					expires = str(item, "Expires", ""),
				})
			end
		end
	end
	table.sort(slots, function(a, b)
		return a.index < b.index
	end)

	for _, slot in ipairs(slots) do
		local reason = nil
		if normalize(slot.code) == "" then
			reason = "has an empty Code"
		elseif not TYPES[slot.kind] then
			reason = ("has Type %q, which is not Cash, Diamonds, Spins, Stat or Custom"):format(slot.kind)
		elseif (slot.kind == "Stat" or slot.kind == "Custom") and slot.target == "" then
			reason = ("has Type %s but an empty Target"):format(slot.kind)
		elseif expiry(slot.expires) == "bad" then
			reason = ("has Expires %q, which is not a YYYY-MM-DD date"):format(slot.expires)
		end
		if reason ~= nil then
			slot.disabled = true
			note("Codes." .. slot.name, reason .. ", so 2.x disabled it at startup: not carried over")
		end
	end

	-- The first slot with a code owns it, disabled or not (Codes.find).
	-- A later slot already disabled for its own reason keeps its one note.
	local owners = {}
	for _, slot in ipairs(slots) do
		local key = normalize(slot.code)
		if key ~= "" then
			local owner = owners[key]
			if owner == nil then
				owners[key] = slot
			elseif not slot.disabled then
				slot.disabled = true
				if owner.disabled then
					note("Codes." .. slot.name, ("repeats the code of %s, which 2.x disabled, so 2.x answered CodeDisabled for it: not carried over"):format(owner.name))
				else
					note("Codes." .. slot.name, ("repeats the code of %s, so 2.x disabled it at startup (%s keeps the code): not carried over"):format(owner.name, owner.name))
				end
			end
		end
	end

	for _, slot in ipairs(slots) do
		if not slot.disabled then
			local entry = { Code = slot.code, Type = slot.kind, Amount = slot.amount }
			if slot.kind == "Stat" or slot.kind == "Custom" then
				entry.Target = slot.target
			elseif slot.target ~= "" then
				note(("Codes.%s.Target"):format(slot.name), ("is %q, which 2.x ignored for Type %s: left out"):format(slot.target, slot.kind))
			end
			if slot.expires ~= "" then
				entry.Expires = slot.expires
			end
			table.insert(config.Codes, entry)
			sources[("Codes[%d]"):format(#config.Codes)] = "Settings.Codes." .. slot.name
		end
	end

	local messages = child(tree, "Messages")
	local known = {}
	for _, key in ipairs(MESSAGE_KEYS) do
		known[key] = true
		local item = child(messages, key)
		if item ~= nil and item.class ~= "StringValue" then
			note("Messages." .. key, ("is not a StringValue (it is a %s), so 2.x used the default: not carried over"):format(item.class))
		elseif item ~= nil and type(item.value) == "string" and item.value ~= "" then
			config.Messages[key] = item.value
			sources["Messages." .. key] = "Settings.Messages." .. key
		end
	end
	for _, item in ipairs(messages and messages.children or {}) do
		if not known[item.name] then
			note("Messages." .. item.name, "is not a Codes System message, so 2.x never read it: not carried over")
		end
	end

	for _, item in ipairs(tree and tree.children or {}) do
		if item.name ~= "Codes" and item.name ~= "Messages" then
			note(item.name, "is not a Codes System 2.x setting, so 2.x never read it: not carried over")
		end
	end
	return config, notes, sources
end
]]></string>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
            <Item class="ModuleScript" referent="27">
              <Properties>
                <string name="Name">Interface</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <ContentId name="LinkedSource">
                  <null>
                  </null>
                </ContentId>
                <bool name="DefinesCapabilities">false</bool>
                <string name="Source"><![CDATA[-- Migrations/Interface - generated by node tools/v3/b-release/run.mjs fingerprints CodesSystem (plan B0)
-- from tools/v3/b-release/packs/CodesSystem.json and fingerprints computed in Studio.
-- Do not edit it: change the descriptor and run that command again.
--   derived: per piece, the properties and attributes a 2.x action wrote
--     from Settings, never counted as an edit (or a function of the
--     configuration that returns them);
--   conditional: for such a function, every rule it may return and the
--     published values of what they name (Migrate.conditionalValues);
--   published: per piece, "<PackId>@<version> <element>" of every published
--     2.x copy and its fingerprint (Migrate.pieceFingerprint);
--   foreign: this pack's own 2.x copies of other packs' pieces, each on its
--     owner piece's slot (key).
return {
	conditional = {},
	derived = {},
	foreign = {},
	published = {
		CodesButton = {
			["CodesSystem@2.0.0 StarterGui/CodesSystem/CodesSystemGUI/Interactables/Codes"] = "f2:e4023159:931bbb81",
			["CodesSystem@2.1.0 StarterGui/CodesSystem/CodesSystemGUI/Interactables/Codes"] = "f2:e4023159:931bbb81",
			["CodesSystem@2.1.1 StarterGui/CodesSystem/CodesSystemGUI/Interactables/Codes"] = "f2:e4023159:931bbb81",
			["GeneralUIKit@2.0.0 StarterGui/GeneralUIKit/GeneralUIKitGUI/Interactables/Container/Codes Button"] = "f2:e4023159:931bbb81",
			["GeneralUIKit@2.1.0 StarterGui/GeneralUIKit/GeneralUIKitGUI/Interactables/Container/Codes Button"] = "f2:e4023159:931bbb81",
			["GeneralUIKit@2.1.1 StarterGui/GeneralUIKit/GeneralUIKitGUI/Interactables/Container/Codes Button"] = "f2:e4023159:931bbb81",
		},
		CodesPanel = {
			["CodesSystem@2.0.0 StarterGui/CodesSystem/CodesSystemGUI/Codes"] = "f2:e4023159:b4906876",
			["CodesSystem@2.1.0 StarterGui/CodesSystem/CodesSystemGUI/Codes"] = "f2:e4023159:3e10d493",
			["CodesSystem@2.1.1 StarterGui/CodesSystem/CodesSystemGUI/Codes"] = "f2:e4023159:3e10d493",
			["GeneralUIKit@2.0.0 StarterGui/GeneralUIKit/GeneralUIKitGUI/Menus/Codes"] = "f2:e4023159:3e10d493",
			["GeneralUIKit@2.1.0 StarterGui/GeneralUIKit/GeneralUIKitGUI/Menus/Codes"] = "f2:e4023159:3e10d493",
			["GeneralUIKit@2.1.1 StarterGui/GeneralUIKit/GeneralUIKitGUI/Menus/Codes"] = "f2:e4023159:3e10d493",
		},
	},
}
]]></string>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
            <Item class="ModuleScript" referent="28">
              <Properties>
                <string name="Name">Keep</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <ContentId name="LinkedSource">
                  <null>
                  </null>
                </ContentId>
                <bool name="DefinesCapabilities">false</bool>
                <string name="Source"><![CDATA[-- Migrations/Keep - what T.migrate must look at in the 2.x pack's own code
-- (plan A3, ruling 8).
--
-- folders: "<service key>/<path under that service's RBLXEssentials>".
--   A module there that no 2.x release shipped is the game's own: T.migrate
--   copies it into ServerScriptService.RBLXEssentials.Extensions.CodesSystem
--   (plan B0: the game's zone, which the pack looks in first) and says so.
-- shipped: every script a 2.x release shipped under the pack's own roots
--   (ReplicatedStorage/Packs/CodesSystem, ServerScriptService/Packs/
--   CodesSystem; the StarterGui folder ships none), keyed like `folders`,
--   with the SourceHash (plugins/Official/src/Shared/SourceHash.lua) of
--   every published version of it, computed from the releases in this
--   repository (tests/fromv2.spec.luau recomputes them). A shipped script
--   whose hash is not in its list was edited by the game (GrantPet's
--   PET_RARITY and PET_ID are set by editing it; a game could also edit the
--   redemption rules in Codes or a rate limit in the server module):
--   T.migrate refuses without force and names what differs; with force the
--   3.0 version replaces it and the edited copy stays in the protected
--   migration backup (and, for a module of the Keep folder, becomes the
--   game's own in Extensions.CodesSystem, plan B0).
-- lines: for each published SourceHash, SourceHash.fnv1a32 of each line of
--   that source, trimmed, blank lines left out, in order. T.migrate names
--   the lines a game changed with them: the lines only the game's copy has,
--   and the published lines it lacks (their text comes from the 3.0 module
--   at the same place when it still has them; the rest are counted).
return {
	folders = {
		"ServerScriptService/Packs/CodesSystem/CustomRewards",
	},
	shipped = {
		-- 2.0.0, 2.1.0, 2.1.1
		["ReplicatedStorage/Packs/CodesSystem/Client"] = { "9544100b" },
		-- 2.0.0, 2.1.0, 2.1.1
		["ReplicatedStorage/Packs/CodesSystem/Config"] = { "410d9076" },
		-- 2.0.0, 2.1.0, 2.1.1
		["ReplicatedStorage/Packs/CodesSystem/Messages"] = { "00d5ab8d" },
		-- 2.0.0; 2.1.0; 2.1.1
		["ReplicatedStorage/Packs/CodesSystem/manifest"] = { "2860c634", "c51944c1", "782e1c28" },
		-- 2.0.0; 2.1.0, 2.1.1
		["ServerScriptService/Packs/CodesSystem"] = { "b73bc758", "54b5fad8" },
		-- 2.0.0, 2.1.0, 2.1.1
		["ServerScriptService/Packs/CodesSystem/Codes"] = { "2fa330a8" },
		-- 2.0.0; 2.1.0, 2.1.1
		["ServerScriptService/Packs/CodesSystem/CustomRewards/GrantPet"] = { "23a8e0e3", "a00757a7" },
	},
	lines = {
		["00d5ab8d"] = "11c35325 e6f81cf3 886fae6e 5298a4cb 495969f7 abb89c8c d5bf189c 58cadb4b a1174aa7 2faf8c9a 4994afe8 1dbcb1c5 8ad5792b 4ded12b6 dc9035b2 5a28b22a 26a37bcc e07a9f25 67545941 35403afe 3006c30f 6d861cb1 43b0e988 8017d7a4 c85684fc 50988f4c 47647ed3 23f0883a 8f9abcf5 36668bf0 c2603e1f 4aaa63c7 f80c48a8 1a5549e5 4953296b 0f134ad7 f23afb9c 119afc59 afacd4e9 227b5854 3bd3c0b2 5d4b09d3 bdbf5bf0 20663b14 6a8e75aa 6a8e75aa d6248a47 6a8e75aa 615ea700 53d6c172 d17f4e69 b02927b2 f88e1671 9ce04e4c 4142fd39 d6248a47 6a8e75aa 1b06ef0d",
		["23a8e0e3"] = "df69d891 e3803c36 20cd1d0f 0ebfebf2 50929c96 e637e3de b549ca17 19abfab4 492c2a94 20cd1d0f 25d5ab41 d340205a 645894c9 20cd1d0f 3dfff4bf 7ac0f115 41e1255b 97bb5297 95833ac3 4eb1b0f5 6e0e1ad9 d71fcfcf ab72d6e4 78092c96 03dfa0a3 0c5ede0f cf9de88e 28580a7d e693520b 02783cbf 6a8e75aa 02e3b050 1a1d6ed4 c8762d46 469e4702 f18d9cce c79b0ffa 766947f3 3e03c74c 48d3e274 6a8e75aa a43b45f3 3c2491c4 4142fd39 8dcbc7b6 f4dd7b57 48d3e274 6a8e75aa ded2f6e1 02783cbf 6a8e75aa 1ce8cbdf 7fe94618 9edadc08 c4e4dc86 4fb86ecd 71baa611 2fce9a6b 01519acd 1ee2668e 1a91757e c3f1bd79 7712a453 2c42c233 10bf74ce 68315547 6a8e75aa 4b29deb1 6a8e75aa a40d8337 a6fcdc1c fdb74848 39386e06 a42870ff 8477cd1a 27f9158c e522e976 0176cfac 6a8e75aa 6a8e75aa 6a8e75aa fc907a49 65201357 6a8e75aa 648838d3 6a8e75aa 9020ce91 5e8bbe18 b96de5ba 0913864a 85ee37bf 6a8e75aa 64d3a298 078d6d40 85ee37bf 6a8e75aa d26540fa 803e4dea 85ee37bf 6a8e75aa 989c99e9 e891f31b 2b6a42f1 33af69cf 4f619d07 85ee37bf 6a8e75aa a522b977 f87bfeb2 b418432d 14da65fd 218953ec fd383430 5a5c9741 fc562313 6a8e75aa 2e8b44ee",
		["2860c634"] = "67747202 27120e02 836bce0a f05197dd 68376c8d 82bf446b 15b50cb4 1769b867 faa2873d 2e3f382e 41cbdfea 065a3522 40e65178 a2ea4dbd 0b0db9db f80c48a8",
		["2fa330a8"] = "11c35325 510f70b4 981c93b1 60638ec2 fd8d9bc6 1e88677d b1089a2a cb914be0 ce4036b9 f1a8c7b5 cf9cb192 3489cbd3 dcc9f357 7acd09bc 42b1b628 3006c30f d4042067 d6e1a829 0bf66aad c5272031 4eb22f9b 66390d8f be6c4c81 d58d8db3 36dc8b4d 6a8e75aa 02bf2589 6a8e75aa 0107926f 377b70a4 6a8e75aa d5736e26 91234fb8 89d9a85c 96cc18d6 c55b1ceb 7a28c43b db7ea262 d7c6a8f3 cf35c48f 3568a3c0 6a8e75aa 5d310882 48d3e274 6a8e75aa 4f6b0652 c9531cd4 3568a3c0 6a8e75aa d9e4fea6 159da490 3568a3c0 6a8e75aa 5b2bfd6c 5c3e0103 3568a3c0 6a8e75aa 5140387c 0a230cb7 8ecbbbbd 6a8e75aa 07bfd212 90c95e2d 6a8e75aa 8a0a7a42 6a8e75aa 86b66f22 8458d244 e2c2e503 6a8e75aa 5a6a8f13 3f6f1515 17b40fae 3006c3e2 837aa8aa 80c15b91 95ffc575 bc04c08d 6a8e75aa f4e06fc8 f1ea906b bc04c08d 6a8e75aa 3568a3c0 6a8e75aa 76aee4ca 71f104c0 48d3e274 6a8e75aa f6657f0e 6a8e75aa c7d0e950 ff9704a4 9e7e3635 dc104f67 48d3e274 6a8e75aa 688b64e6 e27a4a29 e41d5beb 6a8e75aa 6a8e75aa 48d3e274 6a8e75aa c1a9081f d29856e2 c4e4dc86 71c2fa04 3ae254b0 801e4de4 903764dd 9e7e3635 396d839a 1e5a6e07 6a8e75aa 382c0243 e715dd52 7cd89bd1 6a8e75aa e7c04ce8 f1ea7e9a 1309772f 6a8e75aa 0907d0ae 1a0bccaf 6a8e75aa 701f1530 48d482f7 6a8e75aa 65a9cbf2 a6772b99 65446f8d 03fde51b 6a8e75aa bf5561f8 6a8e75aa 849c9fe8 9598c97b 18dd757c 7d5d8086 382c0243 7bf2fd23 3568a3c0 6a8e75aa 698032b5 95753096 3568a3c0 6a8e75aa 47162c53 bc04c08d 6a8e75aa 089018cd",
		["410d9076"] = "11c35325 3ee4cd21 9a61f074 030cdca9 2b32369e d9908627 962115c7 544f0b6d eba8bc35 4176306c 5a6b6fec ddf02d05 1cf83d9f 436d5fb6 0302119f 340474c2 b7337f79 ef793004 32617695 04b48182 3006c30f 58ad94d9 fcf2709b 1094520c 3f65ceeb 14cf2665 0075ebc2 77b6c302 6a8e75aa 62eb3766 6a8e75aa 3e16682e 14cf2665 7b6521b2 77b6c302 6a8e75aa 62eb3766 6a8e75aa 5bb7411a 600e8318 0dff3664 2420c738 1cf03dab c193efb3 c701010f 96a96567 c8f0fc7e d0f00f2c c44e83a0 7637fb64 571ef257 e22dcc94 f66caadf f80c48a8 adb638eb 5104d4a3 f7bf1f6f 6b8e27cb 5104d4a3 4d61d8ab b13d8448 82d772a6 fdea167f 2c0c9a84 2c0c9a84 f95e1aa9 5104d4a3 73b08065 6a8e75aa 7659e582 45f3110e 8b525f39 d44f2e2e 015d4c8d 4d61d8ab 9abfe56e 82d772a6 1517162b 2c0c9a84 2c0c9a84 6a8e75aa 5104d4a3 bdbf5bf0 c12f27fe 6a8e75aa e41d5beb 6a8e75aa 7c2643f3 544337d4 eb35e2e5 478c3b13 849e625b c7f231dc 119afc59 8408a4ac d6248a47 6a8e75aa 202174de 50b35d93 11be0b0e 22a27a4e f2353de6 bdbf5bf0 75b145b0 6a8e75aa 6a8e75aa 6a8e75aa e1d48145 e6355a16 4142fd39 af9acdca cf167cf6 a1c12d73 d4fb5b6c c00e11ca 59518d34 9b4f2d1e 770a8336 fb025656 ce0ef107 5104d4a3 4d61d8ab 55512043 4b49a078 04f9b905 2c0c9a84 2c0c9a84 bdbf5bf0 9e361237 6a8e75aa 6a8e75aa 6a8e75aa d6248a47 6a8e75aa efd379df 6a8e75aa",
		["54b5fad8"] = "11c35325 fd1a8a40 4f682a44 daa7df19 513d22be 1d60e4ca 8bd3825f 8b31ff27 0086da20 4f663c0f 78908fd8 007293e8 81fb1056 91a4d994 b6c603fe 3006c30f 3e7dceeb 41e1255b a3935575 6b79e95c 3f04c6c6 cc0bc482 23ce86d9 a340da7a f07b5132 4449e709 50c6518a e17272b8 0efe1257 b6f55e3a b75ff4fd c6d3532e d80705e5 49d864cd 5a0cb120 3af9e0e0 da503c42 2cef8378 6a8e75aa 5fa1494c e96c5e36 ad820e45 0fc14742 6c3b4350 913df67d 90403f7b d2ecc475 39dc8f4d 16003570 a3d3d167 4e96cbf0 445eb74c 778a6609 6a8e75aa 4593fede 9cd2fa66 bdbf5bf0 8d98c290 a88cef85 d6c0919b c553e018 40676b3e 5104d4a3 6a8e75aa bdbf5bf0 581189cc 40676b3e 5104d4a3 6a8e75aa d19ca73a 5104d4a3 6a8e75aa 6a8e75aa 6a8e75aa 16e4a824 94b19a3e b1bcd3f8 c9fb4100 f0a15699 520bd1c2 7bae18c8 fb521c03 ac0cb477 4e4849fb 19672431 6a8e75aa 02e3b050 1a1d6ed4 73ff8492 dcb8da60 b318981e e21fce83 aa4df3b3 5a283390 d718edd5 94e0157d fce982ed 19672431 6a8e75aa a43b45f3 47939b7e 0f4fbb97 4142fd39 72c0b2d4 8dcbc7b6 2cb536ff 6a8e75aa 19672431 6a8e75aa 5f346c93 f712eb14 68c8fdb3 767dcc32 56fe96f7 a29f7397 f4780811 75fdd5e4 6a8e75aa 9feb7fb6 3568a3c0 6a8e75aa 4f6f3727 76fd6d47 07d9dac9 51908a2c c05bb8c0 43d6eff8 6a8e75aa 0e0d60f9 6a8e75aa 68707429 6a8e75aa 3226ed0e 6848bfb3 3568a3c0 6a8e75aa 8f18ae3f bc04c08d 6a8e75aa 1d14b118 dd50ca52 65cbed96 1c6e139e 0f73d3c0 8dcbc7b6 c5717aeb 6a8e75aa 4d1e8b17 bac6c9e3 cb2548f1 1e351fa5 6a2a1ff0 95964504 605a30a6 3568a3c0 6a8e75aa 89547b11 bc04c08d 7afaf1b3 4aca672a 078cc71d 3568a3c0 6a8e75aa d2569e7a d761f6fa 6a8e75aa 3568a3c0 6a8e75aa 537eb83a 976dc6ad 8d2ba167 69d6b771 11c4037b e74b5293 a022305d 2d36553e 569abf1a b3ba2bba 8c57e3e5 6a8e75aa 6a7476bb 6a8e75aa 3ce36fe5 37ac76a1 60b8c195 5da62a30 82c2c40a 3568a3c0 6a8e75aa a1750920 bc04c08d 6a8e75aa 8a95bf27 5da62a30 547b1b4c 89a364b0 85ee37bf 6a8e75aa c203e8bb 8f4c014c 7c0cd030 3be2f152 20d1e2db 6a8e75aa 6a8e75aa d1996bae ac6ce805 53d09205 0bd0371e 86c35944 6a8e75aa b5759ba4 09759e39 f985edef 7cd89bd1 6a8e75aa 9de51eb9 8dcbc7b6 19f75fef 37ed7a3a 9441889f 9898c38c 1024cdd7 28e8b5bf f985edef 6a8e75aa 0f4cd1a0 6a8e75aa 04742ed6 d92656b9 7a3be759 1b8f98c7 7f0bb53c 44ef123a 6a8e75aa 55d731d8 c47aa97d 992483e3 e4c93150 dc8607da 2b352e93 3a2273e7 15fb8d93 de6c590d 65eea691 1f03cea8 44d9c042 6a8e75aa 6a8e75aa 4142fd39 cecd70dc f985edef eeed9ddc d552fd3c 424dc6b8 ef9939fd 6a8e75aa ab36b230 ef9939fd 6a8e75aa 5ee7d1ce 72887282 6a8e75aa 7b176f6a b5759ba4 09759e39 3568a3c0 6a8e75aa f1b49e4c f131d82a 6a8e75aa a265b129 dca637f5 696eca76 089babbe b5759ba4 09759e39 85ee37bf 6a8e75aa 3f40de1f 85ee37bf 6a8e75aa ea952701 dc8607da 4142fd39 6a8e75aa 4649ffe6 9416a896 6a8e75aa 4034c788 9416a896 4142fd39 720c0d79 0cccd776 b16f5d3b 07446fb2 4142fd39 a741f0ff f223fac6 9df113f5 d6257ff3 4dc62145 2aa424e3 56417b07 6a8e75aa 4a3f1384 6a8e75aa 67747202 7aa74a6a 6a1f4ac0 f80c48a8 6a8e75aa",
		["782e1c28"] = "67747202 27120e02 396d6cb6 f05197dd 68376c8d 82bf446b 15b50cb4 1769b867 faa2873d 2e3f382e 41cbdfea 065a3522 40e65178 a2ea4dbd 0b0db9db f80c48a8",
		["9544100b"] = "11c35325 6ef550fc 4feeb73e 2a89a291 3b15dc25 afaf68c8 01d613a7 e61702ff a4cdab1b cb54ca75 a366dee5 d0d9e5ea 87b26203 b792a4cc 9acbdf81 0cfae206 f29ad770 d5cd57bb dfeb1be0 fba7ad62 07ecb697 d6de4887 9f3b9a0c 4ea22c6e c1580d18 4d77db11 de746b77 def1935b 3006c30f 3f04c6c6 94ea55df b40097a8 0efe1257 295528f8 a18f2551 5a0cb120 213640c6 1406f87e 4b87d007 f1b0ca90 cc2d9904 20b3976d 1464cd70 4df75657 e0ae2091 dfc33c7a 369f78f5 e32da503 4fdd1a77 29230cfe b56fb842 f98c9df9 2368c50c 6a8e75aa 6e747d07 6a8e75aa 5fe1f84c 38d54891 68828754 b5fa517f 85ee37bf 6a8e75aa 3427b6bb 68ffd1e9 ca067e8f facc1167 bb406318 755501c6 45a6cf6a 05cd8995 9ec792b3 b897b64d 71007f76 04d77e57 85ee37bf 6a8e75aa 946df900 84f5a6b1 38fbbb0d c445165c 71fb7180 b897b64d 71007f76 04d77e57 6a8e75aa 39aa6231 7a75bb36 62b5c4e2 6f7e50a0 6a8e75aa 889535a5 e1470831 6a8e75aa 4dde408f cf08f077 2186e7eb f05db9af 10d78870 e091839f 849bdca3 6a8e75aa 80f33c9c 85ee37bf 6a8e75aa 60010bac 5a94958a c3507502 69bb7c4b 1c544186 ef3118db 6a8e75aa 4142fd39 6a8e75aa 3ef467a2 9df113f5 b4f41071 0646e65b a4a37236 85ee37bf 6a8e75aa 4e827965 7716de2b 5422e4b5 09804271 5750a24d 4142fd39 d31987d8 16d93ef0 f2e940a2 eef8d50a 36ff149a 85ee37bf 6a8e75aa 444f6af3 f90edcdc a8fe76c0 30dec250 d7f500d7 5517c16b 7f6b4d9b 45143fcb 28e8726f 31272ce4 b5237c26 177f18b5 a9a1a00c bf165989 788cb441 6c567b89 5ad45d22 20cd1d0f 85c1ed71 3f63a4d5 2f050f29 70ab9fc1 c12d8025 dca18e08 72fd2827 ea297a2d 297190f3 af77f085 84ecdbd0 fc562313 bdbf5bf0 e41a1f71 6a8e75aa 6a8e75aa 775a8bbf e79614b6 8c36fb7d e38a009b 3f9b646e 20cd1d0f 677b0166 f75e5237 026490c9 afb8119c ebab2ea9 0b0d4579 20cd1d0f 3dca7a76 43ecd9af 00f29222 77c8a376 db6b0c2e 18189f7d 85ee37bf 6a8e75aa d4aebcd4 9ea05fb8 84a0adc1 4142fd39 b50d1089 23f36c75 d2ce6576 54dc3095 9a9df931 326b3328 104fbfdb d95d9a72 a611bf00 f7276b9f 9bdb1907 7263f581 8777e1fa 6a8e75aa 4142fd39 4142fd39 5b503ded 6a8e75aa",
		["a00757a7"] = "df69d891 e3803c36 20cd1d0f 0ebfebf2 50929c96 e637e3de b549ca17 6f34acdb 661a84fa 20cd1d0f 25d5ab41 d340205a 645894c9 20cd1d0f 3dfff4bf 7ac0f115 41e1255b 97bb5297 95833ac3 4eb1b0f5 6e0e1ad9 d71fcfcf ab72d6e4 78092c96 03dfa0a3 0c5ede0f cf9de88e 28580a7d e693520b 02783cbf 6a8e75aa 02e3b050 1a1d6ed4 c8762d46 469e4702 f18d9cce c79b0ffa 766947f3 3e03c74c 48d3e274 6a8e75aa a43b45f3 3c2491c4 4142fd39 8dcbc7b6 f4dd7b57 48d3e274 6a8e75aa ded2f6e1 02783cbf 6a8e75aa 1ce8cbdf 7fe94618 9edadc08 c4e4dc86 4fb86ecd 71baa611 2fce9a6b 01519acd 1ee2668e 1a91757e c3f1bd79 7712a453 2c42c233 10bf74ce 68315547 6a8e75aa 4b29deb1 6a8e75aa a40d8337 a6fcdc1c fdb74848 39386e06 a42870ff 8477cd1a 27f9158c e522e976 0176cfac 6a8e75aa 6a8e75aa 6a8e75aa fc907a49 65201357 6a8e75aa 648838d3 6a8e75aa 9020ce91 5e8bbe18 43b01fdc 735867e6 cf6658c6 defa2df3 17deeba2 e86fae6b b291e0dc 6d1dc37e 4884c941 0982e8ae bc04c08d 6a8e75aa 01c9f3b2 ed474426 ff1b9697 417c23b2 31816a46 47fc1055 0b32c334 d9d315c5 c135a414 5fde889d 6a8e75aa 2e8b44ee",
		["b73bc758"] = "11c35325 fd1a8a40 4f682a44 daa7df19 513d22be 1d60e4ca 8bd3825f 8b31ff27 0086da20 4f663c0f 78908fd8 007293e8 81fb1056 91a4d994 b6c603fe 3006c30f 3e7dceeb 41e1255b a3935575 6b79e95c 3f04c6c6 cc0bc482 23ce86d9 a340da7a f07b5132 4449e709 50c6518a e17272b8 0efe1257 b6f55e3a b75ff4fd c6d3532e d80705e5 49d864cd 5a0cb120 3af9e0e0 da503c42 2cef8378 6a8e75aa 5fa1494c e96c5e36 ad820e45 0fc14742 6c3b4350 913df67d 90403f7b d2ecc475 39dc8f4d 16003570 a3d3d167 4593fede bdbf5bf0 8d98c290 a88cef85 d6c0919b c553e018 40676b3e 5104d4a3 6a8e75aa bdbf5bf0 581189cc 40676b3e 5104d4a3 6a8e75aa d19ca73a 5104d4a3 6a8e75aa 6a8e75aa 6a8e75aa 16e4a824 94b19a3e b1bcd3f8 c9fb4100 f0a15699 520bd1c2 7bae18c8 fb521c03 ac0cb477 4e4849fb 19672431 6a8e75aa 02e3b050 1a1d6ed4 73ff8492 dcb8da60 b318981e e21fce83 aa4df3b3 5a283390 d718edd5 94e0157d fce982ed 19672431 6a8e75aa a43b45f3 47939b7e 0f4fbb97 4142fd39 72c0b2d4 8dcbc7b6 2cb536ff 6a8e75aa 19672431 6a8e75aa 5f346c93 f712eb14 68c8fdb3 767dcc32 56fe96f7 a29f7397 f4780811 75fdd5e4 6a8e75aa 9feb7fb6 3568a3c0 6a8e75aa 4f6f3727 76fd6d47 07d9dac9 51908a2c c05bb8c0 43d6eff8 6a8e75aa 0e0d60f9 6a8e75aa 68707429 6a8e75aa 3226ed0e 6848bfb3 3568a3c0 6a8e75aa 8f18ae3f bc04c08d 6a8e75aa 1d14b118 dd50ca52 65cbed96 1c6e139e 0f73d3c0 8dcbc7b6 c5717aeb 6a8e75aa 4d1e8b17 bac6c9e3 cb2548f1 1e351fa5 6a2a1ff0 95964504 605a30a6 3568a3c0 6a8e75aa 89547b11 bc04c08d 7afaf1b3 4aca672a 078cc71d 3568a3c0 6a8e75aa 1f8fa3df bc04c08d 6a8e75aa 3568a3c0 6a8e75aa 537eb83a 976dc6ad 8d2ba167 69d6b771 11c4037b e74b5293 a022305d 2d36553e 569abf1a b3ba2bba 8c57e3e5 6a8e75aa 6a7476bb 6a8e75aa 3ce36fe5 37ac76a1 60b8c195 5da62a30 82c2c40a 3568a3c0 6a8e75aa a1750920 bc04c08d 6a8e75aa 8a95bf27 5da62a30 547b1b4c 89a364b0 85ee37bf 6a8e75aa c203e8bb 8f4c014c 7c0cd030 3be2f152 20d1e2db 6a8e75aa 6a8e75aa d1996bae ac6ce805 53d09205 0bd0371e 86c35944 6a8e75aa b5759ba4 09759e39 f985edef 7cd89bd1 6a8e75aa 9de51eb9 8dcbc7b6 19f75fef 37ed7a3a 9441889f 9898c38c 1024cdd7 28e8b5bf f985edef 6a8e75aa 0f4cd1a0 6a8e75aa 04742ed6 d92656b9 7a3be759 1b8f98c7 7f0bb53c 44ef123a 6a8e75aa 55d731d8 c47aa97d 992483e3 e4c93150 dc8607da 2b352e93 3a2273e7 15fb8d93 de6c590d 65eea691 1f03cea8 44d9c042 6a8e75aa 6a8e75aa 4142fd39 cecd70dc f985edef eeed9ddc d552fd3c 424dc6b8 ef9939fd 6a8e75aa ab36b230 ef9939fd 6a8e75aa 5ee7d1ce 72887282 6a8e75aa 7b176f6a b5759ba4 09759e39 3568a3c0 6a8e75aa f1b49e4c f131d82a 6a8e75aa a265b129 dca637f5 696eca76 089babbe b5759ba4 09759e39 85ee37bf 6a8e75aa 3f40de1f 85ee37bf 6a8e75aa ea952701 dc8607da 4142fd39 6a8e75aa 4649ffe6 9416a896 6a8e75aa 4034c788 9416a896 4142fd39 720c0d79 0cccd776 b16f5d3b 07446fb2 4142fd39 a741f0ff f223fac6 9df113f5 d6257ff3 4dc62145 2aa424e3 56417b07 6a8e75aa 4a3f1384 6a8e75aa 67747202 7aa74a6a 6a1f4ac0 f80c48a8 6a8e75aa",
		["c51944c1"] = "67747202 27120e02 a849de35 f05197dd 68376c8d 82bf446b 15b50cb4 1769b867 faa2873d 2e3f382e 41cbdfea 065a3522 40e65178 a2ea4dbd 0b0db9db f80c48a8",
	},
}
]]></string>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
          </Item>
          <Item class="ModuleScript" referent="29">
            <Properties>
              <string name="Name">Pieces</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- Pieces.lua - the interface pieces Codes System 3.0 drives (plain data;
-- Kernel/Contracts.lua checks them). The pack's own copies ship under
-- StarterGui.RBLXEssentials.CodesSystem.CodesSystemGUI with RBLXE_Piece and
-- RBLXE_Contract; a theme's copy or a fork in StarterGui.RBLXEssentials.
-- Interface replaces one when it satisfies its contract. Every Panel also
-- needs the UI_Panel tag and PanelId "Codes"; the HudButton needs the
-- UI_Interactable tag, Action "TogglePanel" and TargetPanel "Codes".
return {
	CodesPanel = {
		contract = 1,
		kind = "Panel",
		doc = "The panel where a player types a code and redeems it. The client reads the box, the Redeem button and the two verdict labels by these paths.",
		requires = {
			{ path = "Container/Code/TextBox", class = "TextBox" },
			{ path = "Container/Button", class = "GuiButton" },
		},
		optional = {
			{ path = "Container/ErrorText", class = "TextLabel" },
			{ path = "Container/SuccessText", class = "TextLabel" },
		},
	},
	CodesButton = {
		contract = 1,
		kind = "HudButton",
		doc = "The HUD button that opens the codes panel.",
		opens = "CodesPanel",
	},
}
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="30">
            <Properties>
              <string name="Name">Schema</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[-- Schema.lua - the options a game sets for Codes System 3.0, in the Kernel's
-- Schema language (docs/kernel-api.md, section 8). The game's values live in
-- modules no update replaces:
--   ServerScriptService.RBLXEssentials.Config.CodesSystem   Codes (server
--       only: no client ever receives the list, so a code nobody announced
--       yet cannot be read from the game)
--   ReplicatedStorage.RBLXEssentials.Config.CodesSystem     Messages
-- Each returns only what the game changes; everything else is the default
-- below. T.config.reference("CodesSystem") prints this as a reference.
--
-- It accepts every code Codes System 2.x ran (plan A3, ruling 3): a Code
-- that is not blank, any non-empty Target for Custom (the module is looked
-- up by that exact name, as in 2.x), any Stat Target the grant can split
-- into names (Stats.Gold Coins included), and any Amount (a negative one
-- takes away, never below 0, as in 2.x). What it refuses, 2.x either
-- disabled at startup or could never grant.
--
-- Self-contained on purpose: Tools loads this module as a clone with no
-- Parent, so it uses no `script` and requires nothing. Nothing under Codes
-- has a default: this module reaches every client (RT-10), and so do the
-- example codes. They ship live (the owner's decision, V2VIS round 5):
-- shipExample makes a fresh install's server config module state them, so
-- they are visible and editable there and in the plugin; T.validate warns
-- codes-example-live until the game changes or deletes them. A game that
-- comes from 2.x never gets them.
return function(S)
	local TYPES = { "Cash", "Diamonds", "Spins", "Stat", "Custom" }

	-- The same rule Codes.lua matches with: trimmed and upper-cased.
	local function normalize(text)
		return (string.match(text, "^%s*(.-)%s*$") or ""):upper()
	end

	-- One code: Target belongs to Stat (a data path, split on its dots as
	-- the grant splits it) and Custom (a module name), and to nothing else.
	local function checkCode(code)
		local problems = {}
		if normalize(code.Code) == "" then
			table.insert(problems, { field = "Code", message = "must not be only spaces" })
		end
		local needsTarget = code.Type == "Stat" or code.Type == "Custom"
		if needsTarget and code.Target == nil then
			table.insert(problems, { field = "Target", message = ("is required when Type is %s"):format(code.Type) })
		elseif not needsTarget and code.Target ~= nil then
			table.insert(problems, { field = "Target", message = ("is only used when Type is Stat or Custom, not %s: leave it out"):format(code.Type) })
		elseif code.Type == "Stat" and string.find(code.Target, "[^%.]") == nil then
			table.insert(problems, { field = "Target", message = "must name a data path such as Stats.Gems: it has no name between its dots" })
		end
		return problems
	end

	-- The whole list: each code once, ignoring case and surrounding spaces
	-- (a repeated code could never be redeemed).
	local function checkCodes(list)
		local problems, seen = {}, {}
		for index, code in ipairs(list) do
			local key = normalize(code.Code)
			if seen[key] ~= nil then
				table.insert(problems, {
					field = { index, "Code" },
					message = ("repeats Codes[%d].Code (%s): each code must be unique, ignoring case and surrounding spaces"):format(seen[key], key),
				})
			else
				seen[key] = index
			end
		end
		return problems
	end

	local function message(default, doc)
		return S.string({ default = default, minLength = 1, doc = doc })
	end

	return S.config({
		Codes = S.list(S.record({
			Code = S.string({ minLength = 1, doc = "What the player types. Case and surrounding spaces do not matter: LAUNCH, launch and \" Launch \" are the same code." }),
			Type = S.enum(TYPES, { doc = "What the code grants. Cash and Diamonds add Amount to Stats.Cash or Stats.Diamonds; Spins adds roulette spins (needs Roulette System 3.0); Stat adds Amount to the data path in Target; Custom runs the module named in Target." }),
			Target = S.optional(S.string({ minLength = 1, doc = "Only for Stat (a data path, split on its dots: Stats.Gems) and Custom (the exact name of a ModuleScript in ServerScriptService.RBLXEssentials.Extensions.CodesSystem, or of the pack's own CustomRewards, such as GrantPet). Leave it out for Cash, Diamonds and Spins." })),
			Amount = S.number({ doc = "How much the code grants. A negative Amount takes away, never below 0. For Custom, the amount passed to the module (GrantPet: how many pets)." }),
			Expires = S.optional(S.utcDate({ doc = "Optional date, YYYY-MM-DD in UTC. The code stops working AT 00:00 UTC on that date, so \"2026-09-01\" is redeemable through 31 August. Leave it out for a code that never expires." })),
		}, {
			check = checkCode,
			checkDoc = "Target is required when Type is Stat or Custom, and must be left out for Cash, Diamonds and Spins; a Stat Target names at least one key.",
		}), {
			server = true,
			doc = "The redeemable codes. Each player can redeem each code once. Server only: clients never receive this list.",
			example = {
				{ Code = "WELCOME2027", Type = "Cash", Amount = 500, Expires = "2027-12-31" },
				{ Code = "PETDAY2027", Type = "Custom", Target = "GrantPet", Amount = 1 },
			},
			shipExample = true,
			check = checkCodes,
			checkDoc = "Each Code appears once, ignoring case and surrounding spaces.",
		}),
		Messages = S.record({
			Redeemed = message("Redeemed {code}!", "Shown when a code works. {code} is replaced with the code."),
			Empty = message("Type a code first!", "The player pressed Redeem with an empty box."),
			NoSuchCode = message("That code doesn't exist.", "No code by that name, or a typo."),
			AlreadyRedeemed = message("You've already used that code.", "The player redeemed this code before."),
			Expired = message("That code has expired.", "The code is past its Expires date."),
			CodeDisabled = message("That code isn't available.", "The code cannot be granted right now (for example, its Custom module is missing)."),
			TooFast = message("Slow down a moment, then try again.", "Too many wrong guesses in a row: the brute-force throttle."),
			NoData = message("Your data is still loading. Try again.", "The player redeemed before their save finished loading."),
			GrantFailed = message("Something went wrong. Try again.", "The reward could not be granted. Also the answer to any unknown refusal."),
		}, { doc = "What the Codes panel tells the player. Set only the messages you change." }),
	})
end
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="31">
            <Properties>
              <string name="Name">manifest</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[return {
	id = "CodesSystem",
	version = "3.0.0",
	deps = { "DatastorePurchaseSystem" },
	-- Roulette System is an OPTIONAL companion, deliberately not listed here:
	-- `ctx.require` asserts on undeclared deps and declaring it would make it
	-- mandatory. A `Spins` code reaches it through ctx.companion("RouletteSystem")
	-- (its grantSpins) instead and degrades with a warning when it is absent -
	-- the same pattern `Time Rewards System` 3.0 uses.
	thumbnail = "rbxassetid://108514551160071", -- "Codes" card image (ledger 2026-08-23)
	-- The HUD button's own icon, square and drawn to be read small. The
	-- thumbnail above is the wide store card and turns into a thin strip
	-- inside a notification's square plate, which is the whole reason these
	-- are two fields rather than one.
	icon = "rbxassetid://137125277479961",
}
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
        </Item>
      </Item>
    </Item>
    <Item class="Folder" referent="32">
      <Properties>
        <string name="Name">ServerScriptService</string>
        <BinaryString name="AttributesSerialize"></BinaryString>
        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
        <Color3 name="IconTint">
          <R>0</R>
          <G>0</G>
          <B>0</B>
        </Color3>
        <bool name="DefinesCapabilities">false</bool>
        <int64 name="SourceAssetId">-1</int64>
        <BinaryString name="Tags"></BinaryString>
      </Properties>
      <Item class="Script" referent="33">
        <Properties>
          <string name="Name">Kernel</string>
          <BinaryString name="AttributesSerialize"></BinaryString>
          <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
          <bool name="Disabled">false</bool>
          <ContentId name="LinkedSource">
            <null>
            </null>
          </ContentId>
          <token name="RunContext">0</token>
          <bool name="DefinesCapabilities">false</bool>
          <string name="Source"><![CDATA[-- RBLX Essentials Kernel - server boot. The only Script in the ecosystem.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local StarterGui = game:GetService("StarterGui")
local HttpService = game:GetService("HttpService")

local kernelFolder = ReplicatedStorage:WaitForChild("RBLXEssentials"):WaitForChild("Kernel")
local RootPersistence = require(kernelFolder.UI.RootPersistence)

-- Run first, in its own pcall, before any other require or work: make
-- StarterGui.RBLXEssentials survive character respawns (see
-- UI/RootPersistence.lua) before any character spawns. Guarded: a failure
-- here must never stop the packs from booting, and the piece decision below
-- then uses the Folder root as it is.
local persistOk, persistResult, persistWarnings = pcall(RootPersistence.persist, StarterGui, Instance.new, Enum)
local uiRoot
if persistOk then
	uiRoot = persistResult
	for _, message in ipairs(persistWarnings) do
		warn("[RBLXE] UI root: " .. message)
	end
else
	warn("[RBLXE] could not make StarterGui.RBLXEssentials persistent; menus, HUD buttons and pack interfaces may stop working after a respawn: " .. tostring(persistResult))
	uiRoot = StarterGui:FindFirstChild("RBLXEssentials")
end

local Pieces = require(kernelFolder.UI.Pieces)
local Loader = require(kernelFolder.Loader)
local Version = require(kernelFolder.Version)
local Net = require(kernelFolder.Net)
local SFX = require(kernelFolder.SFX)
local Notify = require(kernelFolder.Notify)
local Signal = require(kernelFolder.Signal)
local Trove = require(kernelFolder.Packages.Trove)
local Schema = require(kernelFolder.Schema)
local Config = require(kernelFolder.Config)
local Contracts = require(kernelFolder.Contracts)

local ssRoot = script.Parent -- ServerScriptService/RBLXEssentials
local rsRoot = ReplicatedStorage.RBLXEssentials
local sharedPacks = rsRoot:WaitForChild("Packs")

rsRoot:SetAttribute("RBLXE_KernelVersion", Version)

-- 3.0 pieces: decide once, here, which copy of every declared interface piece
-- the game uses (a valid StarterGui.RBLXEssentials.Interface override, else the
-- pack's default) and publish it for every client, before any pack boots. The
-- JSON encoding runs inside the same pcall: whatever fails here falls back to
-- LEGACY and never stops the server script before Loader.boot.
-- RT-6: packs whose Pieces declaration is invalid; the Loader fails them.
local pieceFailures = {}
local planOk, encodedOrError = pcall(function()
	local declarations, declarationWarnings, declarationFailures = Pieces.collectDeclarations(sharedPacks, require, Contracts)
	pieceFailures = declarationFailures
	local plan = Pieces.plan({
		declarations = declarations,
		root = uiRoot,
		contracts = Contracts,
	})
	for _, message in ipairs(declarationWarnings) do
		warn("[RBLXE] UI: " .. message)
	end
	for _, message in ipairs(Pieces.strays(StarterGui, uiRoot)) do
		warn("[RBLXE] UI: " .. message)
	end
	for _, line in ipairs(plan.lines) do
		print("[RBLXE] UI: " .. line)
	end
	for _, message in ipairs(plan.warnings) do
		warn("[RBLXE] UI: " .. message)
	end
	return Pieces.encodeDecisions(plan, function(value)
		return HttpService:JSONEncode(value)
	end)
end)
if planOk then
	rsRoot:SetAttribute(Pieces.WINNERS_ATTRIBUTE, encodedOrError)
else
	warn("[RBLXE] UI: piece resolution failed, pieces keep 2.x adoption: " .. tostring(encodedOrError))
	rsRoot:SetAttribute(Pieces.WINNERS_ATTRIBUTE, Pieces.LEGACY)
end

local report = Loader.boot({
	side = "Server",
	sharedPacks = sharedPacks,
	serverPacks = ssRoot:WaitForChild("Packs"),
	kernelApi = { Version = Version, Trove = Trove, Signal = Signal, Net = Net, SFX = SFX, Notify = Notify },
	configLib = Config,
	schemaLib = Schema,
	configRoots = { shared = rsRoot:FindFirstChild("Config"), server = ssRoot:FindFirstChild("Config") },
	pieceFailures = pieceFailures,
	extensions = ssRoot:FindFirstChild("Extensions"),
})

-- RT-5: tell every client which packs did not start here. A reason that may
-- quote server-only values (an invalid configuration, a server module's
-- error) reaches clients as a fixed text (Loader.encodeServerReport); the
-- warnings below print it whole in the server output. The encoding and
-- the attribute write share one pcall: a report that cannot be published
-- must never stop the rest of this boot (the warnings below and
-- RBLXE_ServerBooted).
local reportOk, reportError = pcall(function()
	local encodedReport = Loader.encodeServerReport(report, function(value)
		return HttpService:JSONEncode(value)
	end)
	rsRoot:SetAttribute(Loader.SERVER_REPORT_ATTRIBUTE, encodedReport)
end)
if not reportOk then
	warn("[RBLXE] could not publish the boot report for clients; each client waits 30s for it, then boots every pack: " .. tostring(reportError))
end

print(Loader.formatReport("Server", report))
for id, reason in pairs(report.failed) do
	warn(string.format("[RBLXE] %s failed: %s", id, reason))
end
for id, reason in pairs(report.skipped) do
	warn(string.format("[RBLXE] %s skipped: %s", id, reason))
end
for _, notice in ipairs(report.notices or {}) do
	warn("[RBLXE] " .. notice)
end
ssRoot:SetAttribute("RBLXE_ServerBooted", true)
]]></string>
          <int64 name="SourceAssetId">-1</int64>
          <BinaryString name="Tags"></BinaryString>
        </Properties>
      </Item>
      <Item class="Folder" referent="34">
        <Properties>
          <string name="Name">Packs</string>
          <BinaryString name="AttributesSerialize"></BinaryString>
          <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
          <Color3 name="IconTint">
            <R>0</R>
            <G>0</G>
            <B>0</B>
          </Color3>
          <bool name="DefinesCapabilities">false</bool>
          <int64 name="SourceAssetId">-1</int64>
          <BinaryString name="Tags"></BinaryString>
        </Properties>
        <Item class="ModuleScript" referent="35">
          <Properties>
            <string name="Name">CodesSystem</string>
            <BinaryString name="AttributesSerialize">AQAAAAwAAABSQkxYRV9IYXNoZXMCwwIAACNyYmx4ZS1oYXNoZXMgdjEgZm52MWEzMiBsZgpSZXBsaWNhdGVkU3RvcmFnZS9QYWNrcy9Db2Rlc1N5c3RlbS9DbGllbnQJYTBkYmFmNWQKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWVzc2FnZXMJYjRhODA3MDYKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9Gcm9tVjIJZWM2OTQ3NmEKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9JbnRlcmZhY2UJMDM2MjY0ZWMKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vTWlncmF0aW9ucy9LZWVwCTY4NmNlZjhmClJlcGxpY2F0ZWRTdG9yYWdlL1BhY2tzL0NvZGVzU3lzdGVtL1BpZWNlcwk3NzYyZDY4NApSZXBsaWNhdGVkU3RvcmFnZS9QYWNrcy9Db2Rlc1N5c3RlbS9TY2hlbWEJYzExYzhiZmQKUmVwbGljYXRlZFN0b3JhZ2UvUGFja3MvQ29kZXNTeXN0ZW0vbWFuaWZlc3QJYWMxYjkxMGUKU2VydmVyU2NyaXB0U2VydmljZS9QYWNrcy9Db2Rlc1N5c3RlbQkwYzIzNTVkYgpTZXJ2ZXJTY3JpcHRTZXJ2aWNlL1BhY2tzL0NvZGVzU3lzdGVtL0NvZGVMaXN0CTNjOGY0NTJhClNlcnZlclNjcmlwdFNlcnZpY2UvUGFja3MvQ29kZXNTeXN0ZW0vQ29kZXMJMmZhMzMwYTgKU2VydmVyU2NyaXB0U2VydmljZS9QYWNrcy9Db2Rlc1N5c3RlbS9DdXN0b21SZXdhcmRzL0dyYW50UGV0CTBhZDg4NDlm</BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <ContentId name="LinkedSource">
              <null>
              </null>
            </ContentId>
            <bool name="DefinesCapabilities">false</bool>
            <string name="Source"><![CDATA[--[[
	CodesSystem - server module.

	Owns everything impure: the datastore, the remote, the rate limit and the
	grant paths. The rules themselves live in the pure `Codes` module beside
	this file, which is where the interesting behaviour is tested
	(tests/codes.spec.luau) - nothing here needs a clock or a profile of its
	own to be correct.

	3.0: the codes are the game's configuration, `Codes` in
	ServerScriptService.RBLXEssentials.Config.CodesSystem, read through
	ctx.config after the Kernel validated them against Schema.lua. No client
	ever receives them (2.x kept them in ReplicatedStorage).

	The grant machinery is `Time Rewards System v2`'s, deliberately: the same
	four reward types must mean the same thing in every pack, down to
	refusing to overwrite a non-number leaf and to capping Roulette spins
	through Roulette's own rule.

	ONE DIFFERENCE FROM TIME REWARDS, and it is on purpose: there, `grant`
	opens its own `ds.Update` and the claim is marked in a second one. Here
	the grant and the mark share ONE update, so a code can never be recorded
	as used by a write that then failed to land the reward.
]]

local Players = game:GetService("Players")

local Codes = require(script.Codes)
local CodeList = require(script.CodeList)

local TAG = "[CodesSystem]"

-- ── rate limit ──────────────────────────────────────────────────────────
-- A code is guessable by brute force and the panel gives a player unlimited
-- tries. The client's disarmed button is a courtesy; THIS is the defence.
local MIN_INTERVAL = 1 -- seconds between attempts, always
local FAILURES_BEFORE_COOLDOWN = 5
local COOLDOWN_START = 5
local COOLDOWN_CEILING = 60

return function(ctx)
	local ds = ctx.require("DatastorePurchaseSystem")
	local trove = ctx.kernel.Trove.new()

	-- `Expires` is parsed once, here, rather than on every redemption.
	local codes = CodeList.fromConfig(ctx.config.Codes, Codes)
	if #codes == 0 then
		warn(TAG .. " no codes yet: add them as Codes = { ... } in ServerScriptService.RBLXEssentials.Config.CodesSystem")
	end

	-- CustomRewards modules: the game's own in ServerScriptService.
	-- RBLXEssentials.Extensions.CodesSystem first (plan B0: T.migrate puts a
	-- 2.x game's modules there, and no update touches that zone), then this
	-- pack's own CustomRewards; each required once. A module that fails to
	-- load disables every slot naming it.
	local customFns = {}
	local customFolder = script:FindFirstChild("CustomRewards")
	for _, entry in ipairs(codes) do
		if entry.type == "Custom" and not entry.invalid then
			if customFns[entry.target] == nil then
				local mod = ctx.extension(entry.target, customFolder)
				if mod and mod:IsA("ModuleScript") then
					local ok, result = pcall(require, mod)
					if ok and type(result) == "table" and type(result.Grant) == "function" then
						if type(result.Prepare) == 'function' then
							local prepared, ready = pcall(result.Prepare)
							if not prepared or ready == false then entry.invalid = true end
						end
						customFns[entry.target] = result.Grant
						if entry.invalid then customFns[entry.target] = false end
					else
						warn(("%s %s (Extensions.CodesSystem or CustomRewards) loaded but has no .Grant, so %s is disabled"):format(TAG, entry.target, entry.name))
						-- `false` = looked up and failed, so a second slot
						-- naming the same module is disabled without a
						-- duplicate warning.
						customFns[entry.target] = false
						entry.invalid = true
					end
				else
					warn(("%s %s not found in Extensions.CodesSystem or CustomRewards, so %s is disabled"):format(TAG, entry.target, entry.name))
					customFns[entry.target] = false
					entry.invalid = true
				end
			elseif customFns[entry.target] == false then
				entry.invalid = true
			end
		end
	end

	-- ── Roulette, through its companion API and optional ────────────────
	-- 3.0 (plan B3b): no pack reads another pack's instances by path.
	-- Roulette is not in manifest.deps (it is a companion, not a
	-- requirement), so it is reached through ctx.companion, and Roulette
	-- System 3.0's server API has grantSpins(d, amount): its own rule (the
	-- MaxSpins cap, nothing for 0 or less) applied to the profile table
	-- inside the caller's ds.Update, which is what 2.x did here with
	-- Roulette.addSpins and Config.buildSpins. Asked for at every grant,
	-- never cached: the two packs boot in either order, so its API may not
	-- exist yet at start. The same lookup as Time Rewards System 3.0's.
	local function rouletteApi()
		local handle = ctx.companion("RouletteSystem")
		if handle == nil then
			return nil, "Roulette System is not installed"
		end
		local api = handle.api()
		if api == nil then
			return nil, "Roulette System did not start"
		end
		if type(api.grantSpins) ~= "function" then
			return nil, ("Roulette System %s has no grantSpins (it needs Roulette System 3.0)"):format(tostring(handle.version))
		end
		return api, nil
	end

	-- ── granting ────────────────────────────────────────────────────────
	--- Mutates the profile table `d` in place and reports whether the reward
	--- landed. The CALLER owns the `ds.Update` - see the header: the grant
	--- and the redemption record must land together or not at all.
	local function addToPath(d, path, delta)
		local parts = {}
		for part in path:gmatch("[^%.]+") do
			table.insert(parts, part)
		end
		if #parts == 0 then
			return false
		end
		local node = d
		for i = 1, #parts - 1 do
			local key = parts[i]
			if type(node[key]) ~= "table" then
				if node[key] ~= nil then
					return false -- a non-table sits where a branch should be
				end
				node[key] = {}
			end
			node = node[key]
		end
		local leaf = parts[#parts]
		if node[leaf] ~= nil and type(node[leaf]) ~= "number" then
			return false
		end
		node[leaf] = math.max(0, (tonumber(node[leaf]) or 0) + delta)
		return true
	end

	local function grant(player, d, entry): boolean
		if entry.type == "Cash" or entry.type == "Diamonds" then
			return addToPath(d, "Stats." .. entry.type, entry.amount)
		elseif entry.type == "Stat" then
			local ok = addToPath(d, entry.target, entry.amount)
			if not ok then
				warn(("%s %s: '%s' is not a usable data path, so nothing was granted"):format(TAG, entry.name, entry.target))
			end
			return ok
		elseif entry.type == "Spins" then
			-- A table lookup, no `require` and no yield, so it can run inside
			-- the update's transform.
			local api, why = rouletteApi()
			if api == nil then
				warn(("%s %s grants Spins but %s, so nothing was granted"):format(TAG, entry.name, why))
				return false
			end
			-- 2.x's semantics: the code is redeemed even when grantSpins
			-- answers false and adds nothing (a Spins Amount of 0 or less).
			api.grantSpins(d, entry.amount)
			return true
		elseif entry.type == "Custom" then
			local fn = customFns[entry.target]
			if type(fn) ~= "function" then
				return false
			end
			local result = fn(player, d, entry.amount)
			return result ~= false and not (type(result) == 'table' and result.ok == false)
		end
		return false
	end

	-- ── rate limit ──────────────────────────────────────────────────────
	-- Per player: one attempt a second always, and after five consecutive
	-- FAILURES a cooldown doubling from 5 s to a 60 s ceiling. Any success
	-- resets it - someone working through a list of real codes must never be
	-- punished for it.
	local throttle = {} -- [Player] = { last, failures, blockedUntil, cooldown }

	local function throttleOf(player)
		local t = throttle[player]
		if not t then
			t = { last = 0, failures = 0, blockedUntil = 0, cooldown = COOLDOWN_START }
			throttle[player] = t
		end
		return t
	end

	--- true when the attempt may proceed. Stamps `last` only when it does, so
	--- a refused attempt cannot extend its own window.
	local function allowAttempt(player, now: number): boolean
		local t = throttleOf(player)
		if now < t.blockedUntil or (now - t.last) < MIN_INTERVAL then
			return false
		end
		t.last = now
		return true
	end

	local function noteResult(player, success: boolean, now: number)
		local t = throttleOf(player)
		if success then
			t.failures, t.cooldown, t.blockedUntil = 0, COOLDOWN_START, 0
			return
		end
		t.failures += 1
		if t.failures >= FAILURES_BEFORE_COOLDOWN then
			t.blockedUntil = now + t.cooldown
			t.cooldown = math.min(COOLDOWN_CEILING, t.cooldown * 2)
			t.failures = 0
		end
	end

	-- ── redemption ──────────────────────────────────────────────────────
	local function redeem(player, text)
		local now = os.time()
		if not allowAttempt(player, now) then
			return false, "TooFast"
		end

		local okGet, data = pcall(ds.Get, player)
		if not okGet or type(data) ~= "table" then
			noteResult(player, false, now)
			return false, "NoData"
		end

		local ok, reason = Codes.canRedeem(data, codes, text, now)
		if not ok then
			-- An EMPTY box is a slip, not a guess: it reveals nothing and is
			-- not an attack, so it must not push anyone towards the cooldown.
			-- Caught in the E2E, where five real refusals plus one stray empty
			-- submission would have locked a player out. It still costs the
			-- one-per-second budget, which is enough to stop a spinning client.
			if reason ~= "Empty" then
				noteResult(player, false, now)
			end
			return false, reason
		end
		local entry = Codes.find(codes, text)

		-- Validate -> grant -> mark, all inside ONE update so a code cannot be
		-- recorded as used by a write that failed to land the reward.
		local granted = false
		local okUpdate = pcall(ds.Update, player, function(d)
			Codes.ensureData(d)
			-- Re-check inside the transform: `ds.Update` can retry, and
			-- another server may have redeemed this code for the same player
			-- between the Get above and this write.
			if Codes.canRedeem(d, codes, text, now) then
				granted = grant(player, d, entry)
				if granted then
					Codes.markRedeemed(d, entry)
				end
			end
		end)

		if not okUpdate or not granted then
			noteResult(player, false, now)
			if okUpdate then
				-- The write happened but nothing was granted: either the
				-- re-check refused it, or the reward path could not apply.
				return false, "GrantFailed"
			end
			warn(("%s data update failed for %s"):format(TAG, player.Name))
			return false, "GrantFailed"
		end

		noteResult(player, true, now)
		return true, { Code = entry.code, Type = entry.type, Amount = entry.amount }
	end

	local function hasRedeemed(player, text): boolean
		local okGet, data = pcall(ds.Get, player)
		if not okGet or type(data) ~= "table" then
			return false
		end
		local set = type(data.Codes) == "table" and data.Codes.Redeemed or nil
		return type(set) == "table" and set[Codes.normalize(text)] == true
	end

	-- ── joins and leaves ────────────────────────────────────────────────
	local function onJoin(player)
		-- `ensureData` reports whether it changed anything, so an existing
		-- profile costs no write.
		local okGet, data = pcall(ds.Get, player)
		if not okGet or type(data) ~= "table" then
			return
		end
		if type(data.Codes) == "table" and type(data.Codes.Redeemed) == "table" then
			return
		end
		pcall(ds.Update, player, function(d)
			Codes.ensureData(d)
		end)
	end

	for _, player in ipairs(Players:GetPlayers()) do
		task.spawn(onJoin, player)
	end
	trove:Connect(Players.PlayerAdded, function(player)
		task.spawn(onJoin, player)
	end)
	trove:Connect(Players.PlayerRemoving, function(player)
		-- Keyed by the Player INSTANCE, so this is the only place the table
		-- can be cleaned - a fast rejoin gets a fresh, unpunished record.
		throttle[player] = nil
	end)

	-- ── remote ──────────────────────────────────────────────────────────
	local net = ctx.kernel.Net.server(ctx.packId)
	local redeemRemote = net:func("Redeem")
	redeemRemote.OnServerInvoke = function(player, text)
		local ok, result = redeem(player, text)
		if ok then
			return { Success = true, Code = result.Code, Type = result.Type, Amount = result.Amount }
		end
		return { Success = false, Error = result }
	end

	return {
		redeem = redeem,
		hasRedeemed = hasRedeemed,
	}
end
]]></string>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
          </Properties>
          <Item class="ModuleScript" referent="36">
            <Properties>
              <string name="Name">CodeList</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[--[[
	CodeList.lua - turns ctx.config.Codes into the entries the pure rules in
	Codes.lua work on. Pure, like Codes.lua (tests/codelist.spec.luau).

	The Kernel validated the list against Schema.lua before the pack booted,
	so a code here is never malformed: a broken code stops the pack with its
	path in the boot report instead of being disabled at the player. What
	can still disable an entry at boot is the world (a Custom module that is
	missing), which the server module handles.

	`Expires` is parsed ONCE, here, so a redemption never touches a date
	string. The config tables are frozen, so every entry is a fresh table.
]]

local CodeList = {}

function CodeList.fromConfig(codes, CodesRules)
	local out = {}
	for index, code in ipairs(codes or {}) do
		table.insert(out, {
			index = index,
			name = ("Codes[%d]"):format(index),
			code = code.Code,
			type = code.Type,
			target = code.Target or "",
			amount = code.Amount,
			expires = CodesRules.expiresAt(code.Expires or ""),
		})
	end
	return out
end

return CodeList
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="ModuleScript" referent="37">
            <Properties>
              <string name="Name">Codes</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <ContentId name="LinkedSource">
                <null>
                </null>
              </ContentId>
              <bool name="DefinesCapabilities">false</bool>
              <string name="Source"><![CDATA[--[[
	Codes.lua - the pure rules behind redeeming a code.

	No `game`, no Instances, no clock of its own: every function takes the
	profile table, the code list and `now` explicitly, so the whole ruleset is
	exercisable under Lune (tests/codes.spec.luau). The server module owns
	everything impure - the datastore, the remote and the rate limit.

	PERSISTED: `d.Codes.Redeemed` is a SET keyed by the NORMALISED code, the
	same shape `Time Rewards System v2` uses for `TimeRewards.Daily`. Renaming
	a code in Settings therefore lets a player redeem it again, which is
	correct: it is a different code.

	EXPIRY IS DATE-ONLY AND UTC. `Expires = "2026-09-01"` means the code stops
	working AT 00:00 UTC on the 1st, so 31 August is the last redeemable day.
	Both readings of "expires on the 1st" exist in the wild, and a buyer must
	not have to discover which one we meant from a support ticket - so it is
	stated here, in the product doc, and in the setting's own Description.
]]

local Codes = {}

local DAY_SECONDS = 86400
local DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

--- Trimmed and upper-cased. Both sides of every comparison go through this,
--- so `launch `, `LAUNCH` and `Launch` are one code. Inner spaces survive -
--- a buyer may legitimately author "FREE SPINS".
function Codes.normalize(text: any): string
	if type(text) ~= "string" then
		return ""
	end
	return (text:match("^%s*(.-)%s*$") or ""):upper()
end

local function isLeap(year: number): boolean
	return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end

--- Epoch seconds of 00:00 UTC on `spec` (YYYY-MM-DD).
---   nil   - no expiry (an empty spec)
---   false - malformed; the caller disables the slot and warns
--- Plain arithmetic rather than `os.date`, so this module stays pure and
--- Lune-testable: UTC has no DST, so a day IS exactly 86400 seconds and epoch
--- day 0 is 1970-01-01. Same technique `DailyRewards` uses for its Midnight
--- reset mode.
function Codes.expiresAt(spec: any): number | false | nil
	if type(spec) ~= "string" then
		return false
	end
	if spec == "" then
		return nil
	end
	local ys, ms, ds = spec:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
	if not ys then
		return false
	end
	local y, m, d = tonumber(ys) :: number, tonumber(ms) :: number, tonumber(ds) :: number
	if m < 1 or m > 12 or d < 1 then
		return false
	end
	local maxDay = DAYS_IN_MONTH[m] + ((m == 2 and isLeap(y)) and 1 or 0)
	if d > maxDay then
		return false
	end
	local days = 0
	for year = 1970, y - 1 do
		days += isLeap(year) and 366 or 365
	end
	for month = 1, m - 1 do
		days += DAYS_IN_MONTH[month] + ((month == 2 and isLeap(y)) and 1 or 0)
	end
	return (days + d - 1) * DAY_SECONDS
end

--- A fresh record: nothing redeemed yet.
function Codes.newData()
	return { Redeemed = {} }
end

--- Backfills `d.Codes` in place. Returns true when it changed anything, so
--- the caller can skip a write. Repairs field by field rather than discarding
--- the record - a corrupt `Redeemed` must not cost the player anything else
--- stored alongside it.
function Codes.ensureData(d): boolean
	if type(d.Codes) ~= "table" then
		d.Codes = Codes.newData()
		return true
	end
	if type(d.Codes.Redeemed) ~= "table" then
		d.Codes.Redeemed = {}
		return true
	end
	return false
end

local function redeemedSet(d)
	if type(d) ~= "table" or type(d.Codes) ~= "table" or type(d.Codes.Redeemed) ~= "table" then
		return nil
	end
	return d.Codes.Redeemed
end

--- The entry whose code matches `text`, normalised on both sides.
function Codes.find(list, text: any)
	local needle = Codes.normalize(text)
	if needle == "" or type(list) ~= "table" then
		return nil
	end
	for _, entry in ipairs(list) do
		if Codes.normalize(entry.code) == needle then
			return entry
		end
	end
	return nil
end

--- canRedeem(d, list, text, now) -> ok, reason
--- Machine reasons, so the caller owns the wording.
---
--- The order of the checks is deliberate: a slot the buyer broke reports
--- `CodeDisabled` whether or not the player already used it, so a support
--- question has one answer instead of two depending on who asks.
function Codes.canRedeem(d, list, text: any, now: number): (boolean, string?)
	local needle = Codes.normalize(text)
	if needle == "" then
		return false, "Empty"
	end
	local set = redeemedSet(d)
	if set == nil then
		return false, "NoData"
	end
	local entry = Codes.find(list, needle)
	if entry == nil then
		return false, "NoSuchCode"
	end
	if entry.invalid then
		return false, "CodeDisabled"
	end
	if set[needle] == true then
		return false, "AlreadyRedeemed"
	end
	-- `expires` is the instant the code stops working, so the instant itself
	-- is already too late.
	if type(entry.expires) == "number" and now >= entry.expires then
		return false, "Expired"
	end
	return true, nil
end

--- Records a successful redemption. Separate from `canRedeem` because the
--- grant happens between them, and a failed grant must not cost the player
--- the code.
function Codes.markRedeemed(d, entry): boolean
	local set = redeemedSet(d)
	if set == nil or type(entry) ~= "table" then
		return false
	end
	local key = Codes.normalize(entry.code)
	if key == "" then
		return false
	end
	set[key] = true
	return true
end

return Codes
]]></string>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
          </Item>
          <Item class="Folder" referent="38">
            <Properties>
              <string name="Name">CustomRewards</string>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <Color3 name="IconTint">
                <R>0</R>
                <G>0</G>
                <B>0</B>
              </Color3>
              <bool name="DefinesCapabilities">false</bool>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags"></BinaryString>
            </Properties>
            <Item class="ModuleScript" referent="39">
              <Properties>
                <string name="Name">GrantPet</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <ContentId name="LinkedSource">
                  <null>
                  </null>
                </ContentId>
                <bool name="DefinesCapabilities">false</bool>
                <string name="Source"><![CDATA[-- GrantPet - Custom code reward (example)
-- Grants a specific Easy Pet System pet straight into the player's inventory.
--
-- Use it from the game's config (ServerScriptService.RBLXEssentials.Config.
-- CodesSystem): a code with Type = "Custom" and Target = "GrantPet" (this
-- module's name). Your own modules go in ServerScriptService.RBLXEssentials.
-- Extensions.CodesSystem, the game's own folder that no update touches, and
-- are found there first (a copy of this module there runs instead of it) -
-- any ModuleScript returning a table with `.Grant(player, d)` works, where
-- `d` is the live data table inside a ds.Update callback (changes persist
-- and replicate automatically). Amount is the count in one atomic batch.
-- Return false when the reward cannot be granted; the code stays redeemable.
--
-- Configuration:
--   PET_RARITY - grant a pet OF THIS TIER (a rarity id in PetData/Rarities)
--   PET_ID     - or name one pet exactly (a pet id in PetData/Pets); wins
--
-- Needs Easy Pet System v2 installed; warns and grants nothing otherwise.

local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- ── Config ────────────────────────────────────────────────────────────────────

-- WHAT "LEGENDARY" MEANS IS A PROPERTY OF THE PET, NOT A NAME. The code that
-- redeems this ships as `LEGENDARY`, so the reward is picked BY RARITY: a
-- hardcoded id starts lying the moment the buyer edits PetData - which is
-- exactly how this shipped granting a Common "Pig" from a code called
-- LEGENDARY.
local PET_RARITY = "Legendary" -- ← any rarity id defined in PetData/Rarities
local PET_ID = "" -- ← or name one pet exactly; when set it wins over PET_RARITY

-- ── Load Easy Pet System's PetConfig (lazily, once) ───────────────────────────

local config = nil
local function getConfig()
	if config then
		return config
	end
	local rblx = ReplicatedStorage:FindFirstChild("RBLXEssentials")
	local packs = rblx and rblx:FindFirstChild("Packs")
	local eps = packs and packs:FindFirstChild("EasyPetSystem")
	local petConfigModule = eps and eps:FindFirstChild("PetConfig")
	local assets = eps and eps:FindFirstChild("Assets")
	local petData = assets and assets:FindFirstChild("PetData")
	if not (petConfigModule and petConfigModule:IsA("ModuleScript") and petData) then
		warn("[GrantPet] Easy Pet System v2 not found. Install it, or change this code's Type")
		return nil
	end
	local ok, result = pcall(function()
		return require(petConfigModule).load(petData)
	end)
	if not ok then
		warn("[GrantPet] PetConfig failed to load:", result)
		return nil
	end
	config = result
	return config
end

-- ── Which pet ─────────────────────────────────────────────────────────────────

--- The named pet when PET_ID is set, otherwise the most modest pet of
--- PET_RARITY - the LOWEST multiplier in that tier.
---
--- Deliberately not random: this is an example reward, and a lucky roll
--- handing out the buyer's strongest pet is a support ticket, not a feature.
--- (Surprise already has a home in this ecosystem - Roulette's `RandomPet`
--- hatches from an egg's real odds.) Ties break on the pet id so every server
--- answers the same thing.
local function pickPet(cfg): (string?, any)
	if PET_ID ~= "" then
		local def = cfg.Pets[PET_ID]
		if not def then
			warn(("[GrantPet] Pet '%s' not found in PetData/Pets"):format(PET_ID))
			return nil, nil
		end
		return PET_ID, def
	end

	local bestId, bestDef = nil, nil
	for id, def in pairs(cfg.Pets) do
		if def.rarity == PET_RARITY then
			if
				bestDef == nil
				or def.multiplier < bestDef.multiplier
				or (def.multiplier == bestDef.multiplier and id < bestId)
			then
				bestId, bestDef = id, def
			end
		end
	end
	if not bestId then
		warn(("[GrantPet] No pet of rarity '%s' in PetData/Pets. Set PET_ID instead"):format(PET_RARITY))
	end
	return bestId, bestDef
end

-- ── Module ────────────────────────────────────────────────────────────────────

local GrantPet = {}

local rewardRules, discovery
function GrantPet.Prepare()
 local cfg=getConfig();if not cfg then return false end
 local root=game:GetService('ServerScriptService'):FindFirstChild('RBLXEssentials')
 local packs=root and root:FindFirstChild('Packs')
 local pack=packs and packs:FindFirstChild('EasyPetSystem')
 local module=pack and pack:FindFirstChild('InventoryRewards')
 local index=pack and pack:FindFirstChild('Discovery')
 if not (module and index) then warn('[GrantPet] Update Easy Pet System to the inventory reward API');return false end
 rewardRules=require(module);discovery=require(index)
 return true
end
function GrantPet.Grant(player, d, amount)
 if not d then return false end
 if not rewardRules and not GrantPet.Prepare() then return false end
 local cfg=getConfig();local petId=pickPet(cfg)
 if not petId then return false end
 -- One staged batch: insufficient capacity never leaves a partial reward.
 local result=rewardRules.apply(d,cfg,'pet',petId,amount or 1,nil,
  function()return HttpService:GenerateGUID(false)end,nil,os.time(),
  function(profile)discovery.record(profile,cfg)end)
 return result.ok, result.reason
end
return GrantPet
]]></string>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
          </Item>
        </Item>
      </Item>
    </Item>
    <Item class="Folder" referent="40">
      <Properties>
        <string name="Name">StarterPlayerScripts</string>
        <BinaryString name="AttributesSerialize"></BinaryString>
        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
        <Color3 name="IconTint">
          <R>0</R>
          <G>0</G>
          <B>0</B>
        </Color3>
        <bool name="DefinesCapabilities">false</bool>
        <int64 name="SourceAssetId">-1</int64>
        <BinaryString name="Tags"></BinaryString>
      </Properties>
      <Item class="LocalScript" referent="41">
        <Properties>
          <string name="Name">KernelClient</string>
          <BinaryString name="AttributesSerialize"></BinaryString>
          <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
          <bool name="Disabled">false</bool>
          <ContentId name="LinkedSource">
            <null>
            </null>
          </ContentId>
          <token name="RunContext">0</token>
          <bool name="DefinesCapabilities">false</bool>
          <string name="Source"><![CDATA[-- RBLX Essentials Kernel - client boot. The only LocalScript in the ecosystem.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")

-- 3.0: packs, Config modules and the server's piece decisions arrive with the
-- first replication; read them only once it is complete.
if not game:IsLoaded() then
	game.Loaded:Wait()
end

local rsRoot = ReplicatedStorage:WaitForChild("RBLXEssentials")
local kernelFolder = rsRoot:WaitForChild("Kernel")
local Loader = require(kernelFolder:WaitForChild("Loader"))
local Version = require(kernelFolder:WaitForChild("Version"))
local Net = require(kernelFolder:WaitForChild("Net"))
local SFX = require(kernelFolder:WaitForChild("SFX"))
local Notify = require(kernelFolder:WaitForChild("Notify"))
local Signal = require(kernelFolder:WaitForChild("Signal"))
local Trove = require(kernelFolder:WaitForChild("Packages"):WaitForChild("Trove"))
local Schema = require(kernelFolder:WaitForChild("Schema"))
local Config = require(kernelFolder:WaitForChild("Config"))
local uiFolder = kernelFolder:WaitForChild("UI")
local MenuSystem = require(uiFolder:WaitForChild("MenuSystem"))
local Pieces = require(uiFolder:WaitForChild("Pieces"))

local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")

-- The server publishes its piece decisions before a player can join; this
-- bounded wait only covers a server that is still starting.
local encoded = rsRoot:GetAttribute(Pieces.WINNERS_ATTRIBUTE)
local waited = 0
while encoded == nil and waited < 10 do
	waited += task.wait(0.1)
	encoded = rsRoot:GetAttribute(Pieces.WINNERS_ATTRIBUTE)
end
-- nil: every piece is adopted as in 2.x
local decisions = Pieces.decodeDecisions(encoded, function(text)
	return HttpService:JSONDecode(text)
end)
if decisions == nil then
	warn("[RBLXE] UI: no piece decisions from the server; every piece is adopted as in 2.x")
end

-- RT-5: the server publishes which packs did not start there once its boot
-- is done; wait for it (bounded) so this client does not boot them.
local serverEncoded = rsRoot:GetAttribute(Loader.SERVER_REPORT_ATTRIBUTE)
local waitedReport = 0
while serverEncoded == nil and waitedReport < 30 do
	waitedReport += task.wait(0.1)
	serverEncoded = rsRoot:GetAttribute(Loader.SERVER_REPORT_ATTRIBUTE)
end
local serverReport = Loader.decodeServerReport(serverEncoded, function(text)
	return HttpService:JSONDecode(text)
end)
if serverReport == nil then
	warn("[RBLXE] no boot report from the server within 30s; this client boots every pack")
end

local pieces = Pieces.runtime(decisions, { wait = task.wait, warn = warn, defer = task.defer })
pieces:attach(playerGui)
local ui = MenuSystem.new(playerGui, { pieces = pieces })

local report = Loader.boot({
	side = "Client",
	sharedPacks = rsRoot:WaitForChild("Packs"),
	kernelApi = { Version = Version, Trove = Trove, Signal = Signal, Net = Net, SFX = SFX, Notify = Notify },
	ui = ui,
	configLib = Config,
	schemaLib = Schema,
	configRoots = { shared = rsRoot:FindFirstChild("Config") },
	serverReport = serverReport,
	extensions = rsRoot:FindFirstChild("Extensions"),
})

print(Loader.formatReport("Client", report))
for id, reason in pairs(report.failed) do
	warn(string.format("[RBLXE] %s failed: %s", id, reason))
end
for id, reason in pairs(report.skipped) do
	warn(string.format("[RBLXE] %s skipped: %s", id, reason))
end
for _, notice in ipairs(report.notices or {}) do
	warn("[RBLXE] " .. notice)
end
playerGui:SetAttribute("RBLXE_ClientBooted", true)
]]></string>
          <int64 name="SourceAssetId">-1</int64>
          <BinaryString name="Tags"></BinaryString>
        </Properties>
      </Item>
    </Item>
    <Item class="Folder" referent="42">
      <Properties>
        <string name="Name">StarterGui</string>
        <BinaryString name="AttributesSerialize"></BinaryString>
        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
        <Color3 name="IconTint">
          <R>0</R>
          <G>0</G>
          <B>0</B>
        </Color3>
        <bool name="DefinesCapabilities">false</bool>
        <int64 name="SourceAssetId">-1</int64>
        <BinaryString name="Tags"></BinaryString>
      </Properties>
      <Item class="Folder" referent="43">
        <Properties>
          <string name="Name">CodesSystem</string>
          <BinaryString name="AttributesSerialize"></BinaryString>
          <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
          <Color3 name="IconTint">
            <R>0</R>
            <G>0</G>
            <B>0</B>
          </Color3>
          <bool name="DefinesCapabilities">false</bool>
          <int64 name="SourceAssetId">-1</int64>
          <BinaryString name="Tags"></BinaryString>
        </Properties>
        <Item class="ScreenGui" referent="44">
          <Properties>
            <string name="Name">CodesSystemGUI</string>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <bool name="AutoLocalize">true</bool>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <bool name="ClipToDeviceSafeArea">true</bool>
            <int name="DisplayOrder">5</int>
            <bool name="Enabled">true</bool>
            <bool name="ResetOnSpawn">false</bool>
            <Ref name="RootLocalizationTable">null</Ref>
            <token name="SafeAreaCompatibility">1</token>
            <bool name="DefinesCapabilities">false</bool>
            <token name="ScreenInsets">2</token>
            <token name="SelectionBehaviorDown">0</token>
            <token name="SelectionBehaviorLeft">0</token>
            <token name="SelectionBehaviorRight">0</token>
            <token name="SelectionBehaviorUp">0</token>
            <bool name="SelectionGroup">false</bool>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
            <token name="ZIndexBehavior">1</token>
          </Properties>
          <Item class="CanvasGroup" referent="45">
            <Properties>
              <string name="Name">Codes</string>
              <bool name="Active">false</bool>
              <Vector2 name="AnchorPoint">
                <X>0.5</X>
                <Y>0.5</Y>
              </Vector2>
              <BinaryString name="AttributesSerialize">BQAAAAkAAABBbmltYXRpb24CBgAAAEJvdW5jZQkAAABFeGNsdXNpdmUDAQcAAABQYW5lbElkAgUAAABDb2Rlcw4AAABSQkxYRV9Db250cmFjdAYAAAAAAADwPwsAAABSQkxYRV9QaWVjZQIWAAAAQ29kZXNTeXN0ZW0uQ29kZXNQYW5lbA==</BinaryString>
              <bool name="AutoLocalize">true</bool>
              <token name="AutomaticSize">0</token>
              <Color3 name="BackgroundColor3">
                <R>0.21568629</R>
                <G>0.60784316</G>
                <B>1</B>
              </Color3>
              <float name="BackgroundTransparency">0</float>
              <Color3 name="BorderColor3">
                <R>0</R>
                <G>0</G>
                <B>0</B>
              </Color3>
              <token name="BorderMode">0</token>
              <int name="BorderSizePixel">0</int>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <bool name="ClipsDescendants">true</bool>
              <bool name="Draggable">false</bool>
              <Color3 name="GroupColor3">
                <R>1</R>
                <G>1</G>
                <B>1</B>
              </Color3>
              <float name="GroupTransparency">0</float>
              <bool name="Interactable">true</bool>
              <int name="LayoutOrder">0</int>
              <Ref name="NextSelectionDown">null</Ref>
              <Ref name="NextSelectionLeft">null</Ref>
              <Ref name="NextSelectionRight">null</Ref>
              <Ref name="NextSelectionUp">null</Ref>
              <UDim2 name="Position">
                <XS>0.5</XS>
                <XO>0</XO>
                <YS>0.5</YS>
                <YO>0</YO>
              </UDim2>
              <Ref name="RootLocalizationTable">null</Ref>
              <float name="Rotation">0</float>
              <bool name="DefinesCapabilities">false</bool>
              <bool name="Selectable">false</bool>
              <token name="SelectionBehaviorDown">0</token>
              <token name="SelectionBehaviorLeft">0</token>
              <token name="SelectionBehaviorRight">0</token>
              <token name="SelectionBehaviorUp">0</token>
              <bool name="SelectionGroup">false</bool>
              <Ref name="SelectionImageObject">null</Ref>
              <int name="SelectionOrder">0</int>
              <token name="Sink">0</token>
              <UDim2 name="Size">
                <XS>0.35</XS>
                <XO>0</XO>
                <YS>0.30942744</YS>
                <YO>0</YO>
              </UDim2>
              <token name="SizeConstraint">0</token>
              <int64 name="SourceAssetId">-1</int64>
              <BinaryString name="Tags">VUlfUGFuZWw=</BinaryString>
              <bool name="Visible">false</bool>
              <int name="ZIndex">1</int>
            </Properties>
            <Item class="UIAspectRatioConstraint" referent="46">
              <Properties>
                <string name="Name">UIAspectRatioConstraint</string>
                <float name="AspectRatio">2.008982</float>
                <token name="AspectType">0</token>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <token name="DominantAxis">0</token>
                <bool name="DefinesCapabilities">false</bool>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
            <Item class="Frame" referent="47">
              <Properties>
                <string name="Name">Container</string>
                <bool name="Active">false</bool>
                <Vector2 name="AnchorPoint">
                  <X>0</X>
                  <Y>1</Y>
                </Vector2>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <bool name="AutoLocalize">true</bool>
                <token name="AutomaticSize">0</token>
                <Color3 name="BackgroundColor3">
                  <R>1</R>
                  <G>1</G>
                  <B>1</B>
                </Color3>
                <float name="BackgroundTransparency">0</float>
                <Color3 name="BorderColor3">
                  <R>0</R>
                  <G>0</G>
                  <B>0</B>
                </Color3>
                <token name="BorderMode">0</token>
                <int name="BorderSizePixel">0</int>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="ClipsDescendants">false</bool>
                <bool name="Draggable">false</bool>
                <bool name="Interactable">true</bool>
                <int name="LayoutOrder">0</int>
                <Ref name="NextSelectionDown">null</Ref>
                <Ref name="NextSelectionLeft">null</Ref>
                <Ref name="NextSelectionRight">null</Ref>
                <Ref name="NextSelectionUp">null</Ref>
                <UDim2 name="Position">
                  <XS>0</XS>
                  <XO>0</XO>
                  <YS>1</YS>
                  <YO>0</YO>
                </UDim2>
                <Ref name="RootLocalizationTable">null</Ref>
                <float name="Rotation">0</float>
                <bool name="DefinesCapabilities">false</bool>
                <bool name="Selectable">false</bool>
                <token name="SelectionBehaviorDown">0</token>
                <token name="SelectionBehaviorLeft">0</token>
                <token name="SelectionBehaviorRight">0</token>
                <token name="SelectionBehaviorUp">0</token>
                <bool name="SelectionGroup">false</bool>
                <Ref name="SelectionImageObject">null</Ref>
                <int name="SelectionOrder">0</int>
                <token name="Sink">0</token>
                <UDim2 name="Size">
                  <XS>1</XS>
                  <XO>0</XO>
                  <YS>0.8264</YS>
                  <YO>0</YO>
                </UDim2>
                <token name="SizeConstraint">0</token>
                <int64 name="SourceAssetId">-1</int64>
                <token name="Style">0</token>
                <BinaryString name="Tags"></BinaryString>
                <bool name="Visible">true</bool>
                <int name="ZIndex">2</int>
              </Properties>
              <Item class="UIStroke" referent="48">
                <Properties>
                  <string name="Name">UIStroke</string>
                  <token name="ApplyStrokeMode">0</token>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <UDim name="BorderOffset">
                    <S>0</S>
                    <O>0</O>
                  </UDim>
                  <token name="BorderStrokePosition">0</token>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <Color3 name="Color">
                    <R>0.015686275</R>
                    <G>0.015686275</G>
                    <B>0.20784314</B>
                  </Color3>
                  <bool name="Enabled">true</bool>
                  <token name="LineJoinMode">0</token>
                  <bool name="DefinesCapabilities">false</bool>
                  <int64 name="SourceAssetId">-1</int64>
                  <token name="StrokeSizingMode">1</token>
                  <BinaryString name="Tags"></BinaryString>
                  <float name="Thickness">0.01448</float>
                  <float name="Transparency">0</float>
                  <int name="ZIndex">1</int>
                </Properties>
              </Item>
              <Item class="TextLabel" referent="49">
                <Properties>
                  <string name="Name">Text1</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">1</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/FredokaOne.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.4999999</XS>
                    <XO>0</XO>
                    <YS>0.056110114</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.85</XS>
                    <XO>0</XO>
                    <YS>0.19657218</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <string name="Text">Enter codes here to claim gifts!</string>
                  <Color3 name="TextColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">true</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">0</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">2</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">5</int>
                </Properties>
                <Item class="UIStroke" referent="50">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">2</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.1</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
              </Item>
              <Item class="TextLabel" referent="51">
                <Properties>
                  <string name="Name">Text2</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">1</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/FredokaOne.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.49999997</XS>
                    <XO>0</XO>
                    <YS>0.2428538</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.65</XS>
                    <XO>0</XO>
                    <YS>0.0884575</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <string name="Text">(Codes can be found in the map)</string>
                  <Color3 name="TextColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">true</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">0</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">2</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">7</int>
                </Properties>
                <Item class="UIStroke" referent="52">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">2</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.1</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
              </Item>
              <Item class="CanvasGroup" referent="53">
                <Properties>
                  <string name="Name">Code</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0.5</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>0.9019608</R>
                    <G>0.9019608</G>
                    <B>0.9019608</B>
                  </Color3>
                  <float name="BackgroundTransparency">0</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">true</bool>
                  <bool name="Draggable">false</bool>
                  <Color3 name="GroupColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="GroupTransparency">0</float>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <UDim2 name="Position">
                    <XS>0.49999997</XS>
                    <XO>0</XO>
                    <YS>0.5082264</YS>
                    <YO>0</YO>
                  </UDim2>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.9296482</XS>
                    <XO>0</XO>
                    <YS>0.14742918</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">4</int>
                </Properties>
                <Item class="ImageLabel" referent="54">
                  <Properties>
                    <string name="Name">Background</string>
                    <bool name="Active">false</bool>
                    <Vector2 name="AnchorPoint">
                      <X>0</X>
                      <Y>0</Y>
                    </Vector2>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <bool name="AutoLocalize">true</bool>
                    <token name="AutomaticSize">0</token>
                    <Color3 name="BackgroundColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <float name="BackgroundTransparency">1</float>
                    <Color3 name="BorderColor3">
                      <R>0</R>
                      <G>0</G>
                      <B>0</B>
                    </Color3>
                    <token name="BorderMode">0</token>
                    <int name="BorderSizePixel">0</int>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="ClipsDescendants">false</bool>
                    <bool name="Draggable">false</bool>
                    <Color3 name="ImageColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <Content name="ImageContent">
                      <uri>rbxassetid://5506029905</uri>
                    </Content>
                    <Vector2 name="ImageRectOffset">
                      <X>0</X>
                      <Y>0</Y>
                    </Vector2>
                    <Vector2 name="ImageRectSize">
                      <X>0</X>
                      <Y>0</Y>
                    </Vector2>
                    <float name="ImageTransparency">0.9</float>
                    <bool name="Interactable">true</bool>
                    <int name="LayoutOrder">0</int>
                    <Ref name="NextSelectionDown">null</Ref>
                    <Ref name="NextSelectionLeft">null</Ref>
                    <Ref name="NextSelectionRight">null</Ref>
                    <Ref name="NextSelectionUp">null</Ref>
                    <UDim2 name="Position">
                      <XS>0</XS>
                      <XO>0</XO>
                      <YS>0</YS>
                      <YO>0</YO>
                    </UDim2>
                    <token name="ResampleMode">0</token>
                    <Ref name="RootLocalizationTable">null</Ref>
                    <float name="Rotation">0</float>
                    <bool name="DefinesCapabilities">false</bool>
                    <token name="ScaleType">2</token>
                    <bool name="Selectable">false</bool>
                    <token name="SelectionBehaviorDown">0</token>
                    <token name="SelectionBehaviorLeft">0</token>
                    <token name="SelectionBehaviorRight">0</token>
                    <token name="SelectionBehaviorUp">0</token>
                    <bool name="SelectionGroup">false</bool>
                    <Ref name="SelectionImageObject">null</Ref>
                    <int name="SelectionOrder">0</int>
                    <token name="Sink">0</token>
                    <UDim2 name="Size">
                      <XS>1</XS>
                      <XO>0</XO>
                      <YS>1.0309278</YS>
                      <YO>0</YO>
                    </UDim2>
                    <token name="SizeConstraint">0</token>
                    <Rect2D name="SliceCenter">
                      <min>
                        <X>0</X>
                        <Y>0</Y>
                      </min>
                      <max>
                        <X>0</X>
                        <Y>0</Y>
                      </max>
                    </Rect2D>
                    <float name="SliceScale">1</float>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                    <UDim2 name="TileSize">
                      <XS>0.4</XS>
                      <XO>0</XO>
                      <YS>5</YS>
                      <YO>0</YO>
                    </UDim2>
                    <bool name="Visible">true</bool>
                    <int name="ZIndex">5</int>
                  </Properties>
                </Item>
                <Item class="UIStroke" referent="55">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">0</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.09824</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
                <Item class="UICorner" referent="56">
                  <Properties>
                    <string name="Name">UICorner</string>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BottomLeftRadius">
                      <S>0.15</S>
                      <O>0</O>
                    </UDim>
                    <UDim name="BottomRightRadius">
                      <S>0.15</S>
                      <O>0</O>
                    </UDim>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                    <UDim name="TopLeftRadius">
                      <S>0.15</S>
                      <O>0</O>
                    </UDim>
                    <UDim name="TopRightRadius">
                      <S>0.15</S>
                      <O>0</O>
                    </UDim>
                  </Properties>
                </Item>
                <Item class="TextBox" referent="57">
                  <Properties>
                    <string name="Name">TextBox</string>
                    <bool name="Active">true</bool>
                    <Vector2 name="AnchorPoint">
                      <X>0</X>
                      <Y>0</Y>
                    </Vector2>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <bool name="AutoLocalize">true</bool>
                    <token name="AutomaticSize">0</token>
                    <Color3 name="BackgroundColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <float name="BackgroundTransparency">1</float>
                    <Color3 name="BorderColor3">
                      <R>0</R>
                      <G>0</G>
                      <B>0</B>
                    </Color3>
                    <token name="BorderMode">0</token>
                    <int name="BorderSizePixel">0</int>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="ClearTextOnFocus">true</bool>
                    <bool name="ClipsDescendants">false</bool>
                    <bool name="Draggable">false</bool>
                    <Font name="FontFace">
                      <Family>
                        <url>rbxasset://fonts/families/FredokaOne.json</url>
                      </Family>
                      <Weight>400</Weight>
                      <Style>Normal</Style>
                    </Font>
                    <bool name="Interactable">true</bool>
                    <int name="LayoutOrder">0</int>
                    <float name="LineHeight">1</float>
                    <string name="LocalizationMatchIdentifier"></string>
                    <string name="LocalizationMatchedSourceText"></string>
                    <int name="MaxVisibleGraphemes">-1</int>
                    <bool name="MultiLine">false</bool>
                    <Ref name="NextSelectionDown">null</Ref>
                    <Ref name="NextSelectionLeft">null</Ref>
                    <Ref name="NextSelectionRight">null</Ref>
                    <Ref name="NextSelectionUp">null</Ref>
                    <string name="OpenTypeFeatures"></string>
                    <Color3 name="PlaceholderColor3">
                      <R>0.9607843</R>
                      <G>0.9607843</G>
                      <B>0.9607843</B>
                    </Color3>
                    <string name="PlaceholderText">Enter code here...</string>
                    <UDim2 name="Position">
                      <XS>0</XS>
                      <XO>0</XO>
                      <YS>0</YS>
                      <YO>0</YO>
                    </UDim2>
                    <bool name="RichText">false</bool>
                    <Ref name="RootLocalizationTable">null</Ref>
                    <float name="Rotation">0</float>
                    <bool name="DefinesCapabilities">false</bool>
                    <bool name="Selectable">true</bool>
                    <token name="SelectionBehaviorDown">0</token>
                    <token name="SelectionBehaviorLeft">0</token>
                    <token name="SelectionBehaviorRight">0</token>
                    <token name="SelectionBehaviorUp">0</token>
                    <bool name="SelectionGroup">false</bool>
                    <Ref name="SelectionImageObject">null</Ref>
                    <int name="SelectionOrder">0</int>
                    <bool name="ShowNativeInput">true</bool>
                    <token name="Sink">1</token>
                    <UDim2 name="Size">
                      <XS>1</XS>
                      <XO>0</XO>
                      <YS>1</YS>
                      <YO>0</YO>
                    </UDim2>
                    <token name="SizeConstraint">0</token>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags">VUlfVHlwZVNvdW5k</BinaryString>
                    <string name="Text"></string>
                    <Color3 name="TextColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <token name="TextDirection">0</token>
                    <bool name="TextEditable">true</bool>
                    <bool name="TextScaled">true</bool>
                    <float name="TextSize">14</float>
                    <Color3 name="TextStrokeColor3">
                      <R>0</R>
                      <G>0</G>
                      <B>0</B>
                    </Color3>
                    <float name="TextStrokeTransparency">1</float>
                    <float name="TextTransparency">0</float>
                    <token name="TextTruncate">0</token>
                    <bool name="TextWrapped">true</bool>
                    <token name="TextXAlignment">2</token>
                    <token name="TextYAlignment">1</token>
                    <bool name="Visible">true</bool>
                    <int name="ZIndex">6</int>
                  </Properties>
                  <Item class="UIStroke" referent="58">
                    <Properties>
                      <string name="Name">UIStroke</string>
                      <token name="ApplyStrokeMode">0</token>
                      <BinaryString name="AttributesSerialize"></BinaryString>
                      <UDim name="BorderOffset">
                        <S>0</S>
                        <O>0</O>
                      </UDim>
                      <token name="BorderStrokePosition">0</token>
                      <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                      <Color3 name="Color">
                        <R>0.015686275</R>
                        <G>0.015686275</G>
                        <B>0.20784314</B>
                      </Color3>
                      <bool name="Enabled">true</bool>
                      <token name="LineJoinMode">0</token>
                      <bool name="DefinesCapabilities">false</bool>
                      <int64 name="SourceAssetId">-1</int64>
                      <token name="StrokeSizingMode">1</token>
                      <BinaryString name="Tags"></BinaryString>
                      <float name="Thickness">0.1</float>
                      <float name="Transparency">0</float>
                      <int name="ZIndex">1</int>
                    </Properties>
                  </Item>
                </Item>
              </Item>
              <Item class="TextLabel" referent="59">
                <Properties>
                  <string name="Name">ErrorText</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">1</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/FredokaOne.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.49999997</XS>
                    <XO>0</XO>
                    <YS>0.89154226</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.65</XS>
                    <XO>0</XO>
                    <YS>0.08845752</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <string name="Text"></string>
                  <Color3 name="TextColor3">
                    <R>1</R>
                    <G>0.21568628</G>
                    <B>0.21568628</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">true</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">0</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">2</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">8</int>
                </Properties>
                <Item class="UIStroke" referent="60">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">2</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.1</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
              </Item>
              <Item class="TextLabel" referent="61">
                <Properties>
                  <string name="Name">SuccessText</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">1</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/FredokaOne.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.49999997</XS>
                    <XO>0</XO>
                    <YS>0.89154226</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.65</XS>
                    <XO>0</XO>
                    <YS>0.08845752</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <string name="Text"></string>
                  <Color3 name="TextColor3">
                    <R>0.4509804</R>
                    <G>1</G>
                    <B>0</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">true</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">0</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">2</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">6</int>
                </Properties>
                <Item class="UIStroke" referent="62">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">2</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.1</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
              </Item>
              <Item class="ImageLabel" referent="63">
                <Properties>
                  <string name="Name">Background</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0</X>
                    <Y>0</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Color3 name="ImageColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <Content name="ImageContent">
                    <uri>rbxassetid://71978616</uri>
                  </Content>
                  <Vector2 name="ImageRectOffset">
                    <X>0</X>
                    <Y>0</Y>
                  </Vector2>
                  <Vector2 name="ImageRectSize">
                    <X>0</X>
                    <Y>0</Y>
                  </Vector2>
                  <float name="ImageTransparency">0.9</float>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <UDim2 name="Position">
                    <XS>0</XS>
                    <XO>0</XO>
                    <YS>0</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="ResampleMode">0</token>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <token name="ScaleType">2</token>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>1</XS>
                    <XO>0</XO>
                    <YS>1</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <Rect2D name="SliceCenter">
                    <min>
                      <X>0</X>
                      <Y>0</Y>
                    </min>
                    <max>
                      <X>0</X>
                      <Y>0</Y>
                    </max>
                  </Rect2D>
                  <float name="SliceScale">1</float>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <UDim2 name="TileSize">
                    <XS>0.063</XS>
                    <XO>0</XO>
                    <YS>0.155</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">9</int>
                </Properties>
              </Item>
              <Item class="TextButton" referent="64">
                <Properties>
                  <string name="Name">Button</string>
                  <bool name="Active">true</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0.5</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize">AQAAAAYAAABBY3Rpb24CCQAAAENsYWltQ29kZQ==</BinaryString>
                  <bool name="AutoButtonColor">true</bool>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>0.4509804</R>
                    <G>1</G>
                    <B>0</B>
                  </Color3>
                  <float name="BackgroundTransparency">0</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/SourceSansPro.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <Ref name="HoverHapticEffect">null</Ref>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <bool name="Modal">false</bool>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.49999997</XS>
                    <XO>0</XO>
                    <YS>0.7637702</YS>
                    <YO>0</YO>
                  </UDim2>
                  <Ref name="PressHapticEffect">null</Ref>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">true</bool>
                  <bool name="Selected">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">1</token>
                  <UDim2 name="Size">
                    <XS>0.35</XS>
                    <XO>0</XO>
                    <YS>0.17691502</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <token name="Style">0</token>
                  <BinaryString name="Tags">VUlfQnV0dG9u</BinaryString>
                  <string name="Text">Label</string>
                  <Color3 name="TextColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">false</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">1</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">2</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">3</int>
                </Properties>
                <Item class="UICorner" referent="65">
                  <Properties>
                    <string name="Name">UICorner</string>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BottomLeftRadius">
                      <S>0.35</S>
                      <O>0</O>
                    </UDim>
                    <UDim name="BottomRightRadius">
                      <S>0.35</S>
                      <O>0</O>
                    </UDim>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                    <UDim name="TopLeftRadius">
                      <S>0.35</S>
                      <O>0</O>
                    </UDim>
                    <UDim name="TopRightRadius">
                      <S>0.35</S>
                      <O>0</O>
                    </UDim>
                  </Properties>
                </Item>
                <Item class="UIGradient" referent="66">
                  <Properties>
                    <string name="Name">UIGradient</string>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <ColorSequence name="Color">0 1 1 1 0 1 0.835294 0.835294 0.835294 0 </ColorSequence>
                    <bool name="Enabled">true</bool>
                    <Vector2 name="Offset">
                      <X>0</X>
                      <Y>0</Y>
                    </Vector2>
                    <float name="Rotation">90</float>
                    <bool name="DefinesCapabilities">false</bool>
                    <float name="Scale">1</float>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                    <token name="TileMode">0</token>
                    <NumberSequence name="Transparency">0 0 0 1 0 0 </NumberSequence>
                    <token name="Type">0</token>
                  </Properties>
                </Item>
                <Item class="Frame" referent="67">
                  <Properties>
                    <string name="Name">Frame</string>
                    <bool name="Active">false</bool>
                    <Vector2 name="AnchorPoint">
                      <X>0.5</X>
                      <Y>0.5</Y>
                    </Vector2>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <bool name="AutoLocalize">true</bool>
                    <token name="AutomaticSize">0</token>
                    <Color3 name="BackgroundColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <float name="BackgroundTransparency">1</float>
                    <Color3 name="BorderColor3">
                      <R>0</R>
                      <G>0</G>
                      <B>0</B>
                    </Color3>
                    <token name="BorderMode">0</token>
                    <int name="BorderSizePixel">0</int>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="ClipsDescendants">false</bool>
                    <bool name="Draggable">false</bool>
                    <bool name="Interactable">true</bool>
                    <int name="LayoutOrder">0</int>
                    <Ref name="NextSelectionDown">null</Ref>
                    <Ref name="NextSelectionLeft">null</Ref>
                    <Ref name="NextSelectionRight">null</Ref>
                    <Ref name="NextSelectionUp">null</Ref>
                    <UDim2 name="Position">
                      <XS>0.5</XS>
                      <XO>0</XO>
                      <YS>0.5</YS>
                      <YO>0</YO>
                    </UDim2>
                    <Ref name="RootLocalizationTable">null</Ref>
                    <float name="Rotation">0</float>
                    <bool name="DefinesCapabilities">false</bool>
                    <bool name="Selectable">false</bool>
                    <token name="SelectionBehaviorDown">0</token>
                    <token name="SelectionBehaviorLeft">0</token>
                    <token name="SelectionBehaviorRight">0</token>
                    <token name="SelectionBehaviorUp">0</token>
                    <bool name="SelectionGroup">false</bool>
                    <Ref name="SelectionImageObject">null</Ref>
                    <int name="SelectionOrder">0</int>
                    <token name="Sink">0</token>
                    <UDim2 name="Size">
                      <XS>0.9</XS>
                      <XO>0</XO>
                      <YS>1</YS>
                      <YO>0</YO>
                    </UDim2>
                    <token name="SizeConstraint">0</token>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="Style">0</token>
                    <BinaryString name="Tags"></BinaryString>
                    <bool name="Visible">true</bool>
                    <int name="ZIndex">5</int>
                  </Properties>
                  <Item class="ImageLabel" referent="68">
                    <Properties>
                      <string name="Name">Icon</string>
                      <bool name="Active">false</bool>
                      <Vector2 name="AnchorPoint">
                        <X>0</X>
                        <Y>0</Y>
                      </Vector2>
                      <BinaryString name="AttributesSerialize"></BinaryString>
                      <bool name="AutoLocalize">true</bool>
                      <token name="AutomaticSize">0</token>
                      <Color3 name="BackgroundColor3">
                        <R>1</R>
                        <G>1</G>
                        <B>1</B>
                      </Color3>
                      <float name="BackgroundTransparency">1</float>
                      <Color3 name="BorderColor3">
                        <R>0</R>
                        <G>0</G>
                        <B>0</B>
                      </Color3>
                      <token name="BorderMode">0</token>
                      <int name="BorderSizePixel">0</int>
                      <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                      <bool name="ClipsDescendants">false</bool>
                      <bool name="Draggable">false</bool>
                      <Color3 name="ImageColor3">
                        <R>1</R>
                        <G>1</G>
                        <B>1</B>
                      </Color3>
                      <Content name="ImageContent">
                        <uri>rbxassetid://74726235713322</uri>
                      </Content>
                      <Vector2 name="ImageRectOffset">
                        <X>0</X>
                        <Y>0</Y>
                      </Vector2>
                      <Vector2 name="ImageRectSize">
                        <X>0</X>
                        <Y>0</Y>
                      </Vector2>
                      <float name="ImageTransparency">0</float>
                      <bool name="Interactable">true</bool>
                      <int name="LayoutOrder">0</int>
                      <Ref name="NextSelectionDown">null</Ref>
                      <Ref name="NextSelectionLeft">null</Ref>
                      <Ref name="NextSelectionRight">null</Ref>
                      <Ref name="NextSelectionUp">null</Ref>
                      <UDim2 name="Position">
                        <XS>0</XS>
                        <XO>0</XO>
                        <YS>0</YS>
                        <YO>0</YO>
                      </UDim2>
                      <token name="ResampleMode">0</token>
                      <Ref name="RootLocalizationTable">null</Ref>
                      <float name="Rotation">0</float>
                      <bool name="DefinesCapabilities">false</bool>
                      <token name="ScaleType">3</token>
                      <bool name="Selectable">false</bool>
                      <token name="SelectionBehaviorDown">0</token>
                      <token name="SelectionBehaviorLeft">0</token>
                      <token name="SelectionBehaviorRight">0</token>
                      <token name="SelectionBehaviorUp">0</token>
                      <bool name="SelectionGroup">false</bool>
                      <Ref name="SelectionImageObject">null</Ref>
                      <int name="SelectionOrder">0</int>
                      <token name="Sink">0</token>
                      <UDim2 name="Size">
                        <XS>0.9</XS>
                        <XO>0</XO>
                        <YS>0.9</YS>
                        <YO>0</YO>
                      </UDim2>
                      <token name="SizeConstraint">0</token>
                      <Rect2D name="SliceCenter">
                        <min>
                          <X>0</X>
                          <Y>0</Y>
                        </min>
                        <max>
                          <X>0</X>
                          <Y>0</Y>
                        </max>
                      </Rect2D>
                      <float name="SliceScale">1</float>
                      <int64 name="SourceAssetId">-1</int64>
                      <BinaryString name="Tags"></BinaryString>
                      <UDim2 name="TileSize">
                        <XS>1</XS>
                        <XO>0</XO>
                        <YS>1</YS>
                        <YO>0</YO>
                      </UDim2>
                      <bool name="Visible">false</bool>
                      <int name="ZIndex">7</int>
                    </Properties>
                    <Item class="UIAspectRatioConstraint" referent="69">
                      <Properties>
                        <string name="Name">UIAspectRatioConstraint</string>
                        <float name="AspectRatio">1</float>
                        <token name="AspectType">0</token>
                        <BinaryString name="AttributesSerialize"></BinaryString>
                        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                        <token name="DominantAxis">0</token>
                        <bool name="DefinesCapabilities">false</bool>
                        <int64 name="SourceAssetId">-1</int64>
                        <BinaryString name="Tags"></BinaryString>
                      </Properties>
                    </Item>
                  </Item>
                  <Item class="UIListLayout" referent="70">
                    <Properties>
                      <string name="Name">UIListLayout</string>
                      <BinaryString name="AttributesSerialize"></BinaryString>
                      <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                      <token name="FillDirection">0</token>
                      <token name="HorizontalAlignment">0</token>
                      <token name="HorizontalFlex">0</token>
                      <token name="ItemLineAlignment">0</token>
                      <UDim name="Padding">
                        <S>0.04</S>
                        <O>0</O>
                      </UDim>
                      <bool name="DefinesCapabilities">false</bool>
                      <token name="SortOrder">2</token>
                      <int64 name="SourceAssetId">-1</int64>
                      <BinaryString name="Tags"></BinaryString>
                      <token name="VerticalAlignment">0</token>
                      <token name="VerticalFlex">0</token>
                      <bool name="Wraps">false</bool>
                    </Properties>
                  </Item>
                  <Item class="TextLabel" referent="71">
                    <Properties>
                      <string name="Name">Amount</string>
                      <bool name="Active">false</bool>
                      <Vector2 name="AnchorPoint">
                        <X>0</X>
                        <Y>0</Y>
                      </Vector2>
                      <BinaryString name="AttributesSerialize"></BinaryString>
                      <bool name="AutoLocalize">true</bool>
                      <token name="AutomaticSize">1</token>
                      <Color3 name="BackgroundColor3">
                        <R>1</R>
                        <G>1</G>
                        <B>1</B>
                      </Color3>
                      <float name="BackgroundTransparency">1</float>
                      <Color3 name="BorderColor3">
                        <R>0</R>
                        <G>0</G>
                        <B>0</B>
                      </Color3>
                      <token name="BorderMode">0</token>
                      <int name="BorderSizePixel">0</int>
                      <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                      <bool name="ClipsDescendants">false</bool>
                      <bool name="Draggable">false</bool>
                      <Font name="FontFace">
                        <Family>
                          <url>rbxasset://fonts/families/FredokaOne.json</url>
                        </Family>
                        <Weight>400</Weight>
                        <Style>Normal</Style>
                      </Font>
                      <bool name="Interactable">true</bool>
                      <int name="LayoutOrder">0</int>
                      <float name="LineHeight">1</float>
                      <string name="LocalizationMatchIdentifier"></string>
                      <string name="LocalizationMatchedSourceText"></string>
                      <int name="MaxVisibleGraphemes">-1</int>
                      <Ref name="NextSelectionDown">null</Ref>
                      <Ref name="NextSelectionLeft">null</Ref>
                      <Ref name="NextSelectionRight">null</Ref>
                      <Ref name="NextSelectionUp">null</Ref>
                      <string name="OpenTypeFeatures"></string>
                      <UDim2 name="Position">
                        <XS>0</XS>
                        <XO>0</XO>
                        <YS>0</YS>
                        <YO>0</YO>
                      </UDim2>
                      <bool name="RichText">false</bool>
                      <Ref name="RootLocalizationTable">null</Ref>
                      <float name="Rotation">0</float>
                      <bool name="DefinesCapabilities">false</bool>
                      <bool name="Selectable">false</bool>
                      <token name="SelectionBehaviorDown">0</token>
                      <token name="SelectionBehaviorLeft">0</token>
                      <token name="SelectionBehaviorRight">0</token>
                      <token name="SelectionBehaviorUp">0</token>
                      <bool name="SelectionGroup">false</bool>
                      <Ref name="SelectionImageObject">null</Ref>
                      <int name="SelectionOrder">0</int>
                      <token name="Sink">0</token>
                      <UDim2 name="Size">
                        <XS>0.3</XS>
                        <XO>0</XO>
                        <YS>1</YS>
                        <YO>0</YO>
                      </UDim2>
                      <token name="SizeConstraint">0</token>
                      <int64 name="SourceAssetId">-1</int64>
                      <BinaryString name="Tags"></BinaryString>
                      <string name="Text">REDEEM!</string>
                      <Color3 name="TextColor3">
                        <R>1</R>
                        <G>1</G>
                        <B>1</B>
                      </Color3>
                      <token name="TextDirection">0</token>
                      <bool name="TextScaled">true</bool>
                      <float name="TextSize">14</float>
                      <Color3 name="TextStrokeColor3">
                        <R>0</R>
                        <G>0</G>
                        <B>0</B>
                      </Color3>
                      <float name="TextStrokeTransparency">1</float>
                      <float name="TextTransparency">0</float>
                      <token name="TextTruncate">0</token>
                      <bool name="TextWrapped">true</bool>
                      <token name="TextXAlignment">2</token>
                      <token name="TextYAlignment">1</token>
                      <bool name="Visible">true</bool>
                      <int name="ZIndex">6</int>
                    </Properties>
                    <Item class="UIStroke" referent="72">
                      <Properties>
                        <string name="Name">UIStroke</string>
                        <token name="ApplyStrokeMode">0</token>
                        <BinaryString name="AttributesSerialize"></BinaryString>
                        <UDim name="BorderOffset">
                          <S>0</S>
                          <O>0</O>
                        </UDim>
                        <token name="BorderStrokePosition">0</token>
                        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                        <Color3 name="Color">
                          <R>0.015686275</R>
                          <G>0.015686275</G>
                          <B>0.20784314</B>
                        </Color3>
                        <bool name="Enabled">true</bool>
                        <token name="LineJoinMode">2</token>
                        <bool name="DefinesCapabilities">false</bool>
                        <int64 name="SourceAssetId">-1</int64>
                        <token name="StrokeSizingMode">1</token>
                        <BinaryString name="Tags"></BinaryString>
                        <float name="Thickness">0.08</float>
                        <float name="Transparency">0</float>
                        <int name="ZIndex">1</int>
                      </Properties>
                    </Item>
                  </Item>
                </Item>
                <Item class="UIStroke" referent="73">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">1</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">0</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.0614</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
              </Item>
            </Item>
            <Item class="Frame" referent="74">
              <Properties>
                <string name="Name">Top</string>
                <bool name="Active">false</bool>
                <Vector2 name="AnchorPoint">
                  <X>0</X>
                  <Y>0</Y>
                </Vector2>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <bool name="AutoLocalize">true</bool>
                <token name="AutomaticSize">0</token>
                <Color3 name="BackgroundColor3">
                  <R>1</R>
                  <G>1</G>
                  <B>1</B>
                </Color3>
                <float name="BackgroundTransparency">1</float>
                <Color3 name="BorderColor3">
                  <R>0</R>
                  <G>0</G>
                  <B>0</B>
                </Color3>
                <token name="BorderMode">0</token>
                <int name="BorderSizePixel">0</int>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="ClipsDescendants">false</bool>
                <bool name="Draggable">false</bool>
                <bool name="Interactable">true</bool>
                <int name="LayoutOrder">0</int>
                <Ref name="NextSelectionDown">null</Ref>
                <Ref name="NextSelectionLeft">null</Ref>
                <Ref name="NextSelectionRight">null</Ref>
                <Ref name="NextSelectionUp">null</Ref>
                <UDim2 name="Position">
                  <XS>0.00000007890635</XS>
                  <XO>0</XO>
                  <YS>0</YS>
                  <YO>0</YO>
                </UDim2>
                <Ref name="RootLocalizationTable">null</Ref>
                <float name="Rotation">0</float>
                <bool name="DefinesCapabilities">false</bool>
                <bool name="Selectable">false</bool>
                <token name="SelectionBehaviorDown">0</token>
                <token name="SelectionBehaviorLeft">0</token>
                <token name="SelectionBehaviorRight">0</token>
                <token name="SelectionBehaviorUp">0</token>
                <bool name="SelectionGroup">false</bool>
                <Ref name="SelectionImageObject">null</Ref>
                <int name="SelectionOrder">0</int>
                <token name="Sink">0</token>
                <UDim2 name="Size">
                  <XS>0.99999994</XS>
                  <XO>0</XO>
                  <YS>0.1736</YS>
                  <YO>0</YO>
                </UDim2>
                <token name="SizeConstraint">0</token>
                <int64 name="SourceAssetId">-1</int64>
                <token name="Style">0</token>
                <BinaryString name="Tags"></BinaryString>
                <bool name="Visible">true</bool>
                <int name="ZIndex">3</int>
              </Properties>
              <Item class="TextButton" referent="75">
                <Properties>
                  <string name="Name">CloseButton</string>
                  <bool name="Active">true</bool>
                  <Vector2 name="AnchorPoint">
                    <X>1</X>
                    <Y>0.5</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize">AQAAAAYAAABBY3Rpb24CCgAAAENsb3NlUGFuZWw=</BinaryString>
                  <bool name="AutoButtonColor">true</bool>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/FredokaOne.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <Ref name="HoverHapticEffect">null</Ref>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <bool name="Modal">false</bool>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.973</XS>
                    <XO>0</XO>
                    <YS>0.45</YS>
                    <YO>0</YO>
                  </UDim2>
                  <Ref name="PressHapticEffect">null</Ref>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">true</bool>
                  <bool name="Selected">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">1</token>
                  <UDim2 name="Size">
                    <XS>0.65</XS>
                    <XO>0</XO>
                    <YS>0.65</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <token name="Style">0</token>
                  <BinaryString name="Tags">VUlfQnV0dG9u</BinaryString>
                  <string name="Text">X</string>
                  <Color3 name="TextColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">true</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">0</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">2</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">5</int>
                </Properties>
                <Item class="UIAspectRatioConstraint" referent="76">
                  <Properties>
                    <string name="Name">UIAspectRatioConstraint</string>
                    <float name="AspectRatio">1</float>
                    <token name="AspectType">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <token name="DominantAxis">0</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                  </Properties>
                </Item>
              </Item>
              <Item class="TextLabel" referent="77">
                <Properties>
                  <string name="Name">Title</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0</X>
                    <Y>0.5</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Font name="FontFace">
                    <Family>
                      <url>rbxasset://fonts/families/FredokaOne.json</url>
                    </Family>
                    <Weight>400</Weight>
                    <Style>Normal</Style>
                  </Font>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <float name="LineHeight">1</float>
                  <string name="LocalizationMatchIdentifier"></string>
                  <string name="LocalizationMatchedSourceText"></string>
                  <int name="MaxVisibleGraphemes">-1</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <string name="OpenTypeFeatures"></string>
                  <UDim2 name="Position">
                    <XS>0.035</XS>
                    <XO>0</XO>
                    <YS>0.5</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="RichText">false</bool>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.5</XS>
                    <XO>0</XO>
                    <YS>0.65</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                  <string name="Text">CODES</string>
                  <Color3 name="TextColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <token name="TextDirection">0</token>
                  <bool name="TextScaled">true</bool>
                  <float name="TextSize">14</float>
                  <Color3 name="TextStrokeColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <float name="TextStrokeTransparency">1</float>
                  <float name="TextTransparency">0</float>
                  <token name="TextTruncate">0</token>
                  <bool name="TextWrapped">true</bool>
                  <token name="TextXAlignment">0</token>
                  <token name="TextYAlignment">1</token>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">4</int>
                </Properties>
                <Item class="UIStroke" referent="78">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.015686275</R>
                      <G>0.015686275</G>
                      <B>0.20784314</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">0</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.1</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
              </Item>
            </Item>
            <Item class="UIStroke" referent="79">
              <Properties>
                <string name="Name">UIStroke</string>
                <token name="ApplyStrokeMode">0</token>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <UDim name="BorderOffset">
                  <S>0</S>
                  <O>0</O>
                </UDim>
                <token name="BorderStrokePosition">0</token>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <Color3 name="Color">
                  <R>0.015686275</R>
                  <G>0.015686275</G>
                  <B>0.20784314</B>
                </Color3>
                <bool name="Enabled">true</bool>
                <token name="LineJoinMode">0</token>
                <bool name="DefinesCapabilities">false</bool>
                <int64 name="SourceAssetId">-1</int64>
                <token name="StrokeSizingMode">1</token>
                <BinaryString name="Tags"></BinaryString>
                <float name="Thickness">0.02693</float>
                <float name="Transparency">0</float>
                <int name="ZIndex">1</int>
              </Properties>
            </Item>
            <Item class="UICorner" referent="80">
              <Properties>
                <string name="Name">UICorner</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <UDim name="BottomLeftRadius">
                  <S>0.06</S>
                  <O>0</O>
                </UDim>
                <UDim name="BottomRightRadius">
                  <S>0.06</S>
                  <O>0</O>
                </UDim>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
                <UDim name="TopLeftRadius">
                  <S>0.06</S>
                  <O>0</O>
                </UDim>
                <UDim name="TopRightRadius">
                  <S>0.06</S>
                  <O>0</O>
                </UDim>
              </Properties>
            </Item>
          </Item>
          <Item class="Frame" referent="81">
            <Properties>
              <string name="Name">Interactables</string>
              <bool name="Active">false</bool>
              <Vector2 name="AnchorPoint">
                <X>0</X>
                <Y>0.5</Y>
              </Vector2>
              <BinaryString name="AttributesSerialize"></BinaryString>
              <bool name="AutoLocalize">true</bool>
              <token name="AutomaticSize">0</token>
              <Color3 name="BackgroundColor3">
                <R>0.6392157</R>
                <G>0.63529414</G>
                <B>0.64705884</B>
              </Color3>
              <float name="BackgroundTransparency">1</float>
              <Color3 name="BorderColor3">
                <R>0.105882354</R>
                <G>0.16470589</G>
                <B>0.20784314</B>
              </Color3>
              <token name="BorderMode">0</token>
              <int name="BorderSizePixel">0</int>
              <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
              <bool name="ClipsDescendants">false</bool>
              <bool name="Draggable">false</bool>
              <bool name="Interactable">true</bool>
              <int name="LayoutOrder">0</int>
              <Ref name="NextSelectionDown">null</Ref>
              <Ref name="NextSelectionLeft">null</Ref>
              <Ref name="NextSelectionRight">null</Ref>
              <Ref name="NextSelectionUp">null</Ref>
              <UDim2 name="Position">
                <XS>0.01</XS>
                <XO>0</XO>
                <YS>0.5</YS>
                <YO>0</YO>
              </UDim2>
              <Ref name="RootLocalizationTable">null</Ref>
              <float name="Rotation">0</float>
              <bool name="DefinesCapabilities">false</bool>
              <bool name="Selectable">false</bool>
              <token name="SelectionBehaviorDown">0</token>
              <token name="SelectionBehaviorLeft">0</token>
              <token name="SelectionBehaviorRight">0</token>
              <token name="SelectionBehaviorUp">0</token>
              <bool name="SelectionGroup">false</bool>
              <Ref name="SelectionImageObject">null</Ref>
              <int name="SelectionOrder">0</int>
              <token name="Sink">0</token>
              <UDim2 name="Size">
                <XS>0.2</XS>
                <XO>0</XO>
                <YS>0.38</YS>
                <YO>0</YO>
              </UDim2>
              <token name="SizeConstraint">0</token>
              <int64 name="SourceAssetId">-1</int64>
              <token name="Style">0</token>
              <BinaryString name="Tags"></BinaryString>
              <bool name="Visible">true</bool>
              <int name="ZIndex">1</int>
            </Properties>
            <Item class="UIAspectRatioConstraint" referent="82">
              <Properties>
                <string name="Name">UIAspectRatioConstraint</string>
                <float name="AspectRatio">0.93414634</float>
                <token name="AspectType">0</token>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <token name="DominantAxis">1</token>
                <bool name="DefinesCapabilities">false</bool>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
              </Properties>
            </Item>
            <Item class="UIGridLayout" referent="83">
              <Properties>
                <string name="Name">UIGridLayout</string>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <UDim2 name="CellPadding">
                  <XS>0.03</XS>
                  <XO>0</XO>
                  <YS>0.01</YS>
                  <YO>0</YO>
                </UDim2>
                <UDim2 name="CellSize">
                  <XS>0.24</XS>
                  <XO>0</XO>
                  <YS>0.24</YS>
                  <YO>0</YO>
                </UDim2>
                <token name="FillDirection">1</token>
                <int name="FillDirectionMaxCells">0</int>
                <token name="HorizontalAlignment">1</token>
                <bool name="DefinesCapabilities">false</bool>
                <token name="SortOrder">2</token>
                <int64 name="SourceAssetId">-1</int64>
                <token name="StartCorner">0</token>
                <BinaryString name="Tags"></BinaryString>
                <token name="VerticalAlignment">1</token>
              </Properties>
            </Item>
            <Item class="TextButton" referent="84">
              <Properties>
                <string name="Name">Codes</string>
                <bool name="Active">true</bool>
                <Vector2 name="AnchorPoint">
                  <X>0</X>
                  <Y>0</Y>
                </Vector2>
                <BinaryString name="AttributesSerialize">BgAAAAYAAABBY3Rpb24CCwAAAFRvZ2dsZVBhbmVsDgAAAFJCTFhFX0NvbnRyYWN0BgAAAAAAAPA/CwAAAFJCTFhFX1BpZWNlAhcAAABDb2Rlc1N5c3RlbS5Db2Rlc0J1dHRvbgsAAABTaG93VG9vbHRpcAMBCwAAAFRhcmdldFBhbmVsAgUAAABDb2RlcwsAAABUb29sdGlwVGV4dAIFAAAAQ29kZXM=</BinaryString>
                <bool name="AutoButtonColor">true</bool>
                <bool name="AutoLocalize">true</bool>
                <token name="AutomaticSize">0</token>
                <Color3 name="BackgroundColor3">
                  <R>1</R>
                  <G>1</G>
                  <B>1</B>
                </Color3>
                <float name="BackgroundTransparency">1</float>
                <Color3 name="BorderColor3">
                  <R>0</R>
                  <G>0</G>
                  <B>0</B>
                </Color3>
                <token name="BorderMode">0</token>
                <int name="BorderSizePixel">0</int>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="ClipsDescendants">false</bool>
                <bool name="Draggable">false</bool>
                <Font name="FontFace">
                  <Family>
                    <url>rbxasset://fonts/families/SourceSansPro.json</url>
                  </Family>
                  <Weight>400</Weight>
                  <Style>Normal</Style>
                  <CachedFaceId>
                    <url>rbxasset://fonts/SourceSansPro-Regular.ttf</url>
                  </CachedFaceId>
                </Font>
                <Ref name="HoverHapticEffect">null</Ref>
                <bool name="Interactable">true</bool>
                <int name="LayoutOrder">6</int>
                <float name="LineHeight">1</float>
                <string name="LocalizationMatchIdentifier"></string>
                <string name="LocalizationMatchedSourceText"></string>
                <int name="MaxVisibleGraphemes">-1</int>
                <bool name="Modal">false</bool>
                <Ref name="NextSelectionDown">null</Ref>
                <Ref name="NextSelectionLeft">null</Ref>
                <Ref name="NextSelectionRight">null</Ref>
                <Ref name="NextSelectionUp">null</Ref>
                <string name="OpenTypeFeatures"></string>
                <UDim2 name="Position">
                  <XS>0</XS>
                  <XO>0</XO>
                  <YS>0</YS>
                  <YO>0</YO>
                </UDim2>
                <Ref name="PressHapticEffect">null</Ref>
                <bool name="RichText">false</bool>
                <Ref name="RootLocalizationTable">null</Ref>
                <float name="Rotation">0</float>
                <bool name="DefinesCapabilities">false</bool>
                <bool name="Selectable">true</bool>
                <bool name="Selected">false</bool>
                <token name="SelectionBehaviorDown">0</token>
                <token name="SelectionBehaviorLeft">0</token>
                <token name="SelectionBehaviorRight">0</token>
                <token name="SelectionBehaviorUp">0</token>
                <bool name="SelectionGroup">false</bool>
                <Ref name="SelectionImageObject">null</Ref>
                <int name="SelectionOrder">0</int>
                <token name="Sink">1</token>
                <UDim2 name="Size">
                  <XS>0.25</XS>
                  <XO>0</XO>
                  <YS>0.25</YS>
                  <YO>0</YO>
                </UDim2>
                <token name="SizeConstraint">0</token>
                <int64 name="SourceAssetId">-1</int64>
                <token name="Style">0</token>
                <BinaryString name="Tags">VUlfQnV0dG9uAFVJX0ludGVyYWN0YWJsZQ==</BinaryString>
                <string name="Text">Button</string>
                <Color3 name="TextColor3">
                  <R>0</R>
                  <G>0</G>
                  <B>0</B>
                </Color3>
                <token name="TextDirection">0</token>
                <bool name="TextScaled">false</bool>
                <float name="TextSize">14</float>
                <Color3 name="TextStrokeColor3">
                  <R>0</R>
                  <G>0</G>
                  <B>0</B>
                </Color3>
                <float name="TextStrokeTransparency">1</float>
                <float name="TextTransparency">1</float>
                <token name="TextTruncate">0</token>
                <bool name="TextWrapped">false</bool>
                <token name="TextXAlignment">2</token>
                <token name="TextYAlignment">1</token>
                <bool name="Visible">true</bool>
                <int name="ZIndex">1</int>
              </Properties>
              <Item class="Frame" referent="85">
                <Properties>
                  <string name="Name">Notification</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>1</X>
                    <Y>0</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize">AQAAAAsAAABGcmFtZVN0cm9rZQYAAAAAAAAuQA==</BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>0.9607844</R>
                    <G>0.023529414</G>
                    <B>0.039215688</B>
                  </Color3>
                  <float name="BackgroundTransparency">0</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">1</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <UDim2 name="Position">
                    <XS>1.05</XS>
                    <XO>0</XO>
                    <YS>-0.05</YS>
                    <YO>0</YO>
                  </UDim2>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>0.3</XS>
                    <XO>0</XO>
                    <YS>0.3</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">2</token>
                  <int64 name="SourceAssetId">-1</int64>
                  <token name="Style">0</token>
                  <BinaryString name="Tags">RnJhbWVTdHJva2U=</BinaryString>
                  <bool name="Visible">false</bool>
                  <int name="ZIndex">2</int>
                </Properties>
                <Item class="UIStroke" referent="86">
                  <Properties>
                    <string name="Name">UIStroke</string>
                    <token name="ApplyStrokeMode">0</token>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BorderOffset">
                      <S>0</S>
                      <O>0</O>
                    </UDim>
                    <token name="BorderStrokePosition">0</token>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <Color3 name="Color">
                      <R>0.4784314</R>
                      <G>0</G>
                      <B>0.13725491</B>
                    </Color3>
                    <bool name="Enabled">true</bool>
                    <token name="LineJoinMode">0</token>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <token name="StrokeSizingMode">1</token>
                    <BinaryString name="Tags"></BinaryString>
                    <float name="Thickness">0.15</float>
                    <float name="Transparency">0</float>
                    <int name="ZIndex">1</int>
                  </Properties>
                </Item>
                <Item class="UICorner" referent="87">
                  <Properties>
                    <string name="Name">UICorner</string>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <UDim name="BottomLeftRadius">
                      <S>1</S>
                      <O>0</O>
                    </UDim>
                    <UDim name="BottomRightRadius">
                      <S>1</S>
                      <O>0</O>
                    </UDim>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="DefinesCapabilities">false</bool>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                    <UDim name="TopLeftRadius">
                      <S>1</S>
                      <O>0</O>
                    </UDim>
                    <UDim name="TopRightRadius">
                      <S>1</S>
                      <O>0</O>
                    </UDim>
                  </Properties>
                </Item>
                <Item class="TextLabel" referent="88">
                  <Properties>
                    <string name="Name">TextLabel</string>
                    <bool name="Active">false</bool>
                    <Vector2 name="AnchorPoint">
                      <X>0.5</X>
                      <Y>0.5</Y>
                    </Vector2>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <bool name="AutoLocalize">true</bool>
                    <token name="AutomaticSize">0</token>
                    <Color3 name="BackgroundColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <float name="BackgroundTransparency">1</float>
                    <Color3 name="BorderColor3">
                      <R>0</R>
                      <G>0</G>
                      <B>0</B>
                    </Color3>
                    <token name="BorderMode">0</token>
                    <int name="BorderSizePixel">0</int>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="ClipsDescendants">false</bool>
                    <bool name="Draggable">false</bool>
                    <Font name="FontFace">
                      <Family>
                        <url>rbxasset://fonts/families/FredokaOne.json</url>
                      </Family>
                      <Weight>400</Weight>
                      <Style>Normal</Style>
                    </Font>
                    <bool name="Interactable">true</bool>
                    <int name="LayoutOrder">0</int>
                    <float name="LineHeight">1</float>
                    <string name="LocalizationMatchIdentifier"></string>
                    <string name="LocalizationMatchedSourceText"></string>
                    <int name="MaxVisibleGraphemes">-1</int>
                    <Ref name="NextSelectionDown">null</Ref>
                    <Ref name="NextSelectionLeft">null</Ref>
                    <Ref name="NextSelectionRight">null</Ref>
                    <Ref name="NextSelectionUp">null</Ref>
                    <string name="OpenTypeFeatures"></string>
                    <UDim2 name="Position">
                      <XS>0.5</XS>
                      <XO>0</XO>
                      <YS>0.48</YS>
                      <YO>0</YO>
                    </UDim2>
                    <bool name="RichText">false</bool>
                    <Ref name="RootLocalizationTable">null</Ref>
                    <float name="Rotation">0</float>
                    <bool name="DefinesCapabilities">false</bool>
                    <bool name="Selectable">false</bool>
                    <token name="SelectionBehaviorDown">0</token>
                    <token name="SelectionBehaviorLeft">0</token>
                    <token name="SelectionBehaviorRight">0</token>
                    <token name="SelectionBehaviorUp">0</token>
                    <bool name="SelectionGroup">false</bool>
                    <Ref name="SelectionImageObject">null</Ref>
                    <int name="SelectionOrder">0</int>
                    <token name="Sink">0</token>
                    <UDim2 name="Size">
                      <XS>0.95</XS>
                      <XO>0</XO>
                      <YS>0.95</YS>
                      <YO>0</YO>
                    </UDim2>
                    <token name="SizeConstraint">0</token>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                    <string name="Text">!</string>
                    <Color3 name="TextColor3">
                      <R>1</R>
                      <G>1</G>
                      <B>1</B>
                    </Color3>
                    <token name="TextDirection">0</token>
                    <bool name="TextScaled">true</bool>
                    <float name="TextSize">14</float>
                    <Color3 name="TextStrokeColor3">
                      <R>0</R>
                      <G>0</G>
                      <B>0</B>
                    </Color3>
                    <float name="TextStrokeTransparency">1</float>
                    <float name="TextTransparency">0</float>
                    <token name="TextTruncate">0</token>
                    <bool name="TextWrapped">true</bool>
                    <token name="TextXAlignment">2</token>
                    <token name="TextYAlignment">1</token>
                    <bool name="Visible">true</bool>
                    <int name="ZIndex">2</int>
                  </Properties>
                </Item>
              </Item>
              <Item class="UIAspectRatioConstraint" referent="89">
                <Properties>
                  <string name="Name">UIAspectRatioConstraint</string>
                  <float name="AspectRatio">1</float>
                  <token name="AspectType">0</token>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <token name="DominantAxis">0</token>
                  <bool name="DefinesCapabilities">false</bool>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags"></BinaryString>
                </Properties>
              </Item>
              <Item class="ImageLabel" referent="90">
                <Properties>
                  <string name="Name">Icon</string>
                  <bool name="Active">false</bool>
                  <Vector2 name="AnchorPoint">
                    <X>0.5</X>
                    <Y>0.5</Y>
                  </Vector2>
                  <BinaryString name="AttributesSerialize"></BinaryString>
                  <bool name="AutoLocalize">true</bool>
                  <token name="AutomaticSize">0</token>
                  <Color3 name="BackgroundColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <float name="BackgroundTransparency">1</float>
                  <Color3 name="BorderColor3">
                    <R>0</R>
                    <G>0</G>
                    <B>0</B>
                  </Color3>
                  <token name="BorderMode">0</token>
                  <int name="BorderSizePixel">0</int>
                  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                  <bool name="ClipsDescendants">false</bool>
                  <bool name="Draggable">false</bool>
                  <Color3 name="ImageColor3">
                    <R>1</R>
                    <G>1</G>
                    <B>1</B>
                  </Color3>
                  <Content name="ImageContent">
                    <uri>rbxassetid://137125277479961</uri>
                  </Content>
                  <Vector2 name="ImageRectOffset">
                    <X>0</X>
                    <Y>0</Y>
                  </Vector2>
                  <Vector2 name="ImageRectSize">
                    <X>0</X>
                    <Y>0</Y>
                  </Vector2>
                  <float name="ImageTransparency">0</float>
                  <bool name="Interactable">true</bool>
                  <int name="LayoutOrder">0</int>
                  <Ref name="NextSelectionDown">null</Ref>
                  <Ref name="NextSelectionLeft">null</Ref>
                  <Ref name="NextSelectionRight">null</Ref>
                  <Ref name="NextSelectionUp">null</Ref>
                  <UDim2 name="Position">
                    <XS>0.5</XS>
                    <XO>0</XO>
                    <YS>0.5</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="ResampleMode">0</token>
                  <Ref name="RootLocalizationTable">null</Ref>
                  <float name="Rotation">0</float>
                  <bool name="DefinesCapabilities">false</bool>
                  <token name="ScaleType">3</token>
                  <bool name="Selectable">false</bool>
                  <token name="SelectionBehaviorDown">0</token>
                  <token name="SelectionBehaviorLeft">0</token>
                  <token name="SelectionBehaviorRight">0</token>
                  <token name="SelectionBehaviorUp">0</token>
                  <bool name="SelectionGroup">false</bool>
                  <Ref name="SelectionImageObject">null</Ref>
                  <int name="SelectionOrder">0</int>
                  <token name="Sink">0</token>
                  <UDim2 name="Size">
                    <XS>1</XS>
                    <XO>0</XO>
                    <YS>1</YS>
                    <YO>0</YO>
                  </UDim2>
                  <token name="SizeConstraint">0</token>
                  <Rect2D name="SliceCenter">
                    <min>
                      <X>0</X>
                      <Y>0</Y>
                    </min>
                    <max>
                      <X>0</X>
                      <Y>0</Y>
                    </max>
                  </Rect2D>
                  <float name="SliceScale">1</float>
                  <int64 name="SourceAssetId">-1</int64>
                  <BinaryString name="Tags">R2VuZXJhbFVJS2l0X0J1dHRvbkljb24=</BinaryString>
                  <UDim2 name="TileSize">
                    <XS>1</XS>
                    <XO>0</XO>
                    <YS>1</YS>
                    <YO>0</YO>
                  </UDim2>
                  <bool name="Visible">true</bool>
                  <int name="ZIndex">1</int>
                </Properties>
              </Item>
            </Item>
          </Item>
        </Item>
      </Item>
    </Item>
    <Item class="Folder" referent="91">
      <Properties>
        <string name="Name">RBLXE_Major3</string>
        <BinaryString name="AttributesSerialize">AQAAAAoAAABSQkxYRV9Ob3RlAnIAAABSQkxYIEVzc2VudGlhbHMgMy54OiBpbnN0YWxsIGl0IHdpdGggdGhlIFJCTFggRXNzZW50aWFscyAzLjAgcGx1Z2luIG9yIFRvb2xzLiBBIDIueCBwbHVnaW4gcmVmdXNlcyB0aGlzIGNvbnRhaW5lci4=</BinaryString>
        <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
        <Color3 name="IconTint">
          <R>0</R>
          <G>0</G>
          <B>0</B>
        </Color3>
        <bool name="DefinesCapabilities">false</bool>
        <int64 name="SourceAssetId">-1</int64>
        <BinaryString name="Tags"></BinaryString>
      </Properties>
    </Item>
  </Item>
</roblox>