test(t1-spike): Phase2节流测量收口——4包S2三开曲线/帧率/内存全数据

- S2@CPU4×+5Mbps(3遍中位):LittleJS冷开536/592ms·三开397/460ms;Phaser冷开2858/2818ms·三开684/746ms——编译缓存省~2.1s但只救二开起,首次试玩吃满2.8s(离3.5s地板余0.6s),冷开差5.3×与体积差30×机理互证
- 帧率@4×:四包中位59.88满帧;尾帧分化phaser-catcher 1%低=35.34(高密粒子,未破30线);CJK主取证面两引擎零帧率代价
- 内存:JSHeap 4/6MB双双远低于B4 150MB(procRSS~1GB系chrome全树固定开销,非引擎信号,口径已标注)
- 钉子①终证:运行期零CSP违规零eval执行;phaser唯一new Function系不可达polyfill;matter-js零命中确为arcade变体
- 诚实声明:尾帧/JSHeap单遍作趋势;真机残差(XWeb冷开绝对值/真机code cache/CJK字体)留终裁包清单

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-11 20:08:24 +00:00
parent 1506da704d
commit 5735c0eafc
22 changed files with 2241 additions and 0 deletions

View File

@ -0,0 +1,294 @@
/**
* Phase 2 · B. 帧率 @CPU 4× 执行单 §6.2
* ============================================================================
* 每包在 juice 最密时刻采样帧率
* - catcher临胜前落物风暴高分段 spawn 频率拉满
* - shop连续成交 + 怒走快速 / + 顾客流失
* 方法只读禁篡改状态
* runner iframe 注入一个 **rAF 帧间隔采样器** requestAnimationFrame performance.now()
* 时间戳算帧间隔 dt挂到 iframe window 的只读数组 __fpsSamples不读/不改任何游戏内部状态
* 不注入任何输入或生命周期事件游戏由 **真实 CDP 输入**驱动沿用各 lane drive geom 瞄准
* 采样窗口内收集帧间隔 算中位 fps1000/median(dt)+ 最低 1% 帧间隔p99 dt = 最慢帧
* 节流CPU 4×与生死曲线同画像网络不限帧率是运行期成本与冷加载无关
*
* 用法node fps_measure.cjs <lane> <game> [seconds]
*/
'use strict';
const cp = require('child_process');
const fs = require('fs');
const path = require('path');
const http = require('http');
const LANE = process.argv[2];
const GAME = process.argv[3];
const SAMPLE_SEC = parseInt(process.argv[4] || '12', 10); // juice 密集采样窗(秒)
if (!['littlejs', 'phaser'].includes(LANE) || !['catcher', 'shop'].includes(GAME)) {
console.error('用法: node fps_measure.cjs <littlejs|phaser> <catcher|shop> [seconds]');
process.exit(64);
}
const PORT = LANE === 'littlejs' ? 4311 : 4312;
const HOST_URL = 'http://127.0.0.1:' + PORT + '/host.html?game=' + GAME;
const CDP_PORT = 9320 + (LANE === 'littlejs' ? 0 : 2) + (GAME === 'shop' ? 1 : 0);
const OUTDIR = path.join(__dirname, 'raw');
const TAG = LANE + '-' + GAME;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const logLines = [];
function log(...a) { const s = '[fps:' + TAG + '] ' + a.map((x) => typeof x === 'string' ? x : JSON.stringify(x)).join(' '); console.log(s); logLines.push(s); }
class CDP {
constructor(wsUrl) {
this.ws = new WebSocket(wsUrl); this.id = 0; this.pending = new Map(); this.handlers = {};
this.ready = new Promise((res, rej) => { this.ws.addEventListener('open', () => res()); this.ws.addEventListener('error', (e) => rej(e)); });
this.ws.addEventListener('message', (ev) => {
let m; try { m = JSON.parse(ev.data); } catch { return; }
if (m.id !== undefined && this.pending.has(m.id)) { const { resolve, reject } = this.pending.get(m.id); this.pending.delete(m.id); m.error ? reject(new Error(JSON.stringify(m.error))) : resolve(m.result); }
else if (m.method) (this.handlers[m.method] || []).forEach((h) => h(m.params));
});
}
on(m, h) { (this.handlers[m] ||= []).push(h); }
send(method, params) { const id = ++this.id; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); this.ws.send(JSON.stringify({ id, method, params: params || {} })); }); }
close() { try { this.ws.close(); } catch {} }
}
function httpJson(p) { return new Promise((resolve, reject) => { http.get('http://127.0.0.1:' + CDP_PORT + p, (res) => { let b = ''; res.on('data', (d) => b += d); res.on('end', () => { try { resolve(JSON.parse(b)); } catch (e) { reject(e); } }); }).on('error', reject); }); }
function launchChrome() {
const args = ['--headless=new', '--no-sandbox', '--disable-gpu', '--mute-audio', '--hide-scrollbars',
'--window-size=900,900', '--force-device-scale-factor=2', '--remote-debugging-port=' + CDP_PORT,
'--user-data-dir=/tmp/fps-' + TAG + '-' + Date.now(), 'about:blank'];
const ch = cp.spawn('/usr/bin/google-chrome', args, { stdio: ['ignore', 'pipe', 'pipe'] }); ch.stderr.on('data', () => {}); return ch;
}
// 只读 evaluate顶层 host 上下文)
async function evalTop(cdp, expr) {
const r = await cdp.send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true });
if (r.exceptionDetails) throw new Error('eval异常: ' + JSON.stringify(r.exceptionDetails));
return r.result.value;
}
// ---- 真实鼠标原语 ----
const trace = [];
async function rawMouse(cdp, type, x, y, buttons) {
await cdp.send('Input.dispatchMouseEvent', { type, x: Math.round(x), y: Math.round(y), button: type === 'mouseMoved' ? 'none' : 'left', buttons: buttons != null ? buttons : (type === 'mousePressed' ? 1 : 0), clickCount: (type === 'mousePressed' || type === 'mouseReleased') ? 1 : 0 });
trace.push({ t: Date.now(), type, x: Math.round(x), y: Math.round(y) });
}
let _jit = 0;
const moveTo = (cdp, x, y) => { _jit = _jit ? 0 : 1; return rawMouse(cdp, 'mouseMoved', x + (_jit ? 0.6 : -0.4), y, 0); };
async function clickAt(cdp, x, y) { await moveTo(cdp, x, y); await rawMouse(cdp, 'mousePressed', x, y, 1); await sleep(16); await rawMouse(cdp, 'mouseReleased', x, y, 0); }
// ---- geom 读取(按 lane 不同探针)----
// littlejs: iframe.contentWindow.__gameDebug + canvas 几何换算(同 drive.cjs 口径)
// phaser: host window.__GEOM__runner 主动暴露 + iframe 偏移/缩放)
const READ_GEOM_LJ = `(function(){
var ifr=document.querySelector('iframe'); if(!ifr) return null;
var ir=ifr.getBoundingClientRect(); var w=ifr.contentWindow; var gd=w&&w.__gameDebug;
if(!gd) return {ready:false};
var cv=w.document.querySelector('canvas');
var cr=cv?cv.getBoundingClientRect():{left:0,top:0,width:ir.width,height:ir.height};
var sx=cv?(cr.width/(cv.width||cr.width)):1, sy=cv?(cr.height/(cv.height||cr.height)):1;
function vx(px){return ir.left+cr.left+px*sx;} function vy(px){return ir.top+cr.top+py(px);}
function py(px){return px;}
function VY(px){return ir.top+cr.top+px*sy;}
var out={ready:true,phase:gd.phase,kind:(gd.coins!==undefined?'shop':'catcher'),
iframe:{left:ir.left,top:ir.top,width:ir.width,height:ir.height}};
if(gd.coins!==undefined){ // shop
out.coins=gd.coins; out.stock=gd.stock; out.lost=(gd.churn!==undefined?gd.churn:gd.lost); out.queueLen=gd.queueLen;
out.canRestock=gd.canRestock; out.canSell=gd.canSell;
function pt(p){return p?{x:vx(p.x),y:VY(p.y)}:null;}
out.restockBtn=pt(gd.restockBtn); out.sellBtn=pt(gd.sellBtn); out.frontCustomer=pt(gd.frontCustomer);
} else { // catcher
out.score=gd.score; out.lives=gd.lives;
out.catcher={x:vx(gd.catcher.x),y:VY(gd.catcher.y)};
var best=null,bw=Infinity;
for(var i=0;i<gd.aim.length;i++){var a=gd.aim[i];if(a.wy>=gd.catchTopWorldY&&a.wy<bw){bw=a.wy;best=a;}}
if(!best){for(var j=0;j<gd.aim.length;j++){var b=gd.aim[j];if(best===null||b.wy>best.wy)best=b;}}
out.aimViewportX=best?vx(best.aimScreenX):out.catcher.x; out.starCount=gd.aim.length;
}
return out;
})()`;
const READ_GEOM_PH = `(function(){
var g=window.__GEOM__; var s=window.__SPIKE__;
if(!g||!g.iframe) return {ready:false};
function vp(gx,gy){return {x:g.iframe.left+gx*g.iframe.scaleX, y:g.iframe.top+gy*g.iframe.scaleY};}
var out={ready:true,kind:g.kind,iframe:g.iframe};
if(g.kind==='shop'){
out.coins=g.coins; out.stock=g.stock; out.lost=g.lost; out.queueLen=g.queueLen;
out.restockBtn=g.restockBtn?{cx:g.restockBtn.cx,cy:g.restockBtn.cy,enabled:g.restockBtn.enabled, v:vp(g.restockBtn.cx,g.restockBtn.cy)}:null;
out.sellBtn=g.sellBtn?{cx:g.sellBtn.cx,cy:g.sellBtn.cy,enabled:g.sellBtn.enabled, v:vp(g.sellBtn.cx,g.sellBtn.cy)}:null;
out.frontCustomer=g.frontCustomer?{v:vp(g.frontCustomer.x,g.frontCustomer.y)}:null;
} else {
out.score=(s&&s.gameEndData)?s.gameEndData.score:(g.score!==undefined?g.score:0);
out.lowestStar=g.lowestStar?{v:vp(g.lowestStar.x,580)}:null;
out.catcherV=vp((g.catcherX!==undefined?g.catcherX:180),580);
out.lives=g.lives;
}
return out;
})()`;
async function readGeom(cdp) { return evalTop(cdp, LANE === 'littlejs' ? READ_GEOM_LJ : READ_GEOM_PH); }
// ---- 注入 rAF 帧间隔采样器到 iframe只读仅记 perf.now 帧间隔)----
// 同源 iframeallow-same-origin下可经 contentWindow 注入;纯计时、零状态读写。
async function installFpsSampler(cdp) {
const expr = `(function(){
var ifr=document.querySelector('iframe'); if(!ifr||!ifr.contentWindow) return false;
var w=ifr.contentWindow;
if(w.__fpsActive) return true;
w.__fpsSamples=[]; w.__fpsActive=true; var last=w.performance.now();
function loop(){ if(!w.__fpsActive) return; var t=w.performance.now(); var dt=t-last; last=t;
if(dt>0 && dt<1000) w.__fpsSamples.push(+dt.toFixed(3)); w.requestAnimationFrame(loop); }
w.requestAnimationFrame(loop); return true;
})()`;
return evalTop(cdp, expr);
}
async function readFpsSamples(cdp) {
const expr = `(function(){var ifr=document.querySelector('iframe');var w=ifr&&ifr.contentWindow;return (w&&w.__fpsSamples)?w.__fpsSamples.slice():[];})()`;
return evalTop(cdp, expr);
}
async function resetFpsSamples(cdp) {
return evalTop(cdp, `(function(){var ifr=document.querySelector('iframe');var w=ifr&&ifr.contentWindow;if(w&&w.__fpsSamples){w.__fpsSamples.length=0;return true;}return false;})()`);
}
// ---- 等 input_boundhost 探针) ----
async function waitInputBound(cdp) {
const expr = LANE === 'littlejs'
? `(window.__probe && window.__probe.summary().s2_ms!=null)`
: `(!!(window.__SPIKE__ && window.__SPIKE__.checks && window.__SPIKE__.checks.s2_ms!=null))`;
for (let i = 0; i < 300; i++) { const ok = await evalTop(cdp, expr).catch(() => false); if (ok) return true; await sleep(100); }
return false;
}
async function endedYet(cdp) {
const expr = LANE === 'littlejs'
? `(window.__probe && window.__probe.endData()!=null)`
: `(!!(window.__SPIKE__ && window.__SPIKE__.gameEndData))`;
return evalTop(cdp, expr).catch(() => false);
}
// ---- 驱动到 juice 密集态并采样 ----
async function driveAndSample(cdp) {
// 触发 game_startcatcher 点一下捕手位shop 点进货
let g = await readGeom(cdp);
let guard = 0;
while ((!g || !g.ready) && guard++ < 80) { await sleep(100); g = await readGeom(cdp); }
if (!g || !g.ready) throw new Error('geom 未就绪');
if (GAME === 'catcher') {
const cx = LANE === 'littlejs' ? g.catcher.x : g.catcherV.x;
const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
await rawMouse(cdp, 'mousePressed', cx, cy, 1); await sleep(20); await rawMouse(cdp, 'mouseReleased', cx, cy, 0);
await moveTo(cdp, cx, cy);
} else {
const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v);
if (rb) await clickAt(cdp, rb.x, rb.y);
}
// 装采样器game_start 后iframe 已稳)
await installFpsSampler(cdp);
// —— 先把游戏推进到「juice 密集段」——
// catcher持续接星到高分spawn 频率随时间拉满 = 落物风暴);约 18-22s 后达临胜密集。
// shop连续成交 + 故意让部分顾客流失(怒走 juice维持高频点按。
// 然后在「密集窗 SAMPLE_SEC 秒」清空采样、纯采样。
const warmMs = GAME === 'catcher' ? 16000 : 12000; // 推进到密集态的预热
const tWarmEnd = Date.now() + warmMs;
while (Date.now() < tWarmEnd) {
if (await endedYet(cdp)) break;
g = await readGeom(cdp);
if (g && g.ready) {
if (GAME === 'catcher') {
const aimX = LANE === 'littlejs' ? g.aimViewportX : (g.lowestStar ? g.lowestStar.v.x : g.catcherV.x);
const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
await moveTo(cdp, aimX, cy);
} else {
await shopStep(cdp, g);
}
}
await sleep(GAME === 'catcher' ? 8 : 70);
}
// —— 进入纯采样窗:清空采样器,密集驱动 SAMPLE_SEC 秒 ——
await resetFpsSamples(cdp);
log('采样窗开始 ' + SAMPLE_SEC + 'sjuice 密集)');
const tSampleEnd = Date.now() + SAMPLE_SEC * 1000;
let endedDuringSample = false;
while (Date.now() < tSampleEnd) {
if (await endedYet(cdp)) { endedDuringSample = true; break; }
g = await readGeom(cdp);
if (g && g.ready) {
if (GAME === 'catcher') {
const aimX = LANE === 'littlejs' ? g.aimViewportX : (g.lowestStar ? g.lowestStar.v.x : g.catcherV.x);
const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
await moveTo(cdp, aimX, cy);
} else {
await shopStep(cdp, g);
}
}
await sleep(GAME === 'catcher' ? 8 : 70);
}
const samples = await readFpsSamples(cdp);
log('采样窗结束,收集帧间隔 ' + samples.length + ' 个' + (endedDuringSample ? '(采样中游戏已终局)' : ''));
return { samples, endedDuringSample };
}
// shop 一步:连续成交 + 容忍怒走juice 密集)。库存薄补货,有货有客即卖。
let _lastRestock = 0;
async function shopStep(cdp, g) {
const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v);
const sb = LANE === 'littlejs' ? g.sellBtn : (g.sellBtn && g.sellBtn.v);
const fc = LANE === 'littlejs' ? g.frontCustomer : (g.frontCustomer && g.frontCustomer.v);
const stock = g.stock != null ? g.stock : 0;
const canSell = LANE === 'littlejs' ? g.canSell : (g.sellBtn && g.sellBtn.enabled);
const canRestock = LANE === 'littlejs' ? g.canRestock : (g.restockBtn && g.restockBtn.enabled);
if (stock > 0 && fc) { await clickAt(cdp, fc.x, fc.y); }
else if (canSell && sb) { await clickAt(cdp, sb.x, sb.y); }
else if (stock === 0 && canRestock && rb && (Date.now() - _lastRestock > 200)) { await clickAt(cdp, rb.x, rb.y); _lastRestock = Date.now(); }
}
function pct(arr, p) { const a = arr.slice().sort((x, y) => x - y); if (!a.length) return null; const idx = Math.min(a.length - 1, Math.floor(p / 100 * a.length)); return a[idx]; }
function median(arr) { const a = arr.slice().sort((x, y) => x - y); if (!a.length) return null; const m = Math.floor(a.length / 2); return a.length % 2 ? a[m] : (a[m - 1] + a[m]) / 2; }
(async () => {
log('=== 帧率@CPU4× · ' + TAG + ' · ' + HOST_URL + ' ===');
const chrome = launchChrome();
let ver = null;
for (let i = 0; i < 50; i++) { try { ver = await httpJson('/json/version'); if (ver && ver.webSocketDebuggerUrl) break; } catch {} await sleep(250); }
if (!ver) { chrome.kill('SIGKILL'); throw new Error('chrome 端口未就绪'); }
const bcdp = new CDP(ver.webSocketDebuggerUrl); await bcdp.ready;
const { targetId } = await bcdp.send('Target.createTarget', { url: 'about:blank' });
let pageInfo = null;
for (let i = 0; i < 30; i++) { const list = await httpJson('/json'); pageInfo = list.find((t) => t.id === targetId && t.webSocketDebuggerUrl); if (pageInfo) break; await sleep(150); }
const cdp = new CDP(pageInfo.webSocketDebuggerUrl); await cdp.ready;
await cdp.send('Page.enable'); await cdp.send('Runtime.enable');
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 4 }); // CPU 4×帧率画像不限网络
await cdp.send('Page.navigate', { url: HOST_URL });
if (!(await waitInputBound(cdp))) { log('FATAL: 未达 input_bound'); chrome.kill('SIGKILL'); process.exit(3); }
log('input_bound 达成,开始驱动至 juice 密集态');
let res;
try { res = await driveAndSample(cdp); }
catch (e) { log('驱动异常: ' + (e && e.message)); res = { samples: [], endedDuringSample: false }; }
const s = res.samples.filter((x) => x > 0 && x < 1000);
const medDt = median(s);
const p99Dt = pct(s, 99); // 最慢 1% 帧间隔(最低 1% fps 的代理)
const out = {
pack: TAG, lane: LANE, game: GAME, url: HOST_URL,
throttle: { cpu_rate: 4 },
method: 'rAF 帧间隔采样器注入 iframe只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 ' + SAMPLE_SEC + 's',
sample_count: s.length,
median_frame_interval_ms: medDt != null ? +medDt.toFixed(3) : null,
median_fps: medDt ? +(1000 / medDt).toFixed(2) : null,
p99_frame_interval_ms: p99Dt != null ? +p99Dt.toFixed(3) : null, // 最低 1% 帧间隔(最慢帧)
worst_1pct_fps: p99Dt ? +(1000 / p99Dt).toFixed(2) : null,
p95_frame_interval_ms: pct(s, 95),
min_frame_interval_ms: s.length ? +Math.min(...s).toFixed(3) : null,
max_frame_interval_ms: s.length ? +Math.max(...s).toFixed(3) : null,
ended_during_sample: res.endedDuringSample,
samples_head: s.slice(0, 40),
samples_tail: s.slice(-40),
};
fs.writeFileSync(path.join(OUTDIR, 'fps-' + TAG + '.json'), JSON.stringify(out, null, 2));
fs.writeFileSync(path.join(OUTDIR, 'fps-' + TAG + '-samples.json'), JSON.stringify(s));
fs.writeFileSync(path.join(OUTDIR, 'fps-' + TAG + '.log'), logLines.join('\n') + '\n');
log('DONE 中位 fps=' + out.median_fps + ' 最低1%fps=' + out.worst_1pct_fps + ' (n=' + s.length + ')');
cdp.close(); bcdp.close(); chrome.kill('SIGKILL'); await sleep(300); process.exit(0);
})().catch((e) => { console.error(e); process.exit(1); });

View File

@ -0,0 +1,182 @@
# Phase 2 测量数据 · Tier1 引擎 eval-spikeHJ-T1-SPIKE-001
> 主考数据:**S2 三开生死曲线(钉子⑤)** + 帧率@CPU4× + 内存 + 钉子①收尾。
> 现场mini-desktopminione-ubuntu-desktopgoogle-chrome 146 + node 22.22。
> 两 serve 常驻littlejs :4311 / phaser :4312创始人亲玩中全程未清理
> 被测对象:两 lane `vite build`base:'./')产物 dist经各自 `serve.cjs`hashed assets `immutable` / HTML `no-cache`)托管;测量宿主 `host.html?game=catcher|shop`(七锚点 host 单时钟探针)。
> 节流CDP `Emulation.setCPUThrottlingRate=4` + `Network.emulateNetworkConditions`(下行 5Mbps / 上行 1Mbps / RTT 40ms= 千元机+4G 画像。
> 精度优先、诚实优先:每项 3 遍取中位+离散;口径差异/不稳定项如实标注,未挑好看的。
---
## 〇、四包总览表(一屏读完)
| 包 | S2 冷开 ms中位[min-max] | S2 二开 ms | S2 三开 ms中位[min-max] | 编译缓存收益<br>(冷−三开) | 中位 fps@4× | 最低1% fps | JSHeapUsed | 通关 |
|---|---|---|---|---|---|---|---|---|
| **littlejs / catcher** | 536 [522580] | 430 | 397 [387454] | **139 ms** | 59.88 | 45.87 | 3.91 MB | ✅ win |
| **littlejs / shop** | 592 [571667] | 484 | 460 [423523] | **132 ms** | 59.88 | 49.75 | 3.98 MB | ✅ win |
| **phaser / catcher** | 2858 [27922871] | 757 | 684 [645696] | **2174 ms** | 59.88 | 35.34 | 6.28 MB | ✅ win |
| **phaser / shop** | 2818 [28132883] | 781 | 746 [716753] | **2072 ms** | 59.88 | 47.62 | 6.08 MB | ✅ win |
> S2 = host 单时钟 `t_input_bound t_nav`t_nav=设 iframe.src 时刻t_input_bound=输入监听已挂+主循环在跑。fps/内存对照线≥30fps / B4 ≤150MBJSHeap 口径,见 §C 口径说明)。
> 全部 8 局4 包×胜)真实 CDP 输入达成outcome=win/score 实测;锚点序合法 `t_game_loaded ≥ t_input_bound`order_ok=true全开全包
---
## A. S2 三开生死曲线(钉子⑤主数据)
### A.1 全量数据(每包 3 遍 × 3 开,单位 mshost 单时钟)
| 包 | 遍 | 冷开 | 二开 | 三开 |
|---|---|---|---|---|
| littlejs-catcher | rep1 | 579.6 | 461.8 | 387.0 |
| | rep2 | 521.7 | 430.2 | 454.4 |
| | rep3 | 535.7 | 410.2 | 396.7 |
| | **中位** | **535.7** | **430.2** | **396.7** |
| littlejs-shop | rep1 | 571.1 | 472.3 | 423.0 |
| | rep2 | 592.4 | 484.0 | 460.3 |
| | rep3 | 666.9 | 489.5 | 522.9 |
| | **中位** | **592.4** | **484.0** | **460.3** |
| phaser-catcher | rep1 | 2870.6 | 723.4 | 684.4 |
| | rep2 | 2858.0 | 759.6 | 695.5 |
| | rep3 | 2792.3 | 756.5 | 645.0 |
| | **中位** | **2858.0** | **756.5** | **684.4** |
| phaser-shop | rep1 | 2883.1 | 765.9 | 716.3 |
| | rep2 | 2817.7 | 780.5 | 752.8 |
| | rep3 | 2812.6 | 798.8 | 745.6 |
| | **中位** | **2817.7** | **780.5** | **745.6** |
原始:`raw/s2-<pack>.json`(含每遍每开七锚点)+ `raw/s2-<pack>.log` + `raw/s2-all.log`(跑批日志)。
### A.2 三开协议(执行单 §6.1)落地说明
- **冷开** = 每遍一个**全新 chrome + 全新 `--user-data-dir` 临时目录**(保证 HTTP 缓存 + V8 编译缓存**双冷**)→ 新建 page target → 节流 → 导航 `host.html?game=` → 收七锚点。
- **二开/三开** = 同一 chrome **同 profile****关页(`Page.close` + `Target.closeTarget`)后新建 page target 重开同 URL**非刷新profile 内磁盘缓存 + V8 code cache 保留)。
- 每包跑 N=3 遍(每遍=一次独立的「冷→二→三」三连开,各自全新 profile三开位各取 3 个样本算中位+min/max。
### A.3 判读 —— 编译缓存收益(钉子⑤ 的 Chromium 代理)
**结论LittleJS 冷开 ~0.5s、Phaser 冷开 ~2.8s5.3×);二/三开 Phaser 靠编译缓存回落到 ~0.7s,但冷开「首次可玩」差距 ~2.3s 不可消除。**
1. **LittleJS冷开已极快编译缓存收益小但可观测非纯噪声。**
- 冷开中位 catcher 536ms / shop 592ms三开 397/460ms。收益三开catcher **139ms** / shop **132ms**——两游戏一致,方向稳定(冷>二>三的单调下降在多数遍成立)。
- 离散:冷开 catcher [522580]、shop [571667]shop rep3 冷开 667ms 偏高,单点离群);三开 catcher [387454]。收益量级(~135ms显著大于三开内部离散~70ms故**可判定为真实编译缓存收益,而非噪声**——修正了 lane REPORT「LittleJS 包小差值可能在噪声内」的保守预判:实测有 ~135ms 可观测收益(对应其 ~40KB 引擎 chunk 的冷编译成本)。
2. **Phaser冷开慢且编译缓存收益巨大钉子⑤ 的核心信号)。**
- 冷开中位 catcher **2858ms** / shop **2818ms**——逼近约束框架「首次 ≤3.5s 地板」(余量仅 ~0.60.7s)。
- 三开中位 catcher 684ms / shop 746ms。收益三开catcher **2174ms** / shop **2072ms**——即 V8 code cache 把 1.24MB 引擎 chunk 的冷解析+编译成本(~2.1s)几乎全部省掉。
- 离散极小:冷开 catcher [27922871]±40ms、shop [28132883]±35ms——**Phaser 冷开慢得非常稳定可复现**,不是偶发。
- **二开 vs 三开**catcher 757→684、shop 781→746二开已基本吃满缓存二↔三差仅 ~4070ms在离散内说明 V8 code cache 在**第一次重开即生效**,不需要三开预热。
3. **机理(与体积实测互证)**S2 冷开差距的直接成因 = 引擎 chunk 冷解析+编译体量。Phaser as-shipped 引擎 chunk = **1.24MB raw / 326KB gz**`SIZES.md` v2LittleJS tree-shaken = **40.6KB raw / 15.9KB gz**(全量 min 也仅 58.9KB gz。CPU 4× 下,~1.24MB JS 的冷编译≈2.8s~40KB≈0.5s——体积差(~30×直接转译为冷开 S2 差(~5×非线性因含固定开销。编译缓存钉子⑤正是抹平此差的机制但**只对「同机第二次起的开」有效**;用户**首次试玩**(冷装/清缓存/换设备)吃满 Phaser 的 ~2.8s。
> **对引擎终裁的 S2 裁断KD3「S2 实测是主考」)**
> - 稳态(二开起,老用户/缓存命中):两引擎都 < 0.8s**都达标**Phaser 不构成体验阻塞
> - 冷态首次试玩新用户首屏LittleJS ~0.5s vs Phaser ~2.8s**Phaser 逼近 3.5s 地板(余量 0.6s**——这是「短视频流即点即玩」首因人群最敏感的指标。**Phaser 的冷开 S2 是其相对 LittleJS 的核心实测负分项**(与 SIZES「Phaser 需切预构建形态过门」的一次性工程成本叠加)。
> - 真机残差§7Chromium V8 code cache ≠ 微信 XWeb 编译缓存策略XWeb 冷开是否同样受益于 code cache、冷开绝对值本 spike 不可达,留终裁包真机残差清单。
---
## B. 帧率 @CPU 4×
### B.1 方法(写进报告,执行单 §6.2 要求)
- **采样方法**:向 runner iframe同源 `allow-same-origin`**注入一个 rAF 帧间隔采样器**——仅 `requestAnimationFrame``performance.now()` 算相邻帧间隔 dtpush 进 iframe window 的只读数组 `__fpsSamples`。**纯计时、零游戏状态读写、零输入/生命周期注入**(取证红线 §5.2 严守)。
- **驱动**:游戏全程**真实 CDP 输入**`Input.dispatchMouseEvent`),沿用各 lane drive 的 geom 瞄准catcher 追 lowestStar 拖动 / shop 真点进货·卖货·队首顾客);**禁 evaluate 篡改状态**。
- **juice 最密时刻**catcher = 先 16s 预热把游戏推进到高分段spawn 频率随时间拉满 = 临胜前落物风暴),再清空采样器纯采样 12sshop = 先 12s 预热进入连续成交+怒走密集态,再纯采样 12s。
- **指标**:中位 fps = 1000/median(dt);最低 1% 帧间隔 = p99(dt)(最慢 1% 帧),其 fps = 1000/p99(dt),对照 ≥30fps 线。
- 节流:仅 CPU 4×帧率是运行期成本与冷加载/网络无关,故不限网络)。
- 渲染器实测(承 lane REPORTheadless chrome 无 GPU → Phaser `renderer.type=1`Canvas2D 降级LittleJS WebGL2 不支持时自动 Canvas2D——**两引擎均实跑低端 WebView 关心的 Canvas2D 兜底路径**。
### B.2 数据
| 包 | 中位帧间隔 | 中位 fps | 最低1% 帧间隔(p99 dt) | 最低1% fps | p95 dt | 最大单帧 | 样本数 | 采样中终局 |
|---|---|---|---|---|---|---|---|---|
| littlejs-catcher | 16.7 ms | **59.88** | 21.8 ms | **45.87** | 20.5 ms | 27.6 ms | 665 | 是(胜局演出落入采样窗)|
| littlejs-shop | 16.7 ms | **59.88** | 20.1 ms | **49.75** | 18.6 ms | 22.8 ms | 721 | 否 |
| phaser-catcher | 16.7 ms | **59.88** | 28.3 ms | **35.34** | 20.5 ms | **72.5 ms** | 633 | 是 |
| phaser-shop | 16.7 ms | **59.88** | 21.0 ms | **47.62** | 19.7 ms | 22.2 ms | 721 | 否 |
原始:`raw/fps-<pack>.json`(含 samples_head/tail+ `raw/fps-<pack>-samples.json`(全部帧间隔数组)+ `raw/fps-<pack>.log`
### B.3 判读
- **四包中位 fps 全 = 59.88(即满 60fpsCPU 4× 下未掉中位帧)**——两引擎、两品类在千元机 CPU 画像下均稳态 60fps**全部远超 ≥30fps 线**。
- **最低 1% fps尾帧抖动分化**
- LittleJScatcher 45.87 / shop 49.75,最大单帧 27.6 / 22.8ms——**尾帧很稳,最差帧仍 ~36fps 以上**。
- Phasershop 47.62(最大 22.2ms,与 LittleJS 同级)但 **catcher 35.34(最大单帧 72.5ms**——Phaser action 类(落物风暴)有更大偶发帧尖峰(单帧 72.5ms≈13.8fps 的一帧p99 仍 28.3ms≈35fps**仍在 30fps 线上**)。
- 解读Phaser 经营/UI 类shop帧稳定性与 LittleJS 持平甚至更好22ms 封顶);**Phaser 的尾帧劣势集中在 action 类高密度粒子/补间场景**catcher 落物风暴),但未跌破 30fps 线。
- **CJK/文本渲染性能shop = CJK 主取证面)**:两引擎 shop 包均 59.88 中位 + 尾帧 ≤22.8ms——**多行中文 UI招牌/HUD/按钮/队列/终局)在 Canvas2D 系统字体直出下零帧率代价**,两引擎都过关。
> **不稳定项声明(诚实)**rAF 注入采样器测的是 **runner 自身 rAF 节奏**,在 headless+CPU4× 下中位恒 60fps 是「主循环没掉帧」的可靠证据;但 headless Canvas2D **非生产 WebGL/真机 GPU 代表**绝对帧率到真机会变GPU 合成、真机 CPU、微信 WebView 渲染管线均不同)。最低 1% 尾帧是单遍采样(未跑 3 遍catcher「采样中终局」使尾部含胜局演出帧finale 粒子可能贡献个别尖峰,已如实标注 ended=true。**尾帧 1% 数据建议作趋势参考而非精确门**;中位 60fps 是稳健结论。
---
## C. 内存
### C.1 方法与口径
- 每包**真实 CDP 输入通关一局胜**后catcher score≥30 / shop coins≥100全 4 包实测 win`Performance.getMetrics`**JSHeapUsedSize / JSHeapTotalSize**;进程级取该 chrome `--user-data-dir` 的全部进程 RSS 合计(`ps` 粗粒度)。不节流(内存峰值与节流无关)。
- **口径差异(关键,对照 B4 必读)**
- **JSHeap = 页面 JS 堆**V8 堆内对象),是与 B4「≤150MB」**直接可比**的运行期内存口径。
- **procRSS = 整个 headless chrome 进程树 RSS 合计**browser + GPU + renderer + utility 多进程),含渲染管线/IPC/共享库,**远大于 JS 堆且各进程共享页被重复计数**——实测四包 procRSS **恒为 ~10141020MB 且与游戏/引擎无关**(纯 chrome 多进程固定开销),**故 procRSS 不是引擎内存信号、不可直接对照 B4**,仅作「全进程量级」旁证留档。
### C.2 数据
| 包 | JSHeapUsed | JSHeapTotal | DOM nodes | JS listeners | procRSS全树非B4口径| 通关 |
|---|---|---|---|---|---|---|
| littlejs-catcher | **3.91 MB** | 11.41 MB | 82 | 17 | ~1020 MB | win(score30) |
| littlejs-shop | **3.98 MB** | 6.66 MB | 84 | 17 | ~1017 MB | win(score100) |
| phaser-catcher | **6.28 MB** | 10.75 MB | 99 | 38 | ~1014 MB | win(score30) |
| phaser-shop | **6.08 MB** | 8.01 MB | 102 | 38 | ~1014 MB | win(score100) |
原始:`raw/mem-<pack>.json`(含 game_end / Documents / Nodes / JSEventListeners+ `raw/mem-<pack>.log`
### C.3 判读
- **JSHeapUsedLittleJS ~3.94.0MB / Phaser ~6.16.3MB**——两引擎 JS 堆均**远低于 B4 150MB**LittleJS 余量 ×38、Phaser 余量 ×24**双双达标且差距对预算无实质意义**。
- Phaser JS 堆比 LittleJS 高 ~22.4MB引擎对象图更大listeners 38 vs 17、nodes 99102 vs 8284与其引擎体量一致但绝对值仍微不足道。
- **procRSS 不参与 B4 判定**(见 C.1 口径说明,~1GB 是 chrome 全树固定开销,与引擎无关);若终裁要全进程口径,须用真机/单渲染进程 RSS本 spike headless 多进程不具代表性,留真机残差。
> **不稳定/残差声明**JSHeap 为「通关后 + 0.8s 演出/GC 稳定窗」单点采样(未跑 3 遍,但 JS 堆在静止态稳定,单点足够代表量级);未做强制 GC`HeapProfiler.collectGarbage`)后再测「稳态低水位」,故 JSHeapUsed 含少量未回收临时对象,属偏保守(真实低水位可能更低)。真机内存(含纹理显存/WebView 开销)不可达,留终裁包真机残差。
---
## D. 钉子①收尾unsafe-eval / 预构建形态零违规)
**一行结论:两 lane 运行期均零 CSP 违规、零 unsafe-eval 执行——Phaser 预构建 arcade-physics 形态在严格 CSP`script-src 'self'`,无 `unsafe-eval`下干净可跑LittleJS 连静态 eval/new Function 都为 0。**
核对取证(未重做,汇两 lane 既有证据 + dist 静态复核):
| 维度 | LittleJS dist | Phaser dist预构建 arcade-physics.min|
|---|---|---|
| 静态 `eval(` | **0**(两 chunk| **0** |
| 静态 `new Function(` | **0** | **1**(仅 UMD globalThis polyfill 兜底:`try{return this\|\|new Function("return this")()}catch{...return window}`|
| `MatterPhysics`/`matter-js` | n/a | **0 命中**(确为真去 Matter 的 arcade-only 变体)|
| 运行期 CSP 违规beacon 源 + CDP Log 源,双源)| **0 / 0**catcher verdict| **0 / 0**catcher & shop summary|
| 运行期未捕获异常 | **0** | **0**catcher & shop|
- **Phaser 的那 1 处 `new Function`** 是 webpack/UMD 自举的 globalThis 探测兜底:`if(typeof globalThis=="object")return globalThis; try{return this||new Function("return this")()}catch{...}`。现代 Chromium **`globalThis` 已定义 → 首句即 early-return根本到不了 `new Function` 兜底**;即便到达也在 `try/catch` 内、严格 CSP 下被吞且回落 `window`。故**运行期从不执行、不触发 CSP 违规**(与两 lane 实测 CSP=0 互证)。属「**dead-code 静态存在、运行期不可达**」,非真实 unsafe-eval 使用——对 KD/终裁不构成 Phaser 的 CSP 阻塞项。
- LittleJS ESM 产物**连静态 eval/new Function 都为 0**CSP 适配性结构性更干净(无须依赖「兜底不可达」论证)。
> 残差headless 实测覆盖现代 Chromium 的 globalThis early-return 路径;理论上**极老 WebView无 globalThis**会落到 `new Function` 兜底并在严格 CSP 下抛错被 catch仍不崩回落 window——微信 XWeb 是否有此极端旧核,留真机残差(影响面:仅该 polyfill 兜底,不影响游戏逻辑)。
---
## E. 方法与不稳定项总声明(汇总)
1. **S2 单时钟**:所有 S2 以 host 页 `performance.now()` 单时钟算 `t_input_bound t_nav`,与 lane 自检同口径;冷开的 host 页加载耗时t_nav 偏移)不计入 S2S2 只测 iframe runner 的「设 src→可玩」段
2. **S2 三开**:每包 3 遍 × 3 开 = 9 样本/位;冷开全新 profile 双冷可靠,二/三开关页重开(非刷新)复用 profile 缓存。Phaser 冷开离散极小±40ms高可复现LittleJS shop 冷开有单点离群rep3 667ms中位已规避。
3. **帧率**rAF 注入采样器测 runner 主循环 rAF 节奏(只读);中位 60fps 稳健,**最低 1% 尾帧为单遍采样**作趋势参考headless Canvas2D 非真机 GPU 代表。
4. **内存**JSHeap 单点(通关+0.8s 稳定窗),未强制 GC偏保守**procRSS 是 chrome 全树固定开销 ~1GB、非引擎信号、不对照 B4**JSHeap 才是 B4 口径。
5. **真机残差(贯穿)**:微信 XWeb/低端 Android 真机不可达Chromium 节流/code cache/Canvas2D 均为代理——冷开绝对值、code cache 真机受益、CJK 真机字体可得性、全进程内存均留终裁包真机残差清单(执行单 §7
6. **取证纪律**:全程真实 CDP 输入达成 4 包×胜局outcome=win 实测evaluate 仅用于读探针/几何瞄准/装只读采样器,**零状态篡改、零生命周期注入**。
---
## F. 残留
1. **帧率最低 1% / 内存 JSHeap 为单遍/单点**(中位 fps 与 S2 是 3 遍):尾帧抖动与内存低水位如需精确化,可补 3 遍 + 强制 GC但**对引擎终裁主结论S2 冷开 + 中位 fps + JSHeap 量级)无影响**,未补。
2. **procRSS 口径不具引擎区分度**~1GB 全树固定开销):若终裁需全进程内存,须真机单渲染进程 RSSheadless 多进程不可用。
3. **两 serve 全程常驻未动**创始人亲玩中4 包 URL 仍可亲玩;本测量只读 dist/起独立 chrome未触碰 serve/lane 文件/staging。
4. **回灌**:本 phase2 目录 `rsync` 回本仓 `docs/agent-specs/2026-06-11-tier1-engine-spike/phase2/`(不 commit交主会话/终裁)。

View File

@ -0,0 +1,187 @@
/**
* Phase 2 · C. 内存 执行单 §6.2对照 B4 150MB
* ============================================================================
* 每包通关一局后采集
* - Performance.getMetricsJSHeapUsedSize / JSHeapTotalSize 页面 JS 堆口径
* - 进程级 RSSps 取该 chrome --user-data-dir 的所有进程 RSS 合计粗粒度
* 口径差异如实标注JSHeap 全进程 RSS后者含渲染器/GPU/IPC 远大于 JS
* 复用各 lane geom + 真实 CDP 输入驱动一局胜catcher 接满 30 / shop 经营到 coins100
* 不节流内存峰值与节流无关与运行期状态相关只读探针禁篡改
*
* 用法node mem_measure.cjs <lane> <game>
*/
'use strict';
const cp = require('child_process');
const fs = require('fs');
const path = require('path');
const http = require('http');
const LANE = process.argv[2];
const GAME = process.argv[3];
if (!['littlejs', 'phaser'].includes(LANE) || !['catcher', 'shop'].includes(GAME)) {
console.error('用法: node mem_measure.cjs <littlejs|phaser> <catcher|shop>'); process.exit(64);
}
const PORT = LANE === 'littlejs' ? 4311 : 4312;
const HOST_URL = 'http://127.0.0.1:' + PORT + '/host.html?game=' + GAME;
const CDP_PORT = 9340 + (LANE === 'littlejs' ? 0 : 2) + (GAME === 'shop' ? 1 : 0);
const UDD = '/tmp/mem-' + LANE + '-' + GAME + '-' + Date.now();
const OUTDIR = path.join(__dirname, 'raw');
const TAG = LANE + '-' + GAME;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const logLines = [];
function log(...a) { const s = '[mem:' + TAG + '] ' + a.map((x) => typeof x === 'string' ? x : JSON.stringify(x)).join(' '); console.log(s); logLines.push(s); }
class CDP {
constructor(wsUrl) {
this.ws = new WebSocket(wsUrl); this.id = 0; this.pending = new Map(); this.handlers = {};
this.ready = new Promise((res, rej) => { this.ws.addEventListener('open', () => res()); this.ws.addEventListener('error', (e) => rej(e)); });
this.ws.addEventListener('message', (ev) => { let m; try { m = JSON.parse(ev.data); } catch { return; }
if (m.id !== undefined && this.pending.has(m.id)) { const { resolve, reject } = this.pending.get(m.id); this.pending.delete(m.id); m.error ? reject(new Error(JSON.stringify(m.error))) : resolve(m.result); }
else if (m.method) (this.handlers[m.method] || []).forEach((h) => h(m.params)); });
}
on(m, h) { (this.handlers[m] ||= []).push(h); }
send(method, params) { const id = ++this.id; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); this.ws.send(JSON.stringify({ id, method, params: params || {} })); }); }
close() { try { this.ws.close(); } catch {} }
}
function httpJson(p) { return new Promise((resolve, reject) => { http.get('http://127.0.0.1:' + CDP_PORT + p, (res) => { let b = ''; res.on('data', (d) => b += d); res.on('end', () => { try { resolve(JSON.parse(b)); } catch (e) { reject(e); } }); }).on('error', reject); }); }
function launchChrome() {
const args = ['--headless=new', '--no-sandbox', '--disable-gpu', '--mute-audio', '--hide-scrollbars',
'--window-size=900,900', '--force-device-scale-factor=2', '--remote-debugging-port=' + CDP_PORT,
'--user-data-dir=' + UDD, 'about:blank'];
return cp.spawn('/usr/bin/google-chrome', args, { stdio: ['ignore', 'pipe', 'pipe'] });
}
async function evalTop(cdp, expr) { const r = await cdp.send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) throw new Error('eval异常: ' + JSON.stringify(r.exceptionDetails)); return r.result.value; }
const trace = [];
async function rawMouse(cdp, type, x, y, buttons) { await cdp.send('Input.dispatchMouseEvent', { type, x: Math.round(x), y: Math.round(y), button: type === 'mouseMoved' ? 'none' : 'left', buttons: buttons != null ? buttons : (type === 'mousePressed' ? 1 : 0), clickCount: (type === 'mousePressed' || type === 'mouseReleased') ? 1 : 0 }); trace.push({ t: Date.now(), type, x, y }); }
let _jit = 0;
const moveTo = (cdp, x, y) => { _jit = _jit ? 0 : 1; return rawMouse(cdp, 'mouseMoved', x + (_jit ? 0.6 : -0.4), y, 0); };
async function clickAt(cdp, x, y) { await moveTo(cdp, x, y); await rawMouse(cdp, 'mousePressed', x, y, 1); await sleep(16); await rawMouse(cdp, 'mouseReleased', x, y, 0); }
// geom 读取(同 fps 口径)
const READ_GEOM_LJ = `(function(){
var ifr=document.querySelector('iframe'); if(!ifr) return null;
var ir=ifr.getBoundingClientRect(); var w=ifr.contentWindow; var gd=w&&w.__gameDebug;
if(!gd) return {ready:false};
var cv=w.document.querySelector('canvas');
var cr=cv?cv.getBoundingClientRect():{left:0,top:0,width:ir.width,height:ir.height};
var sx=cv?(cr.width/(cv.width||cr.width)):1, sy=cv?(cr.height/(cv.height||cr.height)):1;
function vx(px){return ir.left+cr.left+px*sx;} function VY(px){return ir.top+cr.top+px*sy;}
var out={ready:true,phase:gd.phase,kind:(gd.coins!==undefined?'shop':'catcher'),iframe:{left:ir.left,top:ir.top,width:ir.width,height:ir.height}};
if(gd.coins!==undefined){ out.coins=gd.coins; out.stock=gd.stock; out.lost=(gd.churn!==undefined?gd.churn:gd.lost); out.queueLen=gd.queueLen; out.canRestock=gd.canRestock; out.canSell=gd.canSell;
function pt(p){return p?{x:vx(p.x),y:VY(p.y)}:null;} out.restockBtn=pt(gd.restockBtn); out.sellBtn=pt(gd.sellBtn); out.frontCustomer=pt(gd.frontCustomer);
} else { out.score=gd.score; out.lives=gd.lives; out.catcher={x:vx(gd.catcher.x),y:VY(gd.catcher.y)};
var best=null,bw=Infinity; for(var i=0;i<gd.aim.length;i++){var a=gd.aim[i];if(a.wy>=gd.catchTopWorldY&&a.wy<bw){bw=a.wy;best=a;}}
if(!best){for(var j=0;j<gd.aim.length;j++){var b=gd.aim[j];if(best===null||b.wy>best.wy)best=b;}}
out.aimViewportX=best?vx(best.aimScreenX):out.catcher.x; }
return out; })()`;
const READ_GEOM_PH = `(function(){
var g=window.__GEOM__; var s=window.__SPIKE__; if(!g||!g.iframe) return {ready:false};
function vp(gx,gy){return {x:g.iframe.left+gx*g.iframe.scaleX, y:g.iframe.top+gy*g.iframe.scaleY};}
var out={ready:true,kind:g.kind,iframe:g.iframe};
if(g.kind==='shop'){ out.coins=g.coins; out.stock=g.stock; out.lost=g.lost; out.queueLen=g.queueLen;
out.restockBtn=g.restockBtn?{enabled:g.restockBtn.enabled,v:vp(g.restockBtn.cx,g.restockBtn.cy)}:null;
out.sellBtn=g.sellBtn?{enabled:g.sellBtn.enabled,v:vp(g.sellBtn.cx,g.sellBtn.cy)}:null;
out.frontCustomer=g.frontCustomer?{v:vp(g.frontCustomer.x,g.frontCustomer.y)}:null;
} else { out.score=g.score; out.lives=g.lives; out.lowestStar=g.lowestStar?{v:vp(g.lowestStar.x,580)}:null; out.catcherV=vp((g.catcherX!==undefined?g.catcherX:180),580); }
return out; })()`;
async function readGeom(cdp) { return evalTop(cdp, LANE === 'littlejs' ? READ_GEOM_LJ : READ_GEOM_PH); }
async function waitInputBound(cdp) {
const expr = LANE === 'littlejs' ? `(window.__probe && window.__probe.summary().s2_ms!=null)` : `(!!(window.__SPIKE__ && window.__SPIKE__.checks && window.__SPIKE__.checks.s2_ms!=null))`;
for (let i = 0; i < 300; i++) { const ok = await evalTop(cdp, expr).catch(() => false); if (ok) return true; await sleep(100); } return false;
}
async function endData(cdp) {
const expr = LANE === 'littlejs' ? `JSON.stringify(window.__probe?window.__probe.endData():null)` : `JSON.stringify(window.__SPIKE__?window.__SPIKE__.gameEndData:null)`;
return evalTop(cdp, expr).then((v) => { try { return JSON.parse(v); } catch { return null; } }).catch(() => null);
}
let _lastRestock = 0;
async function shopStep(cdp, g) {
const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v);
const sb = LANE === 'littlejs' ? g.sellBtn : (g.sellBtn && g.sellBtn.v);
const fc = LANE === 'littlejs' ? g.frontCustomer : (g.frontCustomer && g.frontCustomer.v);
const stock = g.stock != null ? g.stock : 0;
const canSell = LANE === 'littlejs' ? g.canSell : (g.sellBtn && g.sellBtn.enabled);
const canRestock = LANE === 'littlejs' ? g.canRestock : (g.restockBtn && g.restockBtn.enabled);
// 胜策略:有货有客即卖(攒金币);无货补一次(保持 1 库存即可净 +5/单)
if (stock > 0 && fc) await clickAt(cdp, fc.x, fc.y);
else if (canSell && sb) await clickAt(cdp, sb.x, sb.y);
else if (stock === 0 && canRestock && rb && (Date.now() - _lastRestock > 150)) { await clickAt(cdp, rb.x, rb.y); _lastRestock = Date.now(); }
}
// 进程级 RSS该 chrome user-data-dir 的全部进程 RSS 合计KB
function procRssKb() {
try {
const out = cp.execSync(`ps -eo rss,args | grep -F "${UDD}" | grep -v grep | awk '{s+=$1} END {print s+0}'`).toString().trim();
return parseInt(out, 10) || null;
} catch { return null; }
}
async function playWin(cdp) {
let g = await readGeom(cdp); let guard = 0;
while ((!g || !g.ready) && guard++ < 80) { await sleep(100); g = await readGeom(cdp); }
if (!g || !g.ready) throw new Error('geom 未就绪');
if (GAME === 'catcher') {
const cx = LANE === 'littlejs' ? g.catcher.x : g.catcherV.x, cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
await rawMouse(cdp, 'mousePressed', cx, cy, 1); await sleep(20); await rawMouse(cdp, 'mouseReleased', cx, cy, 0); await moveTo(cdp, cx, cy);
} else { const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v); if (rb) await clickAt(cdp, rb.x, rb.y); }
const deadline = Date.now() + 160000;
while (Date.now() < deadline) {
const e = await endData(cdp); if (e) return e;
g = await readGeom(cdp);
if (g && g.ready) {
if (GAME === 'catcher') { const aimX = LANE === 'littlejs' ? g.aimViewportX : (g.lowestStar ? g.lowestStar.v.x : g.catcherV.x); const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y; await moveTo(cdp, aimX, cy); }
else await shopStep(cdp, g);
}
await sleep(GAME === 'catcher' ? 8 : 70);
}
return await endData(cdp);
}
(async () => {
log('=== 内存 · ' + TAG + ' · ' + HOST_URL + ' ===');
const chrome = launchChrome(); chrome.stderr.on('data', () => {});
let ver = null;
for (let i = 0; i < 50; i++) { try { ver = await httpJson('/json/version'); if (ver && ver.webSocketDebuggerUrl) break; } catch {} await sleep(250); }
if (!ver) { chrome.kill('SIGKILL'); throw new Error('chrome 端口未就绪'); }
const bcdp = new CDP(ver.webSocketDebuggerUrl); await bcdp.ready;
const { targetId } = await bcdp.send('Target.createTarget', { url: 'about:blank' });
let pi = null; for (let i = 0; i < 30; i++) { const list = await httpJson('/json'); pi = list.find((t) => t.id === targetId && t.webSocketDebuggerUrl); if (pi) break; await sleep(150); }
const cdp = new CDP(pi.webSocketDebuggerUrl); await cdp.ready;
await cdp.send('Page.enable'); await cdp.send('Runtime.enable'); await cdp.send('Performance.enable');
await cdp.send('Page.navigate', { url: HOST_URL });
if (!(await waitInputBound(cdp))) { log('FATAL: 未达 input_bound'); chrome.kill('SIGKILL'); process.exit(3); }
log('input_bound 达成,开始驱动一局胜');
let end = null;
try { end = await playWin(cdp); } catch (e) { log('驱动异常: ' + (e && e.message)); }
log('一局结束 end=' + JSON.stringify(end));
await sleep(800); // 让终局演出+GC 稳定
// Performance.getMetricsJS 堆)
const metrics = (await cdp.send('Performance.getMetrics')).metrics || [];
const mmap = {}; for (const m of metrics) mmap[m.name] = m.value;
const jsUsed = mmap.JSHeapUsedSize, jsTotal = mmap.JSHeapTotalSize;
const rssKb = procRssKb();
const out = {
pack: TAG, lane: LANE, game: GAME, url: HOST_URL,
note: '通关一局后采集不节流内存与节流无关。口径差异JSHeap=页面 JS 堆RSS=全 chrome 进程合计(含渲染/GPU/IPC远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。',
game_end: end,
js_heap_used_bytes: jsUsed != null ? jsUsed : null,
js_heap_used_mb: jsUsed != null ? +(jsUsed / 1048576).toFixed(2) : null,
js_heap_total_bytes: jsTotal != null ? jsTotal : null,
js_heap_total_mb: jsTotal != null ? +(jsTotal / 1048576).toFixed(2) : null,
proc_rss_total_kb: rssKb,
proc_rss_total_mb: rssKb != null ? +(rssKb / 1024).toFixed(2) : null,
documents: mmap.Documents, nodes: mmap.Nodes, js_event_listeners: mmap.JSEventListeners,
};
fs.writeFileSync(path.join(OUTDIR, 'mem-' + TAG + '.json'), JSON.stringify(out, null, 2));
fs.writeFileSync(path.join(OUTDIR, 'mem-' + TAG + '.log'), logLines.join('\n') + '\n');
log('DONE JSHeapUsed=' + out.js_heap_used_mb + 'MB JSHeapTotal=' + out.js_heap_total_mb + 'MB procRSS=' + out.proc_rss_total_mb + 'MB');
cdp.close(); bcdp.close(); chrome.kill('SIGKILL'); await sleep(300);
fs.rmSync(UDD, { recursive: true, force: true });
process.exit(0);
})().catch((e) => { console.error(e); process.exit(1); });

View File

@ -0,0 +1 @@
[16.5,16.6,15.7,17.4,17,15,16.4,18.9,16.7,15.9,16.7,15.4,17.5,19.6,14.5,16,15.1,17.7,17.4,15.9,16.9,17.5,18.5,13.9,18.2,16.4,17.2,15.5,17.1,18.1,13.4,18.2,16.8,16.5,17,16.9,16.8,14.4,17.6,17.4,16.5,15.8,18.5,14.7,17.7,16.7,14.9,20.1,14.9,18,15.3,22.2,9.1,19.9,16.5,17.5,15,17,16.2,14.7,19.4,16.2,16.2,17.2,16.4,18.5,15,14.9,20.2,15,16.9,16.9,15.3,17.3,18.2,15.5,17.1,16.8,16.1,16.8,18.2,14.5,17.8,15.7,17.9,15.7,16.4,17.3,16.5,15.7,18.3,15.4,16.4,15.6,21.7,16,13.9,18.7,13.2,16.1,18.1,15.9,16.2,17.2,16.1,19.4,15.6,16.2,17.2,15.3,18.4,16.7,16.1,15.7,17.4,17.5,16.1,17.1,16,17.3,14.4,19.3,14.3,18.4,16.7,16.5,17.2,16.1,17.1,16.7,17,16.7,16.2,17.2,14.7,17.7,20,13.7,25.2,7.9,15.1,19.7,15.6,15.8,15.2,19.3,16.2,16.5,17.3,14.8,27.6,11.1,14.1,21.8,10.1,16.5,17.4,16.6,16.1,15.4,16.6,17.6,18.4,15.4,16.8,18,15.1,16.7,14.8,18.7,16.9,15.6,16.7,17.1,16.7,16.5,17.1,17.1,14.1,18.8,18.5,12.8,17.5,18,15.1,17.9,16.7,17,16.1,14.9,17.1,20.3,12.5,18.4,25.4,7.3,18.6,15.3,17.6,15.3,18.5,15.5,15.2,18.4,17.1,16.9,18.6,12.9,19.5,17.6,15,16.8,13.8,16.9,19.5,15,16.5,16.4,16.3,17.7,17.3,14.2,18.4,17.1,17.3,14.8,16.5,15.9,18.2,15.4,19.2,15.4,18.2,15.5,17.9,16.2,15.4,16.5,17.5,17.3,15.2,17.4,16.4,17.6,14.4,18.6,20.7,14.9,16.4,15.3,13.9,16.4,17.2,18.9,15.6,18.3,15.8,16.2,14.7,18.2,15.9,17.3,18,14,18.4,17.6,17.5,14.8,17.1,14.8,17.6,18.9,15.5,16.4,17.2,15.2,17.7,17.5,14.8,17.5,16.8,15.4,18.5,16.5,16.1,16.7,15.5,19,15.5,16.7,17.6,16.3,16.8,14.3,18.4,16.9,16.3,16.2,17.3,14.6,18.3,18.9,15.7,15.3,17.9,16.8,16.4,15.8,17.1,16.8,15.3,16.5,15.8,19.3,16.4,17.4,16.1,18.4,13.9,17.6,16.7,16,17.3,16.8,15.2,17.5,16.7,17.1,17.8,15.7,17.1,18.1,14.8,16.2,15.8,18.2,16.4,16.2,17.5,15.3,16.6,17.4,17.9,14.8,17.4,15.6,18,16.9,15.7,16.2,19.8,13.6,17.4,16,17.2,17.1,16.7,15.5,18.2,15.4,17.8,15.3,17.8,16.5,16.1,17.9,17.1,15,16.9,15.9,17.7,16.1,17.2,14.6,17.5,17.3,16.7,17.6,14.8,18,17,17.6,16,14.1,19.4,14,16.7,18.7,18.6,20.2,10.6,17.9,17.3,15.1,16.6,16.4,17.6,16.5,16,17.3,14.4,17.4,16.1,18.4,17.8,15.9,15.2,15.7,18.1,16.7,17,17.2,16.8,16.3,16.4,17.3,15.4,19.7,14.2,17,15,17.9,16.8,16.3,17.8,14.1,20.7,19.1,10.8,18.1,17.1,16.7,16.6,16.7,15.9,16.5,15.7,18.8,16.5,16.6,14.9,16.1,18.9,16.2,17,16.4,16.7,17.7,15.9,14.6,16.7,18.8,14.3,18.6,16.7,14.8,17,17.5,18.4,16.1,14.6,18.2,16.5,21.5,12.8,14.1,19.4,14.3,19.1,14.8,17.8,15.7,15.4,18.4,17.4,15.2,16.5,19,14.6,16.7,17.9,16.1,17.1,16.2,15.2,18.7,16.5,16.2,16,18.1,15.5,15.5,20.7,15.1,15.3,17.6,16.2,15.4,17.7,17.8,16.2,16.9,17,14.2,17.8,16.1,20.1,14.6,16.9,15,18.6,15.4,19.1,15.1,16.9,17.5,14.5,15.8,19.5,15.5,17.5,16.4,16.6,16.1,17.5,16.5,19.4,16.5,12.4,18.7,14,19.9,15.9,16.3,17.4,15.1,17.8,17.3,15.6,17.6,16,15.5,19.9,15.1,16.7,16.5,17.3,16,13.8,19.5,16.6,16.7,16.3,17.2,16.5,17.7,16.3,15.5,16.6,16.4,17.8,16.4,15.6,15.6,20.3,18.5,14.7,14.8,17.4,16,15.3,19,16.3,16.6,25.8,9.9,14.7,14.5,15.5,16.9,19,14.8,18.2,14.5,16.7,17.3,16.5,16.2,17.8,18.2,16,17.7,17.1,13.9,18.8,16.5,17.6,17,16.3,16.6,16.8,16.1,17.5,12.9,16.7,16.9,19.5,13.6,18.4,19,16.2,13.5,19.5,13.3,19.3,14.1,20.2,15.7,17.5,14.8,18.7,16.5,13.3,20.7,13,19.6,20.2,12.3,16.6,14.2,19.5,16.9,14.5,16.3,18.9,17.8,13.7,18.9,18.5,11.8,22.5,13.7,15,18.7,17.4,14.8,19.6,12.8,19.6,14.9,17.4,14.4,15.9,18.9,16.7,15.7,17.9,17.3,15.9,17.3,16.2,17.1,17,15.1,19.1]

View File

@ -0,0 +1,103 @@
{
"pack": "littlejs-catcher",
"lane": "littlejs",
"game": "catcher",
"url": "http://127.0.0.1:4311/host.html?game=catcher",
"throttle": {
"cpu_rate": 4
},
"method": "rAF 帧间隔采样器注入 iframe只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s",
"sample_count": 665,
"median_frame_interval_ms": 16.7,
"median_fps": 59.88,
"p99_frame_interval_ms": 21.8,
"worst_1pct_fps": 45.87,
"p95_frame_interval_ms": 19.5,
"min_frame_interval_ms": 7.3,
"max_frame_interval_ms": 27.6,
"ended_during_sample": true,
"samples_head": [
16.5,
16.6,
15.7,
17.4,
17,
15,
16.4,
18.9,
16.7,
15.9,
16.7,
15.4,
17.5,
19.6,
14.5,
16,
15.1,
17.7,
17.4,
15.9,
16.9,
17.5,
18.5,
13.9,
18.2,
16.4,
17.2,
15.5,
17.1,
18.1,
13.4,
18.2,
16.8,
16.5,
17,
16.9,
16.8,
14.4,
17.6,
17.4
],
"samples_tail": [
19.6,
20.2,
12.3,
16.6,
14.2,
19.5,
16.9,
14.5,
16.3,
18.9,
17.8,
13.7,
18.9,
18.5,
11.8,
22.5,
13.7,
15,
18.7,
17.4,
14.8,
19.6,
12.8,
19.6,
14.9,
17.4,
14.4,
15.9,
18.9,
16.7,
15.7,
17.9,
17.3,
15.9,
17.3,
16.2,
17.1,
17,
15.1,
19.1
]
}

View File

@ -0,0 +1 @@
[17.9,16.1,16.2,16.9,16.9,17.1,16.3,16.8,15.9,16.1,16.6,17.5,17.8,16.3,16.8,16.5,16.3,16.8,16.6,16.4,17.9,16.5,14.7,16.5,16.6,18,17.2,16.1,16.9,15.8,17.7,17.2,14.4,17.7,16.8,17,16.1,16.8,17.1,16.2,16.8,15.4,18.1,17.6,16.4,15.6,16.2,18.1,14.7,18.8,15.3,18.2,16.6,14.7,18,16.9,16.1,16.8,16.4,16.5,16.5,16.7,17.5,15.6,17.7,16.1,17.2,17,15.1,18,16.7,17.1,15.2,16.6,18.3,15.3,16.4,17.8,16.2,16.9,16.8,17.1,14.5,16.7,17.4,16,18.9,15.7,16.4,16,17.8,16.5,16.3,18.1,16.2,15.5,17.2,17.9,15.1,16.7,16.1,22.6,12.2,15.2,17.6,15.1,17.5,18.2,15.4,17.4,15.9,17,17.7,16.2,15.1,18.4,16.4,16.5,16.7,16.2,15.7,16.4,16.7,16.7,18.4,14.8,18.5,14.9,16.8,18.2,14.9,16.6,16.9,18.5,15.7,15.7,18.6,14.9,17.6,17,16.6,17.8,16.1,16.7,15.9,17.8,14.2,17.2,17.8,15.2,19.5,13.8,17,16.5,16.6,18.3,15.1,20.9,13.8,15.1,16.7,18.3,16.1,16.2,17.8,15.8,16.9,16.7,17.5,16.2,16.4,16.8,15.4,17.6,17.6,15.8,17,16.7,15.3,18.3,15.9,17.9,18.7,14,15.3,16.5,19,16.2,18.5,16.1,15.6,16.4,17.4,16.4,17.4,16.4,14.9,19.4,14.9,16.6,20,12.7,16.8,16.1,18.1,16.3,16.9,15.1,18.6,15.3,17.4,15,18.4,17.3,15.7,15.1,18.6,16.2,16.8,17.4,15.8,17,16.7,16.9,16.2,16.9,16.5,16,17.6,14.6,16.7,18.4,16.9,16.7,15.9,15.4,18.4,16.4,15.2,18.4,15.2,17.9,17.3,16,15.3,17.4,15.9,18.5,16.1,17.4,16,15.5,16.5,22.8,10.5,18.8,16.4,16.6,16.8,15.5,16.3,16.9,17.5,16.1,17.6,16,16.8,16.9,17.1,16.6,15.2,16.4,18.6,16,16.9,17.2,14.3,19,16,16.1,17.7,15.6,16.7,16.6,17,17,17,15.1,22.1,11.8,17.2,15.9,16.7,18.2,16.2,16.5,15.2,16.7,18.5,17.8,17.6,13,17.4,16.7,16.9,16.9,15.3,17.8,16.9,17,15.1,17.2,17.9,15,17.2,16.5,17.8,16.5,15.5,16.3,16.7,17.5,18.2,13.9,18.6,17,15.5,17.4,17.3,14.7,17.4,17.2,16.9,17.4,14.9,17.7,17.3,15.8,15.5,17.6,17.5,14.6,18.6,19.5,14.4,15.7,17.4,16,15.5,18.3,17.2,16.4,16.1,15.8,17.2,16.7,17.3,16.2,16.4,17.1,16.7,16,16.5,17.8,14.3,18.5,16.2,16.8,18.1,14.9,18.5,14.9,17,15.2,18.6,15.8,15.6,17.2,17.2,16.2,17.7,15.2,18.5,16.7,20.3,12.5,15.4,18,15,16.8,17.2,18.3,16.2,16,16.8,17.4,16,16.2,20,12.3,18.5,16.6,17.2,16.2,17.5,15.3,17.5,16.2,17.3,15.4,16.3,16.2,17.7,17.9,17.1,15.5,15.1,19.8,14,18.2,16.5,17.1,15.7,15.9,16,18.2,16.8,16.9,14.7,18.7,16.4,18.6,15.1,16.4,16.3,15.9,19,15.6,15,17.4,18.8,14.2,17.8,18.5,13.2,17.5,15.8,16.6,18.6,14.6,17.6,17,16.1,17,16.9,17.1,16.9,17.4,14,17.3,17.3,16.8,15.7,17.6,17.8,16,16.4,16.8,15.9,17.8,15.8,17.4,14.8,18.3,16.7,17.4,16.4,17.2,15.2,17.2,14.8,17.2,17.8,16.6,15.2,18.6,16.2,17,15,16.8,20.1,14.6,16.4,15.6,19.7,15.2,14.8,18.6,16.5,16.7,17.3,15.3,16.9,16.3,17.1,17.9,16.6,17.8,14.3,17.7,15.8,17.1,17,17.9,15.2,14.8,17.9,17.4,16.6,16.8,16.9,16.6,16.9,16.2,16.6,16.3,15.3,18,17.2,17.3,15.1,17.2,17.2,16.8,16,15.1,21.4,13.2,16.4,17,17.5,16.9,15.3,17.5,16.5,16.6,17,15.8,15.9,18.3,15.2,17.8,16.5,16.7,20.2,12.7,17.8,17.9,13,16.9,18.7,15.3,17.9,14.5,17.8,17,17.6,15.6,17.1,17.5,14.1,16.8,17.9,16.3,17,16.9,17.4,16.5,16.3,17.2,14.3,18.6,15.4,18.3,15.7,17.2,15.8,15.9,17.9,17.9,14.1,17.7,17.7,16.8,16.5,16.5,16.4,16.9,16,19.5,14.2,17.6,16.1,16.6,16.7,17.4,14.4,19.1,18.3,12.5,17.7,16.8,17,19,14.3,16.2,16.1,16.8,17.3,15.6,17,16.4,17.5,17,15.3,17.6,16.5,17.2,15,18.5,17,14.7,19,15.3,16.7,15.9,16.3,17.6,16.1,16.7,17.6,15.7,17.4,16.8,17.5,16,16.2,17.1,16.8,16.1,17.3,16.4,15.4,17.7,15.4,17.1,17.6,16,18,16.7,16.6,16,16.6,15.5,18.3,16.6,16.1,17.8,14.8,19.8,15.5,14.9,18.7,16,17.1,17.1,14,18.5,16.7,16.4,17.1,14.6,18.5,16.8,14.9,18.7,16.5,16,18.1,15.8,16.6,17,14.5,18.8,17,16.2,14.8,17,18.1,15.6,16.1,18.1,14.9,17.2,16.8,16.9,17.5,17,14.8,18.3,15.2,18,16.6,17.6,15.8,15.7,16.2,19.1,15.6,17,16.2]

View File

@ -0,0 +1,103 @@
{
"pack": "littlejs-shop",
"lane": "littlejs",
"game": "shop",
"url": "http://127.0.0.1:4311/host.html?game=shop",
"throttle": {
"cpu_rate": 4
},
"method": "rAF 帧间隔采样器注入 iframe只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s",
"sample_count": 721,
"median_frame_interval_ms": 16.7,
"median_fps": 59.88,
"p99_frame_interval_ms": 20.1,
"worst_1pct_fps": 49.75,
"p95_frame_interval_ms": 18.6,
"min_frame_interval_ms": 10.5,
"max_frame_interval_ms": 22.8,
"ended_during_sample": false,
"samples_head": [
17.9,
16.1,
16.2,
16.9,
16.9,
17.1,
16.3,
16.8,
15.9,
16.1,
16.6,
17.5,
17.8,
16.3,
16.8,
16.5,
16.3,
16.8,
16.6,
16.4,
17.9,
16.5,
14.7,
16.5,
16.6,
18,
17.2,
16.1,
16.9,
15.8,
17.7,
17.2,
14.4,
17.7,
16.8,
17,
16.1,
16.8,
17.1,
16.2
],
"samples_tail": [
14.6,
18.5,
16.8,
14.9,
18.7,
16.5,
16,
18.1,
15.8,
16.6,
17,
14.5,
18.8,
17,
16.2,
14.8,
17,
18.1,
15.6,
16.1,
18.1,
14.9,
17.2,
16.8,
16.9,
17.5,
17,
14.8,
18.3,
15.2,
18,
16.6,
17.6,
15.8,
15.7,
16.2,
19.1,
15.6,
17,
16.2
]
}

View File

@ -0,0 +1 @@
[16.2,17.3,17.5,16.2,17.4,15.1,17.6,16.5,15.7,17.3,16.2,17,16.9,15.9,17.1,15.5,19.5,14.6,17.6,14.6,16.8,27.5,6.6,16.8,18.4,16.3,17.5,15,16.1,18,16.1,20.4,13,19,15,14.2,21.9,11.2,17.2,18.1,14.9,18.7,16.7,17.4,19.3,13.8,16.7,20.9,9.7,16.7,18.5,17.9,16.1,14.4,16.4,19,16.9,15.7,17.7,16.1,19.2,13.8,27.8,9.6,14,16.6,13.7,19.4,21.3,11.5,16.8,16.1,17.6,16.8,16,17.2,15.9,16.8,16.2,15.3,16.5,19,14.4,19,16.9,16.9,16.1,14.5,17.6,17.1,14.9,18.6,16.7,17,15.3,17.9,16.3,16.7,16.3,16.3,17.3,17.7,17.4,25.9,5.7,17.1,18.4,14.9,16.8,17.7,16.4,14.5,19.2,16.7,14.9,15.4,16.7,17.9,17.4,16.4,17.4,16.9,15.2,16.4,18.4,15.5,17,17.6,13.7,18.8,17.4,15.7,17.1,16,16.3,16.4,19.2,15,14.5,18.6,17,16.7,15.3,23.2,19.7,6.9,18.6,15.7,21.4,11.1,16.3,19,13.8,16.7,16.8,16.9,18.9,16.3,16.4,19,17.7,16.8,13.9,14,17.1,16.3,18.4,18.2,16.6,15.9,17.1,19.1,14.1,15.9,17,16.3,14.6,16.9,17.6,16.1,17.7,16.5,17,17.7,16.1,28.9,5.4,15.3,17.2,16.5,16,17,17.1,16.4,16.4,17.1,17,16.3,17.5,15.7,17,16.6,16.4,16.9,16.8,15.3,17.6,17.2,16,16.6,16,18.4,16.2,16.5,16.6,16.6,16,16.9,15,17.1,18.9,14.7,16.3,16.3,28.2,9.9,12,16.9,16.4,18.5,17.5,15.8,16.8,19.3,12.7,18.8,17.2,15.7,16.5,14.6,19.6,15.7,16.8,16.3,16.5,15.3,18.4,16.9,17.4,18.2,16.7,12.6,17.5,23.4,12.1,13.1,18.9,16.2,16.4,15.6,18.3,16.1,15.2,19.1,31.6,6.1,11.5,17.6,14.2,18.6,17.7,14.6,16,18.4,16.5,16.1,18.2,15.7,17.5,15.8,15.6,18.1,16.8,16.2,15,18.6,15.9,16.9,16.7,15.1,18.2,17.4,16.4,16.1,16.2,16.3,18.1,14.8,16.7,21.6,11.5,17.9,26.9,6.1,16.8,18,15.7,18,14.5,20.3,12.7,19.2,16.1,15.7,16.3,17.7,16.4,18.7,16.1,14.8,18.3,16.1,16.5,16,19.7,14.3,16.3,16.5,16.7,16.7,18,14.9,17.6,16.2,16,17.3,17,15.9,16.7,19.3,26.1,5.9,15.8,15.1,18.7,14.3,19.3,17.2,15.5,15.2,18.9,15.9,17.1,16.2,17.7,15.4,16.4,15.5,16.8,18.8,19.7,13.7,16.5,17.4,16.6,16.2,14.9,16.8,19.9,16.4,16.9,16,16.3,16.9,15.4,16,17.3,15.6,28.7,6,16.3,18.5,13.3,19.9,15.2,17.1,17.4,13.3,20,15.5,17.7,17.3,14.9,14.6,18.6,17.8,16.8,15.6,15.7,15.9,17.5,18.8,15.8,15,19.5,15.7,16,18.3,14.8,16.9,14.5,16.7,22.3,12.6,16.5,28.3,6.2,16.6,19.3,13.3,14.9,19.7,19.5,11.3,18.7,16.5,17.6,16.4,14.6,18.5,16.8,15.2,16.9,18,16.8,15.6,15.1,19,14.7,18.8,15.2,18.3,16.7,16,15.4,17.2,15.5,18,17.4,15.5,16.2,17.4,16.5,29.3,7.1,13.1,17.7,14.9,17.1,17.7,16.6,17.4,19.6,18.7,11.7,16.5,17.1,14.8,16.4,22.6,17.3,19.3,9.3,16,17.9,15,16.1,19,13.9,23.9,10.8,17.5,15.9,16.8,15.7,16.5,16.2,17.6,17.1,27.3,6,15.1,19.6,14.4,19.3,13.4,16.8,17.1,17.6,17.8,15.4,16.6,17.6,14.3,16.9,21.5,14.8,13.5,16.8,18.9,17.1,14.9,18.2,17.1,15.5,17.6,17.3,15.4,21.3,10.1,21.7,11.3,20,15.4,25.8,7.1,17.2,14.8,18.3,17.1,16.1,18.5,15.7,16.8,20.1,13.3,16.5,15.8,17.6,16.6,16.6,14.1,19,15.3,18.8,15.7,20,12.9,16.9,17,16.8,16.3,15.5,18.3,17.6,14.1,16.9,16.7,21.2,14,31.4,5.6,11.4,16.5,17.6,16.3,15.3,18.3,16.8,16.4,18,15.9,16.1,16.6,15.1,17.9,16.7,17.2,17.2,14,18.6,16.9,16.6,17.3,16.3,15.6,17.7,16.4,14.7,17.6,15.6,18.3,15.3,18.6,26.9,8.7,14.3,15.9,18.2,15.7,16,17.6,20.5,12.3,15.8,17.8,17.2,14.4,18.6,16.3,17.1,16.3,16.7,17.9,15,17.6,17,15.4,15.3,21.6,12.1,18.4,17,15.6,16.8,20.1,13.8,17.4,16.1,72.5,9,8.8,9.6]

View File

@ -0,0 +1,103 @@
{
"pack": "phaser-catcher",
"lane": "phaser",
"game": "catcher",
"url": "http://127.0.0.1:4312/host.html?game=catcher",
"throttle": {
"cpu_rate": 4
},
"method": "rAF 帧间隔采样器注入 iframe只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s",
"sample_count": 633,
"median_frame_interval_ms": 16.7,
"median_fps": 59.88,
"p99_frame_interval_ms": 28.3,
"worst_1pct_fps": 35.34,
"p95_frame_interval_ms": 20.5,
"min_frame_interval_ms": 5.4,
"max_frame_interval_ms": 72.5,
"ended_during_sample": true,
"samples_head": [
16.2,
17.3,
17.5,
16.2,
17.4,
15.1,
17.6,
16.5,
15.7,
17.3,
16.2,
17,
16.9,
15.9,
17.1,
15.5,
19.5,
14.6,
17.6,
14.6,
16.8,
27.5,
6.6,
16.8,
18.4,
16.3,
17.5,
15,
16.1,
18,
16.1,
20.4,
13,
19,
15,
14.2,
21.9,
11.2,
17.2,
18.1
],
"samples_tail": [
18.6,
26.9,
8.7,
14.3,
15.9,
18.2,
15.7,
16,
17.6,
20.5,
12.3,
15.8,
17.8,
17.2,
14.4,
18.6,
16.3,
17.1,
16.3,
16.7,
17.9,
15,
17.6,
17,
15.4,
15.3,
21.6,
12.1,
18.4,
17,
15.6,
16.8,
20.1,
13.8,
17.4,
16.1,
72.5,
9,
8.8,
9.6
]
}

View File

@ -0,0 +1 @@
[19.2,18.2,12.2,20.3,14.7,17.1,12.8,21.5,13.4,19.8,13.5,16.9,16.5,19,16.9,15.6,18.9,16,12.9,21,15.9,17.4,16.2,16.7,13,16.7,15.6,16.7,18.4,16.2,15.5,19.6,14.6,16.4,17.6,17.3,18.8,12.7,16,18.5,15.5,16.6,17.2,18.1,14.7,16.4,17.6,16.2,16.9,17.3,17.2,16.1,15.4,17.1,17.1,16.5,16.3,18.1,16.1,15.8,18.4,14.9,17.6,15.7,17.5,16.2,16.6,18,16.3,14.9,17.3,16.3,19.9,13.4,16.9,16.2,18,18.2,13.6,17.7,17.5,14.6,18.5,15.6,19.8,16.2,14,16.2,19.2,16.6,14.9,19.4,15.5,18.5,14.7,19.5,16.7,16.8,11.4,18.3,16,17,20.1,15.3,13.4,19.4,17.4,16.4,16.5,16.9,16.6,17.5,15.6,16.1,16.7,17.8,13.8,17.8,20,11.3,21.1,13.5,17.4,16.8,14.6,20.1,16.7,18.1,11.8,16.8,16.9,19,14.2,16.7,16.2,19.1,16.8,16.8,19.3,16,12.2,17,18.4,15.6,20.4,13.6,19.8,15.8,15.4,17.3,16.5,17.6,16.2,16.9,17.7,14.2,15.4,16.7,16.3,18.1,14.5,17.9,16.1,18.4,16.1,16.1,19.1,15.1,16.1,17.9,16,17,17.1,19.4,14.3,16.8,15.4,16.7,17.4,16.3,15,18.9,16.5,17.5,14.3,18.7,14,16.6,17.4,17.7,14.3,19.2,16.5,15.5,18.2,13.8,17.7,17.7,19.1,12.3,19.3,17.8,14.3,20.8,16.9,14.7,15.8,16.6,19.2,12.2,19.3,14.8,15.6,16.3,17.7,17.7,15.1,17.7,15.5,19,14.9,19.5,15,16.7,16.5,18.4,14.2,17.6,16.5,16.7,17.5,14.4,19.6,17.2,15.9,16,14.7,19.4,16.5,15.8,17.5,14.8,15.3,16.2,21.2,13.4,18.9,15.8,14,18.6,16.6,15.6,17.4,18.5,17.3,16.3,16.7,15,15.8,18.9,15.6,15.8,16.2,18.9,17.5,15.8,16.8,14.3,20.8,12.4,16.9,16.5,18.4,16.4,15.4,16.1,16.4,18.4,15.9,19.3,14.5,18.1,15.5,20.1,13,17.5,15.2,19.4,14.9,17.7,19.1,14.2,15.1,18.2,17.4,15.1,19.3,14.3,14.6,18.8,18.5,14.8,18.3,13.8,18.2,14.8,19.3,16.6,15.2,22.2,11.1,16.8,14.6,16.7,17.6,17.1,15.4,17.7,19.9,13,17.9,17,16.1,18.8,12.6,18.5,21,14.8,15.8,16.7,14.3,19.7,17.4,14.8,19.1,13.6,18,20.8,15.8,17.4,13.6,18.1,13.6,18.4,16.1,15.8,14.1,19.2,16.3,13.8,20.8,16,19.9,15.1,15.4,16.3,15.1,18.5,14.2,17.3,15,18.9,14.2,19.3,17.8,19.6,10,18.2,15.2,16.8,16.7,16.4,18.9,14.6,16.8,16.6,16.9,16.4,17.7,18.4,20.9,10.7,18.4,14,17.1,17.8,16,16.1,16.8,17.3,18.8,18.8,13.7,17.9,14.3,18.1,14.8,15.8,19,14.4,20.6,16.1,15.1,18.7,15.4,14,19,14.2,17.1,19.5,17.7,17,11.8,19.5,16.6,16.7,14,19.7,14.9,17.8,18.7,15.4,16.1,16.3,16.8,17.6,14.5,18.3,16,16.2,16.7,18.6,18.5,12.7,15.1,17.2,16.4,21,15.2,17.3,16.4,18.4,12,17.6,16.5,16.9,15.9,17.5,16.2,19.2,14.5,15.1,18.1,17.5,17.6,17,15.6,19.7,14,17.4,15.9,19.2,12.5,19.6,17.2,16.5,14.3,16.8,16.1,18.9,15.4,15.3,19.3,13.9,17.9,15.4,17.6,16.7,16,18,16.7,15.5,18.9,14.5,17.5,19,15.4,15.9,15.5,19.2,16,19.6,11.1,16.3,16.2,19.4,15.3,18.2,16.5,15.3,20,16.1,18.3,16.7,15.2,15,17,15.5,19.2,14.1,16.9,16.4,16.2,18,17.2,16.8,17.4,15.8,17.8,12.6,17.4,21.8,12.3,16.8,17.4,19.4,12,17.3,19,17.3,15.9,15.6,16.1,16.8,16.4,16.5,20.2,14.8,19.1,14.7,16.7,19.1,14.4,15.4,18.5,12.1,19.3,16.4,18.3,15.1,17.3,15.6,15.7,18.8,14.6,20.7,14.3,14.8,18,17,16.7,17.9,16.8,17.9,17.3,14.4,18.1,13,19.1,17.5,13.2,19,14.4,16.1,20.5,14.5,16.6,18.3,16.5,14.5,18.8,16.3,17.5,20.3,9.3,19.8,19.1,12.1,18.2,17.2,14.7,16.4,18.8,17.9,15.7,17.7,16.2,15.9,13.9,16.9,18,14.4,17.7,18.2,15.4,16.6,16.1,16.6,20.4,13.8,18.2,13.7,18.4,15,18.7,15.5,17.7,16.6,16.4,15.8,20.6,15.7,13.4,17.5,18,15.3,16.4,19.7,15.5,17.3,15,17,20.9,13.1,15.7,18.6,13.3,18.1,18.6,17,15.2,18.3,18.7,13.6,17.3,16,17.6,12.5,17.9,17.3,18.5,16.2,17.9,17.3,15.5,17.7,13,18.2,18.4,14,19,17.4,14.6,17.9,15.1,17.1,20.6,19,11,16.7,17.9,15,18.3,18.8,10.8,17.1,15.8,17.1,18.2,16.3,18.1,16.8,17.1,15.6,16,15.9,18.4,17.5,15.8,15.9,18.7,16.2,16.3,16.9,17.1,14.2,16.9,15.3,18.4,16.8,17.1,16.4,17.8,14.5,16.2,17.8,18,13.9,17.5,18.2,14.4,17.8,16.7,14.8,19.3,17,16.3,16.6,16.3,17,19.2]

View File

@ -0,0 +1,103 @@
{
"pack": "phaser-shop",
"lane": "phaser",
"game": "shop",
"url": "http://127.0.0.1:4312/host.html?game=shop",
"throttle": {
"cpu_rate": 4
},
"method": "rAF 帧间隔采样器注入 iframe只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s",
"sample_count": 721,
"median_frame_interval_ms": 16.7,
"median_fps": 59.88,
"p99_frame_interval_ms": 21,
"worst_1pct_fps": 47.62,
"p95_frame_interval_ms": 19.7,
"min_frame_interval_ms": 9.3,
"max_frame_interval_ms": 22.2,
"ended_during_sample": false,
"samples_head": [
19.2,
18.2,
12.2,
20.3,
14.7,
17.1,
12.8,
21.5,
13.4,
19.8,
13.5,
16.9,
16.5,
19,
16.9,
15.6,
18.9,
16,
12.9,
21,
15.9,
17.4,
16.2,
16.7,
13,
16.7,
15.6,
16.7,
18.4,
16.2,
15.5,
19.6,
14.6,
16.4,
17.6,
17.3,
18.8,
12.7,
16,
18.5
],
"samples_tail": [
16.8,
17.1,
15.6,
16,
15.9,
18.4,
17.5,
15.8,
15.9,
18.7,
16.2,
16.3,
16.9,
17.1,
14.2,
16.9,
15.3,
18.4,
16.8,
17.1,
16.4,
17.8,
14.5,
16.2,
17.8,
18,
13.9,
17.5,
18.2,
14.4,
17.8,
16.7,
14.8,
19.3,
17,
16.3,
16.6,
16.3,
17,
19.2
]
}

View File

@ -0,0 +1,22 @@
{
"pack": "littlejs-catcher",
"lane": "littlejs",
"game": "catcher",
"url": "http://127.0.0.1:4311/host.html?game=catcher",
"note": "通关一局后采集不节流内存与节流无关。口径差异JSHeap=页面 JS 堆RSS=全 chrome 进程合计(含渲染/GPU/IPC远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
"game_end": {
"score": 30,
"completed": true,
"duration_ms": 27167,
"outcome": "win"
},
"js_heap_used_bytes": 4096696,
"js_heap_used_mb": 3.91,
"js_heap_total_bytes": 11968512,
"js_heap_total_mb": 11.41,
"proc_rss_total_kb": 1044136,
"proc_rss_total_mb": 1019.66,
"documents": 2,
"nodes": 82,
"js_event_listeners": 17
}

View File

@ -0,0 +1,22 @@
{
"pack": "littlejs-shop",
"lane": "littlejs",
"game": "shop",
"url": "http://127.0.0.1:4311/host.html?game=shop",
"note": "通关一局后采集不节流内存与节流无关。口径差异JSHeap=页面 JS 堆RSS=全 chrome 进程合计(含渲染/GPU/IPC远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
"game_end": {
"score": 100,
"completed": true,
"duration_ms": 45067,
"outcome": "win"
},
"js_heap_used_bytes": 4170704,
"js_heap_used_mb": 3.98,
"js_heap_total_bytes": 6987776,
"js_heap_total_mb": 6.66,
"proc_rss_total_kb": 1041632,
"proc_rss_total_mb": 1017.22,
"documents": 2,
"nodes": 84,
"js_event_listeners": 17
}

View File

@ -0,0 +1,22 @@
{
"pack": "phaser-catcher",
"lane": "phaser",
"game": "catcher",
"url": "http://127.0.0.1:4312/host.html?game=catcher",
"note": "通关一局后采集不节流内存与节流无关。口径差异JSHeap=页面 JS 堆RSS=全 chrome 进程合计(含渲染/GPU/IPC远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
"game_end": {
"score": 30,
"completed": true,
"duration_ms": 26542,
"outcome": "win"
},
"js_heap_used_bytes": 6588732,
"js_heap_used_mb": 6.28,
"js_heap_total_bytes": 11272192,
"js_heap_total_mb": 10.75,
"proc_rss_total_kb": 1038164,
"proc_rss_total_mb": 1013.83,
"documents": 2,
"nodes": 99,
"js_event_listeners": 38
}

View File

@ -0,0 +1,22 @@
{
"pack": "phaser-shop",
"lane": "phaser",
"game": "shop",
"url": "http://127.0.0.1:4312/host.html?game=shop",
"note": "通关一局后采集不节流内存与节流无关。口径差异JSHeap=页面 JS 堆RSS=全 chrome 进程合计(含渲染/GPU/IPC远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
"game_end": {
"score": 100,
"completed": true,
"duration_ms": 43250,
"outcome": "win"
},
"js_heap_used_bytes": 6370900,
"js_heap_used_mb": 6.08,
"js_heap_total_bytes": 8400896,
"js_heap_total_mb": 8.01,
"proc_rss_total_kb": 1038140,
"proc_rss_total_mb": 1013.81,
"documents": 2,
"nodes": 102,
"js_event_listeners": 38
}

View File

@ -0,0 +1,201 @@
{
"pack": "littlejs-catcher",
"lane": "littlejs",
"game": "catcher",
"url": "http://127.0.0.1:4311/host.html?game=catcher",
"throttle": {
"cpu_rate": 4,
"download_mbps": 5,
"upload_mbps": 1,
"rtt_ms": 40
},
"reps_count": 3,
"s2_by_open": {
"cold": {
"n": 3,
"median_ms": 535.7000000000007,
"min_ms": 521.7000000000007,
"max_ms": 579.6000000000004,
"raw": [
579.6000000000004,
521.7000000000007,
535.7000000000007
]
},
"open2": {
"n": 3,
"median_ms": 430.2,
"min_ms": 410.20000000000005,
"max_ms": 461.80000000000007,
"raw": [
461.80000000000007,
430.2,
410.20000000000005
]
},
"open3": {
"n": 3,
"median_ms": 396.7,
"min_ms": 387,
"max_ms": 454.40000000000003,
"raw": [
387,
454.40000000000003,
396.7
]
}
},
"cache_benefit_ms_cold_minus_open3": 139,
"detail": [
{
"rep": 1,
"opens": [
{
"open": "cold",
"s2_ms": 579.6000000000004,
"order_ok": true,
"anchors": {
"t_nav": 15490.8,
"t_runner_boot": 15994.6,
"t_sdk_ready": 15995.2,
"t_first_paint": 16072.2,
"t_input_bound": 16070.4,
"t_game_loaded": 16071.9,
"t_game_start": null
},
"wall_ms": 16116
},
{
"open": "open2",
"s2_ms": 461.80000000000007,
"order_ok": true,
"anchors": {
"t_nav": 107.4,
"t_runner_boot": 486.9,
"t_sdk_ready": 489.3,
"t_first_paint": 578.7,
"t_input_bound": 569.2,
"t_game_loaded": 573.1,
"t_game_start": null
},
"wall_ms": 603
},
{
"open": "open3",
"s2_ms": 387,
"order_ok": true,
"anchors": {
"t_nav": 104,
"t_runner_boot": 417,
"t_sdk_ready": 419.9,
"t_first_paint": 497.5,
"t_input_bound": 491,
"t_game_loaded": 491.5,
"t_game_start": null
},
"wall_ms": 527
}
]
},
{
"rep": 2,
"opens": [
{
"open": "cold",
"s2_ms": 521.7000000000007,
"order_ok": true,
"anchors": {
"t_nav": 25079.2,
"t_runner_boot": 25536,
"t_sdk_ready": 25537.4,
"t_first_paint": 25617.7,
"t_input_bound": 25600.9,
"t_game_loaded": 25601.8,
"t_game_start": null
},
"wall_ms": 25643
},
{
"open": "open2",
"s2_ms": 430.2,
"order_ok": true,
"anchors": {
"t_nav": 125.3,
"t_runner_boot": 473.4,
"t_sdk_ready": 474.7,
"t_first_paint": 562.6,
"t_input_bound": 555.5,
"t_game_loaded": 559,
"t_game_start": null
},
"wall_ms": 593
},
{
"open": "open3",
"s2_ms": 454.40000000000003,
"order_ok": true,
"anchors": {
"t_nav": 108.3,
"t_runner_boot": 481,
"t_sdk_ready": 484,
"t_first_paint": 568.4,
"t_input_bound": 562.7,
"t_game_loaded": 566.6,
"t_game_start": null
},
"wall_ms": 602
}
]
},
{
"rep": 3,
"opens": [
{
"open": "cold",
"s2_ms": 535.7000000000007,
"order_ok": true,
"anchors": {
"t_nav": 25066.5,
"t_runner_boot": 25523.2,
"t_sdk_ready": 25525,
"t_first_paint": 25610.7,
"t_input_bound": 25602.2,
"t_game_loaded": 25606.2,
"t_game_start": null
},
"wall_ms": 25642
},
{
"open": "open2",
"s2_ms": 410.20000000000005,
"order_ok": true,
"anchors": {
"t_nav": 108.5,
"t_runner_boot": 424.5,
"t_sdk_ready": 428.8,
"t_first_paint": 525.4,
"t_input_bound": 518.7,
"t_game_loaded": 521.1,
"t_game_start": null
},
"wall_ms": 552
},
{
"open": "open3",
"s2_ms": 396.7,
"order_ok": true,
"anchors": {
"t_nav": 169.2,
"t_runner_boot": 487.1,
"t_sdk_ready": 489.4,
"t_first_paint": 569.5,
"t_input_bound": 565.9,
"t_game_loaded": 567.9,
"t_game_start": null
},
"wall_ms": 604
}
]
}
]
}

View File

@ -0,0 +1,201 @@
{
"pack": "littlejs-shop",
"lane": "littlejs",
"game": "shop",
"url": "http://127.0.0.1:4311/host.html?game=shop",
"throttle": {
"cpu_rate": 4,
"download_mbps": 5,
"upload_mbps": 1,
"rtt_ms": 40
},
"reps_count": 3,
"s2_by_open": {
"cold": {
"n": 3,
"median_ms": 592.4000000000015,
"min_ms": 571.0999999999985,
"max_ms": 666.9000000000001,
"raw": [
571.0999999999985,
592.4000000000015,
666.9000000000001
]
},
"open2": {
"n": 3,
"median_ms": 484.00000000000006,
"min_ms": 472.3,
"max_ms": 489.5,
"raw": [
472.3,
484.00000000000006,
489.5
]
},
"open3": {
"n": 3,
"median_ms": 460.30000000000007,
"min_ms": 423,
"max_ms": 522.9,
"raw": [
423,
460.30000000000007,
522.9
]
}
},
"cache_benefit_ms_cold_minus_open3": 132.1,
"detail": [
{
"rep": 1,
"opens": [
{
"open": "cold",
"s2_ms": 571.0999999999985,
"order_ok": true,
"anchors": {
"t_nav": 25071.9,
"t_runner_boot": 25519.4,
"t_sdk_ready": 25521.3,
"t_first_paint": 25645.3,
"t_input_bound": 25643,
"t_game_loaded": 25645,
"t_game_start": null
},
"wall_ms": 25657
},
{
"open": "open2",
"s2_ms": 472.3,
"order_ok": true,
"anchors": {
"t_nav": 101.2,
"t_runner_boot": 445.4,
"t_sdk_ready": 451,
"t_first_paint": 577.7,
"t_input_bound": 573.5,
"t_game_loaded": 575.4,
"t_game_start": null
},
"wall_ms": 586
},
{
"open": "open3",
"s2_ms": 423,
"order_ok": true,
"anchors": {
"t_nav": 166.4,
"t_runner_boot": 465.8,
"t_sdk_ready": 468.7,
"t_first_paint": 594.4,
"t_input_bound": 589.4,
"t_game_loaded": 591.6,
"t_game_start": null
},
"wall_ms": 602
}
]
},
{
"rep": 2,
"opens": [
{
"open": "cold",
"s2_ms": 592.4000000000015,
"order_ok": true,
"anchors": {
"t_nav": 25096.1,
"t_runner_boot": 25564.9,
"t_sdk_ready": 25566.6,
"t_first_paint": 25690.6,
"t_input_bound": 25688.5,
"t_game_loaded": 25690.1,
"t_game_start": null
},
"wall_ms": 25701
},
{
"open": "open2",
"s2_ms": 484.00000000000006,
"order_ok": true,
"anchors": {
"t_nav": 118.7,
"t_runner_boot": 480.2,
"t_sdk_ready": 483.4,
"t_first_paint": 607.1,
"t_input_bound": 602.7,
"t_game_loaded": 605.7,
"t_game_start": null
},
"wall_ms": 614
},
{
"open": "open3",
"s2_ms": 460.30000000000007,
"order_ok": true,
"anchors": {
"t_nav": 107.9,
"t_runner_boot": 452.7,
"t_sdk_ready": 455.4,
"t_first_paint": 571.6,
"t_input_bound": 568.2,
"t_game_loaded": 570.2,
"t_game_start": null
},
"wall_ms": 580
}
]
},
{
"rep": 3,
"opens": [
{
"open": "cold",
"s2_ms": 666.9000000000001,
"order_ok": true,
"anchors": {
"t_nav": 2488.5,
"t_runner_boot": 2980.9,
"t_sdk_ready": 2983.5,
"t_first_paint": 3159.3,
"t_input_bound": 3155.4,
"t_game_loaded": 3155.8,
"t_game_start": null
},
"wall_ms": 3168
},
{
"open": "open2",
"s2_ms": 489.5,
"order_ok": true,
"anchors": {
"t_nav": 110.5,
"t_runner_boot": 471.6,
"t_sdk_ready": 473.9,
"t_first_paint": 605.1,
"t_input_bound": 600,
"t_game_loaded": 602.7,
"t_game_start": null
},
"wall_ms": 614
},
{
"open": "open3",
"s2_ms": 522.9,
"order_ok": true,
"anchors": {
"t_nav": 112.7,
"t_runner_boot": 496.4,
"t_sdk_ready": 498.8,
"t_first_paint": 641.1,
"t_input_bound": 635.6,
"t_game_loaded": 637,
"t_game_start": null
},
"wall_ms": 649
}
]
}
]
}

View File

@ -0,0 +1,198 @@
{
"pack": "phaser-catcher",
"lane": "phaser",
"game": "catcher",
"url": "http://127.0.0.1:4312/host.html?game=catcher",
"throttle": {
"cpu_rate": 4,
"download_mbps": 5,
"upload_mbps": 1,
"rtt_ms": 40
},
"reps_count": 3,
"s2_by_open": {
"cold": {
"n": 3,
"median_ms": 2858,
"min_ms": 2792.3,
"max_ms": 2870.6,
"raw": [
2870.6,
2858,
2792.3
]
},
"open2": {
"n": 3,
"median_ms": 756.5,
"min_ms": 723.4,
"max_ms": 759.6,
"raw": [
723.4,
759.6,
756.5
]
},
"open3": {
"n": 3,
"median_ms": 684.4,
"min_ms": 645,
"max_ms": 695.5,
"raw": [
684.4,
695.5,
645
]
}
},
"cache_benefit_ms_cold_minus_open3": 2173.6,
"detail": [
{
"rep": 1,
"opens": [
{
"open": "cold",
"s2_ms": 2870.6,
"order_ok": true,
"anchors": {
"t_nav": 25.2,
"t_runner_boot": 2782.8,
"t_sdk_ready": 2784.7,
"t_input_bound": 2895.8,
"t_game_loaded": 2896.7,
"t_renderer_ready": 2897
},
"wall_ms": 27885
},
{
"open": "open2",
"s2_ms": 723.4,
"order_ok": true,
"anchors": {
"t_nav": 23.4,
"t_runner_boot": 572.8,
"t_sdk_ready": 573.8,
"t_input_bound": 746.8,
"t_game_loaded": 747.9,
"t_renderer_ready": 750.4,
"t_first_paint": 752.3
},
"wall_ms": 978
},
{
"open": "open3",
"s2_ms": 684.4,
"order_ok": true,
"anchors": {
"t_nav": 23.3,
"t_runner_boot": 605.5,
"t_sdk_ready": 607.2,
"t_input_bound": 707.7,
"t_game_loaded": 708.7,
"t_renderer_ready": 710.2,
"t_first_paint": 711.7
},
"wall_ms": 819
}
]
},
{
"rep": 2,
"opens": [
{
"open": "cold",
"s2_ms": 2858,
"order_ok": true,
"anchors": {
"t_nav": 27.2,
"t_runner_boot": 2767.8,
"t_sdk_ready": 2770.1,
"t_input_bound": 2885.2,
"t_game_loaded": 2886.6,
"t_renderer_ready": 2886.8,
"t_first_paint": 2887.1
},
"wall_ms": 27907
},
{
"open": "open2",
"s2_ms": 759.6,
"order_ok": true,
"anchors": {
"t_nav": 21,
"t_runner_boot": 602.7,
"t_sdk_ready": 604.2,
"t_input_bound": 780.6,
"t_game_loaded": 781.1,
"t_renderer_ready": 781.4,
"t_first_paint": 784.6
},
"wall_ms": 1000
},
{
"open": "open3",
"s2_ms": 695.5,
"order_ok": true,
"anchors": {
"t_nav": 21.4,
"t_runner_boot": 605.6,
"t_sdk_ready": 607,
"t_input_bound": 716.9,
"t_game_loaded": 718.4,
"t_renderer_ready": 718.7
},
"wall_ms": 843
}
]
},
{
"rep": 3,
"opens": [
{
"open": "cold",
"s2_ms": 2792.3,
"order_ok": true,
"anchors": {
"t_nav": 24.2,
"t_runner_boot": 2700.4,
"t_sdk_ready": 2701.8,
"t_input_bound": 2816.5,
"t_game_loaded": 2817.9,
"t_renderer_ready": 2819.3
},
"wall_ms": 27829
},
{
"open": "open2",
"s2_ms": 756.5,
"order_ok": true,
"anchors": {
"t_nav": 17.7,
"t_runner_boot": 594.2,
"t_sdk_ready": 594.6,
"t_input_bound": 774.2,
"t_game_loaded": 775.5,
"t_renderer_ready": 776.9,
"t_first_paint": 777.1
},
"wall_ms": 977
},
{
"open": "open3",
"s2_ms": 645,
"order_ok": true,
"anchors": {
"t_nav": 24.6,
"t_runner_boot": 571.1,
"t_sdk_ready": 572.5,
"t_input_bound": 669.6,
"t_game_loaded": 670.6,
"t_renderer_ready": 672.4,
"t_first_paint": 673.9
},
"wall_ms": 820
}
]
}
]
}

View File

@ -0,0 +1,199 @@
{
"pack": "phaser-shop",
"lane": "phaser",
"game": "shop",
"url": "http://127.0.0.1:4312/host.html?game=shop",
"throttle": {
"cpu_rate": 4,
"download_mbps": 5,
"upload_mbps": 1,
"rtt_ms": 40
},
"reps_count": 3,
"s2_by_open": {
"cold": {
"n": 3,
"median_ms": 2817.7,
"min_ms": 2812.6,
"max_ms": 2883.1,
"raw": [
2883.1,
2817.7,
2812.6
]
},
"open2": {
"n": 3,
"median_ms": 780.5,
"min_ms": 765.9,
"max_ms": 798.8,
"raw": [
765.9,
780.5,
798.8
]
},
"open3": {
"n": 3,
"median_ms": 745.6,
"min_ms": 716.3,
"max_ms": 752.8,
"raw": [
716.3,
752.8,
745.6
]
}
},
"cache_benefit_ms_cold_minus_open3": 2072.1,
"detail": [
{
"rep": 1,
"opens": [
{
"open": "cold",
"s2_ms": 2883.1,
"order_ok": true,
"anchors": {
"t_nav": 23,
"t_runner_boot": 2764,
"t_sdk_ready": 2765,
"t_input_bound": 2906.1,
"t_game_loaded": 2906.5,
"t_renderer_ready": 2908.9,
"t_first_paint": 2909.1
},
"wall_ms": 25752
},
{
"open": "open2",
"s2_ms": 765.9,
"order_ok": true,
"anchors": {
"t_nav": 20.2,
"t_runner_boot": 579.5,
"t_sdk_ready": 581.1,
"t_input_bound": 786.1,
"t_game_loaded": 786.4,
"t_renderer_ready": 786.7,
"t_first_paint": 789.6
},
"wall_ms": 1014
},
{
"open": "open3",
"s2_ms": 716.3,
"order_ok": true,
"anchors": {
"t_nav": 23.6,
"t_runner_boot": 603.5,
"t_sdk_ready": 605.3,
"t_input_bound": 739.9,
"t_game_loaded": 740.1,
"t_renderer_ready": 740.3,
"t_first_paint": 743.9
},
"wall_ms": 969
}
]
},
{
"rep": 2,
"opens": [
{
"open": "cold",
"s2_ms": 2817.7,
"order_ok": true,
"anchors": {
"t_nav": 25.5,
"t_runner_boot": 2704.6,
"t_sdk_ready": 2705.9,
"t_input_bound": 2843.2,
"t_game_loaded": 2844.9,
"t_renderer_ready": 2845.6,
"t_first_paint": 2847
},
"wall_ms": 27934
},
{
"open": "open2",
"s2_ms": 780.5,
"order_ok": true,
"anchors": {
"t_nav": 24.7,
"t_runner_boot": 594.9,
"t_sdk_ready": 596,
"t_input_bound": 805.2,
"t_game_loaded": 806,
"t_renderer_ready": 807.4,
"t_first_paint": 808.3
},
"wall_ms": 1046
},
{
"open": "open3",
"s2_ms": 752.8,
"order_ok": true,
"anchors": {
"t_nav": 25.9,
"t_runner_boot": 646.5,
"t_sdk_ready": 647.7,
"t_input_bound": 778.7,
"t_game_loaded": 780.1,
"t_renderer_ready": 781
},
"wall_ms": 910
}
]
},
{
"rep": 3,
"opens": [
{
"open": "cold",
"s2_ms": 2812.6,
"order_ok": true,
"anchors": {
"t_nav": 26.3,
"t_runner_boot": 2700.8,
"t_sdk_ready": 2701.8,
"t_input_bound": 2838.9,
"t_game_loaded": 2840.2,
"t_renderer_ready": 2840.4,
"t_first_paint": 2841.9
},
"wall_ms": 27945
},
{
"open": "open2",
"s2_ms": 798.8,
"order_ok": true,
"anchors": {
"t_nav": 14.7,
"t_runner_boot": 600.3,
"t_sdk_ready": 602,
"t_input_bound": 813.5,
"t_game_loaded": 813.8,
"t_renderer_ready": 814.1,
"t_first_paint": 817.6
},
"wall_ms": 1026
},
{
"open": "open3",
"s2_ms": 745.6,
"order_ok": true,
"anchors": {
"t_nav": 28,
"t_runner_boot": 637.9,
"t_sdk_ready": 639.7,
"t_input_bound": 773.6,
"t_game_loaded": 773.9,
"t_renderer_ready": 774
},
"wall_ms": 912
}
]
}
]
}

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Phase 2 · B+C帧率@4× + 内存 —— 4 包顺序跑(避免 chrome 争用)
set -u
cd /root/spike-t1/phase2
echo "=== FPS+MEM 全矩阵开跑 @ $(date -Is) ==="
for pack in "littlejs catcher" "littlejs shop" "phaser catcher" "phaser shop"; do
set -- $pack
echo "--- [FPS] $1/$2 @ $(date -Is) ---"
node fps_measure.cjs "$1" "$2" 12
echo "--- [FPS] $1/$2 完 rc=$? ---"
sleep 2
echo "--- [MEM] $1/$2 @ $(date -Is) ---"
node mem_measure.cjs "$1" "$2"
echo "--- [MEM] $1/$2 完 rc=$? ---"
sleep 2
done
echo "=== FPS+MEM 全矩阵完 @ $(date -Is) ==="

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Phase 2 · A. S2 三开曲线 —— 4 包 × 3 遍,顺序跑(避免 chrome 资源争用/端口碰撞)
set -u
cd /root/spike-t1/phase2
REPS="${1:-3}"
echo "=== S2 全矩阵开跑 reps=$REPS @ $(date -Is) ==="
for pack in "littlejs catcher" "littlejs shop" "phaser catcher" "phaser shop"; do
set -- $pack
echo "--- 包 $1/$2 @ $(date -Is) ---"
node s2_three_open.cjs "$1" "$2" "$REPS"
echo "--- 包 $1/$2 完 rc=$? @ $(date -Is) ---"
sleep 2
done
echo "=== S2 全矩阵完 @ $(date -Is) ==="

View File

@ -0,0 +1,244 @@
/**
* Phase 2 · A. S2 三开生死曲线钉子⑤主数据 执行单 §6.1/§6.2
* ============================================================================
* 对每包lane × game = 4 CDP 节流CPU 4× + 网络 5Mbps/1Mbps/RTT40ms
* profile URL 冷开/二开/三开各记完整七锚点 + S2(t_navt_input_bound)
* - 冷开 = 全新 chrome 用户数据目录HTTP 缓存 + V8 编译缓存 双冷 导航 收七锚点
* - 二开/三开 = 同一 chrome profile关页重开非刷新 URL
* - 编译缓存收益 = 三开 vs 冷开的 S2 差值Chromium 代理真机 XWeb 留残差
*
* 每包跑 N=3 每遍=一个全新 chrome+全新 user-data-dir 三连开取中位 + 离散
* host 自带单时钟 s2_mslittlejs window.__probe.summary().s2_ms / phaser window.__SPIKE__.checks.s2_ms
* 锚点littlejs window.__probe.anchors()绝对 perf.nowphaser window.__SPIKE__.lifecycle相对 T0
*
* 红线只读 host 探针不篡改任何状态不注入事件仅测导航可玩生死曲线不玩游戏
*
* 用法node s2_three_open.cjs <lane> <game> [reps]
* lane=littlejs|phaser game=catcher|shop reps 缺省 3
*/
'use strict';
const cp = require('child_process');
const fs = require('fs');
const path = require('path');
const http = require('http');
// ---- 参数 ----
const LANE = process.argv[2]; // littlejs | phaser
const GAME = process.argv[3]; // catcher | shop
const REPS = parseInt(process.argv[4] || '3', 10);
if (!['littlejs', 'phaser'].includes(LANE) || !['catcher', 'shop'].includes(GAME)) {
console.error('用法: node s2_three_open.cjs <littlejs|phaser> <catcher|shop> [reps]');
process.exit(64);
}
const PORT = LANE === 'littlejs' ? 4311 : 4312;
// 两 lane 同走测量宿主 host.html?game=serve 把 / 与 /host.html 都映射到 host.html带七锚点探针
const HOST_URL = 'http://127.0.0.1:' + PORT + '/host.html?game=' + GAME;
const CDP_PORT = 9300 + (LANE === 'littlejs' ? 0 : 1) * 2 + (GAME === 'shop' ? 1 : 0); // 每包独立调试端口
const OUTDIR = path.join(__dirname, 'raw');
if (!fs.existsSync(OUTDIR)) fs.mkdirSync(OUTDIR, { recursive: true });
const TAG = LANE + '-' + GAME;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const logLines = [];
function log(...a) {
const s = '[s2:' + TAG + '] ' + a.map((x) => (typeof x === 'string' ? x : JSON.stringify(x))).join(' ');
console.log(s);
logLines.push(s);
}
// ---- 极简 CDP over WebSocketnode22 内置 WebSocket零三方依赖----
class CDP {
constructor(wsUrl) {
this.ws = new WebSocket(wsUrl);
this.id = 0;
this.pending = new Map();
this.handlers = {};
this.ready = new Promise((res, rej) => {
this.ws.addEventListener('open', () => res());
this.ws.addEventListener('error', (e) => rej(e));
});
this.ws.addEventListener('message', (ev) => {
let msg; try { msg = JSON.parse(ev.data); } catch { return; }
if (msg.id !== undefined && this.pending.has(msg.id)) {
const { resolve, reject } = this.pending.get(msg.id);
this.pending.delete(msg.id);
if (msg.error) reject(new Error(JSON.stringify(msg.error)));
else resolve(msg.result);
} else if (msg.method) {
(this.handlers[msg.method] || []).forEach((h) => h(msg.params));
}
});
}
on(m, h) { (this.handlers[m] ||= []).push(h); }
send(method, params) {
const id = ++this.id;
return new Promise((resolve, reject) => {
this.pending.set(id, { resolve, reject });
this.ws.send(JSON.stringify({ id, method, params: params || {} }));
});
}
close() { try { this.ws.close(); } catch {} }
}
function httpJson(urlPath, port) {
return new Promise((resolve, reject) => {
http.get('http://127.0.0.1:' + port + urlPath, (res) => {
let b = ''; res.on('data', (d) => (b += d));
res.on('end', () => { try { resolve(JSON.parse(b)); } catch (e) { reject(e); } });
}).on('error', reject);
});
}
// ---- 启 chrome全新 user-data-dir = HTTP 缓存 + V8 编译缓存双冷)----
function launchChrome(userDataDir) {
const args = [
'--headless=new',
'--no-sandbox',
'--disable-gpu',
'--mute-audio',
'--hide-scrollbars',
'--window-size=900,900',
'--force-device-scale-factor=2',
'--remote-debugging-port=' + CDP_PORT,
'--remote-debugging-address=127.0.0.1',
'--user-data-dir=' + userDataDir,
'about:blank',
];
const ch = cp.spawn('/usr/bin/google-chrome', args, { stdio: ['ignore', 'pipe', 'pipe'] });
ch.stderr.on('data', () => {});
return ch;
}
// ---- 在某 page target 上:节流 + 导航 + 等 input_bound + 读锚点 ----
async function openAndMeasure(bcdp, openLabel) {
// 新建 page target关页重开 = 新 target非刷新
const { targetId } = await bcdp.send('Target.createTarget', { url: 'about:blank' });
let pageInfo = null;
for (let i = 0; i < 30; i++) {
const list = await httpJson('/json', CDP_PORT);
pageInfo = list.find((t) => t.id === targetId && t.webSocketDebuggerUrl);
if (pageInfo) break;
await sleep(120);
}
if (!pageInfo) throw new Error(openLabel + ': page target ws 未找到');
const cdp = new CDP(pageInfo.webSocketDebuggerUrl);
await cdp.ready;
await cdp.send('Page.enable');
await cdp.send('Runtime.enable');
await cdp.send('Network.enable');
// —— 节流(执行单 §6.2:千元机+4G 画像)——
// CPU 4×
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 4 });
// 网络:下行 5Mbps / 上行 1Mbps / RTT 40ms字节/秒口径Mbps→bytes/s = Mbps*1e6/8
await cdp.send('Network.emulateNetworkConditions', {
offline: false,
latency: 40,
downloadThroughput: (5 * 1e6) / 8,
uploadThroughput: (1 * 1e6) / 8,
});
// 导航前墙钟仅作旁证S2 以 host 单时钟为准)
const wallStart = Date.now();
await cdp.send('Page.navigate', { url: HOST_URL });
// 轮询 host 探针拿 s2_mshost 内部已算 t_input_bound - t_nav
let anchors = null, s2 = null, orderOk = null, loaded = null;
const readProbe = LANE === 'littlejs'
? `(function(){ if(!window.__probe) return null; return { s2: window.__probe.summary().s2_ms, anchors: window.__probe.anchors(), order: window.__probe.summary().loaded_ge_input_bound }; })()`
: `(function(){ var s=window.__SPIKE__; if(!s) return null; return { s2: s.checks ? s.checks.s2_ms : null, anchors: s.lifecycle, order: s.checks ? s.checks.order_ok : null }; })()`;
for (let i = 0; i < 300; i++) { // 节流下 input_bound 可能较慢,给足 30s
const r = await cdp.send('Runtime.evaluate', { expression: 'JSON.stringify(' + readProbe + ')', returnByValue: true })
.then((x) => { try { return JSON.parse(x.result.value); } catch { return null; } })
.catch(() => null);
if (r && r.s2 != null) { s2 = r.s2; anchors = r.anchors; orderOk = r.order; break; }
await sleep(100);
}
const wallMs = Date.now() - wallStart;
// 关页(下一开是新 targetprofile 内磁盘缓存+V8 code cache 保留)
await cdp.send('Page.close').catch(() => {});
cdp.close();
await bcdp.send('Target.closeTarget', { targetId }).catch(() => {});
return { open: openLabel, s2_ms: s2, order_ok: orderOk, anchors, wall_ms: wallMs };
}
// ---- 一遍 = 一个全新 chrome+全新 profile 的「冷→二→三」三连开 ----
async function oneRep(rep) {
const userDataDir = '/tmp/s2-' + TAG + '-rep' + rep + '-' + Date.now();
fs.rmSync(userDataDir, { recursive: true, force: true });
log('rep#' + rep + ' 启 chrome全新 profile ' + userDataDir + '');
const chrome = launchChrome(userDataDir);
// 等调试端口
let ver = null;
for (let i = 0; i < 50; i++) {
try { ver = await httpJson('/json/version', CDP_PORT); if (ver && ver.webSocketDebuggerUrl) break; } catch {}
await sleep(250);
}
if (!ver) { chrome.kill('SIGKILL'); throw new Error('rep#' + rep + ' chrome 调试端口未就绪'); }
const bcdp = new CDP(ver.webSocketDebuggerUrl);
await bcdp.ready;
const opens = [];
for (const label of ['cold', 'open2', 'open3']) {
const r = await openAndMeasure(bcdp, label);
log('rep#' + rep + ' ' + label + ' S2=' + r.s2_ms + 'ms order_ok=' + r.order_ok + ' wall=' + r.wall_ms + 'ms');
opens.push(r);
await sleep(400); // 让 V8 code cache 落盘
}
bcdp.close();
chrome.kill('SIGKILL');
await sleep(400);
fs.rmSync(userDataDir, { recursive: true, force: true });
return { rep, opens };
}
function median(arr) {
const a = arr.filter((x) => x != null).slice().sort((x, y) => x - y);
if (!a.length) return null;
const m = Math.floor(a.length / 2);
return a.length % 2 ? a[m] : (a[m - 1] + a[m]) / 2;
}
(async () => {
log('=== S2 三开生死曲线 · ' + TAG + ' · URL=' + HOST_URL + ' · 节流 CPU4×+5Mbps/1Mbps/RTT40 · reps=' + REPS + ' ===');
const reps = [];
for (let r = 1; r <= REPS; r++) {
try { reps.push(await oneRep(r)); }
catch (e) { log('rep#' + r + ' 异常: ' + (e && e.message)); reps.push({ rep: r, error: e && e.message, opens: [] }); }
}
// 汇总:每开位 S2 的中位+min/max离散
const byOpen = { cold: [], open2: [], open3: [] };
for (const rep of reps) for (const o of (rep.opens || [])) if (byOpen[o.open]) byOpen[o.open].push(o.s2_ms);
const summ = {};
for (const k of ['cold', 'open2', 'open3']) {
const vals = byOpen[k];
summ[k] = {
n: vals.filter((x) => x != null).length,
median_ms: median(vals),
min_ms: vals.length ? Math.min(...vals.filter((x) => x != null)) : null,
max_ms: vals.length ? Math.max(...vals.filter((x) => x != null)) : null,
raw: vals,
};
}
const coldMed = summ.cold.median_ms, hotMed = summ.open3.median_ms;
const cacheBenefit = (coldMed != null && hotMed != null) ? +(coldMed - hotMed).toFixed(2) : null;
const out = {
pack: TAG, lane: LANE, game: GAME, url: HOST_URL,
throttle: { cpu_rate: 4, download_mbps: 5, upload_mbps: 1, rtt_ms: 40 },
reps_count: REPS,
s2_by_open: summ,
cache_benefit_ms_cold_minus_open3: cacheBenefit,
detail: reps,
};
fs.writeFileSync(path.join(OUTDIR, 's2-' + TAG + '.json'), JSON.stringify(out, null, 2));
fs.writeFileSync(path.join(OUTDIR, 's2-' + TAG + '.log'), logLines.join('\n') + '\n');
log('DONE cold med=' + coldMed + ' open2 med=' + summ.open2.median_ms + ' open3 med=' + hotMed + ' 编译缓存收益(cold-open3)=' + cacheBenefit + 'ms');
process.exit(0);
})().catch((e) => { console.error(e); process.exit(1); });