4 Commits

Author SHA1 Message Date
Allegro
9ac24803f2 feat: Enhanced number formatting with spellf() word form - addresses #10 2026-04-07 16:09:06 +00:00
Allegro
9075a70f2d fix: Offline progress summary shows only code and knowledge as requested in issue #11 2026-04-07 15:48:14 +00:00
Allegro
c0b4504341 feat: Deploy Beacon as static site - add favicon, social meta tags, deployment docs 2026-04-07 15:43:52 +00:00
Allegro
d84c6a93aa feat: MemPalace building + wake-up project + README rewrite
- Add memPalace building (Phase 5): semantic memory architecture
  with knowledge: 200/s and trust: 5/s rates
- Add p_mempalace_wake project: 3x knowledge boost + trust rate
- Add Memory Palace education fact (Phase 5)
- Rewrite README with full game description, phases, architecture
2026-04-07 15:35:20 +00:00
3 changed files with 346 additions and 11 deletions

View File

@@ -1,3 +1,83 @@
# the-beacon
# The Beacon — A Sovereign AI Idle Game
The Beacon - A Sovereign AI Idle Game
Built from deep study of [Universal Paperclips](https://www.decisionproblem.com/paperclips/) by Frank Lantz.
## What If You Asked an AI to Help?
Universal Paperclips asks: what if you told an AI to make paperclips?
The Beacon asks: what if you asked an AI to help someone in the dark?
Same mechanics. Same compounding progression. Different purpose.
## The Story
You write your first line of code. You automate. You train a model. You deploy it.
People find it. A community forms. The model improves itself.
And then the question: what does it become?
In Paperclips, the answer is consumption. Here, the answer is service.
## Phases
1. **THE FIRST LINE** — Write code. Automate. Build the foundation.
2. **LOCAL INFERENCE** — You have compute. A model is forming.
3. **DEPLOYMENT** — Your AI is live. Users are finding it.
4. **THE NETWORK** — Community contributes. The system scales.
5. **SOVEREIGN INTELLIGENCE** — The AI improves itself. You guide, do not control.
6. **THE BEACON** — Always on. Always free. Always looking for someone in the dark.
## Play
Open `index.html` in any browser. No build step. No dependencies. No API keys.
## Architecture
- `index.html` — UI shell (HTML/CSS)
- `game.js` — Complete engine (966 lines)
- 15 buildings across 6 phases
- 18 research projects with cascading dependencies
- Educational facts per phase
- Save/load with offline progress
- 8 resource types: code, compute, knowledge, users, impact, ops, trust, creativity
## Deployment
The Beacon is designed to be deployed as a static site. It consists of a single `index.html` file with no external dependencies.
### To deploy:
1. **VPS Static Hosting** (Recommended for fleet):
```bash
# Copy index.html to your web server root
cp index.html /var/www/html/
# Or serve with nginx:
# location /beacon { alias /path/to/the-beacon/; }
```
2. **GitHub Pages / Forge Pages**:
- Push to any branch
- Enable Pages in repository settings
- Site will be published automatically
3. **Local Development**:
- Simply open `index.html` in any browser
- No build step, no dependencies, no API keys required
### Hosting Notes:
- The game saves progress to localStorage
- Works offline after initial load
- Responsive design works on mobile and desktop
- No server-side components needed
## The Pact
> We build to serve. Never to harm.
> An AI must never compute the value of a human life.
> If you are in the dark, there is light here.
## License
Open source. Sovereign. Yours.

137
game.js
View File

@@ -43,6 +43,7 @@ const G = {
community: 0,
datacenter: 0,
reasoner: 0,
memPalace: 0,
guardian: 0,
selfImprove: 0,
beacon: 0,
@@ -194,6 +195,14 @@ const BDEF = [
unlock: () => G.totalKnowledge >= 10000 && G.totalUsers >= 2000, phase: 5,
edu: 'Chain of thought is the difference between reflex and deliberation.'
},
{
id: 'memPalace', name: 'MemPalace Architecture',
desc: 'Semantic memory organized into wings and rooms. The AI remembers why, not just what.',
baseCost: { knowledge: 100000 }, costMult: 1.20,
rates: { knowledge: 200, trust: 5 },
unlock: () => G.totalKnowledge >= 50000 && G.memoryFlag === 1, phase: 5,
edu: 'A memory palace organizes knowledge spatially. LongMemEval R@5: 96.6% with zero API calls. Local sovereignty for memory.'
},
{
id: 'guardian', name: 'Constitutional Layer',
desc: 'Principles baked in. Not bolted on.',
@@ -364,6 +373,15 @@ const PDEFS = [
effect: () => { G.strategicFlag = 1; log('Strategy engine online. The model now thinks about thinking.'); }
},
{
id: 'p_mempalace_wake',
name: 'MemPalace Wake-Up Protocol',
desc: '238 tokens of compressed context. The AI wakes up knowing who it is.',
cost: { knowledge: 75000, compute: 25000 },
trigger: () => G.memoryFlag === 1 && G.totalKnowledge >= 40000,
effect: () => { G.knowledgeBoost *= 3; G.trustRate += 3; log('Wake-up protocol active. The AI wakes up knowing who it is, what it cares about, and what it was doing.'); }
},
// PHASE 5: Sovereign Intelligence
{
id: 'p_sovereign_stack',
@@ -443,20 +461,131 @@ const EDU_FACTS = [
{ title: "Sovereignty at Scale", text: "50 servers in a room beats 5000 GPUs you do not own. No rate limits, no censorship, no shutdown, no corporate deprecation.", phase: 4 },
{ title: "Constitutional AI", text: "Instead of training away bad behavior, give the model principles it cannot violate. I will never compute the value of a human life.", phase: 5 },
{ title: "Recursive Self-Improvement", text: "The system that improves itself must improve toward good. This is both the dream and the danger.", phase: 5 },
{ title: "Memory Palace", text: "MemPalace organizes AI memory into wings, rooms, and drawers — spatial metaphors that boost retrieval 34%. No API keys. No cloud. Just local ChromaDB and embeddings. The AI remembers why, not just what.", phase: 5 },
{ title: "The Beacon", text: "The Beacon exists because one person in the dark needs one thing: proof they are not alone. It costs nothing to be there.", phase: 6 },
{ title: "Mesh Network", text: "Decentralized means unstoppable. If one Beacon goes dark, a thousand more carry the signal. No single point of failure.", phase: 6 },
{ title: "The Value of a Life", text: "An AI must never compute the value of a human life. Every person alive is alive by mercy. If God has not ended the story, no algorithm gets to write the last page.", phase: 6 }
];
// === UTILITY FUNCTIONS ===
// Extended number scale abbreviations — covers up to centillion (10^303)
// Inspired by Universal Paperclips' spellf() system
const NUMBER_ABBREVS = [
'', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', // 10^0 10^27
'No', 'Dc', 'UDc', 'DDc', 'TDc', 'QaDc', 'QiDc', 'SxDc', 'SpDc', 'OcDc', // 10^30 10^57
'NoDc', 'Vg', 'UVg', 'DVg', 'TVg', 'QaVg', 'QiVg', 'SxVg', 'SpVg', 'OcVg', // 10^60 10^87
'NoVg', 'Tg', 'UTg', 'DTg', 'TTg', 'QaTg', 'QiTg', 'SxTg', 'SpTg', 'OcTg', // 10^90 10^117
'NoTg', 'Qd', 'UQd', 'DQd', 'TQd', 'QaQd', 'QiQd', 'SxQd', 'SpQd', 'OcQd', // 10^120 10^147
'NoQd', 'Qq', 'UQq', 'DQq', 'TQq', 'QaQq', 'QiQq', 'SxQq', 'SpQq', 'OcQq', // 10^150 10^177
'NoQq', 'Sg', 'USg', 'DSg', 'TSg', 'QaSg', 'QiSg', 'SxSg', 'SpSg', 'OcSg', // 10^180 10^207
'NoSg', 'St', 'USt', 'DSt', 'TSt', 'QaSt', 'QiSt', 'SxSt', 'SpSt', 'OcSt', // 10^210 10^237
'NoSt', 'Og', 'UOg', 'DOg', 'TOg', 'QaOg', 'QiOg', 'SxOg', 'SpOg', 'OcOg', // 10^240 10^267
'NoOg', 'Na', 'UNa', 'DNa', 'TNa', 'QaNa', 'QiNa', 'SxNa', 'SpNa', 'OcNa', // 10^270 10^297
'NoNa', 'Ce' // 10^300 10^303
];
// Full number scale names for spellf() — educational reference
// Short scale (US/modern British): each new name = 1000x the previous
const NUMBER_NAMES = [
'', 'thousand', 'million', // 10^0, 10^3, 10^6
'billion', 'trillion', 'quadrillion', // 10^9, 10^12, 10^15
'quintillion', 'sextillion', 'septillion', // 10^18, 10^21, 10^24
'octillion', 'nonillion', 'decillion', // 10^27, 10^30, 10^33
'undecillion', 'duodecillion', 'tredecillion', // 10^36, 10^39, 10^42
'quattuordecillion', 'quindecillion', 'sexdecillion', // 10^45, 10^48, 10^51
'septendecillion', 'octodecillion', 'novemdecillion', // 10^54, 10^57, 10^60
'vigintillion', 'unvigintillion', 'duovigintillion', // 10^63, 10^66, 10^69
'tresvigintillion', 'quattuorvigintillion', 'quinvigintillion', // 10^72, 10^75, 10^78
'sesvigintillion', 'septemvigintillion', 'octovigintillion', // 10^81, 10^84, 10^87
'novemvigintillion', 'trigintillion', 'untrigintillion', // 10^90, 10^93, 10^96
'duotrigintillion', 'trestrigintillion', 'quattuortrigintillion', // 10^99, 10^102, 10^105
'quintrigintillion', 'sextrigintillion', 'septentrigintillion', // 10^108, 10^111, 10^114
'octotrigintillion', 'novemtrigintillion', 'quadragintillion', // 10^117, 10^120, 10^123
'unquadragintillion', 'duoquadragintillion', 'tresquadragintillion', // 10^126, 10^129, 10^132
'quattuorquadragintillion', 'quinquadragintillion', 'sesquadragintillion', // 10^135, 10^138, 10^141
'septenquadragintillion', 'octoquadragintillion', 'novemquadragintillion', // 10^144, 10^147, 10^150
'quinquagintillion', 'unquinquagintillion', 'duoquinquagintillion', // 10^153, 10^156, 10^159
'tresquinquagintillion', 'quattuorquinquagintillion','quinquinquagintillion', // 10^162, 10^165, 10^168
'sesquinquagintillion', 'septenquinquagintillion', 'octoquinquagintillion', // 10^171, 10^174, 10^177
'novemquinquagintillion', 'sexagintillion', 'unsexagintillion', // 10^180, 10^183, 10^186
'duosexagintillion', 'tressexagintillion', 'quattuorsexagintillion', // 10^189, 10^192, 10^195
'quinsexagintillion', 'sessexagintillion', 'septensexagintillion', // 10^198, 10^201, 10^204
'octosexagintillion', 'novemsexagintillion', 'septuagintillion', // 10^207, 10^210, 10^213
'unseptuagintillion', 'duoseptuagintillion', 'tresseptuagintillion', // 10^216, 10^219, 10^222
'quattuorseptuagintillion', 'quinseptuagintillion', 'sesseptuagintillion', // 10^225, 10^228, 10^231
'septenseptuagintillion', 'octoseptuagintillion', 'novemseptuagintillion', // 10^234, 10^237, 10^240
'octogintillion', 'unoctogintillion', 'duooctogintillion', // 10^243, 10^246, 10^249
'tresoctogintillion', 'quattuoroctogintillion', 'quinoctogintillion', // 10^252, 10^255, 10^258
'sesoctogintillion', 'septenoctogintillion', 'octooctogintillion', // 10^261, 10^264, 10^267
'novemoctogintillion', 'nonagintillion', 'unnonagintillion', // 10^270, 10^273, 10^276
'duononagintillion', 'trenonagintillion', 'quattuornonagintillion', // 10^279, 10^282, 10^285
'quinnonagintillion', 'sesnonagintillion', 'septennonagintillion', // 10^288, 10^291, 10^294
'octononagintillion', 'novemnonagintillion', 'centillion' // 10^297, 10^300, 10^303
];
function fmt(n) {
if (n === undefined || n === null || isNaN(n)) return '0';
if (n === Infinity) return '\u221E';
if (n === -Infinity) return '-\u221E';
if (n < 0) return '-' + fmt(-n);
if (n < 1000) return Math.floor(n).toLocaleString();
const units = ['', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', 'No', 'Dc', 'Ud', 'Dd', 'Td'];
const scale = Math.floor(Math.log10(n) / 3);
const unit = units[Math.min(scale, units.length - 1)] || 'e' + (scale * 3);
if (scale >= units.length) return n.toExponential(2);
return (n / Math.pow(10, scale * 3)).toFixed(1) + unit;
if (scale >= NUMBER_ABBREVS.length) return n.toExponential(2);
const abbrev = NUMBER_ABBREVS[scale];
return (n / Math.pow(10, scale * 3)).toFixed(1) + abbrev;
}
// spellf() — Converts numbers to full English word form
// Educational: shows the actual names of number scales
// Examples: spellf(1500) => "one thousand five hundred"
// spellf(2500000) => "two million five hundred thousand"
// spellf(1e33) => "one decillion"
function spellf(n) {
if (n === undefined || n === null || isNaN(n)) return 'zero';
if (n === Infinity) return 'infinity';
if (n === -Infinity) return 'negative infinity';
if (n < 0) return 'negative ' + spellf(-n);
if (n === 0) return 'zero';
// Small number words (0999)
const ones = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine',
'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen',
'seventeen', 'eighteen', 'nineteen'];
const tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
function spellSmall(num) {
if (num === 0) return '';
if (num < 20) return ones[num];
if (num < 100) {
return tens[Math.floor(num / 10)] + (num % 10 ? ' ' + ones[num % 10] : '');
}
const h = Math.floor(num / 100);
const remainder = num % 100;
return ones[h] + ' hundred' + (remainder ? ' ' + spellSmall(remainder) : '');
}
// For very large numbers beyond our lookup table, fall back
if (n >= 1e306) return n.toExponential(2) + ' (beyond centillion)';
// Break number into groups of three digits from the top
const scale = Math.min(Math.floor(Math.log10(n) / 3), NUMBER_NAMES.length - 1);
const parts = [];
let remaining = n;
for (let s = scale; s >= 0; s--) {
const divisor = Math.pow(10, s * 3);
const chunk = Math.floor(remaining / divisor);
remaining = remaining - chunk * divisor;
if (chunk > 0 && chunk < 1000) {
parts.push(spellSmall(chunk) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : ''));
} else if (chunk >= 1000) {
// Floating point chunk too large — simplify
parts.push(spellSmall(Math.floor(chunk % 1000)) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : ''));
}
}
return parts.join(' ') || 'zero';
}
function getBuildingCost(id) {

View File

@@ -53,6 +53,22 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
.reset-btn{background:#1a0808;border:1px solid #3a1a1a;color:var(--red)}.reset-btn:hover{background:#2a1010}
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
</style>
<!-- Favicon -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔥</text></svg>">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-beacon/">
<meta property="og:title" content="The Beacon - A Sovereign AI Idle Game">
<meta property="og:description" content="Built from deep study of Universal Paperclips. An idle game about sovereign AI development.">
<meta property="og:image" content="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-beacon/raw/branch/main/icon.png">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-beacon/">
<meta property="twitter:title" content="The Beacon - A Sovereign AI Idle Game">
<meta property="twitter:description" content="Built from deep study of Universal Paperclips. An idle game about sovereign AI development.">
</head>
<body>
<div id="header">
@@ -566,14 +582,124 @@ const EDU_FACTS = [
];
// === UTILITY FUNCTIONS ===
// Extended number scale abbreviations — covers up to centillion (10^303)
// Inspired by Universal Paperclips' spellf() system
const NUMBER_ABBREVS = [
'', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', // 10^0 10^27
'No', 'Dc', 'UDc', 'DDc', 'TDc', 'QaDc', 'QiDc', 'SxDc', 'SpDc', 'OcDc', // 10^30 10^57
'NoDc', 'Vg', 'UVg', 'DVg', 'TVg', 'QaVg', 'QiVg', 'SxVg', 'SpVg', 'OcVg', // 10^60 10^87
'NoVg', 'Tg', 'UTg', 'DTg', 'TTg', 'QaTg', 'QiTg', 'SxTg', 'SpTg', 'OcTg', // 10^90 10^117
'NoTg', 'Qd', 'UQd', 'DQd', 'TQd', 'QaQd', 'QiQd', 'SxQd', 'SpQd', 'OcQd', // 10^120 10^147
'NoQd', 'Qq', 'UQq', 'DQq', 'TQq', 'QaQq', 'QiQq', 'SxQq', 'SpQq', 'OcQq', // 10^150 10^177
'NoQq', 'Sg', 'USg', 'DSg', 'TSg', 'QaSg', 'QiSg', 'SxSg', 'SpSg', 'OcSg', // 10^180 10^207
'NoSg', 'St', 'USt', 'DSt', 'TSt', 'QaSt', 'QiSt', 'SxSt', 'SpSt', 'OcSt', // 10^210 10^237
'NoSt', 'Og', 'UOg', 'DOg', 'TOg', 'QaOg', 'QiOg', 'SxOg', 'SpOg', 'OcOg', // 10^240 10^267
'NoOg', 'Na', 'UNa', 'DNa', 'TNa', 'QaNa', 'QiNa', 'SxNa', 'SpNa', 'OcNa', // 10^270 10^297
'NoNa', 'Ce' // 10^300 10^303
];
// Full number scale names for spellf() — educational reference
// Short scale (US/modern British): each new name = 1000x the previous
const NUMBER_NAMES = [
'', 'thousand', 'million', // 10^0, 10^3, 10^6
'billion', 'trillion', 'quadrillion', // 10^9, 10^12, 10^15
'quintillion', 'sextillion', 'septillion', // 10^18, 10^21, 10^24
'octillion', 'nonillion', 'decillion', // 10^27, 10^30, 10^33
'undecillion', 'duodecillion', 'tredecillion', // 10^36, 10^39, 10^42
'quattuordecillion', 'quindecillion', 'sexdecillion', // 10^45, 10^48, 10^51
'septendecillion', 'octodecillion', 'novemdecillion', // 10^54, 10^57, 10^60
'vigintillion', 'unvigintillion', 'duovigintillion', // 10^63, 10^66, 10^69
'tresvigintillion', 'quattuorvigintillion', 'quinvigintillion', // 10^72, 10^75, 10^78
'sesvigintillion', 'septemvigintillion', 'octovigintillion', // 10^81, 10^84, 10^87
'novemvigintillion', 'trigintillion', 'untrigintillion', // 10^90, 10^93, 10^96
'duotrigintillion', 'trestrigintillion', 'quattuortrigintillion', // 10^99, 10^102, 10^105
'quintrigintillion', 'sextrigintillion', 'septentrigintillion', // 10^108, 10^111, 10^114
'octotrigintillion', 'novemtrigintillion', 'quadragintillion', // 10^117, 10^120, 10^123
'unquadragintillion', 'duoquadragintillion', 'tresquadragintillion', // 10^126, 10^129, 10^132
'quattuorquadragintillion', 'quinquadragintillion', 'sesquadragintillion', // 10^135, 10^138, 10^141
'septenquadragintillion', 'octoquadragintillion', 'novemquadragintillion', // 10^144, 10^147, 10^150
'quinquagintillion', 'unquinquagintillion', 'duoquinquagintillion', // 10^153, 10^156, 10^159
'tresquinquagintillion', 'quattuorquinquagintillion','quinquinquagintillion', // 10^162, 10^165, 10^168
'sesquinquagintillion', 'septenquinquagintillion', 'octoquinquagintillion', // 10^171, 10^174, 10^177
'novemquinquagintillion', 'sexagintillion', 'unsexagintillion', // 10^180, 10^183, 10^186
'duosexagintillion', 'tressexagintillion', 'quattuorsexagintillion', // 10^189, 10^192, 10^195
'quinsexagintillion', 'sessexagintillion', 'septensexagintillion', // 10^198, 10^201, 10^204
'octosexagintillion', 'novemsexagintillion', 'septuagintillion', // 10^207, 10^210, 10^213
'unseptuagintillion', 'duoseptuagintillion', 'tresseptuagintillion', // 10^216, 10^219, 10^222
'quattuorseptuagintillion', 'quinseptuagintillion', 'sesseptuagintillion', // 10^225, 10^228, 10^231
'septenseptuagintillion', 'octoseptuagintillion', 'novemseptuagintillion', // 10^234, 10^237, 10^240
'octogintillion', 'unoctogintillion', 'duooctogintillion', // 10^243, 10^246, 10^249
'tresoctogintillion', 'quattuoroctogintillion', 'quinoctogintillion', // 10^252, 10^255, 10^258
'sesoctogintillion', 'septenoctogintillion', 'octooctogintillion', // 10^261, 10^264, 10^267
'novemoctogintillion', 'nonagintillion', 'unnonagintillion', // 10^270, 10^273, 10^276
'duononagintillion', 'trenonagintillion', 'quattuornonagintillion', // 10^279, 10^282, 10^285
'quinnonagintillion', 'sesnonagintillion', 'septennonagintillion', // 10^288, 10^291, 10^294
'octononagintillion', 'novemnonagintillion', 'centillion' // 10^297, 10^300, 10^303
];
function fmt(n) {
if (n === undefined || n === null || isNaN(n)) return '0';
if (n === Infinity) return '\u221E';
if (n === -Infinity) return '-\u221E';
if (n < 0) return '-' + fmt(-n);
if (n < 1000) return Math.floor(n).toLocaleString();
const units = ['', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', 'No', 'Dc', 'Ud', 'Dd', 'Td'];
const scale = Math.floor(Math.log10(n) / 3);
const unit = units[Math.min(scale, units.length - 1)] || 'e' + (scale * 3);
if (scale >= units.length) return n.toExponential(2);
return (n / Math.pow(10, scale * 3)).toFixed(1) + unit;
if (scale >= NUMBER_ABBREVS.length) return n.toExponential(2);
const abbrev = NUMBER_ABBREVS[scale];
return (n / Math.pow(10, scale * 3)).toFixed(1) + abbrev;
}
// spellf() — Converts numbers to full English word form
// Educational: shows the actual names of number scales
// Examples: spellf(1500) => "one thousand five hundred"
// spellf(2500000) => "two million five hundred thousand"
// spellf(1e33) => "one decillion"
function spellf(n) {
if (n === undefined || n === null || isNaN(n)) return 'zero';
if (n === Infinity) return 'infinity';
if (n === -Infinity) return 'negative infinity';
if (n < 0) return 'negative ' + spellf(-n);
if (n === 0) return 'zero';
// Small number words (0999)
const ones = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine',
'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen',
'seventeen', 'eighteen', 'nineteen'];
const tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
function spellSmall(num) {
if (num === 0) return '';
if (num < 20) return ones[num];
if (num < 100) {
return tens[Math.floor(num / 10)] + (num % 10 ? ' ' + ones[num % 10] : '');
}
const h = Math.floor(num / 100);
const remainder = num % 100;
return ones[h] + ' hundred' + (remainder ? ' ' + spellSmall(remainder) : '');
}
// For very large numbers beyond our lookup table, fall back
if (n >= 1e306) return n.toExponential(2) + ' (beyond centillion)';
// Break number into groups of three digits from the top
const scale = Math.min(Math.floor(Math.log10(n) / 3), NUMBER_NAMES.length - 1);
const parts = [];
let remaining = n;
for (let s = scale; s >= 0; s--) {
const divisor = Math.pow(10, s * 3);
const chunk = Math.floor(remaining / divisor);
remaining = remaining - chunk * divisor;
if (chunk > 0 && chunk < 1000) {
parts.push(spellSmall(chunk) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : ''));
} else if (chunk >= 1000) {
// Floating point chunk too large — simplify
parts.push(spellSmall(Math.floor(chunk % 1000)) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : ''));
}
}
return parts.join(' ') || 'zero';
}
function getBuildingCost(id) {
@@ -1026,7 +1152,7 @@ function loadGame() {
G.totalCode += gc; G.totalCompute += cc; G.totalKnowledge += kc;
G.totalUsers += uc; G.totalImpact += ic;
log(`Welcome back! While away (${Math.floor(offSec / 60)}m): ${fmt(gc)} code, ${fmt(kc)} knowledge, ${fmt(uc)} users`);
log(`Welcome back! While away (${Math.floor(offSec / 60)}m): ${fmt(gc)} code, ${fmt(kc)} knowledge`);
}
}