feat: release version 1.0.5
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import cardSummary from '$lib/data/cards-summary.json';
|
||||
import boardSummary from '$lib/data/boards-summary.json';
|
||||
import memorySummary from '$lib/data/memory-summary.json';
|
||||
import exactChartCatalog from '$lib/data/exact-chart-catalog.json';
|
||||
import { loadExactSongChart } from '$lib/data/chart-loader.ts';
|
||||
import { createInitialReleaseClock, synchronizeReleaseClock } from '$lib/data/release-clock.ts';
|
||||
import { releasedSongCharts } from '$lib/data/song-release.ts';
|
||||
import PresetManager from '$lib/components/PresetManager.svelte';
|
||||
import { calculateBoardEffects, calculateUnitPower, countOptimizationCandidates, memoryParameterPermil, perfectNoteScoreFromPower, toOptimizerCard } from '$lib/engine/index.ts';
|
||||
import type { CooldownReduction, EffectiveSkillWindow, ExactOptimizationResult, ExactSongChart, OptimizationProgress, ScoreAchievementProbabilities, SkillWindow } from '$lib/engine/index.ts';
|
||||
import { USER_DATA_STORAGE_KEY } from '$lib/storage/browser-storage';
|
||||
@@ -33,13 +37,18 @@
|
||||
};
|
||||
|
||||
const ANALYSIS_STORAGE_KEY = 'holodori.analysis.v2';
|
||||
const ANALYSIS_PRESET_STORAGE_KEY = 'holodori.analysis.presets.v1';
|
||||
const LEGACY_ANALYSIS_STORAGE_KEY = 'holodori.analysis.v1';
|
||||
const MEMBER_COLORS = ['#f08db8', '#64cbe4', '#f2b85b', '#a995f5', '#69cf9b'] as const;
|
||||
const REDUCTION_LOCK_OPTIONS = [null, 0, 4, 8, 12] as const;
|
||||
|
||||
const fiveStarCards = cardSummary.cards.filter((card) => card.rarity === 5);
|
||||
const charts = exactChartCatalog.charts;
|
||||
const songs = Array.from(new Map(charts.map((chart) => [chart.songId, { id: chart.songId, title: chart.title }])).values());
|
||||
const allCharts = exactChartCatalog.charts;
|
||||
const initialReleaseClock = createInitialReleaseClock($page.data?.serverNow);
|
||||
let currentReleaseClock = initialReleaseClock;
|
||||
let releaseNow = currentReleaseClock.now();
|
||||
let charts = releasedSongCharts(allCharts, releaseNow);
|
||||
let songs = Array.from(new Map(charts.map((chart) => [chart.songId, { id: chart.songId, title: chart.title }])).values());
|
||||
const cardById = new Map(fiveStarCards.map((card) => [card.id, card]));
|
||||
|
||||
function uniqueMemberCards(cards: readonly CardRow[], limit = Number.POSITIVE_INFINITY): CardRow[] {
|
||||
@@ -73,6 +82,9 @@
|
||||
let errorMessage = '';
|
||||
let analysisReady = false;
|
||||
|
||||
$: charts = releasedSongCharts(allCharts, releaseNow);
|
||||
$: songs = Array.from(new Map(charts.map((chart) => [chart.songId, { id: chart.songId, title: chart.title }])).values());
|
||||
$: if (!songs.some((song) => song.id === selectedSongId)) selectedSongId = songs[0]?.id ?? '';
|
||||
$: selectedSong = songs.find((song) => song.id === selectedSongId) ?? songs[0];
|
||||
$: selectedChart = charts.find((chart) => chart.songId === selectedSongId && chart.difficulty === difficulty);
|
||||
$: leaderMember = leaderMembers.find((card) => card.characterId === leaderCharacterId);
|
||||
@@ -127,6 +139,16 @@
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
$: presetSnapshot = {
|
||||
schemaVersion: 2,
|
||||
songId: selectedSongId,
|
||||
difficulty,
|
||||
cardIds: selectedCardIds,
|
||||
awakenings: selectedAwakenings,
|
||||
fixedCooldownReductions,
|
||||
leaderCharacterId,
|
||||
leaderOutfitCardId
|
||||
} satisfies SavedAnalysisState;
|
||||
$: if (analysisReady) saveAnalysisState({
|
||||
schemaVersion: 2,
|
||||
songId: selectedSongId,
|
||||
@@ -139,6 +161,15 @@
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
let mounted = true;
|
||||
void synchronizeReleaseClock().then((clock) => {
|
||||
if (!mounted) return;
|
||||
currentReleaseClock = clock;
|
||||
releaseNow = currentReleaseClock.now();
|
||||
});
|
||||
const releaseClock = window.setInterval(() => {
|
||||
releaseNow = currentReleaseClock.now();
|
||||
}, 1_000);
|
||||
const applyStoredUserData = (stored: string | null) => {
|
||||
const parsed = stored ? JSON.parse(stored) : null;
|
||||
collection = parsed?.collection && typeof parsed.collection === 'object' ? parsed.collection : {};
|
||||
@@ -202,6 +233,8 @@
|
||||
};
|
||||
window.addEventListener('storage', syncStorage);
|
||||
return () => {
|
||||
mounted = false;
|
||||
window.clearInterval(releaseClock);
|
||||
window.removeEventListener('storage', syncStorage);
|
||||
worker?.terminate();
|
||||
};
|
||||
@@ -256,6 +289,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function applyPreset(event: CustomEvent<unknown>): void {
|
||||
try {
|
||||
restoreAnalysisState(JSON.stringify(event.detail));
|
||||
result = null;
|
||||
achievementProbabilities = undefined;
|
||||
errorMessage = '';
|
||||
} catch {
|
||||
errorMessage = '이 프리셋의 분석 조건을 불러오지 못했습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
function updateAwakening(slot: number, awakening: number): void {
|
||||
const normalized = normalizeAwakening(awakening);
|
||||
selectedAwakenings = selectedAwakenings.map((value, index) => index === slot ? normalized : value);
|
||||
@@ -538,6 +582,8 @@
|
||||
</div>
|
||||
<p class="power-detail">최대 레벨 기준 · 기본 {unitPower.baseParameterTotal.toLocaleString('ko-KR')} + 개화 {unitPower.potentialTotal.toLocaleString('ko-KR')} + 패시브 {unitPower.passiveTotal.toLocaleString('ko-KR')} + 의상 {unitPower.outfitTotal.toLocaleString('ko-KR')} + 보드 {unitPower.boardTotal.toLocaleString('ko-KR')} + 메모리 {unitPower.memoryTotal.toLocaleString('ko-KR')} ({memoryCount}개) + 멤버 강화 {unitPower.memberPowerUpTotal.toLocaleString('ko-KR')} ({memberPowerUpPercent.toLocaleString('ko-KR')}%)</p>
|
||||
|
||||
<PresetManager storageKey={ANALYSIS_PRESET_STORAGE_KEY} snapshot={presetSnapshot} disabled={running} on:apply={applyPreset} />
|
||||
|
||||
{#if errorMessage}<p class="error" role="alert">{errorMessage}</p>{/if}
|
||||
<button class="analyze" type="button" disabled={running || candidateCount === 0} on:click={analyze}>
|
||||
{running ? `분석 중 ${Math.round(progress.evaluatedCandidates / Math.max(1, progress.totalCandidates) * 100)}%` : '최적 배치 분석'}
|
||||
|
||||
Reference in New Issue
Block a user