Files
shanhe-puzzle/tests/overlap.test.cjs
T
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

12 lines
5.6 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,GROWTH_MS}=require('../src/game.js');const box={window:{}};vm.runInNewContext(fs.readFileSync('src/maps.js','utf8'),box);const hubei=box.window.MAP_DATA.levels.find(l=>l.id==='420000');
const rect=(id,x,w=100,h=100)=>({id:String(id),name:'块'+id,center:[x+w/2,h/2],rings:[[[x,0],[x+w,0],[x+w,h],[x,h]]]});
function create(regions=[rect(0,0),rect(1,100),rect(2,300,10)],edges=[[0,1,0]]){const g=new Game({id:'test',name:'防穿插',width:1000,height:1000,regions,edges},420,620,4,()=>{},()=>.5,1);g.status='playing';g.queue=[];return g;}
function depth(a,b){let d=0;for(const x of (a.parts.length>1?a.parts.slice(1):[a]))for(const y of (b.parts.length>1?b.parts.slice(1):[b])){if(!M.Bounds.overlaps(x.bounds,y.bounds))continue;const c=M.Collision.collides(x,y);if(c)d=Math.max(d,c.depth);}return d;}
test('吸附目标位置被无关地块占住时暂缓合并,不瞬移第三块或扣改得分',()=>{const g=create(),pose={tx:80,ty:200,angle:0},a=g.createGroup([0],pose),b=g.createGroup([1],{...pose,tx:102}),c=g.createGroup([2],pose);M.Body.setPosition(c.body,{x:b.body.bounds.min.x-5,y:b.body.position.y});assert.ok(depth(a.body,c.body)<.01&&depth(b.body,c.body)<.01);const pos={...c.body.position},bodyCount=M.Composite.allBodies(g.engine.world).length;assert.equal(g.merge(a,b,[0,1,0]),null);assert.equal(g.groups.length,3);assert.equal(g.merges,0);assert.equal(g.score,0);assert.deepEqual(c.body.position,pos);assert.equal(M.Composite.allBodies(g.engine.world).length,bodyCount,'试放的碰撞体必须清理');M.Body.translate(c.body,{x:150,y:180});g.clock+=150;assert.ok(g.merge(a,b,[0,1,0]));assert.equal(g.groups.length,2);g.destroy();});
test('长大遇到第三块时保留当前尺寸,有空间后继续长大而不穿插',()=>{const regions=Array.from({length:9},(_,i)=>rect(i,i*100)),g=create(regions,[]),a=g.createGroup([0,1,2,3,4,5,6,7],{tx:40,ty:220,angle:0},1),b=g.createGroup([8],{tx:40,ty:220,angle:0});M.Body.setPosition(b.body,{x:a.body.bounds.max.x+(b.body.bounds.max.x-b.body.bounds.min.x)/2+.1,y:a.body.position.y});a.growth={from:1,to:1.06,started:0};g.clock=GROWTH_MS;g.stepGrowth();assert.equal(a.factor,1);assert.ok(a.growth);assert.ok(depth(a.body,b.body)<.2);M.Body.translate(b.body,{x:80,y:150});g.clock+=GROWTH_MS;g.stepGrowth();assert.ok(Math.abs(a.factor-1.06)<1e-8);assert.equal(a.growth,null);g.destroy();});
test('容器入口已有图块时新块从其上方进入,不生成在旧块内部',()=>{const g=create([rect(0,0),rect(1,0)],[]);g.status='ready';g.queue=[0,1];g.start();g.spawn();const a=g.groups[0];g.spawn();const b=g.groups[1];assert.equal(g.dropped,2);assert.ok(depth(a.body,b.body)<.01);assert.ok(b.entering);assert.ok(b.body.bounds.max.y<a.body.bounds.min.y);for(let i=0;i<60;i++){g.tick(1000/60);assert.ok(depth(a.body,b.body)<=2.001);}g.destroy();});
test('高速碰撞的修正保留相对转角,限制新穿透且不把碎片自动回正',()=>{const g=create([rect(0,0),rect(1,200)],[]),a=g.createGroup([0],{tx:40,ty:200,angle:.4}),b=g.createGroup([1],{tx:40,ty:200,angle:.4});M.Body.setPosition(a.body,{x:150,y:250});M.Body.setPosition(b.body,{x:210,y:250});g.rememberPoses();M.Body.setPosition(a.body,{x:188,y:250});M.Body.setAngle(a.body,.42);assert.ok(depth(a.body,b.body)>3);g.guardPenetrations();assert.ok(depth(a.body,b.body)<=2.001);assert.ok(a.body.angle>=.4&&a.body.angle<=.42);assert.ok(Math.abs(a.body.angle)>.3);g.destroy();});
test('缩短视口高度时整体缩放平移保留地块间距,不逐块压进彼此内部',()=>{const g=create([rect(0,0),rect(1,200)],[]),a=g.createGroup([0],{tx:40,ty:200,angle:.2}),b=g.createGroup([1],{tx:40,ty:200,angle:.2});M.Body.setPosition(a.body,{x:200,y:520});M.Body.setPosition(b.body,{x:200,y:460});g.score=321;g.dropped=2;const before=Math.hypot(a.body.position.x-b.body.position.x,a.body.position.y-b.body.position.y)/g.scale;g.resize(420,280);const [x,y]=g.groups;assert.ok(depth(x.body,y.body)<.01);assert.ok(Math.abs(Math.hypot(x.body.position.x-y.body.position.x,x.body.position.y-y.body.position.y)/g.scale-before)<1e-6);assert.equal(x.body.angle,.2);assert.equal(g.score,321);assert.equal(g.dropped,2);g.destroy();});
test('湖北8组45秒快速落子与连续晃动不再出现超过3px的穿插,碎片仍自由运动',()=>{const results=[];for(let seed=1;seed<=8;seed++){let random=seed;const rng=()=>{random=(random*1664525+1013904223)>>>0;return random/4294967296;},g=new Game(hubei,340,560,4,()=>{},rng,1);g.start();let max=0,travel=0,samples=0;for(let i=0;i<2700&&g.status==='playing';i++){if(g.pending&&i%75===0){g.movePending(30+rng()*280,g.pending.serial);g.holdPending(g.pending.serial);g.releasePending(g.pending.serial);}if(i%13===0&&g.groups.length)g.swipe({x:40+rng()*260,y:260+rng()*270},{x:(rng()-.5)*140,y:(rng()-.7)*140},60);const prior=g.groups.map(group=>({group,x:group.body.position.x,y:group.body.position.y}));g.tick(1000/60);if(i>2100)for(const p of prior)if(g.groups.includes(p.group)){travel+=Math.hypot(p.group.body.position.x-p.x,p.group.body.position.y-p.y);samples++;}if(i%12===0)for(let a=0;a<g.groups.length;a++)for(let b=0;b<a;b++)max=Math.max(max,depth(g.groups[a].body,g.groups[b].body));}assert.ok(max<=3,'seed '+seed+' depth '+max);assert.equal(g.dropped,17);assert.ok(travel/samples>.3,'不能通过冻结地块避免碰撞');results.push({seed,maxPenetration:max,meanTravelPerFrame:travel/samples,dropped:g.dropped});g.destroy();}fs.writeFileSync('artifacts/overlap-regression.json',JSON.stringify({passed:true,scope:'Deterministic Matter physics, logical px; not native performance acceptance',results},null,2));});