feat: save analysis results and compare growth snapshots
Publish GitHub Pages output / publish (push) Successful in 46s

This commit is contained in:
2026-09-06 10:39:06 +09:00
parent b484daf956
commit 476a25caef
9 changed files with 510 additions and 8 deletions
+37
View File
@@ -0,0 +1,37 @@
import type { CooldownReduction, ExactOptimizationResult } from '../engine/types.ts';
export type AnalysisSettings = {
songId: string; difficulty: 'hard' | 'expert'; cardIds: string[]; awakenings: number[];
fixedCooldownReductions?: (CooldownReduction | null)[];
allBlueNodes?: boolean[]; generationBonusOverrides?: (number | null)[];
songScoreBonusOverride?: number | null; leaderCharacterId: string; leaderOutfitCardId: string;
};
export type GrowthSnapshot = {
collection: Record<string, { owned?: boolean; awakening?: number }>;
memberBoards: Record<string, string[]>;
boardCards: Record<string, Record<string, string>>;
memoryCount: number; memberPowerUpPercent: number;
};
export type StoredResult = Omit<ExactOptimizationResult, 'notes' | 'scoreBySecond'>;
export type AnalysisCapture = {
settings: AnalysisSettings; growth: GrowthSnapshot; title: string; durationSeconds: number;
overallPower: number; analyzedAt: string;
};
export type AnalysisRecord = Omit<AnalysisCapture, 'growth'> & {
id: string; name: string; snapshotId: string; result: StoredResult;
};
export function compactResult(result: ExactOptimizationResult): StoredResult {
const { notes, scoreBySecond, ...stored } = result;
return structuredClone(stored);
}
// Object insertion order and board selection order must not create duplicate snapshots.
export function snapshotKey(value: unknown): string {
if (Array.isArray(value)) return JSON.stringify(value.map((item) => snapshotKey(item)).sort());
if (value && typeof value === 'object') return JSON.stringify(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, snapshotKey(item)]));
return JSON.stringify(value) ?? 'null';
}
export function currentGrowthSettings(settings: AnalysisSettings, growth: GrowthSnapshot): AnalysisSettings {
const missing = settings.cardIds.filter((id) => !growth.collection[id]?.owned);
if (missing.length) throw new Error('현재 보유 데이터에 없는 편성 카드가 있습니다. 보유 카드와 개화를 등록한 후 비교해 주세요.');
return { ...structuredClone(settings), awakenings: settings.cardIds.map((id) => growth.collection[id]?.awakening ?? 0) };
}