Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BcWeV2yiMWTjkeaQfSsYuL
100 lines
5.8 KiB
Python
100 lines
5.8 KiB
Python
#!/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())
|