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

[](https://pshenok.github.io/server-survival/)

> 🏭 **New: [Datacenter Survival](https://github.com/pshenok/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](https://pshenok.github.io/datacenter-survival/).

## 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](https://threejs.org/) for 3D visualization.
- **Styling:** [Tailwind CSS](https://tailwindcss.com/) 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](https://pshenok.github.io/server-survival/).** 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](CONTRIBUTING.md) for the workflow.

## Community

Join our Discord server to discuss strategies and share your high scores:
[Join Discord](https://discord.gg/f38NgHDwnK)

---

_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` / `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:

```
/* 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)
4. **Cache the DB** — DB melts under READ. Add Cache. DB load <70%.
5. **Buffer the Spikes** — Bursty traffic. Add Queue. 0 drops in 90s.
6. **Scale Reads** — "API Heavy" shift active. Add Read Replica. DB load <60%.
7. **Search Done Right** — "Search Storm" (50% SEARCH). Add Search Engine. SQL DB load <40%.
8. **NoSQL for Speed** — SQL DB overloaded. Add NoSQL, route READ/WRITE there. Keep SQL only for SEARCH.
9. **Rate Limit Gateway** — Spikes kill DB. Add API Gateway. <10% failed.
10. **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
11. **Defense in Depth** — DDoS wave (70% MALICIOUS). Add WAF + API GW. 0 leaks.
12. **High Availability** — 1 WAF given, Service Outage simulated. Add 2nd WAF. Round-robin handles outage.
13. **Cost Crunch** — Over-engineered architecture (12+ services bleeding money). Reduce upkeep 30%, keep throughput. Net profit >0 in 60s.
14. **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:
```
🎯 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

```
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`.

```
/* 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**

```
/* 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**

```
/* 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**

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

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

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

```
/* 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**

```
/* 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](#game-parameters-summary)
2. [Sustainability Threshold Analysis](#1-sustainability-threshold-analysis)
3. [Economic Viability Analysis](#2-economic-viability-analysis)
4. [Optimal Ratios Analysis](#3-optimal-ratios-analysis)
5. [Upgrade Timing Analysis](#4-upgrade-timing-analysis)
6. [Failure Cascade Analysis](#5-failure-cascade-analysis)
7. [Recommendations](#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.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:**
```
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):**
```
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):**
```
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

```
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**:

```
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*