train-kit / types / index.d.ts
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
 * ardegazu-train-kit — hand-authored type surface for the CLJS-compiled dist.
 * Mirrors the shapes the original TypeScript sources declared (src/*.ts @
 * v1.0.0); the runtime is compiled from src/ardegazu/train/*.cljs.
 */

// ---- model -----------------------------------------------------------------

export interface MlpJson {
  /** Layer sizes, e.g. [12, 32, 1] — dense, relu hidden, linear output. */
  sizes: number[];
  /** Per layer: row-major weights (out×in) and biases (out). */
  layers: { w: number[]; b: number[] }[];
}

/** A value/Q net checkpoint for one game. */
export interface MlpModel<G extends string = string> {
  v: 1;
  game: G;
  episodes: number;
  /**
   * The weights rev the writer computed (`modelRev`), for traceability only —
   * absent in every checkpoint written before v2.1.0, and never trusted:
   * readers always recompute. Written right after `episodes`.
   */
  rev?: string;
  updatedAt: number;
  net: MlpJson;
}

/** A tabular Q checkpoint for one game (small discrete state spaces). */
export interface TabularModel<G extends string = string> {
  v: 1;
  game: G;
  episodes: number;
  /** See MlpModel#rev — traceability only, optional, never trusted. */
  rev?: string;
  updatedAt: number;
  /** State key → Q per action. */
  q: Record<string, number[]>;
}

export type AnyModel = MlpModel | TabularModel;

export function isMlpModel<G extends string>(m: unknown, game?: G): m is MlpModel<G>;
export function isTabularModel<G extends string>(m: unknown, game?: G): m is TabularModel<G>;
/** Any structurally valid checkpoint (callers narrow with the guards above). */
export function isModel(m: unknown): m is AnyModel;
/** Epsilon-greedy over action values (ties break to the first max). */
export function pickAction(qs: number[], epsilon: number, rand?: () => number): number;

// ---- rev -------------------------------------------------------------------

/**
 * The model's 8-char lowercase-hex weights rev: sha256 over a deterministic
 * serialization of the WEIGHTS ONLY (metadata excluded, so `updatedAt` moving
 * does not move the rev), first 8 hex chars. Always computed — a stored `rev`
 * field is never trusted. Throws when `model` is not a valid checkpoint.
 */
export function modelRev(model: AnyModel): string;
/**
 * `"<episodes>@<rev8>"` — the version string a bot advertises for the brain it
 * is playing. Total, unlike `modelRev`: it returns `"stock"` for anything it
 * cannot label — no model (null/undefined), a structurally invalid checkpoint,
 * or an episode count that is not a finite non-negative number. It is the
 * presentation function, called from live beacon paths where a throw would
 * take down something that matters; the trainer/gate side uses `modelRev` and
 * gets the loud failure.
 */
export function modelVersion(model: unknown): string;

// ---- forward ---------------------------------------------------------------

export function mlpForward(net: MlpJson, input: number[]): number[];
/** Fresh net with small deterministic-ish random weights (trainer bootstrap). */
export function mlpInit(sizes: number[], rand?: () => number): MlpJson;

// ---- episodes --------------------------------------------------------------

/** Tabular games: encoded state keys. */
export interface KeyedTransition<G extends string = string> {
  g: G;
  s: string;
  a: number;
  r: number;
  s2: string | null;
  done: boolean;
}
/** Net games: one feature vector per candidate action (the chosen one is f[a]). */
export interface FeatureTransition<G extends string = string> {
  g: G;
  f: number[][];
  a: number;
  r: number;
  /** Candidate features at s' (null when terminal). */
  f2: number[][] | null;
  done: boolean;
}
export type Transition<G extends string = string> = KeyedTransition<G> | FeatureTransition<G>;

export class EpisodeWriter {
  constructor(dataDir: string);
  add(t: Transition): void;
  /** Write the buffered match to disk (one line per transition). */
  flush(): void;
  get pending(): number;
}

export interface IngestState {
  /** file path → bytes already consumed. */
  offsets: Record<string, number>;
}

/** Trainer side: read new transitions from every bot's episode files. */
export function readNewTransitions(episodeDirs: string[], game: string, state: IngestState): Transition[];

// ---- checkpoint ------------------------------------------------------------

/** The live checkpoint a promoted model ships to. */
export function checkpointPath(modelDir: string, game: string): string;
/** Where a model waits while it hasn't passed its promotion gate yet. */
export function candidatePath(modelDir: string, game: string): string;
/** Parse + structurally validate a checkpoint file; null on any failure. */
export function loadModel(file: string): AnyModel | null;
/**
 * Atomic write (temp + rename). The JSON is emitted in a fixed key order —
 * `v, game, episodes, rev, updatedAt`, then the model's remaining keys — with
 * `rev` recomputed from the weights.
 */
export function saveModel(file: string, model: AnyModel): void;

/** A live bot's view of one checkpoint: load now, re-stat on an interval. */
export class CheckpointStore {
  constructor(file: string, reloadMs: number, onReload?: (m: AnyModel) => void);
  get model(): AnyModel | null;
  close(): void;
}

// ---- tabular ---------------------------------------------------------------

/** The Q row for a state, created zeroed on first sight. */
export function ensureRow(model: TabularModel, s: string, nActions: number): number[];
/** One Bellman update: q[a] += α·(r + γ·max q(s') − q[a]); terminal next = 0. */
export function qUpdate(
  model: TabularModel,
  t: KeyedTransition,
  opts: { alpha: number; gamma: number; nActions: number },
): void;

// ---- dqn -------------------------------------------------------------------

/** One value per candidate feature vector (forward pass, output[0]). */
export function netQs(net: MlpJson, feats: number[][]): number[];
/** Frozen-target regression set: y = done||!f2 ? r : r + γ·max netQs(f2); x = f[a]. */
export function dqnTargets(
  net: MlpJson,
  batch: readonly FeatureTransition[],
  gamma: number,
): { xs: number[][]; ys: number[] };
/** Keep only the newest `max` transitions, in place. */
export function capReplay<T>(replay: T[], max: number): void;

// ---- tf --------------------------------------------------------------------

export type TF = typeof import("@tensorflow/tfjs-node");

/** Load tfjs-node once; a clear error when the optional peer dep is absent. */
export function loadTf(): Promise<TF>;
export function netToTf(tf: TF, net: MlpJson, lr: number): import("@tensorflow/tfjs-node").Sequential;
export function tfToNet(tf: TF, m: import("@tensorflow/tfjs-node").Sequential, sizes: number[]): MlpJson;
/** netToTf → fit → tfToNet, with every tensor disposed. Returns the new net. */
export function fitNet(
  net: MlpJson,
  xs: number[][],
  ys: number[],
  opts: { lr: number; epochs: number; batchSize?: number },
): Promise<MlpJson>;

// ---- gate ------------------------------------------------------------------

/** The promoted checkpoint if present, else the unpromoted candidate. */
export function resolveModel(modelDir: string, game: string): AnyModel | null;
/** Write to <game>.json when promoted, else <game>.candidate.json; returns the path. */
export function gatePromotion(modelDir: string, game: string, model: AnyModel, promoted: boolean): string;

// ---- ingest ----------------------------------------------------------------

/** Expand one "*" path segment against the filesystem (per-bot state dirs). */
export function expandDirs(patterns: string[]): string[];
export function ingestStatePath(modelDir: string): string;
export function loadIngestState(modelDir: string): IngestState;
export function saveIngestState(modelDir: string, state: IngestState): void;

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/train-kit.git