Skip to main content

@eigeninteractive/kernel

@eigeninteractive/kernel: the pure decision core. Given the current row, a state snapshot and an intent, it returns a plan: the next state, the transition to append, the observations to fan out, and any effects to schedule. It touches no storage and no clock of its own, so every decision is reproducible from its inputs alone.

Classes​

GameBugError​

Defined in: server/packages/kernel/src/errors.ts:15

A broken game/engine invariant: a bug, not a rejection.

Extends​

  • Error

Constructors​

Constructor​
new GameBugError(message?): GameBugError;

Defined in: web/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1080

Parameters​
ParameterType
message?string
Returns​

GameBugError

Inherited from​

Error.constructor

Constructor​
new GameBugError(message?, options?): GameBugError;

Defined in: web/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1080

Parameters​
ParameterType
message?string
options?ErrorOptions
Returns​

GameBugError

Inherited from​

Error.constructor

Interfaces​

CommitInput​

Defined in: server/packages/kernel/src/commit.ts:99

Properties​

game​
game: GameRow;

Defined in: server/packages/kernel/src/commit.ts:100

intent​
intent: Intent;

Defined in: server/packages/kernel/src/commit.ts:105

now​
now: number;

Defined in: server/packages/kernel/src/commit.ts:108

The commit instant (epoch ms), sampled once by the host and never read here.

roster​
roster: Seat[];

Defined in: server/packages/kernel/src/commit.ts:104

rules​
rules: GameRules;

Defined in: server/packages/kernel/src/commit.ts:111

The version unit for the game's schemaVersion, already resolved by the host from the GameModule.versions map.

staleViews?​
staleViews?: {
current: SeatView | null;
expected: SeatView | null;
};

Defined in: server/packages/kernel/src/commit.ts:119

Same-view material for a stale game action: the acting seat's stored frames at expectedVersion and at the current version. Only consulted when intent.expectedVersion < state.version; if absent (or either frame is missing, e.g. compacted away), the stale action is rejected conservatively.

current​
current: SeatView | null;
expected​
expected: SeatView | null;
state​
state: StateRow | null;

Defined in: server/packages/kernel/src/commit.ts:103

The latest transition, or null before v0 (only a start intent is meaningful then).


CommitPlan​

Defined in: server/packages/kernel/src/commit.ts:142

Properties​

action​
action: TransitionAction | null;

Defined in: server/packages/kernel/src/commit.ts:145

effects​
effects: Effect[];

Defined in: server/packages/kernel/src/commit.ts:156

frames​
frames: ObservationFrame[];

Defined in: server/packages/kernel/src/commit.ts:148

Per-seat projected frames (identified seats only), persisted with the transition, fanned out over sockets. No raw state escapes the kernel.

nextState​
nextState: StateRow;

Defined in: server/packages/kernel/src/commit.ts:144

The next transition row, already versioned (v+1, or 0 for start).

outcomes​
outcomes: OutcomeEntry[] | null;

Defined in: server/packages/kernel/src/commit.ts:155

Per-seat results when this transition ends the game, else null.

Rating deltas are deliberately NOT here: they depend on global cross-game priors (D1-domain data the kernel must never need). The D1 applier computes them inside the rating CAS via computeRatings (ratings.ts) and the host delivers them as a follow-up versioned ratings transition.


GameRow​

Defined in: server/packages/kernel/src/commit.ts:32

The game's standing configuration: the DO meta snapshot.

Properties​

budgetSeconds​
budgetSeconds: number | null;

Defined in: server/packages/kernel/src/commit.ts:39

config​
config: JsonObject;

Defined in: server/packages/kernel/src/commit.ts:37

Stored creation config; parsed against the version unit's config schema before any hook sees it.

incrementSeconds​
incrementSeconds: number | null;

Defined in: server/packages/kernel/src/commit.ts:40

rated​
rated: boolean;

Defined in: server/packages/kernel/src/commit.ts:41

ratingPool​
ratingPool: string | null;

Defined in: server/packages/kernel/src/commit.ts:42

schemaVersion​
schemaVersion: number;

Defined in: server/packages/kernel/src/commit.ts:34

status​
status: GameStatus;

Defined in: server/packages/kernel/src/commit.ts:33

turnSeconds​
turnSeconds: number | null;

Defined in: server/packages/kernel/src/commit.ts:38


NextDeadline​

Defined in: server/packages/kernel/src/timing.ts:59

Properties​

deadline​
deadline: number | null;

Defined in: server/packages/kernel/src/timing.ts:60

turnStartedAt​
turnStartedAt: number | null;

Defined in: server/packages/kernel/src/timing.ts:62

Start of a budget-consuming turn. Null for every other timing mode.


ObservationFrame​

Defined in: server/packages/kernel/src/observe.ts:12

One seat's projected frame, tagged with its seat. The host stamps version/timing when it persists and fans these out.

Properties​

data​
data: JsonObject;

Defined in: server/packages/kernel/src/observe.ts:14

pendingPlayers​
pendingPlayers: number[];

Defined in: server/packages/kernel/src/observe.ts:15

playerIndex​
playerIndex: number;

Defined in: server/packages/kernel/src/observe.ts:13


PlayerInput​

Defined in: server/packages/kernel/src/ratings.ts:26

A player seat to be rated. Self-contained: each seat's current mu/sigma is bundled, so this module never reads a store. displayRating is intentionally NOT carried; it is derived from mu/sigma so the formula lives in one place per side of the wire.

Extends​

  • Rating

Properties​

botId​
botId: string | null;

Defined in: server/packages/kernel/src/ratings.ts:29

placement​
placement: number;

Defined in: server/packages/kernel/src/ratings.ts:31

Ordinal finish rank (1 = best); ties share the same value.

playerIndex​
playerIndex: number;

Defined in: server/packages/kernel/src/ratings.ts:27

teamIndex​
teamIndex: number;

Defined in: server/packages/kernel/src/ratings.ts:34

Players sharing a teamIndex are rated as one team. For individual games this equals playerIndex.

userId​
userId: string | null;

Defined in: server/packages/kernel/src/ratings.ts:28


RatingDelta​

Defined in: server/packages/kernel/src/ratings.ts:54

One rated identity's before → after, exactly the rating_history row minus store keys. Computed by the D1 applier inside the rating CAS and delivered on the post-finish ratings transition (the kind: "ratings" action).

Properties​

displayAfter​
displayAfter: number;

Defined in: server/packages/kernel/src/ratings.ts:62

displayBefore​
displayBefore: number;

Defined in: server/packages/kernel/src/ratings.ts:59

displayChange​
displayChange: number;

Defined in: server/packages/kernel/src/ratings.ts:63

identity​
identity: RatingIdentity;

Defined in: server/packages/kernel/src/ratings.ts:55

muAfter​
muAfter: number;

Defined in: server/packages/kernel/src/ratings.ts:60

muBefore​
muBefore: number;

Defined in: server/packages/kernel/src/ratings.ts:57

pool​
pool: string;

Defined in: server/packages/kernel/src/ratings.ts:56

sigmaAfter​
sigmaAfter: number;

Defined in: server/packages/kernel/src/ratings.ts:61

sigmaBefore​
sigmaBefore: number;

Defined in: server/packages/kernel/src/ratings.ts:58


RatingResult​

Defined in: server/packages/kernel/src/ratings.ts:47

One identity's newly computed rating: the pure OpenSkill posterior, before the store-owned CAS revision is attached by the applier.

Extends​

  • Rating

Properties​

identity​
identity: RatingIdentity;

Defined in: server/packages/kernel/src/ratings.ts:48


Rejected​

Defined in: server/packages/kernel/src/errors.ts:42

An intent the kernel refused. A value, not a throw: rejections are part of the normal protocol.

Properties​

code​
code: RejectCode;

Defined in: server/packages/kernel/src/errors.ts:44

message​
message: string;

Defined in: server/packages/kernel/src/errors.ts:45

rejected​
rejected: true;

Defined in: server/packages/kernel/src/errors.ts:43


Seat​

Defined in: server/packages/kernel/src/commit.ts:47

One seat of the roster. Both ids null ⇒ the account was purged mid-game (the seat plays on as "Deleted User" for display, but can never act).

Properties​

botId​
botId: string | null;

Defined in: server/packages/kernel/src/commit.ts:50

playerIndex​
playerIndex: number;

Defined in: server/packages/kernel/src/commit.ts:48

type​
type: "bot" | "human";

Defined in: server/packages/kernel/src/commit.ts:51

userId​
userId: string | null;

Defined in: server/packages/kernel/src/commit.ts:49


SeatView​

Defined in: server/packages/kernel/src/guards.ts:86

A seat's stored projection at one version: what the same-view compare runs on (and what the DO persists per transition as frames[]).

Properties​

data​
data: JsonObject;

Defined in: server/packages/kernel/src/guards.ts:87

pendingPlayers​
pendingPlayers: number[];

Defined in: server/packages/kernel/src/guards.ts:88


StateRow​

Defined in: server/packages/kernel/src/commit.ts:56

The latest committed transition: state plus the engine-owned clocks. All instants are epoch milliseconds.

Properties​

deadline​
deadline: number | null;

Defined in: server/packages/kernel/src/commit.ts:63

The true turn deadline shown to clients; the alarm arms one millisecond after deadline + grace.

pending​
pending: number[];

Defined in: server/packages/kernel/src/commit.ts:59

playerTimes​
playerTimes: number[] | null;

Defined in: server/packages/kernel/src/commit.ts:65

Per-seat budget banks (ms), budget mode only.

rngSeed​
rngSeed: string;

Defined in: server/packages/kernel/src/commit.ts:60

state​
state: JsonObject;

Defined in: server/packages/kernel/src/commit.ts:58

turnStartedAt​
turnStartedAt: number | null;

Defined in: server/packages/kernel/src/commit.ts:69

When the current turn is consuming a budget bank. Null for untimed, per-action, and hook-override turns. This is persisted so charging the turn that ends never depends on the next envelope.

version​
version: number;

Defined in: server/packages/kernel/src/commit.ts:57

Type Aliases​

Effect​

type Effect =
| {
botId: string;
kind: "wakeBot";
seat: number;
}
| {
kind: "notifyTurn";
seat: number;
userId: string;
}
| {
kind: "notifyFinished";
userIds: string[];
};

Defined in: server/packages/kernel/src/commit.ts:140

A push/wake the host should attempt post-commit (single attempt + error log, with no retry machinery in v1). The kernel names seats; the host resolves delivery (FCM targets, bot webhook vs local bot).


GameStatus​

type GameStatus = "waiting" | "ready" | "active" | "finished" | "aborted";

Defined in: server/packages/kernel/src/commit.ts:29


Intent​

type Intent =
| {
kind: "start";
seed: string;
}
| {
actor: "user" | "bot";
data: unknown;
expectedVersion: number;
kind: "action";
seat: number;
}
| {
kind: "lifecycle";
type: "timeout";
}
| {
kind: "lifecycle";
seat: number;
type: "forfeit" | "autoForfeit";
};

Defined in: server/packages/kernel/src/commit.ts:76

What the host asks the kernel to do: the kernel-facing half of a Command (authorization already happened at the edge; dedupe at the DO).

Union Members​

Type Literal​
{
kind: "start";
seed: string;
}
kind​
kind: "start";
seed​
seed: string;

The game's base RNG seed, freshly generated by the host (randomSeed()); stored on v0 and copied to every later row.


Type Literal​
{
actor: "user" | "bot";
data: unknown;
expectedVersion: number;
kind: "action";
seat: number;
}
actor​
actor: "user" | "bot";
data​
data: unknown;

The raw move payload, parsed against the unit's action schema.

expectedVersion​
expectedVersion: number;

The version the client computed the move against. Equal to the current version in the common case; a lower value is arbitrated by the same-view rule.

kind​
kind: "action";
seat​
seat: number;

Type Literal​
{
kind: "lifecycle";
type: "timeout";
}

Type Literal​
{
kind: "lifecycle";
seat: number;
type: "forfeit" | "autoForfeit";
}

forfeit = a voluntary resign (a user action); autoForfeit = the engine-driven variant (account purge; identity-less system action).


ParseResult​

type ParseResult<T> =
| {
ok: true;
value: T;
}
| {
message: string;
ok: false;
};

Defined in: server/packages/kernel/src/schema.ts:13

A client payload parse: refusal is the caller's fault, so failure comes back as a value for commit() to turn into a rejection.

Type Parameters​

Type Parameter
T

RejectCode​

type RejectCode =
| "notActive"
| "notReady"
| "expired"
| "notPending"
| "stateUpdated"
| "invalidPayload"
| "illegalMove"
| "abstain";

Defined in: server/packages/kernel/src/errors.ts:20

Why an intent was refused. Stable machine codes: the host's transport mapping and the client's retry policy key on these, so treat renames as breaking.


TransitionAction​

type TransitionAction =
| {
data: JsonObject;
kind: "game";
playerIndex: number;
type: "user" | "bot";
}
| {
data: LifecycleAction;
kind: "lifecycle";
playerIndex: number | null;
type: ActionType;
}
| {
data: {
deltas: RatingDelta[];
};
kind: "ratings";
playerIndex: null;
type: "system";
};

Defined in: server/packages/kernel/src/commit.ts:135

The action-log entry for a transition. Null only for the start transition (v0), which no action produced. playerIndex is the performer's seat, and null for identity-less system actions (timeout, auto-forfeit).

The ratings variant is engine-owned, never produced by commit(): the host appends it as the post-finish ratings transition (step 3) once the D1 apply returns the deltas. Game hooks never see it; its data is the engine's, not the game's opaque payload.

Variables​

DEADLINE_GRACE_MS​

const DEADLINE_GRACE_MS: 750 = 750;

Defined in: server/packages/kernel/src/timing.ts:25

Grace window (ms) added to every deadline comparison so a player who submits on time is not rejected because network latency carried the request past the deadline. Keep it small relative to per-action turnSeconds windows. The client's display-only kServerDeadlineGrace mirrors this.

Functions​

alarmForDeadline()​

function alarmForDeadline(deadline): number | null;

Defined in: server/packages/kernel/src/timing.ts:41

The instant a deadline's alarm must fire at: one millisecond past the grace window, so the first fire is already genuinely expired by deadlineExpired. Null for an untimed turn, meaning no alarm.

The host derives its alarm from the committed deadline with this rather than being handed a separate number, so the armed alarm and the deadline it enforces cannot describe different instants.

Parameters​

ParameterType
deadlinenumber | null

Returns​

number | null


assertBudgetPending()​

function assertBudgetPending(
budgetSeconds,
envelope,
schemaVersion
): void;

Defined in: server/packages/kernel/src/guards.ts:26

Enforce budget mode's sequential-pending rule at the source: an accumulated clock only meters individual thinking time when at most one seat drains it, so a hook returning a multi-seat pending set in a budget-timed game is a game bug. computeNextDeadline's MIN-over-pending remains the graceful-degradation safeguard should such a state ever be reached. No-op when the game has no budget clock.

Parameters​

ParameterType
budgetSecondsnumber | null
envelopeEnvelope
schemaVersionnumber

Returns​

void


assertForfeitPending()​

function assertForfeitPending(
targetSeat,
envelope,
schemaVersion
): void;

Defined in: server/packages/kernel/src/guards.ts:36

Enforce that a forfeit actually removes the forfeited seat: a hook that leaves targetSeat in the pending set is a game bug. Left uncaught, the account-deletion purge would turn that seat into a ghost: no identity, yet still holding a deadline the timeout alarm fires at forever.

Parameters​

ParameterType
targetSeatnumber
envelopeEnvelope
schemaVersionnumber

Returns​

void


assertHookPayload()​

function assertHookPayload<T>(
schema,
value,
what
): asserts value is T;

Defined in: server/packages/kernel/src/schema.ts:63

Validate a payload produced by a game hook. Unlike client parsing, a failure is always a game bug. Validate-only: callers retain the hook's original object so a schema library cannot silently normalize or strip a value on the game's behalf.

Type Parameters​

Type Parameter
T

Parameters​

ParameterType
schemaStandardSchemaV1<unknown, T>
valueunknown
whatstring

Returns​

asserts value is T


assertHookState()​

function assertHookState(
schemas,
envelope,
schemaVersion
): void;

Defined in: server/packages/kernel/src/guards.ts:16

Validate the state a hook returned against the game's version schema before it is committed, catching a hook that wrote a malformed or wrong-version shape at the source instead of on the next read. Validate-only: the original envelope object is what gets persisted.

Parameters​

ParameterType
schemasGameSchemas
envelopeEnvelope
schemaVersionnumber

Returns​

void


assertPendingIdentified()​

function assertPendingIdentified(
roster,
envelope,
schemaVersion
): void;

Defined in: server/packages/kernel/src/guards.ts:48

Enforce that every pending seat has someone behind it: a seat whose account was purged mid-game (both ids null) can never act, so a hook that returns it as pending is a game bug, typically rules deriving pending from the participant count instead of from who is still in the game. Backstop to assertForfeitPending: that one catches the forfeit itself; this one catches any later hook resurrecting the seat.

Parameters​

ParameterType
rosterreadonly { botId: string | null; playerIndex: number; userId: string | null; }[]
envelopeEnvelope
schemaVersionnumber

Returns​

void


canonicalJson()​

function canonicalJson(value): string;

Defined in: server/packages/kernel/src/guards.ts:69

Canonical JSON: deterministic serialization with object keys sorted and undefined object values treated as absent, so two structurally equal views compare byte-identical regardless of construction order.

Parameters​

ParameterType
valueJson | undefined

Returns​

string


commit()​

function commit(input): CommitPlan | Rejected;

Defined in: server/packages/kernel/src/commit.ts:166

Parameters​

ParameterType
inputCommitInput

Returns​

CommitPlan | Rejected


computeNextDeadline()​

function computeNextDeadline(input): NextDeadline;

Defined in: server/packages/kernel/src/timing.ts:79

Computes the deadline and budget chargeability for the next action: the precedence chain used by start and every commit mode. Pass gameOver = true when the transition ends the game.

  1. game over → both null
  2. hook returned turnSeconds N → now + N s (banks untouched)
  3. budget mode → now + MIN remaining bank over the new pending set
  4. per-action mode → now + configured turnSeconds
  5. untimed → both null

Budget mode allows at most one pending seat, enforced at the source by assertBudgetPending before any envelope reaches this; the MIN remains the graceful-degradation safeguard should a multi-pending state arrive anyway.

Parameters​

ParameterTypeDescription
input{ actionSeconds: number | null; budgetSeconds: number | null; gameOver: boolean; newPending: readonly number[]; newPlayerTimes: readonly number[] | null; now: number; turnSeconds: number | null; }-
input.actionSecondsnumber | nullThe hook's per-action override (envelope turnSeconds), else null.
input.budgetSecondsnumber | null-
input.gameOverboolean-
input.newPendingreadonly number[]-
input.newPlayerTimesreadonly number[] | null-
input.nownumber-
input.turnSecondsnumber | null-

Returns​

NextDeadline


computeRatings()​

function computeRatings(players): RatingResult[];

Defined in: server/packages/kernel/src/ratings.ts:196

Compute every identity's new rating for one finished game.

Exactly one result per identity, humans and bots alike, matching the one rating row per (game, identity) the store keeps. The field is rated once; single-seat identities read their posterior straight from that rating, while a multi-seat identity is re-rated seat-by-seat into a single net result (see multiSeatUpdate). The single full-field rate() is what every single-seat player is scored against, so a human who faced a two-seat bot is correctly rated against two distinct opponents.

A seat with no identity, because its account was purged mid-game, stays in the field (opponents' posteriors must account for everyone they actually faced, at that seat's supplied baseline) but yields no result: there is no rating row left to update.

Parameters​

ParameterType
playersPlayerInput[]

Returns​

RatingResult[]


deadlineExpired()​

function deadlineExpired(deadline, now): boolean;

Defined in: server/packages/kernel/src/timing.ts:30

TRUE once a turn deadline (plus the grace window) has genuinely passed, measured against the injected now. A null deadline (untimed turn) is never expired.

Parameters​

ParameterType
deadlinenumber | null
nownumber

Returns​

boolean


deductBank()​

function deductBank(
playerTimes,
playerIndex,
now,
turnStartedAt,
incrementSeconds
): number[];

Defined in: server/packages/kernel/src/timing.ts:49

Deducts the acting player's elapsed thinking time from their budget bank and applies the Fischer increment. Returns a new playerTimes array (ms banks, one per seat). Floored at 0: a player who overran their bank lands at 0, not negative.

Parameters​

ParameterType
playerTimesreadonly number[]
playerIndexnumber
nownumber
turnStartedAtnumber | null
incrementSecondsnumber | null

Returns​

number[]


defaultRating()​

function defaultRating(): Rating;

Defined in: server/packages/kernel/src/ratings.ts:73

The OpenSkill prior for a never-rated identity.

Returns​

Rating


deriveRng()​

function deriveRng(seed, version): Rng;

Defined in: server/packages/kernel/src/rng.ts:29

The deterministic RNG for one transition: rand-seed's sfc32 keyed by the game's base seed and the state version the envelope will commit as. The same (seed, version) always yields the same draw sequence, so a replay re-derives it, and every transition gets an independent stream, so hooks draw as many values as they need with no cross-invocation state. The derivation is fixed, so recorded games stay replayable.

Parameters​

ParameterType
seedstring
versionnumber

Returns​

Rng


displayRating()​

function displayRating(mu, sigma): number;

Defined in: server/packages/kernel/src/ratings.ts:68

max(0, round((mu − 3σ) · 40)): the one server-side home of the display formula (the client mirrors it for optimistic display only).

Parameters​

ParameterType
munumber
sigmanumber

Returns​

number


fanOutObservations()​

function fanOutObservations(rules, args): ObservationFrame[];

Defined in: server/packages/kernel/src/observe.ts:25

Project the new state into one slice per seat: the eager fan-out the host persists per transition (frames serve live delivery and the same-view compare, so they stay eager). rules is the game's own version unit, already resolved by the caller. args is the hook's own contract minus the per-seat playerIndex, which the loop supplies; the body still forwards each field explicitly so a new hook arg forces a per-seat-or-shared decision here.

Parameters​

ParameterType
rulesGameRules
argsOmit<ComputeObservationArgs, "playerIndex">

Returns​

ObservationFrame[]


isRejected()​

function isRejected(result): result is Rejected;

Defined in: server/packages/kernel/src/commit.ts:160

Type guard: did commit() refuse the intent?

Parameters​

ParameterType
resultCommitPlan | Rejected

Returns​

result is Rejected


parseClientPayload()​

function parseClientPayload<T>(
schema,
value,
what
): ParseResult<T>;

Defined in: server/packages/kernel/src/schema.ts:40

Parse a client-submitted payload (an action's data, a create request's config) through its schema. Failure is the caller's fault. Returns the parsed value, so what flows onward, into hooks and the action log, is the sanitized shape (unknown keys stripped, defaults applied), never the raw submission.

Type Parameters​

Type Parameter
T

Parameters​

ParameterType
schemaStandardSchemaV1<unknown, T>
valueunknown
whatstring

Returns​

ParseResult<T>


parseStoredPayload()​

function parseStoredPayload<T>(
schema,
value,
what,
schemaVersion
): T;

Defined in: server/packages/kernel/src/schema.ts:51

Parse a stored payload (a state row, the game's config) through its schema. Failure means corrupted data or a schema that no longer matches what this version historically wrote: an engine-side bug, thrown.

Type Parameters​

Type Parameter
T

Parameters​

ParameterType
schemaStandardSchemaV1<unknown, T>
valueunknown
whatstring
schemaVersionnumber

Returns​

T


randomSeed()​

function randomSeed(): string;

Defined in: server/packages/kernel/src/rng.ts:18

A fresh base seed for a new game: 128 random bits, hex-encoded. Stored on the game's v0 state row and copied onto every later row (server-only, never expose it: the whole randomness of the game is derivable from it).

Returns​

string


reject()​

function reject(code, message): Rejected;

Defined in: server/packages/kernel/src/errors.ts:48

Parameters​

ParameterType
codeRejectCode
messagestring

Returns​

Rejected


sameView()​

function sameView(a, b): boolean;

Defined in: server/packages/kernel/src/guards.ts:100

The same-view rule: a stale-expectedVersion action is accepted iff the acting seat's own projected observation (slice data plus the seat's observed pending set) is identical between the expected and current versions, ignoring version/timing bookkeeping. Identical view ⇒ the intent transfers soundly (and applyAction still validates legality against the true current state); changed view ⇒ the conflict is genuine and "state updated" is literally true. The implementor controls this policy implicitly through computeObservation: reveal an event and it invalidates pending stale submissions; hide it and they survive.

Parameters​

ParameterType
aSeatView
bSeatView

Returns​

boolean