feat: streamline search results and open captured analyses
Publish GitHub Pages output / publish (push) Successful in 54s
Publish GitHub Pages output / publish (push) Successful in 54s
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { combinationAnalysisSettings, parseAnalysisTransfer, transferredAnalysisKey, type AnalysisTransfer } from '../models/analysis-transfer.ts';
|
||||
import { calculateExactScore } from './score.ts';
|
||||
import { optimizeExactOrderAndCooldowns } from './exact-optimizer.ts';
|
||||
import type { GrowthSnapshot } from '../models/analysis-history.ts';
|
||||
|
||||
const growth: GrowthSnapshot = { collection: { e: { owned: true, awakening: 5 } },
|
||||
memberBoards: { e: ['G1'] }, boardCards: {}, memoryCount: 30, memberPowerUpPercent: 4.37 };
|
||||
const state = { songId: 'song', difficulty: 'expert' as const, fixedCardIds: ['c', 'd', 'a', 'b'],
|
||||
fixedAwakenings: [5, 3, 4, 4], fixedCooldownReductions: [null, 12, null, 4] as (null | 12 | 4)[],
|
||||
fixedAllBlueNodes: [false, true, false, false], fixedGenerationBonusOverrides: [null, 0, 900, null],
|
||||
awakening: 4, candidateGenerationBonus: 450, songScoreBonusOverride: 10,
|
||||
leaderCharacterId: 'leader', leaderOutfitCardId: 'outfit' };
|
||||
|
||||
test('조합의 표시 순서로 바뀌어도 고정 카드 조건과 자동 후보 보드·보유 개화를 정확히 전달한다', () => {
|
||||
const settings = combinationAnalysisSettings(state, ['a', 'b', 'c', 'd', 'e'], growth);
|
||||
assert.deepEqual(settings.awakenings, [4, 4, 5, 3, 5]);
|
||||
assert.deepEqual(settings.fixedCooldownReductions, [null, 4, null, 12, null]);
|
||||
assert.deepEqual(settings.allBlueNodes, [false, false, false, true, true]);
|
||||
assert.deepEqual(settings.generationBonusOverrides, [900, null, null, 0, 450]);
|
||||
assert.equal(settings.leaderOutfitCardId, 'outfit');
|
||||
assert.equal(settings.songScoreBonusOverride, 10);
|
||||
assert.deepEqual(growth.memberBoards, { e: ['G1'] });
|
||||
});
|
||||
|
||||
test('전달 결과는 재탐색 없이 점수·확률을 유지하고 원래 카드·채보로 시뮬레이션한다', () => {
|
||||
const cards = ['a', 'b', 'c', 'd', 'e'].map((id, i) => ({ id, name: id,
|
||||
active: { cooldownSeconds: 2 + i, durationSeconds: 1, scoreUpPercent: 100, probabilityPercent: 55 },
|
||||
special: { durationSeconds: 1, scoreUpPercent: 100 }, allowedCooldownReductions: [0] as const }));
|
||||
const chart = { songId: 'song', difficulty: 'expert', durationSeconds: 8, scoreRatio: 1,
|
||||
specialStartSeconds: [1, 2, 3, 4, 5], notes: Array.from({ length: 8 }, (_, timeSeconds) => ({timeSeconds, type: 'regular' as const, isFlick: false})) };
|
||||
const result = optimizeExactOrderAndCooldowns(cards, chart, { perfectNoteScore: 100, directSongScoreBonusPercent: 10 });
|
||||
const capture = { settings: combinationAnalysisSettings(state, cards.map(c => c.id), growth), growth,
|
||||
title: 'song', durationSeconds: 8, overallPower: 100, analyzedAt: '2026-09-09T09:00:00Z' };
|
||||
const transfer: AnalysisTransfer = { schemaVersion: 1, capture, result, cards, chart, directSongScoreBonusPercent: 10,
|
||||
achievementProbabilities: { maximum: 0.1, atLeast99Percent: 0.2, atLeast95Percent: 0.4, samples: 25000 } };
|
||||
const loaded = parseAnalysisTransfer(JSON.stringify(transfer));
|
||||
assert.deepEqual(loaded, transfer);
|
||||
const score = calculateExactScore(loaded.result.order.map(id => loaded.cards.find(c => c.id === id)!), loaded.result.cooldownReductions, loaded.chart,
|
||||
{ perfectNoteScore: loaded.result.perfectNoteScore, directSongScoreBonusPercent: loaded.directSongScoreBonusPercent });
|
||||
assert.equal(score.theoreticalMaxScore, result.theoreticalMaxScore);
|
||||
assert.equal(score.expectedScore, result.expectedScore);
|
||||
const originalKey = transferredAnalysisKey(loaded.capture);
|
||||
loaded.capture.growth.memoryCount += 1;
|
||||
assert.notEqual(transferredAnalysisKey(loaded.capture), originalKey);
|
||||
assert.equal(growth.memoryCount, 30);
|
||||
assert.throws(() => parseAnalysisTransfer(JSON.stringify({ ...transfer, cards: [] })));
|
||||
assert.throws(() => parseAnalysisTransfer(JSON.stringify({ ...transfer, chart: { ...chart, songId: 'another song' } })));
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createContext, runInContext } from 'node:vm';
|
||||
import ts from 'typescript';
|
||||
|
||||
const source = readFileSync(new URL('../workers/score-optimizer.worker.ts', import.meta.url), 'utf8');
|
||||
const code = ts.transpileModule(source.replace(/^import .*;\r?\n/gm, '').replace('export {};', ''), {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2022 }
|
||||
}).outputText;
|
||||
|
||||
test('조합 워커는 요청한 결과 수를 점수순으로 반환하고 미지정·잘못된 값은 10개로 처리한다', () => {
|
||||
for (const [limit, expected] of [[undefined, 10], [10, 10], [20, 20], [30, 30], [50, 50], [0, 10]]) {
|
||||
const messages: any[] = [];
|
||||
const context = createContext({
|
||||
self: { postMessage: (message: unknown) => messages.push(message) },
|
||||
countOptimizationCandidates: () => 1,
|
||||
optimizeExactOrderAndCooldowns: (_cards: unknown, _chart: unknown, options: { perfectNoteScore: number }) => ({
|
||||
theoreticalMaxScore: options.perfectNoteScore, expectedScore: options.perfectNoteScore
|
||||
})
|
||||
});
|
||||
runInContext(code, context);
|
||||
context.request = { type: 'optimize-combinations', resultLimit: limit, chart: {},
|
||||
candidates: Array.from({ length: 60 }, (_, i) => ({ cards: [], overallPower: i, perfectNoteScore: i })) };
|
||||
runInContext('self.onmessage({ data: request });', context);
|
||||
const result = messages.at(-1);
|
||||
assert.equal(result.type, 'combination-result');
|
||||
assert.equal(result.rankings.length, expected);
|
||||
assert.deepEqual(Array.from(result.rankings, (row: any) => row.result.theoreticalMaxScore),
|
||||
Array.from({ length: expected! }, (_, i) => 59 - i));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user