iamar
World creators · Getting started

Build your own
iamar world

A world is data the browser already knows how to run — and, when data is not enough, a small TypeScript script with behaviour: creatures that follow you, zones that react, rules you invent. This page takes you from an empty folder to a hosted world.

World API 0.3 — scripting in TypeScript is new; report problems with F4 in the browser.
What a world is

A package of data, models and one script

A world package is a folder — manifest.json (who made it, budgets, what it uses), scene.json (the world as data: terrain, lights, spawn points, NPCs, quests), your models and textures — packed into one signed .iamar file. Most worlds are data only; a script in scripts/ adds the behaviour the data cannot express.

The full format — every field, every budget, signing and pieces — is WORLD_FORMAT.md. Start there for the package; this page is about the script.

my_world/
  manifest.json     name, version, budgets, features, scripts
  scene.json        the world as data
  assets/*.glb      models (or reuse the built-in library)
  scripts/*.wasm    your compiled world.ts lives here
  world.ts          keep the source with the world

Positions are metres, +Y is up, a standing person is about 1.8 m. A script can do exactly what the API reference lists — no files, no network, no threads — which is what makes it safe for a stranger to visit your world.

The workflow

TypeScript in, hosted world out

You write one file, world.ts. The kit compiles it to a sandboxed WebAssembly script against a template world, and the usual kit commands validate, sign and pack the result. Types for every call are in iamar-world.d.ts — give it to your editor or your agent.

Your script must define on_init (plain global functions, no export). Every other callback (on_enter, on_tick, on_ui, …) is optional; export only the ones you use.

  1. Scaffold a world

    Use any kit scaffold as the template: iamar-world new worlds/my_world --title "My World" --author "<name>". Write your script as world.ts in the world folder.

  2. Write world.ts

    One required function, on_init, plus the callbacks you need. Everything the script may call is the global iamar object from the API 0.3 reference.

  3. Compile it

    kit/iamar-world compile-typescript world.ts <template> <out>

    <template> is a world folder to build against (your scaffold); <out> is where the compiled script and the wired-up world are written. The compiled script lands in scripts/ as WebAssembly.

    Works the same on Windows, macOS and Linux and takes a couple of seconds: it needs Node 20 or newer and the official TypeScript 5.9.3 compiler (npm i [email protected]; on Windows run kit\iamar-world.cmd compile-typescript ...). No C toolchain is involved: your script is packed into a prebuilt, sandboxed engine.

  4. Validate

    compile-typescript already wired the script into the world it wrote: scripts/world.wasm is listed in manifest.json, budgets.script_bytes fits it and "api_version" is current, even when the template was a fresh iamar-world new scaffold. Run iamar-world validate <out>/world and fix every ERROR.

  5. Package and host

    iamar-world package signs and packs the world; iamar-world add-to-browser installs it. Then, in the browser: Esc > Host My World, pick your world, choose who may visit, and share the iamar:// link it shows. SELF_HOSTING.md has the details.

First example

Chickens that follow the player

A complete first script: a floating sign, a warm lamp, three chickens that spawn when a player arrives and walk to them, and one screen button that makes the flock peck. Every call is from the API reference; the comments say which group each belongs to.

Handles (entities) and player ids are plain numbers. player_pos returns [x, y, z, yaw] or null when the player is gone — always check.

// world.ts — compile with:
//   kit/iamar-world compile-typescript world.ts <template> <out>

const BUTTON_CALL = 1;
const chickens: number[] = [];
let hostPlayer = 0;

function on_init(): void {
  // entity + render: a floating sign over the spawn point
  const sign = iamar.entity_spawn("label", 0, 3.2, 0, 0);
  iamar.entity_set_text(sign, "Chickens this way", 0.6);

  // entity + render: a warm lamp
  const lamp = iamar.entity_spawn("light", 2, 3, 2, 0);
  iamar.entity_set_light(lamp, 3.0, 12, 45);
  iamar.entity_set_color(lamp, 1, 0.72, 0.42, 1);

  // ui: one button; its clicks arrive in on_ui
  iamar.ui_button(BUTTON_CALL, "Call the chickens", 0.02, 0.86, 0.3, 0.07);
  iamar.ui_note("Chickens incoming. The button calls them.");
}

function on_enter(player: number): void {
  // players: remember the first arrival; the flock follows them
  if (hostPlayer === 0) hostPlayer = player;
  const p = iamar.player_pos(player);   // [x, y, z, yaw] | null
  if (!p) return;
  for (let i = 0; i < 3; i++) {
    const h = iamar.entity_spawn("chicken", p[0] + 2 + i, p[1], p[2] + i, 0);
    iamar.entity_set_grounded(h, true);
    iamar.entity_play_anim(h, "walk");
    chickens.push(h);
  }
  iamar.log("A player arrived; three chickens spawned.");  // core
}

function on_tick(dt: number): void {
  // about 10 times a second: walk the flock to the player
  if (hostPlayer === 0) return;
  const p = iamar.player_pos(hostPlayer);
  if (!p) return;
  for (const h of chickens) iamar.entity_move_to(h, p[0], p[1], p[2], 2.2);
}

function on_ui(id: number): void {
  if (id !== BUTTON_CALL) return;
  for (const h of chickens) iamar.entity_play_anim(h, "peck");
  iamar.ui_note("The chickens are called. They peck, proudly.");
}

Spawn kinds, animation names and every limit are listed per function in the API reference — entity_spawn alone covers chickens, primitives, lights, labels and your own meshes and .glb assets.

Recipes

One small recipe per group

Each recipe is a fragment to drop into world.ts. Names and signatures are exactly as in iamar-world.d.ts.

Physics — a box you can push

Give a primitive a collider and dynamic physics; a shove is an impulse. Clicking the box (needs a collider) fires on_click.

const crate = iamar.entity_spawn("box", 4, 1, 4, 0);
iamar.entity_set_scale(crate, 0.6, 0.6, 0.6);
iamar.entity_set_collider(crate, 1, 0.6, 0.6, 0.6);  // shape 1 = box
iamar.entity_set_physics(crate, 1, 8);               // mode 1 = dynamic, 8 kg

function on_click(player: number, entity: number): void {
  if (entity !== crate) return;
  const p = iamar.player_pos(player), b = iamar.entity_pos(crate);
  if (!p || !b) return;
  iamar.entity_apply_impulse(crate, (b[0] - p[0]) * 40, 60, (b[2] - p[2]) * 40);
}

Physics — a trigger zone

Any entity can become a zone; entering and leaving fire callbacks.

const gate = iamar.entity_spawn("marker", 0, 1, -6, 0);
iamar.entity_set_trigger(gate, 3);   // 3 m radius; 0 turns it off

function on_trigger_enter(player: number, entity: number): void {
  if (entity === gate) iamar.ui_note("You crossed the gate.");
}

Network — multiplayer with an authority

Every visitor runs their own copy of your script. Exactly one copy is the authority: let it run the shared state and broadcast; the others mirror. In TypeScript the adapter handles the message buffers (iamar_alloc is emitted for you); on_message hands you a Uint8Array.

let score = 0;

function on_tick(dt: number): void {
  if (iamar.is_authority() !== 1) return;  // only the authority simulates
  // ... update shared state, then tell every other copy:
  iamar.world_message("score:" + score);
}

function on_message(data: Uint8Array): void {
  iamar.log("shared update arrived, " + data.length + " bytes");
}

Render — a custom mesh

mesh_create takes interleaved vertices (position, normal, uv — 8 floats each) and triangle indices, and returns a mesh id you spawn as mesh:<id>.

const verts = new Float32Array([
  -1, 0, -1,  0, 1, 0,  0, 0,
   1, 0, -1,  0, 1, 0,  1, 0,
   1, 0,  1,  0, 1, 0,  1, 1,
  -1, 0,  1,  0, 1, 0,  0, 1,
]);
const idx = new Uint32Array([0, 1, 2, 0, 2, 3]);
const mesh = iamar.mesh_create(verts, idx);
const platform = iamar.entity_spawn(`mesh:${mesh}`, 0, 1.5, -4, 0);
iamar.entity_set_color(platform, 0.2, 0.5, 0.45, 1);

Camera — a four-second cutscene

Take the camera, look at the spawn, then give it back on a timer. Esc and the browser menus always hand it back to the player at once.

iamar.camera_set(1, 12, 8, 12, 0, 1, 0);  // from (12,8,12), looking at the spawn
iamar.timer_after(4000, 7);                // time group: on_timer(7) in 4 s

function on_timer(tag: number): void {
  if (tag === 7) iamar.camera_set(0, 0, 0, 0, 0, 0, 0);  // mode 0 = player's camera
}

Input — your own keys

Bind a key (Godot key codes: A–Z are 65–90) and on_input fires on press and release. Esc, Enter, Tab and the F keys stay the browser's.

iamar.input_bind(82, true);   // the R key

function on_input(key: number, pressed: boolean): void {
  if (key === 82 && pressed) iamar.ui_note("R pressed — your move.");
}

More groups to explore in the reference: storage (kv_get / kv_set per-world save data), audio (sound_play), world (ground_y), time (timers), and players (player_name, teleports).