fix: 유산 유니크 경매 후보를 종류별로 정렬

Ref의 명마, 무기, 서적, 도구 순서와 종류 내부 선언 순서를 보존한다. 선택 목록은 종류별 optgroup으로 구분하고 API·Chromium 회귀를 추가한다.
This commit is contained in:
2026-08-20 16:16:43 +00:00
parent ce7807bb4f
commit b33b2f454b
4 changed files with 87 additions and 6 deletions
+7 -2
View File
@@ -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<InheritBuffType, string> = {
warAvoidRatio: '회피 확률 증가',
warCriticalRatio: '필살 확률 증가',
@@ -79,7 +82,8 @@ const loadAvailableUniqueItems = async (worldState: WorldStateRow) => {
const loader = new ItemLoader();
const { allItems } = await resolveLegacyCompatibleUniqueConfig(configConst, loader);
const enabledKeys: Array<Parameters<ItemLoader['load']>[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<unknown> } } }) => {
+26
View File
@@ -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({
+23 -3
View File
@@ -6,6 +6,7 @@ import { trpc } from '../utils/trpc';
type InheritStatus = Awaited<ReturnType<typeof trpc.inherit.getStatus.query>>;
type InheritLog = Awaited<ReturnType<typeof trpc.inherit.getLogs.query>>[number];
type JoinConfig = Awaited<ReturnType<typeof trpc.join.getConfig.query>>;
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<UniqueItemSlot, string> = {
horse: '명마',
weapon: '무기',
book: '서적',
item: '도구',
};
const pointHelp: Record<string, string> = {
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(() => {
<label for="specific-unique">유니크 경매</label>
<select id="specific-unique" v-model="uniqueForm.itemId">
<option disabled value="">유니크 선택</option>
<option v-for="item in status.availableUnique" :key="item.key" :value="item.key">
{{ item.name }}
</option>
<optgroup v-for="group in availableUniqueGroups" :key="group.slot" :label="group.label">
<option v-for="item in group.items" :key="item.key" :value="item.key">
{{ item.name }}
</option>
</optgroup>
</select>
</div>
<div class="control-row">