Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180/** * Typed access to tokenling's shared rules: the Rust crate in core/, compiled * to WebAssembly by core/wasm/build.sh. The Rust host links the same crate * natively. Call initCore() (or initCoreSync() in Node) before anything else. */import init, * as wasm from "../core/wasm/pkg/tokenling_core.js";import type { Disturbance, Generation, Offer, PetCommand, PetState, Stage, Stat, Stats, TurnKind, WorldState,} from "./types.ts";
export interface Lists { STAGES: Stage[]; PERSONALITIES: string[]; FOODS: string[]; GIFTS: string[]; DREAMS: string[]; EMOTES: string[]; WANTS: string[]; WALKS: string[]; DOODLES: string[]; WEATHER_DAY: string[]; WEATHER_NIGHT: string[]; TOYS: string[]; COLORS: string[]; EFFECT_TOOLS: string[]; LOOKUP_TOOLS: string[]; ANSWER_TOOLS: string[];}/** The closed word lists of the prompt contract, filled by initCore(). */export const L = {} as Lists;let ready = false;function loaded() { Object.assign(L, wasm.lists()); ready = true;}/** Loads the module beside the bundle that imports this file. */export async function initCore( source: URL | string = new URL("tokenling_core_bg.wasm", import.meta.url),) { if (ready) return; await init({ module_or_path: source }); loaded();}export function initCoreSync(bytes: BufferSource) { if (ready) return; wasm.initSync({ module: bytes }); loaded();}
export interface Tag { name: string; arg: string | null; result: string | null;}/** Something for the LCD to show; the save already changed. */export interface Effect { seq: number; kind: string; arg: string;}export type Task = { type: "chat"; kind: TurnKind } | { type: "diary"; date: string };export interface Alert { kind: "hungry" | "poop" | "sick" | "grew"; at: number; stage: Stage | null;}export type SaveResult = { state: PetState } | { error: string; newer: boolean };
export const header = (kind: "chat" | "diary", st: WorldState): string => wasm.header(kind, st);export const toolResult = (st: WorldState, name: string, arg = ""): string | null => wasm.toolResult(st, name, arg) ?? null;export const recall = (memories: string[], query: string): string => wasm.recall(memories, query);export const partOfDay = (hour: number): string => wasm.partOfDay(hour);export const levelWords = (stats: Stats): Record<Stat, string> => wasm.levelWords(stats);export const wakeChance = (energy: number, kind: Disturbance, n = 1): number => wasm.wakeChance(energy, kind, n);export const dreamThing = (item: string): string => wasm.dreamThing(item);export const event = (name: string, owner: string, pet: string, arg = ""): string => wasm.event(name, owner, pet, arg);export const tags = (text: string): Tag[] => wasm.tags(text);export const cleanText = (text: string): string => wasm.cleanText(text);export const isSilent = (text: string): boolean => wasm.silent(text);export const notCute = (text: string): boolean => wasm.notCute(text);export const cleanName = (name: string, max: number): string => wasm.cleanName(name, max);export const cleanMessage = (text: string): string => wasm.cleanMessage(text);export const stop = ( mode: Generation["mode"], text: string,): { reason: string; text: string } | undefined => wasm.stop(mode, text);export const fit = (g: Generation, count: (text: string) => number): string => wasm.fit(g.header, g.lines, g.prefix, g.maxNew, g.ctx ?? 1024, count);export const stageFor = (age: number): Stage => wasm.stageFor(age) as Stage;/** Growth stages with the age each starts at, oldest first. */export const stageAges = (): [Stage, number][] => wasm.stageAges();export const offerThing = (offer: Offer): string => wasm.offerThing(offer);export const freshSave = (now: number, timeZone: string): PetState => wasm.freshSave(now, timeZone);export const parseSave = (raw: string, now: number, timeZone: string): SaveResult => wasm.parseSave(raw, now, timeZone);export const saveVersion = (): number => wasm.saveVersion();export const simulateBody = ( state: PetState, from: number, to: number, quiet: boolean,): { state: PetState; events: string[] } => wasm.simulateBody(state, from, to, quiet);
export type ChatTurn = wasm.ChatTurn;export type DiaryTurn = wasm.DiaryTurn;/** A running pet: the save plus its turn queue, disturbances and effects. */export class Engine { readonly raw: wasm.Engine; constructor(state: PetState, now: number, seed: string) { this.raw = new wasm.Engine(state, now, seed); } get revision(): number { return this.raw.revision; } get contextRevision(): number { return this.raw.contextRevision; } get pending(): TurnKind | null { return (this.raw.pending as TurnKind | undefined) ?? null; } get game(): { id: string; started: number } | null { return this.raw.game ?? null; } get disturbances(): { count: number; last: number } { return this.raw.disturbances; } /** Disturbances that still count at `now`; they lapse after five quiet minutes. */ disturbancesAt = (now: number): number => this.raw.disturbancesAt(now); state = (): PetState => this.raw.state(); stateJson = (): string => this.raw.stateJson(); body = (): Partial<PetState> => this.raw.body(); replace = (state: PetState, now: number) => this.raw.replace(state, now); edit = (state: PetState) => this.raw.edit(state); /** Returns false when only body() fields changed. */ advance = (now: number, returning = false): boolean => this.raw.advance(now, returning); resetClock = (now: number) => this.raw.resetClock(now); command = (command: PetCommand, now: number) => this.raw.command(command, now); layEgg = (owner: string, now: number) => this.raw.layEgg(owner, now); setPreference = (key: string, value: unknown) => this.raw.setPreference(key, value); applyLine = (line: string, now: number) => this.raw.applyLine(line, now); world = (now: number): WorldState | null => this.raw.world(now); weather = (now: number): string => this.raw.weather(now); dayNum = (now: number): number => this.raw.dayNum(now); wantTurn = (kind: TurnKind) => this.raw.wantTurn(kind); idleAt = (at: number) => this.raw.idleAt(at); chooseTask = (now: number, background: boolean): Task | null => this.raw.chooseTask(now, background) ?? null; beginTurn = (now: number) => this.raw.beginTurn(now); chatTurn = (kind: TurnKind, now: number): ChatTurn => this.raw.chatTurn(kind, now); commitLine = (body: string, now: number) => this.raw.commitLine(body, now); declineOffer = (now: number) => this.raw.declineOffer(now); diaryTurn = (date: string, now: number, replace = false): DiaryTurn | null => this.raw.diaryTurn(date, now, replace) ?? null; commitDiary = (turn: DiaryTurn, generated: string): boolean => this.raw.commitDiary(turn, generated); takeEffects = (): Effect[] => this.raw.takeEffects(); disturb = (kind: Disturbance, now: number): boolean => this.raw.disturb(kind, now); resetDisturbances = () => this.raw.resetDisturbances(); setWakeRoll = (roll: (() => number) | null) => this.raw.setWakeRoll(roll); /** Replay tests: every random draw comes from `rolls`. */ setRolls = (rolls: (() => number) | null) => this.raw.setRolls(rolls); engineJson = (): string => this.raw.engineJson(); setAge = (age: number, now: number) => this.raw.setAge(age, now); simulate = (from: number, to: number, quiet: boolean): string[] => this.raw.simulate(from, to, quiet); alertPlan = (now: number): Alert[] => this.raw.alertPlan(now);}