Testing and debugging
Reading the log, running a scene on demand, and what the game does when a mod misbehaves.
The log is your console
Everything the game notices about mods goes to Saved/Logs/Underlives.log in the game directory. It is rewritten on every launch; the previous runs are kept next to it with a timestamp.
Search it for these prefixes:
| Prefix | Tells you |
|---|---|
[ModLib] loaded |
Each Lua/*.lua library that ran, in order |
[SceneScript] Indexed N scene script(s) |
How many scenes the game found in total |
[SceneTrigger] '<action>' at '<place>': N candidate scene(s) |
After every action: how many scenes passed WHAT/WHERE/WHEN/cooldown |
[SceneTrigger] Scene '…' has no RUN function |
The file returned a table without RUN |
[SceneTrigger] Scene '…' is stuck |
A choice point where no option was available; the scene was closed |
[SceneScript] Unbound Lua API function Game:foo |
A script called a function that does not exist |
LogLuaMachine errors |
Lua syntax and runtime errors, with the file and line |
Game:Log("text") writes your own lines there, so a scene can report what it decided:
luaGame:Log("ynl_bouncer: caffeine=" .. tostring(Player:getStat("caffeine")))
Game:ShowMessage("text", seconds) puts text on screen instead: handy to confirm a script ran at all.
Run a scene without waiting for it
Launch the game with -WorldMapScene=<scene name> (the file name without .ulscene) and that scene runs a moment after the map loads, whatever its WHAT, WHERE and WHEN say. On Windows, add it to a shortcut's target, after the executable.
A few other launch options help while testing:
| Option | Effect |
|---|---|
-WorldMapScene=<scene> |
Run a scene on the map at startup |
-City=<name> |
Start in that city (see Cities) |
-CityStyle=<style> |
Force a city look from Map/city_styles.json |
A test scene that only calls Game:Log (no Say, Narrate or Choice) never waits for the player, so it runs to the end instantly and leaves only log lines behind: the fastest way to check what a function returns.
Changes need a restart
Scenes, actions, stats, quests, libraries and JSON data are all read once per session. After editing, restart the game. There is no hot reload yet.
What the game protects you from
Mods are untrusted by design; a broken one should cost you a scene, never the game.
- Runtime errors end the scene, log the error with its file and line, and hand control back to the player.
- Runaway loops: a scene gets roughly 20 million Lua instructions between two pauses (a line of dialogue, a choice). A
whilewhose exit condition never comes true is stopped with ascene exceeded the … instruction budgeterror instead of freezing the game. - Dead-end choices: if every
Choiceat a choice point is unavailable, the scene closes and the log names the file, rather than leaving the player in front of an empty menu. - Unknown functions return
niland log their name once, instead of crashing somewhere further down. - Missing people: functions that look up or spawn a person return an invalid person instead of failing. Check with
isValid()before using one:
lualocal ex = Game:getSpecific("Ex")
if not ex:isValid() then
return false
end
Common mistakes
| Symptom | Likely cause |
|---|---|
| The scene never plays | WHAT empty or not listing the action; WHERE missing the venue tag; hour outside WHEN (the window excludes both ends); scene still on cooldown; RUN returned false |
| The action is not in the list | Wrong WHERE tag; outside WHEN; on timeout; a CONDITIONS entry failing; a WHO_LUA rejecting the person you clicked |
| A stat never goes above 0 | STAT_MAX is 0. Set a real maximum. |
| My file is ignored | Wrong extension, or the mod folder has no .ulmod file, or the mod is disabled in LoadOrder.txt |
| My change does nothing | Another mod later in the load order ships the same scene name or id |
attempt to index a nil value |
A person lookup came back empty (guard with isValid()) or a typo in a function name (check the log for Unbound Lua API function) |