Files
shanhe-puzzle/tests/overflow.test.cjs
codex-bot 4845826deb
Shanhe source and offline package checks / verify (pull_request) Failing after 5s
feat: sync Shanhe 2.2 source and add remote package verification
2026-11-01 17:13:46 +08:00

57 lines
5.7 KiB
JavaScript

const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm'),M=require('matter-js');
const {Game}=require('../src/game.js');
const box={window:{}};vm.runInNewContext(fs.readFileSync('src/maps.js','utf8'),box);const national=box.window.MAP_DATA.levels[0];
const square=id=>({id:String(id),name:'测试'+id,center:[50,50],rings:[[[0,0],[100,0],[100,100],[0,100]]]});
const fixture={id:'100',name:'支撑测试',width:200,height:300,regions:[square(0),square(1),square(2)],edges:[]};
function setup(level=fixture){const g=new Game(level,320,590,0,()=>{},()=>.5,1);g.status='playing';g.queue=[];return g;}
function advance(g,ms){for(let i=0;i<Math.ceil(ms/(1000/60));i++)g.tick(1000/60);}
test('真实江西贴左右墙的90种姿态均落底,不挂墙或空中判负',()=>{
const id=national.regions.findIndex(r=>r.id==='360000');let runs=0;
for(const side of [-1,1])for(const angle of [-.16,-.08,0,.08,.16])for(const vx of [0,4,9.5])for(const vy of [-8,-2,0]){
const g=setup(national),p=g.createGroup([id],{angle,tx:0,ty:0}),bounds=M.Bounds.create(p.body.vertices);
M.Body.translate(p.body,{x:side<0?-.5-bounds.min.x:g.w+.5-bounds.max.x,y:108-bounds.min.y});M.Body.setVelocity(p.body,{x:side*vx,y:vy});advance(g,20000);
assert.equal(g.status,'playing',JSON.stringify({side,angle,vx,vy}));assert.ok(p.body.bounds.max.y>g.h-5,'必须真正回到容器底部');g.destroy();runs++;
}assert.equal(runs,90);
});
test('缩放窗口后两侧墙仍然可顺畅下滑,底部保留摩擦',()=>{
const g=setup(national);g.resize(390,650);assert.equal(g.walls[1].friction,0);assert.equal(g.walls[2].friction,0);assert.ok(g.walls[0].friction>0);g.destroy();
});
test('红线上方孤立休眠地块会恢复下落,原地悬空不算堆积',()=>{
const g=setup(),p=g.createGroup([0],{tx:50,ty:35,angle:0});M.Sleeping.set(p.body,true);advance(g,10000);
assert.ok(g.unsupportedWakeups>0);assert.equal(g.status,'playing');assert.equal(g.overflow,0);assert.ok(p.body.bounds.max.y>g.h-5);g.destroy();
});
test('休眠堆积通过多层真实接触连到底部,离地后立即失去支撑',()=>{
const g=setup();for(let i=0;i<3;i++){const p=g.createGroup([i],{tx:0,ty:0,angle:0});M.Body.setPosition(p.body,{x:160,y:g.h-(i+.5)*100*g.scale+.05*i});M.Sleeping.set(p.body,true);}
g.updateSupport();assert.ok(g.groups.every(p=>p.grounded));
g.groups.forEach(p=>M.Body.translate(p.body,{x:0,y:-50}));g.updateSupport();assert.ok(g.groups.every(p=>!p.grounded&&!p.body.isSleeping));g.destroy();
});
test('只贴住落地地块的侧面不算受支撑',()=>{
const g=setup(),lower=g.createGroup([0],{tx:0,ty:0,angle:0}),upper=g.createGroup([1],{tx:0,ty:0,angle:0});
const size=100*g.scale;M.Body.setPosition(lower.body,{x:100,y:g.h-size/2});M.Body.setPosition(upper.body,{x:100+size-.05,y:g.h-size});M.Sleeping.set(lower.body,true);M.Sleeping.set(upper.body,true);
g.updateSupport();assert.equal(lower.grounded,true);assert.equal(upper.grounded,false);assert.equal(upper.body.isSleeping,false);g.destroy();
});
test('复合刚体包围盒内的空洞不充当支撑面',()=>{
const rect=(x,y,w,h)=>[[x,y],[x+w,y],[x+w,y+h],[x,y+h]];
const outer={id:'0',name:'带洞外框',center:[50,150],rings:[rect(0,0,100,300)],holes:[rect(20,20,60,260)],physics:[rect(0,0,20,300),rect(80,0,20,300),rect(20,0,60,20),rect(20,280,60,20)]};
const inner={id:'1',name:'洞中悬空块',center:[50,150],rings:[rect(40,140,20,20)]};const g=setup({...fixture,regions:[outer,inner]}),a=g.createGroup([0],{tx:100,ty:0,angle:0});
const dy=g.h-M.Bounds.create(a.body.vertices).max.y;M.Body.translate(a.body,{x:0,y:dy});const b=g.createGroup([1],{tx:100,ty:dy,angle:0});M.Sleeping.set(a.body,true);M.Sleeping.set(b.body,true);g.updateSupport();
assert.equal(a.grounded,true);assert.equal(b.grounded,false);assert.equal(b.body.isSleeping,false);g.destroy();
});
test('合并后的边界修正不抬高或平移无关地块',()=>{
const g=setup(),merged=g.createGroup([0,1],{tx:0,ty:0,angle:0}),other=g.createGroup([2],{tx:180,ty:160,angle:0});
M.Body.translate(merged.body,{x:-80,y:g.h-merged.body.bounds.max.y+35});M.Sleeping.set(other.body,true);const position={...other.body.position};g.fitGroup(merged);
assert.deepEqual(other.body.position,position);assert.equal(other.body.isSleeping,true);const b=M.Bounds.create(merged.body.vertices);assert.ok(b.min.x>=9.9&&b.max.y<=g.h-9.9);g.destroy();
});
test('自然下落形成的满堆仍会判负,不受接触求解器残余速度干扰',()=>{
const level={id:'100000',name:'满堆测试',width:1000,height:1000,regions:Array.from({length:20},(_,i)=>({id:String(i),name:'宽块'+i,center:[500,150],rings:[[[0,0],[1000,0],[1000,300],[0,300]]]})),edges:[]},g=new Game(level,320,590,3,()=>{},()=>.5,0);g.start();advance(g,60000);
assert.equal(g.status,'lost');assert.ok(g.groups.some(p=>p.grounded&&p.settled&&p.overflow>2400));assert.ok(g.dropped>10&&g.clock>30000);g.destroy();
});
test('不同堆顶的时间不串用,同一组恢复后重新连续计时',()=>{
const level={...fixture,regions:Array.from({length:8},(_,i)=>square(i))},g=new Game(level,420,560,0,()=>{},()=>.5,0),tops=[];g.status='playing';g.queue=[];
for(let col=0;col<2;col++)for(let row=0;row<4;row++){const p=g.createGroup([col*4+row],{tx:0,ty:0,angle:0});M.Body.setPosition(p.body,{x:108+col*204,y:g.h-(row+.5)*100*g.scale+.05*row});M.Body.setStatic(p.body,true);if(row===3)tops.push(p);}
g.clock=6000;tops[1].born=2600;advance(g,1500);assert.equal(g.status,'playing');assert.ok(tops[0].overflow>1300);
M.Body.translate(tops[0].body,{x:0,y:150});advance(g,1500);assert.equal(g.status,'playing');assert.equal(tops[0].overflow,0);assert.ok(tops[1].overflow>1200&&tops[1].overflow<1600);
advance(g,1200);assert.equal(g.status,'lost');g.destroy();
});