Files
DndGamePlayer/app/shared/effectEraserHitTest.test.ts
T
Ivan Fontosh 089da3cae0 feat(effects): migrate battle effects to VFX
- add WebP frame-sequence assets for lightning, electric accent, fire, sunbeam, poison, rain, water, and rounded fog effects
- replace procedural lightning rendering with VFX frame playback
- align lightning impact to the click point
- remove the full-screen lightning blur/flash overlay
- add electric accent playback for lightning impact dispersion
- keep lightning scorch marks as the persistent ground trace after the action effect
- consolidate the lightning tool into a single public `Молния` action
- replace procedural fire with the VFX fire implementation
- remove the separate `Огонь 2` tool and keep the VFX fire under `Огонь`
- keep VFX fire brightness fixed at full intensity
- preserve fire as a field effect with brush placement and eraser support
- replace procedural sunbeam with the Pulse Discharge VFX implementation
- remove the separate `Луч света 2` tool and keep the VFX sunbeam under `Луч света`
- align sunbeam impact to the click point instead of the raw frame center
- remove the full-screen sunbeam flash overlay
- add Dust Burst VFX playback for poison cloud
- tint poison smoke green
- keep the skull glyph rendered above the poison smoke
- align poison smoke bottom to the click point
- remove the old procedural poison cloud implementation
- remove the separate `Яд 2` tool and keep the VFX poison under `Яд`
- replace procedural rain strokes with medium-rain VFX frame playback
- recolor rain frames to gray/light-gray so rain no longer renders black
- preserve rain as a field effect with brush placement and eraser support
- replace static water fill with generated animated water VFX frames
- render water VFX through the existing stroke mask so water keeps the painted brush shape
- preserve water as a field effect with brush placement and eraser support
- replace procedural fog texture generation with Smokey Atmosphere VFX frames
- generate rounded fog VFX frames with soft alpha falloff at the edges
- size each fog VFX stamp to the brush diameter
- remove per-stamp fog drift and rotation so fog elements stay fixed in place
- keep fog animation limited to internal frame playback
- preload VFX frame textures for smoother first use
- cache VFX textures per Pixi instance to avoid duplicate decoding
- filter expired action instances before node synchronization to stop old effects from flashing back during rapid casts
- add brush-stroke erasing for field effects using the same model as scene darkness reveal
- keep whole-instance erasing for action effects
- update effect hit tests for the consolidated VFX action effects
- update effect store pruning for renamed and consolidated action effects
- update control-panel buttons for consolidated fire, sunbeam, poison, and VFX-backed field effects
- update editor localization for removed and renamed effect tools
- add tests for field-effect brush erasing
- update eraser hit-test coverage for VFX-backed effects
- update effects store lifetime pruning tests for consolidated poison
- update control-panel tests for consolidated effect buttons
- include the field-effect eraser test in the default test script

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 21:20:43 +08:00

125 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/water — кистью, не целиком', () => {
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, null);
});
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, null);
});
void test('pickEraseTargetId: тьма как заморозка — точка удара', () => {
const darkness: EffectInstance = {
...base,
id: 'd1',
type: 'darkness',
at: { x: 0.4, y: 0.55 },
intensity: 1,
lifetimeMs: 820,
};
const id = pickEraseTargetId([darkness], { x: 0.41, y: 0.55 }, 0.05);
assert.equal(id, 'd1');
});
void test('pickEraseTargetId: shadow с учётом inst.radiusN', () => {
const shadow: EffectInstance = {
...base,
id: 's1',
type: 'shadow',
at: { x: 0.5, y: 0.5 },
radiusN: 0.08,
opacity: 1,
lifetimeMs: null,
};
const id = pickEraseTargetId([shadow], { x: 0.59, y: 0.5 }, 0.02);
assert.equal(id, 's1');
});
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');
});
void test('pickEraseTargetId: яд с учётом inst.radiusN', () => {
const pc: EffectInstance = {
...base,
id: 'pc',
type: 'poisonCloud',
at: { x: 0.5, y: 0.5 },
radiusN: 0.08,
intensity: 1,
lifetimeMs: 1000,
};
const id = pickEraseTargetId([pc], { x: 0.59, y: 0.5 }, 0.02);
assert.equal(id, 'pc');
});