feat: support every song difficulty including Easy and Normal
Publish GitHub Pages output / publish (push) Successful in 41s
Publish GitHub Pages output / publish (push) Successful in 41s
This commit is contained in:
@@ -126,7 +126,7 @@
|
||||
{#each records as record (record.id)}
|
||||
<li class:selected={selected?.id === record.id}>
|
||||
<button class="record" disabled={busy} on:click={() => open(record)} aria-expanded={selected?.id === record.id}>
|
||||
<span class="record-meta"><span class="difficulty">{record.settings.difficulty === 'expert' ? 'EXPERT' : 'HARD'}</span><span>{new Date(record.analyzedAt).toLocaleDateString('ko-KR')}</span></span>
|
||||
<span class="record-meta"><span class="difficulty">{record.settings.difficulty.toUpperCase()}</span><span>{new Date(record.analyzedAt).toLocaleDateString('ko-KR')}</span></span>
|
||||
<strong class="record-name">{record.name}</strong><span class="song-name">{record.title}</span>
|
||||
<span class="members">{#each record.settings.cardIds as id}<span>{cardName(id)}</span>{/each}</span>
|
||||
<span class="record-scores"><span><small>평균 점수</small><b>{record.result.expectedScore.toLocaleString('ko-KR')}</b></span><span><small>이론상 고점</small><b>{record.result.theoreticalMaxScore.toLocaleString('ko-KR')}</b></span></span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { difficultyLabel } from '$lib/data/difficulty.ts';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import type catalog from '$lib/data/exact-chart-catalog.json';
|
||||
import { loadExactSongChart } from '$lib/data/chart-loader.ts';
|
||||
@@ -20,7 +21,7 @@
|
||||
export let getContext: (chart: Chart) => Context;
|
||||
export let onSelect: (chart: Chart, result: ExactOptimizationResult, overallPower: number, probabilities: ScoreAchievementProbabilities) => void;
|
||||
|
||||
const difficulty = 'both';
|
||||
const difficulty = 'all';
|
||||
let running = false;
|
||||
let rows: Row[] = [];
|
||||
let completed = 0;
|
||||
@@ -94,7 +95,7 @@
|
||||
while (id === runId && nextJob < jobs.length) {
|
||||
const jobIndex = nextJob++;
|
||||
const { chart: summary, context } = jobs[jobIndex];
|
||||
const title = `${summary.title} · ${summary.difficulty === 'expert' ? 'Expert' : 'Hard'}`;
|
||||
const title = `${summary.title} · ${difficultyLabel(summary.difficulty)}`;
|
||||
activeJobs = { ...activeJobs, [jobIndex]: { title, fraction: 0 } };
|
||||
let jobWorker: Worker | null = null;
|
||||
let rejectJob: ((reason: Error) => void) | null = null;
|
||||
@@ -221,7 +222,7 @@
|
||||
<div class="contributors-title"><span>레이팅에 합산되는 곡</span><small>곡별 최고 기록 1개</small></div>
|
||||
<ol>{#each topSongs as row, index}<li>
|
||||
<span class="contributor-index">{String(index + 1).padStart(2, '0')}</span>
|
||||
<div><span class="contributor-title">{row.title}</span><small>{row.difficulty === 'expert' ? 'Expert' : 'Hard'} · Lv.{row.difficultyLevel}</small></div>
|
||||
<div><span class="contributor-title">{row.title}</span><small>{difficultyLabel(row.difficulty)} · Lv.{row.difficultyLevel}</small></div>
|
||||
<strong>{format(songRating(row.theoreticalMaxScore))}</strong>
|
||||
</li>{/each}</ol>
|
||||
</div>
|
||||
@@ -232,7 +233,7 @@
|
||||
<thead><tr><th scope="col">순위</th><th scope="col">곡 / 난이도</th><th scope="col">고점 레이팅</th><th scope="col">예상 고점</th><th scope="col">해당 배치 평균</th><th scope="col">점수 달성 확률</th><th scope="col"><span class="sr-only">상세 배치</span></th></tr></thead>
|
||||
<tbody>{#each pageRows as row, index}<tr class:top={topSongs.includes(row)}>
|
||||
<td class="rank-cell"><span class="rank-number" class:first={(pageNumber - 1) * pageSize + index === 0}>{(pageNumber - 1) * pageSize + index + 1}</span></td>
|
||||
<td class="song"><strong>{row.title}</strong><div class="song-meta"><span class="difficulty" class:expert={row.difficulty === 'expert'}>{row.difficulty === 'expert' ? 'Expert' : 'Hard'} <b>{row.difficultyLevel}</b></span>{#if topSongs.includes(row)}<span class="counted">합산 대상</span>{/if}{#if row.scoreCalibrationSource !== 'formula-v1' && row.scoreCalibrationSource !== 'measured'}<span class="estimated">점수 계수 추정</span>{/if}</div></td>
|
||||
<td class="song"><strong>{row.title}</strong><div class="song-meta"><span class="difficulty" class:expert={row.difficulty === 'expert'}>{difficultyLabel(row.difficulty)} <b>{row.difficultyLevel}</b></span>{#if topSongs.includes(row)}<span class="counted">합산 대상</span>{/if}{#if row.scoreCalibrationSource !== 'formula-v1' && row.scoreCalibrationSource !== 'measured'}<span class="estimated">점수 계수 추정</span>{/if}</div></td>
|
||||
<td class="chart-rating"><span class="mobile-label">레이팅</span><strong>{format(songRating(row.theoreticalMaxScore))}</strong></td>
|
||||
<td class="maximum-score"><span class="mobile-label">예상 고점</span>{format(row.theoreticalMaxScore)}</td>
|
||||
<td class="average-score"><span class="mobile-label">배치 평균</span>{format(row.expectedScore)}</td>
|
||||
@@ -266,7 +267,7 @@
|
||||
<summary><span class="info-mark" aria-hidden="true">i</span>계산 기준 및 안내<span class="guide-chevron" aria-hidden="true">⌄</span></summary>
|
||||
<div>
|
||||
<p>멤버 5명과 리더·의상·성장 설정을 반영하고, 곡마다 스페셜 순서와 허용된 빈도 감소를 최적화합니다. 가창 보너스는 모든 곡에 10%를 적용합니다.</p>
|
||||
<p>ALL PERFECT·스킬 최대 발동 기준의 예상 고점 순위입니다. 곡별 Hard·Expert를 각각 표시하며, 최대 20개 결과를 5개씩 확인할 수 있습니다. 상위 3곡 합계는 같은 곡에서 고점이 높은 결과 하나만 집계합니다.</p>
|
||||
<p>ALL PERFECT·스킬 최대 발동 기준의 예상 고점 순위입니다. 곡별 Easy·Normal·Hard·Expert를 각각 표시하며, 최대 20개 결과를 5개씩 확인할 수 있습니다. 상위 3곡 합계는 같은 곡에서 고점이 높은 결과 하나만 집계합니다.</p>
|
||||
<p>점수 달성 확률은 해당 추천 배치에서 모든 노트를 PERFECT로 처리할 때의 액티브 발동 확률을 반영합니다. 최고점 확률은 정확 계산, 최고점의 99%·95% 이상 확률은 25,000회 반복 계산값입니다.</p>
|
||||
<p>곡 레이팅 = 점수 ÷ 5,000 올림 · <a href="https://dreams.wf-calc.net/wiki/live" target="_blank" rel="noreferrer">계산 기준 (비공식 연구 자료) ↗</a>. 실제 플레이 결과와 차이가 있을 수 있습니다. 가창 멤버가 등록되지 않은 곡은 제외합니다.</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const difficulties = ['expert', 'hard', 'normal', 'easy'] as const;
|
||||
export type DifficultyKey = typeof difficulties[number];
|
||||
|
||||
export function isDifficulty(value: unknown): value is DifficultyKey {
|
||||
return typeof value === 'string' && difficulties.some((difficulty) => difficulty === value);
|
||||
}
|
||||
|
||||
export function difficultyLabel(value: string): string {
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { difficulties, isDifficulty } from '../data/difficulty.ts';
|
||||
import { calculateExactScore, perfectNoteScoreFromPower } from './score.ts';
|
||||
|
||||
test('모든 악곡의 네 난이도 채보를 불러와 실제 점수를 계산한다', async () => {
|
||||
const catalog = JSON.parse(await readFile(new URL('../data/exact-chart-catalog.json', import.meta.url), 'utf8'));
|
||||
const bySong = new Map<string, Set<string>>();
|
||||
for (const row of catalog.charts) {
|
||||
assert.ok(isDifficulty(row.difficulty));
|
||||
const seen = bySong.get(row.songId) ?? new Set<string>();
|
||||
assert.ok(!seen.has(row.difficulty), `${row.songId}: 중복 난이도`);
|
||||
seen.add(row.difficulty);
|
||||
bySong.set(row.songId, seen);
|
||||
const chart = JSON.parse(await readFile(new URL(`../../../static${row.path}`, import.meta.url), 'utf8'));
|
||||
assert.equal(chart.songId, row.songId);
|
||||
assert.equal(chart.difficulty, row.difficulty);
|
||||
assert.equal(chart.notes.length, row.totalNotes);
|
||||
assert.ok(chart.notes.length > 0);
|
||||
// 튜토리얼용 짧은 곡은 스페셜 구간이 두 개뿐이다.
|
||||
assert.equal(chart.specialStartSeconds.length, row.songId === 'm9999' ? 2 : 5);
|
||||
assert.equal(chart.scoreCalibrationSource, 'formula-v1');
|
||||
const score = calculateExactScore([], {}, chart, {
|
||||
perfectNoteScore: perfectNoteScoreFromPower(100000, chart.scoreRatio)
|
||||
});
|
||||
assert.ok(Number.isFinite(score.expectedScore) && score.expectedScore > 0, `${row.songId}.${row.difficulty}`);
|
||||
}
|
||||
assert.ok(bySong.size > 0);
|
||||
for (const [songId, actual] of bySong) assert.deepEqual(actual, new Set(difficulties), songId);
|
||||
});
|
||||
@@ -41,6 +41,13 @@ test('성장 상태를 공유하는 기록 백업을 손실 없이 읽고 잘못
|
||||
const parsed = parseAnalysisArchive(JSON.stringify(archive));
|
||||
assert.equal(parsed.records.length, 2); assert.equal(parsed.snapshots.length, 1);
|
||||
assert.deepEqual(parsed.snapshots[0].growth, growth);
|
||||
for (const difficulty of ['easy', 'normal', 'hard', 'expert']) {
|
||||
record.settings.difficulty = difficulty;
|
||||
assert.equal(parseAnalysisArchive(JSON.stringify(archive)).records[0].settings.difficulty, difficulty);
|
||||
}
|
||||
record.settings.difficulty = 'invalid';
|
||||
assert.throws(() => parseAnalysisArchive(JSON.stringify(archive)));
|
||||
record.settings.difficulty = 'expert';
|
||||
record.settings.awakenings[0] = 8;
|
||||
assert.throws(() => parseAnalysisArchive(JSON.stringify(archive)));
|
||||
});
|
||||
|
||||
@@ -31,3 +31,14 @@ test('고점 순위와 상위 3곡 합계는 동일 곡의 난이도를 중복
|
||||
assert.equal(leaderRating([]), 0);
|
||||
assert.equal(results[0].theoreticalMaxScore, 5001);
|
||||
});
|
||||
|
||||
test('전체 난이도 추천은 Easy와 Normal을 포함하고 곡별 최고점만 합산한다', () => {
|
||||
const charts = ['easy', 'normal', 'hard', 'expert'].map((difficulty, index) => ({
|
||||
songId: 'song', title: 'Song', difficulty, singerCharacterIds: ['leader'],
|
||||
theoreticalMaxScore: 20000 - index * 5000, expectedScore: 1000
|
||||
}));
|
||||
assert.deepEqual(leaderRatingCharts(charts, 'leader', 'all', Date.now()), charts);
|
||||
assert.deepEqual(leaderRatingCharts(charts, 'leader', 'normal', Date.now()), [charts[1]]);
|
||||
assert.equal(rankSongs(charts)[0].difficulty, 'easy');
|
||||
assert.equal(leaderRating(charts), 4);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isDifficulty } from '../data/difficulty.ts';
|
||||
import { releasedSongCharts } from '../data/song-release.ts';
|
||||
|
||||
export type RatingChart = {
|
||||
@@ -17,7 +18,7 @@ export function songRating(score: number): number {
|
||||
export function leaderRatingCharts<T extends RatingChart>(charts: readonly T[], leaderId: string, difficulty: string, now: number): T[] {
|
||||
return releasedSongCharts(charts, now).filter((chart) =>
|
||||
Boolean(leaderId) && chart.singerCharacterIds?.includes(leaderId) &&
|
||||
(difficulty === 'both' ? chart.difficulty === 'hard' || chart.difficulty === 'expert' : chart.difficulty === difficulty));
|
||||
(difficulty === 'all' ? isDifficulty(chart.difficulty) : difficulty === 'both' ? chart.difficulty === 'hard' || chart.difficulty === 'expert' : chart.difficulty === difficulty));
|
||||
}
|
||||
|
||||
export type SongRatingScore = {
|
||||
|
||||
@@ -50,7 +50,7 @@ test('공식 적용 카탈로그와 실제 채보가 같은 분모 및 환산값
|
||||
assert.equal(chart.notes.length, ref.fullComboNoteCount);
|
||||
formulaCount += 1;
|
||||
}
|
||||
assert.equal(formulaCount, 388);
|
||||
assert.equal(formulaCount, catalog.charts.length);
|
||||
});
|
||||
|
||||
test('잘못된 분모와 난이도로 공식 환산값을 만들지 않는다', () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { DifficultyKey } from '../data/difficulty.ts';
|
||||
import type { CooldownReduction, ExactOptimizationResult } from '../engine/types.ts';
|
||||
|
||||
export type AnalysisSettings = {
|
||||
songId: string; difficulty: 'hard' | 'expert'; cardIds: string[]; awakenings: number[];
|
||||
songId: string; difficulty: DifficultyKey; cardIds: string[]; awakenings: number[];
|
||||
fixedCooldownReductions?: (CooldownReduction | null)[];
|
||||
allBlueNodes?: boolean[]; generationBonusOverrides?: (number | null)[];
|
||||
songScoreBonusOverride?: number | null; leaderCharacterId: string; leaderOutfitCardId: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isDifficulty } from '../data/difficulty.ts';
|
||||
import { compactResult, snapshotKey, type AnalysisCapture, type AnalysisRecord, type GrowthSnapshot } from '../models/analysis-history.ts';
|
||||
import type { ExactOptimizationResult } from '../engine/types.ts';
|
||||
|
||||
@@ -24,7 +25,7 @@ export function parseAnalysisArchive(text: string): Archive {
|
||||
if (![record.id, record.name, record.title, record.analyzedAt].every((s) => typeof s === 'string') || !Number.isFinite(Date.parse(record.analyzedAt))) return invalid();
|
||||
const s = record.settings, r = record.result;
|
||||
if (!strings(s.cardIds) || s.cardIds.length !== 5 || new Set(s.cardIds).size !== 5 || !Array.isArray(s.awakenings) || s.awakenings.length !== 5 || !s.awakenings.every((n) => Number.isInteger(n) && n >= 0 && n <= 5)) return invalid();
|
||||
if (typeof s.songId !== 'string' || !['hard', 'expert'].includes(s.difficulty) || typeof s.leaderCharacterId !== 'string' || typeof s.leaderOutfitCardId !== 'string') return invalid();
|
||||
if (typeof s.songId !== 'string' || !isDifficulty(s.difficulty) || typeof s.leaderCharacterId !== 'string' || typeof s.leaderOutfitCardId !== 'string') return invalid();
|
||||
if (s.allBlueNodes && (s.allBlueNodes.length !== 5 || !s.allBlueNodes.every((v) => typeof v === 'boolean'))) return invalid();
|
||||
if (s.fixedCooldownReductions && (s.fixedCooldownReductions.length !== 5 || !s.fixedCooldownReductions.every((v) => v === null || [0, 4, 8, 12].includes(v)))) return invalid();
|
||||
if (s.generationBonusOverrides && (s.generationBonusOverrides.length !== 5 || !s.generationBonusOverrides.every((v) => v === null || (finite(v) && v <= 900)))) return invalid();
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</a>
|
||||
<a class="feature-card" href="/rating">
|
||||
<span class="feature-number">03</span>
|
||||
<div><h3>리더 레이팅</h3><p>입력한 편성으로 레이팅을 높이기 좋은 가창곡의 Hard·Expert를 최대 20개 추천하고, 5개씩 비교합니다.</p></div>
|
||||
<div><h3>리더 레이팅</h3><p>입력한 편성으로 레이팅을 높이기 좋은 가창곡의 모든 난이도를 최대 20개 추천하고, 5개씩 비교합니다.</p></div>
|
||||
<span class="arrow" aria-hidden="true">→</span>
|
||||
</a>
|
||||
<a class="feature-card" href="/data/boards">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
export let mode: 'analysis' | 'rating' = 'analysis';
|
||||
|
||||
type CardRow = (typeof cardSummary.cards)[number];
|
||||
type DifficultyKey = 'hard' | 'expert';
|
||||
import { difficulties, difficultyLabel, isDifficulty, type DifficultyKey } from '$lib/data/difficulty.ts';
|
||||
type CollectionState = Record<string, { owned?: boolean; awakening?: number }>;
|
||||
type MemberBoards = Record<string, string[]>;
|
||||
type BoardCards = Record<string, Record<string, string>>;
|
||||
@@ -333,7 +333,7 @@
|
||||
const state = JSON.parse(stored) as Partial<SavedAnalysisState>;
|
||||
if (!legacy && state.schemaVersion !== 2) return;
|
||||
if (typeof state.songId === 'string' && songs.some((song) => song.id === state.songId)) selectedSongId = state.songId;
|
||||
if (state.difficulty === 'hard' || state.difficulty === 'expert') difficulty = state.difficulty;
|
||||
if (isDifficulty(state.difficulty)) difficulty = state.difficulty;
|
||||
songScoreBonusOverride = normalizeSongScoreBonusOverride(state.songScoreBonusOverride);
|
||||
|
||||
if (Array.isArray(state.cardIds) && state.cardIds.length === 5) {
|
||||
@@ -901,7 +901,7 @@
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1>{mode === 'rating' ? '리더 레이팅' : '배치 분석'}</h1>
|
||||
<p class="page-description">{mode === 'rating' ? '입력한 편성으로 리더 가창곡의 Hard·Expert를 예상 레이팅 순으로 최대 20개 추천합니다. 결과는 5개씩 확인할 수 있습니다.' : '특정 악곡과 카드 조합에서 최고 점수를 낼 수 있는 스페셜 발동 순서와 빈도 감소 조합을 찾습니다.'}</p>
|
||||
<p class="page-description">{mode === 'rating' ? '입력한 편성으로 리더 가창곡의 모든 난이도를 예상 레이팅 순으로 최대 20개 추천합니다. 결과는 5개씩 확인할 수 있습니다.' : '특정 악곡과 카드 조합에서 최고 점수를 낼 수 있는 스페셜 발동 순서와 빈도 감소 조합을 찾습니다.'}</p>
|
||||
{#if mode === 'analysis' && selectedChart && selectedChart.scoreCalibrationSource !== 'measured' && selectedChart.scoreCalibrationSource !== 'formula-v1'}
|
||||
<p class="calibration-warning" role="status"><strong>추정 점수 안내</strong> 이 악곡의 점수 계수는 실측값이 아니므로 실제 점수와 오차가 크게 발생할 수 있습니다.</p>
|
||||
{/if}
|
||||
@@ -919,8 +919,7 @@
|
||||
<div class="song-picker-field"><SongPicker {charts} {difficulty} bind:value={selectedSongId} on:change={() => result = null} /></div>
|
||||
<label>난이도
|
||||
<select bind:value={difficulty} on:change={() => result = null}>
|
||||
<option value="expert">Expert</option>
|
||||
<option value="hard">Hard</option>
|
||||
{#each difficulties as value}<option {value}>{difficultyLabel(value)}</option>{/each}
|
||||
</select>
|
||||
</label>
|
||||
<label>가창 보너스 덮어쓰기 (0~10%)
|
||||
@@ -1036,7 +1035,7 @@
|
||||
{#if mode === 'analysis' || result}
|
||||
<section class="panel result" aria-labelledby="result-title">
|
||||
<div class="section-title"><div><span>{mode === 'rating' ? '03' : '02'}</span><h2 id="result-title">분석 결과</h2></div></div>
|
||||
{#if mode === 'rating' && result}<p class="board-guide"><strong>{selectedChart?.title} · {difficulty === 'expert' ? 'Expert' : 'Hard'}</strong> · 종합력 {overallPower.toLocaleString('ko-KR')} · 가창 보너스 10%</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}
|
||||
<div class="order-sections">
|
||||
<section class="order-section">
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
export let eventMode = false;
|
||||
|
||||
type CardRow = (typeof cardSummary.cards)[number];
|
||||
type DifficultyKey = 'hard' | 'expert';
|
||||
import { difficulties, difficultyLabel, isDifficulty, type DifficultyKey } from '$lib/data/difficulty.ts';
|
||||
type CollectionState = Record<string, { owned?: boolean; awakening?: number }>;
|
||||
type MemberBoards = Record<string, string[]>;
|
||||
type BoardCards = Record<string, Record<string, string>>;
|
||||
@@ -370,7 +370,7 @@
|
||||
function applySavedState(state: Partial<SavedState>): void {
|
||||
if (state.schemaVersion !== 1) throw new Error('Unsupported preset');
|
||||
if (typeof state.songId === 'string' && songs.some((song) => song.id === state.songId)) selectedSongId = state.songId;
|
||||
if (state.difficulty === 'hard' || state.difficulty === 'expert') difficulty = state.difficulty;
|
||||
if (isDifficulty(state.difficulty)) difficulty = state.difficulty;
|
||||
fixedCardIds = Array.from({ length: 4 }, (_, index) => cardById.has(state.fixedCardIds?.[index] ?? '') ? state.fixedCardIds![index] : '');
|
||||
candidateAwakening = normalizeAwakening(state.awakening);
|
||||
candidateGenerationBonus = typeof state.candidateGenerationBonus === 'number' && Number.isFinite(state.candidateGenerationBonus)
|
||||
@@ -422,7 +422,7 @@
|
||||
return {
|
||||
schemaVersion: 2,
|
||||
songId: typeof state.songId === 'string' ? state.songId : '',
|
||||
difficulty: state.difficulty === 'hard' ? 'hard' : 'expert',
|
||||
difficulty: isDifficulty(state.difficulty) ? state.difficulty : 'expert',
|
||||
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])),
|
||||
@@ -1140,7 +1140,7 @@
|
||||
<div><span>00</span><h2 id="baseline-title">기준 조합</h2></div>
|
||||
<strong>배치 분석 저장값</strong>
|
||||
</div>
|
||||
<p class="baseline-condition">{baselineChartMetadata?.title} · {baselineState.difficulty === 'expert' ? 'Expert' : 'Hard'}</p>
|
||||
<p class="baseline-condition">{baselineChartMetadata?.title} · {difficultyLabel(baselineState.difficulty)}</p>
|
||||
<div class="baseline-members">
|
||||
{#each baselineResult?.formationOrder ?? baselineState.cardIds as cardId, slot}
|
||||
<div><span>{slot + 1}</span><strong>{baselineCardLabel(cardId)}</strong></div>
|
||||
@@ -1164,7 +1164,7 @@
|
||||
<div class="section-title"><div><span>01</span><h2 id="conditions-title">탐색 조건</h2></div><strong>{ownedCardsOnly ? (eventMode ? '보유 이벤트 후보 대상' : '보유 5성 대상') : (eventMode ? '5성 + 대상 멤버 전 레어리티' : '5성 전체 대상')}</strong></div>
|
||||
<div class="song-fields">
|
||||
<div class="song-picker-field"><SongPicker {charts} {difficulty} bind:value={selectedSongId} on:change={() => result = null} /></div>
|
||||
<label>난이도<select bind:value={difficulty} on:change={() => result = null}><option value="expert">Expert</option><option value="hard">Hard</option></select></label>
|
||||
<label>난이도<select bind:value={difficulty} on:change={() => result = null}>{#each difficulties as value}<option {value}>{difficultyLabel(value)}</option>{/each}</select></label>
|
||||
<div class="chart-meta"><span>길이 {selectedChart?.durationSeconds ?? 0}초</span><span>노트 {selectedChart?.totalNotes?.toLocaleString('ko-KR') ?? 0}개</span><span>점수 계수 · {scoreCalibrationLabel}</span></div>
|
||||
</div>
|
||||
<div class="candidate-fields">
|
||||
|
||||
Reference in New Issue
Block a user