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: eigen-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: eigen-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: eigen-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: eigen-server/packages/kernel/src/commit.ts:96

Properties

game
game: GameRow;

Defined in: eigen-server/packages/kernel/src/commit.ts:97

intent
intent: Intent;

Defined in: eigen-server/packages/kernel/src/commit.ts:102

now
now: number;

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

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

roster
roster: Seat[];

Defined in: eigen-server/packages/kernel/src/commit.ts:101

rules
rules: GameRules;

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

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

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

Defined in: eigen-server/packages/kernel/src/commit.ts:116

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: eigen-server/packages/kernel/src/commit.ts:100

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


CommitPlan

Defined in: eigen-server/packages/kernel/src/commit.ts:139

Properties

action
action: TransitionAction | null;

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

alarm
alarm: number | null;

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

The instant the DO must arm its alarm at — the true deadline plus the grace window — or null to clear it.

effects
effects: Effect[];

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

frames
frames: ObservationFrame[];

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

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: eigen-server/packages/kernel/src/commit.ts:141

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

outcomes
outcomes: OutcomeEntry[] | null;

Defined in: eigen-server/packages/kernel/src/commit.ts:152

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: eigen-server/packages/kernel/src/commit.ts:32

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

Properties

budgetSeconds
budgetSeconds: number | null;

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

config
config: JsonObject;

Defined in: eigen-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: eigen-server/packages/kernel/src/commit.ts:40

rated
rated: boolean;

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

ratingPool
ratingPool: string | null;

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

schemaVersion
schemaVersion: number;

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

status
status: GameStatus;

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

turnSeconds
turnSeconds: number | null;

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


NextDeadline

Defined in: eigen-server/packages/kernel/src/timing.ts:48

Properties

deadline
deadline: number | null;

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

turnStartedAt
turnStartedAt: number | null;

Defined in: eigen-server/packages/kernel/src/timing.ts:50


ObservationFrame

Defined in: eigen-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: eigen-server/packages/kernel/src/observe.ts:14

pendingPlayers
pendingPlayers: number[];

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

playerIndex
playerIndex: number;

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


PlayerInput

Defined in: eigen-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: eigen-server/packages/kernel/src/ratings.ts:29

placement
placement: number;

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

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

playerIndex
playerIndex: number;

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

teamIndex
teamIndex: number;

Defined in: eigen-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: eigen-server/packages/kernel/src/ratings.ts:28


RatingDelta

Defined in: eigen-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: eigen-server/packages/kernel/src/ratings.ts:62

displayBefore
displayBefore: number;

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

displayChange
displayChange: number;

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

identity
identity: RatingIdentity;

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

muAfter
muAfter: number;

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

muBefore
muBefore: number;

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

pool
pool: string;

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

sigmaAfter
sigmaAfter: number;

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

sigmaBefore
sigmaBefore: number;

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


RatingResult

Defined in: eigen-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: eigen-server/packages/kernel/src/ratings.ts:48


Rejected

Defined in: eigen-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: eigen-server/packages/kernel/src/errors.ts:44

message
message: string;

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

rejected
rejected: true;

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


Seat

Defined in: eigen-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: eigen-server/packages/kernel/src/commit.ts:50

playerIndex
playerIndex: number;

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

type
type: "bot" | "human";

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

userId
userId: string | null;

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


SeatView

Defined in: eigen-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: eigen-server/packages/kernel/src/guards.ts:87

pendingPlayers
pendingPlayers: number[];

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


StateRow

Defined in: eigen-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: eigen-server/packages/kernel/src/commit.ts:63

The true turn deadline shown to clients; the alarm arms at deadline + grace.

pending
pending: number[];

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

playerTimes
playerTimes: number[] | null;

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

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

rngSeed
rngSeed: string;

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

state
state: JsonObject;

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

turnStartedAt
turnStartedAt: number | null;

Defined in: eigen-server/packages/kernel/src/commit.ts:66

version
version: number;

Defined in: eigen-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: eigen-server/packages/kernel/src/commit.ts:137

A push/wake the host should attempt post-commit (single attempt + error log — 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: eigen-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: eigen-server/packages/kernel/src/commit.ts:73

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: eigen-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: eigen-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: eigen-server/packages/kernel/src/commit.ts:132

The action-log entry for a transition. Null only for the start transition (v0), which no action produced. playerIndex is the performer's seat — 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: eigen-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

assertBudgetPending()

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

Defined in: eigen-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: eigen-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: eigen-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: eigen-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: eigen-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: eigen-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: eigen-server/packages/kernel/src/commit.ts:166

Parameters

ParameterType
inputCommitInput

Returns

CommitPlan | Rejected


computeNextDeadline()

function computeNextDeadline(input): NextDeadline;

Defined in: eigen-server/packages/kernel/src/timing.ts:67

Computes the deadline and turnStartedAt 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: eigen-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 — 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: eigen-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: eigen-server/packages/kernel/src/timing.ts:38

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: eigen-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: eigen-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 — 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: eigen-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: eigen-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: eigen-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: eigen-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: eigen-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: eigen-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: eigen-server/packages/kernel/src/errors.ts:48

Parameters

ParameterType
codeRejectCode
messagestring

Returns

Rejected


sameView()

function sameView(a, b): boolean;

Defined in: eigen-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