Back to notes
The Tesseract MemoryTechnical note

Smooth scrolling does not guarantee a continuous figure

Native scroll can be correct while neighbouring particles move apart. A hard head–neck boundary needed a continuous motion field, not more scroll smoothing.

  • WebGL
  • Native scroll
  • Debugging
  • Motion

The page can respond correctly to scrolling while the figure still develops a seam. These are different systems. Scroll determines which pose is being visited; the shader determines how neighbouring particles move within and between those poses.

The scroll mapping is direct and reversible. The earlier motion shader, however, contained a discontinuity at the head–neck boundary. Replacing that rule required a spatial correction, not more smoothing of the reader’s scroll input.

Use the page’s real geometry

The homepage measures the Hero, Notes, Builds, and Now sections. Between two consecutive anchors, progress is the current scroll position’s fraction of that interval. Unequal section heights remain unequal; the controller does not manufacture a second animation-sized document.

js
return index +
  (scrollY - anchors[index]) /
  (anchors[index + 1] - anchors[index]);

The scroll listener is passive. It reads the current position without cancelling the browser’s scroll event or moving the page itself. Reversing direction therefore revisits the same progress values, and a large jump goes straight to the corresponding chapter. The homepage also passes zero endpoint dwell to the renderer.

The figure’s overall scale and translation are eased spatially between chapter frames. That does not introduce a time-based follower for scroll progress. The distinction matters: an eased composition can still stay attached to the reader’s current position.

The old displacement boundary

The particle data assigns each grain an anatomical class. In the earlier shader below, a step function turned the head class into a binary face flag. The neck fell on the other side of that boundary and received the torso movement rules.

glsl
float face = 1.0 - step(0.5, aRegion);
float torso = (1.0 - face) * (1.0 - atmosphere);

offset += vec2(
  (point.x - 0.75) * 6.0 * torso,
  -1.15 * torso - 0.25 * face
) * breath;

A head particle received a vertical breathing amplitude of 0.25, while an adjacent neck particle received 1.15 and a torso-only horizontal term. The source pixels could be neighbours, yet the displacement calculation gave them an abrupt change in motion.

Source-data adjacency analysis found 171 neighbouring head–neck pairs with approximately 0.90–1.02 units of breathing displacement discontinuity in the shader’s model CSS-pixel coordinate system. This is a measurement of the displacement calculation, not a measured gap in a browser screenshot.

Two other terms could amplify the difference. Small idle offsets used independent per-particle phases, and pointer gain changed from 4 for the face to 18 for the neck and body. Those were discontinuities in the movement rules, separate from the source image and scroll controller.

The correction is spatial, not temporal

The replacement uses a continuous chest-centred displacement field, with smooth protection around the head and hands. A shared rigid microtranslation moves the figure together; a Gaussian chest rise and expansion add breathing, while a small correlated spatial flow replaces independent per-particle jitter.

The protection collars are C1: their values and first derivatives remain continuous through the boundaries. Pointer response is released smoothly through those same protected regions. Anatomical classes still help match particle identities between poses, but no longer act as abrupt switches for these breathing and pointer gains.

Head and hand bounds are derived once from the frozen particle geometry for each pose, then follow the pose interpolation in the existing master coordinate system. The input images, particle IDs, and source alpha remain unchanged; the correction is in how the existing particles are displaced.

What the checks do—and do not—show

The CPU reference tests replayed the 171 original head–neck neighbour pairs. Their peak displacement difference fell below 0.000054 model CSS pixels in the tested samples, compared with the earlier 0.90–1.02 range. Separate checks cover protected head and hand cores, containment, local strain, and pose-transition samples across multiple widths.

The useful debugging order is to separate progress, correspondence, and deformation. First establish where the animation should be. Then establish which particle is moving where. Finally inspect the field that adds motion around that path. Smooth input is only the first of those guarantees.