124 lines
7.3 KiB
JavaScript
124 lines
7.3 KiB
JavaScript
// 멀티 엔딩 판별기 (Phase 4)
|
|
// 우선순위: 의회 정치 엔딩 > 내러티브 연쇄 엔딩 > 변주된 붕괴 엔딩 > 기본 붕괴 엔딩
|
|
|
|
// 의회 수장 성향 축 — 상임 이사회 8대 이념(§4.1) 중 기존 자원 태그로 매핑 가능한 3개 축
|
|
export const CHAIRMAN_AXES = ['force', 'wealth', 'surveillance'];
|
|
|
|
// 내러티브 연쇄 엔딩: 4종 오염 카드가 이 순서로(연속일 필요는 없음) 한 번씩 등장해야 완성된다
|
|
const NARRATIVE_CHAIN_SEQUENCE = ['신화', '조직저항', '사회공황', '이사회압박'];
|
|
|
|
export const isNarrativeChainComplete = (sequence) => {
|
|
let i = 0;
|
|
for (const type of sequence) {
|
|
if (type === NARRATIVE_CHAIN_SEQUENCE[i]) i++;
|
|
if (i === NARRATIVE_CHAIN_SEQUENCE.length) return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const PARAM_LABEL = { resistance: '내부 저항', entropy: '엔트로피', panic: '대중 공황' };
|
|
|
|
export const ENDING_CATALOG = [
|
|
{ id: 'bankruptcy', title: '파산 문책 (조용한 해임)',
|
|
desc: '텅 빈 금고와 산더미 같은 미결 청구서 앞에서 이사회는 더 이상 웃지 않았습니다. 세상이 끝나기도 전에, 당신의 자리가 먼저 끝났습니다.',
|
|
reason: '연속된 재정 파탄 경고를 개선하지 못함' },
|
|
|
|
{ id: 'council_victory', title: '의회 정치 엔딩 (그림자 뒤의 승리)',
|
|
desc: '탄핵안은 가결되었지만, 이사회 의장은 이미 당신이 쌓아온 힘 앞에 고개를 숙였습니다. 해임장에 서명하는 손이 떨리고 있었습니다. 자리는 잃었지만, 진짜 권력은 잃지 않았습니다.',
|
|
reason: '탄핵 가결 + 의회 의장 성향 조건 충족' },
|
|
|
|
{ id: 'narrative_chain', title: '내러티브 연쇄 엔딩 (진실의 연쇄)',
|
|
desc: '신화, 조직의 균열, 대중의 광기, 권력의 사슬 — 당신은 이 모든 사건이 하나의 손에서 비롯되었다는 진실을 끝내 마주했습니다. 아무도 믿지 않겠지만, 적어도 당신만은 알고 있습니다.',
|
|
reason: '신화 → 조직저항 → 사회공황 → 이사회압박 순서로 내러티브 사건 수집' },
|
|
|
|
{ id: 'impeachment', title: '정치적 탄핵 (윗선 붕괴)',
|
|
desc: '그림자 위원회가 당신의 권한을 박탈했습니다. 당신의 모든 업적은 지워질 것입니다.',
|
|
reason: '주간 의회에서 적대적 파벌 임원 9명 이상 찬성' },
|
|
|
|
{ id: 'resistance_base', title: '사임 (내부 붕괴)',
|
|
desc: '조직원들의 불만이 폭발하여 폭동이 일어났습니다. 당신은 자리에서 끌려 내려왔습니다.',
|
|
reason: '내부 저항 파라미터 5단계 도달' },
|
|
{ id: 'resistance_coup', title: '군사 쿠데타 (내부 붕괴)',
|
|
desc: '무력을 손에 쥔 자들은 더 이상 결재를 기다리지 않았습니다. 총구가 당신의 사무실을 겨눴습니다.',
|
|
reason: '내부 저항 5단계 + 무력 태그 다량 보유' },
|
|
{ id: 'resistance_flee', title: '야반도주 (내부 붕괴)',
|
|
desc: '텅 빈 급여 계좌를 확인한 요원들이 하나둘 사라졌습니다. 마지막까지 남은 건 당신과 꺼진 사무실 불빛뿐이었습니다.',
|
|
reason: '내부 저항 5단계 + 인력 토큰 심각한 마이너스' },
|
|
|
|
{ id: 'entropy_base', title: '세계 멸망 (외부 붕괴)',
|
|
desc: '장막이 찢어지고 이계의 존재들이 쏟아져 들어옵니다. 우리가 알던 세계는 끝났습니다.',
|
|
reason: '엔트로피 파라미터 5단계 도달' },
|
|
{ id: 'entropy_coverup', title: '은폐된 멸망 (외부 붕괴)',
|
|
desc: '언론과 감시망을 총동원해 진실을 덮었지만, 덮은 것은 진실이 아니라 탈출할 시간이었습니다. 세계는 아무도 모르게 끝났습니다.',
|
|
reason: '엔트로피 5단계 + 감시 태그 다량 보유' },
|
|
{ id: 'entropy_blackout', title: '정보 암흑 (외부 붕괴)',
|
|
desc: '정보망이 완전히 말라붙은 순간, 당신은 세계가 무너지는 소리조차 듣지 못했습니다.',
|
|
reason: '엔트로피 5단계 + 정보 토큰 심각한 마이너스' },
|
|
|
|
{ id: 'panic_base', title: '문명 붕괴 (사회 붕괴)',
|
|
desc: '대중은 광기에 휩싸였고, 문명은 스스로를 파괴했습니다. 통제 불능의 시대가 도래했습니다.',
|
|
reason: '대중 공황 파라미터 5단계 도달' },
|
|
{ id: 'panic_capitalism', title: '자본의 폭주 (사회 붕괴)',
|
|
desc: '재난 앞에서도 시장은 웃었습니다. 부가 쌓일수록 거리는 비어갔고, 마지막엔 돈을 셀 사람조차 남지 않았습니다.',
|
|
reason: '대중 공황 5단계 + 재력 태그 다량 보유' },
|
|
{ id: 'panic_riot', title: '긴축의 폭동 (사회 붕괴)',
|
|
desc: '텅 빈 금고를 본 대중은 더 이상 설명을 기다리지 않았습니다.',
|
|
reason: '대중 공황 5단계 + 예산 토큰 심각한 마이너스' },
|
|
|
|
{ id: 'sudden_collapse', title: '돌이킬 수 없는 순간 (즉각 붕괴)',
|
|
desc: '망설임 없는 결정 한 번이 모든 단계를 건너뛰었습니다. 경고도, 예고도 없이 — 그저 끝났습니다.',
|
|
reason: '단일 결정으로 파라미터가 급격히 5단계에 도달' },
|
|
|
|
{ id: 'term_expired', title: '임기 만료 (조용한 퇴장)',
|
|
desc: '붕괴도, 탄핵도 없었습니다. 그저 라운드가 거듭될수록 결재할 시간이 줄어들었고, 결국 정례회의 상정 건수가 바닥났습니다. 세상은 여전히 위태롭지만, 당신의 임기만은 조용히 끝났습니다.',
|
|
reason: '라운드가 거듭되며 정례회의까지의 결재 건수가 0이 됨' },
|
|
|
|
{ id: 'unidentified', title: '미확인 종말',
|
|
desc: '기록되지 않은 파멸입니다.',
|
|
reason: '알 수 없는 원인' },
|
|
];
|
|
|
|
const byId = Object.fromEntries(ENDING_CATALOG.map((e) => [e.id, e]));
|
|
|
|
export const getEndingById = (id) => byId[id] || byId.unidentified;
|
|
|
|
const VARIANT_MAP = {
|
|
resistance: [
|
|
{ id: 'resistance_coup', condition: (c) => c.tags.force >= 3 },
|
|
{ id: 'resistance_flee', condition: (c) => c.tokens.personnel <= -50 },
|
|
],
|
|
entropy: [
|
|
{ id: 'entropy_coverup', condition: (c) => c.tags.surveillance >= 3 },
|
|
{ id: 'entropy_blackout', condition: (c) => c.tokens.info <= -50 },
|
|
],
|
|
panic: [
|
|
{ id: 'panic_capitalism', condition: (c) => c.tags.wealth >= 3 },
|
|
{ id: 'panic_riot', condition: (c) => c.tokens.budget <= -50 },
|
|
],
|
|
};
|
|
|
|
const BASE_MAP = { resistance: 'resistance_base', entropy: 'entropy_base', panic: 'panic_base' };
|
|
|
|
// ctx: { trigger: 'impeachment' | 'collapse', params, tags, tokens, lastDelta, chairmanAxis, narrativeChainUnlocked }
|
|
export function resolveEnding(ctx) {
|
|
if (ctx.trigger === 'impeachment') {
|
|
if (ctx.chairmanAxis && ctx.tags[ctx.chairmanAxis] >= 5) return byId.council_victory;
|
|
if (ctx.narrativeChainUnlocked) return byId.narrative_chain;
|
|
return byId.impeachment;
|
|
}
|
|
|
|
if (ctx.narrativeChainUnlocked) return byId.narrative_chain;
|
|
|
|
const param = ['resistance', 'entropy', 'panic'].find((p) => ctx.params[p] >= 5);
|
|
if (!param) return byId.unidentified;
|
|
|
|
if (ctx.lastDelta && ctx.lastDelta.param === param && ctx.lastDelta.amount >= 3) {
|
|
return { ...byId.sudden_collapse, reason: `${PARAM_LABEL[param]} 단일 결정으로 급격히 5단계 도달` };
|
|
}
|
|
|
|
const variant = (VARIANT_MAP[param] || []).find((v) => v.condition(ctx));
|
|
if (variant) return byId[variant.id];
|
|
|
|
return byId[BASE_MAP[param]];
|
|
}
|