feat: 토너먼트 조별 전투 로그를 복원한다

예선과 본선 조별 경기의 최신 전투 로그를 저장하고 화면에 여덟 조 모두 표시한다. 결선은 최근 완료 경기와 종료 후 결승 로그를 유지하며 기존 대진 난수 순서를 보존한다.
This commit is contained in:
2026-08-22 04:51:46 +00:00
parent a41eb16bb9
commit e7fed61fb3
9 changed files with 345 additions and 30 deletions
+2
View File
@@ -47,6 +47,8 @@ export interface TournamentMatchEntry {
id: number;
stage: number;
roundIndex: number;
/** Ref fight{group}.txt와 같이 조별전의 최신 로그를 식별합니다. */
groupId?: number;
attackerId: number;
defenderId: number;
winnerId?: number;
+33 -2
View File
@@ -39,6 +39,35 @@ import {
type TournamentPrismaClient,
} from './workerHelpers.js';
const persistLatestGroupFightLogs = async (
store: TournamentStore,
stage: 2 | 4,
outcomes: TournamentMatchOutcome[]
): Promise<void> => {
if (outcomes.length === 0) {
return;
}
const matches = await store.getMatches();
const replacedGroupIds = new Set(outcomes.map((outcome) => outcome.groupId));
const retained = matches.filter(
(match) => match.stage !== stage || match.groupId === undefined || !replacedGroupIds.has(match.groupId)
);
const latest = outcomes.map((outcome): TournamentMatchEntry => ({
id: stage * 100 + outcome.groupId + 1,
stage,
roundIndex: outcome.groupId,
groupId: outcome.groupId,
attackerId: outcome.attackerId,
defenderId: outcome.defenderId,
winnerId: outcome.winnerId,
log: outcome.log,
logEntries: outcome.logEntries,
lastEnergy: outcome.lastEnergy,
}));
await store.setMatches(retained.concat(latest));
};
export const applyBattle = async (
store: TournamentStore,
state: TournamentState,
@@ -222,6 +251,7 @@ export const applyPreBattleStage = async (
outcomes.push(result.outcome);
}
await store.setParticipants(updated);
await persistLatestGroupFightLogs(store, 2, outcomes);
if (outcomes.length > 0) {
await Promise.all(
@@ -366,6 +396,7 @@ export const applyPreBattleStage = async (
outcomes.push(result.outcome);
}
await store.setParticipants(updated);
await persistLatestGroupFightLogs(store, 4, outcomes);
if (outcomes.length > 0) {
await Promise.all(
@@ -417,13 +448,13 @@ export const applyPreBattleStage = async (
if (state.stage === 5) {
const matches = await store.getMatches();
if (matches.length === 0) {
if (!matches.some((match) => match.stage >= 7)) {
const fixedMatches = buildFinal16MatchesFromGroups(participants);
const participantIds = fixedMatches
? fixedMatches.flatMap((entry) => [entry.attackerId, entry.defenderId])
: pickFinalists(state, participants);
const initialMatches = fixedMatches ?? buildInitialMatches(state, baseSeed, participantIds);
await store.setMatches(initialMatches);
await store.setMatches(matches.concat(initialMatches));
}
const nextState: TournamentState = {
...state,
+17 -1
View File
@@ -371,9 +371,14 @@ export const fillParticipants = async (options: {
};
export type TournamentMatchOutcome = {
groupId: number;
attackerId: number;
defenderId: number;
result: 'attacker' | 'defender' | 'draw';
winnerId?: number;
log: string[];
logEntries: NonNullable<TournamentMatchEntry['logEntries']>;
lastEnergy?: NonNullable<TournamentMatchEntry['lastEnergy']>;
};
export const applyGroupMatch = (
@@ -419,10 +424,18 @@ export const applyGroupMatch = (
const glDelta = Math.round((result.totalDamage.defender - result.totalDamage.attacker) / 50);
const lastLogEntry = result.logEntries.at(-1);
const outcome: TournamentMatchOutcome = {
groupId: matchIndex,
attackerId: attacker.id,
defenderId: defender.id,
result: result.draw ? 'draw' : result.winnerId === attacker.id ? 'attacker' : 'defender',
winnerId: result.winnerId ?? undefined,
log: result.log,
logEntries: result.logEntries,
lastEnergy: lastLogEntry
? { attacker: lastLogEntry.attackerEnergy, defender: lastLogEntry.defenderEnergy }
: undefined,
};
return {
@@ -572,7 +585,10 @@ export const buildNextMatches = (stage: number, matches: TournamentMatchEntry[])
throw new Error('다음 라운드를 만들 수 없습니다.');
}
const nextIdBase = matches.reduce((max, entry) => Math.max(max, entry.id), 0) + 1;
// 조별 최신 로그도 matches projection에 함께 보존하지만 결선 match ID는
// 전투 RNG seed의 일부이므로 기존 결선 경기만으로 연속 번호를 계산합니다.
const nextIdBase =
matches.filter((entry) => entry.stage >= 7).reduce((max, entry) => Math.max(max, entry.id), 0) + 1;
const nextStageValue = nextStage(stage);
const result: TournamentMatchEntry[] = [];