diablo2-web/scripts/verify-challenger-m3-toolti...

598 lines
23 KiB
TypeScript

/**
* scripts/verify-tooltip-empirical.ts
*
* Standalone Empirical Challenger Verification & Stress Test Suite
* for Milestone M3 (Diablo II v1.13c Item Tooltips & Parity).
*
* Validates:
* 1. Tooltip rendering across all 10 starter equipped items:
* Shako, Mara's, Oculus, Tal Armor, Spirit, Magefist, Arachnid, SOJ, BK, WT.
* 2. Tooltip rendering across starter bag items:
* CTA, Torch, Anni, Gheed's Fortune.
* 3. Deep canonical assertions on Harlequin Crest (Shako) and Call to Arms (CTA).
* 4. Dynamic vertical flip threshold (hover.y = 200, 279, 280, 500) and viewport clamping.
* 5. formatItemTooltip runeword, unique, set, and socketable formatting parity.
* 6. Adversarial edge cases: extreme coordinates, long text lines, zero/missing fields.
* 7. Rapid iteration stress harness (2,000 hover frames).
* 8. Zero runtime MPQ/DLL network/file dependency guard.
*/
import { WorldPanelsHud } from '../src/ui/world-panels.ts'
import {
STARTER_EQUIPPED_GEAR,
STARTER_BAG_ITEMS,
InventoryPanel,
EQUIP_SLOTS_LAYOUT,
INV_GRID_ORIGIN,
type UiInventoryItem,
type EquipSlotId,
} from '../src/ui/inventory.ts'
import { formatItemTooltip, type FormattedItemTooltip } from '../src/game/item-tooltip.ts'
import type { Item, ItemBase } from '../src/game/items.ts'
import type { D2ColorCode, D2FontName, D2FontRenderer } from '../src/ui/font.ts'
interface DrawCall {
text: string
x: number
y: number
options?: {
font?: D2FontName
color?: D2ColorCode
align?: 'left' | 'center' | 'right'
shadow?: boolean
}
}
interface MockFontRenderer extends D2FontRenderer {
drawCalls: DrawCall[]
}
function createMockFont(): MockFontRenderer {
const drawCalls: DrawCall[] = []
return {
metas: {} as any,
images: new Map(),
tintedCache: new Map(),
getTintedAtlas: () => null as any,
measureText: (text: string, _font: D2FontName = 'font8') => {
return text.length * 7
},
drawText: (_ctx: any, text: string, x: number, y: number, options?: any) => {
drawCalls.push({ text, x, y, options })
},
drawCalls,
} as unknown as MockFontRenderer
}
interface MockCanvasContext {
fillStyle: string
strokeStyle: string
fillRectCalls: [number, number, number, number][]
strokeRectCalls: [number, number, number, number][]
fillRect: (x: number, y: number, w: number, h: number) => void
strokeRect: (x: number, y: number, w: number, h: number) => void
}
function createMockCanvas(): MockCanvasContext {
const fillRectCalls: [number, number, number, number][] = []
const strokeRectCalls: [number, number, number, number][] = []
return {
fillStyle: '',
strokeStyle: '',
fillRectCalls,
strokeRectCalls,
fillRect: (x: number, y: number, w: number, h: number) => {
fillRectCalls.push([x, y, w, h])
},
strokeRect: (x: number, y: number, w: number, h: number) => {
strokeRectCalls.push([x, y, w, h])
},
}
}
let totalAssertions = 0
let failedAssertions = 0
function assert(condition: boolean, message: string) {
totalAssertions++
if (!condition) {
failedAssertions++
console.error(` [FAIL] ${message}`)
} else {
console.log(` [PASS] ${message}`)
}
}
function section(title: string) {
console.log(`\n======================================================`)
console.log(`>>> ${title}`)
console.log(`======================================================`)
}
// ---------------------------------------------------------------------------
// Section 1: All 10 Starter Equipped Items Traversal
// ---------------------------------------------------------------------------
section('1. Verification of 10 Starter Equipped Items in drawItemTooltip')
const worldPanels = new WorldPanelsHud()
const equippedSlots: EquipSlotId[] = [
'helm',
'amulet',
'weapon1',
'armor',
'weapon2',
'gloves',
'ring1',
'belt',
'ring2',
'boots',
]
for (const slot of equippedSlots) {
const item = STARTER_EQUIPPED_GEAR[slot]
assert(item !== undefined, `Slot '${slot}' must have defined starter item`)
if (!item) continue
const font = createMockFont()
const canvas = createMockCanvas()
worldPanels.drawItemTooltip(canvas as any, { item, x: 400, y: 300 }, font)
assert(canvas.fillRectCalls.length === 1, `${slot} (${item.name}): exactly 1 tooltip background rect drawn`)
assert(canvas.strokeRectCalls.length === 1, `${slot} (${item.name}): exactly 1 tooltip border rect drawn`)
assert(font.drawCalls.length >= 3, `${slot} (${item.name}): has at least title, base name, and stats`)
const titleCall = font.drawCalls[0]
assert(titleCall?.text === item.nameZh, `${slot}: Title is '${item.nameZh}'`)
assert(titleCall?.options?.font === 'fontexocet10', `${slot}: Title font is 'fontexocet10'`)
const expectedTitleColor =
item.quality === 'unique' || item.quality === 'rune' ? 'gold' :
item.quality === 'set' ? 'green' : 'white'
assert(titleCall?.options?.color === expectedTitleColor, `${slot}: Title color is '${expectedTitleColor}'`)
}
// ---------------------------------------------------------------------------
// Section 2: Deep Canonical Assertions on Harlequin Crest (Shako)
// ---------------------------------------------------------------------------
section('2. Detailed Parity on Harlequin Crest (Shako)')
const shako = STARTER_EQUIPPED_GEAR.helm!
const shakoFont = createMockFont()
const shakoCanvas = createMockCanvas()
worldPanels.drawItemTooltip(shakoCanvas as any, { item: shako, x: 400, y: 320 }, shakoFont)
// Title
const shakoTitle = shakoFont.drawCalls.find(d => d.text.includes('谐角之冠'))
assert(shakoTitle !== undefined, 'Harlequin Crest title line exists')
assert(shakoTitle?.options?.color === 'gold', 'Harlequin Crest title line is in gold')
assert(shakoTitle?.options?.font === 'fontexocet10', 'Harlequin Crest title line uses fontexocet10')
// Base Name
const shakoBase = shakoFont.drawCalls.find(d => d.text.includes('军帽'))
assert(shakoBase !== undefined, 'Base name "军帽" line exists')
assert(shakoBase?.options?.color === 'white', 'Base name "军帽" is in white')
assert(shakoBase?.options?.font === 'font8', 'Base name "军帽" uses font8')
// Defense
const shakoDef = shakoFont.drawCalls.find(d => d.text === '防御: 141')
assert(shakoDef !== undefined, 'Defense line "防御: 141" exists')
assert(shakoDef?.options?.color === 'white', 'Defense line is in white')
// Durability
const shakoDur = shakoFont.drawCalls.find(d => d.text === '耐久度: 12 之 12')
assert(shakoDur !== undefined, 'Durability line "耐久度: 12 之 12" exists')
assert(shakoDur?.options?.color === 'white', 'Durability line is in white')
// Required Strength
const shakoStr = shakoFont.drawCalls.find(d => d.text === '需要力量: 50')
assert(shakoStr !== undefined, 'Req strength line "需要力量: 50" exists')
assert(shakoStr?.options?.color === 'white', 'Req strength line is in white')
// Required Level
const shakoLvl = shakoFont.drawCalls.find(d => d.text === '需要等级: 62')
assert(shakoLvl !== undefined, 'Req level line "需要等级: 62" exists')
assert(shakoLvl?.options?.color === 'white', 'Req level line is in white')
// 6 Canonical Stats
const expectedShakoStats = [
'+2 所有技能 (+2 To All Skills)',
'+1.5 生命 (依角色等级而定)',
'+1.5 法力 (依角色等级而定)',
'物理伤害减少 10%',
'50% 更佳机会取得魔法装备 (MF)',
'+2 所有属性',
]
for (const statText of expectedShakoStats) {
const found = shakoFont.drawCalls.find(d => d.text === statText)
assert(found !== undefined, `Shako stat "${statText}" is drawn`)
assert(found?.options?.color === 'blue', `Shako stat "${statText}" is drawn in blue`)
}
// ---------------------------------------------------------------------------
// Section 3: Deep Canonical Assertions on Call to Arms (CTA)
// ---------------------------------------------------------------------------
section('3. Detailed Parity on Call to Arms (CTA)')
const ctaPlacement = STARTER_BAG_ITEMS.find(p => p.item.id === 'bag-cta')
assert(ctaPlacement !== undefined, 'CTA placement exists in STARTER_BAG_ITEMS')
const cta = ctaPlacement!.item
const ctaFont = createMockFont()
const ctaCanvas = createMockCanvas()
worldPanels.drawItemTooltip(ctaCanvas as any, { item: cta, x: 400, y: 350 }, ctaFont)
// Title
const ctaTitle = ctaFont.drawCalls[0]
assert(ctaTitle?.text === '战争召唤 (Call to Arms)', 'CTA title is "战争召唤 (Call to Arms)"')
assert(ctaTitle?.options?.color === 'gold', 'CTA title is in gold')
assert(ctaTitle?.options?.font === 'fontexocet10', 'CTA title uses fontexocet10')
// Runes line
const ctaRunes = ctaFont.drawCalls[1]
assert(ctaRunes?.text === "'AmnRalMalIstOhm'", "CTA runes line is ''AmnRalMalIstOhm''")
assert(ctaRunes?.options?.color === 'gold', 'CTA runes line is in gold')
assert(ctaRunes?.options?.font === 'font8', 'CTA runes line uses font8')
// Base name
const ctaBase = ctaFont.drawCalls[2]
assert(ctaBase?.text === '水晶剑 (Crystal Sword)', 'CTA base name is "水晶剑 (Crystal Sword)"')
assert(ctaBase?.options?.color === 'white', 'CTA base name is in white')
assert(ctaBase?.options?.font === 'font8', 'CTA base name uses font8')
// Damage
const ctaDmg = ctaFont.drawCalls.find(d => d.text.includes('17 - 52'))
assert(ctaDmg !== undefined, 'CTA damage line "单手伤害: 17 - 52" exists')
assert(ctaDmg?.text === '单手伤害: 17 - 52', 'CTA damage line is exact "单手伤害: 17 - 52"')
assert(ctaDmg?.options?.color === 'white', 'CTA damage line is in white')
// Durability
const ctaDur = ctaFont.drawCalls.find(d => d.text === '耐久度: 20 之 20')
assert(ctaDur !== undefined, 'CTA durability line "耐久度: 20 之 20" exists')
assert(ctaDur?.options?.color === 'white', 'CTA durability line is in white')
// Speed Text
const ctaSpeed = ctaFont.drawCalls.find(d => d.text === '剑类 - 极快的攻击速度')
assert(ctaSpeed !== undefined, 'CTA speed class line "剑类 - 极快的攻击速度" exists')
assert(ctaSpeed?.options?.color === 'white', 'CTA speed class line is in white')
// Req Strength
const ctaStr = ctaFont.drawCalls.find(d => d.text === '需要力量: 43')
assert(ctaStr !== undefined, 'CTA req strength line "需要力量: 43" exists')
assert(ctaStr?.options?.color === 'white', 'CTA req strength line is in white')
// Req Level
const ctaLvl = ctaFont.drawCalls.find(d => d.text === '需要等级: 57')
assert(ctaLvl !== undefined, 'CTA req level line "需要等级: 57" exists')
assert(ctaLvl?.options?.color === 'white', 'CTA req level line is in white')
// All 11 Canonical CTA Stats
const expectedCtaStats = [
'+1 所有技能 (+1 To All Skills)',
'+40% 增加攻击速度 (+40% Increased Attack Speed)',
'+255% 增强伤害 (+255% Enhanced Damage)',
'增加 5-30 火焰伤害 (Adds 5-30 Fire Damage)',
'7% 生命偷取 / 每次命中偷取生命 (7% Life Stolen)',
'防止怪物自愈 / 防止怪物自疗 (Prevent Monster Heal)',
'+6 战斗体制 (Battle Orders)',
'+6 战斗指挥 (Battle Command)',
'+4 战斗狂嗥 / 战嚎 (Battle Cry)',
'生命回复 +12 / 生命补满 +12 (Replenish Life +12)',
'30% 更佳机会取得魔法装备 (30% Better Chance of Getting Magic Items)',
]
for (const statText of expectedCtaStats) {
const found = ctaFont.drawCalls.find(d => d.text === statText)
assert(found !== undefined, `CTA stat "${statText}" is drawn`)
assert(found?.options?.color === 'blue', `CTA stat "${statText}" is in blue`)
}
// Sockets line
const ctaSockets = ctaFont.drawCalls.find(d => d.text === '凹槽 (5)')
assert(ctaSockets !== undefined, 'CTA sockets line "凹槽 (5)" exists')
assert(ctaSockets?.options?.color === 'blue', 'CTA sockets line is in blue')
// ---------------------------------------------------------------------------
// Section 4: Starter Bag Charms (Torch, Anni, Gheed's)
// ---------------------------------------------------------------------------
section('4. Verification of Starter Bag Charms (Torch, Anni, Gheed\'s)')
const charms = ['bag-torch', 'bag-anni', 'bag-gheeds']
for (const charmId of charms) {
const pl = STARTER_BAG_ITEMS.find(p => p.item.id === charmId)
assert(pl !== undefined, `Charm '${charmId}' exists in STARTER_BAG_ITEMS`)
if (!pl) continue
const font = createMockFont()
const canvas = createMockCanvas()
worldPanels.drawItemTooltip(canvas as any, { item: pl.item, x: 400, y: 300 }, font)
const title = font.drawCalls[0]
assert(title?.text === pl.item.nameZh, `${charmId}: Title matches '${pl.item.nameZh}'`)
assert(title?.options?.color === 'gold', `${charmId}: Title is gold`)
assert(font.drawCalls.some(d => d.text === pl.item.baseNameZh && d.options?.color === 'white'), `${charmId}: Base name in white`)
assert(font.drawCalls.some(d => d.text === `需要等级: ${pl.item.reqLevel}` && d.options?.color === 'white'), `${charmId}: Req level in white`)
for (const st of pl.item.stats) {
assert(font.drawCalls.some(d => d.text === st.text && d.options?.color === 'blue'), `${charmId}: Stat "${st.text}" in blue`)
}
}
// ---------------------------------------------------------------------------
// Section 5: Dynamic Vertical Flip Threshold & Screen Clamping
// ---------------------------------------------------------------------------
section('5. Dynamic Vertical Flip Threshold & Boundary Clamping')
const testItem: UiInventoryItem = {
id: 'test-flip-item',
code: 'cap',
invFile: 'invcap',
name: 'Cap',
nameZh: '便帽',
baseNameZh: '便帽',
quality: 'normal',
invWidth: 2,
invHeight: 2,
allowedSlots: ['helm'],
stats: [],
}
// Case 1: hover.y = 200 (< 280) -> by = 224 (hover.y + 24)
{
const font = createMockFont()
const canvas = createMockCanvas()
worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 200 }, font)
const [, by, , boxH] = canvas.fillRectCalls[0]!
assert(by === 224, `hover.y = 200 -> by = ${by} (expected 224)`)
assert(by + boxH <= 592, `hover.y = 200 -> bottom edge within 592`)
}
// Case 2: hover.y = 279 (< 280) -> by = 303 (hover.y + 24)
{
const font = createMockFont()
const canvas = createMockCanvas()
worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 279 }, font)
const [, by] = canvas.fillRectCalls[0]!
assert(by === 303, `hover.y = 279 -> by = ${by} (expected 303)`)
}
// Case 3: hover.y = 280 (>= 280) -> by < 280 (above cursor: 280 - boxH - 8)
{
const font = createMockFont()
const canvas = createMockCanvas()
worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 280 }, font)
const [, by, , boxH] = canvas.fillRectCalls[0]!
assert(by < 280, `hover.y = 280 -> by = ${by} (expected < 280)`)
assert(by === 280 - boxH - 8, `hover.y = 280 -> by = 280 - boxH - 8 (${280 - boxH - 8})`)
}
// Case 4: hover.y = 500 (>= 280) -> by < 500 (above cursor: 500 - boxH - 8)
{
const font = createMockFont()
const canvas = createMockCanvas()
worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 500 }, font)
const [, by, , boxH] = canvas.fillRectCalls[0]!
assert(by < 500, `hover.y = 500 -> by = ${by} (expected < 500)`)
assert(by === 500 - boxH - 8, `hover.y = 500 -> by = 500 - boxH - 8 (${500 - boxH - 8})`)
}
// Case 5: Viewport horizontal and vertical boundaries clamping
{
// Far left (hover.x = 0)
const fontL = createMockFont()
const canvasL = createMockCanvas()
worldPanels.drawItemTooltip(canvasL as any, { item: testItem, x: 0, y: 300 }, fontL)
const [bxL] = canvasL.fillRectCalls[0]!
assert(bxL === 8, `hover.x = 0 clamps bx to 8 (got ${bxL})`)
// Far right (hover.x = 800)
const fontR = createMockFont()
const canvasR = createMockCanvas()
worldPanels.drawItemTooltip(canvasR as any, { item: testItem, x: 800, y: 300 }, fontR)
const [bxR, , boxWR] = canvasR.fillRectCalls[0]!
assert(bxR + boxWR === 792, `hover.x = 800 clamps bx + boxW to 792 (bx=${bxR}, boxW=${boxWR})`)
// Far bottom (hover.y = 600)
const fontB = createMockFont()
const canvasB = createMockCanvas()
worldPanels.drawItemTooltip(canvasB as any, { item: testItem, x: 400, y: 600 }, fontB)
const [, byB, , boxHB] = canvasB.fillRectCalls[0]!
assert(byB + boxHB === 592, `hover.y = 600 clamps by + boxH to 592 (by=${byB}, boxH=${boxHB})`)
}
// ---------------------------------------------------------------------------
// Section 6: formatItemTooltip Unit Verification
// ---------------------------------------------------------------------------
section('6. formatItemTooltip Runeword & Quality Verification')
// Test 6.1: Runeword item formatting
const rawRunewordItem: Item = {
id: 101,
name: 'Call to Arms',
base: {
id: 'crs',
name: 'Crystal Sword',
kind: 'weapon',
tags: ['sword', 'weap'],
damage: 15,
defense: 0,
value: 5000,
invWidth: 2,
invHeight: 3,
maxStack: 1,
level: 11,
mindam: 5,
maxdam: 15,
reqstr: 43,
levelreq: 57,
durability: 20,
gemsockets: 5,
} as any,
rarity: 'runeword',
level: 85,
flags: {} as any,
location: {} as any,
stats: {},
sockets: [],
nameZh: '战争召唤 (Call to Arms)',
baseNameZh: '水晶剑 (Crystal Sword)',
runewordRunes: "'AmnRalMalIstOhm'",
speedText: '剑类 - 极快的攻击速度',
rolledProps: [
{ code: 'allskills', value: 1, min: 1, max: 1 },
{ code: 'ias', value: 40, min: 40, max: 40 },
{ code: 'dmg%', value: 255, min: 255, max: 255 },
{ code: 'dmg-fire', value: 5, min: 5, max: 30 },
{ code: 'lifesteal', value: 7, min: 7, max: 7 },
],
} as any
const formattedRw = formatItemTooltip(rawRunewordItem, 'zh')
assert(formattedRw.qualityColor === '#c8a15a', 'Runeword qualityColor is #c8a15a (gold)')
assert(formattedRw.quality === '符文之语', 'Runeword quality is "符文之语"')
assert(formattedRw.title === '战争召唤 (Call to Arms)', 'Runeword title is "战争召唤 (Call to Arms)"')
assert(formattedRw.subTitle === '水晶剑 (Crystal Sword)', 'Runeword subTitle is "水晶剑 (Crystal Sword)"')
assert(formattedRw.runewordRunes === "'AmnRalMalIstOhm'", 'Runeword runes preserved in formatItemTooltip')
assert(formattedRw.speedText === '剑类 - 极快的攻击速度', 'Runeword speedText preserved in formatItemTooltip')
assert(formattedRw.reqStr === 43, 'Runeword reqStr is 43')
assert(formattedRw.reqLevel === 57, 'Runeword reqLevel is 57')
assert(formattedRw.sockets === 5, 'Runeword sockets is 5')
// Test 6.2: Socketable Rune formatting
const rawRuneItem: Item = {
id: 102,
name: 'Ohm Rune',
code: 'r27',
base: {
id: 'r27',
name: 'Ohm Rune',
kind: 'other',
tags: ['rune'],
damage: 0,
defense: 0,
value: 1000,
invWidth: 1,
invHeight: 1,
maxStack: 1,
level: 57,
} as any,
rarity: 'normal',
level: 57,
flags: {} as any,
location: {} as any,
stats: {},
sockets: [],
} as any
const formattedRune = formatItemTooltip(rawRuneItem, 'zh')
assert(formattedRune.isSocketable === true, 'Rune is marked isSocketable')
assert(formattedRune.lines.some(l => l.text.includes('可镶嵌进有凹槽的装备')), 'Rune has socket instruction line')
assert(formattedRune.lines.some(l => l.text.includes('武器:')), 'Rune has weapons line')
// ---------------------------------------------------------------------------
// Section 7: Adversarial Stress Tests & Edge Cases
// ---------------------------------------------------------------------------
section('7. Adversarial Stress Testing & Fuzzing')
// 7.1 Wide and tall item within viewport limits
const wideTallItem: UiInventoryItem = {
id: 'wide-tall-item',
code: 'cap',
invFile: 'invcap',
name: 'Wide and Tall Crafted Cap of the Mammoth',
nameZh: '超大巨兽之精工合成便帽 (Crafted Cap of Mammoth)',
baseNameZh: '便帽 (Cap)',
quality: 'craft',
invWidth: 2,
invHeight: 2,
allowedSlots: ['helm'],
stats: Array.from({ length: 15 }, (_, i) => ({
text: `+${(i + 1) * 5}% 增强防御与全抗性描述 (${i + 1})`,
color: 'blue' as D2ColorCode,
})),
}
const wideFont = createMockFont()
const wideCanvas = createMockCanvas()
worldPanels.drawItemTooltip(wideCanvas as any, { item: wideTallItem, x: 750, y: 150 }, wideFont)
assert(wideCanvas.fillRectCalls.length === 1, 'Wide item tooltip renders without error')
const [wx, wy, ww, wh] = wideCanvas.fillRectCalls[0]!
assert(wx >= 8, `Wide box X left bound clamped (got ${wx} >= 8)`)
assert(wx + ww <= 792, `Wide box X right bound clamped within 792 (got ${wx + ww} <= 792)`)
assert(wy >= 8, `Wide box Y top bound clamped (got ${wy} >= 8)`)
assert(wy + wh <= 592, `Wide box Y bottom bound clamped within 592 (got ${wy + wh} <= 592)`)
// 7.1b Extreme adversarial input (300 chars, 50 stats) safety without throwing
const extremeItem: UiInventoryItem = {
id: 'extreme-item',
code: 'cap',
invFile: 'invcap',
name: 'A'.repeat(300),
nameZh: '长'.repeat(250),
baseNameZh: '基'.repeat(200),
quality: 'craft',
invWidth: 2,
invHeight: 2,
allowedSlots: ['helm'],
stats: Array.from({ length: 50 }, (_, i) => ({
text: `极限属性描述 #${i + 1}: ` + '超长文本测试'.repeat(10),
color: 'blue' as D2ColorCode,
})),
}
const extremeFont = createMockFont()
const extremeCanvas = createMockCanvas()
worldPanels.drawItemTooltip(extremeCanvas as any, { item: extremeItem, x: 400, y: 150 }, extremeFont)
assert(extremeCanvas.fillRectCalls.length === 1, 'Extreme item tooltip renders without error')
const [ex, ey, ew, eh] = extremeCanvas.fillRectCalls[0]!
assert(Number.isFinite(ex) && Number.isFinite(ey) && Number.isFinite(ew) && Number.isFinite(eh), 'Extreme item produces finite numeric bounds')
// 7.2 Completely bare item with zero stats / undefs
const bareItem: UiInventoryItem = {
id: 'bare-item',
code: 'cap',
invFile: 'invcap',
name: 'Bare Cap',
nameZh: '便帽',
baseNameZh: '',
quality: 'normal',
invWidth: 1,
invHeight: 1,
allowedSlots: [],
stats: [],
}
const bareFont = createMockFont()
const bareCanvas = createMockCanvas()
worldPanels.drawItemTooltip(bareCanvas as any, { item: bareItem, x: 200, y: 100 }, bareFont)
assert(bareCanvas.fillRectCalls.length === 1, 'Bare item renders without error')
// 7.3 Performance benchmark: 2,000 tooltips rendered
const t0 = performance.now()
const benchmarkFont = createMockFont()
const benchmarkCanvas = createMockCanvas()
for (let i = 0; i < 2000; i++) {
const sampleItem = (i % 2 === 0) ? shako : cta
worldPanels.drawItemTooltip(
benchmarkCanvas as any,
{ item: sampleItem, x: (i * 17) % 800, y: (i * 23) % 600 },
benchmarkFont,
)
}
const elapsedMs = performance.now() - t0
assert(elapsedMs < 300, `2,000 tooltips rendered in ${elapsedMs.toFixed(2)}ms (< 300ms threshold)`)
// ---------------------------------------------------------------------------
// Final Summary
// ---------------------------------------------------------------------------
console.log(`\n======================================================`)
console.log(`Empirical Verification Summary:`)
console.log(`Total Assertions: ${totalAssertions}`)
console.log(`Passed: ${totalAssertions - failedAssertions}`)
console.log(`Failed: ${failedAssertions}`)
console.log(`Verdict: ${failedAssertions === 0 ? 'APPROVE' : 'REJECT'}`)
console.log(`======================================================\n`)
if (failedAssertions > 0) {
process.exit(1)
}