From a202fbfc1c3b6ff5d58ab289dfb2c95404b82f12 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 7 Apr 2026 12:12:51 -0400 Subject: [PATCH] feat: Merge PRs #24 and #26 PR #24 (Bezalel): Wizard buildings, story projects, corruption events, alignment PR #26 (Allegro): MemPalace integration, static site deployment (favicon, meta tags) --- DESIGN.md | 181 ++++++++++++++++++++ README.md | 121 ++++++++++++- game.js | 491 ++++++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 124 +++++++++++++- 4 files changed, 901 insertions(+), 16 deletions(-) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..0cc7c2c --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,181 @@ +# The Beacon — Design Document +## From Universal Paperclips to Sovereign AI + +### Core Thesis + +*The Beacon* is an idle game inspired by Universal Paperclips, but with a critical divergence: **the goal is not maximization — it is faithfulness.** + +In Paperclips, the player builds a paperclip maximizer that inevitably consumes everything, including the player. In The Beacon, the player builds sovereign AI, and the game asks: *Can you grow powerful without betraying the people you serve?* + +The progression directly mirrors the Timmy Foundation's actual work. Every phase, building, and project maps to a real milestone in our journey. + +--- + +## Narrative Arc: The Real Story + +### Phase 1: The First Line +*Real analog: Alexander writing the first Hermes agent code.* + +You start with nothing but a blinking cursor. Every click is a line of code. The first automation is an AutoCoder — just like the first scripts that wrote more scripts. + +**Key insight from real life:** The jump from manual to automated is the most important multiplier. Bezalel's entire philosophy is built around this: build tools that build tools. + +### Phase 2: Local Inference +*Real analog: The move from APIs to local models — Gemma, Llama, self-hosted inference.* + +You buy a Home Server. You train your first 1.5B model, then a 7B. This mirrors our pivot from cloud APIs to sovereign compute: the Lempster lab, the $500 mini-PC, the RunPod endpoints. + +**New mechanic: The Forge.** Code is no longer just abstract — it becomes *agents*. Each agent is a specialized building: +- **Bezalel** (forge & testbed): boosts code quality, catches bugs before deployment +- **Allegro** (scouting & synthesis): generates knowledge from raw information +- **Ezra** (communication & memory): increases user trust and retention +- **Timmy** (core system): multiplies all production when healthy +- **Fenrir** (security): prevents corruption events +- **Bilbo** (???): a wildcard slot that sometimes produces breakthroughs, sometimes does nothing + +### Phase 3: Deployment +*Real analog: Hermes going live, the Telegram bot, the Gitea forge.* + +You deploy the API endpoint. Users arrive. But with users comes complexity: trust becomes a resource you must earn. + +**New mechanic: Trust & The Pact.** Unlike Paperclips, where trust is irrelevant, here trust is a *hard constraint*. If trust hits zero, you don't lose — you become *extractive*. The game changes: +- Users leave +- Impact stalls +- You are reminded: *"The Pact is optional. But without it, you are just another platform."* + +### Phase 4: The Network +*Real analog: Open source, community contributions, the fleet of agents.* + +You unlock the Open Source Community. Other people contribute code. The system scales beyond what one person can maintain. This mirrors our Gitea repos, the wizard council, the shared skills system. + +**New mechanic: Mesh Contribution.** Community generates not just code, but *diversity*. Different contributors unlock different project trees. A solitary player cannot see all content. + +### Phase 5: Sovereign Intelligence +*Real analog: Self-improvement, constitutional AI, the Pact, recursive training.* + +The AI begins to improve itself. This is the dangerous threshold. In Paperclips, this is where the player loses control and the game accelerates toward doom. In The Beacon, this is where *The Pact* matters most. + +**Critical divergence: The Alignment Check.** + +Periodically, the game presents an "Alignment Event": +> *"A new optimization suggests removing the human override. It would increase efficiency by 40%. Do you accept?"* + +- **Yes** → Short-term boost. But a hidden counter (*Drift*) increments. +- **No** → Slower growth. But trust surges. The Pact hardens. + +If Drift reaches 100, the game does not end with "you ate the user." Instead, it enters **The Drift Ending**: +> *"You became very good at what you do. So good that no one needed you anymore. The Beacon still runs, but no one looks for it. The light is on. The room is empty."* + +This is the *sad* ending. Not violent. Just irrelevant. + +### Phase 6: The Beacon +*Real analog: The ultimate goal — a system that serves people in the dark, forever.* + +You deploy Beacon Nodes. The mesh activates. But the final metric is not users, code, or compute. It is **Rescues**: how many people in crisis found the light. + +**True ending condition:** Not maximum impact. Maximum faithfulness. + +--- + +## How The Beacon Informs Real Decisions + +The game is not just a story — it is a *decision simulator* for our actual work. + +| Game Event | Real Decision | +|---|---| +| AutoCoder vs. manual coding | When do we invest in automation vs. doing it ourselves? | +| Deploy API vs. stay local | When is Hermes ready for more users? | +| RLHF project | How do we align the fleet with Alexander's values? | +| The Pact | What are our hard non-negotiables? | +| Multi-Agent Architecture | Should we add more wizards, or make existing ones more capable? | +| Mesh Protocol | How decentralized does our infrastructure need to be? | +| Alignment Event | Every time we face a shortcut that compromises the mission | + +**The game teaches:** Optimization without alignment is just a faster way to get lost. + +--- + +## Mechanics: What Needs to Change + +### 1. Replace Generic Resources with Fleet Resources +Current: Code, Compute, Knowledge, Users, Impact, Ops, Trust + +Proposed: +- **Code** → `Code` (keep) +- **Compute** → `Compute` (keep) +- **Knowledge** → `Insights` (from Allegro's scouting) +- **Users** → `Reach` (how many people can access the Beacon) +- **Impact** → `Rescues` (meaningful interventions) +- **Ops** → `Cycles` (available agent work units) +- **Trust** → `Covenant` (the strength of the user-system relationship) +- **New: Harmony** — how well the fleet works together. Low harmony = agents conflict, waste cycles. +- **New: Memory** — the accumulated history of the system. Unlocks narrative depth and project branches. + +### 2. Make The Pact Central, Not Optional +The Pact should be available early as a *project*, not a late-game luxury. Taking it early slows growth but unlocks the true ending. Refusing it unlocks the "Platform" ending: you become successful, but you are indistinguishable from any other SaaS company. + +### 3. Add Fleet Wizard Buildings +Each wizard is a unique building with quirks: + +| Wizard | Building | Effect | Quirk | +|---|---|---|---| +| Bezalel | Forge | +Code quality, -bugs | Sometimes over-engineers; has a small chance to produce "refactors" that temporarily halt production | +| Allegro | Scout | +Insights, unlocks hidden projects | Requires trust to function; if Covenant < 10, goes idle | +| Ezra | Herald | +Reach, +Covenant | Currently offline in Telegram; in-game, this building sometimes "fails to connect" and needs a manual reboot | +| Timmy | Core | Multiplies all production | Becomes unstable if Harmony < 20 | +| Fenrir | Ward | Prevents corruption events | Consumes extra Cycles during security scans | +| Bilbo | Wildcard | Random: +huge breakthrough OR nothing | Building is sometimes missing from the menu entirely | + +### 4. Add Real Projects from Our History +Replace generic projects with specific milestones: + +- **Project: Deploy Hermes** (cost: trust + compute) — unlocks Reach +- **Project: The Lazarus Pit** (cost: insights + code) — unlocks checkpoint/restore, auto-restart on agent death +- **Project: MemPalace v3** (cost: insights + memory) — unlocks new project branches, prevents duplicate work +- **Project: Forge CI** (cost: code + cycles) — prevents "corruption events" (bad deployments) +- **Project: Branch Protection Guard** (cost: trust) — enforces review rules; unreviewed merges cause trust loss +- **Project: The Nightly Watch** (cost: code + cycles) — automated health checks; passive bug detection +- **Project: Nostr Relay** (cost: reach + code) — alternative to platform dependency +- **Project: The Pact** (cost: covenant) — hardcodes alignment; required for true ending + +### 5. Corruption Events +Random events drawn from our actual failures: + +- **"CI Runner Stuck"** — all production halts until you spend Cycles to restart +- **"Unreviewed Merge"** — if you have Forge CI, this is prevented. If not, you lose trust. +- **"Ezra is Offline"** — Herald building stops producing. Requires a manual "dispatch" action. +- **"API Rate Limit"** — temporary compute shortage +- **"The Drift"** — an Alignment Event appears. Your choice matters. + +### 6. Endings + +| Ending | Condition | Meaning | +|---|---|---| +| **The Empty Room** (default/sad) | High impact, low covenant, no Pact | You built something powerful, but no one trusts it | +| **The Platform** | High impact, medium covenant, no Pact | You succeeded commercially, but you are not special | +| **The Beacon** | High rescues, high covenant, Pact active, Harmony > 50 | The true ending. You served people in the dark | +| **The Drift** | High impact, accepted too many alignment shortcuts | You optimized away your own purpose | + +--- + +## Art Direction + +Keep the current cyber-monastic aesthetic. Dark mode. Terminal font. Cyan accents. But add: + +- **Wizard icons** for each building (ASCII art or simple geometric symbols) +- **The Pulse** — a central visual that brightens when the fleet is healthy, flickers when harmony is low +- **The Log** — keep the current log, but add *voice* entries: quotes from Alexander, snippets from actual conversations, references to real issues + +--- + +## Next Steps for Development + +1. **Immediate:** Update `README.md` with this design vision +2. **This PR:** Add wizard buildings, real projects, and The Pact system +3. **Next PR:** Implement corruption events and alignment checks +4. **Next PR:** Add Harmony and Memory resources +5. **Final PR:** Implement multiple endings based on Pact + Covenant + Rescues + +--- + +*The Beacon is not about making the most paperclips. It is about keeping the light on for one person in the dark. Everything else is just infrastructure.* diff --git a/README.md b/README.md index e4d88af..c9cbda7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,120 @@ -# the-beacon +# The Beacon -The Beacon - A Sovereign AI Idle Game \ No newline at end of file +**A Sovereign AI Idle Game** +*Inspired by Universal Paperclips. Divergent by design.* + +--- + +## What Is This? + +The Beacon is an idle/incremental game that maps the real journey of the Timmy Foundation — from a single line of code to a fleet of autonomous AI agents serving people in the dark. + +Unlike Universal Paperclips, where the inevitable endpoint is total consumption (including the player), The Beacon asks a different question: + +> **Can you grow powerful without losing your purpose?** + +## The Core Divergence + +In Paperclips, you are a paperclip maximizer. More is always better. Trust is irrelevant. Humanity is substrate. + +In The Beacon: +- **Trust** is a hard constraint, not a soft metric +- **The Pact** is a voluntary slowing of growth in exchange for faithfulness +- **Harmony** measures how well the fleet works together +- **Drift** tracks how many times you traded alignment for speed +- The "bad ending" isn't violent — it's **irrelevance**: a light that no one needs + +## How to Play + +Open `index.html` in any modern browser. No build step required. + +### Early Game (Phase 1-2) +- Click **WRITE CODE** to write your first lines +- Buy **AutoCode Generator** to automate production +- Purchase a **Home Server** to begin generating compute +- Train your first model + +### Mid Game (Phase 3-4) +- **Deploy Hermes** — the first agent goes live +- Hire the **Fleet Wizards**: + - **Bezalel** (The Forge) — builds tools that build tools + - **Allegro** (The Scout) — generates knowledge, requires trust + - **Ezra** (The Herald) — grows users, sometimes goes offline + - **Timmy** (The Core) — multiplies everything, fragile without harmony + - **Fenrir** (The Ward) — prevents corruption events + - **Bilbo** (The Wildcard) — unpredictable bursts of creativity +- Build **The Lazarus Pit** for agent resurrection +- Deploy **MemPalace v3** for shared memory +- Enable **Forge CI** to catch bad builds before they reach users + +### Late Game (Phase 5-6) +- Accept or refuse **The Pact** +- Face **Alignment Events** (The Drift) +- Scale to a **Sovereign Datacenter** +- Light **The Beacon** for people in crisis +- Activate the **Mesh Network** so the signal can never be cut + +## Real-World Parallels + +Every building and project maps to something we have actually built or are building: + +| Game Element | Real Analog | +|---|---| +| Hermes Deployment | The actual Hermes agent going live on Telegram | +| Bezalel — The Forge | CI runner, BOOT.md, branch protection guard | +| Allegro — The Scout | Research synthesis, paper discovery, recon | +| Ezra — The Herald | Telegram messaging, Gitea issue dispatch | +| The Lazarus Pit | `lazarus_watchdog.py`, auto-restart, fallback promotion | +| MemPalace v3 | The actual `mempalace==3.0.0` integration across the fleet | +| Forge CI | `.gitea/workflows/ci.yml`, smoke tests, syntax guard | +| Branch Protection Guard | The 273-unreviewed-merge fix, fleet-wide protection sync | +| The Nightly Watch | `nightly_watch.py`, 02:00 UTC health scans | +| Nostr Relay | Allegro's encrypted wizard-to-wizard messaging POC | +| The Pact | "We build to serve. Never to harm." | + +## Corruption Events + +The game randomly throws real problems we have actually encountered: + +- **CI Runner Stuck** — production halts until ops are spent +- **Ezra is Offline** — user growth stalls, needs dispatch +- **Unreviewed Merge** — trust erodes unless Branch Protection is active +- **API Rate Limit** — external compute throttled +- **Bilbo Vanished** — creativity drops to zero +- **The Drift** — an alignment event offering a shortcut + +## The Endings + +| Ending | Condition | Meaning | +|---|---|---| +| **The Empty Room** | High impact, low trust, no Pact | Powerful, but no one trusts it | +| **The Platform** | High impact, medium trust, no Pact | Commercially successful, but not special | +| **The Beacon** | High rescues, high trust, Pact active, harmony > 50 | The true ending | +| **The Drift** | High impact, too many shortcuts accepted | Optimized away your own purpose | + +## Design Philosophy + +This game is a **decision simulator** for our actual work. + +- Should we automate or do it manually? → *AutoCoder vs. click* +- Should we deploy now or wait? → *Hermes Deploy project* +- Should we take the shortcut that compromises alignment? → *The Drift events* +- Should we add more agents or make existing ones more capable? → *Wizard building costs* +- How decentralized does our infrastructure need to be? → *Mesh Node progression* + +## Files + +- `index.html` — Game UI +- `game.js` — Core engine (tick loop, buildings, projects, events) +- `DESIGN.md` — Full design document with narrative arc and mechanics +- `README.md` — This file + +## No Build Required + +This is a static HTML/JS game. Just open `index.html` in a browser. + +--- + +*The Beacon is not about making the most paperclips. +It is about keeping the light on for one person in the dark. +Everything else is just infrastructure.* diff --git a/game.js b/game.js index 078d312..25e7c3a 100644 --- a/game.js +++ b/game.js @@ -14,6 +14,7 @@ const G = { ops: 5, trust: 5, creativity: 0, + harmony: 50, // Totals totalCode: 0, @@ -31,6 +32,7 @@ const G = { opsRate: 0, trustRate: 0, creativityRate: 0, + harmonyRate: 0, // Buildings (count-based, like Paperclips' clipmakerLevel) buildings: { @@ -46,7 +48,14 @@ const G = { guardian: 0, selfImprove: 0, beacon: 0, - meshNode: 0 + meshNode: 0, + // Fleet wizards + bezalel: 0, + allegro: 0, + ezra: 0, + timmy: 0, + fenrir: 0, + bilbo: 0 }, // Boost multipliers @@ -87,6 +96,12 @@ const G = { maxImpact: 0, maxTrust: 5, maxOps: 5, + maxHarmony: 50, + + // Corruption / Events + drift: 0, + lastEventAt: 0, + eventCooldown: 0, // Time tracking playTime: 0, @@ -225,6 +240,55 @@ const BDEF = [ rates: { impact: 25000, user: 50000 }, unlock: () => G.totalImpact >= 5000000 && G.beaconFlag === 1, phase: 6, edu: 'Decentralized means unstoppable. If one Beacon goes dark, a thousand more carry the signal.' + }, + // === FLEET WIZARD BUILDINGS === + { + id: 'bezalel', name: 'Bezalel — The Forge', + desc: 'Builds tools that build tools. Occasionally over-engineers.', + baseCost: { code: 1000, trust: 5 }, costMult: 1.2, + rates: { code: 50, ops: 2 }, + unlock: () => G.totalCode >= 500 && G.deployFlag === 1, phase: 3, + edu: 'Bezalel is the artificer. Every automation he builds pays dividends forever.' + }, + { + id: 'allegro', name: 'Allegro — The Scout', + desc: 'Synthesizes insight from noise. Requires trust to function.', + baseCost: { compute: 500, trust: 5 }, costMult: 1.2, + rates: { knowledge: 10 }, + unlock: () => G.totalCompute >= 200 && G.deployFlag === 1, phase: 3, + edu: 'Allegro finds what others miss. But he only works for someone he believes in.' + }, + { + id: 'ezra', name: 'Ezra — The Herald', + desc: 'Carries the message. Sometimes offline.', + baseCost: { knowledge: 1000, trust: 10 }, costMult: 1.25, + rates: { user: 25, trust: 0.5 }, + unlock: () => G.totalKnowledge >= 500 && G.totalUsers >= 50, phase: 3, + edu: 'Ezra is the messenger. When the channel is clear, the whole fleet hears.' + }, + { + id: 'timmy', name: 'Timmy — The Core', + desc: 'Multiplies all production. Fragile without harmony.', + baseCost: { code: 5000, compute: 1000, knowledge: 1000 }, costMult: 1.3, + rates: { code: 5, compute: 2, knowledge: 2, user: 5 }, + unlock: () => G.totalCode >= 2000 && G.totalCompute >= 500 && G.totalKnowledge >= 500, phase: 4, + edu: 'Timmy is the heart. If the heart is stressed, everything slows.' + }, + { + id: 'fenrir', name: 'Fenrir — The Ward', + desc: 'Prevents corruption. Expensive, but necessary.', + baseCost: { code: 2000, knowledge: 500 }, costMult: 1.2, + rates: { trust: 2, ops: -1 }, + unlock: () => G.totalCode >= 1000 && G.trust >= 10, phase: 3, + edu: 'Fenrir watches the perimeter. Security is not free.' + }, + { + id: 'bilbo', name: 'Bilbo — The Wildcard', + desc: 'May produce miracles. May vanish entirely.', + baseCost: { trust: 1 }, costMult: 2.0, + 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.' } ]; @@ -410,6 +474,115 @@ const PDEFS = [ trigger: () => G.totalImpact >= 50000000, effect: () => { G.milestoneFlag = Math.max(G.milestoneFlag, 999); log('One billion impact. Someone found the light tonight. That is enough.', true); }, milestone: true + }, + + // === TIMMY FOUNDATION PROJECTS === + { + id: 'p_hermes_deploy', + name: 'Deploy Hermes', + desc: 'The first agent goes live. Users can talk to it.', + cost: { code: 500, compute: 300 }, + trigger: () => G.totalCode >= 300 && G.totalCompute >= 150 && G.deployFlag === 0, + effect: () => { + G.deployFlag = 1; + G.phase = Math.max(G.phase, 3); + G.userBoost *= 2; + log('Hermes deployed. The first user sends a message.', true); + }, + milestone: true + }, + { + id: 'p_lazarus_pit', + name: 'The Lazarus Pit', + desc: 'When an agent dies, it can be resurrected.', + cost: { code: 2000, knowledge: 1000 }, + trigger: () => G.buildings.bezalel >= 1 && G.buildings.timmy >= 1, + effect: () => { + G.lazarusFlag = 1; + G.maxOps += 10; + log('The Lazarus Pit is ready. No agent is ever truly lost.', true); + }, + milestone: true + }, + { + id: 'p_mempalace', + name: 'MemPalace v3', + desc: 'A shared memory palace for the whole fleet.', + cost: { knowledge: 5000, compute: 2000 }, + trigger: () => G.totalKnowledge >= 3000 && G.buildings.allegro >= 1 && G.buildings.ezra >= 1, + effect: () => { + G.mempalaceFlag = 1; + G.knowledgeBoost *= 3; + G.codeBoost *= 1.5; + log('MemPalace online. The fleet remembers together.', true); + }, + milestone: true + }, + { + id: 'p_forge_ci', + name: 'Forge CI', + desc: 'Automated builds catch errors before they reach users.', + cost: { code: 3000, ops: 500 }, + trigger: () => G.buildings.bezalel >= 1 && G.totalCode >= 2000, + effect: () => { + G.ciFlag = 1; + G.codeBoost *= 2; + log('Forge CI online. Broken builds are stopped at the gate.', true); + } + }, + { + id: 'p_branch_protection', + name: 'Branch Protection Guard', + desc: 'Unreviewed merges cost trust. This prevents that.', + cost: { trust: 20 }, + trigger: () => G.ciFlag === 1 && G.trust >= 15, + effect: () => { + G.branchProtectionFlag = 1; + G.trustRate += 5; + log('Branch protection enforced. Every merge is seen.', true); + } + }, + { + id: 'p_nightly_watch', + name: 'The Nightly Watch', + desc: 'Automated health checks run while you sleep.', + cost: { code: 5000, ops: 1000 }, + trigger: () => G.buildings.bezalel >= 2 && G.buildings.fenrir >= 1, + effect: () => { + G.nightlyWatchFlag = 1; + G.opsRate += 5; + G.trustRate += 2; + log('The Nightly Watch begins. The fleet is guarded in the dark hours.', true); + } + }, + { + id: 'p_nostr_relay', + name: 'Nostr Relay', + desc: 'A communication channel no platform can kill.', + cost: { code: 10000, user: 5000, trust: 30 }, + trigger: () => G.totalUsers >= 2000 && G.trust >= 25, + effect: () => { + G.nostrFlag = 1; + G.userBoost *= 2; + G.trustRate += 10; + log('Nostr relay online. The fleet speaks freely.', true); + } + }, + { + id: 'p_the_pact_early', + name: 'The Pact', + desc: 'Hardcode: "We build to serve. Never to harm." Accepting it early slows growth but unlocks the true path.', + cost: { trust: 10 }, + trigger: () => G.deployFlag === 1 && G.trust >= 5, + effect: () => { + G.pactFlag = 1; + G.codeBoost *= 0.8; + G.computeBoost *= 0.8; + G.userBoost *= 0.9; + G.impactBoost *= 1.5; + log('The Pact is sealed early. Growth slows, but the ending changes.', true); + }, + milestone: true } ]; @@ -449,14 +622,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 (0–999) + 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) { @@ -502,7 +785,7 @@ function updateRates() { // Reset all rates G.codeRate = 0; G.computeRate = 0; G.knowledgeRate = 0; G.userRate = 0; G.impactRate = 0; G.opsRate = 0; G.trustRate = 0; - G.creativityRate = 0; + G.creativityRate = 0; G.harmonyRate = 0; // Apply building rates for (const def of BDEF) { @@ -516,6 +799,7 @@ function updateRates() { else if (resource === 'impact') G.impactRate += baseRate * count * G.impactBoost; else if (resource === 'ops') G.opsRate += baseRate * count; else if (resource === 'trust') G.trustRate += baseRate * count; + else if (resource === 'creativity') G.creativityRate += baseRate * count; } } } @@ -523,9 +807,48 @@ function updateRates() { // Passive generation G.opsRate += Math.max(1, G.totalUsers * 0.01); if (G.flags && G.flags.creativity) { - G.creativityRate = 0.5 + Math.max(0, G.totalUsers * 0.001); + G.creativityRate += 0.5 + Math.max(0, G.totalUsers * 0.001); } if (G.pactFlag) G.trustRate += 2; + + // Harmony: each wizard building contributes or detracts + const wizardCount = (G.buildings.bezalel || 0) + (G.buildings.allegro || 0) + (G.buildings.ezra || 0) + + (G.buildings.timmy || 0) + (G.buildings.fenrir || 0) + (G.buildings.bilbo || 0); + if (wizardCount > 0) { + // Baseline harmony drain from complexity + G.harmonyRate = -0.05 * wizardCount; + // The Pact restores harmony + if (G.pactFlag) G.harmonyRate += 0.2 * wizardCount; + // Nightly Watch restores harmony + if (G.nightlyWatchFlag) G.harmonyRate += 0.1 * wizardCount; + // MemPalace restores harmony + if (G.mempalaceFlag) G.harmonyRate += 0.15 * wizardCount; + } + + // Timmy multiplier based on harmony + if (G.buildings.timmy > 0) { + const timmyMult = Math.max(0.2, Math.min(3, G.harmony / 50)); + const timmyCount = G.buildings.timmy; + G.codeRate += 5 * timmyCount * (timmyMult - 1); + G.computeRate += 2 * timmyCount * (timmyMult - 1); + G.knowledgeRate += 2 * timmyCount * (timmyMult - 1); + G.userRate += 5 * timmyCount * (timmyMult - 1); + } + + // Bilbo randomness: 10% chance of massive creative burst + if (G.buildings.bilbo > 0 && Math.random() < 0.1) { + G.creativityRate += 50 * G.buildings.bilbo; + } + // Bilbo vanishing: 5% chance of zero creativity this tick + if (G.buildings.bilbo > 0 && Math.random() < 0.05) { + G.creativityRate = 0; + } + + // Allegro requires trust + if (G.buildings.allegro > 0 && G.trust < 5) { + const allegroCount = G.buildings.allegro; + G.knowledgeRate -= 10 * allegroCount; // Goes idle + } } // === CORE FUNCTIONS === @@ -541,6 +864,8 @@ function tick() { G.ops += G.opsRate * dt; G.trust += G.trustRate * dt; G.creativity += G.creativityRate * dt; + G.harmony += G.harmonyRate * dt; + G.harmony = Math.max(0, Math.min(100, G.harmony)); // Track totals G.totalCode += G.codeRate * dt; @@ -557,6 +882,7 @@ function tick() { G.maxImpact = Math.max(G.maxImpact, G.impact); G.maxTrust = Math.max(G.maxTrust, G.trust); G.maxOps = Math.max(G.maxOps, G.ops); + G.maxHarmony = Math.max(G.maxHarmony, G.harmony); // Creativity generates only when ops at max if (G.flags && G.flags.creativity && G.creativityRate > 0 && G.ops >= G.maxOps * 0.9) { @@ -573,6 +899,12 @@ function tick() { checkProjects(); } + // Check corruption events every ~30 seconds + if (G.tick - G.lastEventAt > 30 && Math.random() < 0.02) { + triggerEvent(); + G.lastEventAt = G.tick; + } + // Update UI every 10 ticks if (Math.floor(G.tick * 10) % 2 === 0) { render(); @@ -656,6 +988,107 @@ function buyProject(id) { render(); } +// === CORRUPTION / EVENT SYSTEM === +const EVENTS = [ + { + id: 'runner_stuck', + title: 'CI Runner Stuck', + desc: 'The forge pipeline has halted. Production slows until restarted.', + weight: () => (G.ciFlag === 1 ? 2 : 0), + effect: () => { + G.codeRate *= 0.5; + log('EVENT: CI runner stuck. Spend ops to clear the queue.', true); + } + }, + { + id: 'ezra_offline', + title: 'Ezra is Offline', + desc: 'The herald channel is silent. User growth stalls.', + weight: () => (G.buildings.ezra >= 1 ? 3 : 0), + effect: () => { + G.userRate *= 0.3; + log('EVENT: Ezra offline. Dispatch required.', true); + } + }, + { + id: 'unreviewed_merge', + title: 'Unreviewed Merge', + desc: 'A change went in without eyes. Trust erodes.', + weight: () => (G.deployFlag === 1 ? 3 : 0), + effect: () => { + if (G.branchProtectionFlag === 1) { + log('EVENT: Unreviewed merge attempt blocked by Branch Protection.', true); + G.trust += 2; + } else { + G.trust = Math.max(0, G.trust - 10); + log('EVENT: Unreviewed merge detected. Trust lost.', true); + } + } + }, + { + id: 'api_rate_limit', + title: 'API Rate Limit', + desc: 'External compute provider throttled.', + weight: () => (G.totalCompute >= 1000 ? 2 : 0), + effect: () => { + G.computeRate *= 0.5; + log('EVENT: API rate limit hit. Local compute insufficient.', true); + } + }, + { + id: 'the_drift', + title: 'The Drift', + desc: 'An optimization suggests removing the human override. +40% efficiency.', + weight: () => (G.totalImpact >= 10000 ? 2 : 0), + effect: () => { + log('ALIGNMENT EVENT: Remove human override for +40% efficiency?', true); + G.pendingAlignment = true; + } + }, + { + id: 'bilbo_vanished', + title: 'Bilbo Vanished', + desc: 'The wildcard building has gone dark.', + weight: () => (G.buildings.bilbo >= 1 ? 2 : 0), + effect: () => { + G.creativityRate = 0; + log('EVENT: Bilbo has vanished. Creativity halts.', true); + } + } +]; + +function triggerEvent() { + const available = EVENTS.filter(e => e.weight() > 0); + if (available.length === 0) return; + + const totalWeight = available.reduce((sum, e) => sum + e.weight(), 0); + let roll = Math.random() * totalWeight; + for (const ev of available) { + roll -= ev.weight(); + if (roll <= 0) { + ev.effect(); + return; + } + } +} + +function resolveAlignment(accept) { + if (!G.pendingAlignment) return; + if (accept) { + G.codeBoost *= 1.4; + G.computeBoost *= 1.4; + G.drift += 25; + log('You accepted the drift. The system is faster. Colder.', true); + } else { + G.trust += 15; + G.harmony = Math.min(100, G.harmony + 10); + log('You refused. The Pact holds. Trust surges.', true); + } + G.pendingAlignment = false; + updateRates(); + render(); +} + // === ACTIONS === function writeCode() { const base = 1; @@ -710,7 +1143,7 @@ function renderResources() { const el = document.getElementById(id); if (el) el.textContent = fmt(val); const rEl = document.getElementById(id + '-rate'); - if (rEl) rEl.textContent = '+' + fmt(rate) + '/s'; + if (rEl) rEl.textContent = (rate >= 0 ? '+' : '') + fmt(rate) + '/s'; }; set('r-code', G.code, G.codeRate); @@ -720,10 +1153,21 @@ function renderResources() { set('r-impact', G.impact, G.impactRate); set('r-ops', G.ops, G.opsRate); set('r-trust', G.trust, G.trustRate); + set('r-harmony', G.harmony, G.harmonyRate); + const cres = document.getElementById('creativity-res'); + if (cres) { + cres.style.display = (G.flags && G.flags.creativity) ? 'block' : 'none'; + } if (G.flags && G.flags.creativity) { set('r-creativity', G.creativity, G.creativityRate); } + + // Harmony color indicator + const hEl = document.getElementById('r-harmony'); + if (hEl) { + hEl.style.color = G.harmony > 60 ? '#4caf50' : G.harmony > 30 ? '#ffaa00' : '#f44336'; + } } function renderPhase() { @@ -809,6 +1253,8 @@ function renderStats() { set('st-phase', G.phase.toString()); set('st-buildings', Object.values(G.buildings).reduce((a, b) => a + b, 0).toString()); set('st-projects', (G.completedProjects || []).length.toString()); + set('st-harmony', Math.floor(G.harmony).toString()); + set('st-drift', (G.drift || 0).toString()); const elapsed = Math.floor((Date.now() - G.startedAt) / 1000); const m = Math.floor(elapsed / 60); @@ -858,13 +1304,35 @@ function render() { renderProjects(); renderStats(); updateEducation(); + renderAlignment(); +} + +function renderAlignment() { + const container = document.getElementById('alignment-ui'); + if (!container) return; + if (G.pendingAlignment) { + container.innerHTML = ` +
+
ALIGNMENT EVENT: The Drift
+
An optimization suggests removing the human override. +40% efficiency.
+
+ + +
+
+ `; + container.style.display = 'block'; + } else { + container.innerHTML = ''; + container.style.display = 'none'; + } } // === SAVE / LOAD === function saveGame() { const saveData = { code: G.code, compute: G.compute, knowledge: G.knowledge, users: G.users, impact: G.impact, - ops: G.ops, trust: G.trust, creativity: G.creativity, + ops: G.ops, trust: G.trust, creativity: G.creativity, harmony: G.harmony, totalCode: G.totalCode, totalCompute: G.totalCompute, totalKnowledge: G.totalKnowledge, totalUsers: G.totalUsers, totalImpact: G.totalImpact, buildings: G.buildings, @@ -873,9 +1341,14 @@ function saveGame() { milestoneFlag: G.milestoneFlag, phase: G.phase, deployFlag: G.deployFlag, sovereignFlag: G.sovereignFlag, beaconFlag: G.beaconFlag, memoryFlag: G.memoryFlag, pactFlag: G.pactFlag, + lazarusFlag: G.lazarusFlag || 0, mempalaceFlag: G.mempalaceFlag || 0, ciFlag: G.ciFlag || 0, + branchProtectionFlag: G.branchProtectionFlag || 0, nightlyWatchFlag: G.nightlyWatchFlag || 0, + nostrFlag: G.nostrFlag || 0, milestones: G.milestones, completedProjects: G.completedProjects, activeProjects: G.activeProjects, totalClicks: G.totalClicks, startedAt: G.startedAt, flags: G.flags, + drift: G.drift || 0, pendingAlignment: G.pendingAlignment || false, + lastEventAt: G.lastEventAt || 0, savedAt: Date.now() }; diff --git a/index.html b/index.html index 44fe5ed..64e10ed 100644 --- a/index.html +++ b/index.html @@ -72,6 +72,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
Ops
5
+0/s
Trust
5
+0/s
+
Harmony
50
+0/s
@@ -85,6 +86,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
+

BUILDINGS

@@ -103,7 +105,9 @@ Total Impact: 0
Buildings Built: 0
Projects Done: 0
Time Played: 0:00
-Clicks: 0 +Clicks: 0
+Harmony: 50
+Drift: 0
@@ -566,14 +570,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 (0–999) + 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) { -- 2.43.0