Files
nl-game/electron/mario-feel-check.cjs
2026-10-11 09:09:28 +08:00

137 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 手感实测(开发用临时脚本):进入 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)