Caputchin
Marketplace-Spiel-Entwicklung

Das engine-kit (optional)

Diese Seite ist optional. Alles hier ist ein bequemer Weg, ein konformes run zu produzieren; du kannst das Kit ganz ignorieren und ein nacktes run ausliefern, das du von Hand geschrieben hast. Greif zu @caputchin/engine-kit, wenn du lieber gewöhnliche Spiel-Logik schreibst und Determinismus, die Replay-Schleife und Trace-Kodierung für dich gehandhabt haben willst.

Das Kit re-exportiert die ganze Replay-Vertrag-Fläche, sodass Kit-Nutzer eine einzige Import-Stelle haben.

npm install @caputchin/engine-kit

Die Idee: schreib einen Reducer, bekomm ein run

Der Kern-Zug des Kits ist, dich dein Spiel als reinen Reducer ausdrücken zu lassen: eine Funktion, die den aktuellen Zustand und die Eingabe eines Ticks nimmt und den nächsten Zustand zurückgibt. Aus diesem Reducer plus deterministischen Primitiven produziert der toRun-Adapter des Kits ein run(seed, config, trace), das per Konstruktion dem Vertrag entspricht.

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

Deterministische Primitive

Das Kit gibt dir die zwei Dinge, die am häufigsten den Cross-Runtime-Determinismus brechen, beide geseedet und reproduzierbar:

  • cap.rng(seed) (und cap.rngFromState), ein seedbarer PRNG. Nutz ihn für jede Zufallswahl statt Math.random().
  • cap.math, deterministische Transzendente (sin, cos, ...), die über Laufzeiten übereinstimmen, statt der Plattform-Math-Funktionen, die es eventuell nicht tun.

Optionale Shims

  • applyShim(), neutralisiert die nicht-deterministischen Globals (die Wall-Clock, Math.random), sodass ein versehentlicher Aufruf in der Entwicklung laut fehlschlägt, statt beim Replay still zu divergieren.
  • applyDomShim(), ein minimales Headless-DOM für den Framework-Weg, sodass ein Reducer, der eine winzige DOM-Fläche berührt, trotzdem im Kein-DOM-Isolate abspielt.

Lokal laufen lassen vor dem Veröffentlichen

Die replay-Harness des Kits lässt einen Trace durch deine Engine laufen, genauso wie der Server es tun wird, und selfCheck lässt eine Batterie von Fällen laufen und meldet jede Nicht-Determiniertheit bevor du veröffentlichst:

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

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

Ein sauberer lokaler selfCheck ist der beste Prädiktor, dass die Publish-Selbstprüfung des Marketplace bestehen wird (es ist derselbe Determinismus-Standard). Das Kit liefert auch eine CLI, um dies aus package.json-Skripten oder CI zu laufen.

Trace-Kodierung

encodeTrace / decodeTrace geben dir ein kompaktes, versioniertes Trace-Format, sodass der Eingabe-Stream, den dein Live-Spiel emittiert, genau das ist, was dein run beim Replay dekodiert. Sie auf beiden Seiten zu nutzen hält die zwei im Gleichschritt.

Wann das Kit überspringen

Überspring es, wenn du schon deterministische Logik hast (eine Festkomma-Simulation, ein für Spec-Floats kompiliertes WASM-Modul) oder wenn dein Spiel einfach genug ist, dass run von Hand zu schreiben trivial ist. Die Plattform weiß oder kümmert sich nie darum, ob das Kit dein run produziert hat; sie lädt und spielt nur den Vertrag ab.

Siehe auch

Auf dieser Seite