From 9683a590163e129d983174f30a125a5b92cb10a1 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 16 Sep 2026 17:36:47 +0300 Subject: [PATCH] =?UTF-8?q?perf:=20=D0=BF=D1=80=D0=B8=D0=BD=D1=8F=D1=82?= =?UTF-8?q?=D1=8C=20=D1=88=D0=B0=D0=B3=20=D0=B8=D0=B7=D0=BE=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=B8=20=D0=BD=D0=B0=20=D0=B6=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B5=20=D0=B2=20=D0=B1=D1=8E=D0=B4=D0=B6=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=D1=85=20=D0=B4=D0=B2=D1=83=D1=85=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D0=B5=D0=B9=20(#585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Полные бенчмарки кандидата v1.76.0 (прогон 35097102695, SHA c3d64789) против v1.75.0 покраснели на двух скрытых изометрических профилях: | метрика | v1.75.0 | v1.76.0 | предел | |---|---|---|---| | large-house-isometric resizePreview | 603 | 981.4 | 753 | | large-house-isometric panZoom | 90.8 | 205.2 | 150.8 | | stage3-dense stateUpdate | 77.5 | 169.1 | 152.5 | | stage3-dense resizePreview | 582.5 | 765.6 | 732.5 | | stage3-dense panZoom | 90.6 | 171.7 | 150.6 | Шаг настоящий и объяснённый: #583 добавил скрытому 2.5D-виду геометрии, а поиск свободного места для подписей даже после ускорения решёткой стоит вдвое дороже, чем до #583. Абсолютные потолки не тронуты и держатся с запасом (panZoom 205 при 600, resize 981 при 2200); все семь пользовательских профилей зелёные — регрессия целиком внутри вида за `hp_alpha`. Решение владельца 2026-09-16: принять. Рычаг — допуск в миллисекундах, а не коэффициент: он покрывает разовый сдвиг уровня и продолжает ловить рост от нового уровня, тогда как поднятый коэффициент разрешил бы удвоение навсегда. Значения одинаковы у обоих профилей — контракт #160 требует, чтобы плотный двойник Stage 3 делил с историческим профилем каждый общий потолок, и тест `performance-workflow` это стережёт. Допуски временные. #585 переписывает поиск на перебор границ препятствий вместо скана диска 48 px; когда он приедет, значения возвращаются к 150/60/75. Тест `performance-budget` фиксирует и числа, и то, что наблюдённый шаг проходит, а следующий такой же — уже нет. Честно о границе метода: сравнение идёт с ПРЕДЫДУЩЕЙ вершиной main, а она уже несёт эту же линейку, поэтому следующий прогон был бы зелёным и без правки бюджетов. Правка сделана не ради зелёного прогона, а чтобы принятый уровень был записан явно и проверялся тестом. Отдельно завожу, что базой стабильного кандидата должен быть предыдущий стабильный тег, а не вершина main. npm test 2721/2720/0 fail. Issue: #585 User-Visible: no Release: v1.76.0 --- demo/performance/README.md | 15 +++++++ .../budgets-isometric-stage3-dense.json | 6 +-- .../budgets-large-house-isometric.json | 6 +-- test/performance-budget.test.mjs | 45 +++++++++++++++++++ 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/demo/performance/README.md b/demo/performance/README.md index 44d58112..d7d22370 100644 --- a/demo/performance/README.md +++ b/demo/performance/README.md @@ -231,6 +231,21 @@ runner jitter, sat at 16 → 20 against a 19.2 limit on the v1.73.0 stable comparison. The allowance widens the count check alone to 16 → 21 for that baseline; it is not a licence for more work per task. +The same two isometric profiles carry a widened **gesture** allowance: +`resizePreviewMs` 450 ms, `panZoomMs` 150 ms and `stateUpdateMs` 120 ms, held +identical on both profiles because the #160 contract requires the Stage 3 dense +twin to share every common ceiling with `large-house-isometric-v1`. Owner decision 2026-09-16, #585: the Full Performance run +of the v1.76.0 stable candidate against v1.75.0 (run 35097102695) measured +resizePreview 603 → 981 ms and panZoom 91 → 205 ms in the hidden 2.5D view. +The step is real and understood — #583 gives that view more geometry, and the +overlay-collision search still costs about twice what it did before #583, even +after the coarse-lattice speed-up. Absolute ceilings did not move (panZoom 205 +against 600, resize 981 against 2200) and every user-visible profile stayed +green. The lever is milliseconds, not the ratio: it absorbs one level shift and +still gates growth from the new level. #585 rewrites the search to enumerate +obstacle boundaries instead of scanning the 48 px disc; when it lands, these +allowances go back to 150/60/75. + The `cleanFloor` entry ceiling is 100: the reviewed fixture warms exactly 100 deterministic room/physical-body entries. An extra 20 means that one complete floor was invalidated and rebuilt, so fixture extensions must recalibrate this diff --git a/demo/performance/budgets-isometric-stage3-dense.json b/demo/performance/budgets-isometric-stage3-dense.json index 65264d4b..2d2928ce 100644 --- a/demo/performance/budgets-isometric-stage3-dense.json +++ b/demo/performance/budgets-isometric-stage3-dense.json @@ -9,9 +9,9 @@ "openingUpdateMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 75, "hardMaxMs": 1000 }, "overlayInteractionMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 75, "hardMaxMs": 1000 }, "spaceSwitchMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1800 }, - "stateUpdateMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 75, "hardMaxMs": 1000 }, - "resizePreviewMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 150, "hardMaxMs": 2200 }, - "panZoomMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 60, "hardMaxMs": 600 }, + "stateUpdateMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 120, "hardMaxMs": 1000 }, + "resizePreviewMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 450, "hardMaxMs": 2200 }, + "panZoomMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 150, "hardMaxMs": 600 }, "settingsDialogMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1200 }, "switchCycleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 8000 } }, diff --git a/demo/performance/budgets-large-house-isometric.json b/demo/performance/budgets-large-house-isometric.json index 668ae0fb..83700307 100644 --- a/demo/performance/budgets-large-house-isometric.json +++ b/demo/performance/budgets-large-house-isometric.json @@ -7,9 +7,9 @@ "firstStableRenderMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 3500 }, "viewToggleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1500 }, "spaceSwitchMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1800 }, - "stateUpdateMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 75, "hardMaxMs": 1000 }, - "resizePreviewMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 150, "hardMaxMs": 2200 }, - "panZoomMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 60, "hardMaxMs": 600 }, + "stateUpdateMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 120, "hardMaxMs": 1000 }, + "resizePreviewMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 450, "hardMaxMs": 2200 }, + "panZoomMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 150, "hardMaxMs": 600 }, "settingsDialogMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1200 }, "switchCycleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 8000 } }, diff --git a/test/performance-budget.test.mjs b/test/performance-budget.test.mjs index 668357dd..85bdeb60 100644 --- a/test/performance-budget.test.mjs +++ b/test/performance-budget.test.mjs @@ -257,6 +257,51 @@ test('interaction aggregate keeps hosted-runner headroom without weakening compo assert.equal(smoke.timings.editorSeriesMs.hardMaxMs, 750); }); +test('изометрический допуск жеста покрывает ровно принятый шаг #583, не больше (#585)', () => { + const isometric = readBudget('budgets-large-house-isometric.json'); + const dense = readBudget('budgets-isometric-stage3-dense.json'); + // Полные бенчмарки кандидата v1.76.0 (прогон 35097102695) против v1.75.0: + // скрытый 2.5D стал дороже на жесте — #583 добавил виду геометрии, а поиск + // свободного места для подписей всё ещё вдвое дороже, чем до него. Решение + // владельца 2026-09-16: принять шаг по трём метрикам двух изометрических + // профилей. Рычаг — допуск в миллисекундах, а НЕ коэффициент: он покрывает + // разовый сдвиг уровня и продолжает ловить рост от нового уровня. Чинит шаг + // #585 (перебор границ препятствий вместо скана диска), после него допуски + // возвращаются к 150/60/75. + assert.equal(isometric.timings.resizePreviewMs.noiseAllowanceMs, 450); + assert.equal(isometric.timings.panZoomMs.noiseAllowanceMs, 150); + assert.equal(isometric.timings.stateUpdateMs.noiseAllowanceMs, 120, 'профили-близнецы обязаны совпадать по общим метрикам (#160)'); + assert.equal(dense.timings.stateUpdateMs.noiseAllowanceMs, 120); + assert.equal(dense.timings.resizePreviewMs.noiseAllowanceMs, 450); + assert.equal(dense.timings.panZoomMs.noiseAllowanceMs, 150); + for (const budget of [isometric, dense]) { + for (const metric of ['resizePreviewMs', 'panZoomMs', 'stateUpdateMs']) { + assert.equal(budget.timings[metric].maxRegressionRatio, 0.2, 'коэффициент не рычаг'); + } + assert.equal(budget.timings.resizePreviewMs.hardMaxMs, 2200, 'абсолютный потолок не двигался'); + assert.equal(budget.timings.panZoomMs.hardMaxMs, 600); + assert.equal(budget.timings.modelReadyMs.noiseAllowanceMs, 200, 'загрузка допуска не получала'); + assert.equal(budget.timings.spaceSwitchMs.noiseAllowanceMs, 100); + } + for (const file of ['budgets.json', 'budgets-large-house-plan-snap.json', 'budgets-large-house-interaction.json']) { + const other = readBudget(file); + assert.equal(other.timings.panZoomMs.noiseAllowanceMs, 60, `${file} допуска жеста не получал`); + assert.equal(other.timings.resizePreviewMs.noiseAllowanceMs, 150, `${file} допуска ресайза не получал`); + } + // Наблюдённые числа прогона проходят, следующий такой же шаг — нет. + const limit = (budget, metric, baseline) => Math.max( + baseline * (1 + budget.timings[metric].maxRegressionRatio), + baseline + budget.timings[metric].noiseAllowanceMs, + ); + assert.ok(limit(isometric, 'resizePreviewMs', 603) >= 981.4, 'принятый шаг проходит'); + assert.ok(limit(isometric, 'resizePreviewMs', 603) < 1100, 'следующий шаг того же размера — нет'); + assert.ok(limit(isometric, 'panZoomMs', 90.8) >= 205.2); + assert.ok(limit(isometric, 'panZoomMs', 90.8) < 260); + assert.ok(limit(dense, 'stateUpdateMs', 77.5) >= 169.1); + assert.ok(limit(dense, 'resizePreviewMs', 582.5) >= 765.6); + assert.ok(limit(dense, 'panZoomMs', 90.6) >= 171.7); +}); + test('isometric long-task count allowance covers the lazy iso-chunk split, nothing else (#507)', () => { const isometric = readBudget('budgets-large-house-isometric.json'); assert.equal(isometric.longTasks.countNoiseAllowance, 5, 'owner-accepted +2 tasks of the lazy iso-scene-render split plus jitter');