Мы привыкли думать о вычислениях как о битах, регистрах и арифметике. А что, если базовой единицей вычисления сделать не бит, а локальную геометрическую конфигурацию тетраэдров? В этой статье я покажу дискретный тетраэдрический движок состояний, симметрийную канонизацию, аттракторы, иерархические jump-таблицы и реальные замеры на RTX 3090 — с измеренным exact-ускорением в 554.92 раза на одной и той же задаче.

1. Зачем вообще считать не битами, а тетраэдрами

Обычная вычислительная логика устроена очень просто: есть биты 0/1, есть операции над ними, есть длинные цепочки преобразований.

Но можно посмотреть на вычисление иначе: не как на манипуляцию отдельными битами, а как на эволюцию локальных геометрических конфигураций.

Вместо «ячейка памяти хранит 0 или 1» можно взять более богатую сущность:

  • положение объекта,

  • ориентацию,

  • фазу,

  • допустимые симметрии,

  • правила локального преобразования.

В моей модели такой сущностью стал локальный кластер тетраэдров.

Бит — это лампочка: либо включена, либо выключена.

Тетраэдрический мотив — это уже маленькая механическая конструкция, у которой есть:

  • как она стоит,

  • куда повернута,

  • в каком состо��нии находится,

  • как она может перестроиться дальше.

То есть одна «единица состояния» становится намного богаче обычного бита.

2. Почему именно тетраэдр

Тетраэдр — это простейшее трёхмерное тело с богатой симметрией:

  • 4 вершины,

  • 4 грани,

  • 6 рёбер.

У него достаточно сложная геометрия, чтобы порождать интересные классы состояний, но при этом он ещё достаточно прост, чтобы всё это можно было эффективно кодировать.

В вычислительном смысле тетраэдр удобен по трём причинам:

  • у него компактная группа симметрий;

  • локальное состояние можно дискретизировать;

  • последовательности преобразований можно предвычислять.

Тетраэдр — это «минимальная интересная 3D-фигура». Куб слишком привычный. Треугольник — уже не 3D. А тетраэдр — это маленькая 3D-ячейка, на которой уже можно строить нетривиальную «механику состояний».

3. Как устроено состояние в движке

В моей модели локальный кластер устроен так:

  • есть 4 слота;

  • в них находятся 3 тетраэдра;

  • 1 слот пустой.

Каждый занятый слот хранит локальное состояние тетраэдра:

  • ориентация: 0..11,

  • фаза: 0..2.

Итого одно локальное состояние тетраэдра:

[ 12 × 3 = 36 ] вариантов.

Пустой слот кодируется отдельно.
То есть полный raw-state кластера — это конфигурация вида:

  • 4 позиции,

  • из них 3 заняты,

  • в каждой занятой позиции — один из 36 вариантов.

Представьте 4 ячейки, как будто 4 места вокруг маленькой геометрической конструкции. На трёх местах стоят тетраэдры, одно место пустое. Каждый тетраэдр может:

  • стоять в одной из 12 ориентаций,

  • быть в одной из 3 фаз.

Из этого собирается локальная «сцена».

4. Сколько вообще таких состояний

Если не учитывать симметрии, то количество raw-state равно:

  • выбор пустого слота: 4,

  • три заполненных слота, в каждом по 36 вариантов.

[ 4 · 36^3 = 186,624 ]

Именно это и показал код:

Raw states: 186 624

Но это ещё не настоящие рабочие состояния, потому что многие из них эквивалентны с точностью до вращения тетраэдрической схемы.

Если просто «в лоб» перечислить все варианты, их почти 187 тысяч. Но среди них много одинаковых по сути, просто повернутых в пространстве. Как если бы вы посмотрели на один и тот же предмет с другой стороны — сам предмет не изменился, изменился только ракурс.

5. Симметрийная канонизация: как 186 тысяч состояний превращаются в 15 тысяч

Это один из ключевых шагов.

Мы берём группу вращений тетраэдра. Для неё я использовал 12 вращений — это ориентационно-сохраняющая группа симметрий тетраэдра, изоморфная A₄.

Для каждого raw-state мы:

  1. применяем все допустимые симметрии;

  2. получаем множество эквивалентных конфигураций;

  3. выбираем канонический представитель, например, лексикографически минимальный packed-state.

Так raw-state схлопываются в классы эквивалентности.

Результат:

Canonical motifs: 15 576
Symmetry count: 12

То есть:
[ 186,624 → 15,576 ]

Это уже серьёзное сжатие пространства состояний.

Мы перестаём хранить «одну и ту же фигуру, повёрнутую по-разному» как разные состояния. Мы говорим: у этой кон��игурации есть «канонический вид», и всё приводим к нему. Это примерно как вместо «кошка лицом влево» и «кошка лицом вправо» хранить просто «кошка», если для задачи поворот не важен.

6. Что такое мотив

После канонизации базовой единицей вычисления становится motif — канонический локальный класс конфигурации.

То есть дальше движок работает уже не с произвольными «сырыми» состояниями, а с компактным идентификатором мотива:

[ \text{motifId} \in [0,15575] ]

Именно motif становится базовой «единицей вычисления».

После всей уборки и удаления дубликатов остаётся не 186 тысяч хаотичных вариантов, а 15 576 «типовых конструкций». Вот эти типовые конструкции и есть «слова» нашего геометрического языка.

7. Что такое генераторы преобразований

Дальше нужен механизм вычисления — то есть правила, как одна конфигурация переходит в другую.

Для этого вводятся 4 генератора:

[ G_0, G_1, G_2, G_3 ]

Каждый генератор делает три вещи:

  • переставляет слоты;

  • двигает один тетраэдр в пустую позицию;

  • изменяет его ориентацию и фазу;

  • слегка обновляет состояния остальных тетраэдров.

Фактически это локальная геометрическая операция переписывания.

Генератор — это как «одна маленькая команда»: что-то повернуть, что-то сдвинуть, пустое место поменять, обновить локальные состояния. То есть не арифметика, а маленькая геометрическая перестройка.

8. Что такое путь

Одна команда — это хорошо, но вычисление обычно состоит из последовательности команд.

Поэтому вводится путь — короткое слово над генераторами.

Например, базовый путь у меня был такой:

[ (2,2,1,2,2) ]

То есть:
[ F = G_2 \circ G_2 \circ G_1 \circ G_2 \circ G_2 ]

Это уже не единичное движение, а маленькая программа.

Во второй и третьей версиях движка я использовал уже 8 разных путей:

pathId=0 [2,2,1,2,2]
pathId=1 [0,1,2,3,0]
pathId=2 [3,2,1,0,3]
pathId=3 [1,1,1,1,1]
pathId=4 [0,2,0,2,1]
pathId=5 [3,3,0,1,2]
pathId=6 [1,3,2,1,0]
pathId=7 [2,0,3,1,2]

Если генератор — это одна команда, то путь — это короткий скрипт из 5 команд. То есть мы уже не просто «щёлкаем одной операцией», а задаём маленькую программу над геометрической системой.

9. Как происходит вычисление «в лоб»

Если делать всё напрямую, то для каждого мотива и каждого пути нужно последовательно применить все генераторы в слове.

Если путь повторяется много раз, например F⁶⁴, то приходится выполнять длинную це��очку переходов.

Именно так работает direct-режим.

Для случая F⁶⁴ и 4 млн элементов результаты такие:

Direct random time: 17,28 ms
Direct grouped time: 10,10 ms

Это «честный тупой способ»: берём состояние, 5 раз применяем команды пути, потом ещё 64 раза повторяем весь путь, и так для миллионов элементов. Понятно, что это работает, но дорого.

10. Главная идея ускорения: предвычислить длинные композиции

Вот здесь начинается самое интересное.

Если путь фиксирован, то можно заранее вычислить, куда он переводит каждый мотив:

[ \text{motif} \to F(\text{motif}) ]

Это таблица J0 = F¹.

Но можно пойти дальше и заранее вычислить:

  • J1 = F⁶⁴

  • J2 = F⁴⁰⁹⁶

  • J3 = F²⁶²¹⁴⁴

  • J4 = F¹⁶⁷⁷⁷²¹⁶

То есть очень длинная программа превращается в один lookup. Именно это и есть jump-таблицы.

Вместо того чтобы каждый раз снова и снова выполнять длинную последовательность команд, мы делаем как в математике с таблицей степеней: заранее знаем, что получится после 64 шагов, после 4096 шагов, после 262 тысяч шагов, после 16.7 миллионов шагов. Тогда вычисление превращается в «сразу взять готовый ответ из таблицы».

11. Иерархия jump-таблиц

В base-64 варианте иерархия такая:

J0: F^1
J1: F^64
J2: F^4 096
J3: F^262 144
J4: F^16 777 216

Это означает, что:

  • J1 эквивалентен 64 применениям J0,

  • J2 эквивалентен 64 применениям J1,

  • и так далее.

Результат — очень глубокая композиция кодируется одной табличной операцией.

Это как матрёшка уровней ускорения: один уровень знает 1 шаг, следующий — 64 шага, следующий — уже 4096 шагов, и так дальше. Чем выше уровень, тем длиннее программа, спрятанная в одном lookup.

12. Почему понадобился ещё и binary jump hierarchy

Base-64 отлично работает для степеней 64, но плохо подходит для произвольного числа повторов.

Для этого была построена ещё и бинарная иерархия:

B0: F^1
B1: F^2
B2: F^4
B3: F^8
...
B24: F^16 777 216

Тогда любое число повторов N можно разложить по двоичным степеням и применить нужные jump-уровни. Это даёт лучший arbitrary-режим.

И код подтвердил это на практике:

Base64 arbitrary time: 3,86 ms
Binary arbitrary time: 1,84 ms
Binary arbitrary: 2,10x vs base64-arbitrary

Если нужно сделать не «ровно 64 в степени k», а какое-то произвольное число шагов, например 12 345 678, удобнее раскладывать не по 64, а по двоичным кускам: 1, 2, 4, 8, 16... Это стандартная идея «быстрого возведения», только применённая к геометрическим переходам.

13. Аттракторы: почему разные мотивы сходятся к одним и тем же циклам

Если взять отображение

[ \text{motif} \to F(\text{motif}) ]

то оно задаёт функциональный граф.

У такого графа каждая вершина имеет ровно одно исходящее ребро, а значит вся система распадается на:

  • хвосты,

  • циклы,

  • бассейны притяжения этих циклов.

Именно это и даёт аттракторную структуру.

Для разных путей картина оказалась очень разной. Например:

pathId=0 [2,2,1,2,2] | cycles=2, maxCycle=20, avgSteps=66,52, dominantBasin=15 486 (99,42%)
pathId=5 [3,3,0,1,2] | cycles=3, maxCycle=145, avgSteps=26,21, dominantBasin=15 504 (99,54%)
pathId=3 [1,1,1,1,1] | cycles=17, maxCycle=74, avgSteps=20,13, dominantBasin=7 913 (50,80%)

Если много раз применять один и тот же путь, система обычно не гуляет бесконечно куда попало. Она либо приходит в цикл, либо в маленькое семейство устойчивых режимов. Это как шарик, катящийся по поверхности: почти всегда он попадает в какую-то «ямку» или начинает крутиться по устойчивой траектории.

14. Что значат разные типы путей

Пути оказались не просто разными, а семантически разными.

Сильно сжимающие пути
Например, path 0 и path 5: почти все состояния затягиваются в один доминирующий basin. Это похоже на нормализацию или приведение к канонической форме.

Более выразительные пути
Например, path 3, path 2, path 6, path 7: больше циклов, больше разнообразия, меньше «схлопывание». Такие пути ближе к богатой логике состояний.

Какие-то пути работают как «пылесос» — почти всё засасывают в один режим. А какие-то пути больше похожи на «разветвитель» — сохраняют разнообразие и дают системе больше вариантов поведения. То есть пути начинают вести себя как разные инструкции процессора.

15. Packed metadata: как аттракторную информацию упаковали в один int

Для каждого (pathId, motifId) можно знать:

  • представителя цикла,

  • длину цикла,

  • число шагов до цикла.

Чтобы не читать несколько буферов, я упаковал это в один 32-битный integer:

  • representative: 14 бит,

  • cycleLength: 8 бит,

  • stepsToCycle: 10 бит.

Это уменьшает память и сокращает число чтений.

Вместо того чтобы хранить 3 разных числа в 3 разных местах, мы складываем их в одну компактную коробку из 32 бит. Меньше памяти — быстрее lookup.

16. Fused kernel: почему лучше сразу делать и jump, и attractor lookup

Отдельные проходы по памяти — дорого.

Поэтому был сделан fused-режим, который за один dispatch делает:

  • jump;

  • lookup packed attractor metadata.

То есть pipeline стал таким:

[ (\text{motifId},\text{pathId}) \to \text{newMotifId} + \text{attractorMeta} ]

Практически это оказалось очень удачным решением.

Вместо двух походов в память мы идём один раз: сразу прыгаем в итоговое состояние, сразу забираем всё, что нужно знать о его аттракторе. Это как сходить в магазин один раз и купить всё сразу, а не идти сначала за хлебом, потом отдельно за молоком.

17. Почему grouped pathId оказался быстрее random pathId

В direct-режиме путь для каждого элемента выбирается по pathId.

Если pathId случайный, потоки GPU идут по более «рваным» паттернам доступа. Если pathId сгруппирован, потоки становятся более согласованными.

Результат:

Direct random time: 17,28 ms
Direct grouped time: 10,10 ms

То есть grouped scheduling даёт ускорение в 1.71x.

Для fused-режима эффект гораздо слабее:

Fused J1 random time: 1,85 ms
Fused J1 grouped time: 1,74 ms

Это значит, что jump/fused-режим значительно устойчивее к path divergence.

Если соседние потоки делают похожую работу, GPU это любит. Если каждый поток живёт своей жизнью, начинаются потери. Поэтому «сортировать задачи по типу пути» — это хорошая идея, особенно для direct-режима.

18. Результаты по скорости

Вот ключевые результаты замеров.

Базовые тесты

Standard compute: 3,60 ms
Direct F^64 random: 17,28 ms
Direct F^64 grouped: 10,10 ms
Jump J1 random: 1,62 ms
Fused J1 random: 1,85 ms
Fused J1 grouped: 1,74 ms
Base64 arbitrary: 3,86 ms
Binary arbitrary: 1,84 ms
Base64 exact deep: 1,31 ms
Binary exact deep: 1,66 ms

Простыми словами
Что это значит:

  • прямой пересчёт длинных путей — медленный;

  • jump-таблицы — очень быстрые;

  • fused-режим почти не уступает чистому jump;

  • binary лучше для произвольной глубины;

  • base64 лучше для точных степеней 64.

19. Что оказалось лучше для каких задач

Для F⁶⁴
Лучший режим — jump или fused jump.

Jump J1 random: 1,62 ms
Fused J1 random: 1,85 ms

Против direct random:

17,28 ms

Для arbitrary repeat
Лучший режим — binary decomposition.

Binary arbitrary: 1,84 ms
vs
Base64 arbitrary: 3,86 ms

Для exact powers of 64
Лучший режим — base64 exact jump.

Base64 exact deep: 1,31 ms
vs
Binary exact deep: 1,66 ms

Если сильно упростить:

  • ровные большие степени → бери base64;

  • любое произвольное число шагов → бери binary;

  • нужна ещё информация про аттрактор → бери fused.

20. Самый важный тест: реальная точная задача на 30 секунд

Чтобы уйти от «оценок по линейной экстраполяции», я сделал реальный exact benchmark на 30 секунд.

Задача была одна и та же:

[ F^{4096} ]

Считалась двумя способами:

  • Direct exact: 64 последовательных запуска F⁶⁴.

  • Jump exact: 1 запуск J2 = F⁴⁰⁹⁶.

Обе реализации вычисляют одну и ту же точную функцию.

Результаты:

DIRECT EXACT F^4096
 Time: 30,69 s
 Batches completed: 41
 Exact depth per batch: F^4 096
 Batches/sec: 1,3359
 Path applications/sec: 21 887 824 042
 Generator applications/sec: 109 439 120 208
JUMP EXACT F^4096
 Time: 30,00 s
 Batches completed: 22 240
 Exact depth per batch: F^4 096
 Batches/sec: 741,3308
 Path applications/sec: 12 145 964 037 056
 Generator applications/sec: 60 729 820 185 278
Measured exact throughput speedup: 554,92x

Мы не сравниваем «примерно похожие штуки». Мы сравниваем одну и ту же задачу: либо выполнять её длинной прямой цепочкой, либо взять уже сжатый jump. И вот тут ускорение получилось 554.92x. То есть не «в теории», а реально, по секундомеру.

21. Почему 554.92x важнее, чем «миллионы раз по оценке»

Ранее по более глубоким jump-уровням можно было получить очень большие оценочные факторы, например миллионы раз, если линейно экстраполировать direct на F¹⁶·⁷⁷⁷·²¹⁶. Но это всё равно остаётся оценкой.

А вот ускорение в 554.92x на F⁴⁰⁹⁶ — это уже измеренный exact speedup.

22. За счёт чего вообще получается такая скорость

Теперь главный вопрос: почему это всё так быстро работает?

Причина №1. Мы считаем не арифметику, а переходы между классами состояний
Вместо длинной последовательности операций над числами мы работаем с компактными motif-id.
Простыми словами: Мы не считаем формулы в лоб, а ходим по карте уже известных состояний.

Причина №2. Симметрии сильно уменьшают пространство
Raw-state: 186,624 → Canonical motifs: 15,576.
Простыми словами: Мы избавились от огромного количества дубликатов.

Причина №3. Длинные программы предвычислены
F⁴⁰⁹⁶ превращается не в 4096 повторов, а в один lookup.
Простыми словами: Мы не повторяем длинную работу, а достаём уже готовый итог.

Причина №4. GPU хорошо любит табличные переходы малого размера
Все ключевые таблицы маленькие по современным меркам:

  • Generator table: ~0.24 MB

  • Base64 jump tables: ~2.38 MB

  • Packed attractor metadata: ~0.48 MB

  • Binary jump tables: ~11.88 MB
    Простыми словами: Таблицы достаточно компактные, чтобы GPU быстро с ними работал.

Причина №5. Fused kernel уменьшает число проходов по памяти
Jump + attractor metadata делаются за один проход.
Простыми словами: Меньше бегаем туда-сюда по памяти — быстрее работаем.

Причина №6. Для arbitrary repeat используется правильная иерархия
Binary decomposition дала ускорение в 2.10x относительно base64 decomposition для произвольной глубины.
Простыми словами: Для разных типов задач нужен разный «способ прыжка». Мы это учли.

23. Что это НЕ означает

Чтобы остаться честными, нужно сказать и о границах метода.

Это не «ускорение любых вычислений»
Метод ускоряет длинные композиции в дискретной геометрической системе, а не произвольные задачи мира.
Простыми словами: Это не «новый суперкомпьютер для всего подряд», а очень сильный ускоритель для определённого класса вычислений.

Это не замена криптографии
Такой движок не заменяет SHA-256 и не делает валидный Bitcoin mining.
Простыми словами: Биткоин всё равно требует свой хеш, и сеть не примет «тетраэдрический ответ».

Это не магия
Основной выигрыш возникает потому, что вычисление имеет структуру, а структура позволяет сделать jump-таблицы.
Простыми словами: Если задача не имеет повторяемых переходов и симметрий, такого ускорения не будет.

24. Что это уже означает на практике

Зато вот что действительно означает:

  1. У нас есть рабочий дискретный геометрический вычислитель:

    • богатая единица состояния;

    • симметрийная канонизация;

    • множество путей;

    • attractor-структура;

    • jump-компрессия.

  2. У нас есть зачаток instruction set. Разные пути ведут себя по-разному: одни сильно сжимают пространство, другие дают богатую динамику, третьи создают длинные циклы.

  3. У нас есть runtime-стратегия:

    • exact powers of 64 → base64 jump;

    • arbitrary repeats → binary jump;

    • metadata → fused;

    • path grouping → для лучшего scheduler behavior.

Итог

Если формулировать вывод максимально честно и точно, то он такой:

В этой работе я построил дискретный тетраэдрический вычислительный движок, в котором базовой единицей является не бит, а канонический геометрический мотив.

За счёт:

  • симметрийной канонизации,

  • предвычисления переходов,

  • иерархических jump-таблиц,

  • packed attractor metadata,

  • fused GPU-kernel,

  • и правильного выбора стратегии исполнения,

удалось получить измеренное exact-ускорение в 554.92x на одной и той же задаче F⁴⁰⁹⁶ на RTX 3090.

А также показать, что:

  • base64 jump лучше для точных степеней,

  • binary jump лучше для произвольной глубины,

  • разные пути формируют разные attractor-профили, а значит у такой системы уже появляется вычислительная семантика.


Главная мысль статьи очень простая: если считать не битами, а геометрическими состояниями, и заранее знать, как длинные цепочки преобразований схлопываются, то можно получить очень серьёзное ускорение на точных задачах.

Код программы:

Листинг кода
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using ComputeSharp;

namespace TetraCombinationEngineV3
{
    internal static class Config
    {
        // ----------------------------------------------------
        // Дискретная тетра-кодировка
        // ----------------------------------------------------
        public const int SLOT_COUNT = 4;
        public const int OCCUPIED_COUNT = 3;

        public const int ORIENTATION_COUNT = 12;
        public const int PHASE_COUNT = 3;

        public const int LOCAL_STATE_COUNT = ORIENTATION_COUNT * PHASE_COUNT; // 36
        public const int EMPTY = LOCAL_STATE_COUNT; // 36
        public const int PACK_BASE = LOCAL_STATE_COUNT + 1; // 37

        // ----------------------------------------------------
        // Paths
        // ----------------------------------------------------
        public const int PATH_LENGTH = 5;

        public static readonly int[][] PATHS =
        {
 new[] { 2,2,1,2,2 },
 new[] { 0,1,2,3,0 },
 new[] { 3,2,1,0,3 },
 new[] { 1,1,1,1,1 },
 new[] { 0,2,0,2,1 },
 new[] { 3,3,0,1,2 },
 new[] { 1,3,2,1,0 },
 new[] { 2,0,3,1,2 }
 };

        public static readonly string[] PATH_NAMES =
        {
 "2,2,1,2,2",
 "0,1,2,3,0",
 "3,2,1,0,3",
 "1,1,1,1,1",
 "0,2,0,2,1",
 "3,3,0,1,2",
 "1,3,2,1,0",
 "2,0,3,1,2"
 };

        public static int PATH_COUNT => PATHS.Length;

        // ----------------------------------------------------
        // Base-64 hierarchy
        // ----------------------------------------------------
        // J0 = F^1
        // J1 = F^64
        // J2 = F^4096
        // J3 = F^262144
        // J4 = F^16777216
        public const int BASE64_JUMP_BASE = 64;
        public const int BASE64_LEVELS = 5;

        // ----------------------------------------------------
        // Binary hierarchy
        // ----------------------------------------------------
        // B0 = F^1
        // B1 = F^2
        // ...
        // B24 = F^16777216
        public const int BINARY_LEVELS = 25;

        // ----------------------------------------------------
        // Packed attractor metadata
        // rep:14 bits [0..16383]
        // len:8 bits [0..255]
        // step:10 bits [0..1023]
        // total:32 bits
        // ----------------------------------------------------
        public const int REP_BITS = 14;
        public const int LEN_BITS = 8;
        public const int STEP_BITS = 10;

        public const int REP_MASK = (1 << REP_BITS) - 1;
        public const int LEN_MASK = (1 << LEN_BITS) - 1;
        public const int STEP_MASK = (1 << STEP_BITS) - 1;

        public const int LEN_SHIFT = REP_BITS;
        public const int STEP_SHIFT = REP_BITS + LEN_BITS;

        // ----------------------------------------------------
        // Benchmark
        // ----------------------------------------------------
        public const int ELEMENTS = 4_000_000;
        public const int CORRECTNESS_TEST_SIZE = 100_000;
        public const int STANDARD_ITERATIONS = 80;

        public const int DIRECT_GPU_WORD_REPEATS = 64;
        public const int ARBITRARY_REPEAT_COUNT = 12_345_678;

        // exact deep target
        public const int EXACT_DEEP_REPEAT = 16_777_216;

        // ----------------------------------------------------
        // Real 30-second task
        // exact semantic task:F^4096
        // direct exact = 64 launches of F^64
        // jump exact = one launch of J2
        // ----------------------------------------------------
        public const int REAL_TASK_REPEAT = 4_096;
        public const int REAL_TASK_BASE64_LEVEL = 2; // J2 = F^4096
        public const int REAL_TASK_SECONDS = 30;
    }

    internal sealed class PathSummary
    {
        public required int PathId { get; init; }
        public required int CycleCount { get; init; }
        public required int MaxCycleLength { get; init; }
        public required float AverageSteps { get; init; }
        public required int DominantBasinSize { get; init; }
        public required int DominantCycleLength { get; init; }
        public required int DominantRepresentative { get; init; }
    }

    internal sealed class EngineBuildResult
    {
        public required int MotifCount { get; init; }
        public required int RawStateCount { get; init; }
        public required int SymmetryCount { get; init; }

        public required int[] CanonicalPackedByMotif { get; init; }
        public required int[] PathWords { get; init; }
        public required int[] GeneratorTransitions { get; init; }

        public required int[] Base64JumpTables { get; init; } // [((path * levels)+level)*motifCount + motif]
        public required long[] Base64LevelApplications { get; init; }

        public required int[] BinaryJumpTables { get; init; } // [((path * levels)+level)*motifCount + motif]
        public required long[] BinaryLevelApplications { get; init; }

        public required int[] PackedAttractorMetadata { get; init; } // [path * motifCount + motif]
        public required PathSummary[] PathSummaries { get; init; }
    }

    internal sealed class RealTaskResult
    {
        public required string Name { get; init; }
        public required int Elements { get; init; }
        public required int ExactRepeat { get; init; }
        public required double Seconds { get; init; }
        public required long BatchesCompleted { get; init; }

        public long TotalElementEvaluations => BatchesCompleted * (long)Elements;
        public long TotalPathApplications => BatchesCompleted * (long)Elements * ExactRepeat;
        public long TotalGeneratorApplications => TotalPathApplications * Config.PATH_LENGTH;

        public double BatchesPerSecond => BatchesCompleted / Seconds;
        public double PathApplicationsPerSecond => TotalPathApplications / Seconds;
        public double GeneratorApplicationsPerSecond => TotalGeneratorApplications / Seconds;
    }

    // ============================================================
    // STANDARD GPU COMPUTE
    // ============================================================

    [ThreadGroupSize(256, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct StandardComputeKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<float> Input;
        public readonly ReadWriteBuffer<float> Output;
        public readonly int Elements;
        public readonly int Iterations;

        public StandardComputeKernel(
        ReadWriteBuffer<float> input,
        ReadWriteBuffer<float> output,
        int elements,
        int iterations)
        {
            Input = input;
            Output = output;
            Elements = elements;
            Iterations = iterations;
        }

        public void Execute()
        {
            int i = ThreadIds.X;
            if (i >= Elements) return;

            float x = Input[i];

            for (int iter = 0; iter < Iterations; iter++)
            {
                x =
                Hlsl.Sin(x * 1.13f) * Hlsl.Cos(x * 0.91f) +
                0.31f * Hlsl.Exp(Hlsl.Abs(x) * 0.12f) +
                0.27f * Hlsl.Log(1.0f + Hlsl.Abs(x) + 0.1f) +
                0.19f * Hlsl.Sqrt(Hlsl.Abs(x) + 0.2f) -
                0.08f * Hlsl.Tan(x * 0.15f);

                x = x * 0.87f + 0.013f;
            }

            Output[i] = x;
        }
    }

    // ============================================================
    // DIRECT MULTI-PATH F^64
    // ============================================================

    [ThreadGroupSize(256, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct DirectMultiPathKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<int> InputMotifs;
        public readonly ReadWriteBuffer<int> InputPathIds;
        public readonly ReadWriteBuffer<int> OutputMotifs;
        public readonly ReadWriteBuffer<int> GeneratorTransitions;
        public readonly ReadWriteBuffer<int> PathWords;

        public readonly int Elements;
        public readonly int MotifCount;
        public readonly int PathLength;
        public readonly int WordRepeats;

        public DirectMultiPathKernel(
        ReadWriteBuffer<int> inputMotifs,
        ReadWriteBuffer<int> inputPathIds,
        ReadWriteBuffer<int> outputMotifs,
        ReadWriteBuffer<int> generatorTransitions,
        ReadWriteBuffer<int> pathWords,
        int elements,
        int motifCount,
        int pathLength,
        int wordRepeats)
        {
            InputMotifs = inputMotifs;
            InputPathIds = inputPathIds;
            OutputMotifs = outputMotifs;
            GeneratorTransitions = generatorTransitions;
            PathWords = pathWords;
            Elements = elements;
            MotifCount = motifCount;
            PathLength = pathLength;
            WordRepeats = wordRepeats;
        }

        public void Execute()
        {
            int i = ThreadIds.X;
            if (i >= Elements) return;

            int motif = InputMotifs[i];
            int pathId = InputPathIds[i];
            int pathOffset = pathId * PathLength;

            for (int r = 0; r < WordRepeats; r++)
            {
                for (int step = 0; step < PathLength; step++)
                {
                    int g = PathWords[pathOffset + step];
                    motif = GeneratorTransitions[g * MotifCount + motif];
                }
            }

            OutputMotifs[i] = motif;
        }
    }

    // ============================================================
    // SINGLE-LEVEL JUMP LOOKUP
    // Works for both base64 and binary hierarchies
    // ============================================================

    [ThreadGroupSize(256, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct MultiPathJumpKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<int> InputMotifs;
        public readonly ReadWriteBuffer<int> InputPathIds;
        public readonly ReadWriteBuffer<int> OutputMotifs;
        public readonly ReadWriteBuffer<int> JumpTables;

        public readonly int Elements;
        public readonly int MotifCount;
        public readonly int LevelCount;
        public readonly int Level;

        public MultiPathJumpKernel(
        ReadWriteBuffer<int> inputMotifs,
        ReadWriteBuffer<int> inputPathIds,
        ReadWriteBuffer<int> outputMotifs,
        ReadWriteBuffer<int> jumpTables,
        int elements,
        int motifCount,
        int levelCount,
        int level)
        {
            InputMotifs = inputMotifs;
            InputPathIds = inputPathIds;
            OutputMotifs = outputMotifs;
            JumpTables = jumpTables;
            Elements = elements;
            MotifCount = motifCount;
            LevelCount = levelCount;
            Level = level;
        }

        public void Execute()
        {
            int i = ThreadIds.X;
            if (i >= Elements) return;

            int motif = InputMotifs[i];
            int pathId = InputPathIds[i];

            int offset = ((pathId * LevelCount) + Level) * MotifCount;
            OutputMotifs[i] = JumpTables[offset + motif];
        }
    }

    // ============================================================
    // FUSED JUMP + PACKED ATTRACTOR LOOKUP
    // Works for both base64 and binary hierarchies
    // ============================================================

    [ThreadGroupSize(256, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct FusedJumpAttractorKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<int> InputMotifs;
        public readonly ReadWriteBuffer<int> InputPathIds;

        public readonly ReadWriteBuffer<int> OutputMotifs;
        public readonly ReadWriteBuffer<int> OutputPackedMeta;

        public readonly ReadWriteBuffer<int> JumpTables;
        public readonly ReadWriteBuffer<int> PackedAttractorMetadata;

        public readonly int Elements;
        public readonly int MotifCount;
        public readonly int LevelCount;
        public readonly int Level;

        public FusedJumpAttractorKernel(
        ReadWriteBuffer<int> inputMotifs,
        ReadWriteBuffer<int> inputPathIds,
        ReadWriteBuffer<int> outputMotifs,
        ReadWriteBuffer<int> outputPackedMeta,
        ReadWriteBuffer<int> jumpTables,
        ReadWriteBuffer<int> packedAttractorMetadata,
        int elements,
        int motifCount,
        int levelCount,
        int level)
        {
            InputMotifs = inputMotifs;
            InputPathIds = inputPathIds;
            OutputMotifs = outputMotifs;
            OutputPackedMeta = outputPackedMeta;
            JumpTables = jumpTables;
            PackedAttractorMetadata = packedAttractorMetadata;
            Elements = elements;
            MotifCount = motifCount;
            LevelCount = levelCount;
            Level = level;
        }

        public void Execute()
        {
            int i = ThreadIds.X;
            if (i >= Elements) return;

            int motif = InputMotifs[i];
            int pathId = InputPathIds[i];

            int jumpOffset = ((pathId * LevelCount) + Level) * MotifCount;
            motif = JumpTables[jumpOffset + motif];

            int metaOffset = pathId * MotifCount;
            int packed = PackedAttractorMetadata[metaOffset + motif];

            OutputMotifs[i] = motif;
            OutputPackedMeta[i] = packed;
        }
    }

    // ============================================================
    // BASE-64 ARBITRARY DECOMPOSITION
    // ============================================================

    [ThreadGroupSize(256, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct Base64DecomposedMultiPathKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<int> InputMotifs;
        public readonly ReadWriteBuffer<int> InputPathIds;
        public readonly ReadWriteBuffer<int> OutputMotifs;
        public readonly ReadWriteBuffer<int> JumpTables;

        public readonly int Elements;
        public readonly int MotifCount;
        public readonly int LevelCount;
        public readonly int RepeatCount;
        public readonly int JumpBase;

        public Base64DecomposedMultiPathKernel(
        ReadWriteBuffer<int> inputMotifs,
        ReadWriteBuffer<int> inputPathIds,
        ReadWriteBuffer<int> outputMotifs,
        ReadWriteBuffer<int> jumpTables,
        int elements,
        int motifCount,
        int levelCount,
        int repeatCount,
        int jumpBase)
        {
            InputMotifs = inputMotifs;
            InputPathIds = inputPathIds;
            OutputMotifs = outputMotifs;
            JumpTables = jumpTables;
            Elements = elements;
            MotifCount = motifCount;
            LevelCount = levelCount;
            RepeatCount = repeatCount;
            JumpBase = jumpBase;
        }

        public void Execute()
        {
            int i = ThreadIds.X;
            if (i >= Elements) return;

            int motif = InputMotifs[i];
            int pathId = InputPathIds[i];

            int baseOffset = pathId * LevelCount * MotifCount;
            int remaining = RepeatCount;

            int d0 = remaining % JumpBase; remaining /= JumpBase;
            int d1 = remaining % JumpBase; remaining /= JumpBase;
            int d2 = remaining % JumpBase; remaining /= JumpBase;
            int d3 = remaining % JumpBase; remaining /= JumpBase;
            int d4 = remaining % JumpBase;

            int o0 = baseOffset + 0 * MotifCount;
            int o1 = baseOffset + 1 * MotifCount;
            int o2 = baseOffset + 2 * MotifCount;
            int o3 = baseOffset + 3 * MotifCount;
            int o4 = baseOffset + 4 * MotifCount;

            for (int k = 0; k < d0; k++) motif = JumpTables[o0 + motif];
            for (int k = 0; k < d1; k++) motif = JumpTables[o1 + motif];
            for (int k = 0; k < d2; k++) motif = JumpTables[o2 + motif];
            for (int k = 0; k < d3; k++) motif = JumpTables[o3 + motif];
            for (int k = 0; k < d4; k++) motif = JumpTables[o4 + motif];

            OutputMotifs[i] = motif;
        }
    }

    // ============================================================
    // BINARY ARBITRARY DECOMPOSITION
    // ============================================================

    [ThreadGroupSize(256, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct BinaryDecomposedMultiPathKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<int> InputMotifs;
        public readonly ReadWriteBuffer<int> InputPathIds;
        public readonly ReadWriteBuffer<int> OutputMotifs;
        public readonly ReadWriteBuffer<int> JumpTables;

        public readonly int Elements;
        public readonly int MotifCount;
        public readonly int LevelCount;
        public readonly uint RepeatCount;

        public BinaryDecomposedMultiPathKernel(
        ReadWriteBuffer<int> inputMotifs,
        ReadWriteBuffer<int> inputPathIds,
        ReadWriteBuffer<int> outputMotifs,
        ReadWriteBuffer<int> jumpTables,
        int elements,
        int motifCount,
        int levelCount,
        uint repeatCount)
        {
            InputMotifs = inputMotifs;
            InputPathIds = inputPathIds;
            OutputMotifs = outputMotifs;
            JumpTables = jumpTables;
            Elements = elements;
            MotifCount = motifCount;
            LevelCount = levelCount;
            RepeatCount = repeatCount;
        }

        public void Execute()
        {
            int i = ThreadIds.X;
            if (i >= Elements) return;

            int motif = InputMotifs[i];
            int pathId = InputPathIds[i];

            int baseOffset = pathId * LevelCount * MotifCount;
            uint mask = RepeatCount;

            for (int level = 0; level < LevelCount; level++)
            {
                if ((mask & (1u << level)) != 0u)
                {
                    motif = JumpTables[baseOffset + level * MotifCount + motif];
                }
            }

            OutputMotifs[i] = motif;
        }
    }


    // ============================================================
    // FENCE
    // ============================================================

    [ThreadGroupSize(1, 1, 1)]
    [GeneratedComputeShaderDescriptor]
    public readonly partial struct FenceKernel : IComputeShader
    {
        public readonly ReadWriteBuffer<int> Fence;
        public FenceKernel(ReadWriteBuffer<int> fence) => Fence = fence;
        public void Execute() => Fence[0] = Fence[0] + 1;
    }

    internal static class Program
    {
        static void Main()
        {
            Console.OutputEncoding = Encoding.UTF8;

            Console.WriteLine("================================================");
            Console.WriteLine("TETRA COMBINATION ENGINE - V3 BINARY + REAL 30s");
            Console.WriteLine("================================================");
            Console.WriteLine();

            var device = GraphicsDevice.GetDefault();

            Console.WriteLine($"GPU:{device.Name}");
            Console.WriteLine($"Elements:{Config.ELEMENTS:N0}");
            Console.WriteLine($"Path count:{Config.PATH_COUNT}");
            Console.WriteLine($"Path length:{Config.PATH_LENGTH}");
            Console.WriteLine($"Base64 levels:{Config.BASE64_LEVELS}");
            Console.WriteLine($"Binary levels:{Config.BINARY_LEVELS}");
            Console.WriteLine();

            Console.WriteLine("Building engine on CPU...");
            var swBuild = Stopwatch.StartNew();
            EngineBuildResult engine = BuildEngine();
            swBuild.Stop();

            Console.WriteLine($"Build time:{swBuild.Elapsed.TotalMilliseconds:F2} ms");
            Console.WriteLine($"Raw states:{engine.RawStateCount:N0}");
            Console.WriteLine($"Canonical motifs:{engine.MotifCount:N0}");
            Console.WriteLine($"Symmetry count:{engine.SymmetryCount}");
            Console.WriteLine($"Generator table:{engine.GeneratorTransitions.Length:N0} ints (~{engine.GeneratorTransitions.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
            Console.WriteLine($"Path word table:{engine.PathWords.Length:N0} ints");
            Console.WriteLine($"Base64 jump tables:{engine.Base64JumpTables.Length:N0} ints (~{engine.Base64JumpTables.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
            Console.WriteLine($"Binary jump tables:{engine.BinaryJumpTables.Length:N0} ints (~{engine.BinaryJumpTables.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
            Console.WriteLine($"Packed attractor metadata:{engine.PackedAttractorMetadata.Length:N0} ints (~{engine.PackedAttractorMetadata.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
            Console.WriteLine();

            PrintBase64LevelStats(engine);
            Console.WriteLine();
            PrintBinaryLevelStats(engine);
            Console.WriteLine();
            PrintPathSummaries(engine);

            Console.WriteLine("\nTesting standard GPU compute...");
            var standardTime = TestStandardCompute(device);
            Console.WriteLine($"Standard time:{standardTime.TotalMilliseconds:F2} ms");

            Console.WriteLine($"\nTesting direct multi-path F^{Config.DIRECT_GPU_WORD_REPEATS:N0} (random pathId)...");
            var directRandomTime = TestDirectMultiPath(device, engine, groupedPaths: false);
            Console.WriteLine($"Direct random time:{directRandomTime.TotalMilliseconds:F2} ms");

            Console.WriteLine($"\nTesting direct multi-path F^{Config.DIRECT_GPU_WORD_REPEATS:N0} (grouped pathId)...");
            var directGroupedTime = TestDirectMultiPath(device, engine, groupedPaths: true);
            Console.WriteLine($"Direct grouped time:{directGroupedTime.TotalMilliseconds:F2} ms");

            Console.WriteLine("\nTesting base64 jump J1 = F^64 (random pathId)...");
            var jump64RandomTime = TestSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, level: 1, groupedPaths: false);
            Console.WriteLine($"Jump J1 random time:{jump64RandomTime.TotalMilliseconds:F2} ms");

            Console.WriteLine("\nTesting base64 fused J1 = F^64 + attractor (random pathId)...");
            var fused64RandomTime = TestFusedSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, level: 1, groupedPaths: false);
            Console.WriteLine($"Fused J1 random time:{fused64RandomTime.TotalMilliseconds:F2} ms");

            Console.WriteLine("\nTesting base64 fused J1 = F^64 + attractor (grouped pathId)...");
            var fused64GroupedTime = TestFusedSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, level: 1, groupedPaths: true);
            Console.WriteLine($"Fused J1 grouped time:{fused64GroupedTime.TotalMilliseconds:F2} ms");

            Console.WriteLine($"\nTesting base64 arbitrary F^{Config.ARBITRARY_REPEAT_COUNT:N0}...");
            var base64ArbitraryTime = TestBase64Arbitrary(device, engine, Config.ARBITRARY_REPEAT_COUNT, groupedPaths: false);
            Console.WriteLine($"Base64 arbitrary time:{base64ArbitraryTime.TotalMilliseconds:F2} ms");

            Console.WriteLine($"\nTesting binary arbitrary F^{Config.ARBITRARY_REPEAT_COUNT:N0}...");
            var binaryArbitraryTime = TestBinaryArbitrary(device, engine, Config.ARBITRARY_REPEAT_COUNT, groupedPaths: false);
            Console.WriteLine($"Binary arbitrary time:{binaryArbitraryTime.TotalMilliseconds:F2} ms");

            int exactBase64Level = TryGetExactLevel(Config.EXACT_DEEP_REPEAT, engine.Base64LevelApplications);
            int exactBinaryLevel = TryGetExactLevel(Config.EXACT_DEEP_REPEAT, engine.BinaryLevelApplications);

            Console.WriteLine($"\nTesting exact deep base64 jump F^{Config.EXACT_DEEP_REPEAT:N0}...");
            var base64ExactTime = TestFusedSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, exactBase64Level, groupedPaths: false);
            Console.WriteLine($"Base64 exact deep time:{base64ExactTime.TotalMilliseconds:F2} ms");

            Console.WriteLine($"\nTesting exact deep binary jump F^{Config.EXACT_DEEP_REPEAT:N0}...");
            var binaryExactTime = TestFusedSingleJump(device, engine, engine.BinaryJumpTables, Config.BINARY_LEVELS, exactBinaryLevel, groupedPaths: false);
            Console.WriteLine($"Binary exact deep time:{binaryExactTime.TotalMilliseconds:F2} ms");

            Console.WriteLine("\n================================================");
            Console.WriteLine("PERFORMANCE COMPARISON");
            Console.WriteLine("================================================");

            double standardMs = standardTime.TotalMilliseconds;
            double directRandomMs = directRandomTime.TotalMilliseconds;
            double directGroupedMs = directGroupedTime.TotalMilliseconds;
            double jump64RandomMs = jump64RandomTime.TotalMilliseconds;
            double fused64RandomMs = fused64RandomTime.TotalMilliseconds;
            double fused64GroupedMs = fused64GroupedTime.TotalMilliseconds;
            double base64ArbMs = base64ArbitraryTime.TotalMilliseconds;
            double binaryArbMs = binaryArbitraryTime.TotalMilliseconds;
            double base64ExactMs = base64ExactTime.TotalMilliseconds;
            double binaryExactMs = binaryExactTime.TotalMilliseconds;

            Console.WriteLine($"Standard compute:{standardMs:F2} ms (baseline)");
            Console.WriteLine($"Direct F^64 random:{directRandomMs:F2} ms ({standardMs / directRandomMs:F2}x vs baseline)");
            Console.WriteLine($"Direct F^64 grouped:{directGroupedMs:F2} ms ({standardMs / directGroupedMs:F2}x vs baseline,{directRandomMs / directGroupedMs:F2}x vs direct-random)");
            Console.WriteLine($"Jump J1 random:{jump64RandomMs:F2} ms ({standardMs / jump64RandomMs:F2}x vs baseline,{directRandomMs / jump64RandomMs:F2}x vs direct-random)");
            Console.WriteLine($"Fused J1 random:{fused64RandomMs:F2} ms ({standardMs / fused64RandomMs:F2}x vs baseline,{directRandomMs / fused64RandomMs:F2}x vs direct-random)");
            Console.WriteLine($"Fused J1 grouped:{fused64GroupedMs:F2} ms ({standardMs / fused64GroupedMs:F2}x vs baseline,{fused64RandomMs / fused64GroupedMs:F2}x vs fused-random)");
            Console.WriteLine($"Base64 arbitrary:{base64ArbMs:F2} ms");
            Console.WriteLine($"Binary arbitrary:{binaryArbMs:F2} ms ({base64ArbMs / binaryArbMs:F2}x vs base64-arbitrary)");
            Console.WriteLine($"Base64 exact deep:{base64ExactMs:F2} ms");
            Console.WriteLine($"Binary exact deep:{binaryExactMs:F2} ms");

            Console.WriteLine("\n================================================");
            Console.WriteLine("LOGICAL COMPRESSION");
            Console.WriteLine("================================================");

            double equivalentVsDirect64 = (double)Config.EXACT_DEEP_REPEAT / Config.DIRECT_GPU_WORD_REPEATS;
            double estimatedDirectDeepMs = directRandomMs * equivalentVsDirect64;

            Console.WriteLine($"Exact deep target:F^{Config.EXACT_DEEP_REPEAT:N0}");
            Console.WriteLine($"Equivalent path applications vs direct F^64:{equivalentVsDirect64:N0}x");
            Console.WriteLine($"Equivalent generator applications per element:{(long)Config.EXACT_DEEP_REPEAT * Config.PATH_LENGTH:N0}");
            Console.WriteLine($"Estimated direct time for same depth (linear extrapolation):{estimatedDirectDeepMs / 1000.0:F2} s");
            Console.WriteLine($"Estimated speedup of binary exact deep vs extrapolated direct:{estimatedDirectDeepMs / binaryExactMs:F2}x");

            Console.WriteLine("\n================================================");
            Console.WriteLine("REAL 30-SECOND EXACT TASK");
            Console.WriteLine("================================================");
            Console.WriteLine($"Exact semantic task:F^{Config.REAL_TASK_REPEAT:N0}");
            Console.WriteLine("Direct exact implementation:64 sequential launches of F^64");
            Console.WriteLine("Jump exact implementation:one J2 = F^4096 lookup");
            Console.WriteLine($"Duration per mode:{Config.REAL_TASK_SECONDS} seconds");
            Console.WriteLine();

            var realDirect = RunRealThirtySecondDirectExactTask(device, engine, groupedPaths: false);
            var realJump = RunRealThirtySecondJumpExactTask(device, engine, groupedPaths: false);

            PrintRealTaskResult(realDirect);
            PrintRealTaskResult(realJump);

            Console.WriteLine();
            Console.WriteLine($"Measured exact throughput speedup (path applications/sec):{realJump.PathApplicationsPerSecond / realDirect.PathApplicationsPerSecond:F2}x");
            Console.WriteLine($"Measured exact throughput speedup (generator applications/sec):{realJump.GeneratorApplicationsPerSecond / realDirect.GeneratorApplicationsPerSecond:F2}x");
            Console.WriteLine($"Measured exact batch speedup:{realJump.BatchesPerSecond / realDirect.BatchesPerSecond:F2}x");

            Console.WriteLine("\n================================================");
            Console.WriteLine("CORRECTNESS CHECK");
            Console.WriteLine("================================================");

            CheckCorrectness(device, engine);

            Console.WriteLine("\nDone. Press any key...");
            Console.ReadKey();
        }

        // ============================================================
        // BUILD ENGINE
        // ============================================================

        static EngineBuildResult BuildEngine()
        {
            var symmetryGroup = BuildA4SymmetryGroup();

            var canonicalToId = new Dictionary<int, int>(capacity: 32768);
            var motifPackedList = new List<int>(capacity: 32768);

            int rawCount = 0;

            for (int missing = 0; missing < 4; missing++)
            {
                for (int a = 0; a < Config.LOCAL_STATE_COUNT; a++)
                {
                    for (int b = 0; b < Config.LOCAL_STATE_COUNT; b++)
                    {
                        for (int c = 0; c < Config.LOCAL_STATE_COUNT; c++)
                        {
                            rawCount++;

                            int[] slots = new int[4];
                            for (int i = 0; i < 4; i++) slots[i] = Config.EMPTY;

                            int idx = 0;
                            for (int s = 0; s < 4; s++)
                            {
                                if (s == missing) continue;

                                slots[s] = idx switch
                                {
                                    0 => a,
                                    1 => b,
                                    _ => c
                                };
                                idx++;
                            }

                            int packed = PackSlots(slots[0], slots[1], slots[2], slots[3]);
                            int canonical = Canonicalize(packed, symmetryGroup);

                            if (!canonicalToId.ContainsKey(canonical))
                            {
                                canonicalToId[canonical] = motifPackedList.Count;
                                motifPackedList.Add(canonical);
                            }
                        }
                    }
                }
            }

            int motifCount = motifPackedList.Count;

            int[] pathWords = new int[Config.PATH_COUNT * Config.PATH_LENGTH];
            for (int p = 0; p < Config.PATH_COUNT; p++)
            {
                for (int k = 0; k < Config.PATH_LENGTH; k++)
                {
                    pathWords[p * Config.PATH_LENGTH + k] = Config.PATHS[p][k];
                }
            }

            int[] generatorTransitions = new int[4 * motifCount];

            for (int motif = 0; motif < motifCount; motif++)
            {
                int packed = motifPackedList[motif];

                for (int g = 0; g < 4; g++)
                {
                    int nextRaw = ApplyGenerator(packed, g);
                    int nextCanonical = Canonicalize(nextRaw, symmetryGroup);
                    int nextMotif = canonicalToId[nextCanonical];

                    generatorTransitions[g * motifCount + motif] = nextMotif;
                }
            }

            // -------------------------
            // Base64 jump tables
            // -------------------------
            int[] base64JumpTables = new int[Config.PATH_COUNT * Config.BASE64_LEVELS * motifCount];
            long[] base64LevelApps = new long[Config.BASE64_LEVELS];
            base64LevelApps[0] = 1;
            for (int level = 1; level < Config.BASE64_LEVELS; level++)
                base64LevelApps[level] = base64LevelApps[level - 1] * Config.BASE64_JUMP_BASE;

            for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
            {
                int offset0 = GetTableOffset(pathId, 0, motifCount, Config.BASE64_LEVELS);

                for (int motif = 0; motif < motifCount; motif++)
                {
                    int current = motif;
                    for (int step = 0; step < Config.PATH_LENGTH; step++)
                    {
                        int g = pathWords[pathId * Config.PATH_LENGTH + step];
                        current = generatorTransitions[g * motifCount + current];
                    }

                    base64JumpTables[offset0 + motif] = current;
                }
            }

            for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
            {
                for (int level = 1; level < Config.BASE64_LEVELS; level++)
                {
                    int prevOffset = GetTableOffset(pathId, level - 1, motifCount, Config.BASE64_LEVELS);
                    int curOffset = GetTableOffset(pathId, level, motifCount, Config.BASE64_LEVELS);

                    for (int motif = 0; motif < motifCount; motif++)
                    {
                        int current = motif;
                        for (int r = 0; r < Config.BASE64_JUMP_BASE; r++)
                        {
                            current = base64JumpTables[prevOffset + current];
                        }

                        base64JumpTables[curOffset + motif] = current;
                    }
                }
            }

            // -------------------------
            // Binary jump tables
            // -------------------------
            int[] binaryJumpTables = new int[Config.PATH_COUNT * Config.BINARY_LEVELS * motifCount];
            long[] binaryLevelApps = new long[Config.BINARY_LEVELS];
            binaryLevelApps[0] = 1;
            for (int level = 1; level < Config.BINARY_LEVELS; level++)
                binaryLevelApps[level] = binaryLevelApps[level - 1] * 2;

            for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
            {
                int base64Offset0 = GetTableOffset(pathId, 0, motifCount, Config.BASE64_LEVELS);
                int binaryOffset0 = GetTableOffset(pathId, 0, motifCount, Config.BINARY_LEVELS);
                Array.Copy(base64JumpTables, base64Offset0, binaryJumpTables, binaryOffset0, motifCount);
            }

            for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
            {
                for (int level = 1; level < Config.BINARY_LEVELS; level++)
                {
                    int prevOffset = GetTableOffset(pathId, level - 1, motifCount, Config.BINARY_LEVELS);
                    int curOffset = GetTableOffset(pathId, level, motifCount, Config.BINARY_LEVELS);

                    for (int motif = 0; motif < motifCount; motif++)
                    {
                        int current = binaryJumpTables[prevOffset + motif];
                        current = binaryJumpTables[prevOffset + current];
                        binaryJumpTables[curOffset + motif] = current;
                    }
                }
            }

            // -------------------------
            // Attractor metadata from F^1
            // -------------------------
            int[] packedAttractorMetadata = new int[Config.PATH_COUNT * motifCount];
            PathSummary[] pathSummaries = new PathSummary[Config.PATH_COUNT];

            for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
            {
                int offset0 = GetTableOffset(pathId, 0, motifCount, Config.BASE64_LEVELS);

                int[] next = new int[motifCount];
                Array.Copy(base64JumpTables, offset0, next, 0, motifCount);

                AnalyzeFunctionalGraph(
                next,
                out int[] cycleId,
                out int[] cycleRepresentative,
                out int[] cycleLength,
                out int[] stepsToCycle);

                int maxRep = cycleRepresentative.Max();
                int maxLen = cycleLength.Max();
                int maxSteps = stepsToCycle.Max();

                if (maxRep > Config.REP_MASK)
                    throw new InvalidOperationException($"Representative {maxRep} exceeds packed REP range.");

                if (maxLen > Config.LEN_MASK)
                    throw new InvalidOperationException($"Cycle length {maxLen} exceeds packed LEN range.");

                if (maxSteps > Config.STEP_MASK)
                    throw new InvalidOperationException($"Steps-to-cycle {maxSteps} exceeds packed STEP range.");

                int metaOffset = pathId * motifCount;
                for (int motif = 0; motif < motifCount; motif++)
                {
                    packedAttractorMetadata[metaOffset + motif] =
                    PackMeta(cycleRepresentative[motif], cycleLength[motif], stepsToCycle[motif]);
                }

                int cycleCount = cycleId.Distinct().Count();
                int maxCycleLength = cycleLength.Max();
                float averageSteps = (float)stepsToCycle.Average();

                var dominant = cycleId
                .GroupBy(x => x)
                .Select(g =>
                {
                    int firstIndex = Array.IndexOf(cycleId, g.Key);
                    return new
                    {
                        BasinSize = g.Count(),
                        CycleLength = cycleLength[firstIndex],
                        Representative = cycleRepresentative[firstIndex]
                    };
                })
                .OrderByDescending(x => x.BasinSize)
                .First();

                pathSummaries[pathId] = new PathSummary
                {
                    PathId = pathId,
                    CycleCount = cycleCount,
                    MaxCycleLength = maxCycleLength,
                    AverageSteps = averageSteps,
                    DominantBasinSize = dominant.BasinSize,
                    DominantCycleLength = dominant.CycleLength,
                    DominantRepresentative = dominant.Representative
                };
            }

            return new EngineBuildResult
            {
                MotifCount = motifCount,
                RawStateCount = rawCount,
                SymmetryCount = symmetryGroup.Count,

                CanonicalPackedByMotif = motifPackedList.ToArray(),
                PathWords = pathWords,
                GeneratorTransitions = generatorTransitions,

                Base64JumpTables = base64JumpTables,
                Base64LevelApplications = base64LevelApps,

                BinaryJumpTables = binaryJumpTables,
                BinaryLevelApplications = binaryLevelApps,

                PackedAttractorMetadata = packedAttractorMetadata,
                PathSummaries = pathSummaries
            };
        }

        // ============================================================
        // BENCHMARKS
        // ============================================================

        static TimeSpan TestStandardCompute(GraphicsDevice device)
        {
            int size = Config.ELEMENTS;

            using var input = device.AllocateReadWriteBuffer<float>(size);
            using var output = device.AllocateReadWriteBuffer<float>(size);

            FillRandomFloatData(device, input, size, 12345);

            device.For(size, new StandardComputeKernel(input, output, size, 8));
            Synchronize(device);

            var sw = Stopwatch.StartNew();
            device.For(size, new StandardComputeKernel(input, output, size, Config.STANDARD_ITERATIONS));
            Synchronize(device);
            sw.Stop();

            return sw.Elapsed;
        }

        static TimeSpan TestDirectMultiPath(GraphicsDevice device, EngineBuildResult engine, bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
            using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);

            using var transitions = device.AllocateReadWriteBuffer<int>(engine.GeneratorTransitions.Length);
            using var pathWords = device.AllocateReadWriteBuffer<int>(engine.PathWords.Length);

            transitions.CopyFrom(engine.GeneratorTransitions);
            pathWords.CopyFrom(engine.PathWords);

            FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
            if (groupedPaths)
                FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);

            device.For(size, new DirectMultiPathKernel(
            inputMotifs, inputPathIds, outputMotifs,
            transitions, pathWords,
            size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
            Synchronize(device);

            var sw = Stopwatch.StartNew();
            device.For(size, new DirectMultiPathKernel(
            inputMotifs, inputPathIds, outputMotifs,
            transitions, pathWords,
            size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
            Synchronize(device);
            sw.Stop();

            return sw.Elapsed;
        }

        static TimeSpan TestSingleJump(
        GraphicsDevice device,
        EngineBuildResult engine,
        int[] jumpTables,
        int levelCount,
        int level,
        bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
            using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var jumps = device.AllocateReadWriteBuffer<int>(jumpTables.Length);

            jumps.CopyFrom(jumpTables);
            FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);

            if (groupedPaths)
                FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);

            device.For(size, new MultiPathJumpKernel(
            inputMotifs, inputPathIds, outputMotifs,
            jumps, size, engine.MotifCount, levelCount, level));
            Synchronize(device);

            var sw = Stopwatch.StartNew();
            device.For(size, new MultiPathJumpKernel(
            inputMotifs, inputPathIds, outputMotifs,
            jumps, size, engine.MotifCount, levelCount, level));
            Synchronize(device);
            sw.Stop();

            return sw.Elapsed;
        }

        static TimeSpan TestFusedSingleJump(
        GraphicsDevice device,
        EngineBuildResult engine,
        int[] jumpTables,
        int levelCount,
        int level,
        bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
            using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var outputMeta = device.AllocateReadWriteBuffer<int>(size);

            using var jumps = device.AllocateReadWriteBuffer<int>(jumpTables.Length);
            using var meta = device.AllocateReadWriteBuffer<int>(engine.PackedAttractorMetadata.Length);

            jumps.CopyFrom(jumpTables);
            meta.CopyFrom(engine.PackedAttractorMetadata);

            FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
            if (groupedPaths)
                FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);

            device.For(size, new FusedJumpAttractorKernel(
            inputMotifs, inputPathIds, outputMotifs, outputMeta,
            jumps, meta,
            size, engine.MotifCount, levelCount, level));
            Synchronize(device);

            var sw = Stopwatch.StartNew();
            device.For(size, new FusedJumpAttractorKernel(
            inputMotifs, inputPathIds, outputMotifs, outputMeta,
            jumps, meta,
            size, engine.MotifCount, levelCount, level));
            Synchronize(device);
            sw.Stop();

            return sw.Elapsed;
        }

        static TimeSpan TestBase64Arbitrary(GraphicsDevice device, EngineBuildResult engine, int repeatCount, bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
            using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var jumps = device.AllocateReadWriteBuffer<int>(engine.Base64JumpTables.Length);

            jumps.CopyFrom(engine.Base64JumpTables);
            FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);

            if (groupedPaths)
                FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);

            device.For(size, new Base64DecomposedMultiPathKernel(
            inputMotifs, inputPathIds, outputMotifs, jumps,
            size, engine.MotifCount, Config.BASE64_LEVELS, repeatCount, Config.BASE64_JUMP_BASE));
            Synchronize(device);

            var sw = Stopwatch.StartNew();
            device.For(size, new Base64DecomposedMultiPathKernel(
            inputMotifs, inputPathIds, outputMotifs, jumps,
            size, engine.MotifCount, Config.BASE64_LEVELS, repeatCount, Config.BASE64_JUMP_BASE));
            Synchronize(device);
            sw.Stop();

            return sw.Elapsed;
        }

        static TimeSpan TestBinaryArbitrary(GraphicsDevice device, EngineBuildResult engine, int repeatCount, bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
            using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var jumps = device.AllocateReadWriteBuffer<int>(engine.BinaryJumpTables.Length);

            jumps.CopyFrom(engine.BinaryJumpTables);
            FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);

            if (groupedPaths)
                FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);

            device.For(size, new BinaryDecomposedMultiPathKernel(
            inputMotifs, inputPathIds, outputMotifs, jumps,
            size, engine.MotifCount, Config.BINARY_LEVELS, (uint)repeatCount));
            Synchronize(device);

            var sw = Stopwatch.StartNew();
            device.For(size, new BinaryDecomposedMultiPathKernel(
            inputMotifs, inputPathIds, outputMotifs, jumps,
            size, engine.MotifCount, Config.BINARY_LEVELS, (uint)repeatCount));
            Synchronize(device);
            sw.Stop();

            return sw.Elapsed;
        }

        // ============================================================
        // REAL 30-SECOND EXACT TASK
        // ============================================================

        static RealTaskResult RunRealThirtySecondDirectExactTask(GraphicsDevice device, EngineBuildResult engine, bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var motifsA = device.AllocateReadWriteBuffer<int>(size);
            using var motifsB = device.AllocateReadWriteBuffer<int>(size);
            using var pathIds = device.AllocateReadWriteBuffer<int>(size);

            using var transitions = device.AllocateReadWriteBuffer<int>(engine.GeneratorTransitions.Length);
            using var pathWords = device.AllocateReadWriteBuffer<int>(engine.PathWords.Length);

            transitions.CopyFrom(engine.GeneratorTransitions);
            pathWords.CopyFrom(engine.PathWords);

            FillRandomMotifIds(device, motifsA, size, engine.MotifCount, 10101);
            if (groupedPaths)
                FillGroupedPathIds(device, pathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, pathIds, size, Config.PATH_COUNT, 20202);

            // warmup
            device.For(size, new DirectMultiPathKernel(
            motifsA, pathIds, motifsB,
            transitions, pathWords,
            size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
            Synchronize(device);

            bool aToB = true;
            long batches = 0;
            var sw = Stopwatch.StartNew();

            while (sw.Elapsed.TotalSeconds < Config.REAL_TASK_SECONDS)
            {
                // exact F^4096 = 64 dispatches of F^64
                for (int i = 0; i < 64; i++)
                {
                    if (aToB)
                    {
                        device.For(size, new DirectMultiPathKernel(
                        motifsA, pathIds, motifsB,
                        transitions, pathWords,
                        size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
                    }
                    else
                    {
                        device.For(size, new DirectMultiPathKernel(
                        motifsB, pathIds, motifsA,
                        transitions, pathWords,
                        size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
                    }

                    aToB = !aToB;
                }

                Synchronize(device);
                batches++;
            }

            sw.Stop();

            return new RealTaskResult
            {
                Name = "DIRECT EXACT F^4096",
                Elements = size,
                ExactRepeat = Config.REAL_TASK_REPEAT,
                Seconds = sw.Elapsed.TotalSeconds,
                BatchesCompleted = batches
            };
        }

        static RealTaskResult RunRealThirtySecondJumpExactTask(GraphicsDevice device, EngineBuildResult engine, bool groupedPaths)
        {
            int size = Config.ELEMENTS;

            using var motifsA = device.AllocateReadWriteBuffer<int>(size);
            using var motifsB = device.AllocateReadWriteBuffer<int>(size);
            using var pathIds = device.AllocateReadWriteBuffer<int>(size);
            using var metaOut = device.AllocateReadWriteBuffer<int>(size);

            using var jumps = device.AllocateReadWriteBuffer<int>(engine.Base64JumpTables.Length);
            using var meta = device.AllocateReadWriteBuffer<int>(engine.PackedAttractorMetadata.Length);

            jumps.CopyFrom(engine.Base64JumpTables);
            meta.CopyFrom(engine.PackedAttractorMetadata);

            FillRandomMotifIds(device, motifsA, size, engine.MotifCount, 10101);
            if (groupedPaths)
                FillGroupedPathIds(device, pathIds, size, Config.PATH_COUNT);
            else
                FillRandomPathIds(device, pathIds, size, Config.PATH_COUNT, 20202);

            // warmup
            device.For(size, new FusedJumpAttractorKernel(
            motifsA, pathIds, motifsB, metaOut,
            jumps, meta,
            size, engine.MotifCount, Config.BASE64_LEVELS, Config.REAL_TASK_BASE64_LEVEL));
            Synchronize(device);

            bool aToB = true;
            long batches = 0;
            var sw = Stopwatch.StartNew();

            while (sw.Elapsed.TotalSeconds < Config.REAL_TASK_SECONDS)
            {
                if (aToB)
                {
                    device.For(size, new FusedJumpAttractorKernel(
                    motifsA, pathIds, motifsB, metaOut,
                    jumps, meta,
                    size, engine.MotifCount, Config.BASE64_LEVELS, Config.REAL_TASK_BASE64_LEVEL));
                }
                else
                {
                    device.For(size, new FusedJumpAttractorKernel(
                    motifsB, pathIds, motifsA, metaOut,
                    jumps, meta,
                    size, engine.MotifCount, Config.BASE64_LEVELS, Config.REAL_TASK_BASE64_LEVEL));
                }

                aToB = !aToB;

                Synchronize(device);
                batches++;
            }

            sw.Stop();

            return new RealTaskResult
            {
                Name = "JUMP EXACT F^4096",
                Elements = size,
                ExactRepeat = Config.REAL_TASK_REPEAT,
                Seconds = sw.Elapsed.TotalSeconds,
                BatchesCompleted = batches
            };
        }

        // ============================================================
        // CORRECTNESS
        // ============================================================

        static void CheckCorrectness(GraphicsDevice device, EngineBuildResult engine)
        {
            int size = Config.CORRECTNESS_TEST_SIZE;

            using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
            using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);

            using var directOut = device.AllocateReadWriteBuffer<int>(size);
            using var jump64Out = device.AllocateReadWriteBuffer<int>(size);
            using var fused64Out = device.AllocateReadWriteBuffer<int>(size);
            using var fused64Meta = device.AllocateReadWriteBuffer<int>(size);

            using var base64ArbOut = device.AllocateReadWriteBuffer<int>(size);
            using var binaryArbOut = device.AllocateReadWriteBuffer<int>(size);

            using var transitions = device.AllocateReadWriteBuffer<int>(engine.GeneratorTransitions.Length);
            using var pathWords = device.AllocateReadWriteBuffer<int>(engine.PathWords.Length);
            using var base64Jumps = device.AllocateReadWriteBuffer<int>(engine.Base64JumpTables.Length);
            using var binaryJumps = device.AllocateReadWriteBuffer<int>(engine.BinaryJumpTables.Length);
            using var meta = device.AllocateReadWriteBuffer<int>(engine.PackedAttractorMetadata.Length);

            transitions.CopyFrom(engine.GeneratorTransitions);
            pathWords.CopyFrom(engine.PathWords);
            base64Jumps.CopyFrom(engine.Base64JumpTables);
            binaryJumps.CopyFrom(engine.BinaryJumpTables);
            meta.CopyFrom(engine.PackedAttractorMetadata);

            FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 777);
            FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 888);

            device.For(size, new DirectMultiPathKernel(
            inputMotifs, inputPathIds, directOut,
            transitions, pathWords,
            size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));

            device.For(size, new MultiPathJumpKernel(
            inputMotifs, inputPathIds, jump64Out,
            base64Jumps,
            size, engine.MotifCount, Config.BASE64_LEVELS, 1));

            device.For(size, new FusedJumpAttractorKernel(
            inputMotifs, inputPathIds, fused64Out, fused64Meta,
            base64Jumps, meta,
            size, engine.MotifCount, Config.BASE64_LEVELS, 1));

            device.For(size, new Base64DecomposedMultiPathKernel(
            inputMotifs, inputPathIds, base64ArbOut, base64Jumps,
            size, engine.MotifCount, Config.BASE64_LEVELS, Config.ARBITRARY_REPEAT_COUNT, Config.BASE64_JUMP_BASE));

            device.For(size, new BinaryDecomposedMultiPathKernel(
            inputMotifs, inputPathIds, binaryArbOut, binaryJumps,
            size, engine.MotifCount, Config.BINARY_LEVELS, Config.ARBITRARY_REPEAT_COUNT));

            Synchronize(device);

            int[] direct = new int[size];
            int[] jump64 = new int[size];
            int[] fused = new int[size];
            int[] metaOut = new int[size];
            int[] base64Arb = new int[size];
            int[] binaryArb = new int[size];
            int[] pathIds = new int[size];

            directOut.CopyTo(direct, 0, 0, size);
            jump64Out.CopyTo(jump64, 0, 0, size);
            fused64Out.CopyTo(fused, 0, 0, size);
            fused64Meta.CopyTo(metaOut, 0, 0, size);
            base64ArbOut.CopyTo(base64Arb, 0, 0, size);
            binaryArbOut.CopyTo(binaryArb, 0, 0, size);
            inputPathIds.CopyTo(pathIds, 0, 0, size);

            int directVsJump = 0;
            int jumpVsFused = 0;
            int metaMatches = 0;
            int base64VsBinaryArb = 0;

            for (int i = 0; i < size; i++)
            {
                if (direct[i] == jump64[i]) directVsJump++;
                if (jump64[i] == fused[i]) jumpVsFused++;

                int expectedMeta = engine.PackedAttractorMetadata[pathIds[i] * engine.MotifCount + fused[i]];
                if (expectedMeta == metaOut[i]) metaMatches++;

                if (base64Arb[i] == binaryArb[i]) base64VsBinaryArb++;
            }

            Console.WriteLine($"Direct F^64 vs jump J1 match rate:{directVsJump * 100.0 / size:F2}%");
            Console.WriteLine($"Jump J1 vs fused J1 motif match rate:{jumpVsFused * 100.0 / size:F2}%");
            Console.WriteLine($"Fused packed metadata match rate:{metaMatches * 100.0 / size:F2}%");
            Console.WriteLine($"Base64 arbitrary vs binary arbitrary match rate:{base64VsBinaryArb * 100.0 / size:F2}%");

            // Exact deep equality:base64 J4 vs binary B24
            {
                int sanity = 2000;
                int ok = 0;
                var rand = new Random(999);

                for (int i = 0; i < sanity; i++)
                {
                    int pathId = rand.Next(Config.PATH_COUNT);
                    int motif = rand.Next(engine.MotifCount);

                    int j4 = engine.Base64JumpTables[GetTableOffset(pathId, 4, engine.MotifCount, Config.BASE64_LEVELS) + motif];
                    int b24 = engine.BinaryJumpTables[GetTableOffset(pathId, 24, engine.MotifCount, Config.BINARY_LEVELS) + motif];

                    if (j4 == b24) ok++;
                }

                Console.WriteLine($"Base64 J4 vs binary B24 sanity:{ok * 100.0 / sanity:F2}%");
            }

            // Exact F^4096 equality:
            // direct exact by 64x J1 vs one J2
            {
                int sanity = 2000;
                int ok = 0;
                var rand = new Random(1234);

                for (int i = 0; i < sanity; i++)
                {
                    int pathId = rand.Next(Config.PATH_COUNT);
                    int motif = rand.Next(engine.MotifCount);

                    int current = motif;
                    int j1Offset = GetTableOffset(pathId, 1, engine.MotifCount, Config.BASE64_LEVELS);
                    for (int k = 0; k < 64; k++)
                        current = engine.Base64JumpTables[j1Offset + current];

                    int j2 = engine.Base64JumpTables[GetTableOffset(pathId, 2, engine.MotifCount, Config.BASE64_LEVELS) + motif];

                    if (current == j2) ok++;
                }

                Console.WriteLine($"Exact F^4096 sanity (64x J1 == J2):{ok * 100.0 / sanity:F2}%");
            }

            Console.WriteLine("Sample decoded fused metadata:");
            for (int i = 0; i < 5; i++)
            {
                int rep = UnpackRep(metaOut[i]);
                int len = UnpackLen(metaOut[i]);
                int steps = UnpackSteps(metaOut[i]);

                Console.WriteLine(
                $" sample {i}:pathId={pathIds[i]},finalMotif={fused[i]},rep={rep},cycleLen={len},stepsToCycle={steps}");
            }
        }

        // ============================================================
        // REPORTING
        // ============================================================

        static void PrintBase64LevelStats(EngineBuildResult engine)
        {
            Console.WriteLine("BASE64 JUMP HIERARCHY");
            Console.WriteLine("------------------------------------------------");

            for (int level = 0; level < engine.Base64LevelApplications.Length; level++)
                Console.WriteLine($" J{level}:F^{engine.Base64LevelApplications[level]:N0}");
        }

        static void PrintBinaryLevelStats(EngineBuildResult engine)
        {
            Console.WriteLine("BINARY JUMP HIERARCHY");
            Console.WriteLine("------------------------------------------------");

            for (int level = 0; level < engine.BinaryLevelApplications.Length; level += 4)
            {
                var parts = new List<string>();

                for (int k = 0; k < 4 && level + k < engine.BinaryLevelApplications.Length; k++)
                {
                    int l = level + k;
                    parts.Add($"B{l}:F^{engine.BinaryLevelApplications[l]:N0}");
                }

                Console.WriteLine(" " + string.Join(" | ", parts));
            }
        }

        static void PrintPathSummaries(EngineBuildResult engine)
        {
            Console.WriteLine("PATH ATTRACTOR SUMMARIES");
            Console.WriteLine("------------------------------------------------");

            for (int p = 0; p < engine.PathSummaries.Length; p++)
            {
                var s = engine.PathSummaries[p];
                Console.WriteLine(
                $" pathId={p} [{Config.PATH_NAMES[p]}] | cycles={s.CycleCount},maxCycle={s.MaxCycleLength},avgSteps={s.AverageSteps:F2},dominantBasin={s.DominantBasinSize:N0} ({s.DominantBasinSize * 100.0 / engine.MotifCount:F2}%),dominantLen={s.DominantCycleLength},rep={s.DominantRepresentative}");
            }
        }

        static void PrintRealTaskResult(RealTaskResult result)
        {
            Console.WriteLine(result.Name);
            Console.WriteLine($" Time:{result.Seconds:F2} s");
            Console.WriteLine($" Batches completed:{result.BatchesCompleted:N0}");
            Console.WriteLine($" Exact depth per batch:F^{result.ExactRepeat:N0}");
            Console.WriteLine($" Batches/sec:{result.BatchesPerSecond:N4}");
            Console.WriteLine($" Path applications/sec:{result.PathApplicationsPerSecond:N0}");
            Console.WriteLine($" Generator applications/sec:{result.GeneratorApplicationsPerSecond:N0}");
        }

        // ============================================================
        // AUTO / LEVEL HELPERS
        // ============================================================

        static int TryGetExactLevel(int repeatCount, long[] levelApplications)
        {
            for (int level = 0; level < levelApplications.Length; level++)
            {
                if (levelApplications[level] == repeatCount)
                    return level;
            }

            return -1;
        }

        // ============================================================
        // PACKED META
        // ============================================================

        static int PackMeta(int representative, int cycleLength, int stepsToCycle)
        {
            return
            (representative & Config.REP_MASK) |
            ((cycleLength & Config.LEN_MASK) << Config.LEN_SHIFT) |
            ((stepsToCycle & Config.STEP_MASK) << Config.STEP_SHIFT);
        }

        static int UnpackRep(int packed) => packed & Config.REP_MASK;
        static int UnpackLen(int packed) => (packed >> Config.LEN_SHIFT) & Config.LEN_MASK;
        static int UnpackSteps(int packed) => (packed >> Config.STEP_SHIFT) & Config.STEP_MASK;

        // ============================================================
        // GEOMETRIC / COMBINATORIAL CORE
        // ============================================================

        static int ApplyGenerator(int packed, int generator)
        {
            int[] slots = UnpackSlots(packed);

            int[] perm = GetGeneratorPermutation(generator);
            int[] permuted = ApplyPermutation(slots, perm);

            int missing = FindEmpty(permuted);
            int movedTarget;

            if (missing != generator)
            {
                permuted[missing] = TransformMove(permuted[generator], generator, generator, missing);
                permuted[generator] = Config.EMPTY;
                movedTarget = missing;
            }
            else
            {
                int donor = FindFirstOccupied(permuted, generator);
                permuted[generator] = TransformMove(permuted[donor], generator, donor, generator);
                permuted[donor] = Config.EMPTY;
                movedTarget = generator;
            }

            for (int s = 0; s < 4; s++)
            {
                if (permuted[s] == Config.EMPTY) continue;
                if (s == movedTarget) continue;

                permuted[s] = TransformStay(permuted[s], generator, s);
            }

            return PackSlots(permuted[0], permuted[1], permuted[2], permuted[3]);
        }

        static int TransformMove(int localState, int generator, int sourceSlot, int targetSlot)
        {
            if (localState == Config.EMPTY) return Config.EMPTY;

            int orientation = localState % Config.ORIENTATION_COUNT;
            int phase = localState / Config.ORIENTATION_COUNT;

            orientation = (orientation + 1 + 3 * generator + 2 * sourceSlot + targetSlot) % Config.ORIENTATION_COUNT;
            phase = (phase + 1 + generator + sourceSlot + 2 * targetSlot) % Config.PHASE_COUNT;

            return phase * Config.ORIENTATION_COUNT + orientation;
        }

        static int TransformStay(int localState, int generator, int slot)
        {
            if (localState == Config.EMPTY) return Config.EMPTY;

            int orientation = localState % Config.ORIENTATION_COUNT;
            int phase = localState / Config.ORIENTATION_COUNT;

            orientation = (orientation + GeneratorOrientationDelta(generator, slot)) % Config.ORIENTATION_COUNT;
            phase = (phase + GeneratorPhaseDelta(generator, slot) + ((orientation & 3) == 0 ? 1 : 0)) % Config.PHASE_COUNT;

            return phase * Config.ORIENTATION_COUNT + orientation;
        }

        static int GeneratorOrientationDelta(int generator, int slot)
        {
            return generator switch
            {
                0 => slot + 1,
                1 => 2 * slot + 3,
                2 => 3 * slot + 2,
                _ => 5 + slot
            };
        }

        static int GeneratorPhaseDelta(int generator, int slot)
        {
            return (generator + 2 * slot + 1) % Config.PHASE_COUNT;
        }

        static int[] GetGeneratorPermutation(int generator)
        {
            return generator switch
            {
                0 => new[] { 0, 2, 3, 1 },
                1 => new[] { 3, 1, 0, 2 },
                2 => new[] { 1, 3, 2, 0 },
                _ => new[] { 2, 0, 1, 3 }
            };
        }

        static int Canonicalize(int packed, List<int[]> symmetryGroup)
        {
            int best = int.MaxValue;

            foreach (var perm in symmetryGroup)
            {
                int transformed = ApplyPackedPermutation(packed, perm);
                if (transformed < best) best = transformed;
            }

            return best;
        }

        static List<int[]> BuildA4SymmetryGroup()
        {
            int[] a = { 0, 2, 3, 1 };
            int[] b = { 1, 0, 3, 2 };

            var result = new List<int[]>();
            var seen = new HashSet<int>();
            var queue = new Queue<int[]>();

            int[] identity = { 0, 1, 2, 3 };
            queue.Enqueue(identity);
            seen.Add(PermutationCode(identity));

            while (queue.Count > 0)
            {
                int[] p = queue.Dequeue();
                result.Add((int[])p.Clone());

                int[] pa = ComposePermutations(p, a);
                int[] pb = ComposePermutations(p, b);

                int codeA = PermutationCode(pa);
                int codeB = PermutationCode(pb);

                if (seen.Add(codeA)) queue.Enqueue(pa);
                if (seen.Add(codeB)) queue.Enqueue(pb);
            }

            result.Sort((x, y) => PermutationCode(x).CompareTo(PermutationCode(y)));
            return result;
        }

        static int[] ComposePermutations(int[] first, int[] second)
        {
            int[] result = new int[4];
            for (int old = 0; old < 4; old++)
                result[old] = second[first[old]];
            return result;
        }

        static int PermutationCode(int[] perm)
        {
            return perm[0] + 4 * perm[1] + 16 * perm[2] + 64 * perm[3];
        }

        static int[] ApplyPermutation(int[] slots, int[] oldToNew)
        {
            int[] result = new int[4];
            for (int old = 0; old < 4; old++)
                result[oldToNew[old]] = slots[old];
            return result;
        }

        static int ApplyPackedPermutation(int packed, int[] oldToNew)
        {
            int[] slots = UnpackSlots(packed);
            int[] transformed = ApplyPermutation(slots, oldToNew);
            return PackSlots(transformed[0], transformed[1], transformed[2], transformed[3]);
        }

        static int FindEmpty(int[] slots)
        {
            for (int i = 0; i < 4; i++)
                if (slots[i] == Config.EMPTY)
                    return i;

            throw new InvalidOperationException("No empty slot found.");
        }

        static int FindFirstOccupied(int[] slots, int startSlot)
        {
            for (int k = 1; k <= 3; k++)
            {
                int s = (startSlot + k) & 3;
                if (slots[s] != Config.EMPTY)
                    return s;
            }

            throw new InvalidOperationException("No occupied slot found.");
        }

        static int PackSlots(int s0, int s1, int s2, int s3)
        {
            int p = s3;
            p = p * Config.PACK_BASE + s2;
            p = p * Config.PACK_BASE + s1;
            p = p * Config.PACK_BASE + s0;
            return p;
        }

        static int[] UnpackSlots(int packed)
        {
            int[] slots = new int[4];
            for (int i = 0; i < 4; i++)
            {
                slots[i] = packed % Config.PACK_BASE;
                packed /= Config.PACK_BASE;
            }
            return slots;
        }

        static int GetTableOffset(int pathId, int level, int motifCount, int levelCount)
        {
            return ((pathId * levelCount) + level) * motifCount;
        }

        // ============================================================
        // FUNCTIONAL GRAPH / ATTRACTOR ANALYSIS
        // ============================================================

        static void AnalyzeFunctionalGraph(
        int[] next,
        out int[] cycleId,
        out int[] cycleRepresentative,
        out int[] cycleLength,
        out int[] stepsToCycle)
        {
            int n = next.Length;

            cycleId = new int[n];
            cycleRepresentative = new int[n];
            cycleLength = new int[n];
            stepsToCycle = new int[n];

            Array.Fill(cycleId, -1);

            bool[] done = new bool[n];
            int cycleCounter = 0;

            for (int start = 0; start < n; start++)
            {
                if (done[start]) continue;

                var path = new List<int>(64);
                var pos = new Dictionary<int, int>(64);

                int v = start;

                while (true)
                {
                    if (done[v])
                    {
                        int cid = cycleId[v];
                        int rep = cycleRepresentative[v];
                        int clen = cycleLength[v];
                        int steps = stepsToCycle[v];

                        for (int i = path.Count - 1; i >= 0; i--)
                        {
                            int node = path[i];
                            done[node] = true;
                            cycleId[node] = cid;
                            cycleRepresentative[node] = rep;
                            cycleLength[node] = clen;
                            stepsToCycle[node] = steps + 1;
                            steps++;
                        }

                        break;
                    }

                    if (pos.TryGetValue(v, out int cycleStart))
                    {
                        int cid = cycleCounter++;
                        int rep = path[cycleStart];
                        int clen = path.Count - cycleStart;

                        for (int i = cycleStart; i < path.Count; i++)
                            if (path[i] < rep) rep = path[i];

                        for (int i = cycleStart; i < path.Count; i++)
                        {
                            int node = path[i];
                            done[node] = true;
                            cycleId[node] = cid;
                            cycleRepresentative[node] = rep;
                            cycleLength[node] = clen;
                            stepsToCycle[node] = 0;
                        }

                        int steps = 1;
                        for (int i = cycleStart - 1; i >= 0; i--)
                        {
                            int node = path[i];
                            done[node] = true;
                            cycleId[node] = cid;
                            cycleRepresentative[node] = rep;
                            cycleLength[node] = clen;
                            stepsToCycle[node] = steps;
                            steps++;
                        }

                        break;
                    }

                    pos[v] = path.Count;
                    path.Add(v);
                    v = next[v];
                }
            }
        }

        // ============================================================
        // DATA INIT / SYNC
        // ============================================================

        static void FillRandomFloatData(GraphicsDevice device, ReadWriteBuffer<float> buffer, int size, int seed)
        {
            const int CHUNK = 1_000_000;
            var rand = new Random(seed);
            int offset = 0;

            while (offset < size)
            {
                int count = Math.Min(CHUNK, size - offset);
                float[] tmp = new float[count];

                for (int i = 0; i < count; i++)
                    tmp[i] = (float)(rand.NextDouble() * 2.0 - 1.0);

                buffer.CopyFrom(tmp, 0, offset, count);
                offset += count;
            }
        }

        static void FillRandomMotifIds(GraphicsDevice device, ReadWriteBuffer<int> buffer, int size, int motifCount, int seed)
        {
            const int CHUNK = 1_000_000;
            var rand = new Random(seed);
            int offset = 0;

            while (offset < size)
            {
                int count = Math.Min(CHUNK, size - offset);
                int[] tmp = new int[count];

                for (int i = 0; i < count; i++)
                    tmp[i] = rand.Next(motifCount);

                buffer.CopyFrom(tmp, 0, offset, count);
                offset += count;
            }
        }

        static void FillRandomPathIds(GraphicsDevice device, ReadWriteBuffer<int> buffer, int size, int pathCount, int seed)
        {
            const int CHUNK = 1_000_000;
            var rand = new Random(seed);
            int offset = 0;

            while (offset < size)
            {
                int count = Math.Min(CHUNK, size - offset);
                int[] tmp = new int[count];

                for (int i = 0; i < count; i++)
                    tmp[i] = rand.Next(pathCount);

                buffer.CopyFrom(tmp, 0, offset, count);
                offset += count;
            }
        }

        static void FillGroupedPathIds(GraphicsDevice device, ReadWriteBuffer<int> buffer, int size, int pathCount)
        {
            const int CHUNK = 1_000_000;
            int offset = 0;

            while (offset < size)
            {
                int count = Math.Min(CHUNK, size - offset);
                int[] tmp = new int[count];

                for (int i = 0; i < count; i++)
                {
                    long globalIndex = offset + i;
                    tmp[i] = (int)((globalIndex * pathCount) / size);
                    if (tmp[i] >= pathCount) tmp[i] = pathCount - 1;
                }

                buffer.CopyFrom(tmp, 0, offset, count);
                offset += count;
            }
        }

        static void Synchronize(GraphicsDevice device)
        {
            using var fence = device.AllocateReadWriteBuffer<int>(1);
            device.For(1, new FenceKernel(fence));
            int[] tmp = new int[1];
            fence.CopyTo(tmp);
        }
    }
}