feat: 比例适配、bgm 音源选择与 fps 限流,打包 v3
取景(issue 04) - 新增 viewport-fitter.js:让立绘层缩放跟随背景图的 cover 缩放——背景放大多少倍、 特效就放大多少倍。原先背景走 cover、立绘层走 contain,比例一变两者就分叉。 16:9 下与作者设定逐像素一致(kv37 为 0 差异)。实测证伪了"contain 会裁掉作者视口" 这个最初的判断:contain 在任何比例下都完整含住作者视口。 - 应用方式为原地改写 config.viewport 字段后调 setViewport(),不重建播放器; 维护挂在 config.frame 上按画布比例增量进行(config.success 早于 setViewport,拿不到视口)。 WE 属性(issue 05) - project.json 新增 bgm combo(所有壁纸音源的并集 + 「随预设」);所选项不属于当前壁纸时 音频回落该壁纸默认,选择本身保留。 - fps 限流包裹实例 drawFrame 并接管排程。修掉两个实测缺陷:朴素丢帧会把 rAF 抖动放大成 整拍丢失(目标 30fps 实测只有 22.5fps);fps:0 被真值判断吞掉导致无法取消限流。 打包(issue 06) - project.json version 升至 3 并更新描述;preset 的 combo 取值保持 xilian/kv37 不变。 - preview.gif 821 KB → 347 KB(每 2 帧取 1 帧 + 128 色不抖动,逐帧与源帧像素完全相同)。 - index.html 加内联 favicon,消除 CEF 自动请求 /favicon.ico 造成的 404 报错。 其它 - dist/scripts/wallpaper-engine.js 移到 tools/:它没有任何入口引用,且环境判据依赖真实 WE 并不存在的 wallpaperRegisterListener,一旦按它自己的说明挂进 index.html 会在生产环境自我激活。 - 修正死文件检查:原检查会拿文件名匹配到文件自身的内容,使这类文件漏网。 - 新增离线算式验证与 resize / 属性 / 全量验收三套浏览器测试脚本。
This commit is contained in:
1 parent
4662856963
commit
eb56efbd9e
22 files changed
+2766
-1012
No files matched your search
@@ -0,0 +1,126 @@
|
||||
// 验收测试:改窗口尺寸不得重建播放器,且取景要跟着画布比例走。
|
||||
//
|
||||
// 判据是"播放器实例身份":给加载后的实例打上标记,之后每次改视口都核对
|
||||
// window.__player 是否还是同一个对象、标记是否还在。重建会换掉实例,
|
||||
// 标记随之消失——这比数日志可靠得多。
|
||||
import { withPage, sleep } from "../tools/cdp.mjs";
|
||||
|
||||
const URL = "http://127.0.0.1:8190/?__freeze=1";
|
||||
const sizes = [
|
||||
[1920, 1080],
|
||||
[2560, 1440],
|
||||
[3440, 1440],
|
||||
[1080, 1920],
|
||||
[1600, 900],
|
||||
[2560, 1600],
|
||||
[3840, 2160],
|
||||
[1280, 720],
|
||||
[1920, 1080],
|
||||
];
|
||||
const stress = [];
|
||||
for (let i = 0; i < 20; i++) stress.push(i % 2 ? [1080, 1920] : [1920, 1080]);
|
||||
|
||||
const out = await withPage(
|
||||
{ width: 1920, height: 1080, extraArgs: ["--enable-precise-memory-info", "--js-flags=--expose-gc"] },
|
||||
async ({ send, evaluate, consoleLines, exceptions }) => {
|
||||
await send("Page.navigate", { url: URL });
|
||||
const deadline = Date.now() + 30000;
|
||||
while (Date.now() < deadline) {
|
||||
if (await evaluate('!!(window.__test && (window.__test.successAt || window.__test.error))')) break;
|
||||
await sleep(150);
|
||||
}
|
||||
await sleep(600);
|
||||
|
||||
const stamp = await evaluate(`(function(){
|
||||
window.__first = window.__player;
|
||||
if (window.__player) window.__player.__probe = 'A';
|
||||
return { hasPlayer: !!window.__player, error: window.__test ? window.__test.error : null };
|
||||
})()`);
|
||||
|
||||
const rows = [];
|
||||
const measure = async (w, h, tag) => {
|
||||
await send("Emulation.setDeviceMetricsOverride", {
|
||||
width: w,
|
||||
height: h,
|
||||
deviceScaleFactor: 1,
|
||||
mobile: false,
|
||||
});
|
||||
await sleep(420);
|
||||
// 先强制回收再读堆,否则量到的是 V8 的延迟回收节奏而不是真实占用
|
||||
await evaluate('(typeof gc === "function") ? (gc(), true) : false');
|
||||
const r = await evaluate(`(function(){
|
||||
var p = window.__player;
|
||||
if (!p) return null;
|
||||
var v = p.currentViewport;
|
||||
return {
|
||||
same: p === window.__first,
|
||||
probe: p.__probe,
|
||||
css: [p.canvas.clientWidth, p.canvas.clientHeight],
|
||||
resolved: [ +v.x.toFixed(2), +v.y.toFixed(2), +v.width.toFixed(2), +v.height.toFixed(2) ],
|
||||
pad: [v.padLeft, v.padRight, v.padTop, v.padBottom],
|
||||
childViews: window.__player.config.viewport.animations ? Object.keys(window.__player.config.viewport.animations).length : -1,
|
||||
heap: performance.memory ? performance.memory.usedJSHeapSize : 0,
|
||||
drawCalls: window.__test ? window.__test.drawCalls : 0
|
||||
};
|
||||
})()`);
|
||||
rows.push({ tag, w, h, ...(r ?? { same: false, probe: null, css: null, resolved: null, pad: null, heap: 0, drawCalls: 0 }) });
|
||||
};
|
||||
|
||||
for (const [w, h] of sizes) await measure(w, h, `${w}x${h}`);
|
||||
const heapBeforeStress = rows[rows.length - 1].heap;
|
||||
for (const [w, h] of stress) await measure(w, h, `压力 ${w}x${h}`);
|
||||
const heapAfterStress = rows[rows.length - 1].heap;
|
||||
|
||||
return { stamp, rows, heapBeforeStress, heapAfterStress, consoleLines, exceptions };
|
||||
},
|
||||
);
|
||||
|
||||
const { stamp, rows, heapBeforeStress, heapAfterStress, consoleLines, exceptions } = out;
|
||||
|
||||
let failures = 0;
|
||||
const fail = (msg) => {
|
||||
failures++;
|
||||
console.log(` ✗ ${msg}`);
|
||||
};
|
||||
|
||||
console.log(`播放器就绪: ${stamp.hasPlayer ? "是" : "否"} 驱动报错: ${stamp.error ?? "无"}`);
|
||||
console.log("");
|
||||
console.log("画布尺寸 实例未重建 内边距 可见矩形 比例核对");
|
||||
for (const r of rows) {
|
||||
if (r.tag.startsWith("压力")) continue;
|
||||
const a = r.w / r.h;
|
||||
const ra = r.resolved ? r.resolved[2] / r.resolved[3] : NaN;
|
||||
const ratioOk = Math.abs(ra - a) < 0.002;
|
||||
const padOk = r.pad && r.pad.every((p) => p === 0);
|
||||
const rect = r.resolved ? r.resolved.map((n) => String(n).padStart(9)).join(" ") : "—";
|
||||
console.log(
|
||||
`${r.tag.padEnd(14)} ${String(r.same).padEnd(10)} ${(padOk ? "全 0 ✓" : JSON.stringify(r.pad)).padEnd(10)} ${rect} ${ratioOk ? "✓" : `✗ 期望 ${a.toFixed(4)} 实得 ${ra.toFixed(4)}`}`,
|
||||
);
|
||||
if (!r.same) fail(`${r.tag} 重建了播放器(标记丢失: ${r.probe})`);
|
||||
if (!padOk) fail(`${r.tag} 内边距没有归零`);
|
||||
if (!ratioOk) fail(`${r.tag} 可见矩形比例与画布不符`);
|
||||
}
|
||||
|
||||
// 首尾同为 1920x1080:取景必须回到完全相同的值,证明重算可逆、无漂移
|
||||
const first = rows.find((r) => r.tag === "1920x1080");
|
||||
const last = [...rows].reverse().find((r) => r.tag === "1920x1080");
|
||||
const reversible = first.resolved.every((n, i) => Math.abs(n - last.resolved[i]) < 0.01);
|
||||
if (!reversible) fail(`回到 16:9 后取景漂移:${first.resolved} → ${last.resolved}`);
|
||||
console.log(`\n回到 16:9 取景可逆: ${reversible ? "✓" : "✗"} ${first.resolved} → ${last.resolved}`);
|
||||
|
||||
const growth = heapAfterStress - heapBeforeStress;
|
||||
console.log(`连续 20 次 resize 堆内存: ${(heapBeforeStress / 1048576).toFixed(1)} MB → ${(heapAfterStress / 1048576).toFixed(1)} MB(增长 ${(growth / 1048576).toFixed(2)} MB)`);
|
||||
if (growth > 4 * 1048576) fail(`连续 resize 内存增长过大:${(growth / 1048576).toFixed(2)} MB`);
|
||||
|
||||
const drawCalls = rows[rows.length - 1].drawCalls;
|
||||
console.log(`累计绘制帧数: ${drawCalls}`);
|
||||
if (!(drawCalls > 0)) fail("没有绘制任何帧");
|
||||
|
||||
const noisy = consoleLines.filter((l) => /error|warn|uncaught|fail/i.test(l));
|
||||
console.log(`控制台异常: ${noisy.length ? noisy.slice(0, 5).join(" | ") : "无 ✓"}`);
|
||||
if (noisy.length) fail("控制台有报错/警告");
|
||||
console.log(`未捕获异常: ${exceptions.length ? exceptions.slice(0, 4).join(" | ") : "无 ✓"}`);
|
||||
if (exceptions.length) fail("有未捕获异常");
|
||||
|
||||
console.log(`\n结论:${failures === 0 ? "全部通过 ✓" : `${failures} 项失败 ✗`}`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
Reference in new issue
Block a user