test(06-10): cover multi-source map damage stacking (G-06-01-C)

- add inline createTypedInfo/FakeTypedView/SemanticReducer helpers
- assert separated count and reduced damage/type/extra for sourced+sourced,
  sourceless+sourceless, and mixed multi-source combinations
This commit is contained in:
unanmed 2026-09-15 10:04:12 +08:00
parent 714b791339
commit fcba44c756

View File

@ -201,6 +201,83 @@ class FakeReducer implements IMapDamageReducer {
}
}
/**
*
* @param damage
* @param type
* @param catchLocs
* @param repulseLocs
*/
function createTypedInfo(
damage: number,
type: number,
catchLocs: ITileLocator[] = [],
repulseLocs: ITileLocator[] = []
): IMapDamageInfo {
return {
damage,
type,
extra: {
catch: new Set(catchLocs),
repulse: new Set(repulseLocs)
}
};
}
/**
*
*/
class SemanticReducer implements IMapDamageReducer {
/** reduce 调用次数 */
calls: number = 0;
reduce(info: Iterable<Readonly<IMapDamageInfo>>): Readonly<IMapDamageInfo> {
this.calls++;
let damage = 0;
let type = 0;
const catchLocs: ITileLocator[] = [];
const repulseLocs: ITileLocator[] = [];
for (const item of info) {
damage += item.damage;
if (item.type > type) type = item.type;
item.extra.catch.forEach(loc => catchLocs.push(loc));
item.extra.repulse.forEach(loc => repulseLocs.push(loc));
}
return createTypedInfo(damage, type, catchLocs, repulseLocs);
}
}
/**
*
*/
class FakeTypedView implements IMapDamageView<number> {
/** 该视图产生的伤害信息 */
readonly info: IMapDamageInfo;
/** 该视图影响的范围 */
readonly range: FakeRange;
constructor(info: IMapDamageInfo, indexes: number[]) {
this.info = info;
this.range = new FakeRange(indexes);
}
getRange(): IRange<number> {
return this.range as never;
}
getRangeParam(): number {
return 0;
}
getDamageAt(): Readonly<IMapDamageInfo> {
return this.info;
}
getDamageWithoutCheck(): Readonly<IMapDamageInfo> {
return this.info;
}
}
interface MapDamageFixture {
/** 被测地图伤害对象 */
damage: InstanceType<TestModules['MapDamage']>;
@ -467,3 +544,89 @@ describe('MapDamage sourced conversion and reduction', () => {
]).toHaveLength(0);
});
});
describe('MapDamage multi-source stacking', () => {
// 验证同一点上两条有来源伤害会分别保留并求和合并
it('stacks two sourced damages at the same point', () => {
const fixture = createFixture();
const index = fixture.context.indexer.locToIndex(
fixture.locator.x,
fixture.locator.y
);
fixture.damage.useReducer(new SemanticReducer());
fixture.damage.useConverter(
new FakeConverter([
new FakeView(7, [index]),
new FakeView(4, [index])
])
);
const separated = [
...fixture.damage.getSeparatedDamage(fixture.locator)
];
const reduced = fixture.damage.getReducedDamage(fixture.locator);
expect(separated).toHaveLength(2);
expect(reduced!.damage).toBe(11);
expect(reduced!.type).toBe(0);
});
// 验证同一点上两条无来源伤害会分别保留并求和合并
it('stacks two sourceless damages at the same point', () => {
const fixture = createFixture();
fixture.damage.useReducer(new SemanticReducer());
fixture.damage.useConverter(new FakeConverter([]));
fixture.damage.addMapDamage(fixture.locator, createTypedInfo(3, 1));
fixture.damage.addMapDamage(fixture.locator, createTypedInfo(5, 2));
const separated = [
...fixture.damage.getSeparatedDamage(fixture.locator)
];
const reduced = fixture.damage.getReducedDamage(fixture.locator);
expect(separated).toHaveLength(2);
expect(reduced!.damage).toBe(8);
expect(reduced!.type).toBe(2);
});
// 验证多来源(有来源与无来源混合)叠加后伤害求和、类型取最大、额外标记取并集
it('merges mixed sourced and sourceless damages into one reduced result', () => {
const fixture = createFixture();
const index = fixture.context.indexer.locToIndex(
fixture.locator.x,
fixture.locator.y
);
const catchA: ITileLocator = { x: 0, y: 1 };
const repulseB: ITileLocator = { x: 0, y: 2 };
const catchC: ITileLocator = { x: 0, y: 3 };
const repulseD: ITileLocator = { x: 0, y: 4 };
fixture.damage.useReducer(new SemanticReducer());
fixture.damage.useConverter(
new FakeConverter([
new FakeTypedView(createTypedInfo(7, 0, [catchA]), [index]),
new FakeTypedView(createTypedInfo(4, 2, [], [repulseB]), [
index
])
])
);
fixture.damage.addMapDamage(
fixture.locator,
createTypedInfo(3, 1, [catchC])
);
fixture.damage.addMapDamage(
fixture.locator,
createTypedInfo(2, 1, [], [repulseD])
);
const separated = [
...fixture.damage.getSeparatedDamage(fixture.locator)
];
const reduced = fixture.damage.getReducedDamage(fixture.locator);
expect(separated).toHaveLength(4);
expect(reduced!.damage).toBe(16);
expect(reduced!.type).toBe(2);
expect([...reduced!.extra.catch]).toEqual([catchA, catchC]);
expect([...reduced!.extra.repulse]).toEqual([repulseB, repulseD]);
});
});