<m-model src="/assets/museum.glb" x="-20" z="2" sx="20" sy="20" sz="20"></m-model>
<m-light type="point" y="10" intensity="100"></m-light>
<m-cube id="cube1" color="red" x="-3" y="1.5" z="-5">
<m-attr-anim attr="ry" start="0" end="360" duration="5000"></m-attr-anim>
</m-cube>
<m-cube id="cube2" color="green" x="3" y="1.5" z="-5">
<m-attr-anim attr="ry" start="0" end="360" duration="5000"></m-attr-anim>
</m-cube>
<m-group id="stairs" ry="180" y="0.1"></m-group>
<script>
const colors = ["red", "green", "blue", "orange", "purple", "cyan", "yellow"];
document.querySelectorAll("#cube1, #cube2").forEach(cube => {
cube.addEventListener("click", () => {
cube.setAttribute("color", colors[Math.floor(Math.random() * colors.length)]);
});
});
const stairs = document.getElementById("stairs");
function getHexForCurrentTime(lightness) {
const hue = ((Date.now() % 2000)/2000) * 360;
const saturation = 1.0;
const alpha = saturation * Math.min(lightness, 1 - lightness);
const getF = number => {
const k = (number + hue / 30) % 12;
return lightness - alpha * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)))
};
const red = Math.round(255 * getF(0));
const green = Math.round(255 * getF(8));
const blue = Math.round(255 * getF(4));
const hex = "#"+(red.toString(16).padStart(2, "0"))+(green.toString(16).padStart(2, "0"))+(blue.toString(16).padStart(2, "0"));
return hex;
}
for (let i = 0; i < 15; i++) {
const stair = document.createElement("m-cube");
stair.setAttribute("z", i * 0.5);
stair.setAttribute("y", i * 0.2);
stair.setAttribute("width", 2);
stair.setAttribute("height", 0.2);
stair.setAttribute("depth", 0.5);
stair.setAttribute("color", "blue");
stair.setAttribute("collision-interval","1000");
stair.addEventListener("collisionstart", () => {
stair.setAttribute("color", "white");
});
stair.addEventListener("collisionend", () => {
stair.setAttribute("color", getHexForCurrentTime(0.5));
});
stairs.append(stair);
}
</script>WASD to move. Click cubes to change color. Edit the MML on the left. This demo runs entirely in your browser — the same library that powers networked multiplayer servers also works standalone, with no backend required.
Two independent networking systems run in parallel: DeltaNet for real-time multiplayer presence, and the Networked DOM for synchronized MML content.
The server orchestrates two distinct channels. DeltaNet handles high-frequency position and state updates for every connected user. The Networked DOM synchronizes MML document state — interactive 3D content that all users see and can manipulate together.
Rather than sending absolute positions each tick, DeltaNet encodes the change in change — second-order deltas that are almost always zero for moving users. These tiny values pack into single bytes and compress well, achieving a 20:1 reduction over the wire.
Drop MML documents in mml-documents/ and add them to the world.json file to add interactive 3D content. Examples on GitHub