Skip to main content

blockwright-schedule

Этап 3 Blockwright — обмер разреза (по модели, захвату или объявлениям) и манифест частей. Используй при работе с probes/derive.py, derived.json, measure.storey_height / skyline / silhouette / presence, DECLARED_HEIGHTS, DECLARED_STOREY, blockwright.schedule, Item, декларациями и объявлениями частей.

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-schedule
description
Этап 3 Blockwright — обмер разреза (по модели, захвату или объявлениям) и манифест частей. Используй при работе с probes/derive.py, derived.json, measure.storey_height / skyline / silhouette / presence, DECLARED_HEIGHTS, DECLARED_STOREY, blockwright.schedule, Item, декларациями и объявлениями частей.
# Этап 3 — обмер разреза и запись того, что существует Карта не даёт высот. Высоты приходят из 3D-модели, из захвата, из масштабного чертежа — или объявляются словами. `blockwright.measure` — библиотека, которая их читает, и правило в том, что она запускается **при каждой сборке**, а не однажды и руками в прошлом. `probes/derive.py` здания её вызывает, пишет результаты в `out/derived.json`, а `build.py` отказывается стартовать без этого файла. **Когда мерить нечем.** Ни модели, ни захвата — тогда высоты берутся из `DECLARED_HEIGHTS`, по строке на часть, у каждой указан источник. Это законный режим: кто-то описал здание абзацем, и здание построено. Незаконно отчитаться о нём так же, как об измеренном, и гейт этого не сделает — он напишет `ungraded`. **Когда ритма нет.** Автокорреляция на чистой модели с гладким фасадом не находит ничего, и это не отказ поиска: коррелировать нечего. Тогда `DECLARED_STOREY` — шаг этажа и источник. Порядок именно такой: сначала мерим, объявление — запасной вариант, и `derived.json` пишет обе версии, чтобы было видно, какая пошла в дело. Объявление, перебивающее настоящий замер, — это способ получить высоты, которых кто-то ожидал, вместо тех, что есть. ## Ровный шаг важнее точного счёта этажей Сосчитать этажи по фотографии можно. Из этого **не следует**, что ровно столько этажей обязано влезть в измеренную высоту. При метре на блок шаг этажа целый, и `H / N` целым получается редко. Остаток раскидывают двумя способами, и один из них — брак: - **Разнобой.** Часть этажей на 3 блока, часть на 2, один на 1. Здание перестаёт быть многоэтажкой и становится стопкой полок разной толщины. Суммарная высота та же, силуэт тот же, сечение по станциям то же, `storey height agree` сравнивает с объявленным шагом одно число, а не разброс. Ловит это одна строка — `<часть> stands at that storey`: она мерит период **построенной** стены автокорреляцией и сверяет его с замеренным этажом. Полагаться на «плохую корреляцию» тут нельзя: стена с шагом 3-2-3-3-2 коррелирует **хорошо** (r≈0.7) — просто на периоде 5 или 8, а не 3. Отвечает именно период, счёт — порог, ниже которого строка честно уходит в `ungraded`. - **Смена счёта.** Шаг остаётся один на всё здание, `N = round(H / step)`, и здание выходит на этаж выше или ниже задуманного. Остаток метра-двух уходит в парапет, техэтаж или толщину кровли — туда, где он и есть в настоящем здании. Правило: **у многоэтажки шаг этажа один, счёт — то, чем жертвуют.** Этажом больше или меньше на четырнадцатиэтажке никто не заметит; этаж в один блок посреди фасада заметят все. Оговорка ровно одна, и она про масштаб здания. Работает это от шести-семи этажей и выше, где один этаж — несколько процентов высоты. У двух- или трёхэтажного дома этаж стоит трети силуэта: там счёт фиксирован, сходится за счёт шага, и если шаг не сходится — значит, врёт измеренная высота, а не счёт. Шаг применяется штампом — `canvas.repeat(y0, y1, step, count, mask)`, — и это не просто удобство: цикл, который сам распределяет остаток по уровням, и есть тот способ получить разнобой. ## Почему именно так Раньше на здание держали двадцать девять пробников. Каждый печатал гистограмму, человек считывал число и вписывал его в `build.py` с комментарием, называющим пробник. Это измерение с обратным клапаном: когда карту перерисовали или меш переобрезали, пробник сказал бы другое, а константа продолжала говорить старое. Три дефекта из июльского ревью 2026 дошли до заказчика ровно через этот зазор. ## Политика каталога `probes/` - Пробник, читающий **вход** (меш, карту), — это **провенанс**. Ему место в `probes/derive.py`. - Пробник, читающий **`out/`**, — это **проверка**. Ей место в `gate.py` или в корзине. Третьего не дано, и каталог `probes/` не должен превращаться в кладбище. ## Что меряется | Что | Чем | Подвох | |---|---|---| | высота этажа | `measure.storey_height` | автокорреляция гистограммы высот вершин по фасадам: плиты балконов и перемычки окон дают куда больше вершин на уровне перекрытия, чем на стене между ними, поэтому шаг пиков и есть этаж. Сообщает собственную корреляцию — не найденный ритм так и говорит, вместо правдоподобного числа | | высоты крыш | `measure.skyline` | максимум высоты на каждой станции v, **по маске каждой части**, а не по её габаритной коробке: коробка Г-образного крыла накрывает двор, который оно обнимает. Медиана — высота только если станции складываются в одну палубу, поэтому `skyline_of` пишет ещё и **долю станций у медианы** (`PLATEAU`), а гейт спрашивает `<часть> is one height`: сосед, свисающий над частью, даёт тот же разброс `low..high`, что парапет с венткамерой, и различает их только доля | | разрез | `measure.silhouette` | раскладывает каждую вершину по (положение поперёк, высота), **сохраняя всё**, а не только верх | | сколько там на самом деле есть | `measure.presence` | доля объёма коробки, которую меш заполняет | **Одно окно на всю длину годится только там, где здание на всю длину одно и то же.** Там, где крыло кончается или башня отступает, окно надо делить: одна медиана на всю длину делит разницу пополам и ошибается на обоих концах. На реальном примере одно окно на всё заднее крыло вывело его хвост на 2.1 м выше меша, а деление там, где кончается переднее крыло, это починило. **Профиль по максимуму высоты показывает только силуэт и прячет всё под ним** — так двор и превращается в сплошной блок высотой двадцать пять метров. Пустота читается `measure.presence` в плане, а не в объёме: фотограмметрия возвращает поверхность, и заполненная коробка объявила бы каждое настоящее перекрытие и каждую настоящую крышу почти пустыми. **Измерение обязано различать «у меша здесь ничего нет» и «у меша здесь есть что-то низкое».** Рисовать оба как ноль — это способ изобрести двор. ## Меш — это оболочка Фотограмметрия восстанавливает только внешние поверхности. Пустая ячейка внутри контура здания означает **комнату**, а не открытый воздух. Пробник, трактующий отсутствие как пустоту, доложит о дворах, которых нет. ## Регистрация Нужна только тогда, когда план и разрез пришли из **разных** источников. Если план вычитан из того же OBJ, что и высоты, подгонка одна и регистрация — тождество; `derive` так и пишет: `the plan and the section come from the same file`. У клипа меша своя вписанная рама, не делящая с картой ни начала, ни размаха. Прежде чем станция начнёт что-то значить, обе надо привести в одни координаты. `Registration` сопоставляет размахи того, что стоит выше отметки `REGISTER_FLOOR` — на уровне земли меш держит дорогу, посадки, припаркованные машины и соседей, — и потом сверяет две оси между собой. Отметка в **метрах**, а не долей от верха эталона: план описывает всё здание, включая одноэтажное крыло, и доля выбросила бы всё, что ниже половины самой высокой части. Если подгонка вышла 1.15× по u и 0.98× по v, она зацепилась не за здание, и дальше меряется не то. `derive` в этом случае **ничего не пишет** и говорит, почему. Отметку поднимают, когда в клипе стоит пояс деревьев или сосед, которого фотограмметрия не отличает от здания, — понимая, что всё ниже новой отметки перестанет регистрироваться. ## Декларации Не выводится то, чего положенные входы не могут сказать сами: **сколько частей** и **высота того, что фотограмметрия скругляет**. Это приходит из фотографий и записывается как декларация **с названной фотографией**, рядом с измеренными числами, а не спрятанным среди них. Три таблицы наверху `derive.py` — это все объявления, какие бывают, и у каждой строки обязателен источник: | таблица | что объявляет | когда нужна | |---|---|---| | `DECLARED_PLAN` | части плана: прямоугольники и круги | нет ни карты, ни модели, ни захвата | | `DECLARED_HEIGHTS` | высота части | нечем измерить, или эталон над ней ничего не держит | | `DECLARED_STOREY` | шаг этажа | ритм не нашёлся | | `DECLARED` | счёт частей и прочее с фотографий | всегда, когда фотографии что-то решают | Счёт частей — это счёт того, какие части существуют, а это ровно то, для чего фотографии авторитетны. Карта рисует зарубки, `derive` сверяет их шаг со счётом — но шаг зарубок это подтверждение, а не счёт, и меш стыки заглаживает вовсе. ## Манифест Фотографии читаются на этом этапе, и результат их чтения — `blockwright.schedule`: список частей, которые у здания есть, написанный руками наверху `build.py`, **до** кода, который хоть одну из них ставит. Прочитать фотографию нельзя вычислением. Записать, что ты на ней увидел, — можно, и это честный артефакт. Поэтому каждая строка несёт `source`, называющий, где автор это увидел. У расписания три слоя, и каждый зазор между двумя из них — свой класс бага: | Слой | Что это | |---|---| | **манифест** | что у здания есть, по фотографиям | | **декларация** | что постройка утверждает, что поставила: имя, маска, диапазон y, написанные рядом с кодом, который кладёт блоки | | **схематика** | что стоит на самом деле | Две вещи превращают это из формальности в проверку, и обе легко потерять. **Декларируй маску того, что положил, а не входную.** Перекрытие, объявленное на весь контур, держится кольцом стены в тех же клетках, и в день, когда плиты перестанут класться, никто не узнает. Правильно — `foot.erode(THICK)` для плиты, `foot.outline(THICK)` для стены, `build.openings(...)` для проёмов. **Называй материал.** `Item(..., blocks=GLASS)`. Без этого декларация удовлетворяется любым не-воздухом в своей маске, и оконный проём, объявленный на стене, в которую прорезан, удовлетворяется стеной: «остекление не построено» проходит зелёным. С материалом строка говорит `nothing of minecraft:light_blue_stained_glass stands there`. - строка манифеста **без декларации** — часть, которую **никто не построил**; - декларация **без блоков** под ней — часть, **построенная в ничто**: маска сточилась, диапазон цикла вышел пустым, геометрию вынесло за холст; - декларация, у которой **сосед вне досягаемости**, — часть, **построенная не там**: мостик, не дошедший до конуса трёх метров. **Строка без реализации валит гейт в красное.** В этом весь смысл: это спецификация, а спецификация, которая молчит, когда не выполнена, — список пожеланий. Пиши манифест первым, смотри, как он падает, потом строй, пока не перестанет. Смежность (`near`) — проверка сборки, а не пожелание. Дорожка, не дошедшая до конуса, проходит любую проверку собственной формы. Досягаемость по умолчанию 1.5 м, потому что две вещи, соприкасающиеся вдоль стены под 52°, отстоят центрами на `|cos θ| + |sin θ| = 1.41` клетки, и что-либо строже объявило бы настоящий стык разрывом. Разрыв сообщается **числом** — «5.0 м врозь», — а не булевым значением. Без этого часть, которую никогда не строили, неотличима от части, которой не существует, и пайплайн рапортует об успехе. Четыре дефекта из шести на реальном примере были именно такими. **Считать копии — тоже дело фотографии.** `Item(..., copies=8)` — это счёт, и счёт ровно то, что кадр умеет, по тому же аргументу, на котором стоит `placed`. Вектор, которым копии шагают, — измерение, живёт в `derived.json` и берётся через `site.step(...)`; печатать его в манифест нельзя. Рядом со штампом ставится `sched.declare_repeat(имя, мотив, y0, y1, шаг, счёт)`, и гейт сверяет блоки каждой копии с первой **побайтово**. Это единственная строка, которая отличает восемь одинаковых секций от восьми, нарисованных восемь раз: у них один силуэт, одно сечение, один план, одни доли материалов и один аудит. Концы ряда — не копии. Фронтон, лестничное ядро, угловой возврат — свои `Item`, а не «копия с правкой»: отредактированный штамп копией не является. `near` у повторяющейся части считается по каждой копии отдельно и называет худшую. Слитая маска отвечает своей ближайшей клеткой, поэтому семь вилл на палубе отвечали бы за восьмую, висящую в пяти метрах от её края. ## Самопроверка У расписания нет картинки, на которую можно посмотреть, поэтому `tools/schedule_selftest.py` гоняет его на синтетическом объекте 24×24×12, несущем по одной намеренной неисправности на каждый режим отказа, плюс три правильные части и пять авторских ошибок. Это единственная проверка, чьё собственное молчание неотличимо от успеха: если она завтра перестанет замечать пропавшие части, все гейты ниже позеленеют, а отчёт будет читаться ровно так же, как сейчас.
View on GitHub