feat: 오픈 게임 취소와 유산 정산 경로 추가

별도 최고위험 권한으로 실행되는 원자적 취소 작업을 추가한다. 버려진 게임과 장수 기록의 보존·삭제 옵션, 오픈 원금 전액 환급 및 획득 포인트 보전율, 취소 상태와 관리자·과거기록 UI를 함께 반영한다.
This commit is contained in:
2026-08-18 13:31:49 +00:00
parent a7b11811de
commit 383d173790
36 changed files with 1967 additions and 83 deletions
@@ -1,7 +1,10 @@
import type { TurnGeneral } from './types.js';
const DEX_LIMIT = 1_275_975;
interface InheritancePointGeneral {
meta: Record<string, unknown>;
inheritancePoints?: Record<string, number>;
}
const STORED_INHERITANCE_KEYS = [
'lived_month',
'max_domestic_critical',
@@ -31,7 +34,7 @@ const readNumber = (source: Record<string, unknown>, key: string): number => {
return 0;
};
const computeDexPoint = (general: TurnGeneral): number => {
const computeDexPoint = (general: InheritancePointGeneral): number => {
let totalDexterity = 0;
for (let index = 1; index <= 5; index += 1) {
let dexterity = readNumber(general.meta, `dex${index}`);
@@ -44,7 +47,7 @@ const computeDexPoint = (general: TurnGeneral): number => {
return totalDexterity * 0.001;
};
const computeBettingPoint = (general: TurnGeneral): number => {
const computeBettingPoint = (general: InheritancePointGeneral): number => {
const wins = readNumber(general.meta, 'betwin');
const gold = readNumber(general.meta, 'betgold');
const wonGold = readNumber(general.meta, 'betwingold');
@@ -53,7 +56,7 @@ const computeBettingPoint = (general: TurnGeneral): number => {
};
export const computeActiveInheritancePoint = (
general: TurnGeneral,
general: InheritancePointGeneral,
key: MergedInheritanceKey,
storedOverride?: number
): number => {
@@ -245,6 +245,23 @@ const setInheritancePoint = async (db: DatabaseClient, userId: string, value: nu
});
};
const ensureGameInheritanceBaseline = async (
db: DatabaseClient,
worldMeta: Record<string, unknown>,
userId: string,
openingPoint: number
): Promise<void> => {
const serverId = typeof worldMeta.serverId === 'string' ? worldMeta.serverId.trim() : '';
if (!serverId) {
throw new Error('현재 게임의 serverId가 없어 유산 포인트 원금을 기록할 수 없습니다.');
}
await db.gameInheritanceBaseline.upsert({
where: { serverId_userId: { serverId, userId } },
update: {},
create: { serverId, userId, openingPoint, source: 'FIRST_ACTIVITY' },
});
};
const appendInheritanceLog = async (
db: DatabaseClient,
userId: string,
@@ -583,6 +600,7 @@ export const createGeneralFromJoin = async (options: {
const inheritBonus = validateAndNormalizeBonus(input.inheritBonusStat);
const inheritConstants = resolveInheritConstants(worldState);
const worldMeta = asRecord(worldState.meta);
const inheritRequiredPoint = calculateInheritanceCost(input, inheritConstants, inheritBonus);
const currentInheritancePoint = await applyInheritanceUser(
db,
@@ -590,6 +608,7 @@ export const createGeneralFromJoin = async (options: {
worldState.currentYear,
worldState.currentMonth
);
await ensureGameInheritanceBaseline(db, worldMeta, input.userId, currentInheritancePoint);
if (currentInheritancePoint < inheritRequiredPoint) {
fail('BAD_REQUEST', '유산 포인트가 부족합니다. 다시 가입해주세요!');
}
@@ -613,7 +632,6 @@ export const createGeneralFromJoin = async (options: {
buildJoinCreateGeneralSeed(hiddenSeed, input.seedOwnerIdentity, world.dateToGameTick(acceptedAt))
)
);
const worldMeta = asRecord(worldState.meta);
const currentGenius = Math.max(
0,
Math.floor(asNumber(worldMeta.genius, asNumber(configConst.defaultMaxGenius, DEFAULT_MAX_GENIUS)))
@@ -482,7 +482,7 @@ export const persistUnificationFinalization = async (
await transaction.gameHistory.update({
where: { serverId },
data: { winnerNation: input.winnerNationId, date: input.completedAt },
data: { winnerNation: input.winnerNationId, date: input.completedAt, status: 'COMPLETED' },
});
const nationHistoryRows = await transaction.logEntry.findMany({