import { describe, expect, it } from 'vitest' import { getEmbeddedDropTables } from '../src/game/embedded-drop-tables.ts' import { findSafeDropPosition, getIsometricRingCandidates, } from '../src/game/ground-items.ts' import { getGoldFlippyRect, getInventoryGoldLimit, resolveGroundItemSpriteRect, } from '../src/ui/inventory.ts' import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts' import { COLLIDE_NONE, COLLIDE_WALL, COLLIDE_BLANK, COLLIDE_MASK_INVALID, COLLIDE_DOOR, } from '../src/game/d2map.ts' import { D2Rng } from '../src/game/d2-rng.ts' import { GameEngine } from '../src/game/engine.ts' import type { Monster } from '../src/game/combat.ts' import { goldItem } from '../src/game/items.ts' const dropTables = getEmbeddedDropTables() function makeGrid(width: number, height: number, filler: number = COLLIDE_NONE) { const subWidth = width * 5 const subHeight = height * 5 const collisionMasks = new Uint16Array(subWidth * subHeight) const blocked = new Uint8Array(subWidth * subHeight) if (filler !== COLLIDE_NONE) { collisionMasks.fill(filler) blocked.fill(1) } return { cellsX: width, cellsY: height, gridWidth: subWidth, collisionMasks, blocked, } } function setCellMask(grid: any, cx: number, cy: number, mask: number): void { if (cx < 0 || cy < 0 || cx >= grid.cellsX || cy >= grid.cellsY) return const startX = cx * 5 const startY = cy * 5 const isBlock = (mask & (COLLIDE_WALL | COLLIDE_BLANK | COLLIDE_MASK_INVALID)) !== 0 for (let dy = 0; dy < 5; dy++) { for (let dx = 0; dx < 5; dx++) { const idx = (startY + dy) * grid.gridWidth + (startX + dx) grid.collisionMasks[idx] = mask grid.blocked[idx] = isBlock ? 1 : 0 } } } function getCellCenterMask(grid: any, cx: number, cy: number): number { if (cx < 0 || cy < 0 || cx >= grid.cellsX || cy >= grid.cellsY) return COLLIDE_MASK_INVALID const subX = cx * 5 + 2 const subY = cy * 5 + 2 const idx = subY * grid.gridWidth + subX return grid.collisionMasks[idx] ?? COLLIDE_NONE } describe('Milestone 3 Challenger 2: Empirical Stress & Statistical Parity', () => { describe('1. Drop Placement Quadrant Scatter Parity (10,000 Multi-Item Drops)', () => { it('1.1: 10,000 multi-item drops across diverse collision maps produce zero wall/void penetrations', () => { // Map 1: Narrow Corridors const corridorGrid = makeGrid(50, 50, COLLIDE_WALL) for (const y of [10, 11, 20, 21, 30, 31]) { for (let x = 5; x < 45; x++) setCellMask(corridorGrid, x, y, COLLIDE_NONE) } for (const x of [10, 11, 25, 26, 40, 41]) { for (let y = 5; y < 45; y++) setCellMask(corridorGrid, x, y, COLLIDE_NONE) } // Map 2: Chaos Sanctuary Star / Lava Void const sanctuaryGrid = makeGrid(60, 60, COLLIDE_BLANK) for (let y = 25; y <= 35; y++) { for (let x = 25; x <= 35; x++) setCellMask(sanctuaryGrid, x, y, COLLIDE_NONE) } for (let i = 10; i < 25; i++) { for (let w = -2; w <= 2; w++) { setCellMask(sanctuaryGrid, 30 + w, i, COLLIDE_NONE) setCellMask(sanctuaryGrid, 30 + w, 60 - i, COLLIDE_NONE) setCellMask(sanctuaryGrid, i, 30 + w, COLLIDE_NONE) setCellMask(sanctuaryGrid, 60 - i, 30 + w, COLLIDE_NONE) } } // Map 3: Catacombs with Pillars const catacombsGrid = makeGrid(50, 50, COLLIDE_NONE) for (let py of [10, 20, 30, 40]) { for (let px of [10, 18, 32, 42]) { setCellMask(catacombsGrid, px, py, COLLIDE_WALL) setCellMask(catacombsGrid, px + 1, py, COLLIDE_WALL) setCellMask(catacombsGrid, px, py + 1, COLLIDE_WALL) setCellMask(catacombsGrid, px + 1, py + 1, COLLIDE_WALL) } } // Map 4: Maggot Lair 1-cell Tunnels const maggotGrid = makeGrid(50, 50, COLLIDE_WALL) let curX = 5, curY = 5 setCellMask(maggotGrid, curX, curY, COLLIDE_NONE) const pathRng = new D2Rng(0xCAFEF00D) for (let step = 0; step < 800; step++) { const dir = pathRng.rand(4) if (dir === 0 && curX < 45) curX++ else if (dir === 1 && curX > 5) curX-- else if (dir === 2 && curY < 45) curY++ else if (dir === 3 && curY > 5) curY-- setCellMask(maggotGrid, curX, curY, COLLIDE_NONE) } const testMaps = [ { name: 'Corridors', grid: corridorGrid }, { name: 'Sanctuary', grid: sanctuaryGrid }, { name: 'Catacombs', grid: catacombsGrid }, { name: 'Maggot Lair', grid: maggotGrid }, ] let wallPenetrations = 0 let voidPenetrations = 0 let outOfBoundsDrops = 0 let totalPlaced = 0 for (const map of testMaps) { const grid = map.grid const walkable: { x: number; y: number }[] = [] for (let cy = 0; cy < grid.cellsY; cy++) { for (let cx = 0; cx < grid.cellsX; cx++) { const m = getCellCenterMask(grid, cx, cy) if ((m & (COLLIDE_WALL | COLLIDE_BLANK | COLLIDE_MASK_INVALID)) === 0) { walkable.push({ x: cx, y: cy }) } } } const dropRng = new D2Rng(0x5EED0000) for (let i = 0; i < 2500; i++) { const origin = walkable[dropRng.rand(walkable.length)]! const count = 4 + dropRng.rand(5) const occupied: { cellX: number; cellY: number }[] = [] for (let j = 0; j < count; j++) { totalPlaced++ const pos = findSafeDropPosition(grid, origin.x, origin.y, 4, occupied) if (pos.cellX < 0 || pos.cellY < 0 || pos.cellX >= grid.cellsX || pos.cellY >= grid.cellsY) { outOfBoundsDrops++ } const mask = getCellCenterMask(grid, pos.cellX, pos.cellY) if ((mask & COLLIDE_WALL) !== 0) wallPenetrations++ if ((mask & (COLLIDE_BLANK | COLLIDE_MASK_INVALID)) !== 0) voidPenetrations++ occupied.push(pos) } } } expect(totalPlaced).toBeGreaterThanOrEqual(50000) expect(wallPenetrations).toBe(0) expect(voidPenetrations).toBe(0) expect(outOfBoundsDrops).toBe(0) }) it('1.2: verifies concentric ring quadrant symmetry and Manhattan ordering', () => { for (let r = 1; r <= 5; r++) { const ring = getIsometricRingCandidates(r) const posX = ring.filter(c => c.dx > 0).length const negX = ring.filter(c => c.dx < 0).length const posY = ring.filter(c => c.dy > 0).length const negY = ring.filter(c => c.dy < 0).length expect(posX).toBe(negX) expect(posY).toBe(negY) } }) }) describe('2. Gold Pile Monte Carlo Distribution (100,000 Drops)', () => { it('2.1: 100,000 drops strictly populate 3 graphic tiers without 5,000 clamp', () => { const flippyRects = BAKED_UI_MANIFEST.flippyRects ?? {} let tier0 = 0 let tier1 = 0 let tier2 = 0 let over5000 = 0 const goldRng = new D2Rng(0xB16601D) const scenarios = [ { lvl: 5, gf: 0 }, { lvl: 15, gf: 50 }, { lvl: 55, gf: 100 }, { lvl: 85, gf: 200 }, { lvl: 90, gf: 350 }, ] for (let i = 0; i < 100000; i++) { const sc = scenarios[i % scenarios.length]! const baseGold = Math.max(1, goldRng.rand(5 * sc.lvl) + sc.lvl) const mult = 1 + goldRng.rand(3) let amount = baseGold * mult if (sc.gf > 0) { amount = Math.max(1, Math.floor((amount * (100 + sc.gf)) / 100)) } const item = goldItem(amount) const stack = item.stack! if (stack > 5000) over5000++ const rect = getGoldFlippyRect(stack, flippyRects) if (stack < 50) { tier0++ expect(rect).toBe(flippyRects['flpgld_0']) } else if (stack < 500) { tier1++ expect(rect).toBe(flippyRects['flpgld_1']) } else { tier2++ expect(rect).toBe(flippyRects['flpgld_2']) } } expect(tier0).toBeGreaterThan(0) expect(tier1).toBeGreaterThan(0) expect(tier2).toBeGreaterThan(0) expect(over5000).toBeGreaterThan(0) // Absence of 5,000 clamp verified }) it('2.2: character inventory gold limit enforces 10,000 * level across all levels', () => { for (let lvl = 1; lvl <= 99; lvl++) { expect(getInventoryGoldLimit(lvl)).toBe(lvl * 10000) } }) }) describe('3. Monster Seed PRNG Determinism with Queued Drops', () => { it('3.1: queued drop rolled after 20 ticks with intermediate skill casts matches immediate drop', () => { function makeEngine(seed: number, equipped: any[]) { const eng = new GameEngine( { widthPx: 1000, heightPx: 1000, overlap: () => 0 }, { spawn: { x: 500, y: 500 }, stats: [], xpTable: [0, 500, 1500], skills: [{ id: 1, name: 'Fire Bolt', manaCost: 2, cooldownTicks: 5, radius: 20, range: 300, speed: 10, damage: 10 } as any], npcDefs: [], questDefs: [], combatOptions: { playerSpeed: 4, playerReach: 50, playerCooldownTicks: 10, playerDamage: 100, playerManaPerAttack: 1, respawnTicks: 100 }, talkRadius: 50, pickupRadius: 30, inventoryCols: 10, inventoryRows: 4, lootSeed: seed, dropTables, equippedItems: equipped, }, ) eng.setDropTables(dropTables) return eng } function makeMon(index: number): Monster { return { index, stats: { id: 'fallen1', name: 'fallen1', hp: 50, damage: 1, cooldownTicks: 10, reach: 20, aggroRadius: 100, speed: 10, xp: 10, level: 15, rank: 'normal' }, x: 520, y: 500, hp: 50, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, dropRolled: false, } } const seed = 0xABCD1234 const eng1 = makeEngine(seed, [{ name: 'MF', stats: [{ text: '50% Better Chance of Getting Magic Items' }] }]) const m1 = makeMon(0) eng1.world.monsters.push(m1) eng1.damageMonster(0, 999) eng1.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false }) const drops1 = eng1.groundItems.all.map(g => `${g.name}:${g.quality}:${g.amount}`) const eng2 = makeEngine(seed, [{ name: 'MF', stats: [{ text: '50% Better Chance of Getting Magic Items' }] }]) const m2 = makeMon(0) m2.deathGated = true m2.deathTicks = 15 eng2.world.monsters.push(m2) eng2.damageMonster(0, 999) eng2.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false }) expect(eng2.groundItems.count).toBe(0) // Player swaps equipment and casts spells during death animation ;(eng2.opts as any).equippedItems = [] for (let t = 0; t < 14; t++) { eng2.tick({ movement: { x: 1, y: 0 }, attacking: t % 3 === 0, pickingUp: false, talking: false, digits: [1], saving: false, loading: false }) } eng2.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false }) const drops2 = eng2.groundItems.all.map(g => `${g.name}:${g.quality}:${g.amount}`) expect(drops2).toEqual(drops1) }) }) })