fix: restore summer event calculation through source deployment
Publish GitHub Pages output / publish (push) Successful in 39s

This commit is contained in:
2026-09-20 21:26:49 +09:00
parent 5d0361de26
commit 8ed658e804
8 changed files with 137 additions and 25 deletions
+36
View File
@@ -7,6 +7,17 @@ const gamersFeaturedCards = [
'card-00017-5-uniq-0077-00'
] as const;
const summerFeaturedCards = ['card-00004-5-uniq-0081-00', 'card-04013-5-uniq-0083-00', 'card-00035-5-uniq-0082-00', 'card-03005-5-uniq-0084-00'] as const;
// Schedule: https://dengekionline.com/article/202609/88492
// Bonuses: https://gamerch.com/hololive-dreams/1015338
// IDs/names: HolodoriDB kor 5517e40b931fec10253f5b850989346518e233c2.
const summerChapters = [
['아키로제식 여름 휴가', 'chr-00004', 'm0349', 19, 21],
['더위와 풀장과 독서가와', 'chr-04013', 'm0351', 21, 23],
['총수의 파라다이스 침략 작전!', 'chr-00035', 'm0350', 23, 25],
['유능 감독, 성지순례에 나서다', 'chr-03005', 'm0352', 25, 27]
] as const;
export const events = [
{
// HolodoriDB supplies the card/song IDs; the public master-data diff does not include
@@ -32,5 +43,30 @@ export const events = [
4: [0, 6, 7, 8, 9, 10],
5: [0, 15, 18, 22, 26, 30]
}
},
{
id: 'marathon_event-relay-002',
name: '몽환의 서머 이스케이프?!',
startsAt: '2026-09-19T11:00:00.000Z',
endsAt: '2026-09-27T10:59:59.999Z',
bonusCharacterIds: [],
featuredCardIds: summerFeaturedCards,
challengeSongBonusCardIds: {},
formationBonusPercent: 50,
featuredCardBonusPercent: 50,
challengeSongScoreBonusPercent: 10,
awakeningBonusPercentByRarity: {
3: [0, 1, 1, 2, 2, 3],
4: [0, 6, 7, 8, 9, 10],
5: [0, 15, 18, 22, 26, 30]
},
chapters: summerChapters.map(([name, characterId, songId, start, end], index) => ({
id: `marathon_event-relay-002-chapter-0${index + 1}`,
name,
startsAt: `2026-09-${start}T11:00:00.000Z`,
endsAt: `2026-09-${end}T10:59:59.999Z`,
bonusCharacterIds: [characterId],
challengeSongBonusCardIds: { [songId]: [summerFeaturedCards[index]] }
}))
}
] as const satisfies readonly EventDefinition[];
+34
View File
@@ -5,6 +5,40 @@ import { activeEventAt, compareEventRankings, eventBonusBreakdown, eventChalleng
const currentEvent = events[0];
test('솔로 이벤트는 챕터 경계에서 대상 멤버와 과제곡을 함께 전환한다', () => {
assert.equal(activeEventAt(events, Date.parse('2026-09-19T10:59:59.999Z')), null);
const expected = [
[19, 'chr-00004', 'm0349'], [21, 'chr-04013', 'm0351'],
[23, 'chr-00035', 'm0350'], [25, 'chr-03005', 'm0352']
] as const;
for (const [day, characterId, songId] of expected) {
const start = Date.parse(`2026-09-${day}T11:00:00.000Z`);
const chapter = activeEventAt(events, start)!;
assert.deepEqual(chapter.bonusCharacterIds, [characterId]);
assert.deepEqual(Object.keys(chapter.challengeSongBonusCardIds), [songId]);
assert.equal(activeEventAt(events, start + 48 * 60 * 60 * 1000 - 1)?.id, chapter.id);
assert.notEqual(activeEventAt(events, start - 1)?.id, chapter.id);
}
assert.equal(activeEventAt(events, Date.parse('2026-09-27T11:00:00.000Z')), null);
});
test('신규 카드 공통 보너스와 현재 챕터 멤버 보너스를 따로 적용한다', () => {
const aki = activeEventAt(events, Date.parse('2026-09-19T11:00:00Z'))!;
const shiori = activeEventAt(events, Date.parse('2026-09-21T11:00:00Z'))!;
const cards = [
{ id: aki.featuredCardIds[0], characterId: 'chr-00004', rarity: 5 },
{ id: 'shiori-regular', characterId: 'chr-04013', rarity: 3 }
];
assert.deepEqual(eventBonusBreakdown(aki, cards, () => 5), {
formationPercent: 50, featuredCardPercent: 50, awakeningPercent: 33, totalPercent: 133
});
assert.equal(eventBonusBreakdown(shiori, [cards[0]], () => 0).totalPercent, 50);
assert.equal(eventBonusBreakdown(shiori, [cards[1]], () => 0).totalPercent, 50);
assert.equal(eventChallengeSongScoreBonusPercent(aki, 'm0349', [cards[0].id]), 10);
assert.equal(eventChallengeSongScoreBonusPercent(shiori, 'm0349', [cards[0].id]), 0);
assert.equal(eventChallengeSongScoreBonusPercent(aki, 'm0349', [aki.featuredCardIds[1]]), 0);
});
test('이벤트 기간 안에서만 진행 중 이벤트를 반환한다', () => {
assert.equal(activeEventAt(events, Date.parse('2026-08-29T02:59:59.999Z')), null);
assert.equal(activeEventAt(events, Date.parse('2026-08-31T00:00:00.000Z'))?.id, currentEvent.id);
+13 -1
View File
@@ -16,6 +16,14 @@ export type EventDefinition = {
featuredCardBonusPercent: number;
challengeSongScoreBonusPercent: number;
awakeningBonusPercentByRarity: Readonly<Record<number, readonly number[]>>;
chapters?: readonly {
id: string;
name: string;
startsAt: string;
endsAt: string;
bonusCharacterIds: readonly string[];
challengeSongBonusCardIds: Readonly<Record<string, readonly string[]>>;
}[];
};
export type EventBonusBreakdown = {
@@ -30,7 +38,11 @@ export function eventOptimizationCardEligible(card: EventCard, bonusCharacterIds
}
export function activeEventAt(events: readonly EventDefinition[], now: number): EventDefinition | null {
return events.find((event) => Date.parse(event.startsAt) <= now && now <= Date.parse(event.endsAt)) ?? null;
const event = events.find((event) => Date.parse(event.startsAt) <= now && now <= Date.parse(event.endsAt));
if (!event) return null;
if (!event.chapters) return event;
const chapter = event.chapters.find((entry) => Date.parse(entry.startsAt) <= now && now <= Date.parse(entry.endsAt));
return chapter ? { ...event, ...chapter, name: `${event.name} · ${chapter.name}` } : null;
}
export function eventBonusBreakdown(