File: README.md
Server Survival
Server Survival is an interactive 3D simulation game where you play as a Cloud Architect. Your mission is to build and scale a resilient cloud infrastructure to handle increasing traffic loads while fighting off DDoS attacks, managing your budget, and keeping your services healthy.
Learn cloud by playing:
๐ญ New: Datacenter Survival โ the sister game. This one teaches the logical layer of the cloud; that one teaches the physical layer it runs on: power chains, heat, cooling, PUE. Play it here.
Game Modes
- Survival โ the core experience: survive as long as possible against escalating traffic, DDoS spikes, random events, and service degradation.
- Campaign โ 25 hand-crafted levels across 5 chapters (Basics โ Optimization โ Defense & Mastery โ Production Readiness โ The AI Wave). Each level teaches one architecture concept with pre-built diagrams, objectives, and a debrief.
- Sandbox โ a free-play lab: any budget, any traffic mix, no game over. Experiment with all 26 services.
What It Teaches
Every mechanic is a real cloud concept in miniature:
- Observability โ place a Monitoring node to unlock a live metrics dashboard (RPS, error rate, latency sparklines) and alerts. Level 15 makes you fly blind without it first.
- Auto-scaling & cold start โ Compute fleets scale out at 70% utilization and back in at 30%, but new instances need warmup time (Containers even more so).
- Queue-depth scaling โ a queue-fed fleet scales on queue pressure, not just CPU, so backlogs trigger scale-out.
- Circuit breaking & retries โ overloaded downstreams trip a breaker (50% error rate over a rolling window), traffic is retried with backoff, and SPOFs get flagged.
- Multi-region failover โ GeoDNS splits traffic across regional stacks; region-outage events show why one region is never enough.
- Inference serving & batching โ the GPU Cluster batches INFERENCE requests (up to 8/12/16 by tier) into one job that amortizes a fixed per-batch cost, so profit is all utilization: a full batch prints money, a half-fed GPU bleeds its $60/min upkeep.
- Model cold starts & quality โ a GPU loads its model for 12/20/30s before serving anything, and every tier upgrade reloads it; bigger models also answer better (10% โ 4% โ 1% bad-answer risk, โ0.5 reputation per bad answer).
- Inference SLOs โ the Inference Gateway buffers for warming or busy GPUs but expires anything older than its 6s deadline: a stale generation is failed honestly, never served.
- Power as the constraint โ the grid carries 8 kW, every GPU draws 6, and a Substation adds 6: the fleet's ceiling is bought in watts before it is bought in dollars.
- OLTP vs OLAP โ the Data Warehouse takes analytics writes cheaply but refuses realtime reads; the Relational DB does the opposite trade.
- Failure-reason badges โ every dropped request pops a badge on the node that dropped it, saying why (no route, capacity, read-only replica, search-only index...).
How to Play
Objective
Survive as long as possible! Manage your Budget ($), Reputation (%), and Service Health.
- Earn Money by successfully processing legitimate traffic requests.
- Lose Reputation if requests fail or if malicious traffic slips through.
- Maintain Health - Services degrade under load and need repairs.
- Game Over if Reputation hits 0% or you go bankrupt ($-1000).
Traffic Types
| Traffic | Color | Destination | Reward | Description |
|---|---|---|---|---|
| STATIC | Green | CDN / Storage | $0.50 | Static file requests (images, CSS, JS) |
| READ | Blue | Replica / NoSQL / SQL DB | $0.80 | Database read operations |
| WRITE | Orange | NoSQL / SQL DB / Warehouse | $1.20 | Database write operations |
| UPLOAD | Yellow | Storage / Warehouse | $1.50 | File uploads |
| SEARCH | Cyan | Search Engine / SQL DB | $1.20 | Search queries (Search Engine preferred, SQL DB fallback) |
| MALICIOUS | Red | Blocked by Firewall / Identity Provider | $0 | DDoS attacks โ block them or bleed reputation! |
| INFERENCE | Fuchsia | GPU Cluster (via Inference Gateway) | $0.50 | AI generation requests โ batched on GPUs, with per-request generation length |
Infrastructure & Services
Build your architecture using the toolbar. All 26 services are organized into five category tabs โ the same taxonomy as the table below. Each service has a cost, capacity, and per-minute upkeep:
| Category | Service | Cost | Capacity | Upkeep | Role |
|---|---|---|---|---|---|
| Front door | GeoDNS | $50 | 60 | $4/min | Front door for the whole site: splits traffic across independent regional stacks. |
| Front door | CDN | $60 | 50 | $5/min | Caches STATIC content at the edge (95% hit rate). |
| Front door | Firewall | $40 | 30 | $4/min | The first line of defense. Blocks malicious traffic. |
| Front door | Identity Provider | $55 | 20 | $6/min | Adds latency but catches session-based attacks a WAF misses. |
| Front door | API Gateway | $70 | 60 | $8/min | Rate limits traffic. Throttled requests lose less reputation than failures. Upgradeable T1โT3. |
| Front door | Load Balancer | $50 | 20 | $6/min | Distributes traffic to multiple Compute instances. |
| Compute | Compute | $60 | 4 | $12/min | Processes requests. Auto-scales into a fleet (with cold start). Upgradeable T1โT3. |
| Compute | Serverless Function | $45 | 30 | $2/min + $0.03/req | Auto-scales with traffic. Very low upkeep but pays per completed request โ cheap when idle, expensive at high RPS. |
| Compute | Container Cluster | $120 | 12 | $16/min | Dense fixed capacity at a flat fee. Slow node-pool warmup on scale-out. |
| Compute | GPU Cluster | $300 | 8 | $60/min | Batches INFERENCE into one amortized job. Model cold-starts on placement and on every tier upgrade; draws 6 kW. Upgradeable T1โT3. |
| Compute | Inference Gateway | $70 | 10 (queue 20) | $5/min | Holds INFERENCE for warming/full GPUs, dispatches to the least-loaded one โ and expires anything older than its 6s deadline. |
| Data | Relational DB | $150 | 8 | $24/min | Destination for READ/WRITE/SEARCH traffic. Upgradeable T1โT3. |
| Data | NoSQL DB | $80 | 15 | $14/min | Fast for READ/WRITE, but cannot handle SEARCH queries. Upgradeable T1โT3. |
| Data | Memory Cache | $60 | 30 | $8/min | Caches responses to reduce DB load. Upgradeable T1โT3. |
| Data | File Storage | $25 | 25 | $5/min | Destination for STATIC/UPLOAD traffic. |
| Data | Search Engine | $120 | 12 | $16/min | Specialized for SEARCH queries. 3ร faster than SQL DB. Upgradeable T1โT3. |
| Data | Read Replica | $100 | 12 | $12/min | Offloads READ traffic from the master DB. Requires connection to a DB. Upgradeable T1โT3. |
| Data | Data Warehouse | $90 | 40 | $8/min | Stores analytics WRITEs cheaply and slowly. Cannot serve realtime READ. |
| Async | Message Queue | $45 | 50 (queue 200) | $3/min | Buffers requests during spikes. Prevents drops. |
| Async | Pub/Sub Topic | $65 | 30 | $6/min | Fan-out: one event becomes one delivery per subscriber. |
| Async | Stream | $90 | 40 (queue 200) | $10/min | High throughput, strictly ordered per partition. A stalled partition backs up its tail. |
| Async | Dead-Letter Queue | $55 | 25 | $4/min | Parks requests that finally failed, draining them back for a cost. |
| Async | Scheduler | $50 | 1 | $5/min | Injects its own scheduled batch-job bursts on a timer. |
| Async | Notification | $40 | 20 | $4/min | Terminal "send": success earns reputation, failures are silent. |
| Ops | Monitoring | $75 | 1 | $8/min | Unlocks the live metrics dashboard and alerts. |
| Ops | Substation | $150 | 1 | $8/min | +6 kW of grid capacity on the 8 kW base. GPUs draw 6 kW each and cannot be placed past the cap. |
Scoring & Economy
| Action | Money | Score | Reputation |
|---|---|---|---|
| Static Request | +$0.50 | +3 | +0.1 |
| DB Read | +$0.80 | +5 | +0.1 |
| DB Write | +$1.20 | +8 | +0.1 |
| File Upload | +$1.50 | +10 | +0.1 |
| Search Query | +$1.20 | +5 | +0.1 |
| Inference | +$0.50 | +15 | +0.1 |
| GPU Bad Answer | - | - | -0.5 |
| Cache Hit | +20% reward | - | - |
| Attack Blocked | -$1 (mitigation) | +10 | - |
| Request Failed | - | -half | -1 |
| Req. Throttled | - | - | -0.2 |
| Attack Leaked | -$50 (breach) | - | -5 |
Upkeep & Cost Scaling
- Base Upkeep: Each service has per-minute upkeep costs
- Upkeep Scaling: Costs increase 1x to 2x over 10 minutes
- Repair Costs: 15% of service cost to manually repair
- Auto-Repair: +10% upkeep overhead when enabled
Survival Mode
The core experience - survive as long as possible against escalating traffic with constant intervention required:
- RPS Acceleration - Traffic multiplies at time milestones (ร1.3 at 1min โ ร4.0 at 10min)
- Random Events - Cost spikes, capacity drops, traffic bursts, service outages every 15-45 seconds
- Traffic Shifts - Traffic patterns change every 40 seconds
- DDoS Spikes - 50% malicious traffic waves every 45 seconds
- Service Degradation - Services lose health under load, require repairs
- Health bars, event indicator, finances panel, auto-repair toggle, game-over analysis
Campaign Mode
25 levels across 5 chapters, each one a scenario built around a single lesson:
- Basics (levels 1-3) โ your first server, storage, and edge caching with a CDN.
- Optimization (levels 4-10) โ caching, queues, read scaling, search, NoSQL, rate limiting, serverless vs compute.
- Defense & Mastery (levels 11-14) โ defense in depth, high availability, cost crunches, and Black Friday.
- Production Readiness (levels 15-20) โ observability, auto-scaling, node failures, dead-letter queues, fan-out, and two-region failover.
- The AI Wave (levels 21-25) โ GPU inference: model cold starts, batch-fill economics, SLO deadlines, the power wall, and a capstone that has to end in the black.
Levels come with pre-built starting architectures, primary and bonus objectives, speedrun stars, and a debrief tip. Later chapters throw forced outages โ including a whole region going dark โ at your build.
Sandbox Mode
A fully customizable testing environment for experimenting with any architecture:
| Control | Description |
|---|---|
| Budget | Set any starting budget (slider 0-10K, or type any amount) |
| RPS | Control traffic rate (0 = stopped, or type 100+ for stress tests) |
| Traffic Mix | Adjust all 7 traffic type percentages independently |
| Burst | Spawn instant bursts of specific traffic types |
| Upkeep Toggle | Enable/disable service costs |
| Clear All | Reset all services and restore budget |
No game over in Sandbox - experiment freely!
Share Your Architecture
Built something you're proud of? The share panel exports your architecture two ways:
- PNG export โ a clean image of your diagram to post anywhere.
- Shareable URL โ your whole build encoded in a
?arch=link; anyone who opens it gets your architecture loaded and ready to run.
Controls
- Left Click: Select tools, place services, and connect nodes.
- Right Click + Drag: Pan the camera.
- Scroll: Zoom in and out.
- WASD / Arrows: Move camera (pan) when zoomed in.
- ESC: Open main menu and pause game. Press again or click Resume to close menu (stays paused).
- Camera Reset: Press
Rto reset the camera position. - Birds-Eye View: Press
Tto switch between isometric and top-down view. - Hide HUD: Press
Hto toggle UI panels. - Category Tabs: The toolbar groups services into five tabs โ Front Door, Compute, Data, Async, Ops. Keys
1-5switch between them. - Connect Tool: Click two nodes to create a connection (flow direction matters!).
- Valid Flows: Internet -> (GeoDNS/Firewall/CDN/API Gateway) -> Load Balancer -> Queue -> Compute -> Cache -> (Search Engine/Read Replica/SQL DB/NoSQL DB/Storage)
- Delete Tool: Remove services to recover 50% of the cost.
- Time Controls: Pause, Play (1x), and Fast Forward (3x).
Strategy Tips
- Block Attacks First: Always place a Firewall immediately connected to the Internet. Malicious leaks destroy reputation fast (-5 per leak) and cost $50 per breach.
- Use CDN for Static Content: Connect Internet -> CDN -> Storage. The CDN handles 95% of static traffic cheaply!
- Watch Service Health: Damaged services have reduced capacity. Click to repair or enable Auto-Repair.
- Scale for Traffic Surges: RPS multiplies at milestones - prepare before ร2.0 at 3 minutes!
- Balance Income vs Upkeep: Start lean, scale as income grows. Over-provisioning leads to bankruptcy.
- Use Cache Wisely: Reduces database load significantly for READ requests.
- Buffer with Queue: Queue helps survive traffic burst events without dropping requests โ and a queue-fed Compute fleet auto-scales on queue depth.
- React to Events: Watch the event bar - cost spikes mean hold off on purchases, traffic bursts mean ensure capacity.
- API Gateway for Graceful Degradation: Throttled requests only lose -0.2 reputation (vs -1.0 for failures). Great for surviving traffic spikes!
- Split DB Traffic with NoSQL: Route READ/WRITE to NoSQL (faster, cheaper) and keep the Relational DB for SEARCH queries only.
- Search Engine for SEARCH Storms: SEARCH is the heaviest traffic type (2.5x weight). A dedicated Search Engine processes it 3x faster than SQL DB.
- Read Replica for READ offloading: Under "API Heavy" or "Read Heavy" shifts, a Read Replica prevents your main DB from drowning in READ requests.
- Serverless Function = pay-per-use: Great for spiky traffic, but monitor finances โ per-request cost ($0.03) adds up fast at high RPS.
- Place Monitoring early: The metrics dashboard shows error rates and latency before the failure badges start raining.
- Read the failure badges: Every dropped request tells you why it died โ the badge is the lesson.
Tech Stack
- Core: Vanilla JavaScript (native ES modules โ an explicit module graph under
src/, no bundler) - Rendering: Three.js for 3D visualization.
- Styling: Tailwind CSS for the glassmorphism UI.
- Build: No build step required! The game is served raw from this repo by GitHub Pages.
Getting Started
To play, click PLAY NOW. No
install, no build, no account.
To run your own copy โ a fork, or a change you just made โ serve the folder
instead of opening the file. The game is native ES modules, and browsers fetch
module scripts in CORS mode, so a file:// origin is blocked and
double-clicking index.html gets you the UI shell over an empty canvas. Any
static server works, and one is already on your machine:
git clone https://github.com/pshenok/server-survival.git
cd server-survival
python3 -m http.server 8000 # then open http://localhost:8000For Contributors
There is still zero build step โ the dev tooling is optional and for contributors only:
npm installonce, thennpm run checkruns ESLint + the full Vitest suite (47 test files, 989 tests).- CI runs the same check on every PR.
- The code is native ESM:
game.jsplus focused modules undersrc/(sim/,core/,ui/,campaign/,persistence/,input/).
See CONTRIBUTING.md for the workflow.
Community
Join our Discord server to discuss strategies and share your high scores:
Join Discord
Built with code and chaos.
File: docs/superpowers/specs/2026-05-24-campaign-mode-design.md
Campaign Mode Design Spec
Date: 2026-05-24
Status: Approved (pending user review of written spec)
Summary
Add a new Campaign Mode to the game, alongside existing Survival and Sandbox. The campaign teaches one cloud-infrastructure concept per level through a linear sequence of 14 hand-crafted scenarios. Each level pairs a short narrative ("your e-commerce DB is melting") with a concrete win condition that forces the player to use a specific service, then immediately shows the effect (load drops, throughput rises).
The primary goal is teaching, not difficulty โ players walk away understanding why each service exists and when to reach for it.
Goals & Non-Goals
Goals
- Teach all 13 services through scenario-driven play
- Make the educational arc obvious: problem โ solution โ visible effect
- Bite-sized commitment per level (2โ5 min) so players progress without fatigue
- Persist progress and star ratings across sessions
- Reuse existing game systems (Service, Request, traffic shifts, smart hints) โ no new simulation engine
Non-Goals
- Multiplayer / leaderboards (no backend)
- Procedurally generated levels (everything hand-crafted)
- Replacing Survival or Sandbox modes โ Campaign is additive
- Translating levels into all 7 languages on first release (English only initially; locale keys exist as fallback)
Architecture & High-Level Flow
Main Menu
โโโ [Campaign โ
โ
โ
N/14] button (new, alongside Survival/Sandbox)
โโโ Level Select Screen (modal)
โโโ Chapter 1: Basics (#1โ3)
โโโ Chapter 2: Optimization (#4โ10)
โโโ Chapter 3: Defense & Mastery (#11โ14)
โโโ Briefing Modal (per level)
โโโ In-Game Level
โโโ Reuses Survival rendering / Service / Request systems
โโโ Custom traffic distribution & RPS from level config
โโโ Optional pre-placed services (level starting state)
โโโ Optional service whitelist/blacklist (forced learning)
โโโ Real-time objective tracker (uses existing objectivesPanel)
โโโ Debrief Modal (win or lose)
โโโ Win: stars, time, tip โ [Replay] [Next]
โโโ Lose: failure reason, tip โ [Retry] [Map]Reused Systems
Service/Requestclasses โ no changes- Traffic spawn loop, scoring, finances โ same as Survival, configured by level
- Smart hints โ disabled during Campaign (briefing/objectives serve same role)
- DDoS spikes / traffic shifts โ driven from level config rather than survival defaults
New Systems
- Level registry (
src/campaign/levels.js) โ declarative array of all 14 level configs - Campaign controller (
src/campaign/campaign.js) โ loads level, tracks objectives, evaluates win/lose, persists progress - UI overlays (Level Select, Briefing, Debrief modals โ added to
index.html)
Level Format (data shape)
Each level is a declarative object. Example:
/* Detailed source-code truncated for AI context efficiency. */The 14 Levels
Chapter 1: Basics (clean slate, learn the pipeline)
- The First Server โ InternetโWAFโALBโComputeโDB. Process 50 READ. Budget $300.
- Store the Files โ Add Storage for UPLOAD. Process 30 UPLOAD.
- Edge with CDN โ 80% STATIC traffic. Add CDN, survive 60s with DB load <50%.
Chapter 2: Optimization (broken architecture, fix it)
- Cache the DB โ DB melts under READ. Add Cache. DB load <70%.
- Buffer the Spikes โ Bursty traffic. Add Queue. 0 drops in 90s.
- Scale Reads โ "API Heavy" shift active. Add Read Replica. DB load <60%.
- Search Done Right โ "Search Storm" (50% SEARCH). Add Search Engine. SQL DB load <40%.
- NoSQL for Speed โ SQL DB overloaded. Add NoSQL, route READ/WRITE there. Keep SQL only for SEARCH.
- Rate Limit Gateway โ Spikes kill DB. Add API Gateway. <10% failed.
- Serverless or Compute? โ Low RPS (1.5), $500 budget. Build profitable architecture. Profit >$100 in 90s. Bonus: use Serverless, not Compute.
Chapter 3: Defense & Mastery
- Defense in Depth โ DDoS wave (70% MALICIOUS). Add WAF + API GW. 0 leaks.
- High Availability โ 1 WAF given, Service Outage simulated. Add 2nd WAF. Round-robin handles outage.
- Cost Crunch โ Over-engineered architecture (12+ services bleeding money). Reduce upkeep 30%, keep throughput. Net profit >0 in 60s.
- Black Friday โ Finale. Clean slate, $1000 budget. Survive 90s of endgame conditions (ร4 RPS, DDoS, traffic shifts). Bonus: rep >70%, 0 leaks.
Progress Persistence
Stored in localStorage under key serverSurvivalCampaignProgress:
{
version: 1,
completed: {
"1": { stars: 3, bestTimeSec: 42, lastPlayed: 1748000000 },
"2": { stars: 2, bestTimeSec: 51, lastPlayed: 1748001234 },
...
},
highestUnlocked: 5, // level N+1 unlocks when N is completed (any stars)
}- Stars: 1 for completion, +1 for bonus objectives met, +1 for finishing under target time (target =
durationSec * 0.8) - Linear unlock: completing N unlocks N+1 (no skipping)
- Replay any unlocked level for better stars
UI Components
1. Main Menu โ new button
Between Start Survival and Sandbox Mode:
Campaign
โ
โ
โ
0/142. Level Select Modal
Full-screen modal showing all 14 levels grouped by chapter. Each level card:
- Number + title + chapter chip
- Short scenario (1 sentence)
- Stars earned (โ โ โ / โ โ โ / โ โโ) or ๐ if locked
- Best time if completed
- Click โ open Briefing Modal (or unlock message if locked)
3. Briefing Modal
Full-screen modal shown before level starts:
- Large icon/emoji
- Title (
LEVEL 4: CACHE THE DB) - Inline SVG diagram of starting architecture (auto-generated from
preBuilt) - Scenario text (2โ3 sentences)
- "๐ LEARN:" callout with educational text
- "๐ฏ GOAL:" primary objectives
- "โญ BONUS:" bonus objectives
- Buttons:
[Cancel][Start Level โ]
The diagram is generated client-side: each service is a colored circle (matching CONFIG.colors) connected by lines. ~50 lines of SVG-generating JS โ no external assets.
4. In-Game Objectives Panel (existing `objectivesPanel`, repurposed)
Replace static survival objectives with level objectives, real-time checkmarks:
๐ฏ Cache the DB
โ DB load <70% (currently 64%)
โ Survive 60s (12s elapsed)
โญ Hit rate >35% (currently 42%)Check function runs ~1ร/sec from animate loop.
5. Service Toolbar (existing, modified)
If allowedServices is set, gray out buttons NOT in the list. Click on grayed button shows tooltip "Not available in this level".
If allowedServices is empty/missing โ behave as today (all allowed).
6. Debrief Modal
- Win:
๐ LEVEL COMPLETE+ stars earned + time + debrief tip +[Replay][Next Level โ] - Lose:
โ FAILED: <reason>+ tip +[Retry][Back to Map]
State Management
New STATE fields:
STATE.campaign = {
active: false,
currentLevelId: null,
level: null, // current level config object
objectiveResults: {}, // { db_load: true, survive: false, ... }
bonusResults: {},
startedAt: 0,
ended: false,
outcome: null, // "win" | "lose"
};Existing game systems unchanged. Campaign hooks into animate() loop:
- Each tick: call
campaign.checkObjectives()ifSTATE.campaign.active - On win: pause game, show Debrief, persist stars
- On lose: pause game, show Debrief
Survival's auto-degradation and random-events systems are conditioned on STATE.gameMode === "survival" โ they naturally won't fire in Campaign because we'll use STATE.gameMode = "campaign". Smart hints also gated by gameMode and will skip Campaign.
File Layout
src/
campaign/
levels.js # exports CAMPAIGN_LEVELS array (declarative configs for all 14)
campaign.js # controller: load, checkObjectives, win/lose, persistence
objectives.js # reusable objective check helpers (dbLoadBelow, cacheHitRate, etc.)
diagram.js # SVG diagram generator for briefings (~50 lines)game.js gets:
startCampaign(),startCampaignLevel(id),exitToLevelSelect()window-level functions- A
gameMode === "campaign"branch inresetGame()to bootstrap from level config
index.html gets:
- Campaign button in main menu
- Level Select modal
- Briefing modal
- Debrief modal
Out of Scope (for first release)
- Full translations of level scenarios into 7 languages โ English only, with locale keys present for future contribution
- Custom 3D assets per level โ using emoji/SVG illustrations only
- Adaptive difficulty / hints during level โ players Retry as needed
- Tutorial integration โ Campaign IS the new tutorial path; existing 16-step tutorial stays as quick onboarding for Survival
Testing Strategy
No automated tests (project has none). Manual test plan per level:
- Open level briefing โ verify content renders correctly
- Pre-built architecture loads correctly
- Forced traffic mix applies
- Allowed services list grays out the rest
- Objectives update in real-time
- Win condition triggers Debrief with correct stars
- Lose condition triggers correct failure message
- Progress persists across page reloads
- Linear unlock works (level N+1 locked until N completed)
Open Questions
None at this time โ all major design points were settled during brainstorming.
Implementation Notes for the Planner
- Levels 1โ3 (Chapter 1) are the safest starting point โ clean slate, no
preBuiltcomplexity. Build these first to validate the framework. - Diagram generator should be data-driven so adding new levels doesn't require new SVG code.
- Objective check functions should be pure (
(state) => boolean) for testability and reusability. - Each modal should be self-contained CSS/HTML โ avoid coupling to existing modal logic.
- Use existing
glass-panelTailwind classes for visual consistency with current UI.
File: docs/superpowers/specs/2026-07-24-toolbar-categories-design.md
Toolbar categories โ design
Date: 2026-07-24
Status: approved
Problem
The bottom toolbar is one flat row of 30 buttons โ 7 controls plus 23 service
types โ roughly 2000px wide. On any normal screen the right end is off-screen
and the player has to scroll sideways to reach half the palette. Two aggravating
details:
- the container carries
overflow-y-auto(vertical) while the overflow is
horizontal, which is why the scrolling feels broken rather than merely long; help,musicandsfxoccupy the three leftmost slots โ the most valuable
real estate on the bar โ despite being settings, not gameplay.
The palette grew from 13 to 23 services across Wave 1 (#193) and Wave 2 will add
more, so "scroll further" is not a fix.
Approach
Group the services into five category tabs. Only the active category's buttons
are rendered, so the bar never scrolls.
The categories are not an arbitrary bucketing โ they are the cloud-domain map
from the Wave 1 epic (#193). A player absorbs the taxonomy (front door โ
compute โ data โ async โ ops) simply by using the toolbar, which serves the
project's education north star. Two rejected alternatives, for the record:
- pinned favourites + an "all services" pop-up grid โ fixes the mechanics
but teaches nothing and introduces a two-tier mental model; - a vertical side rail โ no horizontal scrolling ever, but it eats canvas
width and the left and right edges already hold four panels.
Categories
| Tab | Services | Count |
|---|---|---|
| Front door | dns, cdn, waf, auth, apigw, alb | 6 |
| Compute | compute, serverless, container | 3 |
| Data | db, nosql, cache, s3, search, replica, warehouse | 7 |
| Async | sqs, pubsub, stream, dlq, scheduler, notify | 6 |
| Ops | monitor | 1 |
The widest tab is 7 buttons (~480px), which fits comfortably on a laptop. Ops
holds a single service today; it stays its own category because it is a real
domain and the multi-region and chaos work will fill it.
Components
src/ui/toolbar.js owns the palette:
SERVICE_CATEGORIESโ plain data, one entry per tab with itslabelKeyand
its list of service types. Adding a service in a later wave means adding its
type to one array, matching the "one file plus one registry line" property the
handler registry already has (#155 PR 9).renderToolbar()โ draws the tab strip and the active category's buttons.setToolbarCategory(id)โ switches tab, re-renders, re-applies campaign
gating.
The button markup stays byte-identical to today โ same classes, sameid="tool-<type>", same cost badge, same data-i18n attributes. Campaign
gating, hover tooltips and locale switching all find their elements exactly as
before and need no changes.
index.html keeps only the shell: the bar, the always-visible tool cluster, the
tab strip and an empty `` that the module fills.
The four build tools (select, link, demolish, unlink) stay visible in every tab.
They are modes, not services; hiding them behind a tab would make every build
action a two-click affair.
Campaign interaction
applyCampaignToolbarGating looks buttons up by id and disables the ones a
level forbids. Tabs introduce a failure mode that does not exist today: level 2
allows only Storage, so a player sitting on the Front door tab sees nothing but
greyed-out buttons and reasonably concludes the game is broken.
Two mitigations, both required:
- After gating, switch automatically to the first tab that contains an allowed
service. - Show a small count of allowed services on each tab (
Data ยท 1) so the
player can see where the level's tools actually are.
Gating must also be re-applied on every tab switch โ freshly rendered buttons
would otherwise come up enabled regardless of the level's rules.
Smaller fixes carried in the same change
help,musicandsfxmove into a compact settings cluster (icon-only,
smaller) at the far edge of the bar.overflow-y-autobecomes the correct horizontal handling.- The active tab is remembered in
localStorage. - Keys
1โ5switch tabs. The existingEsc/H/R/Tshortcuts are
untouched.
Testing
The load-bearing test is a completeness invariant: every placeable type inCONFIG.services belongs to exactly one category. This catches the future
service that someone forgets to categorise and which would silently become
unreachable โ the same class of protection the locale-parity test provides.
Also covered:
- switching tabs preserves campaign gating;
- a campaign level auto-selects a tab that holds one of its allowed services;
- the settings cluster still drives
toggleMusic/toggleSfx; - rendering is idempotent (re-rendering does not duplicate buttons).
i18n
Five category labels across all nine locales. The i18n-usage test will fail on
any key referenced but not defined, so a missed translation cannot ship.
Constraints
No build step; native ESM; no new runtime dependencies. Vector SVG icons only.
The game must remain directly hostable on GitHub Pages, served raw.
File: docs/superpowers/specs/2026-07-30-ai-wave-design.md
The AI Wave โ GPU inference archetypes + Chapter 5
Date: 2026-07-30 (rev 2 โ after a 3-lens adversarial critique; 8 blockers integrated)
Status: approved (packaging + lean power confirmed by KP)
Issue: #87
Why
AI inference is the cloud story of right now, nobody teaches its
infrastructure interactively, and the community asked (#87). Every mechanic
passes the Wave 1 bar: distinguishable behavior or it doesn't ship.
Critique summary (what rev 2 fixes): the batch/deadline mechanics cannot live
in the per-job handler registry (both nodes are tick-nodes); the economy was
off by 4โ8ร (reward retuned $2.50 โ $0.50); the power gate was cheesable via
substation delete-refund; level 22's lesson was physically wrong (smoothing
starves batches); level 23's objective was uncheckable (per-reason counters
violate the #156 inertness contract); survival gating was a chicken-and-egg
deadlock.
1. INFERENCE traffic
TRAFFIC_TYPES.INFERENCE, color 0xd946ef, destination "gpu",
reward $0.50, score 15, not cacheable. STATE.failures gainsINFERENCE: 0; the save-load trafficDistribution fallback and the sandbox
mix panel gain the INFERENCE slider.
Variable duration: req.genLength at spawn โ 70% short (0.6โ1.0),
30% long (1.8โ3.0), mean 1.28. Processing scales by it. No other traffic has
per-request duration variance.
Survival staging (no deadlock, no dead GPU):
- Before 300s game time: base INFERENCE share 0.
- After 300s: base share 3% โ a legible slow bleed (โ1 rep per unserved) that
arms a smart hint ("AI demand is emerging โ GPUs serve it"). - Once the player owns โฅ1 GPU: base rebalances to 10% (taken proportionally
from other shares), so the GPU has steady food between hypes. - "AI hype wave" shift (INFERENCE โ 25%, classic shares scaled
proportionally; written out in CONFIG like existing shifts) enters the shift
rotation only while a GPU exists, evaluated at shift-SELECTION time only โ
losing the last GPU mid-shift does not cancel it.
Routing (exact isValidEdge additions):alb|apigw|compute|serverless|container โ infgw; infgw โ gpu;compute|serverless|container โ gpu; power: no edges (unwireable, like
monitor). genericForward becomes minimally type-aware: for INFERENCE it
prefers infgw then gpu targets; for everything else it EXCLUDES infgw/gpu from
candidates (they join dlq in the exclusion filter). The compute handler gets an
explicit INFERENCE branch: infgw first, direct gpu second, else NO_ROUTE.
A MALICIOUS request reaching gpu/infgw follows the existing failRequest breach
relabel โ the attack got through, consistent with #156.
2. GPU Cluster (`gpu`)
A tick-node (like stream/dlq/scheduler): processQueue() skips type gpu,
no SERVICE_HANDLERS entry. tickGpu(service, dt) drains service.queue
into service.batch while accumulating (game-time window timer โ freezes on
pause), then runs the whole batch as one job.
a) Batching. Accumulate to batchSize or batchWindowSec 1.5s from
first arrival; batchTimeMs = (900 + 150ยทn) ร meanGenLength(batch).
Full batch amortizes; a lonely request pays nearly full price.service.batch depth is folded into totalLoad, into deleteObject's
orphan set, and into triggerRegionOutage's teardown โ exactly asstream.partitions already are in all three sites (leak-battery mandatory).
b) Model cold start. service.modelLoading โ a DEDICATED flag; neverisDisabled (the event system re-enables disabled services โ using it would
let a pause cancel a model load). isRoutable gains one line:modelLoading โ false. Load times 12s/20s/30s by tier; tier upgrade
re-triggers the load. Held-arrival rule: mid-air arrivals and entries
queued when a (re)load starts stay in gpu.queue, age untouched, and batch
normally when the load completes โ the stall IS the lesson; holding satisfies
the termination invariant. The intake queue is BOUNDED at maxQueueSize
= batchSize (no big hidden buffer โ that's infgw's job); overflow = existing
QUEUE_FULL path.
c) Tiers = model size. batchSize 8/12/16 (the economic half);
qualityRisk 10%/4%/1% (the flavor half โ sold on the upgrade card with the
percentage printed).
d) Quality. The roll runs in tickGpu's completion loop only:finishRequest(req, "gpu", service) as usual, then on a hitSTATE.reputation += SCORE_POINTS.QUALITY_RISK_REPUTATION (โ0.5, new
constant) and service.badAnswers++. Legibility (a success-side event must
still be visible): an amber "Bad answer" soft badge over the GPU โ a new
spawn call in the badge module, never through failRequest, preserving the
#156 inertness invariant โ plus a bad answers: N (risk 10%) line in the GPU
tooltip next to batch fill.
Economics. Cost $300, upkeep $60/min, power draw 6 kW. At reward $0.50:
full-fill saturated profit โ +$29/min; break-even โ 52% batch fill in the
saturated (pipelined) regime โ that regime is what the mandated
profit/min-at-fill harness measures (20%/60%/100% fill, curve documented in
the PR). Accepts INFERENCE only (fail_gpu_only at intake in the tick).
Providers: AWS P5/Inferentia ยท Azure ND ยท GCP A3/TPU.
3. Inference Gateway (`infgw`)
A tick-node. Cost $70, upkeep $5/min (below apigw โ single-type
scope). Accepts INFERENCE only. Owns an array of {req, enqueuedAt: STATE.elapsedGameTime}. tickInfgw(service, dt):
- Sweep first: every entry older than
deadlineSec6s is spliced out,
thenfailRequest(req, SLO_TIMEOUT)โ neverfailOrPark(an SLO breach is
not recoverable work; explicit decision), never dispatch an expired entry
(exactly-once). - Then dispatch heads to the least-loaded routable GPU
(gpu.queue + gpu.batch + gpu.incomingCount). Warming/full fleet โ entries
stay, bounded by maxQueueSize (arrival overflow = QUEUE_FULL) plus expiry.
Expiry increments STATE.inference.expired (the resilience-counter
precedent) and service.expiredCount; CampaignObjectives.expiredRequests(s)
reads it. The #156 reason stays inert.
The array joins totalLoad / deleteObject / region-outage teardown.
Why it exists (the honest pitch, stated in its concept card): a
direct-wired GPU has only its tiny bounded intake โ during warmup or overload
requests die fast; infgw holds up to 20 with deadline honesty and dispatches
where the batch has room. Its value is measured in reputation saved during
those windows, not revenue. Providers: vLLM router ยท Triton ยท Bedrock.
4. Power
CONFIG.power = { baseCapKw: 8, gpuDrawKw: 6, substationKw: 6 } โ
substation +6 kW (so a 3-GPU fleet needs TWO substations: watts are a real
marginal decision, not one unlock). Substation: cost $150, upkeep $8/min, Ops
category, unwireable.
recomputePower()โ ONE function derivingSTATE.power = {usedKw, capKw}from live services; called from createService, deleteObject,
restoreServices, loadGameState, AND the campaign prebuild loop (which
constructs vianew Servicedirectly โ a call-site-driven recompute would
go stale there).- Placement gate: allowed when
usedKw + gpuDrawKw <= capKwโ boundary
inclusive (level 24's target sits exactly on it; one test pins this). - Deletion gate (anti-cheese): deleteObject REFUSES to remove a substation
when the reduced cap would strand powered GPUs ("unplug GPUs first",
i18n'd) โ otherwise buy-place-refund runs 18 kW on an 8 kW cap forever and
level 24 is beaten by the exploit. GPU deletion stays free. - HUD
kW used/capbadge only when a gpu or power node exists. - Share links:
encodeArchsorts services power-first and remaps every
index incandithrough the permutation (positionspreordered in
lockstep) โ the current serializer emits placement order, so this is a real
transform, not a comment. Share test: a power+gpu build round-trips to an
identical service set. - Region outages: substations unwireable โ never in a region subtree.
5. Chapter 5 โ "The AI Wave" (levels 21โ25)
Levels 1โ20 byte-identical. Every level proven beatable AND proven to fail on
its named ignore-run. Numbers below are tuning TARGETS โ the beatability
harness is the authority and the shipped comments document final arithmetic
(the Chapter 4 convention).
| # | Title | Teaches | Knobs (target) | Ignore-runs (must lose) |
|---|---|---|---|---|
| 21 | Hello, GPU | cold start + model quality | budget 480, rps 3.0, INF 60/READ 25/WR 10/MAL 5, prebuilt wafโalbโcomputeโdb, allowed [gpu]. Two acts: serve on tier 1; announced surge at 45s that tier 1's bad-answer bleed can't hold โ upgrade in the LULL (re-load done before the surge) wins; upgrading DURING the surge = self-inflicted outage | (a) no GPU โ serve-N unreachable; (b) upgrade mid-surge โ rep collapse |
| 22 | Batch or Bleed | utilization economics โ right-size the fleet | budget 750 (the 2nd-GPU trap is affordable), rps 3.5, INF 70%, allowed [gpu, infgw]; profit objective tuned so ONE near-full GPU clears it and TWO half-fed GPUs miss by ~$390 (least-loaded dispatch halves fill) | add a 2nd GPU โ profit target missed |
| 23 | The Deadline | SLO / queue time | prebuilt front door + infgw + 1 gpu + 1 substation (defuses the power lesson early), bursty INF ~5 rps, allowed [gpu]; primary: serve M AND failureRate < 0.12 AND expiredRequests < X โ the failure-rate leg kills the delete-the-gateway cheese (trading expiries for NO_ROUTEs still fails) |
(a) do nothing โ expiries breach X; (b) delete infgw + direct-wire โ failureRate breaches |
| 24 | The Power Wall | the cap is the constraint | budget 1400 (money plentiful), INF ~6.5 rps (above the tier-3 single-GPU ceiling ~3.8/s), allowed [gpu, power, infgw]; need 3 GPUs = 18 kW = base 8 + TWO substations; timeout sized for tier-3 30s cold starts | no substation โ capped at 1 GPU โ serve target unreachable |
| 25 | The AI Wave | capstone | budget 900, rps 10, forced base INF 12% + enableSurvivalShifts (hype escalation; ownership gate applies โ the base share does the teaching, the wave escalates), allowed all; objectives: survive 120s, rep โฅ 55, netProfit โฅ 0; bonus: badAnswers < 20 (a tier-1-only fleet provably can't hold rep) | no GPU at all โ the 12% base bleeds rep to a provable loss |
6. Cross-cutting
- Cardinal invariant: batch terminates with its batch or its node; expired
entries exactly once (never dispatched after expiry; the 500ms fail-fade
overlap is covered by splice-before-fail); held arrivals during model load
terminate post-load; teardown sites (deleteObject / region outage / clear)
drainbatch,infgwarray, and bounded queues. Leak battery: mid-batch
demolish, region outage over a busy GPU, expiry storm, all-GPU-warming,
share-rebuild of a power+gpu build. - Badges:
fail_slo_timeout("Deadline exceeded"),fail_gpu_only
("GPUs serve inference only") + the amber success-side "Bad answer" soft
badge (new spawn path, #156 contract intact). - Metrics: gpu/infgw are normal rows; batch fill, model-load state and
badAnswers in tooltips. GPUs do NOT auto-scale in v1 (future: queue-pressure
hook, noted in #220's pattern). - Toolbar: gpu, infgw โ Compute tab (3โ5); power โ Ops (1โ2).
- i18n ร9; concept cards ร3 with provider mappings.
- Constraints: no build step; native ESM; no new deps; levels 1โ20 and all
existing behavior byte-identical unless listed here.
File: docs/superpowers/plans/2026-05-24-campaign-mode.md
Campaign Mode Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add a 14-level Campaign Mode that teaches one cloud-infrastructure concept per level through hand-crafted scenarios with briefing, real-time objectives, and debrief.
Architecture: New src/campaign/ directory with four small modules (levels registry, objectives library, SVG diagram generator, controller). Hooks into existing STATE, Service, Request, and the animate loop via a new gameMode === "campaign" branch in resetGame(). Three new modals in index.html (Level Select, Briefing, Debrief). All EN-only on first release.
Tech Stack: Vanilla JS (ES6+), Three.js (existing), Tailwind CDN (existing). No build step. No automated test framework โ verification is manual browser checks per task.
Spec: docs/superpowers/specs/2026-05-24-campaign-mode-design.md
Branch: feat/campaign-mode (cut from current main)
Note for executor: This project has no test runner. Each "verify" step opens the game in a browser (python3 -m http.server) and runs a checklist. Be explicit with the user about what was clicked and what was observed. Never claim a step passes without actually clicking through it.
File Map (locked-in decomposition)
Created:
src/campaign/levels.jsโCAMPAIGN_LEVELSarray, all 14 level configs (data only)src/campaign/objectives.jsโ pure functions for objective checks (dbLoadBelow,cacheHitRate,totalFailures, etc.)src/campaign/diagram.jsโrenderArchitectureSVG(preBuilt, highlights)returning SVG stringsrc/campaign/campaign.jsโCampaignController(load, tick, win/lose, persistence). Exposeswindow.campaign.
Modified:
index.htmlโ script tags for new files, Campaign button in main menu, three new modal divsgame.jsโgameMode === "campaign"branch inresetGame(),animate()callscampaign.tick(), toolbar gating, new top-levelstartCampaign(),startCampaignLevel(id),exitCampaignToMap()src/state.jsโ addSTATE.campaigninitial structuresrc/locales/en.jsโ all EN i18n keys for campaign UI (other locales get same keys as fallback)src/locales/{zh,pt-BR,de,fr,ko,nep}.jsโ fallback keys
Untouched: Service.js, Request.js, SoundService.js, tutorial.js, config.js, style.css.
Task 1: Branch scaffold + empty modules + script wiring
Files:
Create:
src/campaign/levels.jsCreate:
src/campaign/objectives.jsCreate:
src/campaign/diagram.jsCreate:
src/campaign/campaign.jsModify:
index.html(script tags)Modify:
src/state.js(campaign state)Step 1: Create feature branch from latest main
cd /Users/kp/Projects/my/server-survival
git checkout main
git pull origin main
git checkout -b feat/campaign-mode- Step 2: Create empty module files with placeholder exports
src/campaign/levels.js:
// Campaign level definitions.
// Each level is a declarative config consumed by CampaignController.
// See docs/superpowers/specs/2026-05-24-campaign-mode-design.md for schema.
const CAMPAIGN_LEVELS = []; // Populated in Task 2src/campaign/objectives.js:
// Pure objective-check helpers. Each takes the live STATE and returns boolean.
// All checks are stateless and side-effect free.
const CampaignObjectives = {};src/campaign/diagram.js:
// SVG architecture diagram generator for Briefing modal.
// Renders a horizontal flow of service icons from a level's preBuilt array.
function renderArchitectureSVG(preBuilt, highlights) {
return '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="40"></svg>';
}src/campaign/campaign.js:
// Campaign mode controller. Single instance assigned to window.campaign.
class CampaignController {
constructor() {
this.active = false;
}
tick() {} // called from animate loop, no-op until Task 5
}
window.campaign = new CampaignController();- Step 3: Wire script tags in
index.html
In index.html, find the existing locale script block (around <script src="src/i18n.js"></script>). Add immediately after <script src="src/tutorial.js"></script> (or after i18n.js if tutorial.js isn't loaded there):
<script src="src/campaign/objectives.js"></script>
<script src="src/campaign/diagram.js"></script>
<script src="src/campaign/levels.js"></script>
<script src="src/campaign/campaign.js"></script>Order matters: campaign.js depends on the other three.
- Step 4: Add campaign state to
src/state.js
Inside the STATE object literal, after the existing intervention: { ... } block, add:
,
// Campaign mode runtime state. Populated by CampaignController when active.
campaign: {
active: false,
currentLevelId: null,
level: null, // level config object
objectiveResults: {}, // { objectiveId: boolean }
bonusResults: {},
startedAt: 0,
ended: false,
outcome: null, // "win" | "lose" | null
failureReason: null,
}- Step 5: Verify game still loads (manual)
Run python3 -m http.server 8091 & from project root. Open http://localhost:8091/index.html. Confirm:
Main menu renders
Browser console has zero errors
window.campaignexists in consoleCAMPAIGN_LEVELSexists (returns[])Step 6: Commit
git add src/campaign/ src/state.js index.html
git commit -m "feat(campaign): scaffold campaign module + state + script wiring"Task 2: Define all 14 levels in `levels.js`
Files:
Modify:
src/campaign/levels.js(replace placeholder with full data)Step 1: Replace
CAMPAIGN_LEVELSwith the full 14-level array
Open src/campaign/levels.js. Replace its entire contents with the block below.
Note on the schema: every level has the same shape so the controller can treat them uniformly. Objective check functions receive (STATE) and return boolean. They reference helpers from CampaignObjectives (defined in Task 3) โ these are not yet implemented but the references will resolve at call time because objectives.js loads before levels.js.
/* Detailed source-code truncated for AI context efficiency. */- Step 2: Reload browser, verify 14 levels parsed
Open http://localhost:8091/index.html and in console:
CAMPAIGN_LEVELS.length // 14
CAMPAIGN_LEVELS[0].title // "The First Server"
CAMPAIGN_LEVELS[13].title // "Black Friday"All three should match. No console errors.
- Step 3: Commit
git add src/campaign/levels.js
git commit -m "feat(campaign): define all 14 level configs"Task 3: Implement objective check helpers in `objectives.js`
Files:
- Modify:
src/campaign/objectives.js
These are referenced by level configs in Task 2. They must all be present.
- Step 1: Replace
objectives.jswith full implementation
/* Detailed source-code truncated for AI context efficiency. */- Step 2: Reload browser, smoke test
In console:
CampaignObjectives.totalFailures(STATE); // 0 (no failures yet)
CampaignObjectives.hasService(STATE, "waf"); // false (no services)
typeof CampaignObjectives.netProfit // "function"- Step 3: Commit
git add src/campaign/objectives.js
git commit -m "feat(campaign): objective check helper library"Task 4: SVG diagram generator in `diagram.js`
Files:
Modify:
src/campaign/diagram.jsStep 1: Replace
diagram.jswith implementation
/* Detailed source-code truncated for AI context efficiency. */- Step 2: Smoke test in console
const lvl = CAMPAIGN_LEVELS[3]; // "Cache the DB"
const svg = renderArchitectureSVG(lvl.preBuilt, lvl.diagramHighlights);
document.body.insertAdjacentHTML('beforeend', svg);Confirm an SVG appears at the bottom of the page: shows WWW โ WAF โ LB โ CPU โ SQL with the SQL node pulsing red. Remove the inserted SVG (refresh page) before continuing.
- Step 3: Commit
git add src/campaign/diagram.js
git commit -m "feat(campaign): SVG architecture diagram generator"Task 5: Campaign controller in `campaign.js`
Files:
- Modify:
src/campaign/campaign.js
This is the largest task. It owns: loading a level, tracking per-type/per-service completed counts, evaluating objectives every tick, win/lose detection, and progress persistence.
- Step 1: Replace
campaign.jswith full controller
/* Detailed source-code truncated for AI context efficiency. */- Step 2: Smoke test in console
campaign.loadProgress() // { version:1, completed:{}, highestUnlocked:1 }
campaign.isUnlocked(1) // true
campaign.isUnlocked(2) // false
campaign.totalStars() // 0
campaign.completedCount() // 0- Step 3: Commit
git add src/campaign/campaign.js
git commit -m "feat(campaign): controller with lifecycle, objectives, persistence"Task 6: HTML markup for the three Campaign modals
Files:
Modify:
index.htmlStep 1: Add the three modals before the closing `` tag
Find the existing `` tag in index.html. Immediately before it, insert:
<h2 class="text-3xl font-bold text-yellow-400" data-i18n="campaign_select_title">CAMPAIGN</h2>
ร
<h2 id="campaign-briefing-title" class="text-2xl font-bold text-white"></h2>
๐ Learn
๐ฏ Goals
โญ Bonus
โ Back
Start Level โ
<h2 id="campaign-debrief-title" class="text-3xl font-bold mb-2 text-white"></h2>
๐ก Tip
Retry
Next โ
Map- Step 2: Reload, verify modals exist but stay hidden
In console:
document.getElementById("campaign-select-modal").classList.contains("hidden") // true
document.getElementById("campaign-briefing-modal").classList.contains("hidden") // true
document.getElementById("campaign-debrief-modal").classList.contains("hidden") // true- Step 3: Commit
git add index.html
git commit -m "feat(campaign): add Level Select, Briefing, Debrief modal markup"Task 7: Campaign button in main menu + render functions
Files:
Modify:
index.html(button)Modify:
game.js(render and navigation functions)Step 1: Add Campaign button between Start Survival and Sandbox
In index.html, find the existing (Start Survival). Immediately AFTER its closing, add:
Campaign
0/14 โ
0- Step 2: Add navigation + render functions in
game.js
In game.js, after the existing window.startSandbox = ... block, add:
/* Detailed source-code truncated for AI context efficiency. */- Step 3: Verify in browser
Hard-reload. Click Campaign in main menu. Level Select modal should appear with:
- 3 chapter headers
- Level 1 unlocked (clickable, has โโโ), levels 2-14 with ๐
รbutton closes back to main menu
Click level 1 โ should error (no openCampaignBriefing yet). That's expected โ wired in Task 8.
- Step 4: Commit
git add index.html game.js
git commit -m "feat(campaign): main menu button + level select renderer"Task 8: Briefing modal renderer
Files:
Modify:
game.jsStep 1: Add briefing functions in
game.js
Append after the updateCampaignProgressLabel function:
let _pendingCampaignLevelId = null;
window.openCampaignBriefing = (levelId) => {
const level = CAMPAIGN_LEVELS.find((l) => l.id === levelId);
if (!level) return;
_pendingCampaignLevelId = levelId;
document.getElementById("campaign-select-modal").classList.add("hidden");
document.getElementById("campaign-briefing-modal").classList.remove("hidden");
document.getElementById("campaign-briefing-icon").textContent = level.icon;
document.getElementById("campaign-briefing-chapter").textContent =
`Chapter ${level.chapter} ยท Level ${level.id}`;
document.getElementById("campaign-briefing-title").textContent = level.title.toUpperCase();
document.getElementById("campaign-briefing-scenario").textContent = level.scenario;
document.getElementById("campaign-briefing-learn").textContent = level.learn;
document.getElementById("campaign-briefing-diagram").innerHTML =
renderArchitectureSVG(level.preBuilt, level.diagramHighlights);
document.getElementById("campaign-briefing-goals").innerHTML =
level.objectives.primary.map((o) => `<li>โข ${o.label}</li>`).join("");
document.getElementById("campaign-briefing-bonus").innerHTML =
level.objectives.bonus.map((o) => `<li>โข ${o.label}</li>`).join("");
};
window.campaignStartCurrentLevel = () => {
const id = _pendingCampaignLevelId;
if (!id) return;
document.getElementById("campaign-briefing-modal").classList.add("hidden");
startCampaignLevel(id);
};startCampaignLevel is wired in Task 9. For now this will error on click โ expected.
- Step 2: Verify in browser
Open game โ Campaign โ click Level 1.
Briefing modal opens
Icon (๐), chapter/level header, title
THE FIRST SERVER, scenario text all visibleSVG diagram renders (empty for level 1 since
preBuilt.services = [])Goals list and Bonus list shown
โ Backreturns to Level SelectStart Level โerrors in console (startCampaignLevel not defined) โ expected, Task 9 fixes thisStep 3: Commit
git add game.js
git commit -m "feat(campaign): briefing modal renderer"Task 9: In-game integration โ `resetGame` campaign branch + pre-build + toolbar gating
Files:
Modify:
game.jsStep 1: Add
startCampaignLevelfunction ingame.js
Append after campaignStartCurrentLevel:
/* Detailed source-code truncated for AI context efficiency. */- Step 2: Add
gameMode === "campaign"branch inresetGame
Find function resetGame(mode = "survival") in game.js. After the existing if (mode === "sandbox") { ... } else { ... } block, modify so campaign also uses survival defaults but with the level-set values applied later. The simplest change: at the very top of resetGame, treat "campaign" like "survival" for default initialization (rep=100, fresh state, etc.) but skip the survival traffic distribution / startBudget since the level overrides them.
Locate the if (mode === "sandbox") { block. Just before it, insert:
if (mode === "campaign") {
STATE.money = 0; // will be set by startCampaignLevel from level.budget
STATE.upkeepEnabled = true;
STATE.trafficDistribution = { STATIC: 0.3, READ: 0.2, WRITE: 0.15, UPLOAD: 0.05, SEARCH: 0.1, MALICIOUS: 0.2 };
STATE.currentRPS = 1; // overridden by level.rps
} else if (mode === "sandbox") {โฆand change the existing if (mode === "sandbox") { to } else if (mode === "sandbox") { โ i.e. weave the campaign branch into the existing if/else.
Verify the resulting flow looks like:
if (mode === "campaign") { ... }
else if (mode === "sandbox") { ... }
else { /* survival defaults */ }- Step 3: Disable survival's smart hints + ramp-up during campaign
Two spots:
(a) In animate(), find the existing block that ramps RPS up for survival:
if (STATE.gameMode === "survival") {
const gameTime = STATE.elapsedGameTime;
const targetRPS = calculateTargetRPS(gameTime);
STATE.currentRPS += (targetRPS - STATE.currentRPS) * 0.01;
...
}This already gates on survival only โ no change needed. โ
(b) checkSmartHints already starts with if (STATE.gameMode !== "survival") return; โ also no change needed. โ
(c) updateMaliciousSpike, updateTrafficShift, updateRandomEvents all gate on survival. โ
EXCEPT: level 14 has enableSurvivalShifts: true. To honor it, in updateTrafficShift change the early-return to also allow campaign when the level opts in:
function updateTrafficShift(dt) {
if (STATE.gameMode === "campaign") {
if (!STATE.campaign?.level?.enableSurvivalShifts) return;
} else if (STATE.gameMode !== "survival") {
return;
}
// ... rest unchanged
}Apply the same pattern to updateMaliciousSpike and updateRandomEvents.
- Step 4: Hide / show campaign-relevant UI panels
Find the existing block in resetGame() that toggles sandboxPanel / objectivesPanel visibility based on mode. Add a campaign branch:
if (mode === "campaign") {
if (sandboxPanel) sandboxPanel.classList.add("hidden");
if (objectivesPanel) objectivesPanel.classList.remove("hidden");
} else if (mode === "sandbox") {
// ... existing sandbox branch
} else {
// ... existing survival branch
}- Step 5: Verify in browser
Open game โ Campaign โ Level 1 โ Start Level โ.
- Game canvas renders
- Money shows $300
- Toolbar: WAF, ALB, Compute, SQL DB, S3 fully colored; everything else faded
- Game is at 1ร (running)
- Console: no errors
STATE.gameMode === "campaign"andSTATE.campaign.active === true
Try clicking a grayed-out tool (e.g. Cache) โ nothing happens (pointer events blocked). โ
- Step 6: Commit
git add game.js
git commit -m "feat(campaign): in-game integration, pre-built services, toolbar gating"Task 10: Wire `onRequestCompleted` hook for per-service tracking
Files:
- Modify:
game.js
The objective replicaShareOfReads and nosqlShareOfWrites need to know which service ultimately completed each request. We bump these counters from finishRequest.
- Step 1: Update
finishRequestingame.jsto notify campaign
Find function finishRequest(req). Replace it with:
function finishRequest(req, viaServiceType) {
STATE.requestsProcessed++;
updateScore(req, "COMPLETED");
if (window.campaign?.active) {
window.campaign.onRequestCompleted(req, viaServiceType);
}
removeRequest(req);
}- Step 2: Pass
viaServiceTypefromService.jscall sites
This is a delicate change โ Service.update() calls finishRequest(job.req) in several places. Each call site already knows this.type. Update each call to pass this.type as the second argument.
Open src/entities/Service.js. Search for every finishRequest(job.req) occurrence (there are roughly 8 โ one each for db, nosql, s3, cache (hit branch), cdn (hit branch), search, replica, plus any flyTo chains that eventually settle). For each, change:
finishRequest(job.req);to:
finishRequest(job.req, this.type);Do NOT change failRequest or throttleRequest calls โ those are for failures.
- Step 3: Verify in browser
Reload, start Campaign Level 6 ("Scale Reads") which has pre-built Cache + DB. Place a Read Replica, connect ComputeโReplica, ReplicaโDB. Let traffic flow for ~10s. In console:
STATE.campaign.completedByService // { db: N, cache: M, replica: K }
STATE.campaign.completedByType // { READ: X, WRITE: Y, ... }
CampaignObjectives.replicaShareOfReads(STATE) // should be > 0- Step 4: Commit
git add game.js src/entities/Service.js
git commit -m "feat(campaign): per-service completion tracking for objective evaluation"Task 11: Objectives panel rendering + animate loop hook
Files:
Modify:
game.jsStep 1: Add
renderCampaignObjectivesfunction ingame.js
Append after applyCampaignToolbarGating:
function renderCampaignObjectives(level, primaryResults, bonusResults) {
const panel = document.getElementById("objectivesPanel");
if (!panel) return;
// Hide the static survival objectives, render campaign list instead
panel.classList.remove("hidden");
const primaryHtml = level.objectives.primary.map((o) => {
const done = primaryResults[o.id];
const icon = done ? "โ" : "โ";
const color = done ? "text-green-400" : "text-gray-400";
return `<li class="${color}">${icon} ${o.label}</li>`;
}).join("");
const bonusHtml = level.objectives.bonus.map((o) => {
const done = bonusResults[o.id];
const icon = done ? "โญ" : "โ";
const color = done ? "text-yellow-300" : "text-gray-500";
return `<li class="${color}">${icon} ${o.label}</li>`;
}).join("");
panel.innerHTML = `
<h3 class="text-xs font-bold text-yellow-400 uppercase tracking-wider">
Level ${level.id}: ${level.title}
</h3>
${Math.round(STATE.elapsedGameTime)}s / ${level.durationSec}s
${primaryHtml}
Bonus
${bonusHtml}`;
}- Step 2: Hook
campaign.tick(dt)intoanimateloop
In animate(), find where STATE.elapsedGameTime += dt; happens. After it, add:
if (window.campaign?.active) window.campaign.tick(dt);- Step 3: Verify in browser
Start any campaign level. Watch the bottom-left objectives panel โ it should now show level objectives with live checkmarks updating every 0.5s. Timer counts up.
- Step 4: Commit
git add game.js
git commit -m "feat(campaign): live objective rendering and animate-loop tick"Task 12: Debrief modal + win/lose flow
Files:
Modify:
game.jsStep 1: Add
showCampaignDebrief+ nav functions
Append in game.js:
/* Detailed source-code truncated for AI context efficiency. */- Step 2: Verify in browser
Start Campaign Level 1. Build the required pipeline (InternetโWAFโALBโComputeโDB). Let traffic process. When you hit 50 READ + rep>=80%, Debrief modal opens with ๐ LEVEL COMPLETE, star rating, tip, and Next โ button.
Refresh page. Open Campaign menu โ Level 1 should show โ
count, Level 2 should now be unlocked (clickable, not ๐). Progress label shows 1/14 โ
N.
Test failure: start Level 1 again, do nothing, wait until rep drops below 50 (DDoS leaks). Debrief shows โ LEVEL FAILED with reason.
- Step 3: Commit
git add game.js
git commit -m "feat(campaign): debrief modal + win/lose persistence flow"Task 13: EN i18n keys + locale fallback
Files:
Modify:
src/locales/en.jsModify:
src/locales/{zh,pt-BR,de,fr,ko,nep}.js(fallback only)Step 1: Add keys to
en.js
Find the closing }; of EN_TRANSLATIONS. Before it, add a trailing comma on the previous line if needed and insert:
"campaign_mode": "Campaign",
"campaign_select_title": "CAMPAIGN",
"campaign_briefing_start": "Start Level",
"campaign_briefing_back": "Back",
"campaign_complete": "LEVEL COMPLETE",
"campaign_failed": "LEVEL FAILED",
"campaign_retry": "Retry",
"campaign_next": "Next",
"campaign_map": "Map",
"campaign_chapter_1": "Chapter 1: Basics",
"campaign_chapter_2": "Chapter 2: Optimization",
"campaign_chapter_3": "Chapter 3: Defense & Mastery",
"campaign_locked": "Locked",- Step 2: Add the same keys with English values to all other locale files
Repeat the same block in each of zh.js, pt-BR.js, de.js, fr.js, ko.js, nep.js. Use the English values verbatim โ they serve as fallback. Future contributors translate.
- Step 3: Verify in browser
Switch language to e.g. German via the language dropdown. Open Campaign menu โ labels still show in English (correct fallback). No console errors.
- Step 4: Commit
git add src/locales/
git commit -m "feat(campaign): i18n keys (EN with fallback in other locales)"Task 14: Final integration test + PR
Files: none modified โ verification only.
- Step 1: Full playthrough checklist
Open game in browser. Run through each item, ticking only after literally observing it:
Main menu shows
Campaign 0/14 โ 0Click Campaign โ Level Select opens, 3 chapter headers visible, level 1 unlocked, levels 2-14 locked
Click Level 1 โ Briefing opens, SVG diagram visible (empty for level 1), Goals list & Bonus list correct
Start Level โ game runs at 1ร, money = $300, toolbar grays out cache/sqs/cdn/etc.
Build pipeline, hit objectives โ Debrief shows โ โ โ (or โ โ โ/โ โโ), tip, Next button
Refresh page โ progress persists, level 2 unlocked, badge shows
1/14 โ 1(or higher)Start Level 2 โ Briefing shows pre-built pipeline in diagram, game loads with 4 services placed and connected
Start Level 4 ("Cache the DB") โ SQL DB pulses red in diagram, pre-built pipeline visible in-game
Start Level 12 ("High Availability") โ after ~30s a WAF goes offline (transparent), warning shown
Start Level 14 โ traffic shifts fire (e.g. "Read Heavy" warning appears)
Failure: start Level 11, do nothing, watch reputation crash โ Debrief
โwith reasonSurvival mode (separate test) still works unchanged
Sandbox mode still works unchanged
Switch language to non-EN, Campaign UI shows English fallback, no console errors
Step 2: Push and open PR
/* Detailed source-code truncated for AI context efficiency. */- Step 3: Confirm PR URL with user
PR opens in browser-visible URL. Done.
Self-Review
Spec coverage:
- 14 levels in 3 chapters: โ Task 2
- Star rating (1 completion + bonus + speedrun): โ
Task 5 (
_calculateStars) - Linear unlock: โ
Task 5 (
isUnlocked,_persistWin) - Briefing with SVG diagram: โ Tasks 4, 8
- Real-time objectives: โ Task 11
- Debrief with tip: โ Task 12
- Toolbar gating per level: โ
Task 9 (
applyCampaignToolbarGating) - Pre-built starting architectures: โ
Task 9 (
startCampaignLevel) - localStorage persistence: โ Task 5
- EN-only with fallback: โ Task 13
- Reuses existing Service/Request systems: โ no new simulation
- Smart hints disabled in campaign: โ
existing gate (
STATE.gameMode !== "survival") - New STATE.campaign field: โ Task 1 Step 4
- New gameMode "campaign": โ Task 9 Step 2
- Burst patterns + forced outages: โ
Task 5 (
tick) - Survival shifts in finale: โ
Task 9 Step 3 (
enableSurvivalShiftsgate)
Placeholder scan: Re-read all tasks. No "TBD", "implement later", or "similar to Task N" placeholders. All code blocks contain real code. โ
Type/name consistency:
CAMPAIGN_LEVELSused in Tasks 2, 5, 7, 8, 9, 12 โCampaignObjectivesused in Tasks 2 (refs), 3 (defs) โrenderArchitectureSVGused in Tasks 4 (def), 8 (call) โCampaignController.onRequestCompletedused in Tasks 5 (def), 10 (call) โrenderCampaignObjectivesused in Tasks 5 (call via tick), 11 (def) โ Task 11 must come before Task 5's first run; in practice Task 5 only calls itif (typeof renderCampaignObjectives === "function")so it's safe โshowCampaignDebriefused in Tasks 5 (call), 12 (def) โ same pattern, safe โ- localStorage key
serverSurvivalCampaignProgressconsistent โ - All level config field names (
durationSec,preBuilt,allowedServices,forbiddenServices,burstPattern,forceOutageAtSec,enableSurvivalShifts,diagramHighlights,failConditions,debriefTip) appear identically in Task 2 (defs) and Task 5/9 (use) โ
File: docs/GAME_BALANCE_ANALYSIS.md
Game Balance Mathematical Model
This document provides a mathematical analysis of Server Survival's game balance, addressing issue #17.
Table of Contents
- Game Parameters Summary
- Sustainability Threshold Analysis
- Economic Viability Analysis
- Optimal Ratios Analysis
- Upgrade Timing Analysis
- Failure Cascade Analysis
- Recommendations
Game Parameters Summary
Starting Conditions
- Starting Budget: $340
- Base RPS: 0.6 req/s
- RPS Growth Formula:
RPS = 0.6 + ln(1 + t/30) ร 1.8
Traffic Distribution
| Type | Percentage | Destination | Reward | Processing Weight |
|---|---|---|---|---|
| STATIC | 30% | S3 | $0.80 | 0.5 |
| READ | 20% | DB | $1.20 | 1.0 |
| WRITE | 15% | DB | $1.80 | 1.5 |
| UPLOAD | 5% | S3 | $2.00 | 2.0 |
| SEARCH | 10% | DB | $1.20 | 2.5 |
| MALICIOUS | 20% | Blocked | $0.00 | 1.0 |
Legitimate Traffic: 80% (S3: 35%, DB: 45%)
Service Costs & Capacities
| Service | Cost | Upkeep/s | Capacity | Processing Time (ms) |
|---|---|---|---|---|
| WAF | $40 | $4/min | 100 | 20 |
| ALB | $50 | $6/min | 50 | 50 |
| Compute T1 | $60 | $12/min | 4 | 600 |
| Compute T2 | +$100 | $12/min | 10 | 600 |
| Compute T3 | +$160 | $12/min | 18 | 600 |
| S3 | $25 | $5/min | 100 | 200 |
| DB T1 | $150 | $24/min | 8 | 300 |
| DB T2 | +$200 | $24/min | 20 | 300 |
| DB T3 | +$350 | $24/min | 35 | 300 |
| Cache T1 | $60 | $8/min | 30 | 50 |
| SQS | $35 | $2/min | 10 (queue: 200) | 100 |
Upkeep Scaling
- Multiplier scales from 1.0ร to 2.0ร over 600 seconds (10 minutes)
- Formula:
multiplier = 1.0 + (2.0 - 1.0) ร min(t/600, 1.0)
Failure Formula
failChance(load) = 0 if load โค 0.5
= 2 ร (load - 0.5) if load > 0.51. Sustainability Threshold Analysis
Question: At what RPS does minimal architecture become overloaded?
Minimal Architecture: WAF โ ALB โ Compute (T1) โ S3/DB
Throughput Calculations
Compute Node (T1):
- Capacity: 4 concurrent requests
- Processing time: 600ms
- Max throughput:
4 / 0.6s = 6.67 req/s - But only 80% of traffic is legitimate: effective capacity for legitimate =
6.67 ร 0.8 = 5.33 req/s
Database (T1):
- Capacity: 8 concurrent requests
- Processing time: 300ms
- Max throughput:
8 / 0.3s = 26.67 req/s - DB handles 45% of traffic: can handle up to
26.67 / 0.45 = 59.3 RPStotal
S3:
- Capacity: 100 concurrent requests
- Processing time: 200ms
- Max throughput:
100 / 0.2s = 500 req/s - S3 handles 35% of traffic: can handle up to
500 / 0.35 = 1428 RPStotal
WAF:
- Capacity: 100, Processing time: 20ms
- Max throughput:
100 / 0.02s = 5000 req/s(not a bottleneck)
ALB:
- Capacity: 50, Processing time: 50ms
- Max throughput:
50 / 0.05s = 1000 req/s(not a bottleneck)
Bottleneck Analysis
The Compute node is the primary bottleneck at 6.67 req/s theoretical max.
However, the failure formula kicks in at 50% load:
- At 50% load (3.33 req/s), failure chance = 0%
- At 75% load (5 req/s), failure chance = 50%
- At 100% load (6.67 req/s), failure chance = 100%
Safe operating RPS for single Compute T1: ~3.3 req/s (50% capacity)
Marginal operating RPS: ~4-5 req/s (increasing failures)
Time to Reach Critical RPS
Using the RPS formula: RPS = 0.6 + ln(1 + t/30) ร 1.8
| RPS | Time | Status |
|---|---|---|
| 1.0 | 0:07 | Safe |
| 2.0 | 0:32 | Safe |
| 3.0 | 1:22 | Safe |
| 3.3 | 1:41 | 50% load (failure starts) |
| 4.0 | 3:04 | ~25% failure rate |
| 5.0 | 6:26 | ~50% failure rate |
| 6.0 | 12:55 | ~75% failure rate |
Conclusion: With minimal infrastructure, the system becomes unsustainable around 3-4 RPS (reached at ~2-3 minutes). Players MUST expand before this point.
2. Economic Viability Analysis
Question: What's the minimum infrastructure to become cash-flow positive?
Income Calculation
Average reward per legitimate request:
Avg Reward = (0.30 ร $0.80) + (0.20 ร $1.20) + (0.15 ร $1.80) + (0.05 ร $2.00) + (0.10 ร $1.20)
= $0.24 + $0.24 + $0.27 + $0.10 + $0.12
= $0.97 per legitimate requestSince 80% of traffic is legitimate:
Expected income = RPS ร 0.8 ร $0.97 = RPS ร $0.776/s
Minimum Viable Architecture Cost
Essential services:
- WAF: $40 (to block malicious traffic)
- Compute T1: $60 (to process requests)
- S3: $25 (for STATIC/UPLOAD - 35% of traffic)
- DB T1: $150 (for READ/WRITE/SEARCH - 45% of traffic)
Total minimum cost: $275
Starting budget: $340
Remaining: $65
Minimum upkeep (at game start, 1.0ร multiplier):
WAF: $4/min + Compute: $12/min + S3: $5/min + DB: $24/min = $45/min = $0.75/sBreak-even Analysis
Break-even RPS (at 1.0ร upkeep):
Income = Upkeep
RPS ร $0.776/s = $0.75/s
RPS = 0.97 req/sAt game start (0.6 RPS):
- Income: $0.466/s
- Upkeep: $0.75/s
- Net: -$0.284/s (losing money!)
Time to reach break-even (0.97 RPS):
- Approximately 5-6 seconds into the game
With Upkeep Scaling
At 10 minutes (2.0ร multiplier):
- Upkeep: $1.50/s
- Break-even RPS: 1.93 req/s
- Actual RPS at 10 min: ~6.5 req/s
- Income at 6.5 RPS: $5.04/s
- Net at 10 min: +$3.54/s (profitable!)
Cash Flow Projection (Minimum Architecture)
| Time | RPS | Income/s | Upkeep/s | Net/s | Cumulative |
|---|---|---|---|---|---|
| 0:00 | 0.6 | $0.47 | $0.75 | -$0.28 | $65 |
| 0:30 | 1.8 | $1.40 | $0.79 | +$0.61 | $57 |
| 1:00 | 2.4 | $1.86 | $0.83 | +$1.03 | $74 |
| 2:00 | 3.3 | $2.56 | $0.90 | +$1.66 | $153 |
| 3:00 | 4.0 | $3.10 | $0.98 | +$2.12 | $267 |
| 5:00 | 4.9 | $3.80 | $1.13 | +$2.67 | $493 |
Conclusion: The game IS economically viable. After the first ~30 seconds, income exceeds upkeep. However, this doesn't account for the need to EXPAND infrastructure to handle increasing RPS.
Expansion Budget Requirements
When RPS hits 3.3 (at ~1:41), a second Compute is needed:
- Cost: $60
- By 1:41, approximate funds: ~$100-120
- Affordable? YES
When RPS hits 6.67 (theoretical 2ร compute capacity), third Compute or upgrade needed:
- Compute T2 upgrade: $100 (better value)
- By ~6 minutes, approximate funds: ~$400+
- Affordable? YES
3. Optimal Ratios Analysis
Question: What is the correct ratio of EC2:S3:RDS?
Traffic Load Distribution
Per 100 requests:
- S3 bound: 30 STATIC + 5 UPLOAD = 35 requests
- DB bound: 20 READ + 15 WRITE + 10 SEARCH = 45 requests
- WAF blocked: 20 MALICIOUS
Processing Weight Analysis
Weighted load (using processingWeight):
- S3: (30 ร 0.5) + (5 ร 2.0) = 15 + 10 = 25 weighted units
- DB: (20 ร 1.0) + (15 ร 1.5) + (10 ร 2.5) = 20 + 22.5 + 25 = 67.5 weighted units
DB receives 2.7ร more weighted load than S3
Throughput Requirements at Various RPS
| RPS | S3 req/s | DB req/s | Compute req/s |
|---|---|---|---|
| 3 | 1.05 | 1.35 | 2.4 |
| 5 | 1.75 | 2.25 | 4.0 |
| 7 | 2.45 | 3.15 | 5.6 |
| 10 | 3.50 | 4.50 | 8.0 |
Capacity Analysis
S3 (single node):
- Max: 500 req/s (S3-bound only)
- Can handle: 500 / 0.35 = 1428 total RPS
- One S3 is sufficient for any realistic RPS
DB T1 (single node):
- Max: 26.67 req/s (DB-bound only)
- Can handle: 26.67 / 0.45 = 59 total RPS
- At 7 RPS (3.15 DB req/s): ~12% utilization
- One DB T1 is sufficient until very high RPS
Compute T1:
- Max: 6.67 req/s
- At 3.3 RPS: 50% load (safe max)
- Need to scale Compute most frequently
Optimal Ratio Recommendation
For RPS 0-3.3: 1 Compute : 1 S3 : 1 DB (minimum viable)
For RPS 3.3-6.7: 2 Compute : 1 S3 : 1 DB
For RPS 6.7-10: 3 Compute : 1 S3 : 1 DB or 1 Compute T2 + 1 T1 : 1 S3 : 1 DB
For RPS 10+: 2 Compute T2 : 1 S3 : 1 DB or upgrade DB
The intuition of 2:1:1 is correct for mid-game (3-7 RPS).
4. Upgrade Timing Analysis
Question: When to upgrade vs. buy new?
Compute: Upgrade vs. New Node
| Option | Cost | Total Capacity | Cost per Capacity |
|---|---|---|---|
| 2ร Compute T1 | $120 | 8 | $15/slot |
| 1ร Compute T2 | $160 | 10 | $16/slot |
| 3ร Compute T1 | $180 | 12 | $15/slot |
| 1ร Compute T3 | $320 | 18 | $17.78/slot |
| 1ร T2 + 1ร T1 | $220 | 14 | $15.71/slot |
Upkeep Consideration:
- 2ร Compute T1: $24/min
- 1ร Compute T2: $12/min (SAME as T1!)
Recommendation:
- Upgrade to T2 is MORE COST-EFFECTIVE because:
- Same upkeep as T1 ($12/min)
- Better capacity per dollar when considering upkeep savings
- Saves a slot for ALB connections
Optimal Path: T1 โ T2 ($100) โ T3 ($160) = $320 total for 18 capacity
Alternative: 4ร T1 = $240 for 16 capacity BUT $48/min upkeep vs $12/min
Upgrade T2 when: You have $100+ and need to go beyond 3.3 RPS
Upgrade T3 when: You have $160+ and need to go beyond 6.7 RPS
Database: Upgrade vs. New Node
| Option | Cost | Total Capacity | Upkeep |
|---|---|---|---|
| 2ร DB T1 | $300 | 16 | $48/min |
| 1ร DB T2 | $350 | 20 | $24/min |
| 1ร DB T3 | $700 | 35 | $24/min |
Recommendation:
- Always upgrade DB - upkeep savings are massive
- A second DB doubles your upkeep; an upgrade doesn't
5. Failure Cascade Analysis
Question: At what load does a node become useless?
Failure Rate Analysis
Load | Fail Chance | Success Rate | Effective Throughput
--------|-------------|--------------|---------------------
0-50% | 0% | 100% | 100% of processed
60% | 20% | 80% | 80% of processed
70% | 40% | 60% | 60% of processed
80% | 60% | 40% | 40% of processed
90% | 80% | 20% | 20% of processed
100% | 100% | 0% | 0% of processedEffective Capacity Curve
The effective throughput (actual successful requests) peaks at around 66% load:
Effective = Load ร (1 - FailChance)
= Load ร (1 - 2ร(Load - 0.5)) for Load > 0.5
= Load ร (2 - 2รLoad)
= 2รLoad - 2รLoadยฒ
d(Effective)/d(Load) = 2 - 4รLoad = 0
Load = 0.5 โ Maximum effective throughput at 50% loadWait - this means operating at exactly 50% is optimal!
At 50% load:
- Effective throughput = 50% capacity ร 100% success = 50% of max
At 75% load:
- Effective throughput = 75% capacity ร 50% success = 37.5% of max
At 100% load:
- Effective throughput = 100% capacity ร 0% success = 0% of max
Critical Finding: The 50% Threshold
The current failure formula is VERY punishing:
- Operating above 50% load is mathematically inefficient
- A node at 75% load performs WORSE than a node at 50% load
- Nodes become "effectively useless" above ~70-75% load
Is This Too Punishing?
Arguments for current system:
- Forces players to plan ahead
- Creates tension and challenge
- Rewards proactive scaling
Arguments against:
- 50% threshold means you only use half your capacity safely
- Steep curve is unforgiving
- Players may feel infrastructure is "wasted"
Suggested Alternative (softer curve):
// Current: Linear from 50%
failChance = 2 ร (load - 0.5) // 50% at 75%, 100% at 100%
// Alternative: Exponential from 60%
failChance = Math.pow((load - 0.6) / 0.4, 2) // Gentler, starts at 60%
// Alternative: Logarithmic
failChance = load > 0.7 ? Math.log(1 + (load-0.7)*10) / 3 : 0Recommendations
Parameter Changes
Failure Threshold (Medium Priority)
- Current: Starts at 50% load
- Suggested: Start at 60% load
- Rationale: Allows better utilization of purchased capacity
RPS Growth Rate (High Priority)
- Current: Logarithmic (
ln(1 + t/30) ร 1.8) - Issue: Too slow for experienced players (14.7 RPS overnight)
- Suggested: Add difficulty modes
- Easy: Current formula
- Normal:
ln(1 + t/20) ร 2.2 - Hard: Linear or exponential growth
- Current: Logarithmic (
Starting Budget (OK)
- Current: $340
- Analysis: Adequate for minimum viable architecture ($275) with $65 buffer
- No change needed
Upkeep Scaling (OK)
- Current: 1.0ร to 2.0ร over 10 minutes
- Analysis: Creates meaningful late-game pressure
- No change needed
Answer Summary
| Question | Answer |
|---|---|
| 1. Sustainability Threshold | ~3.3 RPS for single Compute T1 (reached at ~1:41) |
| 2. Economic Viability | YES - cash-flow positive after ~30 seconds |
| 3. Optimal Ratios | 2:1:1 (Compute:S3:DB) for mid-game; Compute scales first |
| 4. Upgrade Timing | Always upgrade (especially DB) - upkeep savings are massive |
| 5. Failure Cascade | Nodes become ineffective above 50% load; this IS punishing |
Is the Game Beatable?
YES - with proper play:
- Build WAF โ Compute โ S3 โ DB immediately
- Upgrade Compute to T2 around 2-3 minutes
- Keep Compute below 50% load
- Upgrade DB only when needed (it's expensive but scales well)
- The game becomes a "solved" steady state after ~15-20 minutes if infrastructure keeps pace
Is Death Inevitable?
NO - but growth slows dramatically:
- At 10 minutes: ~6.5 RPS
- At 30 minutes: ~9 RPS
- At 1 hour: ~11 RPS
- At overnight (8h): ~15 RPS
With proper infrastructure (3ร Compute T3 or 6ร T2, 1ร DB T2), this is sustainable indefinitely.
Appendix: RPS Milestone Table
| RPS | Time (MM:SS) | Compute Needed | Notes |
|---|---|---|---|
| 1.0 | 00:07 | 1ร T1 | Safe |
| 2.0 | 00:32 | 1ร T1 | Safe |
| 3.0 | 01:22 | 1ร T1 | Near limit |
| 3.3 | 01:41 | 2ร T1 or T2 | Scale NOW |
| 4.0 | 03:04 | 2ร T1 or T2 | |
| 5.0 | 06:26 | 2ร T1 or T2 | |
| 6.0 | 12:55 | 3ร T1 or T2+T1 | |
| 6.7 | 17:00 | T3 or 2ร T2 | |
| 7.0 | 25:19 | T3 or 2ร T2 | |
| 8.0 | 49:10 | T3+T1 or 2ร T2 | |
| 10.0 | 3:05:32 | 2ร T3 or 3ร T2 | Late game |
Document created for Issue #17 - Calculate game balance mathematical model