This commit is contained in:
33333-33333 2026-06-02 21:43:57 +09:00
commit 8ee6a87d44
7 changed files with 2317 additions and 560 deletions

View file

@ -168,8 +168,20 @@
return out;
}
function normalizeEncodedPlane(value, size, emptyChar = '.') {
const total = Math.max(1, Number(size) || 1) ** 2;
function assetWidth(asset) {
const size = Math.max(1, Number(asset?.size) || 16);
return Math.max(1, Number(asset?.width ?? asset?.w ?? size) || size);
}
function assetHeight(asset) {
const size = Math.max(1, Number(asset?.size) || 16);
return Math.max(1, Number(asset?.height ?? asset?.ht ?? size) || size);
}
function normalizeEncodedPlane(value, width, emptyChar = '.', height = width) {
const w = Math.max(1, Number(width) || 1);
const h = Math.max(1, Number(height) || w);
const total = w * h;
const source = typeof value === 'string' ? value : Array.isArray(value) ? value.map((v) => v || emptyChar).join('') : '';
return (source + emptyChar.repeat(total)).slice(0, total);
}
@ -184,15 +196,17 @@
return best;
}
function cropPlane(encoded, size, emptyChar = '.', options = {}) {
const text = normalizeEncodedPlane(encoded, size, emptyChar);
let minX = size;
let minY = size;
function cropPlane(encoded, width, emptyChar = '.', options = {}, height = width) {
const w0 = Math.max(1, Number(width) || 1);
const h0 = Math.max(1, Number(height) || w0);
const text = normalizeEncodedPlane(encoded, w0, emptyChar, h0);
let minX = w0;
let minY = h0;
let maxX = -1;
let maxY = -1;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (text[y * size + x] !== emptyChar) {
for (let y = 0; y < h0; y++) {
for (let x = 0; x < w0; x++) {
if (text[y * w0 + x] !== emptyChar) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
@ -204,7 +218,7 @@
const w = maxX - minX + 1;
const h = maxY - minY + 1;
let cropped = '';
for (let y = minY; y <= maxY; y++) cropped += text.slice(y * size + minX, y * size + minX + w);
for (let y = minY; y <= maxY; y++) cropped += text.slice(y * w0 + minX, y * w0 + minX + w);
const candidates = [
{ b: [minX, minY, w, h], e: 'raw', v: cropped },
{ b: [minX, minY, w, h], e: 'rle', v: rleEncode(cropped) }
@ -213,9 +227,15 @@
return chooseSmallest(candidates);
}
function expandPlane(packed, size, emptyChar = '.', baseEncoded = null) {
const total = Math.max(1, Number(size) || 1) ** 2;
function expandPlane(packed, width, emptyChar = '.', baseEncoded = null, height = width) {
const w0 = Math.max(1, Number(width) || 1);
const h0 = Math.max(1, Number(height) || w0);
const total = w0 * h0;
const out = Array(total).fill(emptyChar);
if (baseEncoded) {
const base = normalizeEncodedPlane(baseEncoded, w0, emptyChar, h0);
for (let i = 0; i < Math.min(out.length, base.length); i++) out[i] = base[i] || emptyChar;
}
if (!packed || !packed.b) return out.join('');
const [x0, y0, w, h] = packed.b.map((v) => Math.max(0, Number(v) || 0));
let value = '';
@ -227,27 +247,31 @@
const src = y * w + x;
const dx = x0 + x;
const dy = y0 + y;
if (dx >= 0 && dy >= 0 && dx < size && dy < size && src < value.length) out[dy * size + dx] = value[src] || emptyChar;
if (dx >= 0 && dy >= 0 && dx < w0 && dy < h0 && src < value.length) out[dy * w0 + dx] = value[src] || emptyChar;
}
}
return out.join('');
}
function planeForAsset(asset) {
const size = Math.max(1, Number(asset?.size) || 16);
return normalizeEncodedPlane(asset?.faces?.right || asset?.pixels || '', size, '.');
const w = assetWidth(asset);
const h = assetHeight(asset);
return normalizeEncodedPlane(asset?.faces?.right || asset?.pixels || '', w, '.', h);
}
function selectPixelPlanePack(asset, assetMap) {
const size = Math.max(1, Number(asset.size) || 16);
const w = assetWidth(asset);
const h = assetHeight(asset);
const right = planeForAsset(asset);
return cropPlane(right, size, '.', { bitPack: true });
return cropPlane(right, w, '.', { bitPack: true }, h);
}
function packAsset(asset, assetMap = null) {
const size = Math.max(1, Number(asset.size) || 16);
const width = assetWidth(asset);
const height = assetHeight(asset);
const size = Math.max(width, height, Math.max(1, Number(asset.size) || 16));
const category = asset.category === 'dynamic' ? 'dynamic' : 'static';
const depth = asset.meta?.depthPixels ? normalizeEncodedPlane(asset.meta.depthPixels, size, '.') : '';
const depth = asset.meta?.depthPixels ? normalizeEncodedPlane(asset.meta.depthPixels, width, '.', height) : '';
const lights = Array.isArray(asset.meta?.lightPixels)
? asset.meta.lightPixels.map((p) => [Number(p.x) || 0, Number(p.y) || 0, p.c || asset.meta?.lightColor || '']).filter((p) => p[2])
: [];
@ -255,7 +279,7 @@
? asset.meta.particlePixels.map((p) => [Number(p.x) || 0, Number(p.y) || 0, p.c || '', p.dir || 'up']).filter((p) => p[2])
: [];
const meta = {};
if (depth && /[1\-]/.test(depth)) meta.d = cropPlane(depth, size, '.');
if (depth && /[1\-]/.test(depth)) meta.d = cropPlane(depth, width, '.', {}, height);
if (lights.length) meta.l = lights;
if (particles.length) meta.pt = particles;
if (asset.meta?.lightColor) meta.lc = asset.meta.lightColor;
@ -272,6 +296,8 @@
c: category,
t: asset.subtype || (category === 'dynamic' ? 'human' : 'other'),
s: size,
w: width !== size ? width : undefined,
ht: height !== size ? height : undefined,
p: selectPixelPlanePack(asset, assetMap),
f: category === 'dynamic' ? { l: 'mirror' } : null,
pa: asset.parentAssetId || null,
@ -279,6 +305,8 @@
ca: asset.createdAt || Date.now(),
ua: asset.updatedAt || asset.createdAt || Date.now(),
au: asset.author || 'Local Artist',
ow: asset.ownerAccountId || null,
v: Number(asset.version) || 1,
m: Object.keys(meta).length ? meta : null
};
}
@ -286,8 +314,10 @@
function unpackAsset(packed, assetById = null) {
if (!packed || !packed.id) return null;
const size = Math.max(1, Number(packed.s || packed.size) || 16);
const width = Math.max(1, Number(packed.w || packed.width || size) || size);
const height = Math.max(1, Number(packed.ht || packed.height || size) || size);
const category = packed.c === 'dynamic' || packed.category === 'dynamic' ? 'dynamic' : 'static';
const pixels = expandPlane(packed.p, size, '.');
const pixels = expandPlane(packed.p, width, '.', null, height);
if (pixels == null) return null;
const metaPacked = packed.m || {};
const lightPixels = Array.isArray(metaPacked.l)
@ -306,7 +336,7 @@
hasParticles: Boolean(particleConfig) || particlePixels.length > 0,
particlePixels,
particleConfig,
depthPixels: metaPacked.d ? expandPlane(metaPacked.d, size, '.') : null,
depthPixels: metaPacked.d ? expandPlane(metaPacked.d, width, '.', null, height) : null,
door: Array.isArray(metaPacked.dr) ? { x: Number(metaPacked.dr[0]) || 0, y: Number(metaPacked.dr[1]) || 0 } : null
};
return {
@ -315,6 +345,8 @@
category,
subtype: packed.t || (category === 'dynamic' ? 'human' : 'other'),
size,
width,
height,
pixels,
faces: category === 'dynamic' ? { right: pixels, left: 'mirror' } : null,
parentAssetId: packed.pa || null,
@ -322,6 +354,8 @@
createdAt: packed.ca || Date.now(),
updatedAt: packed.ua || packed.ca || Date.now(),
author: packed.au || 'Local Artist',
ownerAccountId: packed.ow || packed.ownerAccountId || '',
version: Number(packed.v || packed.version) || 1,
meta,
contentHash: packed.h || null
};
@ -335,26 +369,28 @@
const meta = {};
if (item.publishedAt) meta.pu = item.publishedAt;
if (item.status && item.status !== 'active') meta.st = item.status;
if (item.ownerAccountId) meta.ow = item.ownerAccountId;
return [item.id, item.assetId, Number(item.x) || 0, Number(item.y) || 0, item.placedAt || Date.now(), Number(item.version) || 1, Object.keys(meta).length ? meta : null];
}
function unpackPlacement(row) {
if (!Array.isArray(row)) return row;
const meta = row[6] || {};
return { id: row[0], assetId: row[1], x: row[2], y: row[3], placedAt: row[4], version: row[5] || 1, publishedAt: meta.pu || row[4], status: meta.st || 'active' };
return { id: row[0], assetId: row[1], x: row[2], y: row[3], placedAt: row[4], version: row[5] || 1, publishedAt: meta.pu || row[4], status: meta.st || 'active', ownerAccountId: meta.ow || '' };
}
function packDynamic(item) {
const meta = {};
if (item.publishedAt) meta.pu = item.publishedAt;
if (item.status && item.status !== 'active') meta.st = item.status;
if (item.ownerAccountId) meta.ow = item.ownerAccountId;
return [item.id, item.assetId, Number(item.homeX) || 0, Number(item.homeY) || 0, item.createdAt || Date.now(), Number(item.version) || 1, Object.keys(meta).length ? meta : null];
}
function unpackDynamic(row) {
if (!Array.isArray(row)) return row;
const meta = row[6] || {};
return { id: row[0], assetId: row[1], homeX: row[2], homeY: row[3], createdAt: row[4], version: row[5] || 1, publishedAt: meta.pu || row[4], status: meta.st || 'active' };
return { id: row[0], assetId: row[1], homeX: row[2], homeY: row[3], createdAt: row[4], version: row[5] || 1, publishedAt: meta.pu || row[4], status: meta.st || 'active', ownerAccountId: meta.ow || '' };
}
function assetMapFor(assets) {
@ -381,7 +417,10 @@
account: state.account || null,
publishLog: Array.isArray(state.publishLog) ? state.publishLog.slice(-300) : [],
eventLog: Array.isArray(state.eventLog) ? state.eventLog.slice(-EVENT_LOG_LIMIT) : [],
sync: state.sync || { lastEventId: null }
sync: state.sync || { lastEventId: null },
worldMode: state.worldMode === 'shared' ? 'shared' : 'local',
serverSync: state.serverSync || { lastServerEventId: null, pendingCommands: [] },
tombstones: state.tombstones || { assets: {}, objects: {} }
};
}
@ -403,7 +442,10 @@
account: input.account || null,
publishLog: Array.isArray(input.publishLog) ? input.publishLog.slice(-300) : [],
eventLog: Array.isArray(input.eventLog) ? input.eventLog.slice(-EVENT_LOG_LIMIT) : [],
sync: input.sync || { lastEventId: null }
sync: input.sync || { lastEventId: null },
worldMode: input.worldMode === 'shared' ? 'shared' : 'local',
serverSync: input.serverSync || { lastServerEventId: null, pendingCommands: [] },
tombstones: input.tombstones || { assets: {}, objects: {} }
};
}
@ -421,7 +463,7 @@
}
function assetManifest(state) {
return (state.assets || []).map((asset) => ({ id: asset.id, h: asset.contentHash || null, s: asset.size, c: asset.category, t: asset.subtype, pa: asset.parentAssetId || null, oa: asset.originalAssetId || null }));
return (state.assets || []).map((asset) => ({ id: asset.id, h: asset.contentHash || null, s: asset.size, w: asset.width || null, ht: asset.height || null, c: asset.category, t: asset.subtype, pa: asset.parentAssetId || null, oa: asset.originalAssetId || null }));
}
function makeSnapshot(state, worldId = 'local-main') {