mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric) (push) Failing after 4m40s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (interaction) (push) Failing after 5m4s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric-stage3) (push) Failing after 4m33s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (blend) (push) Failing after 5m6s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-default) (push) Failing after 3m55s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (overlay) (push) Failing after 4m2s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (plan-snap) (push) Failing after 4m1s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 34s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Successful in 45s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Failing after 1m9s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Failing after 1m47s
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Failing after 2m21s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (large-house) (push) Failing after 12m40s
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Successful in 2m51s
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Failing after 1m33s
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Successful in 4m31s
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Successful in 3m28s
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 26s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-glow) (push) Failing after 29m2s
Полные бенчмарки кандидата 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
320 lines
16 KiB
JavaScript
320 lines
16 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { evaluatePerformanceBudget } from '../demo/performance/evaluate.mjs';
|
|
|
|
const budgets = {
|
|
schema: 1,
|
|
profile: 'large-house-v1',
|
|
minimumSamples: 2,
|
|
timings: {
|
|
firstStableRenderMs: {
|
|
stat: 'median', maxRegressionRatio: 0.25, noiseAllowanceMs: 10, hardMaxMs: 500,
|
|
},
|
|
},
|
|
longTasks: {
|
|
maxSingleMs: 200, maxCountP95: 10, maxTotalP95Ms: 500,
|
|
maxSingleRegressionRatio: 0.5, maxSingleNoiseAllowanceMs: 20,
|
|
maxCountRegressionRatio: 0.5, countNoiseAllowance: 2,
|
|
maxTotalRegressionRatio: 0.5, noiseAllowanceMs: 20,
|
|
},
|
|
heap: {
|
|
required: true, hardMaxGrowthBytes: 1000, maxRegressionRatio: 0.5, noiseAllowanceBytes: 100,
|
|
},
|
|
cacheEntries: { cleanFloor: 3 },
|
|
cacheGrowth: { cleanFloor: 0 },
|
|
renderedDevices: 200,
|
|
};
|
|
|
|
const report = ({ timing = 100, heap = 100, cache = 3, growth = 0, long = 20 } = {}) => ({
|
|
schema: 2,
|
|
profile: 'large-house-v1',
|
|
sourceSha: '1'.repeat(40),
|
|
buildFingerprint: `fixture-${timing}`,
|
|
runtime: { node: 'v22.0.0', chromium: '1.2.3', platform: 'linux', arch: 'x64' },
|
|
fixture: { rooms: 60 },
|
|
summary: { firstStableRenderMs: { median: timing, p95: timing, min: timing, max: timing } },
|
|
longTasks: { maxSingleMs: long, countP95: 1, totalP95Ms: long },
|
|
rows: [0, 1].map(() => ({
|
|
heapGrowthBytes: heap,
|
|
preciseGc: true,
|
|
longTasks: { load: { supported: true, count: 1, maxMs: long, totalMs: long } },
|
|
cacheEntries: { cleanFloor: cache },
|
|
cacheGrowth: { cleanFloor: growth },
|
|
renderedDevices: 200,
|
|
})),
|
|
});
|
|
|
|
test('performance budget accepts a candidate inside relative and absolute limits', () => {
|
|
const result = evaluatePerformanceBudget({
|
|
baseline: report(), candidate: report({ timing: 120, heap: 150 }), budgets,
|
|
});
|
|
assert.equal(result.pass, true);
|
|
assert.deepEqual(result.failures, []);
|
|
});
|
|
|
|
test('performance budget rejects a timing regression even below the hard ceiling', () => {
|
|
const result = evaluatePerformanceBudget({
|
|
baseline: report(), candidate: report({ timing: 150 }), budgets,
|
|
});
|
|
assert.equal(result.pass, false);
|
|
assert.ok(result.failures.some((check) => check.id === 'timing.firstStableRenderMs.median'));
|
|
});
|
|
|
|
test('performance budget rejects long tasks, heap growth, cache growth and missing devices', () => {
|
|
const candidate = report({ heap: 2000, cache: 4, growth: 1, long: 300 });
|
|
candidate.rows.forEach((row) => { row.renderedDevices = 199; });
|
|
const result = evaluatePerformanceBudget({ baseline: report(), candidate, budgets });
|
|
assert.equal(result.pass, false);
|
|
assert.deepEqual(
|
|
new Set(result.failures.map((check) => check.id)),
|
|
new Set([
|
|
'longTask.maxSingleMs',
|
|
'longTask.totalP95Ms',
|
|
'heap.growthP95Bytes',
|
|
'cache.entries.cleanFloor',
|
|
'cache.growth.cleanFloor',
|
|
'renderedDevices',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('performance budget compares single/count Long Tasks to the same-runner baseline', () => {
|
|
const baseline = report({ long: 100 });
|
|
baseline.longTasks.countP95 = 4;
|
|
const candidate = report({ long: 130 });
|
|
candidate.longTasks.countP95 = 6;
|
|
const result = evaluatePerformanceBudget({ baseline, candidate, budgets });
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.checks.find((check) => check.id === 'longTask.maxSingleMs')?.baseline, 100);
|
|
assert.equal(result.checks.find((check) => check.id === 'longTask.countP95')?.baseline, 4);
|
|
});
|
|
|
|
test('performance budget refuses incomparable runtime profiles', () => {
|
|
const candidate = report();
|
|
candidate.runtime.chromium = 'different';
|
|
assert.throws(
|
|
() => evaluatePerformanceBudget({ baseline: report(), candidate, budgets }),
|
|
/runtime mismatch for chromium/,
|
|
);
|
|
});
|
|
|
|
test('isometric reports fail closed on exact SHA, effective projection and current Stage 4 metadata', () => {
|
|
const isoBudgets = { ...budgets, profile: 'isometric-stage3-dense-v1' };
|
|
const valid = report();
|
|
Object.assign(valid, {
|
|
profile: isoBudgets.profile,
|
|
effectiveProjection: ['iso'],
|
|
isoStageRevision: ['4'],
|
|
stage3Required: true,
|
|
});
|
|
valid.rows.forEach((row) => Object.assign(row, {
|
|
effectiveProjection: 'iso', isoStageRevision: '4',
|
|
isoStructuralBuilds: {
|
|
supported: true, initial: 1, beforeHaUpdate: 2, afterHaUpdate: 2, haUpdateDelta: 0,
|
|
},
|
|
}));
|
|
const baseline = structuredClone(valid);
|
|
baseline.sourceSha = '2'.repeat(40);
|
|
baseline.stage3Required = false;
|
|
assert.equal(evaluatePerformanceBudget({
|
|
baseline, candidate: valid, budgets: isoBudgets,
|
|
baselineSha: baseline.sourceSha, candidateSha: valid.sourceSha,
|
|
}).pass, true);
|
|
|
|
for (const mutate of [
|
|
(candidate) => { candidate.sourceSha = null; },
|
|
(candidate) => { candidate.effectiveProjection = ['flat']; },
|
|
(candidate) => { candidate.rows[0].effectiveProjection = 'flat'; },
|
|
(candidate) => { candidate.stage3Required = false; },
|
|
(candidate) => { candidate.isoStageRevision = ['2']; },
|
|
(candidate) => { candidate.rows[0].isoStageRevision = '2'; },
|
|
(candidate) => { candidate.rows[0].isoStructuralBuilds.haUpdateDelta = 1; },
|
|
(candidate) => { delete candidate.rows[0].isoStructuralBuilds; },
|
|
]) {
|
|
const candidate = structuredClone(valid);
|
|
mutate(candidate);
|
|
assert.throws(() => evaluatePerformanceBudget({
|
|
baseline, candidate, budgets: isoBudgets,
|
|
}), /sourceSha|effectiveProjection|Stage 4|structural build count/);
|
|
}
|
|
assert.throws(() => evaluatePerformanceBudget({
|
|
baseline, candidate: valid, budgets: isoBudgets, candidateSha: 'f'.repeat(40),
|
|
}), /sourceSha does not match/);
|
|
});
|
|
|
|
test('absolute performance smoke needs no baseline and enforces hard ceilings', () => {
|
|
const accepted = evaluatePerformanceBudget({
|
|
candidate: report({ timing: 450, heap: 900, long: 190 }), budgets, absoluteOnly: true,
|
|
});
|
|
assert.equal(accepted.pass, true);
|
|
assert.equal(accepted.mode, 'absolute');
|
|
assert.equal(accepted.checks.find((check) => check.id === 'timing.firstStableRenderMs.median')?.baseline, undefined);
|
|
|
|
const rejected = evaluatePerformanceBudget({
|
|
candidate: report({ timing: 501 }), budgets, absoluteOnly: true,
|
|
});
|
|
assert.equal(rejected.pass, false);
|
|
assert.ok(rejected.failures.some((check) => check.id === 'timing.firstStableRenderMs.median'));
|
|
});
|
|
|
|
test('named interaction windows enforce their own absolute Long Task limits', () => {
|
|
const windowBudgets = {
|
|
...budgets,
|
|
longTaskWindows: {
|
|
editorSeries: { maxSingleMs: 150, maxCountP95: 3, maxTotalP95Ms: 300 },
|
|
},
|
|
};
|
|
const candidate = report();
|
|
candidate.rows[0].longTasks.editorSeries = {
|
|
supported: true, count: 3, maxMs: 149, totalMs: 299,
|
|
};
|
|
candidate.rows[1].longTasks.editorSeries = {
|
|
supported: true, count: 4, maxMs: 151, totalMs: 301,
|
|
};
|
|
const result = evaluatePerformanceBudget({ baseline: report(), candidate, budgets: windowBudgets });
|
|
assert.deepEqual(
|
|
new Set(result.failures.map((check) => check.id)),
|
|
new Set([
|
|
'longTask.editorSeries.maxSingleMs',
|
|
'longTask.editorSeries.countP95',
|
|
'longTask.editorSeries.totalP95Ms',
|
|
]),
|
|
);
|
|
});
|
|
|
|
// #473 AC4: smoke-бюджеты диффозависимых профилей повторяют абсолютные потолки
|
|
// полных профилей и пригодны для `compare --absolute-only` на трёх образцах.
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
const readBudget = (name) => JSON.parse(readFileSync(new URL(`../demo/performance/${name}`, import.meta.url), 'utf8'));
|
|
|
|
for (const [smokeName, fullName] of [
|
|
['budgets-isometric-smoke.json', 'budgets-large-house-isometric.json'],
|
|
['budgets-interaction-smoke.json', 'budgets-large-house-interaction.json'],
|
|
]) {
|
|
test(`${smokeName} повторяет hardMaxMs полного профиля и держит 3 образца (#473 AC4)`, () => {
|
|
const smoke = readBudget(smokeName);
|
|
const full = readBudget(fullName);
|
|
assert.equal(smoke.profile, full.profile);
|
|
assert.equal(smoke.minimumSamples, 3);
|
|
assert.deepEqual(Object.keys(smoke.timings), Object.keys(full.timings), 'набор метрик тот же');
|
|
for (const [metric, budget] of Object.entries(smoke.timings)) {
|
|
assert.equal(budget.hardMaxMs, full.timings[metric].hardMaxMs, `${metric}: потолок отличается от полного`);
|
|
assert.equal(budget.stat, full.timings[metric].stat);
|
|
// Регрессионных коэффициентов в смоке нет: сравнивать не с чем (§5).
|
|
assert.equal(budget.maxRegressionRatio, undefined, `${metric}: в смоке нет относительных лимитов`);
|
|
}
|
|
assert.equal(smoke.longTasks.maxSingleMs, full.longTasks.maxSingleMs);
|
|
assert.equal(smoke.longTasks.maxCountP95, full.longTasks.maxCountP95);
|
|
assert.equal(smoke.longTasks.maxTotalP95Ms, full.longTasks.maxTotalP95Ms);
|
|
assert.deepEqual(smoke.longTaskWindows, full.longTaskWindows);
|
|
assert.equal(smoke.heap.hardMaxGrowthBytes, full.heap.hardMaxGrowthBytes);
|
|
assert.deepEqual(smoke.cacheEntries, full.cacheEntries);
|
|
assert.deepEqual(smoke.renderedDevices, full.renderedDevices);
|
|
|
|
// Пригодность для --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 });
|
|
assert.deepEqual(ok.failures, [], 'отчёт под потолками обязан проходить');
|
|
const regressed = evaluatePerformanceBudget({ candidate: build(9870), budgets: smoke, absoluteOnly: true });
|
|
assert.ok(regressed.failures.some((check) => check.id === 'timing.firstStableRenderMs.median'),
|
|
'первый кадр 9 870 мс обязан краснеть');
|
|
});
|
|
}
|
|
|
|
test('interaction aggregate keeps hosted-runner headroom without weakening component gates (#483)', () => {
|
|
const smoke = readBudget('budgets-interaction-smoke.json');
|
|
const full = readBudget('budgets-large-house-interaction.json');
|
|
assert.equal(smoke.timings.interactionSeriesMs.hardMaxMs, 3300);
|
|
assert.equal(full.timings.interactionSeriesMs.hardMaxMs, 3300);
|
|
assert.equal(smoke.timings.hoverSeriesMs.hardMaxMs, 500);
|
|
assert.equal(smoke.timings.panSeriesMs.hardMaxMs, 500);
|
|
assert.equal(smoke.timings.cameraSeriesMs.hardMaxMs, 500);
|
|
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');
|
|
assert.equal(isometric.longTasks.maxCountRegressionRatio, 0.2, 'the ratio is not the lever');
|
|
assert.equal(isometric.longTasks.maxCountP95, 30);
|
|
assert.equal(isometric.longTasks.maxTotalRegressionRatio, 0.2, 'total work is still gated as before');
|
|
assert.equal(isometric.longTasks.maxSingleRegressionRatio, 0.2);
|
|
assert.equal(readBudget('budgets-isometric-stage3-dense.json').longTasks.countNoiseAllowance, 5, 'the dense twin shares the lazy iso chunk and the #160 common-ceiling contract');
|
|
for (const file of ['budgets.json', 'budgets-large-house-plan-snap.json', 'budgets-large-house-interaction.json']) {
|
|
assert.equal(readBudget(file).longTasks.countNoiseAllowance, 3, `${file} keeps the ordinary allowance`);
|
|
}
|
|
// The v1.73.0 stable comparison against v1.72.0: 16 → 20 passes, 22 does not.
|
|
const limit = Math.max(16 * (1 + isometric.longTasks.maxCountRegressionRatio), 16 + isometric.longTasks.countNoiseAllowance);
|
|
assert.equal(limit, 21);
|
|
});
|