// scripts/runtime_min_test.js — runtime 引擎本地最小运行验证(Node 18+) // // 用法:node scripts/runtime_min_test.js // 功能:vm 模拟 window 加载 wwwroot/runtime.js,逐项验证 W-10a~W-10i, // 输出 PASS/FAIL 与汇总,fail=0 时退出码 0(可接入 CI/部署门禁)。 // // 预期输出(全 PASS): // PASS W-10a 实体树构建 (entities=5) // PASS W-10i 独立模式降级初始化 // PASS W-10c/W-10d 事件路由到实体脚本执行 (hero.state=done) // PASS W-10b 实体状态机流转 (idle->active->done) // PASS W-10e 定时器按间隔触发 (box1.y 增大) // PASS W-10f 碰撞检测触发 onCollide 日志 // PASS W-10g 暂停/恢复 // PASS W-10h 错误降级不崩溃 (errorCount 递增, running 仍 true) // PASS W-10h 失败实体标记 failed // ==== RESULT: pass=9 fail=0 ==== 'use strict'; const fs = require('fs'); const path = require('path'); const vm = require('vm'); const jsPath = path.join(__dirname, '..', 'wwwroot', 'runtime.js'); const src = fs.readFileSync(jsPath, 'utf8'); const sandbox = { window: {}, console: console, Date: Date, setTimeout: setTimeout, clearTimeout: clearTimeout, }; sandbox.window.window = sandbox.window; vm.createContext(sandbox); vm.runInContext(src, sandbox); const RuntimeEngine = sandbox.window.RuntimeEngine; const sleep = function (ms) { return new Promise(function (r) { setTimeout(r, ms); }); }; let pass = 0, fail = 0; function check(name, cond, extra) { if (cond) { pass++; console.log('PASS ' + name + (extra ? ' | ' + extra : '')); } else { fail++; console.log('FAIL ' + name + (extra ? ' | ' + extra : '')); } } (async function () { // ---- W-10i + W-10a:独立模式初始化 / 实体树 ---- const rt = new RuntimeEngine({ worldId: 't1', tickMs: 8 }); const st = rt.initWorld(null); check('W-10a 实体树构建', rt.getEntities().length === 5, 'entities=' + rt.getEntities().length); check('W-10i 独立模式降级初始化', st.mode === 'independent' && st.degraded === true && st.running === false, 'mode=' + st.mode + ' degraded=' + st.degraded); const evtLog = []; rt.on('log', function (m) { evtLog.push(m.message); }); // ---- W-10c/W-10d:点击 hero -> click 事件路由到脚本执行 ---- rt.trigger('hero', 'click'); rt.processEvents(); const hero = rt.getEntity('hero'); check('W-10c/W-10d 事件路由到实体脚本执行', hero.state === 'done', 'hero.state=' + hero.state); check('W-10b 实体状态机流转', evtLog.some(function (m) { return m.indexOf('路由事件 click') >= 0; }) && hero.state === 'done', 'log含路由记录'); // ---- W-10e 定时器:box1 绑定 script_timer,3s 语义 -> 测试用 10ms ---- rt.start(); const box1 = rt.getEntity('box1'); const y0 = box1.y; rt.addTimer(10, function () { rt.dispatchEvent('timer', 'box1', {}); }); await sleep(120); // 多个 tick 周期,定时器应已触发且脚本已路由 check('W-10e 定时器按间隔触发', rt.getEntity('box1').y > y0, 'box1.y=' + rt.getEntity('box1').y + ' (from ' + y0 + ')'); // ---- W-10f 碰撞检测:hero 移入 box1 包围盒 ---- const boxNow = rt.getEntity('box1'); hero.x = boxNow.x; hero.y = boxNow.y; const before = evtLog.length; rt.checkCollisions(); check('W-10f 碰撞检测触发', evtLog.length > before && evtLog.slice(before).some(function (m) { return m.indexOf('碰撞') >= 0; }), evtLog.slice(before).join(';')); // ---- W-10g 暂停/恢复 ---- rt.pause(); check('W-10g 暂停', rt.getStatus().paused === true); rt.resume(); check('W-10g 恢复', rt.getStatus().paused === false && rt.getStatus().running === true); // ---- W-10h 错误降级:点击 box2(script_fail 故意抛错) ---- const errBefore = rt.getStatus().errorCount; rt.trigger('box2', 'click'); rt.processEvents(); const st2 = rt.getStatus(); check('W-10h 错误降级不崩溃', st2.errorCount > errBefore && st2.running === true, 'errors=' + st2.errorCount + ' running=' + st2.running); check('W-10h 失败实体标记 failed', rt.getEntity('box2').state === 'failed', 'box2.state=' + rt.getEntity('box2').state); rt.stop(); console.log('\n==== RESULT: pass=' + pass + ' fail=' + fail + ' ===='); process.exit(fail === 0 ? 0 : 1); })().catch(function (e) { console.error('TEST CRASH:', e); process.exit(2); });