Add horizon leveling and harden re-orientation to the front - #643
Merged
Merged
Conversation
added 30 commits
September 25, 2026 13:31
Caching every image of a sequence is a batching optimization for the sequence graph mode, but it gated image caching: it only completes once every batch has been retrieved, which on long sequences takes long enough to starve playback. Run it alongside the sequence request instead of in front of it. Also resolve trajectory positions through a Map instead of Array.indexOf — these are looked up on every animation frame, so the linear scans scaled with sequence length — and return early when the trajectory reaches beyond the sequence into another one, where there is nothing to request.
…jumps Reorientation reset the view to the travel direction on every sequence change, which threw away the view the user had navigated in with. Three changes: Cross-sequence moves now follow the move's intent. Step* keeps the viewing direction by definition, so a step into a new sequence carries the incoming view unchanged and seeds the new sequence's look-around offset from it. Turn* is a deliberate rotation, so it re-bases to the new sequence's travel direction while preserving the look-around offset. Everything else — Next/Prev, map click, shared link, fresh load — still resets to travel plus horizon. The image stream is direction-blind, so Navigator records the direction of the last move and the reorientation component consumes it synchronously with the landing image. Spatial navigation does not all go through moveDir$: the keyboard handler and the pano/step direction circles resolve the edge themselves and call moveTo$, so moveTo$ takes the intended direction too and those call sites pass it. Spherical counts as a step, since that is how panoramas navigate. Within a sequence, a landing with no direction on an image that is not a neighbour of the one we left is a jump (map click, pKey change), not a step. The incoming view says nothing about the new position, so reorient even when the landing image's GPS speed reads as stationary — previously such a jump kept the old heading and left the user facing sideways. Both neighbours are now pre-oriented rather than just the next one. Without a hint, backward navigation only reorients on arrival past the 15 degree threshold, so on gentle stretches it held the carried bearing. The engine records prevId and warms the previous image to support this. Adds getReorientedBearing(id), returning the heading an image will be shown at from the precomputed cache, so a host application can point map UI at the same direction the viewer will land on. The look-around offset applies only to same-sequence ids, since a cross-sequence landing resets it.
…rners Three changes to how the reorientation component decides what the user should be looking at. Arrows keep the view they carry. Crossing into a new sequence with a direction arrow used to branch on Step* vs Turn*, rotating the base to the new sequence's travel on a turn. The state layer has already matched the angle by then, so rotating on top of it fought the transition the arrow just made. Both now keep the carried view and adopt it as the new sequence's look-around offset. The same applies to a sideways hop — an arrow landing somewhere other than the image next to the one we left is a parallel pass or a doubling-back return leg, whose travel can be the reverse of ours, and facing it would swing the user around. Hosts can hand over a view. A shared link carries explicit basic coordinates the user framed themselves, and the component never observed them, so the offset was discarded and the next navigation snapped to travel. adoptView() takes those coordinates as the look-around offset instead. Passing them in rather than reading the viewer avoids racing the host applying them. reorientsOnSpatialNavTo() exposes the arrow decision so a host can predict the landing heading, and reorientOnSpatialNav opts out of in-sequence arrow reorientation to compare against plain carried-view navigation. Sharp corners are motion, not noise. The outlier test rejected a large bearing change between two consecutive segments that were both at sustained speed. That is a corner, not a bad fix — noise only reaches the test through the low-speed branch — and rejecting it marked the sharpest corners, and via the history window the whole stretch after them, as "not moving": exactly where reorientation is wanted. The test now only applies when at least one segment is below the moving speed. The low-speed distance floor drops from 2m to 0.5m for the same reason: walking-pace capture puts frames about a metre apart, and the compass agreement already separates that from a stationary camera's GPS drift.
When SfM compass data is unavailable, derive the panorama frame from the original compass angle and use the sequence GPS track for direction of movement.
added 25 commits
September 25, 2026 13:32
Preserve the panorama view axis in engine results so public predictions and settled events return geographic bearings even when an unmerged panorama uses a fixed equirectangular axis.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
This follows up on #640 to make reorientation reliable across real-world navigation cases, including incomplete reconstruction data, unmerged panoramas, sequence changes, playback, and inconsistent capture poses.
The changes aim to keep navigation responsive while preserving the user’s viewing direction and avoiding sudden spins, horizon jumps, bad-pose flashes, and incompatible transitions.
Have you read the Contributing Guidelines?
Yes
Contribution
Test Plan
yarn lint
yarn compile-test
yarn jest --runInBand --silent
97 test suites passed
1,405 tests passed