diablo2-web/scripts/verify-challenger-m2-toolti...

483 lines
23 KiB
TypeScript

/**
* scripts/verify-challenger-m2-tooltip.ts
*
* Empirical Challenger Verification & Stress Test Suite for Milestone M2.
* Validates:
* 1. M2 Interface & Data Contracts in `src/ui/inventory.ts`.
* 2. Canonical 1.13c STARTER_EQUIPPED_GEAR & STARTER_BAG_ITEMS data integrity.
* 3. Stateful Dye Persistence across equip slot pickup, cursorItem, and bag grid operations.
* 4. 5-Tier Fallback Resolution in `resolveItemSpriteRect` (Tiers 0..5).
* 5. Boundary cases: missing codes, invalid dimensions, case variance, empty items.
* 6. Adversarial Fuzzing Stress Test (10,000 randomized mock items).
*/
import {
InventoryPanel,
resolveItemSpriteRect,
STARTER_BAG_ITEMS,
STARTER_EQUIPPED_GEAR,
type ResolvableItem,
type SpriteRect,
type UiInventoryItem,
type EquipSlotId,
} from '../src/ui/inventory.ts'
import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts'
interface Section {
name: string
assertions: number
failures: string[]
}
const sections: Section[] = []
let currentSection: Section | null = null
function startSection(name: string) {
currentSection = { name, assertions: 0, failures: [] }
sections.push(currentSection)
console.log(`\n=== [${name}] ===`)
}
function assert(condition: boolean, msg: string) {
if (!currentSection) throw new Error('No active section')
currentSection.assertions++
if (!condition) {
currentSection.failures.push(msg)
console.error(` FAIL: ${msg}`)
} else {
console.log(` PASS: ${msg}`)
}
}
// ---------------------------------------------------------------------------
// 1. Static Metadata & Atlas Contract Verification
// ---------------------------------------------------------------------------
startSection('1. Static Manifest & Atlas Rects Verification')
const itemRects = BAKED_UI_MANIFEST.itemRects
assert(Object.keys(itemRects).length > 300, `itemRects has ${Object.keys(itemRects).length} registered entries`)
const dyedKeys = ['invcap_cgrn', 'invgth_dpur', 'invtgl_lgry', 'invvbl_blac']
for (const key of dyedKeys) {
const rect = itemRects[key]
assert(rect !== undefined, `Dyed rect '${key}' exists in itemRects`)
if (rect) {
assert(
rect.x >= 0 && rect.y >= 0 && rect.w > 0 && rect.h > 0 &&
rect.x + rect.w <= BAKED_UI_MANIFEST.atlasWidth &&
rect.y + rect.h <= BAKED_UI_MANIFEST.atlasHeight,
`Rect '${key}' [${rect.x}, ${rect.y}, ${rect.w}, ${rect.h}] within atlas bounds [${BAKED_UI_MANIFEST.atlasWidth}, ${BAKED_UI_MANIFEST.atlasHeight}]`
)
}
}
assert(BAKED_UI_MANIFEST.codeToInvFile['Harlequin Crest'] === 'invcap_cgrn', 'codeToInvFile maps "Harlequin Crest" -> "invcap_cgrn"')
assert(BAKED_UI_MANIFEST.codeToInvFile["Tal Rasha's Guardianship"] === 'invgth_dpur', 'codeToInvFile maps "Tal Rasha\'s Guardianship" -> "invgth_dpur"')
assert(BAKED_UI_MANIFEST.codeToInvFile['Magefist'] === 'invtgl_lgry', 'codeToInvFile maps "Magefist" -> "invtgl_lgry"')
assert(BAKED_UI_MANIFEST.codeToInvFile['Arachnid Mesh'] === 'invvbl_blac', 'codeToInvFile maps "Arachnid Mesh" -> "invvbl_blac"')
// ---------------------------------------------------------------------------
// 2. STARTER_EQUIPPED_GEAR Canonical 1.13c Data Parity
// ---------------------------------------------------------------------------
startSection('2. STARTER_EQUIPPED_GEAR Canonical 1.13c Parity')
// 2.1 Helm (Harlequin Crest)
const shako = STARTER_EQUIPPED_GEAR.helm
assert(shako !== undefined, 'STARTER_EQUIPPED_GEAR.helm is defined')
if (shako) {
assert(shako.invtransform === 'cgrn', `Shako invtransform is 'cgrn' (got: ${shako.invtransform})`)
assert(shako.defense === 141, `Shako defense is 141 (got: ${shako.defense})`)
assert(shako.durability?.current === 12 && shako.durability?.max === 12, `Shako durability is 12/12`)
assert(shako.reqStr === 50, `Shako reqStr is 50 (got: ${shako.reqStr})`)
assert(shako.reqLevel === 62, `Shako reqLevel is 62 (got: ${shako.reqLevel})`)
assert(shako.stats.length === 6, `Shako has exactly 6 canonical stats (got: ${shako.stats.length})`)
const shakoSprite = resolveItemSpriteRect(shako, itemRects)
assert(shakoSprite === itemRects['invcap_cgrn'], 'Shako sprite resolves to invcap_cgrn')
}
// 2.2 Armor (Tal Rasha's Guardianship)
const talArmor = STARTER_EQUIPPED_GEAR.armor
assert(talArmor !== undefined, 'STARTER_EQUIPPED_GEAR.armor is defined')
if (talArmor) {
assert(talArmor.invtransform === 'dpur', `Tal Armor invtransform is 'dpur' (got: ${talArmor.invtransform})`)
assert(talArmor.defense === 941, `Tal Armor defense is 941 (got: ${talArmor.defense})`)
assert(talArmor.durability?.current === 55 && talArmor.durability?.max === 55, 'Tal Armor durability is 55/55')
assert(talArmor.reqStr === 84, `Tal Armor reqStr is 84 (got: ${talArmor.reqStr})`)
assert(talArmor.reqLevel === 71, `Tal Armor reqLevel is 71 (got: ${talArmor.reqLevel})`)
assert(talArmor.setPieces !== undefined && talArmor.setPieces.length === 5, 'Tal Armor declares 5 setPieces')
assert(talArmor.setBonuses !== undefined && talArmor.setBonuses.length === 4, 'Tal Armor declares 4 setBonuses')
const armorSprite = resolveItemSpriteRect(talArmor, itemRects)
assert(armorSprite === itemRects['invgth_dpur'], 'Tal Armor sprite resolves to invgth_dpur')
}
// 2.3 Gloves (Magefist)
const magefist = STARTER_EQUIPPED_GEAR.gloves
assert(magefist !== undefined, 'STARTER_EQUIPPED_GEAR.gloves is defined')
if (magefist) {
assert(magefist.invtransform === 'lgry', `Magefist invtransform is 'lgry' (got: ${magefist.invtransform})`)
assert(magefist.defense === 25, `Magefist defense is 25 (got: ${magefist.defense})`)
assert(magefist.durability?.current === 14 && magefist.durability?.max === 14, 'Magefist durability is 14/14')
assert(magefist.reqStr === 45, `Magefist reqStr is 45 (got: ${magefist.reqStr})`)
assert(magefist.reqLevel === 23, `Magefist reqLevel is 23 (got: ${magefist.reqLevel})`)
const glovesSprite = resolveItemSpriteRect(magefist, itemRects)
assert(glovesSprite === itemRects['invtgl_lgry'], 'Magefist sprite resolves to invtgl_lgry')
}
// 2.4 Belt (Arachnid Mesh)
const arach = STARTER_EQUIPPED_GEAR.belt
assert(arach !== undefined, 'STARTER_EQUIPPED_GEAR.belt is defined')
if (arach) {
assert(arach.invtransform === 'blac', `Arachnid Mesh invtransform is 'blac' (got: ${arach.invtransform})`)
assert(arach.defense === 138, `Arachnid Mesh defense is 138 (got: ${arach.defense})`)
assert(arach.durability?.current === 12 && arach.durability?.max === 12, 'Arachnid Mesh durability is 12/12')
assert(arach.reqStr === 50, `Arachnid Mesh reqStr is 50 (got: ${arach.reqStr})`)
assert(arach.reqLevel === 80, `Arachnid Mesh reqLevel is 80 (got: ${arach.reqLevel})`)
const beltSprite = resolveItemSpriteRect(arach, itemRects)
assert(beltSprite === itemRects['invvbl_blac'], 'Arachnid Mesh sprite resolves to invvbl_blac')
}
// 2.5 Weapons & Other Equipped Slots
const oculus = STARTER_EQUIPPED_GEAR.weapon1
assert(oculus !== undefined, 'STARTER_EQUIPPED_GEAR.weapon1 is defined')
if (oculus) {
assert(oculus.damage === '18 - 42', `Oculus damage is '18 - 42'`)
assert(oculus.durability?.current === 50 && oculus.durability?.max === 50, 'Oculus durability is 50/50')
assert(oculus.speedText === '法球类 - 普通攻击速度', `Oculus speedText is '法球类 - 普通攻击速度'`)
assert(oculus.reqLevel === 42, `Oculus reqLevel is 42`)
assert(resolveItemSpriteRect(oculus, itemRects) === itemRects['invbst'], 'Oculus resolves to invbst via Tier 1 invFile')
}
const spirit = STARTER_EQUIPPED_GEAR.weapon2
assert(spirit !== undefined, 'STARTER_EQUIPPED_GEAR.weapon2 is defined')
if (spirit) {
assert(spirit.quality === 'rune', `Spirit quality is 'rune'`)
assert(spirit.runewordRunes === "'TalThulOrtAmn'", `Spirit runewordRunes is "'TalThulOrtAmn'"`)
assert(spirit.defense === 148, `Spirit defense is 148`)
assert(spirit.durability?.current === 148 && spirit.durability?.max === 148, 'Spirit durability is 148/148')
assert(spirit.reqStr === 156, `Spirit reqStr is 156`)
assert(spirit.reqLevel === 54, `Spirit reqLevel is 54`)
assert(spirit.sockets === 4, `Spirit sockets is 4`)
assert(resolveItemSpriteRect(spirit, itemRects) === itemRects['invtow'], 'Spirit resolves to invtow via Tier 1 invFile')
}
const boots = STARTER_EQUIPPED_GEAR.boots
assert(boots !== undefined, 'STARTER_EQUIPPED_GEAR.boots is defined')
if (boots) {
assert(boots.defense === 139, `War Traveler defense is 139`)
assert(boots.durability?.current === 48 && boots.durability?.max === 48, 'War Traveler durability is 48/48')
assert(boots.reqStr === 95, `War Traveler reqStr is 95`)
assert(boots.reqLevel === 42, `War Traveler reqLevel is 42`)
assert(resolveItemSpriteRect(boots, itemRects) === itemRects['invtbt'], 'War Traveler resolves to invtbt via Tier 1 invFile')
}
// ---------------------------------------------------------------------------
// 3. STARTER_BAG_ITEMS Canonical 1.13c Data Parity
// ---------------------------------------------------------------------------
startSection('3. STARTER_BAG_ITEMS Canonical 1.13c Parity')
const ctaPlacement = STARTER_BAG_ITEMS.find(p => p.item.id === 'bag-cta')
assert(ctaPlacement !== undefined, 'Call to Arms placement found in STARTER_BAG_ITEMS')
if (ctaPlacement) {
const cta = ctaPlacement.item
assert(cta.runewordRunes === "'AmnRalMalIstOhm'", `CTA runewordRunes is "'AmnRalMalIstOhm'"`)
assert(cta.damage === '17 - 52', `CTA damage is '17 - 52'`)
assert(cta.durability?.current === 20 && cta.durability?.max === 20, `CTA durability is 20/20`)
assert(cta.reqStr === 43, `CTA reqStr is 43`)
assert(cta.reqLevel === 57, `CTA reqLevel is 57`)
assert(cta.sockets === 5, `CTA sockets is 5`)
assert(cta.speedText === '剑类 - 极快的攻击速度', `CTA speedText is '剑类 - 极快的攻击速度'`)
assert(cta.stats.length === 11, `CTA has exactly 11 canonical stats (got: ${cta.stats.length})`)
const ctaSprite = resolveItemSpriteRect(cta, itemRects)
assert(ctaSprite === itemRects['invcrs'], 'CTA sprite resolves to invcrs')
}
const bagTorch = STARTER_BAG_ITEMS.find(p => p.item.id === 'bag-torch')?.item
assert(bagTorch?.reqLevel === 75, 'Hellfire Torch reqLevel is 75')
assert(bagTorch?.stats.length === 5, 'Hellfire Torch has 5 stats')
assert(bagTorch ? resolveItemSpriteRect(bagTorch, itemRects) === itemRects['invtrch'] : false, 'Torch resolves to invtrch')
const bagAnni = STARTER_BAG_ITEMS.find(p => p.item.id === 'bag-anni')?.item
assert(bagAnni?.reqLevel === 70, 'Annihilus reqLevel is 70')
assert(bagAnni?.stats.length === 4, 'Annihilus has 4 stats')
assert(bagAnni ? resolveItemSpriteRect(bagAnni, itemRects) === itemRects['invmss'] : false, 'Annihilus resolves to invmss')
const bagGheeds = STARTER_BAG_ITEMS.find(p => p.item.id === 'bag-gheeds')?.item
assert(bagGheeds?.reqLevel === 62, "Gheed's Fortune reqLevel is 62")
assert(bagGheeds?.stats.length === 3, "Gheed's Fortune has 3 stats")
assert(bagGheeds ? resolveItemSpriteRect(bagGheeds, itemRects) === itemRects['invsst'] : false, "Gheed's Fortune resolves to invsst")
// ---------------------------------------------------------------------------
// 4. Stateful Dye Persistence Simulation across UI Operations
// ---------------------------------------------------------------------------
startSection('4. Stateful Dye Persistence across Equip / Bag / Cursor')
const inv = new InventoryPanel()
const slotsToTest: { slot: EquipSlotId; expectedDye: string; expectedSprite: string }[] = [
{ slot: 'helm', expectedDye: 'cgrn', expectedSprite: 'invcap_cgrn' },
{ slot: 'armor', expectedDye: 'dpur', expectedSprite: 'invgth_dpur' },
{ slot: 'gloves', expectedDye: 'lgry', expectedSprite: 'invtgl_lgry' },
{ slot: 'belt', expectedDye: 'blac', expectedSprite: 'invvbl_blac' },
]
for (const { slot, expectedDye, expectedSprite } of slotsToTest) {
const itemBefore = inv.equipped[slot]
assert(itemBefore?.invtransform === expectedDye, `Slot ${slot} starts with invtransform '${expectedDye}'`)
// Pick up to cursor
const picked = inv.clickEquipSlot(slot)
assert(picked === true, `Picked up item from slot ${slot}`)
assert(inv.cursorItem !== null, `Cursor holds picked item`)
assert(inv.cursorItem?.invtransform === expectedDye, `CursorItem retains invtransform '${expectedDye}'`)
const cursorSprite = resolveItemSpriteRect(inv.cursorItem!, itemRects)
assert(cursorSprite === itemRects[expectedSprite], `CursorItem resolves sprite ${expectedSprite}`)
// Re-equip back to slot
const reEquipped = inv.clickEquipSlot(slot)
assert(reEquipped === true, `Re-equipped item to slot ${slot}`)
assert(inv.cursorItem === null, `Cursor empty after re-equipping`)
const itemAfter = inv.equipped[slot]
assert(itemAfter?.invtransform === expectedDye, `Re-equipped slot ${slot} retains invtransform '${expectedDye}'`)
const afterSprite = resolveItemSpriteRect(itemAfter!, itemRects)
assert(afterSprite === itemRects[expectedSprite], `Re-equipped slot ${slot} resolves sprite ${expectedSprite}`)
}
// ---------------------------------------------------------------------------
// 5. Stress-Testing Fallback Tiers (resolveItemSpriteRect)
// ---------------------------------------------------------------------------
startSection('5. Stress-Testing Fallback Tiers (resolveItemSpriteRect)')
// Tier 0: Transformed dyed item lookup
// 5.1 Case sensitivity of invFile and invtransform
const mixedCaseItem: ResolvableItem = {
invFile: 'InvCap',
invtransform: 'CGRN',
}
const mixedCaseSprite = resolveItemSpriteRect(mixedCaseItem, itemRects)
assert(mixedCaseSprite === itemRects['invcap_cgrn'], 'Tier 0 handles mixed-case InvCap + CGRN -> invcap_cgrn')
// 5.2 Invalid invtransform falls back to Tier 1 base invFile
const badTransformItem: ResolvableItem = {
invFile: 'invcap',
invtransform: 'nonexistent_dye_xyz',
}
const badTransformSprite = resolveItemSpriteRect(badTransformItem, itemRects)
assert(badTransformSprite === itemRects['invcap'], 'Invalid invtransform falls back to Tier 1 base invFile (invcap)')
// 5.3 Empty string invtransform falls back to Tier 1
const emptyTransformItem: ResolvableItem = {
invFile: 'invcap',
invtransform: '',
}
const emptyTransformSprite = resolveItemSpriteRect(emptyTransformItem, itemRects)
assert(emptyTransformSprite === itemRects['invcap'], 'Empty invtransform falls back to Tier 1 base invFile (invcap)')
// Tier 1: Direct invFile
const tier1Item: ResolvableItem = { invFile: 'invgth' }
assert(resolveItemSpriteRect(tier1Item, itemRects) === itemRects['invgth'], 'Tier 1 resolves direct invFile invgth')
const tier1UpperItem: ResolvableItem = { invFile: 'INVGTH' }
assert(resolveItemSpriteRect(tier1UpperItem, itemRects) === itemRects['invgth'], 'Tier 1 resolves uppercase INVGTH')
// Tier 2: Unique/set name in codeToInvFile
assert(resolveItemSpriteRect({ name: 'Harlequin Crest' }, itemRects) === itemRects['invcap_cgrn'], 'Tier 2 resolves "Harlequin Crest" -> invcap_cgrn')
assert(resolveItemSpriteRect({ name: 'Magefist' }, itemRects) === itemRects['invtgl_lgry'], 'Tier 2 resolves "Magefist" -> invtgl_lgry')
assert(resolveItemSpriteRect({ name: "Tal Rasha's Guardianship" }, itemRects) === itemRects['invgth_dpur'], 'Tier 2 resolves "Tal Rasha\'s Guardianship" -> invgth_dpur')
assert(resolveItemSpriteRect({ name: 'Arachnid Mesh' }, itemRects) === itemRects['invvbl_blac'], 'Tier 2 resolves "Arachnid Mesh" -> invvbl_blac')
// Tier 3: Base item code in codeToInvFile
const tier3Item: ResolvableItem = { code: 'hax' }
assert(resolveItemSpriteRect(tier3Item, itemRects) === itemRects['invhax'], 'Tier 3 resolves base code "hax" -> invhax')
assert(resolveItemSpriteRect({ code: 'cm1' }, itemRects) === itemRects['invchm'], 'Tier 3 resolves base code "cm1" -> invchm')
// Tier 4: Canonical code overrides (codes without base codeToInvFile collisions)
const tier4Overrides: [string, string][] = [
['tbk', 'invbbk'],
['ibk', 'invrbk'],
['box', 'invbox'],
['cube', 'invbox'],
['anni', 'invmss'],
['torch', 'invtrch'],
['gheeds', 'invsst'],
['crs', 'invcrs'],
['cta', 'invcrs'],
]
for (const [code, expectedInvFile] of tier4Overrides) {
const item: ResolvableItem = { code }
const res = resolveItemSpriteRect(item, itemRects)
assert(res === itemRects[expectedInvFile], `Tier 4 code override '${code}' -> '${expectedInvFile}'`)
}
// Tier 4: Name-based overrides
assert(resolveItemSpriteRect({ name: 'Annihilus Charm' }, itemRects) === itemRects['invmss'], 'Tier 4 name containing "Annihilus" -> invmss')
assert(resolveItemSpriteRect({ name: 'Hellfire Torch' }, itemRects) === itemRects['invtrch'], 'Tier 4 name containing "Torch" -> invtrch')
assert(resolveItemSpriteRect({ name: "Gheed's Fortune" }, itemRects) === itemRects['invsst'], "Tier 4 name containing \"Gheed\" -> invsst")
assert(resolveItemSpriteRect({ name: 'Call to Arms Sword' }, itemRects) === itemRects['invcrs'], 'Tier 4 name containing "Call to Arms" -> invcrs')
// Tier 5: Dimension-based fallbacks (w x h)
const dimensionCases: [number, number, string[]][] = [
[1, 1, ['invchm', 'invrin', 'invcap']],
[1, 2, ['invtrch', 'invbbk', 'invglv']],
[1, 3, ['invsst', 'invclb', 'invwnd']],
[1, 4, ['invbst', 'invstf']],
[2, 1, ['invvbl', 'invlbl']],
[2, 2, ['invbox', 'invcap']],
[2, 3, ['invcrs', 'invgth']],
[2, 4, ['invhxb', 'invpst']],
]
for (const [w, h, candidates] of dimensionCases) {
const item: ResolvableItem = { invWidth: w, invHeight: h }
const res = resolveItemSpriteRect(item, itemRects)
const matchedCandidate = candidates.some(c => itemRects[c] && res === itemRects[c])
assert(matchedCandidate, `Tier 5 dimension fallback (${w}x${h}) matched candidate in [${candidates.join(', ')}]`)
}
// ---------------------------------------------------------------------------
// 6. Boundary Cases & Invalid Dimensions
// ---------------------------------------------------------------------------
startSection('6. Boundary Cases & Invalid Dimensions')
// 6.1 Empty item object
const emptyItem: ResolvableItem = {}
const emptyRes = resolveItemSpriteRect(emptyItem, itemRects)
assert(emptyRes !== null, 'Empty item {} resolves to non-null default (1x1 fallback)')
// 6.2 Unusual / Invalid dimensions
const invalidDimensionItems: ResolvableItem[] = [
{ invWidth: 0, invHeight: 0 },
{ invWidth: -1, invHeight: -2 },
{ invWidth: 3, invHeight: 3 },
{ invWidth: 10, invHeight: 10 },
{ invWidth: NaN, invHeight: NaN },
{ invWidth: Infinity, invHeight: Infinity },
{ invWidth: -Infinity, invHeight: -Infinity },
{ invWidth: 1.5, invHeight: 2.7 },
]
for (const invalidItem of invalidDimensionItems) {
let thrown = false
let res: SpriteRect | null = null
try {
res = resolveItemSpriteRect(invalidItem, itemRects)
} catch (e) {
thrown = true
}
assert(!thrown, `Invalid dimensions (w: ${invalidItem.invWidth}, h: ${invalidItem.invHeight}) does not throw`)
assert(res !== null, `Invalid dimensions returns non-null fallback SpriteRect`)
}
// 6.3 Empty itemRects object
const emptyRectsRes = resolveItemSpriteRect({ code: 'crs' }, {})
assert(emptyRectsRes === null, 'Empty itemRects returns null without throwing')
// ---------------------------------------------------------------------------
// 7. Adversarial Fuzzing Stress Test (10,000 Random Mock Items)
// ---------------------------------------------------------------------------
startSection('7. Adversarial Fuzzing Stress Test (10,000 Randomized Items)')
function pseudoRandom(seed: number): () => number {
let s = seed
return () => {
s = (s * 1664525 + 1013904223) % 4294967296
return s / 4294967296
}
}
const rng = pseudoRandom(0xfeedface)
const sampleCodes = [undefined, '', 'uap', 'uth', 'tgl', 'ulc', 'crs', 'tbk', 'ibk', 'cm1', 'cm2', 'cm3', 'xyz_unknown', '@@@$$$']
const sampleNames = [undefined, '', 'Harlequin Crest', "Tal Rasha's Guardianship", 'Magefist', 'Arachnid Mesh', 'The Oculus', 'Spirit Monarch', 'Call to Arms', 'Unknown Item', 'Random Name 123']
const sampleInvFiles = [undefined, '', 'invcap', 'invgth', 'invtgl', 'invvbl', 'invcrs', 'invbbk', 'nonexistent_file', 'INV_CAP_UPPER']
const sampleTransforms = [undefined, '', 'cgrn', 'dpur', 'lgry', 'blac', 'unknown_dye', 'CGRN', 'DPUR']
const sampleDimensions = [undefined, 0, 1, 2, 3, 4, -1, -5, 10, NaN, Infinity, 1.5]
let fuzzIterations = 10000
let fuzzSuccessCount = 0
let dyedHitCount = 0
for (let i = 0; i < fuzzIterations; i++) {
const mockItem: ResolvableItem = {
code: sampleCodes[Math.floor(rng() * sampleCodes.length)],
name: sampleNames[Math.floor(rng() * sampleNames.length)],
invFile: sampleInvFiles[Math.floor(rng() * sampleInvFiles.length)],
invtransform: sampleTransforms[Math.floor(rng() * sampleTransforms.length)],
invWidth: sampleDimensions[Math.floor(rng() * sampleDimensions.length)],
invHeight: sampleDimensions[Math.floor(rng() * sampleDimensions.length)],
}
let rect: SpriteRect | null = null
try {
rect = resolveItemSpriteRect(mockItem, itemRects)
} catch (err: any) {
assert(false, `Fuzz item ${i} threw error: ${err.message}`)
break
}
// Verification 1: Must never return undefined
if (rect === undefined) {
assert(false, `Fuzz item ${i} returned undefined`)
break
}
// Verification 2: With populated itemRects, fallback chain guarantees non-null SpriteRect
if (rect === null) {
assert(false, `Fuzz item ${i} returned null unexpectedly`)
break
}
// Verification 3: Bounding box validity
if (
!Number.isFinite(rect.x) || !Number.isFinite(rect.y) ||
!Number.isFinite(rect.w) || !Number.isFinite(rect.h) ||
rect.w <= 0 || rect.h <= 0 ||
rect.x < 0 || rect.y < 0 ||
rect.x + rect.w > BAKED_UI_MANIFEST.atlasWidth ||
rect.y + rect.h > BAKED_UI_MANIFEST.atlasHeight
) {
assert(false, `Fuzz item ${i} returned invalid SpriteRect: [${rect.x}, ${rect.y}, ${rect.w}, ${rect.h}]`)
break
}
// Verification 4: If exact dyed match, it MUST resolve the dyed rect
if (mockItem.invFile && mockItem.invtransform) {
const expectedDyedKey = `${mockItem.invFile}_${mockItem.invtransform}`.toLowerCase()
if (itemRects[expectedDyedKey]) {
if (rect !== itemRects[expectedDyedKey]) {
assert(false, `Fuzz item ${i} had dyed match ${expectedDyedKey} but resolved different rect`)
break
}
dyedHitCount++
}
}
fuzzSuccessCount++
}
assert(fuzzSuccessCount === fuzzIterations, `All ${fuzzIterations} fuzzed items resolved safely without crashes or invalid rects`)
console.log(` (Fuzzed: ${fuzzSuccessCount} safe resolutions, ${dyedHitCount} dyed hits)`)
// ---------------------------------------------------------------------------
// Summary
// ---------------------------------------------------------------------------
console.log('\n======================================================')
console.log('CHALLENGER VERIFICATION SUMMARY')
console.log('======================================================')
let totalAssertions = 0
let totalFailures = 0
for (const sec of sections) {
totalAssertions += sec.assertions
totalFailures += sec.failures.length
const status = sec.failures.length === 0 ? 'PASS' : `FAIL (${sec.failures.length})`
console.log(`- ${sec.name}: ${status} [${sec.assertions} assertions]`)
}
console.log('------------------------------------------------------')
console.log(`Total assertions: ${totalAssertions}`)
console.log(`Total failures: ${totalFailures}`)
console.log('======================================================')
if (totalFailures > 0) {
process.exit(1)
} else {
console.log('ALL EMPIRICAL CHALLENGER TESTS PASSED.')
process.exit(0)
}