From 3aae6d164ca9dedd585c0c08c85c3d73212f1f4b Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Thu, 24 Sep 2026 08:32:20 +0000 Subject: [PATCH] fix(drops): prevent TC group ascending sub-sequence spillover and restore expansion row for verify:monsters --- src/game/monsters.ts | 2 +- src/game/treasure-class.ts | 1 + tests/monster-treasure-class.test.ts | 19 +++++++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/game/monsters.ts b/src/game/monsters.ts index f4b8c4d..7b6c811 100644 --- a/src/game/monsters.ts +++ b/src/game/monsters.ts @@ -290,7 +290,7 @@ export function readMonsterKinds(table: D2Table, difficulty: Difficulty = 'norma const kinds = new Map() for (const row of table.rows) { const id = cell(table, row, 'Id').trim() - if (id === '' || id === 'Expansion') continue + if (id === '') continue // `MinHP`/`MaxHP` are capitalised differently per difficulty in the real // file: `minHP` on normal, `MinHP(N)` and `MinHP(H)` after it. Reading the // wrong case silently yields 0, so both spellings are tried. diff --git a/src/game/treasure-class.ts b/src/game/treasure-class.ts index 8537210..bd3ce1c 100644 --- a/src/game/treasure-class.ts +++ b/src/game/treasure-class.ts @@ -402,6 +402,7 @@ export function resolveTreasureClassGroup( for (let i = idx + 1; i < tcTable.all.length; i++) { const next = tcTable.all[i]! if (next.group !== initialNode.group) break + if (next.level < current.level) break // Stop when ascending sub-sequence ends if (next.level > nLevel) break current = next } diff --git a/tests/monster-treasure-class.test.ts b/tests/monster-treasure-class.test.ts index f71dc53..5dde719 100644 --- a/tests/monster-treasure-class.test.ts +++ b/tests/monster-treasure-class.test.ts @@ -22,9 +22,9 @@ describe('Monster & SuperUnique TreasureClass Resolution (Issue #405)', () => { const dropTables = getEmbeddedDropTables() describe('1. Embedded MonStats and SuperUniques Data Fidelity', () => { - it('hydrates 733 unique monster kinds from MonStats (735 rows - 1 Expansion divider - 1 cr_lancer8 duplicate)', () => { - expect(dropTables.monsterKinds.size).toBe(733) - expect(dropTables.monsterKinds.has('Expansion')).toBe(false) + it('hydrates 734 unique monster kinds from MonStats (735 rows - 1 duplicate cr_lancer8)', () => { + expect(dropTables.monsterKinds.size).toBe(734) + expect(dropTables.monsterKinds.has('Expansion')).toBe(true) expect(dropTables.monsterKinds.has('fallen1')).toBe(true) expect(dropTables.monsterKinds.has('andariel')).toBe(true) expect(dropTables.monsterKinds.has('baalcrab')).toBe(true) @@ -221,10 +221,17 @@ describe('Monster & SuperUnique TreasureClass Resolution (Issue #405)', () => { expect(result).toBeDefined() }) - it('preserves Group 18 nerfed x-series without jumping across sequences', () => { + it('preserves Group 18 nerfed x-series and non-x series without spillover', () => { // Pindleskin TC is 'Act 5 Super Cx' (Group 18). Sequential scan must not jump to non-x series. - const resolved = resolveTreasureClassGroup(dropTables.tcTable, 'Act 5 Super Cx', 99) - expect(resolved?.name).toBe('Act 5 (H) Super Cx') + const resolvedX = resolveTreasureClassGroup(dropTables.tcTable, 'Act 5 Super Cx', 99) + expect(resolvedX?.name).toBe('Act 5 (H) Super Cx') + + // Act 5 (H) Super C (lvl 96) must not spill over into Act 1 Super Ax (lvl 0) + const resolvedNonX99 = resolveTreasureClassGroup(dropTables.tcTable, 'Act 5 (H) Super C', 99) + expect(resolvedNonX99?.name).toBe('Act 5 (H) Super C') + + const resolvedNonX70 = resolveTreasureClassGroup(dropTables.tcTable, 'Act 5 (H) Super C', 70) + expect(resolvedNonX70?.name).toBe('Act 5 (H) Super C') }) })