init
36
.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.env/
|
||||||
|
|
||||||
|
# Secrets (OPENROUTER_API_KEY is read from the environment)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.key
|
||||||
|
secrets*
|
||||||
|
|
||||||
|
# Generated sprites (generate_sprite.py --out, default sprites/)
|
||||||
|
sprites/
|
||||||
|
sample_out/
|
||||||
|
test_out/
|
||||||
|
test_out_colors/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Generated contact sheets (preview.py, $PREVIEW_OUT defaults to preview/)
|
||||||
|
preview/
|
||||||
|
|
||||||
|
# Scratch reports
|
||||||
|
size_changes.txt
|
||||||
|
|
||||||
|
# Real business logos used for local testing
|
||||||
|
test_logos/
|
||||||
|
logos/
|
||||||
|
|
||||||
|
# Editor / OS
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
108
AI_PIXEL_ART_GUIDE.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# How to make good pixel art as an AI (no image model)
|
||||||
|
|
||||||
|
This guide is for AI coding agents. It describes the method used to build the Henry pixel-art loading animations (`~/Documents/henry_pixel_art_animations`). No diffusion or image generation was involved: every pixel is written as code, rendered, looked at, and fixed.
|
||||||
|
|
||||||
|
The reference implementation is in `henry_pixel_art_animations/src/lib/henry/`: `engine/` holds the drawing engine, `henry.ts` the mascot sprite, and `scenes/` one file per animation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Constraints first
|
||||||
|
|
||||||
|
Hard limits are what make the result look like real pixel art instead of blurry clip art. Decide them before drawing anything.
|
||||||
|
|
||||||
|
- **Tiny canvas.** Everything is drawn at a low logical resolution (Henry uses 160×120, 4:3), then upscaled. Never draw at screen resolution.
|
||||||
|
- **Whole pixels only.** Round every coordinate. Sub-pixel positions smear when scaled.
|
||||||
|
- **One fixed palette.** Use about 16 colours plus a few named extras. Henry uses Sweetie 16 by GrafxKid. Every scene uses only these colours, which is why they all look like one set. Ban ad-hoc hex values, or give each extra colour a name.
|
||||||
|
- **1px dark outline** (`ink`, the palette's darkest colour) around characters and props, so they read against any background.
|
||||||
|
- **Upscale with nearest-neighbour.** In canvas, set `imageSmoothingEnabled = false`; in CSS, use `image-rendering: pixelated`. For fractional display scales, use a "sharp bilinear" upscale: scale with nearest-neighbour to the next whole multiple, then do one smooth downscale to the exact size. Pixels then stay evenly sized.
|
||||||
|
|
||||||
|
## 2. Sprites are text grids
|
||||||
|
|
||||||
|
Write each sprite as an array of equal-length strings. Each character maps to a palette colour, and `.` is transparent.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const PAL = { k: C.ink, w: C.down, h: C.downLight, s: C.downShade, e: C.ink, o: C.bill, O: C.billShade, f: C.bill };
|
||||||
|
|
||||||
|
const STAND = [
|
||||||
|
'....kkkkk.....',
|
||||||
|
'...kwwwwwk....',
|
||||||
|
'..khwwwwwwk...',
|
||||||
|
'..khwwwwwwwk..',
|
||||||
|
'..kwwwwwwewk..', // e = eye
|
||||||
|
'..kwwwwwwewkk.',
|
||||||
|
'..kwwwwwwwwook', // o = bill
|
||||||
|
'..kswwwwwwwOk.',
|
||||||
|
// ...
|
||||||
|
'.....f..f.....', // feet
|
||||||
|
'.....ff.ff....',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Validate.** Throw if any row has a different width. This catches most authoring mistakes.
|
||||||
|
- **Bake once.** Render each sprite once to a small offscreen canvas, plus a horizontally flipped copy, and `drawImage` those every frame.
|
||||||
|
- **Make new poses by editing rows, not redrawing.** A blink changes the eye cell. Breathing drops one row so the body squashes 1px. A crouch drops two rows. A stride swaps the feet rows. Keeping the head rows identical keeps the character on-model across every pose.
|
||||||
|
- **Reason row by row** about silhouette and symmetry while writing the grid. Get the outline right first, then add the fill, then one highlight colour and one shade colour.
|
||||||
|
- **Keep sprites small.** A 14×18 character is plenty; small sprites read more clearly and are easier to get right.
|
||||||
|
|
||||||
|
## 3. Draw scenery procedurally
|
||||||
|
|
||||||
|
Backgrounds are drawing commands, not grids.
|
||||||
|
|
||||||
|
- **Primitives:** `rect`, `px`, a scanline `disc` (a filled circle with no antialiasing), `pill` (a rounded rectangle with 1px corner notches), `clip`, and `sprite`.
|
||||||
|
- **Dithering:** at colour band edges, use a checkerboard. For example, at a sky band edge, paint the previous band's colour where `(x + y) % 2 === 0`, fading over 2–4 rows.
|
||||||
|
- **Seeded randomness** for skylines, asphalt specks and windows, with a small PRNG such as `mulberry32(seed)`, so the scenery is identical every run.
|
||||||
|
- **Pre-render static layers** (background, foreground props) once into offscreen canvases. Each frame, draw only the moving things between them.
|
||||||
|
- **Layer order:** background, then far movers (clouds, birds, distant cars), then big actors (bus), then foreground props, then the character, then speech bubbles and effects.
|
||||||
|
- **Ambient life** keeps loops from feeling static: drifting clouds, a passing bird, cars, blinking, flickering lights.
|
||||||
|
|
||||||
|
## 4. Animate with timers and state machines
|
||||||
|
|
||||||
|
- **Phases.** Each animation is a small state machine, e.g. `intro → waiting (loops forever) → resolving → finished`. Each phase has its own timer.
|
||||||
|
- **Idle scheduler.** While waiting, the character randomly picks short "acts" (look around, tap foot, hop, think bubble), never the same act twice in a row, with random rests between. This prevents a visibly repeating loop.
|
||||||
|
- **Motion:** use easing functions (`easeOutCubic`, `easeOutBack`) and a parabolic `hop(t, height)`. Step frame animations by time (`Math.floor(t / 0.16) % 2`) or by distance travelled (wheels, walk cycles).
|
||||||
|
- **Cartoon cues:** a 1-frame pop on bubbles, dust puffs on landing, a squash before a jump, sparkles on success, wobble or shake for impact.
|
||||||
|
- **Clamp `dt`** to a range like 0–0.05 s, so a backgrounded tab doesn't fast-forward, and a negative first-frame delta can't break array indexing.
|
||||||
|
- **Transitions:** the iris (circle) wipe paints everything outside a circle with the palette's darkest colour, one scanline at a time so the edges stay crisp. It closes on the character's position and opens the next scene on its character.
|
||||||
|
|
||||||
|
## 5. Render, look, fix — the step that matters most
|
||||||
|
|
||||||
|
You cannot see the art while writing the character grids, so you must render it and inspect it. Never ship unviewed art.
|
||||||
|
|
||||||
|
1. Run the app (e.g. a Vite dev server) and open it in a headless browser.
|
||||||
|
2. Grab frames from the canvas **inside one in-page script**, because headless tabs may freeze between tool calls:
|
||||||
|
```js
|
||||||
|
// in page.evaluate: sample the canvas every 250 ms, trigger UI via setTimeout in the same script
|
||||||
|
const out = []; const c = document.querySelector('canvas');
|
||||||
|
const id = setInterval(() => { out.push(c.toDataURL('image/png')); if (out.length > 40) { clearInterval(id); resolve(out); } }, 250);
|
||||||
|
```
|
||||||
|
For exact, repeatable checks, import the scene module directly in the page and step it with a fixed `dt = 1/60`, drawing to an offscreen canvas.
|
||||||
|
3. Write the frames as PNGs, then tile them into a contact sheet with ffmpeg. Always use nearest-neighbour scaling:
|
||||||
|
```sh
|
||||||
|
ffmpeg -framerate 1 -i f%03d.png -vf "scale=160:120:flags=neighbor,crop=W:H:X:Y,scale=320:-1:flags=neighbor,tile=6x4" -frames:v 1 sheet.png
|
||||||
|
```
|
||||||
|
4. **Look at the image.** Crop and zoom to 4–10× on details such as the character, icons and contact points. Check whether:
|
||||||
|
- the silhouette reads, and the icon is recognisable;
|
||||||
|
- the motion reads across frames;
|
||||||
|
- things line up (feet on the ground, a bill touching a button, nothing clipping through a wall);
|
||||||
|
- the character is still visible when the iris closes.
|
||||||
|
5. Fix and repeat. Expect several rounds on anything important. Real fixes from this project: the bill was one pixel too narrow; the "walk" icon was a blob until it got a gap between head and body; a button was 2px too low for the bill to touch; a peeking pose showed the tail instead of the face until it was flipped.
|
||||||
|
6. For small icons, preview the grid by itself on a plain background at about 14× before placing it in the scene.
|
||||||
|
|
||||||
|
## 6. Scaling up with multiple agents
|
||||||
|
|
||||||
|
If several agents each build a scene, keep the style consistent:
|
||||||
|
|
||||||
|
- **Write a shared guide first.** Cover the engine API, palette, layout constants, character sprite and helpers, the scene contract, style rules (outlines, dithering, ambient life), and the render-and-look verification loop.
|
||||||
|
- **Freeze the shared files** (engine, palette, character). Agents put scene-specific colours and poses in their own files, and build new poses from the shared character rows so the design never drifts.
|
||||||
|
- **Give each agent a way to open its scene directly,** e.g. `?scene=<id>`, and a unique temp directory and browser tab.
|
||||||
|
- **Review every finished scene yourself.** Step each one with fixed timesteps across early, middle and late "done" triggers to catch crashes and scenes that never finish, then look at the contact sheets.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] Low fixed resolution, whole pixels, nearest-neighbour upscale
|
||||||
|
- [ ] One named palette; no stray colours
|
||||||
|
- [ ] 1px dark outlines on characters and props
|
||||||
|
- [ ] Sprites as text grids with validated widths; poses made by editing rows
|
||||||
|
- [ ] Procedural backgrounds with checkerboard dithering and seeded randomness
|
||||||
|
- [ ] State-machine animation, varied idle acts, ambient motion, clamped `dt`
|
||||||
|
- [ ] Rendered, sampled, contact-sheeted, zoomed and **looked at** — then fixed and checked again
|
||||||
66
README.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# sprite_gen
|
||||||
|
|
||||||
|
Procedural 96x96 creature sprites for businesses. You give it a business name and logo. It picks a type, builds a creature from parts and recolors it with the logo's brand colors.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
You only need an [OpenRouter](https://openrouter.ai) API key if you want the type picked automatically. Skip the key by passing `--type`.
|
||||||
|
|
||||||
|
## Generate a sprite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Type picked from the business name
|
||||||
|
OPENROUTER_API_KEY=<API_KEY> python generate_sprite.py "Rosas Landscaping" logos/rosas.png
|
||||||
|
|
||||||
|
# Add a description to help pick the type
|
||||||
|
OPENROUTER_API_KEY=<API_KEY> python generate_sprite.py "Rosas Landscaping" logos/rosas.png --desc "Lawn care and garden design"
|
||||||
|
|
||||||
|
# Set the type yourself (no API key needed)
|
||||||
|
python generate_sprite.py "Rosas Landscaping" logos/rosas.png --type grass
|
||||||
|
|
||||||
|
# Set the colors yourself instead of using a logo (main color first)
|
||||||
|
python generate_sprite.py "Rosas Landscaping" --type grass --colors "#2E7D32,#FBC02D"
|
||||||
|
|
||||||
|
# A different creature for the same business
|
||||||
|
python generate_sprite.py "Rosas Landscaping" logos/rosas.png --type grass --reroll 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Each run writes `sprites/<slug>.png` (96x96), `sprites/<slug>@4x.png` and `sprites/<slug>.json` (type, parts, colors). With `--reroll N`, the file names end in `-rN`.
|
||||||
|
|
||||||
|
| Option | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `--type T` | Skip the classifier. One of: normal fire water grass electric ice fighting poison ground flying psychic bug rock ghost dragon |
|
||||||
|
| `--desc TEXT` | Description sent to the classifier (default: the name) |
|
||||||
|
| `--colors "#main,#accent"` | Manual colors; overrides the logo |
|
||||||
|
| `--reroll N` | Alternate creature; the name alone always gives the same sprite |
|
||||||
|
| `--out DIR` | Output folder (default `sprites`) |
|
||||||
|
| `--scale N` | Size of the upscaled copy (default 4, `1` disables it) |
|
||||||
|
|
||||||
|
Logo colors: transparent pixels, soft edge pixels and a solid white/black/gray background are ignored, and colored brand colors are used before white, black or gray. A logo with only white, black or gray in it still uses those colors.
|
||||||
|
|
||||||
|
## Batch
|
||||||
|
|
||||||
|
Use a CSV with the columns `name,logo,type,description`. Only `name` is required, and rows with no `type` use the classifier.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OPENROUTER_API_KEY=<API_KEY> python generate_sprite.py --batch businesses.csv --out sprites
|
||||||
|
```
|
||||||
|
|
||||||
|
## Editing the art
|
||||||
|
|
||||||
|
The parts are text grids in `parts_src/<slot>/<name>.txt`, compiled to `parts/<slot>/<name>.png`. Weights, compatibility and deny lists live in `sprite_parts.py`. See `AI_PIXEL_ART_GUIDE.md` for the palette and anchor rules.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python build_parts.py # compile all grids (or pass part names)
|
||||||
|
python validate_parts.py --sample 3000 # check colors, anchors, canvas fit on random composites
|
||||||
|
python preview.py bases # contact sheets in preview/
|
||||||
|
python preview.py part back plant_bulb # one part on every base it fits
|
||||||
|
python preview.py zoom body=base_fox tail=tail_leaf
|
||||||
|
python generate_sprite.py --sprite-list SPRITES.md # regenerate the part list and weight report
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not run `--make-placeholders --overwrite`, because it replaces the real art with test placeholders.
|
||||||
319
SPRITES.md
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
# Sprite checklist
|
||||||
|
|
||||||
|
**104 sprites** across 5 slots. Save each as `parts/<slot>/<name>.png`.
|
||||||
|
|
||||||
|
## Art rules
|
||||||
|
|
||||||
|
- Final canvas is 96x96. Sizes below are suggested part canvases; crop tight with transparency.
|
||||||
|
- Characters face left in a 3/4 view, light from the top-left. 1px outline, no anti-aliasing, no semi-transparent pixels.
|
||||||
|
- Draw only with the palette keys below. Any other color (eye white, pupils) is left as-is.
|
||||||
|
|
||||||
|
| Key | Hex | Use |
|
||||||
|
|---|---|---|
|
||||||
|
| main_light | #FF8080 | body / most fur and skin |
|
||||||
|
| main_base | #FF0000 | body / most fur and skin |
|
||||||
|
| main_dark | #800000 | body / most fur and skin |
|
||||||
|
| accent_light | #80FF80 | horns, wings, beaks, crests, shells |
|
||||||
|
| accent_base | #00FF00 | horns, wings, beaks, crests, shells |
|
||||||
|
| accent_dark | #008000 | horns, wings, beaks, crests, shells |
|
||||||
|
| type_light | #8080FF | elemental bits: flames, leaves, sparks, ice, water |
|
||||||
|
| type_base | #0000FF | elemental bits: flames, leaves, sparks, ice, water |
|
||||||
|
| type_dark | #000080 | elemental bits: flames, leaves, sparks, ice, water |
|
||||||
|
| outline | #000000 | outline (recolored) |
|
||||||
|
| pupil | #141414 | not recolored |
|
||||||
|
| eye white | #FFFFFF | not recolored |
|
||||||
|
|
||||||
|
### Anchor marker pixels
|
||||||
|
|
||||||
|
One pixel each. `pivot` is where the part attaches to its parent; the others are where children attach.
|
||||||
|
|
||||||
|
| Marker | Hex | Needed in |
|
||||||
|
|---|---|---|
|
||||||
|
| pivot | #FF00FF | every part (bodies: bottom center between the feet) |
|
||||||
|
| head_top | #FA00FF | all bodies (top of the head) |
|
||||||
|
| tail | #FD00FF | all bodies |
|
||||||
|
| back | #FC00FF | all bodies |
|
||||||
|
| aura | #FB00FF | all bodies (center of mass; belly patch centre) |
|
||||||
|
|
||||||
|
## body (28)
|
||||||
|
|
||||||
|
| Done | File | Size | Palette | Plans | Description | Most likely for |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| [ ] | `body/base_fox.png` | 42x36 | main | quadruped | Chibi fox/cat critter with cheek tufts, big pointed ears and a fluffy bib | fire, electric, psychic |
|
||||||
|
| [ ] | `body/base_cub.png` | 40x34 | main | quadruped | Chunky bear-cub on all fours, round ears, sturdy paws | ground, fighting, rock |
|
||||||
|
| [ ] | `body/base_pup.png` | 42x34 | main | quadruped | Bouncy puppy with floppy ears and a happy open mouth | normal, fire, fighting |
|
||||||
|
| [ ] | `body/base_salamander.png` | 47x27 | main | quadruped | Low chibi salamander/newt with a wide smile and frilly cheeks | fire, water, poison |
|
||||||
|
| [ ] | `body/base_drake.png` | 42x38 | main | quadruped | Cool baby dragon on four legs, nub horns, confident grin | dragon, fire, rock |
|
||||||
|
| [ ] | `body/base_fawn.png` | 42x35 | main | quadruped | Dainty chibi fawn in 3/4 view: big leaf ears, gentle eyes, spotted back | grass, ice, psychic |
|
||||||
|
| [ ] | `body/base_imp.png` | 40x40 | main | biped | Upright mouse-imp with big round ears and a cheeky fanged grin | electric, normal, psychic |
|
||||||
|
| [ ] | `body/base_brawler.png` | 42x43 | main | biped | Stout little fighter with big fists and a determined face | fighting, ground, rock |
|
||||||
|
| [ ] | `body/base_bunny.png` | 36x45 | main | biped | Upright chibi bunny with long ears and a fluffy chest | normal, grass, ice |
|
||||||
|
| [ ] | `body/base_dino.png` | 36x44 | main | biped | Little upright dino with a big head, tiny arms and a toothy grin | dragon, fire, ground |
|
||||||
|
| [ ] | `body/base_pixie.png` | 42x39 | main | biped | Leaf-elf pixie with sweeping pointed ears, round torso, stubby legs and a mischievous grin | psychic, ghost, grass |
|
||||||
|
| [ ] | `body/base_chick.png` | 36x35 | main | avian | Egg-round chick with a feather tuft, wing nubs and a tiny beak | normal, ice, water |
|
||||||
|
| [ ] | `body/base_owl.png` | 36x36 | main | avian | Round owlet with huge eyes and ear tufts | psychic, flying, ghost |
|
||||||
|
| [ ] | `body/base_hawk.png` | 38x37 | main | avian | Cool young hawk, puffed chest, sharp brow and hooked beak | flying, fire, fighting |
|
||||||
|
| [ ] | `body/base_penguin.png` | 36x38 | main | avian | Chubby penguin chick with flipper wings and a bib | ice, water, normal |
|
||||||
|
| [ ] | `body/base_guppy.png` | 46x33 | main | aquatic | Round bubbly fish with big eyes and frilly fins | water, ice, poison |
|
||||||
|
| [ ] | `body/base_otter.png` | 49x30 | main | aquatic | Chubby seal/otter pup lying on its flippers | water, ice, normal |
|
||||||
|
| [ ] | `body/base_axolotl.png` | 51x30 | main | aquatic | Smiling axolotl with feathery gill fronds | water, poison, dragon |
|
||||||
|
| [ ] | `body/base_noodle.png` | 44x40 | main | serpentine | Cute coiled snake with a big round head and a sly smile | poison, ground, normal |
|
||||||
|
| [ ] | `body/base_eel.png` | 45x46 | main | serpentine | Rising sea-serpent with fin frills and big eyes | water, ice, electric |
|
||||||
|
| [ ] | `body/base_wyrm.png` | 43x44 | main | serpentine | Cool coiled young wyrm with whisker barbels and a proud look | dragon, ghost, ice |
|
||||||
|
| [ ] | `body/base_blob.png` | 40x33 | main | blob | Squishy mochi blob with a curl on top and a big smile | normal, grass, ice |
|
||||||
|
| [ ] | `body/base_slime.png` | 40x34 | main | blob | Drippy goo blob with a cheeky tongue-out face | poison, water, ghost |
|
||||||
|
| [ ] | `body/base_wisp.png` | 36x40 | main | blob | Floating ghost wisp with a curly tail-end and a spooky-cute grin | ghost, psychic, fire |
|
||||||
|
| [ ] | `body/base_pebble.png` | 38x32 | main | blob | Round rock lump creature with a sturdy little face and stubby feet | rock, ground, fighting |
|
||||||
|
| [ ] | `body/base_beetle.png` | 42x30 | main | insectoid | Round shiny beetle bug with a horn and stubby legs | bug, ground, rock |
|
||||||
|
| [ ] | `body/base_grub.png` | 44x27 | main | insectoid | Plump caterpillar with a big happy face and tufted segments | bug, grass, poison |
|
||||||
|
| [ ] | `body/base_bee.png` | 38x35 | main | insectoid | Fuzzy round bee/bug with tiny wings and antennae | bug, fighting, flying |
|
||||||
|
|
||||||
|
## head_top (23)
|
||||||
|
|
||||||
|
| Done | File | Size | Palette | Plans | Description | Most likely for |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| [ ] | `head_top/ears_pointed.png` | 16x14 | main | all | Pair of tall pointed ears | electric, normal, fire |
|
||||||
|
| [ ] | `head_top/ears_round.png` | 16x14 | main | all | Pair of round mouse/bear ears | normal, electric, ice |
|
||||||
|
| [ ] | `head_top/ears_long.png` | 18x20 | main | all | Long rabbit ears (drawn behind the creature) | normal, psychic, grass |
|
||||||
|
| [ ] | `head_top/ears_floppy.png` | 26x12 | main | all | Droopy hanging ears (drawn behind the creature) | normal, water, ground |
|
||||||
|
| [ ] | `head_top/ears_bolt.png` | 16x14 | main | all | Zigzag lightning-bolt ears, type-colored tips | electric, normal, fire |
|
||||||
|
| [ ] | `head_top/ears_fin.png` | 16x14 | accent | all | Fin-shaped ear frills | water, ice, dragon |
|
||||||
|
| [ ] | `head_top/horn_single.png` | 10x14 | accent | all | One forehead horn (rhino/beetle) | ground, bug, rock |
|
||||||
|
| [ ] | `head_top/horns_ram.png` | 26x14 | accent | all | Curled ram horns | ground, rock, fighting |
|
||||||
|
| [ ] | `head_top/horns_dragon.png` | 22x16 | accent | all | Swept-back dragon horns | dragon, fire, ghost |
|
||||||
|
| [ ] | `head_top/antennae.png` | 16x16 | main | all | Two thin antennae with tips | bug, electric, psychic |
|
||||||
|
| [ ] | `head_top/crest_feather.png` | 16x14 | accent | all | Swept feather crest | flying, fire, fighting |
|
||||||
|
| [ ] | `head_top/crest_flame.png` | 16x14 | type | all | Flickering flame on top of head | fire, normal, water |
|
||||||
|
| [ ] | `head_top/leaf_sprout.png` | 14x14 | type | all | Leaf/sprout growing from head | grass, normal, fire |
|
||||||
|
| [ ] | `head_top/flower_bloom.png` | 16x14 | accent | all | Small flower on head | grass, poison, psychic |
|
||||||
|
| [ ] | `head_top/mushroom_cap.png` | 20x12 | accent | all | Mushroom cap worn like a hat | poison, grass, bug |
|
||||||
|
| [ ] | `head_top/ice_crown.png` | 18x12 | type | all | Ring of small icicles | ice, normal, fire |
|
||||||
|
| [ ] | `head_top/crystal_spikes.png` | 16x14 | accent | all | Cluster of crystal shards | rock, ice, psychic |
|
||||||
|
| [ ] | `head_top/gem_forehead.png` | 8x8 | accent | all | Gem set in forehead | psychic, dragon, rock |
|
||||||
|
| [ ] | `head_top/spiky_hair.png` | 20x12 | main | all | Spiky tuft of fur/hair | electric, fighting, poison |
|
||||||
|
| [ ] | `head_top/headband.png` | 24x10 | accent | all | Tied headband with trailing ends | fighting, normal, fire |
|
||||||
|
| [ ] | `head_top/wisp_flame.png` | 16x14 | type | all | Ghostly floating flame wisp | ghost, fire, psychic |
|
||||||
|
| [ ] | `head_top/water_spout.png` | 16x14 | type | all | Water droplet/fountain crest | water, ice, normal |
|
||||||
|
| [ ] | `head_top/cloud_tuft.png` | 20x12 | type | all | Puffy cloud tuft | flying, electric, ice |
|
||||||
|
|
||||||
|
## back (22)
|
||||||
|
|
||||||
|
| Done | File | Size | Palette | Plans | Description | Most likely for |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| [ ] | `back/wings_feather.png` | 44x30 | accent | avian, biped, quadruped | Full feathered wings, spread | flying, fire, normal |
|
||||||
|
| [ ] | `back/wings_feather_small.png` | 30x14 | accent | avian, biped, quadruped, blob | Tiny stubby wings | flying, normal, ice |
|
||||||
|
| [ ] | `back/wings_bat.png` | 44x26 | accent | biped, quadruped, avian, blob, serpentine | Membrane bat wings | poison, ghost, flying |
|
||||||
|
| [ ] | `back/wings_bug.png` | 40x28 | accent | insectoid, biped, blob | Translucent veined insect wings | bug, grass, poison |
|
||||||
|
| [ ] | `back/wings_dragon.png` | 48x32 | accent | quadruped, biped, serpentine | Large clawed dragon wings | dragon, fire, flying |
|
||||||
|
| [ ] | `back/shell_round.png` | 40x26 | accent | quadruped, biped, aquatic | Domed turtle shell | water, ground, rock |
|
||||||
|
| [ ] | `back/shell_spiked.png` | 40x28 | accent | quadruped, biped, insectoid | Armored shell with spikes | rock, ground, water |
|
||||||
|
| [ ] | `back/spikes_back.png` | 36x14 | accent | quadruped, biped, serpentine, aquatic, blob | Row of spikes along the spine | ground, poison, rock |
|
||||||
|
| [ ] | `back/sail_fin.png` | 34x20 | accent | quadruped, biped, serpentine | Tall dimetrodon-style back sail | fire, dragon, water |
|
||||||
|
| [ ] | `back/dorsal_fin.png` | 20x16 | accent | aquatic, serpentine, quadruped | Shark-style dorsal fin | water, ice, dragon |
|
||||||
|
| [ ] | `back/plant_bulb.png` | 30x26 | type | quadruped, biped, blob | Large seed pod/bulb with leaves | grass, poison, normal |
|
||||||
|
| [ ] | `back/leaf_cape.png` | 38x20 | type | quadruped, biped, serpentine, insectoid | Overlapping leaves along the back | grass, bug, flying |
|
||||||
|
| [ ] | `back/flower_back.png` | 32x26 | accent | quadruped, biped, blob | Big blooming flower | grass, poison, psychic |
|
||||||
|
| [ ] | `back/mushroom_back.png` | 30x22 | accent | quadruped, insectoid, blob | Cluster of mushrooms growing on back | grass, poison, bug |
|
||||||
|
| [ ] | `back/flame_mane.png` | 36x28 | type | quadruped, biped | Mane/collar of fire | fire, dragon, normal |
|
||||||
|
| [ ] | `back/crystal_cluster.png` | 30x24 | accent | quadruped, biped, blob, serpentine | Crystals jutting from back | rock, ice, psychic |
|
||||||
|
| [ ] | `back/rock_plates.png` | 38x20 | accent | quadruped, biped, serpentine | Boulder armor plates | rock, ground, fighting |
|
||||||
|
| [ ] | `back/icicle_spikes.png` | 29x24 | type | quadruped, biped, aquatic, avian, blob | Tight fan of icicles rising from the back | ice, water, normal |
|
||||||
|
| [ ] | `back/mane_fluffy.png` | 34x26 | main | quadruped, biped | Big fluffy fur collar/mane | normal, fighting, electric |
|
||||||
|
| [ ] | `back/coil_spikes.png` | 26x24 | type | quadruped, biped, blob, insectoid | Chunky coil/battery prongs with a spark | electric, normal, fire |
|
||||||
|
| [ ] | `back/cloud_puffs.png` | 30x20 | type | blob, avian, quadruped, serpentine | Fluffy cloud hugging the back | flying, ice, psychic |
|
||||||
|
| [ ] | `back/ghost_tatters.png` | 26x24 | accent | biped, blob, serpentine, avian | Short ghostly shroud draped from the shoulders | ghost, poison, psychic |
|
||||||
|
|
||||||
|
## tail (17)
|
||||||
|
|
||||||
|
| Done | File | Size | Palette | Plans | Description | Most likely for |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| [ ] | `tail/tail_stub.png` | 10x10 | main | quadruped, biped, avian | Short round stub tail | normal, ice, ground |
|
||||||
|
| [ ] | `tail/tail_fluffy.png` | 26x20 | main | quadruped, biped | Big fluffy fox/squirrel tail | normal, ice, electric |
|
||||||
|
| [ ] | `tail/tail_long_thin.png` | 30x12 | main | quadruped, biped | Long thin tail with tuft | psychic, normal, electric |
|
||||||
|
| [ ] | `tail/tail_curly.png` | 16x16 | main | quadruped, biped | Spiral curly tail | normal, psychic, grass |
|
||||||
|
| [ ] | `tail/tail_flame_tip.png` | 28x20 | type | quadruped, biped, serpentine | Tail ending in a flame | fire, normal, water |
|
||||||
|
| [ ] | `tail/tail_lightning.png` | 26x22 | type | quadruped, biped | Zigzag lightning-bolt tail | electric, normal, fire |
|
||||||
|
| [ ] | `tail/tail_leaf.png` | 24x16 | type | quadruped, biped, insectoid, serpentine | Tail ending in a big leaf | grass, bug, normal |
|
||||||
|
| [ ] | `tail/tail_fish_fin.png` | 22x22 | accent | aquatic, serpentine, quadruped, biped | Forked fish/mermaid tail fin | water, ice, dragon |
|
||||||
|
| [ ] | `tail/tail_club.png` | 28x16 | main | quadruped, biped | Heavy club-ended tail | rock, ground, fighting |
|
||||||
|
| [ ] | `tail/tail_spiked.png` | 28x14 | main | quadruped, biped, serpentine | Tail with spikes | ground, rock, dragon |
|
||||||
|
| [ ] | `tail/tail_stinger.png` | 22x18 | accent | insectoid, serpentine, quadruped, biped | Curved stinger tail | poison, bug, normal |
|
||||||
|
| [ ] | `tail/tail_feather_fan.png` | 22x22 | accent | avian, biped | Fanned tail feathers | flying, fire, psychic |
|
||||||
|
| [ ] | `tail/tail_wispy.png` | 24x16 | main | blob, serpentine, biped, avian | Fading smoky wisp tail | ghost, psychic, fire |
|
||||||
|
| [ ] | `tail/tail_ice_shard.png` | 24x16 | type | quadruped, biped, aquatic, serpentine | Tail ending in an ice shard | ice, normal, fire |
|
||||||
|
| [ ] | `tail/tail_dragon.png` | 30x16 | main | quadruped, biped, serpentine | Thick tapered tail with small fin | dragon, fire, ground |
|
||||||
|
| [ ] | `tail/tail_rattle.png` | 14x12 | accent | serpentine | Rattlesnake rattle tip | poison, ground, normal |
|
||||||
|
| [ ] | `tail/tail_crystal.png` | 24x16 | accent | quadruped, biped, serpentine | Tail tipped with a crystal | psychic, rock, ice |
|
||||||
|
|
||||||
|
## aura (14)
|
||||||
|
|
||||||
|
| Done | File | Size | Palette | Plans | Description | Most likely for |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| [ ] | `aura/aura_embers.png` | 84x84 | type | all | Floating embers | fire |
|
||||||
|
| [ ] | `aura/aura_sparks.png` | 84x84 | type | all | Crackling sparks | electric |
|
||||||
|
| [ ] | `aura/aura_bubbles.png` | 84x84 | type | all | Rising bubbles | water, poison |
|
||||||
|
| [ ] | `aura/aura_leaves.png` | 84x84 | type | all | Drifting leaves | grass, flying, bug |
|
||||||
|
| [ ] | `aura/aura_snowflakes.png` | 84x84 | type | all | Snowflakes | ice |
|
||||||
|
| [ ] | `aura/aura_wisps.png` | 84x84 | type | all | Ghost wisps | ghost, psychic |
|
||||||
|
| [ ] | `aura/aura_rings.png` | 84x84 | type | all | Psychic rings/ripples | psychic |
|
||||||
|
| [ ] | `aura/aura_dust.png` | 84x84 | type | all | Dust cloud at the feet | ground, rock, fighting |
|
||||||
|
| [ ] | `aura/aura_toxic.png` | 84x84 | type | all | Toxic bubbles and drips | poison |
|
||||||
|
| [ ] | `aura/aura_sparkle.png` | 84x84 | type | all | Twinkling sparkles | dragon, normal, psychic |
|
||||||
|
| [ ] | `aura/aura_gusts.png` | 84x84 | type | all | Wind swirls | flying, dragon |
|
||||||
|
| [ ] | `aura/aura_pebbles.png` | 84x84 | type | all | Floating pebbles | rock, ground, psychic |
|
||||||
|
| [ ] | `aura/aura_power.png` | 84x84 | type | all | Power-up energy lines | fighting |
|
||||||
|
| [ ] | `aura/aura_spores.png` | 84x84 | type | all | Floating spores | bug, grass, poison |
|
||||||
|
|
||||||
|
## Simulated pick rates per type
|
||||||
|
|
||||||
|
From 3,000 random seeds per type. Use this to spot parts that never show up or show up too much.
|
||||||
|
|
||||||
|
### Normal
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 41%, biped 32%, avian 15%, blob 8%, serpentine 4%
|
||||||
|
- **body:** base_pup 18%, base_bunny 14%, base_imp 12%, base_chick 9%, base_fox 8%, base_cub 8%, base_blob 7%, base_fawn 4%
|
||||||
|
- **head_top:** none 61%, spiky_hair 9%, horns_ram 8%, headband 6%, horn_single 3%, ears_round 2%, ears_floppy 2%, flower_bloom 1%
|
||||||
|
- **back:** none 47%, mane_fluffy 17%, wings_feather_small 13%, wings_feather 8%, cloud_puffs 6%, coil_spikes 2%, spikes_back 2%
|
||||||
|
- **tail:** none 36%, tail_stub 23%, tail_fluffy 15%, tail_curly 12%, tail_long_thin 7%, tail_feather_fan 3%, tail_rattle 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 42%, belly 31%, spots 15%, speckles 12%
|
||||||
|
|
||||||
|
### Fire
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 43%, biped 26%, avian 12%, serpentine 11%, blob 8%
|
||||||
|
- **body:** base_dino 18%, base_pup 11%, base_salamander 11%, base_fox 10%, base_drake 10%, base_hawk 9%, base_wisp 5%, base_wyrm 4%
|
||||||
|
- **head_top:** crest_flame 53%, none 15%, wisp_flame 10%, horns_dragon 5%, spiky_hair 5%, crest_feather 4%, ears_pointed 2%, horn_single 1%
|
||||||
|
- **back:** none 34%, flame_mane 19%, sail_fin 14%, wings_dragon 10%, spikes_back 7%, wings_feather 6%, mane_fluffy 5%, wings_feather_small 2%
|
||||||
|
- **tail:** none 68%, tail_flame_tip 15%, tail_feather_fan 5%, tail_dragon 3%, tail_wispy 3%, tail_stub 2%, tail_fluffy 2%, tail_long_thin 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 58%, belly 29%, spots 9%, speckles 5%
|
||||||
|
|
||||||
|
### Water
|
||||||
|
|
||||||
|
- **Body plan:** aquatic 43%, serpentine 22%, quadruped 16%, biped 12%, blob 8%
|
||||||
|
- **body:** base_eel 19%, base_guppy 16%, base_otter 14%, base_axolotl 12%, base_salamander 8%, base_slime 6%, base_dino 6%, base_pup 4%
|
||||||
|
- **head_top:** water_spout 48%, none 26%, ears_fin 11%, horn_single 3%, spiky_hair 2%, horns_ram 1%, horns_dragon 1%, flower_bloom 1%
|
||||||
|
- **back:** none 41%, dorsal_fin 37%, icicle_spikes 6%, shell_round 6%, sail_fin 4%, shell_spiked 2%, spikes_back 1%, wings_feather_small 1%
|
||||||
|
- **tail:** none 72%, tail_fish_fin 14%, tail_dragon 7%, tail_rattle 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 48%, spots 25%, belly 23%, speckles 4%
|
||||||
|
|
||||||
|
### Grass
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 38%, biped 28%, blob 18%, insectoid 9%, serpentine 8%
|
||||||
|
- **body:** base_fawn 19%, base_bunny 15%, base_blob 14%, base_cub 9%, base_pixie 9%, base_grub 8%, base_noodle 6%, base_salamander 4%
|
||||||
|
- **head_top:** leaf_sprout 42%, flower_bloom 26%, mushroom_cap 15%, none 10%, ears_long 1%
|
||||||
|
- **back:** leaf_cape 31%, plant_bulb 29%, flower_back 17%, none 9%, mushroom_back 9%, wings_bug 4%
|
||||||
|
- **tail:** none 87%, tail_leaf 11%, tail_curly 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 48%, spots 37%, belly 12%, speckles 4%
|
||||||
|
|
||||||
|
### Electric
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 38%, biped 28%, blob 18%, insectoid 9%, avian 8%
|
||||||
|
- **body:** base_fox 26%, base_imp 22%, base_blob 12%, base_bee 6%, base_chick 5%, base_drake 2%, base_fawn 2%, base_pup 2%
|
||||||
|
- **head_top:** none 23%, cloud_tuft 23%, spiky_hair 22%, antennae 15%, ears_pointed 4%, horn_single 2%, ears_bolt 2%, ears_round 2%
|
||||||
|
- **back:** coil_spikes 33%, none 33%, mane_fluffy 10%, cloud_puffs 9%, wings_bug 5%, sail_fin 4%, wings_feather_small 2%, wings_feather 1%
|
||||||
|
- **tail:** none 73%, tail_lightning 13%, tail_long_thin 5%, tail_fluffy 4%, tail_stub 2%, tail_curly 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 54%, spots 29%, belly 14%, speckles 4%
|
||||||
|
|
||||||
|
### Ice
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 28%, aquatic 19%, biped 18%, avian 13%, blob 13%, serpentine 9%
|
||||||
|
- **body:** base_fawn 14%, base_otter 12%, base_bunny 12%, base_blob 10%, base_fox 9%, base_penguin 8%, base_guppy 7%, base_eel 6%
|
||||||
|
- **head_top:** ice_crown 52%, none 13%, cloud_tuft 10%, crystal_spikes 10%, water_spout 5%, horn_single 1%, ears_fin 1%, ears_round 1%
|
||||||
|
- **back:** icicle_spikes 50%, none 18%, crystal_cluster 12%, spikes_back 6%, cloud_puffs 5%, dorsal_fin 4%, mane_fluffy 2%, wings_feather_small 1%
|
||||||
|
- **tail:** none 77%, tail_ice_shard 14%, tail_stub 3%, tail_crystal 2%, tail_fluffy 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 49%, belly 24%, speckles 20%, spots 6%
|
||||||
|
|
||||||
|
### Fighting
|
||||||
|
|
||||||
|
- **Body plan:** biped 75%, quadruped 20%, insectoid 5%
|
||||||
|
- **body:** base_brawler 61%, base_cub 10%, base_pup 7%, base_dino 4%, base_bee 4%, base_bunny 4%, base_imp 3%, base_pixie 3%
|
||||||
|
- **head_top:** headband 38%, none 20%, horns_ram 16%, horn_single 15%, spiky_hair 7%
|
||||||
|
- **back:** none 42%, mane_fluffy 23%, shell_spiked 11%, rock_plates 10%, spikes_back 2%, shell_round 2%, wings_feather 2%, wings_feather_small 2%
|
||||||
|
- **tail:** none 72%, tail_club 12%, tail_stub 4%, tail_long_thin 3%, tail_fluffy 3%, tail_curly 2%, tail_spiked 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 58%, belly 29%, spots 9%, speckles 5%
|
||||||
|
|
||||||
|
### Poison
|
||||||
|
|
||||||
|
- **Body plan:** blob 32%, serpentine 24%, quadruped 15%, biped 15%, insectoid 14%
|
||||||
|
- **body:** base_slime 27%, base_noodle 21%, base_salamander 10%, base_grub 10%, base_pixie 9%, base_beetle 3%, base_wisp 2%, base_dino 2%
|
||||||
|
- **head_top:** mushroom_cap 29%, none 19%, flower_bloom 15%, horn_single 12%, spiky_hair 12%, antennae 5%, ears_pointed 1%, horns_ram 1%
|
||||||
|
- **back:** none 30%, spikes_back 15%, mushroom_back 11%, plant_bulb 10%, flower_back 10%, wings_bat 10%, wings_bug 8%, ghost_tatters 2%
|
||||||
|
- **tail:** none 72%, tail_stinger 13%, tail_rattle 7%, tail_spiked 4%, tail_long_thin 2%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 48%, spots 37%, belly 12%, speckles 4%
|
||||||
|
|
||||||
|
### Ground
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 48%, biped 29%, serpentine 18%, insectoid 4%
|
||||||
|
- **body:** base_cub 32%, base_noodle 16%, base_brawler 15%, base_dino 10%, base_drake 7%, base_beetle 3%, base_salamander 2%, base_fawn 2%
|
||||||
|
- **head_top:** horn_single 35%, none 34%, horns_ram 18%, spiky_hair 2%, flower_bloom 2%, mushroom_cap 1%, leaf_sprout 1%
|
||||||
|
- **back:** spikes_back 26%, none 22%, rock_plates 19%, shell_spiked 15%, shell_round 14%
|
||||||
|
- **tail:** none 62%, tail_club 13%, tail_spiked 12%, tail_stub 3%, tail_dragon 3%, tail_rattle 3%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 42%, speckles 33%, belly 20%, spots 5%
|
||||||
|
|
||||||
|
### Flying
|
||||||
|
|
||||||
|
- **Body plan:** avian 71%, insectoid 10%, quadruped 10%, serpentine 9%
|
||||||
|
- **body:** base_hawk 35%, base_owl 21%, base_chick 14%, base_bee 8%, base_eel 3%, base_wyrm 3%, base_noodle 3%, base_drake 2%
|
||||||
|
- **head_top:** cloud_tuft 51%, none 35%, crest_feather 5%, flower_bloom 2%, leaf_sprout 1%, horn_single 1%
|
||||||
|
- **back:** cloud_puffs 46%, wings_feather_small 24%, none 15%, wings_feather 9%, leaf_cape 4%
|
||||||
|
- **tail:** none 72%, tail_feather_fan 24%, tail_stub 2%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 69%, belly 17%, spots 9%, speckles 5%
|
||||||
|
|
||||||
|
### Psychic
|
||||||
|
|
||||||
|
- **Body plan:** biped 39%, quadruped 29%, blob 18%, avian 14%
|
||||||
|
- **body:** base_pixie 18%, base_wisp 14%, base_fox 13%, base_fawn 12%, base_owl 12%, base_bunny 9%, base_imp 9%, base_cub 2%
|
||||||
|
- **head_top:** gem_forehead 44%, none 14%, flower_bloom 12%, antennae 7%, wisp_flame 6%, crystal_spikes 4%, ears_long 3%, ears_pointed 2%
|
||||||
|
- **back:** none 31%, crystal_cluster 28%, cloud_puffs 9%, flower_back 7%, ghost_tatters 6%, wings_bug 5%, wings_bat 5%, wings_feather_small 3%
|
||||||
|
- **tail:** none 72%, tail_crystal 6%, tail_long_thin 6%, tail_curly 5%, tail_wispy 4%, tail_fluffy 3%, tail_stub 2%, tail_feather_fan 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 69%, belly 17%, spots 9%, speckles 5%
|
||||||
|
|
||||||
|
### Bug
|
||||||
|
|
||||||
|
- **Body plan:** insectoid 83%, serpentine 11%, avian 6%
|
||||||
|
- **body:** base_grub 29%, base_beetle 28%, base_bee 26%, base_eel 4%, base_noodle 4%, base_wyrm 3%, base_penguin 2%, base_hawk 1%
|
||||||
|
- **head_top:** antennae 31%, mushroom_cap 22%, none 22%, horn_single 20%
|
||||||
|
- **back:** wings_bug 31%, none 29%, mushroom_back 21%, leaf_cape 18%
|
||||||
|
- **tail:** none 86%, tail_stinger 9%, tail_leaf 4%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 54%, spots 29%, belly 14%, speckles 4%
|
||||||
|
|
||||||
|
### Rock
|
||||||
|
|
||||||
|
- **Body plan:** quadruped 32%, biped 31%, blob 19%, serpentine 19%
|
||||||
|
- **body:** base_brawler 22%, base_cub 16%, base_pebble 16%, base_drake 10%, base_wyrm 7%, base_noodle 6%, base_eel 6%, base_pixie 2%
|
||||||
|
- **head_top:** none 23%, horn_single 22%, horns_ram 17%, gem_forehead 17%, crystal_spikes 12%, spiky_hair 2%
|
||||||
|
- **back:** crystal_cluster 27%, rock_plates 19%, spikes_back 18%, none 15%, shell_spiked 11%, shell_round 6%, wings_dragon 1%
|
||||||
|
- **tail:** none 73%, tail_spiked 9%, tail_crystal 8%, tail_club 7%, tail_stub 1%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 46%, speckles 36%, belly 11%, spots 6%
|
||||||
|
|
||||||
|
### Ghost
|
||||||
|
|
||||||
|
- **Body plan:** blob 65%, serpentine 20%, biped 10%, avian 5%
|
||||||
|
- **body:** base_wisp 50%, base_wyrm 17%, base_slime 10%, base_pixie 6%, base_owl 4%, base_pebble 3%, base_blob 3%, base_imp 2%
|
||||||
|
- **head_top:** wisp_flame 44%, none 21%, mushroom_cap 8%, gem_forehead 7%, horns_dragon 6%, ears_pointed 3%, ears_round 2%, horn_single 1%
|
||||||
|
- **back:** ghost_tatters 40%, none 29%, wings_bat 26%, mushroom_back 3%
|
||||||
|
- **tail:** none 84%, tail_wispy 14%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 69%, belly 17%, spots 9%, speckles 5%
|
||||||
|
|
||||||
|
### Dragon
|
||||||
|
|
||||||
|
- **Body plan:** serpentine 39%, quadruped 29%, biped 27%, avian 4%
|
||||||
|
- **body:** base_wyrm 25%, base_dino 21%, base_drake 18%, base_eel 12%, base_salamander 8%, base_hawk 2%, base_brawler 2%, base_imp 2%
|
||||||
|
- **head_top:** none 30%, gem_forehead 29%, horns_dragon 23%, horn_single 7%, ears_fin 2%, spiky_hair 2%, flower_bloom 2%
|
||||||
|
- **back:** none 28%, wings_dragon 25%, sail_fin 14%, spikes_back 14%, wings_bat 10%, dorsal_fin 3%, flame_mane 1%
|
||||||
|
- **tail:** none 57%, tail_dragon 23%, tail_spiked 11%, tail_fish_fin 3%, tail_stub 2%
|
||||||
|
- **aura:** none 100%
|
||||||
|
- **markings:** none 58%, belly 29%, spots 9%, speckles 5%
|
||||||
172
build_parts.py
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Compile text-grid part sources into palette-key PNGs.
|
||||||
|
|
||||||
|
Sources live in parts_src/<slot>/<name>.txt and compile to parts/<slot>/<name>.png.
|
||||||
|
Every non-blank line is one pixel row; lines starting with '#' are comments. All rows
|
||||||
|
must be the same width.
|
||||||
|
|
||||||
|
Legend
|
||||||
|
. transparent o outline
|
||||||
|
1 main_light 2 main_base 3 main_dark
|
||||||
|
a accent_light b accent_base c accent_dark
|
||||||
|
x type_light y type_base z type_dark
|
||||||
|
w white p pupil
|
||||||
|
P pivot T tail B back A aura ^ head_top
|
||||||
|
|
||||||
|
Layout sources (parts_src/<slot>/<name>.layout) assemble stamps onto a fixed canvas,
|
||||||
|
used for the 84x84 auras:
|
||||||
|
size 84 84
|
||||||
|
stamp <stamp-name> <x> <y> [flip] # parts_src/<slot>/stamps/<stamp-name>.txt, top-left at x,y
|
||||||
|
marker <char> <x> <y> # e.g. "marker P 42 42"
|
||||||
|
Later stamps paint over earlier ones; '.' in a stamp never paints.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
python build_parts.py # build everything
|
||||||
|
python build_parts.py body/quad_slim # build some parts
|
||||||
|
python build_parts.py mirror FILE # print FILE + its horizontal mirror (shared center column)
|
||||||
|
python build_parts.py mirror FILE --gap N # mirror with N transparent columns between halves
|
||||||
|
python build_parts.py flip FILE # print FILE flipped horizontally
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from generate_sprite import KEYS, MARKERS, PUPIL, EYE_WHITE
|
||||||
|
|
||||||
|
# PARTS_SRC / PARTS_DIR let experiments build into a separate library (e.g. proto/).
|
||||||
|
SRC = Path(os.environ.get("PARTS_SRC", "parts_src"))
|
||||||
|
OUT = Path(os.environ.get("PARTS_DIR", "parts"))
|
||||||
|
|
||||||
|
LEGEND = {
|
||||||
|
"o": KEYS["outline"],
|
||||||
|
"1": KEYS["main_light"], "2": KEYS["main_base"], "3": KEYS["main_dark"],
|
||||||
|
"a": KEYS["accent_light"], "b": KEYS["accent_base"], "c": KEYS["accent_dark"],
|
||||||
|
"x": KEYS["type_light"], "y": KEYS["type_base"], "z": KEYS["type_dark"],
|
||||||
|
"w": EYE_WHITE, "p": PUPIL,
|
||||||
|
"P": MARKERS["pivot"], "T": MARKERS["tail"], "B": MARKERS["back"],
|
||||||
|
"A": MARKERS["aura"], "^": MARKERS["head_top"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SourceError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def read_grid(path):
|
||||||
|
rows = []
|
||||||
|
for n, line in enumerate(Path(path).read_text().splitlines(), 1):
|
||||||
|
line = line.rstrip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
bad = sorted({c for c in line if c != "." and c not in LEGEND})
|
||||||
|
if bad:
|
||||||
|
raise SourceError(f"{path}:{n}: unknown characters {bad}")
|
||||||
|
rows.append(line)
|
||||||
|
if not rows:
|
||||||
|
raise SourceError(f"{path}: empty grid")
|
||||||
|
widths = {len(r) for r in rows}
|
||||||
|
if len(widths) != 1:
|
||||||
|
for n, r in enumerate(rows):
|
||||||
|
if len(r) != len(rows[0]):
|
||||||
|
raise SourceError(f"{path}: row {n} is {len(r)} wide, row 0 is {len(rows[0])}")
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def paint(canvas, rows, x0, y0, flip=False):
|
||||||
|
for y, row in enumerate(rows):
|
||||||
|
if flip:
|
||||||
|
row = row[::-1]
|
||||||
|
for x, c in enumerate(row):
|
||||||
|
if c == ".":
|
||||||
|
continue
|
||||||
|
px, py = x0 + x, y0 + y
|
||||||
|
if not (0 <= px < len(canvas[0]) and 0 <= py < len(canvas)):
|
||||||
|
raise SourceError(f"stamp pixel ({px},{py}) outside canvas")
|
||||||
|
canvas[py][px] = c
|
||||||
|
|
||||||
|
|
||||||
|
def read_layout(path):
|
||||||
|
path = Path(path)
|
||||||
|
canvas = None
|
||||||
|
for n, line in enumerate(path.read_text().splitlines(), 1):
|
||||||
|
parts = line.split("#")[0].split()
|
||||||
|
if not parts:
|
||||||
|
continue
|
||||||
|
cmd, args = parts[0], parts[1:]
|
||||||
|
try:
|
||||||
|
if cmd == "size":
|
||||||
|
w, h = int(args[0]), int(args[1])
|
||||||
|
canvas = [["."] * w for _ in range(h)]
|
||||||
|
elif cmd == "stamp":
|
||||||
|
rows = read_grid(path.parent / "stamps" / f"{args[0]}.txt")
|
||||||
|
paint(canvas, rows, int(args[1]), int(args[2]), flip=len(args) > 3 and args[3] == "flip")
|
||||||
|
elif cmd == "marker":
|
||||||
|
paint(canvas, [args[0]], int(args[1]), int(args[2]))
|
||||||
|
else:
|
||||||
|
raise SourceError(f"unknown command {cmd!r}")
|
||||||
|
except (IndexError, ValueError, TypeError) as e:
|
||||||
|
raise SourceError(f"{path}:{n}: {e}")
|
||||||
|
if canvas is None:
|
||||||
|
raise SourceError(f"{path}: missing 'size'")
|
||||||
|
return ["".join(r) for r in canvas]
|
||||||
|
|
||||||
|
|
||||||
|
def to_image(rows):
|
||||||
|
h, w = len(rows), len(rows[0])
|
||||||
|
img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||||
|
px = img.load()
|
||||||
|
for y, row in enumerate(rows):
|
||||||
|
for x, c in enumerate(row):
|
||||||
|
if c != ".":
|
||||||
|
px[x, y] = LEGEND[c] + (255,)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def sources():
|
||||||
|
for p in sorted(SRC.glob("*/*")):
|
||||||
|
if p.suffix in (".txt", ".layout"):
|
||||||
|
yield p
|
||||||
|
|
||||||
|
|
||||||
|
def build(path):
|
||||||
|
rows = read_layout(path) if path.suffix == ".layout" else read_grid(path)
|
||||||
|
out = OUT / path.parent.name / f"{path.stem}.png"
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
to_image(rows).save(out)
|
||||||
|
return out, len(rows[0]), len(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if argv and argv[0] in ("mirror", "flip"):
|
||||||
|
rows = read_grid(argv[1])
|
||||||
|
if argv[0] == "flip":
|
||||||
|
print("\n".join(r[::-1] for r in rows))
|
||||||
|
else:
|
||||||
|
gap = int(argv[3]) if len(argv) > 3 and argv[2] == "--gap" else None
|
||||||
|
for r in rows:
|
||||||
|
print(r + "." * gap + r[::-1] if gap is not None else r + r[-2::-1])
|
||||||
|
return 0
|
||||||
|
wanted = set(argv)
|
||||||
|
errors = 0
|
||||||
|
built = 0
|
||||||
|
for path in sources():
|
||||||
|
key = f"{path.parent.name}/{path.stem}"
|
||||||
|
if wanted and key not in wanted:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
out, w, h = build(path)
|
||||||
|
built += 1
|
||||||
|
if wanted:
|
||||||
|
print(f"{out} {w}x{h}")
|
||||||
|
except SourceError as e:
|
||||||
|
print(f"ERROR {e}", file=sys.stderr)
|
||||||
|
errors += 1
|
||||||
|
print(f"built {built} part(s), {errors} error(s)")
|
||||||
|
return 1 if errors else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
76
business_pokemon_type.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
|
||||||
|
if not OPENROUTER_API_KEY:
|
||||||
|
sys.exit("Set OPENROUTER_API_KEY first: export OPENROUTER_API_KEY='sk-or-v1-...'")
|
||||||
|
|
||||||
|
# All 15 Gen 1 types, tuned for home service businesses. Each trade is listed under
|
||||||
|
# exactly one type, and Rock is limited to stone/roofing work so "construction-y"
|
||||||
|
# businesses spread out by what they actually do.
|
||||||
|
POKEMON_TYPES = {
|
||||||
|
"normal": "General or multi-trade home services: general contractors, remodeling, handymen, painters, drywall, flooring, cabinets, house cleaning and maid services, home inspectors",
|
||||||
|
"fire": "Heat and engines: heating and furnace repair, fireplaces, chimney sweeps, outdoor kitchens and grills, welding, auto repair and mechanics",
|
||||||
|
"water": "Water: plumbing, leak detection, water heaters, water damage restoration, pools and spas, irrigation and sprinklers, pressure washing, car washes and auto detailing",
|
||||||
|
"grass": "Plants and yards: landscaping, lawn care and mowing, tree service, sod, gardening, nurseries, mulch",
|
||||||
|
"electric": "Electricity and tech: electricians, lighting, solar, generators, EV chargers, smart home, home theater, alarm and camera systems, appliance repair",
|
||||||
|
"ice": "Cooling: air conditioning repair and install, refrigeration, insulation, ice machines, cold storage",
|
||||||
|
"fighting": "Protection and heavy labor: fencing and gates, security, demolition, moving companies, hauling, furniture assembly",
|
||||||
|
"poison": "Waste and contaminants: junk removal, dumpster rental, septic, mold remediation, asbestos and lead abatement, hazardous waste",
|
||||||
|
"ground": "Earth and dirt work: foundation repair, excavation, grading, drainage and French drains, concrete, driveways, paving, patios and hardscaping",
|
||||||
|
"flying": "Air and high places: gutters, air duct cleaning, dryer vents, indoor air quality, window cleaning, drones, couriers",
|
||||||
|
"psychic": "Knowledge and planning: interior design, architects, real estate, property management, home warranties, insurance, consulting, marketing",
|
||||||
|
"bug": "Insects and critters: pest control, exterminators, termite treatment, wildlife and rodent removal, beekeeping",
|
||||||
|
"rock": "Stone only: roofing, masonry, brick, stone veneer, granite and quartz countertops, tile, retaining walls",
|
||||||
|
"ghost": "Hidden spaces and the unseen: attics, crawl spaces, basement waterproofing, locksmiths, estate cleanouts, trauma and crime scene cleanup, funeral homes",
|
||||||
|
"dragon": "Premium or rare: luxury custom homes, high-end remodels, custom pools, exotic or performance auto shops, elite luxury brands",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_business_type(description: str) -> dict:
|
||||||
|
response = requests.post(
|
||||||
|
url="https://openrouter.ai/api/alpha/decisions",
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
data=json.dumps({
|
||||||
|
"model": "typesafe/jev-1.13",
|
||||||
|
"state": f"Business description: {description}",
|
||||||
|
"questions": {
|
||||||
|
"pokemon_type": {
|
||||||
|
"type": "choice",
|
||||||
|
"instructions": (
|
||||||
|
"Which Gen 1 Pokémon type best matches this business? Decide by its "
|
||||||
|
"specific trade or main service, not by the fact that it is construction, "
|
||||||
|
"home service, or physical work in general. Being construction-related is "
|
||||||
|
"not a reason to choose Rock; choose Rock only for roofing or stone work. "
|
||||||
|
"If the description is only a business name, use the trade the name implies."
|
||||||
|
),
|
||||||
|
"criteria": POKEMON_TYPES,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()["answers"]["pokemon_type"]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
desc = " ".join(sys.argv[1:]) or "Residential lawn mowing, hedge trimming, and flower bed installation"
|
||||||
|
answer = get_business_type(desc)
|
||||||
|
|
||||||
|
choice = answer["choice"]
|
||||||
|
probs = answer["probabilities"]
|
||||||
|
top3 = sorted(probs.items(), key=lambda kv: kv[1], reverse=True)[:3]
|
||||||
|
|
||||||
|
print(f"Business: {desc}")
|
||||||
|
print(f"Type: {choice.upper()} ({probs[choice]:.0%})")
|
||||||
|
print("Top 3: " + ", ".join(f"{t} {p:.0%}" for t, p in top3))
|
||||||
|
|
||||||
|
# Low confidence usually means a vague description or a true dual-type business
|
||||||
|
if probs[choice] < 0.5:
|
||||||
|
print(f"Low confidence. Possible dual type: {top3[0][0]}/{top3[1][0]}")
|
||||||
729
generate_sprite.py
Normal file
@@ -0,0 +1,729 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate a Pokémon-style sprite for a business.
|
||||||
|
|
||||||
|
Pipeline: Jev picks a type -> seeded RNG picks a base creature and its variation parts
|
||||||
|
(weighted by type) -> parts snap together via anchor pixels -> markings painted on ->
|
||||||
|
palette keys swapped for colors pulled from the business logo.
|
||||||
|
|
||||||
|
Logo colors: transparent pixels, anti-aliased edges and a solid white/black/gray background
|
||||||
|
(the colour filling the image border) are ignored; hued brand colours win over white, black
|
||||||
|
or gray text. A logo that is only white/black (or only its background) still uses that.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
python generate_sprite.py "Rosas Landscaping" logos/rosas.png # Jev picks the type
|
||||||
|
python generate_sprite.py "Rosas Landscaping" logos/rosas.png --type grass
|
||||||
|
python generate_sprite.py "Rosas Landscaping" --type grass --colors "#2E7D32,#FBC02D"
|
||||||
|
python generate_sprite.py --batch businesses.csv # columns: name,logo,type,description
|
||||||
|
python generate_sprite.py --make-placeholders # test art for every part
|
||||||
|
python generate_sprite.py --sprite-list SPRITES.md # art checklist + weight report
|
||||||
|
|
||||||
|
Requires: pip install pillow numpy requests
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import colorsys
|
||||||
|
import csv
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
import sprite_parts as cfg
|
||||||
|
|
||||||
|
CANVAS = 96 # final sprite size (square)
|
||||||
|
GROUND_MARGIN = 4 # empty rows under the feet
|
||||||
|
|
||||||
|
# Palette keys: draw every part using ONLY these exact colors (plus pass-through colors
|
||||||
|
# like eye white). They get swapped for logo-derived colors at render time.
|
||||||
|
KEYS = {
|
||||||
|
"main_light": (255, 128, 128), "main_base": (255, 0, 0), "main_dark": (128, 0, 0),
|
||||||
|
"accent_light": (128, 255, 128), "accent_base": (0, 255, 0), "accent_dark": (0, 128, 0),
|
||||||
|
"type_light": (128, 128, 255), "type_base": (0, 0, 255), "type_dark": (0, 0, 128),
|
||||||
|
"outline": (0, 0, 0),
|
||||||
|
}
|
||||||
|
PUPIL = (20, 20, 20) # near-black that is NOT recolored
|
||||||
|
EYE_WHITE = (255, 255, 255)
|
||||||
|
|
||||||
|
# Anchor marker pixels. Place one pixel of the color where a child part attaches.
|
||||||
|
# They are read, then replaced with the surrounding color.
|
||||||
|
MARKERS = {
|
||||||
|
"pivot": (255, 0, 255), "tail": (253, 0, 255), "back": (252, 0, 255),
|
||||||
|
"aura": (251, 0, 255), "head_top": (250, 0, 255),
|
||||||
|
}
|
||||||
|
|
||||||
|
PICK_ORDER = ["body", "head_top", "back", "tail", "aura"]
|
||||||
|
DEFAULT_Z = {"aura": 0, "back": 10, "tail": 20, "body": 30, "head_top": 50}
|
||||||
|
|
||||||
|
|
||||||
|
def warn(msg):
|
||||||
|
print(f" ! {msg}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ seeded picking
|
||||||
|
|
||||||
|
def make_rng(name, reroll=0):
|
||||||
|
key = f"{name.strip().lower()}|{reroll}"
|
||||||
|
return random.Random(int(hashlib.sha256(key.encode()).hexdigest()[:16], 16))
|
||||||
|
|
||||||
|
|
||||||
|
def weighted_pick(rng, items):
|
||||||
|
items = [(it, w) for it, w in items if w > 0]
|
||||||
|
total = sum(w for _, w in items)
|
||||||
|
if total <= 0:
|
||||||
|
return None
|
||||||
|
r = rng.random() * total
|
||||||
|
acc = 0.0
|
||||||
|
for it, w in items:
|
||||||
|
acc += w
|
||||||
|
if r < acc:
|
||||||
|
return it
|
||||||
|
return items[-1][0]
|
||||||
|
|
||||||
|
|
||||||
|
def part_weight(p, ptype):
|
||||||
|
return p["types"].get(ptype, p["other"])
|
||||||
|
|
||||||
|
|
||||||
|
def candidates(slot, plan, ptype, tags, excluded, denied=()):
|
||||||
|
out = []
|
||||||
|
for p in cfg.SLOTS[slot]:
|
||||||
|
if plan not in p["plans"] or p["name"] in denied:
|
||||||
|
continue
|
||||||
|
if p["requires"] and not set(p["requires"]) & tags:
|
||||||
|
continue
|
||||||
|
if set(p["tags"]) & excluded or set(p["excludes"]) & tags:
|
||||||
|
continue
|
||||||
|
out.append((p, part_weight(p, ptype)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def motif_factor(p, motifs):
|
||||||
|
"""Keep one creature's features on a theme: same motif boosted, clashing motifs damped."""
|
||||||
|
m = cfg.MOTIFS.get(p["name"])
|
||||||
|
if m is None or not motifs:
|
||||||
|
return 1.0
|
||||||
|
if m in motifs:
|
||||||
|
return cfg.MOTIF_SAME
|
||||||
|
if all(frozenset((m, o)) in cfg.MOTIF_PAIRS for o in motifs):
|
||||||
|
return 1.0
|
||||||
|
return cfg.MOTIF_CLASH
|
||||||
|
|
||||||
|
|
||||||
|
def pick_parts(ptype, rng):
|
||||||
|
plan = weighted_pick(rng, cfg.PLAN_WEIGHTS[ptype].items())
|
||||||
|
chosen, tags, excluded, denied, motifs = {}, {f"plan:{plan}"}, set(), set(), set()
|
||||||
|
features = 0
|
||||||
|
for slot in PICK_ORDER:
|
||||||
|
if slot in cfg.DISABLED_SLOTS:
|
||||||
|
continue
|
||||||
|
cands = candidates(slot, plan, ptype, tags, excluded, denied)
|
||||||
|
if slot in cfg.FEATURE_SLOTS:
|
||||||
|
if features >= cfg.MAX_FEATURES:
|
||||||
|
cands = [(p, w) for p, w in cands if p["name"] == "none"]
|
||||||
|
cands = [(p, w * motif_factor(p, motifs)) for p, w in cands]
|
||||||
|
p = weighted_pick(rng, cands)
|
||||||
|
if p is None:
|
||||||
|
continue
|
||||||
|
chosen[slot] = p
|
||||||
|
tags |= set(p["tags"])
|
||||||
|
excluded |= set(p["excludes"])
|
||||||
|
denied |= set(p.get("deny", ()))
|
||||||
|
if p["name"] != "none" and slot in cfg.FEATURE_SLOTS:
|
||||||
|
features += 1
|
||||||
|
if cfg.MOTIFS.get(p["name"]):
|
||||||
|
motifs.add(cfg.MOTIFS[p["name"]])
|
||||||
|
markings = weighted_pick(rng, [(m, w.get(ptype, w.get("_other", 0))) for m, w in cfg.MARKINGS.items()])
|
||||||
|
return plan, chosen, markings, tags
|
||||||
|
|
||||||
|
|
||||||
|
def attributes_from(tags):
|
||||||
|
"""Public attributes: drop compatibility plumbing (*_head family tags, _internal tags, plan)."""
|
||||||
|
return sorted(t for t in tags if not t.endswith("_head") and not t.startswith(("_", "plan:")))
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ loading + compositing
|
||||||
|
|
||||||
|
_part_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def load_part(parts_dir, slot, name):
|
||||||
|
path = Path(parts_dir) / slot / f"{name}.png"
|
||||||
|
if path in _part_cache:
|
||||||
|
return _part_cache[path]
|
||||||
|
if not path.exists():
|
||||||
|
_part_cache[path] = None
|
||||||
|
return None
|
||||||
|
arr = np.array(Image.open(path).convert("RGBA"))
|
||||||
|
rgb, alpha = arr[:, :, :3], arr[:, :, 3] > 0
|
||||||
|
anchors, marker_mask = {}, np.zeros(alpha.shape, bool)
|
||||||
|
for mname, col in MARKERS.items():
|
||||||
|
m = np.all(rgb == col, axis=-1) & alpha
|
||||||
|
ys, xs = np.nonzero(m)
|
||||||
|
if len(xs):
|
||||||
|
anchors[mname] = (int(round(xs.mean())), int(round(ys.mean())))
|
||||||
|
marker_mask |= m
|
||||||
|
# Replace marker pixels with the most common opaque, non-marker 4-neighbor
|
||||||
|
h, w = alpha.shape
|
||||||
|
for y, x in zip(*np.nonzero(marker_mask)):
|
||||||
|
neigh = [tuple(arr[ny, nx]) for ny, nx in ((y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1))
|
||||||
|
if 0 <= ny < h and 0 <= nx < w and arr[ny, nx, 3] > 0 and not marker_mask[ny, nx]]
|
||||||
|
arr[y, x] = Counter(neigh).most_common(1)[0][0] if neigh else (0, 0, 0, 0)
|
||||||
|
result = (Image.fromarray(arr), anchors)
|
||||||
|
_part_cache[path] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def key_mask(arr, key):
|
||||||
|
return np.all(arr[:, :, :3] == KEYS[key], axis=-1) & (arr[:, :, 3] > 0)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_markings(img, kind, rng, center=None):
|
||||||
|
"""Paint accent markings onto main-ramp pixels.
|
||||||
|
|
||||||
|
center: the body's aura anchor (torso centre). When given, markings stay on the body
|
||||||
|
below the face (rows from a little above the torso centre down) and the belly patch
|
||||||
|
is centred there."""
|
||||||
|
if kind in (None, "none"):
|
||||||
|
return img
|
||||||
|
arr = np.array(img)
|
||||||
|
h, w = arr.shape[:2]
|
||||||
|
main = key_mask(arr, "main_light") | key_mask(arr, "main_base") | key_mask(arr, "main_dark")
|
||||||
|
ys, xs = np.mgrid[0:h, 0:w]
|
||||||
|
if center:
|
||||||
|
main &= ys >= center[1] - h * 0.12
|
||||||
|
if not main.any():
|
||||||
|
return img
|
||||||
|
if kind == "spots":
|
||||||
|
pat = np.zeros((h, w), bool)
|
||||||
|
pts = np.argwhere(main)
|
||||||
|
for _ in range(rng.randint(4, 8)):
|
||||||
|
y, x = pts[rng.randrange(len(pts))]
|
||||||
|
r = rng.randint(1, 3)
|
||||||
|
pat |= (xs - x) ** 2 + (ys - y) ** 2 <= r * r
|
||||||
|
elif kind == "speckles":
|
||||||
|
pat = np.random.default_rng(rng.randrange(2 ** 32)).random((h, w)) < 0.1
|
||||||
|
elif kind == "belly":
|
||||||
|
oy, ox = np.nonzero(arr[:, :, 3] > 0)
|
||||||
|
x0, x1, y0, y1 = ox.min(), ox.max(), oy.min(), oy.max()
|
||||||
|
cx, cy = center if center else ((x0 + x1) / 2, y0 + (y1 - y0) * 0.62)
|
||||||
|
rx, ry = max((x1 - x0) * 0.2, 1), max((y1 - y0) * 0.17, 1)
|
||||||
|
pat = ((xs - cx) / rx) ** 2 + ((ys - cy) / ry) ** 2 <= 1
|
||||||
|
else:
|
||||||
|
return img
|
||||||
|
mask = pat & main
|
||||||
|
for src, dst in (("main_light", "accent_light"), ("main_base", "accent_base"), ("main_dark", "accent_dark")):
|
||||||
|
arr[mask & key_mask(arr, src), :3] = KEYS[dst]
|
||||||
|
return Image.fromarray(arr)
|
||||||
|
|
||||||
|
|
||||||
|
def outline_behind(canvas, body, pos):
|
||||||
|
"""Where parts drawn behind the body (back, tail, rear ears) touch the body's silhouette,
|
||||||
|
add a second outline pixel so the creature's own shape stays readable against them."""
|
||||||
|
arr = np.array(canvas)
|
||||||
|
h, w = arr.shape[:2]
|
||||||
|
m = np.zeros((h, w), bool)
|
||||||
|
x, y = pos
|
||||||
|
m[y:y + body.height, x:x + body.width] = np.array(body)[:, :, 3] > 0
|
||||||
|
ring = np.zeros_like(m)
|
||||||
|
ring[1:] |= m[:-1]
|
||||||
|
ring[:-1] |= m[1:]
|
||||||
|
ring[:, 1:] |= m[:, :-1]
|
||||||
|
ring[:, :-1] |= m[:, 1:]
|
||||||
|
hit = ring & ~m & (arr[:, :, 3] > 0)
|
||||||
|
arr[hit] = KEYS["outline"] + (255,)
|
||||||
|
return Image.fromarray(arr)
|
||||||
|
|
||||||
|
|
||||||
|
def compose(parts_dir, chosen, markings, rng):
|
||||||
|
body = chosen["body"]
|
||||||
|
loaded = load_part(parts_dir, "body", body["name"])
|
||||||
|
if loaded is None:
|
||||||
|
raise FileNotFoundError(f"Missing body sprite: {parts_dir}/body/{body['name']}.png")
|
||||||
|
bimg, banch = loaded
|
||||||
|
bimg = apply_markings(bimg, markings, rng, banch.get("aura"))
|
||||||
|
bpiv = banch.get("pivot", (bimg.width // 2, bimg.height - 1))
|
||||||
|
bpos = (-bpiv[0], -bpiv[1])
|
||||||
|
|
||||||
|
layers = [(body["z"] or DEFAULT_Z["body"], 0, bimg, bpos)]
|
||||||
|
for i, slot in enumerate(PICK_ORDER[1:], 1):
|
||||||
|
p = chosen.get(slot)
|
||||||
|
if not p or p["name"] == "none":
|
||||||
|
continue
|
||||||
|
loaded = load_part(parts_dir, slot, p["name"])
|
||||||
|
if loaded is None:
|
||||||
|
warn(f"missing sprite {slot}/{p['name']}.png, skipped")
|
||||||
|
continue
|
||||||
|
img, anch = loaded
|
||||||
|
a = banch.get(slot)
|
||||||
|
if a is None:
|
||||||
|
warn(f"no '{slot}' anchor on {body['name']}, {p['name']} skipped")
|
||||||
|
continue
|
||||||
|
piv = anch.get("pivot", (img.width // 2, img.height // 2))
|
||||||
|
pos = (bpos[0] + a[0] - piv[0], bpos[1] + a[1] - piv[1])
|
||||||
|
layers.append((p["z"] if p["z"] is not None else DEFAULT_Z[slot], i, img, pos))
|
||||||
|
|
||||||
|
layers.sort(key=lambda l: (l[0], l[1]))
|
||||||
|
minx = min(l[3][0] for l in layers)
|
||||||
|
miny = min(l[3][1] for l in layers)
|
||||||
|
maxx = max(l[3][0] + l[2].width for l in layers)
|
||||||
|
maxy = max(l[3][1] + l[2].height for l in layers)
|
||||||
|
canvas = Image.new("RGBA", (maxx - minx, maxy - miny), (0, 0, 0, 0))
|
||||||
|
for _, i, img, (x, y) in layers:
|
||||||
|
if i == 0:
|
||||||
|
canvas = outline_behind(canvas, img, (x - minx, y - miny))
|
||||||
|
canvas.alpha_composite(img, (x - minx, y - miny))
|
||||||
|
return canvas.crop(canvas.getbbox())
|
||||||
|
|
||||||
|
|
||||||
|
def place_on_canvas(sprite):
|
||||||
|
w, h = sprite.size
|
||||||
|
size = max(CANVAS, w + 2, h + GROUND_MARGIN + 1)
|
||||||
|
if size > CANVAS:
|
||||||
|
warn(f"sprite is {w}x{h}, canvas expanded to {size}px")
|
||||||
|
out = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||||
|
out.alpha_composite(sprite, ((size - w) // 2, size - GROUND_MARGIN - h))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def recolor(img, palette):
|
||||||
|
arr = np.array(img)
|
||||||
|
masks = {k: key_mask(arr, k) for k in palette} # compute all before writing
|
||||||
|
for k, rgb in palette.items():
|
||||||
|
arr[masks[k], :3] = rgb
|
||||||
|
return Image.fromarray(arr)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ colors
|
||||||
|
|
||||||
|
def hex_to_rgb(h):
|
||||||
|
h = h.strip().lstrip("#")
|
||||||
|
return tuple(int(h[i:i + 2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_to_hex(c):
|
||||||
|
return "#{:02X}{:02X}{:02X}".format(*c)
|
||||||
|
|
||||||
|
|
||||||
|
def to_hls(rgb):
|
||||||
|
return colorsys.rgb_to_hls(*(c / 255 for c in rgb))
|
||||||
|
|
||||||
|
|
||||||
|
def from_hls(h, l, s):
|
||||||
|
return tuple(int(round(c * 255)) for c in colorsys.hls_to_rgb(h % 1, min(max(l, 0), 1), min(max(s, 0), 1)))
|
||||||
|
|
||||||
|
|
||||||
|
def hue_toward(h, target, amount):
|
||||||
|
d = ((target - h + 0.5) % 1.0) - 0.5
|
||||||
|
return (h + max(-amount, min(amount, d))) % 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def color_dist(a, b):
|
||||||
|
rmean = (a[0] + b[0]) / 2
|
||||||
|
dr, dg, db = a[0] - b[0], a[1] - b[1], a[2] - b[2]
|
||||||
|
return ((2 + rmean / 256) * dr * dr + 4 * dg * dg + (2 + (255 - rmean) / 256) * db * db) ** 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def rotate_hue(rgb, amount):
|
||||||
|
h, l, s = to_hls(rgb)
|
||||||
|
return from_hls(h + amount, l, s)
|
||||||
|
|
||||||
|
|
||||||
|
def ramp(rgb):
|
||||||
|
"""light/base/dark with hue shifting: highlights lean yellow, shadows lean blue."""
|
||||||
|
h, l, s = to_hls(rgb)
|
||||||
|
l = min(max(l, 0.25), 0.75)
|
||||||
|
light = from_hls(hue_toward(h, 1 / 6, 0.03), min(l + 0.17, 0.95), s * 0.92)
|
||||||
|
base = from_hls(h, l, s)
|
||||||
|
dark = from_hls(hue_toward(h, 2 / 3, 0.035), max(l - 0.2, 0.06), s * 1.08)
|
||||||
|
return light, base, dark
|
||||||
|
|
||||||
|
|
||||||
|
def outline_color(rgb):
|
||||||
|
h, l, s = to_hls(rgb)
|
||||||
|
return from_hls(hue_toward(h, 2 / 3, 0.05), min(l * 0.35, 0.16), s * 0.8)
|
||||||
|
|
||||||
|
|
||||||
|
def _dist_to(px, rgb):
|
||||||
|
"""color_dist from many pixels (N,3 float) to one colour."""
|
||||||
|
r, g, b = rgb
|
||||||
|
rmean = (px[:, 0] + r) / 2
|
||||||
|
dr, dg, db = px[:, 0] - r, px[:, 1] - g, px[:, 2] - b
|
||||||
|
return np.sqrt((2 + rmean / 256) * dr * dr + 4 * dg * dg + (2 + (255 - rmean) / 256) * db * db)
|
||||||
|
|
||||||
|
|
||||||
|
def is_neutral(rgb):
|
||||||
|
"""White, black or gray: carries no brand hue."""
|
||||||
|
mx, mn = max(rgb), min(rgb)
|
||||||
|
light, chroma = (mx + mn) / 510, (mx - mn) / 255
|
||||||
|
return chroma < 0.12 or light > 0.93 or light < 0.08
|
||||||
|
|
||||||
|
|
||||||
|
def logo_background(arr):
|
||||||
|
"""The solid background colour of a logo, if its border is mostly one opaque colour."""
|
||||||
|
h, w = arr.shape[:2]
|
||||||
|
border = np.concatenate([arr[0], arr[-1], arr[:, 0], arr[:, -1]])
|
||||||
|
opaque = border[border[:, 3] >= 200][:, :3].astype(float)
|
||||||
|
if len(opaque) < 0.4 * len(border):
|
||||||
|
return None # transparent or busy border: no solid background
|
||||||
|
best = None
|
||||||
|
for c in opaque[:: max(1, len(opaque) // 64)]:
|
||||||
|
share = np.mean(_dist_to(opaque, c) < 40)
|
||||||
|
if best is None or share > best[1]:
|
||||||
|
best = (c, share)
|
||||||
|
if best[1] < 0.4:
|
||||||
|
return None
|
||||||
|
near = opaque[_dist_to(opaque, best[0]) < 40]
|
||||||
|
return tuple(int(v) for v in near.mean(0))
|
||||||
|
|
||||||
|
|
||||||
|
def _quantize(px, n):
|
||||||
|
q = Image.fromarray(px.astype(np.uint8).reshape(1, -1, 3), "RGB").quantize(colors=n, method=Image.Quantize.MEDIANCUT)
|
||||||
|
pal, counts = q.getpalette(), q.getcolors()
|
||||||
|
total = sum(c for c, _ in counts)
|
||||||
|
merged = []
|
||||||
|
for rgb, frac in sorted(((tuple(pal[i * 3:i * 3 + 3]), c / total) for c, i in counts), key=lambda cf: -cf[1]):
|
||||||
|
for m in merged:
|
||||||
|
if color_dist(m[0], rgb) < 40:
|
||||||
|
m[1] += frac
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
merged.append([rgb, frac])
|
||||||
|
return [(tuple(c), f) for c, f in merged]
|
||||||
|
|
||||||
|
|
||||||
|
def _interior(arr, tol=24):
|
||||||
|
"""Opaque pixels whose 4 neighbours are opaque and nearly the same colour. Drops the
|
||||||
|
anti-aliased blends at shape edges, which would otherwise show up as muddy extra colours."""
|
||||||
|
rgb = arr[:, :, :3].astype(float)
|
||||||
|
ok = arr[:, :, 3] >= 200
|
||||||
|
keep = ok.copy()
|
||||||
|
for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)):
|
||||||
|
nb = np.roll(np.roll(rgb, dy, 0), dx, 1)
|
||||||
|
nok = np.roll(np.roll(ok, dy, 0), dx, 1)
|
||||||
|
keep &= nok & (np.abs(rgb - nb).max(-1) < tol)
|
||||||
|
keep[0, :] = keep[-1, :] = keep[:, 0] = keep[:, -1] = False
|
||||||
|
return keep
|
||||||
|
|
||||||
|
|
||||||
|
def extract_logo_colors(path, n=6):
|
||||||
|
"""Return [(rgb, fraction)] of a logo's brand colours, most important first.
|
||||||
|
|
||||||
|
- Transparent pixels and anti-aliased edge blends are ignored.
|
||||||
|
- A solid white/black/gray background (the colour filling most of the image border) is
|
||||||
|
removed. A coloured background is kept: it is part of the brand.
|
||||||
|
- Brand hues come first; neutral colours (white, black, gray) that are part of the logo
|
||||||
|
itself rank after every hued colour.
|
||||||
|
- If nothing but the background is left (e.g. a plain logo on its own colour), the
|
||||||
|
background colour is used rather than nothing.
|
||||||
|
"""
|
||||||
|
img = Image.open(path).convert("RGBA")
|
||||||
|
img.thumbnail((256, 256))
|
||||||
|
arr = np.array(img)
|
||||||
|
opaque = arr[:, :, 3] >= 200
|
||||||
|
if not opaque.any():
|
||||||
|
return []
|
||||||
|
inner = _interior(arr)
|
||||||
|
if inner.sum() < 0.25 * opaque.sum(): # tiny or noisy logo: use every opaque pixel
|
||||||
|
inner = opaque
|
||||||
|
px = arr[inner][:, :3].astype(float)
|
||||||
|
bg = logo_background(arr)
|
||||||
|
if bg is not None and is_neutral(bg):
|
||||||
|
fg = px[_dist_to(px, bg) >= 60]
|
||||||
|
if len(fg) < max(20, 0.002 * len(px)):
|
||||||
|
return [(bg, 1.0)]
|
||||||
|
px = fg
|
||||||
|
found = _quantize(px, n)
|
||||||
|
hued = [cf for cf in found if not is_neutral(cf[0])]
|
||||||
|
neutral = [cf for cf in found if is_neutral(cf[0])]
|
||||||
|
return sorted(hued, key=lambda cf: -cf[1]) + sorted(neutral, key=lambda cf: -cf[1])
|
||||||
|
|
||||||
|
|
||||||
|
def pick_main_accent(colors, ptype):
|
||||||
|
type_rgb = hex_to_rgb(cfg.TYPE_COLORS[ptype])
|
||||||
|
if not colors:
|
||||||
|
main = type_rgb
|
||||||
|
accent = rotate_hue(type_rgb, 0.5)
|
||||||
|
else:
|
||||||
|
main = colors[0][0]
|
||||||
|
cands = [(c, f) for c, f in colors[1:] if f >= 0.04 and color_dist(c, main) >= 90]
|
||||||
|
if cands:
|
||||||
|
# hued brand colours beat white/black/gray text for the accent
|
||||||
|
accent = max(cands, key=lambda cf: color_dist(cf[0], main) * cf[1] ** 0.5
|
||||||
|
* (0.6 if is_neutral(cf[0]) else 1.0))[0]
|
||||||
|
elif color_dist(type_rgb, main) >= 90:
|
||||||
|
accent = type_rgb
|
||||||
|
else:
|
||||||
|
accent = rotate_hue(main, 0.5)
|
||||||
|
# Keep main and accent readable against each other
|
||||||
|
(_, lm, _), (ha, la, sa) = to_hls(main), to_hls(accent)
|
||||||
|
if abs(lm - la) < 0.12 and color_dist(main, accent) < 140:
|
||||||
|
la = la + 0.22 if lm < 0.55 else la - 0.22
|
||||||
|
accent = from_hls(ha, la, sa)
|
||||||
|
return main, accent
|
||||||
|
|
||||||
|
|
||||||
|
def build_palette(main, accent, ptype):
|
||||||
|
type_rgb = hex_to_rgb(cfg.TYPE_COLORS[ptype])
|
||||||
|
# Elemental parts (leaves, flames, sparks) must read against the body: if the type colour
|
||||||
|
# is close to the main colour, push its lightness away from the main colour.
|
||||||
|
if color_dist(type_rgb, main) < 150:
|
||||||
|
(_, lm, _), (ht, lt, st) = to_hls(main), to_hls(type_rgb)
|
||||||
|
type_rgb = from_hls(ht, lm + 0.24 if lm < 0.5 else lm - 0.24, st)
|
||||||
|
pal = {}
|
||||||
|
for group, rgb in (("main", main), ("accent", accent), ("type", type_rgb)):
|
||||||
|
pal[f"{group}_light"], pal[f"{group}_base"], pal[f"{group}_dark"] = ramp(rgb)
|
||||||
|
pal["outline"] = outline_color(main)
|
||||||
|
return pal
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ generate
|
||||||
|
|
||||||
|
def slugify(s):
|
||||||
|
return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-") or "business"
|
||||||
|
|
||||||
|
|
||||||
|
def classify(description):
|
||||||
|
try:
|
||||||
|
from business_pokemon_type import get_business_type # imported lazily so --type works offline
|
||||||
|
except ImportError:
|
||||||
|
sys.exit("business_pokemon_type.py not found next to this script; pass --type instead.")
|
||||||
|
except SystemExit:
|
||||||
|
sys.exit("No --type given and OPENROUTER_API_KEY is not set: export it or pass --type <type>.")
|
||||||
|
ans = get_business_type(description)
|
||||||
|
return ans["choice"].lower(), ans.get("probabilities")
|
||||||
|
|
||||||
|
|
||||||
|
def generate(name, parts_dir, out_dir, ptype=None, description=None, logo=None, colors=None,
|
||||||
|
reroll=0, scale=4):
|
||||||
|
probs = None
|
||||||
|
if not ptype:
|
||||||
|
ptype, probs = classify(description or name)
|
||||||
|
ptype = ptype.lower()
|
||||||
|
if ptype not in cfg.PLAN_WEIGHTS:
|
||||||
|
sys.exit(f"Unknown type '{ptype}'. Options: {', '.join(cfg.TYPES)}")
|
||||||
|
|
||||||
|
rng = make_rng(name, reroll)
|
||||||
|
plan, chosen, markings, tags = pick_parts(ptype, rng)
|
||||||
|
|
||||||
|
if colors:
|
||||||
|
logo_colors = [(hex_to_rgb(c), 1.0) for c in colors]
|
||||||
|
elif logo:
|
||||||
|
logo_colors = extract_logo_colors(logo)
|
||||||
|
if not logo_colors:
|
||||||
|
warn("no usable colors in logo, falling back to type color")
|
||||||
|
else:
|
||||||
|
logo_colors = []
|
||||||
|
main, accent = pick_main_accent(logo_colors, ptype)
|
||||||
|
palette = build_palette(main, accent, ptype)
|
||||||
|
|
||||||
|
sprite = recolor(place_on_canvas(compose(parts_dir, chosen, markings, rng)), palette)
|
||||||
|
|
||||||
|
out_dir = Path(out_dir)
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
slug = slugify(name) + (f"-r{reroll}" if reroll else "")
|
||||||
|
sprite.save(out_dir / f"{slug}.png")
|
||||||
|
if scale > 1:
|
||||||
|
sprite.resize((sprite.width * scale, sprite.height * scale), Image.NEAREST).save(out_dir / f"{slug}@{scale}x.png")
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"name": name, "type": ptype, "type_probabilities": probs, "plan": plan,
|
||||||
|
"parts": {s: p["name"] for s, p in chosen.items()}, "markings": markings,
|
||||||
|
"attributes": attributes_from(tags),
|
||||||
|
"colors": {"main": rgb_to_hex(main), "accent": rgb_to_hex(accent),
|
||||||
|
"type": cfg.TYPE_COLORS[ptype], "outline": rgb_to_hex(palette["outline"])},
|
||||||
|
}
|
||||||
|
(out_dir / f"{slug}.json").write_text(json.dumps(info, indent=2))
|
||||||
|
print(f"{name}: {ptype.upper()} {plan} | {', '.join(info['attributes']) or 'no attributes'} | "
|
||||||
|
f"main {info['colors']['main']} accent {info['colors']['accent']} -> {out_dir / slug}.png")
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ placeholders
|
||||||
|
|
||||||
|
BASE_ANCHORS = {"pivot": (0.5, 1.0), "head_top": (0.35, 0.08), "back": (0.7, 0.35), "tail": (0.9, 0.7),
|
||||||
|
"aura": (0.5, 0.55)}
|
||||||
|
CHILD_ANCHORS = {
|
||||||
|
"head_top": {"pivot": (0.5, 1.0)}, "back": {"pivot": (0.5, 0.85)}, "tail": {"pivot": (0.05, 0.6)},
|
||||||
|
"aura": {"pivot": (0.5, 0.5)},
|
||||||
|
}
|
||||||
|
DEFAULT_SIZE = {"head_top": (16, 14), "back": (40, 30), "tail": (26, 14), "aura": (84, 84)}
|
||||||
|
|
||||||
|
|
||||||
|
def anchors_for(slot, p):
|
||||||
|
return BASE_ANCHORS if slot == "body" else CHILD_ANCHORS[slot]
|
||||||
|
|
||||||
|
|
||||||
|
def size_for(slot, p):
|
||||||
|
return tuple(p["size"] or DEFAULT_SIZE[slot])
|
||||||
|
|
||||||
|
|
||||||
|
def _shaded(d, box, pal):
|
||||||
|
x0, y0, x1, y1 = [int(v) for v in box]
|
||||||
|
w, h = x1 - x0, y1 - y0
|
||||||
|
d.ellipse((x0, y0, x1, y1), fill=KEYS[f"{pal}_dark"], outline=KEYS["outline"])
|
||||||
|
if w > 4 and h > 4:
|
||||||
|
d.ellipse((x0 + 1, y0 + 1, x1 - max(2, int(w * 0.2)), y1 - max(2, int(h * 0.25))), fill=KEYS[f"{pal}_base"])
|
||||||
|
d.ellipse((x0 + int(w * 0.2), y0 + int(h * 0.15), x0 + int(w * 0.4), y0 + int(h * 0.3)), fill=KEYS[f"{pal}_light"])
|
||||||
|
|
||||||
|
|
||||||
|
def _rect(d, box, pal):
|
||||||
|
d.rectangle([int(v) for v in box], fill=KEYS[f"{pal}_base"], outline=KEYS["outline"])
|
||||||
|
|
||||||
|
|
||||||
|
def draw_placeholder(slot, p):
|
||||||
|
w, h = size_for(slot, p)
|
||||||
|
img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
pal = p["palette"]
|
||||||
|
if slot == "body": # base creature: round body with a big head and two dot eyes
|
||||||
|
_shaded(d, (w * 0.3, h * 0.45, w - 1, h - 1), pal)
|
||||||
|
_shaded(d, (0, 0, w * 0.65, h * 0.7), pal)
|
||||||
|
for ex in (0.2, 0.42):
|
||||||
|
d.rectangle((int(w * ex), int(h * 0.3), int(w * ex) + 1, int(h * 0.3) + 2), fill=PUPIL)
|
||||||
|
elif slot == "head_top":
|
||||||
|
d.polygon([(0, h - 1), (w // 2, 0), (w - 1, h - 1)], fill=KEYS[f"{pal}_base"], outline=KEYS["outline"])
|
||||||
|
d.line([(w // 2, 2), (w // 2, h - 3)], fill=KEYS[f"{pal}_light"])
|
||||||
|
elif slot == "aura":
|
||||||
|
rng = random.Random(p["name"])
|
||||||
|
for _ in range(14):
|
||||||
|
x, y = rng.randrange(4, w - 8), rng.randrange(4, h - 8)
|
||||||
|
r = rng.randint(2, 3)
|
||||||
|
d.ellipse((x, y, x + r * 2, y + r * 2), fill=KEYS["type_base"], outline=KEYS["type_dark"])
|
||||||
|
else:
|
||||||
|
_shaded(d, (0, 0, w - 1, h - 1), pal)
|
||||||
|
|
||||||
|
for mname, (fx, fy) in anchors_for(slot, p).items():
|
||||||
|
img.putpixel((min(int(fx * w), w - 1), min(int(fy * h), h - 1)), MARKERS[mname] + (255,))
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def make_placeholders(parts_dir, overwrite=False):
|
||||||
|
made = 0
|
||||||
|
for slot, parts in cfg.SLOTS.items():
|
||||||
|
(Path(parts_dir) / slot).mkdir(parents=True, exist_ok=True)
|
||||||
|
for p in parts:
|
||||||
|
if p["name"] == "none":
|
||||||
|
continue
|
||||||
|
path = Path(parts_dir) / slot / f"{p['name']}.png"
|
||||||
|
if path.exists() and not overwrite:
|
||||||
|
continue
|
||||||
|
draw_placeholder(slot, p).save(path)
|
||||||
|
made += 1
|
||||||
|
print(f"Wrote {made} placeholder sprites to {parts_dir}/")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ sprite list + weight report
|
||||||
|
|
||||||
|
def simulate(ptype, n=3000):
|
||||||
|
plans, slots, marks = Counter(), {s: Counter() for s in PICK_ORDER}, Counter()
|
||||||
|
for i in range(n):
|
||||||
|
plan, chosen, m, _ = pick_parts(ptype, random.Random(i))
|
||||||
|
plans[plan] += 1
|
||||||
|
marks[m] += 1
|
||||||
|
for s in PICK_ORDER:
|
||||||
|
slots[s][chosen[s]["name"] if s in chosen else "none"] += 1
|
||||||
|
return plans, slots, marks
|
||||||
|
|
||||||
|
|
||||||
|
def pct(counter, n, top=None):
|
||||||
|
return ", ".join(f"{k} {v / n:.0%}" for k, v in counter.most_common(top) if v / n >= 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def write_sprite_list(path):
|
||||||
|
L = ["# Sprite checklist", ""]
|
||||||
|
total = sum(1 for parts in cfg.SLOTS.values() for p in parts if p["name"] != "none")
|
||||||
|
L += [f"**{total} sprites** across {len(cfg.SLOTS)} slots. Save each as `parts/<slot>/<name>.png`.", "",
|
||||||
|
"## Art rules", "",
|
||||||
|
f"- Final canvas is {CANVAS}x{CANVAS}. Sizes below are suggested part canvases; crop tight with transparency.",
|
||||||
|
"- Characters face left in a 3/4 view, light from the top-left. 1px outline, no anti-aliasing, no semi-transparent pixels.",
|
||||||
|
"- Draw only with the palette keys below. Any other color (eye white, pupils) is left as-is.", ""]
|
||||||
|
L += ["| Key | Hex | Use |", "|---|---|---|"]
|
||||||
|
uses = {"main": "body / most fur and skin", "accent": "horns, wings, beaks, crests, shells",
|
||||||
|
"type": "elemental bits: flames, leaves, sparks, ice, water"}
|
||||||
|
for k, v in KEYS.items():
|
||||||
|
L.append(f"| {k} | {rgb_to_hex(v)} | {'outline (recolored)' if k == 'outline' else uses[k.split('_')[0]]} |")
|
||||||
|
L += [f"| pupil | {rgb_to_hex(PUPIL)} | not recolored |", f"| eye white | {rgb_to_hex(EYE_WHITE)} | not recolored |", ""]
|
||||||
|
L += ["### Anchor marker pixels", "",
|
||||||
|
"One pixel each. `pivot` is where the part attaches to its parent; the others are where children attach.", "",
|
||||||
|
"| Marker | Hex | Needed in |", "|---|---|---|",
|
||||||
|
f"| pivot | {rgb_to_hex(MARKERS['pivot'])} | every part (bodies: bottom center between the feet) |",
|
||||||
|
f"| head_top | {rgb_to_hex(MARKERS['head_top'])} | all bodies (top of the head) |",
|
||||||
|
f"| tail | {rgb_to_hex(MARKERS['tail'])} | all bodies |",
|
||||||
|
f"| back | {rgb_to_hex(MARKERS['back'])} | all bodies |",
|
||||||
|
f"| aura | {rgb_to_hex(MARKERS['aura'])} | all bodies (center of mass; belly patch centre) |", ""]
|
||||||
|
|
||||||
|
for slot in ["body", "head_top", "back", "tail", "aura"]:
|
||||||
|
parts = [p for p in cfg.SLOTS[slot] if p["name"] != "none"]
|
||||||
|
L += [f"## {slot} ({len(parts)})", "", "| Done | File | Size | Palette | Plans | Description | Most likely for |",
|
||||||
|
"|---|---|---|---|---|---|---|"]
|
||||||
|
for p in parts:
|
||||||
|
top = sorted(cfg.TYPES, key=lambda t: -part_weight(p, t))[:3]
|
||||||
|
top = ", ".join(t for t in top if part_weight(p, t) > 0)
|
||||||
|
plans = "all" if p["plans"] == cfg.ALL else ", ".join(p["plans"])
|
||||||
|
w, h = size_for(slot, p)
|
||||||
|
L.append(f"| [ ] | `{slot}/{p['name']}.png` | {w}x{h} | {p['palette']} | {plans} | {p['desc']} | {top} |")
|
||||||
|
L.append("")
|
||||||
|
|
||||||
|
L += ["## Simulated pick rates per type", "",
|
||||||
|
"From 3,000 random seeds per type. Use this to spot parts that never show up or show up too much.", ""]
|
||||||
|
for t in cfg.TYPES:
|
||||||
|
n = 3000
|
||||||
|
plans, slots, marks = simulate(t, n)
|
||||||
|
L += [f"### {t.capitalize()}", "", f"- **Body plan:** {pct(plans, n)}"]
|
||||||
|
for s in PICK_ORDER:
|
||||||
|
L.append(f"- **{s}:** {pct(slots[s], n, 8)}")
|
||||||
|
L += [f"- **markings:** {pct(marks, n)}", ""]
|
||||||
|
Path(path).write_text("\n".join(L))
|
||||||
|
print(f"Wrote {path}")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================ CLI
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description="Generate Pokémon-style business sprites")
|
||||||
|
ap.add_argument("name", nargs="?", help="Business name (also the random seed)")
|
||||||
|
ap.add_argument("logo_file", nargs="?", help="Logo image to pull colors from (same as --logo)")
|
||||||
|
ap.add_argument("--desc", help="Business description for Jev (defaults to the name)")
|
||||||
|
ap.add_argument("--type", help="Skip Jev and use this type")
|
||||||
|
ap.add_argument("--logo", help="Logo image to pull colors from")
|
||||||
|
ap.add_argument("--colors", help='Manual colors, e.g. "#2E7D32,#FBC02D" (main first)')
|
||||||
|
ap.add_argument("--reroll", type=int, default=0, help="Alternate creature for the same business")
|
||||||
|
ap.add_argument("--parts", default="parts", help="Parts folder")
|
||||||
|
ap.add_argument("--out", default="sprites", help="Output folder")
|
||||||
|
ap.add_argument("--scale", type=int, default=4, help="Also save an upscaled copy (1 to disable)")
|
||||||
|
ap.add_argument("--batch", help="CSV with columns name,logo,type,description (only name required)")
|
||||||
|
ap.add_argument("--make-placeholders", action="store_true", help="Create test art for every part")
|
||||||
|
ap.add_argument("--overwrite", action="store_true", help="With --make-placeholders, replace existing files")
|
||||||
|
ap.add_argument("--sprite-list", metavar="PATH", help="Write the art checklist and weight report")
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
if a.make_placeholders:
|
||||||
|
make_placeholders(a.parts, a.overwrite)
|
||||||
|
if a.sprite_list:
|
||||||
|
write_sprite_list(a.sprite_list)
|
||||||
|
colors = a.colors.split(",") if a.colors else None
|
||||||
|
if a.batch:
|
||||||
|
with open(a.batch, newline="", encoding="utf-8") as f:
|
||||||
|
for row in csv.DictReader(f):
|
||||||
|
if not (row.get("name") or "").strip():
|
||||||
|
continue
|
||||||
|
generate(row["name"].strip(), a.parts, a.out, ptype=(row.get("type") or "").strip() or None,
|
||||||
|
description=(row.get("description") or "").strip() or None,
|
||||||
|
logo=(row.get("logo") or "").strip() or None, scale=a.scale)
|
||||||
|
elif a.name:
|
||||||
|
logo = a.logo or a.logo_file
|
||||||
|
if logo and not Path(logo).exists():
|
||||||
|
sys.exit(f"Logo not found: {logo}")
|
||||||
|
generate(a.name, a.parts, a.out, a.type, a.desc, logo, colors, a.reroll, a.scale)
|
||||||
|
elif not (a.make_placeholders or a.sprite_list):
|
||||||
|
ap.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
legacy/parts/body/avian_perched.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
legacy/parts/body/avian_round.png
Normal file
|
After Width: | Height: | Size: 333 B |
BIN
legacy/parts/body/biped_bulky.png
Normal file
|
After Width: | Height: | Size: 617 B |
BIN
legacy/parts/body/biped_small.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
legacy/parts/body/biped_tall.png
Normal file
|
After Width: | Height: | Size: 573 B |
BIN
legacy/parts/body/blob_drip.png
Normal file
|
After Width: | Height: | Size: 535 B |
BIN
legacy/parts/body/blob_floating.png
Normal file
|
After Width: | Height: | Size: 480 B |
BIN
legacy/parts/body/blob_round.png
Normal file
|
After Width: | Height: | Size: 356 B |
BIN
legacy/parts/body/fish_body.png
Normal file
|
After Width: | Height: | Size: 556 B |
BIN
legacy/parts/body/insect_beetle.png
Normal file
|
After Width: | Height: | Size: 529 B |
BIN
legacy/parts/body/insect_larva.png
Normal file
|
After Width: | Height: | Size: 522 B |
BIN
legacy/parts/body/insect_mantis.png
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
legacy/parts/body/quad_long.png
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
legacy/parts/body/quad_slim.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
legacy/parts/body/quad_stocky.png
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
legacy/parts/body/seal_body.png
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
legacy/parts/body/serpent_coiled.png
Normal file
|
After Width: | Height: | Size: 608 B |
BIN
legacy/parts/body/serpent_rising.png
Normal file
|
After Width: | Height: | Size: 662 B |
BIN
legacy/parts/eyes/eyes_angry.png
Normal file
|
After Width: | Height: | Size: 145 B |
BIN
legacy/parts/eyes/eyes_beady.png
Normal file
|
After Width: | Height: | Size: 106 B |
BIN
legacy/parts/eyes/eyes_big_shiny.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
legacy/parts/eyes/eyes_compound.png
Normal file
|
After Width: | Height: | Size: 160 B |
BIN
legacy/parts/eyes/eyes_cyclops.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
legacy/parts/eyes/eyes_glowing.png
Normal file
|
After Width: | Height: | Size: 137 B |
BIN
legacy/parts/eyes/eyes_happy_closed.png
Normal file
|
After Width: | Height: | Size: 100 B |
BIN
legacy/parts/eyes/eyes_masked.png
Normal file
|
After Width: | Height: | Size: 181 B |
BIN
legacy/parts/eyes/eyes_round.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
legacy/parts/eyes/eyes_sharp.png
Normal file
|
After Width: | Height: | Size: 126 B |
BIN
legacy/parts/eyes/eyes_sleepy.png
Normal file
|
After Width: | Height: | Size: 133 B |
BIN
legacy/parts/head/head_bird.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
legacy/parts/head/head_bulky.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
legacy/parts/head/head_canine.png
Normal file
|
After Width: | Height: | Size: 298 B |
BIN
legacy/parts/head/head_feline.png
Normal file
|
After Width: | Height: | Size: 300 B |
BIN
legacy/parts/head/head_flat.png
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
legacy/parts/head/head_ghost.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
legacy/parts/head/head_insect.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
legacy/parts/head/head_reptile.png
Normal file
|
After Width: | Height: | Size: 316 B |
BIN
legacy/parts/head/head_rock.png
Normal file
|
After Width: | Height: | Size: 329 B |
BIN
legacy/parts/head/head_round.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
legacy/parts/snout/beak_duck.png
Normal file
|
After Width: | Height: | Size: 115 B |
BIN
legacy/parts/snout/beak_hooked.png
Normal file
|
After Width: | Height: | Size: 127 B |
BIN
legacy/parts/snout/beak_short.png
Normal file
|
After Width: | Height: | Size: 120 B |
BIN
legacy/parts/snout/fangs.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
legacy/parts/snout/fish_lips.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
legacy/parts/snout/mandibles.png
Normal file
|
After Width: | Height: | Size: 159 B |
BIN
legacy/parts/snout/muzzle_long.png
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
legacy/parts/snout/muzzle_small.png
Normal file
|
After Width: | Height: | Size: 137 B |
BIN
legacy/parts/snout/reptile_jaw.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
legacy/parts/snout/snout_pig.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
legacy/parts/snout/tongue_out.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
legacy/parts/snout/trunk_short.png
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
legacy/parts/snout/tusks.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
legacy/parts/snout/whiskers.png
Normal file
|
After Width: | Height: | Size: 157 B |
32
legacy/parts_src/body/avian_perched.txt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
...............oooooo...........
|
||||||
|
.............oo222222oo.........
|
||||||
|
............o2222222222o........
|
||||||
|
.........ooo2H2222222222o.......
|
||||||
|
........o222221111122B222o......
|
||||||
|
......oo222221111112222222o.....
|
||||||
|
....oo22221111111111222222o.....
|
||||||
|
...o22222111111111111122223o....
|
||||||
|
.oo222221111111111111222223o....
|
||||||
|
o22222211111111111111122222o....
|
||||||
|
.ooo222111111111111111222233o...
|
||||||
|
..o2222222111111111212222233o...
|
||||||
|
.o22222221111112222222222233o...
|
||||||
|
o222222222211122222222222233o...
|
||||||
|
.oooo2222222222222A222222333o...
|
||||||
|
...o222222222222222222222233o...
|
||||||
|
...o223322222222222222222233o...
|
||||||
|
....oooo22222222222222222223o...
|
||||||
|
........o2222222222222222223o...
|
||||||
|
........o22222222222333332333o..
|
||||||
|
.........o22222222333333333T3o..
|
||||||
|
.........o23333333333333333333o.
|
||||||
|
..........o23333333333333333332o
|
||||||
|
...........o3333333333333333333o
|
||||||
|
............o3obco33333oooooooo.
|
||||||
|
.............oobco333ooo........
|
||||||
|
..............obcoooocco........
|
||||||
|
..............obco..occo........
|
||||||
|
.............oobcoo.occo........
|
||||||
|
............oabbbcooobcoo.......
|
||||||
|
............obbbccoobbcco.......
|
||||||
|
............ooooooPoooooo.......
|
||||||
28
legacy/parts_src/body/avian_round.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
..............ooo.............
|
||||||
|
..........oooo222oooo.........
|
||||||
|
........oo22222222222oo.......
|
||||||
|
.......o222222222222222o......
|
||||||
|
......o22221111112222222o.....
|
||||||
|
.....o222111111112B222222o....
|
||||||
|
....o222111111111112222222o...
|
||||||
|
...o22211H11111111122222222o..
|
||||||
|
..o222111111111111122222222o..
|
||||||
|
..o2221111111111112222222223o.
|
||||||
|
.o22222111111111222222222223o.
|
||||||
|
.o22222111111122222222222233o.
|
||||||
|
.o222221111112222222222222T3o.
|
||||||
|
.o2222221112222A222222222333o.
|
||||||
|
.o22222222222222222222233333o.
|
||||||
|
.o22222222222222222222333333o.
|
||||||
|
.o2222222222222222223333333o..
|
||||||
|
..o222222222222222233333333o..
|
||||||
|
..o22222222222222333333333o...
|
||||||
|
...o222222222223333333333o....
|
||||||
|
....o3222222233333333333o.....
|
||||||
|
.....o33333333333333333o......
|
||||||
|
......o333333333333333o.......
|
||||||
|
.......oo33333333333oo........
|
||||||
|
.........ooco333ocoo..........
|
||||||
|
..........ocoooooco...........
|
||||||
|
.........obbco.obbco..........
|
||||||
|
..........ooo.P.ooo...........
|
||||||
39
legacy/parts_src/body/biped_bulky.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.................oooooooooo.................
|
||||||
|
.............oooo2222222222oooo.............
|
||||||
|
...........oo222222222222222222oo...........
|
||||||
|
.........oo22oooooooHooo222222222oo.........
|
||||||
|
........o111111111122222oo222oooo22o........
|
||||||
|
.......o211111111111121122o2o2111222o.......
|
||||||
|
.......o221111111111111122oo221111222o......
|
||||||
|
......o2o22212211122211222oo2111111222o.....
|
||||||
|
......o22o222221222222222oo22111122223o.....
|
||||||
|
.....o2o2o22223o22223o222oo22111122223o.....
|
||||||
|
.....o2o22o223o1o223o1o23oo22111122223o.....
|
||||||
|
....o22o22o23o111o2o111oo2o22211122233o.....
|
||||||
|
....o22o222oo1111o3o122222o22221112233o.....
|
||||||
|
....o222o22oo21111o12222222o22B211223o......
|
||||||
|
....o222o2222211112222222222o222122223o.....
|
||||||
|
...o2223o22222112222222222222o22122223o.....
|
||||||
|
...o222o222222122222222222222o22222223o.....
|
||||||
|
...o222o222222222222222222222o212222223o....
|
||||||
|
..o2222o222222222222222222222o222222223o....
|
||||||
|
..o2222o2222222222222A2222222o222222223o....
|
||||||
|
..o2222o222222222222222222223o2212222233o...
|
||||||
|
.o22222o222222222222222222233o2212222233o...
|
||||||
|
.o222223o22222222222222222333o22122222333o..
|
||||||
|
o2222333o22222222222222233333o22222T23333o..
|
||||||
|
o22223333o2222222222233333333o22222233333o..
|
||||||
|
o22233332o2222222223333333333o22222233333o..
|
||||||
|
.o33333333o222222333333333333o22222233333o..
|
||||||
|
.o33333333oo33323333333333333o22222333333o..
|
||||||
|
..oo3333oo32oo33333333333333ooo222333333o...
|
||||||
|
....oooo.o2223ooo33333333ooo112oo333333o....
|
||||||
|
.........o2322333oo33333o22222222oo33oo.....
|
||||||
|
.........o222233333oooooo2222222223oo.......
|
||||||
|
........oo222333333o....o2222222223o........
|
||||||
|
.......o2222333333o....o222222222233o.......
|
||||||
|
......o222223333333o...o222222233333o.......
|
||||||
|
......o233333333333o...o222223333333o.......
|
||||||
|
.......o3333333333o....o222233333333o.......
|
||||||
|
........oo333333oo......ooo333333ooo........
|
||||||
|
..........oooooo......P....oooooo...........
|
||||||
31
legacy/parts_src/body/biped_small.txt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
..............................
|
||||||
|
..............................
|
||||||
|
............oooooo............
|
||||||
|
..........oo222222oo..........
|
||||||
|
.........o22222H2222o.........
|
||||||
|
........o222211122222o........
|
||||||
|
.......o22211111122222o.......
|
||||||
|
.......o22111111112222o.......
|
||||||
|
......o2221111111122223o......
|
||||||
|
......o2221111111122223o......
|
||||||
|
......o2221111111122223o......
|
||||||
|
......o222211111122ooo3o......
|
||||||
|
.....o2o2211111122o1B2oo......
|
||||||
|
....o22o2111111222o1222o......
|
||||||
|
...o22o22211112222o122o3o.....
|
||||||
|
..o22o22221112222o2223o32o....
|
||||||
|
..o22o222211222A2o222o333o....
|
||||||
|
..o23o22221222222o123o333o....
|
||||||
|
...o3o2222122222o222o3333o....
|
||||||
|
....o22222222222o222o3333o....
|
||||||
|
....o22222222222o223o33T3o....
|
||||||
|
....o222222222222ooo33333o....
|
||||||
|
.....o222222222233333333o.....
|
||||||
|
......o2222222233333333o......
|
||||||
|
.......oo3333333333333o.......
|
||||||
|
.....oo22ooo333322233o........
|
||||||
|
....o2222222o111112222o.......
|
||||||
|
....o2223333o2111222222o......
|
||||||
|
....o33333333o22222232o.......
|
||||||
|
.....oo33333oo2333333o........
|
||||||
|
.......ooooo..Poooooo.........
|
||||||
45
legacy/parts_src/body/biped_tall.txt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
............oooooooo............
|
||||||
|
..........oo22222222oo..........
|
||||||
|
.........o2ooooooooo22o.........
|
||||||
|
........o1o2111H1122oo2oo.......
|
||||||
|
.......o1111111111112oo22o......
|
||||||
|
......o2211111111211o21122o.....
|
||||||
|
......oo22222111222oo11122o.....
|
||||||
|
......o2o2222o22223o2111233o....
|
||||||
|
......o2o222o1o223o2o21223o.....
|
||||||
|
.....o2o2o2o111o2o22o21223o.....
|
||||||
|
.....o22ooo2211o3o223o1222o.....
|
||||||
|
.....o22ooo22212o2233o22223o....
|
||||||
|
.....o233o222222222333o2223o....
|
||||||
|
....o22333o2222222233Bo1223o....
|
||||||
|
....o22333o2222222233o21223o....
|
||||||
|
...oo2233oo2222222233o212233ooo.
|
||||||
|
..o2o2233oo2222222233o221233222o
|
||||||
|
.o2222233oo2222222233oo21223ooo.
|
||||||
|
o32222233o222122A23333o22223o...
|
||||||
|
.oo223333o222122233333oT2223o...
|
||||||
|
..o233333o222222333333oo12233o..
|
||||||
|
..o233333o22222233333oo212233o..
|
||||||
|
...o3333ooo2222233333o3o22333o..
|
||||||
|
....oooo.o2oo333333oo23o2233o...
|
||||||
|
.........o223oooooo2223o333o....
|
||||||
|
.........o223333o222233oooo.....
|
||||||
|
.........o223333o222233o........
|
||||||
|
.........o22333o222223o.........
|
||||||
|
.........o22333o222223o.........
|
||||||
|
.........o22333o222233o.........
|
||||||
|
.........o22333o222233o.........
|
||||||
|
.........o22333o222233o.........
|
||||||
|
.........o22333o222223o.........
|
||||||
|
.........o2233o.o22223o.........
|
||||||
|
.........o2233o.o22223o.........
|
||||||
|
.........o2233o.o22223o.........
|
||||||
|
.........o2333o.o21223o.........
|
||||||
|
.........o2233o.o21223o.........
|
||||||
|
........oo2233o.o21223o.........
|
||||||
|
......oo22233o.oo11223o.........
|
||||||
|
.....o22222333o1111222o.........
|
||||||
|
.....o23333333o21122333o........
|
||||||
|
....o33333333o222222333o........
|
||||||
|
.....oooooooo22223333oo.........
|
||||||
|
.............oooPoooo...........
|
||||||
38
legacy/parts_src/body/blob_drip.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
.....................oooo.................
|
||||||
|
...................oo2222o................
|
||||||
|
.................oo22222o.................
|
||||||
|
..............ooo22222222ooo..............
|
||||||
|
............oo22222222222222oo............
|
||||||
|
..........oo222222112222222222oo..........
|
||||||
|
.........o2222211111122222222222o.........
|
||||||
|
........o222211111111^12222222222o........
|
||||||
|
.......o22211111111111111222222222o.......
|
||||||
|
......o2222111111111111112222oo2222o......
|
||||||
|
......o222211111111111111112o12o222o......
|
||||||
|
.....o2222111111111111111122o22o2223o.....
|
||||||
|
.....o22221111111111111112222oB22223o.....
|
||||||
|
.....o222211111111111112222222222222o.....
|
||||||
|
....o22222211111111E12222222222222233o....
|
||||||
|
....o22222211111111222222222222222233o....
|
||||||
|
....o22222222111222222222222222222333o....
|
||||||
|
....o22222222112222222222222222223333o....
|
||||||
|
....o22222222222222222222222222233333o....
|
||||||
|
...o222222222222222222222222222333333oo...
|
||||||
|
..o22222222222222222222S222222233333332o..
|
||||||
|
..o222222222222222222A22222222233333332o..
|
||||||
|
..o222222222222222222222222222233333333o..
|
||||||
|
..o222o2222222222222222222222223333o333o..
|
||||||
|
..o212oo2222222oo2222oo22222o2233T3o233o..
|
||||||
|
.o2123o2o21223o11o23o12o223o2o21223o223o..
|
||||||
|
.o2123o2o21223o11o23o22o223o2o21223o223o..
|
||||||
|
.o2223o2o22223o22o23o22o223o2o22223o233o..
|
||||||
|
.o22223o222233o22oooo22o223o2o22223oooo...
|
||||||
|
..o223oo222233o22222222o223o2o22223o......
|
||||||
|
...ooo22o22223o222222222ooo22o2223o2ooo...
|
||||||
|
..o11222o22223o22222222222233o2223oo222o..
|
||||||
|
.o222222o22223o222222233333333oooo11o122o.
|
||||||
|
.o2222222o223o2223333333333333333o22o222o.
|
||||||
|
..o2222222ooo333333333333333333333oo333o..
|
||||||
|
...ooo333333333333333333333333333333ooo...
|
||||||
|
......oooooo333333333333333333oooooo......
|
||||||
|
............oooooooooPoooooooo............
|
||||||
40
legacy/parts_src/body/blob_floating.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
......................................
|
||||||
|
...............oooooooo...............
|
||||||
|
............ooo22222222ooo............
|
||||||
|
..........oo22222222222222oo..........
|
||||||
|
.........o222222111222222222o.........
|
||||||
|
........o22221111111222222222o........
|
||||||
|
.......o22211111111^1222222222o.......
|
||||||
|
......o222111111111111122222222o......
|
||||||
|
.....o22211111111111111122222222o.....
|
||||||
|
.....o22211111111111111112222222o.....
|
||||||
|
....o2222211111111111112222222223o....
|
||||||
|
....o2222211111111111222222B22223o....
|
||||||
|
....o2222211111111122222222222233o....
|
||||||
|
....o222222111111E222222222222233o....
|
||||||
|
....o2222221111222222222222222233o....
|
||||||
|
....o2222221112222222222222222333o....
|
||||||
|
....o2222222222222222222222223333o....
|
||||||
|
....o2222222222222222222222233333o....
|
||||||
|
....o22222222222222A2222222333333o....
|
||||||
|
....o2222222222222222S222233333T3o....
|
||||||
|
.....o22222222222222222233333333oo....
|
||||||
|
....o1ooo2222222222222233333333o22o...
|
||||||
|
...o2111o222222222222233333333o2223o..
|
||||||
|
...o2122o22222222222223333333o22333o..
|
||||||
|
...o2223o33222222222223333333o23333o..
|
||||||
|
...o2233oo322222222222233333o.ooooo...
|
||||||
|
....oooo..oo222222222223333o..........
|
||||||
|
...........o22222222222333o...........
|
||||||
|
............o3222222222233o...........
|
||||||
|
.............o322222222223o...........
|
||||||
|
..............o332222222233o..........
|
||||||
|
..............o233233333333o..........
|
||||||
|
...............o33333333333o....o.....
|
||||||
|
................o33333333333o.oo2o....
|
||||||
|
.................o33333333332o2222o...
|
||||||
|
..................oo3333333331222o....
|
||||||
|
....................o33333333222o.....
|
||||||
|
.....................o333333333o......
|
||||||
|
......................ooooo333o.......
|
||||||
|
...........................oPo........
|
||||||
31
legacy/parts_src/body/blob_round.txt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
......................................
|
||||||
|
.............oooooooooooo.............
|
||||||
|
..........ooo222222222222ooo..........
|
||||||
|
........oo222222222222222222oo........
|
||||||
|
.......o22222222222^2222222222o.......
|
||||||
|
......o222222111111122222222222o......
|
||||||
|
.....o22211111111111122222222222o.....
|
||||||
|
....o2221111111111111112222222222o....
|
||||||
|
...o222111111111111111112222222222o...
|
||||||
|
..o22211111111111111111112222222222o..
|
||||||
|
..o2222111111111111111111122B222222o..
|
||||||
|
.o2222211111111111111111111222222223o.
|
||||||
|
.o2222221111111111111122222222222233o.
|
||||||
|
.o222222111111111E222222222222222233o.
|
||||||
|
.o2222222111111222222222222222222233o.
|
||||||
|
.o2222222111122222222222222222222233o.
|
||||||
|
.o22222222122222222A2222222222223333o.
|
||||||
|
.o2222222222222222222222222222223333o.
|
||||||
|
.o2222222222222222222222222222223333o.
|
||||||
|
.o22222222222222222222S2222222223333o.
|
||||||
|
.o2222222222222222222222222222333333o.
|
||||||
|
..o22222222222222222222222223333333o..
|
||||||
|
..o222222222222222222222333333333T3o..
|
||||||
|
.o2222222222222222233333333333333333o.
|
||||||
|
.o2222222222333333333333333333333333o.
|
||||||
|
..o22223223333333333333333333333333o..
|
||||||
|
...o323333333333333333333333333333o...
|
||||||
|
....oo33333333333333333333333333oo....
|
||||||
|
......oooo333333333333333333oooo......
|
||||||
|
..........oooooooooooooooooo..........
|
||||||
|
...................P..................
|
||||||
35
legacy/parts_src/body/fish_body.txt
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
..................................................
|
||||||
|
............ooooooo...............................
|
||||||
|
.........ooo2222222ooooooooo......................
|
||||||
|
........o222222222222o222222oooo..................
|
||||||
|
......oo22222112222222o222222222oo................
|
||||||
|
.....o22221111111222222oo222B22222oo..............
|
||||||
|
....o2222111111^112222222o2222222222oo............
|
||||||
|
...o2221111111111122222222o22222222222oo..........
|
||||||
|
...o2221111111111111222222o2222222222222ooo.......
|
||||||
|
..o222111111111111111222222o222222222222222ooo....
|
||||||
|
..o222211111111111112222222o222222222222222222o...
|
||||||
|
.o22222111111111112222222222o222222222222222222o..
|
||||||
|
.o22222111111111122222222223o222222222222222222o..
|
||||||
|
.o2222211111E112222222222223o222222222222222T23o..
|
||||||
|
.o22222211111122222222222223o222222222222222233o..
|
||||||
|
.o2222221111222222222222ooo3o222222222333333333o..
|
||||||
|
.o2222221122222222222A22oaaoo22222333333333333o...
|
||||||
|
.o2222222122222222222222oaaaaooo233333333333oo....
|
||||||
|
.o2222222222222222222222obaaaaaaooo33333oooo......
|
||||||
|
.o2222S22222222222222223obbbaaaaaaaoo3oo..........
|
||||||
|
.o22222222222222222222333obbbbbbaaaaaoo...........
|
||||||
|
..o2222222222222222223333obbcbbbbbbbbbbo..........
|
||||||
|
..o22222222222222223333333obbccbbbbbbboo..........
|
||||||
|
...o2222222222222233333333obbbbccccccoo...........
|
||||||
|
...o2222222222233333333333oobbbbbbbbo.............
|
||||||
|
....o23222233333333333333o3obbbbbbbbbo............
|
||||||
|
.....o33333333333333333oo333obcbbbbbbbo...........
|
||||||
|
......oo33333333333333o33333obbccbbbccco..........
|
||||||
|
........o333333333333o333333oobbbcccoo............
|
||||||
|
.........ooo3333333ooooooooo..ooooo...............
|
||||||
|
............ooooooo...............................
|
||||||
|
.............obbbbco..............................
|
||||||
|
..............obbbbco.............................
|
||||||
|
...............obbcco.............................
|
||||||
|
................oPoo..............................
|
||||||
30
legacy/parts_src/body/insect_beetle.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
..............................................
|
||||||
|
....................oooooooooooooo............
|
||||||
|
.................ooo22222222222222ooo.........
|
||||||
|
................o2222222222B222222222o........
|
||||||
|
..............oo2222211112222222222222oo......
|
||||||
|
.............o22221111111111122222222222o.....
|
||||||
|
............o2222111111111111111222222222o....
|
||||||
|
...........o2211111oooooooooooooo222222222o...
|
||||||
|
......ooooo22211ooo22222222222222ooo2222222o..
|
||||||
|
.....o22222ooooo21111111111222222222oo22222o..
|
||||||
|
....o221111122o11111111111111222222222o22223o.
|
||||||
|
...o22111H11222o11111111111111112222222o2223o.
|
||||||
|
..o2211111111222o11111111111111111222222o233o.
|
||||||
|
..o2221111111122o111111222222222222222222T33o.
|
||||||
|
..o2221111112222o122222222222222222222222o33o.
|
||||||
|
..o2221111222222o22222222A222222222222222o33o.
|
||||||
|
..o2222222222222o222222222222222222222333o3o..
|
||||||
|
..o2222222222223o222222222222222222223333o3o..
|
||||||
|
...o22222222223o222222222222222222223333o3o...
|
||||||
|
....o22222222oo22333333333333333333oo33o3o....
|
||||||
|
.....o3oooooo12o33333333oo33333333o12oo3o.....
|
||||||
|
......o2233o1122oooooooo12oooooooo1122oo......
|
||||||
|
.....o2223o111222o3333o1122o3333o211122o......
|
||||||
|
.....o223o2112233o333o211122o33oo2211222o.....
|
||||||
|
....o2233o211223oo222o211123oooo2o2122222o....
|
||||||
|
....o223o2212233oo223o211223o...oo2222223o....
|
||||||
|
.....o33o222223o.o233o222223o....oo222233o....
|
||||||
|
......o3o222233o..o33o222233o.....o233333o....
|
||||||
|
.......ooo2233o....o33o2233o.......o3333o.....
|
||||||
|
..........o33o......oo.oP3o.........oooo......
|
||||||
25
legacy/parts_src/body/insect_larva.txt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
................................................
|
||||||
|
................................................
|
||||||
|
................................................
|
||||||
|
.......................o........................
|
||||||
|
......................o2o.......o...............
|
||||||
|
......oooooooo.......o22o......o2o......o.......
|
||||||
|
.....o22222222o.....o122o.....o22o.....o2o......
|
||||||
|
....o2211112222o...o1112o....o122o....o22o......
|
||||||
|
...o22111H122222ooooooooo...oo212o...o122o......
|
||||||
|
..o22111111122222o222222o.ooo2oooo..o2212o......
|
||||||
|
..o22111111112222o1111222o2222222ooooooooo......
|
||||||
|
.o2221111122222223o1112222o11B12222o22222oooo...
|
||||||
|
.o2221111222222223o11122222o1111222o111222o22o..
|
||||||
|
.o2221112222222223o11122223o11111222o111222o22o.
|
||||||
|
.o2222222222222333o22222223o11122223o111223o122o
|
||||||
|
.o2222222222223333o22222A233o2222223o222222T223o
|
||||||
|
..o22222222223333o222222223o22222223o22oo233o23o
|
||||||
|
..o2222222oooo333o222ooo333o22oooo33o2o12o3o233o
|
||||||
|
...o22223o1122o3o222o112o33o2o1122o3oo2112oo333o
|
||||||
|
....o3333o2112oo222o21122oo22o2112oo2o2122oo33o.
|
||||||
|
.....o333o1122o2333o21222o332o1122oo3o1123o33o..
|
||||||
|
......ooo21223oo333o21233o33o21223o3o21223ooo...
|
||||||
|
........o21223o.oooo2223o.ooo21223ooo21223o.....
|
||||||
|
........o22233o....o2223o...o22233o.o22233o.....
|
||||||
|
.........o233o.....o2233P....o233o...o233o......
|
||||||
35
legacy/parts_src/body/insect_mantis.txt
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
...............oooo.................
|
||||||
|
.......o......o2222o................
|
||||||
|
......obo....o221222o...............
|
||||||
|
......obbo..o2211H222o..............
|
||||||
|
......obbboo2oooo11222o.............
|
||||||
|
..o...obbbcoo1122o1222o.............
|
||||||
|
.obo..obbbboo1112o22223o............
|
||||||
|
.obao..obbco21222o22223o............
|
||||||
|
.obaao.obcco21233o22B23o............
|
||||||
|
.obaabo.oco22223o22222o2oo..........
|
||||||
|
.obbaao.obo21233o22223o222o.........
|
||||||
|
..obabaooo22223o222223o1222o........
|
||||||
|
..obbbbbboooo33o2222233o122o........
|
||||||
|
...obbbbbbbbboo2222223o11223o.......
|
||||||
|
...obcbcbbaaboo2222223o11122o.......
|
||||||
|
....occccbbbbo12222223o111223o......
|
||||||
|
.....oocccccco22222233o222222o......
|
||||||
|
.......oooooo22222A233o2222222o.....
|
||||||
|
..........o22222222333o22222222o....
|
||||||
|
...........o222222333o322222222o....
|
||||||
|
...........o2oooo3333o333322T223o...
|
||||||
|
............o1122o33o3o333333333o...
|
||||||
|
............o1112o3o22ooo33333333o..
|
||||||
|
...........o21122oo2233o.ooo333332o.
|
||||||
|
...........o21223o22233o....oo3333o.
|
||||||
|
...........o21223o22233o......oooo..
|
||||||
|
...........o21233oo2233o............
|
||||||
|
...........o2223o.o22333o...........
|
||||||
|
..........o22223o..o2333o...........
|
||||||
|
..........o21233o..o2333o...........
|
||||||
|
..........o2123o...o23333o..........
|
||||||
|
..........o2223o....o2333o..........
|
||||||
|
..........o2223o....o2333o..........
|
||||||
|
.........o22233o....o2333o..........
|
||||||
|
..........o223o..P...o333o..........
|
||||||
20
legacy/parts_src/body/quad_long.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.................oooooooooooooo......
|
||||||
|
.......oooooooooo22222222222222o.....
|
||||||
|
....ooo22222222222222222B2222222o....
|
||||||
|
...o1111111111111111111111oooooo2o...
|
||||||
|
..o11111H111111111111111oo111112o2o..
|
||||||
|
.oaaaaaaaaab11111111111o111111111T2o.
|
||||||
|
.oaaaaaaaabb11111111111o11111111122o.
|
||||||
|
.o2baaaaabb11111111111o2111111111222o
|
||||||
|
o221baaabb111111111111o2111111122223o
|
||||||
|
o22221bb1112222222A222o2111111222223o
|
||||||
|
.o22111112222222222222o2111122222223o
|
||||||
|
.o22211112222222222222o2211222222233o
|
||||||
|
.o222111222222222222222o21222222233o.
|
||||||
|
..o22111232222222222222o22222222333o.
|
||||||
|
...o212223o3333333333333o22222333oo..
|
||||||
|
...o2aaaa3ooooooo3333333oaaaaa3oo3o..
|
||||||
|
...oaaaaabo223333oooooooaaaaaao333o..
|
||||||
|
...obbbbbboo3333o......obbaaabo33o...
|
||||||
|
....obbbbo.o2333o.......obbbbo333o...
|
||||||
|
.....oooo...oooo.P.......oooooooo....
|
||||||
20
legacy/parts_src/body/quad_slim.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.......oooo.oooooooooooo.........
|
||||||
|
.....oo1222o222222222222oo.......
|
||||||
|
....o111112111111122222B22o......
|
||||||
|
...o11111111111111111222222o.....
|
||||||
|
..o11111bH111111111111ooooo2o....
|
||||||
|
.o2aaaaaaaaabb1111111o11112oTo...
|
||||||
|
.oaaaaaaaaaabb111111o1111112oo...
|
||||||
|
.obaaaaaaaaab111111o1111111122o..
|
||||||
|
o2bbaaaaaaabb222222o1111111222o..
|
||||||
|
.o2bbaaaaabb2222A2o211111122222o.
|
||||||
|
.o222ooobbb2222222o211111222223o.
|
||||||
|
.o22o111obb2222222o221112222233o.
|
||||||
|
..o2o111o2b33333333o2112222233o..
|
||||||
|
...o2112o3333333333o2212222333o..
|
||||||
|
...o21123333333ooooo22222233ooo..
|
||||||
|
...o11223332233o....o222223o222o.
|
||||||
|
..o211223o22333o...o222223o22233o
|
||||||
|
..o222223o233333o..o222233o3333o.
|
||||||
|
...o22233oo3333o....o22333ooooo..
|
||||||
|
....ooooo..oooo.P....ooooo.......
|
||||||
26
legacy/parts_src/body/quad_stocky.txt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
............oooooooo..............
|
||||||
|
........oooo22222222oooo..........
|
||||||
|
......oo2222211122222B22oo........
|
||||||
|
.....o22211111111222222222o.......
|
||||||
|
....o2211111111111111222222o......
|
||||||
|
...o221111111111111111122222o.....
|
||||||
|
..o2211H111111111111111122222o....
|
||||||
|
.oaaaaaaaaaab1111111oooooooo22o...
|
||||||
|
.oaaaaaaaaabb11111oo11111122oTo...
|
||||||
|
o22aaaaaaabb11111o1111111111222o..
|
||||||
|
o222baaaabb111111o1111111111122o..
|
||||||
|
o2222baabb111111o211111111111222o.
|
||||||
|
o22221bb11111122A211111111112223o.
|
||||||
|
o222211111112222o111111111122222o.
|
||||||
|
o22111122122222o22111111222222233o
|
||||||
|
.o21111122222222o221112222222223o.
|
||||||
|
.o21111122222222o222112222222233o.
|
||||||
|
.o21111122222222o222122222222233o.
|
||||||
|
.o2111122o2222222o2222222222233o..
|
||||||
|
.o2111222o2222222o222222222333oo..
|
||||||
|
.o2112222o33333333o222222333oo3o..
|
||||||
|
.o2aaaaab3oo3333333oaaaaa3oo333o..
|
||||||
|
.oaaaaaaao23ooo3333oaaaaao23333o..
|
||||||
|
.obbbbbbbo23333oooobbaaabo23333o..
|
||||||
|
..obbbbbbo3333o....obbbbbo3333o...
|
||||||
|
...oooooo.o33o.P....ooooo.o33o....
|
||||||
27
legacy/parts_src/body/seal_body.txt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
............oooooo..............................
|
||||||
|
..........oo222222oo............................
|
||||||
|
.........o2222222222o...........................
|
||||||
|
........o2222H1222222o..........................
|
||||||
|
.......o22211111122222o.........................
|
||||||
|
......o2221111111222222oo.......................
|
||||||
|
.....o2221111111112222222oo.....................
|
||||||
|
...oo2221111111111122222222oo...................
|
||||||
|
..o22222111111111111122222222oo.................
|
||||||
|
.o2222211111111111111122222B222oo...............
|
||||||
|
..oo22221111111111111112222222222o..............
|
||||||
|
..o2222211111111222222222222222222oo............
|
||||||
|
.o2222211111112222222222222222222222o...........
|
||||||
|
..oo222222222222222222222222222222222o..........
|
||||||
|
..o22222222222222222222222222222222222oo........
|
||||||
|
.o2222222222222222222A222222222222222222oo......
|
||||||
|
..oooo222222222222222222222222222222222222oo....
|
||||||
|
.....o22oooooo222222222222222222222222222222oo..
|
||||||
|
....oooo112222o2222222222222222222222222222222o.
|
||||||
|
...o1111112222o3333333333333332222222222222222o.
|
||||||
|
..o1111112223o3333333333333333333333322232T2232o
|
||||||
|
.o2211122223o333333333333333333333333333333333o.
|
||||||
|
.o221222223o3333333333333333333333333333333333o.
|
||||||
|
o2222222333oooooooo33ooooooooo333333333333oooo..
|
||||||
|
o22o22333oo........oo233333333o333333ooooo......
|
||||||
|
.o2o33ooo...........o3333333ooooooooo...........
|
||||||
|
..ooooo..............Poooooo....................
|
||||||
36
legacy/parts_src/body/serpent_coiled.txt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
..........oooooo..............................
|
||||||
|
.........o222222o.............................
|
||||||
|
........o22111222o.....ooo....................
|
||||||
|
........o2111H122o...ooaao....................
|
||||||
|
.......o221111223o.ooaabbo....................
|
||||||
|
.......o221122223ooaabbco.....................
|
||||||
|
......o2221122223oabbcco......................
|
||||||
|
......o2211122223obccoo.......................
|
||||||
|
......o2211112233ooooooo......................
|
||||||
|
.....o22111111233oaabbbco.....................
|
||||||
|
.....o2211111123ooabbbccco....................
|
||||||
|
....o22211111123oobccccoo.....................
|
||||||
|
....o22211111122ooooooooooooooooooo...........
|
||||||
|
....o222111111222oo2222222222222222ooo........
|
||||||
|
....o222111112222o22122222222B11112222oo......
|
||||||
|
....o2222111122222o112222222222211111222o.....
|
||||||
|
....o22222111222222o113333333333221112222o....
|
||||||
|
.....o22221222222222o133333333333211222222o...
|
||||||
|
......o22222222222222o23333333333122222222o...
|
||||||
|
......o222222222222222o22333333211222222233o..
|
||||||
|
.......oo2222222222332o2222222111112222233o...
|
||||||
|
.....oo22o222222223333o2211111111112333333o...
|
||||||
|
...oo22211o22222233333o22221111112222333ooo...
|
||||||
|
..o2221111o22223333333o333222222222233oo222o..
|
||||||
|
.o221111111o2333333333o33A332222223ooo222222o.
|
||||||
|
o22211111111o33333333oooooooooooooo2222222222o
|
||||||
|
o222211111111o333333o22222222222222222222T223o
|
||||||
|
o2222222222222oooooo2222222222222222222222233o
|
||||||
|
o22222222222222222222222222222222222222332333o
|
||||||
|
.o222222222222222222222222222222222223333333o.
|
||||||
|
..o3332222222222222222222222222222333333333o..
|
||||||
|
...oo333333333222222222233333333333333333oo...
|
||||||
|
.....oo33333333333333333333333333333333oo.....
|
||||||
|
.......ooo33333333333333333333333333ooo.......
|
||||||
|
..........oooooo33333333333333oooooo..........
|
||||||
|
................oooooooPoooooo................
|
||||||
47
legacy/parts_src/body/serpent_rising.txt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
............oooo..........................
|
||||||
|
...........o2222o.........................
|
||||||
|
..........o222122oo.......................
|
||||||
|
.........o222111222o......................
|
||||||
|
.........o2211111222o.....................
|
||||||
|
........o22211H111222o....................
|
||||||
|
.........o221111111222o...................
|
||||||
|
.........o222111111222o...................
|
||||||
|
..........o222111222223o..................
|
||||||
|
...........o22211222222o..................
|
||||||
|
............o22222222223o.................
|
||||||
|
.............o2222222223o.................
|
||||||
|
.............o2222222223o.................
|
||||||
|
..............o2222222333o................
|
||||||
|
..............o2222223333ooo..............
|
||||||
|
.............o21122223333obboo............
|
||||||
|
...........oo211122233333obbbco...........
|
||||||
|
..........o22211112333B3ooobccco..........
|
||||||
|
.........o22211112233333oobbccco..........
|
||||||
|
.........o22111122223333obbccco...........
|
||||||
|
.......ooo2211122222333obcccoo............
|
||||||
|
.....ooaao2221222223333oooo...............
|
||||||
|
...ooaaabo221122223333o...................
|
||||||
|
.ooaaaabbo2211122333oo....................
|
||||||
|
oaaaabbbbo221111233o......................
|
||||||
|
.oooabbbco22111113o.......................
|
||||||
|
.oaabbbcco22111123o.......................
|
||||||
|
oabbbbccco22111122o.......................
|
||||||
|
.ooobccco2221111222o......................
|
||||||
|
....oooo2221111222A2o.....................
|
||||||
|
.......o2222112222222o....................
|
||||||
|
.......o22221122222222ooooo...............
|
||||||
|
........o2222222222222o2222oooo...........
|
||||||
|
......oo2o2222222222222o2111222oo.........
|
||||||
|
....oo2221o122222222222o222111122oo.......
|
||||||
|
...o2221111o223222222332o3322111222o......
|
||||||
|
..o221111111o22222222333333321122222ooooo.
|
||||||
|
.o2221111112o2222223333333332222222222222o
|
||||||
|
.o2222111112oo232333333333311222222222222o
|
||||||
|
.o222222122222oo333333333o11112222T222222o
|
||||||
|
.o22222222222222ooooooooo11111222233333oo.
|
||||||
|
..o2222222222222111111111111122233333oo...
|
||||||
|
...o3332222222222211111112222223333oo.....
|
||||||
|
....oo3333333333322222222222223333o.......
|
||||||
|
......oo33333333333222222233333ooo........
|
||||||
|
........oooo333333333333333oooo...........
|
||||||
|
............ooooooooPoooooo...............
|
||||||
7
legacy/parts_src/eyes/eyes_angry.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
oo............o
|
||||||
|
.ooo........ooo
|
||||||
|
...oo.....ooo..
|
||||||
|
.ooo......oooo.
|
||||||
|
owwpo....owwppo
|
||||||
|
oppwo..P.opppwo
|
||||||
|
.ooo......oooo.
|
||||||
3
legacy/parts_src/eyes/eyes_beady.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
oo........oo
|
||||||
|
pp...P....pp
|
||||||
|
pp........pp
|
||||||
9
legacy/parts_src/eyes/eyes_big_shiny.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.oooo......ooooo.
|
||||||
|
oppppo....opppppo
|
||||||
|
owwppo....owwpppo
|
||||||
|
owwppo....owwpppo
|
||||||
|
oppppo..P.opppppo
|
||||||
|
oppppo....opppppo
|
||||||
|
opppwo....oppppwo
|
||||||
|
opwwpo....oppwwpo
|
||||||
|
.oooo......ooooo.
|
||||||
8
legacy/parts_src/eyes/eyes_compound.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.oooo.....ooooo.
|
||||||
|
owabbo...owabbbo
|
||||||
|
oabcbo...oabcbbo
|
||||||
|
obcbco.P.obcbcbo
|
||||||
|
obbcco...obbcbco
|
||||||
|
ocbcco...ocbcbco
|
||||||
|
.occo.....occco.
|
||||||
|
..oo.......ooo..
|
||||||
9
legacy/parts_src/eyes/eyes_cyclops.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
..oooooo..
|
||||||
|
.owwwwwwo.
|
||||||
|
owppppwwwo
|
||||||
|
opwwpppwwo
|
||||||
|
opwwPppwwo
|
||||||
|
opppppwwwo
|
||||||
|
owppppwwwo
|
||||||
|
.owwwwwwo.
|
||||||
|
..oooooo..
|
||||||
7
legacy/parts_src/eyes/eyes_glowing.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.oo......ooo..
|
||||||
|
oyyo....oyyyo.
|
||||||
|
oxyo....oxxyo.
|
||||||
|
oxxo..P.oxxyo.
|
||||||
|
oxyo....oxyyo.
|
||||||
|
oyzo....oyyzo.
|
||||||
|
.oo......ooo..
|
||||||
3
legacy/parts_src/eyes/eyes_happy_closed.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.oo......ooo.
|
||||||
|
oooo..P.ooooo
|
||||||
|
o..o....oo.oo
|
||||||
8
legacy/parts_src/eyes/eyes_masked.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
oo..............oo
|
||||||
|
oaoo..........oobo
|
||||||
|
oaoooooooooooooobo
|
||||||
|
oaowpoaaabbowpwobo
|
||||||
|
oboppobbPbboppwoco
|
||||||
|
oboooobbbbboooooco
|
||||||
|
occccbboooobbcccco
|
||||||
|
.ooooooo..oooooo..
|
||||||
8
legacy/parts_src/eyes/eyes_round.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.oooo.....ooooo.
|
||||||
|
oppppo...opppppo
|
||||||
|
owwppo...owwpppo
|
||||||
|
owwppo...owwpppo
|
||||||
|
oppppo.P.opppppo
|
||||||
|
oppppo...opppppo
|
||||||
|
opppwo...oppppwo
|
||||||
|
.oooo.....ooooo.
|
||||||
6
legacy/parts_src/eyes/eyes_sharp.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
o...........o
|
||||||
|
ooo......oooo
|
||||||
|
oooo....ooooo
|
||||||
|
owpo..P.oppwo
|
||||||
|
oppo....oppwo
|
||||||
|
.oo......ooo.
|
||||||
5
legacy/parts_src/eyes/eyes_sleepy.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.ooo.....oooo.
|
||||||
|
o112o...o1122o
|
||||||
|
ooooo..Poooooo
|
||||||
|
oppwo...opppwo
|
||||||
|
.ooo.....oooo.
|
||||||
21
legacy/parts_src/head/head_bird.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
......................
|
||||||
|
.........oooooooo.....
|
||||||
|
......ooo22222222o....
|
||||||
|
.....o222212^22222o...
|
||||||
|
....o22111111122222o..
|
||||||
|
...o221111111112223o..
|
||||||
|
..o2211111111111222o..
|
||||||
|
.o222111111111222223o.
|
||||||
|
.o222211111112222223o.
|
||||||
|
.o222211112E22222223o.
|
||||||
|
.o222211122222222233o.
|
||||||
|
.o222211222222222233o.
|
||||||
|
.o222222222222222333o.
|
||||||
|
.o2S2222222222223333o.
|
||||||
|
.o222222222222233333o.
|
||||||
|
..o2222222222233333o..
|
||||||
|
...o22222223333333o...
|
||||||
|
....o333333333333o....
|
||||||
|
.....o333333P333o.....
|
||||||
|
......ooo3333ooo......
|
||||||
|
.........oooo.........
|
||||||
24
legacy/parts_src/head/head_bulky.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
...........ooooooo..........
|
||||||
|
........ooo2222222oo........
|
||||||
|
.......o222222222222o.......
|
||||||
|
.....oo22222111222222o......
|
||||||
|
...oo22ooooooo^1222222o.....
|
||||||
|
..o22111111122oooo22222o....
|
||||||
|
.o2211111111122112oo222o....
|
||||||
|
o22222222222222222oo2223o...
|
||||||
|
.o2222222222223ooo222223o...
|
||||||
|
..o2ooooooooooo112222223o...
|
||||||
|
...o22211111112222222222o...
|
||||||
|
..o2221111112222222222223o..
|
||||||
|
..o222111112E222222222222o..
|
||||||
|
.o222211122222222222222223o.
|
||||||
|
o2222221222222222222222333o.
|
||||||
|
o222222222222222222223oo333o
|
||||||
|
o2222S22222222222223oo33333o
|
||||||
|
o22222222222222233oo3333333o
|
||||||
|
o2222222222222233333333333o.
|
||||||
|
.o22222222223333333333333o..
|
||||||
|
..o333323333333333333333o...
|
||||||
|
...oo33333333333333333oo....
|
||||||
|
.....ooo3333333P333ooo......
|
||||||
|
........ooooooooooo.........
|
||||||
22
legacy/parts_src/head/head_canine.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
...........ooooo..........
|
||||||
|
.........oo22222oo........
|
||||||
|
........o222112222o.......
|
||||||
|
.......o22111122222oo.....
|
||||||
|
......o2211111^222222oooo.
|
||||||
|
.....o2221111112222222222o
|
||||||
|
.....o221111111112222222o.
|
||||||
|
.....o22111111112222233o..
|
||||||
|
....o22111111112222222ooo.
|
||||||
|
...oo2111111E222222232322o
|
||||||
|
.oo222111111222222222332o.
|
||||||
|
o2222211112222222222233o..
|
||||||
|
o222222111222222222223ooo.
|
||||||
|
o22222S222222222222333322o
|
||||||
|
o22222222222222222333332o.
|
||||||
|
.o2oooo2222222223333333o..
|
||||||
|
..oo332222222223333333o...
|
||||||
|
....oo322233333333333o....
|
||||||
|
......o3333333333333o.....
|
||||||
|
.......o3333333P33oo......
|
||||||
|
........oooooooooo........
|
||||||
|
..........................
|
||||||
22
legacy/parts_src/head/head_feline.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
..........oooooo..........
|
||||||
|
........oo222222oo........
|
||||||
|
......oo2222122222oo......
|
||||||
|
.....o22221112222222o.....
|
||||||
|
....o22111111^1222222o....
|
||||||
|
....o2211111111112222o....
|
||||||
|
...o222111111111222223o...
|
||||||
|
...o222111111111122223o...
|
||||||
|
...o222211111122222223o...
|
||||||
|
...o221111112222222223o...
|
||||||
|
..oo2222112E2222222233oo..
|
||||||
|
.o2222221222222222222332o.
|
||||||
|
o222222122222222222223322o
|
||||||
|
.ooo222222222222222233ooo.
|
||||||
|
..o22222222222222233333o..
|
||||||
|
.o22222S2222222333333332o.
|
||||||
|
o222222222222333333333332o
|
||||||
|
.ooo222323333333333333ooo.
|
||||||
|
....oo33333333333333oo....
|
||||||
|
......ooo33333P33ooo......
|
||||||
|
.........oooooooo.........
|
||||||
|
..........................
|
||||||
18
legacy/parts_src/head/head_flat.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
..........................
|
||||||
|
..........................
|
||||||
|
............ooooo.........
|
||||||
|
....oooo...o22^22o....oo..
|
||||||
|
...o2222o.o2222222o.oobbo.
|
||||||
|
..o221122o22112222oobbbbo.
|
||||||
|
.o2211122121111222obbbbo..
|
||||||
|
.o22111111111112222oooooo.
|
||||||
|
.o221111111E1112222222obbo
|
||||||
|
.o2111112221112222222obbbo
|
||||||
|
o222222222221222222222obo.
|
||||||
|
o2222222222222o22223333o..
|
||||||
|
oooooSoooooooo22223333obo.
|
||||||
|
.o2222222222222333333obbo.
|
||||||
|
..o22323333333333333oboo..
|
||||||
|
...o3333333333333333oo....
|
||||||
|
....ooo3333333P33ooo......
|
||||||
|
.......oooooooooo.........
|
||||||
22
legacy/parts_src/head/head_ghost.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
..........................o.
|
||||||
|
........................oo2o
|
||||||
|
........oooooooo......oo222o
|
||||||
|
......oo22222222oooooo2222o.
|
||||||
|
.....o22212^2222222222222o..
|
||||||
|
....o22211111222222222222o..
|
||||||
|
...o2211ooo1111112111222o...
|
||||||
|
..o22ooooooooo111111223o....
|
||||||
|
.o22ooooooooooo11122223o....
|
||||||
|
.o2ooooooooooooo1222223o....
|
||||||
|
o2oooooooooooooo2222223o....
|
||||||
|
o2ooooooooooooooo222223o....
|
||||||
|
o2o333333E333333o222233o....
|
||||||
|
o2o3333333333333o222233o....
|
||||||
|
o22o33333333333o2222233o....
|
||||||
|
o22o33333333333o2322233o....
|
||||||
|
.o22ooS333333oo23333333o....
|
||||||
|
.o2222ooooooo2233333333o....
|
||||||
|
.o222322222223P33333333o....
|
||||||
|
.o22333o33333o33333o333o....
|
||||||
|
..o233o.o333o.o333o.o3o.....
|
||||||
|
...ooo...ooo...ooo...o......
|
||||||
20
legacy/parts_src/head/head_insect.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.......ooooooooo......
|
||||||
|
.....oo222222222oo....
|
||||||
|
....o2221112222222o...
|
||||||
|
...o221111111222222o..
|
||||||
|
..o22111111^11122222o.
|
||||||
|
..o22111111111112222o.
|
||||||
|
.o2221111111112222223o
|
||||||
|
.o2222111112222222223o
|
||||||
|
.o2222112222222222223o
|
||||||
|
.o2222222E22222222233o
|
||||||
|
.o2222222222222222333o
|
||||||
|
.o2222222222222223333o
|
||||||
|
.o2222222222222233333o
|
||||||
|
.o2222oo22222223333oo.
|
||||||
|
.o222o22ooo2233333o...
|
||||||
|
.o222oS222oooooooo....
|
||||||
|
..o2o222222223333o....
|
||||||
|
...o2222223P3333o.....
|
||||||
|
....oo3333333333o.....
|
||||||
|
......oooooooooo......
|
||||||
20
legacy/parts_src/head/head_reptile.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.................oo.....oo..
|
||||||
|
...............oobbo..oobbo.
|
||||||
|
..........oo..oaaabo.oabbbo.
|
||||||
|
.........obboobbaaboobbabbo.
|
||||||
|
........obbbo2ooooo22obbbbo.
|
||||||
|
.......obboo2222222222ooco..
|
||||||
|
......oooo21111^11222222oo..
|
||||||
|
...ooo22211111111111222222o.
|
||||||
|
..o22221111111111111122223o.
|
||||||
|
.o211111111111111122222223o.
|
||||||
|
o2222111111111E12222222223o.
|
||||||
|
o2222211111122222222222233o.
|
||||||
|
o222222222222oo22222222333o.
|
||||||
|
oooooSooooooo222222222333o..
|
||||||
|
.o22222222222222222223333o..
|
||||||
|
..o3333332222222222333333o..
|
||||||
|
...ooo333333333333333333o...
|
||||||
|
......oooo3333333333333o....
|
||||||
|
..........ooo33P33333oo.....
|
||||||
|
.............oooooooo.......
|
||||||
22
legacy/parts_src/head/head_rock.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
...........oo.....oo......
|
||||||
|
....ooooooo22o.ooo22oo....
|
||||||
|
...o222221o222o2222222o...
|
||||||
|
...o21111o22222o2222222o..
|
||||||
|
..o22111111^2222o2222223o.
|
||||||
|
.o221111111112222oo22233o.
|
||||||
|
.o2211111111112222o22333o.
|
||||||
|
o22211111111222222o22o333o
|
||||||
|
o22211112222222222o333o33o
|
||||||
|
o222211222E2222223o333o33o
|
||||||
|
o23222222222222223o33o333o
|
||||||
|
o22322222222222223o33o333o
|
||||||
|
o22322222222222233o333333o
|
||||||
|
o22222222222222333o333333o
|
||||||
|
.o2222oooooooo3333o333333o
|
||||||
|
.ooooo11111122ooooo33333o.
|
||||||
|
.o211S111111o111122o333o..
|
||||||
|
.o22111122222o221222o3o...
|
||||||
|
.o22222222222o2222233o....
|
||||||
|
..o22222233333P33233o.....
|
||||||
|
...oo33333333333333o......
|
||||||
|
.....oooooooooooooo.......
|
||||||
22
legacy/parts_src/head/head_round.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
........oooooooooo........
|
||||||
|
......oo2222222222oo......
|
||||||
|
.....o22211111112222o.....
|
||||||
|
....o2211111111122222o....
|
||||||
|
...o221111111^11122222o...
|
||||||
|
..o22111111111111122222o..
|
||||||
|
.o2211111111111111122222o.
|
||||||
|
.o2111111111111111112222o.
|
||||||
|
o222111111111111111122222o
|
||||||
|
o222111111111111111222223o
|
||||||
|
o222111111111111122222223o
|
||||||
|
o222111111111122222222223o
|
||||||
|
o2221111111E1112222222223o
|
||||||
|
o221111111111111222222223o
|
||||||
|
o211111111111111122222233o
|
||||||
|
o211111111111111122222333o
|
||||||
|
o2111S1111111111122223333o
|
||||||
|
o2211111o1o11222222333333o
|
||||||
|
.o2211111o22222333333333o.
|
||||||
|
..o22212222223333333333o..
|
||||||
|
...oo33333oooooooo333oo...
|
||||||
|
.....ooooo....P...ooo.....
|
||||||
6
legacy/parts_src/snout/beak_duck.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.ooooooo.
|
||||||
|
oaaaaaabo
|
||||||
|
oabbbbbbP
|
||||||
|
obbbbbbco
|
||||||
|
.occcccco
|
||||||
|
..oooooo.
|
||||||