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
This commit is contained in:
parent
5d854440ec
commit
93a7fe01ab
1 changed files with 15073 additions and 4754 deletions
111
test/shared-regressions.test.ts
Normal file
111
test/shared-regressions.test.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue