// Budget (readme ยง5): an op + collider regeneration must stay under 2 ms on the server. // Each iteration also steps the world, as the server does every tick: planck only // flushes its broadphase move buffer during step(), and skipping it makes body // destruction quadratic, which never happens in game. import { World } from 'planck'; import { bench, describe } from 'vitest'; import { loadMap } from '../src/server/assets'; import { TerrainColliders } from '../src/server/physics/terrainColliders'; import { Rng } from '../src/shared/rng'; import { DetachFinder } from '../src/shared/terrain/detach'; import { extractContours } from '../src/shared/terrain/marchingSquares'; import { applyTerrainEvent } from '../src/shared/terrain/events'; import { applyOp } from '../src/shared/terrain/ops'; import { rleEncode } from '../src/shared/terrain/rle'; import { simplifyContour } from '../src/shared/terrain/simplify'; import { ASSETS, realMaterials } from './helpers'; const materials = realMaterials(); const { terrain } = loadMap(ASSETS, 'gateau-etages', materials); const world = new World(); const colliders = new TerrainColliders(world, terrain); colliders.rebuildDirty(); const rng = new Rng(7); const finder = new DetachFinder(terrain); // A copy of the map whose top tier has just been cut loose (search only, the bitmap is never modified). const cut = loadMap(ASSETS, 'gateau-etages', materials).terrain; for (let x = 760; x <= 1290; x += 12) applyOp(cut, { kind: 'carve', x, y: 462, r: 14, power: 99 }, materials); const cutFinder = new DetachFinder(cut); /** Removes detached components like the server does, so they are not found again. */ function removeDetached(region: { x0: number; y0: number; x1: number; y1: number }): void { for (const c of finder.find(region)) { const { x0, y0, x1, y1 } = c.bounds; applyTerrainEvent(terrain, { kind: 'detach', id: 0, x: x0, y: y0, w: x1 - x0, h: y1 - y0, mask: rleEncode(c.mask), debris: true }, materials); } } describe('terrain', () => { bench('carve r=40 + detach search + collider rebuild', () => { const { changed } = applyOp(terrain, { kind: 'carve', x: rng.int(300, 1700), y: rng.int(300, 900), r: 40, power: 99 }, materials); if (changed) removeDetached(changed); colliders.rebuildDirty(); world.step(1 / 60); }); bench('fill r=30 + detach search + collider rebuild', () => { const { changed } = applyOp(terrain, { kind: 'fill', x: rng.int(300, 1700), y: rng.int(300, 900), r: 30, material: 1 }, materials); if (changed) removeDetached(changed); colliders.rebuildDirty(); world.step(1 / 60); }); bench('marching squares + simplify, one 64 px chunk', () => { for (const c of extractContours(terrain, 1024, 704, 1088, 768)) simplifyContour(c, 0.8); }); bench('detach search: a whole tier comes loose (~90k px)', () => { if (cutFinder.find({ x0: 746, y0: 448, x1: 1305, y1: 477 }).length === 0) throw new Error('expected a detached tier'); }); bench('full map collider build (map load)', () => { const w = new World(); terrain.markDirty({ x0: 0, y0: 0, x1: terrain.width, y1: terrain.height }); new TerrainColliders(w, terrain).rebuildDirty(); }); });