Files
Emil c7e86663d8
MVP checks / mvp (push) Canceled after 0s
Expand voxel gameplay, lighting, full-height streaming and world imports
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.
2026-09-17 02:10:53 +03:00

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();
});