Files
Toolbox/dev_test_scripts/debug/debug_retro_pinball_arch_search.js
yangxiangyuan 4492b4106a feat: 新增3个思想实验室项目,完善私有剪贴板授权与上传功能,添加配套测试脚本
1. 新增怀旧弹珠台、人性与投资、穿越三体、四维空间四个思想实验室项目
2. 为私有剪贴板添加业务层授权校验,优化上传进度显示与鉴权失败处理
3. 新增多个调试与单元测试脚本,覆盖三体积分、弹珠台物理、集成测试等场景
4. 补全穿越三体项目的完整HTML页面资源
2026-08-07 17:49:24 +08:00

101 lines
4.4 KiB
JavaScript
Raw Permalink 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.
/**
* 调试:retro_pinball 拱形导轨几何搜索
* 目的:找到一组 ARCH(cx,cy,r,a0,a1),让满力发射的球能绕弧 -> 左侧落入 -> 静止落球区
* 运行:node dev_test_scripts/debug/debug_retro_pinball_arch_search.js
*/
'use strict'
const fs = require('fs')
const path = require('path')
const vm = require('vm')
const HTML_PATH = path.join(__dirname, '..', '..', 'public', 'tools', 'thought_lab', 'labs', 'retro_pinball', 'index.html')
const html = fs.readFileSync(HTML_PATH, 'utf-8')
const grab = (a, b) => {
const s = html.indexOf(a), e = html.indexOf(b)
if (s < 0 || e < 0 || e <= s) throw new Error('未找到标记块: ' + a)
return html.slice(s, e)
}
const code =
grab('/* ==PHYSICS_CORE_START== */', '/* ==PHYSICS_CORE_END== */') + '\n' +
grab('/* ==TABLE_GEOM_START== */', '/* ==TABLE_GEOM_END== */')
const sandbox = { Math, console }
vm.createContext(sandbox)
vm.runInContext(code, sandbox)
const PC = sandbox.PhysicsCore
const { BALL_R, G_PX, SPRING_X, SPRING_TOP, LANE_X, buildTableStatics, slotIndexAt, SLOT_X0, SLOT_X1 } = sandbox
const DT = 1 / 360
function defaultParams() {
return {
g: G_PX * Math.sin(14 * Math.PI / 180),
e: 0.55, mu: 0.12, airK: 0.02, rollR: 0.25,
restV: 50, iterations: 8, slop: 0.3, correction: 0.8, maxV: 4200
};
}
// 三点定圆
function fitCircle(p1, p2, p3) {
const [x1, y1] = p1, [x2, y2] = p2, [x3, y3] = p3
const d = 2 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2))
if (Math.abs(d) < 1e-9) return null
const ux = ((x1 * x1 + y1 * y1) * (y2 - y3) + (x2 * x2 + y2 * y2) * (y3 - y1) + (x3 * x3 + y3 * y3) * (y1 - y2)) / d
const uy = ((x1 * x1 + y1 * y1) * (x3 - x2) + (x2 * x2 + y2 * y2) * (x1 - x3) + (x3 * x3 + y3 * y3) * (x2 - x1)) / d
const r = Math.hypot(x1 - ux, y1 - uy)
return { cx: ux, cy: uy, r: r }
}
const deg = a => a * 180 / Math.PI
function angleOf(c, p) { return deg(Math.atan2(p[1] - c.cy, p[0] - c.cx)) }
// 候选:三点(左出口 / 弧顶 / 右端),右端放在发射道上方,让球顺势切入
// laneTop = 发射道左壁顶 (LANE_X,190);右端在右外壁 x=442 附近
const candidates = []
function addCandidate(leftExit, top, rightEnd, label) {
const c = fitCircle(leftExit, top, rightEnd)
if (!c) return
// 角度范围:从右端 -> 顶部 -> 左出口(逆时针,y 向下故角度递减)
let aR = angleOf(c, rightEnd)
let aT = angleOf(c, top)
let aL = angleOf(c, leftExit)
// 归一化:弧要从左出口经弧顶(-90°)扫到右端,需 aL < -90 < aR(角度递增)
while (aL > aR) aL -= 360
candidates.push({ label, arch: { cx: c.cx, cy: c.cy, r: c.r, a0: aL, a1: aR }, _top: aT, c })
}
// 右端扫过发射道:从 (442,180) 到 (442,230),左出口落在钉阵左侧,顶部留 >=30 余量
addCandidate([56, 330], [230, 42], [442, 180], 'C1 右端(442,180)')
addCandidate([56, 330], [235, 45], [442, 200], 'C2 右端(442,200)')
addCandidate([60, 340], [235, 48], [442, 210], 'C3 右端(442,210)')
addCandidate([60, 340], [240, 50], [442, 220], 'C4 右端(442,220)')
addCandidate([50, 320], [225, 40], [442, 190], 'C5 右端(442,190)')
addCandidate([56, 335], [230, 44], [440, 195], 'C6 右端(440,195)')
function simulate(arch, v0, seconds, seedVx) {
sandbox.ARCH = arch
const table = buildTableStatics(5, 1)
const b = PC.createBall(SPRING_X, SPRING_TOP - BALL_R, BALL_R)
b.vy = -v0; b.vx = seedVx || 0
const tr = { minY: 1e9, enteredLeft: false, nan: false }
for (let t = 0; t < seconds; t += DT) {
PC.stepWorld([b], table.statics, defaultParams(), DT)
tr.minY = Math.min(tr.minY, b.y)
if (b.x < 90 && b.y > 200 && b.y < 450) tr.enteredLeft = true
if (!isFinite(b.x) || !isFinite(b.y)) { tr.nan = true; break }
}
tr.b = b
tr.sp = Math.hypot(b.vx, b.vy)
tr.inField = b.x >= SLOT_X0 && b.x <= SLOT_X1
tr.slot = slotIndexAt(b.x, b.y)
return tr
}
for (const cd of candidates) {
const a = cd.arch
const tr = simulate(a, 2900, 10, 5)
const topOk = tr.minY < 90
const ok = !tr.nan && topOk && tr.enteredLeft && tr.inField && tr.sp < 30
console.log(`${ok ? '★' : ' '} ${cd.label} cx=${a.cx.toFixed(0)} cy=${a.cy.toFixed(0)} r=${a.r.toFixed(0)} a:[${a.a0.toFixed(0)}..${a.a1.toFixed(0)}] topAng=${cd._top.toFixed(0)}`)
console.log(` minY=${tr.minY.toFixed(0)} enteredLeft=${tr.enteredLeft} final=(${tr.b.x.toFixed(0)},${tr.b.y.toFixed(0)}) sp=${tr.sp.toFixed(1)} inField=${tr.inField} slot=${tr.slot} nan=${tr.nan}`)
}