diff --git a/app/game-engine/test/monthlyCreateManyNpcAction.test.ts b/app/game-engine/test/monthlyCreateManyNpcAction.test.ts index ceab6680..37a6579a 100644 --- a/app/game-engine/test/monthlyCreateManyNpcAction.test.ts +++ b/app/game-engine/test/monthlyCreateManyNpcAction.test.ts @@ -151,6 +151,25 @@ const buildHarness = ( }; describe('CreateManyNPC monthly action', () => { + it('creates exactly 50 ordinary M generals without fill-count expansion', async () => { + const { world, reservedTurns, handler, environment } = buildHarness(); + + await handler([50, 0], environment, { + id: 1, + targetCode: 'month', + priority: 1, + condition: true, + action: [], + meta: {}, + }); + + const created = world.peekDirtyState().createdGenerals; + expect(created).toHaveLength(50); + expect(created.every((general) => general.npcState === 3 && general.name.startsWith('ⓜ'))).toBe(true); + expect(reservedTurns.peekDirtyState().generalInitializationIds).toHaveLength(50); + expect(world.peekDirtyState().logs.map((log) => log.text)).toContain('장수 50명이 등장하였습니다.'); + }); + it('uses and consumes an available U30 candidate without random-name or random-stat fallback', async () => { const info = { generalName: '풀장수', diff --git a/app/game-engine/test/scenarioLoader.test.ts b/app/game-engine/test/scenarioLoader.test.ts index afb9eaa2..3628fc58 100644 --- a/app/game-engine/test/scenarioLoader.test.ts +++ b/app/game-engine/test/scenarioLoader.test.ts @@ -52,10 +52,38 @@ describe('tracked scenario resources', () => { expect(scenarioIds).toContain(914); expect(scenarioIds).toContain(915); + expect(scenarioIds).toContain(916); const scenarios = await Promise.all(scenarioIds.map((scenarioId) => loadScenarioDefinitionById(scenarioId))); expect(scenarios.every((scenario) => scenario.title.length > 0)).toBe(true); }); + it('keeps scenario 916 equal to ordinary blank land except for its launch modifiers', async () => { + const [ordinaryBlank, dawn] = await Promise.all( + [0, 916].map((scenarioId) => loadScenarioDefinitionById(scenarioId)) + ); + const { uniqueTrialCoef, ...dawnConst } = dawn.config.const; + + expect(dawn.title).toBe('【공백지】 여명'); + expect(uniqueTrialCoef).toBe(2); + expect({ ...dawn.config, const: dawnConst }).toEqual(ordinaryBlank.config); + expect(dawn.history).toEqual(ordinaryBlank.history); + expect(dawn.events[0]).toEqual([ + 'month', + 1000, + ['or', ['Date', '==', null, 12], ['Date', '==', null, 6]], + ['CreateManyNPC', 50, 0], + ['DeleteEvent'], + ]); + expect(dawn.events.slice(1)).toEqual(ordinaryBlank.events.slice(1)); + + expect({ + ...dawn, + title: ordinaryBlank.title, + events: ordinaryBlank.events, + config: ordinaryBlank.config, + }).toEqual(ordinaryBlank); + }); + refSourceIt('preserves the Ref scenario 915 S100 pool and event order exactly', async () => { const referencePath = path.join(resolveRefSourceRoot(), 'hwe', 'scenario', 'scenario_915.json'); const [scenario, referenceSource] = await Promise.all([ diff --git a/app/gateway-api/test/scenarioCatalog.test.ts b/app/gateway-api/test/scenarioCatalog.test.ts index 6e10874a..eefe56be 100644 --- a/app/gateway-api/test/scenarioCatalog.test.ts +++ b/app/gateway-api/test/scenarioCatalog.test.ts @@ -3,6 +3,16 @@ import { describe, expect, it } from 'vitest'; import { listScenarioPreviews, resolveGitCommitSha } from '../src/scenario/scenarioCatalog.js'; describe('scenarioCatalog git ref support', () => { + it('includes the CHE zero-season dawn scenario in the local catalog', async () => { + const previews = await listScenarioPreviews(); + + expect(previews.find((scenario) => scenario.id === 916)).toMatchObject({ + id: 916, + title: '【공백지】 여명', + year: 180, + }); + }); + it('resolves HEAD to a commit hash', async () => { const commitSha = await resolveGitCommitSha('HEAD'); expect(commitSha).toMatch(/^[0-9a-f]{40}$/i); @@ -18,6 +28,11 @@ describe('scenarioCatalog git ref support', () => { expect(previews.every((scenario) => scenario.fiction === null || Number.isInteger(scenario.fiction))).toBe( true ); + expect(previews.find((scenario) => scenario.id === 916)).toMatchObject({ + id: 916, + title: '【공백지】 여명', + year: 180, + }); }); it('rejects without crashing when git cannot be spawned', async () => { diff --git a/app/gateway-frontend/e2e/server-operations.spec.ts b/app/gateway-frontend/e2e/server-operations.spec.ts index f48a20e2..b70f1aac 100644 --- a/app/gateway-frontend/e2e/server-operations.spec.ts +++ b/app/gateway-frontend/e2e/server-operations.spec.ts @@ -125,6 +125,16 @@ const scenarios = [ nations: [], isCurrent: false, }, + { + id: 916, + title: '【공백지】 여명', + year: 180, + npcCount: 0, + npcExCount: 0, + npcNeutralCount: 0, + nations: [], + isCurrent: false, + }, ]; const response = (data: unknown) => ({ result: { data } }); @@ -538,6 +548,7 @@ test('separates branch and commit semantics and submits a reset from the dedicat await expect(page.getByTestId('scenario-select')).toHaveValue('2'); await expect(page.getByTestId('request-reset')).toBeEnabled(); await expect(page.getByTestId('scenario-select').locator('option:checked')).toContainText('현재 시나리오'); + await expect(page.getByTestId('scenario-select').locator('option[value="916"]')).toContainText('【공백지】 여명'); const catalogGeometry = await page.getByTestId('scenario-select').evaluate((select) => { const scenarioSelect = select as HTMLSelectElement; const rect = select.getBoundingClientRect(); @@ -550,7 +561,7 @@ test('separates branch and commit semantics and submits a reset from the dedicat value: scenarioSelect.value, }; }); - expect(catalogGeometry.optionCount).toBe(3); + expect(catalogGeometry.optionCount).toBe(4); expect(catalogGeometry.value).toBe('2'); expect(catalogGeometry.width).toBeGreaterThan(300); await page.screenshot({ path: testInfo.outputPath('current-scenario-catalog.png'), fullPage: true }); diff --git a/resources/scenario/scenario_916.json b/resources/scenario/scenario_916.json new file mode 100644 index 00000000..879514e2 --- /dev/null +++ b/resources/scenario/scenario_916.json @@ -0,0 +1,14 @@ +{ + "title": "【공백지】 여명", + "extends": ["scenario_0.json"], + "const": { + "uniqueTrialCoef": 2 + }, + "events": [ + ["month", 1000, ["or", ["Date", "==", null, 12], ["Date", "==", null, 6]], ["CreateManyNPC", 50, 0], ["DeleteEvent"]], + ["month", 1000, ["Date", "==", 181, 1], ["RaiseNPCNation"], ["DeleteEvent"]], + ["month", 999, ["Date", "==", 181, 1], ["OpenNationBetting", 4, 5000], ["OpenNationBetting", 1, 2000], ["DeleteEvent"]], + ["month", 999, ["and", ["Date", ">=", 183, 1], ["RemainNation", "<=", 8]], ["OpenNationBetting", 1, 1000], ["DeleteEvent"]], + ["destroy_nation", 1000, ["and", ["Date", ">=", 183, 1], ["RemainNation", "==", 1]], ["BlockScoutAction"], ["DeleteEvent"]] + ] +}