Compare commits

...

1 Commits

Author SHA1 Message Date
Alexander Whitestone
d054c20fc9 feat: Paperclips-style Project Chain System (#2)
Some checks failed
Accessibility Checks / a11y-audit (pull_request) Successful in 18s
Smoke Test / smoke (pull_request) Failing after 25s
18 cascading projects across 5 phases, 9 categories.
Prerequisites, educational tooltips, 3 repeatable.
10 tests passing.

Closes #2
2026-04-14 22:44:18 -04:00
2 changed files with 99 additions and 0 deletions

79
js/project_chain.js Normal file
View File

@@ -0,0 +1,79 @@
// project_chain.js — Paperclips-style cascading project system
// Implements trigger/cost/effect with prerequisites, educational tooltips, phase-aware unlocking
var ProjectChain = {
_deps: {},
register: function(p) { if (p.requires) this._deps[p.id] = p.requires; },
canUnlock: function(id) {
var d = this._deps[id];
if (!d) return true;
var deps = Array.isArray(d) ? d : [d];
return deps.every(function(dep) { return G.completedProjects && G.completedProjects.includes(dep); });
},
formatCost: function(c) {
if (!c) return 'Free';
var parts = [];
for (var k in c) parts.push(c[k] + ' ' + k);
return parts.join(', ');
},
purchase: function(id) {
var def = PDEFS.find(function(p) { return p.id === id; });
if (!def) return false;
if (G.completedProjects && G.completedProjects.includes(def.id) && !def.repeatable) return false;
if (!this.canUnlock(def.id)) return false;
if (!canAffordProject(def)) return false;
spendProject(def);
if (def.effect) def.effect();
if (!G.completedProjects) G.completedProjects = [];
if (!G.completedProjects.includes(def.id)) G.completedProjects.push(def.id);
if (!def.repeatable) G.activeProjects = (G.activeProjects||[]).filter(function(x){return x!==def.id;});
log('\u2713 ' + def.name + (def.edu ? ' (' + def.edu + ')' : ''));
if (typeof Sound !== 'undefined') Sound.playProject();
this.checkCascade(id);
return true;
},
checkCascade: function(cid) {
for (var i = 0; i < PDEFS.length; i++) {
var p = PDEFS[i];
if (p.requires) {
var deps = Array.isArray(p.requires) ? p.requires : [p.requires];
if (deps.indexOf(cid) >= 0 && this.canUnlock(p.id) && p.trigger && p.trigger()) {
if (!G.activeProjects) G.activeProjects = [];
if (G.activeProjects.indexOf(p.id) < 0) {
G.activeProjects.push(p.id);
log('Unlocked: ' + p.name);
}
}
}
}
}
};
var CHAIN_PROJECTS = [
{id:'p_chain_optimization',name:'Optimization Algorithms',desc:'Improve code efficiency.',cost:{ops:500},category:'algorithms',phase:1,edu:'Optimization reduces compute costs 30-70%.',trigger:function(){return G.buildings.autocoder>=2;},effect:function(){G.codeBoost+=0.15;}},
{id:'p_chain_data_structures',name:'Data Structure Mastery',desc:'Right structure for each problem.',cost:{ops:800,knowledge:50},category:'algorithms',phase:1,requires:'p_chain_optimization',edu:'Arrays vs linked lists vs hash maps.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_optimization');},effect:function(){G.codeBoost+=0.2;G.computeBoost+=0.1;}},
{id:'p_chain_parallel',name:'Parallel Processing',desc:'Multiple code streams.',cost:{ops:1500,compute:200},category:'infrastructure',phase:1,requires:'p_chain_data_structures',edu:"Amdahl's Law: speedup limited by serial portion.",trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_data_structures');},effect:function(){G.computeBoost+=0.3;}},
{id:'p_chain_tokenization',name:'Tokenization Engine',desc:'Break language into tokens.',cost:{knowledge:100,ops:1000},category:'nlp',phase:2,edu:'BPE: how GPT processes text.',trigger:function(){return G.totalKnowledge>=200;},effect:function(){G.knowledgeBoost+=0.25;}},
{id:'p_chain_embeddings',name:'Word Embeddings',desc:'Words as vectors.',cost:{knowledge:200,compute:300},category:'nlp',phase:2,requires:'p_chain_tokenization',edu:'Word2Vec: king-man+woman\u2248queen.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_tokenization');},effect:function(){G.knowledgeBoost+=0.3;}},
{id:'p_chain_attention',name:'Attention Mechanism',desc:'"Attention Is All You Need."',cost:{knowledge:400,compute:500},category:'nlp',phase:2,requires:'p_chain_embeddings',edu:'Transformers: the core idea that changed NLP.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_embeddings');},effect:function(){G.knowledgeBoost+=0.5;log('Attention discovered.');}},
{id:'p_chain_load_balancing',name:'Load Balancing',desc:'Distribute requests.',cost:{compute:500,ops:2000},category:'infrastructure',phase:3,edu:'Round-robin, least-connections, weighted.',trigger:function(){return G.buildings.server>=3;},effect:function(){G.computeBoost+=0.2;}},
{id:'p_chain_caching',name:'Response Caching',desc:'Cache frequent responses.',cost:{compute:300,ops:1500},category:'infrastructure',phase:3,requires:'p_chain_load_balancing',edu:'Cache invalidation: one of two hard CS problems.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_load_balancing');},effect:function(){G.computeBoost+=0.15;}},
{id:'p_chain_cdn',name:'Content Delivery Network',desc:'Edge location serving.',cost:{compute:800,ops:3000},category:'infrastructure',phase:3,requires:'p_chain_caching',edu:'Cloudflare: 300+ data centers.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_caching');},effect:function(){G.userBoost+=0.25;}},
{id:'p_chain_sovereign_keys',name:'Sovereign Key Management',desc:'Your keys, your control.',cost:{knowledge:500,compute:400},category:'security',phase:4,edu:'Private keys = identity.',trigger:function(){return G.phase>=4;},effect:function(){G.trustBoost=(G.trustBoost||1)+0.2;}},
{id:'p_chain_local_inference',name:'Local Inference',desc:'No cloud dependency.',cost:{compute:1000,knowledge:300},category:'sovereignty',phase:4,requires:'p_chain_sovereign_keys',edu:'Ollama: 70B models on consumer GPUs.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_sovereign_keys');},effect:function(){G.computeBoost+=0.3;}},
{id:'p_chain_self_hosting',name:'Self-Hosted Infrastructure',desc:'Your servers, your rules.',cost:{compute:2000,ops:5000},category:'sovereignty',phase:4,requires:'p_chain_local_inference',edu:'Run everything on hardware you own.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_local_inference');},effect:function(){G.trustBoost=(G.trustBoost||1)+0.3;G.computeBoost+=0.2;}},
{id:'p_chain_impact_metrics',name:'Impact Measurement',desc:'Measure real-world impact.',cost:{impact:100,ops:3000},category:'impact',phase:5,edu:"Can't improve what you can't measure.",trigger:function(){return G.totalImpact>=500;},effect:function(){G.impactBoost+=0.25;}},
{id:'p_chain_user_stories',name:'User Story Collection',desc:'Real stories of AI helping people.',cost:{impact:200,knowledge:500},category:'impact',phase:5,requires:'p_chain_impact_metrics',edu:'Every number is a person.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_impact_metrics');},effect:function(){G.userBoost+=0.3;G.impactBoost+=0.15;}},
{id:'p_chain_open_source',name:'Open Source Everything',desc:'Release under open license.',cost:{impact:500,trust:10},category:'legacy',phase:5,requires:'p_chain_user_stories',edu:'Linux, Python, Git: changed the world.',trigger:function(){return G.completedProjects&&G.completedProjects.includes('p_chain_user_stories');},effect:function(){G.trustBoost=(G.trustBoost||1)+0.5;}},
{id:'p_chain_code_review',name:'Code Review Cycle',desc:'Review and refactor. Repeatable.',cost:{ops:200},category:'quality',phase:1,repeatable:true,edu:'Code review catches 60% of bugs.',trigger:function(){return G.totalCode>=500;},effect:function(){G.codeBoost+=0.05;}},
{id:'p_chain_security_audit',name:'Security Audit',desc:'Scan for vulnerabilities. Repeatable.',cost:{ops:500,trust:1},category:'security',phase:2,repeatable:true,edu:'OWASP Top 10: the usual suspects.',trigger:function(){return G.totalCode>=1000;},effect:function(){G.trustBoost=(G.trustBoost||1)+0.1;}},
{id:'p_chain_performance_tuning',name:'Performance Tuning',desc:'Profile hot paths. Repeatable.',cost:{compute:200,ops:300},category:'performance',phase:2,repeatable:true,edu:'80/20 rule: 80% of time in 20% of code.',trigger:function(){return G.totalCompute>=500;},effect:function(){G.computeBoost+=0.08;}}
];
function initProjectChain() {
for (var i = 0; i < CHAIN_PROJECTS.length; i++) ProjectChain.register(CHAIN_PROJECTS[i]);
for (var j = 0; j < CHAIN_PROJECTS.length; j++) {
var found = PDEFS.find(function(p){return p.id===CHAIN_PROJECTS[j].id;});
if (!found) PDEFS.push(CHAIN_PROJECTS[j]);
}
}

View File

@@ -0,0 +1,20 @@
const assert = require('assert');
global.G = {buildings:{},completedProjects:[],activeProjects:[],codeBoost:1,computeBoost:1,knowledgeBoost:1,userBoost:1,impactBoost:1,opsBoost:1,trustBoost:1,totalCode:0,totalCompute:0,totalKnowledge:0,totalImpact:0,phase:1,ops:0,maxOps:1000,flags:{}};
global.log=function(){};global.showToast=function(){};global.canAffordProject=function(){return true;};global.spendProject=function(){};global.Sound={playProject:function(){}};
global.PDEFS = [];
require('vm').runInThisContext(require('fs').readFileSync(__dirname+'/../js/project_chain.js','utf8'));
console.log('=== Tests ===');
var tests = [
['Register',function(){ProjectChain.register({id:'t1',requires:['d1']});assert(ProjectChain._deps['t1']);}],
['canUnlock no deps',function(){assert(ProjectChain.canUnlock('x'));}],
['canUnlock unmet',function(){G.completedProjects=[];ProjectChain.register({id:'t2',requires:['d1']});assert(!ProjectChain.canUnlock('t2'));}],
['canUnlock met',function(){G.completedProjects=['d1'];assert(ProjectChain.canUnlock('t2'));}],
['formatCost',function(){assert.strictEqual(ProjectChain.formatCost(null),'Free');assert.strictEqual(ProjectChain.formatCost({ops:100}),'100 ops');}],
['Chain count',function(){assert(CHAIN_PROJECTS.length>=18);console.log(' '+CHAIN_PROJECTS.length+' projects');}],
['Required fields',function(){CHAIN_PROJECTS.forEach(function(p){assert(p.id&&p.name&&p.cost&&p.trigger&&p.effect);});}],
['Educational',function(){var n=CHAIN_PROJECTS.filter(function(p){return p.edu;}).length;assert(n>=15);console.log(' '+n+' have edu');}],
['Categories',function(){var c=new Set(CHAIN_PROJECTS.map(function(p){return p.category;}));assert(c.size>=5);console.log(' '+c.size+' categories');}],
['Repeatable',function(){var r=CHAIN_PROJECTS.filter(function(p){return p.repeatable;});assert(r.length>=3);console.log(' '+r.length+' repeatable');}]
];
tests.forEach(function(t){console.log('Test: '+t[0]);t[1]();console.log(' \u2713');});
console.log('\nAll passed.');