#!/usr/bin/env python3 # -*- coding: utf-8 -*- """poryadok.py — детерминированный порядок рук внутри юнита от запиненного сида. КОНТРАКТ, дословно из плана §2 (пре-рег, фриз dc602e9): «четыре руки креста — перестановкой от сида, `glm-off` — ВСЕГДА последним пятым». Зачем именно так: при срабатывании денежного гарда теряется РЕФЕРЕНС, а не пара, на которой стоит главный эндпойнт E1. Зачем вообще рандомизация (план §2, §4-E2): 2-4-я клетки юнита бьют префикс-кэш и вход дешевеет на ~$0.006-0.009. Фиксированный порядок систематически удешевлял бы одни руки и удорожал другие — рандомизация переносит это в шум вместо смещения. СИД ЗАПИНЕН В ПРЕ-РЕГЕ: 20260902. Без запиненного сида пре-рег не является пре-регом (план §9.4). ⚠ ПЕРЕСТАНОВКА СЧИТАЕТСЯ SHA256, А НЕ `random`: `random.Random(...).shuffle` не обещает стабильности между версиями Python, и порядок, записанный в манифест сегодня, мог бы не воспроизвестись при пере-снятии. sha256 — обещает. Использование: python3 eval/dovodka/poryadok.py --unit 1 --seed 20260902 # одна строка: порядок рук python3 eval/dovodka/poryadok.py --seed 20260902 --all 24 # таблица для манифеста python3 eval/dovodka/poryadok.py --selftest """ import argparse import hashlib import sys CROSS = ["p7-off", "p7-low", "p9-off", "p9-low"] # руки креста — перестановка REFERENCE = "glm-off" # референс — ВСЕГДА пятый, не перемешивается def order(unit, seed): """Перестановка четырёх рук креста + референс пятым. Детерминирована (sha256).""" digest = hashlib.sha256(f"{seed}:{unit}".encode("utf-8")).digest() # Алгоритм Фишера-Йетса на байтах дайджеста: детерминирован и не зависит от версии Python. arms = list(CROSS) out = [] for i in range(len(arms)): # берём по байту на шаг; 4 руки ⇒ максимум 4 байта из 32, хватает с запасом j = digest[i] % len(arms) out.append(arms.pop(j)) return out + [REFERENCE] def selftest(): ok = True def check(name, cond): nonlocal ok print((" ✓ " if cond else " ✗ ") + name) ok = ok and cond print("СЕЛФТЕСТ poryadok.py") o1 = order(1, 20260902) check(f"пять клеток в порядке: {o1}", len(o1) == 5) check("референс ВСЕГДА пятый", all(order(u, 20260902)[-1] == REFERENCE for u in range(1, 200))) check("референс не попадает в первые четыре", all(REFERENCE not in order(u, 20260902)[:4] for u in range(1, 200))) check("все четыре руки креста присутствуют ровно по разу", all(sorted(order(u, 20260902)[:4]) == sorted(CROSS) for u in range(1, 200))) check("детерминизм: повторный вызов даёт то же", order(7, 20260902) == order(7, 20260902)) check("сид меняет порядок (иначе рандомизации нет)", any(order(u, 20260902) != order(u, 11111111) for u in range(1, 50))) # ⚠ ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ на саму рандомизацию: если бы перестановка была вырожденной # (всегда один порядок), проба систематически смещала бы кэш-выигрыш в одни и те же руки. distinct = {tuple(order(u, 20260902)[:4]) for u in range(1, 200)} check(f"порядков действительно много: {len(distinct)} различных из 24 возможных", len(distinct) >= 12) # Каждая рука обязана бывать первой (первая клетка юнита НЕ бьёт префикс-кэш и потому дороже) firsts = {} for u in range(1, 200): firsts[order(u, 20260902)[0]] = firsts.get(order(u, 20260902)[0], 0) + 1 check(f"каждая рука бывает первой: {firsts}", len(firsts) == 4 and min(firsts.values()) > 20) print("\n" + ("ПОРЯДОК ГОДЕН" if ok else "⛔ ПОРЯДОК НЕ ГОДЕН")) return 0 if ok else 1 def main(): ap = argparse.ArgumentParser(description="порядок рук внутри юнита от сида") ap.add_argument("--unit", type=int) ap.add_argument("--seed", type=int, default=20260902) ap.add_argument("--all", type=int, metavar="N", help="напечатать таблицу для N юнитов (для манифеста)") ap.add_argument("--selftest", action="store_true") a = ap.parse_args() if a.selftest: return selftest() if a.all: print(f"# порядок клеток внутри юнита, сид {a.seed} (пре-рег)") for u in range(1, a.all + 1): print(f"юнит {u:2d}: " + " ".join(order(u, a.seed))) return 0 if a.unit is None: ap.error("нужен --unit, или --all N, или --selftest") print(" ".join(order(a.unit, a.seed))) return 0 if __name__ == "__main__": sys.exit(main())