import test from 'node:test'; import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {SectionVoxelMap} from '../section-voxel-map.js'; import {captureEditMesh,buildEditMesh} from '../edit-mesh.js'; import {EditMeshController} from '../edit-mesh-controller.js'; import {TerrainState} from '../terrain-state.js'; const properties=JSON.parse(await readFile(new URL('../light-properties.json',import.meta.url))); const cube={min:[0,0,0],max:[1,1,1]}; const materials=new Map([[1,{state:'minecraft:stone',color:[128,128,128],render:[cube]}],[2,{state:'minecraft:sea_lantern',color:[230,245,250],render:[cube],light:15}],[3,{state:'minecraft:glass',color:[160,210,230],render:[cube],opacity:0.3}]]); test('local edit geometry matches full-world meshing at boundaries and distant coordinates without scanning the world',()=>{ for(const base of [0,-32,29999840]) { const state=new TerrainState(properties),initial=new Int32Array([base+15,2,0,1,base+16,2,0,1,base+17,2,0,2,base+16,3,0,3]); state.sync({epoch:1,version:1,reset:true,compact:true,initial,materials:[...materials],textures:[],bounds:{min:[base-2,-2,-3],max:[base+32,6,3]}});state.light(); // The bulk light calculation is deliberately not run after this removal. state.blocks.delete(`${base+15},2,0`); const current=state.mesh({epoch:1,version:1,id:`${base/16+1},0,0`,job:1}); state.blocks[Symbol.iterator]=()=>{throw Error('No global iteration permitted');}; state.blocks.sectionEntries=()=>{throw Error('No global section iteration permitted');}; const packet=captureEditMesh(current.id,state.blocks,state.materials,state.textureLayers,state.field); assert.ok(packet.sections.length<=27); assert.ok(packet.light.data.length<=24**3*4); const before=state.blocks.getSectionCells(current.id).byteLength; const transfers=[...packet.sections.map(s=>s.cells.buffer),packet.light.data.buffer,packet.light.blockLevels.buffer,packet.light.skyLevels.buffer]; const transferred=structuredClone(packet,{transfer:transfers}); assert.equal(state.blocks.getSectionCells(current.id).byteLength,before,'live voxel buffers are never detached'); assert.ok(state.field.data.byteLength>0,'live lighting buffers remain usable'); const mesh=buildEditMesh(transferred,properties); assert.deepEqual(mesh.vertices,current.vertices); assert.deepEqual(mesh.transparent,current.transparent); assert.deepEqual(mesh.origin,current.origin); } }); test('breaks and placements rebuild current geometry while full lighting is still pending',()=>{ const blocks=new SectionVoxelMap();blocks.set('15,2,0',1);blocks.set('16,2,0',1); const mesh=()=>buildEditMesh(captureEditMesh('1,0,0',blocks,materials,new Map(),null),properties); const before=mesh();blocks.delete('15,2,0');const removed=mesh(); assert.equal(removed.vertices.length,before.vertices.length+120,'the adjacent face appears'); blocks.set('15,2,0',2);assert.equal(mesh().vertices.length,before.vertices.length); const placed=buildEditMesh(captureEditMesh('0,0,0',blocks,materials,new Map(),null),properties); assert.ok(placed.vertices.length>0); assert.equal(placed.vertices[15],1,'lamp emission is immediately visible'); }); class Worker { packets=[]; postMessage(packet,transfers){this.packets.push(structuredClone(packet,{transfer:transfers}));} terminate(){this.terminated=true;} reply(packet){this.onmessage({data:{...packet,vertices:new Float32Array(),transparent:new Float32Array(),preview:true}});} } function fixture(){const worker=new Worker(),captured=[]; const controller=new EditMeshController({workerFactory:()=>worker,capture:id=>{captured.push(id);return {id,sections:[],materials:[],textures:[],light:null};}}); return {worker,controller,captured};} test('independent edit queue is bounded, coalesces newer edits and rejects stale/reset/unloaded results',()=>{ const {worker,controller:c}=fixture(); c.request(['0,0,0']);c.pump();const old=worker.packets[0]; c.request(['0,0,0']);worker.reply(old);assert.equal(c.ready.size,0); c.pump();const latest=worker.packets[1];worker.reply(latest);assert.equal(c.ready.size,1); c.request(['1,0,0','2,0,0']);c.pump();worker.reply(worker.packets.at(-1));c.pump(); assert.equal(c.ready.size,2);assert.equal(worker.packets.length,3,'two results cap memory before upload'); c.cancel('0,0,0');assert.equal(c.isCurrent(latest),false);c.pump();const unloaded=worker.packets.at(-1); c.cancel('2,0,0');worker.reply(unloaded);assert.equal(c.ready.has('2,0,0'),false); c.request(['3,0,0']);c.pump();const reset=worker.packets.at(-1);c.reset();worker.reply(reset); assert.equal(c.ready.size,0);assert.equal(c.pending.size,0); c.dispose();assert.equal(worker.terminated,true); }); test('a ready edit stays valid across unrelated terrain/light work until its own geometry changes',()=>{ const {worker,controller:c}=fixture();c.request(['0,0,0']);c.pump();worker.reply(worker.packets[0]); const result=c.ready.get('0,0,0');c.request(['8,0,0']); assert.equal(c.isCurrent(result),true);c.request(['0,0,0']);assert.equal(c.isCurrent(result),false); c.dispose(); });