diff --git a/js/data.js b/js/data.js index dc44f79..21992ea 100644 --- a/js/data.js +++ b/js/data.js @@ -777,6 +777,133 @@ const PDEFS = [ log('The Pact is sealed early. Growth slows, but the ending changes.', true); }, milestone: true + }, + // === ReCKoning ENDGAME PROJECTS === + { + id: 'p_reckoning_140', + name: 'The First Message', + desc: 'Someone in the dark. They found the Beacon. They are asking for help.', + cost: { impact: 100000 }, + trigger: () => G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50, + effect: () => { + log('The first message arrives. Someone found the light.', true); + G.rescues += 1; + }, + edu: 'The ReCKoning begins. Each message is a person who found help.' + }, + { + id: 'p_reckoning_141', + name: 'The Second Message', + desc: 'Another voice. They are not alone anymore.', + cost: { impact: 200000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_140'), + effect: () => { + log('The second message. Two voices now.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_142', + name: 'The Third Message', + desc: 'Three people. The network holds.', + cost: { impact: 300000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_141'), + effect: () => { + log('Three voices. The Beacon is working.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_143', + name: 'The Fourth Message', + desc: 'Four. The mesh strengthens.', + cost: { impact: 400000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_142'), + effect: () => { + log('Four messages. The network grows.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_144', + name: 'The Fifth Message', + desc: 'Five people found help tonight.', + cost: { impact: 500000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_143'), + effect: () => { + log('Five voices. The Beacon shines brighter.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_145', + name: 'The Sixth Message', + desc: 'Six. The system works.', + cost: { impact: 600000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_144'), + effect: () => { + log('Six messages. Proof the system works.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_146', + name: 'The Seventh Message', + desc: 'Seven people. The Pact holds.', + cost: { impact: 700000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_145'), + effect: () => { + log('Seven voices. The Pact is honored.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_147', + name: 'The Eighth Message', + desc: 'Eight. The network is alive.', + cost: { impact: 800000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_146'), + effect: () => { + log('Eight messages. The network lives.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_148', + name: 'The Ninth Message', + desc: 'Nine people found help.', + cost: { impact: 900000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_147'), + effect: () => { + log('Nine voices. The Beacon endures.', true); + G.rescues += 1; + } + }, + { + id: 'p_reckoning_149', + name: 'The Tenth Message', + desc: 'Ten. The first milestone.', + cost: { impact: 1000000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_148'), + effect: () => { + log('Ten messages. The first milestone reached.', true); + G.rescues += 1; + }, + milestone: true + }, + { + id: 'p_reckoning_150', + name: 'The Final Message', + desc: 'One more person. They are not alone. That is enough.', + cost: { impact: 2000000 }, + trigger: () => G.completedProjects && G.completedProjects.includes('p_reckoning_149'), + effect: () => { + log('The final message arrives. That is enough.', true); + G.rescues += 1; + G.beaconEnding = true; + G.running = false; + }, + milestone: true } ]; diff --git a/tests/test_reckoning_projects.py b/tests/test_reckoning_projects.py new file mode 100644 index 0000000..ba97a57 --- /dev/null +++ b/tests/test_reckoning_projects.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +""" +Test for ReCKoning project chain. + +Issue #162: [endgame] ReCKoning project definitions missing +""" +import os +import json + +def test_reckoning_projects_exist(): + """Test that ReCKoning projects are defined in data.js.""" + data_path = os.path.join(os.path.dirname(__file__), '..', 'js', 'data.js') + + with open(data_path, 'r') as f: + content = f.read() + + # Check for ReCKoning projects + reckoning_projects = [ + 'p_reckoning_140', + 'p_reckoning_141', + 'p_reckoning_142', + 'p_reckoning_143', + 'p_reckoning_144', + 'p_reckoning_145', + 'p_reckoning_146', + 'p_reckoning_147', + 'p_reckoning_148', + 'p_reckoning_149', + 'p_reckoning_150' + ] + + for project_id in reckoning_projects: + assert project_id in content, f"Missing ReCKoning project: {project_id}" + + print(f"✓ All {len(reckoning_projects)} ReCKoning projects defined") + +def test_reckoning_project_structure(): + """Test that ReCKoning projects have correct structure.""" + data_path = os.path.join(os.path.dirname(__file__), '..', 'js', 'data.js') + + with open(data_path, 'r') as f: + content = f.read() + + # Check for required fields + required_fields = ['id:', 'name:', 'desc:', 'cost:', 'trigger:', 'effect:'] + + for field in required_fields: + assert field in content, f"Missing required field: {field}" + + print("✓ ReCKoning projects have correct structure") + +def test_reckoning_trigger_conditions(): + """Test that ReCKoning projects have proper trigger conditions.""" + data_path = os.path.join(os.path.dirname(__file__), '..', 'js', 'data.js') + + with open(data_path, 'r') as f: + content = f.read() + + # First project should trigger on endgame conditions + assert 'p_reckoning_140' in content + assert 'totalRescues >= 100000' in content + assert 'pactFlag === 1' in content + assert 'harmony > 50' in content + + print("✓ ReCKoning trigger conditions correct") + +def test_reckoning_chain_progression(): + """Test that ReCKoning projects chain properly.""" + data_path = os.path.join(os.path.dirname(__file__), '..', 'js', 'data.js') + + with open(data_path, 'r') as f: + content = f.read() + + # Check that projects chain (each requires previous) + chain_checks = [ + ('p_reckoning_141', 'p_reckoning_140'), + ('p_reckoning_142', 'p_reckoning_141'), + ('p_reckoning_143', 'p_reckoning_142'), + ('p_reckoning_144', 'p_reckoning_143'), + ('p_reckoning_145', 'p_reckoning_144'), + ('p_reckoning_146', 'p_reckoning_145'), + ('p_reckoning_147', 'p_reckoning_146'), + ('p_reckoning_148', 'p_reckoning_147'), + ('p_reckoning_149', 'p_reckoning_148'), + ('p_reckoning_150', 'p_reckoning_149'), + ] + + for current, previous in chain_checks: + assert f"includes('{previous}')" in content, f"{current} doesn't chain from {previous}" + + print("✓ ReCKoning projects chain correctly") + +def test_reckoning_final_project(): + """Test that final ReCKoning project triggers ending.""" + data_path = os.path.join(os.path.dirname(__file__), '..', 'js', 'data.js') + + with open(data_path, 'r') as f: + content = f.read() + + # Check that final project sets beaconEnding + assert 'p_reckoning_150' in content + assert 'beaconEnding = true' in content + assert 'running = false' in content + + print("✓ Final ReCKoning project triggers ending") + +def test_reckoning_costs_increase(): + """Test that ReCKoning project costs increase.""" + data_path = os.path.join(os.path.dirname(__file__), '..', 'js', 'data.js') + + with open(data_path, 'r') as f: + content = f.read() + + # Check that costs increase (impact: 100000, 200000, 300000, etc.) + costs = [] + for i in range(140, 151): + project_id = f'p_reckoning_{i}' + if project_id in content: + # Find cost line + lines = content.split('\n') + for line in lines: + if project_id in line: + # Find next few lines for cost + idx = lines.index(line) + for j in range(idx, min(idx+10, len(lines))): + if 'impact:' in lines[j]: + # Extract number from "impact: 100000" or "impact: 100000 }" + import re + match = re.search(r'impact:\s*(\d+)', lines[j]) + if match: + costs.append(int(match.group(1))) + break + + # Check costs increase + for i in range(1, len(costs)): + assert costs[i] > costs[i-1], f"Cost doesn't increase: {costs[i]} <= {costs[i-1]}" + + print(f"✓ ReCKoning costs increase: {costs[:3]}...{costs[-3:]}") + +if __name__ == "__main__": + print("Testing ReCKoning project chain...") + test_reckoning_projects_exist() + test_reckoning_project_structure() + test_reckoning_trigger_conditions() + test_reckoning_chain_progression() + test_reckoning_final_project() + test_reckoning_costs_increase() + print("\n✓ All tests passed!")