mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
test(perf): pull the switchCycleMs ceilings down to the warmed level (#747)
Since #735 switchCycleMs times the warmed twelve-switch cycle, and the absolute ceilings of 7000 ms (flat) and 8000 ms (2.5D) sat 7.6-10.2 times above the level. performance_smoke judges only these ceilings, so between full runs the warm floor switch that #694/#725 just sped up was guarded only against a several-fold collapse. Series: every Full Performance run after #735, both sides (the base is measured by the candidate runner, so it is warm too), 7 samples each - 36821241343 (#735, base76558bf2), 36838891001 (#740), 36838952536 (#742) and 36839009721 (#739), the last three against dev7ff2b5ae. flat large-house-v1 / plan-snap / interaction 666.9-812.7 ms 2.5D large-house-isometric / stage3-dense 766.5-1333.9 ms The 2.5D maximum is the dense pair of 36838952536, whose base on the same runner read 1249.7 ms against 849.9-982.4 ms elsewhere: runner noise the series is meant to contain. No 3-sample performance_smoke median is in the series yet; those profiles join Validate only on a src/** diff. Rule (as #692, #675 falls in the same band): one number per family, the first multiple of 50 ms at or above 1.15 x M and no higher than 1.2 x M, M being the family's maximum median: flat 1.15 x 812.7 = 934.6 -> 950 (+16.9 %), 2.5D 1.15 x 1333.9 = 1534.0 -> 1550 (+16.2 %). One number per family keeps the smoke = full (#473 AC4), plan-snap/interaction "every original ceiling" and dense = historical (#160) contracts; the price is wider headroom for the faster profiles. The base-relative comparison of the full workflow (0.35 / 0.2, 250 ms) is unchanged and stays the detector for smaller growth. The new test pins both families: one ceiling in every file of a family, every point of the series passes the smoke budget with --absolute-only, the ceiling follows the rule and stays inside [1.15, 1.2] x M, doubling the level fails, and the full profiles' ratio and noise allowance are unchanged. 7000 left in any flat file or a ceiling under 1.15 x M reds it. The README records the series and the reasoning. Issue: #747 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -87,7 +87,8 @@ profile's Long Task sums before and after #735 are not comparable. Local
|
||||
diagnostics showed the median falling by roughly 1.6–2.8 times (for example
|
||||
`large-house-v1` about 2.0 s to 0.9 s); the exact-SHA Full Performance medians
|
||||
on both sides of the change are recorded in #735. Budgets and `hardMaxMs` did
|
||||
not change.
|
||||
not change in #735; #747 then brought the `switchCycleMs` ceilings down to the
|
||||
warmed level (see "CI contracts" below).
|
||||
|
||||
Every report is tied to the source fingerprint embedded by Rollup. A stale
|
||||
bundle is a hard failure.
|
||||
@@ -146,6 +147,32 @@ wide margin. Occasional hosted runners are about a quarter faster, which a
|
||||
ceiling does not mind. Smaller growth stays the job of the full workflow's
|
||||
base-relative comparison.
|
||||
|
||||
The `switchCycleMs` ceilings are 950 ms for the flat family (`budgets.json`,
|
||||
plan-snap, interaction and the interaction smoke) and 1550 ms for the 2.5D
|
||||
family (both isometric profiles and the isometric smoke), one number per family
|
||||
(#747). Since #735 the metric is the warmed twelve-switch cycle, and the former
|
||||
7000 and 8000 ms sat 7.6–10.2 times above the #735 medians. The series is every
|
||||
Full Performance run after #735, both sides with 7 samples — the base is
|
||||
measured by the candidate runner, so it is warm too: 36821241343 (#735, base
|
||||
`76558bf2`), 36838891001 (#740), 36838952536 (#742) and 36839009721 (#739), the
|
||||
last three against `dev` `7ff2b5ae`. Flat medians span 666.9–812.7 ms (maximum:
|
||||
`large-house-v1`, the base of 36838891001). 2.5D medians span 766.5–1333.9 ms;
|
||||
the maximum is `isometric-stage3-dense-v1` in 36838952536, whose base on the
|
||||
same runner read 1249.7 ms against 849.9–982.4 ms in the other runs — runner
|
||||
noise of 27–47 %, which the series is meant to contain. Each ceiling is the
|
||||
first multiple of 50 ms at or above 1.15 × that maximum and no higher than 1.2 ×
|
||||
(the #692 band, which #675 also falls into): +16.9 % and +16.2 %. Doubling the
|
||||
level fails by a wide margin. The price of one number per family is wider
|
||||
headroom for the faster profiles (`large-house-isometric-v1`, maximum 1135.7 ms,
|
||||
gets +36 %); it keeps the plan-snap and interaction contracts of "every original
|
||||
ceiling" and the dense twin equal to the historical profile. Smaller growth
|
||||
stays the job of the full workflow's base-relative comparison (unchanged: 0.35
|
||||
flat, 0.2 2.5D, 250 ms noise allowance), and a floor built inside the window
|
||||
already fails the sample through the #735 structural guard, not through time. No
|
||||
3-sample `performance_smoke` median is in the series yet: those profiles join
|
||||
Validate only on a `src/**` diff, so the first beta candidate after #747 is
|
||||
their check.
|
||||
|
||||
The 2.5D View toggle (`viewToggleMs`) is reported by the isometric profiles but
|
||||
budgeted by none of them (#720, owner decision in #694 on 2026-09-30). Switching
|
||||
Flat ↔ 2.5D is a one-off General settings change, not something View or kiosk
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"switchCycleMs": {
|
||||
"stat": "median",
|
||||
"hardMaxMs": 7000
|
||||
"hardMaxMs": 950
|
||||
}
|
||||
},
|
||||
"longTasks": {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"switchCycleMs": {
|
||||
"stat": "median",
|
||||
"hardMaxMs": 8000
|
||||
"hardMaxMs": 1550
|
||||
}
|
||||
},
|
||||
"longTasks": {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"resizePreviewMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 150, "hardMaxMs": 2200 },
|
||||
"panZoomMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 60, "hardMaxMs": 600 },
|
||||
"settingsDialogMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1200 },
|
||||
"switchCycleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 8000 }
|
||||
"switchCycleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 1550 }
|
||||
},
|
||||
"longTasks": {
|
||||
"maxSingleMs": 3000,
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"stat": "median",
|
||||
"maxRegressionRatio": 0.35,
|
||||
"noiseAllowanceMs": 250,
|
||||
"hardMaxMs": 7000
|
||||
"hardMaxMs": 950
|
||||
}
|
||||
},
|
||||
"longTasks": {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"resizePreviewMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 150, "hardMaxMs": 2200 },
|
||||
"panZoomMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 60, "hardMaxMs": 600 },
|
||||
"settingsDialogMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 100, "hardMaxMs": 1200 },
|
||||
"switchCycleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 8000 }
|
||||
"switchCycleMs": { "stat": "median", "maxRegressionRatio": 0.2, "noiseAllowanceMs": 250, "hardMaxMs": 1550 }
|
||||
},
|
||||
"longTasks": {
|
||||
"maxSingleMs": 3000,
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"stat": "median",
|
||||
"maxRegressionRatio": 0.35,
|
||||
"noiseAllowanceMs": 250,
|
||||
"hardMaxMs": 7000
|
||||
"hardMaxMs": 950
|
||||
}
|
||||
},
|
||||
"longTasks": {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"stat": "median",
|
||||
"maxRegressionRatio": 0.35,
|
||||
"noiseAllowanceMs": 250,
|
||||
"hardMaxMs": 7000
|
||||
"hardMaxMs": 950
|
||||
}
|
||||
},
|
||||
"longTasks": {
|
||||
|
||||
@@ -307,6 +307,69 @@ test('isometric space switch ceiling covers the 2.5D runner level and still catc
|
||||
}
|
||||
});
|
||||
|
||||
// #747: обоснование потолков `switchCycleMs` и ряд — demo/performance/README.md,
|
||||
// «CI contracts». Ряд — медианы Full Performance после #735 (7 образцов, обе
|
||||
// стороны: база меряется раннером кандидата, окно тёплое) в порядке
|
||||
// [база, кандидат] прогонов 36821241343 (#735, база 76558bf2), 36838891001
|
||||
// (#740), 36838952536 (#742), 36839009721 (#739); у трёх последних база —
|
||||
// `dev` 7ff2b5ae. Смоковых медиан (3 образца) в ряду пока нет.
|
||||
const SWITCH_CYCLE_FAMILIES = {
|
||||
flat: {
|
||||
ceiling: 950,
|
||||
smoke: 'budgets-interaction-smoke.json',
|
||||
files: ['budgets.json', 'budgets-large-house-plan-snap.json', 'budgets-large-house-interaction.json',
|
||||
'budgets-interaction-smoke.json'],
|
||||
full: { maxRegressionRatio: 0.35, noiseAllowanceMs: 250 },
|
||||
medians: {
|
||||
'large-house-v1': [733.4, 683.3, 812.7, 749.9, 750.3, 800, 716.7, 717.9],
|
||||
'large-house-plan-snap-v1': [716.7, 700.1, 762.6, 701, 733.3, 783.2, 749.9, 750.8],
|
||||
'large-house-interaction-v1': [716.1, 754.1, 766.5, 666.9, 720.3, 750.1, 700.4, 703.4],
|
||||
},
|
||||
},
|
||||
isometric: {
|
||||
ceiling: 1550,
|
||||
smoke: 'budgets-isometric-smoke.json',
|
||||
files: ['budgets-large-house-isometric.json', 'budgets-isometric-stage3-dense.json', 'budgets-isometric-smoke.json'],
|
||||
full: { maxRegressionRatio: 0.2, noiseAllowanceMs: 250 },
|
||||
medians: {
|
||||
'large-house-isometric-v1': [1069.5, 1051, 866.5, 766.5, 1134.9, 1100.4, 1135.7, 1089],
|
||||
'isometric-stage3-dense-v1': [966.7, 983.2, 982.4, 916.5, 1249.7, 1333.9, 849.9, 803.6],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const [family, spec] of Object.entries(SWITCH_CYCLE_FAMILIES)) {
|
||||
test(`#747 switchCycleMs (${family}): потолок над тёплым уровнем после #735 с запасом на шум раннера`, () => {
|
||||
// Одно число на семью: смок = полный (#473 AC4), plan-snap и interaction
|
||||
// сохраняют потолки large-house-v1, плотный двойник — исторические (#160).
|
||||
for (const file of spec.files) {
|
||||
const budget = readBudget(file).timings.switchCycleMs;
|
||||
assert.equal(budget.hardMaxMs, spec.ceiling, `${file}: общий потолок switchCycleMs семьи ${family}`);
|
||||
assert.equal(budget.stat, 'median', `${file}: метрика — медиана, как у соседей`);
|
||||
}
|
||||
const smoke = readBudget(spec.smoke);
|
||||
const red = (ms) => evaluatePerformanceBudget({
|
||||
candidate: absoluteSmokeReport(smoke, { switchCycleMs: ms }), budgets: smoke, absoluteOnly: true,
|
||||
}).failures.some((check) => check.id === 'timing.switchCycleMs.median');
|
||||
const series = Object.values(spec.medians).flat();
|
||||
for (const median of series) assert.equal(red(median), false, `${median} из ряда краснеет на ${spec.ceiling}`);
|
||||
const level = Math.max(...series);
|
||||
// Правило #747: первое кратное 50 мс не ниже 1.15 × M и не выше 1.2 × M —
|
||||
// полоса #692, в которую попадает и #675.
|
||||
assert.equal(spec.ceiling, Math.ceil((level * 1.15) / 50) * 50, `потолок — первое кратное 50 над 1.15 × ${level}`);
|
||||
assert.ok(spec.ceiling >= level * 1.15, 'запас над максимумом ряда не меньше 15 %');
|
||||
assert.ok(spec.ceiling <= level * 1.2, 'и не больше 20 %: потолок остаётся гардом');
|
||||
assert.equal(red(2 * level), true, 'удвоение уровня краснеет');
|
||||
// Меньшее ловит относительное сравнение полного прогона: его коэффициент и
|
||||
// допуск не рычаг.
|
||||
for (const file of spec.files.filter((name) => name !== spec.smoke)) {
|
||||
const budget = readBudget(file).timings.switchCycleMs;
|
||||
assert.equal(budget.maxRegressionRatio, spec.full.maxRegressionRatio, `${file}: коэффициент не рычаг`);
|
||||
assert.equal(budget.noiseAllowanceMs, spec.full.noiseAllowanceMs, `${file}: допуск не рычаг`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test('boundary collision search restores the ordinary isometric allowances (#585)', () => {
|
||||
const isometric = readBudget('budgets-large-house-isometric.json');
|
||||
const dense = readBudget('budgets-isometric-stage3-dense.json');
|
||||
|
||||
Reference in New Issue
Block a user