/* ===========================================================================
* WHAT CHANGED FROM 1.3.1 — read this first
* ---------------------------------------------------------------------------
* Accuracy
* - Explicit out-of-vocabulary mass. A lone surviving candidate can no
* longer print 100%; it prints the probability the password is in the
* dictionary at all. This was the TERMINATOR failure.
* - Backoff ladder: trigram -> bigram (both neighbours) -> positional
* frequency -> unigram. The script now always has a recommendation, and
* on out-of-vocabulary words it beats random by a wide margin instead of
* printing "(no matches)".
* - Rank is stored explicitly with a source tier (base list / community
* pool / own crack). In 1.3.1 rank came from array position, so appended
* community words scored ~104x worse than generic top-of-list passwords
* and needed roughly 670 sightings of one word to recover. They now
* start ahead of the base list, which is where the evidence actually is.
* - Observation evidence is additive rather than a multiplicative log
* boost, so confirmed sightings can genuinely dominate a weak prior.
* - Crack level is scraped and feeds the coverage estimate.
* - Coverage self-calibrates from logged outcomes.
*
* Correctness
* - Misses are captured by MutationObserver instead of an 800 ms poll
* racing a transient CSS class. Missed misses were lost exclusions.
* - Observations only count on a real incomplete -> complete transition.
* 1.3.1 re-counted any finished row on every page load, inflating counts
* and manufacturing fake repeats.
* - Outbox: in-flight guard, correct requeue of the untried remainder,
* capped retries with backoff, bounded queue. 1.3.1 dropped words on a
* mid-batch failure and then span forever.
* - Row state lives in a WeakMap keyed on the DOM node, so the virtual list
* recycling a row cleans up automatically. No more random rowKeys, no
* sessionStorage growth.
*
* Speed
* - Words are packed into typed arrays (fixed stride). The 8-char bucket is
* 2.2 MB instead of ~16-20 MB of JS strings.
* - ONE persistent worker that owns the packs and the IndexedDB. 1.3.1
* spawned a worker per update and structured-cloned the whole bucket
* into it every time.
* - IndexedDB handle is cached instead of reopened on every get/set.
* - Auto-sync uses one computed timeout instead of a 1 Hz poll.
* - Theming is CSS custom properties, not inline styles rewritten per row
* per tick. DOM updates are observer-driven with a slow safety net.
*
* Torn passwords are confirmed to be 4-10 characters using A-Z and 0-9.
* Dots and underscores shown by the cracking UI represent slot states, not
* password characters, and therefore do not belong in the model alphabet.
*
* ---------------------------------------------------------------------------
* 2.2.8 — settings are compact/collapsible, the status badge opens them, pool
* sharing uses an explicit switch, statistics use clearer prior-coverage
* labels and compact counts, and completed sync messages return to Ready.
*
* ---------------------------------------------------------------------------
* 2.2.7 — base, community, and character-model indexing now report elapsed
* time and an ETA. Worker scans publish progress every 25,000 words so a
* large bucket no longer looks frozen between coarse phase updates.
*
* ---------------------------------------------------------------------------
* 2.2.6 — a failed/restored exclusion immediately invalidates any scorer
* response already in flight, so the rejected character cannot remain in a
* stale launcher or suggestion modal while the replacement score is queued.
*
* ---------------------------------------------------------------------------
* 2.2.5 — failed character attempts now survive a refresh of the same crack.
* Transitional, partially hydrated Torn markup no longer deletes the saved
* record, and restoration retries as the password identity becomes visible.
*
* ---------------------------------------------------------------------------
* 2.2.4 — batches community-pool membership checks into one scan per packed
* length bucket and atomically saves only changed buckets, so interrupted
* syncs recover without repeating minutes of linear lookups.
*
* ---------------------------------------------------------------------------
* 2.2.3 — detects Torn's real CSS-module rig statistic while it is mounted
* and carries that per-tab value back to the cracking list.
*
* ---------------------------------------------------------------------------
* 2.2.2 — reads each player's current rig panel instead of assuming one
* brute-force strength for everybody. Fractional strength is retained and
* modelled using Torn's per-action stochastic rounding.
*
* ---------------------------------------------------------------------------
* 2.2.1 — replaces the arbitrary per-slot RISK threshold with Torn's actual
* dominant policy: use the free highest-probability guess while guessing is
* available, then brute force. The modal reports the chance of clearing all
* currently open slots within the global miss allowance and the remaining
* brute-force work, and telemetry records those decisions for replay.
*
* ---------------------------------------------------------------------------
* 2.2.0 — completed passwords now upload their Torn target/type, service,
* and level as a separate context association. Confirmed community context
* links sync back to each browser and cautiously boost exact-context words.
*
* ---------------------------------------------------------------------------
* 2.1.10 — completed rows no longer show a launcher. The transparent panel
* no longer intercepts Torn's hover-only abandon button, and failed guesses
* retain their original probabilities across refreshes for display in the
* suggestion modal.
*
* ---------------------------------------------------------------------------
* 2.1.9 — compact row launchers replace the mobile-width suggestion bars.
* Tapping one opens a password-box modal whose suggested characters send to
* the corresponding real Torn slot, with ranked alternatives per open slot.
*
* ---------------------------------------------------------------------------
* 2.1.8 — new installs fetch the base dictionary in bounded chunks instead
* of cloning and splitting one 7.4 MB string. Packed lookup no longer keeps
* a parallel 868k-string Map, and observations no longer trigger a complete
* character-model rebuild after every crack.
*
* ---------------------------------------------------------------------------
* 2.1.7 — community sync now checks lightweight metadata every 15 minutes,
* downloads at 25 pending words or after 24 hours, reports the real number
* added/upgraded, uploads in batches of up to 10, and uses the mobile-safe
* uncompressed base snapshot endpoint.
*
* ---------------------------------------------------------------------------
* 2.1.6 — removed the DECRYPT → X → char feature. Torn's brute-force decrypt
* is random (not slot-specific), so ranking encrypted slots by confidence
* against encryption layer cost was misleading and should not be used.
*
* ---------------------------------------------------------------------------
* 2.1.0 — reported by MoDuL [4022159], diagnosed by MoDuL and TheDno [4443016]
*
* - Embedded-word matching. XTRIDENT66 (= X + TRIDENT + 66) is a generated
* password, so whole-string lookup can never find it. Aligning TRIDENT
* inside the pattern explains six revealed characters at once and pins the
* remaining gap: on that exact pattern the open slot went from I 6.4%
* (truth R ranked 5th) to R 87.7% ranked 1st.
* - Right-hand trigram, mirroring the left. Revealed characters cluster where
* the player has been guessing, so a gap often has all its context on one
* side.
* - A still-loading dictionary no longer reports fake estimates. An untrained
* model produces a flat 1/39 distribution, which put "TRY 1:A 2.6%" on
* every row and read as confidence rather than as "not ready yet".
* - Rows with every hidden slot encrypted no longer recommend a guess they
* cannot accept.
* - IN-DICT is no longer pegged to its ceiling by a single observed word.
* - The LETTERS chip no longer overflows the panel.
*
* ---------------------------------------------------------------------------
* THINGS THE SCRIPTER MUST CONFIRM — search for CONFIRM: in this file
* 1. SEL.level / readLevel() — the crack level selector is a guess.
* 2. AJAX_MATCH + parseGuessResponse() — optional network verdict hook,
* off by default. The MutationObserver path works without it.
* ======================================================================== */