The rail's state is which card, not which pixel.
The common read: The scroll offset is a number the browser holds until something changes it, so content growing to the left of the card you are on pushes that card along the rail.
A snapping container's state is the snap target it has selected, and the scroll offset is derived from that target rather than the other way round. That single sentence is what the last three screens have been circling, and it is worth holding in exactly this form, because everything else about scroll snap is a consequence of it.
Every consequence is a thing you would otherwise have written code for. Content changes and the container re-snaps to the element it had selected, which is the measurement you just watched and is layout-shift immunity you get for declaring two properties.
A scroll ends and the container selects the nearest snap position in each snapped axis: measured on the pinned engines, a request for 1450 on a rail of 300-pixel cards settles at 1500 and a request for 420 settles at 300, in all three.
Anything that scrolls the container goes through the same pipeline, including the scroll the browser performs when focus moves to something off screen: measured on the pinned engines, moving focus into a button in the fourth card takes the rail from 0 to 900 and puts that card's leading edge at the rail's, in all three, where the same move without snapping stops at three different arbitrary offsets.
The one part of the selection you are allowed to constrain is whether a fast gesture may pass over targets on its way. scroll-snap-stop: always on a child forbids that, and normal, the initial value, permits it. Measure this one and you get nothing: on all three pinned engines a programmatic scrollTo that crosses four targets lands on the requested one with always and with normal alike, because there is no fling to pass over.
That is the honest shape of the property, and it is worth knowing before you write a test for it: scroll-snap-stop is a rule about a gesture, so a headless browser, a unit test and your own keyboard will all report that it does nothing.
Stop thinking of the rail's position as a number you manage. Declare the snap targets, declare whether a fling may skip them, and read scrollLeft as a derived value, because the moment you write to it you are arguing with a state machine that will re-snap on the next frame.