From b6ef5d211436a80acdef5bc40c2d968d783cc258 Mon Sep 17 00:00:00 2001 From: hided62 Date: Fri, 21 Aug 2026 01:13:55 +0000 Subject: [PATCH] =?UTF-8?q?test:=20=EA=B3=B5=EB=B0=B1=EC=A7=80=20=EC=8B=9C?= =?UTF-8?q?=EB=82=98=EB=A6=AC=EC=98=A4=EC=9D=98=20=EB=B9=84=EA=B8=89=20?= =?UTF-8?q?=ED=99=95=EC=9E=A5=20=EB=B2=94=EC=9C=84=EB=A5=BC=20=EA=B3=A0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref의 시나리오별 전투 특기 아이템 계약과 Core 리소스 합성 범위를 문서화한다. 일반 공백지로 공통 확장이 누출되지 않고 특수 공백지의 개별 목록과 수량이 보존되는지 회귀 테스트한다. --- app/game-engine/test/scenarioLoader.test.ts | 42 +++++++++++++++++++++ docs/architecture/scenario-composition.md | 20 ++++++++++ 2 files changed, 62 insertions(+) diff --git a/app/game-engine/test/scenarioLoader.test.ts b/app/game-engine/test/scenarioLoader.test.ts index 66608a52..53013df5 100644 --- a/app/game-engine/test/scenarioLoader.test.ts +++ b/app/game-engine/test/scenarioLoader.test.ts @@ -5,6 +5,16 @@ import { describe, expect, it } from 'vitest'; import { loadScenarioDefinitionById, resolveScenarioDefaultsPath } from '../src/scenario/scenarioLoader.js'; +type LoadedScenario = Awaited>; + +const readItemSlot = (scenario: LoadedScenario, slot: string): Record => { + const allItems = scenario.config.const.allItems as Record> | undefined; + return allItems?.[slot] ?? {}; +}; + +const readAvailableSpecialWar = (scenario: LoadedScenario): string[] => + (scenario.config.const.availableSpecialWar as string[] | undefined) ?? []; + describe('tracked scenario resources', () => { it('loads every scenario through its composed resource graph', async () => { const scenarioRoot = path.dirname(resolveScenarioDefaultsPath()); @@ -35,4 +45,36 @@ describe('tracked scenario resources', () => { ]); } }); + + it('keeps the buyable war-special pack scoped to each Ref scenario contract', async () => { + const [ordinaryBlank, legacySecretBlank, mirrorBlank, multiUnitBlank, moreEffectBlank, composedAddon] = + await Promise.all( + [0, 902, 910, 912, 913, 2141].map((scenarioId) => loadScenarioDefinitionById(scenarioId)) + ); + + expect( + Object.keys(readItemSlot(ordinaryBlank, 'item')).filter((key) => key.startsWith('event_전투특기_')) + ).toEqual([]); + + const legacySecretItems = readItemSlot(legacySecretBlank, 'item'); + expect(Object.keys(legacySecretItems).filter((key) => key.startsWith('event_전투특기_'))).toHaveLength(19); + expect(legacySecretItems).not.toHaveProperty('event_전투특기_견고'); + expect(readAvailableSpecialWar(legacySecretBlank)).not.toContain('che_견고'); + + const mirrorItems = readItemSlot(mirrorBlank, 'item'); + expect(Object.keys(mirrorItems).filter((key) => key.startsWith('event_전투특기_'))).toHaveLength(19); + expect(mirrorItems).not.toHaveProperty('event_전투특기_척사'); + expect(readAvailableSpecialWar(mirrorBlank)).not.toContain('che_척사'); + + const multiUnitItems = readItemSlot(multiUnitBlank, 'item'); + expect(Object.keys(multiUnitItems).filter((key) => key.startsWith('event_전투특기_'))).toHaveLength(19); + expect(multiUnitItems).not.toHaveProperty('event_전투특기_견고'); + + const moreEffectItems = readItemSlot(moreEffectBlank, 'item'); + const composedAddonItems = readItemSlot(composedAddon, 'item'); + expect(Object.keys(moreEffectItems).filter((key) => key.startsWith('event_전투특기_'))).toHaveLength(20); + expect(Object.keys(composedAddonItems).filter((key) => key.startsWith('event_전투특기_'))).toHaveLength(20); + expect(readItemSlot(moreEffectBlank, 'horse').che_명마_07_백마).toBe(4); + expect(readItemSlot(composedAddon, 'horse').che_명마_07_백마).toBe(2); + }); }); diff --git a/docs/architecture/scenario-composition.md b/docs/architecture/scenario-composition.md index a8840427..093b5fea 100644 --- a/docs/architecture/scenario-composition.md +++ b/docs/architecture/scenario-composition.md @@ -58,6 +58,26 @@ 시나리오 80개 중 70개가 확장을 사용합니다. 구매 가능한 전특·유니크 표를 사용하는 10개 시나리오는 같은 item 확장을 참조합니다. +## 적용 범위와 Ref 차이 + +Ref에는 설치 시 선택한 시나리오에 별도 기능 팩을 덧붙이는 전역 애드온 단계가 +없습니다. 각 `scenario_*.json`이 `const.allItems`와 +`const.availableSpecialWar`를 직접 소유하고, `Scenario::buildConf()`가 그 값을 +`GameConst`에 반영합니다. + +Core의 `extends`는 이 중복 값을 소스에서 재사용하기 위한 합성 기능입니다. +설치 시 임의의 시나리오에 전역으로 적용되는 옵션이 아니며, 해당 +`scenario_*.json`이 확장을 명시한 경우에만 로더와 Gateway 미리보기가 합성합니다. +따라서 일반 공백지 시나리오에는 +`extensions/items/buyable-war-special-uniques.json`이 암묵적으로 적용되지 않습니다. + +공백지 중 `scenario_902`(천지비급), `scenario_910`(거울세계), +`scenario_912`(다병종), `scenario_913`(무한대흥)은 Ref 자체가 전투 특기 아이템 +풀을 직접 정의합니다. 이 네 시나리오는 최신 공통 확장과 항목 또는 유니크 수량이 +서로 달라 직접 정의를 유지합니다. 특히 902·912는 `견고`가 없는 19종, 910은 +`척사`가 없는 19종이며, 913은 20종이지만 일부 유니크 수량이 공통 확장의 2개가 +아닌 4개입니다. 이를 공통 확장으로 바꾸면 Ref 설치 결과가 달라집니다. + ## 검증 확장 파일을 추가하거나 합성 순서를 바꾼 뒤 다음 검사를 실행해 주세요.