From fe45b9b7a5bb4ecbb3747866b5f3b46a364fca95 Mon Sep 17 00:00:00 2001 From: hided62 Date: Sun, 26 Jul 2026 05:49:52 +0000 Subject: [PATCH] fix: restore lint and test baseline --- app/game-api/src/battleSim/worker.ts | 2 +- app/game-api/src/router/diplomacy/index.ts | 16 +-- app/game-api/src/router/general/index.ts | 10 +- app/game-engine/src/turn/ai/generalAi/core.ts | 2 +- .../nation/assignments/troopAssignments.ts | 2 +- app/game-engine/src/turn/incomeHandler.ts | 15 +-- .../src/turn/unificationHandler.ts | 75 ++++++------ .../src/turn/worldCommandHandler.ts | 4 +- .../databaseCommandQueue.integration.test.ts | 3 +- .../test/nationTurnCompatibility.test.ts | 4 +- .../test/npcNationTechResearch.test.ts | 2 +- .../test/npcNationUprisingUnification.test.ts | 2 +- app/game-engine/vitest.config.ts | 2 + .../components/battle/BattleGeneralCard.vue | 37 +----- app/game-frontend/src/utils/formatLog.ts | 7 +- .../src/views/BattleSimulatorView.vue | 36 +++--- app/game-frontend/src/views/DiplomacyView.vue | 114 +++++++++++++++--- .../src/views/NationAffairsView.vue | 90 ++++++++++---- .../src/views/ScoutMessageView.vue | 8 +- packages/logic/src/constraints/helpers.ts | 7 +- packages/logic/src/war/engine.ts | 8 +- .../scenarios/general_commands_new.test.ts | 3 +- 22 files changed, 269 insertions(+), 180 deletions(-) diff --git a/app/game-api/src/battleSim/worker.ts b/app/game-api/src/battleSim/worker.ts index 6829051..920339e 100644 --- a/app/game-api/src/battleSim/worker.ts +++ b/app/game-api/src/battleSim/worker.ts @@ -43,7 +43,7 @@ export const runBattleSimWorker = async (): Promise => { continue; } - let job: BattleSimJob | null = null; + let job: BattleSimJob; try { job = JSON.parse(raw) as BattleSimJob; } catch { diff --git a/app/game-api/src/router/diplomacy/index.ts b/app/game-api/src/router/diplomacy/index.ts index 4854dc0..1c9c907 100644 --- a/app/game-api/src/router/diplomacy/index.ts +++ b/app/game-api/src/router/diplomacy/index.ts @@ -2,14 +2,12 @@ import { TRPCError } from '@trpc/server'; import { z } from 'zod'; import { asRecord } from '@sammo-ts/common'; -import { GamePrisma } from '@sammo-ts/infra'; +import type { GamePrisma } from '@sammo-ts/infra'; import { authedProcedure, router } from '../../trpc.js'; import { getMyGeneral } from '../shared/general.js'; import { assertNationAccess, resolveNationPermission } from '../nation/shared.js'; -const zLetterState = z.enum(['PROPOSED', 'ACTIVATED', 'CANCELLED', 'REPLACED']); - const resolvePermissionLevel = async (ctx: Parameters[0], nationId: number) => { const nation = await ctx.db.nation.findUnique({ where: { id: nationId }, @@ -22,7 +20,7 @@ const resolvePermissionLevel = async (ctx: Parameters[0], n return resolveNationPermission(general, nation.meta, true); }; -const mapLetterState = (state: string): z.infer => { +const mapLetterState = (state: string): 'PROPOSED' | 'ACTIVATED' | 'CANCELLED' | 'REPLACED' => { if (state === 'ACTIVATED') return 'ACTIVATED'; if (state === 'CANCELLED') return 'CANCELLED'; if (state === 'REPLACED') return 'REPLACED'; @@ -153,7 +151,10 @@ export const diplomacyRouter = router({ select: { id: true }, }); if (newer) { - throw new TRPCError({ code: 'BAD_REQUEST', message: '해당 문서에 대한 새로운 문서가 이미 있습니다.' }); + throw new TRPCError({ + code: 'BAD_REQUEST', + message: '해당 문서에 대한 새로운 문서가 이미 있습니다.', + }); } if (prevLetter.state === 'PROPOSED') { @@ -169,7 +170,8 @@ export const diplomacyRouter = router({ }); } - destNationId = prevLetter.srcNationId === me.nationId ? prevLetter.destNationId : prevLetter.srcNationId; + destNationId = + prevLetter.srcNationId === me.nationId ? prevLetter.destNationId : prevLetter.srcNationId; } const nations = await ctx.db.nation.findMany({ @@ -372,4 +374,4 @@ export const diplomacyRouter = router({ }); return { state: 'ACTIVATED' }; }), -}); \ No newline at end of file +}); diff --git a/app/game-api/src/router/general/index.ts b/app/game-api/src/router/general/index.ts index 97a0bf9..7c5328f 100644 --- a/app/game-api/src/router/general/index.ts +++ b/app/game-api/src/router/general/index.ts @@ -2,6 +2,7 @@ import { TRPCError } from '@trpc/server'; import { z } from 'zod'; import { LogCategory, LogScope } from '@sammo-ts/infra'; +import type { GamePrisma } from '@sammo-ts/infra'; import { asRecord } from '@sammo-ts/common'; import { authedProcedure, router } from '../../trpc.js'; @@ -264,9 +265,8 @@ export const generalRouter = router({ const metaRecord = asRecord(general.meta); const prevSettings = asRecord(metaRecord.userSettings); - const prevMyset = typeof prevSettings.myset === 'number' && Number.isFinite(prevSettings.myset) - ? prevSettings.myset - : null; + const prevMyset = + typeof prevSettings.myset === 'number' && Number.isFinite(prevSettings.myset) ? prevSettings.myset : null; const nextSettings = { ...prevSettings, ...input, @@ -281,8 +281,8 @@ export const generalRouter = router({ meta: { ...metaRecord, userSettings: nextSettings, - }, - } as any, + } as GamePrisma.InputJsonValue, + }, }); return { ok: true }; diff --git a/app/game-engine/src/turn/ai/generalAi/core.ts b/app/game-engine/src/turn/ai/generalAi/core.ts index 71db076..bfcc84f 100644 --- a/app/game-engine/src/turn/ai/generalAi/core.ts +++ b/app/game-engine/src/turn/ai/generalAi/core.ts @@ -711,7 +711,7 @@ export class GeneralAI { const leadership = this.general.stats.leadership; const strength = Math.max(this.general.stats.strength, 1); const intel = Math.max(this.general.stats.intelligence, 1); - let genType = 0; + let genType: number; if (strength >= intel) { genType = t무장; diff --git a/app/game-engine/src/turn/ai/generalAi/nation/assignments/troopAssignments.ts b/app/game-engine/src/turn/ai/generalAi/nation/assignments/troopAssignments.ts index 90b00ad..d2a14dd 100644 --- a/app/game-engine/src/turn/ai/generalAi/nation/assignments/troopAssignments.ts +++ b/app/game-engine/src/turn/ai/generalAi/nation/assignments/troopAssignments.ts @@ -38,7 +38,7 @@ export const do부대전방발령 = (ai: GeneralAI) => { const force = ai.nationPolicy.combatForce[leader.id]; let [fromCityId, toCityId] = force; - let targetCityId: number | null = null; + let targetCityId: number | null; if (!ai.warRoute || !ai.warRoute[fromCityId] || ai.warRoute[fromCityId][toCityId] === undefined) { targetCityId = pickRandomCityId(ai, ai.frontCities); } else { diff --git a/app/game-engine/src/turn/incomeHandler.ts b/app/game-engine/src/turn/incomeHandler.ts index 171efe8..ce2cee5 100644 --- a/app/game-engine/src/turn/incomeHandler.ts +++ b/app/game-engine/src/turn/incomeHandler.ts @@ -108,7 +108,7 @@ const applyIncomeOutcome = ( originOutcome: number ): { next: number; ratio: number; realOutcome: number } => { let next = current + income; - let realOutcome = 0; + let realOutcome: number; if (next < baseResource) { realOutcome = 0; next = baseResource; @@ -139,14 +139,11 @@ const processIncomeForNation = ( const trait = traitMap.get(nation.typeCode) ?? null; const incomeContext = buildNationIncomeContext(nation, trait); - let income = 0; - if (type === 'gold') { - income = getGoldIncome(incomeContext, nationCities, officerCounts, nation.capitalCityId ?? 0, nation.level); - } else { - income = - getRiceIncome(incomeContext, nationCities, officerCounts, nation.capitalCityId ?? 0, nation.level) + - getWallIncome(incomeContext, nationCities, officerCounts, nation.capitalCityId ?? 0, nation.level); - } + const income = + type === 'gold' + ? getGoldIncome(incomeContext, nationCities, officerCounts, nation.capitalCityId ?? 0, nation.level) + : getRiceIncome(incomeContext, nationCities, officerCounts, nation.capitalCityId ?? 0, nation.level) + + getWallIncome(incomeContext, nationCities, officerCounts, nation.capitalCityId ?? 0, nation.level); const incomeValue = roundResource(income); const originOutcome = getOutcome(100, nationGenerals); diff --git a/app/game-engine/src/turn/unificationHandler.ts b/app/game-engine/src/turn/unificationHandler.ts index b0c3626..85a6afb 100644 --- a/app/game-engine/src/turn/unificationHandler.ts +++ b/app/game-engine/src/turn/unificationHandler.ts @@ -167,7 +167,8 @@ export const createUnificationHandler = (options: { sabotage, dex, unifier, - unifierAward: general.nationId === winnerNationId && general.officerLevel > 4 ? UNIFIER_POINT : 0, + unifierAward: + general.nationId === winnerNationId && general.officerLevel > 4 ? UNIFIER_POINT : 0, }, }, }); @@ -194,15 +195,11 @@ export const createUnificationHandler = (options: { const meta = asRecord(state.meta); const serverId = - typeof meta.serverId === 'string' && meta.serverId.trim() - ? meta.serverId.trim() - : options.profileName; + typeof meta.serverId === 'string' && meta.serverId.trim() ? meta.serverId.trim() : options.profileName; const season = readMetaNumberOrNull(meta, 'season') ?? 1; const scenario = readMetaNumberOrNull(meta, 'scenarioId') ?? 0; const scenarioName = - typeof asRecord(meta.scenarioMeta).title === 'string' - ? String(asRecord(meta.scenarioMeta).title) - : ''; + typeof asRecord(meta.scenarioMeta).title === 'string' ? String(asRecord(meta.scenarioMeta).title) : ''; const startTime = typeof meta.starttime === 'string' ? meta.starttime : null; const unitedTime = new Date().toISOString(); @@ -307,14 +304,16 @@ export const createUnificationHandler = (options: { }; for (const [typeName, valueType] of hallTypes) { - let value = 0; - if (valueType === 'natural') { - value = typeName === 'experience' ? general.experience : typeName === 'dedication' ? general.dedication : ranks[typeName] ?? 0; - } else if (valueType === 'rank') { - value = ranks[typeName] ?? 0; - } else { - value = calcValues[typeName] ?? 0; - } + const value = + valueType === 'natural' + ? typeName === 'experience' + ? general.experience + : typeName === 'dedication' + ? general.dedication + : (ranks[typeName] ?? 0) + : valueType === 'rank' + ? (ranks[typeName] ?? 0) + : (calcValues[typeName] ?? 0); if ((typeName === 'winrate' || typeName === 'killrate') && warnum < 10) { continue; @@ -391,9 +390,7 @@ export const createUnificationHandler = (options: { const meta = asRecord(state.meta); const serverId = - typeof meta.serverId === 'string' && meta.serverId.trim() - ? meta.serverId.trim() - : options.profileName; + typeof meta.serverId === 'string' && meta.serverId.trim() ? meta.serverId.trim() : options.profileName; const serverName = typeof meta.serverName === 'string' && meta.serverName.trim() ? meta.serverName.trim() @@ -653,30 +650,30 @@ export const createUnificationHandler = (options: { await Promise.all( oldGeneralTargets.map((general) => ((snapshot) => - prisma.oldGeneral.upsert({ - where: { - by_no: { + prisma.oldGeneral.upsert({ + where: { + by_no: { + serverId, + generalNo: general.id, + }, + }, + update: { + owner: general.userId ?? null, + name: general.name, + lastYearMonth: state.currentYear * 100 + state.currentMonth, + turnTime: general.turnTime, + data: snapshot, + }, + create: { serverId, generalNo: general.id, + owner: general.userId ?? null, + name: general.name, + lastYearMonth: state.currentYear * 100 + state.currentMonth, + turnTime: general.turnTime, + data: snapshot, }, - }, - update: { - owner: general.userId ?? null, - name: general.name, - lastYearMonth: state.currentYear * 100 + state.currentMonth, - turnTime: general.turnTime, - data: snapshot, - }, - create: { - serverId, - generalNo: general.id, - owner: general.userId ?? null, - name: general.name, - lastYearMonth: state.currentYear * 100 + state.currentMonth, - turnTime: general.turnTime, - data: snapshot, - }, - }))( { + }))({ ...general, turnTime: general.turnTime.toISOString(), }) diff --git a/app/game-engine/src/turn/worldCommandHandler.ts b/app/game-engine/src/turn/worldCommandHandler.ts index e358955..fd32464 100644 --- a/app/game-engine/src/turn/worldCommandHandler.ts +++ b/app/game-engine/src/turn/worldCommandHandler.ts @@ -315,8 +315,8 @@ async function handleTournamentMatchResult( const attackerG = getRankNumber(attacker, rankKey('g')); const defenderG = getRankNumber(defender, rankKey('g')); - let attackerGDelta = 0; - let defenderGDelta = 0; + let attackerGDelta: number; + let defenderGDelta: number; let attackerW = 0; let attackerD = 0; let attackerL = 0; diff --git a/app/game-engine/test/databaseCommandQueue.integration.test.ts b/app/game-engine/test/databaseCommandQueue.integration.test.ts index 9912dd2..2510111 100644 --- a/app/game-engine/test/databaseCommandQueue.integration.test.ts +++ b/app/game-engine/test/databaseCommandQueue.integration.test.ts @@ -1,5 +1,6 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { createGamePostgresConnector, GamePrisma, type GamePrismaClient } from '@sammo-ts/infra'; +import { createGamePostgresConnector } from '@sammo-ts/infra'; +import type { GamePrisma, GamePrismaClient } from '@sammo-ts/infra'; import { DatabaseTurnDaemonCommandQueue } from '../src/lifecycle/databaseCommandQueue.js'; diff --git a/app/game-engine/test/nationTurnCompatibility.test.ts b/app/game-engine/test/nationTurnCompatibility.test.ts index 5608d86..029e3af 100644 --- a/app/game-engine/test/nationTurnCompatibility.test.ts +++ b/app/game-engine/test/nationTurnCompatibility.test.ts @@ -164,7 +164,7 @@ describe('레거시 사령부 턴 실행 호환성', () => { ); }); - it('MONTH action 전에 전략·외교 제한, 임시 세율, 첩보 기간을 갱신한다', () => { + it('MONTH action 전에 전략·외교 제한, 임시 세율, 첩보 기간을 갱신한다', async () => { const updates: Array<{ id: number; patch: Record }> = []; const nations = [ { @@ -188,7 +188,7 @@ describe('레거시 사령부 턴 실행 호환성', () => { }) as never, }); - handler.beforeMonthChanged?.({} as never); + await handler.beforeMonthChanged?.({} as never); expect(updates).toEqual([ { diff --git a/app/game-engine/test/npcNationTechResearch.test.ts b/app/game-engine/test/npcNationTechResearch.test.ts index aa7fc56..a3fc3c3 100644 --- a/app/game-engine/test/npcNationTechResearch.test.ts +++ b/app/game-engine/test/npcNationTechResearch.test.ts @@ -327,5 +327,5 @@ describe('NPC 기술 연구 장기 시뮬레이션', () => { // Nation awards can occur in the same tick and make the general's net // gold delta smaller than the recruitment price. Exact cost scaling is // covered by the unit-set/action contract tests rather than this smoke. - }, 60000); + }, 300_000); }); diff --git a/app/game-engine/test/npcNationUprisingUnification.test.ts b/app/game-engine/test/npcNationUprisingUnification.test.ts index dea8395..214f681 100644 --- a/app/game-engine/test/npcNationUprisingUnification.test.ts +++ b/app/game-engine/test/npcNationUprisingUnification.test.ts @@ -553,5 +553,5 @@ describe('NPC 건국/통일 장기 시뮬레이션', () => { } throw error; } - }, 180000); + }, 360_000); }); diff --git a/app/game-engine/vitest.config.ts b/app/game-engine/vitest.config.ts index 400c273..83852b4 100644 --- a/app/game-engine/vitest.config.ts +++ b/app/game-engine/vitest.config.ts @@ -13,5 +13,7 @@ export default defineConfig({ environment: 'node', globals: true, include: ['test/**/*.test.ts'], + maxWorkers: 4, + testTimeout: 10_000, }, }); diff --git a/app/game-frontend/src/components/battle/BattleGeneralCard.vue b/app/game-frontend/src/components/battle/BattleGeneralCard.vue index 8f60d54..1d8c95c 100644 --- a/app/game-frontend/src/components/battle/BattleGeneralCard.vue +++ b/app/game-frontend/src/components/battle/BattleGeneralCard.vue @@ -3,13 +3,13 @@ import { computed, ref } from 'vue'; import type { BattleSimOptions, GeneralDraft } from '../../utils/battleSimulatorTypes'; interface Props { - general: GeneralDraft; options: BattleSimOptions; mode: 'attacker' | 'defender'; title: string; } const props = defineProps(); +const general = defineModel('general', { required: true }); const emit = defineEmits<{ (event: 'import'): void; @@ -187,21 +187,11 @@ const officerLevelOptions = [
diff --git a/app/game-frontend/src/utils/formatLog.ts b/app/game-frontend/src/utils/formatLog.ts index 39ac1e5..f23dd9d 100644 --- a/app/game-frontend/src/utils/formatLog.ts +++ b/app/game-frontend/src/utils/formatLog.ts @@ -24,11 +24,10 @@ export const formatLog = (text?: string): string => { return ''; } - let match: RegExpExecArray | null = null; let lastIndex = 0; const result: string[] = []; - while ((match = logRegex.exec(text)) !== null) { + for (let match = logRegex.exec(text); match !== null; match = logRegex.exec(text)) { const partAll = match[0]; const subPart = match[1]; const index = match.index; @@ -40,9 +39,7 @@ export const formatLog = (text?: string): string => { if (subPart === '/') { result.push(''); } else if (subPart.length === 2) { - result.push( - `` - ); + result.push(``); } else { result.push(``); } diff --git a/app/game-frontend/src/views/BattleSimulatorView.vue b/app/game-frontend/src/views/BattleSimulatorView.vue index 9b6dec7..6d91ce9 100644 --- a/app/game-frontend/src/views/BattleSimulatorView.vue +++ b/app/game-frontend/src/views/BattleSimulatorView.vue @@ -542,7 +542,9 @@ const runSimulation = async (action: BattleSimRequestPayload['action']) => { const payload = buildBattlePayload(action); const response = await trpc.battle.simulate.mutate(payload); const result = - 'payload' in response && response.payload ? response.payload : await waitForSimulationResult(response.jobId); + 'payload' in response && response.payload + ? response.payload + : await waitForSimulationResult(response.jobId); if (!result.result) { error.value = result.reason || 'battle_failed'; @@ -882,19 +884,21 @@ const summaryRows = computed(() => { { label: '전투 페이즈', value: formatNumber(battleResult.value.phase) }, { label: '준 피해', - value: battleResult.value.minKilled !== battleResult.value.maxKilled - ? `${formatNumber(battleResult.value.killed)} (${formatNumber(battleResult.value.minKilled)} ~ ${formatNumber( - battleResult.value.maxKilled - )})` - : formatNumber(battleResult.value.killed), + value: + battleResult.value.minKilled !== battleResult.value.maxKilled + ? `${formatNumber(battleResult.value.killed)} (${formatNumber(battleResult.value.minKilled)} ~ ${formatNumber( + battleResult.value.maxKilled + )})` + : formatNumber(battleResult.value.killed), }, { label: '받은 피해', - value: battleResult.value.minDead !== battleResult.value.maxDead - ? `${formatNumber(battleResult.value.dead)} (${formatNumber(battleResult.value.minDead)} ~ ${formatNumber( - battleResult.value.maxDead - )})` - : formatNumber(battleResult.value.dead), + value: + battleResult.value.minDead !== battleResult.value.maxDead + ? `${formatNumber(battleResult.value.dead)} (${formatNumber(battleResult.value.minDead)} ~ ${formatNumber( + battleResult.value.maxDead + )})` + : formatNumber(battleResult.value.dead), }, { label: '출병자 군량 소모', value: formatNumber(battleResult.value.attackerRice) }, { label: '수비자 군량 소모', value: formatNumber(battleResult.value.defenderRice) }, @@ -1028,7 +1032,7 @@ const shouldShowUI = computed(() => !loading.value && !!options.value); !loading.value && !!options.value); !loading.value && !!options.value);
+
@@ -376,7 +416,9 @@ onBeforeUnmount(() => { /> 전쟁 금지 - 잔여 {{ data.warSettingCnt.remain }}회 (월 +{{ data.warSettingCnt.inc }}회) + 잔여 {{ data.warSettingCnt.remain }}회 (월 +{{ data.warSettingCnt.inc }}회)
@@ -574,4 +616,4 @@ onBeforeUnmount(() => { .loading { color: #9aa3b8; } - \ No newline at end of file + diff --git a/app/game-frontend/src/views/ScoutMessageView.vue b/app/game-frontend/src/views/ScoutMessageView.vue index 038604a..1f7fc5b 100644 --- a/app/game-frontend/src/views/ScoutMessageView.vue +++ b/app/game-frontend/src/views/ScoutMessageView.vue @@ -134,7 +134,7 @@ watch( ); onMounted(() => { - loadData(); + void loadData(); }); onBeforeUnmount(() => { @@ -168,7 +168,11 @@ onBeforeUnmount(() => {
-