137 lines
6.0 KiB
JavaScript
137 lines
6.0 KiB
JavaScript
// 手感实测(开发用临时脚本):进入 1-1 后用 __marioProbe 采样,
|
||
// 测站立满跳/助跑跳的高度与滞空、连跳节奏、按住不自动回弹、走/跑极速
|
||
const { app, BrowserWindow } = require('electron')
|
||
const path = require('path')
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||
|
||
async function main() {
|
||
const win = new BrowserWindow({ width: 1280, height: 860, show: true, backgroundColor: '#0f1220', webPreferences: { backgroundThrottling: false } })
|
||
win.webContents.setBackgroundThrottling(false)
|
||
const pageErrors = []
|
||
win.webContents.on('console-message', (e) => { if (e?.level === 'error' && e?.message) pageErrors.push(e.message) })
|
||
const js = (code) => win.webContents.executeJavaScript(code, true).catch((e) => { console.error('[feel] JS 失败:', String(e.message).slice(0, 80)); return null })
|
||
const waitFor = async (cond, t, d) => {
|
||
const t0 = Date.now()
|
||
while (Date.now() - t0 < t) { try { if (await js(cond)) return } catch {} ; await sleep(300) }
|
||
throw new Error('超时:' + d)
|
||
}
|
||
const key = async (k, down) => js(`window.dispatchEvent(new KeyboardEvent('${down ? 'keydown' : 'keyup'}', { key: '${k}' })); true`)
|
||
// 以 ~28ms 间隔采样探针
|
||
const sample = async (ms) => {
|
||
const out = []
|
||
const t0 = Date.now()
|
||
while (Date.now() - t0 < ms) {
|
||
const p = await js(`JSON.stringify(window.__marioProbe || null)`)
|
||
if (p) out.push({ ...JSON.parse(p), t: Date.now() - t0 })
|
||
await sleep(25)
|
||
}
|
||
return out
|
||
}
|
||
|
||
await win.loadURL('http://localhost:5173')
|
||
await js('localStorage.clear(); true')
|
||
await win.webContents.reload()
|
||
await waitFor(`!!document.querySelector('input[placeholder^="用户名"]')`, 10000, '登录页')
|
||
await js(`
|
||
(() => {
|
||
const us = document.querySelector('input[placeholder^="用户名"]')
|
||
us.value = 'mariotest01'; us.dispatchEvent(new Event('input', { bubbles: true }))
|
||
const ps = document.querySelector('input[type="password"]')
|
||
ps.value = 'test123456'; ps.dispatchEvent(new Event('input', { bubbles: true }))
|
||
document.querySelector('button.submit').click()
|
||
return true
|
||
})()
|
||
`)
|
||
await waitFor(`!!localStorage.getItem('token')`, 8000, '登录')
|
||
await sleep(1200)
|
||
await js(`location.href = '/play/mario'; true`).catch(() => {})
|
||
await waitFor(`!!document.querySelector('.overlay button') && [...document.querySelectorAll('.overlay button')].some((b) => /开始闯关|继续第|开始游戏/.test(b.textContent))`, 20000, '游玩页')
|
||
await js(`
|
||
(() => {
|
||
const b = document.querySelector('.overlay button.btn-lg') ||
|
||
[...document.querySelectorAll('.overlay button')].find((x) => /开始闯关|继续第|开始游戏/.test(x.textContent))
|
||
b.click()
|
||
return true
|
||
})()
|
||
`)
|
||
await sleep(900) // 选关画面出现,光标在最新解锁关(12 → 11=3-4)
|
||
await key('ArrowRight', true); await sleep(50); await key('ArrowRight', false); await sleep(80) // → 0 = 1-1
|
||
await key(' ', true); await sleep(80); await key(' ', false)
|
||
await sleep(2600) // 等横幅结束
|
||
|
||
// —— 测 A:站立满跳(按住)——
|
||
const groundY0 = (await js(`window.__marioProbe.y + window.__marioProbe.h ?? null`)) // 无 h,用落点近似
|
||
const a = []
|
||
await key(' ', true)
|
||
const aS = await sample(1300)
|
||
await key(' ', false)
|
||
await sleep(600)
|
||
// —— 测 C:连跳(原地按 ~0.83s 滞空节奏快速重按,验证落地前缓冲让连跳无缝)——
|
||
await key('x', true)
|
||
await sleep(300)
|
||
const cS = []
|
||
for (let i = 0; i < 5; i++) {
|
||
await key(' ', true)
|
||
const s1 = await sample(280)
|
||
await key(' ', false)
|
||
await sleep(430) // 重按落在落地前缓冲窗内
|
||
cS.push(...s1)
|
||
}
|
||
await key(' ', false)
|
||
await key('x', false)
|
||
await sleep(500)
|
||
// —— 测 B:走/跑极速(地面 0-18 列约 608px,控制跑动时长别掉坑)——
|
||
await key('ArrowRight', true)
|
||
const walkS = await sample(900) // 未按住跑步键 = 走
|
||
await key('x', true)
|
||
await sleep(300)
|
||
const runS = await sample(900) // 按住 x = 跑
|
||
await key('x', false)
|
||
await key('ArrowRight', false)
|
||
await sleep(400)
|
||
// —— 测 D:按住跳不放 ——
|
||
await sleep(500)
|
||
await key(' ', true)
|
||
const dS = await sample(1600)
|
||
await key(' ', false)
|
||
|
||
const analyze = (s, label) => {
|
||
if (!s.length) return null
|
||
const gY = Math.max(...s.map((p) => p.y)) // 最大 y 即地面站立位
|
||
const air = s.filter((p) => !p.ground)
|
||
const apex = air.length ? gY - Math.min(...air.map((p) => p.y)) : 0
|
||
// 起落对:ground false 的连续段
|
||
let hops = 0
|
||
let prev = true
|
||
let airMs = 0
|
||
const dts = []
|
||
for (let i = 1; i < s.length; i++) dts.push(s[i].t - s[i - 1].t)
|
||
for (const p of s) {
|
||
if (prev && !p.ground) hops++
|
||
if (!p.ground) airMs += 28
|
||
prev = p.ground
|
||
}
|
||
return { label, apexPx: apex.toFixed(0), hops, airMsPerHop: hops ? Math.round(airMs / hops) : 0, groundY: gY.toFixed(0) }
|
||
}
|
||
// 极速:相邻采样位移/时间
|
||
const topSpeed = (s) => {
|
||
let best = 0
|
||
for (let i = 1; i < s.length; i++) {
|
||
const dt = (s[i].t - s[i - 1].t) / 1000
|
||
const dx = Math.abs(s[i].x - s[i - 1].x)
|
||
if (dt > 0 && s[i].ground) best = Math.max(best, dx / dt)
|
||
}
|
||
return Math.round(best)
|
||
}
|
||
console.log('[feel] A 站立满跳:', JSON.stringify(analyze(aS, 'A')))
|
||
console.log('[feel] B 走极速:', topSpeed(walkS), 'px/s(采样口径)')
|
||
console.log('[feel] B 跑极速:', topSpeed(runS), 'px/s(采样口径)')
|
||
console.log('[feel] C 连跳:', JSON.stringify(analyze(cS, 'C')))
|
||
console.log('[feel] D 按住不放:', JSON.stringify(analyze(dS, 'D')))
|
||
const report = await js(`({ marioErr: window.__marioErr ?? null, pageErrors: window.__pageErrors || [] })`)
|
||
console.log('[feel] 异常:', JSON.stringify(report), 'console:', pageErrors.length)
|
||
app.exit(0)
|
||
}
|
||
app.whenReady().then(() => main().catch((e) => { console.error('[feel] 失败:', e.message); app.exit(1) }))
|
||
setTimeout(() => { console.error('[feel] 总超时'); app.exit(1) }, 90000)
|