Building and running
Lesson 3.3
Time to see your mod in the game. This lesson shows how to compile it, start the game with it, and where your messages end up.
clight build
Inside your mod’s folder, run:
> clight build
compile main.cpp
link build\bin\my_mod.wasm
packed C:\Projects\my_mod\build\my_mod-1.0.0.clmod (3 files, 930 KiB)
It does three things:
- It compiles every source file in
src/with wasi-sdk’sclang++, for thewasm32-wasip1target, as C++23 with exceptions turned off. - It links them into one WebAssembly module,
build/bin/my_mod.wasm. - It checks the project and packs the manifest,
assets/,datapack/and the.wasmfile intobuild/my_mod-1.0.0.clmod. Yoursrc/folder is not packed: players get the compiled module, not your source code.
The next build only recompiles the files that changed (and the files that include a changed header), so it is fast.
If the compiler finds a mistake, it prints the file, line and column, and the build stops:
compile main.cpp
C:\Projects\my_mod\src\main.cpp:15:13: error: use of undeclared identifier 'playr'; did you mean 'player'?
15 | playr.send_message("Welcome, " + player.name() + "!");
| ^~~~~
| player
C:\Projects\my_mod\src\main.cpp:13:28: note: 'player' declared here
13 | clight::Entity player = event.player;
| ^
1 error generated.
error: compiling main.cpp failed
Read the first error first. Later errors are often caused by the first one. C++ errors about templates can be long; the line that names your file and line is the one to look at.
Debug and release builds
A plain clight build makes a development build: little optimisation and debug information, so errors in the log can name your functions. That is why the first package is almost a megabyte. For the version you publish, build a release:
> clight build --release
compile main.cpp
link build\bin\my_mod.wasm
packed C:\Projects\my_mod\build\my_mod-1.0.0.clmod (3 files, 149 KiB)
It is optimised and much smaller. -v shows the exact compiler commands, which helps when something odd happens.
clight run
> clight run
compile main.cpp
link build\bin\my_mod.wasm
packed C:\Projects\my_mod\build\my_mod-1.0.0.clmod (3 files, 930 KiB)
running C:\Games\Celestial\Celestial.exe with my_mod (C:\Projects\my_mod, hot reload on)
It builds, then starts Celestial in development mode with your project folder loaded next to your normal mods. The game reads the mod straight from the project: the compiled module from build/bin/, and assets/ and datapack/ from where you edit them. Development mode is also stricter: when your code misuses an API function, the log says so.
clight finds the game because the SDK sits in the game’s folder. If you moved the SDK, tell it where the game is with clight run --game C:\Games\Celestial.
Create or open a singleplayer world. When you join, the template mod greets you in chat.
Your installed mods in the mods folder are never touched. While the game runs, your mod’s saved files go to build/run/my_mod/data/, which is kept between runs.
Changing code: rebuild, and the game reloads
While clight run is running, open a second terminal in your project and run clight build after each change. The game notices the new .wasm within a second, loads it next to the old one, and swaps them while you keep playing: your events, commands and keybinds now run the new code. If the new build fails to start (say clight_mod_main returns an error), the game keeps the old one running and the log says why.
Your variables start fresh in the new build, unless you hand them over. For one plain struct that is one line:
struct State { int visitors = 0; int level = 1; };
static State state;
CLIGHT_MOD_HOT_STATE(state) // the struct moves to the new build
Blocks and items you registered keep the old code until you restart the game, because they can’t be registered twice. When something odd happens after many reloads, close the game and run clight run again. clight::core::has("hot_reload.wasm") tells you whether a game can hot-reload WebAssembly.
Textures, models and language files are resources, not code, and the game reads them from your project folder. Change those while the game runs and press F3 + T to reload them, like with resource packs. Recipes and other data-pack files reload with /reload in the world.
The log
Everything your mod writes goes into the game’s log file: logs/latest.log in the Celestial folder. Each line has the time, the level, and your mod’s id in brackets:
[18:04:12.511] [20064/info] [my_mod] my_mod 1.0.0 loaded
Open the file in your editor, or keep it open in a terminal while you play:
> powershell Get-Content C:\Games\Celestial\logs\latest.log -Wait -Tail 20
clight::log
clight::log has one function per level. Each one works like std::format: {} is replaced by the next value.
int rubies = 3;
clight::log::info("Alex has {} rubies", rubies);
// [my_mod] Alex has 3 rubies
clight::log::debug("checking slot {}", 4);
clight::log::warn("config value {} is out of range", "speed");
clight::log::error("could not find the ruby block");
Use warn for things that are odd but not broken, and error for things that are really wrong. debug is for details you only want while developing; the game may hide it. A line can be up to 1023 bytes; longer ones are cut off.
The text inside the quotes is checked when you compile: a {} too many or too few is a compile error, not a surprise in the game. {:.1f} prints a decimal number with one digit after the point, and {:>5} pads to five characters, as in std::format.
All four are built on cl_log(level, text), the C function that takes a ready string.
std::cout and printf work too. What your mod writes to them goes into the same log at the info level. But the standard library may keep that output in a buffer for a while before it reaches the log, so prefer clight::log: it arrives right away.
Building with CMake
You don’t need CMake: clight build does everything. If you like to use CMake (for example because your editor integrates it), the template has a CMakeLists.txt that builds the same module with the same flags:
> cmake -B build/cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=C:/Games/Celestial/clight-sdk/cmake/wasi-sdk.cmake -DCMAKE_BUILD_TYPE=Release
> cmake --build build/cmake
It writes build/bin/my_mod.wasm and packs the .clmod, like clight build. List more source files in clight_add_mod(my_mod SOURCES src/main.cpp src/rubies.cpp).
Try it
Count how often players joined since the game started. Log the count on every join, and send the player a message like “You are visitor number 3.”
Show a solution
#include <clight.hpp>
class VisitorMod final : public clight::Mod {
public:
void on_construct() override {
clight::on<clight::ev::PlayerJoin>([this](cl_ev_player& e) {
clight::Entity player = e.player;
visitors_ = visitors_ + 1;
clight::log::info("visitor {}: {}", visitors_, player.name());
player.send_message("You are visitor number " + std::to_string(visitors_) + ".");
}).detach();
}
private:
int visitors_ = 0;
};
CLIGHT_MOD(VisitorMod)
visitors_ is a member of the mod object, which lives for as long as the game runs, so the lambda captures this and counts on it. After a restart it starts at 0 again. Config and Memory in the sandbox show how to keep values in a file.
Recap: clight build compiles src/ to build/bin/<id>.wasm and packs the mod, clight run starts the game with it, a code change needs a rebuild and a restart, and clight::log::info writes to logs/latest.log.