/** * tests/challenger-m4-inventory-audit.test.ts * * EMPIRICAL CHALLENGER ADVERSARIAL TEST HARNESS * Milestone M4 (Issue #149): Complete base item atlas resolution, bounding limits, * transformed item dyes, authentic charm aspect ratios, and fallback guarantees. */ import { describe, expect, it } from 'vitest' import { join } from 'node:path' import { existsSync } from 'node:fs' import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts' import { resolveItemSpriteRect, STARTER_BAG_ITEMS, STARTER_EQUIPPED_GEAR, } from '../src/ui/inventory.ts' import { MountedArchives } from '../src/mpq/mount.ts' import { MpqArchive } from '../src/mpq/archive.ts' import { fileSource } from '../src/mpq/file-source.ts' import { parseTable } from '../src/game/tables.ts' describe('Challenger M4 Inventory & Item Atlas Audit (Issue #149)', () => { const itemRects = BAKED_UI_MANIFEST.itemRects const atlasWidth = BAKED_UI_MANIFEST.atlasWidth const atlasHeight = BAKED_UI_MANIFEST.atlasHeight describe('Objective 1: Atlas Bounding Limits & Integrity', () => { it('contains at least 399 sprite rects in BAKED_UI_MANIFEST.itemRects', () => { const keys = Object.keys(itemRects) expect(keys.length).toBeGreaterThanOrEqual(399) }) it('enforces that every sprite rect in itemRects is strictly within atlas bounds', () => { expect(atlasWidth).toBe(1024) expect(atlasHeight).toBeGreaterThanOrEqual(1024) for (const [key, rect] of Object.entries(itemRects)) { expect(rect, `Sprite ${key} must be defined`).toBeDefined() expect(rect.w, `Sprite ${key} width must be > 0`).toBeGreaterThan(0) expect(rect.h, `Sprite ${key} height must be > 0`).toBeGreaterThan(0) expect(rect.x, `Sprite ${key} x must be >= 0`).toBeGreaterThanOrEqual(0) expect(rect.y, `Sprite ${key} y must be >= 0`).toBeGreaterThanOrEqual(0) // Strict boundary containment const right = rect.x + rect.w const bottom = rect.y + rect.h expect( right, `Sprite ${key} exceeds atlas width: right edge ${right} > atlasWidth ${atlasWidth}`, ).toBeLessThanOrEqual(atlasWidth) expect( bottom, `Sprite ${key} exceeds atlas height: bottom edge ${bottom} > atlasHeight ${atlasHeight}`, ).toBeLessThanOrEqual(atlasHeight) } }) it('enforces that questRects are strictly within questsAtlas bounds', () => { const questRects = BAKED_UI_MANIFEST.questRects const qW = BAKED_UI_MANIFEST.questsAtlasWidth const qH = BAKED_UI_MANIFEST.questsAtlasHeight expect(Object.keys(questRects).length).toBeGreaterThan(0) for (const [key, rect] of Object.entries(questRects)) { expect(rect.w).toBeGreaterThan(0) expect(rect.h).toBeGreaterThan(0) expect(rect.x).toBeGreaterThanOrEqual(0) expect(rect.y).toBeGreaterThanOrEqual(0) expect(rect.x + rect.w).toBeLessThanOrEqual(qW) expect(rect.y + rect.h).toBeLessThanOrEqual(qH) } }) }) describe('Objective 2: Transformed Items (Dyes) Existence & Bounding', () => { const transformedKeys = [ 'invcap_cgrn', // Emerald Harlequin Crest Shako 'invgth_dpur', // Dark Purple Gothic Plate 'invtgl_lgry', // Light Grey Heavy Gloves 'invvbl_blac', // Black Vampirefang Belt 'invlbl_blac', // Black Light Belt ] it.each(transformedKeys)('transformed item sprite "%s" exists with non-zero dimensions inside atlas', (key) => { const rect = itemRects[key] expect(rect, `Transformed sprite "${key}" must exist in itemRects`).toBeDefined() expect(rect!.w, `Transformed sprite "${key}" width must be > 0`).toBeGreaterThan(0) expect(rect!.h, `Transformed sprite "${key}" height must be > 0`).toBeGreaterThan(0) expect(rect!.x + rect!.w).toBeLessThanOrEqual(atlasWidth) expect(rect!.y + rect!.h).toBeLessThanOrEqual(atlasHeight) }) it('resolves transformed dyed items via resolveItemSpriteRect with case-insensitivity', () => { const shako = resolveItemSpriteRect({ invFile: 'invcap', invtransform: 'cgrn' }, itemRects) expect(shako).toEqual(itemRects['invcap_cgrn']) const shakoUpper = resolveItemSpriteRect({ invFile: 'INVCAP', invtransform: 'CGRN' }, itemRects) expect(shakoUpper).toEqual(itemRects['invcap_cgrn']) const gothic = resolveItemSpriteRect({ invFile: 'invgth', invtransform: 'dpur' }, itemRects) expect(gothic).toEqual(itemRects['invgth_dpur']) const gloves = resolveItemSpriteRect({ invFile: 'invtgl', invtransform: 'lgry' }, itemRects) expect(gloves).toEqual(itemRects['invtgl_lgry']) const beltV = resolveItemSpriteRect({ invFile: 'invvbl', invtransform: 'blac' }, itemRects) expect(beltV).toEqual(itemRects['invvbl_blac']) const beltL = resolveItemSpriteRect({ invFile: 'invlbl', invtransform: 'blac' }, itemRects) expect(beltL).toEqual(itemRects['invlbl_blac']) }) }) describe('Objective 3: 100+ Base Items from weapons.txt, armor.txt, and misc.txt', () => { it('empirically audits 100+ base items from 1.13c MPQ data tables', async () => { const d2Path = join(process.cwd(), 'samples', 'd2') if (!existsSync(join(d2Path, 'Patch_D2.mpq'))) { throw new Error('Authoritative MPQ directory samples/d2 is missing or unmounted') } const archives = new MountedArchives() for (const m of ['d2char.mpq', 'd2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq']) { archives.add(m, await MpqArchive.open(await fileSource(join(d2Path, m)))) } let totalBaseItemsAudited = 0 const auditedCodes = new Set() for (const tblFile of ['weapons.txt', 'armor.txt', 'misc.txt']) { const data = await archives.read(`data/global/excel/${tblFile}`) const tbl = parseTable(new TextDecoder().decode(data)) for (const row of tbl.rows) { const code = row['code']?.trim() if (!code || auditedCodes.has(code)) continue auditedCodes.add(code) const invfile = row['invfile']?.trim() const name = row['name']?.trim() // 1. Resolve with full item info const rectFull = resolveItemSpriteRect({ code, invFile: invfile, name }, itemRects) expect( rectFull, `Base item code="${code}" (invFile="${invfile}", name="${name}") from ${tblFile} must resolve`, ).not.toBeNull() expect(rectFull!.w).toBeGreaterThan(0) expect(rectFull!.h).toBeGreaterThan(0) expect(rectFull!.x + rectFull!.w).toBeLessThanOrEqual(atlasWidth) expect(rectFull!.y + rectFull!.h).toBeLessThanOrEqual(atlasHeight) // 2. Resolve by code alone (Tier 3 lookup in codeToInvFile) const rectByCode = resolveItemSpriteRect({ code }, itemRects) expect( rectByCode, `Base item code="${code}" must resolve without invFile`, ).not.toBeNull() // 3. Strict charm aspect ratio and ground truth assertions if (code === 'cm1') { expect(rectFull!.w).toBe(28) expect(rectFull!.h).toBe(28) expect(rectByCode!.h).toBe(28) } else if (code === 'cm2') { expect(rectFull!.w).toBe(28) expect(rectFull!.h).toBe(56) expect(rectByCode!.h).toBe(56) expect(rectFull).not.toEqual(itemRects['invwnd']) } else if (code === 'cm3') { expect(rectFull!.w).toBe(28) expect(rectFull!.h).toBe(84) expect(rectByCode!.h).toBe(84) expect(rectFull).not.toEqual(itemRects['invsst']) } totalBaseItemsAudited++ } } console.log(`Empirically audited ${totalBaseItemsAudited} unique base items from MPQ data tables.`) expect(totalBaseItemsAudited).toBeGreaterThanOrEqual(100) }) }) describe('Objective 4: Dimension Fallbacks Stress Testing', () => { it('w=1, h=2 resolves to 56px height', () => { const item1x2 = resolveItemSpriteRect({ invWidth: 1, invHeight: 2 }, itemRects) expect(item1x2).not.toBeNull() expect(item1x2!.h).toBe(56) expect(item1x2!.w).toBe(28) }) it('w=1, h=3 resolves to 84px height', () => { // Non-charm generic 1x3 const generic1x3 = resolveItemSpriteRect({ invWidth: 1, invHeight: 3 }, itemRects) expect(generic1x3).not.toBeNull() expect(generic1x3!.h).toBe(84) expect(generic1x3!.w).toBe(28) // Charm-designated 1x3 const charm1x3 = resolveItemSpriteRect({ name: 'Mystic Grand Charm', invWidth: 1, invHeight: 3 }, itemRects) expect(charm1x3).not.toBeNull() expect(charm1x3!.h).toBe(84) expect(charm1x3!.w).toBe(28) expect(charm1x3).toEqual(itemRects['invch3']) }) it('exhaustively verifies all standard grid dimension fallbacks', () => { const testCases = [ { w: 1, h: 1, expectedH: 28 }, { w: 1, h: 2, expectedH: 56 }, { w: 1, h: 3, expectedH: 84 }, { w: 1, h: 4, expectedH: 112 }, { w: 2, h: 1, expectedH: 28 }, { w: 2, h: 2, expectedH: 56 }, { w: 2, h: 3, expectedH: 84 }, { w: 2, h: 4, expectedH: 112 }, ] for (const { w, h, expectedH } of testCases) { const res = resolveItemSpriteRect({ invWidth: w, invHeight: h }, itemRects) expect(res, `Fallback for w=${w}, h=${h} must resolve`).not.toBeNull() expect(res!.h, `Fallback for w=${w}, h=${h} must match height ${expectedH}`).toBe(expectedH) } }) }) describe('Adversarial & Boundary Edge Cases', () => { it('handles empty item object safely without crashing', () => { const empty = resolveItemSpriteRect({}, itemRects) expect(empty).not.toBeNull() expect(empty!.w).toBeGreaterThan(0) expect(empty!.h).toBeGreaterThan(0) }) it('handles unmapped item with unknown code but valid dimensions', () => { const unknownItem = resolveItemSpriteRect( { code: 'nonexistent_code_9999', invWidth: 1, invHeight: 2 }, itemRects, ) expect(unknownItem).not.toBeNull() expect(unknownItem!.h).toBe(56) }) it('handles adversarial keys without prototype pollution or exceptions', () => { const badKeys = ['__proto__', 'constructor', 'toString', 'valueOf', '