import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {World,DT} from '../engine.js?v=45'; import {fieldResourceIntakeKgPerDay,interactionEdge} from '../ecology/feeding.js?v=45'; const quantile=(a,p)=>a.sort((x,y)=>x-y)[Math.floor((a.length-1)*p)]||0; // 1) Module contract: catches missing named exports before browser release. assert.equal(typeof fieldResourceIntakeKgPerDay,'function'); const edge=interactionEdge('primary-consumer','producer'); assert.ok(fieldResourceIntakeKgPerDay(8,.3,edge)>fieldResourceIntakeKgPerDay(8,.02,edge)); // 2) User water edits must remain exact. { const w=new World(77), edited=w.water.map((v,i)=>({...v,r:i===0?v.r*.98:v.r})); assert.equal(w.setWater(edited),true); assert.deepEqual(w.water,edited); } // 3) Free-movement smoke test: no spawn lock, widespread freezing, or ping-pong reversal. const w=new World(481516), start=new Map(w.animals.map(a=>[a.id,{x:a.x,y:a.y}])); const track=new Map(w.animals.map(a=>[a.id,{x:a.x,y:a.y,still:0,ticks:0,dx:0,dy:0,moves:0,rev:0}])); for(let step=0;step<3*24;step++){ w.step(); for(const a of w.animals){ if(a.dead||!track.has(a.id))continue; const t=track.get(a.id),dx=a.x-t.x,dy=a.y-t.y,d=Math.hypot(dx,dy),last=Math.hypot(t.dx,t.dy); t.ticks++; if(d<1e-6)t.still++; if(d>.1){t.moves++;if(last>.1&&(dx*t.dx+dy*t.dy)/(d*last)<-.7)t.rev++;t.dx=dx;t.dy=dy} t.x=a.x;t.y=a.y; } } const alive=w.animals.filter(a=>!a.dead&&start.has(a.id)); const displacement=alive.map(a=>Math.hypot(a.x-start.get(a.id).x,a.y-start.get(a.id).y)); const still=alive.map(a=>track.get(a.id).still/track.get(a.id).ticks); const reversal=alive.map(a=>track.get(a.id).rev/Math.max(1,track.get(a.id).moves)); assert.ok(quantile(displacement,.1)>100); assert.ok(quantile(displacement,.5)>350); assert.ok(quantile(still,.9)<.3); assert.ok(quantile(reversal,.9)<.22); // 4) Short population smoke: births and at least one net-increase day must be possible. const pop=new World(481516), firstNextId=pop.nextId;let previous=pop.stats().population,sawIncrease=false; for(let day=0;day<14;day++){ for(let i=0;i<24;i++)pop.step(); const now=pop.stats().population;if(now>previous)sawIncrease=true;previous=now; } assert.ok(pop.nextId>firstNextId); assert.ok(sawIncrease); assert.ok(pop.animals.every(a=>[a.x,a.y,a.energy,a.age,a.structuralMassKg].every(Number.isFinite))); // 5) Removed constraints/workarounds must not return. const productionFiles=['engine.js','worker.js','ecology/movement.js']; const code=(await Promise.all(productionFiles.map(f=>readFile(new URL('../'+f,import.meta.url),'utf8')))).join('\n'); assert.ok(!/homeRange|homeX|homeY|counter-torque|rotationIndex|angularScale/i.test(code)); assert.ok(!/set-interactions|set-climate|run-batch|schedule-disturbance/i.test(code)); console.log(JSON.stringify({ok:true,dtDays:DT,animals:pop.stats().population,births:pop.nextId-firstNextId},null,2));