fix: scope blue board coverage to selected unit members
Publish GitHub Pages output / publish (push) Successful in 48s

This commit is contained in:
2026-09-09 12:09:47 +09:00
parent cf1ec84af4
commit 96bd5c4514
4 changed files with 57 additions and 5 deletions
@@ -94,6 +94,51 @@ test('큰 노드를 덮는 카드가 부족하면 파란 소켓을 비우거나
}
});
test('편성 밖 파란 소켓은 필수 배치에서 제외하고 여분 카드로도 빈 슬롯을 채우지 않는다', () => {
for (const side of [-1, 1]) {
const scopedSource = blueSource(side);
scopedSource.members = ['okayu', 'korone', 'member'].map((id) => ({ id, positionGroupId: 'model' }));
const boards = Object.fromEntries(scopedSource.members.map(({ id }) => [id, ['S-blue', 'B-large']]));
const coveringCard = { ...rightCard, connect: [{ level: 1, effectPermilUp: 1000, extent: [{ x: -side * 2, y: 0 }] }] };
for (const cards of [[coveringCard], [coveringCard, { ...coveringCard, id: 'spare' }]]) {
const result = optimizeBoardConnectionCards(scopedSource, boards, cards, {
requireBlueActivationCoverage: ['member'],
preservePlacementOnTie: () => true,
placementWeight: () => 0,
placementTieWeight: (_card, memberId, socketId) => memberId === 'member' && socketId === 'S-blue' ? 6 : 0
});
assert.equal(result.assignments.length, 1);
assert.equal(result.assignments[0].memberId, 'member');
assert.equal(result.assignments[0].socketId, 'S-blue');
}
assert.throws(() => optimizeBoardConnectionCards(scopedSource, boards, [], {
requireBlueActivationCoverage: ['member'], placementWeight: () => 0
}), /큰 발동률 노드를 포함할 수 없어/);
}
});
test('편성 밖 보드의 점수 기여와 동률인 기존 배치는 유지한다', () => {
const scopedSource = blueSource(1);
scopedSource.members = ['member', 'outsider'].map((id) => ({ id, positionGroupId: 'model' }));
const cards = ['required', 'global', 'existing'].map((id) => ({
id, awakening: 0, connect: [{ level: 1, effectPermilUp: 1000, extent: [{ x: -2, y: 0 }] }]
}));
const result = optimizeBoardConnectionCards(scopedSource, {
member: ['S-blue', 'B-large'], outsider: ['S-blue', 'B-large']
}, cards, {
requireBlueActivationCoverage: ['member'],
currentPlacements: { outsider: { 'S-blue': 'existing' } },
preservePlacementOnTie: () => true,
placementWeight: (card, memberId, socketId) =>
card.id === 'global' && memberId === 'outsider' && socketId === 'S-center' ? 100 : 0,
placementTieWeight: (_card, memberId, socketId) => memberId === 'member' && socketId === 'S-blue' ? 6 : 0
});
assert.deepEqual(result.placements, {
member: { 'S-blue': 'required' }, outsider: { 'S-center': 'global', 'S-blue': 'existing' }
});
assert.equal(new Set(result.assignments.map(({ cardId }) => cardId)).size, 3);
});
test('모양과 수치가 같은 카드는 기존 배치를 유지하고 중복 배치하지 않는다', () => {
const cards = ['a', 'b', 'c'].map((id) => ({ ...rightCard, id }));
const currentPlacements = { member: { 'S-center': 'c', 'S-right': 'a' } };
+5 -2
View File
@@ -52,7 +52,8 @@ export type BoardConnectionOptimizationOptions = {
targetMemberId?: string;
requireSoloSongBonus?: boolean;
requireGroupSongBonus?: boolean;
requireBlueActivationCoverage?: boolean;
/** true covers every member; a member ID list scopes coverage to the current unit. */
requireBlueActivationCoverage?: boolean | readonly string[];
placementWeight?: (card: OptimizableConnectionCard, memberId: string, socketId: string) => number;
placementPriorityWeight?: (card: OptimizableConnectionCard, memberId: string, socketId: string) => number;
placementTieWeight?: (card: OptimizableConnectionCard, memberId: string, socketId: string) => number;
@@ -137,7 +138,9 @@ export function optimizeBoardConnectionCards(
// Large blue activation nodes must be in the card's actual extent, even
// when inactive. Identify the blue side from coordinates, not left/right.
const requiredNodes = slots.map(({ member, socket, positions }) =>
options.requireBlueActivationCoverage && socket.x !== 0
(options.requireBlueActivationCoverage === true
|| (Array.isArray(options.requireBlueActivationCoverage) && options.requireBlueActivationCoverage.includes(member.id)))
&& socket.x !== 0
? positions.filter((position) => {
const node = nodeForMember(source, position.groupId, member.id);
return position.groupId.startsWith('B-') && position.x * socket.x > 0