Caputchin
Разработка игры для маркетплейса

engine-kit (опциональный)

Эта страница опциональна. Всё здесь это один удобный способ произвести соответствующий run; ты можешь полностью игнорировать кит и поставить голый run, написанный вручную. Тянись к @caputchin/engine-kit, когда предпочитаешь писать обычную игровую логику и иметь детерминизм, цикл реплея и кодирование трассы, обработанные за тебя.

Кит реэкспортирует всю поверхность контракта реплея, так что у пользователей кита единственное место импорта.

npm install @caputchin/engine-kit

Идея: напиши редьюсер, получи run

Ключевой ход кита это дать тебе выразить твою игру как чистый редьюсер: функцию, которая берёт текущее состояние и ввод одного тика и возвращает следующее состояние. Из этого редьюсера плюс детерминированных примитивов адаптер toRun кита производит run(seed, config, trace), который соответствует контракту по построению.

import { defineEngine, toRun, cap } from "@caputchin/engine-kit";

const engine = defineEngine({
  setup(seed) {
    const rng = cap.rng(seed);          // deterministic, seeded RNG
    return { score: 0, targets: spawn(rng), rng };
  },
  tick(state, input) {
    // pure: same (state, input) always yields the same next state
    return applyInput(state, input);
  },
  result(state) {
    return { passed: state.score >= 3, score: state.score, durationMs: state.elapsed };
  },
});

export const run = toRun(engine);       // a conforming RunFn

Детерминированные примитивы

Кит даёт тебе две вещи, которые чаще всего ломают кросс-рантаймовый детерминизм, обе засеваемые и воспроизводимые:

  • cap.rng(seed)cap.rngFromState) - засеваемый PRNG. Используй его для каждого случайного выбора вместо Math.random().
  • cap.math - детерминированные трансцендентные функции (sin, cos, ...), которые согласуются между рантаймами, вместо платформенных Math, которые могут не согласовываться.

Опциональные шимы

  • applyShim() - нейтрализует недетерминированные глобалы (настенные часы, Math.random), чтобы случайный вызов сбоил громко в разработке, а не молча разошёлся при реплее.
  • applyDomShim() - минимальный headless-DOM для пути с фреймворком, чтобы редьюсер, касающийся крошечной DOM-поверхности, всё ещё реплеился в изоляте без DOM.

Запусти локально перед публикацией

Обвязка replay кита прогоняет трассу через твой движок тем же способом, что и сервер, а selfCheck прогоняет батарею случаев и сообщает о любом недетерминизме до того, как ты опубликуешь:

import { selfCheck } from "@caputchin/engine-kit";

const report = selfCheck(engine, { cases: myCases });
if (!report.ok) console.error(report.violations);

Чистый локальный selfCheck это лучший предсказатель того, что самопроверка публикации маркетплейса пройдёт (это тот же стандарт детерминизма). Кит также поставляет CLI для запуска этого из скриптов package.json или CI.

Кодирование трассы

encodeTrace / decodeTrace дают тебе компактный, версионированный формат трассы, чтобы поток ввода, который испускает твоя живая игра, был ровно тем, что декодирует твой run при реплее. Использование их на обеих сторонах держит эти двое в lockstep.

Когда пропустить кит

Пропусти его, когда у тебя уже есть детерминированная логика (симуляция на фиксированной точке, WASM-модуль, скомпилированный для спецификационных float) или когда твоя игра достаточно проста, чтобы написать run вручную тривиально. Платформа никогда не знает и ей всё равно, произвёл ли кит твой run; она лишь загружает и реплеит контракт.

См. также

На этой странице