decla-red/test/shared-regressions.test.ts
Andras Schmelczer 93a7fe01ab
All checks were successful
Build & deploy / Build & deploy website (push) Successful in 32s
Build & deploy / Build & publish server image (push) Successful in 1m2s
Improve netcode and simplify (#4)
- Simplify the logic wherever possible to remove overengineering and simplify the netcode
- Actually improve the netcode
- The new lines are mainly the package locks and tests

Reviewed-on: https://home.schmelczer.dev/git/andras/decla-red/pulls/4
2026-09-15 07:57:57 +01:00

111 lines
4.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { marchCircle } from '../shared/src/physics/march-circle';
import { resolveCircleMovement } from '../shared/src/physics/resolve-circle-movement';
import { finiteVec2, sanitizeName } from '../shared/src/helper/finite';
import { CommandReceiver } from '../shared/src/commands/command-receiver';
import type { PhysicsBody, Sdf } from '../shared/src/physics/sdf';
const bodyAt = (x: number, y: number, vx: number, vy: number): PhysicsBody => ({
center: [x, y],
radius: 1,
velocity: [vx, vy],
lastNormal: [0, 1],
restitution: 1,
});
const rightWall: Sdf = { canCollide: true, distance: ([x]) => 10 - x };
describe('circle collision movement', () => {
it('spends only the remaining step time after a bounce', () => {
const body = bodyAt(0, 0, 100, 0);
expect(resolveCircleMovement(body, 0.2, [rightWall])).toBe(rightWall);
// 9 units to the wall, then 11 units back: 20 units of total travel.
expect(body.center[0]).toBeCloseTo(-2, 6);
expect(body.velocity[0]).toBeCloseTo(-100, 6);
});
it('resolves velocity at the second contact instead of leaving it aimed into a wall', () => {
const body = bodyAt(0, 0, 100, 0);
const leftWall: Sdf = { canCollide: true, distance: ([x]) => x + 5 };
resolveCircleMovement(body, 0.3, [rightWall, leftWall]);
expect(body.center[0]).toBeCloseTo(-4, 6);
expect(body.velocity[0]).toBeCloseTo(100, 6);
});
it('allows slow tangential sliding along a surface', () => {
const body = { ...bodyAt(9, 0, 100, 10), restitution: 0 };
resolveCircleMovement(body, 0.1, [rightWall]);
expect(body.center[0]).toBeCloseTo(9, 6);
expect(body.center[1]).toBeCloseTo(1, 6);
expect(body.velocity[0]).toBeCloseTo(0, 6);
});
it('ignores disabled objects when identifying the contacted surface', () => {
const inactive: Sdf = { canCollide: false, distance: () => -100 };
const onHit = vi.fn();
const body = bodyAt(0, 0, 100, 0);
const result = marchCircle(body, [20, 0], [inactive, rightWall], onHit);
expect(result.hitObject).toBe(rightWall);
expect(onHit).toHaveBeenCalledExactlyOnceWith(rightWall);
expect(result.normal).toEqual([-1, 0]);
});
it('samples the collision normal before the callback removes the obstacle', () => {
const wall = { ...rightWall };
const body = bodyAt(0, 0, 100, 0);
resolveCircleMovement(body, 0.2, [wall], () => {
wall.canCollide = false;
});
expect(body.center.every(Number.isFinite)).toBe(true);
expect(body.velocity[0]).toBeCloseTo(-100, 6);
});
});
describe('network input helpers', () => {
it('clamps huge finite vectors before allocating a float vector', () => {
const direction = finiteVec2([1e300, 1e300], 1)!;
expect(direction.every(Number.isFinite)).toBe(true);
expect(Math.hypot(...direction)).toBeCloseTo(1, 6);
expect(direction[0]).toBeGreaterThan(0.7);
});
it('never calls methods on an untrusted player name', () => {
expect(sanitizeName({ toString: {} }, 10)).toBe('');
expect(sanitizeName('abcdefghijk', 10)).toBe('abcdefghij');
});
it('does not dispatch inherited object methods as commands', () => {
class Receiver extends CommandReceiver {
public fallback = vi.fn();
protected defaultCommandExecutor(command: object) {
this.fallback(command);
}
}
const receiver = new Receiver();
const command = { constructor: { name: 'constructor' } };
receiver.handleCommand(command);
expect(receiver.fallback).toHaveBeenCalledExactlyOnceWith(command);
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('random world seeds', () => {
it('uses the random seed bits instead of truncating every initial seed to zero', async () => {
const random = vi.spyOn(Math, 'random');
const firstValues: number[] = [];
for (const seed of [0.1, 0.5, 0.9]) {
random.mockReturnValue(seed);
vi.resetModules();
const { Random } = await import('../shared/src/helper/random');
firstValues.push(Random.getRandom());
Random.seed = 42;
const expected = [Random.getRandom(), Random.getRandom()];
Random.seed = 42;
expect([Random.getRandom(), Random.getRandom()]).toEqual(expected);
}
expect(new Set(firstValues).size).toBe(3);
});
});