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.c
link build\bin\my_mod.wasm
packed C:\Projects\my_mod\build\my_mod-1.0.0.clmod (3 files, 2 KiB)
It does three things:
- It compiles every
.cfile insrc/with wasi-sdk’s clang, for thewasm32-wasip1target. - 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.c
src/main.c:12:5: error: use of undeclared identifier 'playr'
12 | cl_easy_send_messagef(playr, "Welcome, %s!", name);
| ^
error: compiling main.c failed
Read the first error first. Later errors are often caused by the first one.
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. For the version you publish, build a release:
> clight build --release
It is optimised and smaller. -v shows the exact compiler commands, which helps when something odd happens.
clight run
> clight run
compile main.c
link build\bin\my_mod.wasm
packed C:\Projects\my_mod\build\my_mod-1.0.0.clmod (3 files, 2 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:
typedef struct { int visitors; int level; } State;
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. cl_core_has(CL_STR("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 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
CL_LOG and CL_LOGF
CL_LOG works like printf, and writes one line at the info level:
int rubies = 3;
CL_LOG("Alex has %d rubies", rubies);
// [my_mod] Alex has 3 rubies
CL_LOGF takes the level first:
CL_LOGF(CL_LOG_DEBUG, "checking slot %d", 4);
CL_LOGF(CL_LOG_INFO, "loaded");
CL_LOGF(CL_LOG_WARN, "config value %s is out of range", "speed");
CL_LOGF(CL_LOG_ERROR, "could not find the ruby block");
Use CL_LOG_WARN for things that are odd but not broken, and CL_LOG_ERROR for things that are really wrong. CL_LOG_DEBUG and CL_LOG_TRACE are for details you only want while developing; the game may hide them. A line can be up to 1023 bytes; longer ones are cut off.
Both macros are built on cl_log(level, text), which takes a ready string. You will see that one in the full API.
printf works too. What your mod writes to stdout or stderr goes into the same log at the info level. But the C library may keep printf output in a buffer for a while before it reaches the log, so prefer CL_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.c src/rubies.c).
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_easy.h>
static int visitors = 0;
static void on_player_join(void* payload, void* user) {
(void)user;
cl_entity player = CL_EVENT(cl_ev_player, payload)->player;
char name[64];
cl_easy_player_name(player, name, sizeof name);
visitors = visitors + 1;
CL_LOG("visitor %d: %s", visitors, name);
cl_easy_send_messagef(player, "You are visitor number %d.", visitors);
}
static cl_result mod_main(void) {
return CL_ON(CL_EV_PLAYER_JOIN, on_player_join);
}
CLIGHT_MOD_ENTRY(mod_main)
visitors is static and outside every function, so it keeps its value for as long as the game runs. 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 CL_LOG writes to logs/latest.log.