Chasing MasteryDevelopers
Packages and Practice Modes
Chasing Mastery public platform

Activity engine

Define deterministic Attempt behavior with describe, initialize, and apply.

An activity engine is a pure reducer around one Practice Mode. It receives validated configuration, a host-supplied run context, current package state, and one typed input. It returns replacement state and a validated transition.

Public interface

Prop

Type

Wrap the implementation with defineActivityEngine so every direct call validates the same contracts the runtime validates.

import { defineActivityEngine } from "@chasingmastery/sdk";

type State = {
  index: number;
  hits: number;
  complete: boolean;
};

export const engine = defineActivityEngine<State>({
  describe(configuration) {
    return describeMode(configuration);
  },
  initialize(context) {
    return {
      state: { index: 0, hits: 0, complete: false },
      status: "active",
      effects: [],
    };
  },
  apply(context, state, input) {
    const action = normalizeInput(input);
    return reduceAction(context, state, action);
  },
});

Describe before execution

describe(configuration) declares what the configured mode means:

  • accepted commands and exact observations;
  • available Measurements and primary result contract;
  • whether lower or higher is better;
  • correction support;
  • player, overlay, progression, Story Event, coordination, or narration links;
  • limits the host needs before starting a run.

Description is pure. It cannot inspect a member, read history, fetch a service, or create an Attempt.

Initialize with host context

The run context contains pinned package/release identity, host-issued run and actor context, trusted timing information, and validated configuration. Treat it as input, not authority you can retain outside the transition.

Return all initial package state explicitly. Do not rely on module globals, randomness, wall-clock reads, environment variables, or request ordering.

Apply one typed input

Normalize commands and observations into a package-owned action before domain logic. The reducer should then be oblivious to whether a human tapped the rich player or Station observed the same event automatically.

function normalizeInput(input: ActivityInput): DomainAction {
  if (input.kind === "command") return commandToAction(input.command);
  return observationToAction(input.observation);
}

Return a terminal transition exactly once. The host protects revision and idempotency, but the engine should also make terminal state explicit and reject or ignore invalid post-terminal domain actions consistently.

Worker adapter

createActivityWorkerHandler exposes the engine through the isolated runtime invocation protocol. It validates contribution identity, pinned package context, operation shape, transitions, and safe error responses. Package code should not build a custom HTTP protocol around the engine.

Determinism checklist

  • Same context, state, and input produce the same transition.
  • No direct clock, random, network, filesystem, or persistent storage reads.
  • Every state field is JSON-safe and schema bounded.
  • Invalid inputs fail without partially mutating state.
  • Effects describe intent; they do not execute host operations.
  • Terminal completion needs no later Stop or End command.

On this page