Skip to main content

blockwright-gate

Этап 5 Blockwright, числа — гейт, отчёт, рендеры и листы сравнения. Используй при работе с gate.py, blockwright.gate, Registration, сечениями по эталону, checks.inspect, report.json, render_set, compare.iou, вердиктом ungraded — и когда гейт красный или, наоборот, подозрительно зелёный.

Source facts

Repository
MelonHell/VibeBuilding
Last source activity
August 17, 2026 at 16:54
Detected SKILL.md language
Russian
Stars
1
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
blockwright-gate
description
Этап 5 Blockwright, числа — гейт, отчёт, рендеры и листы сравнения. Используй при работе с gate.py, blockwright.gate, Registration, сечениями по эталону, checks.inspect, report.json, render_set, compare.iou, вердиктом ungraded — и когда гейт красный или, наоборот, подозрительно зелёный.
# Этап 5 — числа Проверок нужно четыре, и ловят они разное: **гейт** меряет то, что сводится к числу; **рендер** показывает то, что не сводится; **лист** ставит результат рядом с тем, из чего он начерчен; **фото-ревью** — это то, что кто-то действительно на это посмотрел (см. `blockwright-review`). ## Гейт **Оценивать по карте сходство — тавтология.** Пятно туда вошло, оно, конечно, оттуда и выйдет; перевёрнутое вверх ногами здание набрало бы столько же. Но оценивать по карте **соответствие** — не тавтология: карта тут спецификация, а не свидетель, и вопрос «построилось ли то, что план говорит» законный. Так работают `COUNTS`, `RINGS` и `<часть> is built square`. Правило не «не сравнивай с картой», а «строка обязана говорить, какой из двух вопросов она закрыла»; зелёная строка соответствия про сходство не говорит ничего. Подробно — `docs/sources.md`, «Сходство и соответствие». Честная проверка **сходства** использует вход, который никогда не подавался как геометрия. Меш годится: он читается ради чисел и никогда ради формы. Поэтому гейт сводит и меш, и готовую схематику к одному и тому же — высшая точка на каждой станции поперёк здания — и сравнивает станцию со станцией на отрезке, где план является простым блоком во всю ширину. Допуск — около двух метров, потому что собственный шум меша примерно с блок. Сравнение ограничивается тем, где меш действительно держит здание: у краёв клипа он держит деревья и дорогу, и сравнение там меряет клип-бокс. На реальном примере это поймало систематическую ошибку 1.7 м по обеим крышам: настил был положен на верхнюю плиту, а меш ясно показывал кровельный пирог метром выше. ## Три вердикта, а не два `pass`, `fail` и `ungraded`. Последний — не мягкий `pass`: он говорит, что всё, на что можно было ответить, отвечено верно, и что часть вопросов осталась без ответа. Это другое здание, чем то, которое проверили и оно устояло. | что положили | что делает гейт | |---|---| | план с карты, разрез по захвату | режет разрез — настоящая внешняя проверка | | модель и захват | режет по захвату: он план не рисовал | | план и разрез из одного OBJ | режет разрез, но пишет `[----] independent witness` | | ни модели, ни захвата | `[----] section` — резать не по чему | | пустой `COUNTS` | `[----] division` — никто не сказал, на сколько частей делится | | окно без станций | `[----] <окно>` — эталон над ним ничего не держит | Строка `[----]` — это вопрос, на который нечем ответить, и она печатается вместо того, чтобы проверку молча пропустить. Пропущенная молча проверка читается ровно как пройденная, а разница между ними — это разница между оценённой постройкой и неосмотренной. Во второй строке таблицы разрез всё равно полезен: он оценивает **упрощение** — часть с покатой крышей или со ступенчатым крылом на нём падает. Чего он не может — поймать ошибку самого эталона, потому что ничего не придержали, чтобы ему возразить. Что закрыло бы этот пробел: второй источник. Карта рядом с моделью, фотографии рядом с захватом. ## Чего гейт не видит Сказать прямо, потому что форма его слепоты решает, что придётся проверять иначе. Профиль по максимуму высоты молчит обо всём под силуэтом, обо всём фасаде, о полостях, о чём угодно висящем в воздухе и о любом сдвиге вдоль длины здания. Две другие секции закрывают то, что он не может: - **«Целое ли оно»** отвечается вообще ни против чего — `blockwright.checks`: связность с землёй, свободные концы элементов, счёт компонент на этаж. Меш тут не помощник: отломившийся и зависший каркас навеса стоит ровно там, где меш говорит быть навесу, поэтому сечение через него меряется правильным, пока вещь видимо разваливается. - **«Всё ли на месте»** отвечается расписанием. Ни одна из двух других спросить этого не может: обе оценивают материал, который существует, а отказ здесь — это материал, которого нет. - **«Согласно ли здание само с собой»** — строка `evenness`, `checks.twins` и `checks.level_runs`. Все остальные строки сравнивают постройку с чем-то снаружи, и поэтому ни одна не видит этого класса: две башни одного пятна дают один силуэт и один профиль, что бы ни творилось на их фасадах, а парапет, построенный на девяти высотах вдоль одной крыши, проходит каждую станцию, на которой его судят, — он с этих станций и снят. И то и другое — первое, что видит человек на рендере. Таблицы в `gate.py`: `TWINS` — плановые части, которые обязаны быть зеркальной парой (ось берётся между ними), `LEVEL` — сколько разных верхних отметок разрешено части. Плановая часть, а не объявленная: объявление сливает все вызовы, которые его сделали, поэтому «parapets» на здании со стилобатом и двумя башнями — это три разные горизонтали в одной маске, и счёт по ним не значит ничего. Пустой `TWINS` — `ungraded`, а не молчание, ровно как пустой `COUNTS`; здание без повторов говорит `SINGULAR = True`. **`SINGULAR` — правда, и поэтому вопрос задаётся вторым способом.** Пять зданий из шести объявили его честно: крылья разной длины, корпуса непохожи, зеркала действительно нет. Повторяется в этих зданиях не отражение, а **сдвиг** — этажи, ряды вилл, секции фасада, — и про него `TWINS` не спрашивает. Поэтому рядом стоит строка `<часть> stands at that storey`, которой таблица не нужна вовсе: `checks.cadence` мерит период построенной стены по её собственному контурному кольцу (`Mask.outline`) и сверяет с этажом из `derived["storeys"]`, домноженным на `derived["stretch"]["vertical"]` — тем же множителем и в том же порядке, что и `Site.storeys`. Отвечает **период**, а не сила корреляции: стена с шагом 3-2-3-3-2 коррелирует хорошо, но на периоде 5 или 8. Счёт — только порог (`CADENCE_CLEAR`), ниже которого стена уходит в `ungraded` строкой «walls without a rhythm». Кольцо читается дважды — по силуэту и по материалу, — и берётся сильное чтение (`by` говорит какое): полоса перекрытия или спандрель силуэт не режет. Единственная таблица здесь только **вычитает** части: `NOT_WALLS` — плановые части, у которых этажей нет по существу (свод раздвижной кровли, рельс, плита плаза). Имя, которого план не рисует, останавливает гейт — переименованная часть не должна тихо вернуться в оценку. Выключенные части печатаются строкой, как у `JAGGED`. **Бюджет — это цель, а не то, что вышло.** Правило про «каждая красная строка либо зелёная, либо объяснена числом» действует и здесь: опустить `LEVEL` до семи, потому что вышло семь, — это проверка, переставшая быть проверкой. Выключатель тоже по частям, а не по зданию: `JAGGED` принимает не только число, но и словарь `{часть: допуск}` с `"*"` на остальные. `JAGGED = None` из-за одной дуги гасит контроль прямизны у трёх прямоугольных крыльев рядом, и это уже стоило здания. Выключенные части печатаются строкой `[----] drawn straight`. - **«Копии — это правда копии?»** — строка `<часть> repeats`, `checks.copies`. То, что `division` спрашивает про форму, спрошенное про материал: восемь одинаковых секций и восемь секций, нарисованных по одним и тем же числам, дают один силуэт, одно сечение, один план, одни доли материалов и один аудит. Разница видна с земли и ни одной другой строке. Сравнение **побайтовое, без допуска вообще**. Бюджет «почти совпадает» впускает обратно ровно тот цикл, ради которого строка есть: он и отличается от копии на горсть клеток на секцию. Одно исключение названо, а не измерено: `Canvas.finalize` раздаёт панелям, заборам и стенкам состояния по соседям, а у крайних копий соседи другие. Клетки на риме мотива, принадлежащие семейству из `blocks.connects`, сравниваются по имени без состояния, и каждая считается и печатается. Заявляется в манифесте (`Item(..., copies=N)`) и рядом со штампом (`sched.declare_repeat`). Пустой список — `[----] repetition`, как пустой `COUNTS`; закрывается `UNREPEATED` с **причиной**, не флагом. - **«Прямой ли угол»** — строка `<часть> is built square`, `checks.corners` и `checks.rectangular`. Дефект вносит сам пайплайн: `Site.footprint` закрывает трассированную маску и раздвигает падом, обе операции евклидовы, а евклидова дилатация — диск, поэтому каждый выпуклый угол приезжает откушенным на `pad + CLOSE`. Не видит этого никто: скруглённый угол даёт тот же силуэт, ту же станцию, ту же площадь, а `jaggedness` меряет форму против её же выпрямленного чтения и фаску в четыре клетки переживает при любом допуске. Спрашивается только у частей, которые **карта** нарисовала прямоугольником (`SQUARE_SAME`), и читается с плиты постройки, обрезанной по маске самой части. Плита ищется по самой постройке и обязана стоять **над воздухом** (`CORNERS_ALOFT`): без этого здание в один этаж, мостящее террасу на отметке первого этажа, грейдит собственный грунт — то есть карту — против карты и проходит всегда. Мера — **метры от угла** (`checks.corner_reach`, `CORNERS_BITE`), оба чтения против **нарисованной** коробки: угол, который скруглил сам картограф, так вычитается. Долей коробки это не мерится — четыре скушенных угла дают шесть десятых процента на части в 1200 клеток, а ритм фасада, которого в плане не было, — восемь; доля печатается рядом и не грейдится. Строит прямоугольник `Site.squared`; часть, которая прямоугольником не является, но составлена из прямых, — `Site.faceted` (углы там выходят пересечениями измеренных граней и прямые по построению). - **«Там ли она вообще стоит»** — строка `<часть> stands where the plan says` по каждой плановой части: `missing` (сколько нарисованного не занято, грейдится, `PLAN_MISSING`) и `outside` (сколько материала вне всего плана — печатается, грейдится только при объявленном `PLAN_OUTSIDE`, потому что балкон и навес свешиваются законно). Рядом пишется `out/compare/plan-vs-build.png`: серое — сошлось, тёплое — план нарисовал, постройка не встала, холодное — постройка вышла за план. Смотреть картинку, а не только процент: «4 % не построено» не говорит, какие четыре процента. - **«Та же ли это стена»** — строки `facades`, `facades built alike` и `the elevations agree about the bay`. `FACADES_DIFFER` спрашивает только то, что кто-то уже заподозрил; уезжает обратное — два участка, которые постройка сделала одной стеной. Матрица `checks.mix_matrix` считается каждый прогон по отрезкам, нарезанным автоматически, если `FACADES` пуст, и печатается целиком. `UNIFORM` принимает **причину**, а не `True`: голый флаг стоил одну строку и гасил единственную проверку, смотрящую на стену. - **«Одна ли это высота»** — строка `<часть> is one height`. Каждая строка ниже берёт у части одно число — медиану скайлайна, — и медиана это высота только тогда, когда станции складываются в одну палубу. Башня, свисающая над переходом, стоит над его пятном: переход в два этажа, а вниз по конвейеру уезжает двадцать восемь метров. Разброс `low..high` этого не ловит — парапет, венткамера и шахта лифта дают такой же, — ловит **доля станций у медианы** (`PLATEAU`, этаж). Никогда не фейл: это свидетельство об эталоне и о плане. Закрывается `PLATEAUS[часть] = "чем это объясняется"` или разрезанием части в плане. - **«Та ли территория»** — строки `<поверхность> covers its ground` и `<поверхность> is a rectangle` по таблицам `GROUNDS` и `RECTANGULAR`. Клип ведут по участку, и строка `the clip holds the site` спрашивает, дотянулся ли эталон до постройки. Форму двора, дороги и пляжа захват по-прежнему не свидетельствует: сверяются площадь и габарит против полосы высот в `derived["grounds"]`. Не форма. - **«Удержал ли холст эталон»** — строка `the canvas holds the reference`. `assemble` отказывает куску, у которого хоть одна клетка не легла на холст плана, и пишет его в `assembly.off_plan`. Гейт читает эту запись и не перемеряет: непустой список краснеет (сколько кусков, сколько площади, два выхода — расширить кроп или обрезать эталон), пустой зеленеет с нулями, а не молчит. Таблицы извинения нет: имена раздаёт `CAPTURE_PARTS` по месту среди выживших, а отказанный кусок — не выживший. Одна фраза на здание спрятала бы виллы за изгородью. ## Сечение режется по нескольким окнам, а не по одному Сечение через равномерную выдавку одинаково, где его ни режь. Всё, что реально ломалось, ломалось в хвостах. По умолчанию `windows()` берёт окна оттуда, где части перестают перекрываться по u: два параллельных крыла — одно окно (рез через них один и тот же), башня за концом крыла — своё. Дробить дальше и склеивать вручную там, где здание само подсказывает. **Верх блока, а не индекс.** Блок в слое y занимает [y, y+1), и захват меряет верх кровли. Сравнение по индексу стоило бы метра на каждой станции в одну сторону — половина допуска, потраченная до первой настоящей ошибки. ## Четыре неочевидные части `blockwright.gate` - **Регистрация перед сравнением.** У клипа меша своя вписанная рама, не делящая с картой ни начала, ни размаха. Сопоставляются размахи того, что стоит выше некоторой отметки, потом две оси сверяются между собой. Если подгонка вышла 1.15× по u и 0.98× по v, она зацепилась не за здание — **гейт так и говорит и не режет сечения**, вместо того чтобы оценивать шум. В `derive` отметка — **высота в метрах** (`REGISTER_FLOOR`), а не доля верха эталона: план описывает всё здание, включая одноэтажное крыло, и доля выбросила бы всё, что ниже половины самой высокой части. Стилобат с башней при этом регистрируют разные здания и падают с сообщением про переклип, который ни при чём. В гейте обе стороны фильтруются одинаково, там доля допустима. - **Исключения по имени, а не допуском.** Там, где меш заведомо не свидетель — фотограмметрия скругляет тонкий сходящий на нет кончик конуса, — покрываемые станции извиняются поимённо, с напечатанной рядом причиной и счётом. Расширение допуска извинило бы заодно и всё остальное. Исключение, которое ничего не покрывает, само сообщается как watch: геометрия, ради которой его писали, переехала, и никто не заметил. - **Свежесть, фатально.** Протухшая схематика регистрируется идеально — это была настоящая сборка того же здания, — поэтому каждая строка после неё продолжает печатать оценку, верную вчера. Гейт сравнивает mtime первым делом и останавливается. - **Швы.** Материал в паре клеток от края окна ничей и не оценивается. Стена под 52° растеризуется с 1.4 клетки неоднозначности, и регистрация оставляет невязку примерно того же размера. Гейт живёт в библиотеке, а не в здании. Сначала он был поскриптовым — у реального примера 592 строки, из которых про само здание примерно шестьдесят, — а скопированный гейт гниёт в каждой копии по-своему, что хуже отсутствия гейта: копии продолжают печатать зелёное. В `gate.py` здания остаются только таблицы: `TOLERANCE`, `REGISTER_AT` (в гейте — доля), бюджеты, `COUNTS`, `UNDIVIDED`, `LEVELS`, `RINGS`, `WATERTIGHT` и функция `windows()`, задающая отрезки для сечений. ## Свидетели Где два входа могут ответить на один вопрос, отвечают оба, а разница ложится в `derived.json` числом с допуском. Гейт читает эти числа и делает из них строки — сам он ничего не перемеряет. | строка | что сравнивает | что ловит | |---|---|---| | `extent agree` | габарит плана против эталона | пересэмплированный кроп, клип не того здания | | `bearing agree` | углы двух рам | развёрнутая или перепутанная ось | | `part heights agree` | высоты по модели и по захвату | модель другой ревизии, крыло без съёмки | | `storey height agree` | измеренный этаж против объявленного | чертёж, прочитанный не в том масштабе | | `plan overlap agree` | IoU двух планов | два контура одного здания, которые не совпадают | | `scale` | \|scale − 1\| постройки против эталона | пересэмплированная карта, эталон не того здания | Расхождение, которое является фактом о входах, а не дефектом — игровая карта против захвата реального прототипа, — объявляется в `EXPECTED` в `probes/derive.py`: строка становится `[----]` с причиной, а не падает. Допуск не трогается: широкий настолько, чтобы проглотить 37 градусов, проглотил бы и раму, севшую на деревья. **Куда повёрнуто — решает регистрация, по данным.** Протяжение одинаково с любого конца, поэтому две рамы одного здания могут оказаться антипараллельны, и всё сойдётся, кроме того, что каждое окно измерится на другом конце. `Registration.orient` перебирает четыре варианта и берёт тот, где совпадает профиль ширины вдоль и поперёк. Отрыв лидера пишется в `derived.json`. Строка `scale` отвечает на другой вопрос, чем `registration`: та спрашивает, согласны ли оси между собой, эта — равно ли это единице. Постройка на четверть меньше эталона по обеим осям регистрируется идеально и режется идеально. ## Порядок сходимости сечения Правки двигают станции в обе стороны, и кажется, что процесс не сходится. Он сходится, если идти в этом порядке — снизу вверх по стоимости ошибки: 1. **Верх материала.** Стены до `T-1`, венчание вниз от `T`, а не вверх от него. Блок в слое `y` занимает [y, y+1). Одна ошибка, напечатанная тридцать три раза. 2. **Одна регистрация, а не две.** `Registration.measured(derived)`, а не собственный фит гейта. 3. **Единицы станций.** Сечение бинует эталон по целым метрам **меша** и отчитывается как `to_build_v(k + 0.5)`. Пробник, который бинует по своим plan-метрам, промахивается на целую станцию при масштабе 1.07. 4. **Форма ступеней.** Высота ступени — середина размаха, а не медиана: сечение судит худшую станцию, а не типичную. 5. **И только потом исключения.** **Два прогона подряд не сдвинули отчёт — не двигай окно вслепую, запусти `--profile`.** `python -m buildings.<имя>.gate --profile` печатает вместо вердикта саму съёмку: обе линии горизонта по всем станциям окна, в одних единицах (build v), одной таблицей, отсортированной по v. Оценённые строки идут как обычно, остальные — с причиной: `under the clutter floor`, `the plan claims nothing here`, `nothing in the reference at this station`, `past the end of the reference clip`. Полоса последней причины с одного конца — участок больше захвата, это норма; станция без эталона **внутри** клипа — дыра в захвате или кусок постройки, которого никто не просил. Обычный вывод показывает только оценённые станции, поэтому «40 из 96» не говорит, какие сорок, — отсюда и берётся цикл «подвинуть окно, получить другое число». В `report.json` те же причины лежат счётчиками в `sections[].dropped`. ## Ловушка медианы Высота части, снятая медианой скайлайна по её пятну, врёт на всём, что сужается кверху или стоит на чём-то другом. Ротонда на палубе бассейна вернула 19.7 м вместо 24.3; плоская крыша с машинным помещением возвращает высоту будки. `derive` теперь печатает разброс по каждой части — палуба, медиана, диапазон — и говорит прямо, когда одна высота эту часть не описывает. Инструменты: `roof.terraces` для ступенчатой кровли, `roof.plateau` для узкой части (читать поперёк, а не сверху), `roof.plant` для надстроек. ## Отчёт Консольный вывод существует, пока терминал не прокрутится. Дефект, который жил только там, нельзя отследить между прогонами, нельзя посчитать и нельзя показать исправленным. `blockwright.report` пишет один JSON на прогон, держащий каждое произведённое число: каждую проверку с её вердиктом, каждую станцию, где сечение разошлось, аудит расписания, счёт блоков, отщепенцев, свободные концы, перекрытия силуэтов. Ничего не сводится в сводку — сводить консоль и так умела. Сам он ничего не оценивает. Единственное, что он добавляет, — плоский список дефектов, выдернутых из разных структур, чтобы у вопроса «что не так с этой сборкой» был один ответ в одном месте, а не прогулка по четырём объектам. ## Рендеры Гейт слеп ко всему, чего не меряет. Долгое время единственными видами были плоские силуэты, а ряд из тринадцати вилл и один сплошной брус дают **одинаковый силуэт** — сборка читалась правильной, будучи неправильной самым заметным способом. Оба отказа на реальном примере пережили каждое посчитанное пайплайном число и умерли в тот момент, когда что-то нарисовало блоки блоками. `blockwright/render.py` рисует холст или схематику ортографической камерой, живущей в собственной раме здания, поэтому рендер можно поставить рядом с орто из Google Earth под тем же углом. Блоки остаются кубами на мировой сетке, стоящей под 52° к этой раме, поэтому ступенька видна — именно её увидел бы стоящий там человек. Грани рисуются от дальних к ближним, что для вокселей точно: осевые единичные кубы не могут пересечься, поэтому у сортировки по глубине нет циклов. Три детали решают, читается ли картинка: - **Отбрасывание соседей.** Грань выпускается только там, где граничит с воздухом. У сплошной сборки это большинство граней. - **Свет обязан разделять крышу и стену.** При слишком высоком свете верхняя грань и затенённая стена сходятся в пределах процентов, и стык двух объёмов становится невидим — то есть ровно тот отказ, который ищут. - **Швы принадлежат виду.** Обводка каждого блока показывает ряды на косом виде. При взгляде вдоль оси рамы ни одна грань не параллельна экрану, каждая стена — диагональная лестница, и обводка каждой ступеньки — шум. Флаг несёт вид, `render` ему подчиняется. **Рендер моделирует каждый блок полным непрозрачным кубом**, что верно сегодня и станет ложью в момент, когда откроется словарь блоков. Стекло уже загораживает то, что за ним; плиты читались бы на полметра выше; стеклянная балюстрада нарисовалась бы сплошным парапетом — то есть отказ «тринадцать домов читаются одним брусом», воспроизведённый точь-в-точь. Рендер — единственный инструмент, поймавший настоящий дефект, поэтому прозрачность и полувысота **предшествуют** любому некубическому блоку, а не следуют за ним. **Незнакомый блок обязан быть видимо незнакомым.** Безопасного цвета по умолчанию не бывает: серый фолбэк однажды нарисовал целый конус стеновым серым и спрятал его на виду на целую итерацию. ## Листы сравнения `blockwright/compare.py` ставит сборку рядом с тем, из чего она начерчена, **в одном масштабе**, с десятиметровой линейкой снизу. Всё там работает в метрах на пиксель: у блендеровского орто своё число в `render_meta.json`, у рендера — `1 / scale`, у фотографии никакого, и она подгоняется по высоте с подписью «(not to scale)». `compare.iou` оценивает два силуэта, предварительно совмещённых по габаритной рамке, поэтому меряет форму и пропорцию, а не положение — положение это работа гейта, и он делает её в метрах. Это **число, за движением которого следят между прогонами, а не оценка**: силуэты приходят из разных вещей и единицы не достигнут никогда. Листы говорят то, чего не могут числа. На реальном примере лист плана показал, что центральный двор в меше — **бассейн**, там где сборка кладёт пол, и что ротонда действительно круглая с дыркой посередине, там где сборка ставит прямоугольник.
View on GitHub