feat: show cumulative maximum score probability in timeline tooltip
Publish GitHub Pages output / publish (push) Successful in 56s
Publish GitHub Pages output / publish (push) Successful in 56s
This commit is contained in:
@@ -62,7 +62,8 @@ function prepareModel(
|
||||
cards: readonly OptimizerCard[],
|
||||
reductions: Readonly<Record<string, CooldownReduction>>,
|
||||
chart: ExactSongChart,
|
||||
options: ExactScoreOptions
|
||||
options: ExactScoreOptions,
|
||||
onNote?: (probability: number) => void
|
||||
): PreparedProbabilityModel {
|
||||
const attempts: Attempt[] = [];
|
||||
const attemptIndexes = new Map<string, number>();
|
||||
@@ -70,6 +71,8 @@ function prepareModel(
|
||||
const grouped = new Map<string, { candidates: NoteCandidate[]; scores: number[] }>();
|
||||
const clauses: number[][] = [];
|
||||
let maximumScore = 0;
|
||||
let prefixProbability = 1;
|
||||
let prefixClauses: number[][] = [];
|
||||
|
||||
scoreNotes(chart).forEach((note, noteIndex) => {
|
||||
const candidates: NoteCandidate[] = [];
|
||||
@@ -80,6 +83,8 @@ function prepareModel(
|
||||
const attemptNumber = Math.floor((note.timeSeconds + 1e-9) / interval);
|
||||
if (attemptNumber <= 0) return;
|
||||
const activationTime = attemptNumber * interval;
|
||||
if (options.excludedActiveWindows?.some((window) =>
|
||||
window.cardId === card.id && Math.abs(window.startSeconds - activationTime) < 1e-9)) return;
|
||||
if (note.timeSeconds >= activationTime + card.active.durationSeconds) return;
|
||||
const key = `${card.id}:${attemptNumber}`;
|
||||
let attemptIndex = attemptIndexes.get(key);
|
||||
@@ -110,11 +115,18 @@ function prepareModel(
|
||||
];
|
||||
maximumScore += scores[0];
|
||||
|
||||
if (candidates.length > 0) {
|
||||
clauses.push(candidates
|
||||
// No activation is required when rounding gives the baseline the same score.
|
||||
if (scores[0] > scores[scores.length - 1]) {
|
||||
const clause = candidates
|
||||
.filter((_, index) => scores[index] === scores[0])
|
||||
.map((candidate) => candidate.attemptIndex));
|
||||
.map((candidate) => candidate.attemptIndex);
|
||||
clauses.push(clause);
|
||||
if (onNote && !prefixClauses.some((existing) => existing.every((variable) => clause.includes(variable)))) {
|
||||
prefixClauses = simplifyClauses([...prefixClauses, clause]);
|
||||
prefixProbability = exactClauseProbability(prefixClauses, attempts);
|
||||
}
|
||||
}
|
||||
onNote?.(prefixProbability);
|
||||
|
||||
const signature = candidates.map((candidate) => `${candidate.attemptIndex}:${candidate.boost.toPrecision(15)}`).join('|');
|
||||
const existing = grouped.get(signature);
|
||||
@@ -130,6 +142,18 @@ function prepareModel(
|
||||
};
|
||||
}
|
||||
|
||||
/** Exact cumulative maximum-score probability after each chart note, in note order. */
|
||||
export function calculateMaximumScoreProbabilityTimeline(
|
||||
cards: readonly OptimizerCard[],
|
||||
reductions: Readonly<Record<string, CooldownReduction>>,
|
||||
chart: ExactSongChart,
|
||||
options: ExactScoreOptions
|
||||
): number[] {
|
||||
const probabilities: number[] = [];
|
||||
prepareModel(cards, reductions, chart, options, (probability) => probabilities.push(probability));
|
||||
return probabilities;
|
||||
}
|
||||
|
||||
function simplifyClauses(clauses: readonly (readonly number[])[]): number[][] {
|
||||
const unique = new Map<string, number[]>();
|
||||
for (const clause of clauses) {
|
||||
|
||||
Reference in New Issue
Block a user