Overview
Duskcone is a Meta Quest/OpenXR port of Dusklight, the reimplementation of The Legend of Zelda: Twilight Princess. Its comfort system presents the game in stereo through a spatial opening—the gameplay portal—during camera movement. The opening stays anchored in the viewer’s surroundings, with a stable environment around it. When the camera settles, the opening can expand and the surroundings fade away, returning to full surrounding VR.
This extends Google Daydream’s tunneling approach in two directions: the aperture responds to the source of camera motion, and its boundary is geometry in the viewer’s space. A larger aperture accompanies ordinary gameplay movement; a smaller one accompanies camera corrections and forced motion. A separate proximity fade, Face Guard, addresses geometry that comes too close to the viewer.
The implementation uses established rendering techniques to combine these views. The following sections trace the system from camera inputs through motion selection and aperture transitions to rendering. Duskcone supplies the concrete implementation and tuning values; another game can use the same arrangement with its own camera and movement hooks. Personal observations behind the comfort policy are collected in the design appendix.
2. Camera and reference frames
The game camera and the tracked viewer are independent inputs. Combining them gives the original third-person view stereo depth and freely tracked looking: the player can turn to see beyond the original camera frame or lean to see objects shift relative to one another. These viewing changes leave the game’s camera direction and controls intact.
The game supplies an authored camera position and orientation; the XR runtime supplies a tracked position, orientation, and field of view for each eye. Each rendered eye camera combines those inputs:
Here p is the tracked eye position in meters, U converts meters to game units, and B is the orthonormal basis formed from the authored camera’s right, up, and backward directions. OpenXR’s local negative Z direction therefore maps onto the game camera’s forward direction. The tracked rotation is applied in this basis. Each eye’s projection is built from its runtime-provided field-of-view (FOV) angles and the game’s near/far clipping distances. The two distinct eye positions and projections provide binocular depth throughout the view, including inside the portal.
Keep head tracking responsive
The authored camera may be interpolated between game updates. Apply that interpolation first, then compose it with the runtime’s eye poses predicted for display time. This lets game-camera movement remain smooth without applying its interpolation delay to the player’s head tracking.
The peripheral room and portal plane belong to the XR presentation space. Each eye projects the same spatial geometry from its current pose. Turning or leaning changes the portal’s position and perspective in the eye image; it does not pull the portal along with the gaze. The room responds to head tracking while remaining independent of the game camera’s travel.
System recentering changes the relationship to the runtime’s reference space. The portal and room use that reference relationship together, preserving normal recentering behavior.
Render the view the eyes can see
Changing the projection is only part of adding tracked stereo viewing. The game’s original visibility tests may discard an object outside its flat camera frame even though a turned or leaning viewer can see it. Duskcone retains the authored visibility test and rescues rejected draws with a conservative stereo frustum. The rendering view can widen while the game continues to use its authored camera for gameplay decisions.
In another engine, the equivalent is to make draw culling cover both composed eye views. This matters inside the portal as well as in full VR: the opening limits which part of the stereo image is shown after visibility has already been decided.
Scale is a presentation decision
The game’s baseline scale is 100 game units per meter. Gameplay is presented at 50 units per meter, making the world twice as large relative to the viewer’s physical head and eye separation.
At baseline scale, viewing the scene stereoscopically from a third-person camera made it resemble a miniature. That impression worked against the tone and apparent scale of the flat game. Doubling the presented world brings it closer to that subjective scale. This changes the conversion used for rendering, not the game’s simulation or camera logic.
The surrounding room uses the baseline 100 units per meter. It is a separate environment around the viewer, so its scale can be chosen independently of the third-person gameplay view.
3. Three aperture targets
The portal moves continuously between three target presentations:
| Target | Selection | Presentation |
|---|---|---|
| Full VR | The game camera has settled. | The peripheral room disappears and gameplay occupies the entire surrounding view. |
| Gameplay aperture | Ordinary camera movement, such as following the player’s movement or manual camera steering. | A larger aperture retains more of the moving scene. |
| Safety aperture | Automatic movement, such as a collision correction, knockback followed by the camera, or a moving cutscene shot. | A smaller aperture exposes more of the stable surroundings. |
The distinction comes from the experience of playing: predictable movement initiated through the controls has been easier to accommodate than imposed camera movement. The implementation approximates that distinction by measuring which parts of the game caused the camera to move. Ordinary motion is the default; a smaller aperture requires positive evidence of automatic motion.
Options can retain the room when still or hold a smaller aperture throughout cutscenes. These alter the policy while using the same geometry and rendering paths.
4. Measure continuous camera motion
Motion measurement uses the game camera before tracked eye poses are applied. Each sample records its position (the authored “eye”), look-at point (the “center” or “focus”), forward and up directions, bank, and vertical FOV. Translation, rotation, roll, and FOV change are normalized separately:
The vectors e, f, and u are the authored eye position, forward direction, and up direction; b is bank and φ is vertical FOV. Δt is the interval between camera samples. In Duskcone, U is 50 game units per meter, v₀ is 0.15 m/s, and ω₀ is 6°/s. Bank differences wrap to the shortest signed angle. Taking the maximum lets a significant change in any component request an aperture without depending on unrelated components.
Camera cuts are not continuous travel
A change of shot is a new starting pose, rather than continuous travel between two positions. Explicit camera cuts and large pose discontinuities reset presentation and motion history. This prevents interpolation through the jump and starts subsequent motion measurements from the new shot.
5. Attribute motion that reached the final camera
The observed score says how much the camera moved. Attribution asks which part of that movement came from an automatic source. A character can be in hit stun without moving the camera; a terrain probe can request a tilt that is later canceled. The classifier therefore uses contributions recorded where camera corrections and actor displacement are actually applied, then matches them against the final authored camera.
The integration needs four kinds of evidence:
| Evidence | Where to record it | How it is used |
|---|---|---|
| Camera correction offset | After collision or shake modifies the intended camera pose. | Changes in the offset identify both the correction and its release. |
| Applied camera movement | After a terrain adjustment passes through smoothing and limits. | Only the surviving contribution is eligible for attribution. |
| Resolved character displacement | Where platforms, knockback, pulls, falls, and ordinary locomotion change the character’s position. | A short per-source history matches delayed camera following. |
| Camera ownership | Where the game selects an event or fixed-angle camera. | An automatic camera can attribute its observed motion directly. |
Camera corrections
Collision and shake publish offsets from the intended camera pose. Their change between consecutive samples supplies the contribution for that update. A constant collision offset produces no motion; entering the obstruction and easing back out both do. Shake can contribute eye and focus offsets, roll, and FOV change.
A zero offset and a missing sample have different meanings. Publishing zero measures the release of a correction. A missing sample breaks that source’s history, so the next value establishes a new baseline instead of replaying an old correction.
Terrain adjustment needs a different measurement because the camera smooths and limits its requested pitch. Duskcone tracks the scalar pitch with and without the terrain contribution through the same smoothing step, then publishes the applied difference as eye-around-focus movement. A later camera override may still cancel it, so the final matching step checks that it survived. This tracks one contribution through the camera solver without rerunning the whole camera.
Actor displacement and delayed following
The actor is the character being followed. Its movement may reach the camera a few updates later. Each source keeps a short budget of outstanding displacement, covering ordinary locomotion and automatic sources such as platforms, knockback, pulls, and falls. The history window accommodates camera-follow lag without leaving old movement eligible indefinitely. A camera cut clears these budgets along with the camera history.
Before matching actor following, subtract camera-center movement already explained by camera corrections. Otherwise a shake that moves the focus could consume a pending knockback impulse as well. The remaining center movement consumes matching displacement from the actor budgets, oldest first. Each matched portion is spent once and each unspent impulse expires independently. New displacement cannot prolong an older impulse’s lifetime.
Using center movement separates following from orbiting. Turning the camera around a stationary focus cannot spend actor displacement. Conversely, a moving focus can change the viewing direction even when the camera’s eye position stays fixed, so attribution retains both eye and center contributions.
When ordinary movement and automatic displacement point in the same direction, they share the observed following proportionally. For example, equal outstanding platform and player displacements each receive half of a smaller matching camera movement. The player’s share is consumed along with the platform’s, but only the platform share contributes to safety attribution. This keeps held input from canceling genuine automatic movement or giving it unlimited credit.
The history is bounded evidence matching, rather than a second camera simulation. Following beyond the history window receives no credit from the expired displacement. Disabling an actor source clears its pending safety evidence and records subsequent displacement with ordinary locomotion.
Bound evidence by observation
Vector matching projects observed motion onto an evidence direction, then bounds its individual axis components by what survived in the observed motion. Projection alone could manufacture vertical evidence from forward movement when an intended vertical correction was canceled elsewhere in the camera process.
The safety score is bounded by the corresponding observed components:
Here a is the attributed score and q is observed motion. Translation, roll, and FOV scores use the matched deltas. Attributed rotation is measured by removing the matched eye-around-focus contribution from the final viewing direction. Bounding each score by its corresponding observation prevents a small automatic rotation from borrowing a large ordinary translation to cross the safety threshold.
An event-owned camera attributes its observed score directly; enabled fixed-angle camera ownership uses the same rule. Merely running an event does not establish that ownership.
The evidence-matching primitive
This helper from src/dusk/openxr_portal_motion.hpp projects movement onto the evidence direction, limits it to the available amount, then bounds each component by the observed movement. It is used for both camera contributions and actor-follow budgets.
inline float match(float observed, float evidence) {
return evidence > 0.0f ? std::clamp(observed, 0.0f, evidence)
: std::clamp(observed, evidence, 0.0f);
}
inline Vector match(Vector observed, Vector evidence) {
const float length = evidence.length();
if (length < 0.0001f) {
return {};
}
const Vector direction = evidence * (1.0f / length);
const Vector projected = direction * std::clamp(observed.dot(direction), 0.0f, length);
return {match(observed.x, projected.x), match(observed.y, projected.y),
match(observed.z, projected.z)};
}
6. Fast narrowing and gradual reopening
The aperture controller confirms sustained motion before selecting a target. It keeps an independent state for the observed and safety scores. Each enters at a normalized score of at least 1.0 for two consecutive samples. Once active, it remains active while its score exceeds 0.5. This difference between entry and exit thresholds is hysteresis; it avoids repeated activation around the entry boundary.
Confirmed ordinary motion selects the gameplay aperture. Confirmed safety motion takes priority and selects the smaller aperture. With neither active, the controller can reopen.
Requiring two samples rejects an isolated change of shot that immediately settles. It adds one camera-update interval after the first qualifying sample. Keeping the two confirmation states separate also prevents an ordinary-motion candidate from becoming a safety request on the strength of a single later attributed sample.
Aperture selection follows updates to the game camera; the visible transition updates at display cadence. The controller approaches its target exponentially:
Openness o ranges from 0 to 1, and Δt is the bounded elapsed presentation time. Narrowing uses a response rate of 10 per second; ordinary reopening uses 4 per second. Established cutscene context uses a slower reopening rate of 0.75 per second. These are exponential response rates, not fixed animation durations.
A safety request also starts a release hold. The ordinary hold is 0.5 seconds; sustained event context can extend it to 2 seconds. This keeps the portal from reopening during brief pauses within a moving sequence. Continued safety motion restarts the hold, and the hold takes precedence over the larger gameplay target as well as full expansion.
Presentation policies can retain an aperture independently of motion: targeting holds the gameplay size, and the fixed-cutscene option holds the safety size. Safety motion and its release hold take priority over targeting. These policies select a target on the same transition path described below.
The asymmetry is deliberate: reduce exposure promptly when continuous motion begins, then restore the surrounding game view gradually. Confirmation rejects isolated changes, hysteresis stabilizes the moving/still decision, and the release hold bridges short pauses. The response rate controls how the visible aperture reaches its selected target.
7. From a spatial window to full VR
The aperture is a rectangle with a center, right/up basis, and dimensions in XR presentation space. Its projected boundary divides each eye’s image into gameplay inside and surroundings outside; this boundary defines the portal’s rendering mask. A nominal vertical angle calibrates the rectangle’s size at a chosen distance:
The base calibration is 48° vertical. Distance d defaults to 2 m; the aspect ratio ρ follows the UI’s widescreen or original framing. Safety and gameplay angles select scale factors relative to that base, with gameplay no smaller than safety. The default sizes are 42° for safety and 72° for gameplay.
At 2 m, a centered 42° aperture has a nominal height of about 1.54 m; a 72° aperture has a nominal height of about 2.91 m. These are authoring dimensions. A lean, an oblique view, or movement of the plane changes the apparent angle, as it would for a physical opening.
Opening the window into a surrounding view
A finite window in front of the viewer cannot become full VR merely by getting wider: it would still exclude the view behind them. The transition therefore has three coordinated parts. The opening grows, the plane approaches the neutral viewing origin—the authored reference position, rather than the live head position—and the stable room fades away. Once the room is gone, the spatial mask is disabled and gameplay is drawn in every direction.
Openness maps to geometric scale using a bounded hyperbola:
The minimum scale is the safety aperture’s scale; the gameplay target selects its larger configured scale. Both motion targets and full expansion use one continuous size-and-distance path. The hyperbola makes the final part of that path grow rapidly beyond the ordinary aperture sizes. Scale is capped at 32, and room opacity follows a smooth fade between scales 8 and 24:
The plane’s forward distance uses a smooth path toward the neutral origin and then behind it, ending at −2 m. It crosses the origin at 95% of the scale path, after the room has faded away. Disabling the mask before that crossing avoids making a plane intersecting or passing behind the viewer define the surrounding view. The path is authored relative to the presentation origin; it is not continually repositioned around the live head pose.
The maximum-size option stops expansion at the beginning of the room fade. It retains an opaque peripheral environment and a bounded opening even when the camera is still. Cutscene trim can additionally crop the plane’s vertical extent, reproducing the authored framing as spatial geometry.
8. Stencil, feather, and reprojection motion
The plane is projected separately for each eye into a stencil buffer, which marks pixels inside and outside the opening. That projected shape is the authoritative mask. An inset band—the feather ring—softens the boundary by blending gameplay and surroundings across it. The passes are arranged as follows:
| Pass | Responsibility |
|---|---|
| Aperture stencil | Clear stencil to exterior 0; write interior 1 with the projected plane. |
| Gameplay | With an opaque room or passthrough exterior, accept fragments inside stencil 1. |
| Room exterior | Draw the stable environment outside the aperture. |
| Feather ring | Blend the boundary in portal-local coordinates. |
| Room fade | Draw complete gameplay, then composite the disappearing room over the exterior and feather. |
During the room fade, gameplay must exist beneath the translucent surroundings. The stencil is rebuilt for the room overlay rather than used to remove the underlying game image. Passthrough instead uses projection-layer alpha to reveal the runtime’s physical-room layer below it. Once the exterior has disappeared, the unrestricted full-VR state skips the visible room and aperture passes.
Conservative hardware scissoring
The union of both eyes’ projected portal corners supplies a conservative hardware scissor: a screen-aligned rectangle that limits rasterization. It intersects any narrower scissor already requested by the game. The per-eye stencil still determines the true boundary. Invalid or near-plane projections fall back to the full viewport rather than incorrectly clipping the view.
Scissoring is disabled during the translucent room fade. While the room is opaque, stencil rejection and scissoring can reduce gameplay fragment work, offsetting some of the added backdrop and feather work. A large union rectangle can cover much more screen area than the visible per-eye opening; its coverage is not an estimate of the actual aperture’s fragment coverage.
A feather attached to the plane
Feather width is authored in degrees at the neutral calibration, then converted to an inset f proportional to the current portal height H:
The resulting boundary is spatial: its apparent width changes with distance and viewing angle along with the plane itself. For current portal width W and local coordinates (x, y), gameplay coverage C is:
The rectangular ring consists of eight non-overlapping triangles. This avoids double blending at corners; the product of edge coverages avoids a visible switch between the nearest horizontal and vertical edges. A shallow inward bevel places the ring’s inner edge behind the stencil plane while preserving the outer boundary. That depth preserves some feather when viewing the portal nearly edge-on, where a completely flat ring would collapse into a thin line. The stencil boundary itself remains flat.
The room’s feather opacity is proportional to one minus coverage. Passthrough uses the complementary projection alpha. A small 0.25° feather remains at the minimum setting to soften aliasing at the edge.
Motion at the boundary
Application SpaceWarp generates intermediate display frames by extrapolating rendered color using depth and motion vectors. At the portal edge, moving gameplay meets a stationary room, so the feather needs a corresponding transition between their motion fields. Duskcone uses a wider 10° feather calibration with SpaceWarp to give that transition room to blend.
A motion-feather pass blends away gameplay motion in proportion to visible room coverage while retaining the projected boundary’s tracking-space motion. Current and previous eye projections supply that boundary motion.
This is an approximate transition between the two motion fields, but in practice it suppresses the most noticeable boundary artifacts in SpaceWarp’s extrapolated frames.
The camera-cut reset described earlier also invalidates the previous-frame data used for extrapolation. The game’s interface is submitted in its own composition layer, separate from the stereo scene whose motion is being extrapolated. Color, depth, and motion need to describe the same portal composition, including its transition to full VR.
9. A stable room within a standalone rendering budget
The exterior needs a stable reference that fits both the game’s atmosphere and the headset’s frame budget. Link’s House supplies a room from the game’s own assets. In our experience it grounds the viewer less strongly than Daydream’s grid: the moving gameplay image can make the stationary house seem to move with it. Passthrough has provided the strongest grounding, at the cost of in-game atmosphere and additional runtime overhead.
The virtual room can be drawn live or reused from captured images. A cubemap stores six views covering the surrounding directions. An RGB-D cubemap stores color and depth, allowing the captured room to be approximately reprojected from a new eye position. These environment paths share the same aperture geometry, with different rendering costs:
| Environment path | Head-tracking response | Rendering tradeoff |
|---|---|---|
| Live room | Render the room from the current stereo eyes. | Direct geometric response, with a second scene to draw each frame. |
| RGB-D room cache | Reproject captured color and depth from the current eyes. | A backdrop sampling pass plus periodic capture; translated views are approximate. |
| Flat cubemap | Select captured color by current ray direction. | Rotation responds directly; translated-view parallax is absent. |
| Passthrough | The runtime reconstructs the physical room. | Strong physical reference with runtime camera/reconstruction cost. |
Drawing a second complete stereo scene is a significant cost on a standalone headset. Duskcone’s normal virtual-room path therefore uses cached per-eye RGB-D cubemaps. Their capture anchors—the eye positions from which the room was captured—and current eye poses belong to the stable environment’s reference frame, independently of the game camera. The captures use visibility culling for their own six directions. This keeps the room responsive to head tracking while spreading its scene rendering over multiple frames.
RGB-D cache integration and sampling
Each eye has six captured color/depth faces. For a current eye ray, the shader samples source depth, reconstructs a point relative to the capture anchor, and estimates where that surface lies along the current ray. The corrected source direction supplies the color lookup. Additional depth lookups can refine the estimate; the default uses one.
For a unit ray r, capture anchor a, current eye e, and captured radial depth D, the first correction is:
The cubemap is addressed by the direction of d; the implementation uses ε = 0.001 m to keep the estimate in front of the eye. This corrects the lookup toward the surface implied by captured depth without rendering that surface again.
Rotation can be handled by changing cubemap direction alone. Translation needs depth to approximate parallax. This inverse sampling method reuses visible surfaces from the capture; it cannot invent surfaces that were hidden behind them.
Position-triggered re-capture
A new capture is requested when the head moves more than 15 cm from the capture position. Rotation alone can reuse the cubemap. Capture is incremental, refreshing a cubemap face for both eyes per step. The eye poses are frozen for the pending batch, so all its faces share coherent capture origins.
The completed snapshots remain in use while a second buffer pair receives the replacement. Publishing only the complete set avoids mixing faces captured from different positions. A smaller refresh distance reduces the translation the cache must approximate but requests captures more often; a larger distance saves capture work while allowing more reprojection error. The threshold and room geometry need to be chosen together.
Choosing the room
A static interior with broad surfaces, comfortable viewing distances, and few thin occluders is a good fit for the cache. Leaning can reveal surfaces that were hidden from the capture, producing disocclusion artifacts. Such artifacts would make the room a distracting peripheral reference, so the environment’s geometry is part of the comfort design.
Link’s House fits these criteria well and retains the game’s atmosphere.† The cache still adds per-pixel backdrop sampling and feathering, but avoids recurring full-scene rendering. Live rendering remains available when direct geometric response is worth that cost.
Passthrough places the runtime’s reconstruction layer beneath an alpha-enabled projection layer. Transparent pixels outside the aperture reveal the physical room; the feather blends that transparency into gameplay. The feature runs while those pixels are exposed and can pause when full VR completely occludes it. An unavailable passthrough path falls back to the virtual room.
10. Face Guard: nearby geometry
The portal limits exposure to camera motion. Face Guard addresses geometry that crowds the viewer’s face. A third-person camera and freely tracked head can place a wall, character, or prop very close to an eye. The flat camera’s framing and collision rules do not necessarily leave suitable clearance for every stereo viewpoint.
Face Guard changes the visibility of nearby geometry without moving the camera or changing collision. Distance determines coverage—the fraction of pixels to retain. A repeating 4 × 4 Bayer pattern supplies a threshold for each pixel, so lower coverage progressively discards more fragments:
Distance r is measured from each eye; r_start and r_end delimit the fade band. The minimum coverage C_min is zero for the normal fade. Squared distance avoids a square root and makes the transition nonlinear in distance. Ordered discard keeps the existing opaque rendering model, avoiding the sorting requirements of general translucent geometry.
The setting controls the outer radius; the normal inner radius is half that value. Standard uses a 0.5–1 m band, Comfortable uses 1–2 m, and Off disables the gameplay fade.
Materials can opt out or use a softer fade. The soft mode uses a smaller band—from 10% to 50% of the configured radius—and retains a minimum coverage of 1/16. This lets selected surfaces remain faintly present very close to the viewer.
Removing foreground fragments can reveal a wall’s reverse side, an object’s interior, or geometry originally hidden by that surface. It can also weaken occlusion and increase fragment work. Strength is therefore adjustable: the feature trades some near-field visual integrity for a less crowded stereo view.
11. Integration and tuning
Integration sequence
- Establish the reference frames. Compose the authored camera with the tracked eye poses for gameplay rendering, after any camera interpolation. Update draw culling to cover the composed stereo view. Place the portal and peripheral environment in the shared XR reference space, applying system recentering consistently to both.
- Implement a fixed aperture first. Project one shared plane into both eyes and use the stencil/pass sequence above. Start with a simple live environment or passthrough. Verify the interior, exterior, and feather from both eyes, including oblique views and near-plane crossings. Keep the hardware scissor conservative and restore its state after gameplay.
- Measure ordinary camera motion. Sample the authored camera independently of tracked viewing, normalize its deltas using the actual sample interval, and confirm sustained movement. Clear history at camera cuts; a settled cut should not act like continuous travel. The thresholds above are starting values for tuning, not requirements for another game.
- Add attribution and safety selection. Publish the correction-offset, applied-movement, resolved-displacement, and ownership evidence described in section 5. Match it against the final camera, removing correction-center motion before consuming delayed-follow budgets. Keep ordinary locomotion in those budgets so mixed movement is shared rather than vetoed, and give the resulting safety score its own confirmation state.
- Connect the aperture controller. Update openness at display cadence, then derive plane scale, distance, feather inset, and room opacity. Preserve the complete gameplay image underneath the fading room. Switch to the unrestricted mask once the exterior disappears, and keep the maximum-size option on the bounded part of the transition.
- Add proximity and temporal rendering support. Apply Face Guard in eye-relative distances, converting units consistently and choosing eligible materials. If the renderer uses motion extrapolation, integrate the feather’s motion/depth treatment and reset temporal history at cuts. Check the complete composition in extrapolated frames as well as native frames.
- Fit the environment to the frame budget. Introduce the cubemap cache if live rendering is too costly. Freeze capture poses for each batch, retain the completed set until its replacement is ready, and tune the refresh distance for the chosen room.
Tuning parameters
Aperture sizes, distance, feather width, environment, Face Guard, and motion-source policies are adjustable. Maximum-size and cutscene options can retain a stable peripheral reference more consistently. Presets provide starting points; individual settings remain available.
| Preset | Safety / gameplay angle | Face Guard outer radius |
|---|---|---|
| Extra Comfort | 30° / 54° | 2 m |
| Standard | 42° / 72° | 1 m |
| Uncomfortable | 60° / 108° | Disabled |
Extra Comfort selects passthrough on Android when available. The wider preset disables optional safety-source triggers; event-camera ownership remains part of attribution. Presets therefore change the environment and source policy as well as the two aperture sizes. Maximum-size and fixed-cutscene behavior remain separate settings.
Thresholds, confirmation, release holds, transition rates, and the plane’s expansion path should be tuned together. A nominal angle is not a constant screen-space field of view once the plane moves or the viewer leans. A response rate is not a fixed completion time, especially after the hyperbolic scale mapping.
Check the boundaries between subsystems
The cases below exercise the places where camera evidence, aperture policy, and rendering meet. They are useful checks while integrating the system, without depending on Duskcone’s particular camera modes.
| Case | Expected behavior |
|---|---|
| Hold the game camera still, then turn, lean, and recenter. | The stereo view responds to tracking, objects beyond the flat camera frame remain visible, and the portal and room share the reference-space change. |
| Cut between still shots. | No interpolated travel is created between shots, and attribution from the previous shot is cleared. |
| Move with a constant collision offset; then enter and leave an obstruction. | The constant offset adds no safety motion; its approach and release supply measured contributions. |
| Request terrain tilt that is clamped or canceled later in the camera solver. | Only the adjustment that reaches the final camera can contribute. |
| Hold movement input during knockback or platform travel. | Ordinary and automatic displacement share following; held input does not erase the automatic share. |
| Orbit around a stationary focus after an actor impulse. | Orbit does not consume the pending follow budget. Later ordinary movement cannot reuse evidence that was spent or expired. |
| Expand the portal while looking obliquely or away from it. | The spatial feather remains visible at oblique angles; full VR eventually has no finite aperture boundary. Unsafe scissor projections fall back conservatively. |
| Lean far enough to request a new room capture. | All pending faces use the frozen capture poses; the completed set stays visible until its replacement is ready. |
| Approach nearby geometry with Face Guard enabled. | Normal, soft, and exempt materials follow their selected policies, including their depth and motion rendering. |
| Inspect the portal edge in extrapolated as well as native frames. | Color, depth, and motion describe the same boundary; a smooth color blend alone does not establish correct temporal behavior. |
The attribution and camera-cut tests in the source map cover the corresponding numerical cases. Spatial composition and the room’s grounding effect also need to be inspected in the headset with the chosen environment and settings.
Appendix A. Design observations
These are personal observations and ideas behind the system, rather than results of a formal study.
Tolerance and wanting to return
Being able to tolerate a headset is not necessarily the same as finding it inviting to use. A player may stop feeling overtly sick while still finding the experience subtly demanding. That burden could show up as shorter sessions or a headset left unused, without a clear sensation of discomfort during play.
This is one reason to treat comfort as part of the presentation for everyone, rather than only as an accommodation for new VR users. The intended audience includes someone with an old Quest on a shelf who wants to sit down and play. Settings should let the experience suit that person without asking them to acquire a particular tolerance first.
Attention and motion source
During traversal, combat, or a directed cutscene, attention is often concentrated on the action. A stable peripheral view may cost relatively little of that moment’s experience. When the game is still, the surrounding world becomes more available to explore. Opening into full VR at those moments follows that distinction.
Predictability matters too. Development experience suggested that self-initiated movement could support a larger aperture than unexpected corrections or forced movement. That is why the motion policy gives ordinary gameplay a wider view and looks for evidence before selecting the smaller aperture.
These observations motivate adjustable presentation. My own tolerance increased while working on the port, making it harder to judge the experience of a less frequent VR user.
Implementation source
The camera and motion policy live in Dusklight’s game integration; the projection and rendering passes live in the Aurora backend. These entry points connect the descriptions above to the implementation. The tuning values belong to this implementation, while the evidence publishers are the main integration work for another game.
| Area | Source and entry points |
|---|---|
| Stereo composition, culling, and scale | src/dusk/openxr_camera.cpp: compose_eye_camera, compose_stereo_clipping_camera, apply_active_stereo, ActiveStereoCameraScope, last_stereo_frustum_contains, and last_stereo_frustum_intersects. Draw-culling integration in src/m_Do/m_Do_lib.cpp. Gameplay scale in src/dusk/openxr.hpp; room scale in src/dusk/openxr_portal_backdrop.cpp. |
| Policy and geometry | src/dusk/openxr.cpp: PortalTuning, sample_portal_camera_motion, sample_portal_camera, target_openness, WorldPortalScissorScope. Anchored-plane calibration in extern/aurora/lib/xr/openxr_ui.cpp. |
| Attribution and evidence publication | src/dusk/openxr_portal_motion.hpp: match, Correction, FollowDisplacements::consume, TerrainPitch, attribute, ComponentScores, and Confirmation. Collision, terrain, and shake publishers in src/d/d_camera.cpp; resolved actor-displacement publishers in src/d/actor/d_a_alink.cpp. |
| Discontinuities | src/dusk/openxr_camera_cut.hpp, src/dusk/interp/camera.cpp, and src/dusk/interp/frame_interpolation.cpp. Regression coverage in tests/openxr_camera_cut_test.cpp and tests/openxr_portal_motion_test.cpp. |
| Runtime eye poses | extern/aurora/lib/xr/openxr_frame.cpp: views located at predicted display time. |
| Update cadence and history | src/dusk/game_clock.h: 30 Hz camera samples before presentation interpolation. FollowDisplacements keeps six updates (200 ms). sample_portal_camera bounds presentation Δt to 1/240–0.1 seconds. |
| Stencil and pass composition | extern/aurora/lib/xr/openxr_backdrop.cpp: portal preparation, projected scissor, room overlay, and passthrough feather. |
| Portal and reprojection shaders | extern/aurora/lib/gfx/rgbd_reprojection.cpp: project_portal, ring_vertex, feather_coverage, snapshot sampling, and motion feather. |
| Room capture and refresh | src/dusk/openxr_portal_backdrop.cpp; extern/aurora/lib/xr/openxr_cubemap_snapshot.cpp; refresh radius in extern/aurora/lib/xr/openxr_state.hpp. |
| Passthrough | extern/aurora/lib/xr/openxr_passthrough.cpp, with layer composition in openxr_frame.cpp. |
| Face Guard | src/dusk/openxr.cpp: gameplay_near_geometry_fade; eye-view parameters in src/dusk/openxr_camera.cpp; discard coverage and soft mode in extern/aurora/lib/gx/shader.cpp and shader_info.cpp. |
| Settings and presets | src/dusk/settings.cpp, src/dusk/ui/settings.cpp, src/dusk/ui/preset.cpp, and preset.hpp. |
Further project notes: docs/openxr-camera-comfort.md and docs/openxr-architecture.md.
References
Rob Jagnow, Google Daydream Labs. Locomotion in VR, 6 June 2017. ↩︎
Google VR Elements. Tunneling demo. Iris/cage terminology, implementation controls, and stable-background guidance. ↩︎
Footnotes
A nearby ladder is the main exception. It has no distinct object identifier, so it is removed from the capture with a distance-based fade, reusing the near-geometry fade machinery described under Face Guard. ↩︎