From b33b2f454bd368c0c44ad5033254e622209a1d36 Mon Sep 17 00:00:00 2001 From: hided62 Date: Thu, 20 Aug 2026 16:16:43 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9C=A0=EC=82=B0=20=EC=9C=A0=EB=8B=88?= =?UTF-8?q?=ED=81=AC=20=EA=B2=BD=EB=A7=A4=20=ED=9B=84=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=A2=85=EB=A5=98=EB=B3=84=EB=A1=9C=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref의 명마, 무기, 서적, 도구 순서와 종류 내부 선언 순서를 보존한다. 선택 목록은 종류별 optgroup으로 구분하고 API·Chromium 회귀를 추가한다. --- app/game-api/src/router/inherit/index.ts | 9 ++++-- app/game-api/test/inheritRouter.test.ts | 26 +++++++++++++++ app/game-frontend/src/views/InheritView.vue | 26 +++++++++++++-- .../inheritance-management.spec.ts | 32 ++++++++++++++++++- 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/app/game-api/src/router/inherit/index.ts b/app/game-api/src/router/inherit/index.ts index edbc8bbc..955ceb2e 100644 --- a/app/game-api/src/router/inherit/index.ts +++ b/app/game-api/src/router/inherit/index.ts @@ -12,6 +12,7 @@ import { isWarTraitKey, } from '@sammo-ts/logic'; import type { InheritBuffType } from '@sammo-ts/logic'; +import type { ItemSlot } from '@sammo-ts/logic'; import { simpleSerialize } from '@sammo-ts/logic/war/utils.js'; import { resolveLegacyCompatibleUniqueConfig } from '@sammo-ts/logic/rewards/legacyUniqueItemPool.js'; import { @@ -39,6 +40,8 @@ const BUFF_KEYS: InheritBuffType[] = [ 'warMagicTrialProbOppose', ]; +const UNIQUE_ITEM_SLOT_ORDER: readonly ItemSlot[] = ['horse', 'weapon', 'book', 'item']; + const BUFF_LABELS: Record = { warAvoidRatio: '회피 확률 증가', warCriticalRatio: '필살 확률 증가', @@ -79,7 +82,8 @@ const loadAvailableUniqueItems = async (worldState: WorldStateRow) => { const loader = new ItemLoader(); const { allItems } = await resolveLegacyCompatibleUniqueConfig(configConst, loader); const enabledKeys: Array[0]> = []; - for (const entries of Object.values(allItems)) { + for (const slot of UNIQUE_ITEM_SLOT_ORDER) { + const entries = allItems[slot] ?? {}; for (const [key, amount] of Object.entries(asRecord(entries))) { if (asNumber(amount, 0) !== 0 && isItemKey(key)) { enabledKeys.push(key); @@ -94,10 +98,11 @@ const loadAvailableUniqueItems = async (worldState: WorldStateRow) => { name: item.name, rawName: item.rawName, info: item.info ?? '', + slot: item.slot, }; }) ); - return items.sort((left, right) => left.name.localeCompare(right.name, 'ko')); + return items; }; const resolveWorld = async (ctx: { db: { worldState: { findFirst: () => Promise } } }) => { diff --git a/app/game-api/test/inheritRouter.test.ts b/app/game-api/test/inheritRouter.test.ts index ae39c870..72d28db3 100644 --- a/app/game-api/test/inheritRouter.test.ts +++ b/app/game-api/test/inheritRouter.test.ts @@ -218,6 +218,32 @@ describe('inherit router actor and permission boundaries', () => { } ); + it('orders unique auction candidates by Ref slot order and preserves order within each slot', async () => { + const fixture = buildContext({ + configConst: { + allItems: { + item: { che_보물_도기: 1 }, + book: { che_서적_07_논어: 1 }, + weapon: { che_무기_12_칠성검: 1 }, + horse: { + che_명마_07_백마: 1, + che_명마_07_기주마: 1, + }, + }, + }, + }); + + const status = await appRouter.createCaller(fixture.context).inherit.getStatus(); + + expect(status.availableUnique.map(({ key, slot }) => ({ key, slot }))).toEqual([ + { key: 'che_명마_07_백마', slot: 'horse' }, + { key: 'che_명마_07_기주마', slot: 'horse' }, + { key: 'che_무기_12_칠성검', slot: 'weapon' }, + { key: 'che_서적_07_논어', slot: 'book' }, + { key: 'che_보물_도기', slot: 'item' }, + ]); + }); + it('loads the first inheritance-log page without an out-of-range integer cursor', async () => { const createdAt = new Date('2026-07-26T00:00:00Z'); const fixture = buildContext({ diff --git a/app/game-frontend/src/views/InheritView.vue b/app/game-frontend/src/views/InheritView.vue index 4aeb761d..4167d6ac 100644 --- a/app/game-frontend/src/views/InheritView.vue +++ b/app/game-frontend/src/views/InheritView.vue @@ -6,6 +6,7 @@ import { trpc } from '../utils/trpc'; type InheritStatus = Awaited>; type InheritLog = Awaited>[number]; type JoinConfig = Awaited>; +type UniqueItemSlot = InheritStatus['availableUnique'][number]['slot']; type BuffKey = | 'warAvoidRatio' @@ -67,6 +68,14 @@ const pointOrder = [ 'betting', ] as const; +const uniqueItemSlotOrder: readonly UniqueItemSlot[] = ['horse', 'weapon', 'book', 'item']; +const uniqueItemSlotLabels: Record = { + horse: '명마', + weapon: '무기', + book: '서적', + item: '도구', +}; + const pointHelp: Record = { previous: '이전에 물려받은 포인트입니다.', lived_month: '살아남은 기간입니다. (1개월 단위)', @@ -196,6 +205,15 @@ const specialNameMap = computed(() => { const selectedSpecialWarInfo = computed( () => status.value?.availableSpecialWar.find((entry) => entry.key === nextSpecialKey.value)?.info ?? '' ); +const availableUniqueGroups = computed(() => + uniqueItemSlotOrder + .map((slot) => ({ + slot, + label: uniqueItemSlotLabels[slot], + items: status.value?.availableUnique.filter((item) => item.slot === slot) ?? [], + })) + .filter((group) => group.items.length > 0) +); const buffCost = (key: BuffKey, target: number): number => { const points = status.value?.inheritConst.inheritBuffPoints ?? [0, 0, 0, 0, 0, 0]; @@ -518,9 +536,11 @@ onMounted(() => {
diff --git a/tools/frontend-legacy-parity/inheritance-management.spec.ts b/tools/frontend-legacy-parity/inheritance-management.spec.ts index 2546e890..7b50b62a 100644 --- a/tools/frontend-legacy-parity/inheritance-management.spec.ts +++ b/tools/frontend-legacy-parity/inheritance-management.spec.ts @@ -81,17 +81,33 @@ const statusFixture = { resetLevels: { resetSpecialWar: 0, resetTurnTime: 0 }, availableSpecialWar: [{ key: 'che_선봉', name: '선봉', info: '공격에 유리합니다.' }], availableUnique: [ + { + key: 'che_명마_07_백마', + name: '백마(+7)', + rawName: '백마', + info: '기동력을 올려주는 유니크 명마입니다.', + slot: 'horse', + }, { key: 'che_무기_12_칠성검', name: '칠성검(+12)', rawName: '칠성검', info: '무력을 올려주는 유니크 무기입니다.', + slot: 'weapon', }, { key: 'che_서적_07_논어', name: '논어(+7)', rawName: '논어', info: '지력을 올려주는 유니크 서적입니다.', + slot: 'book', + }, + { + key: 'che_보물_도기', + name: '도기', + rawName: '도기', + info: '전투를 돕는 유니크 도구입니다.', + slot: 'item', }, ], availableTargetGenerals: [{ id: 8, name: '조조' }], @@ -197,7 +213,21 @@ test.describe('inheritance management legacy parity', () => { await page.setViewportSize({ width: 1280, height: 900 }); await page.goto(gameUrl); await expect(page.locator('#container')).toBeVisible(); - await expect(page.locator('#specific-unique')).toHaveValue('che_무기_12_칠성검'); + await expect(page.locator('#specific-unique')).toHaveValue('che_명마_07_백마'); + await expect(page.locator('#specific-unique optgroup')).toHaveCount(4); + expect( + await page.locator('#specific-unique optgroup').evaluateAll((groups) => + groups.map((group) => ({ + label: group.getAttribute('label'), + values: [...group.querySelectorAll('option')].map((option) => option.value), + })) + ) + ).toEqual([ + { label: '명마', values: ['che_명마_07_백마'] }, + { label: '무기', values: ['che_무기_12_칠성검'] }, + { label: '서적', values: ['che_서적_07_논어'] }, + { label: '도구', values: ['che_보물_도기'] }, + ]); const desktop = await page.evaluate(() => { const rect = (selector: string) => {