diablo2-web/scripts/verify-png-stress.ts

384 lines
14 KiB
TypeScript

/**
* Comprehensive Fuzzing & Differential Stress Harness for scripts/png.ts
*
* Implements the 3-component pattern from the solution-stress-testing playbook:
* 1. Generator: produces systematic boundary & pseudo-random inputs across unaligned widths & strides
* 2. Oracle: independent W3C PNG specification parser & unfilter engine
* 3. Harness: orchestrates 1,000+ test iterations, asserts exact bit-for-bit parity, benchmarks speed
*/
import { inflateSync } from 'node:zlib'
import { encodeIndexedPng, encodeRgbaPng, type IndexedImage, type RgbaImage } from './png'
interface DecodedResult {
readonly width: number
readonly height: number
readonly colorType: number
readonly pixels: Uint8Array
readonly filterTypes: number[]
}
const PNG_SIG = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
const CRC_TBL = new Uint32Array(256)
for (let n = 0; n < 256; n += 1) {
let c = n
for (let k = 0; k < 8; k += 1) c = (c & 1) !== 0 ? (0xedb88320 ^ (c >>> 1)) >>> 0 : c >>> 1
CRC_TBL[n] = c >>> 0
}
function checkCrc(bytes: Uint8Array, expected: number): boolean {
let c = 0xffffffff
for (const byte of bytes) c = (CRC_TBL[(c ^ byte) & 0xff]! ^ (c >>> 8)) >>> 0
return ((c ^ 0xffffffff) >>> 0) === expected
}
function oracleDecode(bytes: Uint8Array): DecodedResult {
for (let i = 0; i < 8; i += 1) {
if (bytes[i] !== PNG_SIG[i]) throw new Error(`Invalid signature at byte ${i}`)
}
let offset = 8
let width = 0
let height = 0
let colorType = 0
const idatParts: Uint8Array[] = []
while (offset < bytes.length) {
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, 8)
const len = view.getUint32(0)
const type = String.fromCharCode(bytes[offset + 4]!, bytes[offset + 5]!, bytes[offset + 6]!, bytes[offset + 7]!)
const payload = bytes.subarray(offset + 8, offset + 8 + len)
const crc = new DataView(bytes.buffer, bytes.byteOffset + offset + 8 + len, 4).getUint32(0)
if (!checkCrc(bytes.subarray(offset + 4, offset + 8 + len), crc)) {
throw new Error(`CRC error in chunk ${type}`)
}
if (type === 'IHDR') {
const hv = new DataView(payload.buffer, payload.byteOffset, 13)
width = hv.getUint32(0)
height = hv.getUint32(4)
colorType = payload[9]!
} else if (type === 'IDAT') {
idatParts.push(payload)
} else if (type === 'IEND') {
break
}
offset += 12 + len
}
const stride = colorType === 3 ? 1 : 4
const rowBytes = width * stride
const totalIdat = idatParts.reduce((sum, p) => sum + p.length, 0)
const idatMerged = new Uint8Array(totalIdat)
let pOff = 0
for (const p of idatParts) {
idatMerged.set(p, pOff)
pOff += p.length
}
const decomp = new Uint8Array(inflateSync(idatMerged))
if (decomp.length !== (rowBytes + 1) * height) {
throw new Error(`Unexpected raw decompressed size: expected ${(rowBytes + 1) * height}, got ${decomp.length}`)
}
const pixels = new Uint8Array(width * height * stride)
const filterTypes: number[] = []
let at = 0
for (let y = 0; y < height; y += 1) {
const filter = decomp[at]!
filterTypes.push(filter)
at += 1
const row = decomp.subarray(at, at + rowBytes)
const outRow = pixels.subarray(y * rowBytes, (y + 1) * rowBytes)
const priorRow = y === 0 ? null : pixels.subarray((y - 1) * rowBytes, y * rowBytes)
if (filter === 0) {
outRow.set(row)
} else if (filter === 1) {
for (let x = 0; x < stride; x += 1) outRow[x] = row[x]!
for (let x = stride; x < rowBytes; x += 1) outRow[x] = (row[x]! + outRow[x - stride]!) & 0xff
} else if (filter === 2) {
for (let x = 0; x < rowBytes; x += 1) {
const up = priorRow ? priorRow[x]! : 0
outRow[x] = (row[x]! + up) & 0xff
}
} else {
throw new Error(`Unsupported PNG filter type: ${filter}`)
}
at += rowBytes
}
return { width, height, colorType, pixels, filterTypes }
}
function verifyParity(expected: Uint8Array, actual: Uint8Array, context: string): void {
if (expected.length !== actual.length) {
throw new Error(`[${context}] Length mismatch: expected ${expected.length}, got ${actual.length}`)
}
for (let i = 0; i < expected.length; i += 1) {
if (expected[i] !== actual[i]) {
throw new Error(`[${context}] Byte mismatch at offset ${i}: expected ${expected[i]}, got ${actual[i]}`)
}
}
}
// PRNG
function splitmix64(seed: bigint) {
let s = seed
return () => {
s = (s + 0x9e3779b97f4a7c15n) & 0xffffffffffffffffn
let z = s
z = ((z ^ (z >> 30n)) * 0xbf58476d1ce4e5b9n) & 0xffffffffffffffffn
z = ((z ^ (z >> 27n)) * 0x94d049bb133111ebn) & 0xffffffffffffffffn
return Number((z ^ (z >> 31n)) & 0xffffffffn)
}
}
// Palette
const PALETTE_768 = new Uint8Array(768)
for (let i = 0; i < 256; i += 1) {
PALETTE_768[i * 3 + 0] = (i * 11) & 0xff
PALETTE_768[i * 3 + 1] = (i * 23) & 0xff
PALETTE_768[i * 3 + 2] = (i * 37) & 0xff
}
console.log('=== STARTING EMPIRICAL DIFFERENTIAL STRESS TEST ===')
let totalCasesTested = 0
// PHASE 1: Exhaustive minimal dimensions (1x1 to 4x4)
console.log('\n--- Phase 1: Exhaustive Minimal Dimensions Sweep ---')
for (let w = 1; w <= 4; w += 1) {
for (let h = 1; h <= 4; h += 1) {
// 1. All zeros
const zIdx = new Uint8Array(w * h)
const encZIdx = encodeIndexedPng({ width: w, height: h, pixels: zIdx, palette: PALETTE_768 })
verifyParity(zIdx, oracleDecode(encZIdx).pixels, `Indexed zero ${w}x${h}`)
totalCasesTested += 1
const zRgba = new Uint8Array(w * h * 4)
const encZRgba = encodeRgbaPng({ width: w, height: h, pixels: zRgba })
verifyParity(zRgba, oracleDecode(encZRgba).pixels, `RGBA zero ${w}x${h}`)
totalCasesTested += 1
// 2. Solid color
const sIdx = new Uint8Array(w * h).fill(42)
const encSIdx = encodeIndexedPng({ width: w, height: h, pixels: sIdx, palette: PALETTE_768 })
verifyParity(sIdx, oracleDecode(encSIdx).pixels, `Indexed solid ${w}x${h}`)
totalCasesTested += 1
const sRgba = new Uint8Array(w * h * 4).fill(128)
const encSRgba = encodeRgbaPng({ width: w, height: h, pixels: sRgba })
verifyParity(sRgba, oracleDecode(encSRgba).pixels, `RGBA solid ${w}x${h}`)
totalCasesTested += 1
// 3. Incrementing values
const iIdx = new Uint8Array(w * h)
for (let i = 0; i < iIdx.length; i += 1) iIdx[i] = (i + 1) & 0xff
const encIIdx = encodeIndexedPng({ width: w, height: h, pixels: iIdx, palette: PALETTE_768 })
verifyParity(iIdx, oracleDecode(encIIdx).pixels, `Indexed ramp ${w}x${h}`)
totalCasesTested += 1
const iRgba = new Uint8Array(w * h * 4)
for (let i = 0; i < iRgba.length; i += 1) iRgba[i] = (i * 3 + 7) & 0xff
const encIRgba = encodeRgbaPng({ width: w, height: h, pixels: iRgba })
verifyParity(iRgba, oracleDecode(encIRgba).pixels, `RGBA ramp ${w}x${h}`)
totalCasesTested += 1
}
}
console.log(`Phase 1 passed: ${totalCasesTested} cases executed.`)
// PHASE 2: Random Small Dimensions (1,000 iterations fuzzing)
console.log('\n--- Phase 2: Differential Fuzzing on Small Inputs (1,000 iterations) ---')
const rngSmall = splitmix64(123456789n)
const phase2Start = Date.now()
for (let iter = 0; iter < 1000; iter += 1) {
const w = (rngSmall() % 23) + 1 // 1..23
const h = (rngSmall() % 23) + 1 // 1..23
const patternType = rngSmall() % 6
const compLvl = (rngSmall() % 10) // 0..9
if (iter % 2 === 0) {
// Indexed
const pixels = new Uint8Array(w * h)
if (patternType === 0) {
// all zero
} else if (patternType === 1) {
pixels.fill(rngSmall() & 0xff)
} else if (patternType === 2) {
// identical rows
const baseRow = new Uint8Array(w)
for (let x = 0; x < w; x += 1) baseRow[x] = rngSmall() & 0xff
for (let y = 0; y < h; y += 1) pixels.set(baseRow, y * w)
} else if (patternType === 3) {
// gradient
for (let y = 0; y < h; y += 1) {
for (let x = 0; x < w; x += 1) pixels[y * w + x] = (x + y * 3) & 0xff
}
} else if (patternType === 4) {
// sparse
for (let i = 0; i < pixels.length; i += 1) {
if ((rngSmall() % 10) === 0) pixels[i] = rngSmall() & 0xff
}
} else {
// high entropy noise
for (let i = 0; i < pixels.length; i += 1) pixels[i] = rngSmall() & 0xff
}
const enc = encodeIndexedPng({
width: w,
height: h,
pixels,
palette: PALETTE_768,
transparentIndex: rngSmall() % 2 === 0 ? 0 : undefined,
compressionLevel: compLvl,
})
const dec = oracleDecode(enc)
verifyParity(pixels, dec.pixels, `Phase 2 Indexed iter ${iter} (${w}x${h} pattern ${patternType})`)
} else {
// RGBA
const pixels = new Uint8Array(w * h * 4)
if (patternType === 0) {
// all zero
} else if (patternType === 1) {
const color = rngSmall()
for (let i = 0; i < w * h; i += 1) {
pixels[i * 4 + 0] = color & 0xff
pixels[i * 4 + 1] = (color >> 8) & 0xff
pixels[i * 4 + 2] = (color >> 16) & 0xff
pixels[i * 4 + 3] = (color >> 24) & 0xff
}
} else if (patternType === 2) {
// identical rows
const baseRow = new Uint8Array(w * 4)
for (let x = 0; x < w * 4; x += 1) baseRow[x] = rngSmall() & 0xff
for (let y = 0; y < h; y += 1) pixels.set(baseRow, y * w * 4)
} else {
for (let i = 0; i < pixels.length; i += 1) pixels[i] = rngSmall() & 0xff
}
const enc = encodeRgbaPng({
width: w,
height: h,
pixels,
compressionLevel: compLvl,
})
const dec = oracleDecode(enc)
verifyParity(pixels, dec.pixels, `Phase 2 RGBA iter ${iter} (${w}x${h} pattern ${patternType})`)
}
totalCasesTested += 1
}
console.log(`Phase 2 passed: 1000 fuzzing iterations in ${Date.now() - phase2Start} ms. Total cases: ${totalCasesTested}.`)
// PHASE 3: Random Medium Dimensions (100 iterations, N = 50..300)
console.log('\n--- Phase 3: Differential Fuzzing on Medium Inputs (100 iterations) ---')
const rngMedium = splitmix64(987654321n)
const phase3Start = Date.now()
for (let iter = 0; iter < 100; iter += 1) {
const w = (rngMedium() % 250) + 50 // 50..299 (unaligned)
const h = (rngMedium() % 250) + 50 // 50..299
const patternType = rngMedium() % 5
const pixels = new Uint8Array(w * h)
if (patternType === 0) {
// 50% transparent rows, 50% content
for (let y = 0; y < h; y += 2) {
for (let x = 0; x < w; x += 1) pixels[y * w + x] = (x ^ y) & 0xff
}
} else if (patternType === 1) {
// Repeated block bands (tests Fast-Path 2 over sequences of rows)
const bandHeight = 5
for (let y = 0; y < h; y += bandHeight) {
const rowPattern = new Uint8Array(w)
for (let x = 0; x < w; x += 1) rowPattern[x] = rngMedium() & 0xff
for (let b = 0; b < bandHeight && y + b < h; b += 1) {
pixels.set(rowPattern, (y + b) * w)
}
}
} else if (patternType === 2) {
// Gradients
for (let y = 0; y < h; y += 1) {
for (let x = 0; x < w; x += 1) pixels[y * w + x] = ((x * 3) + (y * 7)) & 0xff
}
} else {
// Noise
for (let i = 0; i < pixels.length; i += 1) pixels[i] = rngMedium() & 0xff
}
const enc = encodeIndexedPng({
width: w,
height: h,
pixels,
palette: PALETTE_768,
transparentIndex: 0,
compressionLevel: 6,
})
const dec = oracleDecode(enc)
verifyParity(pixels, dec.pixels, `Phase 3 Medium iter ${iter} (${w}x${h})`)
totalCasesTested += 1
}
console.log(`Phase 3 passed: 100 medium iterations in ${Date.now() - phase3Start} ms. Total cases: ${totalCasesTested}.`)
// PHASE 4: Performance & Memory Benchmark (Atlas Scale 2048x1808)
console.log('\n--- Phase 4: Performance & Benchmark Profiling ---')
const W = 2048
const H = 1808
const atlasPixels = new Uint8Array(W * H)
// Simulate realistic Diablo 2 sprite sheet:
// ~60% transparent empty space, ~20% repeating edge runs, ~20% sprite pixel islands
for (let y = 200; y < 700; y += 1) {
for (let x = 200; x < 900; x += 1) {
atlasPixels[y * W + x] = ((x ^ y) & 0x7f) + 1
}
}
for (let y = 900; y < 1500; y += 1) {
for (let x = 800; x < 1600; x += 1) {
atlasPixels[y * W + x] = ((x * 13 + y * 7) & 0xff) || 1
}
}
// Warm up
encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 6 })
// Level 6 benchmark
const trials6: number[] = []
let len6 = 0
for (let t = 0; t < 5; t += 1) {
const t0 = performance.now()
const out6 = encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 6 })
trials6.push(performance.now() - t0)
len6 = out6.length
}
const avg6 = trials6.reduce((a, b) => a + b, 0) / trials6.length
// Level 9 benchmark
const trials9: number[] = []
let len9 = 0
for (let t = 0; t < 5; t += 1) {
const t0 = performance.now()
const out9 = encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 9 })
trials9.push(performance.now() - t0)
len9 = out9.length
}
const avg9 = trials9.reduce((a, b) => a + b, 0) / trials9.length
console.log(`2048x1808 Atlas Benchmark Results:`)
console.log(`- Deflate Level 6 (New Default): ${avg6.toFixed(2)} ms (Size: ${(len6 / 1024).toFixed(1)} KB)`)
console.log(`- Deflate Level 9 (Old Baseline): ${avg9.toFixed(2)} ms (Size: ${(len9 / 1024).toFixed(1)} KB)`)
console.log(`- Speedup: ${(avg9 / avg6).toFixed(2)}x faster!`)
console.log(`- Compression size delta: ${(((len6 - len9) / len9) * 100).toFixed(2)}% difference`)
// Verify bit-for-bit parity on the large atlas
const decAtlas6 = oracleDecode(encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 6 }))
verifyParity(atlasPixels, decAtlas6.pixels, '2048x1808 Atlas Parity')
totalCasesTested += 1
console.log(`\n======================================================`)
console.log(`ALL TESTS PASSED: ${totalCasesTested} total verification cases.`)
console.log(`100% BIT-FOR-BIT PIXEL PARITY EMPIRICALLY CONFIRMED.`)
console.log(`======================================================\n`)