Caputchin
Desenvolvimento de jogo de marketplace

O engine-kit (opcional)

Esta página é opcional. Tudo aqui é uma forma conveniente de produzir um run conforme; você pode ignorar o kit por completo e enviar um run cru que você escreveu à mão. Recorra ao @caputchin/engine-kit quando preferir escrever lógica de jogo comum e ter o determinismo, o loop de replay e a codificação de trace tratados por você.

O kit reexporta toda a superfície do contrato de replay, então os usuários do kit têm um único ponto de import.

npm install @caputchin/engine-kit

A ideia: escreva um reducer, obtenha um run

O movimento central do kit é deixar você expressar seu jogo como um reducer puro: uma função que recebe o estado atual e a entrada de um tick e retorna o próximo estado. Desse reducer mais primitivas determinísticas, o adaptador toRun do kit produz um run(seed, config, trace) que conforma o contrato por construção.

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

Primitivas determinísticas

O kit te dá as duas coisas que mais frequentemente quebram o determinismo entre runtimes, ambas semeadas e reproduzíveis:

  • cap.rng(seed) (e cap.rngFromState) - um PRNG semeável. Use-o para cada escolha aleatória em vez de Math.random().
  • cap.math - transcendentais determinísticos (sin, cos, ...) que concordam entre runtimes, em vez dos Math da plataforma que podem não concordar.

Shims opcionais

  • applyShim() - neutraliza os globais não determinísticos (o relógio, Math.random) para que uma chamada acidental falhe de forma barulhenta no desenvolvimento em vez de divergir silenciosamente no replay.
  • applyDomShim() - um DOM headless mínimo para o caminho de framework, para que um reducer que toca uma pequena superfície de DOM ainda reexecute no isolate sem DOM.

Rode-o localmente antes de publicar

O harness replay do kit roda um trace pela sua engine do mesmo jeito que o servidor vai, e o selfCheck roda uma bateria de casos e reporta qualquer não determinismo antes de você publicar:

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

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

Um selfCheck local limpo é o melhor preditor de que a autoverificação de publicação do marketplace vai passar (é o mesmo padrão de determinismo). O kit também envia uma CLI para rodar isto a partir de scripts do package.json ou da CI.

Codificação de trace

encodeTrace / decodeTrace te dão um formato de trace compacto e versionado para que o fluxo de entrada que seu jogo ativo emite seja exatamente o que seu run decodifica no replay. Usá-los nos dois lados mantém os dois em lockstep.

Quando pular o kit

Pule-o quando você já tem lógica determinística (uma simulação de ponto fixo, um módulo WASM compilado para spec-floats) ou quando seu jogo é simples o bastante para escrever o run à mão ser trivial. A plataforma nunca sabe nem se importa se o kit produziu seu run; ela só carrega e reexecuta o contrato.

Veja também

Nesta página