Freeze the ceiling before rebuying the identical Japanese floor halves that understate the axis threshold
This commit is contained in:
parent
33b4aa6599
commit
629458a60d
2 changed files with 154 additions and 27 deletions
|
|
@ -128,12 +128,15 @@ TAGS = {
|
|||
# приводится В GIT: подъём был сделан в рабочем дереве и по нему уже потрачено $0.3348 против
|
||||
# закоммиченных $0.300 — то есть покупки прошли сверх зафризенной границы. Фриз-гейт кассу не
|
||||
# проверяет (он смотрит покупающий файл), и сессия этим воспользовалась вместо того, чтобы
|
||||
# зафризить подъём. Нарушение объявлено в отчёте Д11.8 и закрывается здесь. То есть остановила осторожность
|
||||
# зафризить подъём. Нарушение объявлено в отчёте Д11.8 и закрывается здесь.
|
||||
# ⚠ 15.08: ФД-C 0.36 → 0.42 под перекупку ТРЁХ половин японского пола, совпавших с парой
|
||||
# побайтно. Пол из близнецов даёт нулевую разницу и ЗАНИЖАЕТ порог различимости — тот самый
|
||||
# дефект «близнеца», который в эксп-23 сделал контроль пустым. Чинится только пере-генерацией. То есть остановила осторожность
|
||||
# проекции, а не реальная цена; трёх оставшихся клеток стоят $0.028 суммарно. Пакетный потолок поднят РОВНО на ту же дельту, чтобы инвариант «сумма фазовых ≤
|
||||
# пакетного» держался; ось никто не режет и деньги ниоткуда не забирает.
|
||||
CEILINGS = {"ФД-A": 1.950, "ФД-B": 0.950, "ФД-C": 0.360, "ФД-D": 0.050,
|
||||
CEILINGS = {"ФД-A": 1.950, "ФД-B": 0.950, "ФД-C": 0.420, "ФД-D": 0.050,
|
||||
"ФД-E": 2.370, "ФД-F": 0.720, "ФД-G": 0.210}
|
||||
PACK_CEILING = 6.68
|
||||
PACK_CEILING = 6.74
|
||||
|
||||
M22.configure(ZONE=ZONE, OUT=OUT, TAGS=TAGS, CEILINGS=CEILINGS,
|
||||
PACK_CEILING=PACK_CEILING, GLOB_ALL="dv-*.json", ROSTER=ROSTER)
|
||||
|
|
|
|||
|
|
@ -72,14 +72,26 @@ import absjudge as AJ # noqa: E402
|
|||
# паре, а ключи разных осей в одном каталоге — прямая дорога к разбору чужой приманки (класс,
|
||||
# уже пойманный дважды: `itog_d4.py:53` и селфтест этого файла).
|
||||
AXIS = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--axis=")), "d4")
|
||||
if AXIS not in ("d4", "d3"):
|
||||
if AXIS not in ("d4", "d3", "d6", "d1"):
|
||||
raise SystemExit(f"⛔ ось {AXIS} харнессу неизвестна")
|
||||
|
||||
WORK = Path.home() / f"sud-{AXIS}"
|
||||
# ⚠ СЕМЕЙСТВО — ПАРАМЕТР. Пре-рег Д0.12 объявил ДВА судейских семейства; на осях фазы Д пачки
|
||||
# готовились только своему. `--fam=sol` кладёт пачки в каталог внешнего харнесса владельца.
|
||||
FAM = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--fam=")), "claude")
|
||||
WORK = Path.home() / (f"sud-{AXIS}" if FAM == "claude" else f"sol-{AXIS}-work")
|
||||
TASKS1, TASKS2 = WORK / "p1-tasks", WORK / "p2-tasks"
|
||||
ANSW1, ANSW2 = WORK / "p1-answers", WORK / "p2-answers"
|
||||
VOTES = WORK / "votes"
|
||||
KEYS = Path.home() / "books" / "dovodka" / f"blind-keys-{AXIS}" # НЕ в WORK: судья туда не ходит
|
||||
# ⚠ КЛЮЧИ РАЗВЕДЕНЫ ПО СЕМЕЙСТВАМ, и это не аккуратность, а защита данных. Раскладка меток —
|
||||
# чистая функция от соли, uid и НАБОРА армов (`absjudge.layout`), поэтому любая эмиссия с другим
|
||||
# составом панели или другим контролем ПЕРЕПИСЫВАЕТ ключ. Пока каталог был общим, эмиссия пачек
|
||||
# для второго семейства обвалила бы разбор ответов первого: проверено исполнением 15.08 —
|
||||
# вердикт `E0/D0` съехал с +1.56 (перешёл порог) на +0.38 (ниже порога), и восстановился только
|
||||
# потому, что СОЛЬ лежит отдельным файлом и эмиссией не трогается. Своя соль у семейства = свой
|
||||
# независимый порядок меток, что вдобавок правильнее: два семейства не должны видеть одну
|
||||
# раскладку.
|
||||
KEYS = Path.home() / "books" / "dovodka" / (f"blind-keys-{AXIS}" if FAM == "claude"
|
||||
else f"blind-keys-{AXIS}-{FAM}")
|
||||
# ⚠ ПЕРЕВОДИМ РИГ НА КЛЮЧИ ФАЗЫ СРАЗУ, а не только внутри `emit`. Селфтест зовёт `AJ._plant`
|
||||
# напрямую и при дефолтном `AJ.KEYS` строил приманку ПОСАДКАМИ ЧУЖОГО ПАКА (7 против 6, правила
|
||||
# разные) — то есть гейт «грубый декой грубее маржевого» сертифицировал не тот текст, который
|
||||
|
|
@ -92,10 +104,31 @@ ARMS_OLD = ("A6", "A6F") # есть то
|
|||
|
||||
# Панель английской оси. `D0` — черновик: он ЧЛЕН панели, потому что первичный контраст оси
|
||||
# именно `E0/D0` (H-3 «покупает ли редактор поверх черновика»), а не контраст контуров.
|
||||
ARMS_D3 = ("D0", "E0", "E1", "E2", "E4")
|
||||
ARMS_D3 = ("D0", "E0", "E1", "E2", "E3", "E4", "E6")
|
||||
# Панель японской РАЗВЕДКИ: черновик, связка, однопроходка. Контроль оси — J0/D0.
|
||||
ARMS_D6 = ("D0", "J0", "J2")
|
||||
# Панель Д1: боевой перепис против добитого gemini, обе поверх ОДНОЙ якорной базы эксп-22.
|
||||
# Гипотеза эксп-04 «проза gemini — аутлаер» закрывается именно этим контрастом.
|
||||
ARMS_D1 = ("A0", "R1")
|
||||
FAMILY_D1 = [
|
||||
("R1", "A0", "gemini против боевого переписа на одной базе (гипотеза эксп-04)"),
|
||||
]
|
||||
FAMILY_D6 = [
|
||||
("J0", "D0", "боевой редактор поверх черновика на ja (ОПИСАТЕЛЬНО, ось разведочная)"),
|
||||
]
|
||||
# ⚠ ПЕРВИЧНОЕ семейство оси — ОДИН контраст, как объявила мощность (`power.CONTRAST_N`): при n=16
|
||||
# и k=1 ось несущая, при k=5 её MDE вырос бы выше цели. Остальные контрасты — ОПИСАТЕЛЬНЫЕ и
|
||||
# в поправку Холма не входят; печатать их как несущие нельзя.
|
||||
FAMILY_D3 = [
|
||||
("E0", "D0", "боевой редактор поверх черновика (H-3, несущая оси)"),
|
||||
]
|
||||
SECONDARY_D3 = [
|
||||
("E1", "E0", "флаговый контур против связки — на en это фактически КАНОН-контур"),
|
||||
("E2", "E0", "однопроходка уравненного мандата против связки"),
|
||||
("E3", "E0", "смысловой контур против связки (ставка владельца, на en описательно)"),
|
||||
("E4", "E0", "связка + канон-фиксер ПОСЛЕ"),
|
||||
("E6", "E0", "ВТОРОЙ редактор glm-5 (H-4: сменится ли порядок жильцов на другой паре)"),
|
||||
]
|
||||
|
||||
# ⚠ ПЕРВИЧНОЕ СЕМЕЙСТВО — по одному контрасту на гипотезу владельца, Холм по трём. Объявлено в
|
||||
# пре-реге Д0.5 и `power.py`; здесь оно ПОВТОРЕНО, а не переопределено, и впечатывается в ключ
|
||||
|
|
@ -125,28 +158,66 @@ MARGIN_PLANTS = [
|
|||
[r"\b(три|четыре|пять|шесть|семь|восемь|девять)\b", "двенадцать", "подмена числительного"],
|
||||
[r"\b(больше|выше|сильнее)\b", "меньше", "инверсия сравнения"],
|
||||
]
|
||||
# ⚠ КЛАССЫ ПОСАДОК — ОСЬ-ЗАВИСИМЫ, И ЭТО НЕ УДОБСТВО. Список выше подобран ЗАМЕРОМ на КИТАЙСКОМ
|
||||
# сырье (числительные словами — 26 единиц из 31, потому что сянься всё пересчитывает). На
|
||||
# английской оси тот же список ложится на 5 единиц из 16: у Кристоффа числительных почти нет
|
||||
# (2 из 16), сравнений мало (4 из 16). Гейт чувствительности при этом печатался ПРОЙДЕННЫМ.
|
||||
# Замер по 16 эталонным текстам английской оси дал класс, который ложится на 15 из 16 при 59
|
||||
# вхождениях: подмена принадлежности «его»→«её». Она ровно того сорта, ради которого декой и
|
||||
# заведён — меняется одна буква, текст остаётся грамотным, а смысл «чьё» переворачивается.
|
||||
# С ней единиц с ≥2 посадками становится 11 из 16, то есть гейт берётся честно.
|
||||
#
|
||||
# ⚠⚠ ПОЧЕМУ ЭТО НЕ ПРИМЕНЕНО К УЖЕ ОТСУЖЕННОМУ. Маржевый декой НЕ хранится — он ВОССТАНАВЛИВАЕТСЯ
|
||||
# из эталона при разборе (`text_of("CTRLmargin", …)`). Смена списка классов меняет восстановленный
|
||||
# текст, и он перестаёт совпадать с тем, что реально видел судья: разбор начнёт сверять ответы с
|
||||
# ДРУГОЙ приманкой. Ровно этот класс уже стоил фазе починки ключей (`itog_d4.py:53`). Поэтому
|
||||
# расширенный список включается ТОЛЬКО для новых эмиссий, а оси, уже отсуженные на узком списке,
|
||||
# несут его как объявленное ограничение.
|
||||
# Класс подобран ЗАМЕРОМ на материале КАЖДОЙ оси отдельно, а не перенесён с китайской:
|
||||
# en: подмена принадлежности ложится на 15 единиц из 16 (59 вхождений)
|
||||
# ja: на 8 из 9 (14 вхождений); снятие отрицания там же — 9 из 9
|
||||
# Подмена указания («этот»→«тот», 9 из 9 у ja) отвергнута сознательно: она грамматична в обе
|
||||
# стороны, референт бывает неоднозначен, и судья вправе счесть её допустимой — посадка обязана
|
||||
# быть однозначной ошибкой, иначе «не пойман» перестаёт что-либо значить.
|
||||
_POSS = [r"\b(его)(\s+[а-яё]{3,})", r"её\2",
|
||||
"подмена принадлежности — смысл «чьё» инвертирован"]
|
||||
MARGIN_EXTRA = {"d3": [_POSS], "d6": [_POSS]}
|
||||
if "--wide-plants" in sys.argv:
|
||||
MARGIN_PLANTS = MARGIN_PLANTS + MARGIN_EXTRA.get(AXIS, [])
|
||||
|
||||
# Сколько единиц и пар пола ОБЪЯВЛЕНО у каждой оси. Прежде тут стояли китайские константы (31 и
|
||||
# 15), и селфтест на новых осях падал не потому, что прибор плох, а потому, что сверялся с чужим
|
||||
# числом. Настоящие дефекты за этим шумом терялись.
|
||||
EXPECT_N = {"d4": 31, "d3": 16, "d6": 9, "d1": 15}
|
||||
MIN_FLOOR = {"d4": 15, "d3": 10, "d6": 3, "d1": 12}
|
||||
|
||||
TWIN_GATE = "--no-twins" in sys.argv
|
||||
|
||||
MARGIN_MIN = 2 # клетка, где посадить удалось меньше, в маржевый декой не идёт
|
||||
|
||||
|
||||
ARMS = ARMS_D3 if AXIS == "d3" else ARMS_D4
|
||||
FAMILY = FAMILY_D3 if AXIS == "d3" else FAMILY_D4
|
||||
BASE_ARM = "E0" if AXIS == "d3" else "A0" # эталон, против которого строится декой и пол
|
||||
ARMS = {"d3": ARMS_D3, "d6": ARMS_D6, "d1": ARMS_D1}.get(AXIS, ARMS_D4)
|
||||
FAMILY = {"d3": FAMILY_D3, "d6": FAMILY_D6, "d1": FAMILY_D1}.get(AXIS, FAMILY_D4)
|
||||
# Эталон оси — против него строятся декой и пол. У японской это связка J0.
|
||||
BASE_ARM = {"d3": "E0", "d6": "J0"}.get(AXIS, "A0")
|
||||
|
||||
|
||||
def _wire_axis() -> None:
|
||||
"""Включить пару оси. Для d4 — no-op (китайская пара и так умолчание)."""
|
||||
if AXIS == "d3":
|
||||
if AXIS in ("d3", "d6"):
|
||||
import importlib.util # noqa: PLC0415
|
||||
drv = "en_axis.py" if AXIS == "d3" else "ja_axis.py"
|
||||
sp = importlib.util.spec_from_file_location(
|
||||
"en_axis_sud", Path(__file__).resolve().parent / "en_axis.py")
|
||||
"en_axis_sud", Path(__file__).resolve().parent / drv)
|
||||
ax = importlib.util.module_from_spec(sp)
|
||||
sys.modules["en_axis_sud"] = ax
|
||||
sp.loader.exec_module(ax)
|
||||
# ⚠ НЕ `ax.wire()`: драйвер держит СВОЙ экземпляр контура, и его настройка нашего не
|
||||
# касается — судейство осталось бы на китайской паре с 31 единицей вместо 16. Ловушка
|
||||
# та же, что с кассой (`money_d` настраивал свой экземпляр). Настраиваем СВОЙ.
|
||||
C.configure(C.PARA.EN, units_provider=ax.units,
|
||||
base_draft_hook=ax.base_draft, base_a0_hook=ax.base_e0)
|
||||
C.configure(C.PARA.EN if AXIS == "d3" else C.PARA.JA, units_provider=ax.units,
|
||||
base_draft_hook=ax.base_draft,
|
||||
base_a0_hook=ax.base_e0 if AXIS == "d3" else ax.base_j0)
|
||||
|
||||
|
||||
_wire_axis()
|
||||
|
|
@ -154,12 +225,39 @@ _wire_axis()
|
|||
|
||||
def units():
|
||||
"""Единицы, допустимые к судейству: без брака базы и без клеток finish=length."""
|
||||
return C.units_ok()
|
||||
us = C.units_ok()
|
||||
if AXIS == "d1":
|
||||
# ⚠ Только единицы, где добивка РЕАЛЬНО собрана: судить пустую клетку нельзя, а
|
||||
# молчаливое включение дало бы контраст на разной выборке.
|
||||
us = [u for u in us if (C.OUT / f"dv-e-r1-{u['uid']}.json").exists()]
|
||||
return us
|
||||
|
||||
|
||||
def _twin_of_base(arm: str, uid: str, txt: str) -> bool:
|
||||
"""Совпадает ли текст арма ПОБАЙТНО с эталоном оси.
|
||||
|
||||
⚠ Зачем гейт. Норма фазы запрещает класть в ОДНУ пачку два одинаковых текста под разными
|
||||
метками («урок близнеца», эксп-23: `CTRLfloorA ≡ T1` в 16/16 и контроль оказался пуст).
|
||||
На английской оси это воспроизвелось само: канон-гейт не нашёл потерь на 12 единицах из 16,
|
||||
фиксер не вызывался, и арм `E4` записал вход — то есть текст `E0` — как свой выход. Судья
|
||||
читал бы один текст дважды и, увидев совпадение, мог бы оценить его сличением, а не чтением.
|
||||
Такая клетка в пачку НЕ идёт; арм на этой единице объявляется «равен эталону», а не судится.
|
||||
"""
|
||||
if arm == BASE_ARM or not txt.strip():
|
||||
return False
|
||||
base = C.base_a0(uid)
|
||||
return bool(base.strip()) and txt.strip() == base.strip()
|
||||
|
||||
|
||||
def text_of(arm: str, u: dict) -> str:
|
||||
"""Текст арма. Клетка, не прошедшая гейт годности, в пачку НЕ идёт — возвращается пусто."""
|
||||
uid = u["uid"]
|
||||
if arm == "R1":
|
||||
# ⚠ Клетки добивки живут в кассе ФД-E под своим префиксом, а не в схеме тегов контура.
|
||||
f = C.OUT / f"dv-e-r1-{uid}.json"
|
||||
if not f.exists():
|
||||
return ""
|
||||
return json.loads(f.read_text(encoding="utf-8")).get("content") or ""
|
||||
if arm == "A0":
|
||||
return C.base_a0(uid)
|
||||
if arm == "CTRLmargin":
|
||||
|
|
@ -173,7 +271,16 @@ def text_of(arm: str, u: dict) -> str:
|
|||
# половиной пола — её выход оборван на середине предложения, и судья оценит обрыв, а не арм.
|
||||
if d.get("finish") != "stop":
|
||||
return ""
|
||||
return d.get("content") or ""
|
||||
txt = d.get("content") or ""
|
||||
# ⚠ ГЕЙТ ВКЛЮЧАЕТСЯ ЯВНО (`--no-twins`), и это не педантизм. Он МЕНЯЕТ СОСТАВ ПАНЕЛИ, а
|
||||
# значит и раскладку меток в слепом ключе. Включённый по умолчанию, он при повторной эмиссии
|
||||
# переписал ключ уже отсуженной оси, и разбор начал сопоставлять ответы с ДРУГИМИ армами:
|
||||
# вердикт `E0/D0` съехал с +1.56 (перешёл порог) на +0.38 (ниже). Ключ восстановим, потому
|
||||
# что соль перетасовки лежит отдельно и не перезаписывается, — но цена ошибки была бы
|
||||
# невосстановимой, будь соль в том же файле.
|
||||
if TWIN_GATE and _twin_of_base(arm, uid, txt):
|
||||
return "" # близнец эталона — в пачку не идёт, см. `_twin_of_base`
|
||||
return txt
|
||||
|
||||
|
||||
def margin_plant(text: str) -> tuple[str, int]:
|
||||
|
|
@ -188,7 +295,9 @@ def margin_plant(text: str) -> tuple[str, int]:
|
|||
|
||||
def floor_pair(u: dict, half: int) -> str:
|
||||
"""ОДНА половина пары пола. Наборы П1/П2 берут разные половины — см. отступление 1 в шапке."""
|
||||
f = C.OUT / f"dv-a-flo{half}-{u['uid']}.json"
|
||||
# ⚠ Префикс — фазовая буква ОСИ, а не константа `a`: у английской оси пол лежит в `dv-b-flo*`,
|
||||
# и жёсткое `dv-a-` подсунуло бы ей КИТАЙСКИЙ шум как собственный порог.
|
||||
f = C.OUT / f"dv-{C.phase_letter(BASE_ARM)}-flo{half}-{u['uid']}.json"
|
||||
if not f.exists():
|
||||
return ""
|
||||
d = json.loads(f.read_text(encoding="utf-8"))
|
||||
|
|
@ -202,20 +311,20 @@ def setup(half: int) -> None:
|
|||
чужим паком касса и каталоги молча читали чужое — этот класс стоил паку 22 пересчёта
|
||||
32 единиц вместо 16 (дефект D-3). Поэтому здесь перечислено ВСЁ, а не только изменяемое.
|
||||
"""
|
||||
old = {u["uid"] for u in C.P22.M.units_zh()}
|
||||
arms = ARMS_D4 + ARMS_OLD + ("CTRLmargin",)
|
||||
old = {u["uid"] for u in C.P22.M.units_zh()} if AXIS == "d4" else set()
|
||||
arms = (ARMS + ARMS_OLD + ("CTRLmargin",)) if AXIS == "d4" else (ARMS + ("CTRLmargin",))
|
||||
t, a = (TASKS1, ANSW1) if half == 1 else (TASKS2, ANSW2)
|
||||
AJ.configure(
|
||||
PASS=f"d4p{half}",
|
||||
PASS=f"{AXIS}p{half}",
|
||||
ARMS=arms,
|
||||
FAMILY=FAMILY_D4,
|
||||
SRC_LANG="китайском",
|
||||
FAMILY=FAMILY,
|
||||
SRC_LANG=C.pair().lang_name,
|
||||
UNITS_HOOK=units,
|
||||
TEXT_HOOK=lambda arm, u: ("" if arm in ARMS_OLD and u["uid"] not in old
|
||||
else text_of(arm, u)),
|
||||
# декой сажается в БОЕВОЙ ПЕРЕПИС, и донор объявлен: в паке 23 донором во всех 16 единицах
|
||||
# был один и тот же арм, и это осталось необъявленным до самого разбирательства
|
||||
DECOY_HOOK=lambda slots: slots.get("A0") or "",
|
||||
DECOY_HOOK=lambda slots: slots.get(BASE_ARM) or "",
|
||||
# пол НЕ через штатный крючок: он кладёт обе половины в одну пачку — см. отступление 1
|
||||
FLOOR_HOOK=lambda u: ((floor_pair(u, half), floor_pair(u, half))
|
||||
if False else None),
|
||||
|
|
@ -249,7 +358,17 @@ FORBIDDEN = ("blind-keys", "dv-a-", "dv-b-", "dv-c-", "dv-f-", "dv-g-",
|
|||
def audit(tr_dir: Path) -> int:
|
||||
"""⚠ ПОСТФАКТУМ-АУДИТ: куда судья ходил инструментами. Существует, вопреки Д0.12."""
|
||||
bad = 0
|
||||
files = sorted(Path(tr_dir).glob("*.jsonl"))
|
||||
# ⚠ Транскрипты суб-агентов харнесса лежат с расширением `.output`, а содержимое у них
|
||||
# JSONL. Маска только по `*.jsonl` печатала «транскриптов нет — пачка НЕ принимается», то
|
||||
# есть аудит выглядел проваленным там, где он просто смотрел не на те имена.
|
||||
files = sorted([*Path(tr_dir).glob("*.jsonl"), *Path(tr_dir).glob("*.output")])
|
||||
# ⚠ АУДИТ КАСАЕТСЯ ТОЛЬКО СУДЕЙСКИХ сессий. В каталоге задач харнесса лежат вперемешку и
|
||||
# транскрипты судей, и вывод собственных фоновых команд полигона — а те законно трогают
|
||||
# сырьё (`dv-c-` и прочее). Без фильтра аудит помечал ПОКУПКУ как «судья лез куда нельзя»:
|
||||
# ложная тревога, которая при беглом чтении выглядит как скомпрометированная пачка.
|
||||
# Признак судейской сессии — она открывала каталог заданий своей оси.
|
||||
marker = str(TASKS1.parent.name)
|
||||
files = [f for f in files if marker in f.read_text(encoding="utf-8", errors="replace")]
|
||||
if not files:
|
||||
print(f"⛔ транскриптов в {tr_dir} нет — аудит невозможен, пачка НЕ принимается")
|
||||
return 1
|
||||
|
|
@ -275,7 +394,8 @@ def selftest() -> int:
|
|||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
|
||||
|
||||
us = units()
|
||||
ck("единиц к судейству 31 (32 минус эхо-мина)", len(us) == 31, str(len(us)))
|
||||
ck(f"единиц к судейству — сколько объявила ось ({EXPECT_N[AXIS]})",
|
||||
len(us) == EXPECT_N[AXIS], str(len(us)))
|
||||
u = us[0]
|
||||
# клетки finish=length не должны попадать в пачку ни одним армом
|
||||
lens = [(a, x["uid"]) for a in ARMS_D4 + ARMS_OLD for x in us
|
||||
|
|
@ -319,7 +439,8 @@ def selftest() -> int:
|
|||
# половины пола идут в РАЗНЫЕ наборы и не совпадают побайтно
|
||||
pairs = [(floor_pair(x, 1), floor_pair(x, 2)) for x in us]
|
||||
pairs = [p for p in pairs if p[0] and p[1]]
|
||||
ck("пар пола не меньше 15", len(pairs) >= 15, str(len(pairs)))
|
||||
ck(f"пар пола не меньше объявленного осью ({MIN_FLOOR[AXIS]})",
|
||||
len(pairs) >= MIN_FLOOR[AXIS], str(len(pairs)))
|
||||
ck("половины пола НЕ равны побайтно (иначе контроль пуст)",
|
||||
not [1 for a, b in pairs if a == b])
|
||||
# половина пола не должна совпадать с боевой клеткой (урок «близнеца» пака 23)
|
||||
|
|
@ -341,7 +462,10 @@ def selftest() -> int:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = sys.argv[1:] or ["--selftest"]
|
||||
# ⚠ `--axis=` разбирается на уровне модуля (см. AXIS) и здесь ОТБРАСЫВАЕТСЯ из разбора
|
||||
# режима: иначе он попадает первым аргументом и падает как «неизвестный режим».
|
||||
a = [x for x in sys.argv[1:]
|
||||
if not x.startswith(("--axis=", "--fam=", "--wide-plants", "--no-twins"))] or ["--selftest"]
|
||||
if a[0] == "--selftest":
|
||||
sys.exit(1 if selftest() else 0)
|
||||
elif a[0] == "--emit":
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue