Wave Function Collapse
Механизм (simple-tile)
Базовый вариант — simple-tile: ты вручную задаёшь, какие тайлы могут соседствовать (трава рядом с травой и с кромкой; вода рядом с водой и кромкой; кромка — между ними). Алгоритм:
Цикл WFC псевдокод
пока есть неопределённые клетки:
cell = клетка с минимальной энтропией # меньше всего вариантов
tile = выбрать из cell.options по весам # частотность тайла
cell.collapse(tile) # observe
очередь = [cell]
пока очередь не пуста: # propagate
c = очередь.pop()
для соседа n клетки c:
до = n.options
n.options ∩= совместимые_с(c) # срез по смежности
если n.options изменились: очередь.push(n)
если у какой-то клетки options пусто:
противоречие → backtrack или restartЕсть и второй вкус — overlapping / NxN-pattern: правила смежности не задаются руками, а извлекаются из примера-картинки (все N×N окошки). Мощнее (учится у образца), но дороже и капризнее. В лабе — simple-tile, чтобы увидеть механику без магии.
🕹 В какие игры поиграть — и что заметить
WFC и его родню видно лучше всего там, где «разнообразно, но всегда когерентно». Четыре кейса от «ты сам дёргаешь коллапс» до контраста с другим подходом к PCG — и что заметить руками (от простого к сложному).
Городок-песочница Оскара Стольберга: ставишь домик-воксель — а движок сам достраивает крыши, стены, окна и арки так, чтобы всё стыковалось. Под капотом — WFC на нерегулярной сетке + marching cubes: каждый блок «коллапсит» геометрию по правилам смежности с соседями. Ты буквально дёргаешь observe рукой, а propagate доводит остальное.
🎮 Сыграй: поставь и убери пару блоков подряд — смотри, как крыши/окна/арки пересобираются под новых соседей. Поставь блок на отшибе, потом соедини с домом — увидишь, как геометрия «схлопывается» под стык. Это constraint propagation, которым управляешь пальцем (ровно как якорь в лабе).
Тот же Стольберг, но WFC генерит геометрию островов (его доклад EPC2018 так и называется — «Wave Function Collapse in Bad North»). Каждый остров — связная, играбельная топология: нет невозможных обрывов, есть где высадиться и обороняться. Реализация неинтерактивная (генерит до боя), в отличие от Townscaper.
🎮 Сыграй: пробеги кампанию по карте — каждый остров разный, но всегда когерентный и проходимый; заметь, что «странных» обрывов и тупиков-ловушек нет. Это ограничения смежности держат форму.
Первое коммерческое применение WFC (Брайан Баклю, Freehold Games; доклады GDC 2019 / Roguelike Celebration). Генерация многопроходная: грубая структура → средние проходы добивают детали через WFC → финальные пасы чинят связность и населяют. Баклю прямо разбирал боли — overfitting/гомогенность и связность уровней — и как их лечит.
🎮 Сыграй: брожи по руинам и постройкам — узоры стен и полов разнообразны, но локально когерентны (WFC учился у примеров), при этом уровень всегда связен (финальный пас-проверка). Контраст «разнообразно, но не мусор».
Не WFC, а соседний подход: уровень собирается из рукотворных комнат-шаблонов на сетке 4×4, поверх прокладывается гарантированный путь вход→выход. Но мораль — ровно из урока: генерация без ограничений = мусор; «связно и проходимо» — это жёсткие констрейнты поверх рандома, а валидный ≠ играбельный.
🎮 Сыграй: пробеги десяток уровней — каждый разный, но путь от входа к выходу есть всегда (тот самый carved path). Попробуй «застрять без бомб» — нащупаешь, где кончается констрейнт проходимости и начинается мастерство дизайна.
Хардкор: WFC как CSP, энтропия Шеннона и неразрешимость замощенияможно пропустить
WFC = задача удовлетворения ограничений (CSP)
Переменные — клетки, домены — множества тайлов, ограничения — смежности. Шаг propagate() — это enforcement дуговой согласованности (AC-3): для каждой дуги (клетка → сосед) выкидываем из домена соседа значения без поддержки и пере-кладываем в очередь затронутые дуги. Псевдокод WFC буквально и есть специализированный AC-3.
Что такое «энтропия» строго
«Клетка с минимальной энтропией» — это энтропия Шеннона по взвешенным вариантам:
(w_t — частотный вес тайла; обычно + малый шум для разрыва ничьих). При равных весах вырождается в «меньше всего оставшихся вариантов» — эвристику MRV (minimum remaining values) из классического CSP.
Завершаемость и сложность
AC-3 корректна, но не полна: дуговая согласованность не гарантирует существование глобального решения. Поэтому WFC без бэктрекинга неполон — может упереться в противоречие, даже когда решение есть. Полнота требует поиска с откатом.
В общем случае жёстче: «замостить плоскость данным набором тайлов Ванга» — неразрешимая задача (Бергер, 1966, domino problem); конечная версия (n×n) — NP-полна. WFC наследует эту жёсткость; работает на практике, потому что игровые тайлсеты «рыхлые» (валидных конфигураций много), а рестарт дёшев.
Хардкор · дизайн: контролируемость против сюрпризаможно пропустить
Чистая процедурка быстро ощущается «одинаковой» и бездушной — это её главный дизайн-провал, не технический. Рычаги контроля:
- Якоря и сет-пизы: вручную фиксируешь ключевые клетки/комнаты, генератор заполняет остальное (босс-арена и вход заданы, путь между — процедурный).
- Гибрид handcrafted + procedural: Spelunky/Diablo собирают уровень из рукотворных кусков процедурно — лучшее из двух миров.
- QA генерации: валидный ≠ интересный и ≠ проходимый. Нужен авто-чек на связность (flood-fill/A*), на тупики, на скучность — иначе игрок получит «технически корректный» мусор.
- Build-time vs runtime: генерить на билде (контроль, кураторство) или на лету (бесконечность, риск) — выбор дизайнерский, не только технический.
Общее: расписания, расстановка (timetabling), раскладка, конфигураторы товаров — всё CSP; под капотом SAT/SMT-солверы.
ML / AI: constrained / grammar-guided decoding — это WFC над токенами (генерация строго по схеме); structured output; diffusion с guidance = генерация под ограничениями; синтетические данные/аугментация = procgen для обучения.
Бэкенд: разрешение зависимостей (npm/cargo/apt) = constraint solving; типичный источник «версионного ада».
Принцип: «генерация = рандом, зажатый правилами»; где «собери валидное целое из кусков с ограничениями» — там CSP.
wfc.py (simple-tile: extract_adjacency, propagate, weighted collapse, anchoring) — запускается как есть. Подбрось в него лог «кто кого схлопнул» и смотри порядок observe / propagate в консоли. Модификации по нарастанию: добавь тайл-гору с редким весом (участишь противоречия), затем backtracking вместо рестарта (полнота вместо «попробуй заново»), затем overlapping-режим — правила из картинки-образца, а не руками. Папка labs/lab-05-wfc-terrain/.«Минимальная энтропия» — это эвристика MRV из CSP?
Если propagate = AC-3, почему он не гарантирует решение?
WFC наследует NP-полноту замощения — почему он тогда мгновенный в играх?
Чем overlapping-вариант вычислительно дороже simple-tile?
Это вообще «волновая функция»?
- Maxim Gumin, оригинальный репозиторий WaveFunctionCollapse (GitHub) — исходник и галерея.
- «WFC is constraint solving in the wild» (Karth & Smith) — академический разбор связи с CSP.
- Модуль 11, раздел 4.1 + Lab 05.