089da3cae0
- 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>
125 lines
3.3 KiB
TypeScript
125 lines
3.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/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');
|
||
});
|