@botharness/pixel-avatar 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,5 +25,7 @@ const svg = pixelAvatarSvg(recipe); // 32×32 viewBox, crisp edges, no ids or sc
25
25
  - `REPLACE_PART_SLOTS`, `replacePartStart(recipe, slot)`: drawn `outfit`, `accessory`, `beard`, `glasses`, `nose`, `cheeks`, `petals` and `flowerBase` parts (front layer only). A drawn part replaces the built-in part pixel for pixel in the layer that part paints into, while the built-in choice stays saved underneath. `replacePartStart` flattens the worn built-in part for this recipe: pixels that are exactly an appearance color at a tone become that color slot, and the rest become fixed colors, so an unchanged copy renders identically. Drawn face parts never cover the speaking mouth, and drawn parts are hidden wherever the species or headwear hides the built-in part. A drawn accessory replacing a helmet or hood no longer hides the hair.
26
26
  - `emptyPartLayer`, `paintPartLayer`, `fillPartLayer`, `mirrorPartX`, `partLayer`, `partCells`, `createCustomPart`: drawing on dense layers, with pencil and eraser strokes, 4-connected flood fill and mirroring across the Avatar centerline.
27
27
  - `partLinePoints`, `partRectPoints`, `gradientPartLayer`, `ditherThreshold`, `noisePartLayer`, `shadePartLayer`: line (with 0°/45°/90° snap), rectangle (with square), tone gradient with 4×4 or 2×2 ordered dithering inside the start region, seeded ±1 tone noise, and a shade stroke that moves each colored cell one tone. All mirror, and all produce ordinary cells: the seed and the tool are not part of the part.
28
+ - `AVATAR_PIECE_COLORS_V4`, `AVATAR_STRANDS`, `AVATAR_HEADPIECES`, `withPieces`, `withBuiltInHeadpiece`, `builtInHeadpiece`, `headpieceStart`: asset version 4. Bangs, back hair and a single `strand` each take an optional color (`bangsColor`, `backHairColor`, `strandColor`) that defaults to `hairColor`, like the side hair colors. The headpiece slot holds a built-in headpiece (ears, horns, halo or small head wings) or a Custom Part, worn together with any accessory. The base sits behind the hair and the rest in front, and the far side hides when the head turns. `withPieces` upgrades a recipe when the Human edits and moves an accessory that is now a headpiece into the empty headpiece slot. Saved recipes are never migrated on their own, so they keep rendering as before. `headpieceStart` flattens the worn headpiece into a Custom Part to start drawing from.
29
+ - `AVATAR_ANIMAL_SPECIES`, `AVATAR_PATTERNS`, `isAnimalSpecies`: animal species in asset version 4. Each has a muzzle, an animal nose and its own ears, uses the skin color as fur, and takes an optional fur `pattern` drawn in fur tones. A drawn `pattern` Custom Part replaces the built-in one. Ear headpieces and ear accessories are kept but hidden on an animal.
28
30
  - `faceCells`, `pixelSymbolCells`, `symbolArtCells`: pixels for [`@botharness/pixel-morph`](../morph).
29
31
  - `pixelAvatarSvg(recipe, { turns, classPrefix })`: optional pre-rendered head turns, and rig layer classes (`<prefix>-body`, `-head`, `-face`, `-gaze`, `-blink`) for CSS animation.
package/dist/index.d.ts CHANGED
@@ -36,9 +36,36 @@ type AvatarColor = (typeof AVATAR_COLORS)[number];
36
36
  */
37
37
  declare const AVATAR_SPECIES: readonly ["human", "goblin", "elf", "dwarf", "orc", "flower"];
38
38
  type AvatarSpecies = (typeof AVATAR_SPECIES)[number];
39
+ /**
40
+ * Anthropomorphic animal species (asset version 4): a muzzle, animal ears and fur colors. The
41
+ * skin color is the fur color, and an optional `pattern` is drawn in its tones.
42
+ */
43
+ declare const AVATAR_ANIMAL_SPECIES: readonly ["cat", "dog", "fox", "rabbit", "bear"];
44
+ type AvatarAnimalSpecies = (typeof AVATAR_ANIMAL_SPECIES)[number];
45
+ type AvatarSpeciesV4 = AvatarSpecies | AvatarAnimalSpecies;
46
+ declare const isAnimalSpecies: (species: unknown) => species is AvatarAnimalSpecies;
47
+ /** Fur patterns for animal species, drawn in skin-color tones so they survive recoloring. */
48
+ declare const AVATAR_PATTERNS: readonly ["solid", "tabby", "spots", "patches", "colorpoint"];
49
+ type AvatarPattern = (typeof AVATAR_PATTERNS)[number];
39
50
  /** Optional per-piece hair colors (asset version 2); an absent piece uses `hairColor`. */
40
51
  declare const AVATAR_PIECE_COLORS: readonly ["leftSideHairColor", "rightSideHairColor"];
41
52
  type AvatarPieceColor = (typeof AVATAR_PIECE_COLORS)[number];
53
+ /**
54
+ * Asset version 4 adds a color for every other hair piece; an absent piece uses `hairColor`.
55
+ * `strandColor` colors the single strand (`strand`).
56
+ */
57
+ declare const AVATAR_PIECE_COLORS_V4: readonly ["bangsColor", "backHairColor", "strandColor"];
58
+ type AvatarPieceColorV4 = (typeof AVATAR_PIECE_COLORS_V4)[number];
59
+ /** A single strand of hair standing up from the crown (asset version 4). */
60
+ declare const AVATAR_STRANDS: readonly ["ahoge", "curl", "double"];
61
+ type AvatarStrand = (typeof AVATAR_STRANDS)[number];
62
+ /**
63
+ * Built-in headpieces (asset version 4), worn in the headpiece slot alongside an accessory and
64
+ * drawn partly behind the hair. The first five were accessories in earlier versions.
65
+ */
66
+ declare const AVATAR_HEADPIECES: readonly ["catears", "bunnyears", "horseears", "horns", "halo", "wings"];
67
+ type AvatarHeadpiece = (typeof AVATAR_HEADPIECES)[number];
68
+ type V4Key = AvatarPieceColorV4 | 'strand' | 'pattern';
42
69
  /** Part choices that exist only in asset version 2, added after every version 1 choice. */
43
70
  declare const AVATAR_PARTS_V2: {
44
71
  readonly outfit: readonly ["tee", "shirttie", "hoodie", "turtleneck", "sailor", "blazer", "overalls", "dress", "kimono", "cardigan", "maid", "jacket", "armor", "robe", "tunic", "cloak"];
@@ -72,7 +99,7 @@ type PixelAvatarRecipeV1 = Meta & {
72
99
  } & { [P in AvatarPart]: (typeof AVATAR_PARTS)[P][number] } & { [P in AvatarHairPart]?: (typeof AVATAR_PARTS)['hair'][number] } & { [P in AvatarRange]?: number } & {
73
100
  species?: never;
74
101
  rightSideHair?: never;
75
- } & { [P in AvatarPieceColor | AvatarExtraPart]?: never } & { [P in CustomPartKey]?: never };
102
+ } & { [P in AvatarPieceColor | AvatarExtraPart]?: never } & { [P in CustomPartKey]?: never } & { [P in V4Key]?: never };
76
103
  /**
77
104
  * Asset version 2: a species, the full split hair and geometry, a separate right side hair
78
105
  * (`sideHair` is then the left side) and optional per-side hair colors.
@@ -82,7 +109,7 @@ type PixelAvatarRecipeV2 = Meta & {
82
109
  } & { [P in AvatarPart]: (typeof AVATAR_PARTS_V2)[P][number] } & { [P in AvatarHairPart]: (typeof AVATAR_PARTS)['hair'][number] } & { [P in AvatarRange]: number } & {
83
110
  species: AvatarSpecies;
84
111
  rightSideHair: (typeof AVATAR_HAIR_PARTS)['sideHair'][number];
85
- } & { [P in AvatarPieceColor]?: string } & { [P in AvatarExtraPart]?: (typeof AVATAR_EXTRA_PARTS)[P][number] } & { [P in CustomPartKey]?: never };
112
+ } & { [P in AvatarPieceColor]?: string } & { [P in AvatarExtraPart]?: (typeof AVATAR_EXTRA_PARTS)[P][number] } & { [P in CustomPartKey]?: never } & { [P in V4Key]?: never };
86
113
  /** The recipe key that embeds the Custom Part worn in each slot. */
87
114
  declare const CUSTOM_PART_KEYS: {
88
115
  readonly headpiece: "headpiece";
@@ -98,6 +125,7 @@ declare const CUSTOM_PART_KEYS: {
98
125
  readonly cheeks: "cheeksPart";
99
126
  readonly petals: "petalsPart";
100
127
  readonly flowerBase: "flowerBasePart";
128
+ readonly pattern: "patternPart";
101
129
  };
102
130
  type CustomPartKey = (typeof CUSTOM_PART_KEYS)[PartSlot];
103
131
  /**
@@ -106,8 +134,23 @@ type CustomPartKey = (typeof CUSTOM_PART_KEYS)[PartSlot];
106
134
  */
107
135
  type PixelAvatarRecipeV3 = Omit<PixelAvatarRecipeV2, 'assetVersion' | CustomPartKey> & {
108
136
  assetVersion: 3;
109
- } & { [P in CustomPartKey]?: PixelCustomPart };
110
- type PixelAvatarRecipe = PixelAvatarRecipeV1 | PixelAvatarRecipeV2 | PixelAvatarRecipeV3;
137
+ } & { [P in Exclude<CustomPartKey, 'patternPart'>]?: PixelCustomPart } & {
138
+ patternPart?: never;
139
+ };
140
+ /**
141
+ * Asset version 4: version 2 with a color for every hair piece, a single `strand`, and the
142
+ * headpiece slot holding either a built-in headpiece or a Custom Part, with any number of
143
+ * Custom Parts worn (none included).
144
+ */
145
+ type PixelAvatarRecipeV4 = Omit<PixelAvatarRecipeV2, 'assetVersion' | 'species' | CustomPartKey | V4Key> & {
146
+ assetVersion: 4;
147
+ } & { [P in Exclude<CustomPartKey, 'headpiece'>]?: PixelCustomPart } & {
148
+ headpiece?: PixelCustomPart | AvatarHeadpiece;
149
+ strand?: AvatarStrand;
150
+ species: AvatarSpeciesV4;
151
+ pattern?: AvatarPattern;
152
+ } & { [P in AvatarPieceColorV4]?: string };
153
+ type PixelAvatarRecipe = PixelAvatarRecipeV1 | PixelAvatarRecipeV2 | PixelAvatarRecipeV3 | PixelAvatarRecipeV4;
111
154
  declare const DEFAULT_RECIPE: PixelAvatarRecipeV1;
112
155
  declare function isPixelAvatarRecipe(value: unknown): value is PixelAvatarRecipe;
113
156
  declare function canonicalRecipe(recipe: PixelAvatarRecipe): PixelAvatarRecipe;
@@ -123,16 +166,30 @@ declare function hiddenChoices(recipe: PixelAvatarRecipe): readonly string[];
123
166
  * When the skin color is one of the previous species' suggested colors, it moves to the new
124
167
  * species' first suggestion; a custom color is kept.
125
168
  */
169
+ declare function withSpecies(recipe: PixelAvatarRecipe, species: AvatarAnimalSpecies): PixelAvatarRecipeV4;
170
+ declare function withSpecies(recipe: PixelAvatarRecipeV4, species: AvatarSpeciesV4): PixelAvatarRecipeV4;
126
171
  declare function withSpecies(recipe: PixelAvatarRecipeV3, species: AvatarSpecies): PixelAvatarRecipeV3;
127
172
  declare function withSpecies(recipe: PixelAvatarRecipeV1 | PixelAvatarRecipeV2, species: AvatarSpecies): PixelAvatarRecipeV2;
128
- declare function withSpecies(recipe: PixelAvatarRecipe, species: AvatarSpecies): PixelAvatarRecipeV2 | PixelAvatarRecipeV3;
173
+ declare function withSpecies(recipe: PixelAvatarRecipe, species: AvatarSpeciesV4): PixelAvatarRecipeV2 | PixelAvatarRecipeV3 | PixelAvatarRecipeV4;
129
174
  /**
130
175
  * Returns the recipe wearing `part` in `slot`, or with that slot's part taken off. The recipe
131
- * embeds its own copy; it is asset version 3 while it wears any part and version 2 otherwise.
176
+ * embeds its own copy; it is asset version 3 while it wears any part and version 2 otherwise,
177
+ * and a version 4 recipe stays version 4.
132
178
  */
133
- declare function withCustomPart(recipe: PixelAvatarRecipe, slot: PartSlot, part: PixelCustomPart | undefined): PixelAvatarRecipeV2 | PixelAvatarRecipeV3;
179
+ declare function withCustomPart(recipe: PixelAvatarRecipeV4, slot: PartSlot, part: PixelCustomPart | undefined): PixelAvatarRecipeV4;
180
+ declare function withCustomPart(recipe: PixelAvatarRecipe, slot: PartSlot, part: PixelCustomPart | undefined): PixelAvatarRecipeV2 | PixelAvatarRecipeV3 | PixelAvatarRecipeV4;
181
+ /**
182
+ * Returns the recipe as asset version 4, keeping every choice. An accessory that is now a
183
+ * headpiece moves into the empty headpiece slot, freeing the accessory slot; call this only when
184
+ * the Human edits, so saved recipes keep rendering as they were.
185
+ */
186
+ declare function withPieces(recipe: PixelAvatarRecipe): PixelAvatarRecipeV4;
187
+ /** Returns the recipe as asset version 4 wearing a built-in headpiece, or none. */
188
+ declare function withBuiltInHeadpiece(recipe: PixelAvatarRecipe, headpiece: AvatarHeadpiece | undefined): PixelAvatarRecipeV4;
189
+ /** The built-in headpiece worn, if any. */
190
+ declare function builtInHeadpiece(recipe: PixelAvatarRecipe): AvatarHeadpiece | undefined;
134
191
  /** `withCustomPart` for the headpiece slot. */
135
- declare function withHeadpiece(recipe: PixelAvatarRecipe, part: PixelCustomPart | undefined): PixelAvatarRecipeV2 | PixelAvatarRecipeV3;
192
+ declare function withHeadpiece(recipe: PixelAvatarRecipe, part: PixelCustomPart | undefined): PixelAvatarRecipeV2 | PixelAvatarRecipeV3 | PixelAvatarRecipeV4;
136
193
  /** The Custom Part worn in a slot, if any. */
137
194
  declare function wornPart(recipe: PixelAvatarRecipe, slot: PartSlot): PixelCustomPart | undefined;
138
195
  declare function detailedRecipe(recipe: PixelAvatarRecipe): PixelAvatarRecipe;
@@ -154,7 +211,7 @@ declare const seededRecipeV2: (seed: string) => PixelAvatarRecipeV2;
154
211
  /** The same name always gives the same face (BotHarness's namespace). */
155
212
  declare const seededRecipe: (seed: string) => PixelAvatarRecipe;
156
213
  /** Suggested body colors per species; any color remains allowed. */
157
- declare const AVATAR_SPECIES_SWATCHES: Record<AvatarSpecies, readonly string[]>;
214
+ declare const AVATAR_SPECIES_SWATCHES: Record<AvatarSpeciesV4, readonly string[]>;
158
215
  declare const AVATAR_PRESETS: readonly PixelAvatarRecipe[];
159
216
  //#endregion
160
217
  //#region src/part.d.ts
@@ -217,6 +274,10 @@ declare const PART_SLOTS: {
217
274
  readonly width: 32;
218
275
  readonly height: 32;
219
276
  };
277
+ readonly pattern: {
278
+ readonly width: 32;
279
+ readonly height: 32;
280
+ };
220
281
  };
221
282
  type PartSlot = keyof typeof PART_SLOTS;
222
283
  /**
@@ -230,7 +291,7 @@ declare const isHairPartSlot: (slot: PartSlot) => slot is HairPartSlot;
230
291
  * Slots whose drawn part replaces a built-in part pixel for pixel: the part's cells are painted
231
292
  * where the built-in part would be, and every cell shows its own color and tone.
232
293
  */
233
- declare const REPLACE_PART_SLOTS: readonly ["outfit", "accessory", "beard", "glasses", "nose", "cheeks", "petals", "flowerBase"];
294
+ declare const REPLACE_PART_SLOTS: readonly ["outfit", "accessory", "beard", "glasses", "nose", "cheeks", "petals", "flowerBase", "pattern"];
234
295
  type ReplacePartSlot = (typeof REPLACE_PART_SLOTS)[number];
235
296
  declare const isReplacePartSlot: (slot: PartSlot) => slot is ReplacePartSlot;
236
297
  /** Tone steps on the rig's shade ramp: two darker, the color itself, two lighter. */
@@ -330,6 +391,16 @@ declare function hairPieceStart(recipe: PixelAvatarRecipe, slot: HairPartSlot):
330
391
  front: PartCell[];
331
392
  back: PartCell[];
332
393
  };
394
+ /**
395
+ * The headpiece as a Custom Part to start drawing from: the drawn headpiece worn, or the
396
+ * built-in headpiece flattened for this recipe in the front pose, its base on the back layer and
397
+ * the rest on the front layer, so an unchanged copy renders identically facing front.
398
+ */
399
+ declare function headpieceStart(recipe: PixelAvatarRecipe): {
400
+ slot: 'headpiece';
401
+ front: PartCell[];
402
+ back: PartCell[];
403
+ };
333
404
  /**
334
405
  * The part in a replacement slot as a Custom Part to start drawing from: the drawn part worn
335
406
  * there, or the built-in part flattened for this recipe in the front pose. Each pixel becomes a
@@ -391,5 +462,5 @@ declare function pixelSymbolCells(symbol: PixelSymbol, color: string): PixelCell
391
462
  */
392
463
  declare function symbolArtCells(rows: readonly string[], color: string): PixelCell$1[];
393
464
  //#endregion
394
- export { AVATAR_COLORS, AVATAR_EXTRA_PARTS, AVATAR_HAIR_PARTS, AVATAR_PARTS, AVATAR_PARTS_V2, AVATAR_PIECE_COLORS, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SPECIES, AVATAR_SPECIES_SWATCHES, AVATAR_SWATCHES, AVATAR_TURNS, type AvatarColor, type AvatarExtraPart, type AvatarHairPart, type AvatarPart, type AvatarPieceColor, type AvatarRange, type AvatarSpecies, CUSTOM_PART_KEYS, type CustomPartKey, DEFAULT_RECIPE, HAIR_PART_SLOTS, type HairPartSlot, MAX_PART_FIXED_COLORS, PART_LAYERS, PART_SLOTS, PART_TONES, PIXEL_SYMBOLS, type PartCell, type PartColor, type PartInk, type PartLayer, type PartLayerName, type PartSlot, type PartTone, type PixelAvatarRecipe, type PixelAvatarRecipeV1, type PixelAvatarRecipeV2, type PixelAvatarRecipeV3, type PixelAvatarSvgOptions, type PixelCell, type PixelCustomPart, type PixelFigureOptions, type PixelGrid, type PixelMouthState, type PixelSymbol, REPLACE_PART_SLOTS, type ReplacePartSlot, canonicalCustomPart, canonicalRecipe, createCustomPart, createSeededRecipe, createSeededRecipeV2, customPartId, detailedRecipe, ditherThreshold, emptyPartLayer, faceCells, fillPartLayer, gradientPartLayer, hairPieceStart, hiddenChoices, isHairPartSlot, isPixelAvatarRecipe, isPixelCustomPart, isReplacePartSlot, mirrorPartX, noisePartLayer, paintPartLayer, partCells, partLayer, partLinePoints, partRectPoints, partToneColor, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, replacePartStart, seededRandom, seededRecipe, seededRecipeV2, shadePartLayer, symbolArtCells, withCustomPart, withHeadpiece, withSpecies, wornPart };
465
+ export { AVATAR_ANIMAL_SPECIES, AVATAR_COLORS, AVATAR_EXTRA_PARTS, AVATAR_HAIR_PARTS, AVATAR_HEADPIECES, AVATAR_PARTS, AVATAR_PARTS_V2, AVATAR_PATTERNS, AVATAR_PIECE_COLORS, AVATAR_PIECE_COLORS_V4, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SPECIES, AVATAR_SPECIES_SWATCHES, AVATAR_STRANDS, AVATAR_SWATCHES, AVATAR_TURNS, type AvatarAnimalSpecies, type AvatarColor, type AvatarExtraPart, type AvatarHairPart, type AvatarHeadpiece, type AvatarPart, type AvatarPattern, type AvatarPieceColor, type AvatarPieceColorV4, type AvatarRange, type AvatarSpecies, type AvatarSpeciesV4, type AvatarStrand, CUSTOM_PART_KEYS, type CustomPartKey, DEFAULT_RECIPE, HAIR_PART_SLOTS, type HairPartSlot, MAX_PART_FIXED_COLORS, PART_LAYERS, PART_SLOTS, PART_TONES, PIXEL_SYMBOLS, type PartCell, type PartColor, type PartInk, type PartLayer, type PartLayerName, type PartSlot, type PartTone, type PixelAvatarRecipe, type PixelAvatarRecipeV1, type PixelAvatarRecipeV2, type PixelAvatarRecipeV3, type PixelAvatarRecipeV4, type PixelAvatarSvgOptions, type PixelCell, type PixelCustomPart, type PixelFigureOptions, type PixelGrid, type PixelMouthState, type PixelSymbol, REPLACE_PART_SLOTS, type ReplacePartSlot, builtInHeadpiece, canonicalCustomPart, canonicalRecipe, createCustomPart, createSeededRecipe, createSeededRecipeV2, customPartId, detailedRecipe, ditherThreshold, emptyPartLayer, faceCells, fillPartLayer, gradientPartLayer, hairPieceStart, headpieceStart, hiddenChoices, isAnimalSpecies, isHairPartSlot, isPixelAvatarRecipe, isPixelCustomPart, isReplacePartSlot, mirrorPartX, noisePartLayer, paintPartLayer, partCells, partLayer, partLinePoints, partRectPoints, partToneColor, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, replacePartStart, seededRandom, seededRecipe, seededRecipeV2, shadePartLayer, symbolArtCells, withBuiltInHeadpiece, withCustomPart, withHeadpiece, withPieces, withSpecies, wornPart };
395
466
  //# sourceMappingURL=index.d.ts.map