import { MAX_VIEW_WIDTH, VIEW_HEIGHT, MAX_VIEW_CELLS, MAX_VIEW_COORDINATE, WORLD_MIN_Y, WORLD_MAX_Y } from "./view-distance.js"; const triple=p=>Array.isArray(p)&&p.length===3&&p.every(Number.isSafeInteger); const sectionId=p=>p.join(','); export function decodeSection(record) { if(!triple(record?.section)||record.section.some(v=>Math.abs(v*16)>MAX_VIEW_COORDINATE))throw Error('Invalid section coordinates'); const {palette,runs}=record; if(!Array.isArray(palette)||!palette.length||palette.length>4096||!palette.every(v=>Number.isInteger(v)&&v>=0&&v<=0xffffffff))throw Error('Invalid section palette'); if(!Array.isArray(runs)||!runs.length||runs.length>8192||runs.length%2)throw Error('Invalid section runs'); const uniform = runs.length === 2 && runs[0] === 4096 && Number.isInteger(runs[1]) && runs[1] >= 0 && runs[1] < palette.length; if (uniform) return {section:[...record.section],cells:new Uint32Array([palette[runs[1]]])}; const cells=new Uint32Array(4096);let offset=0; for(let i=0;i=palette.length||offset+count>4096)throw Error('Invalid section run'); cells.fill(palette[index],offset,offset+count);offset+=count; } if(offset!==4096)throw Error('Incomplete section'); return {section:[...record.section],cells}; } export function sectionInBounds(section,bounds) { return section.every((v,i)=>v*16>=bounds.min[i]&&v*16+15<=bounds.max[i]); } export function applySectionView(view,message) { if(message.world!==view.world)return 'ignored'; if(!Number.isSafeInteger(message.generation)||message.generation<=view.viewGeneration)return 'ignored'; if(message.revision!==view.revision||!triple(message.view_center)||message.view_center.some(v=>v%16)||!triple(message.view_min)||!triple(message.view_max)||!Array.isArray(message.unload))return 'resync'; const bounds={min:message.view_min,max:message.view_max}; if(message.full_height===true){ if(bounds.min[1]!==WORLD_MIN_Y||bounds.max[1]!==WORLD_MAX_Y)return 'resync'; bounds.fullHeight=true; } let volume=1; for(let i=0;i<3;i++) {const length=bounds.max[i]-bounds.min[i]+1;if(length<=0||length>(i===1?VIEW_HEIGHT:MAX_VIEW_WIDTH)||bounds.min[i]%16||(bounds.max[i]+1)%16||Math.abs(bounds.min[i])>MAX_VIEW_COORDINATE||Math.abs(bounds.max[i])>MAX_VIEW_COORDINATE||(!(i===1&&bounds.fullHeight)&&(message.view_center[i]bounds.max[i])))return 'resync';volume*=length;} if(volume>MAX_VIEW_CELLS||message.total_sections!==undefined&&message.total_sections!==volume/4096)return 'resync'; const unload=new Set(); for(const p of message.unload) {if(!triple(p)||sectionInBounds(p,bounds)||unload.has(sectionId(p)))return 'resync';unload.add(sectionId(p));} // Authoritative bounds determine departing sections; explicit unloads are // checked above but cannot accidentally leave old sections resident. for(const id of view.blocks.loadedSectionKeys())if(!sectionInBounds(id.split(',').map(Number),bounds))unload.add(id); for(const id of unload) {view.blocks.deleteSection(id);view.loadedSections.delete(id);} view.viewCenter=[...message.view_center];view.viewBounds=bounds;view.viewGeneration=message.generation; view.totalSections=volume/4096; return {unload:[...unload].map(id=>id.split(',').map(Number))}; } export function applySectionBatch(view,message) { if(message.world!==view.world||message.generation!==view.viewGeneration)return {status:'ignored'}; if(message.revision!==view.revision||!Array.isArray(message.sections)||message.sections.length>128)return {status:'resync'}; const decoded=[],seen=new Set(),columns=[]; try { if(message.columns!==undefined&&(!Array.isArray(message.columns)||message.columns.length>128))throw Error('Invalid skylight columns'); for(const record of message.sections) { const result=decodeSection(record),id=sectionId(result.section); if(seen.has(id)||!sectionInBounds(result.section,view.viewBounds))throw Error('Section outside view'); seen.add(id);decoded.push(result); } for(const column of message.columns||[]) { if(!Array.isArray(column.column)||column.column.length!==2||!column.column.every(Number.isSafeInteger)||!Array.isArray(column.heights)||column.heights.length!==256||!column.heights.every(v=>Number.isInteger(v)&&v>=-32768&&v<=32767))throw Error('Invalid skylight column'); if(!column.column.every((v,i)=>v*16>=view.viewBounds.min[i*2]&&v*16+15<=view.viewBounds.max[i*2]))throw Error('Skylight column outside view'); columns.push({column:[...column.column],heights:new Int16Array(column.heights)}); } } catch {return {status:'resync'};} for(const {section,cells} of decoded) {const id=sectionId(section);view.blocks.setSection(id,cells);view.loadedSections.add(id);} return {status:'applied',sections:decoded,columns}; }