ci: перекалибровать общий потолок spaceSwitchMs изометрии по ряду CI (#675)

spaceSwitchMs изометрии — одно холодное переключение этажа со сборкой
2.5D-геометрии; он растёт с каждой стадией 2.5D, и потолок 1800 мс из #89
stage 1 стал самим уровнем. Медиана perf-smoke на hosted-раннере:
1500 мс (09-09…09-12, 12 прогонов) → 1668 (09-19…09-25, 6) → 1798 после
#649 (9, σ 42, максимум 1867,7). Попытки одного SHA расходятся на 0,4 %,
разные прогоны одного SHA — до 5,5 %: больше образцов вердикт не меняют,
рычаг — потолок.

2200 мс: +17,8 % над наблюдённым максимумом и ниже единственной настоящей
регрессии окна (2719,6 мс, #583 до решётки, 09-16); удвоение уровня
краснеет с запасом. Одно число в трёх файлах: смок = полный профиль
(#473 AC4), плотный двойник = исторический (#160). Коэффициент и допуск
полного сравнения не тронуты. Обоснование — demo/performance/README.md,
тест закрепляет число и три точки ряда.

Issue: #675
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:
Claude
2026-09-27 08:50:34 +00:00
parent 7871efb0d6
commit 6183119f1e
5 changed files with 77 additions and 29 deletions
+58 -26
View File
@@ -188,6 +188,33 @@ import { readFileSync } from 'node:fs';
const readBudget = (name) => JSON.parse(readFileSync(new URL(`../demo/performance/${name}`, import.meta.url), 'utf8'));
// Синтетический отчёт для `compare --absolute-only`: все метрики по 1 мс,
// кроме переданных в `timings`.
const absoluteSmokeReport = (smoke, timings = {}) => ({
schema: 2, profile: smoke.profile, sourceSha: '1'.repeat(40), buildFingerprint: 'fixture',
runtime: { node: 'v22.0.0', chromium: '1.2.3', platform: 'linux', arch: 'x64' },
fixture: { rooms: 60 },
summary: Object.fromEntries(Object.keys(smoke.timings).map((metric) => {
const value = timings[metric] ?? 1;
return [metric, { median: value, p95: value, min: value, max: value }];
})),
longTasks: { maxSingleMs: 1, countP95: 1, totalP95Ms: 1 },
...(smoke.profile === 'large-house-isometric-v1' ? { effectiveProjection: ['iso'] } : {}),
rows: [0, 1, 2].map(() => ({
heapGrowthBytes: 1, preciseGc: true,
longTasks: Object.fromEntries(['load', ...Object.keys(smoke.longTaskWindows ?? {})]
.map((name) => [name, { supported: true, count: 1, maxMs: 1, totalMs: 1 }])),
cacheEntries: { ...smoke.cacheEntries },
cacheGrowth: Object.fromEntries(Object.keys(smoke.cacheGrowth).map((key) => [key, 0])),
renderedDevices: smoke.renderedDevices,
...(smoke.profile === 'large-house-isometric-v1'
? {
effectiveProjection: 'iso',
isoStructuralBuilds: { supported: true, initial: 1, beforeHaUpdate: 2, afterHaUpdate: 2, haUpdateDelta: 0 },
} : {}),
})),
});
for (const [smokeName, fullName] of [
['budgets-isometric-smoke.json', 'budgets-large-house-isometric.json'],
['budgets-interaction-smoke.json', 'budgets-large-house-interaction.json'],
@@ -214,33 +241,11 @@ for (const [smokeName, fullName] of [
// Пригодность для --absolute-only: синтетический отчёт под потолками
// проходит, первый кадр как у de215578 (9 870 мс) — красный.
const build = (firstFrame) => ({
schema: 2, profile: smoke.profile, sourceSha: '1'.repeat(40), buildFingerprint: 'fixture',
runtime: { node: 'v22.0.0', chromium: '1.2.3', platform: 'linux', arch: 'x64' },
fixture: { rooms: 60 },
summary: Object.fromEntries(Object.keys(smoke.timings).map((metric) => {
const value = metric === 'firstStableRenderMs' ? firstFrame : 1;
return [metric, { median: value, p95: value, min: value, max: value }];
})),
longTasks: { maxSingleMs: 1, countP95: 1, totalP95Ms: 1 },
...(smoke.profile === 'large-house-isometric-v1' ? { effectiveProjection: ['iso'] } : {}),
rows: [0, 1, 2].map(() => ({
heapGrowthBytes: 1, preciseGc: true,
longTasks: Object.fromEntries(['load', ...Object.keys(smoke.longTaskWindows ?? {})]
.map((name) => [name, { supported: true, count: 1, maxMs: 1, totalMs: 1 }])),
cacheEntries: { ...smoke.cacheEntries },
cacheGrowth: Object.fromEntries(Object.keys(smoke.cacheGrowth).map((key) => [key, 0])),
renderedDevices: smoke.renderedDevices,
...(smoke.profile === 'large-house-isometric-v1'
? {
effectiveProjection: 'iso',
isoStructuralBuilds: { supported: true, initial: 1, beforeHaUpdate: 2, afterHaUpdate: 2, haUpdateDelta: 0 },
} : {}),
})),
});
const ok = evaluatePerformanceBudget({ candidate: build(1), budgets: smoke, absoluteOnly: true });
const ok = evaluatePerformanceBudget({ candidate: absoluteSmokeReport(smoke), budgets: smoke, absoluteOnly: true });
assert.deepEqual(ok.failures, [], 'отчёт под потолками обязан проходить');
const regressed = evaluatePerformanceBudget({ candidate: build(9870), budgets: smoke, absoluteOnly: true });
const regressed = evaluatePerformanceBudget({
candidate: absoluteSmokeReport(smoke, { firstStableRenderMs: 9870 }), budgets: smoke, absoluteOnly: true,
});
assert.ok(regressed.failures.some((check) => check.id === 'timing.firstStableRenderMs.median'),
'первый кадр 9 870 мс обязан краснеть');
});
@@ -257,6 +262,33 @@ test('interaction aggregate keeps hosted-runner headroom without weakening compo
assert.equal(smoke.timings.editorSeriesMs.hardMaxMs, 750);
});
// #675: обоснование потолка и ряд замеров — demo/performance/README.md,
// «CI contracts». Три файла держат одно число: смок повторяет полный профиль
// (#473 AC4), плотный двойник — исторический (#160).
test('isometric space switch ceiling covers the 2.5D runner level and still catches #583 (#675)', () => {
const files = [
'budgets-isometric-smoke.json',
'budgets-large-house-isometric.json',
'budgets-isometric-stage3-dense.json',
];
for (const file of files)
assert.equal(readBudget(file).timings.spaceSwitchMs.hardMaxMs, 2200, `${file}: общий потолок spaceSwitchMs`);
const smoke = readBudget('budgets-isometric-smoke.json');
const red = (ms) => evaluatePerformanceBudget({
candidate: absoluteSmokeReport(smoke, { spaceSwitchMs: ms }), budgets: smoke, absoluteOnly: true,
}).failures.some((check) => check.id === 'timing.spaceSwitchMs.median');
assert.equal(red(1867.7), false, 'максимум уровня 2.5D на hosted-раннере (прогон 36306131709) проходит');
assert.equal(red(2719.6), true, 'провал #583 до решётки (прогон 35070397356) краснеет и по spaceSwitchMs');
assert.equal(red(2 * 1798.3), true, 'удвоение текущего уровня краснеет');
// Потолок — не детектор тренда: полный прогон по-прежнему сравнивает с базой
// того же раннера, и рычагом не стали ни коэффициент, ни допуск.
for (const file of files.slice(1)) {
const budget = readBudget(file).timings.spaceSwitchMs;
assert.equal(budget.maxRegressionRatio, 0.2, `${file}: коэффициент не рычаг`);
assert.equal(budget.noiseAllowanceMs, 100, `${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');