fix(monster-mapping): update hellbovine token to EC, remove XX tokens, and eliminate startsWith fallback
- Update hellbovine token from CW to EC in MONSTER_ART_MAP - Remove invalid XX cut monster entries from MONSTER_ART_MAP and ACT_MONSTER_SPECS - Replace startsWith iteration in resolveMonsterArtSpec with deterministic O(1) exact and base lookup - Prevent XX tokens from leaking into art requests TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
This commit is contained in:
parent
04d562a99c
commit
2e73d7b02f
|
|
@ -109,11 +109,6 @@ export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
|||
"bloodlord5": { token: "L3", weapon: "hth" },
|
||||
"bloodlord6": { token: "L3", weapon: "hth" },
|
||||
"bloodlord7": { token: "L3", weapon: "hth" },
|
||||
"bloodmage1": { token: "XX", weapon: "hth" },
|
||||
"bloodmage2": { token: "XX", weapon: "hth" },
|
||||
"bloodmage3": { token: "XX", weapon: "hth" },
|
||||
"bloodmage4": { token: "XX", weapon: "hth" },
|
||||
"bloodmage5": { token: "XX", weapon: "hth" },
|
||||
"bloodraven": { token: "CR", weapon: "bow" },
|
||||
"blunderbore1": { token: "PN", weapon: "hth" },
|
||||
"blunderbore2": { token: "PN", weapon: "hth" },
|
||||
|
|
@ -212,11 +207,6 @@ export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
|||
"crownest3": { token: "BN", weapon: "hth" },
|
||||
"crownest4": { token: "BN", weapon: "hth" },
|
||||
"cycleoflife": { token: "K9", weapon: "hth" },
|
||||
"darkguard1": { token: "XX", weapon: "hth" },
|
||||
"darkguard2": { token: "XX", weapon: "hth" },
|
||||
"darkguard3": { token: "XX", weapon: "hth" },
|
||||
"darkguard4": { token: "XX", weapon: "hth" },
|
||||
"darkguard5": { token: "XX", weapon: "hth" },
|
||||
"darkwanderer": { token: "1Z", weapon: "hth" },
|
||||
"deathmauler1": { token: "M5", weapon: "hth" },
|
||||
"deathmauler2": { token: "M5", weapon: "hth" },
|
||||
|
|
@ -345,7 +335,7 @@ export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
|||
"griswold": { token: "GZ", weapon: "hth" },
|
||||
"halbu": { token: "20", weapon: "hth" },
|
||||
"heartofwolverine": { token: "X3", weapon: "hth" },
|
||||
"hellbovine": { token: "CW", weapon: "hth" },
|
||||
"hellbovine": { token: "EC", weapon: "hth" },
|
||||
"hellmeteor": { token: "K9", weapon: "hth" },
|
||||
"hephasto": { token: "5P", weapon: "hth" },
|
||||
"hratli": { token: "HR", weapon: "hth" },
|
||||
|
|
@ -375,7 +365,6 @@ export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
|||
"kashya": { token: "RC", weapon: "hth" },
|
||||
"larva": { token: "LV", weapon: "hth" },
|
||||
"larzuk": { token: "XR", weapon: "hth" },
|
||||
"lightningbeast": { token: "XX", weapon: "hth" },
|
||||
"lightningsentry": { token: "LG", weapon: "hth" },
|
||||
"lightningspire": { token: "AE", weapon: "hth" },
|
||||
"lysander": { token: "LY", weapon: "hth" },
|
||||
|
|
@ -409,17 +398,6 @@ export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
|||
"mephistospirit": { token: "M6", weapon: "hth" },
|
||||
"meshif1": { token: "MS", weapon: "hth" },
|
||||
"meshif2": { token: "M3", weapon: "hth" },
|
||||
"minion1": { token: "XX", weapon: "hth" },
|
||||
"minion10": { token: "XX", weapon: "hth" },
|
||||
"minion11": { token: "XX", weapon: "hth" },
|
||||
"minion2": { token: "XX", weapon: "hth" },
|
||||
"minion3": { token: "XX", weapon: "hth" },
|
||||
"minion4": { token: "XX", weapon: "hth" },
|
||||
"minion5": { token: "XX", weapon: "hth" },
|
||||
"minion6": { token: "XX", weapon: "hth" },
|
||||
"minion7": { token: "XX", weapon: "hth" },
|
||||
"minion8": { token: "XX", weapon: "hth" },
|
||||
"minion9": { token: "XX", weapon: "hth" },
|
||||
"minionspawner1": { token: "XA", weapon: "hth" },
|
||||
"minionspawner2": { token: "XA", weapon: "hth" },
|
||||
"minionspawner3": { token: "XA", weapon: "hth" },
|
||||
|
|
@ -609,7 +587,6 @@ export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
|||
"snowyeti2": { token: "IO", weapon: "hth" },
|
||||
"snowyeti3": { token: "IO", weapon: "hth" },
|
||||
"snowyeti4": { token: "IO", weapon: "hth" },
|
||||
"spiritmummy": { token: "XX", weapon: "hth" },
|
||||
"spiritofbarbs": { token: "X4", weapon: "hth" },
|
||||
"spiritwolf": { token: "WF", weapon: "hth" },
|
||||
"succubus1": { token: "0B", weapon: "hth" },
|
||||
|
|
@ -765,7 +742,7 @@ export const ACT_MONSTER_SPECS: Record<number, readonly MonsterArtSpec[]> = {
|
|||
2: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "SL", weapon: "hth" }, { token: "PW", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "VD", weapon: "hth" }, { token: "PW", weapon: "1ht" }, { token: "BN", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "SW", weapon: "hth" }, { token: "SR", weapon: "hth" }, { token: "SD", weapon: "hth" }, { token: "MM", weapon: "hth" }, { token: "GY", weapon: "hth" }, { token: "SK", weapon: "1hs" }, { token: "SK", weapon: "bow" }, { token: "BB", weapon: "hth" }, { token: "PN", weapon: "hth" }, { token: "BT", weapon: "hth" }, { token: "WR", weapon: "hth" }, { token: "VA", weapon: "hth" }, { token: "GM", weapon: "2hs" }],
|
||||
3: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "BB", weapon: "hth" }, { token: "MO", weapon: "hth" }, { token: "TH", weapon: "hth" }, { token: "FE", weapon: "1hs" }, { token: "FC", weapon: "hth" }, { token: "BK", weapon: "hth" }, { token: "FD", weapon: "hth" }, { token: "FW", weapon: "hth" }, { token: "WW", weapon: "hth" }, { token: "VD", weapon: "hth" }, { token: "SL", weapon: "hth" }, { token: "ZZ", weapon: "hth" }, { token: "SW", weapon: "hth" }, { token: "ZP", weapon: "hth" }, { token: "SR", weapon: "hth" }, { token: "VA", weapon: "hth" }, { token: "SP", weapon: "hth" }, { token: "BT", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "MM", weapon: "hth" }, { token: "FK", weapon: "1hs" }, { token: "WR", weapon: "hth" }, { token: "GY", weapon: "hth" }, { token: "CR", weapon: "1hs" }, { token: "SD", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "CR", weapon: "bow" }, { token: "PN", weapon: "hth" }],
|
||||
4: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "SL", weapon: "hth" }, { token: "VM", weapon: "hth" }, { token: "FR", weapon: "hth" }, { token: "CS", weapon: "hth" }, { token: "UM", weapon: "fgt" }, { token: "DM", weapon: "hth" }, { token: "WW", weapon: "hth" }, { token: "BH", weapon: "hth" }, { token: "BT", weapon: "hth" }, { token: "UM", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "PN", weapon: "hth" }, { token: "UM", weapon: "mgk" }],
|
||||
5: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "XX", weapon: "hth" }, { token: "M5", weapon: "hth" }, { token: "OS", weapon: "hth" }, { token: "IP", weapon: "hth" }, { token: "SK", weapon: "bow" }, { token: "BH", weapon: "hth" }, { token: "OX", weapon: "hth" }, { token: "F0", weapon: "hth" }, { token: "0B", weapon: "hth" }, { token: "L3", weapon: "hth" }, { token: "IO", weapon: "hth" }, { token: "RE", weapon: "hth" }, { token: "0D", weapon: "hth" }, { token: "0F", weapon: "hth" }, { token: "0E", weapon: "hth" }, { token: "45", weapon: "hth" }, { token: "0C", weapon: "hth" }, { token: "XY", weapon: "hth" }, { token: "CR", weapon: "1hs" }, { token: "CR", weapon: "bow" }, { token: "CR", weapon: "2ht" }, { token: "SW", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "SP", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "UM", weapon: "mgk" }, { token: "DM", weapon: "hth" }],
|
||||
5: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "M5", weapon: "hth" }, { token: "OS", weapon: "hth" }, { token: "IP", weapon: "hth" }, { token: "SK", weapon: "bow" }, { token: "BH", weapon: "hth" }, { token: "OX", weapon: "hth" }, { token: "F0", weapon: "hth" }, { token: "0B", weapon: "hth" }, { token: "L3", weapon: "hth" }, { token: "IO", weapon: "hth" }, { token: "RE", weapon: "hth" }, { token: "0D", weapon: "hth" }, { token: "0F", weapon: "hth" }, { token: "0E", weapon: "hth" }, { token: "45", weapon: "hth" }, { token: "0C", weapon: "hth" }, { token: "XY", weapon: "hth" }, { token: "CR", weapon: "1hs" }, { token: "CR", weapon: "bow" }, { token: "CR", weapon: "2ht" }, { token: "SW", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "SP", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "UM", weapon: "mgk" }, { token: "DM", weapon: "hth" }],
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -773,13 +750,15 @@ export const ACT_MONSTER_SPECS: Record<number, readonly MonsterArtSpec[]> = {
|
|||
*/
|
||||
export function resolveMonsterArtSpec(id: string): MonsterArtSpec | null {
|
||||
const lower = id.toLowerCase()
|
||||
if (MONSTER_ART_MAP[lower]) {
|
||||
return MONSTER_ART_MAP[lower]
|
||||
const direct = MONSTER_ART_MAP[lower]
|
||||
if (direct && direct.token !== 'XX' && direct.token.trim() !== '') {
|
||||
return direct
|
||||
}
|
||||
const base = lower.replace(/\d+$/, '')
|
||||
for (const [key, spec] of Object.entries(MONSTER_ART_MAP)) {
|
||||
if (key.startsWith(base)) {
|
||||
return spec
|
||||
if (base !== lower) {
|
||||
const baseSpec = MONSTER_ART_MAP[base]
|
||||
if (baseSpec && baseSpec.token !== 'XX' && baseSpec.token.trim() !== '') {
|
||||
return baseSpec
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { resolveMonsterArtSpec, MONSTER_ART_MAP } from '../src/game/monster-mapping.ts'
|
||||
import { resolveMonsterArtSpec, MONSTER_ART_MAP, ACT_MONSTER_SPECS } from '../src/game/monster-mapping.ts'
|
||||
|
||||
describe('monster-mapping', () => {
|
||||
it('resolves standard Act 1 monsters', () => {
|
||||
|
|
@ -10,13 +10,36 @@ describe('monster-mapping', () => {
|
|||
expect(resolveMonsterArtSpec('cr_archer1')).toEqual({ token: 'CR', weapon: 'bow' })
|
||||
})
|
||||
|
||||
it('resolves numbered variants through prefix fallback', () => {
|
||||
it('resolves numbered variants through exact or base lookup without prefix iteration order', () => {
|
||||
expect(resolveMonsterArtSpec('fallen3')?.token).toBe('FA')
|
||||
expect(resolveMonsterArtSpec('zombie5')?.token).toBe('ZM')
|
||||
expect(resolveMonsterArtSpec('quillrat6')?.token).toBe('SI')
|
||||
// Suffix with arbitrary letters must not prefix-match
|
||||
expect(resolveMonsterArtSpec('fallen_fake')).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves hellbovine to EC token instead of CW', () => {
|
||||
expect(resolveMonsterArtSpec('hellbovine')).toEqual({ token: 'EC', weapon: 'hth' })
|
||||
expect(MONSTER_ART_MAP.hellbovine).toEqual({ token: 'EC', weapon: 'hth' })
|
||||
})
|
||||
|
||||
it('does not leak XX tokens or crash on cut monsters', () => {
|
||||
const cutMonsters = ['bloodmage1', 'darkguard1', 'lightningbeast', 'minion1', 'spiritmummy']
|
||||
for (const id of cutMonsters) {
|
||||
expect(resolveMonsterArtSpec(id)).toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
it('ACT_MONSTER_SPECS does not contain any XX tokens in any act', () => {
|
||||
for (const [act, specs] of Object.entries(ACT_MONSTER_SPECS)) {
|
||||
for (const spec of specs) {
|
||||
expect(spec.token, `Act ${act} contains invalid token ${spec.token}`).not.toBe('XX')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('returns null for unknown monsters', () => {
|
||||
expect(resolveMonsterArtSpec('non_existent_monster_xyz')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe('offline pre-baked entity atlases & full Act 1..5 pack index (Issues #3
|
|||
it('maps 700+ monster IDs across Acts 1..5 in MONSTER_ART_MAP', () => {
|
||||
expect(Object.keys(MONSTER_ART_MAP).length).toBeGreaterThanOrEqual(700)
|
||||
expect(MONSTER_ART_MAP.fallen).toEqual({ token: 'FA', weapon: 'hth' })
|
||||
expect(MONSTER_ART_MAP.hellbovine).toEqual({ token: 'CW', weapon: 'hth' })
|
||||
expect(MONSTER_ART_MAP.hellbovine).toEqual({ token: 'EC', weapon: 'hth' })
|
||||
expect(MONSTER_ART_MAP.skeleton).toEqual({ token: 'SK', weapon: 'hth' })
|
||||
})
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ describe('offline pre-baked entity atlases & full Act 1..5 pack index (Issues #3
|
|||
uniqueSpecs.add(`${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`)
|
||||
}
|
||||
}
|
||||
expect(uniqueSpecs.size).toBe(66)
|
||||
expect(uniqueSpecs.size).toBe(65)
|
||||
|
||||
for (const key of uniqueSpecs) {
|
||||
const slug = `monster-${key}`
|
||||
|
|
|
|||
Loading…
Reference in New Issue