Hungrit Docs

When nothing happens#

Something is not working. This page is the order to check things in.

Where messages show up#

print and every error go to the script console of whoever owns the object, which is you while you are building.

Luau
local self = require("self") self:on_touch(function(player)    print("touched by " .. player.name)end)

That console is for finding out what your script is doing. It is not how you talk to players. For that, see Menus and HUDs.

You get up to 32 lines per event and 512 bytes per line. A print inside a fast timer will be trimmed, and noticing that is itself useful information.


The three kinds of not working#

1. Nothing happens at all#

Either the script did not load, or the handler was never registered.

Save the script and watch the console. A script that fails while loading says so right then, and none of its handlers exist afterwards. So the symptom is silence later, not an error at the moment you click.

Three questions, in this order:

  • Is the script attached to the object you are actually clicking?
  • Did you save?
  • Was there an error at the moment you saved?

2. It worked once and then stopped#

The script was killed. Look for one of these:

MessageWhat happened
script budget exceededone event used more than 50,000 instruction units
script memory limit exceededit went past 4 MB

Both mean a loop with no way out. The script will not run again until you save it.

Luau
local self = require("self") -- kills the scriptwhile true do    self:rotate_by(0, 1, 0)end
Luau
local self = require("self") -- what you meantself:on_timer(0.05, function()    self:rotate_by(0, 1, 0)end)

3. It half works#

An ordinary error threw away that one event. The script is still alive and the next click runs normally. The message names the line.


The messages you will actually see#

attempt to index nil#

You read a field of something that is not there.

Luau
local self = require("self") local p = self:part("hinge")p:rotate_by(0, 5, 0)          -- if there is no "hinge", p is nil

The fix always has the same shape. Check, leave early, carry on:

Luau
local self = require("self") local p = self:part("hinge")if not p then    print("no part called 'hinge'")    returnendp:rotate_by(0, 5, 0)

This is by far the most common script error. Anything that can answer nil deserves the check: self:part, players:get, objects:get, self.store:get, self.contents:get, world:raycast.

attempt to call a nil value#

You called something that is not a function. It is almost always one of three things.

A dot where a colon belongs. self.move_to(0, 5, 0) instead of self:move_to(0, 5, 0).

A typo in the name. self:rotateby instead of self:rotate_by.

A method that does not exist. Check it in the reference rather than guessing. A plausible name is not the same as a real one.

attempt to perform arithmetic, or to concatenate#

You mixed text and numbers.

Luau
print("coins: " .. 5)          -- fineprint("coins: " .. nil)        -- errorlocal n = "5" + 1              -- error

self.store always hands back text, so this bites there most of all:

Luau
local self = require("self") local n = tonumber(self.store:get("coins") or "0") or 0print(n)

unknown module#

require with a name that is not built in and is not one of your modules.

The built-ins are self, world, players, events, objects, vec, json, http and task.

For your own module, attach a sibling script with that name to the same object, or put it on the object named Modules and require "@modules/<name>". The script that asked restarts on its own once the module is saved. See Sharing code.

cyclic require of module#

Two of your modules require each other. One of them has to stop.


When there is no error at all#

This is the hard case. Nothing is red, and nothing happens.

Look at the second return value#

Most verbs refuse quietly unless you look.

Luau
local self = require("self")local players = require("players") local p = players:list()[1]if p then    local ok, why = self:hold({ player = p.id })    if not ok then        print("hold refused: " .. tostring(why))    endend

If a call seems to do nothing, print its second return value. The reason is almost always sitting right there, and every one of them is listed in Limits and refusals.

Count your effects#

Eight world effects per event. The ninth quietly does not happen.

Luau
local world = require("world") for i = 1, 20 do    world:spawn({ shape = "sphere" })   -- only 8 of these existend

Check you are not writing the same thing over and over#

A timer that writes properties every tick, whether or not anything changed, spends the region's budget for nothing and can crowd out the writes that matter. Act when the answer changes:

Luau
if should ~= lit then    lit = should    set_all(lit)end

Check whose screen it is#

A verb that draws on a player refuses when it is aimed at somebody else's screen from the wrong kind of script, and answers nil plus a reason. Print it.


Working out where you are#

When you cannot tell which branch ran, print the branch rather than the value.

Luau
local self = require("self") self:on_touch(function(p)    print("touch: entered")     if self:held_by() then        print("touch: already held, leaving")        return    end     print("touch: holding")    local ok, why = self:hold({ player = p.id })    print("touch: hold said " .. tostring(ok) .. " " .. tostring(why))end)

It is ugly, and it finds the problem in one click. Take the lines out afterwards.

Two reads and nothing found means stop reading

A defect of order, such as two things writing the same value in one frame, or a handler running before the thing it needs exists, is invisible on the page and obvious in a log. One printed line per event beats a third read-through.

Next#

Updated 26 September 2026