# Vol I Internals — qBitTorrz *The annotated internals of the baseline volume: state shape, the two-term download model, seeding income, the catalog tier ladder, the swarm systems, hardware tiers, the upgrade store, and Client Migration. Everything here is cited from `index.html`; the numbers are the real numbers.* qBitTorrz is the reference implementation. Every later volume re-derives the scaffolding it establishes — `freshState`/`save`/`load`/`sanitizeState`, the `dt`-capped tick, `simulateAway` offline catch-up, `toast`. The novel hook is the **two-term download model**: a torrent downloads at `min(Max Line Speed, Seeds × Bandwidth/Peer)`, and almost every system exists to push one of those two terms. ## The `G` state shape `G` is the single global, seeded by `freshState()` (line 530) and round-tripped through `localStorage` under `SAVE_KEY = "qbittorrz_save_v1"`. The annotated keys: | Key | Type | Meaning | |---|---|---| | `v` | number | Save schema version (`1`). | | `data` | number | **Data** — the spendable currency (bytes uploaded, banked). | | `totalUp` / `totalDown` | number | Lifetime-of-run upload / download counters (HUD + ratio). | | `runUp` | number | Upload accrued *this prestige run* — the sole input to `creditsFor`. | | `credits` | number | Crypto-Credits (prestige currency). | | `prestiges` | number | Migration count. | | `lifetimeUp` | number | Upload across all runs; never reset. | | `torrents` | array | Active torrents (each `{uid,cid,name,downloaded,uploaded,status,…}`). | | `upgrades` | object | `id → level`; cleared on prestige. | | `perks` | object | `id → level`; persists across prestige. | | `seq` | number | Monotonic torrent-uid source. | | `sel` | number/null | Selected torrent uid. | | `filter` | string | Sidebar status filter (`all`/`downloading`/…). | | `dtab` | string | Active detail tab. | | `sci` | bool | Scientific-notation toggle (View menu). | | `seenCatalog` | object | Catalog ids already viewed in the Add dialog (drives the **new** badge). | | `trackerList` | array | Announced trackers `{url,seeds,peers,status}`. | | `rssFilter` / `rssOn` | string / bool | RSS glob and auto-add toggle. | | `burstUntil`,`nextBurst`,`throttleUntil`,`nextThrottle` | number | Event timers (ms). | | `irc`,`nextIrc`,`nextMagnet` | array/number | IRC log and drop schedulers. | | `lastSave`,`started` | number | Timestamps; `lastSave` anchors offline catch-up. | | `sidebarW`,`detailH` | number | Persisted splitter geometry. | `sanitizeState()` (line 1577) repairs every one of these — `num()` clamps non-finite/negative numbers, object fields that arrive as arrays or `null` are reset, and torrents whose `cid` is not in `CAT_BY_ID` are dropped. A future-dated `lastSave` self-heals so a clock skew can't grant infinite offline time. ## The download model This is the load-bearing system. Read it in three parts: the **line** (your ceiling), the **peer fraction** (how much of the ceiling you actually get), and the **realization** that multiplies them. ### The line: `clientDown()` and sqrt scaling `lineSpeed()` is literally `clientDown()` (line 625). Line speed is *not* a fixed hardware number — it scales with the **largest file you've unlocked**: ```js const BASE_DOWN = 1000; // B/s anchor before scaling/multipliers const MIN_SIZE = 4e3; // smallest catalog file (Tier A) function maxUnlockedSize(){ let m = MIN_SIZE; for(const c of CATALOG){ if(c.size>m && catalogUnlocked(c)) m=c.size; } return m; } function clientDown(){ return BASE_DOWN * Math.sqrt(maxUnlockedSize()/MIN_SIZE) * downMult(); } ``` The `sqrt` is the keystone. File sizes span seventeen orders of magnitude (`4e3` → `3.0e20`); a linear line term would make late files either trivial or eternal. The square root halves the exponent the player must cover with bandwidth upgrades while leaving big files a real sink. Worked, with `downMult()===1`: | Largest unlocked | `clientDown()` | |---|---| | Tier A only (`4e3`) | **1,000 B/s** | | Cat6 (`Wikipedia` `9.0e8`) | **≈ 474,342 B/s** | | Private (`Every_Linux_ISO_Ever` `2.4e11`) | **≈ 7,745,967 B/s** | | Fiber (`CERN…root` `1.9e14`) | **≈ 217,944,947 B/s** | | Singularity (`The_Final_Hash` `3.0e20`) | **≈ 2.74e11 B/s** | > **DEV NOTE** — unlocking a tier *is* the bandwidth grant. Because `maxUnlockedSize()` reads the catalog through `catalogUnlocked`, buying `cat6` simultaneously reveals MB-class files *and* raises the line to download them, so the player never unlocks content they're structurally unable to finish. The corollary: `maxUnlockedSize()` iterates the full `CATALOG` on every `clientDown()` call, and `clientDown()` runs per-torrent per-tick — cheap at 23 entries, but the first thing to memoize if the catalog grows. `downMult()` (line 568) stacks the multiplicative download bonuses: `bandwidth` (×2.2/level via `HW_TIERS`), `overclock` (×1.8/level), the `dpi` perk, the ×10 DHT burst while `now()=1`. ## Seeding income Income is simpler — there is no peer gating on upload. A completed torrent flips to `seeding` and earns: ```js function torrentUpSpeed(t){ if(t.status!=='seeding') return 0; const c=CAT_BY_ID[t.cid]; return (c.up * upMultGlobal()) + seedboxFlat(); } ``` `c.up` is the per-torrent base income. `upMultGlobal()` (line 577) multiplies `upslot` (×1.55/level), `compress` (×1.8/level), `ratio` (×1.12/level), the `ghost` perk, and the same ×0.4 throttle penalty. `seedboxFlat()` is `lvl('seedbox') * 5e5` — a **flat** addend per torrent, strongest on small files and dominant early after Private. In `step()` (line 668) the gain lands in four counters at once: `G.data`, `G.totalUp`, `G.runUp`, `G.lifetimeUp`. The status-bar **Ratio** is the run aggregate `G.totalUp/G.totalDown`, distinct from each torrent's own `uploaded/c.size`. ## The catalog and tier gating `CATALOG` (line 402) is 23 fixed entries — `{id,name,size,up,seeds,req,rare?}`. `req` is the unlock id that reveals the entry; `catalogUnlocked(c)` resolves it, routing `darknet`/`singularity` to `hasPerkUnlock` (prestige perks) and everything else to `hasUnlock` (bought upgrades): ```js function catalogUnlocked(c){ if(!c.req) return true; if(c.req==='darknet') return hasPerkUnlock('darknet'); if(c.req==='singularity') return hasPerkUnlock('singularity'); return hasUnlock(c.req); } ``` The six tiers, each ~10³–10⁴× the previous in both size and income: | Tier | `req` | Size range | Example | Gated by | |---|---|---|---|---| | Public A | `null` | 4 kB – 6.5 MB | `tiny_release_notes.txt` | always | | Public B | `cat6` | 44 MB – 900 MB | `Wikipedia_Full_Text_Dump.xml` | Cat6 upgrade | | Private | `private` | 4.2 GB – 240 GB | `GameOfThorns.S01.REMUX.2160p` | Private Tracker Invite | | Fiber | `fiber` | 1.6 TB – 190 TB | `CERN_Collider_Event_Logs.root` | Fiber-Optic Trunk | | Darknet | `darknet` | 1.3 PB – 600 PB | `The_Library_Of_Everything.iso` | **perk** (prestige) | | Singularity | `singularity` | 5 EB – 300 EB | `The_Final_Hash.dat` | **perk** (gated behind Darknet) | `rare:true` entries (`t11`, `t15`, `t19`, `t22`) **stall at 99.9%** — see the swarm systems. The unlock chain `Public → Cat6 → Private → Fiber → Darknet → Singularity` is enforced by the upgrades' `reqUp` (`private` requires `cat6`; `fiber` requires `private`) and the perks' `reqPerk` (`singularity` requires `darknet`). The last two tiers are *only* reachable through migration, which is the structural reason to prestige. ## The swarm systems ### Trackers / announce `announceTracker()` (line 736) spends Data to push a `{url,seeds,peers,status}` onto `G.trackerList`, drawing from `TRACKER_URLS` (falling back to generated `.onion` relays once the pool is exhausted). Cost escalates with how many you've added past the two defaults: ```js function announceCost(){ const added=Math.max(0, (G.trackerList||[]).length-2); return Math.max(120, totalUpSpeed()*18) * Math.pow(1.85, added); } ``` Each tracker adds 2–4 `seeds` to *every* torrent (via `trackerSeedBonus`), which is the primary lever against peer-limiting. ### Peer prioritization The Peers detail tab exposes two per-torrent checkboxes, `dropLeechers` and `prioSeeds`, wired through `peer-toggle`. Each contributes `+0.15` in `peerPrioBonus(t)`, so both ticked is `×1.30` on assembled seeds. (The per-row **Prioritize**/**Ban** buttons are cosmetic — `peer-boost` nudges `burstUntil` by 2.5 s, `peer-ban` only toasts.) ### RSS glob filter `globToRe(glob)` (line 715) compiles a shell-glob into a case-insensitive `RegExp`: it escapes regex metacharacters, then maps `*` → `.*` and `?` → `.`, defaulting to `/.*/i` on empty or malformed input. When `rss` is unlocked, `rssAutoAdd()` runs each tick: if a slot is free, it filters the catalog to unlocked entries matching `rssFilter`, sorts by `up` descending, and adds the single highest-income match. It is a hands-off "always fill the best slot" automation, not a queue. ### IRC announce-bot magnets `ircMagnetDrop()` (line 751) periodically pushes a magnet line tagging a random unlocked `cid`; the IRC tab renders it with a clickable `[🧲 grab]`. `grabMagnet(cid)` calls `addTorrent(cid, true, 0.25)` — the third arg is a **head-start fraction**, so the torrent is born 25% downloaded, skipping the slow peer-limited opening: ```js function addTorrent(cid, silent, headstart){ // … downloaded: headstart? Math.min(c.size*headstart, c.size*0.999) : 0, ``` The `Math.min(..., c.size*0.999)` cap matters for rares — a head-start can't accidentally vault a `rare` torrent past its stall point. > **DEV NOTE** — the rare-stall mechanic is the one place the idle contract bites. A `rare` torrent freezes at exactly `c.size*0.999`, sets `status:'stalled'`, and earns nothing until `forceComplete()` (the **Request last piece** / `bribe` action) seeds the final block. Because `simulateAway()` reproduces the same stall logic (line 1636), a rare left downloading while you're away sits at 99.9% — not seeding — when you return. Intended friction, but the canonical "why didn't my offline earnings include that torrent" support question. See **QA Playbook & Post-Mortems** for the broader dead-zone discussion. ### DHT bursts and ISP throttle `handleEvents()` (line 679) schedules both. With `dht` owned, a burst sets `burstUntil = now()+8000` and a ×10 download multiplier fires in `downMult()`, on a 45–90 s cadence. With `private` owned but not `quantum`, the ISP throttle sets `throttleUntil` for 9–16 s and applies ×0.4 to *both* `downMult()` and `upMultGlobal()`. `quantum` (Quantum Encryption Protocol) is a hard immunity — the throttle branch checks `!hasUnlock('quantum')` in three places (the two multipliers and the status banner). ## Hardware tiers `HW_TIERS` (line 494) is the 13-name ladder the `bandwidth` upgrade walks. `hwTierName()` clamps the level into the array and appends a `+N` suffix past the end: ```js const HW_TIERS = ['56k Dial-up Modem','ISDN Line','ADSL Broadband','SDSL Line','Cable Broadband', 'VDSL2','Gigabit Fiber','10G Fiber','Metro Ethernet','OC-768 Backbone','Tier-1 Telecom Trunk', 'Dark Fiber Mesh','Quantum Backbone']; function hwTierName(){ const l=lvl('bandwidth'); return HW_TIERS[Math.min(l,HW_TIERS.length-1)] + (l>=HW_TIERS.length?(' +'+(l-HW_TIERS.length+1)):''); } ``` These names are pure flavor over the `bandwidth` multiplier — throughput comes from `downMult()`, not the string — but they anchor the lore's climb from dial-up to a **Quantum Backbone**. ## The upgrades store `UPGRADES` (line 438) drives the three-tab store (`public` / `private` / `darknet`). Repeatable upgrades cost `floor(base * growth^level)`; `unlock` kinds are one-time at `base`. The full table: | id | tab | kind | base | growth | effect | cap | |---|---|---|---|---|---|---| | `bandwidth` | public | hardware | 64 | 1.7 | ×2.2 line / level | 60 | | `perpeer` | public | perpeer | 320 | 1.6 | ×1.3 per-peer / level | 30 | | `upslot` | public | mult-up | 90 | 1.55 | ×1.55 income / level | 200 | | `upnp` | public | seeds | 140 | 1.7 | +2 seeds / level | 60 | | `storage1` | public | storage | 600 | 1.9 | +1 slot / level | 40 | | `ratio` | public | ratio | 260 | 1.5 | ×1.12 income / level | 150 | | `cat6` | public | unlock | 1,800 | — | reveal Public B | — | | `dht` | public | unlock | 4,200 | — | enable ×10 bursts | — | | `private` | private | unlock | 9,000 | — | reveal Private (req `cat6`) | — | | `magnet` | private | unlock | 24,000 | — | +2 base seeds (req `private`) | — | | `rss` | private | unlock | 60,000 | — | RSS auto-add (req `private`) | — | | `seedbox` | private | flat-up | 40,000 | 1.8 | +5e5 flat income / level | 120 | | `storage2` | private | storage | 5e4 | 1.75 | +1 slot / level (req `private`) | 60 | | `fiber` | darknet | unlock | 5e6 | — | reveal Fiber (req `private`) | — | | `quantum` | darknet | unlock | 2e7 | — | throttle immunity (req `fiber`) | — | | `overclock` | darknet | mult-down | 3e7 | 1.7 | ×1.8 download / level (req `fiber`) | 200 | | `compress` | darknet | mult-up | 3.5e7 | 1.7 | ×1.8 income / level (req `fiber`) | 200 | `upgradeReqMet(u)` enforces `reqUp`; `buyUpgrade(id)` (line 838) deducts Data, increments the level, and on unlocks fires diegetic IRC system lines (`cat6`, `private`, `fiber`). `maxSlots()` is `3 + storage1 + storage2 + plvl('rep')` — the storage upgrades plus the prestige `rep` perk. ## Prestige: Client Migration Prestige is reframed as redeploying onto a **Virtual Seedbox Cluster**. The yield is sub-linear in run-upload: ```js function creditsFor(runUp){ if(runUp<1e9) return 0; return Math.floor(Math.pow(runUp/1e9, 0.42)); } ``` So `1e9` → 1 credit, `1e10` → 2, `1e12` → 18. The 1e9 floor is the gate the toolbar button and modal enforce. `doPrestige()` (line 884) banks the gain and performs a surgical reset: ```js G.credits+=gain; G.prestiges++; G.data=0; G.runUp=0; G.torrents=[]; G.upgrades={}; G.sel=null; G.trackerList=defaultTrackers(); G.burstUntil=0;G.nextBurst=0;G.throttleUntil=0;G.nextThrottle=0;G.nextMagnet=0; ``` Wiped: Data, run upload, torrents, **all bought upgrades**, trackers, and event timers. Kept: `credits`, `perks`, `prestiges`, `lifetimeUp`. Credits buy `PERKS` (line 480) via `buyPerk`: | id | persistent effect | base cost | growth | cap | |---|---|---|---|---| | `dpi` | +25% global download / level | 1 | 1.6 | 50 | | `ghost` | +25% global income / level | 1 | 1.6 | 50 | | `rep` | +1 permanent slot / level | 2 | 1.8 | 25 | | `seed0` | +2 base seeds / level | 2 | 1.7 | 25 | | `darknet` | unlock Petabyte tier (once) | 3 | — | — | | `singularity` | unlock Exabyte+ tier (once, req `darknet`) | 40 | — | — | `darknet` and `singularity` are the *only* path to the top two catalog tiers — `catalogUnlocked` routes their `req` exclusively through `hasPerkUnlock`. The first migration is therefore the structural mid-game wall: you cannot brute-force into Petabyte files with Data alone. > **DEV NOTE** — perks compound multiplicatively with run upgrades because `perkMult` feeds the same `downMult`/`upMultGlobal` products that `bandwidth`/`overclock`/`compress` do. Two `dpi` levels (`×1.25²` ≈ ×1.56) carry across the wipe, so each migration starts from a higher floor even though local hardware is gone. That is the whole feel of prestige here — you lose the machine, you keep the protocol. · · · ## Balance levers & if you change this - **`BASE_DOWN` (1000) and the `sqrt` in `clientDown()`** — the master pacing dial. Dropping the `sqrt` to a smaller exponent stretches every download; removing it makes late tiers either trivial or impossible. Touch this and re-derive the line table above before shipping. - **`SAT_SEEDS` (12) and `perpeer` `mult` (1.3)** — govern how punishing peer-limiting is and how fast `perpeer` trivializes it. Raising `SAT_SEEDS` makes the announce/peer loop matter for longer; raising the `mult` lets a few `perpeer` levels obsolete it. - **Catalog `size`/`up`/`seeds` and the per-tier 10³–10⁴ gaps** — the spine. `maxUnlockedSize()` reads `size` for the line, so a new top entry silently raises everyone's ceiling. Keep new entries in tier order or the sqrt math skips rungs. - **`creditsFor` exponent (0.42) and the 1e9 floor** — prestige cadence. A higher exponent makes each migration worth more credits and shortens the perk grind; lowering the floor lets players prestige before they've meaningfully scaled, which devalues the run. - **`seedboxFlat` (+5e5/level, flat) vs. the multiplicative income chain** — the flat addend is intentionally strongest on small files and early Private. If you make it scale, you erase the reason to prefer big seeders late-game. - **Event tunables** — burst ×10 / 8 s on a 45–90 s cadence; throttle ×0.4 / 9–16 s on 80–160 s. `quantum` is a binary immunity. Rebalance the throttle and you change how badly the player needs `quantum` before Fiber. - **`sanitizeState()` is the safety net** — any new `G` field must be added there with a sane default, or a pre-update save will load it as `undefined` and a tick will likely write `NaN` into `G.data`. Add the key to `freshState` *and* `sanitizeState` together.