server-survival Documentation

Tower defense game that teaches cloud architecture. Build infrastructure, survive traffic, learn scaling.

6,293 stars JavaScript JavaScript 1 file ยท ~21,698 tokens #aws#cloud#devops#education#game#javascript#learning#open-source
RAW Doc

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:

  1. Basics (levels 1-3) โ€” your first server, storage, and edge caching with a CDN.
  2. Optimization (levels 4-10) โ€” caching, queues, read scaling, search, NoSQL, rate limiting, serverless vs compute.
  3. Defense & Mastery (levels 11-14) โ€” defense in depth, high availability, cost crunches, and Black Friday.
  4. Production Readiness (levels 15-20) โ€” observability, auto-scaling, node failures, dead-letter queues, fan-out, and two-region failover.
  5. 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 R to reset the camera position.
  • Birds-Eye View: Press T to switch between isometric and top-down view.
  • Hide HUD: Press H to toggle UI panels.
  • Category Tabs: The toolbar groups services into five tabs โ€” Front Door, Compute, Data, Async, Ops. Keys 1-5 switch 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

  1. 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.
  2. Use CDN for Static Content: Connect Internet -> CDN -> Storage. The CDN handles 95% of static traffic cheaply!
  3. Watch Service Health: Damaged services have reduced capacity. Click to repair or enable Auto-Repair.
  4. Scale for Traffic Surges: RPS multiplies at milestones - prepare before ร—2.0 at 3 minutes!
  5. Balance Income vs Upkeep: Start lean, scale as income grows. Over-provisioning leads to bankruptcy.
  6. Use Cache Wisely: Reduces database load significantly for READ requests.
  7. Buffer with Queue: Queue helps survive traffic burst events without dropping requests โ€” and a queue-fed Compute fleet auto-scales on queue depth.
  8. React to Events: Watch the event bar - cost spikes mean hold off on purchases, traffic bursts mean ensure capacity.
  9. API Gateway for Graceful Degradation: Throttled requests only lose -0.2 reputation (vs -1.0 for failures). Great for surviving traffic spikes!
  10. Split DB Traffic with NoSQL: Route READ/WRITE to NoSQL (faster, cheaper) and keep the Relational DB for SEARCH queries only.
  11. 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.
  12. Read Replica for READ offloading: Under "API Heavy" or "Read Heavy" shifts, a Read Replica prevents your main DB from drowning in READ requests.
  13. Serverless Function = pay-per-use: Great for spiky traffic, but monitor finances โ€” per-request cost ($0.03) adds up fast at high RPS.
  14. Place Monitoring early: The metrics dashboard shows error rates and latency before the failure badges start raining.
  15. 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:

bash
git clone https://github.com/pshenok/server-survival.git
cd server-survival
python3 -m http.server 8000    # then open http://localhost:8000

For Contributors

There is still zero build step โ€” the dev tooling is optional and for contributors only:

  • npm install once, then npm run check runs ESLint + the full Vitest suite (47 test files, 989 tests).
  • CI runs the same check on every PR.
  • The code is native ESM: game.js plus focused modules under src/ (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

text
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 / Request classes โ€” 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

  1. Level registry (src/campaign/levels.js) โ€” declarative array of all 14 level configs
  2. Campaign controller (src/campaign/campaign.js) โ€” loads level, tracks objectives, evaluates win/lose, persists progress
  3. UI overlays (Level Select, Briefing, Debrief modals โ€” added to index.html)

Level Format (data shape)

Each level is a declarative object. Example:

text
/* Detailed source-code truncated for AI context efficiency. */

The 14 Levels

Chapter 1: Basics (clean slate, learn the pipeline)

  1. The First Server โ€” Internetโ†’WAFโ†’ALBโ†’Computeโ†’DB. Process 50 READ. Budget $300.
  2. Store the Files โ€” Add Storage for UPLOAD. Process 30 UPLOAD.
  3. Edge with CDN โ€” 80% STATIC traffic. Add CDN, survive 60s with DB load <50%.

Chapter 2: Optimization (broken architecture, fix it)

  1. Cache the DB โ€” DB melts under READ. Add Cache. DB load <70%.
  2. Buffer the Spikes โ€” Bursty traffic. Add Queue. 0 drops in 90s.
  3. Scale Reads โ€” "API Heavy" shift active. Add Read Replica. DB load <60%.
  4. Search Done Right โ€” "Search Storm" (50% SEARCH). Add Search Engine. SQL DB load <40%.
  5. NoSQL for Speed โ€” SQL DB overloaded. Add NoSQL, route READ/WRITE there. Keep SQL only for SEARCH.
  6. Rate Limit Gateway โ€” Spikes kill DB. Add API Gateway. <10% failed.
  7. 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

  1. Defense in Depth โ€” DDoS wave (70% MALICIOUS). Add WAF + API GW. 0 leaks.
  2. High Availability โ€” 1 WAF given, Service Outage simulated. Add 2nd WAF. Round-robin handles outage.
  3. Cost Crunch โ€” Over-engineered architecture (12+ services bleeding money). Reduce upkeep 30%, keep throughput. Net profit >0 in 60s.
  4. 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:

js
{
  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:

html
Campaign
  โ˜…โ˜…โ˜… 0/14

2. 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:

text
๐ŸŽฏ 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:

js
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:

  1. Each tick: call campaign.checkObjectives() if STATE.campaign.active
  2. On win: pause game, show Debrief, persist stars
  3. 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

text
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 in resetGame() 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:

  1. Open level briefing โ€” verify content renders correctly
  2. Pre-built architecture loads correctly
  3. Forced traffic mix applies
  4. Allowed services list grays out the rest
  5. Objectives update in real-time
  6. Win condition triggers Debrief with correct stars
  7. Lose condition triggers correct failure message
  8. Progress persists across page reloads
  9. 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 preBuilt complexity. 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-panel Tailwind 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, music and sfx occupy 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 its labelKey and
    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, same
id="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:

  1. After gating, switch automatically to the first tab that contains an allowed
    service.
  2. 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, music and sfx move into a compact settings cluster (icon-only,
    smaller) at the far edge of the bar.
  • overflow-y-auto becomes the correct horizontal handling.
  • The active tab is remembered in localStorage.
  • Keys 1โ€“5 switch tabs. The existing Esc/H/R/T shortcuts are
    untouched.

Testing

The load-bearing test is a completeness invariant: every placeable type in
CONFIG.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 gains
INFERENCE: 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 as
stream.partitions already are in all three sites (leak-battery mandatory).

b) Model cold start. service.modelLoading โ€” a DEDICATED flag; never
isDisabled (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 hit
STATE.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):

  1. Sweep first: every entry older than deadlineSec 6s is spliced out,
    then failRequest(req, SLO_TIMEOUT) โ€” never failOrPark (an SLO breach is
    not recoverable work; explicit decision), never dispatch an expired entry
    (exactly-once).
  2. 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 deriving STATE.power = {usedKw, capKw} from live services; called from createService, deleteObject,
    restoreServices, loadGameState, AND the campaign prebuild loop (which
    constructs via new Service directly โ€” 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/cap badge only when a gpu or power node exists.
  • Share links: encodeArch sorts services power-first and remaps every
    index in c and i through the permutation
    (positions p reordered 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)
    drain batch, infgw array, 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_LEVELS array, 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 string
  • src/campaign/campaign.js โ€” CampaignController (load, tick, win/lose, persistence). Exposes window.campaign.

Modified:

  • index.html โ€” script tags for new files, Campaign button in main menu, three new modal divs
  • game.js โ€” gameMode === "campaign" branch in resetGame(), animate() calls campaign.tick(), toolbar gating, new top-level startCampaign(), startCampaignLevel(id), exitCampaignToMap()
  • src/state.js โ€” add STATE.campaign initial structure
  • src/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.js

  • Create: src/campaign/objectives.js

  • Create: src/campaign/diagram.js

  • Create: src/campaign/campaign.js

  • Modify: index.html (script tags)

  • Modify: src/state.js (campaign state)

  • Step 1: Create feature branch from latest main

bash
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:

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 2

src/campaign/objectives.js:

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:

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:

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):

html
<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:

js
,
// 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.campaign exists in console

  • CAMPAIGN_LEVELS exists (returns [])

  • Step 6: Commit

bash
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_LEVELS with 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.

text
/* 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:

js
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
bash
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.js with full implementation
text
/* Detailed source-code truncated for AI context efficiency. */
  • Step 2: Reload browser, smoke test

In console:

js
CampaignObjectives.totalFailures(STATE);   // 0 (no failures yet)
CampaignObjectives.hasService(STATE, "waf"); // false (no services)
typeof CampaignObjectives.netProfit         // "function"
  • Step 3: Commit
bash
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.js

  • Step 1: Replace diagram.js with implementation

text
/* Detailed source-code truncated for AI context efficiency. */
  • Step 2: Smoke test in console
js
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
bash
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.js with full controller
text
/* Detailed source-code truncated for AI context efficiency. */
  • Step 2: Smoke test in console
js
campaign.loadProgress()        // { version:1, completed:{}, highestUnlocked:1 }
campaign.isUnlocked(1)         // true
campaign.isUnlocked(2)         // false
campaign.totalStars()          // 0
campaign.completedCount()      // 0
  • Step 3: Commit
bash
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.html

  • Step 1: Add the three modals before the closing `` tag

Find the existing `` tag in index.html. Immediately before it, insert:

html
<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:

js
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
bash
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:

html
Campaign
  0/14 โ˜…0
  • Step 2: Add navigation + render functions in game.js

In game.js, after the existing window.startSandbox = ... block, add:

text
/* 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
bash
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.js

  • Step 1: Add briefing functions in game.js

Append after the updateCampaignProgressLabel function:

js
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 visible

  • SVG diagram renders (empty for level 1 since preBuilt.services = [])

  • Goals list and Bonus list shown

  • โ† Back returns to Level Select

  • Start Level โ†’ errors in console (startCampaignLevel not defined) โ€” expected, Task 9 fixes this

  • Step 3: Commit

bash
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.js

  • Step 1: Add startCampaignLevel function in game.js

Append after campaignStartCurrentLevel:

text
/* Detailed source-code truncated for AI context efficiency. */
  • Step 2: Add gameMode === "campaign" branch in resetGame

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:

js
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:

js
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:

js
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:

js
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:

js
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" and STATE.campaign.active === true

Try clicking a grayed-out tool (e.g. Cache) โ€” nothing happens (pointer events blocked). โœ…

  • Step 6: Commit
bash
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 finishRequest in game.js to notify campaign

Find function finishRequest(req). Replace it with:

js
function finishRequest(req, viaServiceType) {
    STATE.requestsProcessed++;
    updateScore(req, "COMPLETED");
    if (window.campaign?.active) {
        window.campaign.onRequestCompleted(req, viaServiceType);
    }
    removeRequest(req);
}
  • Step 2: Pass viaServiceType from Service.js call 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:

js
finishRequest(job.req);

to:

js
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:

js
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
bash
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.js

  • Step 1: Add renderCampaignObjectives function in game.js

Append after applyCampaignToolbarGating:

js
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) into animate loop

In animate(), find where STATE.elapsedGameTime += dt; happens. After it, add:

js
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
bash
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.js

  • Step 1: Add showCampaignDebrief + nav functions

Append in game.js:

text
/* 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
bash
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.js

  • Modify: 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:

js
"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
bash
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 โ˜…0

  • Click 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 reason

  • Survival 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

text
/* 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 (enableSurvivalShifts gate)

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_LEVELS used in Tasks 2, 5, 7, 8, 9, 12 โœ…
  • CampaignObjectives used in Tasks 2 (refs), 3 (defs) โœ…
  • renderArchitectureSVG used in Tasks 4 (def), 8 (call) โœ…
  • CampaignController.onRequestCompleted used in Tasks 5 (def), 10 (call) โœ…
  • renderCampaignObjectives used in Tasks 5 (call via tick), 11 (def) โ€” Task 11 must come before Task 5's first run; in practice Task 5 only calls it if (typeof renderCampaignObjectives === "function") so it's safe โœ…
  • showCampaignDebrief used in Tasks 5 (call), 12 (def) โ€” same pattern, safe โœ…
  • localStorage key serverSurvivalCampaignProgress consistent โœ…
  • 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

  1. Game Parameters Summary
  2. Sustainability Threshold Analysis
  3. Economic Viability Analysis
  4. Optimal Ratios Analysis
  5. Upgrade Timing Analysis
  6. Failure Cascade Analysis
  7. 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

text
failChance(load) = 0              if load โ‰ค 0.5
                 = 2 ร— (load - 0.5)  if load > 0.5

1. 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 RPS total

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 RPS total

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:

text
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 request

Since 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):

text
WAF: $4/min + Compute: $12/min + S3: $5/min + DB: $24/min = $45/min = $0.75/s

Break-even Analysis

Break-even RPS (at 1.0ร— upkeep):

text
Income = Upkeep
RPS ร— $0.776/s = $0.75/s
RPS = 0.97 req/s

At 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:
    1. Same upkeep as T1 ($12/min)
    2. Better capacity per dollar when considering upkeep savings
    3. 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

text
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 processed

Effective Capacity Curve

The effective throughput (actual successful requests) peaks at around 66% load:

text
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% load

Wait - 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):

javascript
// 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 : 0

Recommendations

Parameter Changes

  1. Failure Threshold (Medium Priority)

    • Current: Starts at 50% load
    • Suggested: Start at 60% load
    • Rationale: Allows better utilization of purchased capacity
  2. 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
  3. Starting Budget (OK)

    • Current: $340
    • Analysis: Adequate for minimum viable architecture ($275) with $65 buffer
    • No change needed
  4. 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:

  1. Build WAF โ†’ Compute โ†’ S3 โ†’ DB immediately
  2. Upgrade Compute to T2 around 2-3 minutes
  3. Keep Compute below 50% load
  4. Upgrade DB only when needed (it's expensive but scales well)
  5. 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