Детерминированная симуляция и сетевая синхронизация

Детерминированная симуляция — это режим работы физического движка, при котором одинаковые входные данные и одинаковая последовательность шагов интеграции гарантированно приводят к одинаковому результату на всех машинах. Для сетевых приложений (мультиплеерных игр, распределённых симуляторов) это критически важно: состояние мира должно воспроизводиться предсказуемо без постоянной передачи полного набора физических параметров.

Библиотека Oimo.js — лёгкий 3D-физический движок на JavaScript — предоставляет базовые инструменты для управления шагом симуляции, что позволяет реализовать детерминированный режим при соблюдении ряда условий.


Основы временного шага

В центре детерминизма находится фиксированный временной шаг. Метод world.step() в Oimo.js принимает значение timeStep (в секундах). Если использовать переменный шаг (например, равный разнице между кадрами requestAnimationFrame), симуляция становится зависимой от частоты кадров и производительности устройства.

Фиксированный шаг

Классическая схема:

const timeStep = 1 / 60;
world.step(timeStep);

Ключевые свойства:

  • одинаковый timeStep на всех клиентах;
  • одинаковое количество шагов;
  • одинаковый порядок обновления тел.

Накопление времени (Fixed Timestep Loop)

В реальных приложениях рендеринг и физика выполняются независимо. Используется аккумулятор:

let accumulator = 0;
let lastTime = performance.now();
const fixedTimeStep = 1 / 60;

function update() {
    const currentTime = performance.now();
    const delta = (currentTime - lastTime) / 1000;
    lastTime = currentTime;

    accumulator += delta;

    while (accumulator >= fixedTimeStep) {
        world.step(fixedTimeStep);
        accumulator -= fixedTimeStep;
    }

    requestAnimationFrame(update);
}

Такая схема обеспечивает строгое количество шагов при любой частоте кадров.


Источники недетерминизма

Даже при фиксированном шаге возможны расхождения.

1. Числа с плавающей точкой

JavaScript использует IEEE-754 double precision. Разные браузеры обычно ведут себя одинаково, но:

  • порядок операций влияет на накопление ошибки;
  • разные версии движка V8 или SpiderMonkey могут отличаться оптимизациями.

2. Порядок добавления тел

Oimo.js хранит тела и контактные пары в массивах. Порядок их обработки влияет на разрешение столкновений. Если на клиентах объекты создаются в разном порядке — результаты будут различаться.

3. Случайные значения

Использование Math.random() без синхронизации разрушает детерминизм. Необходимо:

  • применять псевдослучайный генератор с фиксированным seed;
  • передавать seed всем участникам симуляции.

4. Различия в частоте обновлений

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


Настройка мира Oimo.js для детерминизма

Создание мира:

const world = new OIMO.World({
    timestep: 1 / 60,
    iterations: 8,
    broadphase: 2,
    worldscale: 1,
    random: false,
    info: false,
    gravity: [0, -9.81, 0]
});

Критические параметры:

  • timestep — фиксированный шаг.
  • iterations — число итераций солвера; должно быть одинаковым на всех клиентах.
  • gravity — вектор гравитации; любое различие приведёт к расхождению.
  • broadphase — алгоритм широкофазной проверки; выбор влияет на порядок контактов.

Детерминированное создание тел

Тела должны создаваться:

  1. В одинаковом порядке.

  2. С идентичными параметрами:

    • масса;
    • форма;
    • коэффициенты трения и реституции;
    • начальное положение и ориентация.

Пример:

world.add({
    type: 'box',
    size: [1, 1, 1],
    pos: [0, 10, 0],
    rot: [0, 0, 0],
    move: true,
    density: 1
});

Важно, чтобы:

  • координаты были одинаково округлены;
  • отсутствовала зависимость от пользовательского ввода в момент создания.

Архитектуры сетевой синхронизации

1. Lockstep (жёсткая синхронизация)

Все клиенты:

  • получают входные данные;
  • выполняют шаг симуляции только после получения данных от всех участников.

Особенности:

  • минимальный сетевой трафик (передаются только input-команды);
  • высокая чувствительность к задержке;
  • идеальный детерминизм при корректной реализации.

Алгоритм:

  1. Игроки отправляют команды (движение, действие).
  2. Сервер собирает команды для шага N.
  3. Рассылает пакет всем клиентам.
  4. Все выполняют world.step().

2. Authoritative Server (авторитетный сервер)

Сервер выполняет физическую симуляцию полностью:

  • клиенты отправляют вход;
  • сервер рассчитывает состояние;
  • клиенты получают снимки состояния (snapshots).

Преимущества:

  • защита от читерства;
  • отсутствие необходимости полного детерминизма на клиентах.

Недостатки:

  • необходимость интерполяции;
  • увеличение сетевой нагрузки.

3. Client-side Prediction + Reconciliation

Гибридный подход:

  • клиент выполняет симуляцию локально;
  • сервер присылает подтверждённые состояния;
  • клиент сравнивает их со своими;
  • при расхождении выполняется откат (rollback).

Механизм отката (Rollback)

Для отката требуется хранить историю состояний мира.

Сохранение состояния

В Oimo.js отсутствует встроенный сериализатор, поэтому необходимо сохранять:

  • положение;
  • кватернион;
  • линейную скорость;
  • угловую скорость.

Пример структуры:

function captureState(body) {
    return {
        position: body.getPosition().clone(),
        quaternion: body.getQuaternion().clone(),
        linearVelocity: body.getLinearVelocity().clone(),
        angularVelocity: body.getAngularVelocity().clone()
    };
}

Восстановление состояния

function restoreState(body, state) {
    body.setPosition(state.position);
    body.setQuaternion(state.quaternion);
    body.setLinearVelocity(state.linearVelocity);
    body.setAngularVelocity(state.angularVelocity);
}

При получении корректного серверного состояния:

  1. Откат к последнему подтверждённому кадру.
  2. Повторная симуляция шагов с учётом сохранённых input-команд.

Интерполяция и экстраполяция

Если используется авторитетный сервер, клиенты получают снимки состояния, например, 20 раз в секунду, а рендеринг происходит 60 FPS.

Интерполяция

Между двумя снапшотами выполняется плавное смешивание:

current = lerp(stateA, stateB, alpha);

Где alpha — доля времени между снапшотами.

Экстраполяция

Если пакет задержался, используется прогноз:

position += velocity * deltaTime

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


Контроль расхождений

Для выявления десинхронизации применяются:

  • хеш состояния мира;
  • контрольные суммы позиций тел;
  • периодическая проверка сервером.

Пример простого хеша:

function worldChecksum(bodies) {
    let sum = 0;
    bodies.forEach(body => {
        const p = body.getPosition();
        sum += p.x + p.y + p.z;
    });
    return sum.toFixed(5);
}

Если хеш отличается — требуется принудительная синхронизация.


Производительность и стабильность

Детерминированность увеличивает требования к:

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

Рекомендуемые меры:

  • отключение ненужных оптимизаций, влияющих на порядок вычислений;
  • использование одинаковых версий Oimo.js на сервере и клиентах;
  • фиксированное количество итераций солвера;
  • ограничение максимальной скорости тел (clamping).

Ограничения Oimo.js в контексте детерминизма

  • Отсутствие встроенной системы снапшотов.
  • Нет гарантии бит-в-бит идентичности между разными JS-движками.
  • Плавающая точка может накапливать ошибку при длительной симуляции.

Практический подход — периодическая синхронизация даже при lockstep-архитектуре.


Пример минимальной lockstep-схемы

  1. Фиксированный timeStep = 1/60.
  2. Очередь входных команд.
  3. Синхронное выполнение world.step().
  4. Хранение состояния каждые N кадров.
  5. Проверка контрольной суммы.

Строгое соблюдение порядка:

  • создание тел;
  • добавление соединений (joints);
  • применение сил.

Любое расхождение в последовательности вызовов API приводит к разным результатам симуляции.


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