Git Worlds

Fork a template, edit your world, push to deploy. No engine, no SDK — just Git.

Build Your First GitWorld

A step-by-step guide to creating, customizing, and deploying your own virtual world using the GitWorld-Starter template. No game engine required — just Git, a text editor, and a GitHub account.


Table of Contents

  1. What Is a GitWorld?
  2. Prerequisites
  3. Create Your World Repository
  4. Enable GitHub Pages
  5. Visit Your World
  6. Project Structure
  7. Understanding VEML
  8. Your First Edit: Customize the World
  9. Adding Entities
  10. Transforms and the Coordinate System
  11. Lighting and Atmosphere
  12. JavaScript Scripting
  13. HTML Panels (UI Overlays)
  14. Using 3D Models
  15. Multiplayer with WorldSync
  16. Input Events and Control Flags
  17. Troubleshooting
  18. Quick Reference

1. What Is a GitWorld?

A GitWorld is a virtual 3D world defined entirely in text files and hosted as a static website. Think of it as a webpage, but instead of HTML rendering a 2D page, VEML (Virtual Environment Markup Language) renders a 3D environment.

The core idea: worlds as websites.

  • Your world is a Git repository
  • World content is defined in XML (VEML)
  • Interactivity comes from JavaScript
  • UI overlays are standard HTML/CSS
  • Pushing to main deploys your world to GitHub Pages
  • Anyone with the URL can visit your world in a browser — no downloads, no installs

When you push a change, a GitHub Actions workflow downloads the WebVerse runtime (a WebGL-based world browser), bundles it with your world content, and deploys everything to GitHub Pages. The runtime never enters your git history, so your repo stays small.


2. Prerequisites

You need:

  • A GitHub account (free tier works)
  • A text editor (VS Code, Sublime, Notepad — anything)
  • Basic familiarity with Git (commit, push)

That's it. No game engine, no build tools, no SDKs.


3. Create Your World Repository

  1. Go to github.com/Five-Squared-Interactive/GitWorld-Starter
  2. Click the green "Use this template" button (or Fork if you prefer)
  3. Name your repository (e.g., my-world)
  4. Set it to Public (required for GitHub Pages on free accounts)
  5. Click Create repository

You now have your own copy of the starter world.


4. Enable GitHub Pages

This step is critical — without it, the deploy workflow has nowhere to publish.

  1. In your new repository, go to Settings (gear icon in the top bar)
  2. In the left sidebar, click Pages
  3. Under Source, select GitHub Actions (not "Deploy from a branch")
  4. Click Save

Important: You must select "GitHub Actions" as the source. The default "Deploy from a branch" option will not work because the workflow uses the Pages artifact API.


5. Visit Your World

The deploy workflow runs automatically when you push to main. Since creating the repo from the template counts as the first push, it should already be running.

  1. Go to the Actions tab in your repository
  2. You should see a workflow run called "Deploy World to GitHub Pages"
  3. Wait for it to complete (usually about 30 seconds)
  4. Visit: https://YOUR-USERNAME.github.io/YOUR-REPO-NAME/

You should see a 3D world with a green ground plane, a stone spawn platform, a log cabin, pine/oak/birch trees, a campfire, a pond with a dock, lantern posts, rocks, bushes, and a welcome panel. Use WASD to move and the mouse to look around.

If you see a loading screen that gets stuck, check Troubleshooting.


6. Project Structure

your-repo/
  world.veml                  # The world definition (entities, sky, lighting)
  scripts/
    init.js                   # Player character setup
    bridge.js                 # HTML panel <-> world engine messaging
    lib/
      thirdpersoncharacter.js # Character controller library
  panels/
    welcome.html              # Screen-space UI overlay
  index.html                  # WebGL runtime loader page
  .github/workflows/
    deploy.yml                # CI: downloads runtime + deploys to Pages
FileWhat It DoesWhen to Edit
world.vemlDefines everything visible in the worldAdding/removing/moving objects
scripts/init.jsCreates the player characterChanging spawn position, movement speed
scripts/bridge.jsRoutes messages between HTML panels and the worldAdding panel interactions
panels/welcome.htmlThe welcome overlay UICustomizing the HUD
index.htmlLoads the WebGL runtimeRarely — only to change the loading screen
deploy.ymlGitHub Actions workflowRarely — only for advanced deployment changes

7. Understanding VEML

VEML (Virtual Environment Markup Language) is an XML format that describes a 3D environment. If you've written HTML, you already know the basics — it's elements with attributes and children.

Document Structure

Every VEML file follows this structure:

<?xml version="1.0" encoding="UTF-8"?>
<veml xmlns="http://www.fivesqd.com/schemas/veml/3.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata>
    <!-- Title, scripts, input events, multiplayer config -->
  </metadata>
  <environment>
    <!-- Background, lighting, entities (objects in the world) -->
  </environment>
</veml>
  • The root element is always <veml> (not <world>)
  • <metadata> contains configuration: title, script references, input bindings, multiplayer settings
  • <environment> contains everything visible: sky, lights, objects, UI panels

Entities

Everything in the world is an entity. Entities have:

  • A type (xsi:type) — what kind of object it is
  • A tag — a human-readable label for grouping/finding entities
  • An id — a unique identifier (must be a valid UUID)
  • A transform — position, rotation, and scale in 3D space
<entity xsi:type="cubemesh" tag="my-box" id="a1b2c3d4-e5f6-7890-abcd-ef1234567890">
  <transform xsi:type="scaletransform">
    <position x="5" y="1" z="5" />
    <rotation x="0" y="0" z="0" w="1" />
    <scale x="2" y="2" z="2" />
  </transform>
</entity>

Important: Entity id values must be valid UUIDs (e.g., a1b2c3d4-e5f6-7890-abcd-ef1234567890). Human-readable IDs like my-box will cause a runtime error. Generate UUIDs at uuidgenerator.net or use your editor's UUID plugin.


8. Your First Edit: Customize the World

Let's make three changes to see immediate results.

Change the World Title

Open world.veml and find the <title> element:

<title>The Overlook</title>

Change it to your world's name:

<title>My First World</title>

Change the Sky Color

Find the <lite-procedural-sky> element and change day-sky-color:

<lite-procedural-sky sun-entity-tag="sun"
                     day-night-cycle-enabled="false"
                     sun-enabled="true" sun-diameter="3"
                     ground-enabled="true" ground-color="#3a5a2a"
                     day-horizon-color="#e8a87c" day-sky-color="#4a0080"
                     clouds-enabled="true" stars-enabled="true"
                     moon-enabled="true" />

Try #4a0080 for a deep purple sky, or #ff6b35 for a sunset orange.

Change the Ground Color

Find the ground entity (the one with tag="ground") and update the init script to set its color. While <color> tags appear in the VEML, primitive colors are most reliably set via JavaScript. Add this to scripts/init.js after the character creation:

// Set ground color after a short delay to ensure entities are loaded
Time.SetTimeout(function() {
    var ground = MeshEntity.Get("ground");
    if (ground != null) {
        ground.SetColor("#2d5a1e");  // Dark forest green
    }
}, 2);

Deploy Your Changes

git add -A
git commit -m "Customize my world"
git push

Wait about 30 seconds, then refresh your world URL. You should see your new sky color and title.


9. Adding Entities

Primitive Types

VEML includes built-in primitive shapes:

TypeShapeNotes
cubemeshBox1x1x1 base size
spheremeshSphere1m diameter
cylindermeshCylinder1m diameter, 1m tall
conemeshCone1m diameter base
planemeshFlat plane1x1, faces up (Y+)
capsuleCapsuleCylinder with rounded ends

Adding a New Entity

To add a red floating cube above the platform, add this inside <environment>:

<!-- A floating cube above the spawn platform -->
<entity xsi:type="cubemesh" tag="floating-box" id="b7e3f1a2-8c4d-4e5f-9a6b-1c2d3e4f5a6b">
  <transform xsi:type="scaletransform">
    <position x="0" y="3" z="0" />
    <rotation x="0" y="0.383" z="0" w="0.924" />
    <scale x="1" y="1" z="1" />
  </transform>
</entity>

Then set its color via JavaScript. Add to scripts/init.js:

Time.SetTimeout(function() {
    var box = MeshEntity.Get("floating-box");
    if (box != null) {
        box.SetColor("#FF5722");  // Deep orange
        box.SetVisibility(true, true);
    }
}, 2);

Entity Visibility

Entities created via VEML with primitive types may need SetVisibility(true, true) called from JavaScript to appear. The first true makes the entity visible; the second true applies to child objects. This is a known quirk of the runtime — always call it in your init script for entities you want visible.


10. Transforms and the Coordinate System

Coordinate System

WebVerse uses a left-handed Y-up coordinate system:

  • X = left/right
  • Y = up/down
  • Z = forward/backward
  • 1 unit = 1 meter

Position

Where the entity is in the world:

<position x="5" y="2" z="10" />

This places the entity 5 meters right, 2 meters up, and 10 meters forward from the origin.

Scale

How large the entity is. All primitives have a base size of 1x1x1, so scale maps directly to world meters:

<scale x="3" y="0.5" z="3" />

This creates an object 3 meters wide, 0.5 meters tall, and 3 meters deep.

Rotation (Quaternions)

Rotations use quaternions — four values (x, y, z, w). If you're not familiar with quaternions, use this cheat sheet:

Rotationxyzw
No rotation (identity)0001
90 degrees around Y (turn right)00.70700.707
180 degrees around Y (face backward)0100
45 degrees around Y00.38300.924
90 degrees around X (tilt forward)0.707000.707
30 degrees around Y00.25900.966

Tip: Search for "quaternion calculator" online to convert from the Euler angles (degrees) you're used to.


11. Lighting and Atmosphere

The Procedural Sky

The <lite-procedural-sky> element creates a dynamic skybox:

<background>
  <lite-procedural-sky
    sun-entity-tag="sun"
    day-night-cycle-enabled="false"
    sun-enabled="true"
    sun-diameter="3"
    ground-enabled="true"
    ground-color="#3a5a2a"
    day-horizon-color="#e8a87c"
    day-sky-color="#1a5276"
    clouds-enabled="true"
    stars-enabled="true"
    moon-enabled="true" />
</background>
AttributeWhat It Does
sun-entity-tagLinks to a light entity for sun direction
day-night-cycle-enabledEnables automatic day/night transitions
day-sky-colorMain sky color (hex)
day-horizon-colorColor near the horizon
ground-colorColor of the ground reflection in the sky
clouds-enabledShow procedural clouds
stars-enabledShow stars (visible at night or dark sky)
moon-enabledShow a moon

Fog

Add atmospheric fog:

<effects>
  <lite-fog fog-enabled="true" color="#c8b89a" density="0.004" />
</effects>

Lower density (e.g., 0.001) = subtle haze. Higher density (e.g., 0.02) = thick fog.

Directional Light (Sun)

The light entity controls the direction of sunlight and shadows. Its rotation determines the sun angle:

<entity xsi:type="light" tag="sun" id="YOUR-UUID-HERE">
  <transform xsi:type="scaletransform">
    <position x="0" y="75" z="0" />
    <rotation x="0.4177" y="-0.2506" z="0" w="0.8733" />
    <scale x="1" y="1" z="1" />
  </transform>
</entity>

The position doesn't matter much for directional lights — the rotation is what controls where the light comes from.


12. JavaScript Scripting

WebVerse includes an embedded JavaScript engine (JINT) that runs scripts declared in the VEML metadata. This is how you add interactivity to your world.

How Scripts Load

Scripts are declared in <metadata> and execute in order:

<script>scripts/lib/thirdpersoncharacter.js</script>
<script>scripts/init.js</script>
<script>scripts/bridge.js</script>

The first script loads the character controller library, the second creates the player, and the third handles UI panel messaging.

Available APIs

The JavaScript runtime provides access to:

APIWhat It Does
Entity.Get(tagOrId)Find an entity by tag or ID
MeshEntity.Get(tag)Get a mesh entity for color/material changes
MeshEntity.Create(...)Create a new mesh entity
CharacterEntity.Create(...)Create a player character
HTMLEntity.Get(tag)Get an HTML panel entity
Camera.SetPosition(vec3, relative)Move the camera
Time.SetTimeout(fn, seconds)Delayed execution
Time.SetInterval(code, seconds)Repeated execution
Logging.Log(message)Console logging
UUID.NewUUID()Generate a new UUID
HTTPNetworking.Request(...)Make HTTP requests
Context.DefineContext(name, obj)Store persistent state
Context.GetContext(name)Retrieve persistent state

Example: Making an Entity Spin

Add a new script file scripts/spinner.js:

// Spin an entity continuously
var spinAngle = 0;

Time.SetInterval(function() {
    spinAngle += 2;
    var radians = spinAngle * (Math.PI / 180);
    var entity = Entity.Get("floating-box");
    if (entity != null) {
        var sin = Math.sin(radians / 2);
        var cos = Math.cos(radians / 2);
        // Rotate around Y axis
        entity.SetRotation(new Quaternion(0, sin, 0, cos), false);
    }
}.toString(), 0.016);  // ~60 FPS

Then add it to your VEML metadata:

<script>scripts/spinner.js</script>

Example: Entity Interaction via JavaScript

// Look up an entity by tag and change its color
Time.SetTimeout(function() {
    var platform = MeshEntity.Get("platform");
    if (platform != null) {
        platform.SetColor("#FFD700");      // Gild the spawn platform
        platform.SetVisibility(true, true);
        platform.SetInteractionState(InteractionState.Static);
    }
}, 2);

The Context System

Since JINT doesn't persist variables across SetInterval calls the same way a browser would, use the Context system to store state:

// Store state
Context.DefineContext("myState", { counter: 0, active: true });

// Retrieve it later
var state = Context.GetContext("myState");
state.counter++;
Context.DefineContext("myState", state);

13. HTML Panels (UI Overlays)

HTML panels are standard web pages rendered as overlays on top of the 3D world. They're perfect for HUDs, menus, settings, and information displays.

Declaring a Panel in VEML

<entity xsi:type="html" tag="my-panel" id="YOUR-UUID-HERE"
        url="panels/my-panel.html"
        on-message="OnMyPanelMessage(?)">
  <transform xsi:type="canvastransform">
    <position-percent x="0.02" y="0.02" />
    <size-percent x="0.25" y="0.3" />
  </transform>
</entity>
  • url — path to the HTML file (relative to the repo root)
  • on-message — JavaScript function that receives messages from the panel
  • position-percent — top-left corner as a fraction of the viewport (0.0 to 1.0)
  • size-percent — width and height as a fraction of the viewport

The Vuplex Bridge

Panels communicate with the world engine through the Vuplex bridge — a message-passing system.

Panel side (HTML/JS):

// Send a message to the world engine
function sendToWorld(data) {
    var msg = JSON.stringify(data);
    if (window.vuplex) {
        window.vuplex.postMessage(msg);
    }
}

// Listen for messages from the world engine
if (window.vuplex) {
    window.vuplex.addEventListener('message', function(event) {
        var data = JSON.parse(event.data);
        // Handle message from world
    });
}

World side (JINT script):

function OnMyPanelMessage(message) {
    var data = JSON.parse(message);

    if (data.type === "button-clicked") {
        Logging.Log("Panel button was clicked!");
        // Send data back to the panel
        var panel = HTMLEntity.Get("my-panel");
        if (panel != null) {
            panel.SendMessage(JSON.stringify({
                type: "update",
                score: 42
            }));
        }
    }
}

Creating a Custom Panel

  1. Create panels/my-panel.html with standard HTML/CSS/JS
  2. Add the Vuplex bridge code for communication
  3. Add the <entity xsi:type="html"> to your VEML
  4. Add a message handler function in a script file
  5. Reference the handler in the on-message attribute

14. Using 3D Models

For realistic objects, use glTF/GLB 3D models instead of primitives.

Mesh Entity Syntax

<entity xsi:type="mesh" tag="tree" id="YOUR-UUID-HERE">
  <transform xsi:type="scaletransform">
    <position x="-8" y="0" z="6" />
    <rotation x="0" y="0" z="0" w="1" />
    <scale x="1" y="1" z="1" />
  </transform>
  <mesh-name>models/oak_tree.glb</mesh-name>
  <mesh-resource>models/oak_tree.glb</mesh-resource>
</entity>
  • mesh-name — the GLB/glTF file the runtime loads. Either a path relative to the world (like the bundled models/ scenery) or an absolute HTTPS URL.
  • mesh-resource — additional files the model needs (a .gltf's .bin and texture files). For a self-contained .glb, repeat the same path here — the element is required by the schema.

This template ships its scenery in the models/ directory (cabin, trees, rocks, campfire, dock, lantern post, bench, fence, bush, stepping stone) — copy any mesh entity in world.veml and change its position/rotation to reuse them. The deploy workflow publishes models/ alongside the world, so relative paths just work.

Hosting Models Elsewhere

Models can also live on any host that serves with CORS headers (Access-Control-Allow-Origin: *): a CDN, GitHub Releases, or your own server. Use the full HTTPS URL as mesh-name in that case.

Finding Free 3D Models

Make sure to download in glTF or GLB format. Other formats (FBX, OBJ) are not supported.


15. Multiplayer with WorldSync

GitWorlds supports real-time multiplayer through WorldSync, a synchronization service built on MQTT. When multiplayer is enabled, players in the same world can see each other's avatars and interact.

How It Works

  1. A WorldSync session is created on an MQTT broker
  2. Your world's VEML references the session via a sync ID and broker URL
  3. When players load your world, the WebVerse runtime connects to the broker
  4. Player positions, rotations, and entity states are synchronized in real-time

Option A: Automatic Registration (GitWorlds Service)

The deploy workflow automatically registers your world with the GitWorlds service when you push. If the service is reachable, it:

  1. Calls POST /register to gitworlds.worldhub.me
  2. Receives a sync ID and broker URL
  3. Injects them into your deployed world.veml

This happens transparently — you don't need to configure anything. The REPLACE-WITH-YOUR-UUID placeholder in the template's VEML is replaced automatically.

Note: If the GitWorlds service is unreachable during deploy, multiplayer will be silently disabled. Your world still works, just without sync.

Option B: WorldHub Sessions (Manual Control)

If you want more control over your multiplayer sessions — for example, naming sessions, reusing them across worlds, or managing quota — you can create sessions through WorldHub.

Step 1: Create a WorldHub Account

  1. Go to worldhub.me
  2. Click Sign Up or Log In to create/access your WorldHubID account

Step 2: Create a Sync Session

  1. Once logged in, click your username in the top-right dropdown
  2. Select Sync Sessions
  3. Click Create Session and enter a name (e.g., "My World Session")
  4. You'll receive a Sync ID (UUID) and Broker URL
  5. Click the copy button next to each value

Step 3: Configure Your World

Open world.veml and find the <synchronizationservice> element in <metadata>:

<synchronizationservice type="vss"
    address="wss://mqtt.webverse.info:55526"
    id="paste-your-sync-id-here"
    session="my-world" />

Replace:

  • address — with the Broker URL from your session
  • id — with the Sync ID from your session
  • session — with a descriptive name for your session

Step 4: Commit and Push

git add world.veml
git commit -m "Enable multiplayer"
git push

After the deploy completes, anyone visiting your world URL will be connected to the same multiplayer session.

Session Limits

Each WorldHub account has a session quota (default: 5 sessions). You can view your quota and manage sessions from the Sync Sessions page. Delete unused sessions to free up quota.

Disabling Multiplayer

To disable multiplayer, remove or comment out the <synchronizationservice> element:

<!-- <synchronizationservice type="vss" ... /> -->

16. Input Events and Control Flags

Input Events

Input events connect user actions (keyboard, mouse, VR controllers) to JavaScript functions. They're declared in <metadata>:

<inputevent input="key" event="HandleKeyPress(?);" />
<inputevent input="endkey" event="HandleKeyRelease(?);" />

The ? is replaced with the input value at runtime (e.g., x/y deltas for movement, key name for keypresses).

Note: This template does not wire any input events for movement — WASD motion, mouse look, and jumping are handled natively by the WebVerse rig once the character is registered as the rig avatar (see scripts/lib/thirdpersoncharacter.js). Use input events for custom interactions like hotkeys or VR controller buttons.

InputTriggered ByValue Passed
moveWASD / left stickx, y movement deltas
endmoveKey release(none)
lookMouse / right stickx, y look deltas
endlookMouse stop(none)
keyAny key pressKey name string
endkeyKey releaseKey name string
leftstickvaluechangeVR left stickx, y values
rightstickvaluechangeVR right stickx, y values

Control Flags

Control flags configure VR-specific behavior:

<controlflags>
  <left-vr-pointer>teleport</left-vr-pointer>
  <right-vr-pointer>ui</right-vr-pointer>
  <turn-locomotion>snap</turn-locomotion>
  <joystick-motion>true</joystick-motion>
</controlflags>
FlagValuesDescription
left-vr-pointerteleport, ui, falseLeft controller pointer mode
right-vr-pointerteleport, ui, falseRight controller pointer mode
turn-locomotionsnap, smooth, falseVR turn style
joystick-motiontrue, falseEnable joystick movement
left-grab-movetrue, falseGrab-to-move with left hand

17. Troubleshooting

"Failed to load runtime" on the loading screen

The GitHub Actions workflow didn't complete, or Pages isn't enabled.

Fix: Check the Actions tab for errors. Make sure Pages source is set to "GitHub Actions" (not "Deploy from a branch").

World loads but shows an empty scene (no objects)

The world.veml file wasn't found or couldn't be parsed.

Fix: Check that world.veml is in the repository root and is valid XML. Look for unclosed tags or invalid characters.

FormatException error in the console

An entity id attribute is not a valid UUID.

Fix: Replace any human-readable IDs (like id="my-box") with valid UUIDs (like id="a1b2c3d4-e5f6-7890-abcd-ef1234567890"). Generate them at uuidgenerator.net.

Objects don't appear even though they're in the VEML

Primitive entities created in VEML may be invisible by default.

Fix: Call SetVisibility(true, true) on the entity from JavaScript:

Time.SetTimeout(function() {
    var entity = MeshEntity.Get("my-tag");
    if (entity != null) {
        entity.SetVisibility(true, true);
    }
}, 2);

3D model (GLB) doesn't load

The model URL is unreachable or lacks CORS headers.

Fix: Verify the URL is accessible in a browser. The server must return Access-Control-Allow-Origin: * in the response headers.

Multiplayer isn't working

The sync session may not have been created, or the broker URL is wrong.

Fix: Check if the <synchronizationservice> element in your deployed world.veml has actual values (not REPLACE-WITH-YOUR-UUID). If using automatic registration, check the deploy workflow logs for warnings about the GitWorlds service being unreachable. If using manual sessions via WorldHub, verify the sync ID and broker URL match your session.

Page shows 404

GitHub Pages isn't enabled or DNS hasn't propagated.

Fix: Verify Pages is enabled in Settings > Pages. Wait a few minutes after the first deploy for DNS propagation.

High memory usage / browser tab crashes

The WebVerse runtime is a WebGL application and uses significant memory. Complex worlds with many entities or large 3D models can exceed browser memory limits.

Fix: Reduce the number of entities, use lower-polygon models, and avoid extremely large textures.


18. Quick Reference

Entity Types

xsi:typeDescription
cubemeshBox primitive
spheremeshSphere primitive
cylindermeshCylinder primitive
conemeshCone primitive
planemeshFlat plane primitive
capsuleCapsule primitive
mesh3D model (GLB/glTF)
lightDirectional/point light
htmlHTML panel overlay
characterPlayer character
text3D text
terrainHeight-mapped terrain
waterWater surface

Common Quaternion Rotations

DegreesAxisxyzw
00001
30Y00.25900.966
45Y00.38300.924
90Y00.70700.707
180Y0100
90X0.707000.707
45X0.383000.924
90Z000.7070.707

Key JavaScript APIs

// Find entities
Entity.Get("tag-or-id")
MeshEntity.Get("tag")
HTMLEntity.Get("tag")

// Create entities
MeshEntity.Create(null, position, rotation, scale, false, tag, uuid)
CharacterEntity.Create(null, position, rotation, scale, false, name, uuid, callback)

// Modify entities
entity.SetPosition(new Vector3(x, y, z), false)
entity.SetRotation(new Quaternion(x, y, z, w), false)
entity.SetScale(new Vector3(x, y, z))
entity.SetVisibility(visible, includeChildren)
entity.SetColor("#hex")  // MeshEntity only
entity.SetInteractionState(InteractionState.Static)

// Timing
Time.SetTimeout(fn, seconds)
Time.SetInterval(codeString, seconds)

// State persistence
Context.DefineContext("name", object)
Context.GetContext("name")

// Utilities
UUID.NewUUID().ToString()
Logging.Log("message")

// Networking
HTTPNetworking.Request("GET", url, headers, body, callback)

Controls

InputAction
W / A / S / DMove forward / left / backward / right
MouseLook around
SpaceJump
VR Left StickMove
VR Right StickLook

Next Steps