textmachine/eval/dovodka/poryadok.py

100 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())