feat: compare top five cooldown combinations in analysis
Publish GitHub Pages output / publish (push) Successful in 42s
Publish GitHub Pages output / publish (push) Successful in 42s
This commit is contained in:
@@ -118,6 +118,23 @@
|
||||
let running = false;
|
||||
let progress: OptimizationProgress = { evaluatedCandidates: 0, totalCandidates: 0 };
|
||||
let result: ExactOptimizationResult | null = null;
|
||||
let cooldownRankings: { result: ExactOptimizationResult; achievementProbabilities?: ScoreAchievementProbabilities }[] = [];
|
||||
$: cooldownColumnMaxima = {
|
||||
score: Math.max(...cooldownRankings.map(({ result }) => result.theoreticalMaxScore)),
|
||||
expected: Math.max(...cooldownRankings.map(({ result }) => result.expectedScore)),
|
||||
maximum: Math.max(...cooldownRankings.map(({ achievementProbabilities: p }) => p?.maximum ?? -Infinity)),
|
||||
atLeast995Percent: Math.max(...cooldownRankings.map(({ achievementProbabilities: p }) => p?.atLeast995Percent ?? -Infinity)),
|
||||
atLeast99Percent: Math.max(...cooldownRankings.map(({ achievementProbabilities: p }) => p?.atLeast99Percent ?? -Infinity)),
|
||||
atLeast95Percent: Math.max(...cooldownRankings.map(({ achievementProbabilities: p }) => p?.atLeast95Percent ?? -Infinity))
|
||||
};
|
||||
|
||||
function selectCooldownRanking(index: number): void {
|
||||
const ranking = cooldownRankings[index];
|
||||
if (!ranking || running) return;
|
||||
result = ranking.result;
|
||||
achievementProbabilities = ranking.achievementProbabilities;
|
||||
clearConnectionOptimizationProposal();
|
||||
}
|
||||
type SimulationContext = { cards: OptimizerCard[]; chart: ExactSongChart; options: ExactScoreOptions };
|
||||
let simulationContext: SimulationContext | null = null;
|
||||
let pendingSimulationContext: SimulationContext | null = null;
|
||||
@@ -393,6 +410,7 @@
|
||||
if (event.data.type === 'progress') progress = event.data.progress;
|
||||
if (event.data.type === 'result') {
|
||||
result = event.data.result;
|
||||
cooldownRankings = event.data.cooldownRankings ?? [];
|
||||
transferredResult = false;
|
||||
resultCapture = pendingCapture;
|
||||
resultInputKey = pendingInputKey;
|
||||
@@ -864,7 +882,8 @@
|
||||
chart,
|
||||
perfectNoteScore: noteScore,
|
||||
directSongScoreBonusPercent: songBonus,
|
||||
includeAchievementProbabilities: true
|
||||
includeAchievementProbabilities: true,
|
||||
cooldownResultLimit: mode === 'analysis' ? 5 : 1
|
||||
});
|
||||
} catch (error) {
|
||||
running = false;
|
||||
@@ -1142,6 +1161,31 @@
|
||||
{#if mode === 'analysis' && resultCapture}<p class="board-guide"><strong>{resultCapture.title} · {difficultyLabel(resultCapture.settings.difficulty)}</strong> · 분석 당시 종합력 {resultCapture.overallPower.toLocaleString('ko-KR')}</p>{/if}
|
||||
{#if mode === 'rating' && result}<p class="board-guide"><strong>{selectedChart?.title} · {difficultyLabel(difficulty)}</strong> · 종합력 {overallPower.toLocaleString('ko-KR')} · 가창 보너스 10%</p>{/if}
|
||||
{#if result}
|
||||
{#if mode === 'analysis' && cooldownRankings.length > 0}
|
||||
<section class="cooldown-rankings" aria-labelledby="cooldown-rankings-title">
|
||||
<h3 id="cooldown-rankings-title">빈도 조합 상위 {cooldownRankings.length}개</h3>
|
||||
<p class="probability-guide">빈도 감소 조합마다 최적의 스페셜 순서를 구해 최고점 내림차순으로 표시합니다. 확률은 각 조합의 최고점 기준이며, 결과를 선택하면 아래 상세 점수와 그래프에 반영됩니다.</p>
|
||||
<div class="cooldown-table-scroll">
|
||||
<table>
|
||||
<thead><tr><th>순위</th><th>카드별 빈도 감소</th><th>최고점</th><th>예상 점수</th><th>100% 달성</th><th>99.5% 이상</th><th>99% 이상</th><th>95% 이상</th></tr></thead>
|
||||
<tbody>
|
||||
{#each cooldownRankings as ranking, index}
|
||||
<tr class:selected={result === ranking.result}>
|
||||
<td><button class="cooldown-select" type="button" aria-label={`${index + 1}위 빈도 조합 선택`} aria-pressed={result === ranking.result} disabled={running} on:click={() => selectCooldownRanking(index)}><span class="cooldown-rank">{index + 1}위</span><span class="cooldown-selection"><span class="cooldown-selection-dot" aria-hidden="true"></span>{result === ranking.result ? '선택됨' : '선택'}</span></button></td>
|
||||
<td>{#each ranking.result.formationOrder as cardId}<div>{resultCardName(cardId)} <strong>{ranking.result.cooldownReductions[cardId]}%</strong></div>{/each}</td>
|
||||
<td><span class:column-maximum={ranking.result.theoreticalMaxScore === cooldownColumnMaxima.score}>{ranking.result.theoreticalMaxScore.toLocaleString('ko-KR')}</span></td>
|
||||
<td><span class:column-maximum={ranking.result.expectedScore === cooldownColumnMaxima.expected}>{ranking.result.expectedScore.toLocaleString('ko-KR')}</span></td>
|
||||
<td><span class:column-maximum={ranking.achievementProbabilities?.maximum === cooldownColumnMaxima.maximum}>{probabilityLabel(ranking.achievementProbabilities?.maximum)}</span></td>
|
||||
<td><span class:column-maximum={ranking.achievementProbabilities?.atLeast995Percent === cooldownColumnMaxima.atLeast995Percent}>{probabilityLabel(ranking.achievementProbabilities?.atLeast995Percent)}</span><small>{scoreThreshold(ranking.result.theoreticalMaxScore, 0.995)}점 이상</small></td>
|
||||
<td><span class:column-maximum={ranking.achievementProbabilities?.atLeast99Percent === cooldownColumnMaxima.atLeast99Percent}>{probabilityLabel(ranking.achievementProbabilities?.atLeast99Percent)}</span><small>{scoreThreshold(ranking.result.theoreticalMaxScore, 0.99)}점 이상</small></td>
|
||||
<td><span class:column-maximum={ranking.achievementProbabilities?.atLeast95Percent === cooldownColumnMaxima.atLeast95Percent}>{probabilityLabel(ranking.achievementProbabilities?.atLeast95Percent)}</span><small>{scoreThreshold(ranking.result.theoreticalMaxScore, 0.95)}점 이상</small></td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
<div class="order-sections">
|
||||
<section class="order-section">
|
||||
<h3>추천 스페셜 발동 순서</h3>
|
||||
@@ -1333,6 +1377,22 @@
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.cooldown-rankings { margin-bottom: 24px; min-width: 0; }
|
||||
.cooldown-rankings .column-maximum { font-weight: 800; }
|
||||
.cooldown-table-scroll { overflow-x: auto; margin-top: 12px; }
|
||||
.cooldown-rankings table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.cooldown-rankings th, .cooldown-rankings td { padding: 12px; text-align: left; border-bottom: 1px solid #334155; white-space: nowrap; }
|
||||
.cooldown-rankings tr.selected { background: #2d2530; }
|
||||
.cooldown-rankings small { display: block; color: #9caaba; margin-top: 4px; }
|
||||
.cooldown-select { display: grid; justify-items: center; gap: 6px; min-width: 76px; min-height: 64px; padding: 10px 12px; border: 1px solid #414b5b; border-radius: 8px; background: #202630; color: #c1c8d3; font: inherit; cursor: pointer; }
|
||||
.cooldown-rank { font-size: 15px; font-weight: 800; }
|
||||
.cooldown-selection { display: flex; align-items: center; gap: 5px; font-size: 11px; }
|
||||
.cooldown-selection-dot { width: 10px; height: 10px; border: 1px solid currentColor; border-radius: 50%; box-sizing: border-box; }
|
||||
.cooldown-select:hover:not(:disabled) { border-color: #df74a2; color: #f5afcc; background: #2d2530; }
|
||||
.cooldown-select[aria-pressed='true'] { border-color: #ed8bb5; color: #f5afcc; background: #37232e; }
|
||||
.cooldown-select[aria-pressed='true'] .cooldown-selection-dot { background: #ed8bb5; border-color: #ed8bb5; box-shadow: inset 0 0 0 2px #37232e; }
|
||||
.cooldown-select:focus-visible { outline: 2px solid #f1a0c2; outline-offset: 3px; }
|
||||
.cooldown-select:disabled { opacity: .55; cursor: default; }
|
||||
.mobile-field-label, .selected-card-name { display: none; }
|
||||
.active-window-list { margin: 12px 0; padding: 12px; border: 1px solid #343c49; border-radius: 10px; }
|
||||
.active-window-list summary { min-height: 44px; align-content: center; cursor: pointer; font-size: 14px; }
|
||||
|
||||
Reference in New Issue
Block a user