fix: 은퇴 명예 기록과 유산 수명주기 정합성을 복원한다

은퇴·사망·통일의 저장 순서와 명예의 전당 및 명장일람 판정을 Ref 흐름에 맞춘다.

유산 행동을 인증된 daemon transaction으로 통합하고 중복 지급·고유 아이템·로그·오류 경계를 회귀 테스트한다.
This commit is contained in:
2026-08-24 03:38:12 +00:00
parent 95cf68dffd
commit 4fc20de8d4
33 changed files with 4160 additions and 1166 deletions
@@ -399,4 +399,153 @@ describe('legacy general turn lifecycle', () => {
}
expect(harness.world.peekDirtyState().lifecycleEvents[0]?.outcome).toBe('retired');
});
it('emits an explicit retirement lifecycle event with the pre-rebirth snapshot', async () => {
const harness = await createTurnTestHarness({
snapshot: makeSnapshot([
makeGeneral({
age: 65,
experience: 1_001,
dedication: 801,
role: {
personality: null,
specialDomestic: null,
specialWar: null,
items: {
horse: 'che_명마_07_백마',
weapon: 'che_무기_07_동추',
book: 'che_서적_07_위료자',
item: 'che_의술_정력견혈산',
},
},
meta: {
killturn: 24,
rank_warnum: 11,
firenum: 9,
inherit_earned: 4_321,
inherit_lived_month: 10,
inherit_active_action: 4,
inheritRandomUnique: 1,
inherit_spent_dyn: 3_000,
dex1: 101,
},
inheritancePoints: { previous: 50 },
}),
]),
state: makeState(),
schedule,
map,
});
harness.reservedTurnStore.getGeneralTurns(1)[0] = { action: 'che_은퇴', args: {} };
harness.reservedTurnStore.getGeneralTurns(1)[1] = { action: 'che_은퇴', args: {} };
await harness.runOneTick();
await harness.runOneTick();
const current = harness.world.getGeneralById(1)!;
const lifecycle = harness.world.peekDirtyState().lifecycleEvents.find((event) => event.outcome === 'retired');
expect(lifecycle).toMatchObject({
outcome: 'retired',
isUnitedAtEvent: 0,
before: {
age: 65,
experience: 1_001,
dedication: 801,
meta: {
rank_warnum: 11,
firenum: 9,
inherit_earned: 4_321,
inherit_lived_month: 12,
inherit_active_action: 4,
inheritRandomUnique: 1,
inherit_spent_dyn: 3_000,
dex1: 101,
},
},
after: {
age: 20,
meta: { inherit_lived_month: 0, inherit_active_action: 0 },
},
});
expect(current).toMatchObject({
age: 20,
experience: 501,
dedication: 401,
inheritancePoints: { previous: 3_050 },
meta: {
rank_warnum: 0,
firenum: 0,
inherit_earned: 0,
inherit_lived_month: 0,
inherit_active_action: 0,
inherit_spent_dyn: -3_000,
dex1: 51,
},
});
expect(current.meta).not.toHaveProperty('inheritRandomUnique');
expect(harness.world.peekDirtyState().inheritancePointAdjustments).toContainEqual({
userId: 'user-1',
key: 'previous',
amount: 3_000,
phase: 'after_lifecycle',
});
expect(harness.world.peekDirtyState().pendingInheritanceLogs).toContainEqual({
userId: 'user-1',
year: 200,
month: 2,
text: '유니크를 얻을 공간이 없어 3000 포인트 반환',
phase: 'after_lifecycle',
});
});
it('refunds a failed pending lottery before automatic retirement and resets the spent rank to zero', async () => {
const harness = await createTurnTestHarness({
snapshot: makeSnapshot([
makeGeneral({
age: 80,
crew: 100,
role: {
personality: null,
specialDomestic: null,
specialWar: null,
items: {
horse: 'che_명마_07_백마',
weapon: 'che_무기_07_동추',
book: 'che_서적_07_위료자',
item: 'che_의술_정력견혈산',
},
},
meta: {
killturn: 24,
inheritRandomUnique: true,
inherit_spent_dyn: 3_000,
inherit_lived_month: 10,
},
inheritancePoints: { previous: 70 },
}),
]),
state: makeState(),
schedule,
map,
});
harness.reservedTurnStore.getGeneralTurns(1)[0] = { action: 'che_훈련', args: {} };
await harness.runOneTick();
const current = harness.world.getGeneralById(1)!;
expect(current).toMatchObject({
age: 20,
inheritancePoints: { previous: 3_070 },
meta: { inherit_spent_dyn: 0, inherit_lived_month: 0 },
});
expect(current.meta).not.toHaveProperty('inheritRandomUnique');
expect(harness.world.peekDirtyState().inheritancePointAdjustments).toContainEqual({
userId: 'user-1',
key: 'previous',
amount: 3_000,
});
const lifecycle = harness.world.peekDirtyState().lifecycleEvents.find((event) => event.outcome === 'retired');
expect(lifecycle?.before.meta).toMatchObject({ inherit_spent_dyn: 0 });
expect(lifecycle?.before.meta).not.toHaveProperty('inheritRandomUnique');
});
});