Mudanças entre as edições de "Módulo:Gb"

De Wiki Gla
Ir para navegação Ir para pesquisar
Linha 1: Linha 1:
--[[
Module:CalculadoraTier


Calculadora de recursos (berry e fragmentos) para evoluir um personagem por Tiers e Estrelas.
-- Módulo:CalculadoraTier
 
-- Calculadora de custos para evolução de tiers/estrelas
✔ Como usar na wiki (Wikitext):
-- Compatível com MediaWiki + Scribunto
  {{#invoke:CalculadoraTier|calc
    |deTier=bronze
    |deEstrelas=2
    |paraTier=diamante
    |paraEstrelas=4
  }}
 
Parâmetros opcionais:
  |moeda=berry            -- nome exibido para a moeda (default: "berry")
  |frag=fragmentos        -- nome exibido para os fragmentos (default: "fragmentos")
  |mostrarPassos=sim      -- mostra o passo a passo (sim/não)
 
📌 Importante: SUBSTITUA os valores de custo abaixo (tabela DATA.custos) pelos valores reais do seu jogo.
A estrutura já está pronta para você só trocar os números.
]]


local p = {}
local p = {}


-- =======================
-- \u26a0\ufe0f EDITÁVEL: Tabela de custos padrão
-- CONFIGURAÇÃO DE DADOS
-- Defina aqui os custos para cada estrela alcançada (1..5) em cada tier,
-- =======================
-- e o custo para subir de um tier para o próximo.
-- Nome dos tiers em ordem de progressão.
local COSTS = {
-- Use chaves em minúsculas, sem acento, para evitar problemas.
   tiers = {"bronze", "prata", "ouro", "diamante"},
local DATA = {
   tiers = { "bronze", "prata", "gold", "diamante" },


   -- Custo para subir 1 estrela DENTRO do mesmo tier.
   -- Custo para alcançar CADA estrela (ex.: STAR[1] = custo para ir de 0 -> 1 estrela)
  -- Para cada tier (chave), defina uma lista de 5 valores que representam o custo para ir:
   star = {
  --   0 ➜ 1, 1 ➜ 2, 2 ➜ 3, 3 ➜ 4, 4 ➜ 5 estrelas.
  -- Exemplo abaixo usa NÚMEROS DE PLACEHOLDER! Troque pelos números reais.
   custos = {
     bronze = {
     bronze = {
       estrelas = {
       [1] = { berry = 100, frag = 1 },
        { berry = 100, frag = 2 }, -- 0->1
      [2] = { berry = 200, frag = 1 },
        { berry = 150, frag = 3 },  -- 1->2
      [3] = { berry = 300, frag = 2 },
        { berry = 200, frag = 4 }, -- 2->3
      [4] = { berry = 450, frag = 2 },
        { berry = 250, frag = 5 }, -- 3->4
       [5] = { berry = 600, frag = 3 },
        { berry = 300, frag = 6 }, -- 4->5
       },
      -- Custo para PROMOVER do tier atual para o próximo (depois de 5★).
      promover = { berry = 500, frag = 10 },
     },
     },
     prata = {
     prata = {
       estrelas = {
       [1] = { berry = 200, frag = 1 },
        { berry = 400, frag = 4 },
      [2] = { berry = 300, frag = 2 },
        { berry = 500, frag = 5 },
      [3] = { berry = 450, frag = 2 },
        { berry = 600, frag = 6 },
      [4] = { berry = 650, frag = 3 },
        { berry = 700, frag = 7 },
       [5] = { berry = 900, frag = 4 },
        { berry = 800, frag = 8 },
       },
      promover = { berry = 1200, frag = 16 },
     },
     },
 
     ouro = {
     gold = {
       [1] = { berry = 400, frag = 2 },
       estrelas = {
      [2] = { berry = 600, frag = 2 },
        { berry = 900, frag = 9 },
      [3] = { berry = 900, frag = 3 },
        { berry = 1100, frag = 10 },
      [4] = { berry = 1300, frag = 4 },
        { berry = 1300, frag = 11 },
       [5] = { berry = 1800, frag = 5 },
        { berry = 1500, frag = 12 },
        { berry = 1700, frag = 13 },
      },
       promover = { berry = 2500, frag = 24 },
     },
     },
     diamante = {
     diamante = {
       estrelas = {
       [1] = { berry = 800, frag = 3 },
        { berry = 2000, frag = 15 },
      [2] = { berry = 1200, frag = 4 },
        { berry = 2300, frag = 16 },
      [3] = { berry = 1700, frag = 5 },
        { berry = 2600, frag = 17 },
      [4] = { berry = 2300, frag = 6 },
        { berry = 2900, frag = 18 },
      [5] = { berry = 3000, frag = 8 },
        { berry = 3200, frag = 19 },
      },
      -- Se diamante for o último tier, manter promover = nil ou {}.
      promover = nil,
     },
     },
  },
  -- Custo de mudar de tier (ex.: BRONZE -> PRATA). Não altera estrelas (reinicia para 0).
  tierUp = {
    bronze  = { berry = 2000, frag = 5 },  -- custo para BRONZE -> PRATA
    prata    = { berry = 6000, frag = 10 }, -- custo para PRATA -> OURO
    ouro    = { berry = 15000, frag = 20 },-- custo para OURO -> DIAMANTE
    diamante = nil,                          -- não há próximo tier
   },
   },
}
}


-- =======================
-- Normaliza texto (case-insensitive, acentos ignorados simples)
-- FUNÇÕES AUXILIARES
-- =======================
 
local function norm(s)
local function norm(s)
   if type(s) ~= "string" then return s end
   if type(s) ~= 'string' then return nil end
   s = s:lower()
   s = mw.ustring.lower(s)
   s = s:gsub("[áàâã]","a"):gsub("[éê]","e"):gsub("[í]","i"):gsub("[óôõ]","o"):gsub("[ú]","u"):gsub("ç","c")
   s = s:gsub("á", "a"):gsub("â", "a"):gsub("ã", "a")
      :gsub("é", "e"):gsub("ê", "e")
      :gsub("í", "i")
      :gsub("ó", "o"):gsub("ô", "o")
      :gsub("ú", "u")
   return s
   return s
end
end


local function findTierIndex(tier)
local function tierIndex(name)
   local t = norm(tier)
   local n = norm(name)
   for i, name in ipairs(DATA.tiers) do
   for i, t in ipairs(COSTS.tiers) do
     if norm(name) == t then return i end
     if n == t then return i end
   end
   end
   return nil
   return nil
end
end


local function validateInputs(args)
local function getTierName(idx)
   local deTier = args.deTier or args.de or args.fromTier
   return COSTS.tiers[idx]
  local paraTier = args.paraTier or args.para or args.toTier
end
  local deEstrelas = tonumber(args.deEstrelas or args.estrelas or args.fromStars) or 0
  local paraEstrelas = tonumber(args.paraEstrelas or args.toStars) or 0


  if not deTier or not paraTier then
-- Soma custos (protege nil)
    return nil, "Informe os parâmetros deTier e paraTier."
local function addCost(a, b)
  end
   a = a or { berry = 0, frag = 0 }
 
   b = b or { berry = 0, frag = 0 }
  local deIdx = findTierIndex(deTier)
   return { berry = (a.berry or 0) + (b.berry or 0), frag = (a.frag or 0) + (b.frag or 0) }
   local paraIdx = findTierIndex(paraTier)
end
   if not deIdx then return nil, "Tier inicial inválido: " .. tostring(deTier) end
   if not paraIdx then return nil, "Tier de destino inválido: " .. tostring(paraTier) end
 
  if deEstrelas < 0 or deEstrelas > 5 then
    return nil, "deEstrelas deve estar entre 0 e 5."
  end
  if paraEstrelas < 0 or paraEstrelas > 5 then
    return nil, "paraEstrelas deve estar entre 0 e 5."
  end


  -- Também não permitir alvo "antes" da origem.
-- Obtém custo para subir 1 estrela dentro do tier
  if paraIdx < deIdx or (paraIdx == deIdx and paraEstrelas < deEstrelas) then
local function starCost(tierName, starTo)
    return nil, "O destino deve ser maior que a origem."
   local t = COSTS.star[tierName]
  end
  return t and t[starTo] or { berry = 0, frag = 0 }
 
   return {
    deIdx = deIdx,
    paraIdx = paraIdx,
    deTier = DATA.tiers[deIdx],
    paraTier = DATA.tiers[paraIdx],
    deEstrelas = deEstrelas,
    paraEstrelas = paraEstrelas,
    moeda = args.moeda or "berry",
    frag = args.frag or "fragmentos",
    mostrarPassos = norm(tostring(args.mostrarPassos or "nao")) == "sim",
  }
end
end


-- Soma custos de estrelas dentro de um tier, do nível deEstrelas até (exclusivo) alvoEstrelas.
-- Obtém custo para subir de tier
local function somarEstrelas(tierKey, deEstrelas, alvoEstrelas)
local function tierUpCost(tierName)
   local cust = DATA.custos[tierKey]
   local c = COSTS.tierUp[tierName]
   local total = { berry = 0, frag = 0 }
   return c or { berry = 0, frag = 0 }
  local passos = {}
  if not cust or not cust.estrelas then return total, passos end
  for s = deEstrelas, alvoEstrelas - 1 do
    local step = cust.estrelas[s + 1] -- +1 porque tabela começa no 1
    if step then
      total.berry = total.berry + (step.berry or 0)
      total.frag  = total.frag  + (step.frag or 0)
      table.insert(passos, { tipo = "estrela", from = s, to = s + 1, tier = tierKey, berry = step.berry or 0, frag = step.frag or 0 })
    end
  end
  return total, passos
end
end


-- =======================
-- Caminho de evolução do estado (tierIdx, star) até (tierIdx2, star2)
-- LÓGICA PRINCIPAL
local function computePath(ti, si, tf, sf)
-- =======================
   local steps = {}
 
local function calcular(args)
  local ok, errOrCfg = pcall(validateInputs, args)
  if not ok then return nil, errOrCfg end
  if not errOrCfg then return nil, "Parâmetros inválidos." end
  if type(errOrCfg) == "string" then return nil, errOrCfg end
   local cfg = errOrCfg
 
   local total = { berry = 0, frag = 0 }
   local total = { berry = 0, frag = 0 }
  local passos = {}


   for idx = cfg.deIdx, cfg.paraIdx do
   local iTier, iStar = ti, si
    local tierKey = DATA.tiers[idx]


    -- Definir início e fim das estrelas para este tier
  local function push(step)
     local startStar = (idx == cfg.deIdx) and cfg.deEstrelas or 0
     table.insert(steps, step)
     local endStar  = (idx == cfg.paraIdx) and cfg.paraEstrelas or 5
     total = addCost(total, step.custo)
  end


     -- Somar estrelas do tier atual
  while (iTier < tf) or (iTier == tf and iStar < sf) do
    local parc, pss = somarEstrelas(tierKey, startStar, endStar)
     if iStar < 5 and (iTier < tf or (iTier == tf and iStar < sf)) then
    total.berry = total.berry + parc.berry
      -- subir estrela dentro do tier atual
    total.frag  = total.frag  + parc.frag
      local tierName = getTierName(iTier)
    for _, x in ipairs(pss) do table.insert(passos, x) end
      local toStar = iStar + 1
 
      local c = starCost(tierName, toStar)
     -- Se não for o último tier de destino e atingiu 5 estrelas, pagar promoção
      push({ tipo = "estrela", tier = tierName, de = iStar, para = toStar, custo = c })
    if endStar == 5 and idx < cfg.paraIdx then
      iStar = toStar
       local promo = (DATA.custos[tierKey] and DATA.custos[tierKey].promover) or { berry = 0, frag = 0 }
     else
       total.berry = total.berry + (promo.berry or 0)
      -- já está com 5 estrelas -> tentar subir de tier
       total.frag  = total.frag  + (promo.frag or 0)
      if iTier >= #COSTS.tiers then
       table.insert(passos, { tipo = "promocao", tier = tierKey, para = DATA.tiers[idx + 1], berry = promo.berry or 0, frag = promo.frag or 0 })
        -- não há próximo tier, evitar loop
        break
       end
       local fromTier = getTierName(iTier)
       local c = tierUpCost(fromTier)
       push({ tipo = "tier", de = fromTier, para = getTierName(iTier + 1), custo = c })
      iTier = iTier + 1
      iStar = 0 -- ao subir de tier, reinicia estrelas
     end
     end
   end
   end


   return { total = total, passos = passos, cfg = cfg }
   return steps, total
end
end


-- =======================
-- Renderização em wikitext/HTML
-- RENDERIZAÇÃO (HTML simples / Wikitext)
local function render(steps, total, opts)
-- =======================
  local h = mw.html.create('div'):addClass('calc-tier')


local function formatNumero(n)
   if opts.titulo then
   -- separador de milhar simples
     h:tag('h3'):wikitext(opts.titulo)
  local s = tostring(math.floor(n or 0))
  local k
  while true do
     s, k = s:gsub("^(%d+)(%d%d%d)", "%1.%2")
    if k == 0 then break end
   end
   end
  return s
end


local function renderResultado(res)
  if opts.mostrarTabela ~= false then
  local moeda = res.cfg.moeda
    local tbl = h:tag('table'):addClass('wikitable'):css('width', '100%')
  local fragN = res.cfg.frag
    local thead = tbl:tag('tr')
    thead:tag('th'):wikitext('Passo')
    thead:tag('th'):wikitext('Ação')
    thead:tag('th'):wikitext('Custo (Berry)')
    thead:tag('th'):wikitext('Custo (Fragmentos)')


  local linhas = {}
    for i, st in ipairs(steps) do
  table.insert(linhas, "{| class=\"wikitable\" style=\"width:420px\"\n! colspan=2 | Totais\n|-")
      local tr = tbl:tag('tr')
  table.insert(linhas, string.format("| %s || %s\n|-", moeda, formatNumero(res.total.berry)))
      tr:tag('td'):wikitext(i)
  table.insert(linhas, string.format("| %s || %s\n|}", fragN, formatNumero(res.total.frag)))
       if st.tipo == 'estrela' then
 
         tr:tag('td'):wikitext(string.format('Tier **%s**: %d → %d estrelas', st.tier, st.de, st.para))
  if res.cfg.mostrarPassos and #res.passos > 0 then
    table.insert(linhas, "\n\n{| class=\"wikitable\" style=\"width:100%%\"\n! Passo !! Detalhe !! " .. moeda .. " !! " .. fragN .. "\n|-")
    for _, pss in ipairs(res.passos) do
       if pss.tipo == "estrela" then
         table.insert(linhas, string.format("| Estrela || %s: %d★ ➜ %d★ || %s || %s\n|-",
          pss.tier, pss.from, pss.to, formatNumero(pss.berry), formatNumero(pss.frag)))
       else
       else
         table.insert(linhas, string.format("| Promoção || %s ➜ %s || %s || %s\n|-",
         tr:tag('td'):wikitext(string.format('Subir de **%s** → **%s**', st.de, st.para))
          pss.tier, pss.para, formatNumero(pss.berry), formatNumero(pss.frag)))
       end
       end
      tr:tag('td'):wikitext(st.custo.berry or 0)
      tr:tag('td'):wikitext(st.custo.frag or 0)
     end
     end
    table.insert(linhas, "|}")
   end
   end


   return table.concat(linhas, "\n")
   local box = h:tag('div'):addClass('calc-tier-total'):css('margin-top','8px')
  box:wikitext(string.format("'''Total:''' %d Berry • %d Fragmentos", total.berry or 0, total.frag or 0))
 
  return tostring(h)
end
end


-- =======================
local function parseInt(v, default)
-- ENTRADA PÚBLICA (Scribunto)
  local n = tonumber(v)
-- =======================
  if not n then return default end
  n = math.floor(n)
  return n
end


function p.calc(frame)
-- Entrada pública via #invoke
   local args = frame.args
function p.calcular(frame)
   -- permitir também Template: wrapper com parent
   local args = frame:getParent() and frame:getParent().args or frame.args
   if frame:getParent() then
 
     for k, v in pairs(frame:getParent().args) do
  local inicioTier = args.inicio_tier or args.de_tier or args.tier_inicial
      if v ~= '' and args[k] == nil then args[k] = v end
   local fimTier    = args.fim_tier    or args.ate_tier or args.tier_final
     end
   local inicioEst  = parseInt(args.inicio_estrela or args.de_estrela or args.estrelas_iniciais or 0, 0)
  local fimEst     = parseInt(args.fim_estrela    or args.ate_estrela  or args.estrelas_finais  or 0, 0)
 
  -- Opções
  local titulo = args.titulo
  local mostrarTabela = (tostring(args.mostrar_tabela or 'sim') ~= 'nao')
 
  if not inicioTier or not fimTier then
    return "Erro: defina 'inicio_tier' e 'fim_tier'."
  end
 
  local ti = tierIndex(inicioTier)
  local tf = tierIndex(fimTier)
  if not ti or not tf then
     return "Erro: tier inválido. Use: bronze, prata, ouro, diamante."
   end
   end


   local res, err = calcular(args)
   -- Sanitiza limites de estrela (0..5)
   if not res then
   if inicioEst < 0 then inicioEst = 0 end
     return string.format("'''Erro:''' %s", err)
  if inicioEst > 5 then inicioEst = 5 end
  if fimEst < 0 then fimEst = 0 end
  if fimEst > 5 then fimEst = 5 end
 
  -- Se o tier inicial já é maior que o final, invertido
  if (ti > tf) or (ti == tf and inicioEst > fimEst) then
     return "Erro: o alvo precisa ser maior que o estado inicial."
   end
   end
  return renderResultado(res)
end


-- =======================
  local steps, total = computePath(ti, inicioEst, tf, fimEst)
-- UTIL: DEPURAÇÃO VIA CONSOLE (opcional)
  return render(steps, total, { titulo = titulo, mostrarTabela = mostrarTabela })
-- =======================
-- Você pode rodar no console do Scribunto para testar:
-- =p._test{deTier="bronze", deEstrelas=2, paraTier="diamante", paraEstrelas=4, mostrarPassos="sim"}
function p._test(args)
  local res, err = calcular(args)
  if not res then return "Erro: " .. err end
  return renderResultado(res)
end
end


return p
return p

Edição das 19h51min de 26 de setembro de 2025

A documentação para este módulo pode ser criada em Módulo:Gb/doc


-- Módulo:CalculadoraTier
-- Calculadora de custos para evolução de tiers/estrelas
-- Compatível com MediaWiki + Scribunto

local p = {}

-- \u26a0\ufe0f EDITÁVEL: Tabela de custos padrão
-- Defina aqui os custos para cada estrela alcançada (1..5) em cada tier,
-- e o custo para subir de um tier para o próximo.
local COSTS = {
  tiers = {"bronze", "prata", "ouro", "diamante"},

  -- Custo para alcançar CADA estrela (ex.: STAR[1] = custo para ir de 0 -> 1 estrela)
  star = {
    bronze = {
      [1] = { berry = 100, frag = 1 },
      [2] = { berry = 200, frag = 1 },
      [3] = { berry = 300, frag = 2 },
      [4] = { berry = 450, frag = 2 },
      [5] = { berry = 600, frag = 3 },
    },
    prata = {
      [1] = { berry = 200, frag = 1 },
      [2] = { berry = 300, frag = 2 },
      [3] = { berry = 450, frag = 2 },
      [4] = { berry = 650, frag = 3 },
      [5] = { berry = 900, frag = 4 },
    },
    ouro = {
      [1] = { berry = 400, frag = 2 },
      [2] = { berry = 600, frag = 2 },
      [3] = { berry = 900, frag = 3 },
      [4] = { berry = 1300, frag = 4 },
      [5] = { berry = 1800, frag = 5 },
    },
    diamante = {
      [1] = { berry = 800, frag = 3 },
      [2] = { berry = 1200, frag = 4 },
      [3] = { berry = 1700, frag = 5 },
      [4] = { berry = 2300, frag = 6 },
      [5] = { berry = 3000, frag = 8 },
    },
  },

  -- Custo de mudar de tier (ex.: BRONZE -> PRATA). Não altera estrelas (reinicia para 0).
  tierUp = {
    bronze   = { berry = 2000, frag = 5 },  -- custo para BRONZE -> PRATA
    prata    = { berry = 6000, frag = 10 }, -- custo para PRATA -> OURO
    ouro     = { berry = 15000, frag = 20 },-- custo para OURO -> DIAMANTE
    diamante = nil,                          -- não há próximo tier
  },
}

-- Normaliza texto (case-insensitive, acentos ignorados simples)
local function norm(s)
  if type(s) ~= 'string' then return nil end
  s = mw.ustring.lower(s)
  s = s:gsub("á", "a"):gsub("â", "a"):gsub("ã", "a")
       :gsub("é", "e"):gsub("ê", "e")
       :gsub("í", "i")
       :gsub("ó", "o"):gsub("ô", "o")
       :gsub("ú", "u")
  return s
end

local function tierIndex(name)
  local n = norm(name)
  for i, t in ipairs(COSTS.tiers) do
    if n == t then return i end
  end
  return nil
end

local function getTierName(idx)
  return COSTS.tiers[idx]
end

-- Soma custos (protege nil)
local function addCost(a, b)
  a = a or { berry = 0, frag = 0 }
  b = b or { berry = 0, frag = 0 }
  return { berry = (a.berry or 0) + (b.berry or 0), frag = (a.frag or 0) + (b.frag or 0) }
end

-- Obtém custo para subir 1 estrela dentro do tier
local function starCost(tierName, starTo)
  local t = COSTS.star[tierName]
  return t and t[starTo] or { berry = 0, frag = 0 }
end

-- Obtém custo para subir de tier
local function tierUpCost(tierName)
  local c = COSTS.tierUp[tierName]
  return c or { berry = 0, frag = 0 }
end

-- Caminho de evolução do estado (tierIdx, star) até (tierIdx2, star2)
local function computePath(ti, si, tf, sf)
  local steps = {}
  local total = { berry = 0, frag = 0 }

  local iTier, iStar = ti, si

  local function push(step)
    table.insert(steps, step)
    total = addCost(total, step.custo)
  end

  while (iTier < tf) or (iTier == tf and iStar < sf) do
    if iStar < 5 and (iTier < tf or (iTier == tf and iStar < sf)) then
      -- subir estrela dentro do tier atual
      local tierName = getTierName(iTier)
      local toStar = iStar + 1
      local c = starCost(tierName, toStar)
      push({ tipo = "estrela", tier = tierName, de = iStar, para = toStar, custo = c })
      iStar = toStar
    else
      -- já está com 5 estrelas -> tentar subir de tier
      if iTier >= #COSTS.tiers then
        -- não há próximo tier, evitar loop
        break
      end
      local fromTier = getTierName(iTier)
      local c = tierUpCost(fromTier)
      push({ tipo = "tier", de = fromTier, para = getTierName(iTier + 1), custo = c })
      iTier = iTier + 1
      iStar = 0 -- ao subir de tier, reinicia estrelas
    end
  end

  return steps, total
end

-- Renderização em wikitext/HTML
local function render(steps, total, opts)
  local h = mw.html.create('div'):addClass('calc-tier')

  if opts.titulo then
    h:tag('h3'):wikitext(opts.titulo)
  end

  if opts.mostrarTabela ~= false then
    local tbl = h:tag('table'):addClass('wikitable'):css('width', '100%')
    local thead = tbl:tag('tr')
    thead:tag('th'):wikitext('Passo')
    thead:tag('th'):wikitext('Ação')
    thead:tag('th'):wikitext('Custo (Berry)')
    thead:tag('th'):wikitext('Custo (Fragmentos)')

    for i, st in ipairs(steps) do
      local tr = tbl:tag('tr')
      tr:tag('td'):wikitext(i)
      if st.tipo == 'estrela' then
        tr:tag('td'):wikitext(string.format('Tier **%s**: %d → %d estrelas', st.tier, st.de, st.para))
      else
        tr:tag('td'):wikitext(string.format('Subir de **%s** → **%s**', st.de, st.para))
      end
      tr:tag('td'):wikitext(st.custo.berry or 0)
      tr:tag('td'):wikitext(st.custo.frag or 0)
    end
  end

  local box = h:tag('div'):addClass('calc-tier-total'):css('margin-top','8px')
  box:wikitext(string.format("'''Total:''' %d Berry • %d Fragmentos", total.berry or 0, total.frag or 0))

  return tostring(h)
end

local function parseInt(v, default)
  local n = tonumber(v)
  if not n then return default end
  n = math.floor(n)
  return n
end

-- Entrada pública via #invoke
function p.calcular(frame)
  local args = frame:getParent() and frame:getParent().args or frame.args

  local inicioTier = args.inicio_tier or args.de_tier or args.tier_inicial
  local fimTier    = args.fim_tier    or args.ate_tier or args.tier_final
  local inicioEst  = parseInt(args.inicio_estrela or args.de_estrela or args.estrelas_iniciais or 0, 0)
  local fimEst     = parseInt(args.fim_estrela    or args.ate_estrela  or args.estrelas_finais   or 0, 0)

  -- Opções
  local titulo = args.titulo
  local mostrarTabela = (tostring(args.mostrar_tabela or 'sim') ~= 'nao')

  if not inicioTier or not fimTier then
    return "Erro: defina 'inicio_tier' e 'fim_tier'."
  end

  local ti = tierIndex(inicioTier)
  local tf = tierIndex(fimTier)
  if not ti or not tf then
    return "Erro: tier inválido. Use: bronze, prata, ouro, diamante."
  end

  -- Sanitiza limites de estrela (0..5)
  if inicioEst < 0 then inicioEst = 0 end
  if inicioEst > 5 then inicioEst = 5 end
  if fimEst < 0 then fimEst = 0 end
  if fimEst > 5 then fimEst = 5 end

  -- Se o tier inicial já é maior que o final, invertido
  if (ti > tf) or (ti == tf and inicioEst > fimEst) then
    return "Erro: o alvo precisa ser maior que o estado inicial."
  end

  local steps, total = computePath(ti, inicioEst, tf, fimEst)
  return render(steps, total, { titulo = titulo, mostrarTabela = mostrarTabela })
end

return p