442 lines
19 KiB
TypeScript
442 lines
19 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import zlib from 'node:zlib'
|
|
import {
|
|
MISSILE_METAS,
|
|
HOLYBOLT_META,
|
|
BLESSEDHAMMER_META,
|
|
FISTOFTHEHEAVENSBOLT_META,
|
|
FOA_META,
|
|
BLESSEDHAMMEREX_META,
|
|
BLOOD1_META,
|
|
type MissileMeta,
|
|
} from '../../../src/render/missiles-meta.ts'
|
|
import {
|
|
OVERLAY_METAS,
|
|
ALL_OVERLAY_NAMES,
|
|
// 10 Offensive Auras
|
|
MIGHT_FRONT_AURA_META,
|
|
MIGHT_BACK_AURA_META,
|
|
HOLY_FIRE_FRONT_AURA_META,
|
|
HOLY_FIRE_BACK_AURA_META,
|
|
THORNS_FRONT_AURA_META,
|
|
THORNS_BACK_AURA_META,
|
|
BLESSED_AIM_FRONT_AURA_META,
|
|
BLESSED_AIM_BACK_AURA_META,
|
|
CONCENTRATION_FRONT_AURA_META,
|
|
CONCENTRATION_BACK_AURA_META,
|
|
HOLY_FREEZE_AURA_META,
|
|
HOLY_SHOCK_FRONT_AURA_META,
|
|
HOLY_SHOCK_BACK_AURA_META,
|
|
SANCTUARY_FRONT_AURA_META,
|
|
SANCTUARY_BACK_AURA_META,
|
|
FANATICISM_FRONT_AURA_META,
|
|
FANATICISM_BACK_AURA_META,
|
|
CONVICTION_AURA_META,
|
|
// 10 Defensive Auras
|
|
PRAYER_FRONT_AURA_META,
|
|
PRAYER_BACK_AURA_META,
|
|
RESIST_FIRE_AURA_META,
|
|
DEFIANCE_FRONT_AURA_META,
|
|
DEFIANCE_BACK_AURA_META,
|
|
RESIST_COLD_AURA_META,
|
|
CLEANSING_FRONT_AURA_META,
|
|
CLEANSING_BACK_AURA_META,
|
|
RESIST_LIGHTNING_AURA_META,
|
|
VIGOR_FRONT_AURA_META,
|
|
VIGOR_BACK_AURA_META,
|
|
MEDITATION_AURA_META,
|
|
REDEMPTION_AURA_META,
|
|
SALVATION_FRONT_AURA_META,
|
|
SALVATION_BACK_AURA_META,
|
|
// 16 Combat, Cast, Hit & State Overlays
|
|
CHARGE_OVERLAY_META,
|
|
CONVERSION_OVERLAY_META,
|
|
CONVERSION_CAST_OVERLAY_META,
|
|
HOLY_BOLT_CAST_OVERLAY_META,
|
|
FIST_OF_THE_HEAVENS_CAST_OVERLAY_META,
|
|
HAND_OF_GOD_OVERLAY_META,
|
|
SANCTUARY_KNOCKBACK_OVERLAY_META,
|
|
HOLY_FIRE_HIT_OVERLAY_META,
|
|
HOLY_FIRE_INDICATE_OVERLAY_META,
|
|
THORNS_HIT_OVERLAY_META,
|
|
THORNS_INDICATE_OVERLAY_META,
|
|
CAST_RESIST_FIRE_OVERLAY_META,
|
|
CAST_RESIST_COLD_OVERLAY_META,
|
|
CAST_RESIST_LIGHTNING_OVERLAY_META,
|
|
CAST_RESIST_ALL_OVERLAY_META,
|
|
STUN_OVERLAY_META,
|
|
type OverlayMeta,
|
|
} from '../../../src/render/overlays-meta.ts'
|
|
|
|
// Strict PNG RGBA Decoder
|
|
function decodeRgbaPng(buf: Buffer) {
|
|
const width = buf.readUInt32BE(16)
|
|
const height = buf.readUInt32BE(20)
|
|
const bitDepth = buf[24]!
|
|
const colorType = buf[25]!
|
|
|
|
const idatParts: Buffer[] = []
|
|
let offset = 8
|
|
while (offset < buf.length) {
|
|
const len = buf.readUInt32BE(offset)
|
|
const type = buf.subarray(offset + 4, offset + 8).toString('ascii')
|
|
if (type === 'IDAT') {
|
|
idatParts.push(buf.subarray(offset + 8, offset + 8 + len))
|
|
}
|
|
offset += 12 + len
|
|
}
|
|
const idat = Buffer.concat(idatParts)
|
|
const raw = zlib.inflateSync(idat)
|
|
|
|
const bpp = 4
|
|
const rowBytes = width * bpp
|
|
const pixels = new Uint8Array(width * height * 4)
|
|
let srcAt = 0
|
|
let dstAt = 0
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
const filter = raw[srcAt++]
|
|
const row = raw.subarray(srcAt, srcAt + rowBytes)
|
|
srcAt += rowBytes
|
|
|
|
const prevRow = y > 0 ? pixels.subarray((y - 1) * rowBytes, y * rowBytes) : null
|
|
const curRow = pixels.subarray(dstAt, dstAt + rowBytes)
|
|
|
|
for (let x = 0; x < rowBytes; x++) {
|
|
const left = x >= bpp ? curRow[x - bpp]! : 0
|
|
const up = prevRow ? prevRow[x]! : 0
|
|
const upLeft = prevRow && x >= bpp ? prevRow[x - bpp]! : 0
|
|
|
|
let val = row[x]!
|
|
if (filter === 0) {
|
|
// None
|
|
} else if (filter === 1) {
|
|
val = (val + left) & 0xff
|
|
} else if (filter === 2) {
|
|
val = (val + up) & 0xff
|
|
} else if (filter === 3) {
|
|
val = (val + Math.floor((left + up) / 2)) & 0xff
|
|
} else if (filter === 4) {
|
|
const p = left + up - upLeft
|
|
const pa = Math.abs(p - left)
|
|
const pb = Math.abs(p - up)
|
|
const pc = Math.abs(p - upLeft)
|
|
let pr = upLeft
|
|
if (pa <= pb && pa <= pc) pr = left
|
|
else if (pb <= pc) pr = up
|
|
val = (val + pr) & 0xff
|
|
}
|
|
curRow[x] = val
|
|
}
|
|
dstAt += rowBytes
|
|
}
|
|
|
|
return { width, height, bitDepth, colorType, pixels }
|
|
}
|
|
|
|
describe('Adv Coverage Audit — Paladin Assets & Metadata Integrity', () => {
|
|
const publicMissilesDir = path.resolve(__dirname, '../../../public/missiles')
|
|
const packMissilesDir = path.resolve(__dirname, '../../../samples/d2-packs/missiles')
|
|
const publicOverlaysDir = path.resolve(__dirname, '../../../public/overlays')
|
|
const packOverlaysDir = path.resolve(__dirname, '../../../samples/d2-packs/overlays')
|
|
|
|
const PALADIN_MISSILE_REQS = [
|
|
{ name: 'holybolt', celFile: 'HolyBoltMissile', meta: HOLYBOLT_META },
|
|
{ name: 'blessedhammer', celFile: 'blessedhammer', meta: BLESSEDHAMMER_META },
|
|
{ name: 'fistoftheheavensbolt', celFile: 'HolyBoltMissile', meta: FISTOFTHEHEAVENSBOLT_META },
|
|
{ name: 'foa', celFile: 'FOAorbiter', meta: FOA_META },
|
|
{ name: 'blessedhammerex', celFile: 'blessedhammer', meta: BLESSEDHAMMEREX_META },
|
|
{ name: 'blood1', celFile: 'BloodSmall01', meta: BLOOD1_META },
|
|
] as const
|
|
|
|
const PALADIN_AURA_REQS = [
|
|
// 10 Offensive Auras
|
|
{ name: 'might_front', celFile: 'AuraMightFront', meta: MIGHT_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'might_back', celFile: 'AuraMightBack', meta: MIGHT_BACK_AURA_META, preDraw: true },
|
|
{ name: 'holy_fire_front', celFile: 'AuraHolyFireFront', meta: HOLY_FIRE_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'holy_fire_back', celFile: 'AuraHolyFireBack', meta: HOLY_FIRE_BACK_AURA_META, preDraw: true },
|
|
{ name: 'thorns_front', celFile: 'AuraThornsFront', meta: THORNS_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'thorns_back', celFile: 'AuraThornsBack', meta: THORNS_BACK_AURA_META, preDraw: true },
|
|
{ name: 'blessed_aim_front', celFile: 'BlessedAim_front', meta: BLESSED_AIM_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'blessed_aim_back', celFile: 'BlessedAim_back', meta: BLESSED_AIM_BACK_AURA_META, preDraw: true },
|
|
{ name: 'concentration_front', celFile: 'Concentration_front', meta: CONCENTRATION_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'concentration_back', celFile: 'Concentration_back', meta: CONCENTRATION_BACK_AURA_META, preDraw: true },
|
|
{ name: 'holy_freeze', celFile: 'HolyFreeze', meta: HOLY_FREEZE_AURA_META, preDraw: true },
|
|
{ name: 'holy_shock_front', celFile: 'HolyShock_front', meta: HOLY_SHOCK_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'holy_shock_back', celFile: 'HolyShock_back', meta: HOLY_SHOCK_BACK_AURA_META, preDraw: true },
|
|
{ name: 'sanctuary_front', celFile: 'SanctuaryAura_Front', meta: SANCTUARY_FRONT_AURA_META, preDraw: true },
|
|
{ name: 'sanctuary_back', celFile: 'SanctuaryAura_Back', meta: SANCTUARY_BACK_AURA_META, preDraw: true },
|
|
{ name: 'fanaticism_front', celFile: 'Fanatacism_Front', meta: FANATICISM_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'fanaticism_back', celFile: 'Fanatacism_Back', meta: FANATICISM_BACK_AURA_META, preDraw: true },
|
|
{ name: 'conviction', celFile: 'Conviction', meta: CONVICTION_AURA_META, preDraw: true },
|
|
// 10 Defensive Auras
|
|
{ name: 'prayer_front', celFile: 'AuraPrayerFront', meta: PRAYER_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'prayer_back', celFile: 'AuraPrayerBack', meta: PRAYER_BACK_AURA_META, preDraw: true },
|
|
{ name: 'resist_fire', celFile: 'AuraResistFireFront', meta: RESIST_FIRE_AURA_META, preDraw: true },
|
|
{ name: 'defiance_front', celFile: 'AuraDefianceFront', meta: DEFIANCE_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'defiance_back', celFile: 'AuraDefianceBack', meta: DEFIANCE_BACK_AURA_META, preDraw: true },
|
|
{ name: 'resist_cold', celFile: 'AuraResistColdBack', meta: RESIST_COLD_AURA_META, preDraw: true },
|
|
{ name: 'cleansing_front', celFile: 'Cleansing_front', meta: CLEANSING_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'cleansing_back', celFile: 'Cleansing_back', meta: CLEANSING_BACK_AURA_META, preDraw: true },
|
|
{ name: 'resist_lightning', celFile: 'AuraResistLightningBack', meta: RESIST_LIGHTNING_AURA_META, preDraw: true },
|
|
{ name: 'vigor_front', celFile: 'Stamina_front', meta: VIGOR_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'vigor_back', celFile: 'Stamina_back', meta: VIGOR_BACK_AURA_META, preDraw: true },
|
|
{ name: 'meditation', celFile: 'Meditation', meta: MEDITATION_AURA_META, preDraw: false },
|
|
{ name: 'redemption', celFile: 'Redemption', meta: REDEMPTION_AURA_META, preDraw: true },
|
|
{ name: 'salvation_front', celFile: 'AuraResistAllFront', meta: SALVATION_FRONT_AURA_META, preDraw: false },
|
|
{ name: 'salvation_back', celFile: 'AuraResistAllBack', meta: SALVATION_BACK_AURA_META, preDraw: true },
|
|
] as const
|
|
|
|
const PALADIN_COMBAT_OVERLAY_REQS = [
|
|
{ name: 'charge', celFile: 'Charge', meta: CHARGE_OVERLAY_META, preDraw: false },
|
|
{ name: 'conversion', celFile: 'Conversion', meta: CONVERSION_OVERLAY_META, preDraw: false },
|
|
{ name: 'conversioncast', celFile: 'AuraResistColdBackCast', meta: CONVERSION_CAST_OVERLAY_META, preDraw: false },
|
|
{ name: 'holy_bolt_cast', celFile: 'HolyBoltCloud', meta: HOLY_BOLT_CAST_OVERLAY_META, preDraw: false },
|
|
{ name: 'fist_of_the_heavens_cast', celFile: 'FOACast', meta: FIST_OF_THE_HEAVENS_CAST_OVERLAY_META, preDraw: false },
|
|
{ name: 'handofgod', celFile: 'HolyShockHit', meta: HAND_OF_GOD_OVERLAY_META, preDraw: false },
|
|
{ name: 'sanctuary_knockback', celFile: 'SanctuaryKnockback', meta: SANCTUARY_KNOCKBACK_OVERLAY_META, preDraw: false },
|
|
{ name: 'holy_fire_hit', celFile: 'AuraHolyFireHit', meta: HOLY_FIRE_HIT_OVERLAY_META, preDraw: false },
|
|
{ name: 'holy_fire_indicate', celFile: 'AuraHfireIndicate', meta: HOLY_FIRE_INDICATE_OVERLAY_META, preDraw: false },
|
|
{ name: 'thorns_hit', celFile: 'AuraThornsHit', meta: THORNS_HIT_OVERLAY_META, preDraw: false },
|
|
{ name: 'thorns_indicate', celFile: 'AuraHfireIndicate', meta: THORNS_INDICATE_OVERLAY_META, preDraw: false },
|
|
{ name: 'cast_resistfire', celFile: 'AuraResistFireFrontCast', meta: CAST_RESIST_FIRE_OVERLAY_META, preDraw: true },
|
|
{ name: 'cast_resistcold', celFile: 'AuraResistColdBackCast', meta: CAST_RESIST_COLD_OVERLAY_META, preDraw: false },
|
|
{ name: 'cast_resistlight', celFile: 'AuraResistLightningBackCast', meta: CAST_RESIST_LIGHTNING_OVERLAY_META, preDraw: false },
|
|
{ name: 'cast_resistall', celFile: 'AuraResistAllCast', meta: CAST_RESIST_ALL_OVERLAY_META, preDraw: true },
|
|
{ name: 'stun', celFile: 'Stun', meta: STUN_OVERLAY_META, preDraw: false },
|
|
] as const
|
|
|
|
const ALL_OVERLAYS = [...PALADIN_AURA_REQS, ...PALADIN_COMBAT_OVERLAY_REQS]
|
|
|
|
describe('Dimension 1: Feature Matrix & Skill Requirements Coverage', () => {
|
|
it('1.1 Verifies all 30 Paladin skills required missiles are registered in MISSILE_METAS', () => {
|
|
for (const m of PALADIN_MISSILE_REQS) {
|
|
expect(MISSILE_METAS[m.name], `Missile ${m.name} must be in MISSILE_METAS`).toBeDefined()
|
|
expect(MISSILE_METAS[m.name]).toBe(m.meta)
|
|
expect(m.meta.name).toBe(m.name)
|
|
expect(m.meta.celFile).toBe(m.celFile)
|
|
}
|
|
})
|
|
|
|
it('1.2 Verifies all 30 Paladin auras and 16 combat/state overlays are in OVERLAY_METAS', () => {
|
|
for (const o of ALL_OVERLAYS) {
|
|
expect(OVERLAY_METAS[o.name], `Overlay ${o.name} must be in OVERLAY_METAS`).toBeDefined()
|
|
expect(OVERLAY_METAS[o.name]).toBe(o.meta)
|
|
expect(o.meta.name).toBe(o.name)
|
|
expect(o.meta.celFile).toBe(o.celFile)
|
|
expect(o.meta.preDraw).toBe(o.preDraw)
|
|
}
|
|
})
|
|
|
|
it('1.3 Verifies ALL_OVERLAY_NAMES contains all Paladin overlays', () => {
|
|
for (const o of ALL_OVERLAYS) {
|
|
expect(ALL_OVERLAY_NAMES).toContain(o.name)
|
|
}
|
|
})
|
|
|
|
it('1.4 Verifies ground rings and back auras have preDraw:true while front auras have preDraw:false', () => {
|
|
expect(HOLY_FREEZE_AURA_META.preDraw).toBe(true)
|
|
expect(CONVICTION_AURA_META.preDraw).toBe(true)
|
|
expect(REDEMPTION_AURA_META.preDraw).toBe(true)
|
|
expect(RESIST_FIRE_AURA_META.preDraw).toBe(true)
|
|
expect(RESIST_COLD_AURA_META.preDraw).toBe(true)
|
|
expect(RESIST_LIGHTNING_AURA_META.preDraw).toBe(true)
|
|
expect(MIGHT_BACK_AURA_META.preDraw).toBe(true)
|
|
expect(MIGHT_FRONT_AURA_META.preDraw).toBe(false)
|
|
expect(FANATICISM_BACK_AURA_META.preDraw).toBe(true)
|
|
expect(FANATICISM_FRONT_AURA_META.preDraw).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('Dimension 2: Zero Null / Missing / NaN Frame Geometry', () => {
|
|
it('2.1 Checks for zero null, undefined, or NaN values in missile frame geometry', () => {
|
|
for (const m of PALADIN_MISSILE_REQS) {
|
|
const meta = m.meta
|
|
expect(meta.width).toBeGreaterThan(0)
|
|
expect(meta.height).toBeGreaterThan(0)
|
|
expect(meta.directions).toBeGreaterThan(0)
|
|
expect(meta.framesPerDirection).toBeGreaterThan(0)
|
|
expect(meta.animSpeed).toBeGreaterThan(0)
|
|
expect(meta.groups).toBeDefined()
|
|
expect(meta.groups.length).toBe(meta.directions)
|
|
|
|
for (let d = 0; d < meta.groups.length; d++) {
|
|
const group = meta.groups[d]!
|
|
expect(group.length).toBe(meta.framesPerDirection)
|
|
for (let f = 0; f < group.length; f++) {
|
|
const frame = group[f]!
|
|
expect(frame.length).toBe(6)
|
|
for (let i = 0; i < 6; i++) {
|
|
const val = frame[i]
|
|
expect(val, `Null/undefined/NaN in ${m.name} dir ${d} frame ${f} idx ${i}`).not.toBeNull()
|
|
expect(val).not.toBeUndefined()
|
|
expect(typeof val).toBe('number')
|
|
expect(Number.isNaN(val)).toBe(false)
|
|
expect(Number.isFinite(val)).toBe(true)
|
|
}
|
|
const [x, y, w, h, ax, ay] = frame
|
|
expect(x).toBeGreaterThanOrEqual(0)
|
|
expect(y).toBeGreaterThanOrEqual(0)
|
|
expect(w).toBeGreaterThan(0)
|
|
expect(h).toBeGreaterThan(0)
|
|
expect(x + w).toBeLessThanOrEqual(meta.width)
|
|
expect(y + h).toBeLessThanOrEqual(meta.height)
|
|
expect(ax).toBeGreaterThanOrEqual(-1000)
|
|
expect(ax).toBeLessThanOrEqual(1000)
|
|
expect(ay).toBeGreaterThanOrEqual(-1000)
|
|
expect(ay).toBeLessThanOrEqual(1000)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
it('2.2 Checks for zero null, undefined, or NaN values in overlay frame geometry', () => {
|
|
for (const o of ALL_OVERLAYS) {
|
|
const meta = o.meta
|
|
expect(meta.width).toBeGreaterThan(0)
|
|
expect(meta.height).toBeGreaterThan(0)
|
|
expect(meta.directions).toBe(1)
|
|
expect(meta.frames).toBeGreaterThan(0)
|
|
expect(meta.framesPerDirection).toBe(meta.frames)
|
|
expect(meta.animRate).toBeGreaterThan(0)
|
|
expect(meta.trans).toBe(3)
|
|
expect(meta.groups).toBeDefined()
|
|
expect(meta.groups.length).toBe(1)
|
|
|
|
const group = meta.groups[0]!
|
|
expect(group.length).toBe(meta.frames)
|
|
for (let f = 0; f < group.length; f++) {
|
|
const frame = group[f]!
|
|
expect(frame.length).toBe(6)
|
|
for (let i = 0; i < 6; i++) {
|
|
const val = frame[i]
|
|
expect(val, `Null/undefined/NaN in ${o.name} frame ${f} idx ${i}`).not.toBeNull()
|
|
expect(val).not.toBeUndefined()
|
|
expect(typeof val).toBe('number')
|
|
expect(Number.isNaN(val)).toBe(false)
|
|
expect(Number.isFinite(val)).toBe(true)
|
|
}
|
|
const [x, y, w, h, ax, ay] = frame
|
|
expect(x).toBeGreaterThanOrEqual(0)
|
|
expect(y).toBeGreaterThanOrEqual(0)
|
|
expect(w).toBeGreaterThan(0)
|
|
expect(h).toBeGreaterThan(0)
|
|
expect(x + w).toBeLessThanOrEqual(meta.width)
|
|
expect(y + h).toBeLessThanOrEqual(meta.height)
|
|
expect(ax).toBeGreaterThanOrEqual(-1000)
|
|
expect(ax).toBeLessThanOrEqual(1000)
|
|
expect(ay).toBeGreaterThanOrEqual(-1000)
|
|
expect(ay).toBeLessThanOrEqual(1000)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Dimension 3: Physical Asset Disk Mirroring & File Health', () => {
|
|
it('3.1 Every Paladin missile exists in public/missiles and samples/d2-packs/missiles with valid PNG header', () => {
|
|
for (const m of PALADIN_MISSILE_REQS) {
|
|
for (const dir of [publicMissilesDir, packMissilesDir]) {
|
|
const pngPath = path.join(dir, `${m.name}.png`)
|
|
const jsonPath = path.join(dir, `${m.name}.json`)
|
|
|
|
expect(fs.existsSync(pngPath), `Missing ${pngPath}`).toBe(true)
|
|
expect(fs.existsSync(jsonPath), `Missing ${jsonPath}`).toBe(true)
|
|
|
|
const stat = fs.statSync(pngPath)
|
|
expect(stat.size).toBeGreaterThan(500)
|
|
|
|
const buf = fs.readFileSync(pngPath)
|
|
expect(buf.readUInt32BE(0)).toBe(0x89504e47) // \x89PNG
|
|
}
|
|
}
|
|
})
|
|
|
|
it('3.2 Every Paladin overlay exists in public/overlays and samples/d2-packs/overlays with valid PNG header', () => {
|
|
for (const o of ALL_OVERLAYS) {
|
|
for (const dir of [publicOverlaysDir, packOverlaysDir]) {
|
|
const pngPath = path.join(dir, `${o.name}.png`)
|
|
const jsonPath = path.join(dir, `${o.name}.json`)
|
|
|
|
expect(fs.existsSync(pngPath), `Missing ${pngPath}`).toBe(true)
|
|
expect(fs.existsSync(jsonPath), `Missing ${jsonPath}`).toBe(true)
|
|
|
|
const stat = fs.statSync(pngPath)
|
|
expect(stat.size).toBeGreaterThan(500)
|
|
|
|
const buf = fs.readFileSync(pngPath)
|
|
expect(buf.readUInt32BE(0)).toBe(0x89504e47) // \x89PNG
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Dimension 4: 100% Pixel Buffer Dark Boundary Audit (Zero Black Fringe)', () => {
|
|
it('4.1 Asserts max(r,g,b) <= 4 implies alpha === 0 across all Paladin missiles', () => {
|
|
for (const m of PALADIN_MISSILE_REQS) {
|
|
const pngPath = path.join(publicMissilesDir, `${m.name}.png`)
|
|
const buf = fs.readFileSync(pngPath)
|
|
const { width, height, pixels } = decodeRgbaPng(buf)
|
|
|
|
expect(width).toBe(m.meta.width)
|
|
expect(height).toBe(m.meta.height)
|
|
|
|
let blackOpaqueCount = 0
|
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
const r = pixels[i]!
|
|
const g = pixels[i + 1]!
|
|
const b = pixels[i + 2]!
|
|
const a = pixels[i + 3]!
|
|
const mVal = Math.max(r, g, b)
|
|
|
|
if (mVal <= 4 && a > 0) {
|
|
blackOpaqueCount++
|
|
}
|
|
}
|
|
expect(blackOpaqueCount, `Dark halo pixels in ${m.name}.png`).toBe(0)
|
|
}
|
|
})
|
|
|
|
it('4.2 Asserts max(r,g,b) <= 4 implies alpha === 0 across all Paladin overlays', () => {
|
|
for (const o of ALL_OVERLAYS) {
|
|
const pngPath = path.join(publicOverlaysDir, `${o.name}.png`)
|
|
const buf = fs.readFileSync(pngPath)
|
|
const { width, height, pixels } = decodeRgbaPng(buf)
|
|
|
|
expect(width).toBe(o.meta.width)
|
|
expect(height).toBe(o.meta.height)
|
|
|
|
let blackOpaqueCount = 0
|
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
const r = pixels[i]!
|
|
const g = pixels[i + 1]!
|
|
const b = pixels[i + 2]!
|
|
const a = pixels[i + 3]!
|
|
const mVal = Math.max(r, g, b)
|
|
|
|
if (mVal <= 4 && a > 0) {
|
|
blackOpaqueCount++
|
|
}
|
|
}
|
|
expect(blackOpaqueCount, `Dark halo pixels in ${o.name}.png`).toBe(0)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Dimension 5: Zero Runtime MPQ Invariant', () => {
|
|
it('5.1 Verifies metadata files contain zero runtime MPQ, DLL or fs imports', () => {
|
|
const missilesMetaPath = path.resolve(__dirname, '../../../src/render/missiles-meta.ts')
|
|
const overlaysMetaPath = path.resolve(__dirname, '../../../src/render/overlays-meta.ts')
|
|
|
|
for (const p of [missilesMetaPath, overlaysMetaPath]) {
|
|
const content = fs.readFileSync(p, 'utf-8')
|
|
expect(content).not.toContain('node:fs')
|
|
expect(content).not.toContain('src/mpq')
|
|
expect(content).not.toContain('.mpq')
|
|
expect(content).not.toContain('.dll')
|
|
}
|
|
})
|
|
})
|
|
})
|