/** * Authoritative Ground Truth Oracle for Diablo II v1.13c Item Drop Parity Tests (Issues #412–#428). * * Direct MPQ sources: * - /usr/local/google/home/taodao/d2-data (d2data.mpq, d2exp.mpq, Patch_D2.mpq, D2Common.dll, D2Game.dll) * - Fallback: Embedded 1.13c canonical tables in src/data/canonical-drop-data.ts */ import { existsSync } from 'node:fs' import { MpqArchive } from '../../src/mpq/archive.ts' import { fileSource } from '../../src/mpq/file-source.ts' import { MountedArchives } from '../../src/mpq/mount.ts' import { type DataTable, numberCell, parseTable, textCell } from '../../src/game/tables.ts' import { RAW_MONSTATS, RAW_SUPER_UNIQUES, RAW_TC_EX, RAW_ITEM_RATIO, } from '../../src/data/canonical-drop-data.ts' export const D2_DATA_DIR = '/usr/local/google/home/taodao/d2-data' export interface DropOracleStack { mounted: MountedArchives | null diffs: DataTable monstats: DataTable superuniques: DataTable tcEx: DataTable itemRatio: DataTable weapons: DataTable armor: DataTable misc: DataTable levels: DataTable monstatsById: Map>> superuniquesByName: Map>> tcExByName: Map>> weaponsByCode: Map>> armorByCode: Map>> miscByCode: Map>> } let cachedOracle: DropOracleStack | null = null export async function getDropOracle(): Promise { const g = globalThis as Record if (g.__d2e2eDropOracle) { return g.__d2e2eDropOracle as DropOracleStack } if (cachedOracle !== null) { g.__d2e2eDropOracle = cachedOracle return cachedOracle } let mounted: MountedArchives | null = null let diffs: DataTable let monstats: DataTable let superuniques: DataTable let tcEx: DataTable let itemRatio: DataTable let weapons: DataTable let armor: DataTable let misc: DataTable let levels: DataTable if (existsSync(`${D2_DATA_DIR}/Patch_D2.mpq`)) { mounted = new MountedArchives() for (const name of ['d2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq']) { const fullPath = `${D2_DATA_DIR}/${name}` if (existsSync(fullPath)) { mounted.add(name, await MpqArchive.open(await fileSource(fullPath))) } } const readTable = async (memberPath: string): Promise => { const bytes = await mounted!.read(memberPath) return parseTable(new TextDecoder('latin1').decode(bytes)) } ;[diffs, monstats, superuniques, tcEx, itemRatio, weapons, armor, misc, levels] = await Promise.all([ readTable('data\\global\\excel\\difficultylevels.txt'), readTable('data\\global\\excel\\monstats.txt'), readTable('data\\global\\excel\\superuniques.txt'), readTable('data\\global\\excel\\TreasureClassEx.txt'), readTable('data\\global\\excel\\ItemRatio.txt'), readTable('data\\global\\excel\\weapons.txt'), readTable('data\\global\\excel\\armor.txt'), readTable('data\\global\\excel\\misc.txt'), readTable('data\\global\\excel\\Levels.txt'), ]) } else { // Hermetic fallback from embedded canonical strings tcEx = parseTable(RAW_TC_EX) monstats = parseTable(RAW_MONSTATS) superuniques = parseTable(RAW_SUPER_UNIQUES) itemRatio = parseTable(RAW_ITEM_RATIO) diffs = parseTable( 'Name\tUberCodeOddsNormal\tUberCodeOddsGood\tUltraCodeOddsNormal\tUltraCodeOddsGood\r\n' + 'Normal\t0\t0\t0\t0\r\n' + 'Nightmare\t10\t20\t0\t0\r\n' + 'Hell\t20\t40\t30\t40\r\n', ) weapons = parseTable('code\tdropsound\tdropsfxframe\r\n') armor = parseTable('code\tdropsound\tdropsfxframe\r\n') misc = parseTable('code\tdropsound\tdropsfxframe\r\n') levels = parseTable('Id\tLevelName\r\n') } const monstatsById = new Map>>() for (const row of monstats.rows) { if (row.Id) monstatsById.set(row.Id, row) } const superuniquesByName = new Map>>() for (const row of superuniques.rows) { if (row.Superunique) superuniquesByName.set(row.Superunique, row) } const tcExByName = new Map>>() for (const row of tcEx.rows) { const name = row['Treasure Class'] if (name) tcExByName.set(name, row) } const weaponsByCode = new Map>>() for (const row of weapons.rows) { if (row.code) weaponsByCode.set(row.code, row) } const armorByCode = new Map>>() for (const row of armor.rows) { if (row.code) armorByCode.set(row.code, row) } const miscByCode = new Map>>() for (const row of misc.rows) { if (row.code) miscByCode.set(row.code, row) } cachedOracle = { mounted, diffs, monstats, superuniques, tcEx, itemRatio, weapons, armor, misc, levels, monstatsById, superuniquesByName, tcExByName, weaponsByCode, armorByCode, miscByCode, } g.__d2e2eDropOracle = cachedOracle return cachedOracle } /** * 1.13c Magic Find diminishing returns (D2Game/src/ITEMS/Items.cpp lines 2346-2433). */ export function compute113cEffectiveMf( mf: number, quality: 'unique' | 'set' | 'rare' | 'magic', ): number { if (mf <= 0) return 0 switch (quality) { case 'unique': return Math.floor((mf * 250) / (mf + 250)) case 'set': return Math.floor((mf * 500) / (mf + 500)) case 'rare': return Math.floor((mf * 600) / (mf + 600)) case 'magic': return mf default: return 0 } } /** * 1.13c NoDrop exponential dampening formula (D2Game/src/ITEMS/Items.cpp lines 2142-2166). */ export function compute113cScaledNoDrop( baseNoDrop: number, totalProb: number, gamePlayers = 1, partyPlayers = 1, ): number { if (baseNoDrop <= 0 || totalProb <= 0) return 0 const g = Math.max(1, Math.min(64, Math.floor(gamePlayers))) const p = Math.max(1, Math.min(g, Math.floor(partyPlayers))) const effectivePlayers = p + Math.floor((g - p) / 2) if (effectivePlayers <= 1) return baseNoDrop const noDropRatio = Math.pow(baseNoDrop / (baseNoDrop + totalProb), effectivePlayers) if (noDropRatio >= 1) return baseNoDrop return Math.floor((totalProb * noDropRatio) / (1 - noDropRatio)) } /** * 1.13c Character inventory gold capacity limit (D2Common/src/Units/Units.cpp line 3103). * UNITS_GetInventoryGoldLimit: 10,000 * level. */ export function compute113cGoldLimit(characterLevel: number): number { const lvl = Math.max(1, Math.min(99, Math.floor(characterLevel))) return lvl * 10_000 } /** * 1.13c Physical gold graphic tier (3 tiers: 1-49 small, 50-499 medium, 500+ large). */ export function compute113cGoldGraphicTier(amount: number): 'small' | 'medium' | 'large' { if (amount >= 500) return 'large' if (amount >= 50) return 'medium' return 'small' } /** * 1.13c Monster level modifiers for elites (D2Game/src/MONSTER/MonsterUnique.cpp lines 269, 294). */ export function compute113cEliteMlvl( baseLevel: number, rank: 'normal' | 'champion' | 'unique' | 'minion' | 'boss', ): number { let bonus = 0 if (rank === 'champion') bonus = 2 else if (rank === 'unique' || rank === 'minion') bonus = 3 return Math.max(1, Math.min(99, baseLevel + bonus)) } /** * 1.13c Ground item label text color rules: * Gray (#808080) for ethereal or socketed normal/superior items. */ export function compute113cGroundLabelColor( quality: string, isEthereal = false, isSocketed = false, ): string { const q = quality.toLowerCase() if (q === 'normal' || q === 'superior' || q === 'inferior' || q === 'low') { if (isEthereal || isSocketed) { return '#808080' } if (q === 'superior') return '#ffffff' if (q === 'inferior' || q === 'low') return '#808080' return '#d8d8d8' } switch (q) { case 'magic': return '#6868ff' case 'rare': return '#ffff64' case 'set': return '#00fc00' case 'unique': return '#c8a15a' case 'craft': case 'rune': return '#ff9c18' case 'gold': return '#d8b420' default: return '#d8d8d8' } }