CNA-GAP-067: ScrollWheelValue drops sub-notch wheel motion: the SDL3 platform counts whole notches only, where XNA accumulates raw wheel units
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
The SDL3 platform truncates every wheel event to whole notches before scaling by 120 (a deliberate FNA-parity rule in docs/input-fna-fidelity.md), so high-resolution wheels and touchpads can scroll without changing ScrollWheelValue; XNA accumulates 1/120 units.
- Identifier
CNA-GAP-067- Category
- Functional gap
- Subsystem
- Input
- Status
- Open
- Verified against
- CNA
c1c316b9(c1c316b9c7a846ce8002809c151fcd1af14942c9) - Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- MouseState::getScrollWheelValueProperty() and getHorizontalScrollWheelValueEXTProperty() as reported by Mouse::GetState()
Expected behaviour
XNA 4.0 on Windows adds the raw signed WM_MOUSEWHEEL delta to the wheel value (MouseMessageHooker.WndProc, decompiled reference), so a high-resolution wheel moves it in steps smaller than 120. The SDL3 backend deliberately follows FNA instead (cast to int before multiplying by 120), which is the documented and tested behaviour; the contract on SDL3 is whole notches.
Actual behaviour at TARGET
Sdl3EventMapper.cpp passes SDL's float wheel.x/wheel.y, and AccumulateWheel in Sdl3InputServices.cpp converts each event to an integer number of notches before multiplying by 120. An event of 0.25 notch adds nothing, so a touchpad or smooth-scrolling wheel that reports fractions per event can leave ScrollWheelValue unchanged for a whole gesture. SDL delivers the fractional amounts and CNA discards them, so this is a narrower contract on CNA's side, not a host constraint.
input-fna-fidelity.md records the truncation as an intentional FNA match that keeps the value “a clean multiple of 120 exactly like XNA”; XNA's own hook does not truncate.
Source locations
modules/platform/src/Sdl3/Sdl3InputServices.cpp— AccumulateWheel and Sdl3Mouse::ObserveEventmodules/platform/src/Sdl3/Sdl3EventMapper.cpp— SDL_EVENT_MOUSE_WHEEL mappingdocs/input-fna-fidelity.md— Mouse: the wheel truncation recorded as an intentional deviationmodules/platform/tests/CNA/Platform/Sdl3InputServicesTests.cpp— Sdl3MouseTest.WheelEventsAccumulateInXnaUnitsAndTruncateBeforeScaling
Evidence
Checked by reading at c1c316b9; not executed. The XNA behaviour was read from the decompiled XNA 4.0 MouseMessageHooker, FNA's from FNA-XNA/FNA b3551247. The SDL3 rule is pinned by Sdl3MouseTest.WheelEventsAccumulateInXnaUnitsAndTruncateBeforeScaling, so the truncation is deliberate, not accidental. Whether a given touchpad reports sub-notch events through SDL depends on the host and was not observed.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
Sdl3InputServicesTests.cpp pins the truncation (1.9 notches count as one). No test asserts XNA's sub-notch accumulation.
Regression test
If XNA's rule is chosen: two half-notch wheel events must change ScrollWheelValue by 60 each (120 together), replacing the truncation test.
Blast radius
Games that read ScrollWheelValue for zoom or scrolling with touchpads or free-spinning wheels.
Workaround
None inside the XNA surface; a game that needs smooth scrolling on SDL3 has to read wheel events itself.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Input: mouse
- Deep dives
- The input model: MouseState
- Known issues
- Functional gap index