Pixabots

SDK

@pixabots/core — ID encoding, random generation, and parts catalog

@pixabots/core is a zero-dependency package for working with pixabot IDs client-side. Works in browsers, Node.js, and edge runtimes.

Install

npm install @pixabots/core
pnpm add @pixabots/core

Encode / Decode

Convert between part indices and 4-char IDs:

import { encode, decode, resolve, resolveId } from "@pixabots/core";

// Indices → ID
const id = encode({ eyes: 2, heads: 1, body: 5, top: 6 });
// "2156"

// ID → indices
const combo = decode("2156");
// { eyes: 2, heads: 1, body: 5, top: 6 }

// ID → part names (combo)
const parts = resolve(combo);
// { eyes: "glasses", heads: "blob", body: "wings", top: "mohawk" }

// ID → part names (one-step shortcut for decode + resolve)
const direct = resolveId("2156");
// { eyes: "glasses", heads: "blob", body: "wings", top: "mohawk" }

Random generation

import { randomId, randomCombo } from "@pixabots/core";

const id = randomId();       // e.g. "7133"
const combo = randomCombo();  // e.g. { eyes: 7, heads: 1, body: 3, top: 3 }

Seeded generation

Generate deterministic pixabots from any string. Same input always returns the same pixabot:

import { seededId, seededCombo } from "@pixabots/core";

seededId("user@example.com");  // always the same ID
seededId("agent-123");         // always the same ID
seededId("user@example.com");  // same as the first call

This is useful for generating avatars from usernames, emails, or any unique identifier without storing the mapping.

Seeded PRNG primitives

When you need more than a single seeded pick — e.g. generating a sequence of variants from one id — use the underlying PRNG:

import { createRng, hashString, mulberry32 } from "@pixabots/core";

const rng = createRng("2156");       // hashString("2156") → mulberry32
rng();                                // 0.0 — 1.0, deterministic from the seed
rng();                                // next value in the same sequence

// Or compose the primitives yourself:
const seed = hashString("2156");      // 32-bit unsigned
const next = mulberry32(seed);        // () => number

Validation

import { isValidId } from "@pixabots/core";

isValidId("2156");  // true
isValidId("f76a");  // true
isValidId("zzzz");  // false — out of range
isValidId("abc");   // false — wrong length

Parts catalog

Access the full parts catalog:

import { PARTS, partCount, getPart, getPartIndex, totalCombinations } from "@pixabots/core";

totalCombinations();  // 10752

partCount("eyes");  // 16
partCount("heads"); // 8
partCount("body");  // 7
partCount("top");   // 12

getPart("eyes", 2);  // { name: "glasses", path: "eyes/glasses.png" }

getPartIndex("eyes", "glasses");  // 2

Animation data

The bounce animation frames are also available:

import { ANIM_FRAMES, FRAME_MS } from "@pixabots/core";

FRAME_MS;      // 72 (ms per frame)
ANIM_FRAMES;   // 8-frame array of { top, heads, eyes, body } offsets

Sub-animations (sprite-sheet parts)

Some parts ship as horizontal sprite sheets (frames × 32 wide) so they can animate inside the 16-tick idle super-loop. The bounce itself (ANIM_FRAMES) stays 8 ticks and repeats twice; the longer super-loop gives blinks room to breathe. Each PartOption declares a kind:

  • static (default) — always frame 0.
  • blink — 2-frame sheet [open, closed]. 16-tick schedule: [0,1,0,1,0,0,0,0, 0,0,0,0,0,0,0,0] (two blinks then eight ticks held open).
  • sequence — N-frame sheet played in order. N should divide LOOP_LENGTH (1 / 2 / 4 / 8 / 16) so the sub-loop fits the super-loop.

Resolve a tick → frame index via resolveFrameIndex:

import { PARTS, LOOP_LENGTH, resolveFrameIndex, BLINK_SCHEDULE } from "@pixabots/core";

const visor = PARTS.eyes.find(p => p.name === "visor");
// { name: "visor", path: "eyes/visor.png", frames: 8, kind: "sequence" }

for (let tick = 0; tick < LOOP_LENGTH; tick++) {
  const idx = resolveFrameIndex(visor, tick);
  // sx = idx * 32 to source-crop the sprite sheet
}

LOOP_LENGTH;     // 16
BLINK_SCHEDULE;  // [0,1,0,1,0,0,0,0, 0,0,0,0,0,0,0,0]

Types

import type { PixabotCombo, PartCategory, PartOption, PartAnimKind, AnimFrame } from "@pixabots/core";

// PixabotCombo = { eyes: number, heads: number, body: number, top: number }
// PartCategory = "eyes" | "heads" | "body" | "top"
// PartAnimKind = "static" | "blink" | "sequence"
// PartOption = { name: string, path: string, frames?: number, kind?: PartAnimKind }
// AnimFrame = { top: number, heads: number, eyes: number, body: number }