Compare commits

..

6 Commits

Author SHA1 Message Date
Alexander Whitestone
9106d3f84c beacon: show locked buildings as dimmed previews up to 2 phases ahead
Previously, buildings from later phases were completely invisible until
unlocked. Players had no idea what was coming next. Now buildings up to
2 phases ahead appear as dimmed (25% opacity) locked entries showing:
- Name and lock icon
- Phase number and name
- Description text
- Education tooltip on hover

This gives players a roadmap of what they're building toward and creates
anticipation for future phases. The preview is a non-interactive div
(not a button) so it cannot be clicked.
2026-04-10 06:45:54 -04:00
3f02359748 Merge pull request 'burn: Add MemPalace Archive as late-game building (closes #25)' (#39) from burn/20260410-0423-25-mempalace-building into main
Merge PR #39: burn: Add MemPalace Archive as late-game building (closes #25)
2026-04-10 09:37:15 +00:00
85a146b690 Merge pull request 'burn: add favicon, meta tags, and social sharing cards (closes #13)' (#31) from burn/20260410-0052-13-static-site-meta into main
Merge PR #31: burn: add favicon, meta tags, and social sharing cards (closes #13)
2026-04-10 09:35:58 +00:00
cb2e48bf9a Merge pull request 'beacon: add production breakdown panel' (#42) from feature/production-breakdown into main
Merge PR #42: beacon: add production breakdown panel
2026-04-10 09:35:52 +00:00
Alexander Whitestone
931473e8f8 burn: Add MemPalace Archive as late-game building (closes #25)
- Added memPalace to buildings state object
- Added MemPalace Archive to BDEF with Phase 5 unlock
- Requires MemPalace v3 research project (mempalaceFlag) + 50k total knowledge
- Cost: 500k knowledge, 200k compute, 100 trust (1.25x scaling)
- Rates: +250 knowledge/s, +100 impact/s
- Educational tooltip on Memory Palace technique and LLM vector space analogy
- Building rates auto-applied via existing updateRates() loop
- Save/load handles new field via G.buildings serialization
2026-04-10 04:23:16 -04:00
Alexander Whitestone
612eb1f4d5 burn: add favicon, meta tags, and social sharing cards (closes #13)
- Inline SVG favicon (beacon emoji) — no external file needed
- Open Graph tags for link previews (title, description, type)
- Twitter Card meta for rich social sharing
- Theme-color for mobile browser chrome
- Meta description for search engines
2026-04-10 00:53:03 -04:00
2 changed files with 160 additions and 13 deletions

156
game.js
View File

@@ -58,7 +58,8 @@ const G = {
ezra: 0,
timmy: 0,
fenrir: 0,
bilbo: 0
bilbo: 0,
memPalace: 0
},
// Boost multipliers
@@ -114,6 +115,9 @@ const G = {
comboTimer: 0,
comboDecay: 2.0, // seconds before combo resets
// Bulk buy multiplier (1, 10, or -1 for max)
buyAmount: 1,
// Time tracking
playTime: 0,
startTime: 0
@@ -300,6 +304,14 @@ const BDEF = [
rates: { creativity: 1 },
unlock: () => G.totalUsers >= 100 && G.flags && G.flags.creativity, phase: 4,
edu: 'Bilbo is unpredictable. That is his value and his cost.'
},
{
id: 'memPalace', name: 'MemPalace Archive',
desc: 'Semantic memory. The AI remembers what matters and forgets what does not.',
baseCost: { knowledge: 500000, compute: 200000, trust: 100 }, costMult: 1.25,
rates: { knowledge: 250, impact: 100 },
unlock: () => G.totalKnowledge >= 50000 && G.mempalaceFlag === 1, phase: 5,
edu: 'The Memory Palace technique: attach information to spatial locations. LLMs use vector spaces the same way — semantic proximity = spatial proximity. MemPalace gives sovereign AI persistent, structured recall.'
}
];
@@ -814,6 +826,57 @@ function getBuildingCost(id) {
return cost;
}
function setBuyAmount(amt) {
G.buyAmount = amt;
render();
}
function getMaxBuyable(id) {
const def = BDEF.find(b => b.id === id);
if (!def) return 0;
let count = G.buildings[id] || 0;
let bought = 0;
while (true) {
const cost = {};
for (const [resource, amount] of Object.entries(def.baseCost)) {
cost[resource] = Math.floor(amount * Math.pow(def.costMult, count));
}
let canAfford = true;
for (const [resource, amount] of Object.entries(cost)) {
if ((G[resource] || 0) < amount) { canAfford = false; break; }
}
if (!canAfford) break;
// Spend from temporary copy
for (const [resource, amount] of Object.entries(cost)) {
G[resource] -= amount;
}
count++;
bought++;
}
// Refund: add back what we spent
let count2 = G.buildings[id] || 0;
for (let i = 0; i < bought; i++) {
for (const [resource, amount] of Object.entries(def.baseCost)) {
G[resource] += Math.floor(amount * Math.pow(def.costMult, count2));
}
count2++;
}
return bought;
}
function getBulkCost(id, qty) {
const def = BDEF.find(b => b.id === id);
if (!def || qty <= 0) return {};
const count = G.buildings[id] || 0;
const cost = {};
for (let i = 0; i < qty; i++) {
for (const [resource, amount] of Object.entries(def.baseCost)) {
cost[resource] = (cost[resource] || 0) + Math.floor(amount * Math.pow(def.costMult, count + i));
}
}
return cost;
}
function canAffordBuilding(id) {
const cost = getBuildingCost(id);
for (const [resource, amount] of Object.entries(cost)) {
@@ -1052,15 +1115,31 @@ function checkProjects() {
function buyBuilding(id) {
const def = BDEF.find(b => b.id === id);
if (!def || !def.unlock()) return;
if (def.phase > G.phase + 1) return;
if (!canAffordBuilding(id)) return;
// Determine actual quantity to buy
let qty = G.buyAmount;
if (qty === -1) {
// Max buy
qty = getMaxBuyable(id);
if (qty <= 0) return;
} else {
// Check affordability for fixed qty
const bulkCost = getBulkCost(id, qty);
for (const [resource, amount] of Object.entries(bulkCost)) {
if ((G[resource] || 0) < amount) return;
}
}
spendBuilding(id);
G.buildings[id] = (G.buildings[id] || 0) + 1;
// Spend resources and build
const bulkCost = getBulkCost(id, qty);
for (const [resource, amount] of Object.entries(bulkCost)) {
G[resource] -= amount;
}
G.buildings[id] = (G.buildings[id] || 0) + qty;
updateRates();
log(`Built ${def.name} (total: ${G.buildings[id]})`);
const label = qty > 1 ? `x${qty}` : '';
log(`Built ${def.name} ${label} (total: ${G.buildings[id]})`);
render();
}
@@ -1508,18 +1587,62 @@ function renderBuildings() {
const container = document.getElementById('buildings');
if (!container) return;
let html = '';
// Buy amount selector
let html = '<div style="display:flex;gap:4px;margin-bottom:8px;align-items:center">';
html += '<span style="font-size:9px;color:#666;margin-right:4px">BUY:</span>';
for (const amt of [1, 10, -1]) {
const label = amt === -1 ? 'MAX' : `x${amt}`;
const active = G.buyAmount === amt;
html += `<button onclick="setBuyAmount(${amt})" style="font-size:9px;padding:2px 8px;border:1px solid ${active ? '#4a9eff' : '#333'};background:${active ? '#0a1a30' : 'transparent'};color:${active ? '#4a9eff' : '#666'};border-radius:3px;cursor:pointer;font-family:inherit">${label}</button>`;
}
html += '</div>';
let visibleCount = 0;
for (const def of BDEF) {
if (!def.unlock()) continue;
if (def.phase > G.phase + 1) continue;
const isUnlocked = def.unlock();
const isPreview = !isUnlocked && def.phase <= G.phase + 2;
if (!isUnlocked && !isPreview) continue;
if (def.phase > G.phase + 2) continue;
visibleCount++;
const cost = getBuildingCost(def.id);
const costStr = Object.entries(cost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
const afford = canAffordBuilding(def.id);
const count = G.buildings[def.id] || 0;
// Locked preview: show dimmed with unlock hint
if (!isUnlocked) {
html += `<div class="build-btn" style="opacity:0.25;cursor:default" title="${def.edu || ''}">`;
html += `<span class="b-name" style="color:#555">${def.name}</span>`;
html += `<span class="b-count" style="color:#444">\u{1F512}</span>`;
html += `<span class="b-cost" style="color:#444">Phase ${def.phase}: ${PHASES[def.phase]?.name || '?'}</span>`;
html += `<span class="b-effect" style="color:#444">${def.desc}</span></div>`;
continue;
}
// Calculate bulk cost display
let qty = G.buyAmount;
let afford = false;
let costStr = '';
if (qty === -1) {
const maxQty = getMaxBuyable(def.id);
afford = maxQty > 0;
if (maxQty > 0) {
const bulkCost = getBulkCost(def.id, maxQty);
costStr = Object.entries(bulkCost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
costStr = `x${maxQty}: ${costStr}`;
} else {
const singleCost = getBuildingCost(def.id);
costStr = Object.entries(singleCost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
}
} else {
const bulkCost = getBulkCost(def.id, qty);
afford = true;
for (const [resource, amount] of Object.entries(bulkCost)) {
if ((G[resource] || 0) < amount) { afford = false; break; }
}
costStr = Object.entries(bulkCost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
if (qty > 1) costStr = `x${qty}: ${costStr}`;
}
const rateStr = def.rates ? Object.entries(def.rates).map(([r, v]) => `+${v}/${r}/s`).join(', ') : '';
html += `<button class="build-btn ${afford ? 'can-buy' : ''}" onclick="buyBuilding('${def.id}')" title="${def.edu}">`;
@@ -1842,6 +1965,7 @@ function saveGame() {
lastEventAt: G.lastEventAt || 0,
activeDebuffIds: debuffIds,
totalEventsResolved: G.totalEventsResolved || 0,
buyAmount: G.buyAmount || 1,
savedAt: Date.now()
};
@@ -1933,7 +2057,7 @@ function initGame() {
log('Click WRITE CODE or press SPACE to start.');
log('Build AutoCode for passive production.');
log('Watch for Research Projects to appear.');
log('Keys: SPACE=Code 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust');
log('Keys: SPACE=Code 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust B=Buy x1/x10/MAX');
}
window.addEventListener('load', function () {
@@ -1974,4 +2098,10 @@ window.addEventListener('keydown', function (e) {
if (e.code === 'Digit2') doOps('boost_compute');
if (e.code === 'Digit3') doOps('boost_knowledge');
if (e.code === 'Digit4') doOps('boost_trust');
if (e.code === 'KeyB') {
// Cycle: 1 -> 10 -> MAX -> 1
if (G.buyAmount === 1) setBuyAmount(10);
else if (G.buyAmount === 10) setBuyAmount(-1);
else setBuyAmount(1);
}
});

View File

@@ -3,6 +3,23 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="The Beacon — a sovereign AI idle game. Build an AI from scratch. Write code, train models, save lives.">
<meta name="theme-color" content="#0a0a0a">
<meta name="author" content="Timmy Foundation">
<!-- Open Graph -->
<meta property="og:title" content="The Beacon">
<meta property="og:description" content="A sovereign AI idle game. Build an AI from scratch. Write code, train models, save lives.">
<meta property="og:type" content="website">
<meta property="og:image" content="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🏠</text></svg>">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="The Beacon">
<meta name="twitter:description" content="A sovereign AI idle game. Build an AI from scratch. Write code, train models, save lives.">
<!-- Favicon (inline SVG beacon) -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🏠</text></svg>">
<title>The Beacon - Build Sovereign AI</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}