bitmax/docs/handbook/10-vol-I-qbittorrz.md
monster 87cfead4d3 docs: complete the Dev Handbook
Adds the 5 chapters that hit a transient rate-limit on first pass:
- 01 Architecture & the Shared DNA
- 02 Building a New Volume (the Cookbook)
- 10 Vol I Internals — qBitTorrz
- 12 Vol III Internals — MACRO_VIRUS.XLS
- 13 Vol IV Internals — UPLINK

The Dev Handbook (Disc One) is now complete at 10 chapters; the Lore Bible (Disc Two) was already complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:21:31 +10:00

277 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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()<G.burstUntil`, and the ×0.4 ISP throttle while `now()<G.throttleUntil && !hasUnlock('quantum')`.
### The peer fraction: seeds and `downFrac`
Seeds are assembled bottom-up. `torrentSeeds(t)` (line 616) sums the catalog swarm, `baseSeedBonus()`, and `trackerSeedBonus()`, then applies a per-torrent prioritization multiplier:
```js
function torrentSeeds(t){
const c=CAT_BY_ID[t.cid];
const base=(c?c.seeds:1)+baseSeedBonus()+trackerSeedBonus();
return Math.max(1, Math.round(base*(1+peerPrioBonus(t))));
}
```
- `baseSeedBonus()` = `2*lvl('upnp')` + 2 if `magnet` is owned + `2*plvl('seed0')` — global seed floor.
- `trackerSeedBonus()` sums `seeds` over `G.trackerList` — the announce loop.
- `peerPrioBonus(t)` = `+0.15` for `dropLeechers` and `+0.15` for `prioSeeds` (the two Peers-tab checkboxes), applied **multiplicatively** to the assembled base.
The fraction of the line you actually hit:
```js
const SAT_SEEDS = 12; // connected seeds that saturate the line
function perPeerMult(){ return Math.pow(UP_BY_ID.perpeer.mult, lvl('perpeer')); } // ×1.3/level
function downFrac(seeds){ return Math.min(1, seeds/SAT_SEEDS*perPeerMult()); }
```
Below `SAT_SEEDS` worth of effective peers you are **peer-limited**: a fresh catalog file with `seeds: 6` and no `perpeer` upgrades runs at `6/12 = 0.50` — half its line. The two ways out are announcing trackers (more `seeds`) and `perpeer` (each level multiplies the fraction by 1.3, so the General tab reports the falling "seeds to saturate" count). At 12 effective seeds, `downFrac` clamps to 1.0 and the torrent goes line-limited.
### The realization
```js
function torrentDownSpeed(t){
if(t.status!=='downloading') return 0;
return lineSpeed() * downFrac(torrentSeeds(t));
}
```
That product is the in-game promise made concrete: `lineSpeed × min(1, seeds/SAT_SEEDS × perPeerMult)` equals `min(lineSpeed, lineSpeed × seeds × perPeerMult / SAT_SEEDS)` — the line, or the peer term, whichever binds. The General tab labels which is active via the `(line-limited)` / `(peer-limited)` tag from `downFrac(...)>=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 24 `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 4590 s cadence. With `private` owned but not `quantum`, the ISP throttle sets `throttleUntil` for 916 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 4590 s cadence; throttle ×0.4 / 916 s on 80160 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.