Skip to content

Debugging

Debugging is an essential part of scripting in Lemonate. This guide covers tools, workflows, and best practices to help you diagnose issues, understand runtime behavior, and improve stability and performance.

Overview

When working with scripts in Lemonate, you will typically debug issues related to:

  • Runtime errors
  • Incorrect logic or state
  • Unexpected lifecycle behavior
  • Event flow issues
  • Performance bottlenecks

Lemonate provides multiple debugging tools:

  • Console logging
  • Runtime error reporting
  • Visual debugging using ImGui
  • Debugging using breakpoints

This page explains how and when to use each approach.

Console logging

The simplest debugging tool in Lemonate is the Console module. Use the Console API to print diagnostic messages:

Console.log("Player initialized")
Console.warn("Health is low")
Console.error("Failed to load asset")

Recommended usage:

  • log for general information
  • warn for non-critical issues
  • error for serious failures

Best practices:

  • Include context in your messages.
  • Avoid excessive logging inside frequently-called functions like update().
  • Prefix logs with system identifiers (e.g., [AI], [Physics]).

Example:

Console.log("[Inventory] Item added:", itemId)

Printing dumps and inspecting

There are two very handy functions that will help you inspecting the contents of variables. The first one is the global printDump(value) function, that will simply write the contents of your variable to the console. How is this different from normal print(value)? It will also print the contents of an object and traverse it. Be careful what you feed it especially if your object is huge.

For bigger objects and better visualization, there is its bigger brother inspect(value), that will not just print text to the console but add a visual inspector panel to the console where you can drill down the content manually, so it will not clutter your console all at once. You can also inspect types of variables with it.

Both functions (as handy as they are) should better not be used in places where they log many times per second as this will bring down performance of your console very quickly and also you will not be able to read much if you see tons of messages flying by per second.

Understanding runtime errors

When a script throws an error:

  • Execution of the current function stops.
  • The Console will display an error message.
  • A stack trace may be shown.

Common causes:

  • Accessing nil values
  • Calling undefined functions
  • Incorrect parameter types
  • Wrong calling convention of functions

Example error scenario:

function MyBehaviour:update()
    player.health = player.health - 1
end

If player is nil, this will throw an error. Use guards to prevent runtime crashes:

if player ~= nil then
    player.health = player.health - 1
end

Calling conventions in Lua (Colon vs. Dot)

A very common source of errors in Lemonate (and Lua in general) stems from mixing up colon (``:``) and dot (``.``) syntax when calling functions. This section explains the difference and how to avoid mistakes.

Methods (functions tied to an object/table) must be called with a colon (:), which automatically passes self as the first argument.

Correct Usage:

function MyBehaviour:doSomething(var1, var2)
    -- 'self' is implicitly passed as the first argument
    print(self, var1, var2)
end

function MyBehaviour:update()
    self:doSomething(1, 2)  -- Output: MyBehaviour (table), 1, 2
end

Incorrect Usage (Will Fail):

function MyBehaviour:update()
    self.doSomething(1, 2)  -- Error: 'self' is missing, arguments shift
end

Why it fails:
- The dot (.) syntax does not pass self automatically. - Lua expects self as the first argument, but since it's missing, var1 becomes self, and var2 is misaligned.

Static functions (not tied to an object) must be called with a dot (.), and self must be passed explicitly.

Correct Usage:

function MyBehaviour.doSomethingStatic(self, var1, var2)
    -- 'self' must be passed manually
    print(self, var1, var2)
end

function MyBehaviour:update()
    self.doSomethingStatic(self, 1, 2)  -- Output: MyBehaviour (table), 1, 2
end

Incorrect Usage (Will Fail):

function MyBehaviour:update()
    self:doSomethingStatic(1, 2)  -- Error: Extra 'self' is passed
end

Why it fails:
- The colon (:) syntax automatically passes ``self``, so now self is duplicated. - Lua sees self as the first argument, but doSomethingStatic expects it explicitly.

Visual debugging with ImGui

For complex systems (AI, physics, state machines), visual debugging is often more effective than logs.

Lemonate provides ImGui integration for runtime UI overlays.

Example:

local ImGui = require 'engine/imgui'

function MyBehaviour:rendergui()
    ImGui.beginDialog("", 10, 30,
          ImGui.WindowFlags.NoTitleBar +
          ImGui.WindowFlags.NoMove +
          ImGui.WindowFlags.NoCollapse +
          ImGui.WindowFlags.NoResize +
          ImGui.WindowFlags.AlwaysAutoResize)

    ImGui.text("Player Health: " .. player.health)
    if (ImGui.button("Restart", 100, 30)) then
        self:restart()
    end

    ImGui.endDialog()
end

Use cases:

  • Display internal state
  • Toggle debug flags
  • Inspect live variables
  • Visualize AI states

Best Practice:
Wrap debug UI in conditional flags so it can be disabled in production.

Performance debugging

Performance issues often come from:

  • Heavy logic inside update()
  • Large loops
  • Frequent object creation
  • Excessive logging

Recommendations:

  • Avoid allocating objects every frame.
  • Cache references where possible. If you create scene objects on the fly, work with a pool instead of creating/deleting all the time.
  • Disable debug logs in performance-critical sections.
  • Avoid await() in update() or render() lifecycle hooks.

Creating a debug mode and debugging variables

A recommended pattern is to define a global debug flag, ideally in the Variables module. This can be set from within the editor UI and in the code you can easily read it and also write to variables to give feedback:

local Variables = require 'engine/variables'

if Variables.debugging.enabled then
    Console.log("Position:", self.node.transform.position)
    Variables.debugging.playerX = self.node.transform.position.x
end

Using the code editor for debugging

The Lemonate Editor includes a built-in debugger. To use it, you can set breakpoints by clicking in the script editor in the gutter area before the line numbers. If you start your game in debug mode (the button directly next to the play button), the execution of that script will break on the marked line and allow you to inspect variables or run the script step by step using the shortcuts F8 (continue), F10 (step over), F11 (step into).