diff --git a/scripts/verify-battle-map-asset-data.mjs b/scripts/verify-battle-map-asset-data.mjs index 009973d..2befce5 100644 --- a/scripts/verify-battle-map-asset-data.mjs +++ b/scripts/verify-battle-map-asset-data.mjs @@ -4,6 +4,8 @@ import { createServer } from 'vite'; const battleMapDir = join('src', 'assets', 'images', 'battle'); const battleMapAssetsSourcePath = join('src', 'game', 'data', 'battleMapAssets.ts'); +const minimumRasterMapWidth = 1024; +const minimumRasterMapHeight = 768; const server = await createServer({ logLevel: 'error', @@ -185,6 +187,18 @@ function validateMapFile(errors, context, path) { bytes.subarray(8, 12).toString('ascii') !== 'WEBP' ) { errors.push(`${context}: "${path}" does not have a WebP RIFF header`); + return; + } + + const dimensions = readWebpDimensions(bytes); + if (!dimensions) { + errors.push(`${context}: "${path}" does not expose readable WebP dimensions`); + return; + } + if (dimensions.width < minimumRasterMapWidth || dimensions.height < minimumRasterMapHeight) { + errors.push( + `${context}: "${path}" is ${dimensions.width}x${dimensions.height}, expected at least ${minimumRasterMapWidth}x${minimumRasterMapHeight}` + ); } return; } @@ -192,6 +206,58 @@ function validateMapFile(errors, context, path) { errors.push(`${context}: "${path}" uses an unsupported map asset extension`); } +function readWebpDimensions(bytes) { + let offset = 12; + while (offset + 8 <= bytes.length) { + const chunkType = bytes.subarray(offset, offset + 4).toString('ascii'); + const chunkSize = bytes.readUInt32LE(offset + 4); + const dataOffset = offset + 8; + if (dataOffset + chunkSize > bytes.length) { + return undefined; + } + + if (chunkType === 'VP8X' && chunkSize >= 10) { + return { + width: readUInt24LE(bytes, dataOffset + 4) + 1, + height: readUInt24LE(bytes, dataOffset + 7) + 1 + }; + } + + if (chunkType === 'VP8L' && chunkSize >= 5 && bytes[dataOffset] === 0x2f) { + const bits = + bytes[dataOffset + 1] | + (bytes[dataOffset + 2] << 8) | + (bytes[dataOffset + 3] << 16) | + (bytes[dataOffset + 4] << 24); + return { + width: (bits & 0x3fff) + 1, + height: ((bits >>> 14) & 0x3fff) + 1 + }; + } + + if ( + chunkType === 'VP8 ' && + chunkSize >= 10 && + bytes[dataOffset + 3] === 0x9d && + bytes[dataOffset + 4] === 0x01 && + bytes[dataOffset + 5] === 0x2a + ) { + return { + width: bytes.readUInt16LE(dataOffset + 6) & 0x3fff, + height: bytes.readUInt16LE(dataOffset + 8) & 0x3fff + }; + } + + offset = dataOffset + chunkSize + (chunkSize % 2); + } + + return undefined; +} + +function readUInt24LE(bytes, offset) { + return bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16); +} + function assetUrlToPath(url) { const marker = 'src/assets/images/battle/'; const index = url.indexOf(marker);