Why XR Breaks Your Instincts
Presence, comfort, and why a flat UI floated in 3D is always wrong.
By the end of this session you will be able to:
- Name the two ways an XR experience fails a person, presence loss and physical discomfort, and say which screen habit causes each.
- Convert an interface from pixels into degrees of visual angle, the only unit a headset respects, and state its size in meters.
- Look at a flat interface floated in 3D and list the four screen assumptions it is still making.
Presence is a prediction your brain keeps checking
Presence is the point at which your body stops treating the headset as a display and starts treating the virtual room as the room. You can watch it happen in a first-time user: shoulders drop, the head moves to look behind things instead of turning like a camera, hands come up.
It is not realism. Untextured grey boxes hold presence fine; a photoreal scene that stutters loses it in one frame. Your brain moves your head a few degrees and predicts what the eyes should see next. Right prediction, real room. Wrong prediction, and the model collapses; that is a break in presence. Latency does it. Wrong scale does it, which is why a 1.6 meter door reads as a dollhouse the moment you walk to it. Hands passing through solid objects do it.
The second failure is worse, because it outlasts the session. Your inner ear reports acceleration, your eyes report motion, and when they disagree for long enough your body concludes it has been poisoned and acts on that. On the web a bad interface costs you a bounce. Here it costs you someone who feels sick for twenty minutes and now believes, about every app, that VR makes them sick.
So: a decision is wrong if it breaks the prediction, and dangerous if it makes the eyes and the inner ear disagree.
Four things your screen taught you that are false here
Every flat interface rests on four assumptions, all of them properties of glass welded to your gaze, not properties of the world.
- Pixels are a size. They are a size only because your monitor is always about 60 cm from your face. In a headset the same panel is a wall at 1 meter and a postage stamp at 8, unchanged.
- There is a viewport. Every layout you have written negotiates with the edges of a screen. In a headset there is no edge. Your field of view is a moving window onto a room, not a canvas to fill.
- Everything sits at one depth. On a monitor the drop shadow is a lie you both agree to, and the eyes focus at one distance for every element. In a headset they converge differently for a panel at 1 meter than for one at 5, involuntarily.
- Content follows the head. A screen is always in front of you. Reproduce that in VR and you get a panel welded to the face that cannot be looked away from or inspected.
Number three has hardware under it. The display sits at a fixed optical distance, so your eye's lens focuses there wherever an object appears to be, while your eyes still converge on its virtual distance. That mismatch is what makes eyes ache after ten minutes. Meta's display guidance sets the floor from it: anything fixated on for an extended period belongs at least 0.5 meters out, and 1 meter is where most teams land for menus.
The math that replaces pixels
Angular size is the unit that survives. An object of height h at distance d subtends theta = 2 * atan(h / (2 * d)), and inverted, h = 2 * d * tan(theta / 2). In degrees:
const angleFor = (size, distance) =>
(2 * Math.atan(size / (2 * distance)) * 180) / Math.PI;
const sizeFor = (degrees, distance) =>
2 * distance * Math.tan((degrees * Math.PI) / 360);Body text on your monitor has a cap height of roughly 3.5 mm at 60 cm, so angleFor(0.0035, 0.60) is about 0.33 degrees. A monitor is a bright, unmoving plane packing far more pixels into each degree than any headset does, so 0.33 degrees is comfortable there and unreadable in a headset, where it is a handful of display pixels and your head is never still.
So do not port the angle. Pick the one you need, roughly 1 degree of cap height for text people read rather than glance at, and get meters:
sizeFor(1.0, 1.0) = 0.0175 m cap height for a panel at 1 m
sizeFor(1.0, 2.0) = 0.0349 m cap height for a panel at 2 m
sizeFor(1.0, 4.0) = 0.0698 m cap height for a panel at 4 mA 17.5 mm capital letter is large-print paperback size. That is the honest cost of readable text in a headset, and a dense dashboard does not survive it.
Why a flat UI floated in 3D is always wrong
The standard beginner move is to take the 1920 by 1080 mockup, make it a textured quad, and park it in front of the camera. It fails four ways at once.
It makes no depth commitment: its shadows and hover states imply lighting and proximity nothing in the scene provides, so it reads as a photograph of an interface, and hands pass through it. It is head-locked, because that is what a screen does, and that violates the one thing the vestibular system is certain about, that fixed things stay fixed while you turn. It assumes gaze is free, when a control behind you costs a neck turn and one at chest height costs a look down nobody repeats forty times. And its scale belongs to no room.
The instinct that replaces it: a control in XR is an object. It has a size in meters and a place registered to the world or the body rather than the head.
Try it
No headset required.
- Open an interface you built. From devtools take two CSS pixel numbers: its main panel width, and the cap height of its body text (font size times about 0.7).
- Substitute your numbers and run this in the console:
const angleFor = (s, d) => (2 * Math.atan(s / (2 * d)) * 180) / Math.PI;
const sizeFor = (deg, d) => 2 * d * Math.tan((deg * Math.PI) / 360);
const panelWidthPx = 1920; // your panel width
const capHeightPx = 11; // your body text cap height
const perPx = sizeFor(1.0, 1.0) / capHeightPx;
const width = panelWidthPx * perPx;
console.log(width.toFixed(2), "m wide to be readable at 1 m");
console.log(angleFor(width, 1.0).toFixed(0), "degrees of your view");
console.log("fits 45 degrees at", Math.round(sizeFor(45, 1) / perPx), "px");- Put two pieces of tape on a wall that far apart, stand 1 meter back, and look at the gap. That is your panel at the size its own text demands.
- Then run:
if (!navigator.xr) {
console.log("no WebXR in this browser");
} else {
navigator.xr.isSessionSupported("immersive-vr").then((ok) => {
console.log("immersive-vr:", ok);
});
navigator.xr.isSessionSupported("immersive-ar").then((ok) => {
console.log("immersive-ar:", ok);
});
}You are done when you can state your interface's readable size in meters and degrees, name the chrome you would cut to get under the last number printed, and say what navigator.xr reported. On a laptop with no headset, expect false twice. That is a baseline, not an error: isSessionSupported resolves rather than throws.
Common mistakes
- Calling an interface spatial because it has a z coordinate. Depth is a claim about where a thing sits relative to the hands and the room, and every other cue has to agree.
- Head-locking a HUD because flat games do it. There the HUD and the world share one screen. In a headset it sits at a different depth from everything it overlaps and cannot be inspected.
- Designing at 1080p and scaling in the editor until it looks right. The editor viewport is a monitor. It will let you ship 0.3 degree text nobody can read on device.
- Chasing realism to get presence. Presence is bought with low-latency response to head motion, not texture budget.
- Testing only on yourself. You acclimated on day two, so your judgment about comfort stopped being representative then.
Where this goes next
The next session, A Scene You Can Stand In, spends these constraints: a three.js scene with meshes, lights, and a camera, sized in meters from the first line and previewed in the Immersive Web Emulator.