/* ============================================================ * debug_physics_balls_volley_loop.js · 物理弹球 · 大连发死循环回归 * 复现场景:队列 15 球连发。旧 bug:连发期间回收球回队使 queue 永不空, * 状态永远停在 firing(无限连发循环,回合不推进)。 * 断言:1) firing 在 ~3s 内结束 2) 60s 内回合推进(turn>=2 或 over) * 运行:node dev_test_scripts/debug/debug_physics_balls_volley_loop.js * ============================================================ */ 'use strict'; const fs = require('fs'); const path = require('path'); const vm = require('vm'); const LAB = path.join(__dirname, '..', '..', 'public', 'tools', 'thought_lab', 'labs', 'physics_balls'); const ctx = { Math, console, PBAudio: { fire() {}, hit() {}, wall() {}, item() {}, breakBlock() {}, over() {} }, PBStorage: { getEscape() { return 50; } } }; vm.createContext(ctx); for (const f of ['pb_physics.js', 'pb_rules.js', 'pb_game.js']) { vm.runInContext(fs.readFileSync(path.join(LAB, f), 'utf8'), ctx, { filename: f }); } const G = ctx.PBGame; let ok = true; for (const n of [15, 25]) { const g = G.create(); G.startGame(g); g.queue = Array(n).fill(true); // 15/25 个大球,复现截图局面 g.aimAngle = 0.2; G.fire(g); let firingEnd = -1, turnAdvanced = false; for (let i = 1; i <= 60 * 60; i++) { G.update(g, 1 / 60); if (firingEnd < 0 && g.state !== 'firing') firingEnd = i / 60; if (g.turn >= 2 || g.state === 'over') { turnAdvanced = true; break; } } const p1 = firingEnd > 0 && firingEnd <= n * 0.13 + 1.5; console.log((p1 ? 'PASS ' : 'FAIL ') + n + '球连发 firing 结束于 ' + (firingEnd < 0 ? '未结束(死循环)' : firingEnd.toFixed(1) + 's')); console.log((turnAdvanced ? 'PASS ' : 'FAIL ') + n + '球连发 回合推进 turn=' + g.turn + ' state=' + g.state + ' queue=' + g.queue.length); ok = p1 && turnAdvanced && ok; } process.exit(ok ? 0 : 1);