fix: prioritize theoretical max score

This commit is contained in:
dal4segno committed 2026-08-31 04:15:51 +09:00
1 parent a06cacb91c
commit d85a4ddfbf
3 files changed
+65 -9

No files matched your search

+17 -8
View File
@@ -1,5 +1,5 @@
import { effectiveActiveProbability } from './active-probability.ts';
import { calculateExactScore } from './score.ts';
import { calculateExactScore, finalNoteScore } from './score.ts';
import { passiveScoreSupportByCard } from './passive-support.ts';
import { effectiveCooldownSeconds } from './timeline.ts';
import { COOLDOWN_REDUCTIONS } from './types.ts';
@@ -180,7 +180,7 @@ function scoreAssignment(
profiles: ReadonlyMap<string, CandidateProfile>,
base: Float64Array,
directBonus: number
): number {
): { expectedScore: number; theoreticalMaxScore: number } {
const selected = cards.map((card) => {
const reduction = reductions[card.id] ?? 0;
const profile = profiles.get(`${card.id}:${reduction}`);
@@ -189,7 +189,8 @@ function scoreAssignment(
});
const boosts = new Float64Array(cards.length);
const probabilities = new Float64Array(cards.length);
let rawScore = 0;
let expectedScore = 0;
let theoreticalMaxScore = 0;
for (let noteIndex = 0; noteIndex < base.length; noteIndex += 1) {
let count = 0;
@@ -212,9 +213,10 @@ function scoreAssignment(
expectedBoost += previousFailed * probabilities[position] * boosts[position];
previousFailed *= 1 - probabilities[position];
}
rawScore += base[noteIndex] * (1 + expectedBoost);
expectedScore += finalNoteScore(base[noteIndex] * (1 + expectedBoost), directBonus);
theoreticalMaxScore += finalNoteScore(base[noteIndex] * (1 + (count > 0 ? boosts[0] : 0)), directBonus);
}
return Math.floor(Math.floor(rawScore) * (1 + directBonus / 100) + Number.EPSILON * Math.max(1, rawScore) * 4);
return { expectedScore, theoreticalMaxScore };
}
export function optimizeExactOrderAndCooldowns(
@@ -230,7 +232,8 @@ export function optimizeExactOrderAndCooldowns(
const orderCount = cards.reduce((value, _, index) => value * (index + 1), 1);
const totalCandidates = orderCount * reductionAssignments.length;
let evaluatedCandidates = 0;
let bestScore = Number.NEGATIVE_INFINITY;
let bestTheoreticalMaxScore = Number.NEGATIVE_INFINITY;
let bestExpectedScore = Number.NEGATIVE_INFINITY;
let bestOrder: OptimizerCard[] | null = null;
let bestReductions: Record<string, CooldownReduction> | null = null;
let bestReductionTotal = Number.POSITIVE_INFINITY;
@@ -240,8 +243,14 @@ export function optimizeExactOrderAndCooldowns(
for (const reductions of reductionAssignments) {
const score = scoreAssignment(order, reductions, profiles, base, options.directSongScoreBonusPercent ?? 0);
const reductionTotal = Object.values(reductions).reduce<number>((sum, value) => sum + value, 0);
if (score > bestScore || (score === bestScore && reductionTotal < bestReductionTotal)) {
bestScore = score;
const isHigherMaximum = score.theoreticalMaxScore > bestTheoreticalMaxScore;
const isHigherExpectedTie = score.theoreticalMaxScore === bestTheoreticalMaxScore &&
score.expectedScore > bestExpectedScore;
const isCheaperTie = score.theoreticalMaxScore === bestTheoreticalMaxScore &&
score.expectedScore === bestExpectedScore && reductionTotal < bestReductionTotal;
if (isHigherMaximum || isHigherExpectedTie || isCheaperTie) {
bestTheoreticalMaxScore = score.theoreticalMaxScore;
bestExpectedScore = score.expectedScore;
bestOrder = order;
bestReductions = reductions;
bestReductionTotal = reductionTotal;