Compare commits
5 Commits
1826c23826
...
900223d261
| Author | SHA1 | Date |
|---|---|---|
|
|
900223d261 | |
|
|
d036738490 | |
|
|
b09c034c99 | |
|
|
04848d2921 | |
|
|
c031c8e0be |
|
|
@ -37,7 +37,8 @@
|
||||||
"mcp__chrome-devtools__upload_file",
|
"mcp__chrome-devtools__upload_file",
|
||||||
"mcp__chrome-devtools__wait_for",
|
"mcp__chrome-devtools__wait_for",
|
||||||
"WebFetch(domain:banatie.app)",
|
"WebFetch(domain:banatie.app)",
|
||||||
"Bash(npx skills:*)"
|
"Bash(npx skills:*)",
|
||||||
|
"Bash(identify:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
---
|
---
|
||||||
name: gen-image
|
name: gen-image
|
||||||
description: Generate images via Banatie API — text-to-image with optional reference images, aspect ratios, and enhancement templates
|
description: >
|
||||||
|
Generate and modify images via Banatie API. Use this skill whenever the user
|
||||||
|
asks to generate, create, or make an image, picture, icon, illustration,
|
||||||
|
background, banner, hero image, photo, thumbnail, or any visual asset. Also
|
||||||
|
trigger when the user wants to modify, change, fix, adjust, or iterate on an
|
||||||
|
existing image — e.g. "too detailed", "change the background", "make it
|
||||||
|
darker", "remove X", "more like Y". Also trigger when the user mentions
|
||||||
|
Banatie, asks for a sticker, product photo, comic-style art, photorealistic
|
||||||
|
render, minimalist graphic, or needs to use reference images for generation.
|
||||||
|
Covers text-to-image, image modification via references, aspect ratios, and
|
||||||
|
enhancement templates.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Image Generation Skill
|
# Image Generation Skill
|
||||||
|
|
||||||
Generate images using the Banatie API. Parses user arguments, validates inputs, and runs the generation script.
|
Generate and modify images using the Banatie API. Parse user arguments, validate inputs, and run the bundled generation script.
|
||||||
|
|
||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
Parse these from the user's message. Use `AskUserQuestion` for any missing required arguments.
|
Parse these from the user's message. Ask the user for any missing required arguments.
|
||||||
|
|
||||||
| Argument | Required | Default | Description |
|
| Argument | Required | Default | Description |
|
||||||
|----------|----------|---------|-------------|
|
|----------|----------|---------|-------------|
|
||||||
|
|
@ -18,27 +28,79 @@ Parse these from the user's message. Use `AskUserQuestion` for any missing requi
|
||||||
| **Aspect ratio** | No | `1:1` | `1:1`, `16:9`, `9:16`, `3:2`, `4:3`, `3:4`, `21:9` |
|
| **Aspect ratio** | No | `1:1` | `1:1`, `16:9`, `9:16`, `3:2`, `4:3`, `3:4`, `21:9` |
|
||||||
| **Reference images** | No | — | Local file paths or `@alias` names (max 3) |
|
| **Reference images** | No | — | Local file paths or `@alias` names (max 3) |
|
||||||
| **Enhancement template** | No | `general` | `general`, `photorealistic`, `illustration`, `minimalist`, `sticker`, `product`, `comic` |
|
| **Enhancement template** | No | `general` | `general`, `photorealistic`, `illustration`, `minimalist`, `sticker`, `product`, `comic` |
|
||||||
|
| **Auto enhance** | No | `true` | Set to `false` to skip AI prompt enhancement and use the prompt as-is |
|
||||||
|
|
||||||
|
## Two Modes of Operation
|
||||||
|
|
||||||
|
### New image — generate from scratch
|
||||||
|
The user asks to create something new. No existing image is involved.
|
||||||
|
|
||||||
|
### Modify image — iterate on an existing image
|
||||||
|
The user wants to change, fix, or adjust an image that was already generated or exists in the project. Detect this mode when the user says things like "too detailed", "change the background", "make it brighter", "remove the text", "more like X", or any feedback about a previously generated image.
|
||||||
|
|
||||||
|
**In modification mode, always use the current image as a `--ref` argument.** The prompt should describe the desired result (not the diff). For example, if the user says "too many details, should look like an irregular boulder" about `assets/items/asteroid1.png`, run:
|
||||||
|
```bash
|
||||||
|
node <skill-dir>/banatie-gen.mjs \
|
||||||
|
--prompt "simple irregular boulder, smooth rock with minimal details, in No Man's Sky style on white background" \
|
||||||
|
--output assets/items/asteroids/asteroid1.png \
|
||||||
|
--ref assets/items/asteroids/asteroid1.png \
|
||||||
|
--template minimalist
|
||||||
|
```
|
||||||
|
|
||||||
|
The reference image gives the AI a visual anchor (composition, colors, overall shape) while the prompt steers it toward the desired changes. This produces much better results than generating from scratch with a new prompt, because the output stays visually consistent with the original.
|
||||||
|
|
||||||
|
## Reference Image Policy
|
||||||
|
|
||||||
|
**Never add `--ref` silently when creating a new image.** The rules:
|
||||||
|
|
||||||
|
1. **User explicitly provides a ref** (file path or @alias) → use it
|
||||||
|
2. **Modification mode** (user gives feedback on an existing image) → use the existing image as ref automatically
|
||||||
|
3. **New image, similar assets exist nearby** → **ask the user first**: "I see [filename] in the same folder. Would you like to use it as a reference for visual consistency, or generate from scratch?" Do not assume.
|
||||||
|
4. **New image, no similar context** → generate from scratch, no ref
|
||||||
|
|
||||||
|
The project's CLAUDE.md may override this policy with project-specific ref rules (e.g. "always use X as ref for assets in folder Y"). If CLAUDE.md provides ref guidance, follow it without asking.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
1. **Parse arguments** from the user's message. Extract prompt, output path, aspect ratio, references, and template inline where provided.
|
1. **Determine the mode.** Is this a new image or a modification of an existing one? If the user gives feedback on a recently generated image or asks to change something about an existing file, use modification mode.
|
||||||
|
|
||||||
2. **Fill missing required arguments** using `AskUserQuestion`. Suggest an output path based on context (e.g. `assets/backgrounds/` for backgrounds, `assets/icons/` for icons).
|
2. **Parse arguments** from the user's message. Extract prompt, output path, aspect ratio, references, template, and auto-enhance flag.
|
||||||
|
|
||||||
3. **Validate** that any referenced local files exist before proceeding.
|
3. **Fill missing required arguments.** Suggest an output path based on context. In modification mode, default to overwriting the original file unless the user asks for a variation.
|
||||||
|
|
||||||
4. **Read API docs** from `docs/` subfolder when the user needs advanced features (references, flows, aliases). The docs are:
|
4. **In modification mode:** automatically add the existing image path as `--ref`. Write the prompt as a full description of the desired result, incorporating the user's requested changes. Do not describe only the changes — describe what the final image should look like.
|
||||||
|
|
||||||
|
5. **Validate** that any referenced local files exist before proceeding.
|
||||||
|
|
||||||
|
6. **Read API docs** from the `docs/` subfolder of this skill when the user needs advanced features (references, flows, aliases). The docs are:
|
||||||
- `docs/image-generation.md` — basic generation, aspect ratios, prompt enhancement, templates
|
- `docs/image-generation.md` — basic generation, aspect ratios, prompt enhancement, templates
|
||||||
- `docs/image-generation-advanced.md` — reference images, aliases, flows, regeneration
|
- `docs/image-generation-advanced.md` — reference images, aliases, flows, regeneration
|
||||||
- `docs/images-upload.md` — image upload, alias management
|
- `docs/images-upload.md` — image upload, alias management
|
||||||
|
|
||||||
5. **Run generation**:
|
7. **Run generation** using the bundled script (path relative to this skill's directory):
|
||||||
```bash
|
```bash
|
||||||
node .claude/skills/gen-image/banatie-gen.mjs --prompt "<prompt>" --output <path> [--aspect-ratio <ratio>] [--ref <file_or_alias>]...
|
node <skill-dir>/banatie-gen.mjs \
|
||||||
|
--prompt "<prompt>" \
|
||||||
|
--output <path> \
|
||||||
|
[--aspect-ratio <ratio>] \
|
||||||
|
[--template <template>] \
|
||||||
|
[--no-enhance] \
|
||||||
|
[--ref <file_or_alias>]...
|
||||||
```
|
```
|
||||||
|
Where `<skill-dir>` is the directory containing this SKILL.md (e.g. `.claude/skills/gen-image`).
|
||||||
|
|
||||||
6. **Report results**: output file path, image dimensions, and the full command for reproducibility.
|
The script handles polling automatically — if the API returns a pending/processing status, it waits until generation completes (up to 2 minutes).
|
||||||
|
|
||||||
|
8. **Evaluate the result.** View the generated image and assess whether it matches the user's request. If it clearly doesn't (wrong style, missing key elements, too different from what was asked), tell the user what went wrong and suggest another attempt with an adjusted prompt. This self-evaluation loop is encouraged.
|
||||||
|
|
||||||
|
9. **Handle errors.** If generation fails:
|
||||||
|
- `UNAUTHORIZED` → check that `BANATIE_KEY` is set in `.env` at the project root
|
||||||
|
- `RATE_LIMIT_EXCEEDED` → wait and retry, or inform the user (limit: 100 requests/hour)
|
||||||
|
- `VALIDATION_ERROR` → check prompt, aspect ratio, and reference file formats (PNG, JPEG, WebP, max 5MB)
|
||||||
|
- Timeout → the generation took too long, suggest retrying with a simpler prompt
|
||||||
|
|
||||||
|
10. **Report results**: output file path, image dimensions, and the full command used for reproducibility.
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
The script reads `BANATIE_KEY` from `.env` in the project root. Rate limit: 100 requests/hour.
|
The script reads `BANATIE_KEY` from `.env` in the project root. Rate limit: 100 requests per hour.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,48 @@ try {
|
||||||
const API_BASE = 'https://api.banatie.app/api/v1';
|
const API_BASE = 'https://api.banatie.app/api/v1';
|
||||||
const API_KEY = process.env.BANATIE_KEY || '';
|
const API_KEY = process.env.BANATIE_KEY || '';
|
||||||
|
|
||||||
|
const POLL_INTERVAL_MS = 2000;
|
||||||
|
const POLL_MAX_ATTEMPTS = 60; // 2 minutes total
|
||||||
|
|
||||||
|
async function pollGeneration(generationId) {
|
||||||
|
for (let attempt = 1; attempt <= POLL_MAX_ATTEMPTS; attempt++) {
|
||||||
|
const response = await fetch(`${API_BASE}/generations/${generationId}`, {
|
||||||
|
headers: { 'X-API-Key': API_KEY },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
console.error(`Poll error ${response.status}: ${text}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (!result.success) {
|
||||||
|
console.error(`Poll failed:`, result.error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status } = result.data;
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'failed') {
|
||||||
|
console.error(`Generation failed: ${result.data.errorMessage || 'unknown error'}`);
|
||||||
|
console.error('Suggestions: try a simpler prompt, check rate limits, or verify your BANATIE_KEY.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still pending/processing — wait and retry
|
||||||
|
console.log(`Status: ${status} (attempt ${attempt}/${POLL_MAX_ATTEMPTS})...`);
|
||||||
|
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Generation timed out after ${POLL_MAX_ATTEMPTS * POLL_INTERVAL_MS / 1000}s. Try again or use a simpler prompt.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
async function uploadImage(filePath, { flowId, alias }) {
|
async function uploadImage(filePath, { flowId, alias }) {
|
||||||
const absolutePath = resolve(filePath);
|
const absolutePath = resolve(filePath);
|
||||||
const fileBuffer = readFileSync(absolutePath);
|
const fileBuffer = readFileSync(absolutePath);
|
||||||
|
|
@ -41,6 +83,8 @@ async function uploadImage(filePath, { flowId, alias }) {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
console.error(`Upload error ${response.status}: ${text}`);
|
console.error(`Upload error ${response.status}: ${text}`);
|
||||||
|
if (response.status === 401) console.error('Check that BANATIE_KEY is set correctly in .env');
|
||||||
|
if (response.status === 429) console.error('Rate limit exceeded. Wait before retrying (limit: 100 req/hour).');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,20 +127,28 @@ async function resolveRefs(refs) {
|
||||||
return { referenceImages: aliases, flowId };
|
return { referenceImages: aliases, flowId };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateImage({ prompt, output, aspectRatio = '1:1', refs }) {
|
export async function generateImage({ prompt, output, aspectRatio = '1:1', refs, template, autoEnhance = true }) {
|
||||||
if (!API_KEY) {
|
if (!API_KEY) {
|
||||||
console.error('BANATIE_KEY environment variable is not set');
|
console.error('BANATIE_KEY environment variable is not set.');
|
||||||
|
console.error('Add BANATIE_KEY=your_key to the .env file in the project root.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = await resolveRefs(refs);
|
const resolved = await resolveRefs(refs);
|
||||||
const body = { prompt, aspectRatio };
|
|
||||||
|
const body = { prompt, aspectRatio, autoEnhance };
|
||||||
|
|
||||||
|
if (template && autoEnhance) {
|
||||||
|
body.enhancementOptions = { template };
|
||||||
|
}
|
||||||
|
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
body.referenceImages = resolved.referenceImages;
|
body.referenceImages = resolved.referenceImages;
|
||||||
if (resolved.flowId) body.flowId = resolved.flowId;
|
if (resolved.flowId) body.flowId = resolved.flowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Generating: "${prompt}" (${body.aspectRatio})${resolved ? ` with ${resolved.referenceImages.length} ref(s)` : ''}...`);
|
const enhanceInfo = autoEnhance ? ` [template: ${template || 'general'}]` : ' [no enhance]';
|
||||||
|
console.log(`Generating: "${prompt}" (${body.aspectRatio})${enhanceInfo}${resolved ? ` with ${resolved.referenceImages.length} ref(s)` : ''}...`);
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}/generations`, {
|
const response = await fetch(`${API_BASE}/generations`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -110,6 +162,8 @@ export async function generateImage({ prompt, output, aspectRatio = '1:1', refs
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
console.error(`API error ${response.status}: ${text}`);
|
console.error(`API error ${response.status}: ${text}`);
|
||||||
|
if (response.status === 401) console.error('Check that BANATIE_KEY is set correctly in .env');
|
||||||
|
if (response.status === 429) console.error('Rate limit exceeded. Wait before retrying (limit: 100 req/hour).');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +173,20 @@ export async function generateImage({ prompt, output, aspectRatio = '1:1', refs
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageUrl = result.data.outputImage.storageUrl;
|
// Handle async generation: poll if not yet complete
|
||||||
|
let data = result.data;
|
||||||
|
if (data.status === 'pending' || data.status === 'processing') {
|
||||||
|
console.log(`Generation queued (id: ${data.id}), waiting for completion...`);
|
||||||
|
data = await pollGeneration(data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.outputImage?.storageUrl) {
|
||||||
|
console.error('Generation completed but no output image found.');
|
||||||
|
console.error('Response data:', JSON.stringify(data, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrl = data.outputImage.storageUrl;
|
||||||
console.log(`Downloading from ${imageUrl}...`);
|
console.log(`Downloading from ${imageUrl}...`);
|
||||||
|
|
||||||
const imageResponse = await fetch(imageUrl);
|
const imageResponse = await fetch(imageUrl);
|
||||||
|
|
@ -133,16 +200,18 @@ export async function generateImage({ prompt, output, aspectRatio = '1:1', refs
|
||||||
mkdirSync(dirname(outputPath), { recursive: true });
|
mkdirSync(dirname(outputPath), { recursive: true });
|
||||||
writeFileSync(outputPath, buffer);
|
writeFileSync(outputPath, buffer);
|
||||||
|
|
||||||
console.log(`Image saved: ${outputPath} (${result.data.outputImage.width}x${result.data.outputImage.height})`);
|
console.log(`Image saved: ${outputPath} (${data.outputImage.width}x${data.outputImage.height})`);
|
||||||
return { path: outputPath, generation: result.data };
|
return { path: outputPath, generation: data };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArgs(args) {
|
function parseArgs(args) {
|
||||||
const result = {};
|
const result = { autoEnhance: true };
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
if (args[i] === '--prompt') result.prompt = args[++i];
|
if (args[i] === '--prompt') result.prompt = args[++i];
|
||||||
else if (args[i] === '--output') result.output = args[++i];
|
else if (args[i] === '--output') result.output = args[++i];
|
||||||
else if (args[i] === '--aspect-ratio') result.aspectRatio = args[++i];
|
else if (args[i] === '--aspect-ratio') result.aspectRatio = args[++i];
|
||||||
|
else if (args[i] === '--template') result.template = args[++i];
|
||||||
|
else if (args[i] === '--no-enhance') result.autoEnhance = false;
|
||||||
else if (args[i] === '--ref') {
|
else if (args[i] === '--ref') {
|
||||||
if (!result.refs) result.refs = [];
|
if (!result.refs) result.refs = [];
|
||||||
result.refs.push(args[++i]);
|
result.refs.push(args[++i]);
|
||||||
|
|
@ -155,6 +224,6 @@ const args = parseArgs(process.argv.slice(2));
|
||||||
if (args.prompt && args.output) {
|
if (args.prompt && args.output) {
|
||||||
generateImage(args);
|
generateImage(args);
|
||||||
} else if (process.argv.length > 2) {
|
} else if (process.argv.length > 2) {
|
||||||
console.error('Usage: node banatie-gen.mjs --prompt "<description>" --output <path> [--aspect-ratio <ratio>] [--ref <file|@alias>]...');
|
console.error('Usage: node banatie-gen.mjs --prompt "<description>" --output <path> [--aspect-ratio <ratio>] [--template <template>] [--no-enhance] [--ref <file|@alias>]...');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
CLAUDE.md
|
|
@ -147,6 +147,16 @@ When generating HTML worksheets:
|
||||||
|
|
||||||
Use the `/gen-image` skill to generate images via the Banatie API. The skill has its own script and full API documentation in `.claude/skills/gen-image/`. Auth via `BANATIE_KEY` in `.env`. Rate limit: 100 requests/hour.
|
Use the `/gen-image` skill to generate images via the Banatie API. The skill has its own script and full API documentation in `.claude/skills/gen-image/`. Auth via `BANATIE_KEY` in `.env`. Rate limit: 100 requests/hour.
|
||||||
|
|
||||||
|
### Reference Policy for This Project
|
||||||
|
|
||||||
|
When generating new assets for `assets/items/`, use existing images from the same subfolder as `--ref` to maintain visual consistency across the set. For example, when creating `asteroid2.png`, reference `asteroid1.png` from the same folder. The gen-image skill's default policy is to ask before using refs — this project overrides that: use refs automatically for items in the same asset group.
|
||||||
|
|
||||||
|
Style reference chain for themed assets:
|
||||||
|
- `assets/items/asteroids/` — use any existing asteroid as ref for new ones
|
||||||
|
- `assets/items/crystals/` — use any existing crystal as ref for new ones
|
||||||
|
- `assets/icons/` — generate independently (icons have their own pipeline via icon packs)
|
||||||
|
- `assets/hero-images/`, `assets/footers/`, `assets/backgrounds/` — generate independently, no refs needed
|
||||||
|
|
||||||
## Background Removal
|
## Background Removal
|
||||||
|
|
||||||
Script `src/scripts/remove-bg.mjs` removes white backgrounds from PNG icons using flood-fill from edges (like magic wand in Photoshop). Uses sharp. White areas inside objects are preserved.
|
Script `src/scripts/remove-bg.mjs` removes white backgrounds from PNG icons using flood-fill from edges (like magic wand in Photoshop). Uses sharp. White areas inside objects are preserved.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Asteroid Asset Generation Rules
|
||||||
|
|
||||||
|
## Style overview
|
||||||
|
|
||||||
|
The set has two tiers:
|
||||||
|
|
||||||
|
**Single-piece asteroids (asteroid1–asteroid9)** — each is a standalone rock with its own unique shape and crack color. They serve as style sources for multi-piece variants.
|
||||||
|
|
||||||
|
**Multi-piece asteroids (asteroid10–asteroid14)** — compact clusters of 3 pieces tightly pressed together. Each one inherits its style from a corresponding single-piece asteroid.
|
||||||
|
|
||||||
|
## Style map
|
||||||
|
|
||||||
|
| File | Shape type | Crack color | Style source |
|
||||||
|
|------|-----------|-------------|--------------|
|
||||||
|
| asteroid1 | round sphere | orange/amber | — |
|
||||||
|
| asteroid3 | flat angular boulder | red glowing | — |
|
||||||
|
| asteroid5 | tall elongated shard | purple/magenta | — |
|
||||||
|
| asteroid7 | flat horizontal disc | pink/purple | — |
|
||||||
|
| asteroid9 | tall spire with spikes | cyan/blue | — |
|
||||||
|
| asteroid11 | 3-piece cluster | red (from asteroid3) | asteroid3 |
|
||||||
|
| asteroid12 | 3-piece cluster | orange/golden (from asteroid1) | asteroid1 — **ideal composition reference** |
|
||||||
|
| asteroid13 | 3-piece cluster | purple/magenta (from asteroid5) | asteroid5 |
|
||||||
|
|
||||||
|
## Rules for generating new multi-piece asteroids
|
||||||
|
|
||||||
|
### Composition
|
||||||
|
- Always 3 pieces of **different sizes** tightly pressed together
|
||||||
|
- Pieces must touch each other — no gaps, no space between them
|
||||||
|
- The cluster must read as one compact object, not a scattered group
|
||||||
|
- Vary the arrangement: triangle, stacked, side-by-side — avoid repeating the same layout
|
||||||
|
|
||||||
|
### Style
|
||||||
|
- Use `--ref` with the corresponding single-piece asteroid (not asteroid12)
|
||||||
|
- Do not use `--template illustration` — it makes cracks too prominent and overrides the source style
|
||||||
|
- Use the default `--template general`
|
||||||
|
- Crack color, surface texture, and painting style must come from the reference
|
||||||
|
|
||||||
|
### What to avoid
|
||||||
|
- No floating debris or separate small fragments
|
||||||
|
- No explosion effects
|
||||||
|
- No atmospheric glow, energy bursts, or lightning spread outward from the object
|
||||||
|
- No changing the crack color relative to the reference (e.g. if the ref has red cracks, the output must have red cracks — not orange, not purple)
|
||||||
|
|
||||||
|
## Generation command pattern
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node .claude/skills/gen-image/banatie-gen.mjs \
|
||||||
|
--prompt "three dark rocky asteroid pieces of different sizes tightly pressed together, <crack description from ref>, painterly art style, white background, compact cluster no gaps between stones, varied shapes and sizes" \
|
||||||
|
--output assets/items/asteroids/asteroidN.png \
|
||||||
|
--ref assets/items/asteroids/asteroidX.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `asteroidX` is the style source from the table above.
|
||||||
|
|
||||||
|
## Approved reference for composition
|
||||||
|
|
||||||
|
**asteroid12.png** is the approved example of correct multi-piece composition: compact, no gaps, pieces vary in size, reads as one unit. Use it as a visual benchmark when evaluating new generations — but do NOT add it as `--ref` alongside the style source, as it will pull the style toward orange/golden.
|
||||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 975 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 889 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 981 KiB |