merge: 최신 main을 가오픈 서버 시계 수정에 통합한다

This commit is contained in:
2026-08-21 16:57:52 +00:00
43 changed files with 1643 additions and 481 deletions
+5 -4
View File
@@ -49,7 +49,6 @@ const zServerAction = z.enum([
'SHUTDOWN',
]);
const TURN_TERM_MINUTES = [1, 2, 5, 10, 20, 30, 60, 120] as const;
const AUTORUN_USER_OPTIONS = ['develop', 'warp', 'recruit', 'recruit_high', 'train', 'battle', 'chief'] as const;
const ADMIN_ROLE_PREFIX = 'admin.';
@@ -430,7 +429,9 @@ const zInstallAutorun = z.object({
options: z.array(z.enum(AUTORUN_USER_OPTIONS)),
});
const isAllowedTurnTerm = (value: number): boolean => TURN_TERM_MINUTES.some((term) => term === value);
// Ref aligns a 12-month game year inside a 24-hour wall-clock day. That makes
// every positive divisor of 120 valid, including 3 and 8 minutes.
const isAllowedTurnTerm = (value: number): boolean => Number.isInteger(value) && value > 0 && 120 % value === 0;
const zRuntimeSettings = z
.object({
@@ -438,7 +439,7 @@ const zRuntimeSettings = z
.number()
.int()
.refine((value) => isAllowedTurnTerm(value), {
message: 'turnTermMinutes must divide 120.',
message: '턴 시간은 120의 약수여야 합니다.',
})
.optional(),
blockGeneralCreate: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
@@ -464,7 +465,7 @@ const zInstallOptions = z.object({
.number()
.int()
.refine((value) => isAllowedTurnTerm(value), {
message: 'turnTermMinutes must divide 120.',
message: '턴 시간은 120의 약수여야 합니다.',
}),
sync: z.boolean(),
fiction: z.number().int().min(0).max(1),
+39 -1
View File
@@ -825,7 +825,7 @@ describe('admin operation API', () => {
{ adminRoles: ['admin.profiles.settings:che:2'], firstUserIsAdmin: false }
);
const resetDefaults = {
turnTermMinutes: 10,
turnTermMinutes: 3,
sync: true,
fiction: 1 as const,
extend: true,
@@ -853,6 +853,44 @@ describe('admin operation API', () => {
).rejects.toBeDefined();
});
it('queues a three-minute reset because it is a valid divisor of 120', async () => {
const harness = await buildCaller(async (input) => ({
id: '66666666-6666-4666-8666-666666666666',
profileName: input.profileName,
type: 'RESET',
status: 'QUEUED',
sourceMode: input.sourceMode,
sourceRef: input.sourceRef,
payload: input.payload ?? {},
requestedBy: input.requestedBy,
createdAt: '2026-08-21T00:00:00.000Z',
updatedAt: '2026-08-21T00:00:00.000Z',
}));
await harness.caller.admin.operations.requestReset({
profileName: 'che:2',
sourceMode: 'COMMIT',
sourceRef: 'HEAD',
install: {
scenarioId: 1010,
turnTermMinutes: 3,
sync: true,
fiction: 1,
extend: true,
blockGeneralCreate: 0,
npcMode: 0,
showImgLevel: 3,
tournamentTrig: true,
joinMode: 'full',
},
});
expect(harness.createdInputs[0]).toMatchObject({
type: 'RESET',
payload: { install: { turnTermMinutes: 3 } },
});
});
it('stores event season zero as the next season number', async () => {
const harness = await buildCaller(
async () => {