// ==UserScript== // @name Faucet Dice Analyzer v17.13 FAUCETMATRIX // @namespace dice-analyzer // @version 17.13 // @description Faucet Dice Analyzer for ultimate dice game // @match *://*.freeshib.in/* // @match *://*.freetron.in/* // @match *://*.freebnb.in/* // @match *://*.freebch.in/* // @match *://*.freetroncoin.in/* // @match *://*.freetrump.in/* // @match *://*.usdpick.io/* // @match *://*.freebtcco.in/* // @match *://*.freearb.in/* // @match *://*.dogedrop.co/* // @run-at document-idle // @license MIT // @grant none // ==/UserScript== (function(){ 'use strict'; // ==================== CONFIG ==================== var SITE = location.hostname; var ZONE_WIDTH = 1; var ZONE_COUNT = Math.floor(100 / ZONE_WIDTH); // Storage keys (v17.2 — fresh, imports from v17) var SK_HIST = 'dv172_h_' + SITE; var SK_CHAINS = 'dv172_c_' + SITE; var SK_MODEL = 'dv172_m_' + SITE; var SK_STATE = 'dv172_s_' + SITE; var SK_PLOG = 'dv172_p_' + SITE; var SK_MACROS = 'dv172_mc_' + SITE; var SK_GHOSTS = 'dv172_gh_' + SITE; // v17.12: SK_UCHAINS removed — user-chain module deleted. // Core SEED zones var SEED = [0,11,14,22,33,42,44,46,49,50,52,55,66,77,86,88,89,92,99]; var SEED_SET = {}; for (var si = 0; si < SEED.length; si++) SEED_SET[SEED[si]] = true; // Chain config var NGRAMS = [1, 2, 3]; var MIN_OBS = 2; var MAX_CHAINS = 10000; var MAX_GAP_REC = 250; var GAP_KEEP = 20; // Lifecycle var LF_WIN = 80; var LF_PAT_RATE = 0.17; var LF_CHAOS_RATE = 0.10; var LF_ENTER_DUR = 4; var LF_EXIT_DUR = 10; // Dynamic zone discovery var DYN_WIN = 150; var DYN_MIN = 5; var DYN_MULT = 2.0; // Burst var BURST_WIN = 15; var BURST_MIN = 3; var BURST_BOOST = 25; // Prediction queue var QUEUE_SIZE = 4; var PRD_MIN_WAIT = 15; var PRD_MAX_WAIT = 55; var PRD_WAIT_MULT = 2.0; var PRD_MIN_CF = 18; var ZONE_SPREAD = 1; var DELAYED_EXT = 35; // v17: was 30, extended by 5 // Queue stability var IMMUNITY_FRAC = 0.6; var REPLACE_CF_BOOST = 25; var NO_REBUILD_PERIOD = 12; // Absent zone penalty var ABSENT_PENALTY_R1 = 200; var ABSENT_PENALTY_R2 = 400; var ABSENT_PENALTY_FACTOR = 0.3; // Non-SEED confidence gate var NON_SEED_CF_MULT = 2.0; // Macro config var MACRO_MAX = 600; // v17: was 500, more capacity var MACRO_MIN_LEN = 5; var MACRO_MAX_LEN = 10; var MACRO_PREFIX = [2, 3, 4]; // v17: added 4-gram prefixes var MACRO_MAX_AGE = 15000; var MACRO_MIN_OBS = 3; var MACRO_SCAN_LEN = 50; // v17: was 30, wider scan // NEW v17: Overdelayed ghost tracking var OVERDELAYED_WINDOW = 30; // track for 30 rolls after expiry var MAX_GHOSTS = 10; // max concurrent ghosts // NEW v17: Chaos interrupt detection var CHAOS_BOUNDARIES = [0, 14, 88, 92, 99]; var CHAOS_BOUNDARY_SET = {}; for (var ci = 0; ci < CHAOS_BOUNDARIES.length; ci++) CHAOS_BOUNDARY_SET[CHAOS_BOUNDARIES[ci]] = true; var CHAOS_CLUSTER_WIN = 5; // 2+ boundaries within 5 rolls var CHAOS_MEMORY = 25; // chaos flag lasts 25 rolls var CHAOS_DELAY_BONUS = 15; // bonus mw when chaos detected var CHAOS_MIN_CLUSTER = 2; // minimum boundaries for chaos event // NEW v17.2: Drift corrector config var DRIFT_LR = 0.15; // learning rate (v17 was 0.02) var DRIFT_CLAMP = 30; // max correction in either direction var DRIFT_EXPECTED = 0.6; // expected hit as fraction of mw var DRIFT_MIN_OBS = 3; // need 3+ resolved preds before applying global drift // NEW v17.2: Anti-premature-chaos config var SEED_RECENT_WIN = 8; // if SEED seen within this many rolls, don't drop to ENTER // v17.12: USER_CHAIN_* config removed. // NEW v17.7: Prediction cooldown — prevents re-predicting the same zone // immediately after it expired. Forces the engine to pick a different // zone, breaking the "stuck on zone X" loop. var PRED_COOLDOWN_ROLLS = 25; // rolls to skip a zone after expiry var PRED_COOLDOWN_HIT_CLEAR = true; // clear cooldown immediately on HIT // NEW v17.7: Flow window config var FLOW_PANEL_COUNT_DEFAULT = 40; // default visible notable zones var FLOW_PANEL_COUNT_MIN = 10; var FLOW_PANEL_COUNT_MAX = 200; var FLOW_LONG_GAP_DEFAULT = 15; // default long-gap threshold (rolls) var FLOW_LONG_GAP_MIN = 5; var FLOW_LONG_GAP_MAX = 100; var FLOW_REFRESH_MS = 2000; // History var MAX_HIST = 5000; var MAX_NOTABLE = 600; // ==================== STATE ==================== var history = ld(SK_HIST, []); var chains = ld(SK_CHAINS, {}); var model = ld(SK_MODEL, {}); var st = ld(SK_STATE, {}); var plog = ld(SK_PLOG, []); var macros = ld(SK_MACROS, {}); var ghosts = ld(SK_GHOSTS, []); // NEW v17: overdelayed ghosts // NEW v17.2: Drift corrector state var globalDrift = st.gd || 0; // global drift offset (positive = predictions late) var zoneDrift = st.zd || {}; // per-zone drift {zone: drift_value} var driftObs = st.do || 0; // number of observations for global drift // v17.12: userChains removed (module deleted). var rollCounter = st.rc || history.length; var lifecycle = st.lf || 'CHAOS'; var lifeCount = st.lc || 0; var notableSeq = st.ns || []; var dynZones = st.dz || {}; var burstZones = st.bz || {}; var predQueue = st.pq || []; var lastRebuild = st.lr || 0; var chaosTimer = st.ct || 0; // NEW v17: chaos countdown var chaosCount = st.cc || 0; // NEW v17: total chaos events // NEW v17.7: Prediction cooldown state // Map: { zone: rollCounter_when_cooldown_started } // A zone is in cooldown if (rollCounter - predCooldown[zone]) < PRED_COOLDOWN_ROLLS var predCooldown = st.pcd || {}; // NEW v17.7: Flow window state (persisted so user's preference survives reloads) var flowPanelCount = st.fpc || FLOW_PANEL_COUNT_DEFAULT; var flowLongGap = st.flg || FLOW_LONG_GAP_DEFAULT; var overlay; var flowOverlay = null; var flowRefreshInterval = null; // ==================== STORAGE ==================== function ld(k, d) { try { var v = JSON.parse(localStorage.getItem(k)); return (v !== null && v !== undefined) ? v : d; } catch(e) { return d; } } function sv() { try { localStorage.setItem(SK_HIST, JSON.stringify(history)); localStorage.setItem(SK_CHAINS, JSON.stringify(chains)); localStorage.setItem(SK_MODEL, JSON.stringify(model)); localStorage.setItem(SK_MACROS, JSON.stringify(macros)); st = { rc: rollCounter, lf: lifecycle, lc: lifeCount, ap: null, ns: notableSeq.slice(-MAX_NOTABLE), dz: dynZones, bz: burstZones, pq: predQueue.slice(-QUEUE_SIZE), lr: lastRebuild, ct: chaosTimer, cc: chaosCount, gd: globalDrift, zd: zoneDrift, do: driftObs, pcd: predCooldown, fpc: flowPanelCount, flg: flowLongGap }; localStorage.setItem(SK_STATE, JSON.stringify(st)); if (plog.length > 4000) plog = plog.slice(-4000); localStorage.setItem(SK_PLOG, JSON.stringify(plog)); localStorage.setItem(SK_GHOSTS, JSON.stringify(ghosts.slice(-MAX_GHOSTS))); } catch(e) { trimChains(); trimMacros(); try { sv(); } catch(e2) {} } } function trimChains() { var keys = Object.keys(chains); if (keys.length <= MAX_CHAINS) return; keys.sort(function(a, b) { return (chains[a]._t || 0) - (chains[b]._t || 0); }); for (var i = 0; i < keys.length - MAX_CHAINS; i++) delete chains[keys[i]]; } function trimMacros() { var keys = Object.keys(macros); if (keys.length <= MACRO_MAX) return; keys.sort(function(a, b) { return getMacroAge(a) - getMacroAge(b); }); for (var i = 0; i < keys.length - MACRO_MAX; i++) delete macros[keys[i]]; } function getMacroAge(key) { var entries = macros[key]; if (!entries || !entries.length) return 999999; var maxLast = 0; for (var i = 0; i < entries.length; i++) { if (entries[i].last > maxLast) maxLast = entries[i].last; } return rollCounter - maxLast; } // ==================== ZONE DETECTION ==================== function detectZone(v) { var z = Math.floor(v / ZONE_WIDTH); return (z >= 0 && z < ZONE_COUNT) ? z : null; } // ==================== CHAIN ALPHABET ==================== function isChainable(zone) { if (zone === null) return false; return SEED_SET[zone] || dynZones.hasOwnProperty(String(zone)); } function computeDynZones() { var counts = {}; var ws = Math.max(0, history.length - DYN_WIN); var total = 0; for (var i = ws; i < history.length; i++) { var z = history[i].zone; if (z !== null) { counts[z] = (counts[z] || 0) + 1; total++; } } var expected = total > 0 ? total / ZONE_COUNT : 0; var result = {}; for (var k in counts) { var zk = parseInt(k); if (!SEED_SET[zk] && counts[k] >= DYN_MIN && counts[k] >= expected * DYN_MULT) { result[k] = counts[k]; } } return result; } // ==================== BURST DETECTION ==================== function updateBurstZones(zone) { if (zone === null) return; var recentHits = 0; for (var i = history.length - 1; i >= 0 && i >= history.length - BURST_WIN; i--) { if (history[i].zone === zone) recentHits++; } if (recentHits >= BURST_MIN) { if (!burstZones[zone]) burstZones[zone] = {count: 0, first: 0}; burstZones[zone].count = recentHits; burstZones[zone].first = rollCounter; } else if (burstZones[zone] && rollCounter - burstZones[zone].first > BURST_WIN) { delete burstZones[zone]; } } function isBurst(zone) { return zone !== null && burstZones.hasOwnProperty(String(zone)); } // ==================== CHAOS DETECTION (NEW v17) ==================== // Detects clusters of boundary zones (0,14,88,92,99) that // interrupt macro chains. After chaos, chains still complete // but with extra delay. var recentBoundaryRolls = []; // tracks recent boundary zone roll indices function detectChaos(zone) { if (zone === null || !CHAOS_BOUNDARY_SET[zone]) { // No boundary zone — just decay the timer if (chaosTimer > 0) chaosTimer--; return false; } // Record this boundary zone recentBoundaryRolls.push(rollCounter); // Keep only recent entries within CHAOS_CLUSTER_WIN while (recentBoundaryRolls.length > 0 && rollCounter - recentBoundaryRolls[0] > CHAOS_CLUSTER_WIN) { recentBoundaryRolls.shift(); } // Check if we have a cluster if (recentBoundaryRolls.length >= CHAOS_MIN_CLUSTER) { // Chaos event detected! if (chaosTimer <= 0) { // New chaos event (not extending existing) chaosCount++; } chaosTimer = CHAOS_MEMORY; // Extend active prediction's max wait if (predQueue.length > 0 && predQueue[0].status === 'active') { if (!predQueue[0].chaosExtended) { predQueue[0].mw += CHAOS_DELAY_BONUS; predQueue[0].chaosExtended = true; } } // Record chaos→next pattern in macro dict // (what comes after chaos clusters historically) recordChaosMacro(); return true; } if (chaosTimer > 0) chaosTimer--; return false; } function recordChaosMacro() { // Find the boundary zone cluster in notableSeq if (notableSeq.length < 3) return; // Look for the last 2+ boundary zones in notableSeq var clusterEnd = -1; var clusterStart = -1; var clusterCount = 0; for (var i = notableSeq.length - 1; i >= 0; i--) { if (CHAOS_BOUNDARY_SET[notableSeq[i].z]) { if (clusterEnd === -1) clusterEnd = i; clusterStart = i; clusterCount++; if (clusterCount >= CHAOS_MIN_CLUSTER) break; } else if (clusterCount > 0) { break; // gap in cluster } } if (clusterCount < CHAOS_MIN_CLUSTER || clusterStart < 1) return; // Build prefix from zones before the cluster var preLen = Math.min(2, clusterStart); var prefix = []; for (var i = clusterStart - preLen; i < clusterStart; i++) { if (i >= 0) prefix.push(notableSeq[i].z); } if (prefix.length === 0) return; // Build continuation from zones after the cluster var contStart = clusterEnd + 1; if (contStart >= notableSeq.length) return; var cont = []; var gaps = []; for (var i = contStart; i < notableSeq.length && cont.length < 5; i++) { if (i > contStart) { gaps.push(notableSeq[i].ri - notableSeq[i-1].ri); } cont.push(notableSeq[i].z); } if (cont.length === 0) return; // Store as special "CHAOS:" prefix macro var chaosPrefix = 'CHAOS:' + prefix.join(','); if (!macros[chaosPrefix]) macros[chaosPrefix] = []; var contStr = cont.join(','); var found = false; for (var j = 0; j < macros[chaosPrefix].length; j++) { if (macros[chaosPrefix][j].contStr === contStr) { macros[chaosPrefix][j].count++; macros[chaosPrefix][j].last = rollCounter; for (var gi = 0; gi < gaps.length; gi++) { macros[chaosPrefix][j].gaps.push(gaps[gi]); if (macros[chaosPrefix][j].gaps.length > GAP_KEEP * 3) macros[chaosPrefix][j].gaps.shift(); } found = true; break; } } if (!found) { macros[chaosPrefix].push({ contStr: contStr, cont: cont.slice(), gaps: gaps.slice(), count: 1, first: rollCounter, last: rollCounter }); } } function isInChaos() { return chaosTimer > 0; } // ==================== MODEL (with BIAS) ==================== function initModel() { for (var z = 0; z < ZONE_COUNT; z++) { if (!model[z]) model[z] = { h: 0, c: ZONE_COUNT, ls: 0, bias: 0 }; } } // ==================== ABSENT ZONE PENALTY ==================== function getAbsentPenalty(zone) { if (!model[zone] || model[zone].ls === 0) return 1.0; var absent = rollCounter - model[zone].ls; if (absent > ABSENT_PENALTY_R2) return 0; if (absent > ABSENT_PENALTY_R1) return ABSENT_PENALTY_FACTOR; return 1.0; } // ==================== DELAY FEEDBACK ==================== function updateBias(zone, predictedGap, actualDelay) { if (!model[zone]) return; var m = model[zone]; var error = actualDelay - predictedGap; m.bias = (m.bias || 0) + error * 0.02; if (m.bias > 4) m.bias = 4; if (m.bias < -4) m.bias = -4; } function applyBias(zone, eta) { if (!model[zone] || !model[zone].bias) return eta; return eta * (1 + model[zone].bias * 0.08); } // ==================== DRIFT CORRECTOR (NEW v17.2) ==================== function updateDrift(entry) { // Called when a prediction resolves (HIT/DELAYED/NEAR/OVERDELAYED/EXPIRED) if (!entry || !entry.rtr || !entry.mw) return; var expected = entry.mw * DRIFT_EXPECTED; var error = entry.rtr - expected; // positive = came late // Global drift if (driftObs < DRIFT_MIN_OBS) { globalDrift += error; } else { globalDrift = globalDrift * (1 - DRIFT_LR) + error * DRIFT_LR; } if (globalDrift > DRIFT_CLAMP) globalDrift = DRIFT_CLAMP; if (globalDrift < -DRIFT_CLAMP) globalDrift = -DRIFT_CLAMP; driftObs++; // Per-zone drift var z = entry.z; if (zoneDrift[z] === undefined) zoneDrift[z] = 0; zoneDrift[z] = zoneDrift[z] * (1 - DRIFT_LR) + error * DRIFT_LR; if (zoneDrift[z] > DRIFT_CLAMP) zoneDrift[z] = DRIFT_CLAMP; if (zoneDrift[z] < -DRIFT_CLAMP) zoneDrift[z] = -DRIFT_CLAMP; } function applyDrift(zone, eta) { // Apply global + per-zone drift correction to ETA if (driftObs < DRIFT_MIN_OBS) return eta; var correction = globalDrift + (zoneDrift[zone] || 0); // Drift is positive when predictions arrive late → reduce ETA // Drift is negative when predictions arrive early → increase ETA var corrected = eta - correction; return Math.max(1, corrected); } // ==================== LIFECYCLE ==================== function getSeedRate() { var ws = Math.max(0, history.length - LF_WIN); var seedHits = 0, total = 0; for (var i = ws; i < history.length; i++) { var z = history[i].zone; if (z !== null) { total++; if (SEED_SET[z]) seedHits++; } } return total > 0 ? seedHits / total : 0; } function updateLifecycle() { var rate = getSeedRate(); // v17.2 FIX 8: If a SEED zone appeared recently (within SEED_RECENT_WIN rolls), // don't let lifecycle drop below ACTIVE. This prevents killing predictions // during spread-out but real SEED chains where rate temporarily dips. var recentSeed = false; if (lifecycle === 'ACTIVE' || lifecycle === 'ENTER') { for (var ri = history.length - 1; ri >= 0 && ri >= history.length - SEED_RECENT_WIN; ri--) { if (history[ri].zone !== null && SEED_SET[history[ri].zone]) { recentSeed = true; break; } } } switch (lifecycle) { case 'CHAOS': if (rate >= LF_PAT_RATE) { lifecycle = 'ENTER'; lifeCount = 0; } break; case 'ENTER': lifeCount++; if (rate < LF_CHAOS_RATE && !recentSeed) { lifecycle = 'CHAOS'; lifeCount = 0; } else if (lifeCount >= LF_ENTER_DUR) { lifecycle = 'ACTIVE'; lifeCount = 0; } break; case 'ACTIVE': lifeCount++; if (rate < LF_CHAOS_RATE && !recentSeed) { lifecycle = 'EXIT'; lifeCount = 0; } break; case 'EXIT': lifeCount++; if (rate >= LF_PAT_RATE || recentSeed) { lifecycle = 'ACTIVE'; lifeCount = 0; } else if (lifeCount >= LF_EXIT_DUR) { lifecycle = 'CHAOS'; lifeCount = 0; for (var i = 0; i < predQueue.length; i++) { if (predQueue[i].status === 'active') { resolveQueueEntry(i, 'CANCEL'); } } predQueue = []; } break; } } // ==================== CHAIN DATABASE ==================== function addChainEntries(newZone) { if (notableSeq.length === 0) return; var gap = rollCounter - notableSeq[notableSeq.length - 1].ri; if (gap <= 0 || gap > MAX_GAP_REC) return; for (var ni = 0; ni < NGRAMS.length; ni++) { var K = NGRAMS[ni]; if (notableSeq.length < K) continue; var parts = []; for (var i = notableSeq.length - K; i < notableSeq.length; i++) parts.push(notableSeq[i].z); var key = parts.join(','); if (!chains[key]) chains[key] = { _t: 0 }; chains[key]._t++; var zk = String(newZone); if (!chains[key][zk]) chains[key][zk] = { g: [], n: 0, m: 0, s: 0 }; var e = chains[key][zk]; e.g.push(gap); if (e.g.length > GAP_KEEP) e.g.shift(); e.n = e.g.length; var sum = 0; for (var gi = 0; gi < e.g.length; gi++) sum += e.g[gi]; e.m = sum / e.n; var ss = 0; for (var gi = 0; gi < e.g.length; gi++) ss += (e.g[gi] - e.m) * (e.g[gi] - e.m); e.s = Math.sqrt(ss / e.n); } } function queryChains() { if (notableSeq.length < 1) return []; var candidates = {}; for (var ni = 0; ni < NGRAMS.length; ni++) { var K = NGRAMS[ni]; if (notableSeq.length < K) continue; var parts = []; for (var i = notableSeq.length - K; i < notableSeq.length; i++) parts.push(notableSeq[i].z); var key = parts.join(','); var chain = chains[key]; if (!chain || (chain._t || 0) < MIN_OBS) continue; for (var zk in chain) { if (zk === '_t') continue; var z = parseInt(zk); if (!isChainable(z)) continue; var e = chain[zk]; if (e.n < MIN_OBS) continue; var ap = getAbsentPenalty(z); if (ap === 0) continue; var kls = K * 20; var ms = Math.min(25, e.n * 5); var cv = e.m > 0 ? e.s / e.m : 1; var cs = Math.max(0, 1 - cv) * 35; var lastAge = rollCounter - notableSeq[notableSeq.length - 1].ri; var ad = Math.abs(lastAge - e.m); var gr = Math.exp(-(ad * ad) / 128); var gs = gr * 20; var score = (kls + ms + cs + gs) * ap; // v17: chaos bonus — if in chaos, boost score for post-chaos zones if (isInChaos()) { // Zones that historically follow chaos patterns get a bonus score *= 0.9; // slight penalty during chaos — less confident } var mdl = model[z]; if (mdl && mdl.ls > 0 && mdl.c > 0) { var age = rollCounter - mdl.ls; var ratio = age / mdl.c; if (ratio >= 0.7 && ratio <= 1.4) score += 8; } if (isBurst(z)) score += BURST_BOOST; var isSeed = SEED_SET[z]; if (!isSeed) score *= 0.5; if (!candidates[zk] || candidates[zk].cf < score) { candidates[zk] = { z: z, cf: score, src: key + '\u2192' + zk, n: e.n, m: e.m, s: e.s, lg: e.g.slice(-5), gr: gr, K: K, burst: isBurst(z), isSeed: isSeed, gap: e.m }; } } } var result = []; for (var k in candidates) result.push(candidates[k]); // v17.7 FIX: was b.sc - a.sc, but objects have .cf not .sc — sort was a no-op result.sort(function(a, b) { return (b.cf || 0) - (a.cf || 0); }); return result; } // ==================== MACRO CHAIN DICTIONARY ==================== function buildMacros() { if (notableSeq.length < MACRO_MIN_LEN) return; var scanLen = Math.min(notableSeq.length, MACRO_SCAN_LEN); var start = Math.max(0, notableSeq.length - scanLen); for (var pi = 0; pi < MACRO_PREFIX.length; pi++) { var PL = MACRO_PREFIX[pi]; for (var si = start; si <= notableSeq.length - MACRO_MIN_LEN; si++) { // v17: skip if prefix contains non-chainable zones (noise reduction) var allChainable = true; for (var p = 0; p < PL; p++) { if (!isChainable(notableSeq[si + p].z)) { allChainable = false; break; } } if (!allChainable) continue; var prefix = []; for (var p = 0; p < PL; p++) prefix.push(notableSeq[si + p].z); var cont = []; var gaps = []; for (var ci = si + PL; ci < notableSeq.length && ci - si < MACRO_MAX_LEN; ci++) { gaps.push(notableSeq[ci].ri - notableSeq[ci - 1].ri); cont.push(notableSeq[ci].z); } if (cont.length < 1) continue; var key = prefix.join(','); if (!macros[key]) macros[key] = []; var contStr = cont.join(','); var found = false; for (var j = 0; j < macros[key].length; j++) { if (macros[key][j].contStr === contStr) { macros[key][j].count++; macros[key][j].last = rollCounter; for (var gi = 0; gi < gaps.length; gi++) { macros[key][j].gaps.push(gaps[gi]); if (macros[key][j].gaps.length > GAP_KEEP * 3) macros[key][j].gaps.shift(); } found = true; break; } } if (!found) { macros[key].push({ contStr: contStr, cont: cont.slice(), gaps: gaps.slice(), count: 1, first: notableSeq[si].ri, last: rollCounter }); } } } } function matchMacros() { if (notableSeq.length < 2) return []; var results = []; for (var pi = 0; pi < MACRO_PREFIX.length; pi++) { var PL = MACRO_PREFIX[pi]; if (notableSeq.length < PL) continue; var prefix = []; for (var p = 0; p < PL; p++) prefix.push(notableSeq[notableSeq.length - PL + p].z); var key = prefix.join(','); var entries = macros[key]; if (!entries) continue; var bestEntry = null; var bestScore = 0; for (var j = 0; j < entries.length; j++) { var e = entries[j]; if (e.count < MACRO_MIN_OBS) continue; var age = rollCounter - e.last; var recency = Math.exp(-age * age / (2 * 5000 * 5000)); var avgGap = 0; if (e.gaps.length > 0) { for (var gi = 0; gi < e.gaps.length; gi++) avgGap += e.gaps[gi]; avgGap /= e.gaps.length; } var gapVar = 0; if (e.gaps.length > 1) { for (var gi = 0; gi < e.gaps.length; gi++) gapVar += (e.gaps[gi] - avgGap) * (e.gaps[gi] - avgGap); gapVar = Math.sqrt(gapVar / e.gaps.length); } var consistency = avgGap > 0 ? Math.max(0, 1 - gapVar / avgGap) : 0; var score = e.count * 15 + recency * 20 + consistency * 30; if (score > bestScore) { bestScore = score; bestEntry = e; } } if (!bestEntry) continue; var lastAge = rollCounter - notableSeq[notableSeq.length - 1].ri; var cumulativeGap = lastAge; for (var ci = 0; ci < bestEntry.cont.length && results.length < QUEUE_SIZE; ci++) { var z = bestEntry.cont[ci]; var dup = false; for (var ri = 0; ri < results.length; ri++) { if (results[ri].z === z) { dup = true; break; } } if (dup) continue; var ap = getAbsentPenalty(z); if (ap === 0) continue; var isSeed = SEED_SET[z]; if (ci < bestEntry.gaps.length) { cumulativeGap += bestEntry.gaps[ci]; } else { cumulativeGap += avgGap || 8; } var posBonus = Math.max(0, 40 - ci * 10); var cf = Math.min(90, posBonus + bestEntry.count * 8 + bestScore * 0.3); cf *= ap; if (!isSeed) cf *= 0.5; // v17: chaos context — if matching a CHAOS: prefix macro, // slightly boost confidence since we're in post-chaos recovery if (key.indexOf('CHAOS:') === 0) { cf = Math.min(90, cf * 1.15); } results.push({ z: z, cf: cf, src: 'M:' + key + '\u2192' + bestEntry.cont.slice(0, ci + 1).join(','), eta: cumulativeGap, gap: ci < bestEntry.gaps.length ? bestEntry.gaps[ci] : (avgGap || 8), macro: true, pos: ci, isSeed: isSeed }); } } // v17.12: User-defined chain block removed (module deleted). // v17: Also match CHAOS: prefix macros when in/recently in chaos if (isInChaos() && notableSeq.length >= 2) { // Build prefix from last 2 notable zones before boundary cluster var cprefix = 'CHAOS:'; var pStart = Math.max(0, notableSeq.length - 3); var pp = []; for (var i = pStart; i < notableSeq.length; i++) { if (!CHAOS_BOUNDARY_SET[notableSeq[i].z]) { pp.push(notableSeq[i].z); } if (pp.length >= 2) break; } if (pp.length >= 2) { cprefix += pp.join(','); var chaosEntries = macros[cprefix]; if (chaosEntries) { for (var j = 0; j < chaosEntries.length; j++) { var ce = chaosEntries[j]; if (ce.count < 2) continue; // Add first continuation as candidate if (ce.cont.length > 0) { var cz = ce.cont[0]; var dup = false; for (var ri = 0; ri < results.length; ri++) { if (results[ri].z === cz) { dup = true; break; } } if (!dup && isChainable(cz)) { var ap2 = getAbsentPenalty(cz); if (ap2 > 0) { var avgGap2 = 0; if (ce.gaps.length > 0) { for (var gi = 0; gi < ce.gaps.length; gi++) avgGap2 += ce.gaps[gi]; avgGap2 /= ce.gaps.length; } results.push({ z: cz, cf: Math.min(85, 30 + ce.count * 10) * ap2, src: 'M:' + cprefix + '\u2192' + ce.cont[0], eta: avgGap2 || 10, gap: avgGap2 || 10, macro: true, pos: 0, isSeed: SEED_SET[cz] }); } } } } } } } return results; } // ==================== OVERDELAYED GHOST TRACKING (NEW v17) ==================== function createGhost(entry) { if (ghosts.length >= MAX_GHOSTS) { // Remove oldest ghost ghosts.shift(); } ghosts.push({ z: entry.z, cf: entry.cf, src: entry.src, at: entry.at, expiredAt: rollCounter, gap: entry.gap || 10, isSeed: !!entry.isSeed, tracked: false }); } function checkGhosts(zone) { if (zone === null || ghosts.length === 0) return; for (var i = ghosts.length - 1; i >= 0; i--) { var g = ghosts[i]; var age = rollCounter - g.expiredAt; if (age > OVERDELAYED_WINDOW) { // Ghost expired — remove ghosts.splice(i, 1); continue; } if (zone === g.z && !g.tracked) { // OVERDELAYED HIT! g.tracked = true; g.hitAt = rollCounter; g.totalDelay = rollCounter - g.at; // Apply bias feedback (learn from this delayed hit) updateBias(g.z, g.gap, g.totalDelay); // Log as OVERDELAYED in prediction log plog.push({ id: plog.length + 1, at: g.at, z: g.z, cf: g.cf, src: g.src + ' [OVERDELAYED]', lf: '?', // lifecycle at original prediction time unknown mw: 0, // already expired macro: false, burst: false, queue: [g.z], done: true, rt: rollCounter, res: 'OVERDELAYED', rtr: g.totalDelay, delayed: true, overdelayed: true, nearHit: false, ghostAge: age }); // Remove ghost after logging ghosts.splice(i, 1); } } } // ==================== PREDICTION QUEUE ENGINE (v17) ==================== function isZoneInCooldown(zone) { if (zone === null || zone === undefined) return false; var ts = predCooldown[zone]; if (ts === undefined) return false; var age = rollCounter - ts; if (age >= PRED_COOLDOWN_ROLLS) { // Cooldown expired — clean up delete predCooldown[zone]; return false; } return true; } function setZoneCooldown(zone) { if (zone === null || zone === undefined) return; predCooldown[zone] = rollCounter; // Trim map: keep only zones with active cooldown var keys = Object.keys(predCooldown); for (var i = 0; i < keys.length; i++) { var z = parseInt(keys[i]); if (rollCounter - predCooldown[z] >= PRED_COOLDOWN_ROLLS) { delete predCooldown[z]; } } } function clearZoneCooldown(zone) { if (zone === null || zone === undefined) return; if (predCooldown[zone] !== undefined) delete predCooldown[zone]; } function buildQueue() { if (lifecycle === 'CHAOS') { if (burstZones) { var bestB = null, bestC = 0; for (var bz in burstZones) { if (burstZones[bz].count > bestC) { bestC = burstZones[bz].count; bestB = parseInt(bz); } } if (bestB !== null && bestC >= BURST_MIN + 1) { var bcf = Math.min(80, 35 + (bestC - BURST_MIN) * 12); return [{z: bestB, cf: bcf, src: 'BURST(' + bestC + 'x)', eta: 5, gap: 5, mw: 15, macro: false, burst: true, isSeed: SEED_SET[bestB]}]; } } return []; } // 1. Get candidates from both sources var macroCands = matchMacros(); var chainCands = queryChains(); // 2. Merge all candidates var allCands = []; for (var i = 0; i < macroCands.length; i++) allCands.push(macroCands[i]); for (var i = 0; i < chainCands.length; i++) allCands.push(chainCands[i]); // Apply lifecycle multiplier for (var i = 0; i < allCands.length; i++) { var lm = lifecycle === 'ENTER' ? 0.65 : lifecycle === 'ACTIVE' ? 1.0 : 0.35; if (allCands[i].burst) lm = Math.max(lm, 0.7); allCands[i].cf = Math.min(95, allCands[i].cf * lm); } // 3. Deduplicate by zone, keep highest confidence // v17.7: skip zones in cooldown (recently expired without hit) // v17.10: also track the skipped CANDIDATE OBJECTS (not just zone numbers) // so the stuck-loop guard below can fall back to them as alternatives. var bestPerZone = {}; var cooldownSkipped = []; // zone numbers (for display/logging) var cooldownSkippedCands = {}; // zone → best candidate object (for fallback) for (var i = 0; i < allCands.length; i++) { var c = allCands[i]; var effectiveMinCf = c.isSeed ? PRD_MIN_CF : PRD_MIN_CF * NON_SEED_CF_MULT; if (c.cf < effectiveMinCf) continue; // v17.7: cooldown gate if (isZoneInCooldown(c.z)) { if (cooldownSkipped.indexOf(c.z) === -1) cooldownSkipped.push(c.z); // v17.10: keep the best candidate object per skipped zone if (!cooldownSkippedCands[c.z] || cooldownSkippedCands[c.z].cf < c.cf) { cooldownSkippedCands[c.z] = c; } continue; } if (!bestPerZone[c.z] || bestPerZone[c.z].cf < c.cf) { bestPerZone[c.z] = c; } } // 4. Sort by ETA (soonest first) var sorted = []; for (var zk in bestPerZone) sorted.push(bestPerZone[zk]); sorted.sort(function(a, b) { return (a.eta || 999) - (b.eta || 999); }); // v17.10: Track ALL candidates that were filtered out by cooldown, so we can // fall back to them if the final queue has only one entry and it's the same // zone as the just-expired prediction (stuck-loop guard). // 5. Build queue var queue = []; var lastAge = rollCounter - (notableSeq.length > 0 ? notableSeq[notableSeq.length - 1].ri : 0); for (var i = 0; i < sorted.length && queue.length < QUEUE_SIZE; i++) { var c = sorted[i]; var rawEta = Math.max(1, c.gap - lastAge); var adjEta = Math.max(1, Math.round(applyBias(c.z, rawEta))); var mw = calcMaxWait(c.gap || 8); // v17: if currently in chaos, extend mw if (isInChaos()) { mw += CHAOS_DELAY_BONUS; mw = Math.min(PRD_MAX_WAIT + CHAOS_DELAY_BONUS, mw); } queue.push({ z: c.z, cf: c.cf, src: c.src, eta: adjEta, gap: c.gap, mw: mw, macro: !!c.macro, burst: !!c.burst, isSeed: !!c.isSeed, at: rollCounter, status: queue.length === 0 ? 'active' : 'queued', delayed: false, delayStart: 0, nearHit: false, chaosExtended: false // v17: track if chaos already extended this }); } // v17.10 STUCK-LOOP GUARD: if (queue.length === 1 && queue[0].z !== undefined) { var altBest = null; // First try cooldown-skipped candidates (the most likely source of alternatives) for (var altZ in cooldownSkippedCands) { if (parseInt(altZ) === queue[0].z) continue; var altC = cooldownSkippedCands[altZ]; if (!altBest || altC.cf > altBest.cf) altBest = altC; } if (altBest) { var altRawEta = Math.max(1, altBest.gap - lastAge); var altAdjEta = Math.max(1, Math.round(applyBias(altBest.z, altRawEta))); var altMw = calcMaxWait(altBest.gap || 8); if (isInChaos()) { altMw += CHAOS_DELAY_BONUS; altMw = Math.min(PRD_MAX_WAIT + CHAOS_DELAY_BONUS, altMw); } queue.push({ z: altBest.z, cf: Math.max(1, Math.round(altBest.cf)), src: altBest.src + ' [ALT]', eta: altAdjEta, gap: altBest.gap, mw: altMw, macro: !!altBest.macro, burst: !!altBest.burst, isSeed: !!altBest.isSeed, at: rollCounter, status: 'queued', delayed: false, delayStart: 0, nearHit: false, chaosExtended: false }); } } return queue; } function calcMaxWait(gapMean) { var mw = Math.round(gapMean * PRD_WAIT_MULT); return Math.max(PRD_MIN_WAIT, Math.min(PRD_MAX_WAIT, mw)); } function updateQueue() { var needRebuild = false; if (predQueue.length === 0) { needRebuild = true; } else { var active = predQueue[0]; if (!active) { needRebuild = true; } else if (active.status === 'resolved') { predQueue.shift(); if (predQueue.length > 0 && predQueue[0].status === 'queued') { predQueue[0].status = 'active'; predQueue[0].at = rollCounter; } if (predQueue.length === 0) needRebuild = true; } else if (active.status === 'active') { var age = rollCounter - active.at; if (age > active.mw && !active.delayed) { active.delayed = true; active.delayStart = rollCounter; } if (active.delayed && rollCounter - active.delayStart > DELAYED_EXT) { // FEEDBACK — learn from this failure updateBias(active.z, active.gap || 10, rollCounter - active.at); // v17.2: Update drift on expiry too updateDrift({z: active.z, rtr: rollCounter - active.at, mw: active.mw}); var res = active.nearHit ? 'NEAR' : 'EXPIRED'; // v17: CREATE GHOST before resolving createGhost(active); // v17.7: Set cooldown on the expired zone so it doesn't get // re-predicted immediately. NEAR counts as a soft failure // (close but no hit) so it also enters cooldown. HIT/DELAYED // do NOT enter cooldown (handled in resolveQueueEntry/checkQueueHit). if (res === 'EXPIRED' || res === 'NEAR') { setZoneCooldown(active.z); } resolveQueueEntry(0, res); needRebuild = true; } // Last resort: in-place confidence update if (!needRebuild && !active.delayed && age > active.mw * 0.8 && !active.nearHit) { if (rollCounter - lastRebuild > NO_REBUILD_PERIOD) { var betterCf = findBestCfForZone(active.z); if (betterCf > active.cf + REPLACE_CF_BOOST) { active.cf = betterCf; lastRebuild = rollCounter; } } } } } if (needRebuild) { var newQueue = buildQueue(); if (newQueue.length > 0) { if (predQueue.length > 0) { var cur = predQueue[0]; if (cur.status === 'active') { var curAge = rollCounter - cur.at; if (curAge < cur.mw * IMMUNITY_FRAC) { for (var i = 0; i < newQueue.length; i++) { if (newQueue[i].z === cur.z) { if (newQueue[i].cf > cur.cf + 5) { cur.cf = Math.round((cur.cf + newQueue[i].cf) / 2); } return; } } return; // immune — keep current } } } for (var i = 0; i < predQueue.length; i++) { if (predQueue[i].status === 'active') { resolveQueueEntry(i, 'REPLACED'); } } var entry = newQueue[0]; plog.push({ id: plog.length + 1, at: rollCounter, z: entry.z, cf: entry.cf, src: entry.src, lf: lifecycle, mw: entry.mw, macro: !!entry.macro, burst: !!entry.burst, queue: newQueue.map(function(q) { return q.z; }), done: false }); predQueue = newQueue; lastRebuild = rollCounter; } } } function findBestCfForZone(zone) { var chainCands = queryChains(); var best = 0; for (var i = 0; i < chainCands.length; i++) { if (chainCands[i].z === zone && chainCands[i].cf > best) { best = chainCands[i].cf; } } var macroCands = matchMacros(); for (var i = 0; i < macroCands.length; i++) { if (macroCands[i].z === zone && macroCands[i].cf > best) { best = macroCands[i].cf; } } return best; } function resolveQueueEntry(idx, result) { if (idx >= predQueue.length) return; var entry = predQueue[idx]; if (entry.status !== 'active') return; entry.status = 'resolved'; entry.res = result; entry.rt = rollCounter; entry.rtr = rollCounter - entry.at; // DELAY FEEDBACK on resolution if (result === 'HIT' || result === 'DELAYED') { updateBias(entry.z, entry.gap || 10, entry.rtr); // v17.7: clear cooldown on successful hit — the zone proved it can hit if (PRED_COOLDOWN_HIT_CLEAR) clearZoneCooldown(entry.z); } // v17.2: Update drift corrector on ANY resolution updateDrift(entry); // Update prediction log for (var i = plog.length - 1; i >= 0; i--) { var pl = plog[i]; if (!pl.done && pl.z === entry.z && pl.at === entry.at) { pl.done = true; pl.rt = rollCounter; pl.res = result; pl.rtr = rollCounter - pl.at; pl.delayed = !!entry.delayed; pl.nearHit = !!entry.nearHit; if (entry.nearZone !== undefined) pl.nearZone = entry.nearZone; break; } } } function checkQueueHit(zone) { if (zone === null || predQueue.length === 0) return; for (var i = 0; i < predQueue.length; i++) { var q = predQueue[i]; if (q.status !== 'active' && q.status !== 'queued') continue; // Exact hit if (zone === q.z) { if (i === 0) { var result = q.delayed ? 'DELAYED' : 'HIT'; resolveQueueEntry(i, result); predQueue.shift(); if (predQueue.length > 0 && predQueue[0].status === 'queued') { predQueue[0].status = 'active'; predQueue[0].at = rollCounter; } if (predQueue.length === 0) { var nq = buildQueue(); if (nq.length > 0) { plog.push({ id: plog.length + 1, at: rollCounter, z: nq[0].z, cf: nq[0].cf, src: nq[0].src, lf: lifecycle, mw: nq[0].mw, macro: !!nq[0].macro, burst: !!nq[0].burst, queue: nq.map(function(q) { return q.z; }), done: false }); predQueue = nq; lastRebuild = rollCounter; } } } else { q.status = 'resolved'; q.res = 'HIT'; q.rt = rollCounter; q.rtr = rollCounter - (q.at || rollCounter); } return; } // Near hit (active entry only) if (i === 0 && Math.abs(zone - q.z) <= ZONE_SPREAD && zone !== q.z) { if (!q.nearHit) { q.nearHit = true; q.nearZone = zone; if (!q.delayed) { q.mw = Math.max(q.mw, rollCounter - q.at + 15); } } } } } // ==================== REGISTER HIT ==================== function registerHit(zone) { if (zone !== null) { var m = model[zone]; var gap = rollCounter - m.ls; if (m.ls > 0 && gap > 0) m.c = m.c * 0.7 + gap * 0.3; m.h++; m.ls = rollCounter; } if (rollCounter % 50 === 0) dynZones = computeDynZones(); updateBurstZones(zone); updateLifecycle(); // v17: detect chaos BEFORE checking queue detectChaos(zone); checkQueueHit(zone); // v17: check ghosts AFTER queue (ghosts are passive observers) checkGhosts(zone); if (isChainable(zone)) { addChainEntries(zone); notableSeq.push({ z: zone, ri: rollCounter }); if (notableSeq.length > MAX_NOTABLE) notableSeq = notableSeq.slice(-MAX_NOTABLE); buildMacros(); } updateQueue(); } // ==================== OVERLAY ==================== function createOverlay() { overlay = document.createElement('div'); overlay.id = 'dice-analyzer-overlay'; overlay.style.cssText = 'position:fixed;top:40px;right:20px;width:350px;max-height:94vh;' + 'overflow:auto;z-index:999999;background:#0a0a0a;color:#00ff00;' + 'font-family:monospace;font-size:12px;line-height:1.2;' + 'border:2px solid #00ff00;padding:6px 8px;cursor:move;' + 'box-shadow:0 0 15px rgba(0,255,0,0.1);'; document.body.appendChild(overlay); dragElement(overlay); } function dragElement(el) { var x = 0, y = 0; el.onmousedown = function(e) { x = e.clientX; y = e.clientY; document.onmouseup = stop; document.onmousemove = move; }; function move(e) { el.style.top = (el.offsetTop - (y - e.clientY)) + 'px'; el.style.left = (el.offsetLeft - (x - e.clientX)) + 'px'; x = e.clientX; y = e.clientY; } function stop() { document.onmouseup = null; document.onmousemove = null; } } // ==================== RENDER ==================== // v17: safe toFixed helper function sfmt(v, d) { var n = parseFloat(v); if (isNaN(n)) return d !== undefined ? d : '0'; return n.toFixed(d === undefined ? 0 : d); } function render() { var html = ''; var seedRate = getSeedRate(); // Header html += '
' + '[DATA] ' + '[PRED] ' + '[FLOW] ' + '[IMPORT] ' + '[RESET]' + '
' + '' + SITE + ' v17.13 R:' + rollCounter + '' + '
'; // Lifecycle var mc, mt; switch (lifecycle) { case 'CHAOS': mc = '#f44'; mt = 'CHAOS'; break; case 'ENTER': mc = '#ff0'; mt = 'ENTERING'; break; case 'ACTIVE': mc = '#0f0'; mt = 'PATTERN'; break; case 'EXIT': mc = '#f80'; mt = 'EXITING'; break; default: mc = '#666'; mt = '?'; break; } var chainableCount = SEED.length + Object.keys(dynZones).length; var burstList = Object.keys(burstZones); var macroCount = 0; for (var k in macros) { if (macros[k] && macros[k].length) macroCount++; } html += '
' + '\u25CF ' + '' + mt + '' + ' ' + lifeCount + 'r' + '
'; // v17.2: Drift display var driftStr = driftObs >= DRIFT_MIN_OBS ? 'GD:' + globalDrift.toFixed(1) + '' : 'GD:--'; var driftCfStr = driftObs >= DRIFT_MIN_OBS ? ' (' + (globalDrift > 0 ? 'ETA-' : 'ETA+') + Math.abs(globalDrift).toFixed(0) + 'r)' : ''; html += '
' + driftStr + driftCfStr + ' [' + driftObs + 'obs]
'; html += '
' + 'seed: ' + (seedRate * 100).toFixed(1) + '%' + (burstList.length > 0 ? ' | BURST:' + burstList.map(function(z){return String(z).padStart(2,'0');}).join(',') : '') + (isInChaos() ? ' | CHAOS!' + chaosTimer + 'r' : '') + ' | M:' + macroCount + ' | G:' + ghosts.length + '
'; html += '
'; // PREDICTION QUEUE if (predQueue.length > 0) { html += '
' + '\u26A1 PREDICTION QUEUE (' + predQueue.length + ')
'; for (var qi = 0; qi < predQueue.length; qi++) { var q = predQueue[qi]; var isActive = (qi === 0 && q.status === 'active'); var isResolved = (q.status === 'resolved'); var conf = q.cf || 0; var cc = conf > 60 ? '#0f0' : conf > 35 ? '#ff0' : '#f44'; var cl = conf > 75 ? 'V.HIGH' : conf > 55 ? 'HIGH' : conf > 35 ? 'MED' : 'LOW'; var bf = Math.min(10, Math.max(0, Math.round(conf / 10))); var bar = ''; for (var bi = 0; bi < 10; bi++) bar += (bi < bf) ? '\u2588' : '\u2591'; var statusTag = ''; if (isResolved) { var rc = q.res === 'HIT' ? '#0f0' : q.res === 'DELAYED' ? '#f80' : q.res === 'NEAR' ? '#ff0' : '#f44'; statusTag = ' ' + q.res + ''; } else if (isActive && q.delayed) { var dAge = rollCounter - q.delayStart; var dLeft = DELAYED_EXT - dAge; statusTag = ' DELAYED ' + dLeft + 'r'; } var nearTag = ''; if (q.nearHit && !isResolved) { nearTag = ' ~' + String(q.nearZone).padStart(2,'0') + ''; } var rowBg = isActive ? 'background:rgba(255,255,0,0.05);' : ''; var arrow = isActive ? '\u25B6' : ' '; var macroTag = q.macro ? 'M' : ''; var burstTag = q.burst ? 'P' : ''; var seedTag = !q.isSeed ? '~S' : ''; var zRange = ZONE_SPREAD > 0 ? '\u00B1' + ZONE_SPREAD + '' : ''; // v17: show bias for active entry var biasTag = ''; if (isActive && !isResolved && model[q.z]) { var bias = model[q.z].bias || 0; if (Math.abs(bias) > 0.3) { biasTag = ' b:' + sfmt(bias, 1) + ''; } } // v17: show absent warning var absentTag = ''; if (isActive && !isResolved && model[q.z]) { var absent = rollCounter - (model[q.z].ls || 0); if (absent > ABSENT_PENALTY_R1) { absentTag = ' ABSENT:' + absent + 'r'; } } // v17: chaos extension indicator var chaosTag = ''; if (q.chaosExtended) { chaosTag = ' +CHAOS'; } html += '
' + '' + arrow + ' ' + '' + String(q.z).padStart(2, '0') + '' + zRange + ' ' + '' + sfmt(conf) + '% ' + '' + cl + ' ' + macroTag + burstTag + seedTag + statusTag + nearTag + biasTag + absentTag + chaosTag; if (isActive && !isResolved) { var age = rollCounter - q.at; var pct = q.mw > 0 ? Math.min(100, Math.round(age / q.mw * 100)) : 0; html += '
' + 'eta~' + q.eta + 'r age:' + age + '/' + q.mw + 'r (' + pct + '%)' + ''; if (q.src) html += ' ' + q.src + ''; } html += '
'; } } else if (lifecycle === 'CHAOS') { html += '
' + '\u23F8 STANDBY
' + '
waiting for pattern...
'; } else { html += '
' + '\u23F8 scanning...
'; } // v17: Ghost tracker display if (ghosts.length > 0) { html += '
' + '
\u{1F47B} GHOSTS (' + ghosts.length + '):
'; for (var gi = 0; gi < ghosts.length; gi++) { var g = ghosts[gi]; var gAge = rollCounter - g.expiredAt; var gLeft = OVERDELAYED_WINDOW - gAge; html += '
' + String(g.z).padStart(2, '0') + ' ' + gLeft + 'r left' + ' ' + g.src + '' + '
'; } } // Chain context — v17.7 enhanced: 8 zones with gap info, single-chip prediction highlight if (notableSeq.length > 0) { var cxStart = Math.max(0, notableSeq.length - 8); // v17.7: build a set of predicted zones (active entry only, not whole queue, // to avoid the "12 in batches" highlight flood the user reported) var activePredZone = null; if (predQueue.length > 0 && predQueue[0].status === 'active') { activePredZone = predQueue[0].z; } // Build ghost zone set var ghostZoneSet = {}; for (var gi = 0; gi < ghosts.length; gi++) ghostZoneSet[ghosts[gi].z] = true; // Build cooldown zone set var cooldownZoneSet = {}; var cdKeys = Object.keys(predCooldown); for (var cki = 0; cki < cdKeys.length; cki++) { var cdz = parseInt(cdKeys[cki]); if (isZoneInCooldown(cdz)) cooldownZoneSet[cdz] = true; } var cxParts = []; for (var i = cxStart; i < notableSeq.length; i++) { var cz = notableSeq[i].z; var cColor = '#aaa'; // v17.7: priority — active pred > ghost > cooldown > burst > default if (cz === activePredZone) cColor = '#ff0'; else if (ghostZoneSet[cz]) cColor = '#f0f'; else if (cooldownZoneSet[cz]) cColor = '#88a'; // muted blue for cooldown else if (isBurst(cz)) cColor = '#f0f'; // v17: highlight boundary zones (highest priority visual) if (CHAOS_BOUNDARY_SET[cz]) cColor = '#f80'; // v17.7: gap between this and previous notable (shown as subscript) var gapStr = ''; if (i > cxStart) { var g = notableSeq[i].ri - notableSeq[i-1].ri; gapStr = '' + g + ''; } cxParts.push( '' + String(cz).padStart(2, '0') + '' + (i < notableSeq.length - 1 ? gapStr : '') ); } html += '
' + '
CTX ' + '(last ' + Math.min(8, notableSeq.length) + ' notable, gap# shown between):
' + '
' + cxParts.join('\u2192') + '\u2192?
'; // v17.7: cooldown summary line var cdCount = Object.keys(cooldownZoneSet).length; if (cdCount > 0) { html += '
' + 'cooldown: ' + cdCount + ' zone' + (cdCount > 1 ? 's' : '') + ' (' + Object.keys(cooldownZoneSet).map(function(z){return String(parseInt(z)).padStart(2,'0');}).join(',') + ')
'; } } // v17.7: native FLOW section REMOVED — use the floating Flow window instead. // Burst zones if (burstList.length > 0) { html += '
' + '
BURST:
'; for (var i = 0; i < burstList.length; i++) { var binfo = burstZones[burstList[i]]; html += '' + String(parseInt(burstList[i])).padStart(2,'0') + '' + '(' + binfo.count + 'x) '; } html += '
'; } // Prediction log html += '
' + '
PRED LOG:
'; var recentDone = []; for (var i = plog.length - 1; i >= 0 && recentDone.length < 10; i--) { if (plog[i].done) recentDone.push(plog[i]); } if (recentDone.length === 0) { html += '
no resolved predictions yet
'; } else { var rH = 0, rN = 0, rD = 0, rO = 0, rT = recentDone.length; for (var i = 0; i < recentDone.length; i++) { var pl = recentDone[i]; var icon, ic; if (pl.res === 'HIT') { icon = '\u2713'; ic = '#0f0'; rH++; } else if (pl.res === 'DELAYED') { icon = '\u23F3'; ic = '#f80'; rD++; } else if (pl.res === 'OVERDELAYED') { icon = '\u{1F552}'; ic = '#f0f'; rO++; } // v17 else if (pl.res === 'NEAR') { icon = '\u2248'; ic = '#ff0'; rN++; } else if (pl.res === 'EXPIRED') { icon = '\u2717'; ic = '#f44'; } else if (pl.res === 'REPLACED') { icon = '\u2192'; ic = '#f80'; } else { icon = '\u2022'; ic = '#555'; } var queueStr = pl.queue ? ' [' + pl.queue.map(function(z){return String(z).padStart(2,'0');}).join(',') + ']' : ''; // v17: safe cf display (was crash point) var cfDisplay = pl.cf !== null && pl.cf !== undefined ? sfmt(pl.cf) + '%' : '--'; html += '
' + '' + icon + ' ' + String(pl.z).padStart(2, '0') + (pl.nearZone !== undefined ? '~' + String(pl.nearZone).padStart(2,'0') + '' : '') + ' ' + cfDisplay + ' ' + '' + pl.rtr + 'r ' + '' + pl.res + '' + (pl.macro ? 'M' : '') + (pl.overdelayed ? 'OD' : '') + '' + queueStr + '' + '
'; } var eff = rH + rN + rD + rO; // v17: include overdelayed html += '
' + 'recent: ' + rH + 'H/' + rN + 'N/' + rD + 'D/' + rO + 'OD/' + rT + ' (' + (rT > 0 ? (100 * eff / rT).toFixed(0) : '0') + '% eff)
'; } // Overall stats (v17: include OVERDELAYED) var allH = 0, allN = 0, allD = 0, allO = 0, allR = 0, allT = 0; for (var i = 0; i < plog.length; i++) { if (plog[i].done) { allT++; var r = plog[i].res; if (r === 'HIT') allH++; else if (r === 'DELAYED') allD++; else if (r === 'OVERDELAYED') allO++; // v17 else if (r === 'NEAR') allN++; else if (r === 'REPLACED') allR++; } } if (allT > 0) { var allEff = allH + allN + allD + allO; // v17 html += '
' + 'all: ' + allH + 'H/' + allN + 'N/' + allD + 'D/' + allO + 'OD/' + allR + 'R/' + allT + ' (' + (100 * allEff / allT).toFixed(1) + '% eff, ' + (100 * allR / allT).toFixed(0) + '% repl)
'; } // Footer html += '
' + '
' + 'chains:' + Object.keys(chains).length + ' macros:' + macroCount + ' notable:' + notableSeq.length + ' plog:' + plog.length + ' chaos:' + chaosCount + '
'; overlay.innerHTML = html; overlay.querySelector('#xd').onclick = exportData; overlay.querySelector('#xp').onclick = exportPredLog; overlay.querySelector('#xf').onclick = openFlowPanel; // v17.12: open/toggle Flow window overlay.querySelector('#xi').onclick = importData; // v17 overlay.querySelector('#xr').onclick = resetAll; } // ==================== FLOW WINDOW (NEW v17.7) ==================== function openFlowPanel() { if (flowOverlay) { // Toggle visibility flowOverlay.style.display = flowOverlay.style.display === 'none' ? 'flex' : 'none'; if (flowOverlay.style.display === 'flex') { renderFlowPanel(); _startFlowRefresh(); } else { _stopFlowRefresh(); } return; } flowOverlay = document.createElement('div'); flowOverlay.id = 'dv177-flow-panel'; flowOverlay.style.cssText = 'position:fixed;top:10px;left:10px;width:560px;max-height:70vh;overflow-y:auto;' + 'z-index:999995;background:#0a0a14;color:#ddd;' + 'font-family:"Courier New",Consolas,monospace;font-size:11px;line-height:1.4;' + 'border:2px solid #0fa;border-radius:6px;padding:6px 8px;cursor:default;' + 'user-select:none;display:flex;flex-direction:column;' + 'scrollbar-width:thin;scrollbar-color:#066 #0a0a14;'; document.body.appendChild(flowOverlay); // Drag by header flowOverlay.addEventListener('mousedown', function(e) { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'LABEL') return; var ox = e.clientX, oy = e.clientY; var mm = function(e2) { flowOverlay.style.top = (flowOverlay.offsetTop + (e2.clientY - oy)) + 'px'; flowOverlay.style.left = (flowOverlay.offsetLeft + (e2.clientX - ox)) + 'px'; ox = e2.clientX; oy = e2.clientY; }; var mu = function() { document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); }; document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu); }); renderFlowPanel(); _startFlowRefresh(); } function _startFlowRefresh() { _stopFlowRefresh(); flowRefreshInterval = setInterval(function() { if (!flowOverlay || flowOverlay.style.display === 'none') { _stopFlowRefresh(); return; } // Skip refresh if user is interacting with an input (so typing isn't interrupted) var ae = document.activeElement; if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'SELECT') && flowOverlay.contains(ae)) return; renderFlowPanel(); }, FLOW_REFRESH_MS); } function _stopFlowRefresh() { if (flowRefreshInterval) { clearInterval(flowRefreshInterval); flowRefreshInterval = null; } } function _applyFlowSettings() { // Read inputs, clamp, persist, re-render var cntInput = document.getElementById('dv177-flow-count'); var gapInput = document.getElementById('dv177-flow-longgap'); if (cntInput) { var c = parseInt(cntInput.value); if (!isNaN(c)) { c = Math.max(FLOW_PANEL_COUNT_MIN, Math.min(FLOW_PANEL_COUNT_MAX, c)); flowPanelCount = c; cntInput.value = c; } } if (gapInput) { var g = parseInt(gapInput.value); if (!isNaN(g)) { g = Math.max(FLOW_LONG_GAP_MIN, Math.min(FLOW_LONG_GAP_MAX, g)); flowLongGap = g; gapInput.value = g; } } try { sv(); } catch(e) {} renderFlowPanel(); } function renderFlowPanel() { if (!flowOverlay) return; var N = Math.max(FLOW_PANEL_COUNT_MIN, Math.min(FLOW_PANEL_COUNT_MAX, flowPanelCount)); var longGap = Math.max(FLOW_LONG_GAP_MIN, Math.min(FLOW_LONG_GAP_MAX, flowLongGap)); // Collect last N notable zones (oldest first, newest last) var total = notableSeq.length; var startIdx = Math.max(0, total - N); var seeds = []; for (var i = startIdx; i < total; i++) { seeds.push({ z: notableSeq[i].z, pos: notableSeq[i].ri }); } // Determine current active prediction zone (single zone, for single-chip highlight) var activePredZone = null; if (predQueue.length > 0 && predQueue[0].status === 'active') { activePredZone = predQueue[0].z; } // Build ghost zone set var ghostZoneSet = {}; for (var gi = 0; gi < ghosts.length; gi++) ghostZoneSet[ghosts[gi].z] = true; // Build cooldown zone set var cooldownZoneSet = {}; var cdKeys = Object.keys(predCooldown); for (var cki = 0; cki < cdKeys.length; cki++) { var cdz = parseInt(cdKeys[cki]); if (isZoneInCooldown(cdz)) cooldownZoneSet[cdz] = true; } var h = ''; // Header: title + settings inputs + close h += '
'; h += 'FLOW ' + 'last ' + seeds.length + ' notable / ' + total + ' total'; h += '
'; h += ''; h += ''; h += ''; h += ''; h += '
'; h += '
'; if (seeds.length === 0) { h += '
' + 'No notable zones yet. Wait for SEED/dynamic zones to roll.
'; } else { // Gap stats var gaps = []; for (var gi2 = 0; gi2 < seeds.length - 1; gi2++) { gaps.push(seeds[gi2 + 1].pos - seeds[gi2].pos); } var gapMin = gaps.length ? gaps.reduce(function(a,b){return Math.min(a,b);}, gaps[0]) : 0; var gapMax = gaps.length ? gaps.reduce(function(a,b){return Math.max(a,b);}, gaps[0]) : 0; var gapAvg = gaps.length ? (gaps.reduce(function(a,b){return a+b;}, 0) / gaps.length) : 0; var longCount = gaps.length ? gaps.filter(function(g){return g >= longGap;}).length : 0; h += '
'; h += 'gap min/avg/max: ' + gapMin + '/' + gapAvg.toFixed(1) + '/' + gapMax + ''; h += 'long gaps (>=' + longGap + '): ' + longCount + ''; if (activePredZone !== null) { h += 'active pred: ' + String(activePredZone).padStart(2, '0') + ''; } var cdLen = Object.keys(cooldownZoneSet).length; if (cdLen > 0) { h += 'cooldown: ' + cdLen + ''; } if (ghosts.length > 0) { h += 'ghosts: ' + ghosts.length + ''; } h += '
'; // Chips with arrows h += '
'; // For active prediction: only highlight the LAST occurrence (most recent) var lastOccurrenceOfActive = -1; if (activePredZone !== null) { for (var si = seeds.length - 1; si >= 0; si--) { if (seeds[si].z === activePredZone) { lastOccurrenceOfActive = si; break; } } } for (var si = 0; si < seeds.length; si++) { var s = seeds[si]; var zstr = (s.z < 10 ? '0' : '') + s.z; // Default chip colors var chipBg = '#2a1a0a'; var chipColor = '#ff8'; var chipBorder = '#553'; // Repeat highlighting (consecutive same zone) if (si > 0 && seeds[si - 1].z === s.z) { chipBg = '#4a2a0a'; chipColor = '#fc6'; chipBorder = '#773'; } // Most recent seed: green border if (si === seeds.length - 1) { chipBorder = '#0fa'; chipBg = '#0a2a1a'; chipColor = '#0fa'; } // Active prediction: cyan border on the LAST occurrence only if (si === lastOccurrenceOfActive && si !== seeds.length - 1) { chipBorder = '#0ff'; chipBg = '#0a2a2a'; chipColor = '#0ff'; } // Ghost zone: purple tint (subtle, doesn't override pred/recent) if (ghostZoneSet[s.z] && si !== lastOccurrenceOfActive && si !== seeds.length - 1) { chipBorder = '#a0a'; } // Cooldown zone: muted blue tint if (cooldownZoneSet[s.z] && si !== lastOccurrenceOfActive && si !== seeds.length - 1) { chipBg = '#1a1a2a'; chipColor = '#88a'; } // Boundary zone: orange border (chaos) if (CHAOS_BOUNDARY_SET[s.z]) { chipBorder = '#f80'; } h += '' + zstr + ''; // Arrow to next chip if (si < seeds.length - 1) { var g = seeds[si + 1].pos - s.pos; var isLong = g >= longGap; var arrowColor = isLong ? '#ff4444' : '#666'; var arrowChar = isLong ? '\u27F6' : '\u2192'; var tip = 'gap: ' + g + ' rolls (pos ' + s.pos + ' \u2192 ' + seeds[si + 1].pos + ')' + (isLong ? ' [LONG]' : ''); h += '' + arrowChar + ''; } } h += '
'; // Legend h += '
'; h += '\u2192 short   '; h += '\u27F6 long (>=' + longGap + ')   '; h += 'green border = most recent   '; h += 'cyan border = active prediction   '; h += 'purple = ghost   '; h += 'blue = cooldown   '; h += 'orange = boundary (chaos)'; h += '
'; // Top 8 zones by frequency in displayed window var freq = {}; for (var fi = 0; fi < seeds.length; fi++) { var fz = seeds[fi].z; freq[fz] = (freq[fz] || 0) + 1; } var freqArr = []; for (var fk in freq) freqArr.push({ z: parseInt(fk), count: freq[fk] }); freqArr.sort(function(a, b) { return b.count - a.count; }); freqArr = freqArr.slice(0, 8); if (freqArr.length > 0) { h += '
Top in window: '; for (var ti = 0; ti < freqArr.length; ti++) { if (ti > 0) h += ' '; var fz = freqArr[ti].z; var fzstr = (fz < 10 ? '0' : '') + fz; var tag = ''; if (fz === activePredZone) tag = '*'; else if (ghostZoneSet[fz]) tag = '~'; else if (cooldownZoneSet[fz]) tag = 'c'; h += '' + fzstr + '\u00d7' + freqArr[ti].count + tag + ''; } h += '
'; } } flowOverlay.innerHTML = h; // Wire close button var closeBtn = flowOverlay.querySelector('#dv177-flow-close'); if (closeBtn) closeBtn.onclick = function() { flowOverlay.style.display = 'none'; _stopFlowRefresh(); }; // Wire apply button var applyBtn = flowOverlay.querySelector('#dv177-flow-apply'); if (applyBtn) applyBtn.onclick = _applyFlowSettings; // Wire input Enter key var cntInput = flowOverlay.querySelector('#dv177-flow-count'); if (cntInput) cntInput.addEventListener('keydown', function(e){ if(e.key==='Enter') _applyFlowSettings(); }); var gapInput = flowOverlay.querySelector('#dv177-flow-longgap'); if (gapInput) gapInput.addEventListener('keydown', function(e){ if(e.key==='Enter') _applyFlowSettings(); }); // Hover highlight: identical zones + last seen N rolls ago var chips = flowOverlay.querySelectorAll('.dv177-flow-chip'); for (var ci = 0; ci < chips.length; ci++) { (function(chip) { chip.addEventListener('mouseenter', function() { var z = parseInt(chip.getAttribute('data-zone')); var pos = parseInt(chip.getAttribute('data-pos')); var all = flowOverlay.querySelectorAll('.dv177-flow-chip'); for (var ai = 0; ai < all.length; ai++) { if (parseInt(all[ai].getAttribute('data-zone')) === z) { all[ai].style.borderColor = '#ff0'; all[ai].style.background = '#4a4a0a'; all[ai].style.color = '#ff8'; } } var rollsAgo = rollCounter - pos; var tip = 'zone ' + (z < 10 ? '0' : '') + z + ' @ roll ' + pos + ' (' + rollsAgo + ' rolls ago)'; // Find next occurrence AFTER this chip var nextPos = null; for (var ni = 0; ni < all.length; ni++) { var np = parseInt(all[ni].getAttribute('data-pos')); if (parseInt(all[ni].getAttribute('data-zone')) === z && np > pos) { if (nextPos === null || np < nextPos) nextPos = np; } } if (nextPos !== null) { tip += ' \u2192 next @ roll ' + nextPos + ' (gap ' + (nextPos - pos) + ')'; } else { tip += ' \u2192 no repeat in window'; } chip.setAttribute('title', tip); }); chip.addEventListener('mouseleave', function() { // Re-render to restore styles (cheap, throttled by interval anyway) // For instant feedback we just clear inline overrides; the next interval tick will refresh. var all = flowOverlay.querySelectorAll('.dv177-flow-chip'); for (var ai = 0; ai < all.length; ai++) { all[ai].style.borderColor = ''; all[ai].style.background = ''; all[ai].style.color = ''; } }); })(chips[ci]); } } // ==================== EXPORT ==================== function exportData() { var blob = new Blob([JSON.stringify({ history: history, model: model, chains: chains, macros: macros, notableSeq: notableSeq, lifecycle: lifecycle, dynZones: dynZones, burstZones: burstZones, plog: plog, version: '17.0', rollCounter: rollCounter, chaosCount: chaosCount }, null, 2)], { type: 'application/json' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = SITE + '_dice_v17_data.json'; a.click(); } function exportPredLog() { var blob = new Blob([JSON.stringify({ site: SITE, version: '17.0', exportedAt: new Date().toISOString(), totalRolls: rollCounter, predictions: plog, summary: { total: plog.length, resolved: plog.filter(function(p){return p.done;}).length, hits: plog.filter(function(p){return p.res==='HIT';}).length, near: plog.filter(function(p){return p.res==='NEAR';}).length, delayed: plog.filter(function(p){return p.res==='DELAYED';}).length, overdelayed: plog.filter(function(p){return p.res==='OVERDELAYED';}).length, // v17 expired: plog.filter(function(p){return p.res==='EXPIRED';}).length, replaced: plog.filter(function(p){return p.res==='REPLACED';}).length, cancelled: plog.filter(function(p){return p.res==='CANCEL';}).length } }, null, 2)], { type: 'application/json' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = SITE + '_dice_v17_predlog.json'; a.click(); } // ==================== IMPORT (NEW v17) ==================== function importData() { var input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(ev) { try { var data = JSON.parse(ev.target.result); // Validate structure if (!data.history || !Array.isArray(data.history)) { alert('Invalid file: no history array'); return; } var ver = data.version || '?'; var hLen = data.history.length; var mCount = data.macros ? Object.keys(data.macros).length : 0; var cCount = data.chains ? Object.keys(data.chains).length : 0; var pLen = data.plog ? data.plog.length : 0; var msg = 'Import v' + ver + ' data for ' + SITE + '?\n\n' + 'History: ' + hLen + ' rolls\n' + 'Chains: ' + cCount + '\n' + 'Macros: ' + mCount + '\n' + 'Pred log: ' + pLen + ' entries\n\n' + 'This will REPLACE all current v17 data. Continue?'; if (!confirm(msg)) return; // Apply imported data history = data.history || []; chains = data.chains || {}; model = data.model || {}; macros = data.macros || {}; notableSeq = data.notableSeq || []; dynZones = data.dynZones || {}; burstZones = data.burstZones || {}; plog = data.plog || []; rollCounter = data.rollCounter || history.length; lifecycle = data.lifecycle || 'CHAOS'; lifeCount = 0; chaosCount = data.chaosCount || 0; chaosTimer = 0; predQueue = []; ghosts = []; lastRebuild = 0; recentBoundaryRolls = []; predCooldown = {}; // v17.7: clear cooldown on import // Ensure model is complete initModel(); // Trim to limits if (history.length > MAX_HIST) history = history.slice(-MAX_HIST); if (notableSeq.length > MAX_NOTABLE) notableSeq = notableSeq.slice(-MAX_NOTABLE); if (plog.length > 4000) plog = plog.slice(-4000); sv(); render(); alert('Import successful! ' + hLen + ' rolls loaded.'); } catch(err) { alert('Import error: ' + err.message); } }; reader.readAsText(file); }; input.click(); } function resetAll() { if (!confirm('Reset ALL v17 data for ' + SITE + '?')) return; history = []; chains = {}; macros = {}; for (var z = 0; z < ZONE_COUNT; z++) model[z] = { h: 0, c: ZONE_COUNT, ls: 0, bias: 0 }; rollCounter = 0; lifecycle = 'CHAOS'; lifeCount = 0; notableSeq = []; dynZones = {}; burstZones = {}; plog = []; predQueue = []; lastRebuild = 0; ghosts = []; chaosTimer = 0; chaosCount = 0; recentBoundaryRolls = []; predCooldown = {}; // v17.7: clear cooldown on reset sv(); render(); if (flowOverlay) renderFlowPanel(); } // ==================== PARSE ==================== var _lastRollZone = -1, _lastRollTs = 0, _lastRollTxId = null; var _lastApiTs = 0; // v17.11: timestamp of last fetch/XHR roll (for adaptive body suppression) var ROLL_DEDUP_MS = 1000; // secondary layer: same zone within this window = same roll var MIN_ROLL_INTERVAL_MS = 800; // v17.11: applies ONLY to body observer source var BODY_SUPPRESSION_MS = 10000; // v17.11: if API fired within this window, suppress body function _extractRoll(data) { // Returns { zone, tx } or null. Tries multiple response shapes. if (!data || typeof data !== 'object') return null; var zone = null, tx = null, id = null; // Shape 1 (primary, same as v17.2/v17.7): data.bet_data.extra_data.result if (data.bet_data && data.bet_data.extra_data && data.bet_data.extra_data.result !== undefined) { var r1 = Number(data.bet_data.extra_data.result); if (r1 >= 0 && r1 < 100) { zone = Math.floor(r1); tx = data; id = data.bet_data.id; } } // Shape 2: data.result (top-level) if (zone === null && data.result !== undefined) { var r2 = Number(data.result); if (r2 >= 0 && r2 < 100) { zone = Math.floor(r2); tx = { bet_data: { extra_data: { result: r2 }, id: 'fetch_' + Date.now() + '_' + r2 } }; id = tx.bet_data.id; } } // Shape 3: data.data.result (alternate wrapper) if (zone === null && data.data && data.data.result !== undefined) { var r3 = Number(data.data.result); if (r3 >= 0 && r3 < 100) { zone = Math.floor(r3); tx = { bet_data: { extra_data: { result: r3 }, id: 'fetch3_' + Date.now() + '_' + r3 } }; id = tx.bet_data.id; } } // Shape 4: data.data.bet_data.extra_data.result (deep wrapper) if (zone === null && data.data && data.data.bet_data && data.data.bet_data.extra_data && data.data.bet_data.extra_data.result !== undefined) { var r4 = Number(data.data.bet_data.extra_data.result); if (r4 >= 0 && r4 < 100) { zone = Math.floor(r4); tx = data.data; id = data.data.bet_data.id; } } if (zone === null) return null; return { zone: zone, tx: tx, id: id }; } function _onRollDetected(data, source) { try { var ext = _extractRoll(data); if (!ext) return; var zone = ext.zone; if (zone < 0 || zone > 99) return; var now = Date.now(); var isBody = (source !== 'api'); // default to body if source unspecified if (isBody && _lastApiTs > 0 && (now - _lastApiTs) < BODY_SUPPRESSION_MS) { return; } if (isBody && (now - _lastRollTs) < MIN_ROLL_INTERVAL_MS) return; if (zone === _lastRollZone && (now - _lastRollTs) < ROLL_DEDUP_MS) return; if (ext.id !== null && ext.id === _lastRollTxId && (now - _lastRollTs) < (ROLL_DEDUP_MS * 5)) return; _lastRollZone = zone; _lastRollTs = now; _lastRollTxId = ext.id || null; if (!isBody) _lastApiTs = now; // mark API channel as alive parse(ext.tx); } catch(e) { /* swallow — never let hook crash the page */ } } function parse(tx) { if (!tx || !tx.bet_data) return; tx = tx.bet_data; for (var i = 0; i < history.length; i++) { if (history[i].id === tx.id) return; } var roll = Number(tx.extra_data.result); var zone = detectZone(roll); rollCounter++; registerHit(zone); history.push({ id: tx.id, idx: rollCounter, roll: roll, zone: zone }); if (history.length > MAX_HIST) history = history.slice(-MAX_HIST); sv(); render(); // v17.7: refresh flow panel on each roll (cheap, only updates if visible) if (flowOverlay && flowOverlay.style.display !== 'none') renderFlowPanel(); // v17.7: fallback auto-open if the BOOT setTimeout failed (e.g. document.body wasn't ready) else if (!flowOverlay) { try { openFlowPanel(); } catch(e) {} } } // ==================== HOOK FETCH + XHR (v17.8) ==================== function hookFetch() { var origFetch = window.fetch; if (!origFetch) return; window.fetch = function() { var promise = origFetch.apply(this, arguments); try { promise.then(function(response) { if (!response || !response.ok) return; var clone = response.clone(); clone.json().then(function(data) { _onRollDetected(data, 'api'); // v17.11: source='api' — trusted, no interval gate }).catch(function() {}); }).catch(function() {}); } catch(e) {} return promise; }; } function hookXHR() { var origSend = XMLHttpRequest.prototype.send; if (!origSend) return; XMLHttpRequest.prototype.send = function() { var self = this; try { this.addEventListener('load', function() { try { var txt = self.responseText; if (!txt) return; // Fast path: skip if no bet_data marker AND no result marker if (txt.indexOf('bet_data') < 0 && txt.indexOf('result') < 0) return; var data = JSON.parse(txt); _onRollDetected(data, 'api'); // v17.11: source='api' — trusted, no interval gate } catch(e) {} }); } catch(e) {} return origSend.apply(this, arguments); }; } // ==================== BODY OBSERVER (v17.9 — restored from v17.6/Turbo) ==================== var _bodyObserver = null; function hookBodyObserver() { try { if (typeof MutationObserver === 'undefined') return; if (!document.body) { // Body not ready yet — retry on next tick setTimeout(hookBodyObserver, 200); return; } if (_bodyObserver) return; // already hooked _bodyObserver = new MutationObserver(function(mutations) { for (var i = 0; i < mutations.length; i++) { var m = mutations[i]; if (m.type === 'childList') { for (var j = 0; j < m.addedNodes.length; j++) { var node = m.addedNodes[j]; if (!node) continue; // Text node: parse directly if (node.nodeType === 3) { var txt1 = (node.textContent || '').trim(); if (txt1.length > 0 && txt1.length < 10) { var v1 = parseFloat(txt1); if (!isNaN(v1) && v1 >= 0 && v1 < 100) { _onRollDetected({ result: Math.floor(v1) }, 'body'); } } } // Element node: try its textContent if (node.nodeType === 1) { var txt2 = (node.textContent || '').trim(); if (txt2.length > 0 && txt2.length < 10) { var v2 = parseFloat(txt2); if (!isNaN(v2) && v2 >= 0 && v2 < 100) { _onRollDetected({ result: Math.floor(v2) }, 'body'); } } } } } if (m.type === 'characterData') { if (m.target && m.target.textContent) { var txt3 = (m.target.textContent || '').trim(); if (txt3.length > 0 && txt3.length < 10) { var v3 = parseFloat(txt3); if (!isNaN(v3) && v3 >= 0 && v3 < 100) { _onRollDetected({ result: Math.floor(v3) }, 'body'); } } } } } }); _bodyObserver.observe(document.body, { childList: true, subtree: true, characterData: true }); } catch(e) { /* swallow */ } } // ==================== MIGRATION ==================== function migrateFrom() { if (history.length > 0) return; // v17.2: Try v17 first (most likely), then v16 var prevH = ld('dv17_h_' + SITE, null); var prevC = ld('dv17_c_' + SITE, null); var prevM = ld('dv17_mc_' + SITE, null); var prevMod = ld('dv17_m_' + SITE, null); if (!prevH || !prevH.length) { prevH = ld('dv16_h_' + SITE, null); prevC = ld('dv16_c_' + SITE, null); prevM = ld('dv16_mc_' + SITE, null); prevMod = ld('dv16_m_' + SITE, null); } if (!prevH || !prevH.length) { prevH = ld('dv15_h_' + SITE, null); prevC = ld('dv15_c_' + SITE, null); prevM = ld('dv15_mc_' + SITE, null); } if (!prevH || !prevH.length) { prevH = ld('dv14_h_' + SITE, null); prevC = ld('dv14_c_' + SITE, null); } if (!prevH || !prevH.length) { prevH = ld('dv13_h_' + SITE, null); prevC = ld('dv13_c_' + SITE, null); } if (!prevH || !prevH.length) { prevH = ld('dv12_hist_' + SITE, null); } if (!prevH || !prevH.length) return; for (var z = 0; z < ZONE_COUNT; z++) model[z] = { h: 0, c: ZONE_COUNT, ls: 0, bias: 0 }; chains = prevC || {}; macros = prevM || {}; // v17: migrate model with bias from v16 if (prevMod) { for (var zk in prevMod) { var pm = prevMod[zk]; if (pm && typeof pm === 'object') { model[parseInt(zk)] = { h: pm.h || 0, c: pm.c || ZONE_COUNT, ls: pm.ls || 0, bias: pm.bias || 0 // preserve v16 bias! }; } } } notableSeq = []; rollCounter = 0; lifecycle = 'CHAOS'; lifeCount = 0; dynZones = {}; burstZones = {}; plog = []; predQueue = []; ghosts = []; chaosTimer = 0; chaosCount = 0; recentBoundaryRolls = []; globalDrift = 0; zoneDrift = {}; driftObs = 0; predCooldown = {}; // v17.7: clear cooldown on migration for (var i = 0; i < prevH.length; i++) { var e = prevH[i]; var zone = (e.zone !== undefined && e.zone !== null) ? e.zone : detectZone(e.roll); rollCounter++; if (zone !== null) { var m = model[zone]; var gap = rollCounter - m.ls; if (m.ls > 0 && gap > 0) m.c = m.c * 0.7 + gap * 0.3; m.h++; m.ls = rollCounter; } history.push({ id: e.id || ('m' + i), idx: rollCounter, roll: e.roll, zone: zone }); if (rollCounter % 50 === 0) dynZones = computeDynZones(); updateBurstZones(zone); updateLifecycle(); // v17: also detect chaos during migration detectChaos(zone); if (isChainable(zone)) { addChainEntries(zone); notableSeq.push({ z: zone, ri: rollCounter }); if (notableSeq.length > MAX_NOTABLE) notableSeq = notableSeq.slice(-MAX_NOTABLE); buildMacros(); } } if (history.length > MAX_HIST) history = history.slice(-MAX_HIST); lifecycle = 'CHAOS'; lifeCount = 0; predQueue = []; lastRebuild = 0; sv(); } // v17.12: USER CHAIN MANAGEMENT block removed (module deleted). // ==================== BOOT ==================== initModel(); migrateFrom(); if (rollCounter > 0 && rollCounter % 50 !== 0) dynZones = computeDynZones(); createOverlay(); hookFetch(); hookXHR(); hookBodyObserver(); render(); setTimeout(function() { try { openFlowPanel(); } catch(e) { /* will retry on next render */ } }, 500); })();