From c1df1e01167deae8f336b7f3f9a9e12587b423d8 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Thu, 17 Sep 2026 17:23:01 +0800 Subject: [PATCH] fix(07-13): prune deleted views and empty sourceless points - deleteEnemy removes the deleted view from point.affectedBy and drops its point.damages entry so lazy refreshIndex never revisits a dead view (IN-01) - deleteMapDamage removes the sourcelessDamage IPointInfo once both damages and affectedBy are empty, keeping markDirtyIndex - add IN-01 regressions: dead-view witness (getDamageWithoutCheck not called) and the two arms of the sourceless empty-point cleanup --- .../data-system/src/combat/mapDamage.test.ts | 45 +++++++++++++++++++ .../data-system/src/combat/mapDamage.ts | 7 +++ 2 files changed, 52 insertions(+) diff --git a/packages-user/data-system/src/combat/mapDamage.test.ts b/packages-user/data-system/src/combat/mapDamage.test.ts index 6a97e10..b82a2ef 100644 --- a/packages-user/data-system/src/combat/mapDamage.test.ts +++ b/packages-user/data-system/src/combat/mapDamage.test.ts @@ -404,6 +404,35 @@ describe('MapDamage sourceless damage', () => { ...fixture.damage.getSeparatedDamage({ x: 3, y: 2 }) ]).toHaveLength(0); }); + + // 验证删除无来源伤害的两条臂:删一条保留剩余,删净后返回空且可再次添加(IN-01 空点清理) + it('prunes the sourceless point once every damage is deleted', () => { + const fixture = createFixture(); + fixture.damage.useReducer(fixture.reducer); + const first = createInfo(5); + const second = createInfo(3); + fixture.damage.addMapDamage(fixture.locator, first); + fixture.damage.addMapDamage(fixture.locator, second); + expect(fixture.damage.getReducedDamage(fixture.locator)?.damage).toBe( + 8 + ); + + fixture.damage.deleteMapDamage(fixture.locator, first); + expect(fixture.damage.getReducedDamage(fixture.locator)?.damage).toBe( + 3 + ); + + fixture.damage.deleteMapDamage(fixture.locator, second); + expect(fixture.damage.getReducedDamage(fixture.locator)).toBeNull(); + expect([ + ...fixture.damage.getSeparatedDamage(fixture.locator) + ]).toHaveLength(0); + + fixture.damage.addMapDamage(fixture.locator, createInfo(6)); + expect(fixture.damage.getReducedDamage(fixture.locator)?.damage).toBe( + 6 + ); + }); }); describe('MapDamage sourced conversion and reduction', () => { @@ -545,6 +574,22 @@ describe('MapDamage sourced conversion and reduction', () => { ]).toHaveLength(0); }); + // 验证删除怪物后惰性重算不再触达被删视图(IN-01 的删除端剪除) + it('does not rebuild damage from a deleted view after pruning', () => { + const fixture = createFixture(); + fixture.damage.useReducer(fixture.reducer); + fixture.damage.useConverter(fixture.converter); + const spy = vi.spyOn(fixture.damageView, 'getDamageWithoutCheck'); + + fixture.damage.deleteEnemy(fixture.view); + + expect(fixture.damage.getReducedDamage(fixture.locator)).toBeNull(); + expect(spy).not.toHaveBeenCalled(); + expect([ + ...fixture.damage.getSeparatedDamage(fixture.locator) + ]).toHaveLength(0); + }); + // 验证伤害来源消失后旧坐标不再返回幽灵伤害(CR-02 视图集变空) it('drops ghost damage when the enemy view set becomes empty', () => { const fixture = createFixture(); diff --git a/packages-user/data-system/src/combat/mapDamage.ts b/packages-user/data-system/src/combat/mapDamage.ts index b670b0b..43418cc 100644 --- a/packages-user/data-system/src/combat/mapDamage.ts +++ b/packages-user/data-system/src/combat/mapDamage.ts @@ -116,6 +116,9 @@ export class MapDamage implements IMapDamage { const current = this.sourcelessDamage.get(index); if (!current) return; current.damages.delete(info); + if (current.damages.size === 0 && current.affectedBy.size === 0) { + this.sourcelessDamage.delete(index); + } this.markDirtyIndex(index); } @@ -157,6 +160,10 @@ export class MapDamage implements IMapDamage { affecting.damages.forEach((dam, index) => { this.damageStore.delete(dam); collection.add(index); + const point = this.sourcedDamage.get(index); + if (!point) return; + point.affectedBy.delete(viewItem); + point.damages.delete(dam); }); this.viewStore.delete(viewItem); }