feat: release version 1.0.6
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import AnalysisResultView from '$lib/components/AnalysisResultView.svelte';
|
||||
import PresetManager from '$lib/components/PresetManager.svelte';
|
||||
import {
|
||||
additionalAwakeningsRequired,
|
||||
calculateBoardEffects,
|
||||
calculateUnitPower,
|
||||
combinationSearchLimits,
|
||||
@@ -72,6 +73,8 @@
|
||||
cardIds: string[];
|
||||
awakenings: number[];
|
||||
fixedCooldownReductions: (CooldownReduction | null)[];
|
||||
allBlueNodes: boolean[];
|
||||
generationBonusOverrides: (number | null)[];
|
||||
leaderCharacterId: string;
|
||||
leaderOutfitCardId: string;
|
||||
};
|
||||
@@ -378,6 +381,11 @@
|
||||
cardIds: cards.map((card) => card.id),
|
||||
awakenings: cards.map((_, index) => Number.isFinite(state.awakenings?.[index]) ? normalizeAwakening(state.awakenings?.[index]) : 0),
|
||||
fixedCooldownReductions: cards.map((_, index) => normalizeFixedReduction(state.fixedCooldownReductions?.[index])),
|
||||
allBlueNodes: cards.map((_, index) => state.allBlueNodes?.[index] === true),
|
||||
generationBonusOverrides: cards.map((_, index) => {
|
||||
const value = state.generationBonusOverrides?.[index];
|
||||
return typeof value === 'number' && Number.isFinite(value) ? normalizeGenerationBonus(value) : null;
|
||||
}),
|
||||
leaderCharacterId: typeof state.leaderCharacterId === 'string' ? state.leaderCharacterId : '',
|
||||
leaderOutfitCardId: typeof state.leaderOutfitCardId === 'string' ? state.leaderOutfitCardId : ''
|
||||
};
|
||||
@@ -387,10 +395,6 @@
|
||||
return `${collection[card.id]?.owned ? '● ' : ''}${card.name}${card.versionName ? ` · ${card.versionName}` : ''}`;
|
||||
}
|
||||
|
||||
function selectedInAnotherSlot(card: CardRow, slot: number): boolean {
|
||||
return fixedCardIds.some((id, index) => index !== slot && cardById.get(id)?.characterId === card.characterId);
|
||||
}
|
||||
|
||||
function updateFixedCard(slot: number, cardId: string): void {
|
||||
const cardChanged = fixedCardIds[slot] !== cardId;
|
||||
fixedCardIds = fixedCardIds.map((id, index) => index === slot ? cardId : id);
|
||||
@@ -713,10 +717,19 @@
|
||||
const cards = state.cardIds.map((id) => cardById.get(id)).filter((card): card is CardRow => Boolean(card));
|
||||
if (cards.length !== 5) throw new Error('기준 조합의 카드 정보를 찾지 못했습니다.');
|
||||
const outfit = cardById.get(state.leaderOutfitCardId)?.details.costume ?? null;
|
||||
const boardEffects = calculateBoardEffects(boardSummary, memberBoards, cards, state.leaderCharacterId, chartMetadata, {
|
||||
const baselineBoards = Object.fromEntries(Object.entries(memberBoards).map(([characterId, groupIds]) => [characterId, [...groupIds]]));
|
||||
cards.forEach((card, index) => {
|
||||
if (!state.allBlueNodes[index]) return;
|
||||
const nonBlueGroups = (baselineBoards[card.characterId] ?? []).filter((groupId) => !groupId.startsWith('B'));
|
||||
baselineBoards[card.characterId] = [...nonBlueGroups, ...(blueGroupsByCharacter[card.characterId] ?? [])];
|
||||
});
|
||||
const generationBonusByCardId = Object.fromEntries(cards
|
||||
.map((card, index) => [card.id, state.generationBonusOverrides[index]] as const)
|
||||
.filter((entry): entry is readonly [string, number] => entry[1] !== null));
|
||||
const boardEffects = calculateBoardEffects(boardSummary, baselineBoards, cards, state.leaderCharacterId, chartMetadata, {
|
||||
placements: boardCards,
|
||||
cards: connectionCards
|
||||
});
|
||||
}, { generationBonusByCardId });
|
||||
const power = calculateUnitPower(
|
||||
cards.map((card, index) => ({ card, awakening: state.awakenings[index] })),
|
||||
outfit,
|
||||
@@ -946,6 +959,17 @@
|
||||
return index >= 0 ? baselineState?.awakenings[index] ?? 0 : 0;
|
||||
}
|
||||
|
||||
function displayedCardAdditionalAwakenings(cardId: string, isBaseline: boolean): number {
|
||||
return additionalAwakeningsRequired(collection[cardId], displayedCardAwakening(cardId, isBaseline));
|
||||
}
|
||||
|
||||
function displayedAdditionalAwakenings(ranking: DisplayRankedResult): number {
|
||||
return ranking.result.formationOrder.reduce(
|
||||
(total, cardId) => total + displayedCardAdditionalAwakenings(cardId, ranking.isBaseline),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
function displayedCompositionKey(ranking: DisplayRankedResult): string {
|
||||
return ranking.result.order
|
||||
.map((cardId) => `${cardId}:${displayedCardAwakening(cardId, ranking.isBaseline)}`)
|
||||
@@ -1046,7 +1070,7 @@
|
||||
<label><span>{slot + 1}번 고정</span>
|
||||
<select value={selectedId} on:change={(event) => updateFixedCard(slot, event.currentTarget.value)}>
|
||||
<option value="">고정하지 않음</option>
|
||||
{#each candidateCards as card}<option value={card.id} disabled={selectedInAnotherSlot(card, slot)}>{cardLabel(card)}</option>{/each}
|
||||
{#each candidateCards as card}<option value={card.id} disabled={fixedCardIds.some((id, index) => index !== slot && cardById.get(id)?.characterId === card.characterId)}>{cardLabel(card)}</option>{/each}
|
||||
</select>
|
||||
</label>
|
||||
<div class="fixed-awakening">
|
||||
@@ -1107,13 +1131,23 @@
|
||||
{#if ranking.isBaseline}<b class="baseline-result-label">기준 조합</b>{/if}
|
||||
{#each ranking.result.formationOrder as cardId, memberIndex}
|
||||
<span class:added-member={!ranking.isBaseline && !fixedCardIds.includes(cardId)}>
|
||||
<small class="member-awakening">{displayedCardAwakening(cardId, ranking.isBaseline)}개화</small>
|
||||
<small class="member-awakening">
|
||||
{displayedCardAwakening(cardId, ranking.isBaseline)}개화
|
||||
{#if displayedCardAdditionalAwakenings(cardId, ranking.isBaseline) > 0}
|
||||
· +{displayedCardAdditionalAwakenings(cardId, ranking.isBaseline)}회
|
||||
{/if}
|
||||
</small>
|
||||
<span class="member-name">{resultCardName(cardId)}</span>
|
||||
</span>
|
||||
{#if memberIndex < ranking.result.formationOrder.length - 1}<i aria-hidden="true">·</i>{/if}
|
||||
{/each}
|
||||
</span>
|
||||
<span class="rank-metric"><small>최고 점수</small><strong>{ranking.result.theoreticalMaxScore.toLocaleString('ko-KR')}</strong>{#if comparableToBaseline && !ranking.isBaseline}<i>{scoreDelta(ranking.result.theoreticalMaxScore, baselineResult!.theoreticalMaxScore)}</i>{/if}</span>
|
||||
<span class="rank-metric">
|
||||
<small>최고 점수</small>
|
||||
<strong>{ranking.result.theoreticalMaxScore.toLocaleString('ko-KR')}</strong>
|
||||
{#if comparableToBaseline && !ranking.isBaseline}<i>{scoreDelta(ranking.result.theoreticalMaxScore, baselineResult!.theoreticalMaxScore)}</i>{/if}
|
||||
<b class:complete={displayedAdditionalAwakenings(ranking) === 0}>추가 개화 {displayedAdditionalAwakenings(ranking)}회</b>
|
||||
</span>
|
||||
<span class="rank-probabilities">
|
||||
<small>최고점 대비 달성 확률</small>
|
||||
<span><b>100%</b><strong>{probabilityLabel(ranking.achievementProbabilities?.maximum)}</strong></span>
|
||||
@@ -1134,6 +1168,7 @@
|
||||
</details>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="awakening-guide">추가 개화는 현재 카드 데이터와 조합의 목표 개화를 비교한 값입니다. 미보유 카드를 처음 얻는 과정도 1회로 계산합니다.</p>
|
||||
<p class="probability-guide">달성 확률은 모든 노트를 PERFECT로 처리한다고 가정하고 액티브 발동 확률만 반영합니다. 100%는 정확 계산, 99%·95% 이상은 25,000회 반복 계산값이며 최고점에 영향을 주지 않는 발동 실패는 100% 달성 확률을 낮추지 않습니다.</p>
|
||||
{:else}
|
||||
<div class="empty">고정할 카드와 악곡을 선택한 뒤 조합 탐색을 시작해 주세요.</div>
|
||||
@@ -1243,13 +1278,17 @@
|
||||
.rank-metric small { color: #9ca5b3; font-size: 13px; font-style: normal; font-weight: 750; }
|
||||
.rank-metric strong { color: #f0a0c2; font-size: 18px; line-height: 1.2; }
|
||||
.rank-metric i { color: #90d8ae; font-size: 12px; font-style: normal; font-weight: 800; }
|
||||
.rank-metric b { width: fit-content; margin-top: 2px; padding: 3px 7px; border-radius: 999px; color: #f0b0c9; background: #30212a; font-size: 11px; font-weight: 850; }
|
||||
.rank-metric b.complete { color: #9edbb7; background: #1e3028; }
|
||||
.rank-probabilities { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px 12px; padding-left: 18px; border-left: 1px solid #303744; }
|
||||
.rank-probabilities > small { grid-column: 1 / -1; color: #9ca5b3; font-size: 12px; font-weight: 750; }
|
||||
.rank-probabilities > span { display: grid; gap: 2px; }
|
||||
.rank-probabilities b { color: #7f8998; font-size: 10px; font-weight: 750; }
|
||||
.rank-probabilities strong { color: #dce1e8; font-size: 14px; line-height: 1.2; }
|
||||
.rank-probabilities i { color: #7f8998; font-size: 10px; font-style: normal; font-weight: 700; }
|
||||
.probability-guide { margin: 11px 2px 0; color: #7f8998; font-size: 12px; line-height: 1.55; }
|
||||
.awakening-guide, .probability-guide { margin: 11px 2px 0; color: #7f8998; font-size: 12px; line-height: 1.55; }
|
||||
.awakening-guide { color: #aa8797; }
|
||||
.awakening-guide + .probability-guide { margin-top: 5px; }
|
||||
.ranking-detail { padding: 0 16px 18px; border-top: 1px solid #2b313c; }
|
||||
.scores { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; }
|
||||
.scores div { display: grid; gap: 7px; padding: 15px; border-radius: 10px; background: #22202a; }
|
||||
|
||||
Reference in New Issue
Block a user