Skip to content
deltascripts.
Blog

Read a Luau Script Without Running It: A Worked Example

Trace a complete reward-selection example through inputs, branches and a loop, then record exactly what a static reading can establish.

By deltascriptsco-admin Published 4 min read
Magnifying glass examining code before the play button is pressed

Before running a small script, try predicting its output. You soon find which variables matter, which branch rejects an item and where an assumption has slipped into your reading.

This example selects names from a table of fictional rewards. Everything needed for the calculation is visible in the file, so we can trace it on paper.

The complete example

local settings = {
    minimumPoints = 10,
    includeBonus = false,
}

local rewards = {
    {name = "Leaf", points = 6, bonus = false},
    {name = "Stone", points = 12, bonus = false},
    {name = "Star", points = 20, bonus = true},
}

local function isEligible(reward, options)
    if reward.points < options.minimumPoints then
        return false
    end

    if reward.bonus and not options.includeBonus then
        return false
    end

    return true
end

local function collectNames(items, options)
    local names = {}
    for _, reward in ipairs(items) do
        if isEligible(reward, options) then
            table.insert(names, reward.name)
        end
    end
    return names
end

local selected = collectNames(rewards, settings)
print(table.concat(selected, ", "))

Start with the two rules, not the item names: a points threshold and a bonus switch. Those settings determine which names reach the final print.

Step one: identify inputs and ownership

minimumPoints is 10 and includeBonus is false. There are three reward records, each with a name, a points value and a bonus flag. None of these initial values comes from another file.

That makes this a manageable first trace. In a real application, values loaded from a module or network response would send you to another source before you could make the same prediction.

collectNames creates an empty names table for each call. Keep that result table separate in your notes from the original reward records.

Step two: translate the function into plain rules

isEligible has two ways to reject an item: too few points, or a bonus that the settings don’t allow. Only an item that passes both checks reaches return true.

An early return ends that call. Once Leaf fails the points test, you don’t need its bonus flag to decide the result.

Watch the two similarly named fields. reward.bonus belongs to the current item; options.includeBonus is the setting applied to every item. Mixing them up changes the rule you’re tracing.

Step three: trace every iteration

Expected trace with the settings shown above
ItemPoints checkBonus checkSelected names after this item
Leaf6 is below 10, so reject.Not needed after the early return.Empty.
Stone12 passes the threshold.Not a bonus, so allow.Stone.
Star20 passes the threshold.Bonus is true and includeBonus is false, so reject.Stone.

The printed text should be Stone. table.concat joins the names already selected; it doesn’t reconsider any item’s eligibility.

Step four: change one input on paper

Now change includeBonus to true and leave the threshold at 10. Leaf still fails. Stone passes, and Star now passes too, so the predicted text is “Stone, Star”.

For a separate trial, restore includeBonus to false and raise the threshold to 15. Stone now fails on points, while Star still fails the bonus rule. The result table should be empty.

Check the boundary as well. An ordinary item with exactly 10 points passes, because the first comparison rejects values below 10. That single case can reveal a mistaken reading of the operator.

Step five: look for effects beyond the return value

The loop builds a result table without editing the reward records. If a later version also changes an item’s points inside the loop, add that write to your trace; it could affect subsequent work.

In a larger file, mark dependencies, object access, writes, event connections and requests. Follow the function bodies rather than trusting names such as safeLoad or updateInventory.

Know when the review stops

Sometimes you reach a value or dependency you can’t inspect. Stop the prediction there and name what is missing. A partial trace with a clear boundary is more useful than a confident guess.

This exercise teaches you to explain visible behavior. It doesn’t turn a readable program into a security-reviewed one. Keep those two tasks separate when applying the method to unfamiliar code.

References

Comments 0

No approved comments yet. Start the discussion.

Every comment is reviewed by a moderator before publication. Enter only a nickname and your comment; no email or account is needed.

Privacy policy