Worlds
A world package is one folder a game can load and play at once: the place, every thing in it with a name and a position, what can move, what it sounds like, and where the player starts. A maker writes it (from a picture or a sentence); gameable/world reads it. This page is format 1.
The rules
- Metres, y up, right-handed. The origin is where the picture was taken, on the floor.
- Nothing is forced to be points or a mesh. The place and every thing may carry either form or both (
forms), andusesays which a game draws. - Every thing has a label and a place in the world, whether it is its own object (
loose) or part of the place (fixed). - Files are named by a path inside the folder. The engine turns them into asset ids,
<world id>.<thing id>.<part>(forgotten-attic.steamer-trunk.mesh); game code never sees a path. - A reader ignores fields it does not know, and refuses a newer
version. facingandyaware degrees about y, as a turn of a figure whose front is +z: 0 looks along +z, 90 along +x.
The folder
<world id>/
world.json the record below
place/
place.spz the place as points (full)
place.lite.spz a lighter copy for phones (optional)
place.mesh.glb the place as a mesh (optional)
collision.glb what stops a body (or heights.bin: a height map, float32, row by row)
panorama.webp the view all round from the origin: the sky, and the light
things/<thing id>/
thing.glb the thing as a mesh, in metres, origin at the middle of its base
thing.lite.glb a lighter copy for games (optional)
thing.spz the thing as points, lit to match the place (optional)
hull.glb a simple closed shape for collision (optional; else a box from `size`)
impact-1.mp3 ... the sounds it makes when it hits something
sounds/room-1.mp3 ... loops for the placeworld.json
jsonc
{
"version": 1,
"id": "forgotten-attic",
"name": "Forgotten attic",
"about": "A long attic under a pitched timber roof.",
"kind": "room", // "room" (walls all round) | "open" (a field, a street) | "object" (no place: see "A thing by itself")
"place": {
"forms": {
"splat": { "file": "place/place.spz", "lite": "place/place.lite.spz" },
"mesh": { "file": "place/place.mesh.glb" }, // optional
},
"use": "splat",
// The place's own files to metres, floor at y = 0: times `scale`, half a
// turn about x when `flipY`, then up by `floorOffset`.
"fileTransform": { "scale": 1.0856, "flipY": true, "floorOffset": 0.6354 },
"collision": { "shape": "mesh", "file": "place/collision.glb" },
// or { "shape": "heightfield", "file": "place/heights.bin", "columns": 256,
// "rows": 256, "cell": 0.25, "origin": [-32, 0, -32] }
"panorama": "place/panorama.webp",
"spawn": { "position": [0, 0, 0], "facing": 180 },
"eyeHeight": 1.6,
"bounds": { "min": [-3, 0, -5.2], "max": [3, 2.7, 5.1] },
"walk": { "shape": "inside-collision" },
// open: { "shape": "radius", "centre": [0, 0, 0], "radius": 14, "fadeFrom": 11 }
"light": {
// read from the panorama; three's own units
"sun": { "direction": [0.1, -0.5, -0.86], "colour": [1, 0.82, 0.62], "strength": 2.4 },
"ambient": [], // nine red-green-blue terms: a second-order light probe
},
},
"things": [
{
"id": "steamer-trunk",
"label": "steamer trunk",
"kind": "chest",
"about": "Large travel trunk in dark worn leather.",
"state": "loose", // its own object
"placement": { "position": [-2.1, 0, -2.4], "yaw": 35, "scale": 1 },
"size": [1.1, 0.72, 0.68], // width, height, depth
"rests": "floor", // "floor" | "on:<thing id>" | "hanging" | "wall"
"stand": { "position": [-1.3, 0, -1.9], "facing": 215 },
"forms": {
"mesh": {
"file": "things/steamer-trunk/thing.glb",
"lite": "things/steamer-trunk/thing.lite.glb",
},
},
"use": "mesh",
// kind: "dynamic" | "static" | "none"; shape: "box" | "hull" | "mesh"
"body": { "kind": "dynamic", "shape": "hull", "mass": 18, "material": "wood" },
"sounds": { "impact": ["things/steamer-trunk/impact-1.mp3"] },
},
{
"id": "wardrobe",
"label": "wardrobe",
"kind": "wardrobe",
"state": "fixed", // no files: it is in the place and its collision
"box": { "min": [-3, 0, -4.4], "max": [-2.4, 2.1, -2] },
"stand": { "position": [-1.7, 0, -3.2], "facing": 270 },
},
],
"sounds": { "room": ["sounds/room-1.mp3"] },
}What a game gets
- The place drawn, with its collision, its sky and its light, and every loose thing at its spot: a body that can be pushed, with its own sound on a real knock (
packages/world/README.md). - The list of things, loose or fixed: label, kind, box, where to stand, and where it is now. The host asks
engine.get('world'); game code asks the SDK'sthingsfacade (things.find('the trunk'),things.near(point, 2)nearest first,things.standFor(thing)), which answers inside the guest from the list theloadedevent carried. - The start (
things.world.spawn,eyeHeight) and the walk limit (things.limit(x, z, velocity): nothing in a room, a soft edge in the open). world-events inctx.events:loaded,hit(which thing, how hard, 0 to 1),moved(where it is now),unloaded. Commands back:things.push,things.move,things.reset.
At every load, mesh and heightfield worlds with a radius walk limit sample the static floor: an unsupported spawn moves to the nearest supported sample, and poor floor coverage reduces the walk radius; corrections are logged as [world] errors.
A thing as points
Whichever form of the loose things use names, the host can ask for either: engine.get('world').drawThingsAs('splat').
jsonc
"forms": {
"mesh": { "file": "things/steamer-trunk/thing.glb", "lite": "things/steamer-trunk/thing.lite.glb" },
// The points are in the thing's own frame, as its model is: metres, y up, the origin at the
// middle of its base. `points` is how many the file holds; `light: "place"` says they already
// carry the place's light (worked out per point by the maker, for where the thing stands).
"splat": { "file": "things/steamer-trunk/thing.spz", "points": 250000, "light": "place" },
},
"use": "mesh",In a place made of points the thing's points are drawn inside the place's own object, so the two sort together (what stands in front of the thing hides it), and it moves and turns with its body. This is createSplatParts underneath, and it needs WebGPU; on the WebGL fallback a thing's points are an object of their own, drawn over the place's points wherever they stand.
A thing by itself
A world may have no place ("kind": "object"): one thing, made alone, standing in front of the start. Nothing was pictured, so there is no fileTransform, panorama or picture.
jsonc
"kind": "object",
"place": {
"forms": {}, "use": null, // no place: the loader draws a plain world
"background": [1, 1, 1], // its colour, linear: the floor and all round (white when absent)
"collision": { "shape": "plane", "height": 0 }, // a level floor at y = height, without end
"spawn": { "position": [0, 0, 0], "facing": 180 },
"eyeHeight": 1.6,
"walk": { "shape": "radius", "centre": [0, 0, 0], "radius": 17, "fadeFrom": 14 },
"light": { /* as for any place: an even white studio */ },
},
"things": [ /* the thing, loose, along -z from the start, its front toward the player */ ],The loader draws the plain world: the colour all round, a floor that is solid and seen (it takes the thing's shadow from the package's sun), and no horizon or wall: the floor fades into the colour with distance. The thing loads as any loose thing does; its kind is the package's own word (vehicle).
A thing made alone can be heavy; its record says how heavy:
jsonc
"mesh": {
"file": "things/pink-menace/thing.glb", "triangles": 1889260,
"textures": [[4096, 4096], [8192, 8192], [4096, 4096]], // a colour texture may be 8192 a side
"lite": "things/pink-menace/thing.lite.glb", "liteTriangles": 299997,
"liteTextures": [[1024, 1024], [1024, 1024], [1024, 1024]],
},On a desk the full copy is taken, textures of 8192 and all; with lite: true (a phone) the lighter one: fewer triangles when the maker made it so (liteTriangles), textures 1024 a side.
A thing with moving parts
A thing may be whole parts joined at named joints instead of one piece: a bike's wheels, arms and handlebars, a chest's lid. Nothing is bent. Every part is a named node of the thing's own model file, so a reader that ignores parts still draws the thing whole.
jsonc
"parts": [
{ "id": "body", "parent": null, "joint": null, "forms": { "mesh": { "node": "body" } },
"body": { "shape": "hull", "hull": "things/bike/parts/body.hull.glb", "mass": 276 } },
{ "id": "front-wheel", "parent": "front-arm", "joint": "front-wheel-spin", /* ... */ },
],
"joints": [
{ "id": "steering", "kind": "hinge", "parent": "body", "child": "handlebars",
"at": [0, 0.665, 0.514], "axis": [0, 0.894, -0.447], // a point on the axis, and its direction
"limits": [-14, 14], "rest": 0 }, // degrees (a slide: metres); null = turns freely
],
"rider": { "seat": { "part": "body", "position": [0, 0.806, -0.253], "forward": [0, 0, 1], "up": [0, 1, 0] } },
"vehicle": { "kind": "motorbike", "mass": 350, "wheelbase": 1.829, "steeringLimit": 14,
"wheels": [ { "part": "front-wheel", "axle": [0, 0.349, 0.917], "radius": 0.349, "width": 0.332,
"spin": "front-wheel-spin", "travel": "front-travel", "steer": "steering", "driven": false,
"rise": { "up": 0.045, "down": 0.027 } } /* , the rear */ ] },Everything is in the thing's own frame at rest (metres, y up, front +z, so its left is +x): the parts' triangles, a joint's at and axis, the rider's spots, an axle. A joint moves its child and all that hangs from it: a hinge turns them right-handed about the axis, a slide moves them along it. Work down from the root (jointMatrix, orderParts).
The loader draws the thing from its parts and keeps each joint's value: engine.get('world').setJoint(thing, 'steering', 10), jointOf(thing, id). Game code asks the same with things.joint(thing, 'steering', 10); a thing's joints are in its list entry (thing.joints: id, kind, min, max, free, rest, and value, at, axis). A joint with limits that stands somewhere new is told to the game as a joint event (which joint, its value), so value is where it stands now; a wheel on its axle is never told.
A thing with a vehicle of kind motorbike and two wheels drives: its body is its root part's hull, clear of the ground, and its wheels are Jolt's own (createMotorcycle): they hold it up on their springs, lean it into a turn and keep it upright at rest. things.drive(thing, throttle, right, brake) is the rider's wish, held until the next; the wheels' spin, the arms' travel and the steering joint follow the physics every step. thing.drives says so; under way, a game hears moved ten times a second. A wheel may name steerAlso (joints that turn by its angle: handlebars, when the wheel turns in its own arm) and no travel joint (no springing drawn; the physics keeps a centimetre of give). A steering joint with less room than steeringLimit (a wheel in a housing) shows its share of the turn (steerShown); the thing still steers by steeringLimit. Pull and brakes grow with vehicle.mass and wheel radius above a 350 kg bike's on 0.35 m wheels (never less), so a heavy thing gets away and stops as briskly; under throttle it holds its top speed. Its hold of the lean grows with its weight too, so it leans into a turn, never out; vehicle.tread (0.2 to 1, the share of a tyre's width that stands flat) says how far: narrower, further.
A vehicle of kind monowheel, ONE wheel, drives too by things.drive: a great ring the thing stands in, its frame inside.
jsonc
"vehicle": { "kind": "monowheel", "mass": 250, "steeringLimit": 0, "lean": 47,
"wheels": [ { "part": "ring", "spin": "ring-spin", "travel": null, "steer": null, "driven": true /* , axle, radius, width, rise */ } ],
"controls": [ { "joint": "left-lever", "steer": 12, "throttle": 5, "brake": -6 } /* , the right */ ] },Its body is the thing's hull cut clear of the ground at its lean, its wheel cast under its middle (createMonowheel). It keeps level front to back (the frame swings a little under power and brake, then settles), leans into a turn (at most lean degrees; 47 without), stands upright at rest and holds its heading when nothing is asked (MONOWHEEL). The ring's spin joint follows the ground covered.
The parts a rider works. vehicle.controls names joints (a lever, a pedal), on either kind, that follow what is asked: each gives what full right steering, full throttle and full brake add to its rest value, within its limits (controlValue). A grip on such a part goes with it, and the rider's hand.
Where a rider meets it. rider names spots, each on a part: seat, gripLeft, gripRight, footLeft, footRight. The loader makes an object at each, in the scene, under its part, so a grip turns with the handlebars and every spot leans and moves with the thing: engine.get('world').rideOf(thing) gives them (seat, hands, feet, spots by name, and floor, the middle of the thing's base). Each object's +z is the way a character there faces, its +y up, its +x the character's left (a grip's +x runs along its bar; spotFrame). Game code has the same spots in its list entry (thing.rider: each with the joints that move it) and asks where one is now with things.spot(thing, 'gripLeft') (spotOf).
A character rides it. things.getOn(thing, entity) sits a character on those spots and things.getOff(thing) takes it off (the thing command's ride). The host seats it every frame through the character bridge's seat (hand the bridge to createWorldBridge({ characters })), in the pose createSeatedPose of gameable/animation works out from the character's own skeleton (lying back in a seat that leans back, leaning with the thing), so any character whose joints the engine knows rides with no numbers of its own. The entity stays where it was: take its body out first (physics.setEnabled(entity, false)) and move it beside the thing before getting off. Getting on and off are cuts.
In pieces. Parts on break joints are one body until things.breakApart(thing); then each piece (<thing>: <piece>) is its own body (a static one stays put). things.reset mends it. body.hulls: a body of several shapes.
See also
- Load a made world
- Worlds made of several places:
places,backdrop,routes - The wasm boundary — events in, commands out
- Physics
packages/world/README.md— gameable/world