import { WorldPanelsHud, ACT_WAYPOINTS, ACT_QUESTS, ALL_QUESTS, findWaypointBySlug, } from '../src/ui/world-panels.ts' import { InventoryPanel, resolveItemSpriteRect, INV_GRID_ORIGIN, EQUIP_SLOTS_LAYOUT, STARTER_EQUIPPED_GEAR, STARTER_BAG_ITEMS, } from '../src/ui/inventory.ts' import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts' console.log('--- STARTING ADVERSARIAL BOUNDARY AUDIT ---') // 1. Act Tab Switching & Act 4 Socket Clamping const wp = new WorldPanelsHud() // Default state console.assert(wp.selectedActTab === 1, 'Initial act tab should be 1') console.assert(wp.selectedQuestIdx === 0, 'Initial quest index should be 0') // Test clicking Act tabs at boundary X coordinates // ox = 80, oy = 60. Tab Y: oy + 6 .. oy + 42 = 66 .. 102. // Tab 1: 88 .. 146 // Tab 2: 149 .. 207 // Tab 3: 210 .. 268 // Tab 4: 271 .. 329 // Tab 5: 332 .. 390 const callbacks = { onClose: () => {}, onWaypointTeleport: (act: number, slug: string, levelId: number) => { state.dispatchedWp = { act, slug, levelId } }, } const state = { dispatchedWp: null as { act: number; slug: string; levelId: number } | null, cubeOpened: false, tpCasted: false, } const getDispatchedWp = () => state.dispatchedWp const isCubeOpened = () => state.cubeOpened const isTpCasted = () => state.tpCasted // Select quest 5 in Act 1 wp.handleLeftDockClick('quest', 80 + 28 + 2 * 92 + 10, 60 + 64 + 1 * 100 + 10, callbacks) console.assert(wp.selectedQuestIdx === 5, `Expected quest index 5, got ${wp.selectedQuestIdx}`) // Switch to Act 4 (tab 4: x = 80 + 8 + 3 * 61 + 10 = 281, y = 75) wp.handleLeftDockClick('quest', 281, 75, callbacks) console.assert(wp.selectedActTab === 4, `Expected Act 4, got ${wp.selectedActTab}`) // BOUNDARY CHECK: selectedQuestIdx MUST clamp to 0 because Act 4 only has 3 quests (0, 1, 2) console.assert( wp.selectedQuestIdx === 0, `Act 4 clamping failed! selectedQuestIdx should be 0, got ${wp.selectedQuestIdx}` ) // In Act 4, click on empty socket 3 (row 1, col 0: x = 80 + 28 + 10 = 118, y = 60 + 64 + 100 + 10 = 234) // This socket does not exist in Act 4 (only 3 quests exist: indices 0, 1, 2) wp.handleLeftDockClick('quest', 118, 234, callbacks) console.assert( wp.selectedQuestIdx === 0, `Act 4 socket 3 should be unassigned/unclickable, but selectedQuestIdx changed to ${wp.selectedQuestIdx}` ) // In Act 4, click on empty socket 4 (row 1, col 1: x = 80 + 28 + 92 + 10 = 210, y = 234) wp.handleLeftDockClick('quest', 210, 234, callbacks) console.assert( wp.selectedQuestIdx === 0, `Act 4 socket 4 should be unassigned/unclickable, but selectedQuestIdx changed to ${wp.selectedQuestIdx}` ) // In Act 4, click on empty socket 5 (row 1, col 2: x = 80 + 28 + 184 + 10 = 302, y = 234) wp.handleLeftDockClick('quest', 302, 234, callbacks) console.assert( wp.selectedQuestIdx === 0, `Act 4 socket 5 should be unassigned/unclickable, but selectedQuestIdx changed to ${wp.selectedQuestIdx}` ) // Select valid quest 2 in Act 4 (row 0, col 2: x = 302, y = 134) wp.handleLeftDockClick('quest', 302, 134, callbacks) console.assert( wp.selectedQuestIdx === 2, `Act 4 quest 2 selection failed, got ${wp.selectedQuestIdx}` ) // Extreme out-of-bounds clicks for act tabs // Far left (< 88): should clamp to Act 1 wp.handleLeftDockClick('quest', 50, 75, callbacks) console.assert(wp.selectedActTab === 1, `Expected Act 1 on left clamp, got ${wp.selectedActTab}`) // Far right (395): should clamp to Act 5 wp.handleLeftDockClick('quest', 395, 75, callbacks) console.assert(wp.selectedActTab === 5, `Expected Act 5 on right clamp, got ${wp.selectedActTab}`) // Beyond left panel boundary (> 400): should return false without state change const handledBeyond = wp.handleLeftDockClick('quest', 450, 75, callbacks) console.assert(handledBeyond === false, 'Click beyond 400 should return false') console.assert(wp.selectedActTab === 5, 'Act tab should not change on click beyond 400') // 2. Waypoint Menu Act switching & clicks across all 5 acts for (let act = 1; act <= 5; act++) { wp.selectedActTab = act const list = ACT_WAYPOINTS[act]! const expectedCount = act === 4 ? 3 : 9 console.assert( list.length === expectedCount, `Act ${act} should have ${expectedCount} waypoints, got ${list.length}` ) // Click each waypoint for (let i = 0; i < list.length; i++) { state.dispatchedWp = null const wy = 60 + 62 + i * 36 + 10 const clicked = wp.handleLeftDockClick('waypoint', 80 + 50, wy, callbacks) console.assert(clicked === true, `Waypoint click failed for Act ${act} index ${i}`) console.assert(getDispatchedWp() !== null, `No waypoint dispatched for Act ${act} index ${i}`) console.assert( getDispatchedWp()?.act === act, `Expected act ${act}, got ${getDispatchedWp()?.act}` ) console.assert( getDispatchedWp()?.levelId === list[i]!.levelId, `Expected levelId ${list[i]!.levelId}, got ${getDispatchedWp()?.levelId}` ) console.assert( getDispatchedWp()?.slug === list[i]!.slug, `Expected slug ${list[i]!.slug}, got ${getDispatchedWp()?.slug}` ) } // In Act 4, clicking rows 3..8 should NOT dispatch any waypoint if (act === 4) { for (let i = 3; i < 9; i++) { state.dispatchedWp = null const wy = 60 + 62 + i * 36 + 10 wp.handleLeftDockClick('waypoint', 80 + 50, wy, callbacks) console.assert( getDispatchedWp() === null, `Act 4 row ${i} should be empty, but dispatched waypoint!` ) } } } // 3. Waypoint slug lookups console.assert( findWaypointBySlug('halls-of-pain')?.levelId === 123, 'Halls of Pain levelId should be 123' ) console.assert( findWaypointBySlug('the-pandemonium-fortress')?.levelId === 103, 'The Pandemonium Fortress levelId should be 103' ) console.assert( findWaypointBySlug('nonexistent') === undefined, 'Nonexistent slug should return undefined' ) // 4. Inventory item sprite resolution: Adversarial inputs const itemRects = BAKED_UI_MANIFEST.itemRects console.assert( resolveItemSpriteRect({}, itemRects) !== null, 'Empty item should resolve fallback rect' ) console.assert( resolveItemSpriteRect({ invFile: 'NON_EXISTENT_DC6' }, itemRects) !== null, 'Invalid invFile should fallback' ) console.assert( resolveItemSpriteRect({ width: 0, height: 0 }, itemRects) !== null, 'Zero dimensions should fallback' ) console.assert( resolveItemSpriteRect({ invWidth: -5, invHeight: 100 }, itemRects) !== null, 'Negative/weird dimensions should fallback' ) // 5. Inventory panel drag, drop, swap, out of bounds const inv = new InventoryPanel() inv.visible = true // Out of bounds click in grid console.assert(inv.cursorItem === null, 'Cursor should start null') const beforeCount = inv.gridItems.length // Pick up Horadric Cube at (0, 0) inv.handleClick(INV_GRID_ORIGIN.x + 10, INV_GRID_ORIGIN.y + 10, { onOpenCube: () => {}, onCastTownPortal: () => {}, }) // Cube right-click or left-click without cursor opens cube if code is box // But handleClick with hit?.item.code === 'box' triggers onOpenCube state.cubeOpened = false inv.handleClick(INV_GRID_ORIGIN.x + 10, INV_GRID_ORIGIN.y + 10, { onOpenCube: () => { state.cubeOpened = true }, onCastTownPortal: () => {}, }) console.assert(isCubeOpened() === true, 'Clicking cube without cursorItem should open cube') // Click on normal item to pick up (Tome of Town Portal at col 2, row 0: x = origin + 2 * 29 + 10) state.tpCasted = false inv.handleClick(INV_GRID_ORIGIN.x + 2 * 29 + 10, INV_GRID_ORIGIN.y + 10, { onOpenCube: () => {}, onCastTownPortal: () => { state.tpCasted = true }, }) console.assert(isTpCasted() === true, 'Clicking tbk should trigger town portal') // Directly pick up an item via clickGridCell inv.clickGridCell(3, 0) // Hellfire torch console.assert(inv.cursorItem?.code === 'cm2', 'Hellfire torch should be on cursor') // Tooltip hover check: when cursorItem is held, hoveredItem should still be set by mouseMove, // but HudManager suppresses tooltip drawing via `!this.inventory.cursorItem` inv.handleMouseMove(INV_GRID_ORIGIN.x + 10, INV_GRID_ORIGIN.y + 10) console.assert(inv.hoveredItem !== null, 'Hovered item detected in inventory') // Place torch in empty space (col 6, row 0) const placedEmpty = inv.clickGridCell(6, 0) console.assert(placedEmpty === true, 'Placing torch in empty space should succeed') console.assert(inv.cursorItem === null, 'Cursor item should now be null') // Now pick up torch again from (6, 0) and swap with Call to Arms at (8, 0) inv.clickGridCell(6, 0) console.assert(inv.cursorItem?.code === 'cm2', 'Picked up torch again') const swapped = inv.clickGridCell(8, 0) // Overlaps with Call to Arms (crs) console.assert(swapped === true, 'Swapping torch with Call to Arms should succeed') console.assert(inv.cursorItem?.code === 'crs', 'Cursor item should now be Call to Arms') // Weapon swap I <-> II console.assert(inv.weaponSwapSet === 0, 'Initial weapon swap set is 0') inv.toggleWeaponSwap() console.assert(inv.weaponSwapSet === 1, 'Weapon swap set is now 1') inv.toggleWeaponSwap() console.assert(inv.weaponSwapSet === 0, 'Weapon swap set is now 0') console.log('--- ALL ADVERSARIAL BOUNDARY AUDIT ASSERTIONS PASSED! ---')