Jik

  • 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 mygame

The 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 extern declarations and C wrappers to copy into your cart's @embed block.
  • 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.jik

callbacks

A cart implements the callbacks it cares about. In Jik they look like this:

export func update() -> void

See anatomy of a cart for the full list and what each one is passed.