Skip to content

Advanced Usage

Ghost in a Nutshell

Welcome to the advanced documentation of Nutshell.

While the basic framework handles straightforward, single-window games, scaling a project requires mastering the underlying runtime environment.

Scripts & Asset Management

Path Management

When loading an image, loading an audio file, or requiring external scripts, file paths are resolved by Nutshell using specific rules.

Let's assume your project uses the following directory structure:

/path/to/my/game
├── main.nut
├── audio
│   ├── crunch.ogg
│   └── background-theme.mp3
├── images
│   ├── acorn.png
│   ├── forest.png
│   └── squirrel.png
└── src
    ├── draw.nut
    └── update.nut

Relative paths

By default, assets are resolved via a path relative to the script that executes the load call.

Relative Path Example

To reference acorn.png from inside src/update.nut, you must look up one directory level using ../images/acorn.png.

Using images/acorn.png would cause the engine to search for src/images/acorn.png, which does not exist.

Absolute paths

You can also use system-specific absolute paths to target files anywhere on the host machine.

Absolute Path Example

If you want to load a system font directly from a Linux directory to display localized Arabic text, you can specify its full absolute path:

local sampleFont = Font("/usr/share/fonts/truetype/noto/NotoSansArabic-Regular.ttf");
sampleFont.draw(10, 10, "مرحبا بالعالم")

Project Root & Resource Paths (res://)

The Project Root is explicitly defined as the directory containing your game's entry point, main.nut.

To reference files from anywhere in your codebase without writing complex relative paths, you can use a Resource Path. A resource path bypasses the current script's location and evaluates paths relative to the project root using the res:// prefix.

This approach is highly recommended because it prevents broken paths if you decide to reorganize or move scripts into different subfolders later.

Resource Path Example

To safely reference acorn.png inside src/update.nut, you can use res://images/acorn.png.

If you move update.nut to a different folder later, the asset reference remains perfectly valid and unbroken.

Dynamic Resolution: System.script_path()

While relative tracks and the res:// protocol handle the vast majority of asset loading use cases, you may occasionally need to inspect or dynamically manipulate paths at runtime—especially when writing reusable plugins, shared libraries, or advanced logging tools.

The System.script_path() static method returns a string containing the full filesystem path of the currently executing script file.

The simplest and most common use case for this is loading adjacent data configurations. If you separate your game logic into isolated modules (like an achievement manager or an enemy spawner), you often want that script's data files to live in the exact same folder so everything stays organized.

Loading Adjacent Data Files

Imagine you have an enemy setup script (src/entities/enemy.nut) and you want it to load a balance configuration file (src/entities/enemy_stats.json) that sits in the exact same directory:

// Inside src/entities/enemy.nut
local currentScript = System.script_path();

local lastSlashIdx = -1;
local searchIdx = currentScript.find("/");

// Keep finding the next slash until find() returns null
while (searchIdx != null) {
    lastSlashIdx = searchIdx;
    searchIdx = currentScript.find("/", searchIdx + 1);
}

if (lastSlashIdx != -1) {
    local folderPath = currentScript.slice(0, lastSlashIdx);
    local dataPath = folderPath + "/enemy_stats.json";

    print("Loading stats from: " + dataPath);
}

By using System.script_path(), you can safely move the entire entities folder anywhere else in your project tree later on, and the script will never break or lose track of its data file.

Script Modularization with require(path)

The require(path) utility allows you to modularize your code by loading and executing external scripts from within your current script.

This function is registered globally within the Squirrel root table of the active virtual machine. You can use it to keep your codebase clean, decoupled, and scaling beyond a single main.nut file:

main.nut
// Split game states out into dedicated sub-scripts
require("src/update.nut")
require("src/draw.nut")
src/update.nut
function update(dt) {
    // Process game logic and state changes here
}
src/draw.nut
function draw() {
    // Render visual elements here
    Font("").draw(10, 10, "Hello Nutshell!")
}

Multi-Virtual Machine Architecture

By default, Nutshell initializes and runs a single virtual machine. However, you can spawn and run multiple independent virtual machines simultaneously using the System API.

Virtual Machine Initialization Rules

Every new virtual machine MUST be initialized with a valid target script path. These initialization scripts carry the same architectural requirements as your main entry point: they must define, at minimum, a global update(dt) function.

Inter-VM Communication

At the moment, you can't share data between two virtual machines.

The only actions you can take in a virtual machine is either to spawn or kill another virtual machine by its identifier.

Multi-Window Management

Nutshell supports multiple windows. You can spawn and configure multiple concurrent game windows using the Window API.

Contextual Limitations

A disclaimer though:

Windows are bound to their Parent VM

Nutshell windows are strictly bound to the specific virtual machine that spawned them.

A script running inside one virtual machine CANNOT access, manipulate, or draw to windows owned by a separate virtual machine.

Assets are bound to Windows

Images and Fonts are cached and allocated explicitly per window context.

You CANNOT render an image or draw text onto Window B if that asset resource was loaded while Window A was active. When loading assets, ensure the target window is actively set beforehand.

Active Window

An active window is where all current canvas drawing operations are actively directed.

  • To get the active window reference: Window.active().
  • To bind drawing operations to a specific window: window.activate().

Focused Window

A focused window is the one currently intercepting active OS hardware mouse, keyboard, or controller inputs.

  • To get the currently focused window: Window.focused_id().
  • To check if a window instance has the focus: window.focused().
  • To forcefully request focus for a specific window: window.focus().

Automatic Windows

By default, Nutshell automatically instantiates a new window with default dimensions whenever a new virtual machine is initialized.

You can pass the following command-line flags to the nutshell binary to alter or override this automatic window behavior:

Option Type Default Description
--title string nutshell Title string for automatic windows
--width int 640 Width for automatic windows
--height int 480 Height for automatic windows
--no-window bool false When set to true, disable the automatic creation of windows for new virtual machines.

Security Considerations

Script Execution Privileges

Nutshell does not currently sandbox script execution. Scripts have full access to the host filesystem via absolute paths and run with the same permissions as the compiled binary executable.

  • Players: Only run games or binaries from creators you trust.
  • Developers: Never pass unsanitized player input, chat commands, or remote network payloads into functions like require() or System.spawn_vm().