5180 fichiers 38.5 Mo 0 téléchargements 185 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / api / snowwar / SnowWarMath.test.ts
SnowWarMath.test.ts 6.4 Ko Télécharger le fichier
import { describe, expect, it } from 'vitest';
import {
    calculateFlightPath,
    calculateFlightPathWorld,
    direction360To8,
    fastSqrt,
    getAngleFromComponents,
    getBaseVelX,
    getBaseVelY,
    iterateSeed,
    moveTowards,
    SERVER_TICK_MS,
    SUBTURN_MS,
    SUBTURNS_PER_TICK,
    tileToWorld,
    validateDirection360,
    worldToTile,
} from './SnowWarMath';
import { ANGLE_COMPONENT, BASE_VEL_X, BASE_VEL_Y, SQRT_TABLE } from './SnowWarTables';

describe('AIR timing', () =>
{
    it('uses three 50 ms subturns per server turn', () =>
    {
        expect(SUBTURNS_PER_TICK).toBe(3);
        expect(SUBTURN_MS).toBe(50);
        expect(SERVER_TICK_MS).toBe(150);
    });
});

describe('SnowWarTables', () =>
{
    it('has the exact table sizes from the spec', () =>
    {
        expect(SQRT_TABLE.length).toBe(256);
        expect(ANGLE_COMPONENT.length).toBe(256);
        expect(BASE_VEL_X.length).toBe(360);
        expect(BASE_VEL_Y.length).toBe(360);
    });

    it('velocity tables are 90-degree phase-shifted copies (cos/sin relationship)', () =>
    {
        // Y[d] tracks X[(d + 270) % 360]; the original tables carry ±1
        // rounding asymmetries, so allow that tolerance.
        for (let d = 0; d < 360; d++)
        {
            expect(Math.abs(BASE_VEL_Y[d] - BASE_VEL_X[(d + 270) % 360])).toBeLessThanOrEqual(1);
        }
    });

    it('velocity magnitudes stay within [-256, 256]', () =>
    {
        for (let d = 0; d < 360; d++)
        {
            expect(Math.abs(BASE_VEL_X[d])).toBeLessThanOrEqual(256);
            expect(Math.abs(BASE_VEL_Y[d])).toBeLessThanOrEqual(256);
        }
    });
});

describe('coordinate conversion', () =>
{
    it('round-trips tile coordinates', () =>
    {
        for (const tile of [0, 1, 5, 17, 49])
        {
            expect(worldToTile(tileToWorld(tile))).toBe(tile);
        }
    });

    it('worldToTile rounds to the nearest tile center', () =>
    {
        expect(worldToTile(1599)).toBe(0);
        expect(worldToTile(1600)).toBe(1);
        expect(worldToTile(3200 + 1599)).toBe(1);
    });
});

describe('direction helpers', () =>
{
    it('validateDirection360 wraps values', () =>
    {
        expect(validateDirection360(360)).toBe(0);
        expect(validateDirection360(-1)).toBe(359);
        expect(validateDirection360(725)).toBe(5);
    });

    it('direction360To8 maps the compass', () =>
    {
        // Angle system: 0=E-ish per velocity tables; the 8-way mapping just
        // needs to be stable and wrap correctly.
        const zero = direction360To8(0);
        expect(direction360To8(360)).toBe(zero);
        expect(direction360To8(45)).toBe((zero + 1) % 8);
    });
});

describe('fastSqrt', () =>
{
    it('approximates the integer square root across magnitudes', () =>
    {
        for (const x of [0, 1, 16, 255, 256, 1024, 65535, 65536, 1_000_000, 250_000_000, 2_000_000_000])
        {
            const approx = fastSqrt(x);
            const real = Math.sqrt(x);
            // Table-based sqrt is coarse; allow ~7% relative error beyond tiny values.
            if (real < 20)
            {
                expect(Math.abs(approx - real)).toBeLessThanOrEqual(2);
            }
            else
            {
                expect(Math.abs(approx - real) / real).toBeLessThan(0.07);
            }
        }
    });
});

describe('getAngleFromComponents', () =>
{
    it('returns axis-aligned angles for cardinal directions', () =>
    {
        // Straight up (negative Y): 360 - COMPONENT[0] = 360 wraps to 0.
        expect(getAngleFromComponents(0, -100)).toBe(0);
        expect(getAngleFromComponents(100, 0)).toBe(90 + ANGLE_COMPONENT[0]);
    });

    it('always returns 0..359', () =>
    {
        for (let i = 0; i < 100; i++)
        {
            const angle = getAngleFromComponents((i * 37) % 200 - 100, (i * 53) % 200 - 100);
            expect(angle).toBeGreaterThanOrEqual(0);
            expect(angle).toBeLessThan(360);
        }
    });
});

describe('iterateSeed', () =>
{
    it('is deterministic and 32-bit', () =>
    {
        expect(iterateSeed(1)).toBe(iterateSeed(1));
        expect(iterateSeed(1)).not.toBe(1);
        expect(Number.isInteger(iterateSeed(123456789))).toBe(true);
        expect(iterateSeed(42)).toBe(iterateSeed(42) | 0);
    });

    it('treats zero as -1', () =>
    {
        expect(iterateSeed(0)).toBe(iterateSeed(0));
        expect(iterateSeed(0)).not.toBe(0);
    });
});

describe('moveTowards', () =>
{
    it('clamps to the target', () =>
    {
        expect(moveTowards(0, 500, 534)).toBe(500);
        expect(moveTowards(0, 5000, 534)).toBe(534);
        expect(moveTowards(5000, 0, 534)).toBe(4466);
        expect(moveTowards(7, 7, 534)).toBe(7);
    });
});

describe('calculateFlightPath', () =>
{
    it('uses AIR quick-throw timing and velocity', () =>
    {
        expect(calculateFlightPath(0, 0, 10, 0, 0)).toEqual({
            direction: 90,
            timeToLive: 10,
            parabolaOffset: 5,
            planarVelocity: 2000,
            trajectory: 0,
        });
    });

    it('uses AIR short and long lob coefficients', () =>
    {
        expect(calculateFlightPath(0, 0, 10, 0, 1)).toMatchObject({
            timeToLive: 17,
            parabolaOffset: 8,
            planarVelocity: 1882,
            trajectory: 1,
        });
        expect(calculateFlightPath(0, 0, 10, 0, 2)).toMatchObject({
            timeToLive: 22,
            parabolaOffset: 11,
            planarVelocity: 1454,
            trajectory: 2,
        });
    });

    it('resolves AIR adaptive throws by distance', () =>
    {
        expect(calculateFlightPath(0, 0, 10, 0, 3).trajectory).toBe(0);
        expect(calculateFlightPath(0, 0, 15, 0, 3).trajectory).toBe(1);
        expect(calculateFlightPath(0, 0, 25, 0, 3).trajectory).toBe(2);
    });

    it('caps AIR lob distance before deriving TTL and velocity', () =>
    {
        expect(calculateFlightPath(0, 0, 30, 0, 1)).toMatchObject({
            timeToLive: 33,
            parabolaOffset: 16,
            planarVelocity: 1818,
        });
        expect(calculateFlightPath(0, 0, 40, 0, 2)).toMatchObject({
            timeToLive: 70,
            parabolaOffset: 35,
            planarVelocity: 1428,
        });
    });

    it('preserves mid-tile world coordinates when resolving a live throw', () =>
    {
        expect(calculateFlightPathWorld(500, 700, 42500, 700, 3).trajectory).toBe(0);
        expect(calculateFlightPathWorld(500, 700, 48500, 700, 3).trajectory).toBe(1);
    });
});
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser