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.

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
| Item | Points check | Bonus check | Selected names after this item |
|---|---|---|---|
| Leaf | 6 is below 10, so reject. | Not needed after the early return. | Empty. |
| Stone | 12 passes the threshold. | Not a bonus, so allow. | Stone. |
| Star | 20 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.



Comments 0
No approved comments yet. Start the discussion.