🧩Jik also transpiles to C. Its types do not line up with the null0 ABI, so the bindings are a set of extern declarations and C wrappers you copy into your cart.
- Template: cart_jik - press "Use this template" and you have a working game.
- Docker image:
ghcr.io/notnullgames/null0-cart-jik:latest(linux/amd64 only - runs emulated on Apple Silicon) - Your code:
cart/main.jik, compiled with jik (transpiles to C) + wasi-sdk - Bindings:
carts/jik/null0.jik(a reference list to copy from) - WASI is available, so ordinary file and stdio calls work.
- See carts made with Jik
- API docs for Jik
building
You do not install jik (transpiles to C) + wasi-sdk - the docker image has it, and the current null0 bindings, baked in. There is nothing in your project to keep in sync with the engine.
docker run --rm --user $(id -u):$(id -g) --platform linux/amd64 \
-v ./cart:/src -v ./webroot:/out \
ghcr.io/notnullgames/null0-cart-jik:latest mygameThe template wraps that in npm start, which also serves the result and rebuilds when you save.
Jik notes
- Jik's types don't line up with the null0 ABI, so null0.jik is a set of
externdeclarations and C wrappers to copy into your cart's@embedblock. - Jik has no string type at all, so the 25 functions taking text cannot be declared - keep the literal inside the C wrapper.
a cart, in Jik
This is carts/jik/simple/main.jik in the engine repo - the same file CI builds into the cart below, so it always compiles.
// simple jik cart for null0
//
// Jik has no u8/f32/uint, no pointer types and no string type, so it cannot
// name null0's Color (four bytes) or pass a char*. The pattern is a small C
// wrapper in an @embed block that builds those and calls through - see
// carts/jik/null0.jik for the full list of declarations to copy from.
//
// That's why the text lives in C here: `text: *char` and `text: string` are
// both rejected by the jik parser, so the literal can't cross the boundary.
@embed{C_END}
#include "null0.h"
static void jik_clear(int r, int g, int b, int a) {
Color c = { (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a };
clear(c);
}
static void jik_draw_circ(int cx, int cy, int rad, int r, int g, int b, int a) {
Color c = { (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a };
draw_circle(cx, cy, rad, c);
}
static void jik_draw_hello(int x, int y, int r, int g, int b, int a) {
Color c = { (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a };
draw_text(FONT_DEFAULT, "hello from Jik", x, y, c);
}
C_END
extern func jik_clear as clear(r: int, g: int, b: int, a: int) -> void
extern func jik_draw_circ as draw_circle(cx: int, cy: int, rad: int, r: int, g: int, b: int, a: int) -> void
extern func jik_draw_hello as draw_hello(x: int, y: int, r: int, g: int, b: int, a: int) -> void
func load():
clear(0, 121, 241, 255)
draw_circle(100, 100, 50, 230, 41, 55, 255)
draw_hello(170, 92, 255, 255, 255, 255)
end
// jik needs a main, even though null0 never calls it
func main():
end
carts/jik/simple/main.jikcallbacks
A cart implements the callbacks it cares about. In Jik they look like this:
export func update() -> voidSee anatomy of a cart for the full list and what each one is passed.