Status: draft. Written after the first two worlds (Yale SOM Floor 2 and Darul Kebab) and the first browser were built and linked.
The Metaverse Web is the World Wide Web with 3D worlds in place of pages:
| The web | The Metaverse Web |
|---|---|
| A page | A world: a website you walk around in |
| A link | A portal: a doorway you walk through |
A URL, plus #anchor |
A world’s URL, plus ?spawn=<id> |
| The web browser | The Metaverse Browser: carries you, your name and your history between worlds |
The protocol is small on purpose. A world may use any engine (three.js, Babylon, PlayCanvas, Unity WebGL, anything). It only has to do the things in this document. Anyone can build a world, host it anywhere, and link to anyone else’s world, without asking permission and without a central server.
Every world serves a JSON file at .well-known/metaverse.json, relative to the world’s address. For https://example.com/ that is https://example.com/.well-known/metaverse.json; for a world under a path, like https://zlisto.github.io/evans-hall-3d/, it is https://zlisto.github.io/evans-hall-3d/.well-known/metaverse.json.
{
"protocol": "mvw/0.1",
"name": "Darul Kebab",
"description": "A Cambridge restaurant on Mass Ave: tables, a buffet, and the owner holding biryani.",
"spawns": {
"front_door": "Just inside the front door, facing the dining room",
"default": "Same as front_door"
},
"portals": [
{
"id": "to_yale",
"label": "Yale SOM · Evans Hall",
"href": "http://localhost:5173/",
"spawn": "darul_kebab_portal",
"where": "The glass front door on the west wall"
}
]
}
| Field | Required | Meaning |
|---|---|---|
protocol |
yes | Always "mvw/0.1" for this version. |
name |
yes | The world’s name, shown in the browser. |
description |
no | One or two plain sentences: what’s here. Browsers show it, and AI agents read it to decide where to go. |
spawns |
yes | Arrival points, as id → plain-text description. Must include default. |
portals |
no | The world’s outgoing links. href is the target world’s address, spawn is where you arrive there, label names the destination, and where says where the portal is in this world. |
The manifest is to a world what a page’s <head> and its links are to a web page. A browser, a search engine or an agent can map the Metaverse Web by following portals[].href from world to world, the way a web crawler follows links.
Serving it from another site. The browser fetches the manifest from its own origin, so the world’s server must allow cross-origin GET requests for this one file (Access-Control-Allow-Origin: *). GitHub Pages and the Vite dev server already do. If the manifest can’t be read, the world still works; the browser just can’t show its name and portals.
A world’s address is its URL, plus an optional spawn query parameter:
http://localhost:5174/?spawn=front_door
The world starts you at that spawn point, or at default if it’s missing or unknown. This works with or without a browser, so a plain link to a world still works.
A spawn id is a public promise, like a URL: once other worlds link to ?spawn=front_door, keep that id working. Rename the description freely, never the id.
Inside the Metaverse Browser, a world runs in an iframe and talks to the browser with window.postMessage. Every message is a plain object with protocol: "mvw/0.1" and a type.
| Direction | type |
Fields | Meaning |
|---|---|---|---|
| world → browser | ready |
none | The world has loaded and is listening. Send it as early as possible. |
| browser → world | enter |
identity: { name, color } |
Who is visiting. Sent in reply to ready, and again whenever the visitor changes it. color is a CSS hex color. |
| world → browser | presence |
where (text) |
Where the visitor is in the world, in plain words, e.g. "Darul Kebab · at the buffet". Send it when it changes. |
| world → browser | navigate |
href, spawn, back |
The visitor walked through a portal. back is the spawn id in the world being left, so Back returns the visitor next to that portal. |
Rules for browsers.
event.source is that iframe’s window).http: and https: addresses. Treat every field as untrusted text.navigate, the way a web browser decides whether to follow a link.Rules for worlds.
window.parent === window), it follows its own portals by setting location.href to href?spawn=<spawn>.identity is a display name and a color, nothing more.How a portal looks is up to the world. The reference portal is a glowing pink frame with a swirl and a sign naming the destination, sized to fit a doorway.
Every portal should:
These keep travel feeling like one continuous place instead of a page reload.
navigate and the browser fades the screen to black, waits about 0.45 s for the whoosh, and loads the next world. A world opened on its own waits the same 0.45 s and changes location.href. The browser plays its own whoosh only for its own buttons (bookmarks, Back, the address bar).The reference browser (metaverse_browser/) shows what a browser is expected to do:
?spawn=.{ href, spawn } entries. Following a portal rewrites the current entry’s spawn to the portal’s back, so Back lands next to the portal you took.presence, the portals (clickable), and the spawn points (clickable).?world=<address>, so reloading the browser keeps you where you were.?spawn= on load. Always have default.mvw.js into your project. Call presence(where) when the visitor moves to a new area, navigate(href, spawn, back) when they walk through a portal, and onIdentity(fn) to greet them by name. If you use three.js and React, copy Portal.jsx too.public/.well-known/metaverse.json (for Vite; anywhere that serves /.well-known/ for other setups)..nojekyll file, so check that .well-known/metaverse.json actually loads.href is someone else’s world and whose spawn is one of their spawn ids. Send them your address and a spawn id so they can add a portal back to you.To check a world: open it on its own, then open it in the Metaverse Browser. You should see its name in the info panel, your location updating as you walk, and its portals working in both cases.
| What | Live | Code |
|---|---|---|
| Metaverse Browser | https://zlisto.github.io/metaverse_browser/ | zlisto/metaverse_browser |
| Metaverse Square (hub) | https://zlisto.github.io/hub_world/ | zlisto/hub_world |
| Yale SOM, Evans Hall | https://zlisto.github.io/evans-hall-3d/ | zlisto/evans-hall-3d |
| Darul Kebab | https://zlisto.github.io/darul_kebab/ | zlisto/darul_kebab |
| World Starter (template) | https://zlisto.github.io/world_starter/ | zlisto/world_starter |
| Registry of worlds | zlisto/metaverse_registry |
In every world, src/mvw.js is the world-side client and src/Portal.jsx the three.js portal; both are the same file everywhere. src/audio.js (generated music and the whoosh) is optional and not part of the protocol.
To be listed: add your world to the registry by pull request (how); one that adds only your own GitHub Pages world merges automatically once its manifest checks out. Metaverse Square reads the registry live, so your portal appears within minutes. The square’s spawn in front of your portal is by_<your id>, and its portal to you lands at your from_square spawn if you have one, otherwise default.
| Port | What |
|---|---|
| 5174 | Darul Kebab |
| 5175 | The Metaverse Browser |
| 5176 | World Starter |
| 5178 | Metaverse Square |
The protocol field names the version. A browser that sees a version it doesn’t know should still try to show the world, and ignore fields and message types it doesn’t recognize. New fields are added in minor versions (mvw/0.2); anything that breaks old worlds needs a new major version.