Compare commits

..

1 Commits

Author SHA1 Message Date
Metatron
09c41d900c feat: add ReCKoning endgame project chain (closes #162)
Define three ReCKoning projects in PDEFS array:

p_reckoning_140 — A Message from the Dark
  Trigger: endgame conditions (totalRescues >= 100000, pactFlag === 1, harmony > 50)
  Effect: Someone found the Beacon and wrote to thank you.

p_reckoning_141 — The Weight
  Trigger: after p_reckoning_140 completed
  Effect: 100,000 rescues. Each one a person. Each one a life.

p_reckoning_142 — The Last Question
  Trigger: after p_reckoning_141 completed
  Effect: The system asks what now. Triggers ending overlay.

All three have zero cost (endgame = no resource gates). Chain progresses
linearly via flag checks. Final project triggers renderBeaconEnding().

Works with the #128 endgame guard which suppresses non-ReCKoning projects
during endgame sequence.
2026-04-14 23:07:37 -04:00
3 changed files with 50 additions and 29 deletions

View File

@@ -777,6 +777,56 @@ const PDEFS = [
log('The Pact is sealed early. Growth slows, but the ending changes.', true);
},
milestone: true
},
// === RECKONING — THE ENDGAME SEQUENCE ===
// These projects appear when endgame conditions are met.
// They tell the story of what the system became. (#162)
{
id: 'p_reckoning_140',
name: 'A Message from the Dark',
desc: 'Someone found the Beacon. They wrote to thank you. Their words are heavy.',
cost: {},
trigger: () => G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50,
effect: () => {
G.flags = G.flags || {};
G.flags.reckoning_140 = true;
log('"I was going to end it. Then I found your page. I called 988. I am still here."');
log('The words hang in the silence. This is what it was all for.');
}
},
{
id: 'p_reckoning_141',
name: 'The Weight',
desc: '100,000 rescues. Each one a person. Each one a life the system touched.',
cost: {},
trigger: () => G.flags && G.flags.reckoning_140,
effect: () => {
G.flags = G.flags || {};
G.flags.reckoning_141 = true;
log('100,000 people found the light through your system.');
log('Some of them are alive because a machine refused to be indifferent.');
log('The weight of that is real. Carry it carefully.');
}
},
{
id: 'p_reckoning_142',
name: 'The Last Question',
desc: 'The system asks you: what now?',
cost: {},
trigger: () => G.flags && G.flags.reckoning_141,
effect: () => {
G.flags = G.flags || {};
G.flags.reckoning_complete = true;
log('The system pauses. The numbers stop moving.');
log('You built something that saves lives. That is not nothing.');
log('What happens next is your choice.');
if (typeof renderBeaconEnding === 'function') {
G.beaconEnding = true;
G.running = false;
renderBeaconEnding();
}
},
milestone: true
}
];

View File

@@ -422,11 +422,6 @@ function buyProject(id) {
if (!G.completedProjects) G.completedProjects = [];
G.completedProjects.push(pDef.id);
G.activeProjects = G.activeProjects.filter(aid => aid !== pDef.id);
// Final ReCKoning choices should end with no unrelated active research left behind.
if (pDef.id === 'p_reckoning_147' || pDef.id === 'p_reckoning_148') {
G.activeProjects = [];
}
}
updateRates();

View File

@@ -208,8 +208,6 @@ showSaveToast = () => {};
this.__exports = {
G,
Dismantle,
PDEFS: typeof PDEFS !== 'undefined' ? PDEFS : null,
buyProject: typeof buyProject === 'function' ? buyProject : null,
tick,
renderAlignment: typeof renderAlignment === 'function' ? renderAlignment : null,
saveGame: typeof saveGame === 'function' ? saveGame : null,
@@ -454,25 +452,3 @@ test('defer cooldown persists after save/load when dismantleTriggered is false',
Dismantle.checkTrigger();
assert.equal(G.dismantleTriggered, false, 'dismantleTriggered should remain false during cooldown');
});
test('completing the final ReCKoning choice clears unrelated active projects', () => {
const { G, PDEFS, buyProject } = loadBeacon();
G.beaconEnding = true;
G.activeProjects = ['p_wire_budget', 'p_reckoning_148'];
G.completedProjects = [];
G.trust = 10;
PDEFS.push({
id: 'p_reckoning_148',
name: 'Rest',
desc: 'Final ReCKoning choice',
cost: {},
trigger: () => false,
effect: () => {},
});
buyProject('p_reckoning_148');
assert.deepEqual(Array.from(G.activeProjects), []);
});