MVP checks / mvp (push) Canceled after 0s
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
76 lines
5.0 KiB
JavaScript
76 lines
5.0 KiB
JavaScript
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();
|
|
});
|