@joystyk/testing 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/NOTICE +36 -0
- package/README.md +191 -2
- package/dist/builders.d.ts +59 -0
- package/dist/builders.js +129 -0
- package/dist/builders.js.map +1 -0
- package/dist/clock.d.ts +30 -0
- package/dist/clock.js +58 -0
- package/dist/clock.js.map +1 -0
- package/dist/deep.d.ts +25 -0
- package/dist/deep.js +105 -0
- package/dist/deep.js.map +1 -0
- package/dist/engine-conformance.d.ts +166 -0
- package/dist/engine-conformance.js +995 -0
- package/dist/engine-conformance.js.map +1 -0
- package/dist/fixtures/dice-race.d.ts +41 -0
- package/dist/fixtures/dice-race.js +153 -0
- package/dist/fixtures/dice-race.js.map +1 -0
- package/dist/fixtures/draw-play.d.ts +54 -0
- package/dist/fixtures/draw-play.js +188 -0
- package/dist/fixtures/draw-play.js.map +1 -0
- package/dist/fixtures/drift.d.ts +34 -0
- package/dist/fixtures/drift.js +109 -0
- package/dist/fixtures/drift.js.map +1 -0
- package/dist/fixtures/guards.d.ts +11 -0
- package/dist/fixtures/guards.js +16 -0
- package/dist/fixtures/guards.js.map +1 -0
- package/dist/fixtures/hint-puzzle.d.ts +47 -0
- package/dist/fixtures/hint-puzzle.js +129 -0
- package/dist/fixtures/hint-puzzle.js.map +1 -0
- package/dist/fixtures/index.d.ts +6 -0
- package/dist/fixtures/index.js +7 -0
- package/dist/fixtures/nim.d.ts +34 -0
- package/dist/fixtures/nim.js +141 -0
- package/dist/fixtures/nim.js.map +1 -0
- package/dist/fixtures/sealed-bids.d.ts +46 -0
- package/dist/fixtures/sealed-bids.js +141 -0
- package/dist/fixtures/sealed-bids.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/replay.d.ts +58 -0
- package/dist/replay.js +110 -0
- package/dist/replay.js.map +1 -0
- package/dist/report.d.ts +68 -0
- package/dist/report.js +104 -0
- package/dist/report.js.map +1 -0
- package/dist/rng.d.ts +18 -0
- package/dist/rng.js +25 -0
- package/dist/rng.js.map +1 -0
- package/dist/schema.d.ts +13 -0
- package/dist/schema.js +21 -0
- package/dist/schema.js.map +1 -0
- package/dist/session-ports.d.ts +16 -0
- package/dist/session-ports.js +32 -0
- package/dist/session-ports.js.map +1 -0
- package/dist/simulation-conformance.d.ts +71 -0
- package/dist/simulation-conformance.js +312 -0
- package/dist/simulation-conformance.js.map +1 -0
- package/dist/storage.d.ts +29 -0
- package/dist/storage.js +63 -0
- package/dist/storage.js.map +1 -0
- package/dist/vitest.d.ts +32 -0
- package/dist/vitest.js +45 -0
- package/dist/vitest.js.map +1 -0
- package/dist/waiting.d.ts +58 -0
- package/dist/waiting.js +97 -0
- package/dist/waiting.js.map +1 -0
- package/package.json +55 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-conformance.js","names":["makeSeats","replayEngine","runnerReplayEngine"],"sources":["../src/engine-conformance.ts"],"sourcesContent":["// The Layer 2 conformance suite (game-contract.md, \"Conformance tests\"). It plays many random\n// games from fixed seeds and checks every rule a discrete engine must keep, collecting\n// reproducible failures instead of stopping at the first.\n\nimport {\n isGameError,\n isJson,\n runMigrations,\n type DiscreteEngine,\n type EngineAssist,\n type EngineInput,\n type EngineReplay,\n type Json,\n type Migration,\n type Outcome,\n type Rng,\n type RngState,\n type SeatId,\n type SeatInfo,\n type Transition,\n type Validator,\n} from '@joystyk/contracts';\nimport { restoreRng } from '@joystyk/kit';\nimport { replayEngine as runnerReplayEngine } from '@joystyk/session';\nimport { engineReplay, seats as makeSeats, type SaveFixture } from './builders.ts';\nimport { deepEqual, deepFreeze, errorMessage, firstDifference, jsonProblem, jsonRoundTrip, show } from './deep.ts';\nimport { replayEngine, undoByReplay } from './replay.ts';\nimport { Recorder, type ConformanceReport, type Failure } from './report.ts';\nimport { cloneRng, testRng, testSeed } from './rng.ts';\nimport { formatIssues, validate } from './schema.ts';\n\n/** One way to start a game: a config and the seats (a count, or explicit `SeatInfo`s). */\nexport type EngineSetup<Config> = {\n readonly config: Config;\n readonly seats: number | readonly SeatInfo[];\n readonly label?: string;\n};\n\n/**\n * What an invariant hook returns: nothing or `true` when the state is fine; `false` or a\n * message when it isn't. Throwing (for example from `expect`) also counts as a violation.\n */\nexport type InvariantResult = void | boolean | string;\n\n/** Save-related inputs for the `saves` check. */\nexport type SaveOptions = {\n /** The current save version (`GameVersions.save`). */\n readonly version: number;\n /** The oldest save version that was ever shipped. Default 1. */\n readonly firstVersion?: number;\n readonly migrations: readonly Migration[];\n /** At least one stored state for every shipped save version, the current one included. */\n readonly fixtures: readonly SaveFixture[];\n};\n\n/** Original successor snapshots: preserve what the viewer legitimately learns in them. */\nexport type HiddenTransition<State, Command> = {\n readonly input: EngineInput<Command>;\n readonly states: readonly State[];\n};\n\n/** Options for `checkEngine` and `runEngineConformance`. */\nexport type EngineConformanceOptions<State, Command, Config> = {\n /** A name for reports and the vitest `describe` block. */\n readonly name?: string;\n /** The game id written into replays and saves. Default \"test-game\". */\n readonly gameId?: string;\n /** How to start games. Or give `configs` and `seatCounts`, and every pair is used. */\n readonly setups?: readonly EngineSetup<Config>[];\n readonly configs?: readonly Config[];\n readonly seatCounts?: readonly number[];\n /** Random games per setup. Default 12. */\n readonly games?: number;\n /** The base seed. Game `g` of setup `s` uses the seed string `${seed}/${s}/${g}`. Default \"conformance\". */\n readonly seed?: string;\n /** The most inputs one game may take. Default 400. */\n readonly maxSteps?: number;\n /** Fail a game that has no outcome after `maxSteps` inputs. Default true. */\n readonly requireOutcome?: boolean;\n /**\n * Candidate commands for `seat` in `state`: every legal command, or a sample of them.\n * Illegal candidates are fine; the engine's answer decides. Without it, the suite asks\n * `engine.ai` for one command per pending seat.\n */\n readonly commands?: (state: State, seat: SeatId, rng: Rng) => readonly Command[];\n /**\n * Changes only what `viewer` may not see (other hands, the deck, a solution), usually with\n * the kit's `perturbHidden`. Without it the no-leak check is skipped, which is right only\n * for games whose state is entirely public.\n */\n readonly perturbHidden?: (state: State, viewer: SeatId | null, rng: Rng) => State;\n /**\n * Optional event-check retry: preserve the viewer's current and newly learned facts,\n * including the input's required cards, while varying information that stays hidden.\n * Separate from perturbHidden so legacy position helpers never receive transition work.\n */\n readonly perturbTransition?: (\n state: State,\n viewer: SeatId | null,\n rng: Rng,\n transition: HiddenTransition<State, Command>,\n ) => State;\n /** Perturbations per viewer per position. Default 2. */\n readonly perturbations?: number;\n /** Called on every state the game reaches (after `init` and after every applied input). */\n readonly invariants?: (state: State) => InvariantResult;\n /**\n * Which platform inputs the random games send. `resign` (default true) is accepted by every\n * engine while the game is in progress; turn on `timeout` and `seatChanges` for engines\n * that support clocks and seat hand-overs.\n */\n readonly platformInputs?: { readonly resign?: boolean; readonly timeout?: boolean; readonly seatChanges?: boolean };\n /** Fake job runners by job kind: they return the outcome's `value`. */\n readonly jobs?: { readonly [kind: string]: (payload: Json, rng: Rng) => Json };\n /** How often a job fails instead (`{ ok: false }`). Default 0.1. */\n readonly jobFailureRate?: number;\n /**\n * Which inputs the game's spec makes undo barriers. When given, every transition's\n * `undoBarrier` must match it.\n */\n readonly undoBarrier?: (input: EngineInput<Command>, before: State, after: State) => boolean;\n /**\n * Which inputs the game's spec counts as assistance (a hint, a check, a reveal). When\n * given, every transition's `assist` must match it.\n */\n readonly assist?: (input: EngineInput<Command>, before: State, after: State) => EngineAssist | null;\n /** Save fixtures and migrations. Without them only the save round trip is checked. */\n readonly saves?: SaveOptions;\n /** Run only one game, to reproduce a reported failure. */\n readonly only?: { readonly setup: number; readonly game: number };\n /** Failures kept per check in the report. Default 3. */\n readonly maxFailuresPerCheck?: number;\n};\n\n/** The checks this suite runs, in report order. */\nexport const ENGINE_CHECKS = [\n {\n id: 'determinism',\n title: 'Determinism: the same seed and inputs give the same states, beats, barriers and outcomes',\n },\n { id: 'replay', title: 'Replay: the recorded log reproduces the final state' },\n { id: 'json', title: 'JSON safety of configs, states, views, events, commands, jobs and decision keys' },\n { id: 'schemas', title: \"Schema agreement: every value passes the engine's own schemas\" },\n { id: 'no-leak', title: 'No leaks: viewFor, eventFor and decisionKey ignore what the viewer may not see' },\n {\n id: 'rejections',\n title: 'Rejected inputs change nothing: apply never throws or mutates, and rejects with a GameError',\n },\n { id: 'turns', title: 'Turn discipline: only pending decisions and declared untimed side commands are allowed' },\n { id: 'resign', title: 'Resignation is accepted from any seat, in turn or not, while the game is in progress' },\n { id: 'invariants', title: 'Invariants hold on every state the game reaches' },\n { id: 'ai', title: 'AI legality: ai.choose never returns a rejected command' },\n { id: 'saves', title: 'Saves: round trip, restore and continue, and the migration chain from fixtures' },\n { id: 'jobs', title: 'Job hygiene: job outcomes are accepted only while their job is awaited' },\n { id: 'stale', title: \"Staleness: while a seat's decisionKey is unchanged, the commands it may send are unchanged\" },\n { id: 'undo', title: \"Undo by replay returns to the human's previous decision and never steps back past a barrier\" },\n { id: 'assist', title: 'Assistance: transitions flag hints, checks and reveals, and replay reproduces the flags' },\n {\n id: 'session-hooks',\n title: 'Session hooks: startsClock is declared and set at most once a game, and worthSaving is pure',\n },\n {\n id: 'outcome',\n title: 'Every game reaches a valid outcome in time, and a finished game rejects commands and resignations',\n },\n] as const;\n\n/** The id of one engine check. */\nexport type EngineCheckId = (typeof ENGINE_CHECKS)[number]['id'];\n\n/** The engine's answer to one tried input. */\ntype Attempt<State, Event> =\n | { readonly kind: 'accepted'; readonly transition: Transition<State, Event>; readonly rngAfter: RngState }\n | { readonly kind: 'rejected'; readonly code: string }\n | { readonly kind: 'threw'; readonly message: string };\n\n/** One state the game reached. */\ntype Position<State, Command> = {\n readonly state: State;\n /** The generator's state before the next input. */\n readonly rng: RngState;\n readonly pending: readonly SeatId[];\n readonly keys: readonly Json[];\n /** Accepted candidate commands per pending seat. */\n readonly accepted: ReadonlyMap<SeatId, readonly Command[]>;\n};\n\ntype Step<State, Command, Event> = {\n readonly input: EngineInput<Command>;\n readonly transition: Transition<State, Event>;\n readonly rngAfter: RngState;\n};\n\nconst ENGINE_ASSISTS: readonly unknown[] = ['hint', 'check', 'reveal', 'shuffle'];\n\nconst DEFAULTS = { games: 12, maxSteps: 400, perturbations: 2, jobFailureRate: 0.1, maxFailures: 3 };\nconst CANDIDATE_CAP = 12;\n\n/** Builds the setups from `setups`, or from every `configs` × `seatCounts` pair. */\nconst resolveSetups = <Config>(\n options: Pick<EngineConformanceOptions<unknown, unknown, Config>, 'setups' | 'configs' | 'seatCounts'>,\n): EngineSetup<Config>[] => {\n if (options.setups !== undefined && options.setups.length > 0) return [...options.setups];\n const configs = options.configs ?? [];\n const counts = options.seatCounts ?? [];\n const setups = configs.flatMap((config) => counts.map((seats) => ({ config, seats })));\n if (setups.length === 0)\n throw new TypeError('Engine conformance needs `setups`, or both `configs` and `seatCounts`.');\n return setups;\n};\n\nconst safely = <T>(make: () => readonly T[]): readonly T[] => {\n try {\n return make();\n } catch {\n return [];\n }\n};\n\nconst sample = <T>(items: readonly T[], count: number, rng: Rng): T[] =>\n items.length <= count ? [...items] : rng.shuffle(items).slice(0, count);\n\nconst invariantMessage = (result: InvariantResult): string | null => {\n if (result === undefined || result === true) return null;\n if (result === false) return 'the invariant hook returned false';\n return result;\n};\n\nconst outcomeProblem = (outcome: Outcome, seats: readonly SeatInfo[]): string | null => {\n if (!['won', 'lost', 'draw', 'completed', 'abandoned'].includes(outcome.result))\n return `unknown result ${show(outcome.result)}`;\n if (typeof outcome.summary !== 'string') return 'summary must be a string';\n if (outcome.elapsedMs !== undefined || outcome.undosUsed !== undefined || outcome.assists !== undefined) {\n return 'engines never set elapsedMs, undosUsed or assists; the runner adds them';\n }\n const placed = outcome.placements.map((p) => p.seat);\n for (const p of outcome.placements) {\n if (!seats.some((s) => s.id === p.seat)) return `placement for unknown seat ${show(p.seat)}`;\n if (!Number.isInteger(p.place) || p.place < 1)\n return `seat ${p.seat} has place ${show(p.place)}; places start at 1`;\n }\n if (new Set(placed).size !== placed.length) return 'a seat is placed twice';\n if (placed.length !== seats.length) {\n return `placements cover ${placed.length} of ${seats.length} seats; every seat gets exactly one placing`;\n }\n return null;\n};\n\n/**\n * Runs the Layer 2 conformance suite against `engine` and returns a report. It never throws\n * for a failing check; it throws only for unusable options. Use `runEngineConformance` to get\n * one vitest case per check instead.\n */\nexport const checkEngine = async <\n State extends Json,\n Command extends Json,\n View extends Json,\n Event extends Json,\n Config extends Json,\n>(\n engine: DiscreteEngine<State, Command, View, Event, Config>,\n options: EngineConformanceOptions<State, Command, Config>,\n): Promise<ConformanceReport> => {\n const setups = resolveSetups(options);\n if (options.commands === undefined && engine.ai === undefined) {\n throw new TypeError(\n 'Engine conformance needs a `commands` generator, or an engine with `ai`, to play random games.',\n );\n }\n const rec = new Recorder(ENGINE_CHECKS, options.maxFailuresPerCheck ?? DEFAULTS.maxFailures);\n const gameCount = options.games ?? DEFAULTS.games;\n const maxSteps = options.maxSteps ?? DEFAULTS.maxSteps;\n const requireOutcome = options.requireOutcome ?? true;\n const perturbations = options.perturbations ?? DEFAULTS.perturbations;\n const resignOn = options.platformInputs?.resign ?? true;\n const timeoutOn = options.platformInputs?.timeout ?? false;\n const seatChangesOn = options.platformInputs?.seatChanges ?? false;\n const failureRate = options.jobFailureRate ?? DEFAULTS.jobFailureRate;\n const baseSeed = options.seed ?? 'conformance';\n const gameId = options.gameId ?? 'test-game';\n const keyOf = (state: State, seat: SeatId): Json =>\n engine.decisionKey === undefined ? engine.viewFor(state, seat) : engine.decisionKey(state, seat);\n\n if (options.perturbHidden === undefined)\n rec.skip('no-leak', 'no perturbHidden given; only right when all state is public');\n if (engine.ai === undefined) rec.skip('ai', 'the engine has no runner-driven ai');\n if (options.invariants === undefined) rec.skip('invariants', 'no invariants hook given');\n if (!resignOn) rec.skip('resign', 'resign is turned off in platformInputs');\n if (engine.clockStart === undefined && engine.worthSaving === undefined) {\n rec.skip('session-hooks', 'the engine declares neither clockStart nor worthSaving');\n }\n if (engine.clockStart !== undefined && engine.clockStart !== 'first-input' && engine.clockStart !== 'transition') {\n rec.fail('session-hooks', {\n message: `clockStart must be 'first-input' or 'transition', got ${show(engine.clockStart)}`,\n });\n }\n\n const validations: Promise<void>[] = [];\n // Runners validate commands, states, configs and job data on their ordered input path, so\n // those schemas must answer synchronously (game-contract.md, \"Schemas at every boundary\").\n const asyncAllowed = new Set<unknown>([engine.schemas.view, engine.schemas.event]);\n const checkSchema = (\n schema: Validator<unknown> | undefined,\n value: unknown,\n what: string,\n fail: (message: string) => void,\n ): void => {\n if (schema === undefined) return;\n rec.ran('schemas');\n if (!asyncAllowed.has(schema)) {\n let answer: unknown;\n try {\n answer = schema['~standard'].validate(value);\n } catch {\n answer = null;\n }\n if (typeof (answer as { then?: unknown } | null)?.then === 'function') {\n fail(\n `${what}: the schema validates asynchronously; runners need synchronous command, state, config and job schemas`,\n );\n }\n }\n validations.push(\n validate(schema, value).then(\n (result) => {\n if (!result.ok) fail(`${what} fails its schema: ${formatIssues(result.issues)}; value ${show(value)}`);\n },\n (error: unknown) => fail(`${what}: the schema threw ${errorMessage(error)}`),\n ),\n );\n };\n\n let totalGames = 0;\n let totalSteps = 0;\n let perturbedChanged = 0;\n let perturbedTotal = 0;\n\n // Config schemas, once per setup.\n for (const [index, setup] of setups.entries()) {\n rec.ran('json');\n if (!isJson(setup.config))\n rec.fail('json', {\n message: `setup ${index}: the config isn't JSON: ${jsonProblem(setup.config)}`,\n setup: index,\n });\n checkSchema(engine.schemas.config, setup.config, `setup ${index}: the config`, (message) =>\n rec.fail('schemas', { message, setup: index }),\n );\n }\n\n for (const [setupIndex, setup] of setups.entries()) {\n const seatList: readonly SeatInfo[] = typeof setup.seats === 'number' ? makeSeats(setup.seats) : setup.seats;\n const viewers: readonly (SeatId | null)[] = [...seatList.map((s) => s.id), null];\n for (let gameIndex = 0; gameIndex < gameCount; gameIndex += 1) {\n if (options.only !== undefined && (options.only.setup !== setupIndex || options.only.game !== gameIndex))\n continue;\n totalGames += 1;\n const seed = `${baseSeed}/${setupIndex}/${gameIndex}`;\n const seedState = testSeed(seed, 'engine');\n const driver = testRng(seed, 'driver');\n const perturbRng = testRng(seed, 'perturb');\n const positions: Position<State, Command>[] = [];\n const steps: Step<State, Command, Event>[] = [];\n\n const replayUpTo = (count: number): EngineReplay<Command, Config> =>\n engineReplay({\n gameId,\n rulesVersion: engine.rulesVersion,\n config: setup.config,\n seats: seatList,\n seed: seedState,\n inputs: steps.slice(0, count).map((s) => s.input),\n });\n const failure = (message: string, step: number, input?: unknown): Failure => ({\n message,\n setup: setupIndex,\n game: gameIndex,\n seed,\n step,\n replay: replayUpTo(step),\n ...(input === undefined ? {} : { input }),\n });\n const fail = (id: EngineCheckId, message: string, step: number, input?: unknown): void =>\n rec.fail(id, failure(message, step, input));\n\n /** Tries `input` on `state` with a copy of the generator at `rng`; nothing is committed. */\n const attempt = (\n state: State,\n input: EngineInput<Command>,\n rng: RngState,\n step: number,\n ): Attempt<State, Event> => {\n deepFreeze(input);\n let result: ReturnType<typeof engine.apply>;\n try {\n result = engine.apply(state, input, restoreRng(rng));\n } catch (error) {\n const message = errorMessage(error);\n const mutation = /read[- ]only|not extensible|frozen|Cannot (assign|add|delete|define)/i.test(message);\n fail(\n 'rejections',\n mutation\n ? `apply mutated its input state or input (states are frozen): ${message}`\n : `apply threw instead of returning a result: ${message}`,\n step,\n input,\n );\n return { kind: 'threw', message };\n }\n rec.ran('rejections');\n if (typeof result !== 'object' || result === null || typeof result.ok !== 'boolean') {\n fail('rejections', `apply returned ${show(result)}, not a Result`, step, input);\n return { kind: 'threw', message: 'not a result' };\n }\n if (!result.ok) {\n if (!isGameError(result.error) || !isJson(result.error)) {\n fail(\n 'rejections',\n `a rejection must carry a JSON GameError with a code and a message, got ${show(result.error)}`,\n step,\n input,\n );\n }\n return { kind: 'rejected', code: String((result.error as { code?: unknown }).code) };\n }\n const transition = deepFreeze(result.value);\n return { kind: 'accepted', transition, rngAfter: rngAfterApply(state, input, rng, transition, step) };\n };\n\n /**\n * The generator's state after applying an input. Applying twice from the same state also\n * checks that `apply` is a function of (state, input, generator): the second answer must\n * equal the first.\n */\n const rngAfterApply = (\n state: State,\n input: EngineInput<Command>,\n rng: RngState,\n first: Transition<State, Event>,\n step: number,\n ): RngState => {\n const again = restoreRng(rng);\n try {\n const second = engine.apply(state, input, again);\n rec.ran('determinism');\n const diff = second.ok ? firstDifference(first, second.value) : 'the second try was rejected';\n if (diff !== null) {\n fail(\n 'determinism',\n `apply gave two different answers for the same state, input and generator: ${diff}`,\n step,\n input,\n );\n }\n } catch (error) {\n fail(\n 'determinism',\n `apply threw on a second try with the same arguments: ${errorMessage(error)}`,\n step,\n input,\n );\n }\n return again.save();\n };\n\n const outsideCommand = (state: State, seat: SeatId, command: Command, rng: RngState, step: number): void => {\n const input = { kind: 'command' as const, seat, command };\n if (engine.sideCommand?.(state, seat, command) !== true) {\n mustReject('turns', state, input, rng, step, `a command from seat ${seat}, which isn't pending`);\n return;\n }\n const tried = attempt(state, input, rng, step);\n rec.ran('turns');\n if (tried.kind !== 'accepted') {\n fail('turns', 'declared side command was rejected', step, input);\n return;\n }\n const after = tried.transition.state,\n pendingBefore = engine.pending(state);\n if (JSON.stringify(engine.pending(after)) !== JSON.stringify(pendingBefore))\n fail('turns', 'side command changed pending decisions', step, input);\n for (const actor of pendingBefore)\n if (\n JSON.stringify(engine.decisionKey?.(state, actor) ?? engine.viewFor(state, actor)) !==\n JSON.stringify(engine.decisionKey?.(after, actor) ?? engine.viewFor(after, actor))\n )\n fail('turns', 'side command changed a pending decision key', step, input);\n };\n\n const mustReject = (\n id: EngineCheckId,\n state: State,\n input: EngineInput<Command>,\n rng: RngState,\n step: number,\n why: string,\n ): void => {\n rec.ran(id);\n const tried = attempt(state, input, rng, step);\n if (tried.kind === 'accepted') fail(id, `${why}, but the engine accepted it`, step, input);\n };\n\n // init\n let started: ReturnType<typeof engine.init>;\n const initRng = restoreRng(seedState);\n try {\n started = engine.init(setup.config, seatList, initRng);\n } catch (error) {\n fail('rejections', `init threw: ${errorMessage(error)}`, 0);\n continue;\n }\n if (!started.ok) {\n fail('outcome', `init rejected setup ${setupIndex}: ${show(started.error)}`, 0);\n continue;\n }\n let state = deepFreeze(started.value);\n let rngState = initRng.save();\n /** Requested jobs with no accepted outcome yet, each with its planned outcome. */\n const awaited = new Map<string, { kind: string; value: Json; fails: boolean }>();\n /** Jobs answered, or superseded (the engine stopped awaiting them): their outcomes must be refused. */\n const closed: string[] = [];\n const requested = new Set<string>();\n const resignAt = resignOn && gameIndex % 3 === 2 ? driver.int(0, 24) : -1;\n /** The inputs whose transitions set `startsClock`. */\n const clockStarts: number[] = [];\n\n /** `startsClock` needs `clockStart: 'transition'`, and starts the clock at most once a game. */\n const checkClockStart = (\n transition: Transition<State, Event>,\n step: number,\n input: EngineInput<Command>,\n ): void => {\n if (transition.restartsGame !== undefined) {\n rec.ran('session-hooks');\n if (typeof transition.restartsGame !== 'boolean')\n fail('session-hooks', 'restartsGame must be a boolean', step, input);\n if (transition.restartsGame) {\n if (!transition.undoBarrier || engine.outcome(transition.state) !== null)\n fail('session-hooks', 'restart requires an undo barrier and an unfinished state', step, input);\n clockStarts.length = 0;\n }\n }\n const flag: unknown = transition.startsClock;\n if (flag === undefined) return;\n rec.ran('session-hooks');\n if (typeof flag !== 'boolean')\n return fail('session-hooks', `startsClock must be a boolean, got ${show(flag)}`, step, input);\n if (!flag) return;\n if (engine.clockStart !== 'transition') {\n return fail(\n 'session-hooks',\n \"a transition sets startsClock, but the engine doesn't declare clockStart: 'transition', so runners ignore it\",\n step,\n input,\n );\n }\n if (clockStarts.length > 0) {\n fail(\n 'session-hooks',\n `startsClock is set again, after input ${clockStarts[0]} set it; it starts the clock at most once a game`,\n step,\n input,\n );\n }\n clockStarts.push(step);\n };\n\n /** `worthSaving` answers a boolean, the same one every time, for the state and for its JSON copy. */\n const checkWorthSaving = (state: State, step: number): void => {\n const hook = engine.worthSaving;\n if (hook === undefined) return;\n rec.ran('session-hooks');\n try {\n const first: unknown = hook(state);\n if (typeof first !== 'boolean')\n return fail('session-hooks', `worthSaving returned ${show(first)}, not a boolean`, step);\n const again: unknown = hook(state);\n const copy: unknown = hook(deepFreeze(jsonRoundTrip(state)));\n if (again !== first || copy !== first) {\n fail(\n 'session-hooks',\n `worthSaving answered ${String(first)}, then ${show(again)}, then ${show(copy)} for a JSON copy of the same state; it must be pure`,\n step,\n );\n }\n } catch (error) {\n const message = errorMessage(error);\n const mutation = /read[- ]only|not extensible|frozen|Cannot (assign|add|delete|define)/i.test(message);\n fail(\n 'session-hooks',\n mutation\n ? `worthSaving mutated its state (states are frozen): ${message}`\n : `worthSaving threw: ${message}`,\n step,\n );\n }\n };\n\n /** Checks every transition the engine accepts for the game: JSON, schemas, events. */\n const checkTransition = (\n transition: Transition<State, Event>,\n step: number,\n input: EngineInput<Command>,\n ): void => {\n rec.ran('json');\n if (!isJson(transition)) fail('json', `a transition isn't JSON: ${jsonProblem(transition)}`, step, input);\n checkSchema(engine.schemas.state, transition.state, 'a transition state', (m) =>\n fail('schemas', m, step, input),\n );\n for (const beat of transition.beats) {\n checkSchema(engine.schemas.event, beat.event, 'an event', (m) => fail('schemas', m, step, input));\n if (beat.state !== undefined)\n checkSchema(engine.schemas.state, beat.state, 'a beat state', (m) => fail('schemas', m, step, input));\n for (const viewer of viewers) {\n const redacted = engine.eventFor(beat.event, viewer);\n if (redacted === null) continue;\n rec.ran('json');\n if (!isJson(redacted))\n fail('json', `eventFor(event, ${viewer}) isn't JSON: ${jsonProblem(redacted)}`, step, input);\n checkSchema(engine.schemas.event, redacted, `eventFor(event, ${viewer})`, (m) =>\n fail('schemas', m, step, input),\n );\n }\n }\n };\n\n /** Commits an input: the game moves on. Returns false if the engine refused it. */\n const commit = (input: EngineInput<Command>, expected?: Transition<State, Event>): boolean => {\n const step = steps.length;\n const tried = attempt(state, input, rngState, step);\n if (tried.kind !== 'accepted') return false;\n if (expected !== undefined) {\n const diff = firstDifference(expected, tried.transition);\n if (diff !== null)\n fail('determinism', `the same input gave a different transition when committed: ${diff}`, step, input);\n }\n checkTransition(tried.transition, step, input);\n checkClockStart(tried.transition, step, input);\n if (input.kind === 'command')\n checkSchema(engine.schemas.command, input.command, 'a command', (m) => fail('schemas', m, step, input));\n if (input.kind === 'job') {\n awaited.delete(input.jobId);\n closed.push(input.jobId);\n }\n for (const job of tried.transition.jobs ?? []) {\n rec.ran('jobs');\n if (requested.has(job.id))\n fail('jobs', `job id \"${job.id}\" was requested twice; ids must never repeat within a game`, step + 1);\n requested.add(job.id);\n const spec = engine.jobs?.[job.kind];\n if (spec === undefined) {\n fail('jobs', `a transition requested job kind \"${job.kind}\", which engine.jobs doesn't declare`, step + 1);\n } else {\n checkSchema(spec.payload, job.payload, `the payload of job \"${job.id}\"`, (m) =>\n fail('schemas', m, step + 1),\n );\n }\n rec.ran('json');\n if (!isJson(job)) fail('json', `a job request isn't JSON: ${jsonProblem(job)}`, step + 1);\n // Plan the job's outcome now, as a real runner would produce it later.\n const runner = options.jobs?.[job.kind];\n let value: Json = null;\n if (runner === undefined) {\n fail('jobs', `no fake runner for job kind \"${job.kind}\"; add it to the jobs option`, step + 1);\n } else {\n try {\n value = runner(job.payload, driver);\n if (spec !== undefined)\n checkSchema(spec.result, value, `the fake runner's value for job \"${job.id}\"`, (m) =>\n fail('jobs', m, step + 1),\n );\n } catch (error) {\n fail('jobs', `the fake runner for \"${job.kind}\" threw: ${errorMessage(error)}`, step + 1);\n }\n }\n awaited.set(job.id, { kind: job.kind, value, fails: runner === undefined || driver.next() < failureRate });\n }\n steps.push({ input, transition: tried.transition, rngAfter: tried.rngAfter });\n state = tried.transition.state;\n rngState = tried.rngAfter;\n return true;\n };\n\n // Play.\n for (;;) {\n const step = steps.length;\n // Position checks.\n let pending: readonly SeatId[] = [];\n let outcome: Outcome | null = null;\n try {\n pending = engine.pending(state);\n outcome = engine.outcome(state);\n } catch (error) {\n fail('rejections', `pending or outcome threw: ${errorMessage(error)}`, step);\n break;\n }\n rec.ran('turns');\n const seatIds = seatList.map((s) => s.id);\n if (\n !Array.isArray(pending) ||\n pending.some((s) => !seatIds.includes(s)) ||\n new Set(pending).size !== pending.length\n ) {\n fail('turns', `pending() must list distinct seat ids, got ${show(pending)}`, step);\n pending = [];\n }\n if (outcome !== null && pending.length > 0)\n fail('turns', `pending() is ${show(pending)} although the game has an outcome`, step);\n\n rec.ran('json');\n if (!isJson(state)) fail('json', `the state isn't JSON: ${jsonProblem(state)}`, step);\n if (outcome !== null && !isJson(outcome)) fail('json', `the outcome isn't JSON: ${jsonProblem(outcome)}`, step);\n checkSchema(engine.schemas.state, state, 'the state', (m) => fail('schemas', m, step));\n const views = new Map<SeatId | null, View>();\n for (const viewer of viewers) {\n const view = engine.viewFor(state, viewer);\n views.set(viewer, view);\n rec.ran('json');\n if (!isJson(view)) fail('json', `viewFor(state, ${viewer}) isn't JSON: ${jsonProblem(view)}`, step);\n checkSchema(engine.schemas.view, view, `viewFor(state, ${viewer})`, (m) => fail('schemas', m, step));\n }\n const keys = seatList.map((s) => keyOf(state, s.id));\n for (const [i, key] of keys.entries()) {\n rec.ran('json');\n if (!isJson(key)) fail('json', `decisionKey(state, ${i}) isn't JSON: ${jsonProblem(key)}`, step);\n }\n if (options.invariants !== undefined) {\n rec.ran('invariants');\n try {\n const problem = invariantMessage(options.invariants(state));\n if (problem !== null) fail('invariants', `invariant violated: ${problem}`, step);\n } catch (error) {\n fail('invariants', `invariant violated: ${errorMessage(error)}`, step);\n }\n }\n checkWorthSaving(state, step);\n\n // Candidate commands for the pending seats.\n const accepted = new Map<SeatId, Command[]>();\n const acceptedTransitions = new Map<SeatId, Transition<State, Event>[]>();\n if (outcome === null) {\n for (const seat of pending) {\n let candidates: readonly Command[] = [];\n try {\n if (options.commands !== undefined) {\n candidates = sample(options.commands(state, seat, driver), CANDIDATE_CAP, driver);\n } else if (engine.ai !== undefined) {\n const level = driver.int(0, engine.ai.levels.length);\n candidates = [\n engine.ai.choose(views.get(seat) as View, seat, engine.ai.levels[level] ?? '', cloneRng(driver)),\n ];\n }\n } catch (error) {\n fail(\n options.commands === undefined ? 'ai' : 'outcome',\n `generating commands threw: ${errorMessage(error)}`,\n step,\n );\n }\n const ok: Command[] = [];\n const okTransitions: Transition<State, Event>[] = [];\n for (const command of candidates) {\n rec.ran('json');\n if (!isJson(command)) fail('json', `a command isn't JSON: ${jsonProblem(command)}`, step);\n const tried = attempt(state, { kind: 'command', seat, command }, rngState, step);\n if (tried.kind === 'accepted') {\n ok.push(command);\n okTransitions.push(tried.transition);\n }\n }\n accepted.set(seat, ok);\n acceptedTransitions.set(seat, okTransitions);\n }\n }\n positions.push({ state, rng: rngState, pending, keys, accepted });\n\n if (outcome !== null) {\n // The game is over: validate the outcome and refuse late inputs.\n rec.ran('outcome');\n const problem = outcomeProblem(outcome, seatList);\n if (problem !== null) fail('outcome', `invalid outcome: ${problem}; outcome ${show(outcome)}`, step);\n const last = [...steps].reverse().find((s) => s.input.kind === 'command')?.input;\n for (const seat of seatList) {\n if (last !== undefined && last.kind === 'command') {\n mustReject(\n 'outcome',\n state,\n { kind: 'command', seat: seat.id, command: last.command },\n rngState,\n step,\n `a command from seat ${seat.id} after the outcome`,\n );\n }\n if (options.commands !== undefined) {\n const generate = options.commands;\n for (const command of sample(\n safely(() => generate(state, seat.id, driver)),\n 3,\n driver,\n )) {\n mustReject(\n 'outcome',\n state,\n { kind: 'command', seat: seat.id, command },\n rngState,\n step,\n `a command from seat ${seat.id} after the outcome`,\n );\n }\n }\n mustReject(\n 'outcome',\n state,\n { kind: 'resign', seat: seat.id },\n rngState,\n step,\n `a resign from seat ${seat.id} after the outcome`,\n );\n }\n break;\n }\n if (step >= maxSteps) {\n rec.ran('outcome');\n if (requireOutcome)\n fail('outcome', `no outcome after ${maxSteps} inputs (raise maxSteps, or set requireOutcome: false)`, step);\n break;\n }\n\n // Turn discipline: other seats' commands, re-addressed, and their own candidates.\n const pendingCommands = [...accepted.values()].flat();\n const outsiders = [...seatList.map((s) => s.id).filter((id) => !pending.includes(id)), -1, seatList.length];\n for (const seat of outsiders) {\n for (const command of sample(pendingCommands, 2, driver)) {\n outsideCommand(state, seat, command, rngState, step);\n }\n const generate = options.commands;\n if (generate !== undefined && seat >= 0 && seat < seatList.length) {\n for (const command of sample(\n safely(() => generate(state, seat, driver)),\n 3,\n driver,\n )) {\n outsideCommand(state, seat, command, rngState, step);\n }\n }\n }\n\n // Resignation, in turn and out of turn.\n if (resignOn) {\n const notPending = seatList.filter((s) => !pending.includes(s.id));\n const probes = [\n ...sample(notPending, 1, driver),\n ...sample(\n seatList.filter((s) => pending.includes(s.id)),\n 1,\n driver,\n ),\n ];\n for (const seat of probes) {\n rec.ran('resign');\n const input: EngineInput<Command> = { kind: 'resign', seat: seat.id };\n const tried = attempt(state, input, rngState, step);\n if (tried.kind === 'rejected') {\n const when = pending.includes(seat.id) ? 'in turn' : 'out of turn';\n fail('resign', `a resign from seat ${seat.id} (${when}) was rejected with \"${tried.code}\"`, step, input);\n } else if (tried.kind === 'accepted') {\n checkTransition(tried.transition, step, input);\n }\n }\n }\n\n // AI legality.\n if (engine.ai !== undefined) {\n for (const seat of pending) {\n for (const level of engine.ai.levels) {\n rec.ran('ai');\n let command: Command;\n try {\n command = engine.ai.choose(views.get(seat) as View, seat, level, cloneRng(driver));\n } catch (error) {\n fail('ai', `ai.choose threw for seat ${seat} at level \"${level}\": ${errorMessage(error)}`, step);\n continue;\n }\n const input: EngineInput<Command> = { kind: 'command', seat, command };\n const tried = attempt(state, input, rngState, step);\n if (tried.kind === 'rejected')\n fail(\n 'ai',\n `ai.choose (level \"${level}\") chose a command the engine rejects with \"${tried.code}\"`,\n step,\n input,\n );\n }\n }\n }\n\n // Jobs: an unknown id and closed ids are refused. An awaited job accepts both a value and\n // a failure; when the engine refuses both, it has superseded the job, which stays closed.\n mustReject(\n 'jobs',\n state,\n { kind: 'job', jobId: 'conformance:never-requested', outcome: { ok: true, value: null } },\n rngState,\n step,\n 'a job outcome for an id no transition requested',\n );\n for (const jobId of closed.slice(-3)) {\n mustReject(\n 'jobs',\n state,\n { kind: 'job', jobId, outcome: { ok: false, reason: 'timeout' } },\n rngState,\n step,\n `an outcome for job \"${jobId}\", which was already answered or superseded`,\n );\n }\n const answerable: string[] = [];\n for (const [jobId, job] of [...awaited]) {\n rec.ran('jobs');\n const success: EngineInput<Command> = { kind: 'job', jobId, outcome: { ok: true, value: job.value } };\n const failed: EngineInput<Command> = { kind: 'job', jobId, outcome: { ok: false, reason: 'timeout' } };\n const a = attempt(state, success, rngState, step).kind === 'accepted';\n const b = attempt(state, failed, rngState, step).kind === 'accepted';\n if (a && b) answerable.push(jobId);\n else if (a || b) {\n fail(\n 'jobs',\n `the engine accepts a ${a ? 'successful' : 'failed'} outcome for job \"${jobId}\" but refuses a ${a ? 'failed' : 'successful'} one; while it awaits a job it must accept both (it decides what a failure means)`,\n step,\n a ? failed : success,\n );\n answerable.push(jobId);\n } else {\n awaited.delete(jobId);\n closed.push(jobId);\n }\n }\n\n // No leaks: perturb what each viewer can't see; views and keys must not move.\n const perturbed: { viewer: SeatId | null; state: State }[] = [];\n if (options.perturbHidden !== undefined) {\n for (const viewer of viewers) {\n for (let p = 0; p < perturbations; p += 1) {\n let other: State;\n try {\n other = deepFreeze(options.perturbHidden(state, viewer, perturbRng));\n } catch (error) {\n fail('no-leak', `perturbHidden threw for viewer ${viewer}: ${errorMessage(error)}`, step);\n continue;\n }\n rec.ran('no-leak');\n perturbedTotal += 1;\n if (!deepEqual(other, state)) perturbedChanged += 1;\n const diff = firstDifference(views.get(viewer), engine.viewFor(other, viewer));\n if (diff !== null) {\n fail(\n 'no-leak',\n `viewFor(state, ${viewer}) changed when only hidden information changed: ${diff}`,\n step,\n );\n continue;\n }\n if (viewer !== null) {\n const keyDiff = firstDifference(keys[viewer], keyOf(other, viewer));\n if (keyDiff !== null) {\n fail(\n 'no-leak',\n `decisionKey(state, ${viewer}) changed when only hidden information changed: ${keyDiff}`,\n step,\n );\n continue;\n }\n }\n perturbed.push({ viewer, state: other });\n }\n }\n }\n\n // Choose the next input.\n const awaitedIds = answerable;\n const movable = pending.filter((seat) => (accepted.get(seat) ?? []).length > 0);\n let input: EngineInput<Command> | null = null;\n let expected: Transition<State, Event> | undefined;\n if (step === resignAt && seatList.length > 0) {\n const outside = seatList.filter((s) => !pending.includes(s.id));\n const seat = (outside.length > 0 ? outside : seatList)[\n driver.int(0, outside.length > 0 ? outside.length : seatList.length)\n ];\n if (seat !== undefined) input = { kind: 'resign', seat: seat.id };\n } else if (timeoutOn && pending.length > 0 && driver.next() < 0.1) {\n input = { kind: 'timeout', seat: pending[driver.int(0, pending.length)] ?? 0 };\n } else if (seatChangesOn && driver.next() < 0.05) {\n const seat = seatList[driver.int(0, seatList.length)];\n if (seat !== undefined) {\n const toAi = seat.controller === 'human';\n const level = engine.ai?.levels[0];\n input = {\n kind: 'seat',\n seat: seat.id,\n change: toAi\n ? {\n type: 'controller',\n controller: 'ai',\n name: `${seat.name} (AI)`,\n ...(level === undefined ? {} : { aiLevel: level }),\n }\n : { type: 'controller', controller: 'human', name: seat.name },\n };\n }\n }\n if (input === null) {\n const choices = movable.length + awaitedIds.length;\n if (choices === 0) {\n rec.ran('outcome');\n fail(\n 'outcome',\n pending.length === 0\n ? 'nobody can move: pending() is empty, no job is awaited, and there is no outcome'\n : `no candidate command was accepted for pending seats ${show(pending)}; check the commands generator or the engine`,\n step,\n );\n break;\n }\n const pick = driver.int(0, choices);\n if (pick < movable.length) {\n const seat = movable[pick] as SeatId;\n const options_ = accepted.get(seat) ?? [];\n const index = driver.int(0, options_.length);\n input = { kind: 'command', seat, command: options_[index] as Command };\n expected = acceptedTransitions.get(seat)?.[index];\n } else {\n const jobId = awaitedIds[pick - movable.length] as string;\n const job = awaited.get(jobId);\n input =\n job === undefined || job.fails\n ? { kind: 'job', jobId, outcome: { ok: false, reason: 'failed' } }\n : { kind: 'job', jobId, outcome: { ok: true, value: job.value } };\n }\n }\n\n const before = state;\n const beforeRng = rngState;\n if (!commit(input, expected)) {\n const id: EngineCheckId =\n input.kind === 'resign'\n ? 'resign'\n : input.kind === 'job'\n ? 'jobs'\n : input.kind === 'command'\n ? 'determinism'\n : 'turns';\n const why =\n input.kind === 'command'\n ? 'a command it had just accepted from the same state and generator'\n : `a ${input.kind} input, which is valid whenever the game is in progress`;\n fail(id, `the engine rejected ${why}`, step, input);\n break;\n }\n const committed = steps[steps.length - 1] as Step<State, Command, Event>;\n\n // Events: keep authorized new knowledge fixed while varying everything still hidden.\n for (const { viewer, state: other } of perturbed) {\n let tried = attempt(other, input, beforeRng, step);\n const trace = (t: Transition<State, Event>) => ({\n final: engine.viewFor(t.state, viewer),\n beats: t.beats\n .map((b) => ({\n event: engine.eventFor(b.event, viewer),\n view: b.state === undefined ? null : engine.viewFor(b.state, viewer),\n }))\n .filter((b) => b.event !== null),\n });\n const a = trace(committed.transition);\n const comparable = (b: ReturnType<typeof trace>): boolean =>\n deepEqual(a.final, b.final) &&\n (a.beats.length !== b.beats.length ||\n deepEqual(\n a.beats.map((x) => x.view),\n b.beats.map((x) => x.view),\n ));\n if (\n options.perturbTransition !== undefined &&\n (tried.kind !== 'accepted' || !comparable(trace(tried.transition)))\n ) {\n let kept: State;\n try {\n kept = deepFreeze(\n options.perturbTransition(before, viewer, cloneRng(perturbRng), {\n input,\n states: [\n committed.transition.state,\n ...committed.transition.beats.flatMap((beat) => (beat.state === undefined ? [] : [beat.state])),\n ],\n }),\n );\n if (deepEqual(kept, before)) continue;\n if (\n !deepEqual(engine.viewFor(before, viewer), engine.viewFor(kept, viewer)) ||\n (viewer !== null && !deepEqual(keyOf(before, viewer), keyOf(kept, viewer)))\n ) {\n fail(\n 'no-leak',\n `transition perturbation changed information already visible to viewer ${viewer}`,\n step,\n input,\n );\n continue;\n }\n } catch (error) {\n fail(\n 'no-leak',\n `transition perturbation threw for viewer ${viewer}: ${errorMessage(error)}`,\n step,\n input,\n );\n continue;\n }\n tried = attempt(kept, input, beforeRng, step);\n }\n if (tried.kind !== 'accepted') continue;\n const b = trace(tried.transition);\n if (!deepEqual(a.final, b.final)) continue;\n if (\n a.beats.length === b.beats.length &&\n !deepEqual(\n a.beats.map((x) => x.view),\n b.beats.map((x) => x.view),\n )\n )\n continue;\n rec.ran('no-leak');\n const diff = firstDifference(a.beats, b.beats);\n if (diff !== null) {\n fail(\n 'no-leak',\n `eventFor(event, ${viewer}) differs between two transitions that look identical to that viewer: ${diff}`,\n step,\n input,\n );\n }\n }\n\n // Assistance: a valid kind or nothing, and what the spec says when it says.\n {\n rec.ran('assist');\n const got = committed.transition.assist;\n if (got !== undefined && !ENGINE_ASSISTS.includes(got)) {\n fail(\n 'assist',\n `assist must be 'hint', 'check' or 'reveal' (undo is the platform's own), got ${show(got)}`,\n step,\n input,\n );\n } else if (options.assist !== undefined) {\n const want = options.assist(input, before, committed.transition.state) ?? undefined;\n if (want !== got)\n fail(\n 'assist',\n `the spec makes this input's assistance ${show(want ?? null)}, but the transition has assist: ${show(got ?? null)}`,\n step,\n input,\n );\n }\n }\n\n // Undo barrier oracle.\n if (options.undoBarrier !== undefined) {\n rec.ran('undo');\n const want = options.undoBarrier(input, before, committed.transition.state);\n const got = committed.transition.undoBarrier === true;\n if (want !== got) {\n fail(\n 'undo',\n `the spec makes this input ${want ? 'an undo barrier' : 'undoable'}, but the transition has undoBarrier: ${String(committed.transition.undoBarrier)}`,\n step,\n input,\n );\n }\n }\n }\n totalSteps += steps.length;\n const final = positions[positions.length - 1];\n if (final === undefined) continue;\n\n // Determinism: replay the same inputs from the same seed and compare everything.\n {\n rec.ran('determinism');\n const rng = restoreRng(seedState);\n const again = engine.init(setup.config, seatList, rng);\n let ok = again.ok && deepEqual(again.value, positions[0]?.state);\n if (!ok)\n fail(\n 'determinism',\n `init gave a different state for the same seed: ${again.ok ? firstDifference(positions[0]?.state, again.value) : 'rejected'}`,\n 0,\n );\n let current = again.ok ? again.value : null;\n for (const [i, s] of steps.entries()) {\n if (!ok || current === null) break;\n try {\n const result = engine.apply(current, s.input, rng);\n if (!result.ok) {\n fail('determinism', `the same input was rejected on a second run: ${show(result.error)}`, i, s.input);\n ok = false;\n break;\n }\n const diff = firstDifference(s.transition, result.value) ?? firstDifference(s.rngAfter, rng.save());\n if (diff !== null) {\n fail('determinism', `a second run from the same seed diverged: ${diff}`, i, s.input);\n ok = false;\n break;\n }\n current = result.value.state;\n const outcomeDiff = firstDifference(\n engine.outcome(positions[i + 1]?.state as State),\n engine.outcome(current),\n );\n if (outcomeDiff !== null) fail('determinism', `outcome differs on a second run: ${outcomeDiff}`, i + 1);\n } catch (error) {\n fail('determinism', `apply threw on a second run: ${errorMessage(error)}`, i, s.input);\n ok = false;\n }\n }\n }\n\n // Replay through the replay format, after a JSON round trip.\n {\n rec.ran('replay');\n const replay = replayUpTo(steps.length);\n if (!isJson(replay)) {\n fail('replay', \"the replay isn't JSON\", steps.length);\n } else {\n try {\n const result = replayEngine(engine, jsonRoundTrip(replay));\n if (!result.ok) fail('replay', `the replay doesn't apply: ${show(result.error)}`, steps.length);\n else {\n const diff =\n firstDifference(final.state, result.value.state) ??\n firstDifference(engine.outcome(final.state), result.value.outcome) ??\n firstDifference(\n steps.map((s) => s.transition.undoBarrier === true),\n result.value.barriers,\n );\n if (diff !== null) fail('replay', `the replay ends somewhere else: ${diff}`, steps.length);\n }\n // The session runner's own replay, which undo and restore rely on in production.\n const runner = runnerReplayEngine(engine, jsonRoundTrip(replay));\n if (!runner.ok)\n fail('replay', `the session runner's replay doesn't apply: ${show(runner.error)}`, steps.length);\n else {\n const diff =\n firstDifference(final.state, runner.value.state) ?? firstDifference(final.rng, runner.value.rng);\n if (diff !== null)\n fail('replay', `the session runner's replay ends somewhere else: ${diff}`, steps.length);\n }\n } catch (error) {\n fail('replay', `replaying threw: ${errorMessage(error)}`, steps.length);\n }\n }\n }\n\n // Save round trip: save at a random position, restore after JSON, and continue.\n {\n rec.ran('saves');\n const k = driver.int(0, positions.length);\n const at = positions[k] as Position<State, Command>;\n const envelope = {\n gameId,\n build: '0.0.0-test',\n rulesVersion: engine.rulesVersion,\n saveVersion: options.saves?.version ?? 1,\n rng: at.rng,\n seats: seatList,\n state: at.state,\n replay: replayUpTo(k),\n };\n if (!isJson(envelope)) fail('saves', \"the save envelope isn't JSON\", k);\n else {\n const restored = jsonRoundTrip(envelope);\n const decoded = await validate(engine.schemas.state, restored.state);\n if (!decoded.ok) fail('saves', `a saved state fails schemas.state: ${formatIssues(decoded.issues)}`, k);\n else if (!deepEqual(decoded.value, at.state)) {\n fail('saves', `schemas.state changed a valid saved state: ${firstDifference(at.state, decoded.value)}`, k);\n } else {\n try {\n const rng = restoreRng(restored.rng);\n let current = decoded.value;\n for (const s of steps.slice(k)) {\n const result = engine.apply(current, s.input, rng);\n if (!result.ok) throw new Error(`input rejected after restoring: ${show(result.error)}`);\n current = result.value.state;\n }\n const diff = firstDifference(final.state, current);\n if (diff !== null)\n fail('saves', `a game restored from a save at input ${k} ended differently: ${diff}`, k);\n } catch (error) {\n fail('saves', `continuing a restored save failed: ${errorMessage(error)}`, k);\n }\n }\n }\n }\n\n // Staleness: within a run of equal decision keys, the seat's accepted commands stay the same.\n for (const seat of seatList.map((s) => s.id)) {\n let runStart = 0;\n for (let i = 1; i <= positions.length; i += 1) {\n const same = i < positions.length && deepEqual(positions[i]?.keys[seat], positions[i - 1]?.keys[seat]);\n if (same) continue;\n const run = positions.slice(runStart, i).map((p, offset) => ({ p, index: runStart + offset }));\n const turns = run.filter(({ p }) => p.pending.includes(seat) && engine.outcome(p.state) === null);\n const [base, ...later] = turns;\n if (base !== undefined) {\n for (const { p, index } of later.slice(0, 4)) {\n rec.ran('stale');\n const rejectedAt = (cmds: readonly Command[], pos: Position<State, Command>, where: number) =>\n cmds.filter(\n (command) =>\n attempt(pos.state, { kind: 'command', seat, command }, pos.rng, where).kind !== 'accepted',\n );\n const lost = rejectedAt(base.p.accepted.get(seat) ?? [], p, index);\n const gained = rejectedAt(p.accepted.get(seat) ?? [], base.p, base.index);\n if (lost.length > 0 || gained.length > 0) {\n const command = lost[0] ?? gained[0];\n fail(\n 'stale',\n `decisionKey for seat ${seat} is the same after input ${base.index} and after input ${index}, but command ${show(command)} is ${lost.length > 0 ? 'accepted at the first and rejected at the second' : 'rejected at the first and accepted at the second'}; the room would let a stale command through`,\n index,\n { kind: 'command', seat, command },\n );\n }\n }\n }\n runStart = i;\n }\n }\n\n // Undo by replay, after every barrier and a few other inputs. What undo must do is\n // worked out from the recorded game; the helper works it out by replaying the log.\n {\n const humanAt: boolean[] = [];\n let seatsNow = seatList;\n for (const s of steps) {\n const input = s.input;\n humanAt.push(input.kind === 'command' && seatsNow.find((x) => x.id === input.seat)?.controller === 'human');\n if (input.kind === 'seat' && input.change.type === 'controller') {\n const { change } = input;\n seatsNow = seatsNow.map((x) =>\n x.id === input.seat ? { id: x.id, name: change.name, controller: change.controller } : x,\n );\n }\n }\n const barrierAt = steps.flatMap((s, i) => (s.transition.undoBarrier === true ? [i] : []));\n const assistAt = steps.flatMap((s, i) => (s.transition.assist === undefined ? [] : [i]));\n const others = sample(\n steps.map((_, i) => i).filter((i) => !barrierAt.includes(i)),\n 4,\n driver,\n );\n for (const k of new Set([...barrierAt, ...assistAt, ...others])) {\n rec.ran('undo');\n const cut = humanAt.slice(0, k + 1).lastIndexOf(true);\n const blocked = cut >= 0 && steps.slice(cut, k + 1).some((s) => s.transition.undoBarrier === true);\n try {\n const result = undoByReplay(engine, replayUpTo(k + 1));\n if (cut < 0) {\n if (result.ok || result.error.code !== 'NothingToUndo')\n fail(\n 'undo',\n `after input ${k} no human command is left to undo, but undo answered ${show(result.ok ? 'ok' : result.error.code)}`,\n k + 1,\n );\n } else if (blocked) {\n if (result.ok)\n fail(\n 'undo',\n `undo after input ${k} would step back past an undo barrier (replay doesn't reproduce the barrier)`,\n k + 1,\n );\n } else if (!result.ok) {\n fail(\n 'undo',\n `undo after input ${k} was refused with \"${result.error.code}\" although no barrier lies in the way`,\n k + 1,\n );\n } else {\n const diff = firstDifference(positions[cut]?.state, result.value.state);\n if (diff !== null)\n fail('undo', `undo after input ${k} didn't restore the state before input ${cut}: ${diff}`, k + 1);\n rec.ran('assist');\n const flagged = steps\n .slice(0, k + 1)\n .flatMap((s) => (s.transition.assist === undefined ? [] : [s.transition.assist]));\n const missing = flagged.filter((kind) => !result.value.assists.includes(kind));\n if (missing.length > 0)\n fail(\n 'assist',\n `after an undo, replay no longer reports the assistance ${show(missing)} flagged during play`,\n k + 1,\n );\n }\n } catch (error) {\n fail('undo', `undo by replay threw: ${errorMessage(error)}`, k + 1);\n }\n }\n }\n }\n }\n\n // Save fixtures and the migration chain.\n if (options.saves !== undefined) {\n const { version, migrations, fixtures } = options.saves;\n const first = options.saves.firstVersion ?? 1;\n for (const m of migrations) {\n rec.ran('saves');\n if (m.to !== m.from + 1)\n rec.fail('saves', { message: `migration ${m.from} → ${m.to} must step exactly one version` });\n }\n for (let v = first; v <= version; v += 1) {\n rec.ran('saves');\n if (!fixtures.some((f) => f.saveVersion === v)) {\n rec.fail('saves', {\n message: `no save fixture for save version ${v}; keep one for every version ever shipped`,\n });\n }\n }\n for (const fixture of fixtures) {\n rec.ran('saves');\n try {\n const migrated = runMigrations(migrations, fixture.state, fixture.saveVersion, version);\n if (!migrated.ok) {\n rec.fail('saves', {\n message: `save fixture \"${fixture.name}\" (v${fixture.saveVersion}) doesn't migrate: ${show(migrated.error)}`,\n });\n continue;\n }\n const decoded = await validate(engine.schemas.state, migrated.value);\n if (!decoded.ok) {\n rec.fail('saves', {\n message: `save fixture \"${fixture.name}\" (v${fixture.saveVersion}) migrates to a state that fails schemas.state: ${formatIssues(decoded.issues)}`,\n });\n }\n } catch (error) {\n rec.fail('saves', { message: `migrating save fixture \"${fixture.name}\" threw: ${errorMessage(error)}` });\n }\n }\n } else {\n rec.note('saves', 'round trip only; give saves.fixtures to check the migration chain');\n }\n\n await Promise.all(validations);\n if (options.perturbHidden !== undefined && perturbedTotal > 0 && perturbedChanged === 0) {\n rec.fail('no-leak', {\n message: `perturbHidden never changed a state in ${perturbedTotal} tries, so the no-leak check proved nothing; it must reshuffle what the viewer can't see`,\n });\n }\n return rec.report(options.name ?? 'engine', totalGames, totalSteps);\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Json, type SeatId } from '@joystyk/contracts';
|
|
2
|
+
import type { EngineConformanceOptions } from '../engine-conformance.js';
|
|
3
|
+
export type DiceRaceState = {
|
|
4
|
+
readonly target: number;
|
|
5
|
+
readonly scores: readonly number[];
|
|
6
|
+
readonly turn: SeatId;
|
|
7
|
+
/** The id of the throw job being awaited, if any. */
|
|
8
|
+
readonly awaiting: string | null;
|
|
9
|
+
readonly throws: number;
|
|
10
|
+
readonly resigned: SeatId | null;
|
|
11
|
+
readonly winner: SeatId | null;
|
|
12
|
+
};
|
|
13
|
+
export type DiceRaceCommand = {
|
|
14
|
+
readonly type: 'roll';
|
|
15
|
+
};
|
|
16
|
+
export type DiceRaceEvent = {
|
|
17
|
+
readonly type: 'rolling';
|
|
18
|
+
readonly seat: SeatId;
|
|
19
|
+
} | {
|
|
20
|
+
readonly type: 'rolled';
|
|
21
|
+
readonly seat: SeatId;
|
|
22
|
+
readonly faces: readonly number[];
|
|
23
|
+
} | {
|
|
24
|
+
readonly type: 'fumbled';
|
|
25
|
+
readonly seat: SeatId;
|
|
26
|
+
} | {
|
|
27
|
+
readonly type: 'resigned';
|
|
28
|
+
readonly seat: SeatId;
|
|
29
|
+
};
|
|
30
|
+
export type DiceRaceConfig = {
|
|
31
|
+
readonly target: number;
|
|
32
|
+
};
|
|
33
|
+
export type ThrowPayload = {
|
|
34
|
+
readonly seed: number;
|
|
35
|
+
readonly dice: number;
|
|
36
|
+
};
|
|
37
|
+
/** The job runner: two dice from the payload's seed, so a re-run gives the same faces. */
|
|
38
|
+
export declare const throwDice: (payload: Json) => Json;
|
|
39
|
+
export declare const diceRace: import("@joystyk/contracts").DiscreteEngine<DiceRaceState, DiceRaceCommand, DiceRaceState, DiceRaceEvent, DiceRaceConfig>;
|
|
40
|
+
/** Conformance options for the Dice Race fixture. */
|
|
41
|
+
export declare const diceRaceConformance: EngineConformanceOptions<DiceRaceState, DiceRaceCommand, DiceRaceConfig>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { guardSchema } from "../builders.js";
|
|
2
|
+
import { isArrayOf, isInt, isRecord, isSeat, isSeatOrNull, rankPlaces } from "./guards.js";
|
|
3
|
+
import { defineEngine, err, gameError, ok } from "@joystyk/contracts";
|
|
4
|
+
import { createRng } from "@joystyk/kit";
|
|
5
|
+
//#region src/fixtures/dice-race.ts
|
|
6
|
+
var isFaces = (v) => isArrayOf(v, (n) => isInt(n, 1, 6)) && v.length === 2;
|
|
7
|
+
var isPayload = (v) => isRecord(v) && isInt(v["seed"], 0) && v["dice"] === 2;
|
|
8
|
+
var isState = (v) => isRecord(v) && isInt(v["target"], 1) && isArrayOf(v["scores"], (n) => isInt(n, 0)) && isSeat(v["turn"]) && (v["awaiting"] === null || typeof v["awaiting"] === "string") && isInt(v["throws"], 0) && isSeatOrNull(v["resigned"]) && isSeatOrNull(v["winner"]);
|
|
9
|
+
var isEvent = (v) => isRecord(v) && isSeat(v["seat"]) && ([
|
|
10
|
+
"rolling",
|
|
11
|
+
"fumbled",
|
|
12
|
+
"resigned"
|
|
13
|
+
].includes(String(v["type"])) || v["type"] === "rolled" && isFaces(v["faces"]));
|
|
14
|
+
/** The job runner: two dice from the payload's seed, so a re-run gives the same faces. */
|
|
15
|
+
var throwDice = (payload) => {
|
|
16
|
+
const seed = isPayload(payload) ? payload.seed : 0;
|
|
17
|
+
const rng = createRng(`dice-race:${seed}`);
|
|
18
|
+
return [rng.int(1, 7), rng.int(1, 7)];
|
|
19
|
+
};
|
|
20
|
+
var over = (s) => s.winner !== null || s.resigned !== null;
|
|
21
|
+
var diceRace = defineEngine({
|
|
22
|
+
rulesVersion: 1,
|
|
23
|
+
schemas: {
|
|
24
|
+
config: guardSchema((v) => isRecord(v) && isInt(v["target"], 1, 200), "Expected { target }."),
|
|
25
|
+
command: guardSchema((v) => isRecord(v) && v["type"] === "roll", "Expected { type: \"roll\" }."),
|
|
26
|
+
state: guardSchema(isState, "Not a Dice Race state."),
|
|
27
|
+
view: guardSchema(isState, "Not a Dice Race view."),
|
|
28
|
+
event: guardSchema(isEvent, "Not a Dice Race event.")
|
|
29
|
+
},
|
|
30
|
+
jobs: { throw: {
|
|
31
|
+
where: "either",
|
|
32
|
+
payload: guardSchema(isPayload, "Expected { seed, dice: 2 }."),
|
|
33
|
+
result: guardSchema(isFaces, "Expected two faces from 1 to 6."),
|
|
34
|
+
timeoutMs: 5e3
|
|
35
|
+
} },
|
|
36
|
+
init: (config, seats, rng) => seats.length < 1 || seats.length > 2 ? err(gameError("Seats", "Dice Race takes one or two players.")) : ok({
|
|
37
|
+
target: config.target,
|
|
38
|
+
scores: seats.map(() => 0),
|
|
39
|
+
turn: rng.int(0, seats.length),
|
|
40
|
+
awaiting: null,
|
|
41
|
+
throws: 0,
|
|
42
|
+
resigned: null,
|
|
43
|
+
winner: null
|
|
44
|
+
}),
|
|
45
|
+
apply: (state, input, rng) => {
|
|
46
|
+
if (over(state)) return err(gameError("GameOver", "The game is over."));
|
|
47
|
+
switch (input.kind) {
|
|
48
|
+
case "resign":
|
|
49
|
+
if (state.scores[input.seat] === void 0) return err(gameError("NoSuchSeat", "There is no such seat."));
|
|
50
|
+
return ok({
|
|
51
|
+
state: {
|
|
52
|
+
...state,
|
|
53
|
+
resigned: input.seat,
|
|
54
|
+
awaiting: null
|
|
55
|
+
},
|
|
56
|
+
beats: [{ event: {
|
|
57
|
+
type: "resigned",
|
|
58
|
+
seat: input.seat
|
|
59
|
+
} }]
|
|
60
|
+
});
|
|
61
|
+
case "command": {
|
|
62
|
+
if (state.awaiting !== null || input.seat !== state.turn) return err(gameError("NotYourTurn", "It's not your turn."));
|
|
63
|
+
const id = `throw-${state.throws + 1}`;
|
|
64
|
+
return ok({
|
|
65
|
+
state: {
|
|
66
|
+
...state,
|
|
67
|
+
awaiting: id,
|
|
68
|
+
throws: state.throws + 1
|
|
69
|
+
},
|
|
70
|
+
beats: [{ event: {
|
|
71
|
+
type: "rolling",
|
|
72
|
+
seat: input.seat
|
|
73
|
+
} }],
|
|
74
|
+
jobs: [{
|
|
75
|
+
id,
|
|
76
|
+
kind: "throw",
|
|
77
|
+
payload: {
|
|
78
|
+
seed: rng.int(0, 2147483647),
|
|
79
|
+
dice: 2
|
|
80
|
+
}
|
|
81
|
+
}]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
case "job": {
|
|
85
|
+
if (input.jobId !== state.awaiting) return err(gameError("NotAwaited", "That throw is no longer awaited."));
|
|
86
|
+
const seat = state.turn;
|
|
87
|
+
if (!input.outcome.ok) return ok({
|
|
88
|
+
state: {
|
|
89
|
+
...state,
|
|
90
|
+
awaiting: null
|
|
91
|
+
},
|
|
92
|
+
beats: [{ event: {
|
|
93
|
+
type: "fumbled",
|
|
94
|
+
seat
|
|
95
|
+
} }]
|
|
96
|
+
});
|
|
97
|
+
const faces = input.outcome.value;
|
|
98
|
+
if (!isFaces(faces)) return err(gameError("BadThrow", "The dice landed strangely."));
|
|
99
|
+
const score = (state.scores[seat] ?? 0) + faces[0] + faces[1];
|
|
100
|
+
const scores = state.scores.map((s, i) => i === seat ? score : s);
|
|
101
|
+
const winner = score >= state.target ? seat : null;
|
|
102
|
+
return ok({
|
|
103
|
+
state: {
|
|
104
|
+
...state,
|
|
105
|
+
scores,
|
|
106
|
+
awaiting: null,
|
|
107
|
+
winner,
|
|
108
|
+
turn: (seat + 1) % state.scores.length
|
|
109
|
+
},
|
|
110
|
+
beats: [{ event: {
|
|
111
|
+
type: "rolled",
|
|
112
|
+
seat,
|
|
113
|
+
faces
|
|
114
|
+
} }]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
default: return err(gameError("Unsupported", "Dice Race has no clocks or seat changes."));
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
pending: (state) => over(state) || state.awaiting !== null ? [] : [state.turn],
|
|
121
|
+
viewFor: (state) => state,
|
|
122
|
+
eventFor: (event) => event,
|
|
123
|
+
outcome: (state) => {
|
|
124
|
+
if (!over(state)) return null;
|
|
125
|
+
if (state.scores.length === 1) return {
|
|
126
|
+
result: state.resigned === null ? "completed" : "abandoned",
|
|
127
|
+
placements: [{
|
|
128
|
+
seat: 0,
|
|
129
|
+
place: 1,
|
|
130
|
+
score: state.scores[0] ?? 0
|
|
131
|
+
}],
|
|
132
|
+
summary: `Reached ${state.target} in ${state.throws} throws.`
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
result: "won",
|
|
136
|
+
placements: rankPlaces(state.scores, state.resigned),
|
|
137
|
+
summary: "The race is over."
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
/** Conformance options for the Dice Race fixture. */
|
|
142
|
+
var diceRaceConformance = {
|
|
143
|
+
name: "dice-race",
|
|
144
|
+
configs: [{ target: 30 }],
|
|
145
|
+
seatCounts: [1, 2],
|
|
146
|
+
commands: () => [{ type: "roll" }],
|
|
147
|
+
jobs: { throw: throwDice },
|
|
148
|
+
jobFailureRate: .2
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
export { diceRace, diceRaceConformance, throwDice };
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=dice-race.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dice-race.js","names":[],"sources":["../../src/fixtures/dice-race.ts"],"sourcesContent":["// Dice Race: one or two players race to a target score. Rolling requests a `throw` job, as\n// Greed's physics dice do; nobody can act until the job's outcome arrives. A failed throw lets\n// the same player roll again. It exercises job requests, awaited job ids and failures.\n\nimport { defineEngine, err, gameError, ok, type Json, type SeatId } from '@joystyk/contracts';\nimport { createRng } from '@joystyk/kit';\nimport { guardSchema } from '../builders.ts';\nimport type { EngineConformanceOptions } from '../engine-conformance.ts';\nimport { isArrayOf, isInt, isRecord, isSeat, isSeatOrNull, rankPlaces } from './guards.ts';\n\nexport type DiceRaceState = {\n readonly target: number;\n readonly scores: readonly number[];\n readonly turn: SeatId;\n /** The id of the throw job being awaited, if any. */\n readonly awaiting: string | null;\n readonly throws: number;\n readonly resigned: SeatId | null;\n readonly winner: SeatId | null;\n};\nexport type DiceRaceCommand = { readonly type: 'roll' };\nexport type DiceRaceEvent =\n | { readonly type: 'rolling'; readonly seat: SeatId }\n | { readonly type: 'rolled'; readonly seat: SeatId; readonly faces: readonly number[] }\n | { readonly type: 'fumbled'; readonly seat: SeatId }\n | { readonly type: 'resigned'; readonly seat: SeatId };\nexport type DiceRaceConfig = { readonly target: number };\nexport type ThrowPayload = { readonly seed: number; readonly dice: number };\n\nconst isFaces = (v: unknown): v is number[] => isArrayOf(v, (n): n is number => isInt(n, 1, 6)) && v.length === 2;\nconst isPayload = (v: unknown): v is ThrowPayload => isRecord(v) && isInt(v['seed'], 0) && v['dice'] === 2;\nconst isState = (v: unknown): v is DiceRaceState =>\n isRecord(v) &&\n isInt(v['target'], 1) &&\n isArrayOf(v['scores'], (n): n is number => isInt(n, 0)) &&\n isSeat(v['turn']) &&\n (v['awaiting'] === null || typeof v['awaiting'] === 'string') &&\n isInt(v['throws'], 0) &&\n isSeatOrNull(v['resigned']) &&\n isSeatOrNull(v['winner']);\nconst isEvent = (v: unknown): v is DiceRaceEvent =>\n isRecord(v) &&\n isSeat(v['seat']) &&\n (['rolling', 'fumbled', 'resigned'].includes(String(v['type'])) || (v['type'] === 'rolled' && isFaces(v['faces'])));\n\n/** The job runner: two dice from the payload's seed, so a re-run gives the same faces. */\nexport const throwDice = (payload: Json): Json => {\n const seed = isPayload(payload) ? payload.seed : 0;\n const rng = createRng(`dice-race:${seed}`);\n return [rng.int(1, 7), rng.int(1, 7)];\n};\n\nconst over = (s: DiceRaceState): boolean => s.winner !== null || s.resigned !== null;\n\nexport const diceRace = defineEngine<DiceRaceState, DiceRaceCommand, DiceRaceState, DiceRaceEvent, DiceRaceConfig>({\n rulesVersion: 1,\n schemas: {\n config: guardSchema((v): v is DiceRaceConfig => isRecord(v) && isInt(v['target'], 1, 200), 'Expected { target }.'),\n command: guardSchema(\n (v): v is DiceRaceCommand => isRecord(v) && v['type'] === 'roll',\n 'Expected { type: \"roll\" }.',\n ),\n state: guardSchema(isState, 'Not a Dice Race state.'),\n view: guardSchema(isState, 'Not a Dice Race view.'),\n event: guardSchema(isEvent, 'Not a Dice Race event.'),\n },\n jobs: {\n throw: {\n where: 'either',\n payload: guardSchema(isPayload, 'Expected { seed, dice: 2 }.'),\n result: guardSchema(isFaces, 'Expected two faces from 1 to 6.'),\n timeoutMs: 5000,\n },\n },\n init: (config: DiceRaceConfig, seats, rng) =>\n seats.length < 1 || seats.length > 2\n ? err(gameError('Seats', 'Dice Race takes one or two players.'))\n : ok({\n target: config.target,\n scores: seats.map(() => 0),\n turn: rng.int(0, seats.length),\n awaiting: null,\n throws: 0,\n resigned: null,\n winner: null,\n }),\n apply: (state: DiceRaceState, input, rng) => {\n if (over(state)) return err(gameError('GameOver', 'The game is over.'));\n switch (input.kind) {\n case 'resign':\n if (state.scores[input.seat] === undefined) return err(gameError('NoSuchSeat', 'There is no such seat.'));\n return ok({\n state: { ...state, resigned: input.seat, awaiting: null },\n beats: [{ event: { type: 'resigned', seat: input.seat } }],\n });\n case 'command': {\n if (state.awaiting !== null || input.seat !== state.turn)\n return err(gameError('NotYourTurn', \"It's not your turn.\"));\n const id = `throw-${state.throws + 1}`;\n return ok({\n state: { ...state, awaiting: id, throws: state.throws + 1 },\n beats: [{ event: { type: 'rolling', seat: input.seat } }],\n jobs: [{ id, kind: 'throw', payload: { seed: rng.int(0, 2147483647), dice: 2 } }],\n });\n }\n case 'job': {\n if (input.jobId !== state.awaiting) return err(gameError('NotAwaited', 'That throw is no longer awaited.'));\n const seat = state.turn;\n if (!input.outcome.ok) {\n return ok({ state: { ...state, awaiting: null }, beats: [{ event: { type: 'fumbled', seat } }] });\n }\n const faces = input.outcome.value;\n if (!isFaces(faces)) return err(gameError('BadThrow', 'The dice landed strangely.'));\n const score = (state.scores[seat] ?? 0) + faces[0]! + faces[1]!;\n const scores = state.scores.map((s, i) => (i === seat ? score : s));\n const winner = score >= state.target ? seat : null;\n return ok({\n state: { ...state, scores, awaiting: null, winner, turn: (seat + 1) % state.scores.length },\n beats: [{ event: { type: 'rolled', seat, faces } }],\n });\n }\n default:\n return err(gameError('Unsupported', 'Dice Race has no clocks or seat changes.'));\n }\n },\n pending: (state) => (over(state) || state.awaiting !== null ? [] : [state.turn]),\n viewFor: (state) => state,\n eventFor: (event) => event,\n outcome: (state) => {\n if (!over(state)) return null;\n if (state.scores.length === 1) {\n return {\n result: state.resigned === null ? 'completed' : 'abandoned',\n placements: [{ seat: 0, place: 1, score: state.scores[0] ?? 0 }],\n summary: `Reached ${state.target} in ${state.throws} throws.`,\n };\n }\n return { result: 'won', placements: rankPlaces(state.scores, state.resigned), summary: 'The race is over.' };\n },\n});\n\n/** Conformance options for the Dice Race fixture. */\nexport const diceRaceConformance: EngineConformanceOptions<DiceRaceState, DiceRaceCommand, DiceRaceConfig> = {\n name: 'dice-race',\n configs: [{ target: 30 }],\n seatCounts: [1, 2],\n commands: () => [{ type: 'roll' }],\n jobs: { throw: throwDice },\n jobFailureRate: 0.2,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type SeatId } from '@joystyk/contracts';
|
|
2
|
+
import { perturbHidden, type SuitedCard } from '@joystyk/kit';
|
|
3
|
+
import type { EngineConformanceOptions } from '../engine-conformance.js';
|
|
4
|
+
export type DrawPlayCard = SuitedCard;
|
|
5
|
+
export type DrawPlayState = {
|
|
6
|
+
readonly hands: readonly (readonly DrawPlayCard[])[];
|
|
7
|
+
readonly deck: readonly DrawPlayCard[];
|
|
8
|
+
readonly table: readonly {
|
|
9
|
+
readonly seat: SeatId;
|
|
10
|
+
readonly card: DrawPlayCard;
|
|
11
|
+
}[];
|
|
12
|
+
readonly turn: SeatId;
|
|
13
|
+
readonly scores: readonly number[];
|
|
14
|
+
readonly resigned: SeatId | null;
|
|
15
|
+
readonly over: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type DrawPlayCommand = {
|
|
18
|
+
readonly type: 'play';
|
|
19
|
+
readonly cardId: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: 'draw';
|
|
22
|
+
};
|
|
23
|
+
export type DrawPlayEvent = {
|
|
24
|
+
readonly type: 'drew';
|
|
25
|
+
readonly seat: SeatId;
|
|
26
|
+
readonly card: DrawPlayCard | null;
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: 'played';
|
|
29
|
+
readonly seat: SeatId;
|
|
30
|
+
readonly card: DrawPlayCard;
|
|
31
|
+
} | {
|
|
32
|
+
readonly type: 'resigned';
|
|
33
|
+
readonly seat: SeatId;
|
|
34
|
+
};
|
|
35
|
+
export type DrawPlayView = {
|
|
36
|
+
/** Your own cards; null for every other hand and for a spectator. */
|
|
37
|
+
readonly hand: readonly DrawPlayCard[] | null;
|
|
38
|
+
readonly handSizes: readonly number[];
|
|
39
|
+
readonly deckSize: number;
|
|
40
|
+
readonly table: DrawPlayState['table'];
|
|
41
|
+
readonly turn: SeatId;
|
|
42
|
+
readonly scores: readonly number[];
|
|
43
|
+
readonly resigned: SeatId | null;
|
|
44
|
+
readonly over: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type DrawPlayConfig = {
|
|
47
|
+
readonly handSize: number;
|
|
48
|
+
};
|
|
49
|
+
export declare const DRAW_PLAY_CARDS = 26;
|
|
50
|
+
export declare const drawPlay: import("@joystyk/contracts").DiscreteEngine<DrawPlayState, DrawPlayCommand, DrawPlayView, DrawPlayEvent, DrawPlayConfig>;
|
|
51
|
+
/** Shuffles everything `viewer` can't see: the deck and every other hand. */
|
|
52
|
+
export declare const perturbDrawPlay: (state: DrawPlayState, viewer: SeatId | null, rng: Parameters<typeof perturbHidden>[2]) => DrawPlayState;
|
|
53
|
+
/** Conformance options for the Draw and Play fixture. */
|
|
54
|
+
export declare const drawPlayConformance: EngineConformanceOptions<DrawPlayState, DrawPlayCommand, DrawPlayConfig>;
|