character
Generate a unique ASCII character from a seed string. Choose from 16 species, 10 eyes, 8 mouths, 10 hats, and 12 accessories. Use mood presets for quick expression changes.
Instructions
Generate a unique ASCII character from a seed. 153600 possible combinations (16 species × 10 eyes × 8 mouths × 10 hats × 12 accessories). Same seed always produces the same character. Use mood for quick expression presets. Output can be piped to the animate tool.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| seed | Yes | Deterministic seed string — same seed = same character | |
| species | No | Body override | |
| eyes | No | Eyes override | |
| mouth | No | Mouth override | |
| hat | No | Hat override | |
| accessory | No | Accessory override | |
| mood | No | Expression preset (overrides eyes+mouth). Explicit eyes/mouth still take priority. | |
| size | No | Size: "standard" (full character) or "mini" (2-line inline, no hat/accessory) | standard |
Implementation Reference
- src/mcp.ts:308-334 (registration)Registration of the 'character' tool with the MCP server, including its schema definition (seed, species, eyes, mouth, hat, accessory, mood, size) and handler that calls generateCharacter().
server.tool( 'character', `Generate a unique ASCII character from a seed. ${SPECIES.length * EYES.length * MOUTHS.length * HATS.length * ACCESSORIES.length} possible combinations (${SPECIES.length} species × ${EYES.length} eyes × ${MOUTHS.length} mouths × ${HATS.length} hats × ${ACCESSORIES.length} accessories). Same seed always produces the same character. Use mood for quick expression presets. Output can be piped to the animate tool.`, { seed: z.string().min(1).max(MAX_CHARACTER_SEED_LENGTH).describe('Deterministic seed string — same seed = same character'), species: z.enum(SPECIES as unknown as [string, ...string[]]).optional().describe('Body override'), eyes: z.enum(EYES as unknown as [string, ...string[]]).optional().describe('Eyes override'), mouth: z.enum(MOUTHS as unknown as [string, ...string[]]).optional().describe('Mouth override'), hat: z.enum(HATS as unknown as [string, ...string[]]).optional().describe('Hat override'), accessory: z.enum(ACCESSORIES as unknown as [string, ...string[]]).optional().describe('Accessory override'), mood: z.enum(MOODS as unknown as [string, ...string[]]).optional().describe('Expression preset (overrides eyes+mouth). Explicit eyes/mouth still take priority.'), size: z.enum(SIZES as unknown as [string, ...string[]]).default('standard').describe('Size: "standard" (full character) or "mini" (2-line inline, no hat/accessory)'), }, async ({ seed, species, eyes, mouth, hat, accessory, mood, size }) => { const result = generateCharacter({ seed, species: species as any, eyes: eyes as any, mouth: mouth as any, hat: hat as any, accessory: accessory as any, mood: mood as any, size: size as any, }); return { content: [{ type: 'text', text: result }] }; } ); - src/character.ts:327-363 (handler)The generateCharacter() function that executes the character generation logic: hashes the seed, selects species/eyes/mouth/hat/accessory deterministically, replaces markers in templates, prepends hat, and attaches accessories.
export function generateCharacter(options: CharacterOptions): string { const hash = hashSeed(options.seed); const size = options.size ?? 'standard'; // Mood sets eyes+mouth defaults (explicit eyes/mouth still override) const moodPreset = options.mood ? MOOD_MAP[options.mood] : undefined; const species = options.species ?? SPECIES[selectIndex(hash, 0, SPECIES.length)]; const eyes = options.eyes ?? moodPreset?.eyes ?? EYES[selectIndex(hash, 1, EYES.length)]; const mouth = options.mouth ?? moodPreset?.mouth ?? MOUTHS[selectIndex(hash, 2, MOUTHS.length)]; // 1. Get body template (copy) const templates = size === 'mini' ? MINI_TEMPLATES : BODY_TEMPLATES; const bodyTemplate = [...templates[species]]; // 2. Replace eye/mouth markers const [leftEye, rightEye] = EYE_PARTS[eyes]; const mouthChar = MOUTH_PARTS[mouth]; const body = replaceMarkers(bodyTemplate, leftEye, rightEye, mouthChar); // Mini: no hat/accessory — return immediately if (size === 'mini') { return body.join('\n'); } const hat = options.hat ?? HATS[selectIndex(hash, 3, HATS.length)]; const accessory = options.accessory ?? ACCESSORIES[selectIndex(hash, 4, ACCESSORIES.length)]; // 3. Prepend hat const hatLines = HAT_PARTS[hat]; const withHat = prependLines(hatLines, body); // 4. Attach accessory const final = attachAccessory(withHat, ACCESSORY_PARTS[accessory]); return final.join('\n'); } - src/character.ts:1-28 (schema)Type definitions including CharacterOptions interface and the exported enums: SPECIES, EYES, MOUTHS, HATS, ACCESSORIES, MOODS, SIZES.
// --- Types --- export const SPECIES = ['blob', 'cat', 'bear', 'robot', 'bird', 'bunny', 'ghost', 'alien', 'fox', 'frog', 'penguin', 'octopus', 'dragon', 'mushroom', 'cactus', 'skull'] as const; export const EYES = ['dot', 'round', 'wide', 'wink', 'happy', 'star', 'x', 'at', 'dash', 'asym'] as const; export const MOUTHS = ['smile', 'grin', 'open', 'flat', 'cat', 'teeth', 'tongue', 'zigzag'] as const; export const HATS = ['none', 'tophat', 'crown', 'party', 'cap', 'beanie', 'antenna', 'bow', 'halo', 'headband'] as const; export const ACCESSORIES = ['none', 'bowtie', 'scarf', 'sword', 'shield', 'tail', 'glasses', 'cape', 'wings', 'staff', 'bag', 'flower'] as const; export const MOODS = ['happy', 'sad', 'angry', 'surprised', 'sleepy', 'cool', 'love', 'silly'] as const; export const SIZES = ['mini', 'standard'] as const; export type Species = (typeof SPECIES)[number]; export type Eyes = (typeof EYES)[number]; export type Mouth = (typeof MOUTHS)[number]; export type Hat = (typeof HATS)[number]; export type Accessory = (typeof ACCESSORIES)[number]; export type Mood = (typeof MOODS)[number]; export type CharacterSize = (typeof SIZES)[number]; export interface CharacterOptions { seed: string; species?: Species; eyes?: Eyes; mouth?: Mouth; hat?: Hat; accessory?: Accessory; mood?: Mood; size?: CharacterSize; } - src/character.ts:32-50 (helper)Helper functions: hashSeed() implements FNV-1a 32-bit hash for deterministic seeding, and selectIndex() uses bit rotation and golden ratio mixing for slot-independent selection.
export function hashSeed(seed: string): number { let h = 0x811c9dc5; for (let i = 0; i < seed.length; i++) { h ^= seed.charCodeAt(i); h = Math.imul(h, 0x01000193); } return h >>> 0; // unsigned 32-bit } // --- Slot-independent selection via bit rotation + golden ratio mixing --- export function selectIndex(hash: number, slot: number, count: number): number { // Rotate bits by slot * 7 to decorrelate slots const rotation = (slot * 7) % 32; const rotated = ((hash << rotation) | (hash >>> (32 - rotation))) >>> 0; // Golden ratio mixing const mixed = Math.imul(rotated, 0x9e3779b9) >>> 0; return mixed % count; } - src/constants.ts:31-31 (helper)Constant MAX_CHARACTER_SEED_LENGTH = 200 used for seed validation in the tool schema.
export const MAX_CHARACTER_SEED_LENGTH = 200;