20c838da7d
- Поле: вода (сплошная заливка по штриху, превью кистью), туман/огонь/дождь без изменений логики. - Действия: облако яда (частицы, круглая текстура, звук oblako-yada.mp3, длительность как у трека), луч света и заморозка со звуками из public/. - Пульт: инструменты воды и яда, синхрон SFX, тесты панели и ластика. - Окно управления: дочернее от окна просмотра (Z-order). - Типы эффектов, effectsStore prune, hit-test ластика. Made-with: Cursor
84 lines
2.3 KiB
TypeScript
84 lines
2.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
|
||
import { minDistSqEffectToPoint, pickEraseTargetId } from './effectEraserHitTest';
|
||
import type { EffectInstance } from './types/effects';
|
||
|
||
const base = { seed: 1, createdAtMs: 0 };
|
||
|
||
void test('pickEraseTargetId: fire/rain по штриху как туман', () => {
|
||
const fire: EffectInstance = {
|
||
...base,
|
||
id: 'f1',
|
||
type: 'fire',
|
||
points: [{ x: 0.5, y: 0.5, tMs: 0 }],
|
||
radiusN: 0.05,
|
||
opacity: 1,
|
||
lifetimeMs: null,
|
||
};
|
||
const id = pickEraseTargetId([fire], { x: 0.51, y: 0.5 }, 0.05);
|
||
assert.equal(id, 'f1');
|
||
});
|
||
|
||
void test('pickEraseTargetId: вода по штриху как туман', () => {
|
||
const water: EffectInstance = {
|
||
...base,
|
||
id: 'w1',
|
||
type: 'water',
|
||
points: [{ x: 0.4, y: 0.55, tMs: 0 }],
|
||
radiusN: 0.06,
|
||
opacity: 0.5,
|
||
lifetimeMs: null,
|
||
};
|
||
const id = pickEraseTargetId([water], { x: 0.41, y: 0.55 }, 0.05);
|
||
assert.equal(id, 'w1');
|
||
});
|
||
|
||
void test('minDistSqEffectToPoint: молния — расстояние до отрезка', () => {
|
||
const bolt: EffectInstance = {
|
||
...base,
|
||
id: 'L1',
|
||
type: 'lightning',
|
||
start: { x: 0, y: 0 },
|
||
end: { x: 1, y: 0 },
|
||
widthN: 0.02,
|
||
intensity: 1,
|
||
lifetimeMs: 500,
|
||
};
|
||
const mid = minDistSqEffectToPoint(bolt, { x: 0.5, y: 0.1 });
|
||
assert.ok(Math.abs(mid - 0.01) < 1e-9);
|
||
const end = minDistSqEffectToPoint(bolt, { x: 1, y: 0 });
|
||
assert.equal(end, 0);
|
||
});
|
||
|
||
void test('minDistSqEffectToPoint: луч света — как у молнии, отрезок', () => {
|
||
const beam: EffectInstance = {
|
||
...base,
|
||
id: 'S1',
|
||
type: 'sunbeam',
|
||
start: { x: 0.5, y: 0 },
|
||
end: { x: 0.5, y: 0.8 },
|
||
widthN: 0.04,
|
||
intensity: 1,
|
||
lifetimeMs: 220,
|
||
};
|
||
const onBeam = minDistSqEffectToPoint(beam, { x: 0.5, y: 0.4 });
|
||
assert.equal(onBeam, 0);
|
||
const aside = minDistSqEffectToPoint(beam, { x: 0.52, y: 0.4 });
|
||
assert.ok(aside > 0 && aside < 0.01);
|
||
});
|
||
|
||
void test('pickEraseTargetId: scorch с учётом inst.radiusN', () => {
|
||
const sc: EffectInstance = {
|
||
...base,
|
||
id: 's1',
|
||
type: 'scorch',
|
||
at: { x: 0.5, y: 0.5 },
|
||
radiusN: 0.08,
|
||
opacity: 1,
|
||
lifetimeMs: 1000,
|
||
};
|
||
const id = pickEraseTargetId([sc], { x: 0.59, y: 0.5 }, 0.02);
|
||
assert.equal(id, 's1');
|
||
});
|