Как агент смотрит на свою работу — и как слепнет незаметно
Цикл работы агента в 3D простой: сделал — посмотрел — поправил. Второй шаг —
get_viewport_screenshot. Без него агент пишет код вслепую и не имеет способа
узнать, что вышло.
Именно этот шаг ломается на сервере без монитора. Ломается так, что это не похоже на поломку.
Симптом
Агент просит скриншот. Приходит ответ:
{"status": "success", "result": {"success": true, "width": 400, "height": 239}}
Файл на месте, открывается, это валидный PNG нужного размера. И он полностью чёрный.
Дальше происходит самое неприятное. Агент не получил ошибки — он получил картинку. Он делает из неё вывод: сцена пустая или объект не в кадре. Начинает «чинить»: двигает камеру, добавляет свет, пересобирает модель. Каждый раз просит скриншот, каждый раз получает чёрное, каждый раз объясняет это новой причиной. Модель может быть при этом идеальной.
Отчёты об успехе продолжают идти.
Причина
Дело в том, откуда берётся картинка.
Оригинальный код зовёт bpy.ops.screen.screenshot_area(). Этот оператор читает
передний буфер окна — тот кадр, который прямо сейчас показан пользователю.
В обычной графике работает двойная буферизация: приложение рисует в задний буфер, а по готовности композитор меняет буферы местами, и нарисованное становится видимым. Передний буфер — результат последней такой перестановки.
На сервере под Xvfb композитора нет. Показывать некому, менять буферы незачем, перестановка не происходит никогда. Передний буфер так и остаётся в цвете очистки — чёрном.
Blender при этом отработал честно: его попросили прочитать буфер, он прочитал буфер. Буфер чёрный. Ошибки нет — потому что с точки зрения кода ничего не сломалось. Сломалось предположение, что в переднем буфере есть картинка.
Починка
Не читать окно вовсе. Рендерить вьюпорт в собственный буфер, которого экран никогда не касался:
offscreen = gpu.types.GPUOffScreen(width, height)
with offscreen.bind():
offscreen.draw_view3d(scene, view_layer, space_data, region,
view_matrix, projection_matrix,
do_color_management=True)
GPUOffScreen — внеэкранный буфер, draw_view3d рисует в него ровно то, что
показал бы вьюпорт. Композитор не нужен: перестановки буферов в этой схеме нет
вообще.
В наборе digitable-lol/blender-mcp
аддон уже пропатчен, изменён ровно один метод, и это записано в NOTICE.
Обновляя аддон с оригинала, патч придётся накладывать заново — иначе слепота
вернётся молча.
Как отличить одно от другого
«Посмотреть глазами» — плохая проверка, когда проверять должен агент. Нужен критерий, выражаемый кодом.
Он простой: сосчитать различные значения пикселей. У равномерно чёрной картинки оно ровно одно. У любого непустого вьюпорта — сотни: сетка, оси, градиент фона.
w, h, distinct = read_png(path)
assert len(distinct) > 1, "равномерно чёрный — аддон не пропатчен"
На проверке связки из этого трека: снимок 400×239 с одним объектом в сцене дал 1071 различное значение. Сломанный вариант дал бы 1. Разница не тонкая — её не нужно интерпретировать.
Такую проверку стоит гнать при установке, а не когда что-то пошло не так. Дешёвый тест, отвечающий на вопрос «мой инструмент вообще видит?».
Вывод, который шире 3D
Классификация отказов по неприятности:
- Падение с внятной ошибкой — лучший случай. Видно сразу, стектрейс показывает место.
- Падение с невнятной ошибкой — хуже, но всё же видно.
- Зависание — плохо, но заметно.
- Успешный ответ с неверными данными — худшее, что бывает.
Четвёртый тип опасен тем, что потребляет доверие. Система докладывает
success, потребитель верит и строит на этом. Ошибка всплывает далеко от места
возникновения — а к тому времени поверх неё уже наросли выводы.
Для агентов это особенно больно. У человека есть здравый смысл: увидев десятый подряд чёрный кадр, он заподозрит инструмент. Агент склонен верить инструменту и искать причину в своей работе — то есть чинить исправное.
Отсюда практики:
- Проверяйте инструмент отдельно от задачи. Первым делом после установки — снимок заведомо непустой сцены и подсчёт цветов. Не «работает ли пайплайн», а «не врёт ли инструмент».
success— это не результат. Статус говорит, что код дошёл до конца, а не что он сделал полезное. Проверяйте артефакт: пиксели, треугольники, байты.- Ищите опровержение, а не подтверждение. Вопрос «что я увижу, если инструмент сломан?» полезнее вопроса «похоже ли на правду?».
- Осмысленно относитесь к значениям по умолчанию. Чёрный — цвет очистки
буфера. Многие тихие отказы возвращают именно значение по умолчанию: пустую
строку, ноль,
None. Если ответ подозрительно круглый, спросите себя, не является ли он просто неинициализированным.
Тот же принцип, с той же формулировкой, лежит в https://courses.digitable.life/post/testing/00-overview/: тест, который не может упасть, ничего не проверяет.
Дальше — https://courses.digitable.life/post/3d-pipeline/05-materials/: материалы, свет и рендер, где нас ждёт ещё одна перевёрнутая интуиция.