80 lines
3.7 KiB
TypeScript
80 lines
3.7 KiB
TypeScript
/**
|
|
* Regression: monster drops rendered with the wrong ground sprite.
|
|
*
|
|
* Simulation items produced by executeDropPipeline carry `code` as the 4-byte
|
|
* space-padded .d2s field ('dr3 '). The ground resolver looked that up verbatim
|
|
* in codeToFlippyFile, missed, and fell through to the inventory-size guess, so
|
|
* every 2x2 drop (Antlers 'dr3', caps, ...) rendered as the Horadric Cube
|
|
* (invbox). Picking the item up rebuilt it through itemToUiInventoryItem, which
|
|
* is why re-dropping it looked correct.
|
|
*/
|
|
import { describe, it, expect } from 'vitest'
|
|
import { getEmbeddedDropTables } from '../src/game/embedded-drop-tables.ts'
|
|
import { createDroppedItem, executeDropPipeline } from '../src/game/drop-pipeline.ts'
|
|
import { D2Rng } from '../src/game/d2-rng.ts'
|
|
import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts'
|
|
import { normalizeItemCode, resolveGroundItemSpriteRect, resolveItemSpriteRect } from '../src/ui/inventory.ts'
|
|
import { itemToUiInventoryItem } from '../src/ui/item-bridge.ts'
|
|
|
|
const VARIANT_INV_CODES = new Set(['amu', 'rin', 'jew'])
|
|
|
|
describe('monster drop ground sprite resolution', () => {
|
|
const tables = getEmbeddedDropTables()
|
|
const flippyRects = BAKED_UI_MANIFEST.flippyRects
|
|
const codeToFlippy = BAKED_UI_MANIFEST.codeToFlippyFile
|
|
|
|
it('normalizes padded .d2s codes and falls back to base.id', () => {
|
|
expect(normalizeItemCode({ code: 'dr3 ' })).toBe('dr3')
|
|
expect(normalizeItemCode({ code: 'hp1 ' })).toBe('hp1')
|
|
expect(normalizeItemCode({ base: { id: 'cap' } } as any)).toBe('cap')
|
|
expect(normalizeItemCode({})).toBeUndefined()
|
|
})
|
|
|
|
it('renders a pipeline-built Antlers (dr3) with flpdr3, not the Horadric Cube', () => {
|
|
const base = tables.getBase('dr3')!
|
|
expect(base).toBeDefined()
|
|
const item = createDroppedItem(base, 'normal', { ilvl: 5, dwInitSeed: 1 } as any)
|
|
expect(item.code).toBe('dr3 ')
|
|
const rect = resolveGroundItemSpriteRect(item as any)
|
|
expect(rect).toBe(flippyRects[codeToFlippy['dr3']!])
|
|
expect(rect).not.toBe(flippyRects['flpbox'])
|
|
expect(rect).not.toBe(BAKED_UI_MANIFEST.itemRects['invbox'])
|
|
})
|
|
|
|
it('ground sprite of a raw drop equals the sprite after the pickup→UI round trip', () => {
|
|
const rng = new D2Rng(7)
|
|
let checked = 0
|
|
for (let i = 0; i < 400; i++) {
|
|
for (const drop of executeDropPipeline(tables, { tcName: 'Act 1 Champ A', nLevel: 5, monsterType: 2, monsterRng: rng } as any)) {
|
|
if (drop.base.id === 'gold') continue
|
|
const raw = resolveGroundItemSpriteRect(drop as any)
|
|
const ui = resolveGroundItemSpriteRect(itemToUiInventoryItem(drop, tables) as any)
|
|
expect(raw, `${drop.base.id} ${drop.name}`).toBe(ui)
|
|
checked++
|
|
}
|
|
}
|
|
expect(checked).toBeGreaterThan(100)
|
|
})
|
|
|
|
it('every droppable weapon/armor/misc base with a baked flippy resolves to it directly', () => {
|
|
const missing: string[] = []
|
|
let checked = 0
|
|
for (const [code, flp] of Object.entries(codeToFlippy)) {
|
|
const base = tables.getBase(code)
|
|
if (!base || !flippyRects[flp]) continue
|
|
{
|
|
checked++
|
|
const item = createDroppedItem(base, 'normal', { ilvl: 1, dwInitSeed: 1 } as any)
|
|
if (resolveGroundItemSpriteRect(item as any) !== flippyRects[flp]) missing.push(code)
|
|
const inv = BAKED_UI_MANIFEST.codeToInvFile[code]
|
|
// amu/rin/jew pick one of several invfile variants (invamu1..3 etc.) per item seed.
|
|
if (!VARIANT_INV_CODES.has(code) && inv && BAKED_UI_MANIFEST.itemRects[inv] && resolveItemSpriteRect(item as any) !== BAKED_UI_MANIFEST.itemRects[inv]) {
|
|
missing.push(`inv:${code}`)
|
|
}
|
|
}
|
|
}
|
|
expect(checked).toBeGreaterThan(300)
|
|
expect(missing).toEqual([])
|
|
})
|
|
})
|