Compare commits
1 Commits
sprint/iss
...
burn/2-177
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d054c20fc9 |
79
js/project_chain.js
Normal file
79
js/project_chain.js
Normal 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]);
|
||||
}
|
||||
}
|
||||
20
tests/project_chain.test.cjs
Normal file
20
tests/project_chain.test.cjs
Normal 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.');
|
||||
Reference in New Issue
Block a user