Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/05-transform-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ Defences (wall arming in `RunTick`'s `ReadForegroundAndArmWall`, write clamp in

DWM magnifies with nearest neighbour unless something calls
`MagSetFullscreenUseBitmapSmoothing` (Magnification.dll, undocumented, resolved BY NAME).
`txSamplingMode`: 0 nearest, 1 smooth (default since #508).
`txSamplingMode`: 0 nearest (default), 1 smooth.

- Until 0.24.0 it was resolved by ordinal 1, which does not exist (the export ordinals start at
100), so Wind never set the filter: the image showed whatever state another process had left.
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/08-config-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Every key works in the ini whether or not Settings shows it. Keys hot-reload unl

| Keys | Meaning |
|---|---|
| `maxLevel` (30), `zoomInSpeed` and `zoomOutSpeed` (1.5), `zoomEaseOutMs` (115), `smoothZoom*` | Range and feel of the zoom |
| `maxLevel` (30), `zoomInSpeed` and `zoomOutSpeed` (1.75), `zoomEaseOutMs` (115), `smoothZoom*` | Range and feel of the zoom |
| `cursorSensitivity` (1.0), `panSpeed` (1.0), `panGlideMaxPx` (0) | Mouse and arrow-key pan speed; the pan glide's distance, which also sets its ease (#430, #434) |
| `gameCursorLagMs` (-1) | How late the view follows the pointer while a full-screen game hides it and draws its own cursor; -1 = one display frame, 0 = off (#443). Ini only |
| `mouseAlign` (0 centred, 1 within the edges), `mouseMarginPct` | Where the pointer sits while the view moves |
Expand All @@ -210,7 +210,7 @@ Every key works in the ini whether or not Settings shows it. Keys hot-reload unl

| Keys | Meaning |
|---|---|
| `txSamplingMode` (1) | Transform sampling: 0 nearest, 1 smooth (Settings: Transform scaling, formerly "High resolution cursor", [05](05-transform-engine.md)) |
| `txSamplingMode` (0) | Transform sampling: 0 nearest, 1 smooth (Settings: Transform scaling, formerly "High resolution cursor", [05](05-transform-engine.md)) |
| `edgeClip` (1, hot) | While a transform session is zoomed, clips the pointer 1 px inside the monitor's left and top edges so it cannot rest on the contested outermost pixel (cursor-shape flicker there). The right and bottom stay open so the cursor can slide off the screen, as at 1x. A tighter existing clip (game confine, Inspect) always wins; 0 turns it off. Rationale: `Config::edgeClip` in `src/config/config.h`, mechanics in `TransformModel::edgeClipManage` |
| `renderScaling` (0), `upscaleTiming` (0) | Render engine scaling: 0 nearest, 1 xBR, 2 NVIDIA Image Scaling (Settings: Render scaling, [04](04-render-engine.md)); `bilinear` is retired and only migrates (0 keeps nearest). `upscaleTiming=1` logs the filter's GPU time |
| `sharpness`, `brightness`, `hdrTonemap` (1) | Render engine image |
Expand Down
4 changes: 2 additions & 2 deletions src/config/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,9 @@ std::string DefaultIniText() {
"panUpVk=0\npanUpMods=0\npanDownVk=0\npanDownMods=0\npanSpeed=1.0\n"
"; maxLevel: how far you can zoom (does not affect zoom speed)\n"
"maxLevel=30.0\n"
"; zoomInSpeed/zoomOutSpeed: zoom rate multipliers (1.5=default, 3.0=twice as fast, 0.75=half);\n"
"; zoomInSpeed/zoomOutSpeed: zoom rate multipliers (1.75=default, 3.5=twice as fast, 0.875=half);\n"
"; speed is independent of maxLevel\n"
"zoomInSpeed=1.5\nzoomOutSpeed=1.5\n"
"zoomInSpeed=1.75\nzoomOutSpeed=1.75\n"
"; smoothZoom: 0=linear constant speed; 1=zoom-IN soft-starts, easing up to linear (shipped on)\n"
"smoothZoom=1\n"
"; smoothZoomAccel: ease-in depth - zoom-in starts at zoomInSpeed/this and climbs to\n"
Expand Down
6 changes: 3 additions & 3 deletions src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ struct Config {
// --- Zoom experience (see docs/specs/2026-05-26-configurable-zoom-design.md) ---
// Per-direction rate multipliers (1.0 = default speed); apply in BOTH linear and smooth modes.
// Speed is independent of maxLevel (a fixed doublings/sec base inside ZoomController).
double zoomInSpeed = 1.5; // 0.25-4.0
double zoomOutSpeed = 1.5; // 0.25-4.0
double zoomInSpeed = 1.75; // 0.25-4.0
double zoomOutSpeed = 1.75; // 0.25-4.0
int zoomTrace = 0; // 1 = log a zoom-in/zoom-out timeline per zoom (#310, diagnostics)
// Hitch recorder (#361): log every long zoomed frame with its classified cause, plus a
// per-minute summary. On by default: the record costs ~0.5 us per tick and logs nothing on a
Expand Down Expand Up @@ -210,7 +210,7 @@ struct Config {
// to come and go between builds; it is re-applied per magnification context. KNOWN
// INTERACTION: the 1px warm pulse (txWarmMode=1) puts the view 1px off-true on rest ticks;
// nearest masks that as sub-block noise, smoothing can render it as visible shaking.
int txSamplingMode = 1;
int txSamplingMode = 0; // nearest (owner pick 2026-10-24, #510)
// MPO buster (issue #191, hot): 1 (default) = during transform GAME sessions on MPO-ENABLED
// machines, show a fullscreen alpha-1 click-through ghost that demotes the game off its
// hardware overlay plane - off the plane there is no 16-bit translation field to overflow,
Expand Down
4 changes: 2 additions & 2 deletions src/tray_app/flyout_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ inline const SliderSpec* SliderSpecs(int* count) {
{ "colorWarmPct", L"Warmth", 0, 100, 0, ValueFmt::Percent, "warm", 5 },
{ "colorDimPct", L"Brightness", 1, 100, 100, ValueFmt::Percent, "bright", 1 },
{ "maxLevel", L"Max zoom", 2, 30, 30, ValueFmt::TimesInt, "maxz", 1 },
{ "zoomInSpeed", L"Zoom-in speed", 0.25, 4, 1.5, ValueFmt::Times2, "zin", 0.05 },
{ "zoomOutSpeed", L"Zoom-out speed", 0.25, 4, 1.5, ValueFmt::Times2, "zout", 0.05 },
{ "zoomInSpeed", L"Zoom-in speed", 0.25, 4, 1.75, ValueFmt::Times2, "zin", 0.05 },
{ "zoomOutSpeed", L"Zoom-out speed", 0.25, 4, 1.75, ValueFmt::Times2, "zout", 0.05 },
{ "panSpeed", L"Arrow key speed", 0.25, 4, 1, ValueFmt::Times2, "pan", 0.05 },
{ "panGlideMaxPx", L"Pan glide", 0, 200, 0, ValueFmt::Pixels, "smooth", 5 },
{ "zoomEaseOutMs", L"Release glide", 0, 300, 115, ValueFmt::Millis, "glide", 5 },
Expand Down
6 changes: 3 additions & 3 deletions tests/fixtures/config_defaults.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ panKeysOn - - 1
hideCursorOn - - 1
cursorLockOn - - 1
maxLevel 1 30 30
zoomInSpeed 0.25 4 1.5
zoomOutSpeed 0.25 4 1.5
zoomInSpeed 0.25 4 1.75
zoomOutSpeed 0.25 4 1.75
smoothZoom - - 1
smoothZoomAccel 1 8 3
smoothZoomRamp 0.1 3 0.6
Expand Down Expand Up @@ -96,7 +96,7 @@ mouseMarginPct 0 40 0
trackLog - - 0
ixDecimate 1 16 4
mpoBuster - - 1
txSamplingMode - - 1
txSamplingMode - - 0
txWarmMode 0 1 1
txTrace - - 0
txRestLevel 1 1.01 1
Expand Down
10 changes: 5 additions & 5 deletions tests/test_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ TEST_CASE("defaults when text is empty") {
Config c = ParseConfig("");
CHECK(c.zoomInButton == 0); // shipped unbound (onboarding captures it)
CHECK(c.zoomOutButton == 0); // shipped unbound
CHECK(c.maxLevel == doctest::Approx(30.0)); // owner pick (#508)
CHECK(c.maxLevel == doctest::Approx(30.0)); // owner pick (#508, #510)
CHECK(c.diagnostics == 0);
// Ramp step cap ships ON (issue #219): uncapped, ~15% of high-level zoom-ins stall 35-43ms
// then snap 1.2-1.9 levels; capped at 2.5%/tick every measured ramp was even. 0 would
Expand Down Expand Up @@ -81,8 +81,8 @@ TEST_CASE("renderer knobs have sane defaults") {
CHECK(c.multiMonitor == 1); // follows the cursor's monitor (#508)
CHECK(c.cropCapture == 0); // shipped off: always copy all changed regions (no edge staleness)
CHECK(c.smoothZoom == 1); // shipped on (eased-in zoom)
CHECK(c.zoomInSpeed == doctest::Approx(1.5)); // owner pick (#508)
CHECK(c.zoomOutSpeed == doctest::Approx(1.5));
CHECK(c.zoomInSpeed == doctest::Approx(1.75)); // owner pick (#508, #510)
CHECK(c.zoomOutSpeed == doctest::Approx(1.75));
CHECK(c.zoomEaseOutMs == 115);
CHECK(c.smoothZoomAccel == doctest::Approx(3.0));
CHECK(c.smoothZoomRamp == doctest::Approx(0.6));
Expand Down Expand Up @@ -174,7 +174,7 @@ TEST_CASE("parses overrides and ignores comments/blank lines") {
CHECK(c.maxLevel == doctest::Approx(12.5));
CHECK(c.zoomInButton == 1);
CHECK(c.cursorSensitivity == doctest::Approx(0.5));
CHECK(c.zoomInSpeed == doctest::Approx(1.5)); // untouched default
CHECK(c.zoomInSpeed == doctest::Approx(1.75)); // untouched default
}
TEST_CASE("malformed lines are ignored, keep defaults") {
Config c = ParseConfig("garbage line\nmaxLevel\n=5\n");
Expand Down Expand Up @@ -640,7 +640,7 @@ TEST_CASE("the first-run ini carries the new keys and parses to the struct defau
}

TEST_CASE("txSamplingMode folds the nearest aliases 2..4 to 0 (review 2026-10-09 #23)") {
CHECK(ParseConfig("").txSamplingMode == 1); // Smooth by default (#508)
CHECK(ParseConfig("").txSamplingMode == 0); // Nearest by default (#510)
CHECK(ParseConfig("txSamplingMode=0\n").txSamplingMode == 0);
CHECK(ParseConfig("txSamplingMode=1\n").txSamplingMode == 1);
CHECK(ParseConfig("txSamplingMode=-1\n").txSamplingMode == -1); // leave DWM alone stays valid
Expand Down
4 changes: 2 additions & 2 deletions tests/test_profiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ TEST_CASE("profile names: a device name is reserved with any extension, and the
TEST_CASE("SessionDiffers: UI keys outside the first-run template read as their built-in default (review #70, #76)") {
CHECK_FALSE(SessionDiffers("zoomEaseOutMs=115\n", "maxLevel=30\n"));
CHECK(SessionDiffers("zoomEaseOutMs=100\n", "maxLevel=30\n"));
CHECK_FALSE(SessionDiffers("txSamplingMode=1\n", ""));
CHECK(SessionDiffers("txSamplingMode=0\n", ""));
CHECK_FALSE(SessionDiffers("txSamplingMode=0\n", ""));
CHECK(SessionDiffers("txSamplingMode=1\n", ""));
CHECK_FALSE(SessionDiffers("lockApps=\n", ""));
CHECK(SessionDiffers("lockApps=game.exe\n", ""));
// The core's missing-key engine is transform, the template's too; the retired hybrid reads as it.
Expand Down
6 changes: 3 additions & 3 deletions ui/src/settings-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export const groups = [
cards: [
{ caption: 'Level and speed', rows: [
{ key: 'maxLevel', type: 'slider', label: 'Max zoom', keywords: ['maximum', 'limit', 'highest', 'magnification', 'level', 'cap', 'zoom level', 'factor', 'times', 'upper'], desc: 'The highest zoom level', min: 2, max: 30, step: 1, def: 30.0, unit: 'times' },
{ key: 'zoomInSpeed', type: 'slider', label: 'Zoom-in speed', keywords: ['faster', 'slower', 'rate', 'magnify speed', 'zoom rate', 'acceleration', 'velocity', 'how fast'], desc: 'How fast the view zooms in', min: 0.25, max: 4, step: 0.05, def: 1.5, unit: 'times' },
{ key: 'zoomOutSpeed', type: 'slider', label: 'Zoom-out speed', keywords: ['faster', 'slower', 'rate', 'zoom rate', 'velocity', 'how fast'], desc: 'How fast the view zooms out', min: 0.25, max: 4, step: 0.05, def: 1.5, unit: 'times' },
{ key: 'zoomInSpeed', type: 'slider', label: 'Zoom-in speed', keywords: ['faster', 'slower', 'rate', 'magnify speed', 'zoom rate', 'acceleration', 'velocity', 'how fast'], desc: 'How fast the view zooms in', min: 0.25, max: 4, step: 0.05, def: 1.75, unit: 'times' },
{ key: 'zoomOutSpeed', type: 'slider', label: 'Zoom-out speed', keywords: ['faster', 'slower', 'rate', 'zoom rate', 'velocity', 'how fast'], desc: 'How fast the view zooms out', min: 0.25, max: 4, step: 0.05, def: 1.75, unit: 'times' },
{ key: 'zoomEaseOutMs', type: 'slider', label: 'Release glide', keywords: ['inertia', 'coast', 'momentum', 'ease out', 'easing', 'smooth stop', 'slow down', 'deceleration', 'stop', 'let go', 'release', 'delay', 'milliseconds'], desc: 'How long the zoom coasts after you let go', min: 0, max: 300, step: 5, def: 115, unit: 'ms' },
] },
{ caption: 'Easing', rows: [
Expand All @@ -79,7 +79,7 @@ export const groups = [
// Transform: txSamplingMode, DWM's own filter (0 nearest / 1 smooth = Lanczos, the former High
// resolution cursor toggle, #227). It applies live (#369) and turning Smooth on asks first (#441).
{ key: 'txSamplingMode', type: 'select', label: 'Transform scaling', tag: 'Experimental', keywords: ['scaling', 'upscaling', 'filter', 'sharp', 'crisp', 'smooth', 'blurry', 'pixelated', 'hidpi', 'sampling', 'quality', 'nearest', 'lanczos', 'bitmap smoothing', 'high resolution cursor', 'mpo', 'overlay', 'cursor', 'pointer', 'resolution', '4k', 'antialiasing'],
desc: 'Smooth also sharpens the pointer at high zoom', options: ['0', '1'], optionLabels: { '0': 'Nearest', '1': 'Smooth' }, def: '1',
desc: 'Smooth also sharpens the pointer at high zoom', options: ['0', '1'], optionLabels: { '0': 'Nearest', '1': 'Smooth' }, def: '0',
showIf: { key: 'model', ne: 'render' } },
// Render: renderScaling (issue #456), the engine's scaling filter: nearest (default, #508), xBR
// or NVIDIA Image Scaling, picked in the 2026-10-12 upscaler A/B. Applies on the config reload.
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/scaling-notice.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
window.__skipSplash = true;
window.__msgs = [];
window.__live = { maxLevel: '12', model: 'transform', txSamplingMode: '0', uiTheme: 'dark' };
window.__live = { maxLevel: '12', model: 'transform', uiTheme: 'dark' };
window.__saved = { ...window.__live };
const listeners = new Set();
const send = (data) => listeners.forEach((fn) => fn({ data }));
Expand Down
1 change: 0 additions & 1 deletion ui/tests/session.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ test('a Wind with both engines up switches engine live: no Restart Wind button',
});

test('Smooth transform scaling applies live: no MPO registry write, no restart prompt (#369)', async ({ page }) => {
await page.addInitScript(() => { window.__live.txSamplingMode = '0'; window.__saved.txSamplingMode = '0'; });
await page.goto('/');
await go(page, 'zoom');
const sel = page.locator('[data-key="txSamplingMode"] select');
Expand Down
1 change: 0 additions & 1 deletion ui/tests/settings.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ test('View: the edge margin shows only when the pointer is kept within the edges
});

test('Smooth transform scaling is tagged Experimental and keeps the release glide (#439)', async ({ page }) => {
await page.addInitScript(() => { window.__cfgExtra = { txSamplingMode: '0' }; });
await page.goto('/');
await go(page, 'zoom');
await expect(key(page, 'txSamplingMode').locator('.tag')).toHaveText('Experimental');
Expand Down
Loading