Scroll Semantics for 3D Sites: Rails, Detents, and Scroll Grammars
A 3D website does not need a new input model. The approach described here keeps the visitor's familiar one-dimensional scroll gesture and maps it, through a single scalar called the rail cursor, onto authored camera and world motion — with snap detents for section structure, a small catalog of named "scroll grammars" for what forward motion means, and build-time vestibular safety checks. The reference implementation is the Software 4 Senses 3D Spatial Web Studio ("the Studio" below).
Scroll here is a 2D input grammar driving a 3D document: rails bind the camera and the world-side elements of the Scene Document Model — pure 3D elements with document structure, among them the distance-field text and image planes the visitor reads. "World-moves" grammars are document elements traveling past a still camera.
Rails, not WASD
"Rails, not WASD" means the visitor never pilots the camera: they keep the one-dimensional scroll input they already have, and the site translates it into bounded, choreographed motion. Scroll stays scroll and click stays click; what changes is what the world does in response.
Rails, not WASD — the input philosophy of a 3D Spatial Web Interface: the visitor emits simple one-dimensional intents, and the site translates them into authored, bounded camera and world motion. The camera is on rails, and the rails are the design — spatial richness lives in the choreography, not in free movement.
Free piloting fails general audiences for a structural reason: WASD-plus-mouselook is a trained skill, and a website's audience arrives with reading reflexes, not piloting ones. The moment a visitor can steer freely, "lost", "stuck", "facing the wrong way", and "motion sick" all become reachable states, and content becomes missable. The Studio's design contract makes the alternative checkable as guardrail G1: one-dimensional input must suffice to reach every piece of content. A second stance follows: world-moves by default, camera-moves for meaningful navigation — within a section content flows past a nearly still camera; between sections the camera makes one deliberate, eased move.
One scalar to rule the stage
Every camera pose and every bound world translation on a stage — one continuous camera-space experience, the spatial equivalent of a page — is a pure function of one
scalar: the rail cursor s, measured in rail units (approximately CSS pixels of wheel
travel). A rail is a piecewise sequence of spans laid end to end along that axis — camera
spans carry an arc-length-sampled pose track (the visitor travels), while world spans
bind group translations (content travels past a fixed camera). The path model has no engine, DOM, or time dependency, which makes the whole scroll
system deterministic and unit-testable: give it an s, get back the exact pose.
Between the input device and that pure function sits a six-mode dynamics machine — idle, tracking, coasting, settling, gliding, suspended. The modes encode a deliberate asymmetry between self-driven and autonomous motion:
- Tracking follows the hand 1:1 with no easing and no smoothing. The design rationale is stated in the Studio's rail dynamics module: self-driven motion is the comfort exemption — "the user's hand IS the easing".
- Coasting decays release velocity exponentially (friction default 4/s, a designed feel constant), and resolves its projected stopping point against the snap map once, at the moment of release.
- Settling and gliding approach a target with a velocity-capped trapezoid (accelerate, cruise at the cap, decelerate to land exactly), entered with the live velocity so a coast hands its momentum straight in. The cap is the point: a duration-based ease has no velocity ceiling — the module's design analysis notes a fixed-duration full-rail glide would peak at roughly six times the design scrub velocity the rail was safety-certified against — while the trapezoid is provably capped, so a certified rail stays certified under programmatic navigation.
Autonomous glides are additionally driven, when available, by a real Web Animations API animation on one leased CSS custom property — the rail cursor itself — so easing is closed-form and time-parameterized: every sampled value sits exactly on the curve regardless of frame jitter. The interruption contract holds throughout: input wins immediately, from the live value — same-direction input steals control back to tracking, opposing input reverses the transition natively.
Detents and snap
A detent is a named snap point on the rail — the anchor of one section — and each one declares one of three snap modes. Soft detents capture a coast whose projected stop lands inside their basin (the default capture fraction of 0.5 tiles the whole gap between neighbors, so a soft rail always comes to rest on a detent). Hard detents always complete the settle to the nearest detent, whatever the distance — elevator floors, where parking between floors is not a legal state. None marks a labeled free span where reading can idle anywhere.
Detent — a named snap point on a scroll rail, the 3D equivalent of a section anchor. Snap modes are soft (basin capture), hard (always settle to the nearest detent), and none (free span); detent ids are stable addresses, designed to double as URL anchors.
Release behavior is directional, not merely nearest-neighbor. Displacement from the last settled detent past a commit fraction of the gap — 12% by default — commits the release forward to the neighbor rather than tugging the visitor back into the basin they left. That threshold began at 20% and was tuned down to 12% in user feel-testing (): a slow, deliberate scroll should not have to fight the basin it is leaving. Two companion heuristics round out the feel: wheels emit no release event, so a short input-silence window (50 ms by default) stands in for "gesture ended", and a release faster than a flick-skip threshold (2,500 rail units/s by default) targets one detent past the natural stopping point — one hard flick, one section skipped.
Input devices are normalized before any of this runs. Discrete mouse wheels report large, quantized pixel deltas (the ±120-per-notch family), while trackpads stream small fractional deltas at high rate; the rail input layer maps each detected notch to a fixed 100 rail units, so a notched mouse and a trackpad traverse a rail comparably instead of the notched mouse arriving everywhere first.
The eight scroll grammars
A scroll grammar is a named answer to the question "what does scrolling mean here?" — chosen per rail span, and allowed to change at section boundaries. Scroll on the 2D web quietly conflates three roles: advancing through content, sensing position, and pacing. Each grammar answers all three with a different spatial metaphor, and because position along the rail is visible in the world itself, every grammar gets a diegetic progress read — you can see how much is left.
Scroll grammar — a named genre of forward motion that a rail span adopts, defining what the one-dimensional scroll gesture does to the camera and the world on that span. The Studio's design catalog names eight.
| ID | Grammar | What scrolling means | Status |
|---|---|---|---|
| S1 | Conveyor | The world flows past a fixed camera — the comfort default for content flows. | Shipped choreography (working scene, including departure choreography). |
| S2 | Dolly Rail | The camera travels a corridor of content. | Mechanics shipped — camera rail spans implement dolly movement; no dedicated showcase scene. |
| S3 | Elevator | Sections are floors; scroll moves the cab vertically, hard-snapping to floors. | Mechanics shipped — hard detents settle a vertical camera span onto its floors. |
| S4 | Turntable | Scroll rotates a radial hub of content around the viewer's position. | Named design taxonomy. |
| S5 | Focus Pull | Scroll moves the focal plane through a z-stack of content. | Named design taxonomy. |
| S6 | Page Turn / Fold | Scroll folds or turns content like leaves of a physical document. | Named design taxonomy. |
| S7 | Semantic Zoom | Scroll changes scale of attention — a "Powers of Ten" descent through detail levels. | Named design taxonomy; explicitly a v2 candidate. |
| S8 | Orbit Vignettes | Scroll advances between vignettes, each allowing a bounded ±30° orbit. | Named design taxonomy. |
The status column is the honest part of the table. The taxonomy of eight is published design vocabulary; what ships today as working choreography is the Conveyor, with elevator mechanics (hard detents on a vertical camera span) and dolly mechanics (camera spans) present in the rail layer. The remaining five are the named roadmap — useful now precisely because naming a grammar is how a span declares its intent.
Vestibular safety as lintable code
Comfort in this model is not a review checklist — the properties that protect the visitor's inner ear are structural, or are validated when the rail is built, not discovered when a visitor gets dizzy.
- The horizon cannot roll, by construction. Camera up is never an authorable value: it is derived at every sample by projecting world-up perpendicular to the view direction, using the same orthonormal projection the renderer uses to build its view basis — so rail-produced views are roll-free in the exact basis the shader sees.
- Pitch is capped short of vertical. Pose tracks that pitch beyond ±80° from horizontal approach the up-projection singularity and are rejected when the rail is built.
- Turn rate is guarded at build time. A validator walks every camera span at the design scrub velocity (default 3,000 rail units/s) and rejects any track whose turns could exceed roughly 90°/s at that speed — the Studio's own source calls it "the vestibular checklist made lintable". Quantified: a 90° corridor turn at the default design velocity needs at least 3,000 rail units of span. The guard bounds what inertia and programmatic glides can do; raw hand-tracking is exempt, because self-driven motion is the comfort exemption.
- Transitions are short and always interruptible. The design contract caps navigation transitions at 600 ms and micro-moves at 300 ms; the hard guarantee in shipped code is interruptibility — input steals control on any tick, from the live value, in every dynamics mode.
- Reduced motion degrades to cuts. Under
prefers-reduced-motion, autonomous moves (settle, glide, coast) land in a single step with events firing in order, while hand-tracking is untouched — the visitor who asked for less motion gets identical behavior with the animation removed, not a different site.
The division of labor matters: the roll-free up vector, the pitch cap, the angular-rate guard, and the reduced-motion collapse are shipped code in the Studio's rail layer; the broader ten-guardrail comfort contract they come from is a published design contract the code progressively absorbs. A comfort rule a build step can reject is worth ten a style guide can only recommend.
Related reading
- Mapping CSS Box Models and Grid Layouts to 3D Coordinates — how the slabs a rail scrolls past get their positions from real CSS.
- Event Propagation in 3D: From Screen Space to Scene Records — how clicks and pointer events reach the scene elements the rail moves.
- Synchronizing Web Animations API Easing with a GPU Render Loop — the timing substrate the rail's autonomous glides ride.
- The Scene Document Model: Document Structure for 3D Elements — the document structure of the 3D elements a rail drives.