Files
DndGamePlayer/app/shared/fieldEffectEraser.test.ts
T
Ivan Fontosh 2c0e6fbf09 feat(effects): extinguish fire with water/rain and ambient SFX volume
Water and rain erase fire under a tight brush hit radius; add looping fire/rain ambients and an Effects sound slider that also scales one-shot effect SFX.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 13:12:41 +08:00

192 lines
5.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyFieldEffectEraserStroke } from './fieldEffectEraser';
import type { EffectInstance } from './types/effects';
const base = { seed: 1, createdAtMs: 0 };
const makeId = (prefix: string) => `${prefix}_new`;
void test('applyFieldEffectEraserStroke: удаляет точки в радиусе кисти', () => {
const fog: EffectInstance = {
...base,
id: 'f1',
type: 'fog',
points: [
{ x: 0.2, y: 0.5, tMs: 0 },
{ x: 0.5, y: 0.5, tMs: 100 },
{ x: 0.8, y: 0.5, tMs: 200 },
],
radiusN: 0.02,
opacity: 0.5,
lifetimeMs: null,
};
const next = applyFieldEffectEraserStroke([fog], [{ x: 0.5, y: 0.5, tMs: 0 }], 0.05, makeId);
assert.equal(next.length, 2);
const left = next[0];
const right = next[1];
assert.ok(left);
assert.ok(right);
assert.equal(left.type, 'fog');
assert.equal(right.type, 'fog');
assert.equal(left.points.length, 1);
assert.equal(left.points[0]?.x, 0.2);
assert.equal(right.points.length, 1);
assert.equal(right.points[0]?.x, 0.8);
});
void test('applyFieldEffectEraserStroke: стирает вдоль сегмента штриха', () => {
const rain: EffectInstance = {
...base,
id: 'r1',
type: 'rain',
points: [
{ x: 0.1, y: 0.5, tMs: 0 },
{ x: 0.3, y: 0.5, tMs: 50 },
{ x: 0.5, y: 0.5, tMs: 100 },
{ x: 0.7, y: 0.5, tMs: 150 },
{ x: 0.9, y: 0.5, tMs: 200 },
],
radiusN: 0.01,
opacity: 0.6,
lifetimeMs: null,
};
const next = applyFieldEffectEraserStroke(
[rain],
[
{ x: 0.25, y: 0.5, tMs: 0 },
{ x: 0.75, y: 0.5, tMs: 50 },
],
0.04,
makeId,
);
assert.equal(next.length, 2);
const xs = next.flatMap((i) => (i.type === 'rain' ? i.points.map((p) => p.x) : []));
assert.deepEqual(xs, [0.1, 0.9]);
});
void test('applyFieldEffectEraserStroke: VFX-огонь стирается как полевой эффект', () => {
const fire: EffectInstance = {
...base,
id: 'fire_vfx',
type: 'fire',
points: [
{ x: 0.2, y: 0.5, tMs: 0 },
{ x: 0.5, y: 0.5, tMs: 100 },
],
radiusN: 0.02,
opacity: 0.8,
lifetimeMs: null,
};
const next = applyFieldEffectEraserStroke([fire], [{ x: 0.5, y: 0.5, tMs: 0 }], 0.05, makeId);
assert.equal(next.length, 1);
const kept = next[0];
assert.equal(kept?.type === 'fire' && kept.points[0]?.x, 0.2);
});
void test('applyFieldEffectEraserStroke: не трогает эффекты действия', () => {
const fog: EffectInstance = {
...base,
id: 'f1',
type: 'fog',
points: [{ x: 0.5, y: 0.5, tMs: 0 }],
radiusN: 0.05,
opacity: 1,
lifetimeMs: null,
};
const bolt: EffectInstance = {
...base,
id: 'L1',
type: 'lightning',
start: { x: 0.5, y: 0 },
end: { x: 0.5, y: 0.8 },
widthN: 0.02,
intensity: 1,
lifetimeMs: 500,
};
const next = applyFieldEffectEraserStroke([fog, bolt], [{ x: 0.5, y: 0.5, tMs: 0 }], 0.1, makeId);
assert.equal(next.length, 1);
assert.equal(next[0]?.id, 'L1');
});
void test('applyFieldEffectEraserStroke: пустой штрих — без изменений', () => {
const water: EffectInstance = {
...base,
id: 'w1',
type: 'water',
points: [{ x: 0.4, y: 0.4, tMs: 0 }],
radiusN: 0.05,
opacity: 0.5,
lifetimeMs: null,
};
const next = applyFieldEffectEraserStroke([water], [], 0.05, makeId);
assert.equal(next.length, 1);
assert.equal(next[0]?.id, 'w1');
});
void test('applyFieldEffectEraserStroke: types=["fire"] гасит только огонь', () => {
const fire: EffectInstance = {
...base,
id: 'fire1',
type: 'fire',
points: [
{ x: 0.5, y: 0.5, tMs: 0 },
{ x: 0.55, y: 0.5, tMs: 50 },
],
radiusN: 0.03,
opacity: 1,
lifetimeMs: null,
};
const fog: EffectInstance = {
...base,
id: 'fog1',
type: 'fog',
points: [{ x: 0.5, y: 0.5, tMs: 0 }],
radiusN: 0.03,
opacity: 0.5,
lifetimeMs: null,
};
const next = applyFieldEffectEraserStroke(
[fire, fog],
[{ x: 0.5, y: 0.5, tMs: 0 }],
0.08,
makeId,
['fire'],
);
assert.equal(next.length, 1);
assert.equal(next[0]?.id, 'fog1');
});
void test('applyFieldEffectEraserStroke: hitMode=brush не гасит издалека', () => {
const fire: EffectInstance = {
...base,
id: 'fire1',
type: 'fire',
points: [{ x: 0.5, y: 0.5, tMs: 0 }],
radiusN: 0.08,
opacity: 1,
lifetimeMs: null,
};
// brush ≈ 0.38 * 0.08 ≈ 0.030; dist 0.06 — мимо, dist 0.02 — попадание.
const far = applyFieldEffectEraserStroke(
[fire],
[{ x: 0.5, y: 0.56, tMs: 0 }],
0.08,
makeId,
['fire'],
'brush',
);
assert.equal(far.length, 1);
assert.equal(far[0]?.id, 'fire1');
const near = applyFieldEffectEraserStroke(
[fire],
[{ x: 0.5, y: 0.52, tMs: 0 }],
0.08,
makeId,
['fire'],
'brush',
);
assert.equal(near.length, 0);
});