Getting started

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
tip

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 while whose exit condition never comes true is stopped with a scene exceeded the … instruction budget error instead of freezing the game.
  • Dead-end choices: if every Choice at 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 nil and 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)