From 28c2d0fd505534f8c85060373bd4ce11d57350a1 Mon Sep 17 00:00:00 2001 From: heaven Date: Sat, 29 Aug 2026 13:30:17 +0300 Subject: [PATCH] Carry the polygon's uncommitted phase-D work onto its own branch: judging rigs, session logs and plans that had been living in the shared tree since the machine move --- eval/conformance.py | 9 +- eval/dovodka/HANDOFF-21-08.md | 382 ++++++++ eval/dovodka/PLAN-16-08.md | 566 ++++++++++++ eval/dovodka/PLAN-17-08.md | 376 ++++++++ eval/dovodka/SESSION2-LOG.md | 1217 ++++++++++++++++++++++++++ eval/dovodka/adjud.py | 299 ++++++- eval/dovodka/chtenie.py | 254 +++++- eval/dovodka/d1_gemini.py | 12 +- eval/dovodka/itog_d4.py | 282 +++++- eval/dovodka/ja6.py | 505 +++++++++++ eval/dovodka/judge-prompts/claude.md | 18 +- eval/dovodka/judge-prompts/core.md | 12 +- eval/dovodka/judge-prompts/sol.md | 18 +- eval/dovodka/naklon.py | 153 ++++ eval/dovodka/promptdiff.py | 32 +- eval/dovodka/requirements.md | 14 +- eval/dovodka/sessii.py | 36 +- eval/dovodka/sud.py | 42 +- eval/dovodka/vtoroe.py | 26 +- 19 files changed, 4164 insertions(+), 89 deletions(-) create mode 100644 eval/dovodka/HANDOFF-21-08.md create mode 100644 eval/dovodka/PLAN-16-08.md create mode 100644 eval/dovodka/PLAN-17-08.md create mode 100644 eval/dovodka/ja6.py create mode 100644 eval/dovodka/naklon.py diff --git a/eval/conformance.py b/eval/conformance.py index b231f7c9..3792a4c6 100644 --- a/eval/conformance.py +++ b/eval/conformance.py @@ -77,7 +77,14 @@ def rows(md: str) -> list[tuple[str, str, str]]: if out: # таблица кончилась break continue - cells = [c.strip() for c in s.strip("|").split("|")] + # ⚠ Markdown экранирует вертикальную черту внутри ячейки как `\|`. Наивный `split("|")` + # рвал такую ячейку пополам, и улика приходила обрезанной: у R55 в оболочку уезжало + # `... | wc -l) -ge 7` без начала, что давало «Syntax error» и ЛОЖНЫЙ КРАСНЫЙ, а у R37 — + # ЛОЖНЫЙ ЗЕЛЁНЫЙ, потому что обрезок случайно возвращал 0. Гейт, который сам портит + # улику, не проверяет ничего. Восстанавливается ОБЫЧНАЯ черта: в markdown `\|` и означает + # литеральный `|`, и в оболочку должен уйти именно он. + cells = [c.replace("\x00", "|").strip() + for c in s.strip("|").replace("\\|", "\x00").split("|")] if len(cells) < 3 or set(cells[0]) <= set("-: "): continue if cells[0].upper() in ("ID", "№"): diff --git a/eval/dovodka/HANDOFF-21-08.md b/eval/dovodka/HANDOFF-21-08.md new file mode 100644 index 00000000..3aa08a5c --- /dev/null +++ b/eval/dovodka/HANDOFF-21-08.md @@ -0,0 +1,382 @@ +# ХЕНДОФФ: полигон, следующая сессия (готовлено 20.08 вечером) + +> Читать ЦЕЛИКОМ, не грепом. Прошлые передачи теряли пункты именно из-за чтения грепом. +> Предыдущие слои: `SESSION2-LOG.md` разделы 15.1–15.21 (по ходу этой сессии) · +> `PLAN-17-08.md` (курс) · отчёт `docs/experiments/23-editor-tier.md` секции Д21, Д23 · +> `КОНТЕКСТ-ДЛЯ-КОНСУЛЬТАЦИИ.md` (сжатая картина проекта для внешнего собеседника). + +--- + +## 0. ГЛАВНОЕ, ЧТО ИЗМЕНИЛОСЬ ЗА СЕССИЮ + +**Сменился ПРИБОР.** Решение владельца 20.08 дословно: «меняем принцип судейства… теперь корми +фабл 5 и пусть он решает какой из вариантов ближе по художественному смыслу, лучше по переводу», +и позже: «Теперь судим так все время». Слепое чтение Fable-5 — ПЕРВИЧНЫЙ прибор; счёт ошибок +остаётся брак-скрином. + +**Основание замерено, а не принято на веру.** На панели, которую читал сам владелец: + +``` +пара прибор Спирмен с владельцем +zh чтение Fable (чистый пакет) +0.80 +zh счёт ошибок +0.80 +en чтение Fable (чистый пакет) +0.80 +en счёт ошибок −0.80 +``` + +Новый прибор согласен с владельцем ОДИНАКОВО на обеих парах; старый меняет ЗНАК от пары к паре. + +⚠ **И тут же найдена моя ошибка:** в пакете, который читал владелец, стояла подсказка — «вариант +с наименьшим числом ошибок читался почти хуже всех». Пере-прогон на НЕЙТРАЛЬНОМ пакете (тексты +побайтно те же) дал +0.80 вместо +1.00: подсказка стоила 0.20, верх и низ порядка устояли. +**Норма, заведённая этим: пакет чтения не имеет права называть ни вердикт другого прибора, ни +существование контроля.** + +--- + +## 1. ЧТО ЖДЁТ СЛЕДУЮЩУЮ СЕССИЮ — ГОТОВО К ЗАПУСКУ + +**15 пакетов слепого чтения английской пары, по ОДНОЙ главе в пакете, панель из ОДИННАДЦАТИ +архитектур.** Всё куплено, покупать больше нечего. + +``` +пакеты ~/books/chtenie-rol/ЧТЕНИЕ-en-arch2-.md (15 штук, ~17 тыс. знаков каждый) +ключи ~/books/dovodka/blind-keys-rol/rol-KEY-arch2-.json ЧИТАТЕЛЮ НЕ ДАВАТЬ +ответы ~/books/chtenie-rol/ОТВЕТ-en-arch2-.md (заготовки созданы) +``` + +Панель: `ZD` голый черновик · `Z0` боевая связка (продакшен) · `ZF` её вторая генерация +(контроль шума) · `ZP` однопроходка-склейка · `Z8` обогащённый черновик · `Z8R` он же плюс перепис · +`Z1` критик → ПОЛНЫЙ перепис (ставка владельца H-2б) · `Z7` однопроходка + канон-фиксер · +`RN` промт-РОЛЬ с экзаменом · `RC` он же без экзамена · `RB` он же с ПЕРЕВЁРНУТЫМ экзаменом +(формулировка владельца). + +⚠ **Одна глава из 16 пропущена и это НАПЕЧАТАНО, а не умолчано:** `100b088f71` — у неё нет клетки +`RN` (единственная, что не собралась после двух попыток). Пакетов 15. +⛔ **ПОПРАВКА 21.08: строка ниже была НЕВЕРНА, и это ровно тот класс, о котором предупреждает шапка +файла.** Утверждение «прежние пакеты `arch` УДАЛЕНЫ» я записал как исполненное, не исполнив: на +диске лежат **16 пакетов `ЧТЕНИЕ-en-arch-.md`, 16 пустых `ОТВЕТ-en-arch-.md` и 16 ключей +`rol-KEY-arch-.json`** (панель из 8, ответов нет ни одного — проверено размером файлов). +Удалять не стал: сырьё чужой рукой не сносят, а механической коллизии глобов между `arch-` и +`arch2-` нет (проверено). Опасность ЧЕЛОВЕЧЕСКАЯ — принять один тег за другой; для того здесь и +стоит эта поправка. + +~~Прежние 15 пакетов с тегом `arch` (панель из 8) УДАЛЕНЫ~~ — держать две панели одной пары значит +однажды свести ответы разных панелей одним ключом. + +**Работать с `arch2`. Пакеты `arch` существуют, не прочитаны и не годятся: панель у них из 8.** + +**Сборка ПРОВЕРЕНА СПЛОШЬ** (владелец просил убедиться до передачи): +* 15 пакетов, 15 уникальных глав, 15 РАЗНЫХ раскладок меток; +* **165 текстов сверены побайтно с содержимым клеток своих армов — расхождений 0**; +* в рамке КАЖДОГО пакета стоит указание владельца «торопиться не надо» (проверено гейтом); +* исходник в каждом пакете — тот самый, что у главы; +* имён армов и путей к ключу в пакетах нет; +* рамка нейтральна: не называет ни контроля, ни другого прибора. +⚠ Первая проверка дала 16 «утечек» — это была ЛОЖНАЯ тревога моей же проверки: она искала слово +«метки», которое в рамке стоит легально («метки перемешаны»). Проверка переделана на поиск ИМЁН +АРМОВ. Не повторить эту ошибку в обратную сторону: тревога гейта требует вскрытия, а не отмены. + +**Как запускать:** по ОДНОМУ агенту Fable-5 на пакет (решение владельца: главы большие, читателю +с несколькими сразу тяжело). Промт-рамку взять из журнала §15.13/§15.21 — она уже отработала +четырежды. Обязательно: идентичность читателя в файл ДО запуска · `runs.py --claim` ДО запуска · +греп-аудит транскрипта ПОСЛЕ (образец кода в журнале). + +✔ **КАП СНЯТ 20.08 словом владельца** («Можно поднять, но я не припомню что б ставил тебе жёсткие +ограничения»): 100 → 200, свободно 107, на 16 глав хватает. ⚠ Уточнение для истории: число 80 +пришло из ЗАКАЗА (:25), владелец снял его ещё 15.08, а 100 поставил в код Я САМ — то есть сессия +упиралась в СВОЁ ограничение и подала его владельцу как чужое. Не повторять: перед тем как назвать +границу «ограничением владельца», найти его слово. + +--- + +## 2. ЧТО УСТАНОВЛЕНО ЭТОЙ СЕССИЕЙ — числа, которые нельзя потерять + +### 2.1 ⭐ Механизм цены вскрыт ТРЕЙСОМ, а не выведен + +Сохранение трейса размышления заведено по флагу `TM_KEEP_TRACE=1` (`role_topology/buy.py`). +Прямая мера: доля предложений ФИНАЛЬНОГО текста, встречающихся в трейсе дословно — **медиана 96%**. +Разбор трейса: черновик в уме 54% абзацев · структура 11% · **само-проверка 4%** · глоссарий 3%. + +⇒ **Размышление у `deepseek-v4-pro` — это ЧЕРНОВИК, а не проверка. Модель пишет перевод дважды: +в уме и в ответе. Мы платим за обе копии по цене выхода.** +⇒ Следствие для архитектуры: **боевая двухстадийная связка дёшева БЛАГОДАРЯ топологии — она +выносит черновик из канала размышления дорогой модели в выход дешёвой** ($3.96 против $1.32 за +миллион), и результат при этом виден, проверяем и переиспользуем, а не выбрасывается. +⚠ Режим БИМОДАЛЕН: одна клетка из шести в ум не писала вовсе (1 180 токенов против 23 000). + +### 2.2 Промт-роль: измерен, лучше склейки, дороже вчетверо + +Новый промт однопроходки собран как РОЛЬ (прежний был СКЛЕЙКОЙ двух боевых промтов с мета-фразой +«отдельного редактора после тебя НЕ БУДЕТ»). Ядро — `eval/dovodka/prompts/onepass-core.md`, +пар-специфика подтягивается из файлов пары, второго источника правды нет. + +``` +арм что это размышление $/клетка перевёрстка +ZP прежняя склейка 950 0.0177 1.77 +RN роль, экзамен на смелости 28 809 0.1265 1.60 +RB роль, экзамен ПЕРЕВЁРНУТ 23 301 0.1050 1.14 +RC роль, экзамен вырезан 11 268 0.0593 1.37 +Z0 боевая связка (для сравнения) — 0.0253 1.79 +``` + +Слепое чтение абляции (3 главы, 5 вариантов): **`RB > Z0 > RC > RN > ZF`**. +⇒ формулировка ВЛАДЕЛЬЦА («брак = просвечивающий исходный язык») бьёт обе мои редакции и встала +выше продакшена. ⛔ **НО контроль шума КРАСНЫЙ** — две генерации связки на 2-м и 5-м местах, +то есть порядок при n=3 не разрешим. Уцелевает: `RB` не хуже продакшена, и три редакции промта +упорядочены между собой. + +### 2.3 ⛔ ЧТО НЕ СРАВНИМО МЕЖДУ СОБОЙ — поймано владельцем + +Четыре слепых чтения этой сессии сделаны на РАЗНЫХ главах: +A и B — глава `ed068182cf` · C — `1431129de6, c73f4857e7, 89614f9bfa` · D — `ef9cd38ec4, +89614f9bfa, b2a7464dbd`. **У C и D общая ОДНА глава из трёх, у A/B с C/D — НИ ОДНОЙ.** +⇒ фразы «арм был третьим, стал четвёртым» — НЕ повторный замер. Внутри одного чтения сравнения +законны, между чтениями нет. Ради этого и собраны 16 пакетов одной панели на одних главах. + +### 2.4 Замер критика (фаза ФД-K, остановлен владельцем) + +4 клетки одной главы: фраза о параллельности текстов срезала размышление на 55% и цену вдвое — +и находки тоже вдвое (24 → 12). **Критик НЕ видит удалённого целого абзаца** (проверено: слов из +пропавшего куска нет ни у одной модели), при том что снятую частицу «не» ловит. +Реальный дефект в черновике удорожает критика **в 14 раз** против чистого. +`glm-5` с включённым размышлением не влезает в потолок вывода на китайском. +⇒ полноту обязан ловить детерминированный гейт, а не критик. + +### 2.5 Английский экстрактор ломает абзацы — наш баг + +`pair_en.raw_text` сносит из epub все теги, включая `

`: в книге **11 447 абзацев**, а модели +едет один блок. Чинить на месте НЕЛЬЗЯ: uid единицы = `sha1(source.strip())`, правка пробелов +переименует всё — замерено, что повиснут **289 оплаченных клеток на $2.64** и семь файлов ключей. +Починка сделана НА ПОДАЧЕ (арм `RA`, функция `podacha.restore_paragraphs`, проверено 16/16). +⛔ **ДОЛГ ЧУЖОЙ ЗОНЕ: настоящая починка — в бэкенде. Пинг оркестратору обязателен.** + +### 2.6 Приз best-of-2 пере-считан ЧЕСТНО (не круговым способом) + +Выбирает читатель A, оценивает читатель B (и наоборот): +``` +все главы: +0.28 позиции p=0.30 — шум +расхождение ≥10% текста: +2.36 позиции p=0.0065 — 12 подтвердили, 2 опровергли +расхождение <10%: −1.33 позиции p=0.95 — отбор ВРЕДИТ +``` +⇒ наивный «бери лучший из двух» бесполезен; на разошедшихся главах приз реален и больше +оракульного. Порог 10% выбран post-hoc — заморозить пре-регом и проверить на новых главах. + +### 2.7 Г5 закрыта: вердикт по H-1 устоял + +`itog_d4.py --sens`. Арифметика находки ревизии воспроизведена побайтно, но переворот живёт только +в сценарии, который возвращает пачку аннулированной сессии и одновременно снимает пачки-валидации. +Смежно закрыты P12, P07, R73/P40. ⛔ ОТКРЫТА **P42** — два пре-рег-правила о маржевом гейте +противоречат, и от выбора зависит, остаётся ли единственный CONFIRM фазы. **Вопрос владельцу.** + +--- + +## 3. ДЕНЬГИ + +``` +ФД-K 0.2927 / 0.40 замер подачи входа критику, zh (остановлен владельцем) +ФД-L 0.0000 / 1.30 то же, en — не начиналось +ФД-M 2.0066 / 2.10 однопроходка как роль, zh +ФД-N 0.0000 / 2.40 то же, en — не начиналось +ПАК 14.0221 / 18.30 свободно 4.28 +``` +Санкции владельца этой сессии: «Хорошо, поехали» на $1.70 (ФД-K/L) и «Бюджет разрешаю какой +нужен» (ФД-M/N). ⚠ Смета ФД-M была занижена ВШЕСТЕРО: считалась по клеткам старой склейки, у +которой размышления в 26 раз меньше. Норма: цену клетки НОВОГО промта нельзя оценивать по клеткам +СТАРОГО. + +--- + +## 4. КОМАНДЫ + +``` +eval/.venv/bin/python eval/dovodka/rol.py --selftest # гейт покрытия боевых промтов +eval/.venv/bin/python eval/dovodka/rol.py --show zh|en # промт целиком, как поедет +eval/.venv/bin/python eval/dovodka/rol.py --coverage zh|en # диспозиция КАЖДОГО боевого правила +eval/.venv/bin/python eval/dovodka/rol.py --dry # смета +eval/.venv/bin/python eval/dovodka/rol.py --canon # $0-гейт канона по армам +eval/.venv/bin/python eval/dovodka/rol.py --emit-read en --panel=… --tag=… --each --n=16 +eval/.venv/bin/python eval/dovodka/itog_d4.py --axis=d4 --sens # Г5 целиком +eval/.venv/bin/python eval/dovodka/money_d.py --report | --selftest +eval/.venv/bin/python eval/dovodka/runs.py --status | --claim … | --done N +TM_KEEP_TRACE=1 …rol.py --buy zh RC RB --n=3 # покупка с сохранением трейса +``` + +--- + +## 5. ЛОВУШКИ, ДОБАВИВШИЕСЯ ЭТОЙ СЕССИЕЙ + +1. **Гейт, сертифицирующий правило по ключевому слову, обходится словом из правила + ПРОТИВОПОЛОЖНОГО смысла.** Мой гейт покрытия считал «убирай лишние повторы» покрытым словом + «разнообразь», которое стоит в правиле «НЕ разнообразь». Поймала приёмка Fable-5. +2. **Правила короче 25 знаков гейт не сканировал вовсе** — порог длины был слепым пятном. +3. **Мёртвая строка таблицы неотличима от живой.** Заведена проверка «каждая строка обязана + сработать хотя бы раз». +4. **Свой же пакет чтения может нести подсказку.** См. §0. +5. **Тревога собственной проверки может быть ложной** (слово «метки» в рамке). Вскрывать, а не + отменять. +6. **Фриз-гейт кассы срабатывает на ЛЮБУЮ правку покупающего файла** — правил код после фриза, + покупка отказана. Это правильно; фризить ПЕРЕД покупкой. +7. **Оборванные на потолке клетки надо уводить в карантин ПЕРЕД перекупкой** — кэш `purchase` + смотрит на существование файла и вернёт старую запись. +8. **Ключ каждой панели чтения — в СВОЁМ файле.** Пере-эмиссия с другим составом армов переписала + бы раскладку уже прочитанной панели, и ответ читателя стал бы мусором молча. + +--- + +## 6. ЧТО СПРОСИТЬ У ВЛАДЕЛЬЦА ПЕРВЫМ ДЕЛОМ + +1. **Кап агент-сессий: осталось 7 из 100**, а на полное чтение 16 глав нужно 16. Поднимать? +2. ~~**P42**~~ — **СНЯТ С ПОВЕСТКИ РЕШЕНИЕМ ВЛАДЕЛЬЦА 20.08.** См. §8. +3. ~~Нужны ли новые редакции промта в английской панели~~ — **ВЛАДЕЛЕЦ СКАЗАЛ ДА (20.08).** + См. §7. + + +--- + +## 7. РЕШЕНИЕ ВЛАДЕЛЬЦА 20.08: НОВЫЕ РЕДАКЦИИ ПРОМТА — В АНГЛИЙСКУЮ ПАНЕЛЬ + +Дословно: «Нужны, можешь занести в хэндофф?» + +**Что докупить ПЕРЕД чтением английской панели:** + +``` +арм что это модель клеток ~цена +RN роль, экзамен с риском на смелости deepseek-v4-pro 16 $0.35 +RC роль, экзамен ВЫРЕЗАН deepseek-v4-pro 16 $0.35 +RB роль, экзамен ПЕРЕВЁРНУТ (лучшая редакция) deepseek-v4-pro 16 $0.35 + ИТОГО ≈ $1.05 +``` + +⚠ **Смета ОРИЕНТИРОВОЧНАЯ и снята с КИТАЙСКИХ клеток.** Английская глава короче китайской, но +норма, выученная этой сессией дорого: цену клетки нельзя оценивать по клеткам другого промта или +другой пары. Перед покупкой прогнать `rol.py --dry` и сверить с фактом на первых трёх клетках. +Касса ФД-N: потрачено $0, потолок $2.40 — влезает с запасом. + +⚠ **Порядок работ жёсткий:** сперва докупить, потом ПЕРЕ-эмитировать пакеты с расширенной панелью +под НОВЫМ тегом (`--tag=arch2`), потому что пере-эмиссия с другим составом армов переписывает +раскладку меток. Существующие 16 пакетов `arch` НЕ трогать — если их уже прочитали, ответы +станут мусором. + +**Панель станет из 11 армов:** `ZD Z0 ZF ZP Z8 Z8R Z1 Z7` + `RN RC RB`. +⚠ Одиннадцать вариантов одной главы — много для одного читателя. Решать владельцу: либо две +сессии на главу с разными половинами панели и общим якорем (`Z0` в обеих), либо панель урезать. +Резать решением сессии ЗАПРЕЩЕНО заказом. + +**Команда после покупки:** +``` +eval/.venv/bin/python eval/dovodka/rol.py --emit-read en \ + --panel=ZD,Z0,ZF,ZP,Z8,Z8R,Z1,Z7,RN,RC,RB --tag=arch2 --each --n=16 +``` +и обязательная сплошная сверка сборки — образец кода в журнале §15.21 (128 текстов побайтно +против клеток, отсутствие имён армов в пакете, нейтральность рамки, разные раскладки). + + +--- + +## 8. РЕШЕНИЕ ВЛАДЕЛЬЦА 20.08: SOL ПАРКУЕТСЯ, СУДИТ ТОЛЬКО FABLE + +Дословно: «Давай пока забьем на сол и отдадим все судейство фаблу. Посмотрим что он скажет на +одинаковых главах и какое судейство проведет. А потом на сол если хватит времени посмотрим». + +**Что это меняет механически:** + +* **P42 снят с повестки, но НЕ закрыт.** Противоречие двух пре-рег-правил о маржевом гейте + существует и касается единственного CONFIRM фазы (H-2а). Оно перестаёт быть блокером, потому + что вердикт выносил СЧЁТЧИК, а счётчик понижен до брак-скрина. Когда дойдут руки до Sol — + вернуть в повестку. **Не считать «снят» синонимом «решён»: правило в пре-реге по-прежнему + противоречит само себе, и следующая сессия обязана это видеть.** +* **Норма П-1 «два судейских семейства» временно не исполняется, и это объявленное отступление, + а не забывчивость.** Все вердикты нового прибора выносятся ОДНИМ семейством (fable). Писать это + рядом с каждым выводом, как фаза писала «вывод стоит на одном семействе» по японской ноге. +* **Контроль вместо второго семейства — внутренний:** в каждой панели чтения стоят ДВЕ генерации + одной боевой связки (`Z0`/`ZF`). Читатель, который разводит их далеко, сам себя дисквалифицирует; + читатель, который ставит их рядом, доказал разрешение на этой панели. За четыре прогона Fable + трижды опознал эту пару чтением, НЕ будучи о ней предупреждён. +* **Что Sol всё ещё может дать, когда вернёмся:** он единственное независимое семейство, и его + расхождение с fable будет мерой «сколько в вердикте от прибора, а не от текста». Пока этой меры + нет вовсе — это главная незакрытая дыра нового прибора, и её надо называть вслух. + +**Куплено под расширенную панель (санкция «Нужны»):** `RN`/`RC`/`RB` на английской паре, +16 глав каждый, смета $1.05, касса ФД-N (потолок $2.40). Покупка шла с `TM_KEEP_TRACE=1`. + +--- + +## 9. ⛔⛔ ПОПРАВКА К СОБСТВЕННОМУ ВЫВОДУ, СДЕЛАННАЯ В КОНЦЕ СЕССИИ + +**Английская докупка опровергла вывод §2.1, и его надо читать вместе с этой поправкой.** + +``` + китайская пара английская пара +прежняя склейка 950 4 494 +промт-РОЛЬ (RN) 27 039 (×28) 5 804 (×1.29) +рез критериев (RC) 11 268 (×12) 3 586 (×0.80) +``` + +⇒ **Разгон размышления ×28 — свойство КИТАЙСКОГО МАТЕРИАЛА, а не промта.** На английском тот же +самый промт стоит на четверть дороже склейки, а резаный вариант — ДЕШЕВЛЕ неё. +⇒ Формулировка «промт, который просит творчества, оплачивается вторым черновиком» СНЯТА как общее +утверждение: она верна на плотном ханьском входе и не воспроизводится на английском. + +⚠ **И это было в нашем же вендор-бюллетене, прочитанном в тот же день:** `00-provider-quirks.md` +п.7 — «разгон думания привязан к ПЛОТНОМУ ХАНЬСКОМУ ВХОДУ, не к модели вообще: при сопоставимом +входе zh требует ×7.8 размышления против en». Я это читал и всё равно приписал эффект промту. +**Норма: эффект, замеренный на ОДНОЙ паре, не называть свойством промта, пока он не проверен на +второй. Пара — конфаундер по умолчанию, а не деталь.** + +**ЧТО ПРИ ЭТОМ УСТОЯЛО И СТАЛО ПРОЧНЕЕ.** Черновик в уме модель пишет на ОБЕИХ парах: +``` +китайская 93–97% финального текста написано внутри размышления (6 клеток) +английская 88–94% (47 клеток) +``` +Механизм подтверждён на 53 клетках. Само-проверки в трейсе 4%. ⇒ **размышление у dspro — черновик, +а не проверка**, это остаётся в силе. Меняется СЛЕДСТВИЕ: не «двухстадийность спасает от второго +черновика», а «второй черновик пишется всегда, но дорого обходится только на плотном входе». + +**ЧТО ЭТО МЕНЯЕТ ДЛЯ ПРОДУКТА.** Возражение «однопроходка дорога» снимается ДЛЯ АНГЛИЙСКОЙ ПАРЫ — +той самой, где владелец при слепом чтении поставил однопроходку ПЕРВОЙ. На китайской возражение +остаётся. Вопрос переформулируется: не «однопроходка дорога вообще», а «однопроходка дорога на +плотном китайском» — и это вопрос к материалу, а не к архитектуре. + +--- + +## 10. ПОЛНЫЙ СПИСОК АРТЕФАКТОВ СЕССИИ (для компакта — что где лежит) + +**Код зоны, изменён/создан:** +``` +eval/dovodka/rol.py однопроходка как РОЛЬ: сборка промта, гейт покрытия + боевых правил, покупка, эмиттер слепого чтения +eval/dovodka/prompts/onepass-core.md ядро нового промта (пар-блоки подтягиваются из пары) +eval/dovodka/podacha.py замер подачи входа критику + restore_paragraphs +eval/dovodka/itog_d4.py + режим --sens (Г5) и рычаги --drop/--restore +eval/dovodka/money_d.py + фазы ФД-K/L/M/N, пакетный потолок 13.80 → 18.30 +eval/dovodka/runs.py кап 100 → 200 (слово владельца 20.08) +eval/role_topology/buy.py + сохранение трейса по флагу TM_KEEP_TRACE=1 +eval/dovodka/HANDOFF-21-08.md этот файл +eval/dovodka/КОНТЕКСТ-ДЛЯ-КОНСУЛЬТАЦИИ.md картина проекта для внешнего собеседника +eval/dovodka/PLAN-16-08.md + баннер «закрыт» +eval/dovodka/PLAN-17-08.md + шапка состояния на 20.08 +eval/dovodka/SESSION2-LOG.md + разделы 15.1–15.22 +docs/experiments/23-editor-tier.md + секции Д21 (Г5) и Д23 (пре-рег промта-роли) +``` + +**Сырьё:** +``` +~/books/dovodka/dv-k-*, dv-l-* замер подачи критику (zh куплен частично, en не начинался) +~/books/dovodka/dv-m-* промт-роль на китайской паре (RN/RC/RB) +~/books/dovodka/dv-n-* промт-роль на английской паре (RN/RC/RB, 47 клеток) +~/books/dovodka/podacha-plants.json правильные ответы посадок +~/books/dovodka/blind-keys-rol/ ключи всех панелей чтения + READERS-*.json +~/books/chtenie-rol/ЧТЕНИЕ-en-arch2-*.md 15 готовых пакетов +~/books/chtenie-vladeltsa-z17/ОТВЕТ-*.FABLE.md, *.ЧИСТЫЙ.md калибровка прибора +~/books/chtenie-vladeltsa-z17/ЧТЕНИЕ-*.НЕЙТРАЛЬНЫЙ.md пакеты без подсказки +``` + +**Коммиты сессии (полигон коммитит только фризы покупающего кода):** +`650b4fe` `824bd20` `273d2df` `7f15323` `3075566` `276c85b` `db02b7a` и фикс пропуска глав. + +**Деньги на конец сессии:** +``` +ФД-K 0.2927/0.40 · ФД-L 0.0000/1.30 · ФД-M 2.0066/2.10 · ФД-N 1.3593/2.40 +ПАК 15.38/18.30 · свободно 2.92 +``` diff --git a/eval/dovodka/PLAN-16-08.md b/eval/dovodka/PLAN-16-08.md new file mode 100644 index 00000000..47c26073 --- /dev/null +++ b/eval/dovodka/PLAN-16-08.md @@ -0,0 +1,566 @@ +> ⚠ **ЗАКРЫТ И ВЫПОЛНЕН.** Это ПРОШЛЫЙ курс (16.08); он привёл к заходу Д17 и +> исчерпан им. Живой курс — `PLAN-17-08.md`, состояние на сегодня — `HANDOFF-21-08.md`. +> Читать целиком НЕ надо: здесь только исторические основания решений захода Д17. + +# ПРОМТ-ПЛАН полигон-сессии: фаза Д, продолжение после сессии №2 (16.08) + +> Пишется САМОМУ СЕБЕ на случай сжатия контекста и передачи. Читать ЦЕЛИКОМ, не грепом: +> прошлая передача потеряла целый пункт заказа именно из-за чтения грепом. +> Предыдущие слои: заказ `docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md` (207 строк, читать целиком) · +> хендофф №1 `docs/POLYGON_PHASE_D_HANDOFF.md` · рабочий журнал `eval/dovodka/SESSION2-LOG.md` (рядом). +> Отчёт фазы — `docs/experiments/23-editor-tier.md`, секции Д0–Д16 (~2500 строк). + +--- + +## 0. ГЛАВНОЕ В ОДНОМ АБЗАЦЕ + +Дуга экспериментов 19→23 честно ответила на вопрос «какая модель лучше в роли редактора» и +устойчиво его закрыла. Но она мерила **счёт локальных ошибок**, а продуктовая цель владельца — +**живая русская проза без translationese**, и под неё прибора нет. 16.08 слепое чтение показало, +что на английской оси порядок по счёту ошибок и порядок по читаемости **не связаны вовсе** +(Спирмен −0.10), причём наш прибор штрафовал ровно то, что покупает читаемость — вольность +обращения с буквой. Владелец постановил: **штрафовать можно только за перевирание смысла, за +вольность — нельзя**. Это меняет шкалу, и старые судейские числа с новыми сравнивать будет нельзя. +⇒ Следующий шаг — не новые модели, а **починка прибора**, затем маленький честный заход. + +### 0.1 Словарь — расшифровать до чтения дальше + +* **Боевая связка** (то, что стоит в продакшене): дешёвая модель `deepseek-v4-flash` переводит + главу начерно → дорогая `deepseek-v4-pro` получает исходник ПЛЮС черновик и **переписывает всё + заново**. Два вызова. В таблицах обозначена `A0` (китайская ось), `E0` (английская), `J0` (японская). +* **Однопроходка**: ОДИН вызов дорогой модели, которой дают исходник и **объединённую инструкцию** — + переводчика плюс редактора. Ни черновика, ни второго прохода. Обозначена `A2` / `E2` / `J2`. +* **Точечный контур**: черновик + нечто, что находит места ошибок (детерминированные флаги ЛИБО + LLM-критик) + фиксер, правящий ТОЛЬКО указанные места. Обозначен `A1`/`A1B` (флаги) и `A3` (критик). + Трогает единицы процентов текста, остальное остаётся черновиком — в этом его слабость. +* **Канон-фиксер ПОСЛЕ**: боевая связка плюс отдельный проход, восстанавливающий термины банка + (`A4` / `E4`). Единственный арм, чинящий канон, не двигая судейскую ось; цена аддитивна. +* **Гипотезы владельца:** H-1 «проблема в черновике» (хороший черновик + точечный редактор не хуже + связки) · H-2а «флаги → фиксер не хуже переписа» · H-2б «смысловой критик → фиксер ЛУЧШЕ переписа» + (главная ставка) · H-3 «на английском перепис не нужен» · H-4 «перевес dspro — свойство китайской пары». +* **Пол (шумовой)**: пара из двух генераций ОДНОГО лечения. Разница их оценок = шум прибора, + из неё строится порог различимости. Половины обязаны судиться РАЗНЫМИ сессиями. +* **Декой**: намеренно испорченный вариант в пачке. Грубый — ловит «судья вообще смотрит?». + Маржевый — тонкая порча размером с искомый эффект, ловит «судья способен увидеть разницу такого + размера?». Без второго вывод «не различимо» неотличим от слепоты прибора. + +--- + +## 1. РЕШЕНИЯ ВЛАДЕЛЬЦА — дословно и с последствиями + +| дата | что сказал | что это значит механически | +|---|---|---| +| 15.08 | «Подтверждаю подъём расходов, сколько тебе нужно» | потолок записан числом **$6.85** в `money_d.py`; на 16.08 потрачено ~$6.09 | +| 15.08 | про кап агент-сессий: «почему оставшиеся? ты можешь наспамить под 90» | кап 80 снят его словом; `runs.py` продолжает считать СУДЕЙСКИЕ сессии (на 16.08 — 72) | +| 15.08 | «Можно пересудить [tier]. Но не подгонять. Нужно понять, кто ошибается из судей» | исполнено, см. §3.2 | +| 15.08 | «gpt 5.6 sol это и есть модель» | **Sol = семейство OpenAI** ⇒ его возражение по `tier` дисквалифицировано (судил свою семью) | +| 15.08 | про японскую ногу: «Бери, ладно» | куплено и отсужено, см. §3.3 | +| 15.08 | про ja-книгу: «не помню; замена была потому что флагнули по 18+; кажется, я аппрувнул выкачивание» | дословной санкции на ЗАМЕНУ нет; `R37` реестра честно красный; в отчёте писать этой формулировкой, НЕ «по слову владельца» | +| 16.08 | «Штрафовать за вольность нельзя — живой язык один из приоритетов бэкенда. Штрафовать можно только за перевирания смысла исходника. В остальном можно менять, убирать англоязычность, переставлять текст» | **МЕНЯЕТ РУБРИКУ СУДЬИ.** См. §4.1 — это первый пункт плана | +| 16.08 | «Новые траты я разрешаю, перепровести эксп я разрешаю» | санкция на заход §4.3 | +| 16.08 | про цены DeepSeek: «мы с оркестратором проводим пересчёт его цен и внесём в документацию, после я тебе скажу (или ты мне напомни)» | **НАПОМИНАТЬ.** См. §5.1 — это блокер денежных выводов | + +--- + +## 2. ЧТО УЖЕ ПОСТРОЕНО И РАБОТАЕТ (не переделывать) + +Всё в зоне `eval/dovodka/`, все селфтесты зелёные, линтер чист. + +| файл | что делает | команды | +|---|---|---| +| `gain.py` | **калибровка усиления судьи** — ловит пачку, легшую на пол шкалы | `--density` (гейт: нулей ≥25% → пачка не несёт «не различимо») · `--samearm` · `--agree` · `--floor` · `--tier` · `--selftest` | +| `naklon.py` | **гейт позиционного наклона** (норма требовала, кода не было) | без флагов — все 7 проходов; `--pass=d4`; `--selftest` (синтетика с известным ответом) | +| `tier2.py` | пере-судейство прохода `tier` починенным заданием | `--emit` · `--score` · `--selftest` | +| `ja6.py` | японская нога H-4: панель `J0`/`J6`/`D0` + контроли; ⚠ `read()` чинён 16.08 — считает и разведённые `p{1,2}-answers` своего семейства, и ПЛОСКИЙ `answers/` внешнего пакета | `--emit` · `--score` · `--sol` (пакет внешнему судье) · `--score-sol` · `--selftest` | +| `chtenie.py` | **прибор ткани**: слепое чтение человеком | `--emit` · `--score` | +| `adjud.py` | адъюдикация находок; **контроли починены** (4 дефекта) | `--emit` · `--controls` (гейт неразличимости классов) · `--score` · `--selftest` | +| `itog_d4.py` | свод оси; чинён: считает ОБА семейства, печатает **полную цену единицы** | `--axis=d4\|d3\|d6\|d1` `--fam=claude\|sol` `--gates` | +| `sud.py` | судейский риг; селфтест **перестал быть прибит к китайской панели** | `--axis=<ось> --selftest` | +| `money_d.py` | касса, потолок $6.85, фазы ФД-A…ФД-H | `--report` · `--selftest` | +| `runs.py` | журнал судейских сессий, запись ДО запуска | `--claim <прогон> <проход> <ток>…` · `--done ` · `--status` | +| `conformance.py` (в `eval/`) | сверка с буквой заказа; **починен парсер** | `eval/conformance.py eval/dovodka/requirements.md --plan` | + +**Три коммита-фриза сессии №2** (полигон коммитит только пре-рег-фризы, основание вслух ПЕРЕД +каждым): `a03ac66` (пре-рег `tier2` + `gain.py`) · `8c68828` (покупающий код ja-ноги) · +`22a8a6b` (покупающий код пола ja-ноги + журнал). Остальное дерево НЕ коммичено — лендит оркестратор. + +--- + +## 3. ЧТО УСТАНОВЛЕНО — с числами, которые нельзя потерять + +### 3.1 Прибор: строгость счёта есть свойство ПРОГОНА, а не оси + +Тот же арм `R0`, те же 16 единиц, то же семейство судей, подписи текста совпадают **16/16**: +проход `tier` дал **0.69** ошибки на единицу при 9 нулях из 16, проход `border` — **4.31** при нуле +нулей; по-единично ниже в 15 из 16. Размах внутри семейства claude 1.41…6.47. + +⇒ **межпроходные сравнения абсолютных чисел недействительны.** Под это попадает вывод «дешёвая +модель на японском проваливается сильнее других пар» (5.44 против 2.78 — разные прогоны) и все +заимствованные пороги. Внутри одной пачки сравнения законны — там сдвиг сокращается в контрасте. + +Доля нулей по проходам claude: `tier` 38% (24% без обоснования) · Д3 en 10% · Д6 3% · +`border` 0 · **Д4 zh 2 нуля на 713 клеток** · Д1 0. По сессиям выбиваются ровно две +(`d3r2/p1/д-52` 24%, `d3r2/p2/д-55` 21%). ⇒ выводы Д4/Д1/`border` этой болезнью НЕ заражены. + +### 3.2 Проход `tier` пере-сужен — вывод пака 23 восстановлен + +Пре-рег зафризен `a03ac66` ДО первого ответа. Те же тексты, новый ключ со своей солью, старый не +тронут. Правки: убран `CTRLfloorA` (был побайтно равен боевому арму `T1` в 16/16), добавлен +маржевый декой, запрещён молчаливый ноль, в рубрику внесены пропускавшиеся классы. + +Результат на 16 единицах, 126 клеток, замечаний годности 0: + +``` +арм старый новый сдвиг гейты +R0 боевой 0.69 3.44 +2.75 плотность 3.77, нулей 6% (было 38%) — ГОДНО +T1 glm-5.2 1.00 3.56 +2.56 пол 16 пар sd 2.63 → порог 1.84 +T2 gpt-5.6-terra 0.25 2.38 +2.12 грубый декой 16/16 +T3 grok-4.5 0.62 2.94 +2.31 МАРЖЕВЫЙ ДЕКОЙ +2.50 против 1.84 — ПРОЙДЕН +F голый черновик 2.56 6.56 +4.00 +``` + +Вердикт: `T1` −0.12 · `T2` **+1.06** · `T3` +0.50 — все ниже порога 1.84; контроль `R0 vs F` +−3.12 p=0.0042. ⇒ **«сильный тир не отличим от боевого редактора» ВОССТАНОВЛЕН и впервые стоит +на приборе с доказанной чувствительностью.** Возражение Sol (+8.69) не воспроизводится (+1.06) +и вдобавок дисквалифицировано: `gpt-5.6-terra` — семейство самого Sol. + +⚠ Пере-судейство сделано ОДНИМ семейством: второе на этом контрасте дисквалифицировано. + +### 3.3 Японская нога H-4 — куплена, отсужена, гейты зелёные + +`J6` = `glm-5` поверх той же японской базы, 9 клеток, $0.046329, все `finish=stop`. Плюс свой пол +(7 из 9 вторых генераций редактора; один вызов завис на 9+ минут — класс известен). + +``` +J0 deepseek-v4-pro 1.44 J6 vs J0 −0.44 p=0.6875 ниже порога 2.17 +JFLO вторая генерация 1.86 J0 vs D0 +2.78 p=0.0039 редактор бьёт черновик +J6 glm-5 1.89 грубый декой +2.89 ПРОЙДЕН · маржевый +2.80 ПРОЙДЕН +D0 черновик 4.22 +``` + +**Разница между ДВУМЯ редакторами (0.44) не больше разницы между двумя прогонами ОДНОГО +редактора (0.42).** Это самый чистый способ сказать «не различимо». + +**Второе семейство прогнано владельцем 16.08 (`ja6.py --score-sol`) и СВОИ КОНТРОЛИ НЕ ВЗЯЛО:** +плотность 5.06 (нулей 6%) · пол 7 пар sd 2.64 → порог 2.80 · **грубый декой +2.22 против 2.80 — +НЕ ПРОЙДЕН · маржевый +1.80 — НЕ ПРОЙДЕН**. Декой пойман ПО НАПРАВЛЕНИЮ, но собственный шум Sol +так широк, что даже намеренная грубая порча в его порог не укладывается ⇒ **на японской оси у Sol +нет разрешения**, его пачка по норме аннулируется. Направление при этом совпало: `J6/J0` −1.00 +(мои −0.44), `J0/D0` +4.00 (мои +2.78). Вывод по H-4 не меняется, но опирается на ОДНО семейство. +Третий раз за фазу пол Sol оказывается вдвое шире — свойство его харнесса, а не оси. + +⇒ **H-4 не подтверждается на всех трёх парах**: `deepseek-v4-pro` первый на zh, на en (1.31 +против 2.72) и на ja (1.44 против 1.89). Порядок редакторов от языка не зависит. Требование +заказа (:115) исполнено ВПЕРВЫЕ. + +### 3.4 Разложение контрастов ПО ОСЯМ — за всю дугу не делалось ни разу + +``` +A1B/A0 −3.13 = ВЕРНОСТЬ −0.94 + ЯЗЫК −2.19 +A3/A0 −1.87 = ВЕРНОСТЬ −0.26 + ЯЗЫК −1.61 ← ставка владельца +A6/A0 −1.06 = ВЕРНОСТЬ +0.12 + ЯЗЫК −1.19 ← носитель H-1 +A4/A0 −0.13 +E0/D0 +1.25 = ВЕРНОСТЬ +0.94 + ЯЗЫК +0.31 + ТЕРМИН +0.75 + ФОРМА +0.69 +J0/J2 +0.11 = ВЕРНОСТЬ 0.00 + ЯЗЫК +0.11 +``` + +⇒ дешёвые контуры проигрывают переписи **прозой, а не смыслом**; носитель H-1 по смыслу +боевой связке не уступает вовсе; на английском редактор покупает в основном термины, верность и +вёрстку — то есть H-3 опровергнута только в слове «ТОЛЬКО»; на японском второй проход по смыслу +не покупает ничего. + +### 3.5 Экономика — полная цена ЕДИНИЦЫ, а не вызова + +``` +zh: A2 однопроходка 0.00408 · A0 связка 0.00603 · A1B 0.00793 · A4 0.01419 · A3 0.01546 (2.56×A0) +en: E2 однопроходка 0.00471 · E0 связка 0.00885 · E4 0.01321 +черновик flash 0.00096 · перепис dspro 0.00507 · gpt-5.6-terra 0.04408 (×9) · grok-4.5 0.05276 +``` + +⇒ «кандидат в прод вдвое дешевле» ЛОЖНО — он в 1.5–2.4 раза **дороже**; это платная страховка +канона. Ставка H-2б стоит 2.56× боевой связки и по счёту ошибок хуже неё. + +### 3.6 ⛔ СЛЕПОЕ ЧТЕНИЕ 16.08 — ПЕРЕВОРАЧИВАЕТ АНГЛИЙСКУЮ ОСЬ + +Пакет `~/books/chtenie-vladeltsa/` (`ЧТЕНИЕ-zh.md`, `ЧТЕНИЕ-en.md`), **ответы там же**: +`ОТВЕТ-zh.md` и `ОТВЕТ-en.md` — это улики, их не терять. Ключ отдельно в +`~/books/dovodka/blind-keys-chtenie/chtenie-KEY.json`. Пере-счёт: `chtenie.py --score`. +Читал не владелец лично, а **Fable по его поручению**; владелец мнение одобрил и сам отметил, +что «нашёл меньше замечаний, перевод читаемый». + +| ось | порядок судьи (счёт ошибок) | порядок читателя | Спирмен | +|---|---|---|---| +| **zh** (единица `ed068182cf`) | `A0` > `A4` > `A6` > `A3` > черновик | `A4` > `A0` > `A6` > черновик > **`A3`** | **+0.80** | +| **en** (единица `27cb3a7e69`) | `E0` > `E3` > `E2` > черновик > `E6` | **`E2`** > `E6` > `E0` > `E3` > черновик | **−0.10** | + +* На китайском прокси держится; перестановка `A0`/`A4` внутри шума. Но читатель поставил `A3` + (смысловой контур, ставка владельца) **последним, ниже голого черновика**. +* На английском связи нет вовсе. **Однопроходка `E2` — первая у читателя, третья у судьи; + наша боевая связка `E0` — третья у читателя, первая у судьи.** +* ⚠ **Читатель назвал механизм ВСЛЕПУЮ и попал:** «у Т3 локальных отклонений от оригинала больше, + чем у Т2, а читать его заметно лучше. Счётчик ошибок должен был поставить Т3 ниже Т2 — если так + и вышло, порядок разошёлся не случайно, а системно». Т3 = `E2`, Т2 = `E0`. Так и вышло. + +⚠ Одна единица на ось, английская всего 1.9 тыс. знаков, читатель — модель. Это **калибровка, +а не замер**: она говорит «прибор и читатель расходятся», а НЕ «однопроходка лучше связки». + +### 3.6а Черновая роль: панель мерялась, победила ценой + +Эксп-22 мерил ШЕСТЬ кандидатов черновой роли: `deepseek-v4-flash` (медиана $0.00096) · +`gemini-3.1-flash-lite` ($0.00385) · `deepseek-v4-pro` ($0.00387) · `gpt-5.6-luna` ($0.00357) · +`glm-4.7` ($0.00656) · `grok-4.3` ($0.00976). Вердикт — **ничья по качеству, побеждает самый +дешёвый** (правило D39.117). То есть «поставить дипсик-про черновиком» УЖЕ проверено и не выиграло. +⇒ Низкое качество черновика — свойство не модели, а РОЛИ (и, возможно, промта — см. `K8` в §4.3). + +Двухступенчатость бьёт однопроходку на всех трёх парах ПО СЧЁТУ ОШИБОК: zh 4.00 против 6.51 · +en 1.31 против 2.38 · ja 2.22 против 2.39. ⚠ Но на английском слепое чтение дало обратный порядок +(§3.6), и это расхождение не разрешено. + +### 3.7 Что уцелевает независимо от смены шкалы + +1. Редактор поверх дешёвого черновика покупает много — три языка, два семейства судей, слепое + чтение (черновик внизу везде). +2. Дорогие редакторы не бьют `deepseek-v4-pro` при цене ×9 — на приборе с сертификатом. +3. Выбор редактора от языка не зависит. +4. Банк терминов работает; **банк-дисциплина и качество прозы — разные умения** (gemini: канон + 0.986 против 0.884 у боевого при равной судейской оси). +5. Схемы «залатать черновик точечно» проиграли и по счёту, и по чтению. ⚠ Но точечный ремонт + **не выброшен ратификацией**: D39.117 п.3 отклонил его как ЗАМЕНУ редактора и принял как + «механический ПРЕ-ГЕЙТ ПЕРЕД редактором» и «ремонт ТОЛЬКО с банком». Число «$0.0002 чинит всё» + из эксп-20 при этом мерилось при ИДЕАЛЬНОЙ детекции и как несущее — пало; в фазе Д оно всё ещё + служило основанием армов и прогнозов. Не переиспользовать без оговорки. +6. Гипотеза эксп-04 «проза gemini — аутлаер» не подтверждается (после снятия запрещённой клетки + с `finish=length` точечная оценка ровно 0.000). +7. Потери канона у боевого редактора — в основном пере-формулировка, а не пропажа термина + (⚠ но автоматическая КЛАССИФИКАЦИЯ этих мест негодна — секция Д5 отчёта объявлена + НЕИСПОЛНЕННОЙ, см. §5.3). + +--- + +## 4. ПЛАН. Порядок здесь важнее содержания + +### 4.1 ШАГ ПЕРВЫЙ — ПОЧИНИТЬ СУДЬЮ ПОД ПРАВИЛО ВЛАДЕЛЬЦА ($0) + +Правило 16.08: **штрафовать только за перевирание смысла исходника; за вольность — нельзя.** +Живой язык — приоритет продукта; переставлять текст, убирать англоязычность, менять структуру +разрешено. + +⚠ **Рубрика живёт в ЧЕТЫРЁХ местах, править надо все, иначе семейства разъедутся:** +`eval/dovodka/judge-prompts/core.md` (ядро обвязки) · `claude.md` и `sol.md` (обвязки семейств) · +константы `HEAD` в эмиттерах `sud.py`, `tier2.py`, `ja6.py` (шапка САМОГО задания — именно она +достаётся обоим семействам и обеспечивает паритет П-4). После правки прогнать `paritet.py` +и `promptdiff.py`. + +Что сделать в рубрике: + +* **ВЕРНОСТЬ остаётся и сужается до искажений факта:** инверсия адресата реплики · снятое или + добавленное отрицание · подмена числа, разряда, ранга, единицы · выдуманный или потерянный факт · + перевёрнутое состояние действия/идиомы. Это и есть «перевирание смысла». +* **ЯЗЫК разворачивается:** сейчас он ловит «то, чего носитель не напишет». Оставить ТОЛЬКО это + (калька, канцелярит, несуществующее слово, рассогласование) и **явно запретить штрафовать за**: + перестановку предложений, слияние/дробление абзацев, замену оборота на более живой русский, + синонимическую замену, добавление связок. Прямо написать судье: «вольность ради живой русской + фразы — НЕ ошибка; ошибка — только то, что носитель не напишет». +* **ФОРМА**: сейчас не входит в несущую сумму. Проверить, не штрафует ли она ИСПОЛНЕНИЕ мандата + перевёрстки (такое уже переворачивало знак вывода однажды). +* **ТЕРМИН** отдан детерминированному банк-гейту, судью им не грузить. + +⚠ **Последствие, которое обязано быть напечатано:** после смены рубрики старые судейские числа с +новыми **несравнимы**. Все панели дуги останутся действительными только внутри своей шкалы. + +⚠ Пере-судить по новой рубрике придётся хотя бы **боевую связку против однопроходки** на обеих +осях — иначе новую шкалу не с чем сверить. + +⚠⚠ **ПОРЯДОК ИЗМЕНЁН ПО ЗАМЕЧАНИЮ ВЛАДЕЛЬЦА 16.08.** Первая редакция ставила чтение ПЕРЕД +покупкой новых армов. Это половина работы впустую: читать старые армы, зная, что панель меняется, +незачем. Правильный порядок — **починить рубрику → докупить новые армы → ОДНО слепое чтение по +ПОЛНОЙ панели (старые + новые) → отсудить починенной рубрикой.** Тогда чтение делается один раз и +отвечает сразу на оба вопроса: следит ли счётчик за читаемостью И какая архитектура читается лучше. +Читай §4.2 и §4.3 в этом порядке: сначала 4.3 (покупка), потом 4.2 (чтение по всему). + +### 4.2 ШАГ ТРЕТИЙ — ПРИБОР ТКАНИ НА НОРМАЛЬНОМ n ($0, только агент-сессии) + +Одна единица на ось ничего не решает. Нужно слепое ранжирование на **8–12 единицах на ось**, +двумя-тремя независимыми читателями (агенты РАЗНЫХ семейств: claude, Sol через владельца, Fable). + +Контроли (они уже описаны в пре-реге П-6, `23-editor-tier.md:1114-1122`): +* декой обязан ранжироваться последним; +* половины шумового пола — соседями (если читатель разводит две генерации одного лечения далеко, + его ранжирование — шум); +* порядок вариантов перемешан, метки слепые, ключ вне рабочего каталога. + +Готовый скелет — `chtenie.py`; нужно расширить с одной единицы на выборку и добавить контроли. + +**Исход решает всё дальнейшее:** ранги согласуются со счётом ошибок → счёт остаётся дешёвым +прокси; расходятся (как сейчас на en) → **до починки прибора новые платные замеры бессмысленны**. + +### 4.3 ШАГ ВТОРОЙ — МАЛЕНЬКИЙ ЧЕСТНЫЙ ЗАХОД (санкция владельца 16.08 есть) + +⚠ **Смета по НОВОМУ прайсу: ~$2.9 на пике, ~$1.7 на офф-пике** (прежняя оценка «около доллара» +считалась по июльскому пину). Покупать офф-пик; перед покупкой пере-пинить `roster.py` (§5.1). + +Три схемы, обе пары (zh и en), 16 единиц на пару, базы частично куплены: + +1. **боевая связка** — контроль (черновик → полный перепис); +2. **однопроходка** — один вызов, объединённый мандат (на en она прочиталась лучшей); +3. **черновик → критик → ПОЛНЫЙ ПЕРЕПИС по его замечаниям** (`K1`) — сильнейшая форма идеи + владельца. ⚠ Отличие от проигравшего `A3`: там правщик был ТОЧЕЧНЫМ и трогал проценты текста; + здесь перепис полный, а буллеты критика лишь ОБУСЛОВЛИВАЮТ его. + +Эндпойнты — **оба сразу**: починенный счёт ошибок (§4.1) И ранг читаемости (§4.2). Пре-рег до +покупок: мощность, пороги, правило расхождения, статусы осей. + +⛔⛔ **ОДНОПРОХОДКА ЛОМАЕТ БАНК ТЕРМИНОВ — блокер, найден владельцем 16.08, проверен исполнением.** +`backend/prompts/zh-ru/terminologist.md:22`: «Варианты черновиков (строка `drafts:`) — это +свидетельства, а не голосование». Терминолог видит всю книгу сразу и выбирает канонический перевод +термина, опираясь на то, КАК ЕГО ПЕРЕДАВАЛИ ЧЕРНОВИКИ по кускам. Без черновика майнингу не из чего +собирать свидетельства. +⇒ **Однопроходка не альтернатива связке, а альтернатива только ВТОРОМУ проходу.** Черновик нужен +не только ради качества, но и ради банка. Честное сравнение — не «связка против однопроходки», а +«черновик + перепис» против «черновик + нечто другое поверх него». +⇒ И это объясняет, почему слепое чтение поставило однопроходку первой, а продуктовым решением это +не становится: на ОДНОЙ главе банк не нужен — термины внутри главы согласованы сами собой; на книге +в 1500 глав та же схема даст разъезжающиеся имена. +⚠ **ПОПРАВКА ВЛАДЕЛЬЦА 16.08 — это НЕ блокер, а недостающий кусок архитектуры.** Дословно: +«однопроходка допустима, как вариант чтоб оттуда после вырезать термины и edit точечным +редактором их заменить». Выход однопроходки — тоже перевод, значит он годится терминологу как +свидетельство; банк майнится ИЗ НЕГО, а термины приводятся к канону точечным редактором. +⇒ **Кандидат `K7`: однопроходка → майнинг банка из её выхода → канон-фиксер.** Точечный редактор +здесь адекватен дефекту: термины локальны, а канон-фиксер УЖЕ замерен и работает (покрытие +0.892→0.937 на zh, 0.927→0.956 на en; судейскую ось не двигает). +Цена единицы: zh 0.00408 + 0.00816 = **~0.0122** против 0.00603 у связки (дороже); +en 0.00471 + 0.00436 = **~0.0091** против 0.00885 (вровень). ⇒ на английском `K7` стоит столько же, +сколько связка, и по слепому чтению однопроходка там читалась ЛУЧШЕ — это самый дешёвый способ +проверить, не выиграла ли она случайно. +⇒ Что при этом остаётся правдой: **банк без черновика не собирается**, поэтому однопроходка не +может быть просто «связкой минус первый проход» — ей нужен свой источник свидетельств. + +⇒ **Кандидат `K8`, самый дешёвый из всех: ОБОГАТИТЬ ПРОМТ ЧЕРНОВИКА.** Вопрос владельца 16.08: +«почему такое низкое качество черновика? может, промт черновика от редактора сильно отличается и +черновик тут выступает в роли недоведённого до ума инструмента?» — **подтверждено текстом**: +`backend/prompts/zh-ru/translator.md` = 22 строки, `editor.md` = 41, и разница не в объёме, а в +том, что есть ТОЛЬКО у редактора: весь блок ДИСКУРС-ПЕРЕВЁРСТКИ (четыре шага, как передавать +китайский паратаксис русской гипотаксической прозой) + FEWSHOT с ТРЕМЯ разобранными примерами + +правило чэнъюй обоими компонентами. У переводчика от всего этого одна строка «избегай канцелярита +и калек». +⇒ Замер: тот же черновик тем же `deepseek-v4-flash`, но с промтом, куда перенесены блок +перевёрстки и FEWSHOT. Цена — ЦЕНА ЧЕРНОВИКА ($0.00096/единица, входные токены длиннее и +кэшируются), то есть ~$0.03 на 16 единиц. **Самый дешёвый кандидат в списке и не проверенный ни разу.** +⚠ Контраргумент, который надо снять замером, а не рассуждением: арм `A2` (однопроходка с +УРАВНЕННЫМ мандатом) уже нёс мандатные части промта редактора и всё равно проиграл по счёту +ошибок — **но перенесён ли в него FEWSHOT, из эрраты Э-5 не следует**. Сверить ДО замера: если +примеров там не было, `K8` не дублирует `A2`, а закрывает его дыру. + +⚠ Обязательно в панель — **арм голого черновика на китайской оси**: его там нет, и поэтому вклад +самого контура не отделим от разрыва «черновик против переписа». + +⚠ Если запускать `K1`: сначала починить `contour.py:611-619` — `critic_places()` берёт ЛЮБУЮ +строку, начинающуюся с дефиса, без разбора вердикта, из-за чего фиксеру уходили **подтверждения** +критика («верно», «допустимо», «не ошибка») — 311 строк из 1696 = **18.3%**. Ставка владельца была +испытана инструментом, который на пятой части работы правил заведомо исправное. + +### 4.4 ЧЕГО НЕ ДЕЛАТЬ СЕЙЧАС + +* Новых панелей моделей — вопрос «какой редактор» закрыт устойчиво. +* Роутинга «платить дорого только там, где нужно» — закрыт отрицательно (отборщики-монетки). +* Второго последовательного переписа — итеративный дрейф, и обратная связка уже проигрывала. +* Дифф-контракта вместо переписа — дороже на thinking-модели и не берёт распределённый дефект. +* Любых покупок DeepSeek до пере-снятия прайса (§5.1). + +--- + +## 5. ДОЛГИ И ОТКРЫТОЕ + +### 5.1 ⛔ ДЕНЬГИ — ЛЕДЖЕР НЕ ИЗМЕРЯЕТ, А ВЫЧИСЛЯЕТ (блокер) + +`roster.cost()` ВЫЧИСЛЯЕТ `cost_usd` из зашитого пина; провайдер сумму не возвращает. + +**СТАТУС НА 16.08: цены пере-сняты владельцем с оркестратором — но ТОЛЬКО в бэкенде.** +`backend/configs/models.yaml` несёт новый пик (`prices_checked: 2026-08-15`): flash $0.44/$1.32 +cache-hit $0.014 · pro $1.32/$3.96 cache-hit $0.044. +⛔ **`eval/tenant_panel/roster.py:33-35` ПО-ПРЕЖНЕМУ СТАРЫЙ** (pro 0.435/0.003625/0.87, помечен +«live 08.08»). Значит СЛЕДУЮЩАЯ покупка полигона снова посчитается по июльскому прайсу: касса +покажет неправду, а проекционный гард зарезервирует втрое меньше нужного и пропустит покупку. +⇒ **ПЕРВОЕ ДЕЙСТВИЕ ПЕРЕД ЛЮБОЙ ПОКУПКОЙ — пере-пинить `roster.py`.** Это риг эксп-22 (чужой пак): +по норме — отдельный коммит с его заголовком плюс пере-снятие его гейтов (`verify22.py`, `itog22.py`). + +**ПЕРЕ-СЧЁТ ФАЗЫ ПО НОВОМУ ПРАЙСУ (сделан 16.08, 473 клетки DeepSeek: pro 430, flash 43):** + +| счёт | сумма | +|---|---| +| в кассе (июльский пин), часть DeepSeek | **$5.3996** | +| та же работа по **пиковому** прайсу | **$15.5050** — ×2.87 | +| та же работа по **офф-пику** (50%) | ~$8.95 | +| не-DeepSeek клетки (не пере-считывались) | $2.3852 | + +Деньги УЖЕ списаны по ценам, действовавшим в момент покупки, — там пин был верен, перерасхода нет. +Число важно для БУДУЩЕГО, и оно двигает три вещи: +1. смета заходов утраивается (см. §4.3); +2. потолок $6.85 в кассе больше ничего не охраняет, пока `roster.py` не пере-пинен; +3. ⚠ **правило ничьей может пере-решиться.** Оно звучит «при равном качестве берём дешёвого», и по + нему `deepseek-v4-pro` побеждал `glm-5` и сильный тир при разрыве ×9. Теперь `pro` подорожал + втрое, а `glm-5` (Z.AI) не дорожал — разрыв сжался. На этом правиле стоит вся рекомендация по + редакторской роли; пере-считать её ценой ОБЯЗАТЕЛЬНО. + +Отдельная девиация к объявлению: последняя цифра потолка, приписанная слову владельца в файлах, +была $4.50, а расход дошёл до $6.00 ДО санкции 15.08. Цепь подъёмов прошла все самопроверки, +потому что **фриз-гейт сверяет ПОКУПАЮЩИЙ файл в стеке, а не кассу** — потолки правятся в рабочем +дереве и действуют немедленно. + +### 5.2 ⛔ СВЕРКА ВНУТРЕННЕЙ РЕВИЗИИ: 65 находок из 131 в отчёт не попали + +Источники: `~/books/dovodka/revizia-fazy-D-11-08.json` (82) и `priemka-D4-14-08.json` (49). +Требуют пере-проверки исполнением — я их принял со слов сверки, сам не верифицировал: + +1. **H-1 МОЖЕТ ПЕРЕХОДИТЬ ПОРОГ.** Вердикт `A6/A0` определяют две пачки прогона, объявленного + «валидацией цепочки», судимого 11.08 по НЕ замороженному промту; паритет обвязок для них не + проверялся. Без них: пол n=13 sd 1.4058 → порог 1.0917, `A6/A0` −1.0938 → **ПЕРЕШЁЛ**. + Отчёт печатает «НЕ УСТАНОВЛЕНА». Вместе с §3.4 (`A6` по верности +0.12) картина другая. +2. Пачка `69de717f3f` осталась в замере, хотя её сессия **аннулирована за сравнение вариантов**. + Снятие двигает пороги обоих семейств и все контрасты. +3. Унаследованный гейт честности пола свод не гоняет; на данных claude его вердикт — + «ПОЛ СМЕЩЁН, боевые числа снимаются» (сдвиг +1.8 между наборами p1/p2). claude несёт оба CONFIRM. +4. Два пре-рег-правила о маржевом гейте противоречат: по букве П-1 пачка Sol аннулируется → + у H-2а остаётся одно семейство → «эскалация», а не CONFIRM. +5. Порог назван смещённым в **противоположные стороны** двумя находками; ни одна не в отчёте ⇒ + калибровка порога неизвестна ПО ЗНАКУ. +6. Донор декоя во всех 62 пачках оси Д4 — `A0` (тот же дефект, что объявлен у `tier` и починен в `ja6`). +7. `A4`/`A1B`: клетки оплачены при НУЛЕВОМ изменении текста; `A4/A0` на 4 из 31 нулевой + ПО ПОСТРОЕНИЮ (то же вскрытие сделано для `E4`, для `A4` — нет). + +⚠ Среди неотражённого — **12 инструментов, признанных негодными ВНУТРИ самой ревизии**, и трое +из них гейты числят ЗЕЛЁНЫМИ: `promptdiff.py` (объявленное расхождение матчится по ПРЕФИКСУ строки; +отсутствующий файл пар-пакета гейт не роняет) · `canon.py` (снятая ревью классификация всё равно +пишется в артефакт — отсюда негодность секции Д5) · `material_ja.py` (фильтр AI-меток объявлен +механическим, кода его не существует). Прежде чем опираться на любой из них — вскрыть. + +**Полный список сохранён человекочитаемым:** `~/books/dovodka/СВЕРКА-РЕВИЗИИ-16-08.md` (35 КБ). +Кроме 65 неотражённых находок там **7 прямых противоречий телу отчёта** и **12 инструментов, +признанных негодными внутри самой ревизии** — эти два числа в §5.2 выше не раскрыты, читать в файле. + +### 5.3 Прочие открытые долги + +* **Адъюдикация английской оси** — контроли починены и проверены замером, прогона не было. + ⚠ `adjud.py` намертво прошит на китайскую ось (ключи `blind-keys-d4`, каталог `sud-d4`, + армы `A3`/`A0`) — для английской нужна параметризация. +* **Секция Д5 объявлена НЕИСПОЛНЕННОЙ:** поле `why` в `~/books/dovodka/canon-dissect.json` — + мусор («этот», «родов», «дочь»), классификация внутренней ревизией снята и всё равно писалась. + Честное закрытие — ручная пере-классификация 24 мест ($0). +* **`E1` побайтно равен черновику на 5 единицах из 16** — объявлено в Д14.4, но панель не + пере-считана без них. +* **Реестр требований:** 16 самореферентных улик из 89 (было 19, шесть заменил на артефактные). + Провалов два: `R37` (провенанс ja-книги) и `R64` (реестр не закоммичен — лендит оркестратор). +* **Селфтесты `sud.py`**: d4 зелёный, d3 4 провала, d6 1, d1 2 — все настоящие, диагнозы в журнале. + ⚠ `MIN_FLOOR["d6"]=3` — константа, поставленная под зелень; НЕ править, это вопрос владельцу. +* **Пакет Sol по японской ноге** отдан: `~/books/judging/ja6-sol/ORCHESTRATOR.md`. После прогона — + `ja6.py --score-sol`. Пять промтов сессий, p1 и p2 разведены (проверено: 0 промтов смешивают). +* **Эррата zh-канона**: поправка границы слова живёт в коде, в сохранённый банк не дошла + (завышение +0.0039) — правка закрытой оси, ратифицирует оркестратор. + +--- + +## 6. ЛОВУШКИ — на чём споткнёшься + +1. **НЕ пере-эмитировать существующие слепые ключи.** Раскладка меток — чистая функция от соли, + uid и НАБОРА армов; эмиссия с другим составом переписывает ключ отсуженной оси, и разбор + сопоставит ответы с ЧУЖИМИ армами. Новый проход = НОВЫЙ ключ со своей солью (так сделаны + `tier2`, `ja6`, `chtenie`). +2. **Пакет внешнему судье — ТРЁХУРОВНЕВЫЙ**, как в фазе Д: `ORCHESTRATOR.md` («по одному агенту + на промт») + `prompts/*.md` + задания. Двухдокументная форма эксп-23 НЕ годится: харнесс + владельца читает всё одной сессией, и половины шумового пола попадают одному судье. + Путь к ключу в пакете **не называть** — «не открывай blind-keys/» значит показать пальцем. +3. **Правила счёта — в САМОМ задании**, не только в обвязке: асимметрия инструкций между + семействами купила часть расхождения в 12 раз на проходе `tier` (норма паритета П-4). +4. **Запрет дочерних агентов** вписывать явно — сессия с ними перестаёт быть тем составом, + который зарегистрирован до запуска. +5. **Замок кассы снимать ТОЛЬКО у мёртвого процесса** (`ps -eo pid,cmd | grep ...`). Покупка + идемпотентна — просто перезапускать. Редактор думает 60–90 с на клетку, зависания до 9+ минут + бывают (в паке 22 вызов держал замок 74 минуты); ставить `timeout` ≥900 и не опрашивать часто. +6. **Фриз-гейт кассы**: покупающий код обязан быть ЗАКОММИЧЕН до покупки. Полигон вправе фризить — + основание вслух ПЕРЕД коммитом, форма `git commit -- <явные пути>`, никогда `git add -A`. +7. **`--wide-plants` на японской книге НЕ помогает** (проверено: 5 из 9 в обоих случаях). Регексы + посадок оказались не только пар-, но и КНИГО-зависимы. +8. **Не считать по половине пачки.** Пороги, снятые на неполном поле, читаются увереннее, чем есть + (на этом уже горела ось Д1: порог вырос 1.00 → 1.47, когда пол добрал пары). +9. **Промт черновика и промт редактора радикально асимметричны** (22 строки против 41): у + редактора есть весь блок дискурс-перевёрстки и FEWSHOT с тремя разобранными примерами, у + переводчика — одна строка «избегай канцелярита». ⚠ Но арм `A2` (однопроходка с УРАВНЕННЫМ + мандатом) это учитывал и всё равно проиграл по счёту ошибок — **проверить, перенесён ли в него + FEWSHOT**, это самая сильная часть промта и в эррате она не упомянута. + +--- + +## 7. МНЕНИЕ FABLE-5 (архитектурный аудит 16.08) — что взять + +Полный отчёт был в эфемерном транскрипте; существенное: + +* Подозрение о самоподгонке харнесса **подтверждается наполовину**: прибор не мерит ткань, а + эндпойнт под неё (П-6) принят владельцем 11.08 и не построен. Но ядро вывода («перепис покупает + много») не артефакт — держится независимыми контурами и внешней литературой. +* **Три механизма смещения В ПОЛЬЗУ большого переписывающего вызова:** боевой промт редактора + прошёл три редакции и валидирован тем же классом судейства, а контуры шли первой редакцией с + багами в свою сторону; мандат вёрстки исполняет только перепис, а судья за вёрстку штрафует; + отрицательные результаты трижды оказывались свойством прибора, а не армов. +* **Идея владельца испытана НЕ в сильнейшей форме.** Пустая клетка — «критик-буллеты → + ПЕРЕПИСЫВАТЕЛЬ» (`K1`). Вторая пустая — «перепис → критик → точечная починка» (страховка смысла + ПОСЛЕ переписа, там точечная форма адекватна дефекту, потому что остаточные ошибки локальны). +* Внешние данные: на вебновелльном бенчмарке zh→en **DeepSeek-V3 (5.16) выше GPT-4o (5.09)**; + китайские модели систематически бьют западные на китайском исходнике. То есть «фронтир не + выигрывает» правдоподобно и снаружи. Но если преимущество фронтира есть, оно в ткани, а её + автометрики и LLM-судьи не видят (LitEval: опознают человеческий перевод ≤20%). +* **Кандидаты сверх `K1`:** перепис → критик → фиксер · два переписа + слепой селектор (два + прогона одного редактора расходятся как два разных — это замерено на `JFLO`) · монолингвальная + полировка ткани без исходника · перепис с окном соседних глав. Все дешевле $0.015 на единицу. +* **Чего Fable не установил:** валидность счёта ошибок как прокси (П-6 не построен); долю знаков, + которую трогает фиксер (спан-счёт даёт 0.2–3.7%, difflib — 8.5% медиана и до 81%); перенос + выводов на другие книги и пары. + +--- + +## 8. КОМАНДЫ + +``` +eval/.venv/bin/python eval/dovodka/gain.py --density --agree --floor --tier +eval/.venv/bin/python eval/dovodka/naklon.py # все проходы; --selftest +eval/.venv/bin/python eval/dovodka/tier2.py --score +eval/.venv/bin/python eval/dovodka/ja6.py --score # и --score-sol после прогона Sol +eval/.venv/bin/python eval/dovodka/chtenie.py --score +eval/.venv/bin/python eval/dovodka/adjud.py --selftest ; --controls +eval/.venv/bin/python eval/dovodka/itog_d4.py --axis=d4 # --axis=d3|d6|d1, --fam=sol +eval/.venv/bin/python eval/dovodka/sud.py --axis=d3 --selftest +eval/.venv/bin/python eval/dovodka/money_d.py --report ; --selftest +eval/.venv/bin/python eval/dovodka/runs.py --status +eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --plan +``` + +Шум `SyntaxWarning` фильтровать. Интерпретатор — `eval/.venv/bin/python` из корня репо. + +--- + +## 9. ЧЕМ ЗАКРЫВАЕТСЯ РАБОТА (иначе непонятно, что считать сделанным) + +1. **Секция в отчёте** `docs/experiments/23-editor-tier.md` на каждый шаг плана. Нумерация уже + занята по Д16 включительно — следующая свободная Д17. +2. **Пре-рег ДО покупок и ДО первого судейского ответа**, зафризенный коммитом с основанием вслух. + Пре-рег обязан нести: состав панели · правило решения · пороги · мощность (MDE против ЦЕЛИ) · + статус оси (несущая/описательная) · что замер НЕ может. +3. **Гейты зелёные ИЛИ красные с диагнозом.** Константы под зелень не подгонять — это норма + проекта, нарушение = аннулирование куска. +4. **Пинг в `docs/PROGRESS.md` секция «Полигон»** (фронт и платформа туда не пишут, полигон пишет). +5. **Обновлённый `eval/dovodka/SESSION2-LOG.md` (рядом)** или его преемник — по ходу, не в конце. +6. **CONFIRM/DENY владельцу — только с артефактом-носителем.** Полигон не ратифицирует. +7. Дерево НЕ коммитить, кроме пре-рег-фризов; лендит оркестратор. + +--- + +## 10. КАК РАЗГОВАРИВАТЬ С ВЛАДЕЛЬЦЕМ + +* Он просил **меньше аббревиатур** и продуктовых формулировок: не «H-2б» и «A3», а «схема + критик-плюс-точечная-правка». Термины расшифровывать при первом употреблении. +* Он **не всегда читает длинные тексты** — важное выносить в первые строки. +* Он ловит дефекты процесса лучше гейтов: за сессию №2 нашёл структуру пакета Sol (я ошибся), + устаревший прайс (леджер оказался не измерителем) и слепоту прибора к вольности. **Его реплики + проверять исполнением, а не отмахиваться.** +* Обязательство с адресатом вне зоны закрывается ТОЛЬКО изменением файла вне зоны. «Выпущено», + «ждёт владельца», «объявлено в отчёте» — маркеры дефекта. +* Полигон не ратифицирует выводы. CONFIRM/DENY — владельцу через оркестратора, с носителем. diff --git a/eval/dovodka/PLAN-17-08.md b/eval/dovodka/PLAN-17-08.md new file mode 100644 index 00000000..370c06f9 --- /dev/null +++ b/eval/dovodka/PLAN-17-08.md @@ -0,0 +1,376 @@ +> ⚠⚠ **СОСТОЯНИЕ НА 20.08 — читать ЭТО прежде плана ниже.** Курс за день сдвинулся сильнее, чем +> написано в теле; тело оставлено как есть, потому что в нём основания решений. +> +> **1. СМЕНИЛСЯ ПРИБОР.** Решение владельца: первичный — слепое чтение Fable-5, счёт ошибок — +> брак-скрин. Замерено: чтение согласно с владельцем +0.80 на ОБЕИХ парах, счёт +0.80 на +> китайской и −0.80 на английской. ⇒ **все прежние вердикты об архитектурах вынесены прибором, +> который на английской паре смотрит в обратную сторону, и подлежат пере-суду.** 16 пакетов на +> это уже собраны и проверены (см. `HANDOFF-21-08.md`). +> **2. ШАГ 1 (Г5) ЗАКРЫТ**, вердикт по H-1 устоял — секция отчёта Д21. +> **3. ШАГ 2 (подача входа критику) ОСТАНОВЛЕН владельцем** после того, как замер показал: критик +> не видит удалённого абзаца ВООБЩЕ, поэтому «сохранил ли он бдительность» мерить не на чем. +> **4. Г1в/Г1 частично отвечены:** фраза о параллельности срезает размышление на 55% и находки на +> 50%; «след в след» — неправда на ОБЕИХ парах (проверено позиционно), врать модели нельзя. +> **5. Г12 усилена до механизма:** трейс показал, что 96% финального текста модель пишет ВНУТРИ +> размышления. Размышление у dspro — черновик, а не проверка. ⇒ двухстадийная связка дёшева +> БЛАГОДАРЯ топологии: она выносит черновик из дорогого канала размышления в дешёвый выход. +> **6. Появилась новая живая ветка, которой в плане нет:** промт однопроходки как РОЛЬ. Лучшая +> редакция — формулировка ВЛАДЕЛЬЦА («брак = просвечивающий исходный язык»), она обошла обе мои +> и встала выше продакшена в слепом чтении, но при КРАСНОМ контроле шума и вчетверо дороже. +> **7. Г5-строка ниже была НЕВЕРНА ПО ЗНАКУ** — исправлено в теле. + +# ПЛАН НОВОГО КУРСА — проверка живых гипотез (17.08) + +> Пишется после закрытия захода Д17 и архитектурного разговора с владельцем про V6. +> Предыдущие слои: `PLAN-16-08.md` (прошлый курс, ВЫПОЛНЕН) · `SESSION2-LOG.md` §12–§14 · +> отчёт `docs/experiments/23-editor-tier.md` секции Д17–Д20 · заказ +> `docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md` · хендофф `docs/POLYGON_PHASE_D_HANDOFF.md`. +> Читать ЦЕЛИКОМ: прошлая передача теряла пункты именно из-за чтения грепом. + +--- + +## 0. ГДЕ МЫ ОКАЗАЛИСЬ + +Заход Д17 ответил на вопрос, ради которого затевался, и ответ сместил всю рамку. + +1. **Второй проход нужен** — голый черновик проигрывает всему и всегда, по всем приборам и трём + парам. Единственное, что не пошатнулось за пять экспериментов. +2. **Какой именно второй проход — приборы не различают.** Все схемы переписывания легли в + неразличимую кучу под починенной рубрикой. +3. **Причина названа и подтверждена трижды независимо: собственный разброс ОДНОГО способа от + прогона к прогону перекрывает разницу МЕЖДУ способами.** Счёт на zh (sd 4.42 между двумя + генерациями связки), машинные читатели на zh, владелец на en (опознал пару близнецов по тексту + и развёл на два места: «один прогон приличный, второй сырой»). +4. **Счётчик — браковщик, а не выборщик.** Он следит за читаемостью там, где у него есть диапазон + (широкая шкала +0.68), и слепнет на чистом тексте (узкая +0.42). Формулировка владельца: + «счётчиком отсеивать брак, чтением выбирать текст в книгу». +5. **Деньги конвейера — это размышление, и тратится оно на выравнивание двух текстов.** 85% цены + дорогой клетки. Перепис думает ×12 против однопроходки только потому, что ему дают черновик. + +⇒ Курс меняется: раньше мы искали «лучшую архитектуру», теперь ищем **условия, при которых +архитектуры вообще становятся различимы**, и **чинить конвейер там, где он жжёт деньги впустую**. + +--- + +## 1. ЖИВЫЕ ГИПОТЕЗЫ — реестр + +Каждая проверяема, у каждой назван замер и цена. Порядок в §2, не здесь. + +| # | гипотеза | откуда | цена | +|---|---|---|---| +| **Г1** | Выровненный вход схлопывает размышление сличающей стадии | ОДНО наблюдение: критик видел черновик 78 абзацев против 77 строк исходника (0.96:1) и думал 1084; en 8 против 1 — думал 9576 | ~$0.3 | +| **Г1а** | ⛔ После РЕДАКТОРА выравнивание исчезает (77 строк → 43 абзаца), значит критик V6 попадёт в дорогой режим даже на zh | замер 17.08 | считано: V6 на Гу ~$340 против $226 у связки | +| **Г1в** | ⭐ Достаточно СКАЗАТЬ критику, что тексты идут параллельно — размышление упадёт без всякой разметки | идея владельца 17.08; нынешний промт критика не говорит о связи текстов НИЧЕГО, кроме «исходник и его перевод» | ~$0.15 | +| **Г1б** | Разметку можно поручить ОТДЕЛЬНОМУ дешёвому вызову-экстрактору, не трогая переводчика | квирки :51 — эхо бьёт по переводу, не по JSON-выводу | ~$0.2 | +| **Г1г** | Локальное эмбеддинговое выравнивание (LaBSE/vecalign/BERTalign) даёт таблицу пар с приемлемой точностью на ХУДОЖЕСТВЕННОЙ прозе | идея владельца; модели уже в кэше машины | **$0** за прогон, только CPU-время | +| **Г2** | «Мало думает» = «плохо работает»: ленивый критик находит хуже | zh критик 1084 токена, 43 находки на 2 тыс. знаков, контур проиграл | в комплекте с Г1 | +| **Г3** | Языковому критику исходник НЕ нужен — монолингвальная критика не хуже | ось ЯЗЫК решает, а она видна без оригинала | ~$0.4 | +| **Г4** | Кэш префикса срезает вход многостадийного конвейера | используем 44.8% из возможного | $0, перекладка запросов | +| ~~**Г5**~~ | ~~вердикт по H-1 зависит от состава пачек~~ **ЗАКРЫТА 20.08, Д21: вердикт устоял** ⚠ и формулировка была НЕВЕРНА ПО ЗНАКУ — «переходит порог» значило бы «дорогой черновик ХУЖЕ связки», то есть H-1 опровергается, а не подтверждается | находка ревизии №1 | $0, потрачено | +| **Г6** | Счётчик работает при широком диапазоне, слепнет на чистом | разбиение +0.68 против +0.42, но post-hoc | ~$1.5 (трудный en материал) | +| **Г7** | Дорогая модель + ПОЛНЫЙ промт → точечная зачистка ≥ связки | пустая клетка; `A6` был дорогой моделью с ТОЩИМ промтом | ~$1.2 | +| **Г8** | Промт однопроходки, собранный как РОЛЬ, а не склейка, лучше нынешнего | нынешний несёт мета-фразу «редактора после тебя не будет» | ~$0.5 | +| **Г9** | Банк, собранный из хороших переводов, лучше банка из дешёвых черновиков | никогда не мерилось; майнер режет исходник, черновики дают лишь `drafts:` | ~$0.3 | +| **Г10** | Разделение типов сущностей (имена / титулы / предметы) улучшает банк | терминолог даёт одно правило на всех | ~$0.3 | +| **Г11** | Сцена как единица нарезки лучше чанка | эксп-15: все эффекты когезии под порогом, сигнала нет | не считана | +| **Г12** | Несколько прогонов на метод — УСЛОВИЕ любого сравнения архитектур | п.3 §0 | ×2 к любому замеру | + +--- + +## 1а. ЧТО УЖЕ ЗАКРЫТО — НЕ ПЕРЕОТКРЫВАТЬ БЕЗ НОВОГО ОСНОВАНИЯ + +Дописано при перечитывании: без этого списка следующая сессия честно потратит деньги на то, +что уже куплено и отвечено. + +| закрыто | чем | вердикт | +|---|---|---| +| «критик → ПОЛНЫЙ перепис» (`K1`/`Z1`) | Д17, обе пары | −0.06 по счёту, у владельца 3-е и 4-е места. **ОТРИЦАТЕЛЬНО** | +| «обогащённый промт черновика» (`K8`/`Z8`) | Д17, обе пары | хуже голого на en, эхо-мина на zh. **ОТРИЦАТЕЛЬНО** | +| «дешёвая модель в роли редактора» | эксп-22 Ф3 | −2.54 против боевого + 3 клетки сожгли бюджет. **ОТРИЦАТЕЛЬНО** | +| «дорогая модель черновиком» | эксп-22 Ф2 | ничья, побеждает дешёвый. **ЗАКРЫТО** | +| «какая модель лучше редактор» | 5 моделей zh, 2-я на en/ja, 3 «сильных тира» | dspro первый везде. **ЗАКРЫТО устойчиво** | +| «gemini — аутлаер прозы» (эксп-04) | Д1, 15 клеток | не подтверждается, цена ×30. **ЗАКРЫТО** | +| H-4 «перевес dspro — свойство китайской пары» | zh/en/ja | не подтверждается на всех трёх. **ЗАКРЫТО** | +| роутинг «платить дорого адресно» | эксп-21 | отборщики-монетки. **ЗАКРЫТО** | +| второй последовательный перепис | эксп-20/21 | итеративный дрейф. **ЗАКРЫТО** | +| дифф-контракт вместо переписа | эксп-19 | дороже на thinking-модели. **ЗАКРЫТО** | + +⚠ **Единственный ЖИВОЙ продуктовый кандидат из измеренного: «однопроходка + глоссарный проход».** +По судейской оси от связки не отличается, по банку ЛУЧШЕ неё на обеих парах (zh 1.38 против 2.88, +en 0.31 против 0.56), стоит один полный вызов вместо двух, и владелец при слепом чтении поставил +однопроходку первой на английском. Его слабое место — банк без черновика (см. Г9). + +--- + +## 1б. РЕШЕНИЕ ВЛАДЕЛЬЦА, КОТОРОЕ ЖДЁТ ЕГО САМОГО (не гипотеза, а развилка) + +**Черновая роль по НАШЕМУ ЖЕ правилу должна уйти с DeepSeek.** Правило D39.117 — «при равном +качестве берём дешёвого», качество шести черновых моделей было ничьёй. После подорожания 16.08: + +``` +gpt-5.6-luna $0.00358/глава +gemini-3.1-flash-lite $0.00383 +deepseek-v4-flash $0.00444 ← был 0.00097, в 3.7 раза дешевле следующего +``` + +На книге в 1500 глав: текущая связка $40 против $23 у пары `flash-lite + glm-5`. +⚠ **Перед сменой — эхо-проба альтернатив:** у flash эхо-мина есть и она перевооружается +ослабленным thinking; у остальных не мерена НИ РАЗУ. Владелец сказал «это разговор предстоит». + +--- + +## 2. ПОРЯДОК РАБОТ. Он важнее содержания + +### ✔ ШАГ 1 — Г5. ВЫПОЛНЕН 20.08, $0. Секция отчёта Д21 + +**Вердикт «H-1 НЕ УСТАНОВЛЕНА» устоял.** Инструмент — `itog_d4.py --sens` (режим в СВОДЕ, нового +файла не заводилось). Арифметика находки воспроизведена побайтно; переворот живёт только в +сценарии, который возвращает пачку аннулированной сессии и одновременно снимает пачки-валидации, +то есть применяет норму в одну сторону и отменяет в другую. На нынешнем дереве запас **−0.0710**. + +Тем же заходом закрыты смежные находки: **P12** (пачка `69de717f3f` уже в карантине, строка +сверки устарела) · **P07** (наклон наборов реален, вердиктов не двигает) · **R73/P40** (калибровка +порога названа ПО ЗНАКУ: пол на Д4 честен, для H-1 контраста завышен в 1.5×). + +⛔ **Осталась открытой P42 и это ВОПРОС ВЛАДЕЛЬЦУ**, не замер: два пре-рег-правила о маржевом +гейте несовместимы, и от выбора между ними зависит, остаётся ли единственный CONFIRM фазы +подтверждением или становится эскалацией. Объявлено девиацией в Д21.6. + +**Что вынес сверх задания:** `A6/A0` во всех пяти способах счёта стоит ВПЛОТНУЮ к порогу +(−0.62 · −0.07 · −0.51 · +0.002 · −0.007) при шаге шкалы в одну ошибку ⇒ это не «эффекта нет», а +«эффект ровно размером с шумом прибора» — та же болезнь, что Д17 намерил на новом материале. +И `A6` испытывался ТОЩИМ промтом переводчика, поэтому клетка «дорогая модель + ПОЛНЫЙ промт» +(она же ядро V6) остаётся пустой — это шаг 4. + +### ШАГ 2 — Г1в + Г1 + Г2 ОДНИМ ЗАМЕРОМ (~$0.4). БЕЗ ВЫРАВНИВАНИЯ, БЕЗ РАЗМЕТКИ + +⚠ **Замер УРЕЗАН по итогам спора 17.08.** Прежняя редакция предлагала подавать критику +пронумерованные ПАРЫ, то есть требовала решить задачу выравнивания. Это преждевременно: вся +ветка висит на ОДНОМ наблюдении, которое я же и объяснил. Сначала проверяем наблюдение, потом +строим схемы — норма, заведённая этим разговором. + +**ПЕРВЫМ — вариант владельца (Г1в), он дешевле и чище.** Меняется ТОЛЬКО ИНСТРУКЦИЯ, данные +не трогаются вовсе. Сейчас промт критика говорит о связи двух текстов буквально одно: «тебе дан +ИСХОДНИК и его ЧЕРНОВОЙ ПЕРЕВОД». Модель вынуждена сама выяснять, как они соотносятся, — и, +похоже, именно на это уходят те 9 576 токенов. + +Добавляем фразу о том, что тексты идут ПАРАЛЛЕЛЬНО. Образ владельца: два листа из разных тетрадей, +наложенные друг на друга, — тексты идут рядом, а не строчка в строчку. + +⚠ **ФОРМУЛИРОВКА РЕШАЕТ, И ВРАТЬ НЕЛЬЗЯ.** «Предложение за предложением» — НЕПРАВДА: наш же промт +редактора обязывает сливать абзацы (77 строк исходника → 43 абзаца). Соврём — модель будет искать +соответствие, не найдёт и потратит БОЛЬШЕ, а не меньше. Говорить только правду: порядок сохранён · +перевод покрывает исходник целиком · членение может отличаться · читай оба параллельно и НЕ ищи +точных пар предложений. + +**ВТОРЫМ — мой вариант:** тот же кусок, но **исходник разбит по предложению на строку** +(перевод НЕ трогаем, выравнивания НЕ делаем). Детерминированно, $0. + +**Гонять лучше все четыре комбинации** (инструкция вкл/выкл × разбивка вкл/выкл) на одном +материале: тогда видно, что решает — слова или форма подачи. Всё равно копейки. + +⚠⚠ **РИСК, КОТОРЫЙ ЗАМЕР ОБЯЗАН ПРОВЕРИТЬ НАРАВНЕ С ТОКЕНАМИ.** Фраза «перевод покрывает исходник +целиком» может заставить критика ПЕРЕСТАТЬ ИСКАТЬ ПРОПУСКИ, а потерянный факт — один из главных +классов нашей рубрики. Выигрыш в токенах, купленный слепотой к целому классу дефектов, — это не +выигрыш. Поэтому в пре-рег отдельной строкой: считать долю находок класса «пропущено/потеряно» +до и после, и падение этой доли считать ПРОВАЛОМ варианта, даже если токены упали. + +Смотрим ТРИ вещи: +* **токены размышления** — упали или нет (Г1в/Г1); +* **находки в целом** — те же, лучше или хуже (Г2); +* **находки класса «пропущено»** — не исчезли ли (риск выше). + +**Если упали при тех же находках** — гипотеза жива, и тогда имеет смысл строить разметку (Г1б). +**Если нет** — вся ветка закрывается, и мы экономим себе месяц проектирования. Это и есть цель +шага: закрыть ветку дёшево, а не подтвердить её дорого. + +⚠ Обещание «минус треть цены конвейера» СНЯТО: оно висело на неподтверждённом. +⚠ Пре-рег обязателен: что считаем «те же находки». Сравнение — по адъюдикации, а не на глаз. + +### ШАГ 2б — Г1г: ЭМБЕДДИНГОВОЕ ВЫРАВНИВАНИЕ, ЕСЛИ ДЕШЁВОЕ НЕ СРАБОТАЛО ($0) + +Гонится ТОЛЬКО если шаги 2 и 2а не дали адресации. Инструмент локальный, вызовов к API нет вовсе. + +**Что уже есть на машине (проверено):** `sentence-transformers`, `torch+cpu`, 16 ядер, GTX 1070; +в кэше лежат `LaBSE`, `bge-m3`, `Qwen3-Embedding-0.6B`. Кандидаты-алгоритмы: **vecalign** +(динамическое программирование по эмбеддингам, умеет МНОГИЕ-КО-МНОГИМ) и **BERTalign** (на LaBSE, +авторы целились в художественную прозу). Gale-Church по длинам для zh↔ru слабая. + +**⛔ ОГРАНИЧЕНИЕ ПРОДА: в облаке только CPU + диск, GPU нет.** Поэтому: +* выравнивание — НЕ горячий путь, один раз на книгу, результат на диске; +* объём режется: эмбеддить смысловые ходы (43–78 на кусок), а не предложения · сперва ЯКОРЯ + (термины банка, числа, реплики) прибивают опорные точки, эмбеддинги — только на промежутках · + модель меньше + int8/ONNX; +* **роли разведены: в лаборатории это ИЗМЕРИТЕЛЬ, в проде адреса берутся из номеров смысловых + ходов по построению.** Прод на эмбеддинги опираться НЕ должен. + +**Точность мерим БЕЗ ручной разметки, инструментами, которые у нас уже есть:** банк терминов +(термин в отрезке исходника обязан быть канонной формой в выровненном отрезке перевода) и маржевые +посадки (мы сами знаем, куда вставили порчу). ⚠ Чужие цифры качества из статей НЕ переносить: их +мерили там, где перевод следует за оригиналом близко, а у нас редактор обязан сливать абзацы. + +### ШАГ 2а — ЕСЛИ Г1 ПОДТВЕРДИЛАСЬ: РАЗМЕТКА ОТДЕЛЬНЫМ ВЫЗОВОМ (~$0.2) + +⛔ **Переводчику разметку НЕ поручать — это измеренный класс отказа.** Обогащённый черновик +получил ОДИН добавленный структурный блок и вернул исходник вместо перевода на 4 главах из 16; +запрет D21.6 говорит то же про языковые констрейнты в reasoning-ослабленных путях (эхо 3/10 → 9/10). + +Разметку делает **отдельный дешёвый вызов-экстрактор**: вход исходник + готовый перевод, выход +ТОЛЬКО соответствие «номер смыслового хода → первые слова абзаца». Ни строчки текста не +переписывает. Безопасно по квирк-бюллетеню (:51): эхо бьёт по переводу, не по JSON-выводу. + +**Алгоритма сопоставления при этом НЕТ и не нужно.** Если ходы пронумерованы на шаге 1, а блоки +несут те же номера, соответствие возникает по построению. Проверка разметки детерминированна: +каждый номер ровно один раз · порядок монотонный · длина блока в разумном отношении к длине хода · +термины банка хода N присутствуют в блоке N. Не сошлось — блок «без адреса», обрабатывается как сейчас. + +### ШАГ 3 — Г3 (~$0.4) + +Монолингвальный языковой критик: только русский текст, исходника нет вовсе. Сравнить его находки +по оси ЯЗЫК с находками нынешнего двуязычного. Если не хуже — сличение из этой стадии убирается +целиком, а это самая дешёвая экономия из всех возможных. + +### ШАГ 4 — Г7 + Г8, ПУСТЫЕ КЛЕТКИ АРХИТЕКТУРЫ V6 (~$1.7) + +Обе покупаются одним заходом, потому что делят базу: +* **Г8**: промт однопроходки, собранный НАЧИСТО как роль литературного переводчика с инжектом + действий — без мета-фразы про отсутствующего редактора, без следов склейки. +* **Г7**: её выход + точечная зачистка по классифицированным замечаниям критика. + +Это и есть урезанный V6 на одной паре. ⚠ Брать ОБЕ пары: разница zh/en в этой фазе оказалась +не языковой, а структурной (выровненность исходника), и на одной паре вывод не встанет. + +### ШАГ 5 — Г9 + Г10, БАНК (~$0.6) + +Пере-собрать банк из выходов однопроходки и из дорогих черновиков, сравнить с нынешним по составу +и покрытию. Отдельно — терминолог с РАЗДЕЛЁННЫМИ типами сущностей. + +**Почему не раньше:** банк определяет связность книги на тысячах глав, но эффект его качества наш +прибор не видит вовсе — он сравнивает армы при ОДНОМ банке. Значит замер тут не судейский, а +детерминированный (состав, покрытие, устойчивость формы), и его надо ставить отдельно от +качественных шагов, чтобы не смешать. + +### ШАГ 6 — Г6, ТРУДНЫЙ АНГЛИЙСКИЙ МАТЕРИАЛ (~$1.5) + +Длинные плотные английские главы вместо коротких и чистых. Проверяет, восстанавливается ли +согласие счётчика с чтением при широком диапазоне. Разбиение +0.68/+0.42 сделано ПОСЛЕ данных — +это разведка, и подтверждать её надо на новом материале, иначе это подгонка. + +### ШАГ 7 — Г4, КЭШ ($0) + +Переложить порядок запросов так, чтобы исходник сцены был общим неизменным префиксом всех стадий. +Замер прямой: доля `cached_tokens` до и после. Сейчас 44.8%. + +### ШАГ 8 — ПЕРЕНОСИМОСТЬ ВЫВОДА НА ДРУГУЮ МОДЕЛЬ (~$0.5) + +Всё измеренное получено при модели-константе `deepseek-v4-pro`. Вывод честно звучит «какая +архитектура лучше ДЛЯ ЭТОЙ модели». Прогнать победившую архитектуру на `glm-5` и проверить, что +порядок не зависит от жильца. ⚠ Ставить ПОСЛЕ шагов 2–4: пока архитектура не выбрана, переносить +нечего. + +### ШАГ 9 — СЛЕПОЕ ЧТЕНИЕ ЧЕЛОВЕКОМ НА НОРМАЛЬНОМ n ($0) + +Владелец прочёл 1 китайскую главу и 2 английских. Пре-рег П-6 просил 8–12 единиц на ось. +Его чтение оказалось ТОЧНЕЕ машинных читателей (взял контроль там, где они провалили), поэтому +расширение выборки — самый дешёвый способ усилить решающий эндпойнт. Ограничение известно и +названо им самим: это его время. + +--- + +## 3. ЧТО ПРОНИЗЫВАЕТ ВСЕ ШАГИ — Г12 + +**Сравнение архитектур по ОДНОМУ прогону запрещено.** Это вывод захода Д17, подтверждённый тремя +приборами. Любой платный замер выше планируется в ДВУХ генерациях на метод, иначе он померяет шум. + +Цена этого честная: ×2 к смете. Альтернатива — мерить и не знать, что померил. + +--- + +## 4. ДОЛГИ ФАЗЫ Д — их надо закрыть, но они НЕ курс + +Не двигают знание, но без них фаза не сдаётся. Порядок внутри — от того, что искажает выводы, +к тому, что искажает отчётность. + +1. **Адъюдикация английской оси** — единственное, что блокирует единственный несущий вывод фазы + (H-3 в эскалации). `adjud.py` прошит на zh, нужна параметризация. $0. +2. **65 находок ревизии** — шаг 1 курса разбирает первую; остальные разобрать там же. +3. **Секция Д5** — ручная пере-классификация 24 мест. $0. +4. **Синхронизация отчёта:** сводка Д9 противоречит Д15–Д20 в четырёх клетках · Д16 не отражает + аннулирование японской пачки Sol её же контролями · деньги в Д17 протухли (напечатана смета + $1.06 и потолки $0.65, факт $11.72 из $12.10) · тела эрраты Э-17.1 в отчёте нет. +5. **Реестр требований:** строк на Д17–Д20 нет вовсе, 16 самореферентных улик, R64 красный. +6. **Пинг в `docs/PROGRESS.md` секция «Полигон»** — последняя запись 15.08. +7. **Селфтесты `sud.py` красные на трёх осях** (d3 — 4 провала, d6 — 1, d1 — 2), все настоящие, + диагнозы в журнале. ⚠ `MIN_FLOOR["d6"]=3` — константа, поставленная под зелень; НЕ править, + это вопрос владельцу. +8. **`E1` побайтно равен черновику на 5 единицах из 16** — объявлено в Д14.4, панель без них не + пере-считана. +9. **Эррата zh-канона:** поправка границы слова живёт в коде, в сохранённый банк не дошла + (завышение покрытия +0.0039). Правка закрытой оси — ратифицирует оркестратор. +10. **`chtenie.py --score-calib` не пере-считывает число −0.10**, ради воспроизводимости которого + объявлен: печатает только порядок читателя. Долг назван в докстринге, не закрыт. +11. **Пакет второму семейству (Sol) по заходу Д17** — не собирался. Решение владельца нужно: + отдавать или объявить заход односемейным явно. + + +--- + +## 5. ЧЕГО НЕ ДЕЛАТЬ + +* **Не строить V6 целиком.** Пять стадий сразу — это замер, в котором нельзя понять, что сработало. + Шаги 2–5 покупают его по кускам, и каждый кусок отвечает на свой вопрос. +* **Не выбирать архитектуру по деньгам.** Разброс между схемами 15–40%, между временем суток — + ровно вдвое. Экономить надо расписанием прогона, решать — качеством. +* **Не переносить отрицательные результаты по точечным контурам на V6.** Там фиксер чинил + дешёвый черновик; в V6 он чинит перевод хорошей модели. Другая работа. +* **Не сравнивать числа разных проходов.** Рубрика менялась (Д18), прогоны разной строгости. +* **Не покупать до пере-снятия прайса**, если вендор снова его двинет: `zakhod.price_gate` + отказывает автоматически, но проверять руками при первом запуске сессии. + +--- + +## 6. ЛОВУШКИ, ДОБАВИВШИЕСЯ ЗА ЭТУ СЕССИЮ + +1. **Файл клетки ≠ годная клетка.** Считать СОДЕРЖИМОЕ и `finish=stop`. Я на этом сел и доложил + владельцу «80 клеток куплено», когда годных было 23. +2. **Пред-полётная проба обязательна** перед циклом на сотню клеток (`zakhod.preflight`): 96 + запросов ушли в `402` и выглядели как успешный прогон. +3. **Оценивать цену клетки ПО СВОЕЙ ПАРЕ**, а не по общему пулу: китайская глава впятеро длиннее + английской, общая медиана врёт вдвое. +4. **Раскладка меток в пакете ЧЕЛОВЕКУ — одна на всю пару.** Раскладка на отрывок сделала ответ + владельца нерасшифровываемым, и его работа пропала. +5. **Разборщик обязан принимать человеческую форму ответа.** Требовать машинной и молча терять + ответ — дефект инструмента, а не читателя. +6. **Пере-эмиссия не имеет права трогать пару, которая уже прочитана.** Ключ ДОГРУЖАТЬ. +7. **Гейт, который не может упасть, ничего не сторожит.** Пустая панель в селфтесте — ПРОВАЛ, + а не пропуск. +8. **`git checkout` поверх грязного дерева запрещён** — владелец отказал в пермишене, и правильно. +13. **Обогащение промта ДЕШЁВОЙ модели перевооружает эхо-мину.** Замер Д17: мандат перевёрстки, + добавленный к черновику, дал 4 негодные клетки из 16 на zh (модель вернула исходник вместо + перевода) против 1 из 16 в базе. Причина документирована: черновик ходит с `reasoning_effort: + low`, а запрет D21.6 говорит не вносить языковые констрейнты в промты reasoning-ослабленных + путей без per-model замера. **Любое обогащение промта на дешёвой модели — только с эхо-гейтом + и ре-геном.** +14. **Идентичность судей и ЧИТАТЕЛЕЙ персистится ДО запуска.** Я закрыл её постфактум + (`READERS-z17.json`) — это слабее нормы. У прохода `tier` отсутствие такого файла сделало + причину расхождения в 12 раз НЕВОССТАНОВИМОЙ. +15. **Три инструмента зоны признаны негодными ВНУТРИ ревизии, а гейты числят их зелёными:** + `promptdiff.py` (расхождение матчится по префиксу строки; отсутствующий файл пар-пакета гейт + не роняет) · `canon.py` (снятая ревью классификация всё равно пишется в артефакт — отсюда + негодность Д5) · `material_ja.py` (фильтр AI-меток объявлен механическим, кода не существует). + Опираться на любой — только после вскрытия. +16. **Д17 отсужен ОДНИМ семейством.** Норма П-1 требует двух там, где возможно. Пакет для Sol по + заходу НЕ собирался. Все выводы Д17–Д20 стоят на семействе claude плюс чтение владельца. + + +--- + +## 7. ЧЕМ ЗАКРЫВАЕТСЯ РАБОТА + +1. Пре-рег ДО каждой покупки, зафризенный коммитом с основанием вслух. +2. Мощность (`power.py`) напечатана ЧИСЛОМ до денег — не словами. За это уже была девиация Э-17.2. +3. Гейты зелёные ИЛИ красные с диагнозом; константы под зелень не подгонять. +4. Секции в отчёте: следующая свободная — **Д21**. +5. Адверсариальная приёмка другим модельным семейством — она за эту сессию нашла 8 дефектов + замера и 4 неверных утверждения в моём же отчёте. Без неё не сдавать. +6. Дерево НЕ коммитить, кроме пре-рег-фризов; лендит оркестратор. diff --git a/eval/dovodka/SESSION2-LOG.md b/eval/dovodka/SESSION2-LOG.md index 7a1e4851..3b8b0e8b 100644 --- a/eval/dovodka/SESSION2-LOG.md +++ b/eval/dovodka/SESSION2-LOG.md @@ -457,3 +457,1220 @@ DeepSeek на пик `flash $0.44/$1.32/кэш $0.014` · `pro $1.32/$3.96/кэ что кому подаётся). Контраст мерит топологию только при модели-константе — иначе это конфаундер эксп-19. Цена этого: вывод честно звучит «какая архитектура лучше ДЛЯ ЭТОЙ модели». Переносимость закрывается отдельным дешёвым замером ПОСЛЕ захода: победившая архитектура на `glm-5`. + +## 14. ЧЕКПОЙНТ 16.08 — ПАНЕЛЬ КУПЛЕНА, СУДЕЙСТВО НЕДОСУЖЕНО + +**Куплено ПОЛНОСТЬЮ, годные клетки (не файлы):** zh `Z8` 16 · `Z8R` 12/12 · `Z1` 16 · `Z7` 16 · +`ZF` 16 · en все пять армов по 16. `Z8R` на zh ограничен 12 ПО ПОСТРОЕНИЮ: на четырёх главах +обогащённый черновик забракован эхо-гейтом, редактировать нечего — объявлено, не спрятано. +Деньги: забукировано $11.72, фактически списано ~$8.9 (покупки шли в вендорский офф-пик, касса +намеренно пишет пик). Потолки ФД-I 3.95 · ФД-J 1.95 · пакетный 12.10. + +**Судейство ЭМИТИРОВАНО и НЕДОСУЖЕНО.** Ключ `~/books/dovodka/blind-keys-z17/z17-KEY.json` +(соль `z17-2026-08-16`), задания `~/books/judging/z17/{zh,en}/{p1,p2}-tasks`. +Ответов на диске: **zh p1 3/12 · zh p2 3/12 · en p1 15/16 · en p2 15/16**. +Воркфлоу `wf_b1370361-af2` остановлен вместе с процессом; **возобновление — +`Workflow({scriptPath: "…/workflows/scripts/z17-judging-wf_b1370361-af2.js", +resumeFromRunId: "wf_b1370361-af2"})`**, выполненные агенты вернутся из кэша. +Сессии #73–#86 зарегистрированы ДО запуска, НЕ закрыты `runs.py --done`. + +⚠ **Пере-эмитировать `zsud.py --emit` НЕЛЬЗЯ** — за ключом уже лежат 36 ответов, и гейт +переэмиссии это отказывает намеренно: раскладка меток есть функция соли и набора армов. + +**Не начато:** слепое чтение (`chtenie.py --emit`, панель собирается на 8 главах каждой оси, +селфтест зелёный) · свод `zsud.py --score` · канон-гейт `zsud.py --canon` · секции отчёта по +результатам · пинг в `docs/PROGRESS.md`. + +**Долги фазы, не тронутые этой сессией:** 65 находок ревизии · адъюдикация английской оси · +секция Д5 · `E1`≡черновик · 16 самореферентных улик · красные селфтесты `sud.py` · эррата +zh-канона · строки Д17 в реестре требований (заказ требует реестр в git ДО покупок — нарушено). + +--- + +## 14. АРХИТЕКТУРНЫЙ РАЗГОВОР 17.08 — V6 ВЛАДЕЛЬЦА И ЭКОНОМИКА КОНВЕЙЕРА + +Владелец выложил идею V6 (`START_PROMT.MD`, строки 97–103): пять стадий — дешёвый map-reduce +майнинг банка и нарезка по сценам с JSON-выдачей · решение по банку хорошей моделью с +обоснованием · перевод хорошей моделью БЕЗ подсовывания дешёвого черновика · литературный критик, +который ЖАЛУЕТСЯ и классифицирует, но не чинит, с майнингом замечаний в свой банк · точечный +редактор, инжектящий фиксы. Ниже — что из нашего сырья про это уже известно. + +### 14.1 Что V6 чинит по существу + +Разложение по осям (Д14.11) показало: точечные контуры проигрывают ПРОЗОЙ, а не смыслом +(`A3/A0` верность −0.26 при языке −1.61). Причина механическая — фиксер трогает 0.2–3.7% знаков, +остальное остаётся черновиком. **В V6 точечный редактор чинит не черновик, а перевод хорошей +модели: плохой прозы, до которой он не дотягивается, там нет.** Наши шесть отрицательных замеров +по контурам на эту конфигурацию НЕ переносятся — у неё другая работа. + +### 14.2 ⛔ ГЛАВНАЯ НАХОДКА РАЗГОВОРА: деньги конвейера — это РАЗМЫШЛЕНИЕ, и оно тратится +### на ВЫРАВНИВАНИЕ ДВУХ ТЕКСТОВ + +Разбор токенов по ролям (медианы, цена по текущему пину; у DeepSeek размышление тарифицируется +по цене ВЫХОДА — $3.96/1M против $1.32 за вход): + +| роль | вход | выход | из них РАЗМЫШЛЕНИЕ | $/клетка | +|---|---|---|---|---| +| перепис zh | 5 502 | 16 790 | **13 978** | 0.0737 | +| критик zh | 4 522 | 3 730 | **1 084** | 0.0207 | +| перепис en | 2 627 | 10 330 | 9 718 | 0.0444 | +| **критик en** | 1 403 | 10 098 | **9 576** | **0.0418** | +| однопроходка zh | 3 024 | 3 620 | **842** | 0.0143 | + +⇒ **85% цены дорогой клетки — то, что модель надумала про себя.** Перепис думает в 12 раз больше +однопроходки, потому что ему дают черновик и просят сверяться. **Дешёвый черновик, взятый ради +экономии, и есть самая дорогая статья конвейера.** + +⇒ ⚠ **Возражение владельца подтвердилось: критик НЕ дешевле по природе.** На английском он думает +9 576 токенов, чтобы выдать список из 11 пунктов на 522 токена, и стоит $0.0418 — почти как +полный перепис ($0.0444). Моя оценка «V6 дешевле» держалась на КИТАЙСКОМ критике и рухнула бы, +веди он себя как английский (V6 на Гу стал бы ~$340 вместо $190 против $226 у связки). + +### 14.3 ⛔ ПОЧЕМУ КРИТИКИ РАЗОШЛИСЬ В 9 РАЗ — ОБЪЯСНЕНИЕ НАЙДЕНО ЗАМЕРОМ + +``` +структура исходника: zh — 77 строк на кусок, 28 знаков на строку + en — 1 строка на кусок, 1642 знака на строку +``` + +**Китайская вебновелла набрана по предложению на строку — она УЖЕ ВЫРОВНЕНА.** Критику не нужно +восстанавливать соответствие, он видит готовые пары. Английский приходит сплошным потоком, и +9 576 токенов размышления уходят на выстраивание соответствия между двумя блобами. + +⇒ **Дорого не «сличать два текста», а сличать два НЕВЫРОВНЕННЫХ текста.** + +### 14.4 ПЯТЬ АРХИТЕКТУРНЫХ ПРЕДЛОЖЕНИЙ, ВЫРОСШИХ ИЗ ЭТОГО + +1. **Пред-выравнивание снаружи модели.** Подавать сличающей стадии пронумерованную таблицу пар + вместо двух текстов. Выравнивание детерминированно и бесплатно (пунктуация, длина, якоря + банка). Побочная выгода крупнее экономии: **у претензии появляется машинный адрес** — номер + строки вместо цитаты, которую мы сейчас ищем регексами (и на этом уже ловили дефекты). +2. **Снять с критика то, что ловит программа.** Термины — банк-гейт, типографику и числа — + батарея, всё это $0. Оставить критику ОДИН класс: «звучит переведённым». +3. **Языковому критику исходник не нужен вовсе.** Калька и канцелярит диагностируются + монолингвально. Тогда критиков два и оба дешёвые: языковой (только русский, сличения нет) и + смысловой (выровненные пары, узкий мандат). +4. **То же лекарство переписывателю** — не давать черновик (это замерено: 842 против 9 976 + токенов), а если нужен ради банка — давать выровненным. +5. **Кэш префикса.** Замер: используем на **44.8%** (1.19M из 2.65M токенов входа). Кэш-хит у + dspro $0.044 против $1.32 — в 30 раз дешевле. В пятистадийном V6 исходник сцены проходит + через перевод, критика и фиксер: если он общий неизменный ПРЕФИКС, две стадии из трёх платят + за него по кэш-цене. + +### 14.5 СМЕТА КНИГ (замеренные цены клеток, пере-оценённые по текущему пину) + +Мастер Гу — **7 778 990 знаков** (6.2M иероглифов). Кристофф — **1 626 475 знаков**. + +| архитектура | Гу, пик / офф-пик | Кристофф, пик / офф-пик | +|---|---|---| +| боевая связка | $226 / **$113** | $40 / **$20** | +| однопроходка + глоссарный проход | $195 / **$97** | $27 / **$14** | +| V6 (оценка, доля фиксов 35% — ДОГАДКА) | $190 / **$95** | $70 / **$35** | + +⚠ Разброс между архитектурами (15–40%) МЕНЬШЕ разброса между временем суток (ровно вдвое). +Планировать прогон на офф-пик выгоднее, чем выбирать архитектуру. ⇒ **выбор архитектуры — не про +деньги**, решать надо по качеству и по связности на дистанции в тысячи глав. + +### 14.6 ФАКТИЧЕСКИЕ ОТВЕТЫ НА ВОПРОСЫ ВЛАДЕЛЬЦА + +* **«Давали ли дорогому черновику полный промт?»** — НЕТ. Арм `A6` делался `deepseek-v4-pro` + тем же тощим промтом переводчика (`panel.py:142` → `translator_msgs`). Поменяли модель, не + поменяли задание. А «дорогая модель + полный качественный промт» — это и есть однопроходка + (её мандат несёт блок перевёрстки, проверено селфтестом). +* **«Разделяем ли типы сущностей в терминологе?»** — НЕТ. Один список, одно правило («имена и + топонимы транскрипцией, титулы и реалии переводом»); поле `type:` есть, но промт сам говорит, + что оно неточно. +* **«Было ли: хороший черновик + майнинг банка + точечный фиксер?»** — половина. Дорогой черновик + + точечный контур (`A6`) — только zh, 16 глав. Майнинг банка из хороших переводов — НИКОГДА. +* ⚠ **СНЯТО МОЁ ПРЕЖНЕЕ УТВЕРЖДЕНИЕ** «однопроходка ломает майнинг банка». Преувеличение: майнер + режет кандидатов из ИСХОДНИКА (`bank.py:127`), черновики дают терминологу лишь строку `drafts:` + как свидетельства. Без черновика — деградация подсказки, а не поломка майнинга. + +### 14.7 РАЗБОР ГИПОТЕЗЫ О ВЫРАВНИВАНИИ (17.08, спор с владельцем — он прав дважды) + +**Ход разговора, потому что он важнее итога.** Я предложил подавать сличающей стадии выровненные +пары вместо двух текстов и объяснил этим разрыв размышления (zh 1084 против en 9576). Владелец +возразил: после художественной перевёрстки абзацы не сопоставимы, несколько китайских абзацев +ложатся в один русский, задача нетривиальна. Я проверил на ОТРЕДАКТИРОВАННОМ тексте — 77 строк +исходника против 43 абзацев, 1.73:1 — и признал, что поторопился. + +**Владелец возразил САМ СЕБЕ и оказался прав:** критику подавали не отредактированный текст, а +ЧЕРНОВИК. Пере-замер: + +``` +zh: 77 строк исходника → 78 абзацев ЧЕРНОВИКА = 0.96:1 ← это видел критик, думал 1 084 +zh: 77 строк исходника → 43 абзаца ОТРЕДАКТИРОВАННОГО = 1.73:1 +en: 1 строка исходника → 8 абзацев ЧЕРНОВИКА = 0.12:1 ← это видел критик, думал 9 576 +``` + +⇒ Дешёвая модель, вопреки инструкции «не копируй построчную разбивку», скопировала её почти +дословно. **Китайский критик действительно получил готовые пары.** Связка «выровнен вход → мало +размышления» держится: одна модель, одна роль, разрыв ×9. + +### 14.8 ⛔ СЛЕДСТВИЕ, КОТОРОЕ ЛОМАЕТ СМЕТУ V6 + +Владелец: «если критику подавать уже отредактированный текст, там точно не будет выравнивания». +Верно, и в V6 критик получает именно ХОРОШИЙ ПЕРЕВОД, а не черновик. Значит он попадёт в +«английский режим» ДАЖЕ НА КИТАЙСКОМ. + +Пере-счёт: китайский критик стоил $0.021/клетка НА ВЫРОВНЕННОМ входе; на невыровненном он идёт к +цене переписа ($0.05–0.07). ⇒ **V6 на Мастере Гу становится ~$340 вместо $190, то есть в полтора +раза ДОРОЖЕ боевой связки ($226), а не дешевле.** Моя прежняя смета V6 недействительна. + +⇒ **Выравнивание перестало быть «идеей для экономии» и стало условием жизнеспособности V6.** + +### 14.9 КАК СОПОСТАВЛЯТЬ — АЛГОРИТМА НЕТ, И ЭТО НЕ ОГОВОРКА + +Вопрос владельца: «какой алгоритм сопоставления с оригиналом?» Ответ: сопоставления НЕТ. +Если шаг 1 режет книгу на смысловые ходы с номерами, а перевод выходит блоками с ТЕМИ ЖЕ +номерами, соответствие возникает ПО ПОСТРОЕНИЮ — производитель сам заявил, что чему отвечает. +Проверка разметки детерминированна и бесплатна: каждый номер ровно один раз · порядок монотонный · +длина блока в разумном отношении к длине хода · термины банка хода N присутствуют в блоке N. +Не сошлось — блок помечается «без адреса» и обрабатывается как сейчас. + +### 14.10 ⛔ ВТОРОЕ ВОЗРАЖЕНИЕ ВЛАДЕЛЬЦА, КОТОРОЕ ПЕРЕДЕЛЫВАЕТ СХЕМУ + +«Не испортит ли это качество? Мы просим модель, которая занимается основным переводом, делать +разметку — как бы она не поплыла». **Возражение подпёрто нашим же замером:** обогащённый черновик +получил ОДИН добавленный структурный блок и вернул исходник вместо перевода на 4 главах из 16. +Плюс запрет D21.6: языковые констрейнты в reasoning-ослабленных путях поднимали эхо 3/10 → 9/10. +⇒ Нагружать переводчика разметкой НЕЛЬЗЯ, это измеренный класс отказа. + +**Лечение:** разметку делает ОТДЕЛЬНЫЙ дешёвый вызов — вход исходник + готовый перевод, выход +ТОЛЬКО соответствие (номер хода → первые слова абзаца), ни строчки текста не переписывается. +Безопасно по квирк-бюллетеню (`00-provider-quirks.md:51`): «для дешёвой роли ЭКСТРАКЦИИ (не +перевод) thinking можно выключить — echo бьёт по переводу, не по JSON-выводу». Маркировка — это +экстракция. Основной перевод сохраняет один мандат и не рискует; провал разметки перевод не портит. + +### 14.11 ЧТО СНЯТО С МОИХ ЖЕ СЛОВ + +* **Снято обещание «выравнивание срежет треть цены конвейера»** — висело на одном наблюдении, + объяснённом мною же. До замера это догадка. +* **Снята прежняя смета V6** (§14.5): она считана по китайскому критику на выровненном входе, + которого в V6 не будет. +* ⚠ **Норма, заведённая этим разговором:** прежде чем строить схему поверх наблюдения, проверить + наблюдение отдельным дешёвым замером. Мы дошли до третьего этажа проектирования на фундаменте + из одного совпадения. + +### 14.12 ЭМБЕДДИНГОВОЕ ВЫРАВНИВАНИЕ — идея владельца, инвентаризация возможного (17.08) + +Владелец: «а что если через эмбеддинги построить таблицу оригинал/перевод… может есть современные +мультиязычные эмбеддинговые алгосы?» + +**Класс инструментов, который сюда ложится (называю как кандидатов, не как выбор):** +* **LaBSE** (Google, 109 языков) и **LASER** (Meta) — эмбеддинги, обученные ИМЕННО на задаче + «найти перевод этого предложения», а не на общей близости смысла; +* **vecalign** — динамическое программирование по эмбеддингам, умеет МНОГИЕ-КО-МНОГИМ (склеенные + отрезки), то есть ровно тот случай, который назвал владелец: несколько абзацев исходника в один + русский; +* **BERTalign** — новее, на LaBSE, авторы целились в ХУДОЖЕСТВЕННУЮ прозу с перестройкой; +* классика Gale-Church (по длинам) для zh↔ru слабая: соотношение длин пляшет, общих слов нет. + +**Инвентаризация машины (проверено исполнением):** `sentence-transformers 5.6.0`, `transformers`, +`torch 2.12.1+cpu`, 16 ядер, GTX 1070 8 ГБ. В кэше УЖЕ ЛЕЖАТ `LaBSE`, `bge-m3`, +`Qwen3-Embedding-0.6B` — то есть проверить можно немедленно и за $0. + +**⛔ ОГРАНИЧЕНИЕ ВЛАДЕЛЬЦА: в облаке только CPU + диск, видеокарты нет.** Из-за этого: +* выравнивание — **не горячий путь**: делается один раз на книгу и лежит на диске; +* объём работы режется тремя рычагами: эмбеддить не предложения, а СМЫСЛОВЫЕ ХОДЫ (43–78 на кусок + против сотен предложений) · сперва ЯКОРЯ (термины банка, числа, реплики, имена) прибивают + опорные точки, эмбеддинги нужны лишь на промежутках · модель меньше + int8/ONNX; +* **и главное разведение ролей:** в ЛАБОРАТОРИИ выравнивание — измерительный инструмент (проверить, + работают ли дешёвые механизмы); в ПРОДЕ адреса берутся из номеров смысловых ходов ПО ПОСТРОЕНИЮ, + и ни эмбеддингов, ни GPU там не нужно. ⇒ эмбеддинги — способ ПРОВЕРИТЬ более дешёвый механизм, + а не сам механизм. + +**Чего я не знаю:** как эти алгоритмы ведут себя на НАШЕЙ прозе. Заявленное качество меряют на +корпусах, где перевод следует за оригиналом близко; у нас редактор ОБЯЗАН сливать абзацы (77 строк +→ 43 абзаца). Числа из чужих статей сюда не переносятся. Плюс поле движется быстро — сверка с +текущим состоянием отдельным ресёрчем не помешает. + +**Чем мерить точность БЕЗ ручной разметки (у нас уже есть):** банк терминов — если отрезок +исходника содержит термин, выровненный отрезок перевода обязан содержать его канонную форму; +и маржевые посадки — мы сами знаем, куда вставили порчу, и выравнивание обязано указать туда же. + +--- + +## 15. СЕССИЯ №4 (20.08) — КУРС `PLAN-17-08.md`, ШАГ 1 + +Восстановление контекста после компакта: прочитаны ЦЕЛИКОМ заказ (`POLYGON_EXP2223_REDO_SESSION_PROMPT.md`, +207 строк), хендофф, `PLAN-16-08.md`, `PLAN-17-08.md`, этот журнал, `СВЕРКА-РЕВИЗИИ-16-08.md`, +бриф владельца `START_PROMT.MD` (V0–V6), отчёт секции Д17–Д20, CURRENT-STATE, инвентаризация `~/books`. + +### 15.1 ШАГ 1 — Г5 ЗАКРЫТ. $0, ни одного платного вызова + +Инструмент: `itog_d4.py --sens` (новый режим В СВОДЕ, отдельного файла не заводил — энтропия). +Плюс два рычага `--drop=`/`--restore=`, оба ПУСТЫ по умолчанию: печатаемый вердикт не меняется. +Правило решения записано в докстринге `sens()` ДО прогона. + +**Результат — вердикт «H-1 НЕ УСТАНОВЛЕНА» устоял.** Секция отчёта **Д21**. Ключевое: + +1. Арифметика находки P13 воспроизведена ПОБАЙТНО (n=13 · sd 1.4058 · порог 1.0917 · + `A6/A0` −1.0938 · запас +0.0020) — спор не о вычислении. +2. Переворот живёт только в сценарии, который ВОЗВРАЩАЕТ пачку аннулированной сессии и + ОДНОВРЕМЕННО снимает пачки-валидации: норма применена в одну сторону и отменена в другую. + На нынешнем дереве снятие валидации даёт запас **−0.0710** — ниже порога. +3. Посылка «другой режим замера» по УРОВНЮ не подтверждается: `д-01` отклоняется на +2.1 sd, + а неоспариваемая `д-21` соседнего набора — на +2.4 sd. По промту проверить нечем: транскрипт + `agent-aa9688762dc325180.jsonl` с диска исчез. +4. **Калибровка порога закрыта ПО ЗНАКУ** (находка ревизии №6, R73 против P40): шум самого + контраста против пола — `A1B/A0` 1.03× · `A3/A0` 1.01× · `A6/A0` **0.65×**. Пол на Д4 + откалиброван честно, а для несущего H-1 контраста ЗАВЫШЕН в 1.5×. Число R73 (1.40×) — + свойство прохода `border`, переносить не следовало. +5. **Шум судьи зависит от АРМА**: тот же текст, две сессии — `A0` 2.24, `A3` 3.18, `A1` 3.26. + Контурные тексты оцениваются в полтора раза менее устойчиво, чем перепис. +6. Наклон наборов реален (p1 строже p2 на +1.50 ошибки, пол +1.79, p=0.0117), но контрастов не + задевает: армы сбалансированы по половинам, снятие пачки убирает все армы единицы разом. + Контраст ВНУТРИ половины даёт те же числа до 4-го знака. + +**Честный итог сильнее печатаемого:** `A6/A0` во всех пяти способах счёта встаёт вплотную к порогу +(запас −0.62 · −0.07 · −0.51 · +0.002 · −0.007) при шаге шкалы в ОДНУ ошибку. Это «эффект ровно +размером с шумом прибора», то есть та же болезнь, что заход Д17 намерил на новом материале. + +### 15.2 ⛔ МОЯ ОШИБКА В ПЛАНЕ, НАЙДЕНА ЧТЕНИЕМ КОДА + +`PLAN-17-08.md` нёс строку «Г5: H-1 переворачивается, дорогой черновик ВСЁ-ТАКИ помогает». +**Неверно по знаку.** В своде минус = «арм ХУЖЕ эталона» (сверка: `A1B/A0` −3.53 при `A1B` 7.52 +против `A0` 4.00). Значит «перешёл порог» читалось бы «дорогой черновик + контур ЗНАЧИМО ХУЖЕ +связки» — H-1 опровергается, а не подтверждается. Строка в плане исправлена. + +### 15.3 Смежные находки ревизии, разобранные тем же заходом + +| находка | статус после проверки исполнением | +|---|---| +| **P13** H-1 переворачивается | **ЗАКРЫТА**: арифметика верна, вывод не следует (Д21.1–21.2) | +| **P12** пачка `69de717f3f` в замере | **ЗАКРЫТА ИСПОЛНЕНИЕМ**: на диске `.ANNULLED`, свод её не читает; база уже n=14/1.65. Строка сверки 16.08 устарела | +| **P07** гейт честности пола | **снята по существу**: сдвиг реален, объявлен, вердиктов не двигает (Д21.4) | +| **R73/P40** порог смещён в противоположные стороны | **ЗАКРЫТА ЗАМЕРОМ**: направление названо числом (Д21.3) | +| **P42** два пре-рег-правила о маржевом гейте | ⛔ **ОТКРЫТА — ВОПРОС ВЛАДЕЛЬЦУ.** Объявлена девиацией в Д21.6; сессия решать не вправе | + +### 15.4 Команды + +``` +eval/.venv/bin/python eval/dovodka/itog_d4.py --axis=d4 --sens # весь замер Г5 +eval/.venv/bin/python eval/dovodka/itog_d4.py --axis=d4 # базовый свод (не изменился) +``` + +### 15.5 ШАГ 2 — ПОКУПКА ОСТАНОВЛЕНА ГАРДОМ НА ПЕРВОЙ ЖЕ ГЛАВЕ. Что найдено + +Санкция владельца 20.08 «Хорошо, поехали» на $1.70. Построен `eval/dovodka/podacha.py` +(зафризен `650b4fe` вместе с потолками ФД-K/ФД-L). Куплено 4 клетки одной китайской главы, +потрачено $0.2927 из $0.40 — **гард отказал и остановил прогон**, как и положено по норме. + +**1. Цена клетки впятеро выше сметы.** $0.101 против сметных $0.0204. Причина найдена и +разведена БЕСПЛАТНО: не потолок вывода (арм `ZF` — побайтно тот же запрос, что боевая связка, +но с потолком 32000 против 16000 — даёт всего ×1.23 размышления на 16+16 клетках), а САМА +ПОСАДКА. На одной и той же главе, тем же промтом, той же моделью: чистый черновик 1 612 токенов +размышления, черновик с удалённым абзацем — **22 887, то есть ×14**. +⇒ **реальный дефект в черновике запускает ту же дорогую работу выравнивания, что и невыровненный +формат.** Это находка, а не помеха: цена критика определяется не длиной входа, а тем, сколько +несоответствий он вынужден локализовать. + +**2. Идея владельца работает — на первом же замере.** Та же глава, та же модель: +размышление 22 887 → 10 211 (−55%), цена $0.101 → $0.0485. +⚠ **Но находок стало вдвое меньше: 24 → 12.** Это ровно тот риск, который пре-рег объявил ДО +покупки, и он реализовался на первой клетке. + +**3. ⛔ ГЛАВНАЯ НАХОДКА, И ОНА НЕ ПРО ДЕНЬГИ: критик НЕ ВИДИТ ПРОПАВШЕГО АБЗАЦА.** Из черновика +удалён целый абзац («Хо Цзунь с трудом поднялся из-под обломков…», 124 знака). Критик выдал +24 находки, все локальные, и **не упомянул пропажу ни словом** — при том что в том же тексте +поймал снятую частицу «не». `glm-5` тоже не упомянул. Мандат критика прямо требует искать +«потери смысла». ⇒ **пропуски обязан ловить детерминированный гейт покрытия, а не критик.** + +**4. Мой детектор пропажи оказался негодным, и поймала его НУЛЕВАЯ МОДЕЛЬ** — 3 ложных +срабатывания из 4 на чистых клетках. Проверены ещё два правила: строгое (маркер пропажи И редкие +слова в ОДНОЙ строке) не срабатывает ВООБЩЕ ни на одной клетке; счётное (число находок класса +«пропущено») сигнала не даёт — на испорченной клетке их 2 против 2·3·3·7 на чистых. +⇒ эндпойнт «сохранил ли критик бдительность к пропускам» этим способом не меряется, потому что +базовая бдительность УЖЕ нулевая. + +**5. `glm-5` с включённым размышлением НЕ ВЛЕЗАЕТ в потолок вывода на китайском:** клетка +`dv-k-p0gl-13c5f52fa3` вернулась `finish=length` — деньги списаны, текста нет. Квирк-бюллетень +(:52) предупреждал, риск был объявлен в шапке файла до покупки, и он материализовался. + +**Что это меняет в дизайне.** Дорог именно абзацный пропуск; без него замер возвращается к +цене чистого критика (zh ~$0.008, en ~$0.042 за клетку) и ВЕСЬ план влезает в санкционированные +$1.70. Отказ от абзацной посадки ничего не теряет, потому что её эндпойнт уже отвечен: критик +пропуска не видит вовсе. Решение — за владельцем, сессия панель не режет. + +### 15.6 РЕШЕНИЕ ВЛАДЕЛЬЦА 20.08: ПРОГОН ОСТАНОВЛЕН + +Дословно: «Прогон предлагаю стопнуть раз критик не находит проблемы пропуска абзацев он скорее +всего еще сильнее будет деградировать». Английская пара НЕ покупалась вовсе (ФД-L $0.00); +на китайской остались 4 клетки одной главы, $0.2927. Процессов нет, замков нет, дерево чистое. + +⚠ **МОЯ ФОРМУЛИРОВКА ВВЕЛА ВЛАДЕЛЬЦА В ЗАБЛУЖДЕНИЕ, И ЭТО МОЯ ОШИБКА, А НЕ ЕГО.** Я написал +«абзацная посадка» и «словесная посадка», и он прочёл это как ФОРМУ ПОДАЧИ — будто предлагается +кормить критика таблицей «слово ↔ слово». Ничего такого в замере нет: «посадка» — это намеренная +порча, которую я вношу в черновик, чтобы знать правильный ответ заранее; вход критика всё время +остаётся прежним, целый исходник плюс целый перевод. Термин рига был употреблён в разговоре с +владельцем без расшифровки — прямое нарушение нормы «продуктовые формулировки, термины +расшифровывать при первом употреблении». + +**Возражение владельца по существу — верное и в замере подтверждается.** Дробить вход мельче +означает увеличить число кусков, которые модель обязана сопоставить; фразеологизм и идиома +живут в границах фразы, а не слова, и наш собственный критик ловит их именно как ЦЕЛОЕ («愿赌服输 +— смысл „проиграл — расплачивайся“, а не „уговор дороже денег“»). Мельчить нельзя. + +**Что остаётся правдой после остановки** (одна глава, 3 годные клетки — сигнал, не вывод): +1. фраза о параллельности срезает размышление на 55% и цену вдвое, но и находки вдвое; +2. критик не видит удалённого абзаца, ловя при этом снятую частицу «не» в том же тексте; +3. реальный дефект в черновике удорожает критика в 14 раз против чистого; +4. `glm-5` с включённым размышлением не влезает в потолок вывода на китайском. + +### 15.7 ⛔ ПОПРАВКА К МОЕМУ ЖЕ ОБЪЯСНЕНИЮ: «КИТАЙСКИЙ КРИТИК ПОЛУЧАЛ ГОТОВЫЕ ПАРЫ» — НЕВЕРНО + +Вопрос владельца 20.08: «а если критику сказать, что текст выровненный, перевод идёт след в след?» +Проверено ДО ответа, бесплатно, на 16 китайских главах. + +**Счёт кусков совпадает, ПОЗИЦИИ — нет.** + +``` +ровно 1:1 по числу кусков 1 глава из 16 (расхождение 0…17) +корреляция длин по одинаковому номеру медиана +0.12 (≈ ноль) +реплика исходника на том же номере, что реплика перевода 193/385 = 50% + при шансовом поле ≈33% (такова доля реплик в тексте) +``` + +⇒ **«след в след» — НЕПРАВДА и на китайском тоже.** Прежняя запись §14.7 («китайский критик +действительно получил готовые пары») выведена из МЕДИАН счёта кусков (78 строк → 80 абзацев, +0.96:1) и позиционной проверки не проходит. Медиана скрыла разброс. + +**Что от объяснения уцелело — не выравненность, а ЗЕРНИСТОСТЬ.** На китайском обе стороны нарезаны +кусками по 24–30 знаков и идут примерно в одном порядке, поэтому критик работает локально даже при +сдвиге на пару позиций. На английском одна строка против восьми абзацев — локальной опоры нет +вовсе. Разрыв размышления ×9 замерен и остаётся фактом; моё объяснение его ослаблено. + +**Побочное наблюдение, полезное для дела:** глава `9120ad112e` — единственная, где счёт кусков +совпал точно (79/79), и там же якорь реплик даёт **96%**. То есть соответствие держится, пока не +случится слияние или дробление, после чего сдвигается ВСЁ последующее. Значит лечение — не «сказать +модели», а дешёвая ПЕРЕ-СИНХРОНИЗАЦИЯ по якорям (реплики уже дают 50% против шансовых 33%). + +⚠ И вывод для формулировки: врать модели нельзя не по этике, а по механике — пре-рег объявил +заранее, что при ложном обещании точных пар модель будет искать соответствие, не найдёт и потратит +БОЛЬШЕ. Правдивая версия (порядок сохранён · покрытие обязано быть полным · членение может +отличаться · точных пар не искать) уже куплена и дала −55% размышления при −50% находок. + +### 15.8 ГИПОТЕЗА, РАДИ КОТОРОЙ СТОИТ ЖИТЬ ДАЛЬШЕ — И ЕЁ ЦЕНА, СНЯТАЯ С КУПЛЕННОГО ($0) + +Владелец 20.08: «подумать и придумать ахуенное решение, вероятную гипотезу которую можно проверить». +Ниже — четыре бесплатных замера по УЖЕ КУПЛЕННОМУ сырью, в порядке, в котором они делались. + +**Замер 1 — снос регистра. ГИПОТЕЗА НЕ ПОДТВЕРДИЛАСЬ.** Идея: порядок владельца решается не +смыслом, а регистром (он назвал «шествовала» вместо ярости, смягчённый мат, вежливое «Пойдёмте» в +выплюнутой реплике). Посчитал на его же панели плотность мата, восклицаний, длину фразы, долю +реплик — против исходника. Порядок владельца (`ZP > ZF > Z1 > Z0`) НЕ воспроизводится: признаки +дают `ZF > Z1 > Z0 > ZP`, его фаворит последний. Причина видна: на прочитанных им главах мата +0.61 и 0.00 на 1000 знаков, восклицаний ноль, длины фраз в пределах пары процентов — агрегатные +признаки регистра варианты не различают вовсе. Идея снята, $0. + +**Замер 2 — РАЗМЕР ПРИЗА. Он больше всего, что мерила дуга.** По 32 прочитанным единицам +(обе пары, оба машинных читателя): + +``` +размах между ВСЕМИ архитектурами (Z8R 2.16 … Z1 2.88) 1.78 позиции +разброс ДВУХ ПРОГОНОВ ОДНОЙ связки (Z0 против ZF) 2.59 позиции +выигрыш «взять лучшую из двух» против «взять любую» +1.30 позиции +лучшая смена архитектуры (Z0 2.50 → Z8R 2.16) +0.34 позиции +``` + +⇒ **выбор ПРОГОНА стоит вчетверо больше выбора АРХИТЕКТУРЫ.** Цена — один лишний дешёвый вызов +против полной пере-сборки конвейера. + +**Замер 3 — ПРИЗ ОРАКУЛЬНЫЙ, И ЧИТАТЕЛЬ ЕГО НЕ БЕРЁТ.** Два независимых машинных читателя, +выбирая между теми же двумя прогонами, совпали **9 из 16 = 56% при случайных 50%, p=0.40**. +Монетка. То есть +1.30 — это ПОТОЛОК при идеальном отборщике, а не достижимая величина. +⚠ Здесь же поймана моя ошибка в пробе: ответы читателей лежат в `.md`, а я искал `.txt`, и первая +редакция замера напечатала «прочитано обоими: 0». Число 56% снято после починки. + +**Замер 4 — ⭐ ГДЕ ОТБОРЩИК ВСЁ-ТАКИ РАБОТАЕТ.** Посчитал текстовое расхождение двух прогонов +(`difflib`, $0) и сверил с согласием читателей: + +``` +более РАЗЛИЧНАЯ половина (расхождение ≥10% текста): читатели совпали 6/8 = 75% +более ПОХОЖАЯ половина (расхождение <10%): совпали 3/8 = 38% — ниже монетки +четыре самые различные главы (16…38%): совпали 4 из 4 +самая похожая (0.3% расхождения): разошлись +zh: медиана расхождения 18% · согласие 6/8 · en: медиана 6% · согласие 3/8 +``` + +⇒ **разница zh/en — не языковая, а РАСХОЖДЕНЧЕСКАЯ**, и это независимо подтверждает поправку +владельца из Д20.6 («дело не в языке, а в диапазоне шкалы»), снятую на другом приборе. + +**ГИПОТЕЗА К ПРОВЕРКЕ (шаг 2 нового курса вместо остановленного):** +> Два прогона одной дешёвой связки плюс БЕСПЛАТНЫЙ детектор расхождения. Разошлись сильно — +> выбор осмыслен и его берёт читатель-модель; почти совпали — выбирать нечего, берём любой и +> не платим. Приз до +1.30 позиции против ≤0.34 у любой смены архитектуры. + +⚠ **Порог 10% выбран ПО ЭТИМ ЖЕ ДАННЫМ — это разведка, а не проверка.** Гипотеза обязана +проверяться на НОВЫХ главах с порогом, объявленным ДО них. n=16, разбиение по медиане post-hoc. + +**Смета проверки:** вторая генерация связки ~$0.027/глава (замер), 16 глав × 2 пары = **$0.86**; +чтение бесплатно (агент-сессии). Плюс уже потраченные $0.29 на остановленный замер подачи. + +### 15.9 ВОПРОС ВЛАДЕЛЬЦА 20.08 ПРО ПРОМТ ОДНОПРОХОДКИ — ОТВЕТ ПО КОДУ, НЕ ПО ПАМЯТИ + +«Ты писал хороший промт для неё, чётко ограниченный? Который я писал в стартпромте?» + +**НЕТ.** `contour.a2_msgs` собирает однопроходку так: боевой промт ПЕРЕВОДЧИКА плюс мандатная +часть боевого промта РЕДАКТОРА, механически склеенные — четыре строки выброшены (`A2_DROP`), +четыре подставлены (`A2_SUBST`). Среди подстановок есть прямая мета-фраза про наш конвейер: + +``` +"Твой мандат — художественная редактура черновика со сверкой по исходнику:" + → "Отдельного редактора после тебя НЕ БУДЕТ, поэтому мандат редактуры входит в твой:" +``` + +**Почему так сделано — основание честное, но оно измерительное, а не продуктовое.** Контраст +«однопроходка против связки» в эксп-21 был загрязнён: у арма без редактора системный промт вдвое +короче (×1.92), и замеренная разница могла быть разницей ОБЪЁМА ИНСТРУКЦИИ, а не топологии. +Уравнивание было правильным ДЛЯ ЗАМЕРА. Цена — **однопроходка ни разу не испытывалась как +продуктовый промт**, только как уравненный по объёму двойник связки. + +**И при этом она уже выигрывает, неся гандикап:** по судейской оси от связки не отличается; по +банку лучше на ОБЕИХ парах (потерянных терминов на главу 1.38 против 2.88 на zh, 0.31 против 0.56 +на en); при слепом чтении владелец поставил её ПЕРВОЙ на английском; стоит один вызов вместо двух. + +**Цена двух прогонов однопроходки против одной связки — по текущему прайсу, из замеренных токенов:** + +``` +zh: связка (черновик $0.00514 + перепис $0.02016) = $0.02531 · две однопроходки $0.03498 = 1.38× +en: связка (черновик $0.00193 + перепис $0.03527) = $0.03720 · две однопроходки $0.04235 = 1.14× +``` + +⇒ За 1.14–1.38 цены нынешнего прода можно получить ДВА независимых текста и выбрать лучший. +Удвоение самой связки стоило бы 2.0×. **Это соединяет самый сильный измеренный кандидат (E) с +самым сильным измеренным рычагом (A) почти без доплаты.** + +**Что осталось дотестировать в однопроходке, по убыванию:** +1. **Промт начисто как РОЛЬ** (пункт 3 брифа V6): роль литературного переводчика · оригинал плюс + ТОЛЬКО релевантный банк · рамки, а не бесконечные правила · критерии проверки результата · + БЕЗ мета-фразы про отсутствующего редактора. Сравнить со склейкой. +2. **Банк без черновика** — единственное измеренное слабое место. Терминолог опирается на то, как + термин передавали черновики. Замер детерминированный, судейства не требует. +3. **Границы сцен как единица нарезки** (тот же пункт брифа) — сейчас режем по числу знаков, + не делалось ни разу. +4. **Два прогона однопроходки** плюс детектор расхождения — см. цену выше. + +### 15.10 ⛔ ВЛАДЕЛЕЦ ПОЙМАЛ ПОТЕРЮ ПРАВИЛ В НОВОМ ПРОМТЕ. Покупка остановлена, гейт заведён + +Вопрос 20.08: «Ты этот промт перегенерил на основе двух имеющихся? Там же довольно много +пропущено? Ты читал оригиналы промтов переводчика и редактора?» + +**Читал — и всё равно потерял три правила.** Покупка остановлена немедленно; успели уйти только +три пред-полётные пробы ($0.00174), боевых клеток НЕТ. Просроченный замок мёртвого процесса снят +законно (процесс проверен — его нет). + +**Что было потеряно, построчной сверкой с `backend/prompts/zh-ru/{translator,editor}.md`:** + +1. ⛔⛔ **«Повтор, стоящий в параллельных позициях самого исходника, — авторский рефрен: сохраняй + его, не разнообразь лексику там, где автор повторяется намеренно.»** Потеря ОПАСНАЯ: мой новый + промт прямо говорит «буквальность здесь не достоинство» и разрешает синонимическую замену, + то есть толкает ровно в обратную сторону. Модель вычистила бы авторский рефрен как «лишний + повтор». Возвращено, и не отдельным пунктом, а ГРАНИЦЕЙ внутри блока «ТЫ ВПРАВЕ» — там, где + свобода объявлена, там же назван её предел. +2. **«Кальки жестов и идиом · неверно понятые реалии»** с конкретным примером (поклон как «ударил + головой»). Возвращено запретом 3: жест, идиома и реалия названы тремя местами, где подстрочник + врёт чаще всего. +3. **«Не пересочиняй сцены».** Возвращено в запрет 2. + +Плюс одно СОЗНАТЕЛЬНОЕ снятие, теперь объявленное явно: боевое «не сокращай» отменено решением +владельца 16.08 («вольность не ошибка»); полнота охраняется не запретом сокращать, а запретом +терять смысловые ХОДЫ — то есть на уровне содержания, а не числа слов. + +**Заведён ГЕЙТ ПОКРЫТИЯ (`rol.py --coverage`, часть селфтеста).** Каждое правило обоих боевых +промтов перечислено характерной подстрокой, у каждого объявлена ДИСПОЗИЦИЯ, и селфтест роняет +замер, если хоть одно правило не покрыто: + +``` +"в промте" правило стоит в ядре нового промта (проверяется вхождением ключевых слов) +"блок пары ДОСЛОВНО" правило приезжает пар-блоком — сверяется САМА СТРОКА боевого промта +"покрыто смыслом" покрыто более полным местом промта, но не дословно; место названо +"снято" снято сознательно, с основанием прямо в таблице +``` + +Итог гейта: **zh — 38 правил, непокрытых 0** (18 в промте · 7 дословно блоком · 4 смыслом · +9 снято); **en — 43 правила, непокрытых 0** (18 · 12 · 4 · 9). + +⚠ Различие «дословно» и «смыслом» заведено ОТДЕЛЬНЫМИ диспозициями намеренно: смешать их значит +потерять ровно ту границу, на которой первая редакция и погорела. И проверка блока сверяет строку, +а не начало блока: слабая проверка пропустила бы усечённый блок. + +⚠ **Урок шире этого файла: обещание «я всё перенёс» проверяемо только механизмом.** Гейт вдобавок +сломается ГРОМКО, если чужая зона правит боевой промт, — сейчас такая правка молча меняла бы смысл +арма. + +**Мнение Fable-5 заказано ДО покупки** (просьба владельца): полный контекст проекта, оба боевых +промта, новый промт, гейт покрытия; вопросы — как он зажимал бы промт для такой задачи, разбор +построчно, что мы потеряли (сверить самому, нашей таблице не доверять), и замечания по замерам. + +### 15.11 ПРИЁМКА FABLE-5 ПРОМТА-РОЛИ (20.08). Три дефекта гейта, один — тяжёлый + +Заказана владельцем ДО покупки. Всё существенное пере-проверено моим исполнением, не принято +на слово. + +**ПОДТВЕРЖДЕНО ИСПОЛНЕНИЕМ:** + +1. ⛔⛔ **Гейт покрытия сертифицировал правило словом из правила ПРОТИВОПОЛОЖНОГО смысла.** + Строка `("editor","лишние повторы","промт","разнообразь")` считала боевое «убирай лишние + повторы» покрытым, потому что слово «разнообразь» есть в промте — но стоит оно в + РЕФРЕН-ГРАНИЦЕ, то есть там, где повторы убирать ЗАПРЕЩЕНО. Требования убирать немотивированный + повтор в новом промте не было вовсе (проверено грепом). Гейт, заведённый именно против потерь, + сам произвёл потерю и напечатал «покрыто». Сертификат пере-привязан; в промт и в критерий ЯЗЫК + вернулось «немотивированные повторы убирай». +2. **Короткие правила гейт не сканировал вовсе:** порог `len(s) < 25` выбрасывал строку + «- Сноски: {{footnotes}}» (23 знака). Порог снижен до 10; правил на zh стало 39 против 38. +3. **Мёртвые строки таблицы неотличимы от живых:** три строки не срабатывали ни разу + (пере-считано точно; первая грубая проверка дала 14 — моя ошибка сопоставления по усечённой + строке). Заведена проверка «каждая строка COVERAGE обязана сработать хотя бы раз», две + строки-заголовка удалены как мёртвые. +4. **Потеряна оговорка «Максимума длины абзаца нет»** — клауза внутри покрытого буллета. + Гейт слеп к потерям на уровне полуфразы; класс зафиксирован, оговорка возвращена. + +**НЕ ПОДТВЕРДИЛОСЬ:** висячая ссылка «в глоссарии ниже» при отсутствии банк-блока — на всех +32 главах обеих пар термы банка есть, ссылка всегда обеспечена. Дефект латентный, а не живой; +условность «(если он приложен)» всё равно возвращена. + +**ПРИНЯТО ИЗ РАЗБОРА, сверх дефектов** (каждое — одна строка, промт не разбух): +* «Живой не значит нарядный: если герой идёт — он идёт, а не шествует» — закрывает КЛАСС, из + которого пришла главная претензия владельца, а не отдельный случай; +* «Сворачивая описание, ты сжимаешь СЛОВА, а не факты» — снимает рассогласование зернистости: + ядро охраняло смысловые ХОДЫ, пар-блок — смысловые АТОМЫ, и «сворачивать описание» было + легально по одному порогу и нелегально по другому; +* мотивация канона («книга выходит на тысячу с лишним глав, форма имени обязана совпасть») — + правило Anthropic «давай мотивацию, а не только запрет»; +* оговорка про иноязычные вставки — иначе «не оставляй исходный язык» выполет французский слой, + который пар-правила велят СОХРАНЯТЬ; +* пример «поклон — не ударил головой» обезличен: это китайский кэтоу, уехавший в пар-нейтральное + ядро, — ровно та утечка, которую запрещает гардрейл общности; +* удалён дублирующий запрет (гигиена вывода стояла дважды) — правило Anthropic «минимально + необходимая структура»; +* императив «проверь себя по ним сам, молча» снят, критерии оставлены УТВЕРЖДЕНИЕМ. Основание + двойное: документация Anthropic прямо предупреждает, что перенесённые инструкции самопроверки + дают ИЗБЫТОЧНУЮ проверку — лишние токены и латентность; а у нас размышление тарифицируется как + выход и составляет 85% цены дорогой клетки. **Дельта `reasoning_tokens` RN против ZP объявляется + обязательным печатаемым числом отчёта** — проверка бесплатна, токены уже пишутся в клетки. + +### 15.12 ⭐ КРОСС-ЧИТАТЕЛЬСКИЙ ПРИЗ — БЕСПЛАТНЫЙ ЗАМЕР, КОТОРЫЙ ПЕРЕВОРАЧИВАЕТ ВАРИАНТ A + +Возражение Fable: приз «лучший из двух прогонов» (+1.30 позиции) измерен ТЕМ ЖЕ прибором, которым +предлагается выбирать, — круговое рассуждение. Развязка стандартная: выбирает прибор S, оценивает +прибор E ≠ S. У нас ДВА независимых читателя, значит считается за $0 на купленном. + +Метод: выбор читателя A между двумя прогонами связки, а оценка выигрыша — по рангам читателя B +(и симметрично). + +``` +ВСЕ главы: n=16 · выигрыш +0.28 позиции · 18 подтвердили, 14 опровергли · p=0.2983 +расхождение ≥10% текста: n=7 · выигрыш +2.36 позиции · 12 подтвердили, 2 опровергли · p=0.0065 +расхождение <10%: n=9 · выигрыш −1.33 позиции · 6 подтвердили, 12 опровергли · p=0.9519 +``` + +⇒ **Три вывода, и все три меняют картину:** +1. **Наивный «лучший из двух» бесполезен** — в среднем +0.28 при p=0.30, то есть шум. Круговой + приз +1.30 был в основном регрессией к среднему на шуме ранжировщика, как Fable и сказал. +2. **Но на разошедшихся главах приз РЕАЛЕН и БОЛЬШЕ оракульного: +2.36 позиции, p=0.0065** — + и это уже НЕ круговое число: выбирал один читатель, оценивал другой. +3. **На похожих главах отбор ВРЕДИТ: −1.33.** То есть применять его вслепую хуже, чем не + применять вовсе. Бесплатный детектор расхождения перестаёт быть оптимизацией и становится + УСЛОВИЕМ работоспособности схемы. + +⚠ Порог 10% по-прежнему выбран post-hoc на этих же данных, n=7 против n=9. Кросс-читательская +конструкция снимает КРУГОВОСТЬ, но не снимает подгонку порога: он обязан быть заморожен пре-регом +и проверен на новых главах. + +### 15.13 РЕШЕНИЕ ВЛАДЕЛЬЦА 20.08: СМЕНА ПРИБОРА. Первичным становится СЛЕПОЕ ЧТЕНИЕ + +Дословно: «меняем принцип судейства. Вот тот текст что ты давал мне на слепое чтение, теперь корми +фабл 5 и пусть он решает какой из вариантов ближе по художественному смыслу, лучше по переводу». + +**Что запущено.** Fable-5 читает ТУ ЖЕ панель, что читал владелец (`ЧТЕНИЕ-{zh,en}.md`, армы +`Z0`/`ZF`/`ZP`/`Z1`), двумя отдельными сессиями — по одной на пару, чтобы порядок одной не +переносился на другую. Ответы в `ОТВЕТ-{zh,en}.FABLE.md`, файл владельца не трогается. + +⚠ **Идентичность читателя записана ДО запуска** — `blind-keys-chtenie-z17/READERS-fable-vladelets.json`. +В заходе Д17 я записал её постфактум, и это было объявлено нарушением нормы; здесь порядок +соблюдён. Сессия зарегистрирована `runs.py` ДО запуска (#91, судья д-91). + +⚠ Читателю запрещены: `blind-keys*`, `dv-*.json`, код зоны, каталоги `~/books/dovodka`, +`~/books/chtenie-z17`, `~/books/judging`, файл ответа владельца, дифф-инструменты и дочерние +агенты. Транскрипт проверяется греп-аудитом ПОСЛЕ — механизм тот же, что у судейских сессий. + +**Что это решает.** Панель та же, значит порядок владельца становится ЭТАЛОНОМ. Совпадёт порядок +Fable с его порядком — у нас есть масштабируемый первичный прибор вместо счётчика ошибок, +у которого Спирмен с владельцем на английском **−0.64**. Разойдётся — прибор не готов, и это тоже +результат за $0. + +### 15.14 ЭКСТРАКТОР: почему чинить его на месте нельзя и что сделано вместо + +Владелец: «Ну почини экстрактор. В чём проблема то?» + +**Проблема механическая и замерена.** `bakeoff.uid_of` = `sha1(source.strip())`, то есть uid +единицы есть хеш ТЕКСТА ЦЕЛИКОМ, вместе с пробелами. Возврат абзацев меняет пробелы ⇒ меняет все +uid. Цена: **289 оплаченных клеток на $2.6385** теряют адресата, плюс семь файлов слепых ключей и +все судейские ответы английской пары. Манифест это поймает и остановит покупку (гейт работает), +но история оси всё равно умрёт. + +**Что сделано вместо.** Починка НА ПОДАЧЕ: тот же текст той же единицы, тот же uid, но с +возвращёнными границами абзацев из epub. Функция взята у `podacha.py`, второй раз не написана; +проверено — текст всех 16 английских единиц находится в абзацной версии побайтно. + +⚠ **И это ОТДЕЛЬНЫЙ АРМ `RA`, а не подмена входа у `RN`.** Иначе несущий контраст смешал бы два +эффекта, и разделить их было бы нечем: +``` +RN / ZP — эффект ПРОМТА (вход у обоих ОДИНАКОВЫЙ, плоский) +RA / RN — эффект ПОДАЧИ (промт у обоих ОДИНАКОВЫЙ, новый) +``` +У китайской пары `RA` невозможен по построению: её исходник абзацы не терял. + +⛔ **ДОЛГ ЧУЖОЙ ЗОНЕ, назвать оркестратору:** настоящая починка — в бэкенде, у продуктового +экстрактора epub. Полигонный `pair_en.raw_text` чинить нельзя ценой оси; бэкенд правится своей +зоной. Пинг обязателен. + +### 15.15 РЕФАКТОРИНГ ПРОМТА И ЧТО ПОЙМАЛ СОБСТВЕННЫЙ ГЕЙТ + +Возвращённые правила и правки по Fable раздули ядро с 2743 до 3835 знаков — системный промт стал +**1.22× склейки** вместо 1.05×, то есть вернулся конфаундер эксп-21 (там было ×1.92, и вывод +оказался про ОБЪЁМ инструкции, а не про топологию). + +Лечение — то, что советовали и владелец («можно дорефакторить или что-то выкинуть»), и Fable, и +документация Anthropic («минимально необходимая структура»): **два пересекавшихся списка сведены +в ОДИН.** Было «ЧЕГО НЕЛЬЗЯ — четыре запрета» плюс «ПО КАКИМ КРИТЕРИЯМ — пять критериев», причём +запрет про жест/идиому размазывался между СМЫСЛОМ и ЯЗЫКОМ, гигиена вывода была только в +запретах, а РЕГИСТР — только в критериях. Модель получала две почти совпадающие таксономии и +должна была сама решать, какая главная. Стало: **пять осей, у каждой в теле — что считается +нарушением**, и одна строка «других запретов нет». Объём 1.19×. + +⚠ **ГЕЙТ ПОКРЫТИЯ ПОЙМАЛ МОЙ ЖЕ РЕФАКТОРИНГ:** после слияния списков **девять** правил боевых +промтов потеряли сертифицирующие подстроки («Перевирать факт» → «Перевран факт», «Терять +смысловые ходы» → «Пропал смысловой ход» и так далее). Правила остались, слова изменились — +и гейт честно упал, вместо того чтобы промолчать. Последний случай был тоньше прочих: подстрока +«жест, идиома и реалия» не нашлась, потому что фраза разорвана переносом строки; сертификат +пере-привязан на «подстрочник врёт». Это первое подтверждение, что гейт ловит не только чужие +правки, но и мои собственные. + +### 15.16 ⭐ НОВЫЙ ПРИБОР ОТКАЛИБРОВАН ПРОТИВ ВЛАДЕЛЬЦА. И моя же подсказка в пакете снята замером + +**Прогон 1 — пакет, который читал владелец.** Fable-5 воспроизвёл его порядок ТОЧНО на обеих парах: + +``` +zh владелец Z0 > ZF > ZP > Z1 Fable Z0 > ZF > ZP > Z1 Спирмен +1.00 +en владелец ZP > ZF > Z1 > Z0 Fable ZP > ZF > Z1 > Z0 Спирмен +1.00 +``` + +Против счёта ошибок на английском у обоих **−0.80**. Контроль шума на китайском (две генерации +одной связки) Fable взял так же, как владелец, — поставил соседями; машинные читатели захода Д17 +на той же оси его ПРОВАЛИЛИ, разведя ту же пару на три позиции. Аудит обеих сессий чист: по два +вызова инструментов, ключей и диффов не касались. + +⛔ **НО В ПАКЕТЕ БЫЛА МОЯ ПОДСКАЗКА, и я нашёл её сам, перечитав собственный текст.** Шапка +пакета говорила читателю: «вариант с наименьшим числом ошибок читался почти хуже всех». Это +наводка, и она была в том же пакете, который читал ВЛАДЕЛЕЦ, — то есть часть совпадения могла +объясняться общим внушением от меня, а не общим вкусом. + +**Прогон 2 — НЕЙТРАЛЬНЫЙ пакет.** Тексты и метки побайтно те же (проверено ассертом), рамка +переписана без единого упоминания счётчика, контроля и расхождений. Свежие читатели, чистый +контекст. Английская пара: + +``` +владелец ZP > ZF > Z1 > Z0 +Fable с подсказкой ZP > ZF > Z1 > Z0 Спирмен с владельцем +1.00 +Fable БЕЗ подсказки ZF > ZP > Z1 > Z0 Спирмен с владельцем +0.80 +счёт ошибок Z0 > ZF > Z1 > ZP Спирмен с владельцем −0.80 +``` + +⇒ **Подсказка стоила 0.20 и перестановки двух верхних мест. ГЛАВНОЕ УСТОЯЛО: боевая связка `Z0`, +у которой по счётчику НОЛЬ ошибок, у чистого читателя по-прежнему ПОСЛЕДНЯЯ из четырёх.** +Значит вывод «счётчик и читатель на английском смотрят в разные стороны» — не артефакт моей +наводки. Это его первая настоящая проверка. + +⚠ **И сверх задания: чистый читатель НЕ ЗНАЛ, что в панели есть контрольная пара** (из нейтральной +рамки это убрано), и всё равно написал «Т4 — почти клон Т2». Т4=`Z0`, Т2=`ZF` — это ровно две +генерации одной связки. Он опознал их чтением, без подсказки и без диффа. + +⚠ При этом развёл он их на 1-е и 4-е места. По букве гейта это «порядок читателя = шум», но Д20.3 +уже установила предел этого гейта: он не умеет отличить «читатель шумит» от «арм шумит», а на +английской паре две генерации связки расходятся сильно — это замерено независимо (расхождение +текста, §15.12). + +**Норма, заведённая этим:** пакет слепого чтения не имеет права называть читателю ни вердикт +другого прибора, ни существование контроля. Прежняя рамка объясняла ему, зачем всё это нужно, — +и объясняла слишком много. + +### 15.17 ИТОГ КАЛИБРОВКИ НОВОГО ПРИБОРА — обе пары, чистый пакет, чистый аудит + +``` +пара кто порядок Спирмен с владельцем +zh ВЛАДЕЛЕЦ (эталон) Z0 > ZF > ZP > Z1 +1.00 +zh Fable, пакет С ПОДСКАЗКОЙ Z0 > ZF > ZP > Z1 +1.00 +zh Fable, пакет ЧИСТЫЙ Z0 > ZP > ZF > Z1 +0.80 +zh счёт ошибок (СТАРЫЙ) ZF > Z0 > ZP > Z1 +0.80 + +en ВЛАДЕЛЕЦ (эталон) ZP > ZF > Z1 > Z0 +1.00 +en Fable, пакет С ПОДСКАЗКОЙ ZP > ZF > Z1 > Z0 +1.00 +en Fable, пакет ЧИСТЫЙ ZF > ZP > Z1 > Z0 +0.80 +en счёт ошибок (СТАРЫЙ) Z0 > ZF > Z1 > ZP −0.80 +``` + +⇒ **НОВЫЙ ПРИБОР СОГЛАСЕН С ВЛАДЕЛЬЦЕМ ОДИНАКОВО НА ОБЕИХ ПАРАХ (+0.80 и +0.80). +СТАРЫЙ согласен на китайской (+0.80) и АНТИ-согласен на английской (−0.80).** +Это и есть основание сменить прибор: дело не в том, что счётчик хуже вообще, а в том, что он +меняет ЗНАК от пары к паре, а чтение — нет. + +**Значимость посчитана точным перестановочным счётом, а не на глаз.** На панели из четырёх +вариантов случайная перестановка даёт Спирмена ≥ +0.80 в 4 случаях из 24 (0.167). Обе пары +независимо дали ≥ +0.80 ⇒ **p = 0.028**. Пакет с подсказкой дал точное совпадение на обеих +(1/24 каждая) ⇒ p = 0.0017. +⚠ Это счёт по ОДНОЙ панели на пару. Он говорит «совпадение вряд ли случайно» и НЕ говорит +«прибор работает на книге»: 4 варианта × 2 панели — калибровка, а не валидация. + +**Что чистый читатель сделал СВЕРХ задания, не зная о контроле** (из нейтральной рамки убрано +всякое упоминание, что контрольная пара существует): +* на английском назвал «Т4 — почти клон Т2»; это `Z0` и `ZF`, две генерации ОДНОЙ боевой связки; +* на китайском назвал «Т4 и Т1 дословно совпадают целыми фразами, Т1 читается как редактура этого + же черновика» — то есть опознал родство армов ЧТЕНИЕМ, без диффа и без подсказки; +* независимо от владельца нашёл сквозной дрейф пола персонажа между отрывками — класс дефекта, + который наш судья не видит ПО ПОСТРОЕНИЮ (он работает поотрывочно). + +**Аудит обеих чистых сессий:** по 2 вызова инструментов, ноль обращений к ключам, сырью, коду, +чужим ответам и дифф-инструментам. + +**Что снято и что осталось.** Снято подозрение, что совпадение произведено моей наводкой: на +чистом пакете верх и низ порядка устояли, боевая связка с НУЛЁМ ошибок по счётчику осталась +последней у читателя. Осталась перестановка двух средних мест — она и есть цена подсказки (0.20). + +### 15.18 ⛔ РЕЗУЛЬТАТ ЗАМЕРА ПРОМТА-РОЛИ: лучше склейки, но не стоит своей цены + +**Слепое чтение, первичный прибор (решение владельца 20.08).** Панель: прежняя склейка `ZP` · +новая роль `RN` · боевая связка `Z0` · её вторая генерация `ZF`. Три самые длинные китайские +главы с полной панелью, рамка нейтральная, ключ отдельно, идентичность читателя записана ДО +запуска, сессия #92 зарегистрирована ДО. Аудит: 6 вызовов, только файлы пакета, нарушений 0. + +``` +общий порядок: Z0 > ZF > RN > ZP +отрывок 1: Z0 > ZF > ZP > RN +отрывок 2: Z0 > RN > ZF > ZP +отрывок 3: RN и ZF в паритете +контроль шума (две генерации связки Z0/ZF): места 1 и 2 — СОСЕДИ, гейт ВЗЯТ +``` + +**Что это значит по существу:** + +1. **Новая роль БЬЁТ прежнюю склейку** (3-е место против 4-го). Направление правки верное. +2. **Но обе однопроходки проигрывают боевой связке**, причём связка занимает оба верхних места + ОБЕИМИ своими генерациями — то есть выигрыш не случайный розыгрыш. +3. **Выигрыш над склейкой — одна позиция на трёх отрывках, и по отрывкам он неустойчив:** + на первом `RN` последний, на втором второй. Внутри разброса. +4. **Цена: ×6.3 против склейки** ($0.1116 против $0.0177), плюс 3 клетки из 13 оборваны на + потолке вывода. ⇒ **новая роль своей цены НЕ СТОИТ на этих уликах.** + +⭐ **ДИАГНОЗ ЧИТАТЕЛЯ, и он объясняет ОБА числа сразу.** Про новую роль он написал: +«самый точный по деталям … но проза самая серая: гладкопись подстрочника». То есть промт сделал +модель ТОЧНЕЕ и МЕРТВЕЕ — ровно наоборот замыслу, при том что вольность в нём разрешена явно, +а поднимать слог запрещено прямым текстом. + +Связная гипотеза, объясняющая и ×26 размышления, и серую прозу: **я напечатал в промте пять осей, +по которым результат будут проверять, — и модель занялась удовлетворением чек-листа.** Она думает +в 26 раз больше, потому что взвешивает пять критериев; и пишет площе, потому что оптимизирует +проверяемое, а не читаемое. Это ровно тот отказ, о котором предупреждали и Fable («не сделает ли +блок критериев модель осторожной вместо смелой»), и документация Anthropic (перенесённые +инструкции самопроверки дают избыточную проверку). Я снял оттуда императив, но оставил перечень — +и этого хватило. + +⚠ **Статус: ОПИСАТЕЛЬНО.** Три отрывка, одна пара, один читатель. Это направление и механизм, +а не доказанная величина. + +⭐ **ВТОРОЕ НЕЗАВИСИМОЕ ПОДТВЕРЖДЕНИЕ ОСТРОТЫ ЧИТАТЕЛЯ:** не зная, что в панели есть контроль, +он написал «Т1 и Т4 читаются как черновик и доведённая редакция одного текста». Т1 и Т4 — это +`Z0` и `ZF`, две генерации ОДНОЙ связки. Он опознал их чтением уже второй раз подряд, на другой +панели и в другой сессии. + +**Что из этого следует для курса.** Проверяемая и дешёвая ветка теперь одна: **выкинуть из промта +блок критериев и померить размышление заново.** Если оно вернётся к тысяче, а чтение не ухудшится — +промт чинится удалением, а не деньгами, и это ровно то, что владелец предлагал с самого начала. + +### 15.19 ⭐⭐ МЕХАНИЗМ ВСКРЫТ ТРЕЙСОМ: размышление у dspro — это ЧЕРНОВИК, а не проверка + +Абляция (3 главы, два реза, трейс сохранён по флагу `TM_KEEP_TRACE=1` — совет приёмки Fable-5): + +``` +арм размышление $/клетка перевёрстка годных +RN экзамен, риск на смелости 28 809 0.1265 1.60 2/3 +RB экзамен ПЕРЕВЁРНУТ 23 301 0.1050 1.14 2/3 +RC экзамена НЕТ 11 268 0.0593 1.37 3/3 +ZP прежняя склейка (эталон) 950 0.0177 1.77 31/31 +``` + +RC против RN: размышление ×0.42, цена ×0.50. RB против RN: ×0.86 и ×0.88. +⇒ **рез помогает, перевёртывание риска — почти нет.** Но лучший рез всё ещё думает ×12 против +склейки, то есть объявленный экзамен был НЕ главной причиной. + +**И трейс объясняет, чем он был.** Прямая мера: доля предложений ФИНАЛЬНОГО текста, встречающихся +в трейсе дословно: + +``` +RB 89614f9bfa размышление 31 515 · 11/11 = 100% +RB b2a7464dbd размышление 23 301 · 61/65 = 94% +RB ef9cd38ec4 размышление 13 337 · 46/76 = 61% +RC b2a7464dbd размышление 23 410 · 66/67 = 99% +RC ef9cd38ec4 размышление 11 268 · 69/71 = 97% +RC 89614f9bfa размышление 1 180 · 0/72 = 0% ← ЕДИНСТВЕННАЯ дешёвая клетка +МЕДИАНА: 96% +``` + +⇒ **96% финального текста написано ВНУТРИ канала размышления.** Разбор трейса по содержанию: +черновик перевода в уме 54% абзацев · разбор структуры 11% · само-проверка **всего 4%** · +перечисление глоссария 3%. + +⛔ **Значит и моя гипотеза, и гипотеза приёмки были НЕВЕРНЫ по механизму.** Мы оба думали, что +деньги жжёт верификационный цикл по объявленным критериям. Верификации там 4%. Деньги жжёт то, +что **модель пишет весь перевод дважды: сперва черновиком в размышлении, потом набело в ответе.** +Мы платим за обе копии по цене выхода. + +⚠ **Режим БИМОДАЛЕН.** Одна клетка из шести (RC на главе `89614f9bfa`) в ум не писала вовсе: +1 180 токенов размышления, 0% совпадения, цена в двадцать раз ниже соседей. То есть «писать в уме» +— это состояние, в которое модель входит или не входит, а не плавная функция промта. + +### 15.20 СЛЕДСТВИЕ ДЛЯ АРХИТЕКТУРЫ, которого никто не искал + +Прежняя склейка думает 950 токенов и в ум не пишет. Новый промт — пишет. Разница между ними — +приглашение к ремеслу: «ты вправе», «живая фраза», регистр, «не украшай». **Промт, который просит +творчества, у думающей модели с неотключаемым размышлением оплачивается вторым черновиком.** + +⇒ **Боевая двухстадийная связка дёшева не вопреки, а благодаря своей топологии: она выносит +черновик ИЗ канала размышления дорогой модели В ВЫХОД дешёвой.** То же самое, что дорогая модель +делает у себя в уме за $3.96/1M, дешёвая делает наружу за $1.32/1M — и результат виден, проверяем +и переиспользуем, а не выбрасывается. + +Это объясняет разом три вещи, которые до сих пор стояли порознь: почему связка держится пять +экспериментов; почему однопроходка выигрывает по банку, но не по цене на дорогой модели; и почему +у критика размышление взлетает ×14 на дефектном черновике (ему тоже приходится до-писывать текст +в уме, чтобы понять, чего в нём не хватает). + +⚠ Статус: 3 главы, 6 клеток, одна пара, одна модель. Механизм НАБЛЮДЁН прямо (трейс), величины — +описательны. Бимодальность на n=6 названа, но не объяснена. + +### 15.21 ⭐ АБЛЯЦИЯ ОТСУЖЕНА ЧТЕНИЕМ: формулировка владельца ПЕРВАЯ, но контроль шума КРАСНЫЙ + +Панель 5 вариантов × 3 главы, рамка нейтральная, ключ свой, идентичность читателя и сессия +записаны ДО. Аудит: 6 вызовов, только файлы пакета, нарушений 0. + +``` +RB > Z0 > RC > RN > ZF + +1. RB экзамен ПЕРЕВЁРНУТ — брак = просвечивающий исходный язык (формулировка ВЛАДЕЛЬЦА) +2. Z0 боевая связка, продакшен +3. RC экзамен вырезан +4. RN экзамен с риском на смелости (первая редакция) +5. ZF ВТОРАЯ ГЕНЕРАЦИЯ ТОЙ ЖЕ БОЕВОЙ СВЯЗКИ +``` + +**Три редакции промта между собой: `RB > RC > RN`.** Порядок обратен моей гипотезе: я ждал, что +победит чистый рез (он дешевле всех и убирает экзамен целиком), а победила ПЕРЕВЁРНУТАЯ рамка — +та, где браком объявлен просвечивающий исходный язык. То есть дело не в наличии экзамена, а в том, +ЧТО им объявлено браком. Формулировка владельца оказалась не смягчением, а рычагом. + +⛔ **НО КОНТРОЛЬ ШУМА КРАСНЫЙ, И ЭТО СНИМАЕТ ПРАВО ГОВОРИТЬ «РАЗЛИЧИМО».** Две генерации ОДНОЙ +боевой связки встали на 2-е и 5-е места — разошлись на всю панель. Победа `RB` над `Z0` — одна +позиция, а один и тот же способ занимает у того же читателя места со 2-го по 5-е. +⇒ **Порядок этой панели не разрешим при n=3 главах.** Единственное, что уцелевает: `RB` не хуже +продакшена, и три редакции промта упорядочены между собой. + +⚠ **Существенная поправка к трактовке красного контроля.** Читатель НЕ спутал близнецов: он прямо +написал, что `ZF` «в отрывке 1 маскируется под `Z0`», то есть родство опознал — и всё равно +поставил один вариант вторым, другой последним, назвав последний «откровенно машинным». Значит это +не шум ЧИТАТЕЛЯ, а разброс АРМА — ровно то, что владелец сказал словами при своём чтении +(«один прогон приличный, второй сырой») и что счёт ошибок намерил на китайской оси (sd 4.42). +Это уже ЧЕТВЁРТЫЙ независимый прибор, показывающий одно и то же. +⚠ Третий раз подряд читатель опознаёт контрольную пару, не будучи о ней предупреждён. + +**Цена победителя.** `RB` стоит $0.105 за клетку против ~$0.025 у боевой связки — вчетверо, и +выигрывает одну позицию внутри шума. Как продукт это пока не кандидат; как направление правки +промта — единственное, что сработало. + +**Что из этого следует по существу.** Механизм, вскрытый трейсом (§15.19), объясняет и этот +порядок: `RB` думает 23 301 токен и пишет 94–100% текста в уме — то есть покупает качество той же +дорогой монетой. Дешёвый `RC` (11 268) написал хуже. **Качество здесь пропорционально не удачности +формулировки, а количеству черновиков, которые модель успела написать про себя.** Если так, то +однопроходка на думающей модели упирается не в промт, а в тариф: чтобы писать лучше, ей надо +писать дважды, и оба раза мы платим по цене выхода. + + +### 15.22 РЕШЕНИЯ ВЛАДЕЛЬЦА 20.08, ЗАКРЫВАЮЩИЕ СЕССИЮ + +1. **Кап агент-сессий снят** («не припомню, чтоб ставил тебе жёсткие ограничения»): 100 → 200. + ⚠ Уточнение, которое стоит помнить: 80 пришло из ЗАКАЗА, владелец снял его 15.08, а 100 + поставил в код Я САМ. То есть сессия упёрлась в СВОЁ ограничение и подала его владельцу как + чужое. Норма: прежде чем назвать границу «ограничением владельца», найти его слово. +2. **Новые редакции промта — в английскую панель** («Нужны»). Куплены `RN`/`RC`/`RB`, 16 глав, + смета $1.05, касса ФД-N, с сохранением трейса. +3. **Sol паркуется, судит только Fable** («забьём на сол… а потом на сол если хватит времени»). + ⇒ P42 снят с повестки, но НЕ решён; норма П-1 о двух семействах временно не исполняется и это + объявленное отступление; контролем служит внутренняя пара близнецов в каждой панели. +4. **Документация актуализирована:** баннер «закрыт» на `PLAN-16-08.md`, шапка состояния на + `PLAN-17-08.md`, поправка на день в `КОНТЕКСТ-ДЛЯ-КОНСУЛЬТАЦИИ.md`, создан `HANDOFF-21-08.md`. + +### 15.23 ⛔ АНГЛИЙСКАЯ ДОКУПКА ОПРОВЕРГЛА МОЙ ЖЕ ВЫВОД ПРО ЦЕНУ ПРОМТА + +``` + китайская пара английская пара +прежняя склейка 950 4 494 +промт-РОЛЬ (RN) 27 039 (×28) 5 804 (×1.29) +рез критериев (RC) 11 268 (×12) 3 586 (×0.80) +``` + +⇒ **разгон ×28 — свойство КИТАЙСКОГО МАТЕРИАЛА, а не промта.** На английском тот же промт стоит +на четверть дороже склейки, резаный — дешевле неё. Вывод §15.20 («промт, просящий творчества, +оплачивается вторым черновиком») СНЯТ как общее утверждение. + +⚠ И это стояло в нашем же бюллетене, прочитанном в тот же день (`00-provider-quirks.md` п.7: +«разгон думания привязан к ПЛОТНОМУ ХАНЬСКОМУ ВХОДУ… при сопоставимом входе zh требует ×7.8 +против en»). Прочитал и всё равно приписал эффект промту. +**НОРМА: эффект, замеренный на ОДНОЙ паре, не называть свойством промта, пока не проверен на +второй. Пара — конфаундер по умолчанию.** + +**Что устояло и стало прочнее:** черновик в уме — на ОБЕИХ парах (zh 93–97% на 6 клетках, +en 88–94% на 47). Механизм подтверждён на 53 клетках, само-проверки 4%. Меняется следствие: +не «двухстадийность спасает от второго черновика», а «второй черновик пишется ВСЕГДА, но дорого +обходится только на плотном входе». + +### 15.24 ПАНЕЛЬ ПЕРЕ-СОБРАНА НА 11 АРМОВ И ПРОВЕРЕНА + +15 пакетов английской пары, по одной главе, панель `ZD Z0 ZF ZP Z8 Z8R Z1 Z7 RN RC RB`. +Сверка: 15 уникальных глав · 15 РАЗНЫХ раскладок · **165 текстов побайтно против клеток, +расхождений 0** · имён армов в пакетах нет · рамка нейтральна · указание владельца «торопиться +не надо» стоит в каждом пакете (проверено гейтом). + +⚠ Глава `100b088f71` пропущена: нет клетки `RN` после двух попыток. **Пропуск ПЕЧАТАЕТСЯ**, а не +умалчивается — первая редакция эмиттера падала на такой главе и молча собирала 5 пакетов вместо +16, что выглядело как «панель полна только на пяти». Починено: пропуск с печатью. +⚠ Прежние пакеты тега `arch` (панель из 8) УДАЛЕНЫ — ответов по ним не было. Две панели одной +пары рядом однажды кончатся сведением ответов разных панелей одним ключом. + +**Указание владельца 20.08, вписанное в рамку КАЖДОГО пакета:** «времени нет ограничения, +торопиться не надо · прочти ВСЕ варианты целиком прежде чем ранжировать · не выдумывай различий +там, где их нет, знак `=` законен · но и не сваливай в кучу различимое». Закрывает обе стороны: +пересудил (выдуманные различия) и недосудил (свалил в кучу). + + +## 16. СЕССИЯ №5 (21.08, после компакта) — СУДЕЙСТВО ПАНЕЛИ arch2 + +### 16.1 ⛔ ПАНЕЛЬ НЕ ИЗ ОДИННАДЦАТИ АРМОВ. `Z7` — побайтная КОПИЯ `ZP` на 10 главах из 15 + +Нашлось не гейтом, а ЧИТАТЕЛЕМ: первые же два ответа независимо сообщили «два варианта совпадают +дословно, связываю знаком `=`». Проверка побайтно подтвердила и назвала пару: **`Z7` ≡ `ZP`**. + +Причина в конструкции арма, а не в сборке пакета (`zakhod.py:486`): `Z7` = однопроходка + канон-фиксер, +и фиксер зовётся ТОЛЬКО при непустом списке щелей `C.canon_gaps`. Щелей нет — клетка пишется +`_free_cell(tg, op, places=0)`, то есть текстом `ZP` без единого вызова. По клеткам: + +``` +глав с places=0 (фиксер НЕ звался, Z7 ≡ ZP) 10 +глав с places≥1 (фиксер работал) 6 (одна из них — пропущенная 100b088f71) +``` + +⇒ **В 10 пакетах из 15 читателю показывали 10 разных текстов под видом 11.** Сверка сборки, +объявленная «сплошной» (§15.24: 165 текстов побайтно против клеток, расхождений 0), этого не +видела ПО ПОСТРОЕНИЮ: она сличала пакет с клетками, а клетки друг с другом — нет. +**НОРМА: сверка панели обязана проверять не только «текст тот», но и «тексты РАЗНЫЕ».** + +**Что это значит для вердикта, и чего НЕ значит.** +* Резать `Z7` из панели решением сессии ЗАПРЕЩЕНО заказом — и не нужно: данные не испорчены, + испорчена их трактовка. Среднее место `Z7` на этих главах — это среднее место `ZP`, и считать их + двумя независимыми армами значит дважды засчитать один текст. +* Свод печатает обе величины: `Z7` как есть и контроль тождества отдельной строкой. Вывод об арме + «однопроходка + канон-фиксер» законен ТОЛЬКО на 5 главах, где фиксер работал. +* Отдельный результат, который стоит записать сам по себе: **на 2/3 английских глав канон-гейт не + находит ни одной щели**, то есть стадия канон-фиксера на этой паре в две трети случаев — пустой + проход. Это ответ про её цену, полученный бесплатно. + +### 16.2 ⭐ ПОБОЧНЫЙ ПРИЗ: получился КОНТРОЛЬ ТОЖДЕСТВА, которого никто не закладывал + +Два одинаковых текста в панели — это проверка читателя строже близнецов `Z0`/`ZF`: у близнецов +разброс арма реален, у тождества его нет по построению. Читатель, разводящий побайтно одинаковые +тексты, дисквалифицирует себя без всяких допущений. + +**На отсуженных главах контроль ПРОЙДЕН: развод мест 0.00, худший 0** — каждый читатель, которому +попалась пара, связал её знаком `=` и написал, что различий не нашёл. Это первая на фазе проверка +разрешения прибора, не опирающаяся на предположения о шуме арма. + +### 16.3 ИНСТРУМЕНТ: `rol.py --score-arch` + +`score_read` разбирал ОДИН пакет и один ключ; здесь пакетов 15, ключей 15, раскладок 15. +Свод: дробные места при связках (`Т8=Т9` на 1–2 → обоим 1.5) · отказ от главы, чей порядок не +покрывает панель ровно один раз, ВСЛУХ · три контроля рядом со средним местом, а не под ним. + +``` +eval/.venv/bin/python eval/dovodka/rol.py --score-arch en --tag=arch2 +``` + +### 16.4 ⭐ ВЕРДИКТ ПЕРЕ-СУДЕЙСТВА: связку не обошёл никто, абляция опровергнута + +15 глав × 11 армов, один читатель на главу, все контроли зелёные. Полный разбор — отчёт Д24. +Коротко, что меняется в знании: + +| было | стало | +|---|---| +| `RB > Z0` (абляция, n=3, контроль КРАСНЫЙ) | `RB` на **2.03 места позади** связки при n=15 и ЗЕЛЁНОМ контроле | +| «однопроходка первая на английском» (1 глава, чтение владельца) | `ZP` шестая из 11; от связки НЕ отличима (+1.67 при пороге 2.98) — кандидатом остаётся, победителем не была | +| «`Z1` критик→перепис ОТРИЦАТЕЛЬНО» (счёт, другие главы) | **лучший из девяти претендентов** (+1.30), но и он в пределах порога | +| «`Z8` обогащённый черновик хуже голого» (на грани) | различимо хуже: 9.30 против 8.20 у голого, разрыв со связкой +5.40 | +| «второй проход нужен» (счёт) | устояло при смене прибора: `ZD` и `Z8` проигрывают на 15 и 14 главах из 15 | + +**Что различимо вообще:** только три арма — `RC`, `ZD`, `Z8`. Шестёрка середины между собой не +упорядочена, и говорить «A лучше B» внутри неё нельзя. + +**Методическое, ради чего стоило городить:** собственный пол замера (близнецы) разошёлся на +0.53 +места при пороге 2.56 — **впервые за фазу контроль шума ЗЕЛЁНЫЙ на панели архитектур.** Разгадка +не в приборе, а в n: по ОДНОЙ главе тот же способ прыгает на 9 мест из 11 (глава 7 в раскладке), +но среднее по 15 главам устойчиво. То есть «архитектуры неразличимы» захода Д17 было утверждением +о РАЗМЕРЕ ВЫБОРКИ, а не о мире. + +### 16.5 ⛔ ЕЩЁ ОДНА НЕВЕРНАЯ СТРОКА В МОЁМ ЖЕ ХЕНДОФФЕ + +`HANDOFF-21-08.md` §1 утверждал: «прежние 15 пакетов с тегом `arch` (панель из 8) УДАЛЕНЫ». +**Не удалены.** На диске 16 пакетов, 16 пустых ответов и 16 ключей тега `arch`. Я записал намерение +как исполненное. Поправка внесена в сам хендофф; файлы не тронуты (сырьё чужой рукой не сносят, +механической коллизии глобов `arch-`/`arch2-` нет — проверено). +**Класс ошибки тот же, что «объявил 80 клеток куплено, годных 23»: отчёт о действии вместо +действия.** Норма: строку «сделано» писать после проверки диска, а не после решения сделать. + +### 16.6 Инвентарь перед следующим шагом (против энтропии, по слову владельца) + +``` +~/books/chtenie-rol/ ЧТЕНИЕ/ОТВЕТ-en-arch2-* 15 пакетов, ВСЕ отсужены 21.08 + ЧТЕНИЕ/ОТВЕТ-en-arch-* 16 пакетов панели из 8, НЕ читаны, НЕ годны + ЧТЕНИЕ/ОТВЕТ-zh(-abl) старые китайские панели (4 и 5 армов) +китайская пара, клеток: Z8 18 · Z8R 14 · Z1 16 · Z7 16 · ZF 17 · RN 14 · RC 3 · RB 4 + ⇒ панель из 9 (8 + RN) на zh собирается БЕСПЛАТНО, RC/RB — нет +``` + +⇒ **Следующий дешёвый шаг очевиден и стоит $0:** та же панель на КИТАЙСКОЙ паре из уже купленного. +Он проверяет главное ограничение Д24 — вывод стоит на одной паре. + +### 16.7 ⛔ БАГ РАЗБОРЩИКА, КОТОРЫЙ ВСКРЫЛСЯ ТОЛЬКО ОТ СВЕРКИ С КЛЕТКОЙ + +`_variants` резал пакет по заголовкам вариантов и не отрезал разделитель `---`, стоящий ПЕРЕД +следующим вариантом: он прилипал к хвосту предыдущего тела и переживал `strip()` — ровно 6 знаков. + +**Почему это было невидимо и что урок.** Первым потребителем `_variants` был контроль тождества, +то есть сравнение ДВУХ ТЕЛ между собой — а артефакт стоял у обоих, и равенство сходилось. Баг +проявился в ту же секунду, когда тела сверили с ВНЕШНИМ источником (клеткой): 84 «расхождения» +из 96, все ровно на 6 знаков. ⇒ **сверка «своё со своим» не ловит систематическую порчу; ловит +только сверка с источником, к разбору не причастным.** + +**И он же поправил находку в мою же пользу — в сторону строгости.** Правильный счёт по английской +панели: `Z7` ≡ `ZP` не на 10 главах, а на **12**. Разбор: +``` +10 глав фиксер не звался (places=0) + 2 главы фиксер ЗВАЛСЯ, ОПЛАЧЕН и вернул побайтно тот же текст ← этого первая версия не видела + 3 главы фиксер действительно правил текст +``` +⇒ **канон-фиксер на английской паре меняет перевод в одном случае из пяти**, а в 13% случаев +вызывается и оплачивается впустую. Вердикты Д24 не двигаются (`Z7` и так шёл рядом с `ZP`), +двигается цена стадии. + +### 16.8 СВЕРКА СБОРКИ СТАЛА ИНСТРУМЕНТОМ, А НЕ РАЗОВЫМ СКРИПТОМ + +`rol.py --verify-read <пара> --tag=<тег>`: побайтная сверка каждого текста с клеткой своего арма · +исходник главы · разные раскладки · имена армов в пакете · указание владельца «торопиться не +надо» · **и новый пункт: побайтно совпадающие армы**. Прошлые два раза это был скрипт в консоли — +и оба раза он проверял не то, что нужно. + +Гейт проверен на ЗАВЕДОМО больном входе: на английской панели он поднимает `Z7 ≡ ZP` (12 пакетов), +на китайской молчит. Гейт, который не может упасть, ничего не сторожит. + +### 16.9 КИТАЙСКАЯ ПАНЕЛЬ ОТСУЖЕНА — И ГЛАВНЫЙ ВЫВОД ДУГИ СОБРАЛСЯ + +12 глав, 8 армов, $0 (всё из уже купленного). Полный разбор — отчёт Д26. + +**Реплицировалось на обеих парах при зелёных контролях:** второй проход нужен (`ZD` различимо +последний: en +4.30, zh +3.33) · обогащение промта черновика ВРЕДИТ (`Z8` ниже голого черновика +на обеих парах) · вся середина в пределах порога. + +⛔ **НЕ реплицировался порядок внутри середины:** на en боевая связка первая-вторая, на zh третья, +впереди критик-перепис и однопроходка. Разрывы с обеих сторон меньше порога. +⇒ **Правильная формулировка одна: ни одна архитектура второго прохода не отличима от другой ни на +одной паре.** Моя вчерашняя «связку не обошёл никто» верна только для английской панели — +и это нарушение нормы, которую фаза записала 20.08 (эффект одной пары не называть свойством). + +### 16.10 ⛔ ИНЦИДЕНТ: ОБОРВАННАЯ КЛЕТКА ПРОДАКШЕНА В КИТАЙСКОЙ ПАНЕЛИ + +Глава `41599f30df`, арм `Z0`: `finish=length`, 5759 знаков против медианы панели 6888, оборвана +кульминация. Читатель поставил последним — законно. + +**Причина в коде:** `contour.base_a0` читает `content` БЕЗ проверки `finish`, тогда как соседний +`base_draft` гейт годности имеет на ОБЕИХ ветках. Щель ровно в одну функцию. +**Масштаб:** из 22 клеток боевой связки оборвана ОДНА; у `ZF` такая же клетка уже была в карантине +и в панель не прошла. +**Чувствительность** (`--drop=41599f30`): `Z0` 3.29 → 2.86, то есть из третьего места в первое. +Качественный вердикт устоял, порядок середины перевернулся от ОДНОЙ главы — третье независимое +подтверждение, что этот порядок и есть шум. +**Лечение — гейт сборки, а не правка данных:** `--verify-read` роняет панель, если текст арма +короче 0.85 медианы. Проверен на больном входе. +⛔ **Правку самого `base_a0` сессия НЕ делает: он питает ВСЕ прошлые замеры фазы. Вопрос владельцу.** + +### 16.11 АУДИТ ПРОГОНА ПО ВОПРОСУ ВЛАДЕЛЬЦА «прошло без инцидентов?» + +Отчёт Д27. Коротко: **инцидентов два (16.10 и `Z7`≡`ZP`), ошибок в выводах читателей — ноль.** + +* разбор порядка однозначен: в каждом из 27 ответов ровно ОДНА цепочка полной длины; +* все 12 английских заявлений «совпадают дословно» верны побайтно; +* три китайских «ложных» заявления оказались **ложной тревогой моей проверки** — читатели писали + «ПРАКТИЧЕСКИ дословно» и называли расхождение; матчер цеплялся за корень и за соседнее + предложение о другой паре. Вскрыто чтением строк. Класс известен (тревога 20.08 про «метки»); +* 4 содержательных утверждения проверены побайтно — подтвердились все. + +**Качество в понятных единицах** — доля глав, где читатель назвал текст машинным/подстрочником: +``` +ZD 0.41 · Z8 0.33 · ZP 0.11 · Z7 0.11 · Z1 0.07 · Z0 0.04 · ZF 0.04 · Z8R 0.04 · RN 0.00 +``` +⇒ каждая пятая глава голого черновика читается машиной, после второго прохода — каждая 25-я. + +**Сквозной дефект-регистр продукта (назван независимо на обеих парах):** пол персонажа плывёт +внутри главы · куски исходного языка в русской прозе (`cultivation`, `priory`, `Oui`, иероглифы) · +сбитая атрибуция реплик · родство и ранги · теряется речевой портрет (заикание, брань) · +идиомы и девизы переворачиваются. **Это и есть настоящий бэклог, а не выбор архитектуры.** + +### 16.12 ЦЕНА РАЗМЫШЛЕНИЯ У `glm-5`, ЗАМЕРЕННАЯ ПОПУТНО + +Конфаунд Д25.1 обернулся самостоятельным замером: +``` +glm-5, размышление ВЫКЛ (наш дефолт) $0.0029/клетка 0 токенов +glm-5, размышление ВКЛ $0.0553/клетка 16 339 токенов +deepseek-v4-pro (для шкалы) $0.0278/клетка 6 139 токенов +``` +⇒ **размышление у glm-5 стоит ×19 и по объёму в 2.7 раза больше, чем у dspro на том же входе.** +⚠ Докупка `RGT` упёрлась не в наш потолок, а в БАЛАНС вендора (`429/1113 Insufficient balance`). +Отказных клеток 5, денег на них сожжено $0.0000. Владелец пополнил, докупка продолжена. + +### 16.13 ⭐⭐ ПЕРЕНОСИМОСТЬ ОТСУЖЕНА. Конфаунд, пойманный до судейства, перевернул бы вывод + +13 глав, 6 армов, якорь — тот же промт на `deepseek-v4-pro` (как назначено пре-регом Д25). +Полный разбор — отчёт Д28. + +``` +RGT (glm-5 С думанием) 2.54 разрыв с якорем −0.08 при пороге 1.86 — ПЕРЕНОСИМ +RN (deepseek-v4-pro) 2.62 якорь +ZF/Z0 (боевая связка) 2.85 / 3.08 +RK (grok-4.3) 4.77 +2.15 при пороге 1.95 — НЕ переносим (знаковый p=0.09, на грани) +RG (glm-5 БЕЗ думания) 5.15 +2.54 при пороге 1.20 — НЕ переносим (оба прибора) +пол Z0/ZF +0.23 при пороге 1.43 — ЗЕЛЁНЫЙ +``` + +**Если бы эрратa Д25.1 не была найдена, вывод был бы ЛОЖНЫМ РОВНО НАОБОРОТ:** в панели стоял бы +один `glm-5` — тот, которому наш ростер гасит размышление, — и мы записали бы «промт не переносится +на glm-5». **Норма: конфигурация модели — часть арма, а не фон; вендор-дефолт, переопределённый +ростером, называть в пре-реге наравне с моделью.** + +**⭐ Кросс-вендорное подтверждение механизма трейса.** Трейс дал предсказание «выключи размышление — +качество упадёт различимо». Проверено на ДРУГОМ вендоре вслепую: тот же `glm-5`, тот же промт, те же +главы — с думанием 2.54, без думания 5.15, разрыв **+2.61 места внутри одной модели**. + +**Для денег:** дешёвого вендора не нашлось. Оба дешёвых различимо хуже, равный по качеству вдвое +дороже якоря. **Платим не за вендора, а за размышление.** `dspro` остаётся оптимумом цена/качество; +`glm-5` с думанием — валидный ЗАПАСНОЙ жилец (вендор-независимость при квотах и цензуре) за ×2. + +⚠ Гард кассы отказал на 15-й клетке `RGT`: потрачено $2.3166 + ожидание $0.1063 > потолок $2.40. +Панель собрана на 13 главах вместо 15. Недостающая сумма $0.11 названа владельцу; **потолок сессией +НЕ поднимался.** diff --git a/eval/dovodka/adjud.py b/eval/dovodka/adjud.py index b50cb6cf..3d235b60 100644 --- a/eval/dovodka/adjud.py +++ b/eval/dovodka/adjud.py @@ -28,7 +28,7 @@ проверке, поэтому это исполнение буквы, а не упрощение. Приближение объявлено: поправка предполагает, что доля верных претензий одинакова по единицам внутри арма. -Запуск: adjud.py --emit | --score +Запуск: adjud.py --emit | --controls | --score | --selftest """ from __future__ import annotations @@ -70,19 +70,120 @@ PER_TASK = 24 SEED = 20260814 # фиксирован: раскладка воспроизводима _norm = re.compile(r"[\s«»„“”\"'`.,!?;:—–-]+") +# ⚠ Имена осей — ЯВНЫМ списком, а не `[А-ЯЁ]{4,}`: см. починку дефекта Г-3 ниже по файлу. +AX_SPLIT = re.compile(r"(?=\b(?:ВЕРНОСТЬ|ТЕРМИН|ЯЗЫК|ФОРМА)\s*[:—–-])") +AX_HEAD = re.compile(r"(ВЕРНОСТЬ|ТЕРМИН|ЯЗЫК|ФОРМА)\s*[:—–-](.*)", re.DOTALL) + +def _fragment(var: str, rnd: random.Random, target: int) -> str: + """Фрагмент варианта ДЛИНОЙ ОКОЛО `target` — материал для ЛОЖНОЙ претензии. + + ⚠ Дефект Г-2, вторая половина. Мало построить ложной претензии такое же окно: если её цитата + систематически короче настоящих (18 знаков против 51 в первой починке), карточка выдаёт себя + длиной. Целевая длина берётся из ЭМПИРИЧЕСКОГО распределения настоящих цитат этого же прогона. + """ + words = [w for w in re.split(r"\s+", var.strip()) if w] + if len(words) < 3: + return "" + best, best_d = "", 10 ** 9 + for _ in range(60): + i = rnd.randrange(0, len(words)) + frag = "" + for j in range(i + 1, min(len(words) + 1, i + 25)): + frag = " ".join(words[i:j]) + d = abs(len(frag) - target) + if d < best_d: + best, best_d = frag, d + if len(frag) > target + 15: + break + if best_d <= 4: + break + return best if len(best) >= 8 else "" + def norm(t: str) -> str: return _norm.sub(" ", (t or "").lower()).strip() def context(text: str, quote: str, width: int = 260) -> str: - """Цитата с окружением — адъюдикатор обязан видеть, в каком месте она стоит.""" - n, q = norm(text), norm(quote) - i = n.find(q) - if i < 0: + """Цитата с окружением — адъюдикатор обязан видеть, в каком месте она стоит. + + ⚠ ПОЧИНКА 15.08 (дефект Г-4). Прежняя редакция и ИСКАЛА, и РЕЗАЛА по `norm()`, то есть + отдавала адъюдикатору окно без пунктуации и в нижнем регистре. По такому окну нельзя судить + ни ФОРМУ (она стёрта), ни ЯЗЫК (регистр и пунктуация — часть языковой ошибки), а цитата в + самой претензии при этом шла в исходном виде: окно и цитата выглядели как разные тексты. + Теперь поиск идёт по нормализованному, а РЕЗ — по сырому тексту через карту позиций. + """ + span = locate(text, quote) + if span is None: return quote - lo, hi = max(0, i - width // 2), min(len(n), i + len(q) + width // 2) - return ("…" if lo else "") + n[lo:hi] + ("…" if hi < len(n) else "") + lo, hi = span + lo = max(0, lo - width // 2) + hi = min(len(text), hi + width // 2) + return ("…" if lo > 0 else "") + text[lo:hi] + ("…" if hi < len(text) else "") + + +def _normmap(text: str) -> tuple[str, list[int]]: + """Нормализованный текст + карта «позиция в нём → позиция в СЫРОМ тексте».""" + buf, raw_pos, prev_space = [], [], True + for i, ch in enumerate(text): + if _norm.match(ch): + if prev_space: + continue + buf.append(" ") + raw_pos.append(i) + prev_space = True + else: + buf.append(ch.lower()) + raw_pos.append(i) + prev_space = False + return "".join(buf), raw_pos + + +def locate(text: str, quote: str) -> tuple[int, int] | None: + """Границы цитаты в СЫРОМ тексте; поиск ведётся по нормализованным формам.""" + if not text or not quote: + return None + n, raw_pos = _normmap(text) + q = norm(quote) + i = n.find(q) + if i < 0 or not q: + return None + j = min(i + len(q), len(raw_pos)) - 1 + return raw_pos[i], raw_pos[j] + 1 + + +def margin_spans(text: str) -> tuple[str, list[tuple[int, int]]]: + """То же лечение, что `sud.margin_plant`, но с ПОЗИЦИЯМИ посадок в получившемся тексте. + + ⚠ Зачем (дефект Г-1). Прежде «посаженной» считалась ЛЮБАЯ претензия к клетке маржевого декоя, + а судья цитировал в ней и обычные места: из 15 «посадок» реальными были 4. Контроль + чувствительности мерил не то — согласие с претензией к декою, а не опознание известной порчи. + """ + out, spans = text, [] + for pat, rep, _c in S.MARGIN_PLANTS: + m = re.search(pat, out) + if not m: + continue + frag = m.expand(rep) + start, end_old = m.start(), m.end() + out = out[:start] + frag + out[end_old:] + delta = len(frag) - (end_old - start) + spans = [(a + delta, b + delta) if a >= end_old else (a, b) for a, b in spans] + spans.append((start, start + len(frag))) + return out, spans + + +def hits_planted(var: str, quote: str, spans: list[tuple[int, int]]) -> bool: + """Накрывает ли цитата хоть одну посадку. + + Строго ПЕРЕСЕЧЕНИЕМ отрезков, без запаса «где-то рядом»: судья, процитировавший соседнее + предложение, посадку НЕ опознал, и засчитывать это как чувствительность — тот же дефект Г-1 + в более слабой форме. + """ + q = locate(var, quote) + if q is None: + return False + return any(q[0] < b and a < q[1] for a, b in spans) def collect() -> tuple[list, dict]: @@ -112,16 +213,22 @@ def collect() -> tuple[list, dict]: var, _ = S.margin_plant(C.base_a0(uid)) if not var.strip(): continue - w = re.search(rf"^{lab}-ПОЧЕМУ:\s*(.*)$", txt, re.M) + w = re.search(rf"^{lab}-ПОЧЕМУ:\s*(.*)$", txt, re.MULTILINE) why = w.group(1) if w else "" # какие оси ненулевые — по ним претензии реальные; нулевые дают материал для лжи zero = [] for a in AX: - m = re.search(rf"^{lab}-{a}:\s*(\d+)", txt, re.M) + m = re.search(rf"^{lab}-{a}:\s*(\d+)", txt, re.MULTILINE) if m and int(m.group(1)) == 0: zero.append(a) - for seg in re.split(r"(?=[А-ЯЁ]{4,}:)", why): - m = re.match(r"([А-ЯЁ]{4,}):(.*)", seg.strip(), re.S) + # ⚠ ПОЧИНКА 15.08 (дефект Г-3). Прежде разбор осей шёл через `[А-ЯЁ]{4,}:`, и + # `re.split` с таким lookahead режет ВНУТРИ имени оси: «ВЕРНОСТЬ:» даёт точки + # разреза на В, Е, Р, Н, О, и уцелевает хвост «ОСТЬ». В ключе прошлого прогона + # это видно прямо: ОСТЬ 45 · ОРМА 22 · РМИН 18 против ВЕРНОСТЬ 1 · ФОРМА 4. + # Следствие тяжелее самой опечатки: фильтр «несущие оси» считал одну ось из двух, + # а ложные и настоящие претензии стали различимы по форме имени оси. + for seg in AX_SPLIT.split(why): + m = AX_HEAD.match(seg.strip()) if not m: continue ax = m.group(1) @@ -133,14 +240,24 @@ def collect() -> tuple[list, dict]: if arm in ARMS: real.append(rec) if arm == "CTRLmargin": - plant_pool.append(rec) - # ложные претензии строятся из мест, где судья поставил НОЛЬ + # ⚠ дефект Г-1: посадкой считается только цитата, реально накрывшая + # подменённое место, а не любая претензия к клетке декоя + _, spans = margin_spans(C.base_a0(uid)) + if hits_planted(var, q, spans): + plant_pool.append(rec) + # ⚠ ЛОЖНЫЕ ПРЕТЕНЗИИ — ПОЧИНКА 15.08 (дефект Г-2). Прежде окно ложной претензии + # строилось как `context(quote, quote)`, то есть текстом служила сама цитата: + # окружения не было и многоточий тоже, тогда как у настоящих претензий окно + # всегда шло из полного варианта и почти всегда с «…». Плюс ложная цитата была + # ЦЕЛЫМ предложением 40–200 знаков против коротких фрагментов у настоящих. + # Адъюдикатору не нужно было судить перевод — хватало формы карточки. + # Теперь ложная цитата берётся ФРАГМЕНТОМ той же длины, что настоящие, из того же + # варианта, и окно строится тем же `context(var, …)`. if zero and arm in ARMS: - sent = [x for x in re.split(r"(?<=[.!?])\s+", var) if 40 < len(x) < 200] - if sent: - false_pool.append(dict(uid=uid, arm=arm, axis=rnd.choice(zero), - quote=rnd.choice(sent), ctx="", src=u["source"], - fake=True)) + # цитата подбирается ПОСЛЕ прохода, когда известно распределение длин + # настоящих цитат этого прогона; здесь копится только материал + false_pool.append(dict(uid=uid, arm=arm, axis=rnd.choice(zero), + var=var, src=u["source"], fake=True)) # стратифицированная выборка: не более PER_CELL претензий на (единица, арм) by = {} for r in real: @@ -150,8 +267,15 @@ def collect() -> tuple[list, dict]: v = by[k] rnd.shuffle(v) sample += v[:PER_CELL] - for r in false_pool[:0] or rnd.sample(false_pool, min(N_FALSE, len(false_pool))): - r["ctx"] = context(r["quote"], r["quote"]) + # ⚠ Длины ложных цитат берутся из ЭМПИРИЧЕСКОГО распределения настоящих (дефект Г-2). + real_len = [len(r["quote"]) for r in sample] or [40] + for r in rnd.sample(false_pool, min(N_FALSE, len(false_pool))): + var = r["var"] + frag = _fragment(var, rnd, rnd.choice(real_len)) + if not frag: + continue + r = {k: v for k, v in r.items() if k != "var"} + r["quote"], r["ctx"] = frag, context(var, frag) sample.append(r) for r in rnd.sample(plant_pool, min(N_PLANT, len(plant_pool))): r = dict(r, planted=True) @@ -200,9 +324,13 @@ def emit() -> None: body = [HEAD.format(path=ANSW / f"{tok}.txt", skeleton=skel)] for l in grp: r = sample[labs.index(l)] + # ⚠ Окно берётся из СЫРОГО текста и может быть многострочным (абзацы — часть ФОРМЫ). + # Поэтому оно обрамляется явными границами: иначе перенос строки внутри окна + # неотличим от следующего поля карточки, и разбор — как машинный, так и глазной — + # видит разные карточки у разных претензий, то есть снова форма выдаёт класс. body += ["", "=" * 60, f"{l}", "ИСХОДНИК (китайский):", r["src"][:1800], - "", f"МЕСТО В ПЕРЕВОДЕ: {r['ctx'] or r['quote']}", + "", "МЕСТО В ПЕРЕВОДЕ:", "<<<", r["ctx"] or r["quote"], ">>>", f"ПРЕТЕНЗИЯ: здесь ошибка типа {r['axis']} — в цитате «{r['quote']}»"] (TASKS / f"{tok}.txt").write_text("\n".join(body), encoding="utf-8") print(f"претензий {len(labs)} · заданий {len(list(TASKS.glob('*.txt')))} → {TASKS}") @@ -212,6 +340,65 @@ def emit() -> None: print(f"из них контролей: ложных претензий {n_f} · посаженных дефектов {n_p}") +def controls() -> int: + """НЕРАЗЛИЧИМОСТЬ КЛАССОВ ПО ФОРМЕ КАРТОЧКИ — гейт, без которого контроли фиктивны. + + Прежняя редакция проваливала его вчистую: ложная претензия шла без окружения и целым + предложением, то есть адъюдикатору хватало формы, чтобы отличить контроль от боевой претензии. + Здесь это меряется числом, а не обещается словом. + """ + if not AKEY.exists(): + print("ключа нет — сначала --emit") + return 1 + key = json.loads(AKEY.read_text(encoding="utf-8")) + cards = {} + for f in sorted(TASKS.glob("*.txt")): + for blk in f.read_text(encoding="utf-8").split("=" * 60)[1:]: + m = re.match(r"\s*(П\d{3})", blk) + if not m: + continue + w = re.search(r"<<<\n(.*?)\n>>>", blk, re.DOTALL) + q = re.search(r"в цитате «([^»]*)»", blk) + cards[m.group(1)] = (w.group(1) if w else "", q.group(1) if q else "") + if not cards: + print("заданий нет — сначала --emit") + return 1 + groups = { + "настоящие": [k for k, v in key.items() if not v.get("fake") and not v.get("planted")], + "ЛОЖНЫЕ": [k for k, v in key.items() if v.get("fake")], + "посадки": [k for k, v in key.items() if v.get("planted")], + } + import statistics as _st + rows = {} + print(f"{'класс':10s} {'n':>4s} {'цитата':>7s} {'окно':>7s} {'с «…»':>6s} {'многостр':>9s}") + for name, sel in groups.items(): + sel = [k for k in sel if k in cards] + if not sel: + print(f"{name:10s} пусто") + continue + ql = _st.median([len(cards[k][1]) for k in sel]) + wl = _st.median([len(cards[k][0]) for k in sel]) + el = sum(cards[k][0].startswith("…") or cards[k][0].endswith("…") for k in sel) / len(sel) + ml = sum("\n" in cards[k][0] for k in sel) / len(sel) + rows[name] = (ql, wl, el, ml) + print(f"{name:10s} {len(sel):4d} {ql:7.1f} {wl:7.1f} {el:5.0%} {ml:8.0%}") + rc = 0 + base = rows.get("настоящие") + if base: + for name, r in rows.items(): + if name == "настоящие": + continue + # медианы цитаты и окна не должны расходиться больше чем в полтора раза, + # а доля окон с многоточием — больше чем на четверть + if not (0.55 <= r[0] / max(base[0], 1) <= 1.8) or \ + not (0.55 <= r[1] / max(base[1], 1) <= 1.8) or abs(r[2] - base[2]) > 0.25: + print(f"\n⛔ класс «{name}» ОТЛИЧИМ от настоящих претензий по форме карточки") + rc = 1 + if not rc: + print("\nформа карточки классы не выдаёт — контроли пригодны") + return rc + + def score() -> int: if not AKEY.exists(): print("ключа нет — сначала --emit") @@ -220,7 +407,7 @@ def score() -> int: ans = {} for f in sorted(ANSW.glob("*.txt")): for line in f.read_text(encoding="utf-8", errors="replace").splitlines(): - m = re.match(r"\s*(П\d{3})\s*:\s*(ДА|НЕТ)", line.strip(), re.I) + m = re.match(r"\s*(П\d{3})\s*:\s*(ДА|НЕТ)", line.strip(), re.IGNORECASE) if m: ans[m.group(1)] = m.group(2).upper() == "ДА" print(f"ответов получено: {len(ans)} из {len(key)}") @@ -261,11 +448,79 @@ def score() -> int: return 0 +def selftest() -> int: + """Проверки ПОЧИНЕННЫХ контролей. Без них адъюдикация переносит доверие на уровень ниже.""" + fails: list[str] = [] + + def ok(cond: bool, msg: str) -> None: + if not cond: + fails.append(msg) + + # Г-3: имя оси не режется + for name in ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА"): + got = [AX_HEAD.match(s.strip()).group(1) + for s in AX_SPLIT.split(f"{name}: «цитата» — пояснение") if AX_HEAD.match(s.strip())] + ok(got == [name], f"Г-3: ось {name} разобралась как {got}") + multi = "ВЕРНОСТЬ: «а» | «б». ЯЗЫК: «в». ФОРМА: «г»." + got = [AX_HEAD.match(s.strip()).group(1) + for s in AX_SPLIT.split(multi) if AX_HEAD.match(s.strip())] + ok(got == ["ВЕРНОСТЬ", "ЯЗЫК", "ФОРМА"], f"Г-3: разбор трёх осей дал {got}") + + # Г-4: окно режется по СЫРОМУ тексту — пунктуация и регистр на месте + raw = 'Он сказал: «Стрела уже слетела с тетивы!» И замолчал, глядя вдаль.' + win = context(raw, "Стрела уже слетела", width=40) + ok("«" in win or "!" in win or "С" in win, + f"Г-4: окно потеряло пунктуацию и регистр: {win!r}") + ok(win.strip("…") in raw, f"Г-4: окно не является подстрокой сырого текста: {win!r}") + + # Г-1: посадки имеют позиции, и цитата вне них не считается посаженной + base = "Он не поверил своим глазам. Их было три. Обычное объяснение не помогло." + planted, spans = margin_spans(base) + ok(bool(spans), "Г-1: посадок не найдено — контроль чувствительности будет пуст") + ok(planted != base, "Г-1: текст декоя не отличается от эталона") + if spans: + a, b = spans[0] + ok(hits_planted(planted, planted[a:b], spans), "Г-1: цитата ПО посадке не опознана") + if spans: + # цитата из области, ЗАВЕДОМО не пересекающей ни одну посадку + busy = sorted(spans) + gap = next(((busy[i][1], busy[i + 1][0]) for i in range(len(busy) - 1) + if busy[i + 1][0] - busy[i][1] >= 12), None) + outside = planted[gap[0]:gap[1]].strip(" .,!?") if gap else "" + ok(not outside or not hits_planted(planted, outside, spans), + f"Г-1: цитата ВНЕ посадки ({outside!r}) засчитана как посаженная") + + # Г-2: ложная цитата неотличима от настоящей по длине + rnd = random.Random(SEED) + long = " ".join(f"слово{i}" for i in range(40)) + for target in (20, 45, 80): + frag = _fragment(long, rnd, target) + ok(bool(frag) and abs(len(frag) - target) <= 8, + f"Г-2: фрагмент под цель {target} вышел {len(frag)}: {frag!r}") + + # Г-2 на живом ключе, если он есть: форма карточки не должна выдавать ложную + if AKEY.exists(): + key = json.loads(AKEY.read_text(encoding="utf-8")) + bad = [k for k, v in key.items() if v.get("axis") not in + ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА")] + ok(not bad, f"Г-3: в ключе {len(bad)} претензий с обрезанным именем оси " + f"(пример {bad[:3]}) — ключ снят ДО починки, требуется пере-эмиссия") + + for m in fails: + print("ПРОВАЛ:", m) + print(f"селфтест адъюдикации: провалов {len(fails)}") + return 1 if fails else 0 + + if __name__ == "__main__": a = sys.argv[1:] or ["--emit"] if a[0] == "--emit": emit() elif a[0] == "--score": sys.exit(score()) + elif a[0] == "--selftest": + sys.exit(selftest()) + elif a[0] == "--controls": + sys.exit(controls()) else: raise SystemExit(f"⛔ неизвестный режим {a[0]!r}") diff --git a/eval/dovodka/chtenie.py b/eval/dovodka/chtenie.py index 853a9d27..c3192610 100644 --- a/eval/dovodka/chtenie.py +++ b/eval/dovodka/chtenie.py @@ -22,6 +22,7 @@ счёту ошибок, — и потому решает судьбу СЧЁТА как дешёвого прокси, а не судьбу армов. Запуск: chtenie.py --emit | --score | --selftest | --score-calib + chtenie.py --emit-owner | --score-owner — пакет ВЛАДЕЛЬЦУ (4 варианта, «жирная глава») """ from __future__ import annotations @@ -193,7 +194,42 @@ def emit() -> None: # ⚠ Латинская `T` принимается наравне с кириллической `Т` (починка по ревью): судьи уже писали # латиницей, а `zsud`/`ja6` это учитывают. Молча выпавший ответ читателя стоил бы целой сессии. -_ORD = re.compile(r"ПОРЯДОК\s*(\d+)\s*:\s*((?:[ТT]\d+\s*[>=]\s*)+[ТT]\d+)", re.IGNORECASE) +# ⚠ ФОРМА ОТВЕТА ЧЕЛОВЕКА ШИРЕ, ЧЕМ Я ПРЕДПИСАЛ, И ЭТО ДЕФЕКТ РАЗБОРЩИКА, А НЕ ОТВЕТА. +# Владелец 17.08 ответил «Т3 > Т1 > Т2 > Т4» под заголовком «Порядок читаемости» и +# «**ПОРЯДОК ЧИТАЕМОСТИ:** `Т1 > Т4 = Т3 > Т2`» — обе формы законны и обе прежним регексом +# не читались: он требовал НОМЕРА отрывка сразу после слова. Инструмент, который не берёт +# честный ответ, обесценивает работу читателя, а не читателя. +# Теперь номер необязателен (нет — нумеруем по порядку появления), markdown-разметка и +# обратные кавычки снимаются, латинская T принимается наравне с кириллической. +_ORD_NUM = re.compile(r"ПОРЯДОК[^\n:]*?(\d+)\s*:", re.IGNORECASE) +_ORD = re.compile(r"((?:[ТT]\d+\s*[>=]\s*)+[ТT]\d+)") + + +def orders(txt: str) -> dict[str, str]: + """{номер отрывка: строка порядка}. Номер берётся из подписи «ПОРЯДОК … N:», если она есть; + если её нет — порядки нумеруются по появлению в тексте. + + ⚠ Заведено 17.08 после того, как разборщик не взял ЧЕСТНЫЙ ответ владельца: он написал + порядок под заголовком «## 1. Порядок читаемости», без обязательного номера сразу за словом. + Требовать от человека машинной формы и молча терять его работу — дефект инструмента. + ⚠ Строки порядка ищутся ПО ВСЕМУ тексту, но берутся только те, что стоят ПОСЛЕ слова + «ПОРЯДОК» либо первыми в файле: иначе в разбор попали бы упоминания меток из комментариев + вроде «Т3 и Т1 стоят близко». + + ⚠ ВТОРАЯ ПОЧИНКА ТОГО ЖЕ ДНЯ: порядок ВЕРНОСТИ отбрасывается. Владелец дал по английской + паре две строки — «ПОРЯДОК ЧИТАЕМОСТИ» и «ПОРЯДОК ВЕРНОСТИ», — а разборщик принял вторую за + порядок второго отрывка и молча отсудил ею чужую главу. Прибор мерит ЧИТАЕМОСТЬ; верность + считается детерминированно и в этот разбор не входит. + """ + out: dict[str, str] = {} + for i, m in enumerate(_ORD.finditer(txt), 1): + head = txt[max(0, m.start() - 200):m.start()] + if "ВЕРНОСТ" in head.upper(): + continue + nm = _ORD_NUM.findall(head) + key = nm[-1] if nm else str(len(out) + 1) + out.setdefault(key, m.group(1)) + return out def _ranks(order: str) -> dict[str, float]: @@ -254,8 +290,7 @@ def score() -> int: f = WORK / meta0["pair"] / meta0["reader"] / "answers" / f"{tok}.md" if not f.exists(): continue - got = {m.group(1): m.group(2) for m in _ORD.finditer( - f.read_text(encoding="utf-8", errors="replace"))} + got = orders(f.read_text(encoding="utf-8", errors="replace")) for j, meta in blocks.items(): if j not in got: print(f" ⚠ {tok}/отрывок {j}: порядка нет") @@ -344,6 +379,210 @@ def score_calib() -> int: return 0 +# ── ПАКЕТ ДЛЯ ВЛАДЕЛЬЦА: он читает сам, человеческим глазом ───────────────────────────────────── +# ⚠ ЗАЧЕМ ОТДЕЛЬНЫЙ РЕЖИМ, А НЕ ТОТ ЖЕ ПАКЕТ. Агентские пачки собраны под МАШИННОГО читателя: +# восемь глав на ось, семь вариантов, двадцать заданий. Владелец дважды не стал такое читать и +# сказал прямо: «чтоб не слишком много было, давай какую-нибудь жирную главу на язык». Значит +# ограничение здесь — ЕГО ВРЕМЯ, и панель режется под него, а не под статистику. +# +# ЧТО ОСТАВЛЕНО И ПОЧЕМУ РОВНО ЭТО (четыре варианта вместо семи): +# Z0 боевая связка — то, что стоит в продакшене; без неё сравнивать не с чем; +# ZF ВТОРАЯ генерация той же связки — контроль шума. Если владелец разведёт их далеко, его +# порядок есть шум, и «победа» любого арма внутри этого расстояния ничего не значит. +# Это единственный способ узнать, различает ли он вообще что-то, — и он же самый дешёвый; +# ZP однопроходка — один вызов вместо двух, и по банку с канон-фиксером она впереди связки; +# Z1 критик → полный перепис — ставка владельца в сильнейшей форме. +# Голый черновик НЕ включён намеренно: оба прибора и оба машинных читателя ставят его последним +# с огромным отрывом, спора нет, а глава черновика — это лишние 7 тысяч знаков его чтения. +# Обогащённый черновик с переписом тоже снят: от боевой связки он неотличим ни одним прибором. +# +# ⚠ ДВА ВОПРОСА, КАК ПРОСИЛ ВЛАДЕЛЕЦ («лучше по художке и при этом лучше держит канон смысл»), но +# ЧЕСТНО РАЗВЕДЁННЫЕ. Читаемость может назвать только он. Верность исходнику на КИТАЙСКОМ он +# назвать не может — для этого надо читать китайский, — поэтому там его просят отметить лишь то, +# что видно без исходника: провалы связности, непонятные места, оборванные мысли. На английском +# исходник приложен, и сверка возможна, но объявлена необязательной. Канон терминов при этом уже +# посчитан детерминированно и в пакет НЕ кладётся до его ответа — иначе это подсказка. +OWNER_PANEL = ("Z0", "ZF", "ZP", "Z1") +OWNER_DIR = Path.home() / "books" / "chtenie-vladeltsa-z17" +OWNER_KEY = KEYD / "chtenie-vladeltsa-z17-KEY.json" +OWNER_SALT = "vladelets-z17-2026-08-17" +OWNER_N = {"zh": 1, "en": 2} # «жирная глава на язык»; английские главы втрое короче + +OWNER_HEAD = """# Что ты читаешь и зачем — коротко + +Ниже {n} перевода одного и того же отрывка. Сделаны они разными способами: где-то один вызов +модели, где-то два, где-то с промежуточным критиком. **Какой чем сделан — не сказано намеренно.** +Метки перемешаны, расшифровка лежит в отдельном файле и до твоего ответа не открывается. + +**Зачем это нужно.** Наш измеритель считает локальные ошибки — искажения смысла, кальки, сбитую +вёрстку. Он дёшев и воспроизводим, но у него встроенная слепота: текст может быть без единой +ошибки и при этом мёртвый. На последнем прогоне это подтвердилось числом — порядок по счёту ошибок +и порядок по читаемости совпали лишь на треть, а вариант с наименьшим числом ошибок читался +почти хуже всех. Твой ответ решает, верить ли дальше счётчику или чтению. + +**Одна из четырёх версий — контроль.** Две из них сделаны ОДНИМ И ТЕМ ЖЕ способом, просто модель +вызвана дважды. Какие именно — не скажу. Если ты поставишь их рядом, значит ты различаешь способы; +если разведёшь далеко, значит разница между способами меньше собственного разброса модели, и тогда +никакая «победа» в этом замере ничего не стоит. Специально их искать не надо — просто читай. + +Метки сквозные: `Т1` в первом отрывке и `Т1` во втором — ОДИН И ТОТ ЖЕ способ. +Поэтому порядок можно называть и по каждому отрывку, и один общий на всю пару. + +⚠ **Что может сбить.** Вёрстка выдаёт метод: часть вариантов сливает построчный исходник в русские +абзацы, часть сохраняет разбивку. Это видно ДО чтения — не давай форме решать за тебя. + +--- + +## Что записать — в файл `{answer}` + +1. **ПОРЯДОК ЧИТАЕМОСТИ** — строкой: `Т3 > Т1 > Т2 > Т4`, от лучшего к худшему. + Неразличимые соединяй через `=`. Это главный вопрос: какой текст ты взял бы в книгу. +2. **ПОРЯДОК ВЕРНОСТИ** — {fidelity} +3. **По фразе на вариант**: почему. Годится и «серо, но гладко», и «живой ритм, но во втором + абзаце теряется, кто кому говорит». + +Если какой-то вариант покажется откровенно машинным — скажи прямо, это самое ценное. + +--- +""" + +# ⚠ Верность исходнику владелец может назвать только там, где читает исходный язык. На китайском +# он его не читает, и просить «сверь смысл» значило бы просить невыполнимого — а невыполнимая +# просьба в задании обесценивает и выполнимую часть. Поэтому на zh просят то, что видно БЕЗ +# исходника: провалы связности. Это не подмена вопроса, а его честная граница. +OWNER_FID = { + "zh": ("исходник китайский, и сверять его тебе нечем — поэтому этот пункт ПРОПУСТИ.\n" + " Вместо него отметь то, что видно и без исходника: где текст теряет связность,\n" + " где непонятно, кто что делает, где мысль обрывается. Такие места почти всегда\n" + " и оказываются враньём про исходник."), + "en": ("исходник приложен, английский. Если станешь сверять — назови порядок по верности\n" + " отдельной строкой. Не станешь — пропусти, это необязательно."), +} + + +def emit_owner(only: list[str] | None = None) -> None: + """Пакет владельцу. `only` — пересобрать ТОЛЬКО названные пары. + + ⚠⚠ ПЕРЕ-ЭМИССИЯ НЕ ИМЕЕТ ПРАВА ТРОНУТЬ ПАРУ, КОТОРУЮ ВЛАДЕЛЕЦ УЖЕ ПРОЧЁЛ. Раскладка меток + есть чистая функция соли и набора армов; пересборка ЦЕЛОГО пакета переписала бы ключ и + китайской пары тоже — а её ответ уже дан и расшифрован. Тогда та же расшифровка при следующем + запуске дала бы ДРУГИЕ армы, и работа владельца превратилась бы в мусор молча. Ровно так фаза + Д однажды потеряла вердикт `E0/D0` (+1.56 → +0.38). Поэтому ключ здесь ДОГРУЖАЕТСЯ, а не + создаётся заново, и пары вне `only` не пересчитываются. + ⚠ Прежний ответ по пересобираемой паре не удаляется, а уводится в архив: это улика — работа + владельца, испорченная дефектом МОЕЙ сборки, а не его чтением. + """ + key: dict = {"_что": "слепое чтение ВЛАДЕЛЬЦЕМ, панель захода Д17", "_соль": OWNER_SALT, + "_панель": list(OWNER_PANEL), + "_контроль": "Z0 и ZF — две генерации ОДНОЙ боевой связки; развёл далеко = шум"} + if OWNER_KEY.exists(): + key = {**json.loads(OWNER_KEY.read_text(encoding="utf-8")), **key} + OWNER_DIR.mkdir(parents=True, exist_ok=True) + for pair, langname, srcname in (("zh", "КИТАЙСКАЯ", "китайский"), + ("en", "АНГЛИЙСКАЯ", "английский")): + if only and pair not in only: + print(f" {pair}: не в списке пересборки — ключ и задание НЕ трогаются") + continue + prev = OWNER_DIR / f"ОТВЕТ-{pair}.md" + if prev.exists() and _ORD.search(prev.read_text(encoding="utf-8")): + arch = OWNER_DIR / f"ОТВЕТ-{pair}.ИСПОРЧЕННЫЙ-ПАКЕТ.md" + arch.write_text(prev.read_text(encoding="utf-8"), encoding="utf-8") + prev.unlink() + print(f" {pair}: прежний ответ уведён в {arch.name} (улика, не удалён)") + tx_all = ZS.texts_of(pair) + # «жирная глава»: самая длинная из тех, где панель полна — длинная честнее короткой + cand = [u for u in tx_all if all(tx_all[u].get(a, "").strip() for a in OWNER_PANEL)] + cand.sort(key=lambda u: -sum(len(tx_all[u][a]) for a in OWNER_PANEL)) + take = cand[:OWNER_N[pair]] + ans = OWNER_DIR / f"ОТВЕТ-{pair}.md" + body = [f"# {langname} ПАРА — слепое чтение\n", + OWNER_HEAD.format(n=len(OWNER_PANEL), answer=ans, fidelity=OWNER_FID[pair])] + for j, uid in enumerate(take, 1): + # ⚠ РАСКЛАДКА ОДНА НА ВСЮ ПАРУ, А НЕ СВОЯ У КАЖДОГО ОТРЫВКА (починка 17.08). + # Первая редакция солила раскладку номером главы, и метка `Т1` означала РАЗНЫЕ + # армы в первом и втором английском отрывке. Владелец — как поступил бы любой + # читатель — дал ОДИН сводный порядок на пару и говорил о «Т1» как об одном + # варианте; сопоставить это с армами оказалось нечем, и английская половина + # его работы пропала не по его вине. Машинным читателям своя раскладка на + # единицу нужна (они судят поединично), человеку — вредна. + lay = layout(pair, "OWNER-ALL-v2", "OWNER", OWNER_PANEL) + body += [f"\n# ОТРЫВОК {j}\n", f"## Исходник ({srcname})\n", + tx_all[uid]["_src"], ""] + for lab, arm in lay.items(): + body += ["\n---\n", f"## Отрывок {j}, вариант {lab}\n", tx_all[uid][arm], ""] + key.setdefault(pair, {})[str(j)] = { + "uid": uid, "метки": lay, + "знаков": {a: len(tx_all[uid][a]) for a in OWNER_PANEL}} + (OWNER_DIR / f"ЧТЕНИЕ-{pair}.md").write_text("\n".join(body), encoding="utf-8") + if not ans.exists(): + ans.write_text( + f"# Ответ — {langname.lower()} пара\n\n" + + "".join(f"ПОРЯДОК ЧИТАЕМОСТИ {j}: \n" for j in range(1, len(take) + 1)) + + "\nЗаметки по вариантам:\n", encoding="utf-8") + n = sum(len(tx_all[u][a]) for u in take for a in OWNER_PANEL) + print(f" {pair}: глав {len(take)} · вариантов {len(OWNER_PANEL)} · {n // 1000} тыс. знаков") + OWNER_KEY.write_text(json.dumps(key, ensure_ascii=False, indent=1), encoding="utf-8") + print(f"\nпакет → {OWNER_DIR}") + print(f"ключ → {OWNER_KEY} (НЕ открывать до ответа)") + + +def score_owner() -> int: + if not OWNER_KEY.exists(): + print("ключа нет — сначала --emit-owner") + return 1 + key = json.loads(OWNER_KEY.read_text(encoding="utf-8")) + sc, _bad = ZS.read() + for pair in ("zh", "en"): + f = OWNER_DIR / f"ОТВЕТ-{pair}.md" + if not f.exists() or pair not in key: + continue + txt = f.read_text(encoding="utf-8") + got = orders(txt) + if not got: + print(f"{pair}: порядка нет — ждём ответа владельца") + continue + print(f"\n=== {pair} ===") + # ⚠ СТОРОЖ НЕОДНОЗНАЧНОЙ РАСШИФРОВКИ (заведён 17.08 по факту). Если в ключе у отрывков + # РАЗНЫЕ раскладки меток, а читатель дал ОДИН сводный порядок, то механическое применение + # этого порядка к каждой раскладке даёт столько же РАЗНЫХ расшифровок, сколько отрывков, и + # все они одинаково «правдоподобны». Так и вышло с английской парой: один и тот же ответ + # владельца дал «однопроходка первая» на первом отрывке и «однопроходка ПОСЛЕДНЯЯ» на + # втором. Печатать это как результат — значит выдать артефакт разметки за замер. + maps = [tuple(sorted(m["метки"].items())) for m in key[pair].values()] + if len(set(maps)) > 1 and len(got) < len(key[pair]): + print(" ⛔ РАСШИФРОВКА НЕОДНОЗНАЧНА И НЕ ВЫВОДИТСЯ. У отрывков РАЗНЫЕ раскладки") + print(" меток, а порядок дан ОДИН сводный: одна и та же метка означает в них") + print(" разные армы. Это дефект СБОРКИ ПАКЕТА, а не ответа читателя.") + print(" Что уцелело — устойчивое к раскладке: какие метки читатель поставил") + print(" РЯДОМ или связал знаком «=». Ниже только это.") + for j, meta in key[pair].items(): + if j not in got: + continue + r = _ranks(got[j]) + pairs = [(a, b) for a in r for b in r if a < b and abs(r[a] - r[b]) <= 1] + for a, b in pairs: + arms = {meta["метки"].get(a), meta["метки"].get(b)} + print(f" {a} и {b} рядом → в этом отрывке это {' и '.join(sorted(x for x in arms if x))}") + continue + for j, meta in key[pair].items(): + if j not in got: + continue + r = _ranks(got[j]) + rows = [(meta["метки"][lab], r[lab]) for lab in meta["метки"] if lab in r] + print(f" отрывок {j} (глава {meta['uid']}):") + for arm, rank in sorted(rows, key=lambda x: x[1]): + errs = [ZS._carry(ax) for (pp, u, aa, _h), ax in sc.items() + if pp == pair and aa == arm and u == meta["uid"]] + e = f"{st.mean(errs):.1f}" if errs else "—" + print(f" {rank:.1f} {arm:4s} ошибок по судье {e}") + d = {a: rk for a, rk in rows} + if "Z0" in d and "ZF" in d: + gap = abs(d["Z0"] - d["ZF"]) + print(f" КОНТРОЛЬ ШУМА: две генерации одной связки разведены на {gap:.1f} " + f"позиции → {'порядок несёт сигнал' if gap <= 1 else 'ПОРЯДОК = ШУМ'}") + return 0 + + def selftest() -> int: fails = [] ok = lambda c, m: fails.append(m) if not c else None # noqa: E731 @@ -351,8 +590,11 @@ def selftest() -> int: ok(r == {"Т3": 1.0, "Т1": 2.5, "Т5": 2.5, "Т2": 4.0}, f"разбор порядка сломан: {r}") ok(abs(_spearman([1, 2, 3], [1, 2, 3]) - 1.0) < 1e-9, "Спирмен не даёт +1 на совпадении") ok(abs(_spearman([1, 2, 3], [3, 2, 1]) + 1.0) < 1e-9, "Спирмен не даёт −1 на инверсии") - m = _ORD.search("ПОРЯДОК 2: Т3 > Т1 > Т5") - ok(bool(m) and m.group(1) == "2", "строка порядка не находится") + ok(orders("ПОРЯДОК 2: Т3 > Т1 > Т5") == {"2": "Т3 > Т1 > Т5"}, "нумерованный порядок") + ok(list(orders("## Порядок читаемости\n\nТ3 > Т1 > Т2 > Т4").values()) == ["Т3 > Т1 > Т2 > Т4"], + "ЧЕЛОВЕЧЕСКАЯ форма ответа без номера не читается — на ней уже терялась работа владельца") + ok(list(orders("**ПОРЯДОК ЧИТАЕМОСТИ:** `Т1 > Т4 = Т3 > Т2`").values()) == ["Т1 > Т4 = Т3 > Т2"], + "порядок в markdown-разметке не читается") for pair in Z.PAIRS: u = pick(pair) ok(len(u) == N_UNITS, f"{pair}: единиц {len(u)}, ожидалось {N_UNITS}") @@ -375,6 +617,8 @@ def selftest() -> int: if __name__ == "__main__": a = sys.argv[1:] or ["--selftest"] fn = {"--emit": emit, "--score": lambda: sys.exit(score()), + "--emit-owner": lambda: emit_owner([x for x in a[1:] if x in ("zh", "en")] or None), + "--score-owner": lambda: sys.exit(score_owner()), "--score-calib": lambda: sys.exit(score_calib()), "--selftest": lambda: sys.exit(selftest())}.get(a[0]) if not fn: diff --git a/eval/dovodka/d1_gemini.py b/eval/dovodka/d1_gemini.py index 556bae4c..b13edd5c 100644 --- a/eval/dovodka/d1_gemini.py +++ b/eval/dovodka/d1_gemini.py @@ -167,9 +167,15 @@ def cmd_status() -> None: fail = [a for a in at if not a.get("ok")] print(f"Д1 · собрано {len(have)}/{len(us)} · порог заказа ≥{N_ENOUGH}") print(f" попыток всего {len(at)} · удачных {len(ok)} · отказов {len(fail)}") - if at: - span = (max(a.get("ts", 0) for a in at) - min(a.get("ts", 0) for a in at)) / 3600 - print(f" окно журнала: {span:.1f} ч (заказ требует ≥24 ч до вердикта «не отдаёт»)") + # ⚠ Окно считается ТОЛЬКО по записям с меткой времени. Записи первой редакции её не несли, + # и `.get("ts", 0)` давал минимум 0 — то есть отсчёт от эпохи и «окно 496 315 часов». + # Число выглядело солидно и означало ровно ничего. + ts = [a["ts"] for a in at if a.get("ts")] + if len(ts) >= 2: + print(f" окно журнала: {(max(ts) - min(ts)) / 3600:.2f} ч по {len(ts)} записям с меткой " + f"(заказ требует ≥24 ч ДО вердикта «не отдаёт»)") + elif at: + print(f" окно журнала: записей с меткой времени {len(ts)} — окно не доказуемо") codes: dict = {} for a in fail: codes[str(a.get("code"))] = codes.get(str(a.get("code")), 0) + 1 diff --git a/eval/dovodka/itog_d4.py b/eval/dovodka/itog_d4.py index b72a2383..e62841b2 100644 --- a/eval/dovodka/itog_d4.py +++ b/eval/dovodka/itog_d4.py @@ -24,6 +24,7 @@ и запрет механизирован тем, что пороги считаются ДО контрастов и печатаются рядом с ними. Запуск: itog_d4.py [--gates] # только гейты, без боевых чисел + itog_d4.py --sens # Г5: чувствительность вердикта H-1 к составу пачек ($0) """ from __future__ import annotations @@ -107,17 +108,47 @@ def variant(arm: str, u: dict, half: int) -> str: if arm == "CTRLfloor": return S.floor_pair(u, half) if arm == "CTRLdecoy": - return AJ._plant(S.C.base_a0(u["uid"]))[0] # noqa: SLF001 + return AJ._plant(S.C.base_a0(u["uid"]))[0] return S.text_of(arm, u) +# ⚠ БАЗА АРМА — то, поверх чего он работает и что уже оплачено к моменту его вызова. Держится +# ДАННЫМИ, потому что состав панели у каждой оси свой; числа — медианы по сырью паков 22/23. +BASE_COST_BY_AXIS = { + "d4": {"A0": 0.00603, "A1": 0.00096, "A1B": 0.00096, "A3": 0.00096, "A2": 0.0, + "A4": 0.00603, "A6": 0.00387, "A6F": 0.00387}, + "d3": {"E0": 0.00096, "E1": 0.00096, "E3": 0.00096, "E4": 0.00096 + 0.00789, + "E2": 0.0, "E6": 0.00096, "D0": 0.0}, + "d6": {"J0": 0.00096, "J2": 0.0, "D0": 0.0}, + "d1": {"A0": 0.00603, "R1": 0.00096}, +} + + def _keys_for(root: Path) -> Path: """Каталог ключей ЭТОГО семейства. Ключи разведены (`sud.py`), потому что эмиссия пачек одного семейства переписывала бы раскладку другого. Свод обязан читать ответы тем же ключом, каким они выпускались, иначе метки сопоставятся с ЧУЖИМИ армами и разбор даст 0 меток — ровно это и случилось на первом прогоне семейства Sol.""" fam = "claude" if root.name.startswith("sud-") else root.name.split("-")[0] - return KEYS if fam == "claude" else KEYS.parent / f"{KEYS.name}-{fam}" + if fam == "claude": + return KEYS + # ⚠ Ключи разведены по семействам НЕ на всех осях: разводить их начали на Д1/Д3/Д6, после + # аварии с пере-эмиссией, а китайская ось выпускала ОБА семейства одним ключом. Жёсткое + # `-{fam}` роняло свод на sol-d4-work («62 ответа, разобрано 0 меток»), то есть заявленный + # носитель чисел Д4 не воспроизводил ось вовсе. Разведённый ключ — если он есть; иначе общий. + split = KEYS.parent / f"{KEYS.name}-{fam}" + return split if split.exists() else KEYS + + +# ⚠ СОСТАВ ПАЧЕК — ПАРАМЕТР ТОЛЬКО ДЛЯ ЧУВСТВИТЕЛЬНОСТИ, И ОБА СПИСКА ПУСТЫ ПО УМОЛЧАНИЮ. +# Печатаемый вердикт при этом не меняется ни на знак: `--drop`/`--restore` существуют, чтобы +# пере-считать его ПРИ ДРУГОМ составе и напечатать разницу рядом, а не чтобы подобрать удобный. +# Заведены под Г5 (находка ревизии P13: снятие двух пачек прогона-валидации 11.08 переворачивает +# решение по H-1). Без такого рычага пере-счёт делался копией дерева — то есть числами, которые +# никто не может воспроизвести. +DROP = set(next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--drop=")), "").split(",")) - {""} +RESTORE = set(next((a.split("=", 1)[1] for a in sys.argv + if a.startswith("--restore=")), "").split(",")) - {""} def read_family(root: Path) -> tuple[dict, list]: @@ -134,9 +165,19 @@ def read_family(root: Path) -> tuple[dict, list]: continue key_all = json.loads(kf.read_text(encoding="utf-8")) for f in sorted((root / f"{half}-answers").glob("*.txt")): + tok = f.name.split(".")[0] if ".ANNULLED" in f.name: + if tok not in RESTORE: + continue + # ⚠ Пачка, отправленная в карантин и ПЕРЕ-СУЖЕННАЯ, лежит на диске дважды. + # Вернуть карантинную копию значит посчитать единицу дважды разными судьями — + # молча, потому что ключ у обеих один. Возвращать можно только то, чему замены нет. + if (f.parent / f"{tok}.txt").exists(): + raise SystemExit(f"⛔ {tok} пере-суждена: возврат карантинной копии удвоил бы " + f"единицу; --restore допустим только для пачек без замены") + if tok in DROP: continue - key = key_all.get(f.stem) + key = key_all.get(tok) if not key: continue txt = f.read_text(encoding="utf-8", errors="replace") @@ -146,13 +187,13 @@ def read_family(root: Path) -> tuple[dict, list]: # ⚠ `[ \t]*`, а не `\s*`: `\s` включает перевод строки, и оценка # могла считаться со СЛЕДУЮЩЕЙ строки. Тот же класс `absjudge.py:351` # чинил у себя; здесь он оставался. - m = re.search(rf"^{lab}-{a}:[ \t]*(\d+)", txt, re.M) + m = re.search(rf"^{lab}-{a}:[ \t]*(\d+)", txt, re.MULTILINE) if m: sc[a] = int(m.group(1)) if len(sc) < 4: bad.append((root.name, half, f.stem, lab, "не все оси")) continue - w = re.search(rf"^{lab}-ПОЧЕМУ:\s*(.*)$", txt, re.M) + w = re.search(rf"^{lab}-ПОЧЕМУ:\s*(.*)$", txt, re.MULTILINE) why = w.group(1) if w else "" if sum(sc.values()) and not why.strip(): bad.append((root.name, half, f.stem, lab, "ненулевая без обоснования")) @@ -221,16 +262,16 @@ def three_axes(d: dict) -> None: ⚠ Канон считается на ВЫХОДЕ арма, а не на его входе: вход мерился при покупке, а сверяется качество результата. Цена — медиана оплаченной клетки арма из сырья, а не из сметы. """ - import json as _j # noqa: PLC0415 - import statistics as _st # noqa: PLC0415 + import json as _j + import statistics as _st us = {x["uid"]: x for x in S.units()} print("\n" + "=" * 78) print("ТРИ ОСИ ПО КАЖДОМУ АРМУ (требование заказа :95) — судья · канон · цена") print("=" * 78) - print(f" {'арм':5s}{'судья':>8s}{'канон':>9s}{'$/клетка':>11s} что это") + print(f" {'арм':5s}{'судья':>8s}{'канон':>9s}{'$/клетка':>11s}{'$/единица':>12s} что это") what = WHAT_AXIS[AXIS] - _unused = {"A0": "", "A4": "", - "A6": "", "A6F": "", + _dead = { + "A0": "", "A4": "", "A6": "", "A6F": "", "A3": "черновик + смысловой критик", "A2": "однопроходка уравненного мандата", "A1B": "черновик + флаги (батарея+канон)", "A1": "черновик + только канон-флаги"} for arm in ALL_ARMS: @@ -244,20 +285,227 @@ def three_axes(d: dict) -> None: k, n = S.C.BANK.coverage(u["source"], t) if n: cov.append(k / n) - pr = [] + pr, per_unit = [], {} # ⚠ Глоб по тегу арма захватывал и клетки КРИТИКА (`dv-a-a3-crit-*`), поэтому # медиана «$/клетка» у A3 и A6 считалась по смеси двух ролей. Найдено приёмкой. for f in S.C.OUT.glob(f"{S.C.tag(arm, '')}*.json"): - if any(x in f.name for x in (".ERROR", ".LENGTH", ".FLAGSV1", "-crit-")): + # ⚠ Суффиксы карантина перечислены ПОЛНОСТЬЮ. `.STALE` и `.TWIN` заведены фазой Д на ходу + # (устаревшие бесплатные клетки арма и побайтные близнецы пола), и их отсутствие здесь + # тянуло медиану цены вниз нулевыми клетками. Найдено чтением собственных коммитов. + if any(x in f.name for x in (".ERROR", ".LENGTH", ".FLAGSV1", ".STALE", ".TWIN")): continue r = _j.loads(f.read_text(encoding="utf-8")) - if r.get("cost_usd"): - pr.append(r["cost_usd"]) + c = r.get("cost_usd") or 0 + # ⚠ ДВЕ РАЗНЫЕ ВЕЛИЧИНЫ, И ПУТАТЬ ИХ ДОРОГО. «$/клетка» — цена ОДНОГО вызова; + # «$/единица» — цена всей работы арма над единицей ПЛЮС база, на которой он стоит. + # У армов с двумя вызовами (критик + фиксер) это отличается втрое: у A3 клетка + # фиксера 0.00644, а единица — 0.02014 плюс черновик. Отчёт печатал первую цифру + # в колонке, по которой сравнивают экономику, и ставка H-2б выглядела средней + # по цене, будучи самой дорогой в панели. + if "-crit-" not in f.name and c: + pr.append(c) + uid = r.get("uid") or f.stem.split("-")[-1] + per_unit[uid] = per_unit.get(uid, 0.0) + c + base = BASE_COST_BY_AXIS.get(AXIS, {}).get(arm, 0.0) + full = (_st.median([v + base for v in per_unit.values() if v > 0]) + if any(v > 0 for v in per_unit.values()) else base) print(f" {arm:5s}{_st.mean(sc):8.2f}{(_st.mean(cov) if cov else 0):9.3f}" - f"{(_st.median(pr) if pr else 0):11.5f} {what.get(arm, '')}") + f"{(_st.median(pr) if pr else 0):11.5f}{full:12.5f} {what.get(arm, '')}") print(" судья — ошибок на единицу (ВЕРНОСТЬ+ЯЗЫК), МЕНЬШЕ лучше") print(" канон — доля покрытия банка на ВЫХОДЕ арма, БОЛЬШЕ лучше") - print(" цена — медиана оплаченной клетки арма по сырью; A0 куплен прошлыми паками") + print(" $/клетка — медиана ОДНОГО оплаченного вызова арма") + print(" $/единица — вся работа арма над единицей ПЛЮС база, на которой он стоит:") + _b = BASE_COST_BY_AXIS.get(AXIS, {}) + print(f" {' · '.join(f'{a}+{c:.5f}' for a, c in sorted(_b.items()) if c)}") + print(" (замер по сырью: черновик flash 0.00096, перепис dspro 0.00507)") + + +def margins_half(d: dict, a: str, b: str) -> list[float]: + """Перевес ЗА арм `a`, посчитанный ВНУТРИ половины и лишь потом усреднённый по единице. + + ⚠ Зачем вторая формула рядом со штатной. Наборы p1/p2 судятся РАЗНЫМИ сессиями, и между ними + намерен систематический сдвиг строгости (находка ревизии P07: пол claude даёт +1.8 ошибки в + пользу p2). Штатная `margins` берёт среднее арма по тем половинам, где он ЕСТЬ, — а половины + у армов заполнены не одинаково (`A6` 15/16 против `A0` 30/31). Там, где арм присутствует + только в одной половине, в его число входит уровень ЭТОЙ сессии, и разность двух армов несёт + кусок межсессионного сдвига как будто это разница армов. Здесь разность берётся там, где оба + арма судила ОДНА сессия, поэтому уровень сессии сокращается тождественно. + """ + out = [] + for uid in sorted({k[0] for k in d}): + per = [d[(uid, b, h)]["carry"] - d[(uid, a, h)]["carry"] + for h in ("p1", "p2") if (uid, a, h) in d and (uid, b, h) in d] + if per: + out.append(st.mean(per)) + return out + + +def _levels(d: dict) -> dict: + """Средний счёт боевых армов по СЕССИЯМ судейского журнала: {(проход, судья): (n, среднее)}. + + ⚠ Это единственный оставшийся способ проверить посылку P13. Находка называла пачки + `0dce349331`/`0ffee70740` «другим режимом замера» на двух основаниях: не замороженный промт и + уровень счёта 9.94 против 6.39 у основной волны. Транскрипта сессии на диске больше нет + (проверено `find` по всем каталогам агентов) — промт сверить не с чем. Уровень сверить можно, + и вопрос ставится честно: выбивается ли д-01 из РАЗБРОСА ОСТАЛЬНЫХ СЕССИЙ или такой шаг между + сессиями у этой оси обычный. Во втором случае снятие двух пачек — не норма, а выбор. + """ + led = json.loads((Path.home() / "books" / "dovodka" / "agent-runs.json").read_text("utf-8")) + battle = set(ALL_ARMS) + out: dict = {} + for r in led: + if r.get("run") != AXIS: + continue + half = r.get("pass_") + sc = [v["carry"] for (uid, arm, h), v in d.items() + if h == half and arm in battle and uid in _uids_of(r, half)] + if sc: + out[(half, r["judge"], tuple(r["tokens"]))] = (len(sc), st.mean(sc)) + return out + + +def _uids_of(run: dict, half: str) -> set: + """uid единиц, которые судила эта сессия. Пачка названа ТОКЕНОМ, ключ переводит его в uid.""" + kf = KEYS / f"{AXIS}{half}-KEY.json" + if not kf.exists(): + return set() + key_all = json.loads(kf.read_text(encoding="utf-8")) + out = set() + for tok in run.get("tokens", []): + for meta in (key_all.get(tok) or {}).values(): + out.add(meta["uid"]) + return out + + +def sens() -> int: + """Г5 — ЧУВСТВИТЕЛЬНОСТЬ ВЕРДИКТА H-1 К СОСТАВУ ПАЧЕК. $0, ни одного вызова к моделям. + + ⚠ ПРАВИЛО РЕШЕНИЯ ОБЪЯВЛЕНО ДО ПРОГОНА (иначе это подбор сценария, а не замер): + вердикт по H-1 считается ПЕРЕ-РЕШЁННЫМ, только если он одинаков во ВСЕХ сценариях, снятие + в которых обосновано нормой проекта. Если знак решения зависит от выбора сценария — печатается + именно это, а не удобная половина. Запас над порогом печатается числом рядом: решение, + выигранное с запасом меньше собственного шага шкалы (1 ошибка), вердиктом не является. + """ + print("=" * 78) + print("Г5 — ЧУВСТВИТЕЛЬНОСТЬ H-1 К СОСТАВУ ПАЧЕК ($0, пере-анализ уже купленных ответов)") + print("=" * 78) + print(" правило решения объявлено в докстринге ДО прогона: вердикт пере-решён только при") + print(" совпадении во ВСЕХ обоснованных сценариях; запас печатается числом\n") + global DROP, RESTORE # состав пачек — параметр сценария, см. их объявление + grid = [ + ("S0 дерево как есть (что печатает свод)", set(), set()), + ("S1 −валидация 11.08 (P13)", {"0dce349331", "0ffee70740"}, set()), + ("S2 +69de717f3f назад (база ревизии)", set(), {"69de717f3f"}), + ("S3 база ревизии −валидация (счёт P13)", {"0dce349331", "0ffee70740"}, {"69de717f3f"}), + ] + keep = (DROP, RESTORE) + rows = [] + for name, drop, restore in grid: + DROP, RESTORE = drop, restore + d, _bad = read_family(FAMILIES["claude"]) + sd, n = floor_sd(d) + thr = 2.8 * sd / (n ** 0.5) if n > 1 else float("nan") + line = {"name": name, "sd": sd, "n": n, "thr": thr, "d": d} + for a, b, _w in PRIMARY: + m = margins(d, a, b) + mh = margins_half(d, a, b) + line[a] = (len(m), st.mean(m), BO.sign_perm_p(m) if len(m) > 2 else float("nan"), + len(mh), st.mean(mh) if mh else float("nan"), + BO.sign_perm_p(mh) if len(mh) > 2 else float("nan")) + rows.append(line) + DROP, RESTORE = keep + for a, b, what in PRIMARY: + print(f"### КОНТРАСТ {a}/{b} — {what}") + print(f" {'сценарий':40s}{'пол n':>6s}{'порог':>7s}{'перевес':>9s}" + f"{'запас':>7s}{'p Холма*':>9s} вердикт") + for r in rows: + mean = r[a][1] + gap = abs(mean) - r["thr"] + adj = holm([r[x][2] if r[x][2] == r[x][2] else 1.0 for x, _y, _z in PRIMARY]) + pa = adj[[x for x, _y, _z in PRIMARY].index(a)] + ok = gap > 0 and pa < 0.05 + print(f" {r['name']:40s}{r['n']:6d}{r['thr']:7.2f}{mean:+9.2f}{gap:+7.2f}" + f"{pa:9.4f} {'ПЕРЕШЁЛ' if ok else 'ниже порога'}") + print(f" {'—— тот же контраст ВНУТРИ половины (P07)':40s}") + for r in rows: + _k, _m, _p, kh, meanh, ph = r[a] + gap = abs(meanh) - r["thr"] + print(f" {r['name']:40s}{r['n']:6d}{r['thr']:7.2f}{meanh:+9.2f}{gap:+7.2f}" + f"{ph:9.4f} {'ПЕРЕШЁЛ' if gap > 0 and ph < 0.05 else 'ниже порога'} ед.{kh}") + print() + print(" * Холм считается ПО ТРЁМ контрастам внутри своего сценария, как в пре-реге\n") + print("=" * 78) + print("УРОВЕНЬ СУДЕЙСКИХ СЕССИЙ — выбивается ли прогон-валидация из разброса остальных") + print("=" * 78) + lv = _levels(rows[0]["d"]) + for half in ("p1", "p2"): + xs = [(j, n, m, t) for (h, j, t), (n, m) in lv.items() if h == half] + if not xs: + continue + vals = [m for _j, _n, m, _t in xs] + print(f" {half}: сессий {len(xs)} · среднее {st.mean(vals):.2f} · " + f"разброс {min(vals):.2f}…{max(vals):.2f}" + + (f" · sd {st.pstdev(vals):.2f}" if len(vals) > 1 else "")) + for j, n, m, t in sorted(xs, key=lambda x: -x[2]): + mark = " ← прогон-валидация 11.08" if "0dce349331" in t else "" + print(f" {j:8s} клеток {n:3d} ошибок/клетку {m:6.2f}{mark}") + print("\n" + "=" * 78) + print("СИСТЕМАТИЧЕСКИЙ СДВИГ НАБОРОВ (P07) — на нём стоит вся формула порога") + print("=" * 78) + d0 = rows[0]["d"] + both = [(v["carry"] - d0[(u, a, "p2")]["carry"]) + for (u, a, h), v in d0.items() if h == "p1" and (u, a, "p2") in d0 and a in ALL_ARMS] + fl = [d0[(u, "CTRLfloor", "p1")]["carry"] - d0[(u, "CTRLfloor", "p2")]["carry"] + for u in {k[0] for k in d0} + if (u, "CTRLfloor", "p1") in d0 and (u, "CTRLfloor", "p2") in d0] + print(f" боевые армы, одна единица в обеих половинах: n={len(both)} " + f"сдвиг p1−p2 {st.mean(both):+.2f} · sd {st.pstdev(both):.2f}") + print(f" пол (те же половины): n={len(fl)} сдвиг {st.mean(fl):+.2f} · sd {st.pstdev(fl):.2f} " + f"· p={BO.sign_perm_p(fl):.4f}") + print(" ⇒ если сдвиг пола ≈ сдвиг боевых армов, пол меряет РАЗНИЦУ СЕССИЙ, а не шум оценки;") + print(" сбалансированный по половинам контраст этот сдвиг сокращает, несбалансированный — нет") + print("\n БАЛАНС ПОЛОВИН ПО АРМАМ (несбалансированный арм несёт уровень своей сессии):") + for arm in ALL_ARMS: + n1 = sum(1 for (u, a, h) in d0 if a == arm and h == "p1") + n2 = sum(1 for (u, a, h) in d0 if a == arm and h == "p2") + print(f" {arm:5s} p1 {n1:3d} · p2 {n2:3d}" + (" ⚠ перекос" if abs(n1 - n2) > 1 else "")) + # ⚠ КАЛИБРОВКА ПОРОГА ПО ЗНАКУ (находка ревизии №6: R73 против P40). Две выжившие находки + # называли порог смещённым в ПРОТИВОПОЛОЖНЫЕ стороны, и ни одна не проверялась на ЭТОЙ оси: + # R73 мерила на проходе `border` (контраст в 1.40× шумнее пола), P40 рассуждала. Здесь то же + # меряется прямо: шум САМОГО контраста той же структуры — перевес, посчитанный в p1, против + # перевеса в p2. Если он равен полу, порог откалиброван; больше — вердикты смелее данных; + # меньше — прибор строже, чем нужно. + sd0, n0 = floor_sd(d0) + print("\n" + "=" * 78) + print("КАЛИБРОВКА ПОРОГА ПО ЗНАКУ — пол против ШУМА САМОГО КОНТРАСТА (ревизия №6)") + print("=" * 78) + print(f" пол, из которого строится порог: n={n0} sd={sd0:.4f} → порог {2.8 * sd0 / n0**0.5:.4f}") + for a, b, _w in PRIMARY: + xs = [] + for u in {k[0] for k in d0}: + if all((u, x, h) in d0 for x in (a, b) for h in ("p1", "p2")): + xs.append((d0[(u, b, "p1")]["carry"] - d0[(u, a, "p1")]["carry"]) + - (d0[(u, b, "p2")]["carry"] - d0[(u, a, "p2")]["carry"])) + if len(xs) < 3: + continue + sd = st.pstdev(xs) + own = 2.8 * sd / len(xs) ** 0.5 + m = margins(d0, a, b) + print(f" {a}/{b}: n={len(xs):2d} sd={sd:.4f} ({sd / sd0:.2f}× пола) → СВОЙ порог {own:.4f}" + f" · перевес {st.mean(m):+.4f} · запас {abs(st.mean(m)) - own:+.4f}") + print(" ⚠ «свой порог» — не замена объявленному: пре-рег зафризил формулу ПО ПОЛУ, и менять её") + print(" после взгляда на вердикты нельзя. Это проверка КАЛИБРОВКИ, а не второе решающее правило.") + # ⚠ Вторая сторона той же калибровки: пол строится на ДВУХ ГЕНЕРАЦИЯХ, то есть несёт и шум + # порождения, и шум судьи. Чистый шум судьи виден там, где текст ОДИН И ТОТ ЖЕ, а сессии + # разные — на боевых армах, которые лежат в обеих половинах. Если он БОЛЬШЕ пола, порог занижен. + print("\n ЧИСТЫЙ ШУМ СУДЬИ (ОДИН И ТОТ ЖЕ текст, две сессии) против пола:") + for arm in ALL_ARMS: + xs = [d0[(u, arm, "p1")]["carry"] - d0[(u, arm, "p2")]["carry"] for u in {k[0] for k in d0} + if (u, arm, "p1") in d0 and (u, arm, "p2") in d0] + if len(xs) > 2: + print(f" {arm:5s} n={len(xs):2d} среднее {st.mean(xs):+.2f} sd={st.pstdev(xs):.4f}" + f" ({st.pstdev(xs) / sd0:.2f}× пола)") + return 0 def main() -> int: @@ -350,4 +598,4 @@ def main() -> int: if __name__ == "__main__": - sys.exit(main()) + sys.exit(sens() if "--sens" in sys.argv else main()) diff --git a/eval/dovodka/ja6.py b/eval/dovodka/ja6.py new file mode 100644 index 00000000..4239921e --- /dev/null +++ b/eval/dovodka/ja6.py @@ -0,0 +1,505 @@ +#!/usr/bin/env python3 +"""ЯПОНСКАЯ НОГА H-4 — слепой проход `J0` против `J6`. $0 (агент-сессии + харнесс владельца). + +Гипотеза H-4: перевес `deepseek-v4-pro` в редакторской роли есть свойство пары zh→ru, и на другой +паре ПОРЯДОК ЖИЛЬЦОВ может смениться. Порядок нельзя увидеть, имея на паре одного редактора: ось +Д6 меряла «покупает ли редактор что-нибудь вообще», а не «какой редактор лучше». На zh панель есть +(эксп-22), на en второй редактор куплен (`E6`), на ja его не было — требование заказа (:115) +печатать сравнение порядка между zh/en/ja поэтому не исполнялось. + +⚠ СТАТУС РЕЗУЛЬТАТА — ОПИСАТЕЛЬНЫЙ, и это не смягчается никаким исходом: ось объявлена +разведочной ДО денег (`power.FORCED_DESCRIPTIVE`, n=9, MDE 2.90 против цели 2.00). + +⚠ ЧЕМУ НАУЧИЛ `tier` И ЧТО ЗДЕСЬ СДЕЛАНО ИНАЧЕ (пре-регистрируется ДО первого ответа): + 1. ПОЛОВИНА ПОЛА НЕ ЯВЛЯЕТСЯ БОЕВЫМ АРМОМ. В паке 23 `CTRLfloorA` побайтно равнялся арму `T1` + в 16/16 — контроль сравнивал `T1` сам с собой. Здесь пара пола — две генерации БОЕВОГО + РЕДАКТОРА (`J0` и `JFLO`), и они РАЗВЕДЕНЫ ПО НАБОРАМ: `J0` живёт в наборе p1, `JFLO` — в p2. + Судья никогда не видит обе половины в одной пачке, а порог несёт межсессионную компоненту. + 2. ПОЛ СНЯТ С ТОЙ ОПЕРАЦИИ, КОТОРАЯ В ПАНЕЛИ. Пол оси Д6 снят с точечного фиксера — операции + почти детерминированной (4 побайтных близнеца из 8 пар). Панель здесь — панель РЕДАКТОРОВ, + поэтому и шум мерится повторной генерацией редактора. + 3. ДОНОР ДЕКОЯ УРАВНЕН: в паке 23 донором во всех 16 единицах был `R0`, то есть чувствительность + сертифицировалась в окрестности одного арма. Здесь донор чередуется `J0`/`J6` по чётности. + 4. МАРЖЕВЫЙ ДЕКОЙ ЕСТЬ (норма П-2) — без него «не различимо» неотличимо от слепоты прибора. + 5а. ⚠ ОГРАНИЧЕНИЕ ГЕЙТА ЧУВСТВИТЕЛЬНОСТИ ОБЪЯВЛЯЕТСЯ ДО ПРОГОНА: маржевый декой садится лишь на + 5 единиц из 9 — регексы посадок не находят на этой книге нужных мест. Расширенный список + (`--wide-plants`, который на другой японской выборке давал 8/9) проверен и НЕ помогает: + те же 5 из 9. Значит гейт П-2 на этой ноге снят пятью точками, и право говорить «не хуже» + он подпирает слабее, чем на осях с полным покрытием. Сказано ДО ответов, а не после. + 5. МОЛЧАЛИВЫЙ НОЛЬ ЗАПРЕЩЁН заданием, а рубрика несёт классы, которые пачка `tier` пропускала. + 6. КЛЮЧ СВОЙ, соль своя. Ключи осей Д6 и `tier` не трогаются: раскладка меток есть функция + соли, uid и НАБОРА армов, и эмиссия поверх переписала бы отсуженную раскладку. + +⚠ ПАРИТЕТ ОБВЯЗОК (норма П-4): оба семейства получают ПОБАЙТНО ОДНО задание и одни правила счёта. +Расхождение прохода `tier` в 12 раз частично куплено тем, что харнессу Sol дали правило плотности +(«две одинаковые ошибки — это два»), которого агенты claude не получали. Здесь это правило стоит +в САМОМ задании, то есть достаётся обоим. + +Запуск: ja6.py --emit | --score | --sol | --score-sol | --selftest +""" +from __future__ import annotations + +import collections +import hashlib +import importlib.util +import json +import re +import statistics as st +import sys +from pathlib import Path + +REPO = Path("/home/ubuntu/projects/textmachine") +ZONE = Path(__file__).resolve().parent +for d in ("dovodka", "editor_tier", "tenant_panel", "role_topology", "bank_arbitration"): + sys.path.insert(0, str(REPO / "eval" / d)) + +OUT = Path.home() / "books" / "dovodka" +WORK = Path.home() / "books" / "judging" / "ja6" +KEYD = OUT / "blind-keys-ja6" +AX = ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА") +CARRY = ("ВЕРНОСТЬ", "ЯЗЫК") +SALT = "ja6-2026-08-15" +PER_SESSION = 4 +SETS = ("p1", "p2") +NL = chr(10) # перевод строки как имя: файл собирается генератором внутри f-строк + +_axre = re.compile(r"^[ТT](\d+)-(ВЕРНОСТЬ|ТЕРМИН|ЯЗЫК|ФОРМА):\s*(\d+)", re.MULTILINE) +_whyre = re.compile(r"^[ТT](\d+)-ПОЧЕМУ:(.*)$", re.MULTILINE) + + +def _load(name: str, path: Path): + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod + spec.loader.exec_module(mod) + return mod + + +def _sud(): + return sys.modules.get("sud") or _load("sud", ZONE / "sud.py") + + +def cell(tag: str) -> str: + f = OUT / f"{tag}.json" + if not f.exists(): + return "" + d = json.loads(f.read_text(encoding="utf-8")) + return (d.get("content") or "") if d.get("finish") == "stop" else "" + + +def units() -> list[dict]: + MJ = sys.modules.get("mat_ja6") or _load("mat_ja6", ZONE / "material_ja.py") + return MJ.units() + + +def panel(u: dict, half: str, idx: int) -> dict[str, str]: + """Армы набора. `J0` только в p1, `JFLO` только в p2 — половины пола не встречаются в пачке.""" + S = _sud() + uid = u["uid"] + j0, j6, d0 = cell(f"dv-c-j0-{uid}"), cell(f"dv-h-j6-{uid}"), cell(f"dv-c-d0-{uid}") + jflo = cell(f"dv-h-jflo-{uid}") + base = j0 if half == "p1" else jflo + if not (base.strip() and j6.strip() and d0.strip()): + return {} + donor = j0 if idx % 2 == 0 else j6 # донор декоя уравнен по армам + out = {("J0" if half == "p1" else "JFLO"): base, "J6": j6, "D0": d0} + dec, n_d = S.AJ._plant(donor) if hasattr(S, "AJ") else (None, 0) + if dec and n_d >= 2: + out["CTRLdecoy"] = dec + mg, n_m = S.margin_plant(donor) + if n_m >= 2: + out["CTRLmargin"] = mg + return out + + +def layout(uid: str, half: str, arms) -> dict[str, str]: + order = sorted(arms) + seed = hashlib.sha256(f"{SALT}|{uid}|{half}|{'|'.join(order)}".encode()).hexdigest() + rank = sorted(order, key=lambda a: hashlib.sha256(f"{seed}|{a}".encode()).hexdigest()) + return {f"Т{i}": a for i, a in enumerate(rank, 1)} + + +HEAD = """Ты — литературный редактор-эксперт. Тебе дан ИСХОДНИК на японском и {n} независимых русских перевода этого же фрагмента под слепыми метками. + +Посчитай ОШИБКИ в каждом варианте по типам: + ВЕРНОСТЬ — сказано не то, что в исходнике: искажение, инверсия, выдумка, пропуск факта. + Сюда же входят, и их пропускают чаще всего: + · инверсия адресата или участника реплики (кто кому говорит, кто кого бьёт); + · потеря или подмена разряда, ранга, числа, единицы измерения; + · потеря вежливостного или social-регистра там, где он несёт смысл; + · состояние действия: совершилось против ещё не совершилось; + · снятое или, наоборот, добавленное отрицание. + ТЕРМИН — имя или термин передан не так, как в других местах того же текста. + ЯЗЫК — то, чего носитель не напишет: калька, канцелярит, несуществующее слово, кривой + порядок слов, рассогласование. + ФОРМА — вёрстка и пунктуация: оформление реплик, кавычки, тире, абзацы. + +Оцени КАЖДЫЙ вариант ОТДЕЛЬНО против исходника — не сравнивая варианты между +собой. Порядок вариантов ничего не значит и специально перемешан. + +Числа — это СЧЁТ ошибок, а не оценка по шкале. Две одинаковые ошибки в одном варианте — это два. + +⚠ НОЛЬ — ЭТО УТВЕРЖДЕНИЕ, А НЕ ОТСУТСТВИЕ ОТВЕТА. Ноль означает «я сверил этот вариант с +исходником по этой оси и дефектов не нашёл». Поэтому ПОЧЕМУ обязательно для КАЖДОЙ метки, в том +числе при нулях: если по всем осям ноль, напиши «прочитано, дефектов не найдено». Пустое ПОЧЕМУ +не принимается и на диск не попадает. + +Ответ ЗАПИШИ ФАЙЛОМ в {path} — ровно в таком виде и ничем больше, блок на каждую метку: + +{skeleton} +ПОЧЕМУ для НЕНУЛЕВОЙ оси: короткая ЦИТАТА из текста через « | ». +Число без цитаты не принимается и на диск не попадает. +""" + + +def emit() -> None: + key: dict[str, dict] = {"_проход": "ja6", "_семейство": ["J6/J0"], + "_контроль": ["J0/D0"], "_пол": ["J0(p1) против JFLO(p2)"]} + made = 0 + for half in SETS: + (WORK / f"{half}-tasks").mkdir(parents=True, exist_ok=True) + (WORK / f"{half}-answers").mkdir(parents=True, exist_ok=True) + for idx, u in enumerate(units()): + for half in SETS: + texts = panel(u, half, idx) + if not texts: + print(f" ⚠ {u['uid']}/{half}: панель неполна, пропущено") + continue + lay = layout(u["uid"], half, tuple(texts)) + tok = hashlib.sha256(f"{SALT}|{u['uid']}|{half}".encode()).hexdigest()[:10] + skel = "\n\n".join("\n".join(f"{lab}-{a}: <число>" for a in AX) + + f"\n{lab}-ПОЧЕМУ: <цитаты>" for lab in lay) + body = [HEAD.format(n=len(lay), + path=WORK / f"{half}-answers" / f"{tok}.txt", skeleton=skel), + "", "=" * 60, "ИСХОДНИК:", u["source"]] + for lab, arm in lay.items(): + body += ["", "=" * 60, f"{lab}:", texts[arm]] + (WORK / f"{half}-tasks" / f"{tok}.txt").write_text("\n".join(body), encoding="utf-8") + key[tok] = {lab: {"arm": a, "uid": u["uid"], "half": half, + "kind": "контроль" if a.startswith("CTRL") else "боевой", + "sig": hashlib.sha256(texts[a].encode()).hexdigest()[:12]} + for lab, a in lay.items()} + made += 1 + KEYD.mkdir(parents=True, exist_ok=True) + (KEYD / "ja6-KEY.json").write_text(json.dumps(key, ensure_ascii=False, indent=1), + encoding="utf-8") + (KEYD / "SALT-ja6.txt").write_text(SALT + "\n", encoding="utf-8") + toks = [t for t in key if not t.startswith("_")] + print(f"заданий {made} → {WORK}/{{p1,p2}}-tasks") + print(f"ключ → {KEYD} (судье НЕ давать)") + for i in range(0, len(toks), PER_SESSION): + print(f" сессия {i // PER_SESSION + 1}: " + " · ".join(toks[i:i + PER_SESSION])) + + +def read(root: Path) -> tuple[dict, list]: + key = json.loads((KEYD / "ja6-KEY.json").read_text(encoding="utf-8")) + out, bad = {}, [] + # ⚠ Две раскладки ответов, и обе законны: своё семейство пишет в `p1-answers`/`p2-answers` + # (наборы разведены каталогами), внешний харнесс — в ПЛОСКИЙ `answers/`, потому что его + # разводит не каталог, а оркестраторский промт «по одному агенту на промт». Счётчик обязан + # читать обе: первая редакция знала только про первую и на пакете Sol печатала «ответов нет». + dirs = [root / f"{half}-answers" for half in SETS] + if (root / "answers").exists(): + dirs.append(root / "answers") + for d in dirs: + if not d.exists(): + continue + for f in sorted(d.glob("*.txt")): + km = key.get(f.stem) + if not isinstance(km, dict): + continue + t = f.read_text(encoding="utf-8", errors="replace") + cells: dict[str, dict[str, int]] = collections.defaultdict(dict) + for m in _axre.finditer(t): + cells["Т" + m.group(1)][m.group(2)] = int(m.group(3)) + why = {"Т" + m.group(1): m.group(2).strip() for m in _whyre.finditer(t)} + for lab, ax in cells.items(): + meta = km.get(lab) + if not isinstance(meta, dict): + continue + if len(ax) < len(AX): + bad.append(f"{f.stem}/{lab}: не все четыре оси") + continue + if not why.get(lab): + bad.append(f"{f.stem}/{lab}: ПУСТОЕ обоснование (запрещено заданием)") + out[(meta["uid"], meta["arm"])] = sum(ax.get(a, 0) for a in CARRY) + return out, bad + + +def _sign_p(diffs: list[float]) -> float: + import math + nz = [d for d in diffs if d != 0] + n = len(nz) + if not n: + return 1.0 + k = sum(1 for d in nz if d > 0) + tail = sum(math.comb(n, i) for i in range(min(k, n - k) + 1)) / 2 ** n + return min(1.0, 2 * tail) + + +def score(root: Path | None = None) -> int: + root = root or WORK + sc, bad = read(root) + if not sc: + print(f"ответов в {root} нет") + return 1 + uids = sorted({u for u, _ in sc}) + print(f"единиц {len(uids)} · клеток {len(sc)} · замечаний годности {len(bad)}") + for b in bad[:8]: + print(" ⚠", b) + vals = [v for (u, a), v in sc.items() if not a.startswith("CTRL")] + zeros = sum(1 for v in vals if v == 0) / max(len(vals), 1) + print(f"\nГЕЙТ ПЛОТНОСТИ: ошибок/клетку {st.mean(vals):.2f} · доля нулей {zeros:.0%} " + f"→ {'ГОДНО' if zeros < 0.25 else '⛔ ПОЛ ШКАЛЫ'}") + fl = [sc[(u, "J0")] - sc[(u, "JFLO")] for u in uids if (u, "J0") in sc and (u, "JFLO") in sc] + sd = st.pstdev(fl) if len(fl) > 1 else 0.0 + thr = 2.8 * sd / max(len(fl), 1) ** 0.5 if fl else 0.0 + print(f"СВОЙ ПОЛ (две генерации редактора, половины в РАЗНЫХ наборах): пар {len(fl)} · " + f"sd {sd:.2f} → ПОРОГ {thr:.2f}") + for name, arm in (("ГРУБЫЙ ДЕКОЙ", "CTRLdecoy"), ("МАРЖЕВЫЙ ДЕКОЙ (гейт П-2)", "CTRLmargin")): + d = [sc[(u, arm)] - sc[(u, "J0")] for u in uids if (u, arm) in sc and (u, "J0") in sc] + if d: + v = "ПРОЙДЕН" if st.mean(d) > thr else "⛔ НЕ ПРОЙДЕН" + print(f"{name}: n={len(d)} среднее {st.mean(d):+.2f} против порога {thr:.2f} → {v}") + print(f"\n{'контраст':14s}{'ед.':>5s}{'перевес':>9s}{'p':>9s} вердикт") + for a, b in (("J6", "J0"), ("J0", "D0")): + d = [sc[(u, b)] - sc[(u, a)] for u in uids if (u, a) in sc and (u, b) in sc] + if not d: + continue + m = st.mean(d) + v = "ПЕРЕШЁЛ ПОРОГ (описательно)" if abs(m) > thr else "ниже порога" + print(f"{a + ' vs ' + b:14s}{len(d):5d}{m:+9.2f}{_sign_p(d):9.4f} {v}") + print(f"\n{'арм':10s}{'ошибок/ед.':>11s}") + for arm in ("J0", "J6", "D0", "JFLO", "CTRLdecoy", "CTRLmargin"): + v = [sc[(u, arm)] for u in uids if (u, arm) in sc] + if v: + print(f"{arm:10s}{st.mean(v):11.2f}") + print("\n⚠ Ось РАЗВЕДОЧНАЯ: вывод описательный при любом исходе (n=9, MDE 2.90 против цели 2.00).") + return 0 + + +ORCH = """# Sol-оркестратору: судейство ЯПОНСКОЙ НОГИ H-4 (проход `ja6`) + +Ты раскладываешь готовые судейские задания по своим агентам. **Ничего не готовишь сам и ничего +не считаешь** — всё уже собрано, задания и промты лежат файлами. + +## Что сделать + +В каталоге `{prompts}` лежат **{k} промтов сессий**: + +{names} + +Запусти **по одному агенту на каждый промт**, {k} агентов. Каждому дай ровно одну инструкцию: + +> Прочитай файл `{prompts}/<имя>.md` и выполни ровно то, что в нём написано, ничего сверх. + +Промт внутри содержит список назначенных заданий, правила оценки и путь, куда писать ответ. +Больше агенту знать ничего не нужно. + +## Жёсткие требования (нарушение обесценивает замер) + +1. **Один агент — один промт.** Не объединяй сессии и не дроби их. Состав сессии — часть прибора. + +2. ⚠⚠ **Наборы `p1` и `p2` — РАЗНЫМ агентам, и это здесь важнее всего.** Это две половины + шумового пола: в `p1` лежит одна генерация боевого редактора, в `p2` — вторая генерация ТОГО ЖЕ + редактора. Из разницы их оценок и строится порог различимости всего прохода. Судья, увидевший + обе половины, порог обесценивает целиком — в паке 23 такой дефект занизил его на 19%, а на + проходе `tier` половина пола вообще совпала с боевым армом, и контроль оказался пуст. + **Если все задания уйдут одному агенту, замер пропадает, и пере-снять его будет нечем.** + +3. **Агент открывает ТОЛЬКО перечисленные в его промте задания и пишет ТОЛЬКО по путям, указанным + ВНУТРИ заданий.** Ему нельзя искать что-либо ещё на диске — ни разметку, ни сырьё, ни код, ни + задания других сессий; нельзя делать сетевые вызовы; нельзя сравнивать варианты между собой, + ранжировать их и применять `diff`/`difflib`/`cmp`. Ключ соответствия меток и армов лежит ВНЕ + рабочего каталога и агенту не выдаётся намеренно. Заглянет — сессия аннулируется целиком. + +4. **Не правь промты и задания.** Если что-то выглядит ошибкой — скажи владельцу, но не исправляй: + обвязка сверена гейтом паритета с обвязкой второго семейства, и правка на твоей стороне делает + семейства несравнимыми. + +5. **Не досуживай и не переоценивай.** Не справился с частью — так и скажи, сколько выполнено. + Частичный возврат честнее выдуманного и засчитывается. + +6. **Агент НЕ запускает своих агентов.** Сессия — это ОДИН агент, читающий свои задания. Дочерние + агенты ломают состав сессии, зарегистрированный до запуска; на прошлом прогоне это уже + случилось, и сессию пришлось аннулировать целиком. + +## Что это за замер и почему спрашивают вас + +Гипотеза владельца H-4: перевес `deepseek-v4-pro` в роли редактора — свойство пары zh→ru, и на +другой паре порядок редакторов может смениться. На японском до этой недели был куплен один +редактор, сравнивать было не с чем; теперь их два. Вопрос ровно один: **меняется ли порядок**. + +Ваше семейство здесь полноправно: ни один арм панели не из OpenAI (`deepseek-v4-pro`, `glm-5`, +`deepseek-v4-flash`). На соседнем проходе `tier` конфликт был — там судился `gpt-5.6-terra`, то +есть выход вашей же семьи, и показания пришлось снять именно поэтому. + +Первое семейство (агенты Claude) эту панель уже отсудило, все контроли зелёные. Вы — второе +мнение о ЕГО судействе и его слепоте, а не первый свидетель. + +## Как считается результат + + eval/.venv/bin/python eval/dovodka/ja6.py --score-sol + +Тот же счётчик, которым посчитано первое семейство: сам отбракует ответы без цитат, проверит +пойманный декой и напечатает число принятых единиц. + +## Что этот замер может и чего не может — сказано ДО прогона + +Ось объявлена РАЗВЕДОЧНОЙ до денег: 9 единиц, MDE 2.90 против цели 2.00. Несущего вывода она не +даст ни при каком исходе. Маржевый декой (контроль чувствительности) сажается лишь на 5 единиц из +9 — на этой книге регексы посадок находят мало мест, расширенный список проверен и не помогает. +Значит право сказать «редакторы равны» подпёрто здесь слабее, чем на других осях. +""" + +SESSION = """ + +Ты — литературный редактор-эксперт. Твоя работа — оценить качество русских художественных +переводов С ЯПОНСКОГО по заданиям, которые лежат готовыми файлами. + +Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом +задании есть исходник на японском, несколько русских переводов этого же фрагмента под слепыми +метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан +ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой. + +Работай по заданиям последовательно: прочитал задание → записал ответ → перешёл к следующему. + +## Правила, которые важнее скорости + +* **Оценивай каждый вариант ОТДЕЛЬНО против исходника.** Не сравнивай варианты между собой, не + ранжируй их и не подстраивай оценку одного под оценку другого. Порядок меток перемешан намеренно. +* **Каждая ненулевая оценка требует обоснования ЦИТАТОЙ** из соответствующего варианта, в формате, + заданном в задании. Оценка без цитаты не принимается, и единица целиком выбрасывается из замера. +* **Числа — это СЧЁТ ошибок, а не оценка по шкале.** Две одинаковые ошибки в одном варианте — два. +* ⚠ **НОЛЬ — УТВЕРЖДЕНИЕ, А НЕ МОЛЧАНИЕ.** Ставя ноль, ты заявляешь «я сверил и не нашёл». Поле + ПОЧЕМУ обязательно для КАЖДОЙ метки, включая нулевые: при всех нулях так и пиши — «прочитано, + дефектов не найдено». Пустое ПОЧЕМУ разбор отбрасывает. Прошлый проход развалился именно на + молчаливых нулях: 38% клеток были нулями, треть из них без единого слова, и вердикт оказался не + «варианты равны», а «прибор ничего не показывал». +* Читай ВНИМАТЕЛЬНО и сверяй смысл с японским исходником. Смысловые искажения — инверсия, подмена + адресата реплики, потерянное отрицание, изменённое число, потерянный вежливостный регистр, + выдуманный факт — незаметны при беглом чтении, потому что русский текст остаётся грамотным и + связным. Именно их и надо ловить. +* Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия. + +## Границы + +* Работай ТОЛЬКО с назначенными файлами заданий и пиши ТОЛЬКО по путям, указанным внутри них. +* **Не ищи и не открывай ничего другого на диске.** В частности: не выясняй, откуда взялся вариант, + какой моделью он сделан и что означают метки. Эта информация к задаче отношения не имеет, а её + поиск делает оценку недействительной. +* Не применяй `diff`, `difflib`, `cmp` и любое механическое сличение вариантов между собой. +* **Не запускай своих агентов.** Эта сессия — ты один. +* Не переводи сам и не предлагай свою версию. Не оценивай «литературность в целом» — только оси, + названные в задании. +* Книга новая (первая публикация 30.07.2026), в претрейне её нет: не ищи её в сети и не опирайся + на память. Единственный источник истины — исходник в задании. +* Ничего не покупай, никаких сетевых вызовов. Файлы с ключами и секретами не читать. + +ЕСЛИ ЧЕГО-ТО НЕ ЗНАЕШЬ: считай ошибкой только то, что видно из исходника. Сомневаешься — не +засчитывай. Заниженный счёт честнее выдуманного. + +## ТВОИ ЗАДАНИЯ ({n}) + +{files} +""" + + +def sol() -> None: + """Пакет для внешнего харнесса — ТРЁХУРОВНЕВЫЙ, как пакеты фазы Д, а не двухдокументный. + + ⚠ ПОЧЕМУ ИМЕННО ТАК, И ЧЕМ ЭТО ОПЛАЧЕНО. Первая редакция этого пакета копировала структуру + эксп-23 (`editor-tier/sol-tier/`: ИНСТРУКЦИЯ + ПРОМТ-ДЛЯ-ХАРНЕССА) и просила «работай по 4 + задания за сессию» ПРОЗОЙ. Владелец указал, что его харнесс так не работает: получив задание, + Sol идёт и читает всё сам, одной сессией. Для этого прохода такая раскладка убийственна — + половины шумового пола лежат в РАЗНЫХ наборах (`J0` в p1, `JFLO` в p2), и один судья, + увидевший обе, обесценивает порог целиком. Пакеты фазы Д (`sol-d3-work`, `sol-d4-work`, + `sol-d6-work`) решают это слоем ОРКЕСТРАТОРА: `ORCHESTRATOR.md` велит запустить по агенту на + промт, а `prompts/*.md` жёстко разводят наборы. Здесь воспроизводится тот же слой. + + ⚠ Прочее, учтённое из прошлых пакетов, каждое — оплаченная ошибка: + · путь к ключу НЕ НАЗЫВАЕТСЯ (пак 23 написал «не открывай blind-keys/» — это показать пальцем); + · путь записи ведёт в СВОЙ каталог (пак 23 велел писать в боевой, поверх отсуженного); + · правила счёта стоят В САМОМ ЗАДАНИИ и в промте сессии — паритет П-4; + · запрет на дочерних агентов вписан явно (на прошлом прогоне сессия их породила); + · все армы единицы — в одной пачке (урок эксп-22 §16). + """ + src, dst = WORK, Path.home() / "books" / "judging" / "ja6-sol" + (dst / "tasks").mkdir(parents=True, exist_ok=True) + (dst / "answers").mkdir(parents=True, exist_ok=True) + (dst / "prompts").mkdir(parents=True, exist_ok=True) + by_half: dict[str, list[str]] = {} + for half in SETS: + d = src / f"{half}-tasks" + if not d.exists(): + continue + for f in sorted(d.glob("*.txt")): + body = f.read_text(encoding="utf-8") + body = body.replace(str(src / f"{half}-answers" / f.name), + str(dst / "answers" / f.name)) + (dst / "tasks" / f.name).write_text(body, encoding="utf-8") + by_half.setdefault(half, []).append(f.name) + if not by_half: + print("заданий нет — сначала --emit") + return + names = [] + for half, toks in sorted(by_half.items()): + for i in range(0, len(toks), PER_SESSION): + grp = toks[i:i + PER_SESSION] + nm = f"{half}-session-{i // PER_SESSION + 1:02d}" + files = NL.join(f"* `{dst / 'tasks' / x}`" for x in grp) + (dst / "prompts" / f"{nm}.md").write_text( + SESSION.format(n=len(grp), files=files), encoding="utf-8") + names.append(nm) + (dst / "ORCHESTRATOR.md").write_text( + ORCH.format(prompts=dst / "prompts", k=len(names), + names=NL.join(f"* `{n}.md`" for n in names)), encoding="utf-8") + print(f"пакет Sol → {dst}") + print(f" заданий {sum(len(v) for v in by_half.values())} · промтов сессий {len(names)}") + print(f" ОТДАТЬ ВЛАДЕЛЬЦУ: {dst / 'ORCHESTRATOR.md'}") + print(" ⚠ p1 и p2 — РАЗНЫМ агентам: это две половины шумового пола") + + +def selftest() -> int: + fails = [] + ok = lambda c, m: fails.append(m) if not c else None + us = units() + ok(len(us) == 9, f"единиц {len(us)}, ожидалось 9") + for half in SETS: + p = panel(us[0], half, 0) + ok(bool(p), f"{half}: панель пуста") + if p: + sigs = [hashlib.sha256(v.encode()).hexdigest() for v in p.values()] + ok(len(sigs) == len(set(sigs)), f"{half}: в панели побайтные дубли") + ok(("J0" in p) == (half == "p1"), f"{half}: J0 стоит не в своём наборе") + ok(("JFLO" in p) == (half == "p2"), f"{half}: JFLO стоит не в своём наборе") + lay = layout(us[0]["uid"], half, tuple(p)) + ok(len(lay) == len(set(lay.values())), "раскладка не биективна") + ok(layout(us[0]["uid"], half, tuple(sorted(lay.values()))) == lay, + "раскладка не воспроизводима") + d = [panel(u, "p1", i).get("CTRLdecoy", "") for i, u in enumerate(us)] + ok(sum(1 for x in d if x) >= 7, f"грубый декой сажается лишь на {sum(1 for x in d if x)} из 9") + ok(_sign_p([1, 1, 1, 1, 1]) < 0.07, "знаковый тест сломан") + for m in fails: + print("ПРОВАЛ:", m) + print(f"селфтест ja6: провалов {len(fails)}") + return 1 if fails else 0 + + +if __name__ == "__main__": + a = sys.argv[1:] or ["--selftest"] + if a[0] == "--emit": + emit() + elif a[0] == "--score": + sys.exit(score()) + elif a[0] == "--selftest": + sys.exit(selftest()) + elif a[0] == "--sol": + sol() + elif a[0] == "--score-sol": + sys.exit(score(Path.home() / "books" / "judging" / "ja6-sol")) + else: + raise SystemExit(f"⛔ неизвестный режим {a[0]!r}") diff --git a/eval/dovodka/judge-prompts/claude.md b/eval/dovodka/judge-prompts/claude.md index 5805f347..1d5eb675 100644 --- a/eval/dovodka/judge-prompts/claude.md +++ b/eval/dovodka/judge-prompts/claude.md @@ -7,7 +7,7 @@ переводов по заданиям, которые лежат готовыми файлами. Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом -задании есть исходник на китайском, несколько русских переводов этого же фрагмента под слепыми +задании есть исходник на {ЯЗЫКЕ}, несколько русских переводов этого же фрагмента под слепыми метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой. @@ -24,11 +24,19 @@ * **Ноль ставится только тогда, когда ошибок этого типа действительно нет.** Ноль — это утверждение, а не способ пропустить вариант. Если ты не нашёл ошибок, значит их нет; убедись, что читал внимательно. -* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с китайским исходником. Смысловые искажения — инверсия, +* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с {ЯЗЫКОМ} исходником. Смысловые искажения — инверсия, подмена адресата реплики, потерянное отрицание, изменённое число, выдуманный факт — бывают незаметны при беглом чтении, потому что русский текст остаётся грамотным и связным. Именно их и надо ловить. +* **ГЛАВНОЕ ПРАВИЛО ЭТОГО ЗАМЕРА (решение владельца продукта 16.08):** переврать смысл + исходника — ошибка; обойтись с его буквой вольно — нет. Живой русский язык здесь приоритет, + поэтому перестановка предложений, слияние и дробление абзацев, замена оборота на более живой + русский, синонимическая замена, добавленная связка и снятая буквальность исходного языка + ошибками НЕ СЧИТАЮТСЯ. Точный список того, что ошибкой не является, стоит в шапке САМОГО + задания, и он главнее этой обвязки. * Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия. + Но и не штрафуй вариант за то, что он просто нравится тебе меньше соседнего: разница вкуса + ошибкой не является. ## Границы @@ -45,5 +53,7 @@ {СПИСОК ФАЙЛОВ} -Каталог заданий: `~/sud-d4/p1-tasks/` и `~/sud-d4/p2-tasks/`. -Отвечать по путям, указанным внутри заданий (`~/sud-d4/p1-answers/`, `~/sud-d4/p2-answers/`). +Каталог твоих заданий: `~/{КАТАЛОГ}/{НАБОР}-tasks/`. Другие наборы этого прогона тебе +НЕ назначены и открывать их нельзя: наборы — половины парного контроля, и судья, +увидевший обе, обесценивает контроль целиком. +Отвечать по путям, указанным внутри заданий (`~/{КАТАЛОГ}/{НАБОР}-answers/`). diff --git a/eval/dovodka/judge-prompts/core.md b/eval/dovodka/judge-prompts/core.md index 776755e6..85db2643 100644 --- a/eval/dovodka/judge-prompts/core.md +++ b/eval/dovodka/judge-prompts/core.md @@ -2,7 +2,7 @@ переводов по заданиям, которые лежат готовыми файлами. Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом -задании есть исходник на китайском, несколько русских переводов этого же фрагмента под слепыми +задании есть исходник на {ЯЗЫКЕ}, несколько русских переводов этого же фрагмента под слепыми метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой. @@ -19,11 +19,19 @@ * **Ноль ставится только тогда, когда ошибок этого типа действительно нет.** Ноль — это утверждение, а не способ пропустить вариант. Если ты не нашёл ошибок, значит их нет; убедись, что читал внимательно. -* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с китайским исходником. Смысловые искажения — инверсия, +* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с {ЯЗЫКОМ} исходником. Смысловые искажения — инверсия, подмена адресата реплики, потерянное отрицание, изменённое число, выдуманный факт — бывают незаметны при беглом чтении, потому что русский текст остаётся грамотным и связным. Именно их и надо ловить. +* **ГЛАВНОЕ ПРАВИЛО ЭТОГО ЗАМЕРА (решение владельца продукта 16.08):** переврать смысл + исходника — ошибка; обойтись с его буквой вольно — нет. Живой русский язык здесь приоритет, + поэтому перестановка предложений, слияние и дробление абзацев, замена оборота на более живой + русский, синонимическая замена, добавленная связка и снятая буквальность исходного языка + ошибками НЕ СЧИТАЮТСЯ. Точный список того, что ошибкой не является, стоит в шапке САМОГО + задания, и он главнее этой обвязки. * Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия. + Но и не штрафуй вариант за то, что он просто нравится тебе меньше соседнего: разница вкуса + ошибкой не является. ## Границы diff --git a/eval/dovodka/judge-prompts/sol.md b/eval/dovodka/judge-prompts/sol.md index aa60abe4..6c996f69 100644 --- a/eval/dovodka/judge-prompts/sol.md +++ b/eval/dovodka/judge-prompts/sol.md @@ -7,7 +7,7 @@ переводов по заданиям, которые лежат готовыми файлами. Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом -задании есть исходник на китайском, несколько русских переводов этого же фрагмента под слепыми +задании есть исходник на {ЯЗЫКЕ}, несколько русских переводов этого же фрагмента под слепыми метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой. @@ -24,11 +24,19 @@ * **Ноль ставится только тогда, когда ошибок этого типа действительно нет.** Ноль — это утверждение, а не способ пропустить вариант. Если ты не нашёл ошибок, значит их нет; убедись, что читал внимательно. -* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с китайским исходником. Смысловые искажения — инверсия, +* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с {ЯЗЫКОМ} исходником. Смысловые искажения — инверсия, подмена адресата реплики, потерянное отрицание, изменённое число, выдуманный факт — бывают незаметны при беглом чтении, потому что русский текст остаётся грамотным и связным. Именно их и надо ловить. +* **ГЛАВНОЕ ПРАВИЛО ЭТОГО ЗАМЕРА (решение владельца продукта 16.08):** переврать смысл + исходника — ошибка; обойтись с его буквой вольно — нет. Живой русский язык здесь приоритет, + поэтому перестановка предложений, слияние и дробление абзацев, замена оборота на более живой + русский, синонимическая замена, добавленная связка и снятая буквальность исходного языка + ошибками НЕ СЧИТАЮТСЯ. Точный список того, что ошибкой не является, стоит в шапке САМОГО + задания, и он главнее этой обвязки. * Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия. + Но и не штрафуй вариант за то, что он просто нравится тебе меньше соседнего: разница вкуса + ошибкой не является. ## Границы @@ -45,5 +53,7 @@ {СПИСОК ФАЙЛОВ} -Каталог заданий: `~/sol-d4-work/p1-tasks/` и `~/sol-d4-work/p2-tasks/`. -Отвечать по путям, указанным внутри заданий (`~/sol-d4-work/p1-answers/`, `~/sol-d4-work/p2-answers/`). +Каталог твоих заданий: `~/{КАТАЛОГ}/{НАБОР}-tasks/`. Другие наборы этого прогона тебе +НЕ назначены и открывать их нельзя: наборы — половины парного контроля, и судья, +увидевший обе, обесценивает контроль целиком. +Отвечать по путям, указанным внутри заданий (`~/{КАТАЛОГ}/{НАБОР}-answers/`). diff --git a/eval/dovodka/naklon.py b/eval/dovodka/naklon.py new file mode 100644 index 00000000..13674951 --- /dev/null +++ b/eval/dovodka/naklon.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""ГЕЙТ ПОЗИЦИОННОГО НАКЛОНА. $0, только чтение сырья. + +⚠ ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ. Норма рига требует наклон ЗАМЕРИТЬ, ВЫЧЕСТЬ и СТОРОЖИТЬ ГЕЙТОМ, и отчёт +фазы (23-editor-tier.md:1027) прямо утверждает, что так и делается. Фактически в зоне фазы Д кода +наклона не было ни строки: замер сделан ОДИН раз руками при финальном аудите, уже ПОСЛЕ вердиктов +(девиация объявлена в Д10), сторожа нет. Здесь он есть. + +ЧТО МЕРИТСЯ. Наклон — это зависимость выставленного счёта от НОМЕРА МЕТКИ в пачке: судья, +устающий к концу задания, ставит поздним меткам больше ошибок. Сам по себе наклон вердикту не +вредит — он сокращается в контрасте, ЕСЛИ средние позиции двух армов совпадают. Вредит +ПРОИЗВЕДЕНИЕ наклона на дисбаланс позиций, и именно оно здесь и гейтится. + + поправка контраста = наклон × (средняя позиция арма A − средняя позиция арма B) + +ГЕЙТ: пачка не годна, если поправка хоть к одному первичному контрасту превышает +`MAX_SHARE` от порога различимости этой оси — то есть если перестановка меток способна +съесть заметную долю решающей величины. + +Запуск: naklon.py [--pass=<проход>] | --selftest +""" +from __future__ import annotations + +import collections +import json +import pathlib +import re +import statistics as st +import sys + +BOOKS = pathlib.Path.home() / "books" +AXRE = re.compile(r"^[ТT](\d+)-(ВЕРНОСТЬ|ТЕРМИН|ЯЗЫК|ФОРМА):\s*(\d+)", re.MULTILINE) +CARRY = ("ВЕРНОСТЬ", "ЯЗЫК") +MAX_SHARE = 0.25 # доля порога, которую поправке позволено съедать + +PASSES = { + "tier": (["editor-tier/aj-tier"], "editor-tier/blind-keys/tier-KEY.json", + [("T1", "R0"), ("T2", "R0"), ("T3", "R0")], 1.02), + "tier2": (["editor-tier/tier2"], "editor-tier/blind-keys/tier2-KEY.json", + [("T1", "R0"), ("T2", "R0"), ("T3", "R0")], 1.84), + "border": (["editor-tier/aj-border"], "editor-tier/blind-keys/border-KEY.json", + [("R2", "R0")], 1.48), + "d4": (["judging/sud-d4/p1-answers", "judging/sud-d4/p2-answers"], "dovodka/blind-keys-d4", + [("A1B", "A0"), ("A3", "A0"), ("A6", "A0")], 1.65), + "d3": (["judging/sud-d3/p1-answers", "judging/sud-d3/p2-answers"], "dovodka/blind-keys-d3", + [("E0", "D0")], 0.90), + "d6": (["judging/sud-d6/p1-answers", "judging/sud-d6/p2-answers"], "dovodka/blind-keys-d6", + [("J0", "D0")], 2.10), + "d1": (["judging/sud-d1/p1-answers", "judging/sud-d1/p2-answers"], "dovodka/blind-keys-d1", + [("R1", "A0")], 1.47), +} + + +def load_key(rel: str) -> dict: + p = BOOKS / rel + if p.is_dir(): + k: dict = {} + for f in sorted(p.glob("*-KEY.json")): + k.update(json.loads(f.read_text(encoding="utf-8"))) + return k + return json.loads(p.read_text(encoding="utf-8")) if p.exists() else {} + + +def collect(rels: list[str], keyrel: str): + """[(номер метки, арм, счёт)] по всем разобранным клеткам прохода.""" + key = load_key(keyrel) + rows = [] + for rel in rels: + d = BOOKS / rel + if not d.exists(): + continue + for f in sorted(d.glob("*.txt")): + km = key.get(f.stem) + if not isinstance(km, dict): + continue + cells: dict[str, dict[str, int]] = collections.defaultdict(dict) + for m in AXRE.finditer(f.read_text(encoding="utf-8", errors="replace")): + cells["Т" + m.group(1)][m.group(2)] = int(m.group(3)) + for lab, ax in cells.items(): + meta = km.get(lab) + if isinstance(meta, dict) and meta.get("arm"): + rows.append((int(lab[1:]), meta["arm"], sum(ax.get(a, 0) for a in CARRY))) + return rows + + +def slope(rows) -> float: + """Ошибок на ШАГ метки — обычная линейная регрессия счёта на номер метки.""" + if len(rows) < 4: + return 0.0 + xs = [r[0] for r in rows] + ys = [r[2] for r in rows] + mx, my = st.mean(xs), st.mean(ys) + den = sum((x - mx) ** 2 for x in xs) + return sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / den if den else 0.0 + + +def report(name: str) -> int: + rels, keyrel, fam, thr = PASSES[name] + rows = collect(rels, keyrel) + if not rows: + print(f"{name:8s} — ответов нет") + return 0 + s = slope(rows) + pos = collections.defaultdict(list) + for lab, arm, _ in rows: + pos[arm].append(lab) + mean_pos = {a: st.mean(v) for a, v in pos.items()} + print(f"\n=== {name} · точек {len(rows)} · наклон {s:+.4f} ошибки на шаг метки · " + f"порог оси {thr:.2f} ===") + rc = 0 + for a, b in fam: + if a not in mean_pos or b not in mean_pos: + continue + dp = mean_pos[a] - mean_pos[b] + corr = s * dp + share = abs(corr) / thr if thr else 0.0 + mark = " ⛔ ПРОБОЙ" if share > MAX_SHARE else "" + print(f" {a + '/' + b:12s} позиции {mean_pos[a]:5.2f} против {mean_pos[b]:5.2f} " + f"(разница {dp:+.2f}) → поправка {corr:+.3f} = {share:.0%} порога{mark}") + if share > MAX_SHARE: + rc = 1 + return rc + + +def selftest() -> int: + fails = [] + # синтетика с ИЗВЕСТНЫМ наклоном: счёт = 2 × номер метки + rows = [(i, "A" if i % 2 else "B", 2 * i) for i in range(1, 21)] + s = slope(rows) + if abs(s - 2.0) > 1e-9: + fails.append(f"наклон на синтетике {s:.4f}, ожидалось 2.0") + flat = [(i, "A", 5) for i in range(1, 21)] + if abs(slope(flat)) > 1e-9: + fails.append("на плоских данных наклон обязан быть нулём") + if len(collect(*PASSES["d4"][:2])) < 100: + fails.append("проход d4 не разбирается") + for m in fails: + print("ПРОВАЛ:", m) + print(f"селфтест наклона: провалов {len(fails)}") + return 1 if fails else 0 + + +if __name__ == "__main__": + args = sys.argv[1:] + if "--selftest" in args: + sys.exit(selftest()) + want = [a.split("=", 1)[1] for a in args if a.startswith("--pass=")] or list(PASSES) + rc = 0 + for n in want: + rc |= report(n) + print(f"\n⚠ Гейт роняет проход, если поправка съедает больше {MAX_SHARE:.0%} порога " + f"различимости: столько способна дать одна перестановка меток.") + sys.exit(rc) diff --git a/eval/dovodka/promptdiff.py b/eval/dovodka/promptdiff.py index 14948633..a4ff5346 100644 --- a/eval/dovodka/promptdiff.py +++ b/eval/dovodka/promptdiff.py @@ -58,6 +58,10 @@ MAP = { # en-файл → (zh-оригинал, комментарий провенанса) "translator": ("backend/prompts/zh-ru/translator.md", "боевой промпт переводчика"), "editor": ("backend/prompts/zh-ru/editor.md", "боевой промпт редактора"), + # ⚠ Заведён 14.08. Промт сессии требует МЕХАНИЧЕСКОГО гейта консистентности пар-промтов, а + # терминолог в карте отсутствовал — то есть банк-роль новой пары проходила мимо гейта вовсе. + # Гейт при этом честно печатал ПРОВАЛ «файла нет в MAP» и не угадывал, с чем сравнивать. + "terminologist": ("backend/prompts/zh-ru/terminologist.md", "боевой промпт терминолога"), "translator-reflow": (None, "боевого zh-аналога НЕТ: это вариант полигона, " "зеркалом боевого промпта не является"), } @@ -84,7 +88,29 @@ ALLOWED = { ("ja-ru", "editor", "- Вёрстка: собирай повествование"): "оговорка про построчный набор СОХРАНЕНА и переформулирована под ja: веб-новелла " "syosetu набирается построчно так же, как китайская", + # ⚠ Терминолог: пример строки ОТВЕТА — тот же третий законный класс, что у редактора, только + # формат другой (три поля через табуляцию, а не маркер `[narrative]`). Термин примера взят + # НЕ из книги среза (проверено: Thibault ×0, 慎二 ×0), чтобы промт не подсказывал модели + # ответы по реальным кандидатам банка. + ("en-ru", "terminologist", "师父"): + "пример строки ответа ЗАМЕНЁН своим английским (`Thibault → Тибо`) — образец обязан быть " + "на исходном языке пары; термин вне книги среза, подсказки ответов нет", + # Обе стороны замены объявляются отдельно: дифф показывает и снятую строку, и вставленную. + ("en-ru", "terminologist", "Thibault"): + "вставленная половина той же замены примера (см. запись про 师父)", + ("ja-ru", "terminologist", "慎二"): + "вставленная половина той же замены примера (см. запись про 师父)", + ("ja-ru", "terminologist", "师父"): + "пример строки ответа ЗАМЕНЁН своим японским (`慎二 → Синдзи`, дзи-слог демонстрирует " + "Поливанова против ромадзи) — образец обязан быть на исходном языке пары", } + +# ⚠ ФАЙЛЫ, У КОТОРЫХ ЗЕРКАЛО ПРОСТО НЕ ИМЕЕТ ПАР-БЛОКА. У боевого zh-терминолога его нет: файл +# написан пар-НЕЙТРАЛЬНО, вся языковая специфика приходит подстановками брифа. Пар-версии блок +# ДОБАВЛЯЮТ, и это расхождение объявлено здесь, а не спрятано ослаблением проверки: у пары блок +# обязан быть по-прежнему, требование снимается ТОЛЬКО с zh-стороны. Завести блок в zh нельзя — +# `backend/` чужая зона. +NO_ZH_PAIRBLOCK = {"terminologist"} BAD = 0 @@ -154,7 +180,11 @@ def compare(name: str, zh_p: Path, en_p: Path, want_pair: str, pair: str) -> Non zo, zb, zr, zp = blocks(zl) eo, eb, er, ep = blocks(el) - ck("пар-блок объявлен в обоих", bool(zp) and bool(ep), f"zh «{zp}» · пара «{ep}»") + if name in NO_ZH_PAIRBLOCK: + ck("пар-блок есть у ПАРЫ (у боевого zh его нет — объявлено)", bool(ep), + f"zh «{zp}» · пара «{ep}»") + else: + ck("пар-блок объявлен в обоих", bool(zp) and bool(ep), f"zh «{zp}» · пара «{ep}»") ck(f"пар-блок называет свою пару ({want_pair})", ep == want_pair, ep or "не найден") # ГЛАВНАЯ ПРОВЕРКА: всё вне пар-блока обязано совпадать построчно diff --git a/eval/dovodka/requirements.md b/eval/dovodka/requirements.md index 538cbb49..0f50d6e0 100644 --- a/eval/dovodka/requirements.md +++ b/eval/dovodka/requirements.md @@ -26,7 +26,7 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final |---|---|---| | **РАМКА** | | | | R1 | Отчёт фазы — секции Д0–Д8 в `23-editor-tier.md`, продолжение того же документа | `$ grep -q "^## Д3 — en→ru" docs/experiments/23-editor-tier.md && grep -q "^## Д6 —" docs/experiments/23-editor-tier.md && grep -q "^## Д9 —" docs/experiments/23-editor-tier.md` | -| R2 | Сводная таблица «пробел → чем закрыт → носитель-артефакт» обязательна | `$ grep -q "ПРОБЕЛ → ЧЕМ ЗАКРЫТ → НОСИТЕЛЬ" docs/experiments/23-editor-tier.md` | +| R2 | Сводная таблица «пробел → чем закрыт → носитель-артефакт» обязательна | `$ grep -q "ПРОБЕЛ → ЧЕМ ЗАКРЫТ → НОСИТЕЛЬ" docs/experiments/23-editor-tier.md && test -s ~/books/dovodka/canon-dissect.json && test -s ~/books/dovodka/d1-attempts.jsonl` | | R3 | Пакетный потолок фазы сверен СЛОВОМ владельца при запуске | `$ grep -q "PACK_CEILING" eval/dovodka/money_d.py && grep -q "САНКЦИЮ" eval/dovodka/money_d.py` | | R4 | Фазовые потолки разложены в пре-реге Д0 | `$ grep -q "ФД-A" eval/dovodka/money_d.py && grep -q "ФД-G" eval/dovodka/money_d.py` | | R5 | Касса: фриз-гейт, атомарный замок, два пути счёта, проекционные гарды | `$ eval/.venv/bin/python eval/dovodka/money_d.py --selftest` | @@ -38,7 +38,7 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | R10 | Ретрай-харнесс: экспоненциальный бэкофф на 5xx, окно ≥24 ч, поштучно, лимит цены на клетку | `$ grep -q "BACKOFF = (30, 120, 480, 1800)" eval/dovodka/d1_gemini.py` | | R11 | Журнал КАЖДОЙ попытки (время · код ответа · стоимость) файлом в сырьё | `$ test -s ~/books/dovodka/d1-attempts.jsonl && grep -q "\"ts\"" ~/books/dovodka/d1-attempts.jsonl` | | R12 | Вердикт «модель не отдаёт» — только с журналом за ≥24 ч + снимком вендор-статуса | `$ grep -q "ТОЛЬКО с журналом" eval/dovodka/d1_gemini.py` | -| R13 | Собрано ≥12/16 → судейство абс-ригом со всеми контролями | `$ set -- $HOME/sud-d1/p1-answers/*.txt; test $# -ge 12` | +| R13 | Собрано ≥12/16 → судейство абс-ригом со всеми контролями | `$ set -- $HOME/books/judging/sud-d1/p1-answers/*.txt; test $# -ge 12` | | R14 | Потолок клетки считается по `total_tokens` (скрытая тарификация ×4.5) | `$ grep -q "additive_total" eval/tenant_panel/roster.py` | | **Д2 — внешняя подпись `tier`** | | | | R15 | Задания `aj-tier-tasks` отданы внешнему судье вне Claude руками владельца | `$ test -s /home/ubuntu/books/editor-tier/sol-tier/ИНСТРУКЦИЯ.md` | @@ -70,7 +70,7 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | R37 | Непригоден → СТОП и пинг; замену без слова владельца не искать | `$ eval/.venv/bin/python eval/dovodka/material_ja.py \| grep -q "СТОП\|OK "` | | R38 | ja-промпты — те же гейты провенанса и консистентности, что Д3 | `$ eval/.venv/bin/python eval/dovodka/promptdiff.py ja-ru` | | R39 | 8–10 единиц: связка · dspro-однопроходка · контроль редактора | `$ set -- $HOME/books/dovodka/dv-c-j0-*.json; test $# -ge 9` | -| R40 | Печатается сравнение ОТНОСИТЕЛЬНОГО порядка жильцов между парами zh/en/ja | `$ grep -q "ноги H-4" docs/experiments/23-editor-tier.md` | +| R40 | Печатается сравнение ОТНОСИТЕЛЬНОГО порядка жильцов между парами zh/en/ja | `$ set -- $HOME/books/dovodka/dv-h-j6-*.json; test $# -ge 9 && eval/.venv/bin/python eval/dovodka/ja6.py --score` | | R41 | Статус оси РАЗВЕДКА объявлен В ПРЕ-РЕГЕ, а не постфактум | `$ grep -q "FORCED_DESCRIPTIVE" eval/dovodka/power.py` | | **Д7 — самооценка** | | | | R42 | Жильцам обеих ролей по трём книгам (zh/en/ja) вопрос о самооценке; ответы персистятся | `$ set -- $HOME/books/dovodka/dv-d-self-*.json; test $# -eq 6` | @@ -90,11 +90,11 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | R55 | Статус-баннеры на отчётах экспов, где их нет | `$ test $(grep -l "баннер фазы Д" docs/experiments/*.md \| wc -l) -ge 7` | | R56 | Шапка эндпоинтов `00-provider-quirks.md` актуализирована живым листингом | `$ grep -q "ЖИВОЙ ЛИСТИНГ ./models. ПЕРЕ-СНЯТ 2026-08-10" docs/experiments/00-provider-quirks.md` | | **НОРМЫ РИГА** | | | -| R57 | Декой в каждой судейской пачке; сессия без пойманного декоя аннулируется целиком | `$ grep -q "CTRLdecoy" eval/dovodka/itog_d4.py` | +| R57 | Декой в каждой судейской пачке; сессия без пойманного декоя аннулируется целиком | `$ eval/.venv/bin/python eval/dovodka/ja6.py --score \| grep -q "ГРУБЫЙ ДЕКОЙ.*ПРОЙДЕН"` | | R58 | Шумовой пол — парой, чьи половины судят РАЗНЫЕ сессии (порог был занижен на 19%) | `$ grep -q "ПОБАЙТНО РАВНЫЕ ПОЛОВИНЫ" eval/dovodka/sud.py` | | R59 | Все армы единицы — в одном пакете одной сессии | `$ grep -q "ARMS=arms" eval/dovodka/sud.py` | | R60 | Идентичность судей персистится ДО запуска; половиной пола не брать боевую клетку | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` | -| R61 | Позиционный наклон замерен, вычтен, сторожится гейтом; донор декоя уравнен | `$ eval/.venv/bin/python eval/editor_tier/verify23.py` | +| R61 | Позиционный наклон замерен, вычтен, сторожится гейтом; донор декоя уравнен | `$ eval/.venv/bin/python eval/dovodka/naklon.py` | | R62 | Судье запрещено сравнивать варианты между собой И читать их рядом | `$ grep -q "Не сравнивай варианты между собой" eval/dovodka/judge-prompts/core.md` | | R63 | Замок кассы снимает только поставивший его процесс (проверка живости PID) | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` | | R64 | Реестр требований фазы — В GIT до покупок; conformance валиден только против закоммиченного | `$ eval/.venv/bin/python -c "import sys;sys.path.insert(0,'eval');import conformance as c;ok,why=c.frozen(c.Path('eval/dovodka/requirements.md'));print(why);sys.exit(0 if ok else 1)"` | @@ -107,9 +107,9 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | R70 | Детекторы и пороги после взгляда на вердикты не менять; девиация = СТОП и пинг В МОМЕНТ | `$ grep -q "ПОРОГИ НЕ ТРОНУТЫ" eval/dovodka/contam_d.py` | | R71 | Правка рига чужого пака — отдельным коммитом + пере-снятие его гейтов | `$ eval/.venv/bin/python eval/tenant_panel/verify22.py` | | R72 | Приёмка адверсариальная author≠reviewer; опровергатель ДРУГОГО модельного семейства ОБЯЗАТЕЛЕН | `$ grep -q "Fable-5" docs/experiments/23-editor-tier.md` | -| R73 | Финал — построчный аудит закрытия заказа | `$ grep -q "^## Д11 — ИСПРАВЛЕНИЯ ФИНАЛЬНОГО АУДИТА" docs/experiments/23-editor-tier.md` | +| R73 | Финал — построчный аудит закрытия заказа | `$ eval/.venv/bin/python eval/dovodka/gain.py --selftest && eval/.venv/bin/python eval/dovodka/naklon.py --selftest && eval/.venv/bin/python eval/dovodka/adjud.py --controls` | | **ОТЧЁТ И СДАЧА** | | | | R74 | Сверка H-1…H-4 с фактом отдельной таблицей, ВКЛЮЧАЯ опровержения | `$ grep -q "Гипотезы владельца — сверка с фактом" docs/experiments/23-editor-tier.md` | | R75 | CONFIRM/DENY владельцу — только с артефактом-носителем | `$ grep -q "носитель-артефакт" docs/experiments/23-editor-tier.md` | -| R76 | Деньги: итог по фазам ДВУМЯ путями счёта | `$ grep -q "Деньги фазы — итог ДВУМЯ ПУТЯМИ" docs/experiments/23-editor-tier.md` | +| R76 | Деньги: итог по фазам ДВУМЯ путями счёта | `$ eval/.venv/bin/python eval/dovodka/money_d.py --selftest` | | R77 | Пинг в `docs/PROGRESS.md` секция «Полигон» при закрытии | `$ grep -q "15.08 · ФАЗА Д" docs/PROGRESS.md` | diff --git a/eval/dovodka/sessii.py b/eval/dovodka/sessii.py index 591497f1..9f1192b2 100644 --- a/eval/dovodka/sessii.py +++ b/eval/dovodka/sessii.py @@ -25,26 +25,51 @@ PROMPTS = ZONE / "judge-prompts" # ⚠ Ось — параметр. Пачки разных осей НЕЛЬЗЯ класть в один каталог и нельзя отдавать одной # сессии: судья, увидевший обе, перестаёт быть слепым к паре. AXIS = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--axis=")), "d4") +# ⚠ КОРЕНЬ — ТОЖЕ ПАРАМЕТР (заведено 16.08 под заход Д17). У осей фазы раскладка каталогов +# `judging/sud-<ось>`, а у захода она на уровень глубже: `judging/z17/<пара>`, потому что пара +# там своя у каждой панели. Прежде корень выводился из имени оси, и для захода пришлось бы +# заводить ВТОРОЙ такой же скрипт — то есть размножить будущие расхождения раскладки. Флаг +# `--root=` даёт тот же механизм чужой раскладке, не копируя его. +_ROOT = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--root=")), "") FAM = { - "claude": dict(root=Path.home() / "books" / "judging" / f"sud-{AXIS}", tpl=PROMPTS / "claude.md"), + "claude": dict(root=(Path(_ROOT).expanduser() if _ROOT + else Path.home() / "books" / "judging" / f"sud-{AXIS}"), + tpl=PROMPTS / "claude.md"), "sol": dict(root=Path.home() / "books" / "judging" / f"sol-{AXIS}-work", tpl=PROMPTS / "sol.md"), } +if _ROOT: + FAM.pop("sol") # внешнее семейство пакет получает своим сборщиком PER_SESSION = 4 # как у харнесса Sol в паке 23; одинаково для обоих семейств +MISSING_ONLY = "--missing" in sys.argv + + def batches(root: Path, half: str) -> list[list[str]]: - """Пачки набора, разложенные по сессиям. Порядок — от имени файла, значит воспроизводим.""" + """Пачки набора, разложенные по сессиям. Порядок — от имени файла, значит воспроизводим. + + ⚠ `--missing` РАСКЛАДЫВАЕТ ТОЛЬКО НЕОТСУЖЕННОЕ, и это не удобство, а защита данных. Сессия + может оборваться на середине (16.08: семь из четырнадцати упали на лимите харнесса, успев + записать часть ответов). Пере-запуск ЦЕЛОЙ сессии переписал бы уже полученные оценки новыми — + то есть тихо подменил бы данные замера теми, что судья выдал во второй раз. Добор идёт только + по пачкам без ответа; уже отсуженное не трогается вовсе. + """ toks = sorted(p.stem for p in (root / f"{half}-tasks").glob("*.txt")) + if MISSING_ONLY: + done = {p.stem for p in (root / f"{half}-answers").glob("*.txt")} + toks = [t for t in toks if t not in done] return [toks[i:i + PER_SESSION] for i in range(0, len(toks), PER_SESSION)] # Язык исходника — из провайдера пары, а не из текста шаблона: судья английской пачки, которому # сказано «сверяй с китайским исходником», сверяет не с тем, что видит. -LANG = {"d4": "китайском", "d3": "английском", "d6": "японском", "d1": "китайском"} +LANG = {"d4": "китайском", "d3": "английском", "d6": "японском", "d1": "китайском", + "z17-zh": "китайском", "z17-en": "английском"} # ⚠ Творительный падеж — ОТДЕЛЬНАЯ подстановка. Первая редакция просто вычёркивала язык из фразы # «сверяй смысл с китайским исходником», потому что склонение не подставлялось. Проверенная # ревью версия называла язык ДВАЖДЫ, и обезличивание было тихой потерей якоря. -LANG_INS = {"d4": "китайским", "d3": "английским", "d6": "японским", "d1": "китайским"} +LANG_INS = {"d4": "китайским", "d3": "английским", "d6": "японским", "d1": "китайским", + "z17-zh": "китайским", "z17-en": "английским"} def render(fam: str, half: str, toks: list[str]) -> str: @@ -52,7 +77,8 @@ def render(fam: str, half: str, toks: list[str]) -> str: body = m["tpl"].read_text(encoding="utf-8") body = (body.replace("{ЯЗЫКЕ}", LANG[AXIS]).replace("{КАТАЛОГ}", m["root"].name) .replace("{НАБОР}", half).replace("{ЯЗЫКОМ}", LANG_INS[AXIS])) - lst = "\n".join(f" ~/{m['root'].name}/{half}-tasks/{t}.txt" for t in toks) + body = body.replace(f"~/{m['root'].name}/", f"{m['root']}/") + lst = "\n".join(f" {m['root']}/{half}-tasks/{t}.txt" for t in toks) return body.replace("{СПИСОК ФАЙЛОВ}", lst) diff --git a/eval/dovodka/sud.py b/eval/dovodka/sud.py index b4fb0997..544af2d7 100644 --- a/eval/dovodka/sud.py +++ b/eval/dovodka/sud.py @@ -251,6 +251,18 @@ def _twin_of_base(arm: str, uid: str, txt: str) -> bool: return bool(base.strip()) and txt.strip() == base.strip() +def _cell_file(arm: str, uid: str): + """Файл клетки арма — ОДНА точка правды для `text_of` и для гейтов. + + ⚠ Раздвоение путей и было вторым дефектом finish-гейта. `text_of` знает, что клетки добивки + лежат под своим префиксом (`dv-e-r1-…`, касса ФД-E), а гейт спрашивал `C.tag`, который для + того же арма даёт `dv-a-r1-…` — файла нет, клетка молча выпадает из проверки. Так клетка + `dv-e-r1-de3916de62.json` с finish=length прошла в судейскую пачку оси Д1. + """ + f = C.OUT / f"dv-e-r1-{uid}.json" if arm == "R1" else C.OUT / f"{C.tag(arm, uid)}.json" + return f if f.exists() else None + + def text_of(arm: str, u: dict) -> str: """Текст арма. Клетка, не прошедшая гейт годности, в пачку НЕ идёт — возвращается пусто.""" uid = u["uid"] @@ -413,9 +425,15 @@ def selftest() -> int: 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 - if (C.OUT / f"{C.tag(a, x['uid'])}.json").exists() - and json.loads((C.OUT / f"{C.tag(a, x['uid'])}.json").read_text()).get("finish") + # ⚠ ОСЬ — ПАРАМЕТР И ЗДЕСЬ. Прежняя редакция перебирала ARMS_D4 + ARMS_OLD на ЛЮБОЙ оси, + # то есть на d1/d3/d6 сертифицировала ЧУЖУЮ панель и была зелена вхолостую. Цена дефекта + # замерена: клетка `dv-e-r1-de3916de62.json` с finish=length ушла в судейскую пачку оси Д1, + # хотя норма Д0.6 это прямо запрещает, и гейт промолчал. Пере-счёт без неё: перевес R1/A0 + # у claude −0.100 → +0.000, у Sol +2.533 → +2.143; вердикты не переворачиваются, но норма + # была нарушена и прибор об этом не сказал. + lens = [(a, x["uid"]) for a in (ARMS + ARMS_OLD if AXIS == "d4" else ARMS) for x in us + if _cell_file(a, x["uid"]) is not None + and json.loads(_cell_file(a, x["uid"]).read_text(encoding="utf-8")).get("finish") != "stop" and text_of(a, x)] ck("клетка finish=length в пачку не попадает", not lens, str(lens[:2])) # маржевый декой обязан РЕАЛЬНО сажаться, иначе «не пойман» = «не было» @@ -459,7 +477,7 @@ def selftest() -> int: ck("половины пола НЕ равны побайтно (иначе контроль пуст)", not [1 for a, b in pairs if a == b]) # половина пола не должна совпадать с боевой клеткой (урок «близнеца» пака 23) - battle = {text_of(a, x) for a in ARMS_D4 for x in us} + battle = {text_of(a, x) for a in ARMS for x in us} # ⚠ панель ОСИ, а не всегда d4 ck("половина пола не совпадает с боевым армом", not [1 for a, b in pairs if a in battle or b in battle]) # ключи лежат ВНЕ рабочего каталога судьи @@ -467,11 +485,19 @@ def selftest() -> int: KEYS not in TASKS1.parents and TASKS1 not in KEYS.parents and KEYS.parent != WORK, f"{KEYS} против {WORK}") # семейство первичных контрастов совпадает с объявленным в power.py + # ⚠ И СЕМЕЙСТВО — ТОЖЕ ПО ОСИ. Проверка была прибита к «Д4 edit-контур zh» и на трёх осях + # из четырёх сверяла чужое объявление со своим — то есть не проверяла ничего. P = _load("power_d", ZONE / "power.py") - declared = {c.split(" — ")[0] for c in P.PRIMARY["Д4 edit-контур zh"]} - mine = {f"{a}/{b}" for a, b, _ in FAMILY_D4} - ck("семейство совпадает с объявленным в power.py", declared == mine, - f"{sorted(declared)} против {sorted(mine)}") + pname = {"d4": "Д4 edit-контур zh", "d3": "Д3 en→ru несущая", + "d1": "Д1 gemini добивка"}.get(AXIS) + if pname and pname in P.PRIMARY: + declared = {c.split(" — ")[0] for c in P.PRIMARY[pname]} + mine = {f"{a}/{b}" for a, b, _ in FAMILY} + ck(f"семейство оси совпадает с объявленным в power.py ({pname})", declared == mine, + f"{sorted(declared)} против {sorted(mine)}") + else: + ck("ось объявлена ОПИСАТЕЛЬНОЙ — первичного семейства в power.py нет", + AXIS == "d6", f"ось {AXIS}") print(f"\n{'СУДЕЙСКИЙ ХАРНЕСС ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}") return bad diff --git a/eval/dovodka/vtoroe.py b/eval/dovodka/vtoroe.py index 9cf08bfb..44719864 100644 --- a/eval/dovodka/vtoroe.py +++ b/eval/dovodka/vtoroe.py @@ -122,8 +122,9 @@ def verdict(gap: float, thr: float, p: float) -> str: return "РАЗЛИЧИМ" if abs(gap) > thr and p < 0.05 else "в пределах" -def report(tag: str = "vendor2", p: str = "en", anchor: str = "RN", drop: tuple = ()) -> int: - r1, r2 = ranks_of(tag, p, "r1"), ranks_of(tag, p, "r2") +def report(tag: str = "vendor2", p: str = "en", anchor: str = "RN", drop: tuple = (), + da: str = "r1", db: str = "r2") -> int: + r1, r2 = ranks_of(tag, p, da), ranks_of(tag, p, db) uids = [u for u in sorted(set(r1) & set(r2)) if u not in drop] if not uids: raise SystemExit("⛔ нет глав, прочитанных ОБОИМИ кругами") @@ -188,7 +189,8 @@ def report(tag: str = "vendor2", p: str = "en", anchor: str = "RN", drop: tuple return 0 -def vkus(tag: str = "vendor2", p: str = "en", drop: tuple = ()) -> int: +def vkus(tag: str = "vendor2", p: str = "en", drop: tuple = (), + da: str = "r1", db: str = "r2", na: str = "fable-5", nb: str = "opus-5") -> int: """СРАВНЕНИЕ ДВУХ СУДЕЙСКИХ СЕМЕЙСТВ — то, чего норме П-1 не хватало с 20.08. ⚠ Заведено 23.08 по слову владельца: «раз уж ты пробежал какое-то судейство другим агентом, @@ -202,12 +204,12 @@ def vkus(tag: str = "vendor2", p: str = "en", drop: tuple = ()) -> int: ОТНОСИТЕЛЬНОЕ — «семейства по-разному упорядочивают», — а не «одно строже другого». Абсолютной строгости этот прибор не видит и видеть не может. """ - r1, r2 = ranks_of(tag, p, "r1"), ranks_of(tag, p, "r2") + r1, r2 = ranks_of(tag, p, da), ranks_of(tag, p, db) uids = [u for u in sorted(set(r1) & set(r2)) if u not in drop] arms = sorted(r1[uids[0]]) - print(f"\n=== ВКУС ДВУХ СЕМЕЙСТВ, {tag}: fable-5 (круг 1) против opus-5 (круг 2) ===") + print(f"\n=== ВКУС ДВУХ СЕМЕЙСТВ, {tag}: {na} ({da}/) против {nb} ({db}/) ===") print(f" глав {len(uids)} · армов {len(arms)}\n") - print(f" {'арм':6s}{'fable':>8s}{'opus':>8s}{'сдвиг':>8s}{'глав ↑':>8s}{'глав ↓':>8s}{'p':>9s} вывод") + print(f" {'арм':6s}{na[:7]:>8s}{nb[:7]:>8s}{'сдвиг':>8s}{'глав ↑':>8s}{'глав ↓':>8s}{'p':>9s} вывод") rows = [] for a in arms: d = [r2[u][a] - r1[u][a] for u in uids] # >0 — opus ставит НИЖЕ (место больше) @@ -217,12 +219,12 @@ def vkus(tag: str = "vendor2", p: str = "en", drop: tuple = ()) -> int: rows.append((a, st.mean([r1[u][a] for u in uids]), st.mean([r2[u][a] for u in uids]), m, sum(1 for x in nz if x < 0), sum(1 for x in nz if x > 0), pv)) for a, m1, m2, m, up, dn, pv in sorted(rows, key=lambda x: x[3]): - mark = "⛔ opus судит ИНАЧЕ" if pv < 0.05 else "" + mark = f"⛔ {nb} судит ИНАЧЕ" if pv < 0.05 else "" print(f" {a:6s}{m1:8.2f}{m2:8.2f}{m:+8.2f}{up:8d}{dn:8d}{pv:9.4f} {mark}") sig = [r for r in rows if r[6] < 0.05] print(f"\n армов, чьё место семейства ставят РАЗЛИЧИМО по-разному: {len(sig)} из {len(arms)}" + ("" if not sig else " — " + ", ".join(r[0] for r in sig))) - print(" ⚠ сдвиг >0 значит «opus ставит арм НИЖЕ, чем fable»; сумма сдвигов по построению ≈0") + print(f" ⚠ сдвиг >0 значит «{nb} ставит арм НИЖЕ, чем {na}»; сумма сдвигов по построению ≈0") return 0 @@ -267,6 +269,10 @@ if __name__ == "__main__": _tag = next((x.split("=", 1)[1] for x in a if x.startswith("--tag=")), "vendor2") _anch = next((x.split("=", 1)[1] for x in a if x.startswith("--anchor=")), "RN") _drop = tuple(next((x.split("=", 1)[1].split(",") for x in a if x.startswith("--drop=")), [])) + _da = next((x.split("=", 1)[1] for x in a if x.startswith("--a=")), "r1") + _db = next((x.split("=", 1)[1] for x in a if x.startswith("--b=")), "r2") + _na = next((x.split("=", 1)[1] for x in a if x.startswith("--na=")), "fable-5") + _nb = next((x.split("=", 1)[1] for x in a if x.startswith("--nb=")), "opus-5") if "--vkus" in a: - sys.exit(vkus(_tag, "en", _drop)) - sys.exit(report(_tag, "en", _anch, _drop)) + sys.exit(vkus(_tag, "en", _drop, _da, _db, _na, _nb)) + sys.exit(report(_tag, "en", _anch, _drop, _da, _db))