From 8073dd180e938c2aa78856b1ed8dba715726ce81 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:52:46 +0200 Subject: [PATCH 01/12] refactor(transform): extract resolveLevel from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 39 +++++++++++++++++++++++---------------- src/transform_model.h | 4 ++++ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index 0f17c686..2e0502cf 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -506,22 +506,7 @@ void TransformModel::idleTick() { } } -void TransformModel::present(const MapResult& r, double level, const Config& cfg, - const MonitorTarget& mon, const PresentExtras& ex) { - (void)mon; - // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the - // render model's mapper already solves the whole centered geometry - including the edge zones, - // where the pointer slides away from the centre as the source rect clamps. So the mapper's - // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame - // itself (native cursor, issue #369): nothing here places a pointer of our own. - // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources - // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. - // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface - // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). - // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a - // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s - // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle - // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). +double TransformModel::resolveLevel(const MapResult& r, double level, const Config& cfg, double& preLadderOut) { // Ramp cost limiter (measured: 150-215ms spikes during zoom ramps at high level): every LEVEL // change makes DWM re-scale its cached surfaces, and the cost grows with the level. Apply the // ramping level at most every 3rd tick (48Hz on a 144Hz panel - visually still a smooth ramp); @@ -606,6 +591,28 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg rampStepHeld_ = cfg.txSamplingMode == 1 && dwmCentreOn_ && RampStepHeld(applyLevel, lastLevel_, cfg.txRampMinStep / 1000.0, cfg.txRampMinFrom, rampStopped); if (rampStepHeld_) applyLevel = lastLevel_; + preLadderOut = preLadderLevel; + return applyLevel; +} + +void TransformModel::present(const MapResult& r, double level, const Config& cfg, + const MonitorTarget& mon, const PresentExtras& ex) { + (void)mon; + // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the + // render model's mapper already solves the whole centered geometry - including the edge zones, + // where the pointer slides away from the centre as the source rect clamps. So the mapper's + // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame + // itself (native cursor, issue #369): nothing here places a pointer of our own. + // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources + // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. + // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface + // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). + // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a + // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s + // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle + // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). + double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it + const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); double srcL = r.srcLeft, srcT = r.srcTop; if (applyLevel != level) { OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); diff --git a/src/transform_model.h b/src/transform_model.h index 98667ec0..82845b42 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -157,6 +157,10 @@ class TransformModel : public IMagnifierModel { void noteWrite(double ms, bool ok); void noteIxWrite(double ms, bool ok); // input-transform publish stats (issue #189) void noteIxStomp(); // foreign writer overwrote our publish (#217) + // present() steps, in call order (split out of present(), issue #476). + // The level this tick applies: step cap, smooth-zoom ladder, held ramp steps. Also reports the + // level before the ladder snap (the ramp test reads it). + double resolveLevel(const MapResult& r, double level, const Config& cfg, double& preLadderOut); std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 31b850a718a57d752276b5d3ade5bdcccf583b79 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:52:58 +0200 Subject: [PATCH 02/12] refactor(transform): extract applySamplingMode from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 58 ++++++++++++++++++++++------------------- src/transform_model.h | 1 + 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index 2e0502cf..bf2242ba 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -595,33 +595,7 @@ double TransformModel::resolveLevel(const MapResult& r, double level, const Conf return applyLevel; } -void TransformModel::present(const MapResult& r, double level, const Config& cfg, - const MonitorTarget& mon, const PresentExtras& ex) { - (void)mon; - // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the - // render model's mapper already solves the whole centered geometry - including the edge zones, - // where the pointer slides away from the centre as the source rect clamps. So the mapper's - // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame - // itself (native cursor, issue #369): nothing here places a pointer of our own. - // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources - // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. - // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface - // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). - // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a - // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s - // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle - // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). - double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it - const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); - double srcL = r.srcLeft, srcT = r.srcTop; - if (applyLevel != level) { - OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); - srcL = o.x; srcT = o.y; - } - ClickInProgress(); // keep the click window current between nudges - DeliverOwedNudge(); - restLevel_ = cfg.txRestLevel; // hot - if (!ensureMag()) return; // lazy context: the session's first write brings DWM up +void TransformModel::applySamplingMode(const Config& cfg) { // Bitmap smoothing (issue #197/#227), once per magnification context. The smooth filter // is the WHOLE quality gap to native Magnifier: sharp magnified image and a cursor that // grows naturally with the zoom instead of pixelating. Known trade, accepted after a full @@ -662,6 +636,36 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg } } } +} + +void TransformModel::present(const MapResult& r, double level, const Config& cfg, + const MonitorTarget& mon, const PresentExtras& ex) { + (void)mon; + // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the + // render model's mapper already solves the whole centered geometry - including the edge zones, + // where the pointer slides away from the centre as the source rect clamps. So the mapper's + // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame + // itself (native cursor, issue #369): nothing here places a pointer of our own. + // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources + // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. + // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface + // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). + // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a + // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s + // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle + // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). + double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it + const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); + double srcL = r.srcLeft, srcT = r.srcTop; + if (applyLevel != level) { + OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); + srcL = o.x; srcT = o.y; + } + ClickInProgress(); // keep the click window current between nudges + DeliverOwedNudge(); + restLevel_ = cfg.txRestLevel; // hot + if (!ensureMag()) return; // lazy context: the session's first write brings DWM up + applySamplingMode(cfg); traceOn_ = cfg.txTrace != 0; if (level > sessionMaxLevel_) sessionMaxLevel_ = level; const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); diff --git a/src/transform_model.h b/src/transform_model.h index 82845b42..8220e52f 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -161,6 +161,7 @@ class TransformModel : public IMagnifierModel { // The level this tick applies: step cap, smooth-zoom ladder, held ramp steps. Also reports the // level before the ladder snap (the ramp test reads it). double resolveLevel(const MapResult& r, double level, const Config& cfg, double& preLadderOut); + void applySamplingMode(const Config& cfg); // bitmap smoothing, bounded retries (#274) std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From d4adeefa2fce34baa06f48c68e1ff94e5f218aa0 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:03 +0200 Subject: [PATCH 03/12] refactor(transform): extract writeSiteTransform from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 67 ++++++++++++++++++++++------------------- src/transform_model.h | 3 ++ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index bf2242ba..1aaa128f 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -638,37 +638,7 @@ void TransformModel::applySamplingMode(const Config& cfg) { } } -void TransformModel::present(const MapResult& r, double level, const Config& cfg, - const MonitorTarget& mon, const PresentExtras& ex) { - (void)mon; - // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the - // render model's mapper already solves the whole centered geometry - including the edge zones, - // where the pointer slides away from the centre as the source rect clamps. So the mapper's - // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame - // itself (native cursor, issue #369): nothing here places a pointer of our own. - // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources - // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. - // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface - // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). - // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a - // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s - // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle - // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). - double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it - const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); - double srcL = r.srcLeft, srcT = r.srcTop; - if (applyLevel != level) { - OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); - srcL = o.x; srcT = o.y; - } - ClickInProgress(); // keep the click window current between nudges - DeliverOwedNudge(); - restLevel_ = cfg.txRestLevel; // hot - if (!ensureMag()) return; // lazy context: the session's first write brings DWM up - applySamplingMode(cfg); - traceOn_ = cfg.txTrace != 0; - if (level > sessionMaxLevel_) sessionMaxLevel_ = level; - const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); +MagTransform TransformModel::writeSiteTransform(double srcL, double srcT, double applyLevel, const Config& cfg) { // Edge sampling margin (see transform.h) applied to the SOURCE, not just to the written // transform: srcL/srcT go on to feed the input-transform publish below, and a visual rect // that sat one texel inside a published rect that did not would put the pointer framework's @@ -710,6 +680,41 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg if (m.offX > maxOff) m.offX = maxOff; if (m.txX < -32000) m.txX = -32000; } + return m; +} + +void TransformModel::present(const MapResult& r, double level, const Config& cfg, + const MonitorTarget& mon, const PresentExtras& ex) { + (void)mon; + // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the + // render model's mapper already solves the whole centered geometry - including the edge zones, + // where the pointer slides away from the centre as the source rect clamps. So the mapper's + // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame + // itself (native cursor, issue #369): nothing here places a pointer of our own. + // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources + // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. + // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface + // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). + // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a + // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s + // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle + // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). + double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it + const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); + double srcL = r.srcLeft, srcT = r.srcTop; + if (applyLevel != level) { + OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); + srcL = o.x; srcT = o.y; + } + ClickInProgress(); // keep the click window current between nudges + DeliverOwedNudge(); + restLevel_ = cfg.txRestLevel; // hot + if (!ensureMag()) return; // lazy context: the session's first write brings DWM up + applySamplingMode(cfg); + traceOn_ = cfg.txTrace != 0; + if (level > sessionMaxLevel_) sessionMaxLevel_ = level; + const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); + MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); bool txWroteThisTick = false; // for the trace // DWM CENTRING (issue #369, src/native_cursor.h). In a native-cursor session where the view is // a pure function of the pointer (RunTick's ex.dwmCentre), DWM re-centres the view itself on diff --git a/src/transform_model.h b/src/transform_model.h index 8220e52f..70163cba 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -1,6 +1,7 @@ #pragma once #include "magnifier_model.h" #include "mag_host.h" +#include "transform.h" // MagTransform #include "comp_pin.h" #include "cursor_blanker.h" #include "cursor_sprite.h" @@ -162,6 +163,8 @@ class TransformModel : public IMagnifierModel { // level before the ladder snap (the ramp test reads it). double resolveLevel(const MapResult& r, double level, const Config& cfg, double& preLadderOut); void applySamplingMode(const Config& cfg); // bitmap smoothing, bounded retries (#274) + // The transform to write: edge floor, ComputeMagTransform, 16-bit backstop, tdrTest probe. + MagTransform writeSiteTransform(double srcL, double srcT, double applyLevel, const Config& cfg); std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 590c9bac2c79153b4dc1e6a1d2e6c85c532e52d3 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:07 +0200 Subject: [PATCH 04/12] refactor(transform): extract switchDwmCentre from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 46 ++++++++++++++++++++++------------------- src/transform_model.h | 1 + 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index 1aaa128f..8f061dd7 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -683,6 +683,30 @@ MagTransform TransformModel::writeSiteTransform(double srcL, double srcT, double return m; } +void TransformModel::switchDwmCentre(double applyLevel, const PresentExtras& ex) { + // DWM CENTRING (issue #369, src/native_cursor.h). In a native-cursor session where the view is + // a pure function of the pointer (RunTick's ex.dwmCentre), DWM re-centres the view itself on + // every cursor update, latched with the pointer it draws: no tick-to-frame drift at any speed. + // Switching off is allowed on a paused tick (it stops DWM moving the view); switching on waits + // for a tick that may write, because the switch needs the forced write that follows it. + // The owed write survives paused ticks (forceWrite_), so a switch-off during a pause still puts + // Wind's offset back on the first tick that may write. + { + // A broken export (dwmCentreBroken_) removes the wish, so the switch is not retried (and the + // write forced, the line logged) on every tick; the failure itself is logged once, in + // setDwmCentre. + const bool want = nativeSession_ && ex.dwmCentre && applyLevel > 1.001; + if (WantDwmCentreSwitch(want, dwmCentreOn_, dwmCentreBroken_, ex.pauseWrites)) { + const bool before = dwmCentreOn_; + setDwmCentre(want); + if (dwmCentreOn_ != before) { + forceWrite_ = true; + wind::Log(wind::LogLevel::Info, "transform", "DWM centring %s", dwmCentreOn_ ? "ON" : "off"); + } + } + } +} + void TransformModel::present(const MapResult& r, double level, const Config& cfg, const MonitorTarget& mon, const PresentExtras& ex) { (void)mon; @@ -716,27 +740,7 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); bool txWroteThisTick = false; // for the trace - // DWM CENTRING (issue #369, src/native_cursor.h). In a native-cursor session where the view is - // a pure function of the pointer (RunTick's ex.dwmCentre), DWM re-centres the view itself on - // every cursor update, latched with the pointer it draws: no tick-to-frame drift at any speed. - // Switching off is allowed on a paused tick (it stops DWM moving the view); switching on waits - // for a tick that may write, because the switch needs the forced write that follows it. - // The owed write survives paused ticks (forceWrite_), so a switch-off during a pause still puts - // Wind's offset back on the first tick that may write. - { - // A broken export (dwmCentreBroken_) removes the wish, so the switch is not retried (and the - // write forced, the line logged) on every tick; the failure itself is logged once, in - // setDwmCentre. - const bool want = nativeSession_ && ex.dwmCentre && applyLevel > 1.001; - if (WantDwmCentreSwitch(want, dwmCentreOn_, dwmCentreBroken_, ex.pauseWrites)) { - const bool before = dwmCentreOn_; - setDwmCentre(want); - if (dwmCentreOn_ != before) { - forceWrite_ = true; - wind::Log(wind::LogLevel::Info, "transform", "DWM centring %s", dwmCentreOn_ ? "ON" : "off"); - } - } - } + switchDwmCentre(applyLevel, ex); // Trace inputs, captured here and appended at the END of present(). bool trChanged = false, trRamping = ramping, trWarm = false; const double trLevel = applyLevel; diff --git a/src/transform_model.h b/src/transform_model.h index 70163cba..657ecec7 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -165,6 +165,7 @@ class TransformModel : public IMagnifierModel { void applySamplingMode(const Config& cfg); // bitmap smoothing, bounded retries (#274) // The transform to write: edge floor, ComputeMagTransform, 16-bit backstop, tdrTest probe. MagTransform writeSiteTransform(double srcL, double srcT, double applyLevel, const Config& cfg); + void switchDwmCentre(double applyLevel, const PresentExtras& ex); std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 2f5e7c14cd9df189f7a9cc0b5b3da84be8d84cb9 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:11 +0200 Subject: [PATCH 05/12] refactor(transform): extract writeAndWarm from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 94 ++++++++++++++++++++++------------------- src/transform_model.h | 3 ++ 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index 8f061dd7..991738cc 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -707,50 +707,8 @@ void TransformModel::switchDwmCentre(double applyLevel, const PresentExtras& ex) } } -void TransformModel::present(const MapResult& r, double level, const Config& cfg, - const MonitorTarget& mon, const PresentExtras& ex) { - (void)mon; - // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the - // render model's mapper already solves the whole centered geometry - including the edge zones, - // where the pointer slides away from the centre as the source rect clamps. So the mapper's - // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame - // itself (native cursor, issue #369): nothing here places a pointer of our own. - // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources - // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. - // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface - // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). - // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a - // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s - // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle - // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). - double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it - const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); - double srcL = r.srcLeft, srcT = r.srcTop; - if (applyLevel != level) { - OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); - srcL = o.x; srcT = o.y; - } - ClickInProgress(); // keep the click window current between nudges - DeliverOwedNudge(); - restLevel_ = cfg.txRestLevel; // hot - if (!ensureMag()) return; // lazy context: the session's first write brings DWM up - applySamplingMode(cfg); - traceOn_ = cfg.txTrace != 0; - if (level > sessionMaxLevel_) sessionMaxLevel_ = level; - const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); - MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); - bool txWroteThisTick = false; // for the trace - switchDwmCentre(applyLevel, ex); - // Trace inputs, captured here and appended at the END of present(). - bool trChanged = false, trRamping = ramping, trWarm = false; - const double trLevel = applyLevel; - const int trTxX = m.txX, trOffX = m.offX; - // pauseWrites (issue #148): a click's injected cursor move is in flight - a transform write - // racing a cursor-position update is the proven TDR, so those ticks write NOTHING. State is - // untouched; the next unpaused tick lands the same values. (The native cursor's pixel-and-back - // nudges are the intentional exception to "no cursor events beside a write": they run right - // AFTER the write on the same thread, never racing it, and never during a click.) - if (!ex.pauseWrites) { +bool TransformModel::writeAndWarm(const MagTransform& m, double applyLevel, bool ramping, const Config& cfg, + const PresentExtras& ex, bool& txWroteThisTick, bool& trChanged, bool& trWarm) { const unsigned long long nowMs = GetTickCount64(); const bool changed = m.offX != lastOffX_ || m.offY != lastOffY_ || m.txX != lastTxX_ || m.txY != lastTxY_ || applyLevel != lastLevel_; @@ -824,6 +782,54 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg } forceWrite_ = false; } + return changed; +} + +void TransformModel::present(const MapResult& r, double level, const Config& cfg, + const MonitorTarget& mon, const PresentExtras& ex) { + (void)mon; + // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the + // render model's mapper already solves the whole centered geometry - including the edge zones, + // where the pointer slides away from the centre as the source rect clamps. So the mapper's + // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame + // itself (native cursor, issue #369): nothing here places a pointer of our own. + // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources + // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. + // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface + // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). + // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a + // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s + // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle + // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). + double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it + const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); + double srcL = r.srcLeft, srcT = r.srcTop; + if (applyLevel != level) { + OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); + srcL = o.x; srcT = o.y; + } + ClickInProgress(); // keep the click window current between nudges + DeliverOwedNudge(); + restLevel_ = cfg.txRestLevel; // hot + if (!ensureMag()) return; // lazy context: the session's first write brings DWM up + applySamplingMode(cfg); + traceOn_ = cfg.txTrace != 0; + if (level > sessionMaxLevel_) sessionMaxLevel_ = level; + const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); + MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); + bool txWroteThisTick = false; // for the trace + switchDwmCentre(applyLevel, ex); + // Trace inputs, captured here and appended at the END of present(). + bool trChanged = false, trRamping = ramping, trWarm = false; + const double trLevel = applyLevel; + const int trTxX = m.txX, trOffX = m.offX; + // pauseWrites (issue #148): a click's injected cursor move is in flight - a transform write + // racing a cursor-position update is the proven TDR, so those ticks write NOTHING. State is + // untouched; the next unpaused tick lands the same values. (The native cursor's pixel-and-back + // nudges are the intentional exception to "no cursor events beside a write": they run right + // AFTER the write on the same thread, never racing it, and never during a click.) + if (!ex.pauseWrites) { + const bool changed = writeAndWarm(m, applyLevel, ramping, cfg, ex, txWroteThisTick, trChanged, trWarm); // Input transform. Mode 1 (THE SHIPPED DEFAULT; field-verified 4x-20x, // POINTER-HITTEST-FINDINGS.md): publish the visual source rect on every change, exactly // like native Magnifier. Pointer-framework apps (Explorer/Settings/shell) hit-test mouse diff --git a/src/transform_model.h b/src/transform_model.h index 657ecec7..20f8e31d 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -166,6 +166,9 @@ class TransformModel : public IMagnifierModel { // The transform to write: edge floor, ComputeMagTransform, 16-bit backstop, tdrTest probe. MagTransform writeSiteTransform(double srcL, double srcT, double applyLevel, const Config& cfg); void switchDwmCentre(double applyLevel, const PresentExtras& ex); + // The transform write and the warm pulse; returns whether the values changed this tick. + bool writeAndWarm(const MagTransform& m, double applyLevel, bool ramping, const Config& cfg, + const PresentExtras& ex, bool& txWroteThisTick, bool& trChanged, bool& trWarm); std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From de5dc981760d12cbfe1c30e1efb52f594d5f2857 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:14 +0200 Subject: [PATCH 06/12] refactor(transform): extract publishInputTransform from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 95 ++++++++++++++++++++++------------------- src/transform_model.h | 3 ++ 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index 991738cc..b78b0bca 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -785,51 +785,8 @@ bool TransformModel::writeAndWarm(const MagTransform& m, double applyLevel, bool return changed; } -void TransformModel::present(const MapResult& r, double level, const Config& cfg, - const MonitorTarget& mon, const PresentExtras& ex) { - (void)mon; - // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the - // render model's mapper already solves the whole centered geometry - including the edge zones, - // where the pointer slides away from the centre as the source rect clamps. So the mapper's - // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame - // itself (native cursor, issue #369): nothing here places a pointer of our own. - // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources - // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. - // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface - // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). - // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a - // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s - // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle - // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). - double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it - const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); - double srcL = r.srcLeft, srcT = r.srcTop; - if (applyLevel != level) { - OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); - srcL = o.x; srcT = o.y; - } - ClickInProgress(); // keep the click window current between nudges - DeliverOwedNudge(); - restLevel_ = cfg.txRestLevel; // hot - if (!ensureMag()) return; // lazy context: the session's first write brings DWM up - applySamplingMode(cfg); - traceOn_ = cfg.txTrace != 0; - if (level > sessionMaxLevel_) sessionMaxLevel_ = level; - const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); - MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); - bool txWroteThisTick = false; // for the trace - switchDwmCentre(applyLevel, ex); - // Trace inputs, captured here and appended at the END of present(). - bool trChanged = false, trRamping = ramping, trWarm = false; - const double trLevel = applyLevel; - const int trTxX = m.txX, trOffX = m.offX; - // pauseWrites (issue #148): a click's injected cursor move is in flight - a transform write - // racing a cursor-position update is the proven TDR, so those ticks write NOTHING. State is - // untouched; the next unpaused tick lands the same values. (The native cursor's pixel-and-back - // nudges are the intentional exception to "no cursor events beside a write": they run right - // AFTER the write on the same thread, never racing it, and never during a click.) - if (!ex.pauseWrites) { - const bool changed = writeAndWarm(m, applyLevel, ramping, cfg, ex, txWroteThisTick, trChanged, trWarm); +void TransformModel::publishInputTransform(const MagTransform& m, double applyLevel, bool ramping, bool changed, + const Config& cfg, const PresentExtras& ex) { // Input transform. Mode 1 (THE SHIPPED DEFAULT; field-verified 4x-20x, // POINTER-HITTEST-FINDINGS.md): publish the visual source rect on every change, exactly // like native Magnifier. Pointer-framework apps (Explorer/Settings/shell) hit-test mouse @@ -957,6 +914,54 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg ixExpectedOn_ = false; } if (changed) lastInputXformOn_ = cfg.magInputTransform != 0; +} + +void TransformModel::present(const MapResult& r, double level, const Config& cfg, + const MonitorTarget& mon, const PresentExtras& ex) { + (void)mon; + // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the + // render model's mapper already solves the whole centered geometry - including the edge zones, + // where the pointer slides away from the centre as the source rect clamps. So the mapper's + // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame + // itself (native cursor, issue #369): nothing here places a pointer of our own. + // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources + // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. + // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface + // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). + // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a + // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s + // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle + // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). + double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it + const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); + double srcL = r.srcLeft, srcT = r.srcTop; + if (applyLevel != level) { + OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); + srcL = o.x; srcT = o.y; + } + ClickInProgress(); // keep the click window current between nudges + DeliverOwedNudge(); + restLevel_ = cfg.txRestLevel; // hot + if (!ensureMag()) return; // lazy context: the session's first write brings DWM up + applySamplingMode(cfg); + traceOn_ = cfg.txTrace != 0; + if (level > sessionMaxLevel_) sessionMaxLevel_ = level; + const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); + MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); + bool txWroteThisTick = false; // for the trace + switchDwmCentre(applyLevel, ex); + // Trace inputs, captured here and appended at the END of present(). + bool trChanged = false, trRamping = ramping, trWarm = false; + const double trLevel = applyLevel; + const int trTxX = m.txX, trOffX = m.offX; + // pauseWrites (issue #148): a click's injected cursor move is in flight - a transform write + // racing a cursor-position update is the proven TDR, so those ticks write NOTHING. State is + // untouched; the next unpaused tick lands the same values. (The native cursor's pixel-and-back + // nudges are the intentional exception to "no cursor events beside a write": they run right + // AFTER the write on the same thread, never racing it, and never during a click.) + if (!ex.pauseWrites) { + const bool changed = writeAndWarm(m, applyLevel, ramping, cfg, ex, txWroteThisTick, trChanged, trWarm); + publishInputTransform(m, applyLevel, ramping, changed, cfg, ex); } // !ex.pauseWrites // Edge clip: engaged while genuinely zoomed and the pointer is OURS to manage (not Inspect, // whose 1px freeze clip must never be disturbed - ex.clickOverride marks it). diff --git a/src/transform_model.h b/src/transform_model.h index 20f8e31d..a4d42669 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -169,6 +169,9 @@ class TransformModel : public IMagnifierModel { // The transform write and the warm pulse; returns whether the values changed this tick. bool writeAndWarm(const MagTransform& m, double applyLevel, bool ramping, const Config& cfg, const PresentExtras& ex, bool& txWroteThisTick, bool& trChanged, bool& trWarm); + // Input-transform publish: decimation, ramp hold, read-back verdict and self-heal. + void publishInputTransform(const MagTransform& m, double applyLevel, bool ramping, bool changed, + const Config& cfg, const PresentExtras& ex); std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From e04ceaaac05bff9ca72a2f0a1bead7273d8cca11 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:24 +0200 Subject: [PATCH 07/12] refactor(transform): extract guardInputTransformStomp from publishInputTransform (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 61 ++++++++++++++++++++++------------------- src/transform_model.h | 1 + 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index b78b0bca..fa7c1c04 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -795,34 +795,7 @@ void TransformModel::publishInputTransform(const MagTransform& m, double applyLe // Both rects in VIRTUAL-SCREEN coordinates (the old 0,0-based dst was wrong off-primary). // Needs UIAccess: the ENABLED publish fails without it (rig-measured ERROR_ACCESS_DENIED; // the DISABLED call succeeds regardless - never probe availability with the disable shape). - // Stomp guard (issue #217, docs/history/NATIVE-MAGNIFIER-STOMP.md): the input transform is ONE - // system-wide slot, and native Magnifier re-publishes an ENABLED IDENTITY into it - // continuously while it runs - even sitting unzoomed at 100%. Under a Wind zoom that - // identity mapping unmoors the visible cursor (the wobble); a dirty Magnifier exit strands - // its last rect the same way. So every zoomed tick reads the slot back (~0.1ms) and, when - // it does not hold what we last published, forces a republish past the decimation. Wind at - // tick rate wins the two-writer war for as long as the foreign writer lives, and a stale - // corpse is overwritten on the first tick of the next session. - bool ixForce = false; - if (cfg.magInputTransform == 1 && applyLevel > 1.001 && ixExpectedValid_) { - bool aOn = false; RECT aSrc{}, aDst{}; - if (host_.getInputTransform(aOn, aSrc, aDst) && - InputTransformStomped(ixExpectedOn_, ixExpL_, ixExpT_, ixExpR_, ixExpB_, - aOn, aSrc.left, aSrc.top, aSrc.right, aSrc.bottom)) { - ixForce = true; - noteIxStomp(); - // The same foreign writer owns the shared cursor-visibility global - // (MagShowSystemCursor) - re-assert our hide on the stomp tick so the raw pointer - // plane it re-showed does not stay visible under the Inspect crosshair. - if (cursorHidden_) ShowSystemCursor(FALSE); - if (!ixStompWarned_) { - ixStompWarned_ = true; - wind::Log(wind::LogLevel::Warn, "transform", - "foreign input-transform writer detected (native Magnifier running?) - " - "republishing per tick"); - } - } - } + const bool ixForce = guardInputTransformStomp(applyLevel, cfg); if (cfg.magInputTransform != 0 && (changed || ixPending_ || ixForce)) { // Decimation (issue #189): the publish exists for pointer-framework HOVER hit-testing // (clicks ride the welded cursor and never consult it), so it does not need the 144Hz @@ -916,6 +889,38 @@ void TransformModel::publishInputTransform(const MagTransform& m, double applyLe if (changed) lastInputXformOn_ = cfg.magInputTransform != 0; } +bool TransformModel::guardInputTransformStomp(double applyLevel, const Config& cfg) { + // Stomp guard (issue #217, docs/history/NATIVE-MAGNIFIER-STOMP.md): the input transform is ONE + // system-wide slot, and native Magnifier re-publishes an ENABLED IDENTITY into it + // continuously while it runs - even sitting unzoomed at 100%. Under a Wind zoom that + // identity mapping unmoors the visible cursor (the wobble); a dirty Magnifier exit strands + // its last rect the same way. So every zoomed tick reads the slot back (~0.1ms) and, when + // it does not hold what we last published, forces a republish past the decimation. Wind at + // tick rate wins the two-writer war for as long as the foreign writer lives, and a stale + // corpse is overwritten on the first tick of the next session. + bool ixForce = false; + if (cfg.magInputTransform == 1 && applyLevel > 1.001 && ixExpectedValid_) { + bool aOn = false; RECT aSrc{}, aDst{}; + if (host_.getInputTransform(aOn, aSrc, aDst) && + InputTransformStomped(ixExpectedOn_, ixExpL_, ixExpT_, ixExpR_, ixExpB_, + aOn, aSrc.left, aSrc.top, aSrc.right, aSrc.bottom)) { + ixForce = true; + noteIxStomp(); + // The same foreign writer owns the shared cursor-visibility global + // (MagShowSystemCursor) - re-assert our hide on the stomp tick so the raw pointer + // plane it re-showed does not stay visible under the Inspect crosshair. + if (cursorHidden_) ShowSystemCursor(FALSE); + if (!ixStompWarned_) { + ixStompWarned_ = true; + wind::Log(wind::LogLevel::Warn, "transform", + "foreign input-transform writer detected (native Magnifier running?) - " + "republishing per tick"); + } + } + } + return ixForce; +} + void TransformModel::present(const MapResult& r, double level, const Config& cfg, const MonitorTarget& mon, const PresentExtras& ex) { (void)mon; diff --git a/src/transform_model.h b/src/transform_model.h index a4d42669..ff2805f7 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -172,6 +172,7 @@ class TransformModel : public IMagnifierModel { // Input-transform publish: decimation, ramp hold, read-back verdict and self-heal. void publishInputTransform(const MagTransform& m, double applyLevel, bool ramping, bool changed, const Config& cfg, const PresentExtras& ex); + bool guardInputTransformStomp(double applyLevel, const Config& cfg); // true: republish now (#217) std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 08b96fde2828c3cbf36edd379b4f2216ed523ccc Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:27 +0200 Subject: [PATCH 08/12] refactor(transform): extract weldPointer from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 38 +++++++++++++++++++++----------------- src/transform_model.h | 1 + 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index fa7c1c04..22dc47e5 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -921,6 +921,26 @@ bool TransformModel::guardInputTransformStomp(double applyLevel, const Config& c return ixForce; } +void TransformModel::weldPointer(const MapResult& r, const PresentExtras& ex) { + // WELD: the locked (mouselook, pointer hidden) and Inspect regimes park the real pointer on the + // lens point once per tick (ex.suppressCursorSync is false there). A free session never welds: + // the pointer is the input and DWM centres on it. Deduped so an idle tick injects nothing. + // Inspect pins the point via clickOverride; otherwise clickDesktop is monitor-local, so add the + // monitor origin. Drag-follow (#169) belongs to the render engine. weldedLastFrame_ records + // whether SetCursorPos REALLY ran, so RunTick can baseline on the weld point only when it did + // (#169 measured-baseline law; assuming it landed is the unstable-servo bug). + weldedLastFrame_ = false; + if (!ex.suppressCursorSync) { + int cx = ex.clickOverride ? ex.clickDesktopX : (r.clickDesktopX + mon_.x); + int cy = ex.clickOverride ? ex.clickDesktopY : (r.clickDesktopY + mon_.y); + if (!haveLastClick_ || cx != lastClickX_ || cy != lastClickY_) { + SetCursorPos(cx, cy); + lastClickX_ = cx; lastClickY_ = cy; haveLastClick_ = true; + weldedLastFrame_ = true; + } + } +} + void TransformModel::present(const MapResult& r, double level, const Config& cfg, const MonitorTarget& mon, const PresentExtras& ex) { (void)mon; @@ -975,23 +995,7 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg if (cfg.edgeClip != 0 || edgeClipActive_) edgeClipManage(cfg.edgeClip != 0 && applyLevel > 1.001 && !ex.clickOverride); - // WELD: the locked (mouselook, pointer hidden) and Inspect regimes park the real pointer on the - // lens point once per tick (ex.suppressCursorSync is false there). A free session never welds: - // the pointer is the input and DWM centres on it. Deduped so an idle tick injects nothing. - // Inspect pins the point via clickOverride; otherwise clickDesktop is monitor-local, so add the - // monitor origin. Drag-follow (#169) belongs to the render engine. weldedLastFrame_ records - // whether SetCursorPos REALLY ran, so RunTick can baseline on the weld point only when it did - // (#169 measured-baseline law; assuming it landed is the unstable-servo bug). - weldedLastFrame_ = false; - if (!ex.suppressCursorSync) { - int cx = ex.clickOverride ? ex.clickDesktopX : (r.clickDesktopX + mon_.x); - int cy = ex.clickOverride ? ex.clickDesktopY : (r.clickDesktopY + mon_.y); - if (!haveLastClick_ || cx != lastClickX_ || cy != lastClickY_) { - SetCursorPos(cx, cy); - lastClickX_ = cx; lastClickY_ = cy; haveLastClick_ = true; - weldedLastFrame_ = true; - } - } + weldPointer(r, ex); if (ex.cursorLocked && ex.drawCursor) { // Inspect mode: the real cursor is frozen at the (overridden) click point, but the thing the diff --git a/src/transform_model.h b/src/transform_model.h index ff2805f7..fde103bc 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -173,6 +173,7 @@ class TransformModel : public IMagnifierModel { void publishInputTransform(const MagTransform& m, double applyLevel, bool ramping, bool changed, const Config& cfg, const PresentExtras& ex); bool guardInputTransformStomp(double applyLevel, const Config& cfg); // true: republish now (#217) + void weldPointer(const MapResult& r, const PresentExtras& ex); // sets weldedLastFrame_ std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 8083ebacdb0203b6f3d0f8c5e46d0fa1f6577b0a Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:29 +0200 Subject: [PATCH 09/12] refactor(transform): extract presentCursor from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 116 +++++++++++++++++++++------------------- src/transform_model.h | 2 + 2 files changed, 62 insertions(+), 56 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index 22dc47e5..b9fbebe5 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -941,62 +941,7 @@ void TransformModel::weldPointer(const MapResult& r, const PresentExtras& ex) { } } -void TransformModel::present(const MapResult& r, double level, const Config& cfg, - const MonitorTarget& mon, const PresentExtras& ex) { - (void)mon; - // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the - // render model's mapper already solves the whole centered geometry - including the edge zones, - // where the pointer slides away from the centre as the source rect clamps. So the mapper's - // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame - // itself (native cursor, issue #369): nothing here places a pointer of our own. - // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources - // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. - // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface - // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). - // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a - // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s - // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle - // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). - double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it - const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); - double srcL = r.srcLeft, srcT = r.srcTop; - if (applyLevel != level) { - OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); - srcL = o.x; srcT = o.y; - } - ClickInProgress(); // keep the click window current between nudges - DeliverOwedNudge(); - restLevel_ = cfg.txRestLevel; // hot - if (!ensureMag()) return; // lazy context: the session's first write brings DWM up - applySamplingMode(cfg); - traceOn_ = cfg.txTrace != 0; - if (level > sessionMaxLevel_) sessionMaxLevel_ = level; - const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); - MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); - bool txWroteThisTick = false; // for the trace - switchDwmCentre(applyLevel, ex); - // Trace inputs, captured here and appended at the END of present(). - bool trChanged = false, trRamping = ramping, trWarm = false; - const double trLevel = applyLevel; - const int trTxX = m.txX, trOffX = m.offX; - // pauseWrites (issue #148): a click's injected cursor move is in flight - a transform write - // racing a cursor-position update is the proven TDR, so those ticks write NOTHING. State is - // untouched; the next unpaused tick lands the same values. (The native cursor's pixel-and-back - // nudges are the intentional exception to "no cursor events beside a write": they run right - // AFTER the write on the same thread, never racing it, and never during a click.) - if (!ex.pauseWrites) { - const bool changed = writeAndWarm(m, applyLevel, ramping, cfg, ex, txWroteThisTick, trChanged, trWarm); - publishInputTransform(m, applyLevel, ramping, changed, cfg, ex); - } // !ex.pauseWrites - // Edge clip: engaged while genuinely zoomed and the pointer is OURS to manage (not Inspect, - // whose 1px freeze clip must never be disturbed - ex.clickOverride marks it). - // Hot like the other knobs: switched off mid-zoom, give the clip back now rather than at the - // end of the session (issue #274). Gated so an unused edgeClip costs no per-tick syscall. - if (cfg.edgeClip != 0 || edgeClipActive_) - edgeClipManage(cfg.edgeClip != 0 && applyLevel > 1.001 && !ex.clickOverride); - - weldPointer(r, ex); - +void TransformModel::presentCursor(const MapResult& r, double level, const PresentExtras& ex) { if (ex.cursorLocked && ex.drawCursor) { // Inspect mode: the real cursor is frozen at the (overridden) click point, but the thing the // user aims with is the LOOK POINT (mapper center). Draw the crosshair (the same design the @@ -1062,6 +1007,65 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg } } } +} + +void TransformModel::present(const MapResult& r, double level, const Config& cfg, + const MonitorTarget& mon, const PresentExtras& ex) { + (void)mon; + // CENTERED-CURSOR geometry (issue #148 revival). Wind's identity is the centred cursor, and the + // render model's mapper already solves the whole centered geometry - including the edge zones, + // where the pointer slides away from the centre as the source rect clamps. So the mapper's + // CENTERED source rect is the transform, and DWM draws the real pointer into the magnified frame + // itself (native cursor, issue #369): nothing here places a pointer of our own. + // KEEP-ALIVE v2 (issue #148 action-start spike): DWM discards its magnification resources + // when the transform VALUE sits still and pays a ~1fps rebuild on the next real change. + // v1 jittered the LEVEL by an epsilon - that forced a full re-SCALE of every cached surface + // every tick, whose cost grows with zoom (constant ~1fps at 16x: the cure was the disease). + // v2 jitters only the private-channel TRANSLATION by 1 screen px on alternating ticks - a + // re-COMPOSITE of already-scaled surfaces, cheap at any level - and only while within 1.5s + // of the last REAL change: brief pauses (the pan-stop-pan pattern) stay hot, while true idle + // lets DWM park legitimately (one spike after a long idle is acceptable; one per pause was not). + double preLadderLevel = 0.0; // the level before the smooth-zoom ladder snapped it + const double applyLevel = resolveLevel(r, level, cfg, preLadderLevel); + double srcL = r.srcLeft, srcT = r.srcTop; + if (applyLevel != level) { + OffsetF o = ComputeOffsetF(r.centerX, r.centerY, applyLevel, mon_.w, mon_.h); + srcL = o.x; srcT = o.y; + } + ClickInProgress(); // keep the click window current between nudges + DeliverOwedNudge(); + restLevel_ = cfg.txRestLevel; // hot + if (!ensureMag()) return; // lazy context: the session's first write brings DWM up + applySamplingMode(cfg); + traceOn_ = cfg.txTrace != 0; + if (level > sessionMaxLevel_) sessionMaxLevel_ = level; + const bool ramping = preLadderLevel != level || (applyLevel != lastLevel_ && lastLevel_ > 0.0); + MagTransform m = writeSiteTransform(srcL, srcT, applyLevel, cfg); + bool txWroteThisTick = false; // for the trace + switchDwmCentre(applyLevel, ex); + // Trace inputs, captured here and appended at the END of present(). + bool trChanged = false, trRamping = ramping, trWarm = false; + const double trLevel = applyLevel; + const int trTxX = m.txX, trOffX = m.offX; + // pauseWrites (issue #148): a click's injected cursor move is in flight - a transform write + // racing a cursor-position update is the proven TDR, so those ticks write NOTHING. State is + // untouched; the next unpaused tick lands the same values. (The native cursor's pixel-and-back + // nudges are the intentional exception to "no cursor events beside a write": they run right + // AFTER the write on the same thread, never racing it, and never during a click.) + if (!ex.pauseWrites) { + const bool changed = writeAndWarm(m, applyLevel, ramping, cfg, ex, txWroteThisTick, trChanged, trWarm); + publishInputTransform(m, applyLevel, ramping, changed, cfg, ex); + } // !ex.pauseWrites + // Edge clip: engaged while genuinely zoomed and the pointer is OURS to manage (not Inspect, + // whose 1px freeze clip must never be disturbed - ex.clickOverride marks it). + // Hot like the other knobs: switched off mid-zoom, give the clip back now rather than at the + // end of the session (issue #274). Gated so an unused edgeClip costs no per-tick syscall. + if (cfg.edgeClip != 0 || edgeClipActive_) + edgeClipManage(cfg.edgeClip != 0 && applyLevel > 1.001 && !ex.clickOverride); + + weldPointer(r, ex); + + presentCursor(r, level, ex); if (traceOn_) { LARGE_INTEGER qf, qc; QueryPerformanceFrequency(&qf); QueryPerformanceCounter(&qc); diff --git a/src/transform_model.h b/src/transform_model.h index fde103bc..aab41e46 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -174,6 +174,8 @@ class TransformModel : public IMagnifierModel { const Config& cfg, const PresentExtras& ex); bool guardInputTransformStomp(double applyLevel, const Config& cfg); // true: republish now (#217) void weldPointer(const MapResult& r, const PresentExtras& ex); // sets weldedLastFrame_ + // Inspect crosshair, legacy sprite session (#463), or the native pointer's hide/restore. + void presentCursor(const MapResult& r, double level, const PresentExtras& ex); std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 5299c574f52474b21a809dbb910c5a76998be1c9 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:32 +0200 Subject: [PATCH 10/12] refactor(transform): extract appendTrace from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 27 ++++++++++++++++----------- src/transform_model.h | 2 ++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index b9fbebe5..ade57323 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -1009,6 +1009,21 @@ void TransformModel::presentCursor(const MapResult& r, double level, const Prese } } +void TransformModel::appendTrace(double trLevel, int trTxX, int trOffX, bool txWroteThisTick, bool trChanged, + bool trRamping, bool trWarm) { + if (traceOn_) { + LARGE_INTEGER qf, qc; QueryPerformanceFrequency(&qf); QueryPerformanceCounter(&qc); + TxTick& e = traceBuf_[traceHead_ % kTraceCap]; + e.ms = double(qc.QuadPart) * 1000.0 / double(qf.QuadPart); + e.level = trLevel; e.txX = trTxX; e.offX = trOffX; + e.wrote = (unsigned char)(txWroteThisTick ? 1 : 0); + e.changed = (unsigned char)(trChanged ? 1 : 0); + e.ramping = (unsigned char)(trRamping ? 1 : 0); + e.warm = (unsigned char)(trWarm ? 1 : 0); + ++traceHead_; + } +} + void TransformModel::present(const MapResult& r, double level, const Config& cfg, const MonitorTarget& mon, const PresentExtras& ex) { (void)mon; @@ -1067,17 +1082,7 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg presentCursor(r, level, ex); - if (traceOn_) { - LARGE_INTEGER qf, qc; QueryPerformanceFrequency(&qf); QueryPerformanceCounter(&qc); - TxTick& e = traceBuf_[traceHead_ % kTraceCap]; - e.ms = double(qc.QuadPart) * 1000.0 / double(qf.QuadPart); - e.level = trLevel; e.txX = trTxX; e.offX = trOffX; - e.wrote = (unsigned char)(txWroteThisTick ? 1 : 0); - e.changed = (unsigned char)(trChanged ? 1 : 0); - e.ramping = (unsigned char)(trRamping ? 1 : 0); - e.warm = (unsigned char)(trWarm ? 1 : 0); - ++traceHead_; - } + appendTrace(trLevel, trTxX, trOffX, txWroteThisTick, trChanged, trRamping, trWarm); if (smoothPan_ && level > 1.0) { unsigned long long now = GetTickCount64(); if (now - lastPinAssertMs_ >= 500) { lastPinAssertMs_ = now; pin_.assert_(); } diff --git a/src/transform_model.h b/src/transform_model.h index aab41e46..06c25087 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -176,6 +176,8 @@ class TransformModel : public IMagnifierModel { void weldPointer(const MapResult& r, const PresentExtras& ex); // sets weldedLastFrame_ // Inspect crosshair, legacy sprite session (#463), or the native pointer's hide/restore. void presentCursor(const MapResult& r, double level, const PresentExtras& ex); + void appendTrace(double trLevel, int trTxX, int trOffX, bool txWroteThisTick, bool trChanged, + bool trRamping, bool trWarm); // one txTrace row std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 288ffcbf598fe3471772e3997575a7c18104c9e0 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:53:35 +0200 Subject: [PATCH 11/12] refactor(transform): extract pinAndGhostUpkeep from present (#476) Move-only step of the TransformModel::present split (#476); review with git diff --color-moved. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- src/transform_model.cpp | 70 ++++++++++++++++++++++------------------- src/transform_model.h | 1 + 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/transform_model.cpp b/src/transform_model.cpp index ade57323..9978f6da 100644 --- a/src/transform_model.cpp +++ b/src/transform_model.cpp @@ -1024,6 +1024,42 @@ void TransformModel::appendTrace(double trLevel, int trTxX, int trOffX, bool txW } } +void TransformModel::pinAndGhostUpkeep(double level) { + if (smoothPan_ && level > 1.0) { + unsigned long long now = GetTickCount64(); + if (now - lastPinAssertMs_ >= 500) { lastPinAssertMs_ = now; pin_.assert_(); } + } else { + pin_.hide(); + } + // MPO buster (issue #191): keep the ghost asserted while wanted (500ms cadence; assert_ + // also shows it on the first wanted tick). Hidden promptly when the session stops being + // MPO-exposed (alt-tab to the desktop mid-zoom, knob turned off). + if (mpoBusterWanted_ && level > 1.001) { + unsigned long long nowG = GetTickCount64(); + // THE DEMOTION IS A RACE, AND A BLIND 500ms CADENCE LOSES IT (measured 2026-08-26 with + // tools/plane_race_probe.ps1: 3 of 6 alt-tab sessions left the game on its overlay plane + // for ~2.5s, against native Magnifier's 6 of 6 composited on the same machine). While the + // game holds a hardware plane DWM is not compositing it, so nothing we write can drive the + // composition rate and every pan start lands late - that IS the field stutter, and its + // per-session randomness is this race. + // + // So the OPENING of a session is asserted hard and the steady state stays calm: the first + // kGhostAggressiveMs are re-asserted every kGhostFastMs, which is what actually has to win + // against DWM re-promoting the game as it takes the foreground back. assert_() is already + // read-first (it transacts only when the ghost is hidden, moved, or stripped of TOPMOST), + // so a tighter cadence costs three cheap reads per tick, not a z-order transaction. + static constexpr unsigned long long kGhostAggressiveMs = 2000; + static constexpr unsigned long long kGhostFastMs = 100; + if (ghostSessionStartMs_ == 0) ghostSessionStartMs_ = nowG; + const bool opening = (nowG - ghostSessionStartMs_) < kGhostAggressiveMs; + const unsigned long long cadence = opening ? kGhostFastMs : 500; + if (nowG - lastGhostAssertMs_ >= cadence) { lastGhostAssertMs_ = nowG; mpoGhost_.assert_(); } + } else { + ghostSessionStartMs_ = 0; // next session re-opens with the aggressive window + mpoGhost_.hide(); + } +} + void TransformModel::present(const MapResult& r, double level, const Config& cfg, const MonitorTarget& mon, const PresentExtras& ex) { (void)mon; @@ -1083,39 +1119,7 @@ void TransformModel::present(const MapResult& r, double level, const Config& cfg presentCursor(r, level, ex); appendTrace(trLevel, trTxX, trOffX, txWroteThisTick, trChanged, trRamping, trWarm); - if (smoothPan_ && level > 1.0) { - unsigned long long now = GetTickCount64(); - if (now - lastPinAssertMs_ >= 500) { lastPinAssertMs_ = now; pin_.assert_(); } - } else { - pin_.hide(); - } - // MPO buster (issue #191): keep the ghost asserted while wanted (500ms cadence; assert_ - // also shows it on the first wanted tick). Hidden promptly when the session stops being - // MPO-exposed (alt-tab to the desktop mid-zoom, knob turned off). - if (mpoBusterWanted_ && level > 1.001) { - unsigned long long nowG = GetTickCount64(); - // THE DEMOTION IS A RACE, AND A BLIND 500ms CADENCE LOSES IT (measured 2026-08-26 with - // tools/plane_race_probe.ps1: 3 of 6 alt-tab sessions left the game on its overlay plane - // for ~2.5s, against native Magnifier's 6 of 6 composited on the same machine). While the - // game holds a hardware plane DWM is not compositing it, so nothing we write can drive the - // composition rate and every pan start lands late - that IS the field stutter, and its - // per-session randomness is this race. - // - // So the OPENING of a session is asserted hard and the steady state stays calm: the first - // kGhostAggressiveMs are re-asserted every kGhostFastMs, which is what actually has to win - // against DWM re-promoting the game as it takes the foreground back. assert_() is already - // read-first (it transacts only when the ghost is hidden, moved, or stripped of TOPMOST), - // so a tighter cadence costs three cheap reads per tick, not a z-order transaction. - static constexpr unsigned long long kGhostAggressiveMs = 2000; - static constexpr unsigned long long kGhostFastMs = 100; - if (ghostSessionStartMs_ == 0) ghostSessionStartMs_ = nowG; - const bool opening = (nowG - ghostSessionStartMs_) < kGhostAggressiveMs; - const unsigned long long cadence = opening ? kGhostFastMs : 500; - if (nowG - lastGhostAssertMs_ >= cadence) { lastGhostAssertMs_ = nowG; mpoGhost_.assert_(); } - } else { - ghostSessionStartMs_ = 0; // next session re-opens with the aggressive window - mpoGhost_.hide(); - } + pinAndGhostUpkeep(level); } bool TransformModel::retarget(const MonitorTarget& m) { diff --git a/src/transform_model.h b/src/transform_model.h index 06c25087..8eaa9aea 100644 --- a/src/transform_model.h +++ b/src/transform_model.h @@ -178,6 +178,7 @@ class TransformModel : public IMagnifierModel { void presentCursor(const MapResult& r, double level, const PresentExtras& ex); void appendTrace(double trLevel, int trTxX, int trOffX, bool txWroteThisTick, bool trChanged, bool trRamping, bool trWarm); // one txTrace row + void pinAndGhostUpkeep(double level); // composition pin + MPO ghost cadence (#191) std::mutex statMx_; unsigned long long statLogMs_ = 0; double statMaxMs_ = 0.0, statSumMs_ = 0.0; From 322a26521fce540567c574d2fa9bace6078c4d06 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:54:31 +0200 Subject: [PATCH 12/12] docs(05): describe the present() step sequence (#476) Names the private steps TransformModel::present now runs and points the write-clamp reference at writeSiteTransform. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TdjopXZfsUiesYxYLYHEdH --- docs/architecture/05-transform-engine.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/architecture/05-transform-engine.md b/docs/architecture/05-transform-engine.md index d01ccc70..c39cd394 100644 --- a/docs/architecture/05-transform-engine.md +++ b/docs/architecture/05-transform-engine.md @@ -53,6 +53,17 @@ A failed private write latches `privateBroken_` and the session falls back to pu init. Both forms come from the pure `ComputeMagTransform` (`src/transform.h`), so they always describe the same rect. +## The tick path + +`TransformModel::present` runs one zoomed tick as a fixed sequence of private steps (declared in +`src/transform_model.h` in call order): `resolveLevel` (step cap, smooth-zoom ladder, held ramp +steps), `applySamplingMode`, `writeSiteTransform` (edge floor and the 16-bit backstop), +`switchDwmCentre`, then, unless the click pauses writes, `writeAndWarm` (the transform write and +warm pulse) and `publishInputTransform` (stomp guard, decimation, self-heal). After those come the +edge clip, `weldPointer`, `presentCursor` (Inspect crosshair, legacy sprite session, native hide), +`appendTrace` and `pinAndGhostUpkeep`. The order is load-bearing: keep new work inside the step it +belongs to. + ## Write cadence **Apply the level every tick, continuously.** Large discrete level jumps are what cost DWM (each @@ -180,7 +191,7 @@ channels and on the plain desktop too. With MPO disabled Smooth sampling takes a float path and survives the same corner, as does the built-in Magnifier, which samples smooth. -Defences (wall arming in `RunTick`, write clamp in `TransformModel::present`): +Defences (wall arming in `RunTick`, write clamp in `TransformModel::writeSiteTransform`): | MPO at boot | Sampling | Pan walls (`setMaxSourceLeft/Top`, `|src*level| <= 32000`) | |---|---|---|