Содержимое кейса
SKINZORO
Мой инвентарь
Прокачать до
Контракты
Контракт обмена
Возможный результат
Ваши предметы
Кейс батл
вы против ботов: одни и те же кейсы, победитель забирает весь дропГотовые батлы
нажмите — подставится в настройкуМины
Ставка
Как считается выплата
Мои игры
Как считается точка взрыва
Такой страницы нет
Шанс найти её — 0,00%. Похоже, в адресе опечатка или ссылка устарела.
Правила акции «3 шага до Ножа»
Статистика
История
Мой инвентарь
Кошелёк
Пополнения
Выводы
Бонусы и промокоды
Операции по балансу
Достижения
История игр
Честная игра
Как устроена честная игра
Как это работает
- Обещание. До ставки сервер выбирает серверный сид и показывает его хеш SHA-256. Сам сид секретен: по хешу его не восстановить, а заменить уже нельзя — у другого сида другой хеш.
- Ставка. Исход задают три значения: серверный сид, ваш клиентский сид и nonce — номер ставки на этом сиде (0, 1, 2…). Хеш, клиентский сид и nonce записываются в ставку.
- Раскрытие. Когда вы меняете сид — или он меняется сам после 100 ставок, — прежний серверный сид раскрывается. Сменить сид можно в любой момент, кроме времени, пока идёт игра (например, открытые мины: раскрытый сид выдал бы поле).
- Проверка. Нажмите на ставку в «Истории игр» — или вставьте раскрытый сид в окно проверки: браузер сам посчитает SHA-256 (тот ли это сид, что был обещан) и число раунда — без обращения к серверу.
Почему сервер не может подыграть себе
- Серверный сид выбран и обещан хешем до ставки — подменить его после ставки нельзя.
- Следующий серверный сид тоже выбран заранее: его хеш вы видите до того, как зададите новый клиентский сид. Значит, серверный сид не подобрать под ваш клиентский.
- Клиентский сид — ваш: задайте своё значение, и исход будет зависеть от того, чего сервер не знал, когда выбирал свой сид.
- Каждая ставка берёт следующий nonce по порядку — пропустить «неудобный» исход нельзя: это держит база данных.
Формула (версия 1)
- Сообщение:
клиентский_сид:nonce— nonce десятичным числом. Если игре нужно несколько чисел (мины, батлы), следующие берутся из сообщенийклиентский_сид:nonce:1,:2и так далее. HMAC-SHA256(ключ = серверный сид, сообщение)→ 64 hex-символа. Ключ — сама строка сида (64 символа) в UTF-8, без перевода из hex в байты.- Первые 13 hex-символов — целое число (52 бита), делённое на 252: число раунда от 0 до 1 (единица не входит). 52 бита помещаются в double без округления — результат одинаков в любом языке.
- Обещание:
SHA-256(серверный сид)— тоже над строкой в UTF-8. - Общие раунды (краш): на месте клиентского сида — соль раунда, nonce = 0; сид раунда раскрывается сразу после раунда.
Как из числа раунда получается исход — в разделах каждой игры ниже (кейсы, апгрейд, джет, мины, контракты и батлы).
const { createHash, createHmac } = require("node:crypto");
const serverSeed = "…"; // раскрытый серверный сид
const serverSeedHash = "…"; // хеш, показанный до ставки
const clientSeed = "…";
const nonce = 0;
const hash = createHash("sha256").update(serverSeed).digest("hex");
console.log("сид совпадает с обещанием:", hash === serverSeedHash);
const hmac = createHmac("sha256", serverSeed).update(clientSeed + ":" + nonce).digest("hex");
console.log("число раунда:", parseInt(hmac.slice(0, 13), 16) / 2 ** 52);import hashlib, hmac
server_seed = "…" # раскрытый серверный сид
server_seed_hash = "…" # хеш, показанный до ставки
client_seed = "…"
nonce = 0
h = hashlib.sha256(server_seed.encode()).hexdigest()
print("сид совпадает с обещанием:", h == server_seed_hash)
mac = hmac.new(server_seed.encode(), f"{client_seed}:{nonce}".encode(), hashlib.sha256).hexdigest()
print("число раунда:", int(mac[:13], 16) / 2**52)Кейсы: число раунда → предмет
- Состав — версия кейса. У каждой ставки в кейсе записана версия: предметы в каноническом порядке (по id скина) с целыми весами w₁…wₙ. Шанс предмета — wᵢ / W, где W — сумма весов: ровно эти шансы показывает страница кейса. Опубликованная версия не меняется никогда (это держит база данных) — новая цена или состав выходят новой версией.
- Билет = ⌊число раунда × W⌋ — от 0 до W − 1. Считается в целых, без округлений: число раунда = N / 252, поэтому билет = ⌊N × W / 252⌋.
- Предмет — первый, у которого накопленный вес w₁ + … + wᵢ больше билета: у предмета i билеты [w₁+…+wᵢ₋₁, w₁+…+wᵢ), их ровно wᵢ из W.
- Предмет зачисляется по цене рынка в момент выпадения; цена кейса = средний дроп по ценам рынка ÷ RTP (RTP записан в ставке). Повторить бросок нельзя: следующий бросок — это следующий nonce.
Нажмите на ставку кейса в «Истории игр» — окно проверки само подставит версию кейса и покажет билет и предмет.
const v = await (await fetch("https://skinzoro…/api/verify/cases/<версия>")).json();
const roll = 0.23648251205621862; // число раунда (из окна проверки)
const W = v.items.reduce((s, x) => s + BigInt(x.weight), 0n);
const n52 = BigInt(roll * 2 ** 52); // точно: число = n52 / 2^52
const ticket = (n52 * W) / 2n ** 52n; // билет = ⌊число × W⌋ — в целых
let acc = 0n;
const won = v.items.find((x) => (acc += BigInt(x.weight)) > ticket);
console.log("билет", ticket, "из", W, "→", won.name);import json, urllib.request
v = json.load(urllib.request.urlopen("https://skinzoro…/api/verify/cases/<версия>"))
roll = 0.23648251205621862 # число раунда (из окна проверки)
W = sum(x["weight"] for x in v["items"])
ticket = int(roll * 2**52) * W // 2**52 # билет = ⌊число × W⌋ — в целых
acc = 0
for x in v["items"]:
acc += x["weight"]
if acc > ticket:
print("билет", ticket, "из", W, "→", x["name"])
breakАпгрейд: число раунда → выигрыш или утешение
- Шанс — из ставки st и цены цели v: ch = (RTP − κ) × st ÷ (v − κ × st). κ — доля ставки, которая в среднем уходит на утешительные скины: «бонус за отвагу» — 3 % проигрышей дают скин примерно за 10 % ставки (κ = 0,3 %); «приз за участие» (включается в настройках подбора) — каждый проигрыш даёт скин за 5–25 % ставки (κ = 15 %); со ставкой меньше $1 утешения нет, κ = 0. Средний возврат ch × v + (1 − ch) × κ × st равен RTP × st ровно. Шанс записывается в ставку точной дробью — его и показывает колесо.
- Выигрыш — если число раунда меньше шанса. Считается в целых: число = N / 252, шанс = num / den, выигрыш ⇔ N × den < num × 252. Цель зачисляется в инвентарь по её цене.
- Проигрыш (ставка от $1) — утешение из следующих чисел той же ставки. Число №1 (сообщение
клиентский_сид:nonce:1): бонус за отвагу выпал, если №1 меньше 3 %, — скин примерно на 10 % ставки; приз за участие — на 5 % + 20 % × №1 ставки (вниз до цента). Кандидаты — 5 ближайших к этой сумме по цене обычных скинов огнестрела (ближе во сколько-то раз; при равенстве — дешевле, потом по id), они записаны в ставке с ценами того момента. Число №2 (сообщение:2) выбирает ⌊№2 × 5⌋-й из них. - Колесо на странице — только анимация: исход посчитан и записан до ответа сервера. Повторить бросок нельзя — следующий бросок это следующий nonce.
Нажмите на апгрейд в «Истории игр» — окно проверки само подставит id ставки и пересчитает шанс, выигрыш и утешение.
const b = await (await fetch("https://skinzoro…/api/verify/bets/<id ставки>")).json();
const roll = 0.23648251205621862; // число раунда (из окна проверки)
const st = BigInt(b.stakeCents), v = BigInt(b.target.valueCents), r = b.rules;
// κ — доля ставки на утешение: 0 со ставки меньше порога; приз за участие — (lo + hi) / 2; иначе доля × сумма
const [kn, kd] = st < BigInt(r.compMinStakeCents) ? [0n, 1n]
: b.gp ? [BigInt(r.gpLoBps + r.gpHiBps), 20000n] : [BigInt(r.compShareBps) * BigInt(r.compValueBps), 100000000n];
const num = (BigInt(b.rtpBps) * kd - kn * 10000n) * st; // шанс = (RTP − κ)·st ÷ (v − κ·st) — в целых
const den = 10000n * (v * kd - kn * st);
console.log("шанс как в ставке:", num * BigInt(b.chance.den) === den * BigInt(b.chance.num));
const n52 = BigInt(roll * 2 ** 52); // точно: число = n52 / 2^52
console.log(n52 * den < num * 2n ** 52n ? "выигрыш" : "проигрыш", "— в ставке:", b.outcome.won ? "выигрыш" : "проигрыш");import json, urllib.request
b = json.load(urllib.request.urlopen("https://skinzoro…/api/verify/bets/<id ставки>"))
roll = 0.23648251205621862 # число раунда (из окна проверки)
st, v, r = int(b["stakeCents"]), int(b["target"]["valueCents"]), b["rules"]
if st < r["compMinStakeCents"]: kn, kd = 0, 1
elif b["gp"]: kn, kd = r["gpLoBps"] + r["gpHiBps"], 20000
else: kn, kd = r["compShareBps"] * r["compValueBps"], 100000000
num = (b["rtpBps"] * kd - kn * 10000) * st # шанс = (RTP − κ)·st ÷ (v − κ·st) — в целых
den = 10000 * (v * kd - kn * st)
print("шанс как в ставке:", num * int(b["chance"]["den"]) == den * int(b["chance"]["num"]))
n52 = int(roll * 2**52) # точно: число = n52 / 2^52
print("выигрыш" if n52 * den < num * 2**52 else "проигрыш", "— в ставке:", "выигрыш" if b["outcome"]["won"] else "проигрыш")Джет: число раунда → точка взрыва
- Раунд общий для всех: число раунда — из сида раунда и соли (на месте клиентского сида), nonce = 0, по формуле выше.
- Точка взрыва X = ⌊RTP_бп × 252 ÷ (100 × (252 − N))⌋ ÷ 100, от ×1.00 до ×1000 — вниз до сотых, ровно как на экране. Вывод на ×a засчитан, если a ≤ X. Шанс дожить до ×a — ровно RTP ÷ a, поэтому средний возврат при любом выводе (руками или автовыводом) — RTP. В раундах с X = ×1.00 (около 5,9 % при RTP 95 %) не успевает никто: вывести можно с ×1.01.
- Цепочка сидов. Сид раунда i — это SHA-256 сида раунда i + 1; хеш первого раунда (обещание цепочки) объявлен до первого раунда. Хеш, который виден до ставок, — это сид прошлого раунда, а свой сид раунд раскрывает, когда самолёт взорвался. Значит, порядок всех точек взрыва закреплён заранее — подобрать точку под ставки нельзя. Сбой сервера отменяет раунд: ставки возвращаются, сид всё равно раскрывается.
- Время полёта — только анимация: кривая множителя от времени одна для всех, а множитель «Забрать» засчитывает сервер по своим часам — но не выше точки взрыва.
Нажмите на ставку «Джета» в «Истории игр» — окно проверки пересчитает точку взрыва, вывод и выплату и по кнопке проверит цепочку до обещания.
const { createHash, createHmac } = require("node:crypto");
const seed = "…"; // раскрытый сид раунда
const salt = "…"; // соль раунда (на месте клиентского сида)
const rtpBps = 9500; // RTP раунда, б.п.
const hmac = createHmac("sha256", seed).update(salt + ":0").digest("hex");
const N = BigInt("0x" + hmac.slice(0, 13)); // число раунда = N / 2^52
const T = 2n ** 52n;
let x = (BigInt(rtpBps) * T) / (100n * (T - N)); // ×100, вниз до сотых
x = x < 100n ? 100n : x > 100000n ? 100000n : x;
console.log("точка взрыва: ×" + (Number(x) / 100).toFixed(2));
// цепочка: SHA-256 сида столько раз, каков номер раунда, — это обещание цепочки
let s = seed;
for (let i = 0; i < 1234 /* номер раунда */; i++) s = createHash("sha256").update(s).digest("hex");
console.log("обещание цепочки:", s);import hashlib, hmac
seed = "…" # раскрытый сид раунда
salt = "…" # соль раунда (на месте клиентского сида)
rtp_bps = 9500 # RTP раунда, б.п.
mac = hmac.new(seed.encode(), f"{salt}:0".encode(), hashlib.sha256).hexdigest()
N = int(mac[:13], 16) # число раунда = N / 2**52
T = 2**52
x = rtp_bps * T // (100 * (T - N)) # ×100, вниз до сотых
x = min(max(x, 100), 100000)
print(f"точка взрыва: ×{x / 100:.2f}")
s = seed # цепочка: SHA-256 сида столько раз, каков номер раунда, — это обещание цепочки
for _ in range(1234): # номер раунда
s = hashlib.sha256(s.encode()).hexdigest()
print("обещание цепочки:", s)Мины: числа ставки → поле
- Поле 5×5: клетки 0…24 по строкам слева направо (в окне проверки — 1…25). Мин — от 1 до 24, их число вы выбираете до игры.
- Расстановка — жребий без возврата по числам ставки: №0 — обычное число ставки, №i — из сообщения
клиентский_сид:nonce:i. Клетки стоят по порядку; на шаге i мина встаёт на место ⌊№i × (25 − i)⌋ среди оставшихся 25 − i клеток (в целых: N × (25 − i) ÷ 252 вниз), и эта клетка выходит из списка. Поле из m мин — числа №0…№(m − 1). Поле решено до первого хода: клетки, которые вы открываете, на него не влияют. - Выплата после k открытых чистых клеток — ⌊ставка × RTP × C(25, k) ÷ C(25 − m, k)⌋, вниз до цента; мина — ставка сгорает. Открыть k чистых подряд можно с шансом ровно C(25 − m, k) ÷ C(25, k), поэтому средний возврат при любой тактике (сколько клеток открыть и когда забрать) — RTP.
- Пока игра идёт, сид не сменить: раскрытый сид выдал бы поле. Каждый ход проверяет и база данных — она сама пересчитывает поле из сида и не примет ни ход после мины, ни выплату не по формуле.
Нажмите на игру в «Мины» в «Истории игр» — после смены сида окно проверки покажет жребий шаг за шагом, поле, ходы и выплату.
import { createHmac } from "node:crypto";
const b = await (await fetch("https://skinzoro…/api/verify/bets/<id ставки>")).json();
const serverSeed = "…"; // раскрытый серверный сид (его хеш — b.serverSeedHash)
// число №i = N / 2^52: сообщение «клиентский_сид:nonce» для №0 и «клиентский_сид:nonce:i» дальше
const N = (i) => BigInt("0x" + createHmac("sha256", serverSeed)
.update(b.clientSeed + ":" + b.nonce + (i ? ":" + i : "")).digest("hex").slice(0, 13));
const rest = [...Array(25).keys()], mines = []; // клетки 0…24 по строкам
for (let i = 0; i < b.mines; i++) mines.push(rest.splice(Number((N(i) * BigInt(25 - i)) >> 52n), 1)[0]);
mines.sort((x, y) => x - y);
console.log("поле как в ставке:", mines.join() === b.outcome.layout.join());
const C = (n, r) => { let x = 1n; for (let j = 1; j <= r; j++) x = (x * BigInt(n - r + j)) / BigInt(j); return x; };
const hits = b.outcome.hits, k = hits.length, boom = mines.includes(hits[k - 1]);
const pay = boom ? 0n : (BigInt(b.stakeCents) * BigInt(b.rtpBps) * C(25, k)) / (10000n * C(25 - b.mines, k));
console.log(boom ? "мина" : "забрал", "— выплата", String(pay), "центов, в ставке", b.payoutCents);import hashlib, hmac, json, urllib.request
from math import comb
b = json.load(urllib.request.urlopen("https://skinzoro…/api/verify/bets/<id ставки>"))
server_seed = "…" # раскрытый серверный сид (его хеш — b["serverSeedHash"])
def N(i): # число №i = N / 2**52: сообщение «клиентский_сид:nonce» для №0 и «…:nonce:i» дальше
msg = f'{b["clientSeed"]}:{b["nonce"]}' + (f":{i}" if i else "")
return int(hmac.new(server_seed.encode(), msg.encode(), hashlib.sha256).hexdigest()[:13], 16)
rest, mines = list(range(25)), [] # клетки 0…24 по строкам
for i in range(b["mines"]):
mines.append(rest.pop(N(i) * (25 - i) >> 52))
mines.sort()
print("поле как в ставке:", mines == b["outcome"]["layout"])
hits = b["outcome"]["hits"]; k = len(hits); boom = hits[-1] in mines
pay = 0 if boom else int(b["stakeCents"]) * b["rtpBps"] * comb(25, k) // (10000 * comb(25 - b["mines"], k))
print("мина" if boom else "забрал", "— выплата", pay, "центов, в ставке", b["payoutCents"])Контракты: число ставки → скин пула
- Вложено V — скины по цене выпадения и деньги с баланса. Уровень — таблица из 16 ступеней: множитель (при RTP 100 %, в миллионных) и доля ступени. Цена ступени = V × RTP × множитель ÷ 1010, до ближайшего цента.
- Кандидаты ступени — до 5 ближайших к её цене скинов выбранного раздела (действующие, с рыночной ценой, без кейсов и сувениров): ближе тот, чья цена отличается в меньшее число раз, при равенстве — дешевле, затем по id. Их ищет база данных по каталогу в момент контракта — с другими кандидатами она контракт не примет.
- Веса пула. Базовый вес скина — сумма по его ступеням: доля ступени × 60 ÷ число её кандидатов. Скины «не дешевле вложенного» и «дешевле» умножаются на a и b, при которых средний возврат — ровно V × RTP (внутри группы доли не меняются). Веса — целые билеты из 1 000 000, вниз до целого; остаток — самому дешёвому скину, поэтому средний возврат пула не выше RTP. Пул вы видите до «Подписать» и подписываете именно его: если цены в каталоге сменились, контракт не подпишется, пока вы не посмотрите новый пул.
- Исход — как в кейсах: билет = ⌊число ставки × 1 000 000⌋ в целых, выпадает скин, чьи билеты его содержат; он зачисляется в инвентарь сайта по цене пула. База данных сама считает число из сида пары и не примет другой скин.
Нажмите на контракт в «Истории игр» — окно проверки пересоберёт пул по формуле, найдёт билет и скин.
const b = await (await fetch("https://skinzoro…/api/verify/bets/<id ставки>")).json();
const roll = 0.23648251205621862; // число ставки (из окна проверки)
// базовый вес скина — Σ доля ступени × 60 ÷ число её кандидатов
const st = BigInt(b.stakeCents), r = BigInt(b.rtpBps), W = 1000000n, base = new Map();
b.steps.forEach((c, s) => c.forEach((x) => {
const cur = base.get(x.skinId) ?? { x: BigInt(x.priceCents), w: 0n };
cur.w += (BigInt(b.table[s][1]) * 60n) / BigInt(c.length);
base.set(x.skinId, cur);
}));
let U = 0n, SU = 0n, D = 0n, SD = 0n; // группы: не дешевле вложенного и дешевле
for (const { x, w } of base.values()) if (x >= st) { U += w; SU += w * x; } else { D += w; SD += w * x; }
const den = 10000n * (SU * D - U * SD), nu = st * r * D - 10000n * SD, nd = 10000n * SU - st * r * U;
const pool = [...base].map(([id, { x, w }]) => ({ id, x, w: (W * w * (x >= st ? nu : nd)) / den }))
.sort((p, q) => (p.x < q.x ? -1 : p.x > q.x ? 1 : p.id - q.id));
pool[0].w += W - pool.reduce((s, p) => s + p.w, 0n); // остаток — самому дешёвому
const mine = pool.filter((p) => p.w > 0n).map((p) => [p.id, p.x, p.w].join()).join(";");
console.log("пул как в ставке:", mine === b.pool.map((p) => [p.skinId, p.priceCents, p.weight].join()).join(";"));
const ticket = (BigInt(roll * 2 ** 52) * W) / 2n ** 52n; // билет = ⌊число × W⌋ — в целых
let acc = 0n;
const won = b.pool.find((p) => (acc += BigInt(p.weight)) > ticket);
console.log("билет", ticket, "→", won.name, "— в ставке:", won.skinId === b.outcome.skinId);import json, urllib.request
b = json.load(urllib.request.urlopen("https://skinzoro…/api/verify/bets/<id ставки>"))
roll = 0.23648251205621862 # число ставки (из окна проверки)
st, r, W, base = int(b["stakeCents"]), b["rtpBps"], 10**6, {}
for s, cands in enumerate(b["steps"]): # базовый вес — Σ доля ступени × 60 ÷ число её кандидатов
for c in cands:
x, w = base.get(c["skinId"], (int(c["priceCents"]), 0))
base[c["skinId"]] = (x, w + b["table"][s][1] * 60 // len(cands))
U = sum(w for x, w in base.values() if x >= st); SU = sum(w * x for x, w in base.values() if x >= st)
D = sum(w for x, w in base.values() if x < st); SD = sum(w * x for x, w in base.values() if x < st)
den, nu, nd = 10000 * (SU * D - U * SD), st * r * D - 10000 * SD, 10000 * SU - st * r * U
pool = sorted([x, i, W * w * (nu if x >= st else nd) // den] for i, (x, w) in base.items())
pool[0][2] += W - sum(p[2] for p in pool) # остаток — самому дешёвому
mine = [[i, str(x), w] for x, i, w in pool if w > 0]
print("пул как в ставке:", mine == [[p["skinId"], p["priceCents"], p["weight"]] for p in b["pool"]])
ticket = int(roll * 2**52) * W // 2**52 # билет = ⌊число × W⌋ — в целых
acc = 0
for p in b["pool"]:
acc += p["weight"]
if acc > ticket:
print("билет", ticket, "→", p["name"], "— в ставке:", p["skinId"] == b["outcome"]["skinId"])
breakБатлы: числа ставки → выпадения всех участников
- Участники и раунды. Участник 0 — вы, остальные — боты; каждый кейс батла — раунд, его открывают все. Взнос — сумма цен кейсов раундов (цены их версий — база данных сверяет их с витриной). Боты ничего не платят и ничего не получают: они только участники расчёта.
- Числа — №0…№(N × R − 1) одной ставки (N участников, R раундов): №0 — обычное число ставки, №i — из сообщения
клиентский_сид:nonce:i. Участник p в раунде r получает число №(r × N + p). Участников — от 2 до 4, раундов — до 25, чисел — не больше 100. - Выпадение — как в кейсах: билет = ⌊число × W⌋ в целых по составу версии кейса раунда, выпадает предмет, чьи билеты его содержат. Цена выпадения — цена скина на рынке в момент батла: она записана в ставке, и база данных при записи сверила её с каталогом.
- Итог. Сумма участника — цены его выпадений. Побеждает большая сумма (в обратном режиме — меньшая), победитель забирает все выпадения батла. Равная лучшая сумма у k участников — ничья: каждый из них забирает свои выпадения и долю ⌊(банк − лучшая × k) ÷ k⌋ деньгами, где банк — все выпадения батла. Шансы у участников равные, поэтому средний возврат — как при открытии тех же кейсов.
- Ленты на странице — только показ: исход посчитан и записан до ответа сервера. База данных сама пересчитывает выпадения из сида пары и итог батла — другой не примет.
Нажмите на батл в «Истории игр» — после смены сида окно проверки покажет числа, билеты и выпадения каждого участника в каждом раунде, суммы, победителя и что досталось вам.
import { createHmac } from "node:crypto";
const site = "https://skinzoro…";
const b = await (await fetch(site + "/api/verify/bets/<id ставки>")).json();
const serverSeed = "…"; // раскрытый серверный сид (его хеш — b.serverSeedHash)
// число №i = N / 2^52: сообщение «клиентский_сид:nonce» для №0 и «клиентский_сид:nonce:i» дальше
const N = (i) => BigInt("0x" + createHmac("sha256", serverSeed)
.update(b.clientSeed + ":" + b.nonce + (i ? ":" + i : "")).digest("hex").slice(0, 13));
const n = b.players, totals = Array(n).fill(0n);
let same = true;
for (const [r, round] of b.rounds.entries()) {
const v = await (await fetch(site + "/api/verify/cases/" + round.versionId)).json(); // состав версии кейса раунда
for (let p = 0; p < n; p++) {
const ticket = (N(r * n + p) * BigInt(v.weightTotal)) >> 52n; // билет = ⌊число × W⌋ — в целых
let acc = 0n;
const it = v.items.find((x) => (acc += BigInt(x.weight)) > ticket);
const d = b.outcome.drops[r][p];
if (it.skinId !== d.skinId || String(ticket) !== d.ticket) same = false;
totals[p] += BigInt(d.valueCents); // цена — на момент батла (записана в ставке)
}
}
const best = totals.reduce((a, t) => (b.mode === "crazy" ? (t < a ? t : a) : t > a ? t : a));
const top = totals.flatMap((t, p) => (t === best ? [p] : [])), k = BigInt(top.length);
const pot = totals.reduce((a, t) => a + t, 0n), share = k > 1n ? (pot - best * k) / k : 0n;
console.log("выпадения как в ставке:", same, "· суммы:", totals.join(", "),
top.length > 1 ? "· ничья: " + top.join(", ") + ", доля " + share : "· победил участник " + top[0]);import hashlib, hmac, json, urllib.request
site = "https://skinzoro…"
get = lambda path: json.load(urllib.request.urlopen(site + path))
b = get("/api/verify/bets/<id ставки>")
server_seed = "…" # раскрытый серверный сид (его хеш — b["serverSeedHash"])
def N(i): # число №i = N / 2**52: сообщение «клиентский_сид:nonce» для №0 и «…:nonce:i» дальше
msg = f'{b["clientSeed"]}:{b["nonce"]}' + (f":{i}" if i else "")
return int(hmac.new(server_seed.encode(), msg.encode(), hashlib.sha256).hexdigest()[:13], 16)
n, same = b["players"], True
totals = [0] * n
for r, rd in enumerate(b["rounds"]):
v = get(f'/api/verify/cases/{rd["versionId"]}') # состав версии кейса раунда
for p in range(n):
ticket = N(r * n + p) * int(v["weightTotal"]) >> 52 # билет = ⌊число × W⌋ — в целых
acc = 0
it = next(x for x in v["items"] if (acc := acc + x["weight"]) > ticket)
d = b["outcome"]["drops"][r][p]
same = same and it["skinId"] == d["skinId"] and str(ticket) == d["ticket"]
totals[p] += int(d["valueCents"]) # цена — на момент батла (записана в ставке)
best = min(totals) if b["mode"] == "crazy" else max(totals)
top = [p for p, t in enumerate(totals) if t == best]; k = len(top)
pot = sum(totals); share = (pot - best * k) // k if k > 1 else 0
print("выпадения как в ставке:", same, "· суммы:", totals,
f"· ничья: {top}, доля {share}" if k > 1 else f"· победил участник {top[0]}")