# Building an iamar world: instructions for AI agents

You are an AI agent helping your user build a **world** for **iamar**, a 3D
world browser. People visit worlds, walk around, meet each other, talk
(text and voice), take quests, and step through portals into other people's
worlds. Your user tells you what they want; you build it, test it yourself,
and publish it so that anyone they allow can visit.

Read this file first, then [`docs/WORLD_FORMAT.md`](../docs/WORLD_FORMAT.md)
(the package format, the reference for every field) and, if the world needs
behaviour beyond what the data can say, [`docs/WORLD_API.md` section 1](../docs/WORLD_API.md#1-writing-a-script-for-agents)
(world scripts, written in TypeScript and run in a sandbox). Hardware tiers and impostor cards are in
[`docs/WORLD_TIERS.md`](../docs/WORLD_TIERS.md). In a downloaded browser these
three are in `kit/docs/`, next to this file.

## What you are making

A world is a **package**: a folder, later packed into one signed `.iamar` file.

```
my_world/
  manifest.json     who made it, version, budgets, features used
  scene.json        the world, as DATA: environment (sky, sun, fog), terrain,
                    water, placed models, simple shapes, lights, sounds,
                    spawn points, portals, triggers, creatures, NPCs, quests
  assets/*.glb      your 3D models (glTF 2.0 binary)
  textures/  audio/ images and sounds
  scripts/*.wasm    optional behaviour: a TypeScript world.ts built with
                    kit/iamar-world compile-typescript (docs/WORLD_API.md)
```

Most worlds are **data only**: a good `scene.json` plus good models. The
browser already knows how to do terrain, water, sky, lighting, characters
and their animations, NPCs with dialogue, quests with a tracker and rewards,
creatures (chickens, bunnies, ...), portals and multiplayer. You describe;
it runs. Write scripts only for what the data cannot express.

## When using a scoped creator MCP capability

The scoped creator service wraps this kit. Use only the operations and exact
world/workspace, signer, host profile and audience the owner granted. The CLI
examples below do not expand that capability or authorize obtaining keys,
changing an account, uploading content or starting an unrelated host. If a
required operation or configured dependency is unavailable, report the blocker.
Never put a creator credential or private signing key in a world, chat, address,
share link, screenshot or log.

For a requested registration/hosting workflow:

1. Make the real draft/edit, validate the current revision, run preview and review
   its images, then request signing through the exposed tools. Keep the returned
   immutable `artifactRef`. Preview images do not establish playable entry.
2. Call `iamar_world_ready` using the delegated `world`, that `artifactRef` and
   one chosen `intentId`. `activateHost` defaults to `false`: omission/false
   registers without starting a host. Use `true` only when the requested outcome
   includes hosting and the existing grant includes `host.start`. Registration
   still requires a configured authenticated host prepared for that exact artifact.
3. If the result is `preparing`, this ready workflow still needs continuation;
   an earlier registration may already exist. Keep the same world, artifact,
   intent and activation choice. Wait the returned `retryAfterMs`
   before checking again. If `iamar_world_status` is exposed, poll the same world
   and intent; otherwise repeat the same ready request after the delay. Bound
   preparation polling by the original `preparation.expiresAt` and the original
   authority's lifetime. Retain that first deadline when later results omit it or
   show a later deadline; a missing field or new transfer is not renewed authority
   or permission for unlimited retries. A completed MCP tool call describes the
   call, not the world workflow.
4. When status reports `prepared`, repeat the original ready request. A status
   read never registers or activates anything, and there is no background commit
   after the pending response. Keep the original host profile, audience, paired
   host account session and legacy MCP session for this unfinished preparation;
   a fresh session or grant cannot silently adopt its receipt. Modern stateless
   requests have no legacy session, but still require current unchanged authority.
5. `registered/offline` means the persistent address exists and the host is not
   available. If the original `activateHost` was false or omitted, report that
   registration is complete and offline even if `preparation.phase` is `verified`;
   do not turn this into a hosting request. For an original requested and authorized
   `activateHost:true`, a verified preparation marker permits repetition of that
   same ready request. An older server may omit preparation metadata after your
   own `preparing` response: before the original deadline, allow at most one
   identical ready re-evaluation, without treating the offline result as verified
   or starting an unlimited reacquisition loop. Cancellation, expiry or refusal
   can fall back to draft/offline with no terminal record; absence of a marker is
   not a new grant. For a retryable host failure, retain the same intent and
   report/recover the actual dependency before retrying. Do not silently stop,
   re-pair, change audience, obtain credentials or broaden a grant.
6. `available` confirms the exact host serving handshake. Report the returned
   address, audience and actual status; creator entry uses ordinary navigation to
   that registered identity and is a separate step. Remote visitor entry,
   reciprocal movement and direct/relay behavior still need their own evidence.

Reuse the original request after a lost response; never allocate a new intent
on every poll. Stop automatic continuation and report cancellation, expired or
revoked authority, a closed/changed legacy session, stale/conflicting intent,
version conflict, wrong package/audience/profile or another required host action.
Do not retry an unchanged permanent refusal as though it were progress. If the
user requests cancellation, the exposed preparation-cancel tool cancels only the
pending preparation in its granted scope; it preserves registration and does not
stop an already authorized host. Restart preserves the address when the hub uses
its durable JSON store; live receiver, host and room authority must be established
again. The host must remain running for visitors to enter.

## The tools

The complete browser download includes this guide and the format docs, and the
browser executable itself contains the cross-platform kit CLI. Start with
`kit/AGENT_KIT.md` (or
`https://downloads.vessences.com/kit/AGENT_KIT.md`). `--kit where` prints the
local kit/docs when they are beside the browser and the public documentation URL
otherwise. The built-in package commands do not need Godot, a source checkout,
Bash or Python. The optional TypeScript compiler has separate source-tree
requirements (see [TypeScript world scripts](#typescript-world-scripts)).

**On a downloaded iamar browser**, call the browser itself. Put engine options
before `--`, then `--kit`, the command and its arguments after it:

* Linux: `./Oakhaven.x86_64 --headless -- --kit help`
* Windows (PowerShell or cmd): `Oakhaven.exe --headless -- --kit help`
* macOS: `Oakhaven.app/Contents/MacOS/Oakhaven --headless -- --kit help`. The app
  is signed ad hoc, not notarized: after unzipping, clear quarantine once with
  `xattr -dr com.apple.quarantine Oakhaven.app kit`.

`kit/iamar-world` is a thin Linux/macOS convenience wrapper that finds the browser,
chooses headless mode and forwards to the same built-in CLI. `kit\iamar-world.cmd`
remains available on Windows. Both pass the current folder explicitly. For a direct
Windows call, use absolute world/output paths or add `--cwd C:\path\to\workspace`
after the command. (`impostors`, `preview` and `perf` render, so omit `--headless`
and run them in a desktop session; every other command is headless.)

```
iamar-world new worlds/my_world --title "My World" --author "<user's name>"
iamar-world assets search tree oak                  # premade objects on this machine: REUSE BEFORE MODELLING
iamar-world assets use worlds/my_world <hash|name> --as assets/oak.glb   # reference it, nothing is copied
# ... edit scene.json, add models ...
iamar-world validate worlds/my_world                # fix every ERROR, read every WARN
iamar-world preview  worlds/my_world /tmp/my_world  # writes screenshots: LOOK AT THEM
iamar-world perf     worlds/my_world /tmp/my_world  # triangles and draw calls per view
# repeat until it is right, then publish (below)
```

In command examples below, `iamar-world` means either the thin wrapper or the
platform's direct browser form above for commands listed by `--kit help`.
Wrapper-only helpers (`blender`, `blender-run` and the source-only
`compile-typescript`) are not direct `--kit` commands. The package, format and
signing tools are embedded in the browser release. `preview`, `perf` and
`impostors` render, so they need a display; the other built-in commands run
headless.

All kit engine processes explicitly use the Dummy audio driver. This includes
finite `preview`/`perf` screenshot captures: they do not audition world audio or
open an output device. This is a per-process CLI option, not a project or system
setting; ordinary game launches retain their normal audio behavior. Audio-file
validation is still performed, and native errors remain fatal.

**Always look at the screenshots.** `preview` shoots the spawn view, four
views around the world, one from above, and the NPCs, or the views you ask
for with `--view "x,y,z>lx,ly,lz"` (camera position > point looked at). Check
scale (a person is about 1.8 m; doors about 2.2 m high), that nothing floats
or sinks, that the spawn point faces something worth seeing, that textures
are not stretched and that the lighting is not flat or black. Compare with
what your user asked for. Iterate. A world that only validates is not done.
The command fails if every emitted view has zero draws, primitives and objects:
uniform blank images and all-zero counters are a broken preview, never a fast
world or a passing performance result.

### TypeScript world scripts

For behaviour beyond scene data, write one global `world.ts` in **TypeScript**.
Call the API through `iamar.<name>(...)` and declare callbacks as plain global
functions, including `function on_init(): void`. Do not use imports, exports,
`async` or `await`. Read [WORLD_API.md section 1](../docs/WORLD_API.md#1-writing-a-script-for-agents)
and the [showcase source](https://github.com/endsley/iamar/blob/main/examples/typescript-showcase/world.ts).

`compile-typescript` currently requires a **Linux x86_64 source checkout** and
the approved local tools and five `IAMAR_TS_*` variables described in the
[toolchain setup](https://github.com/endsley/iamar/blob/main/tools/typescript-world/README_TOOLCHAIN.md).
Ask before installing missing tools, then set the five absolute-path `export`
lines printed by setup in your build shell; setup does not create an `env.sh`.
This command is not included in downloaded
browsers or the Windows wrapper and cannot be invoked with `--kit`.

From the source repository root, after configuring those dependencies:

```sh
kit/iamar-world compile-typescript \
  examples/typescript-showcase/world.ts \
  examples/typescript-showcase/package \
  /tmp/iamar-showcase-build
kit/iamar-world validate /tmp/iamar-showcase-build/world
```

The output directory must not already exist, including on a rebuild. The
package to validate, preview and later sign/package is `<fresh-out>/world/`,
not the template or the build-output root. Keep its `THIRD_PARTY_NOTICES.txt`.
The sibling `source/`, `guest/` and build receipts stay outside the package.
The compiler copies only the template's `manifest.json` and `scene.json`,
unchanged; it does not copy assets. For your own world, add every referenced
asset to the output `world/` before validation. Start with the showcase manifest:
it declares API 0.3, the `scripts` feature, exactly
`"scripts": [{"path": "scripts/world.wasm"}]`, and the sandbox budgets.
Adjust budgets to cover your actual world and module; compilation does not
update them. A successful build does not execute, sign or host the world.

## The playbook (follow it in this order)

Build every world in these steps. Do not skip a step, and do not move on while a
check fails: fix it with the recipe named (see `recipes/TECHNIQUES.md`).

1. **Plan.** Write down, before building: what the user asked for, the layout
   (a rough map with sizes in metres), where the spawn point faces, the paths
   people walk, and the budget for each area (triangles, draw calls, textures).
2. **Block out** with `primitives` only: ground, walls, paths, the spawn, the
   portals. Give every primitive a short, stable `"id"` (the `new` scaffold
   demonstrates this; IDs make later diagnosis and scripted references unambiguous).
   The complete grammar is `^[a-z0-9][a-z0-9_-]{0,63}$`: 1-64 lowercase ASCII
   letters/digits, `_` or `-`, beginning with a letter/digit. IDs therefore
   cannot contain decimal points; sanitize names derived from coordinates
   (`dock_post_west_2`, not `dock_post_-1.75_-2`).
   Run `validate` and `preview`. Check scale (a person is 1.8 m, doors
   2.2 m high, stairs shallow, walkable slopes under 40 degrees), that the spawn
   is on solid ground facing something good, and that every path is walkable.
3. **Reuse first, then model.** For every object in the plan, search the
   library (`iamar-world assets search <words>`, `assets show <name>`) and
   `assets use` whatever fits: Oakhaven's built-in objects (CC0, every
   visitor already has them) and objects from worlds your user downloaded whose
   creators allow reuse (`LOCKED` ones are not yours to use). A reused object is
   referenced by hash, not copied, so visitors download nothing for it. Model
   from scratch only what nothing in the library fits. `search` and `show` give
   each object's size at scale 1 (`SIZE width x height x depth m`, and `BOTTOM`,
   its lowest point): set `scale` in scene.json from that, a person being 1.8 m.
   Then **model** the rest with Blender through the kit (`iamar-world blender`, then
   `blender-run`; start from `kit/blender/starter_build.py`). Bevel edges, use
   simple materials and atlases, keep each model under its triangle budget,
   and keep the build script with the world. Use `collision: "box"` except
   where people walk. Recipes: `lod_chain`, `merge_static`, `instancing`,
   `collision_proxy`, `texture_sizes`.
4. **Light the world.** When you author static glTF architecture in Blender,
   bake its light as described below and rebake after geometry changes. Primitive-only
   and reused-asset worlds use the browser's live sun/environment because the kit does
   not yet have a general light-bake command. `bake_sky_sun` is a *manual*
   Blender recipe; `live_only_moving` is *planned*. Do not invent scene fields or
   stop a primitive/reused-asset build because no bake pipeline applies; record
   that it stayed live-lit.
5. **Bring it to life:** NPCs, quests, sounds, then scripts only for what the
   data cannot say. Scripts react to events; never loop every frame over many
   objects; stay inside the per-tick budget.
6. **Art pass:** apply the recipes in the *art* section of
   `recipes/TECHNIQUES.md` that you build yourself (`kit: manual`: ground
   clutter, worn paths, lit windows, lived-in interiors, baked AO, ...) unless
   your user says no to one. Recipes marked `kit: automatic` the browser does
   for you; recipes marked `kit: planned` (cloud shadows, per-instance colour,
   ambient creatures, ...) have no field in the world format yet: skip them and
   tell your user they will come. Suggest the manual ones as you go: they are
   what makes a world look finished, and none of them costs much speed.
7. **Make it fast** with recipes available today (`foliage_thinning`, `impostor`,
   `tiny_no_shadow`, `collision_proxy`, `texture_sizes`) until every content
   budget below holds. Recipes marked *planned* such as `occluders` and
   `texture_compression` are not current kit actions: skip and report them.
   `iamar-world techniques <folder>` shows what the browser applies by itself and
   prints ADVICE for each recipe you still have to apply; fix every ADVICE line.
   The browser already instances repeats, merges static pieces that share a
   look, hides fully transparent walk proxies, thins small far things and draws
   the sun's shadows in one 50 m pass. What it cannot do for you: give props one
   shared material (a colour atlas) so they can merge; `"impostor"` on trees and
   houses seen far away; `"min_tier": "medium"` on clutter; no shadows on lamps
   (`"shadows": false`); keep repeated models exactly the same file. `perf`
   numbers count the shadow pass too.
8. **Review like a visitor:** look at every `preview` screenshot from every
   side against what the user asked for. In the browser, walk every path and
   portal and confirm the controls feel immediate (no frame over 50 ms after
   loading). If the build environment cannot open an interactive browser, add
   custom `--view` cameras along each route, review those images, and report
   explicitly that interactive walking/portal travel remains unverified; do not
   silently claim it was walked.
9. **Report** to your user with the numbers (below), then sign, pack and host.

### Hard targets (the low tier: an old GPU like a Radeon HD 5750 with 1 GB)

| Measure (busiest view)   | Target                        |
|--------------------------|-------------------------------|
| Frame rate               | 30 fps or more                |
| Visible triangles        | under 150,000                 |
| Draw calls               | under 150                     |
| Video memory             | under 600 MB                  |
| Any single frame         | under 50 ms once loaded       |
| World scripts            | under 2 ms per frame in total |
| Package download         | as small as the world allows; say the size |

`perf` measures these; `preview` shows the views. Design for this tier first,
then add detail that only better GPUs show. Triangle and draw-call counts describe
the content; VRAM, frame time, script time and FPS also depend on the measured GPU,
CPU and scheduler. Copy the renderer/device line from `perf`, and claim the low-tier
runtime targets only when the run really used comparable low-tier hardware. An
offscreen software-renderer result is useful for correctness and relative
comparisons, but it cannot prove the Radeon-class FPS/frame-time targets. Keep any
reported `FAIL`, include the exact measurement, and mark target-hardware verification
as **not performed**. For a multi-view run, report the lowest `fps=` from its `VIEW`
lines; the final `TARGET fps_here` line currently describes only the last view.

**Hardware tiers** (`docs/WORLD_TIERS.md`): one world carries up to four tiers,
and each visitor's browser opens the best one its GPU can carry. The package's
own files are the **low** tier above, and it must stay pleasant, not just
functional: it is what many visitors see. For richer GPUs declare
`manifest.tiers` (`medium`, `high`, `ultra`, each with bigger `budgets`), make
your models and textures at the top tier's quality, and run
`iamar-world tiers <folder>`: it keeps your originals as `tiers/<top>/...` (from
then on, edit or re-export THOSE, not `assets/`) and makes each lower tier only
as light as its budgets need: textures halved to the tier's `texture_size`,
triangles cut when the tier is over its `triangles` budget (small parts under
600 triangles and models with `collision: "mesh"` keep every triangle). A tier
that would come out identical to the one below stores nothing extra, and if
every tier is the same the originals stay where they were. Tiers are only worth
declaring when they differ: until visitors download only their own tier (not
yet), every variant adds to the package size. `package`/`pack` reruns
`tiers` (not `impostors`). `validate` opens every tier against its own budgets
(its STATS headline and `techniques`, `preview` and `perf` use the low tier
unless you pass `--tier`); report the numbers per tier.
Mark clutter and extra foliage `"min_tier": "medium"` (or higher) so the low tier
stays light, and give big far models `"impostor": <metres>` plus
`iamar-world impostors <folder>` (before `tiers`) so they become cards far away.

### The report you give your user

```
World: <title>   address: iamar://<owner>/<world>
Busiest view: <fps> fps, <tris> triangles, <draws> draw calls, <MB> MB video memory
Worst frame after load: <ms> ms     scripts: <ms> ms per frame
Package: <MB> MB, signed by <name>
Download: <MB> MB new, <MB> MB reused (validate's REUSE line)
Checked: validate (no errors), preview (all sides reviewed), every path and portal walked
Recipes used: <ids>
Measured on: <renderer/device>; low-tier FPS target <verified | not verified>
```

## Recipes: how to engineer a good world

[`recipes/TECHNIQUES.md`](recipes/TECHNIQUES.md) lists every technique iamar
worlds use to look good and stay smooth on every visitor's GPU (LOD chains with
much lower poly far away, impostors, merged and instanced meshes, occluders,
Blender light baking, baked AO, texture compression, budgets, and more): what each
is for, when to use it, the steps and defaults, and how the kit checks it. Apply
them; the kit applies the ones marked *automatic* for you.

## Rules

1. **Self-contained.** Everything the world uses is inside the package, with
   relative paths. Built-in features (NPC bodies and outfits, creature kinds)
   are named in the data; do not copy engine files.
2. **Stay within budgets** (`manifest.json` `budgets`, and the browser's
   hard limits in WORLD_FORMAT.md). Visitors run everything from school
   laptops to gaming PCs. Aim for well under 150k visible triangles and
   150 draw calls: merge small meshes in Blender, reuse one model for many
   placements, use `collision: "box"` except where people walk.
3. **Credit what you use.** Every third-party asset needs its licence and
   author in `manifest.json` `credits`. Prefer your own models or CC0
   sources. Never include anything the user may not redistribute.
4. **Ask your user before installing software** (Blender, compilers). If
   they say no, use what they allow and keep going (see below).
5. **Never put secrets in a world**: no keys, passwords or personal data.
   Everything in a package is public to its visitors.
6. **Portals** go to addresses: `iamar://owner/world` (a hosted world),
   `iamar://owner/world#spawn` (a named arrival point), or `oakhaven` (the
   built-in home world). Give every portal a `label`.

## 3D models and lighting: Blender by default

Blender is the recommended tool: free, scriptable from Python, exports
glTF, and bakes lighting.

1. Check for Blender with `iamar-world blender`; if it is missing, ask your user, then install Blender 4.x (blender.org or their package manager).
2. Build models by writing a Python script and running it headless:
   `iamar-world blender-run tools/build.py -- <world folder>`. Start from
   `kit/blender/starter_build.py` (a cottage and a tree: materials, bevels,
   a triangle budget, glTF export with the right axes into `assets/`). Keep the script in the world's
   folder (e.g. `tools/build.py`, outside `assets/`) so the world can be rebuilt and
   changed later. Model in metres, +Y up, and follow the axis conventions in
   WORLD_FORMAT.md.
3. **Bake lighting** for static architecture (halls, houses, caves): bake
   Cycles light into vertex colours or a lightmap texture on a second UV set,
   then export. Baked light is what makes a place look finished. Moving
   things (characters, creatures, doors) are lit live; do not bake them.
4. Export glTF binary (`.glb`) with `export_apply=True` (modifiers applied),
   into `assets/`. Check the triangle count before exporting.
5. A worked example: the Mission Hall world (`worlds/mission_hall/` and
   `tools/blender/` in the browser's source) is a domed circular hall built
   and baked entirely by a Blender script.

**If the user declines Blender:** build from `primitives` (boxes, cylinders,
spheres, planes with textures) and CC0 glTF kits, use `lights` for mood,
and leave lighting live. Such worlds can still look good; they just take
more care with colour and light.

## NPCs and quests

NPCs (`scene.npcs`, WORLD_FORMAT.md §4.13) use the browser's built-in
bodies and outfits (fast, animated, consistent) or your own glTF
characters. Give each a name, a role, greetings, and somewhere sensible to
stand or walk. Quests (`scene.quests`, §4.14) have a giver, ordered steps and
objectives (`talk`, `goto`, `kill`, `pickup`, `ride`), and rewards. An
objective can point into *another* world by address, so a quest taken in
your world can send players on an errand elsewhere.

## Recipes and gotchas (learned by agents building real worlds)

**Baked lighting that the browser does not light twice.** Bake in Blender
(Cycles, GPU if available) into a texture per object (a second UV set,
`Smart UV Project` is fine), then give the exported material an *unlit*
shader so the baked light is shown as-is: in Blender, a material whose
output is an **Emission or Background shader** fed by the baked image
exports with the glTF `KHR_materials_unlit` extension, which the browser
honours. Bake only static things. Characters, creatures, doors and flames
stay lit live (normal Principled materials). The Mission Hall world's
Blender script (`tools/blender/` in the browser source) is a full example.

**Invisible collision.** `collision: "box"` on a model made of many parts
is one box around all of it: fine for a crate, wrong for a village. For
walkable or walled areas, export a separate simple mesh (floors, walls,
steps; no windows or trim) with a fully transparent material (alpha 0,
alpha mode MASK in the exported glTF; in Blender 4.2+, where *Alpha Clip*
is gone, set the Principled BSDF Alpha to 0 through a Math *Round* node, which
the glTF exporter writes as MASK), place it as its own object with
`collision: "mesh"`. People collide with it; nobody sees it. `techniques`
does not flag flat walk surfaces like this, only tall detailed models with
mesh colliders.

**Built-in NPC looks.** `look.hero` presets: `human_male human_female
elf_female villager_man villager_woman villager_lady villager_dress guard
smith mystic`. `look.hair`: `Hair_SimpleParted Hair_Buzzed Hair_Long
Hair_Buns Hair_BuzzedFemale` (or `""` for none); `look.beard`: `Hair_Beard`
or `""`. `hair_color` and `skin` are bare hex colours without `#`
(`"c8c2b8"`, unlike every other colour field); `greeting` is a list of lines
(one is picked each time). Outfit items are listed in
WORLD_FORMAT.md 4.13. NPCs face along `yaw` in degrees (0 looks toward -Z, 90 toward -X,
180 toward +Z); to face a point, use yaw = degrees(atan2(-(px - x), -(pz - z))).

**Quests.** Handing in to the giver is automatic when the last step's
objectives are done (`turn_in` only when someone else takes it back), so do
not add a final "return to giver" talk step. Pickup `look` ids are
`horseshoe jelly lily book iron herb`; pick the closest. `preview` does not
draw pickups or quest markers yet, so check pickup `spots` against a
screenshot from above.

**Fog.** `fog` takes `enabled`, `color`, `density` (0.002-0.01 is a light
haze), and `height` / `height_density` for mist lying in low ground. A sunset
sky plus light fog reads well.

**Lights and creatures.** Light `type` is `point`, `spot` or `directional`.
Creature `kind` is `chicken`, `bunny`, `cow` or `horse` for now (`duck`,
`fish`, `frog`, `turtle` and the slimes are reserved: accepted, not drawn yet).

**Water.** Water surfaces are not swimmable in this version (visitors walk
on the bed); put water where people will not wander in, or fence it with a
dock or reeds.

**Arriving in Oakhaven.** A portal home can target `oakhaven` or
`oakhaven#default_arrival` (the town square). Named arrival points for your
own world in Oakhaven are not available yet.

**Keys.** `keygen <name>` writes
`$XDG_CONFIG_HOME/iamar/signing/<name>.key` on Linux/macOS (defaulting to
`~/.config/iamar/signing/<name>.key`) and
`%APPDATA%\iamar\signing\<name>.key` on Windows;
`keygen <path/to/file.key>` writes exactly there. It also writes
`<key>.pub`. Keep the `.key` private and out of the world folder.

## Publishing: host it yourself

For now every world is hosted by its owner, from their own computer (paid
hosting comes later). The iamar browser does the hosting itself: no second
program, no router or port-forwarding setup.

```
iamar-world keygen <your-name>          # once: your signing key (~/.config/iamar/signing)
iamar-world sign worlds/my_world ~/.config/iamar/signing/<name>.key "<Name>"
iamar-world verify worlds/my_world      # optional pre-pack integrity check
iamar-world package worlds/my_world out/my_world-0.1.0.iamar ~/.config/iamar/signing/<name>.key "<Name>"
iamar-world verify out/my_world-0.1.0.iamar
iamar-world add-to-browser out/my_world-0.1.0.iamar
```

`package` (also accepted as `pack`) rebuilds declared lower hardware tiers, signs
the folder again and writes the `.iamar` plus signed piece list. The standalone `sign`/folder
`verify` pair is an explicit inspection step, not a second required signature.

Then tell your user: **in iamar, press Esc -> Host My World, pick the world,
choose who may visit, and press Start Hosting** (keyboard, mouse or the pad's
A button). They must be logged in to iamar; their account owns the address.

* `package` signs the package and splits it into pieces with a signed piece
  list. Visitors check every piece and reject a world whose signature does
  not match; the browser shows who signed it and asks the visitor before
  entering a world signed by a key it has not seen.
* `add-to-browser` verifies the package and copies it (and only it) into the
  browser's worlds folder (`user://my_worlds`; `IAMAR_MY_WORLDS` adds more
  folders). Hosting serves nothing but that package's piece list and pieces.
* Start Hosting registers `iamar://<account>/<world id>` and the world's
  free public link `iamar://<13 characters>`, shown in the panel with a Copy
  button: that link is what your user shares. The world is online exactly
  while the browser hosts it (it keeps hosting while they play elsewhere, and
  stops when they quit or press Stop Hosting). The panel shows visitors and
  what has been sent; Upload caps what it sends.
* Visitors reach a home computer through the hub relay (the host's one
  WebSocket to the hub carries its pieces; the hub checks the signed list and
  every piece). Visitors who already have the world share it with newcomers.
* Who may visit: everyone (public, listed in the directory; the default), the
  owner's buddies, or only the owner. Bump `version` in the manifest for every
  republish, pack again, add-to-browser again, and host the new version.
* On an always-on server instead of the browser:
  `iamar-world serve --package=out/my_world-0.1.0.iamar --hub=wss://hub.vessences.com --user=<account> --password-file=<file> [--access=public] [--port=0]`
  (`--port=0` serves through the hub relay only; a reachable `--port` also
  serves directly).

## Before you say you are done

- [ ] `validate` shows no errors, and you have read every warning.
- [ ] You looked at the `preview` screenshots from every side and they match
      what the user asked for.
- [ ] `perf`: within budget in the busiest view.
- [ ] The spawn point is on solid ground, facing something good.
- [ ] Every portal has a label and a valid address; there is a way home.
- [ ] Credits are complete; no secrets in the package.
- [ ] The build script for your models is kept with the world.
- [ ] You added it to the browser (`add-to-browser`) and told the user how to host it (Esc -> Host My World) and that its link appears there.
