Add Stormbringer art gallery: 39 gpt-image-2 images, HTML gallery, prompts, generator script
- scenarios/art/stormbringer/: 39 PNG images generated with gpt-image-2 at high quality covering locations, characters, demons, elementals, creatures, weapons, religion - asset-library/stormbringer-1e/stormbringer_reference.html: updated with full image gallery section, lightbox viewer, and nav entry - scenarios/art/stormbringer/stormbringer-art-prompts.md: full prompt library (39 prompts) - scenarios/art/stormbringer/dalle_generate.py: queue-based DALL-E/gpt-image generator
@@ -297,6 +297,76 @@ td:first-child { color: var(--cream); }
|
||||
section { padding: 18px 18px 8px; }
|
||||
.page-title { padding: 20px 18px 14px; }
|
||||
}
|
||||
|
||||
/* ── ART GALLERY ── */
|
||||
.gallery-category { margin-bottom: 2.5rem; }
|
||||
.gallery-category h3 { color: var(--gold); margin-bottom: 1rem; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.gallery-item {
|
||||
background: var(--panel-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
.gallery-item:hover { border-color: var(--gold-dim); transform: translateY(-2px); }
|
||||
.gallery-item img {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.gallery-item.portrait img { height: 240px; }
|
||||
.gallery-caption {
|
||||
padding: 8px 10px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--cream-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
/* Lightbox */
|
||||
#lightbox {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.92);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
#lightbox.open { display: flex; }
|
||||
#lightbox img {
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
#lightbox-caption {
|
||||
color: var(--cream);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
#lightbox-close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 24px;
|
||||
color: var(--cream-dim);
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
#lightbox-close:hover { color: var(--gold); }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1266,6 +1336,217 @@ Effective maximum: 99</div>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="art-gallery">
|
||||
<h2>Art Gallery</h2>
|
||||
<p style="margin-bottom:1.5rem;color:var(--cream-dim);font-style:italic;">Generated with gpt-image-2 in the style of 1970s Michael Whelan sword and sorcery illustration. Click any image to view full size.</p>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Locations</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l01-imrryr.png" data-caption="Imrryr — The Dreaming City of Melniboné">
|
||||
<div class="gallery-caption">Imrryr — The Dreaming City</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l02-karlaak.png" data-caption="Karlaak — City on the Edge of the Weeping Waste">
|
||||
<div class="gallery-caption">Karlaak — City by the Weeping Waste</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l03-pan-tang.png" data-caption="Pan Tang — Theocracy of Chaos">
|
||||
<div class="gallery-caption">Pan Tang — Theocracy of Chaos</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l04-nadsokor.png" data-caption="Nadsokor — City of Beggars">
|
||||
<div class="gallery-caption">Nadsokor — City of Beggars</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l05-map.png" data-caption="The Young Kingdoms — World Map">
|
||||
<div class="gallery-caption">The Young Kingdoms — World Map</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l06-boiling-sea.png" data-caption="The Boiling Sea — Southern Boundary">
|
||||
<div class="gallery-caption">The Boiling Sea</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l07-tavern.png" data-caption="A Young Kingdoms Tavern — Common Meeting Ground">
|
||||
<div class="gallery-caption">A Young Kingdoms Tavern</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-l08-demon-realm.png" data-caption="The Demon Realm — Beyond the Mortal Plane">
|
||||
<div class="gallery-caption">The Demon Realm</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Characters</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c01-elric-portrait.png" data-caption="Elric of Melniboné — The Pale Prince">
|
||||
<div class="gallery-caption">Elric of Melniboné — Portrait</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c02-elric-action.png" data-caption="Elric — Stormbringer Raised in Battle">
|
||||
<div class="gallery-caption">Elric — In Battle</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c03-moonglum.png" data-caption="Moonglum of Elwher — Elric's Companion">
|
||||
<div class="gallery-caption">Moonglum of Elwher</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c04-melnibonean-noble.png" data-caption="A Melnibonean Noble — Decadent Aristocrat">
|
||||
<div class="gallery-caption">Melnibonean Noble</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c05-pan-tang-chaos-knight.png" data-caption="Pan Tang Chaos Knight — Servant of the Lords of Chaos">
|
||||
<div class="gallery-caption">Pan Tang Chaos Knight</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c06-sorcerer.png" data-caption="A Young Kingdoms Sorcerer — Master of Summoning">
|
||||
<div class="gallery-caption">A Young Kingdoms Sorcerer</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c07-tarkeshite-captain.png" data-caption="Tarkeshite Sea Captain — Warrior of the Purple Towns">
|
||||
<div class="gallery-caption">Tarkeshite Sea Captain</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-c08-beggar-of-nadsokor.png" data-caption="A Beggar of Nadsokor — The City of Beggars">
|
||||
<div class="gallery-caption">Beggar of Nadsokor</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Demons</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-d01-demon-weapon.png" data-caption="Demon Weapon — A Bound Enchanted Sword">
|
||||
<div class="gallery-caption">Demon Weapon</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-d02-combat-demon.png" data-caption="Combat Demon — Bound to Fight">
|
||||
<div class="gallery-caption">Combat Demon</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-d03-intellect-demon.png" data-caption="Intellect Demon — Bound to Know">
|
||||
<div class="gallery-caption">Intellect Demon</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-d04-beast-demon.png" data-caption="Beast Demon — A Bound Animal Demon">
|
||||
<div class="gallery-caption">Beast Demon</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-d05-guardian-demon.png" data-caption="Guardian Demon — Set to Ward a Threshold">
|
||||
<div class="gallery-caption">Guardian Demon</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Elementals</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-e01-straasha.png" data-caption="Straasha — Lord of the Water Elementals">
|
||||
<div class="gallery-caption">Straasha — Lord of Water</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-e02-grome.png" data-caption="Grome — Lord of the Earth Elementals">
|
||||
<div class="gallery-caption">Grome — Lord of Earth</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-e03-lassa.png" data-caption="Lassa — Lord of the Air Elementals">
|
||||
<div class="gallery-caption">Lassa — Lord of Air</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-e04-fire-elemental.png" data-caption="A Fire Elemental — Summoned and Bound">
|
||||
<div class="gallery-caption">Fire Elemental</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Creatures</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-cr01-cave-bear.png" data-caption="Cave Bear — Four Times the Size of a Man">
|
||||
<div class="gallery-caption">Cave Bear</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-cr02-tiger-pan-tang.png" data-caption="Tiger of Pan Tang — Ridden Into Battle">
|
||||
<div class="gallery-caption">Tiger of Pan Tang</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-cr03-gorilla.png" data-caption="Gorilla — From the Jungles Beyond the Boiling Sea">
|
||||
<div class="gallery-caption">Gorilla</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-cr04-baboon-troop.png" data-caption="Baboon Troop — Pack Predators of the Steppe">
|
||||
<div class="gallery-caption">Baboon Troop</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-cr05-falcon.png" data-caption="Falcon — The Aerial Hunter">
|
||||
<div class="gallery-caption">Falcon</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-cr06-chaos-creature.png" data-caption="Chaos Creature — Touched Beyond Classification">
|
||||
<div class="gallery-caption">Chaos Creature</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Weapons & Equipment</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-i01-stormbringer-sword.png" data-caption="Stormbringer — The Black Runesword">
|
||||
<div class="gallery-caption">Stormbringer — The Black Runesword</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-i02-broadsword.png" data-caption="Broadsword — The Weapon of a Working Fighter">
|
||||
<div class="gallery-caption">Broadsword</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-i03-lormyrian-axe.png" data-caption="Lormyrian Great Axe — 165cm of Northern Steel">
|
||||
<div class="gallery-caption">Lormyrian Great Axe</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-i04-grimoire.png" data-caption="A Grimoire — Ten Thousand Gold per Spell">
|
||||
<div class="gallery-caption">Grimoire</div>
|
||||
</div>
|
||||
<div class="gallery-item portrait" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-i05-armour.png" data-caption="Full Plate Armour — Equipment of a Wealthy Fighter">
|
||||
<div class="gallery-caption">Full Plate Armour</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-category">
|
||||
<h3>Religion & The Cosmic Balance</h3>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-r01-church-of-law.png" data-caption="The Church of Law — Order Made Architectural">
|
||||
<div class="gallery-caption">Church of Law</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-r02-chaos-cult.png" data-caption="A Chaos Cult — Worshippers of the Lords of Chaos">
|
||||
<div class="gallery-caption">Chaos Cult</div>
|
||||
</div>
|
||||
<div class="gallery-item" onclick="openLightbox(this)">
|
||||
<img src="../../scenarios/art/stormbringer/sb-r03-the-balance.png" data-caption="The Cosmic Balance — Law and Chaos in Equilibrium">
|
||||
<div class="gallery-caption">The Cosmic Balance</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- LIGHTBOX -->
|
||||
<div id="lightbox" onclick="closeLightbox(event)">
|
||||
<button id="lightbox-close" onclick="document.getElementById('lightbox').classList.remove('open')">✕</button>
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
<div id="lightbox-caption"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- end #main -->
|
||||
|
||||
<script>
|
||||
@@ -1363,6 +1644,14 @@ const navData = [
|
||||
{ text: "GM Guidance", id: "gm-guidance" },
|
||||
{ text: "What Magic Solves", id: "gm-guidance" },
|
||||
]},
|
||||
{ title: "Art Gallery", links: [
|
||||
{ text: "Locations", id: "art-gallery" },
|
||||
{ text: "Characters", id: "art-gallery" },
|
||||
{ text: "Demons & Elementals", id: "art-gallery" },
|
||||
{ text: "Creatures", id: "art-gallery" },
|
||||
{ text: "Weapons & Equipment", id: "art-gallery" },
|
||||
{ text: "Religion & The Balance", id: "art-gallery" },
|
||||
]},
|
||||
];
|
||||
|
||||
// ── BUILD NAV ──
|
||||
@@ -1484,6 +1773,23 @@ sections.forEach(s => observer.observe(s));
|
||||
// Auto-open first nav section
|
||||
document.querySelector('.nav-section-title').classList.add('open');
|
||||
document.querySelector('.nav-links').classList.add('open');
|
||||
|
||||
// ── LIGHTBOX ──
|
||||
function openLightbox(item) {
|
||||
const img = item.querySelector('img');
|
||||
document.getElementById('lightbox-img').src = img.src;
|
||||
document.getElementById('lightbox-caption').textContent = img.dataset.caption || '';
|
||||
document.getElementById('lightbox').classList.add('open');
|
||||
}
|
||||
function closeLightbox(e) {
|
||||
if (e.target === document.getElementById('lightbox') || e.target.id === 'lightbox-close') {
|
||||
document.getElementById('lightbox').classList.remove('open');
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') document.getElementById('lightbox').classList.remove('open');
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DALL-E 3 image generator for the Stormbringer MJ bridge queue.
|
||||
|
||||
Reads queued prompts from the mj_bridge queue.jsonl, generates images via
|
||||
DALL-E 3, downloads them into the asset library, and marks jobs as done.
|
||||
|
||||
Usage:
|
||||
python3 scripts/dalle_generate.py --api-key sk-...
|
||||
python3 scripts/dalle_generate.py --api-key sk-... --limit 5
|
||||
python3 scripts/dalle_generate.py --api-key sk-... --quality hd
|
||||
python3 scripts/dalle_generate.py --api-key sk-... --dry-run
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_QUEUE = REPO_ROOT / "output" / "assets" / "midjourney" / "queue.jsonl"
|
||||
DEFAULT_LIBRARY = REPO_ROOT / "output" / "assets" / "midjourney"
|
||||
DEFAULT_PROJECT = "Stormbringer"
|
||||
|
||||
|
||||
# ── Aspect ratio → DALL-E 3 size mapping ─────────────────────────────────────
|
||||
|
||||
AR_TO_SIZE: dict[str, str] = {
|
||||
"1:1": "1024x1024",
|
||||
"2:3": "1024x1792",
|
||||
"3:4": "1024x1792",
|
||||
"9:16": "1024x1792",
|
||||
"16:9": "1792x1024",
|
||||
"3:2": "1792x1024",
|
||||
"2:1": "1792x1024",
|
||||
}
|
||||
DEFAULT_SIZE = "1792x1024"
|
||||
|
||||
|
||||
def extract_ar(params: str) -> str:
|
||||
"""Pull --ar value from a Midjourney param string and return DALL-E size."""
|
||||
match = re.search(r"--ar\s+(\d+:\d+)", params or "")
|
||||
if match:
|
||||
return AR_TO_SIZE.get(match.group(1), DEFAULT_SIZE)
|
||||
return DEFAULT_SIZE
|
||||
|
||||
|
||||
def clean_prompt(prompt: str) -> str:
|
||||
"""Strip Midjourney-specific flags that DALL-E doesn't understand."""
|
||||
cleaned = re.sub(r"--(?:ar|v|style|no|chaos|weird|tile|q|s|seed)\s*\S*", "", prompt)
|
||||
cleaned = re.sub(r"\s{2,}", " ", cleaned).strip().rstrip(",")
|
||||
return cleaned
|
||||
|
||||
|
||||
# ── Queue I/O ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def read_queue(path: Path) -> list[dict]:
|
||||
if not path.exists():
|
||||
return []
|
||||
jobs = []
|
||||
with path.open("r", encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if line:
|
||||
jobs.append(json.loads(line))
|
||||
return jobs
|
||||
|
||||
|
||||
def write_queue(path: Path, jobs: list[dict]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8") as fh:
|
||||
for job in jobs:
|
||||
fh.write(json.dumps(job, ensure_ascii=True, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def append_index(library: Path, metadata: dict) -> None:
|
||||
index = library / "index.jsonl"
|
||||
index.parent.mkdir(parents=True, exist_ok=True)
|
||||
with index.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(metadata, ensure_ascii=True, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
# ── DALL-E 3 API call ─────────────────────────────────────────────────────────
|
||||
|
||||
# Size mapping for gpt-image-2
|
||||
GI2_SIZE_MAP: dict[str, str] = {
|
||||
"1024x1024": "1024x1024",
|
||||
"1024x1792": "1024x1536", # portrait
|
||||
"1792x1024": "1536x1024", # landscape
|
||||
}
|
||||
|
||||
|
||||
def generate_image(
|
||||
api_key: str,
|
||||
prompt: str,
|
||||
size: str = "1792x1024",
|
||||
quality: str = "standard",
|
||||
) -> bytes:
|
||||
"""Call gpt-image-2 and return raw PNG bytes."""
|
||||
# Map legacy DALL-E sizes to gpt-image-2 sizes
|
||||
gi2_size = GI2_SIZE_MAP.get(size, "1536x1024")
|
||||
# Map quality: standard→medium, hd→high
|
||||
gi2_quality = "high" if quality == "hd" else "medium"
|
||||
|
||||
payload = json.dumps({
|
||||
"model": "gpt-image-2",
|
||||
"prompt": prompt,
|
||||
"n": 1,
|
||||
"size": gi2_size,
|
||||
"quality": gi2_quality,
|
||||
"output_format": "png",
|
||||
}).encode("utf-8")
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.openai.com/v1/images/generations",
|
||||
data=payload,
|
||||
headers={
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=180) as resp:
|
||||
body = json.loads(resp.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
error_body = exc.read().decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"OpenAI API error {exc.code}: {error_body}") from exc
|
||||
|
||||
import base64
|
||||
b64 = body["data"][0]["b64_json"]
|
||||
return base64.b64decode(b64)
|
||||
|
||||
|
||||
def download_image(image_bytes: bytes, dest: Path) -> None:
|
||||
"""Write image bytes to dest."""
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_bytes(image_bytes)
|
||||
|
||||
|
||||
# ── Slug / filename helpers ───────────────────────────────────────────────────
|
||||
|
||||
def slugify(value: str, max_len: int = 60) -> str:
|
||||
slug = re.sub(r"[^a-zA-Z0-9]+", "-", value.lower()).strip("-")
|
||||
return slug[:max_len] or "image"
|
||||
|
||||
|
||||
def utc_stamp() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
||||
def compact_stamp() -> str:
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
|
||||
# ── Main generation loop ──────────────────────────────────────────────────────
|
||||
|
||||
def run(args: argparse.Namespace) -> int:
|
||||
jobs = read_queue(args.queue)
|
||||
queued = [j for j in jobs if j.get("status") == "queued"
|
||||
and j.get("project", DEFAULT_PROJECT) == args.project]
|
||||
|
||||
if not queued:
|
||||
print("No queued prompts found for project:", args.project)
|
||||
return 0
|
||||
|
||||
limit = args.limit if args.limit else len(queued)
|
||||
to_process = queued[:limit]
|
||||
|
||||
project_dir = args.library / slugify(args.project)
|
||||
project_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"Generating {len(to_process)} image(s) via DALL-E 3 "
|
||||
f"[quality={args.quality}]")
|
||||
print(f"Output → {project_dir}\n")
|
||||
|
||||
job_index = {j["id"]: j for j in jobs}
|
||||
generated = 0
|
||||
failed = 0
|
||||
|
||||
for job in to_process:
|
||||
notes = job.get("notes", "")
|
||||
raw_prompt = job.get("prompt", "")
|
||||
params = job.get("midjourney_params", "")
|
||||
size = extract_ar(params)
|
||||
prompt = clean_prompt(raw_prompt)
|
||||
|
||||
label = notes or job["id"]
|
||||
print(f"[{generated + 1}/{len(to_process)}] {label}")
|
||||
print(f" size={size} prompt={prompt[:80]}…")
|
||||
|
||||
if args.dry_run:
|
||||
print(" [dry-run — skipped]\n")
|
||||
continue
|
||||
|
||||
try:
|
||||
image_bytes = generate_image(args.api_key, prompt, size=size, quality=args.quality)
|
||||
|
||||
filename = f"{compact_stamp()}_{slugify(notes or job['id'], 50)}.png"
|
||||
dest = project_dir / filename
|
||||
download_image(image_bytes, dest)
|
||||
|
||||
sidecar = {
|
||||
"id": f"asset_{dest.stem}",
|
||||
"generated_at": utc_stamp(),
|
||||
"prompt_id": job["id"],
|
||||
"prompt": prompt,
|
||||
"raw_mj_prompt": raw_prompt,
|
||||
"asset_path": str(dest),
|
||||
"project": args.project,
|
||||
"notes": notes,
|
||||
"model": "gpt-image-2",
|
||||
"size": size,
|
||||
"quality": args.quality,
|
||||
|
||||
}
|
||||
sidecar_path = dest.with_suffix(".json")
|
||||
sidecar_path.write_text(
|
||||
json.dumps(sidecar, indent=2, ensure_ascii=True, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
append_index(args.library, sidecar)
|
||||
|
||||
job_index[job["id"]]["status"] = "done"
|
||||
job_index[job["id"]]["asset_path"] = str(dest)
|
||||
job_index[job["id"]]["generated_at"] = utc_stamp()
|
||||
|
||||
print(f" ✓ saved → {dest.name}\n")
|
||||
generated += 1
|
||||
|
||||
except Exception as exc:
|
||||
print(f" ✗ FAILED: {exc}\n", file=sys.stderr)
|
||||
job_index[job["id"]]["status"] = "error"
|
||||
job_index[job["id"]]["error"] = str(exc)
|
||||
failed += 1
|
||||
|
||||
# Brief pause to stay inside OpenAI rate limits
|
||||
if generated < len(to_process):
|
||||
time.sleep(args.delay)
|
||||
|
||||
write_queue(args.queue, list(job_index.values()))
|
||||
|
||||
print(f"Done — {generated} generated, {failed} failed.")
|
||||
if failed:
|
||||
print("Failed jobs have status='error' in the queue. Re-run to retry.")
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
# ── CLI ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Generate queued Stormbringer prompts via DALL-E 3."
|
||||
)
|
||||
p.add_argument(
|
||||
"--api-key",
|
||||
default=os.environ.get("OPENAI_API_KEY", ""),
|
||||
help="OpenAI API key (or set OPENAI_API_KEY env var).",
|
||||
)
|
||||
p.add_argument("--queue", type=Path, default=DEFAULT_QUEUE)
|
||||
p.add_argument("--library", type=Path, default=DEFAULT_LIBRARY)
|
||||
p.add_argument("--project", default=DEFAULT_PROJECT)
|
||||
p.add_argument("--quality", choices=["standard", "hd"], default="hd",
|
||||
help="DALL-E 3 quality tier (default: hd).")
|
||||
p.add_argument("--limit", type=int, default=0,
|
||||
help="Max images to generate (0 = all queued).")
|
||||
p.add_argument("--delay", type=float, default=3.0,
|
||||
help="Seconds between API calls (default: 3).")
|
||||
p.add_argument("--dry-run", action="store_true",
|
||||
help="Print what would be generated without calling the API.")
|
||||
return p
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
if not args.api_key and not args.dry_run:
|
||||
print("Error: provide --api-key or set OPENAI_API_KEY.", file=sys.stderr)
|
||||
return 2
|
||||
return run(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
@@ -0,0 +1,487 @@
|
||||
# Midjourney Prompts — Stormbringer RPG (French Oriflam 1984 / Young Kingdoms)
|
||||
|
||||
Dark sword and sorcery. Michael Moorcock's Young Kingdoms: a world of decaying empire, ancient pacts with demons, and the eternal struggle of Law versus Chaos. Art direction: 1970s–80s fantasy illustration — Michael Whelan, Chris Achilleos, John Blanche. Rich jewel tones bleeding into deep shadow. Ornate detail. Decay under beauty.
|
||||
|
||||
**Global style anchor (add to any prompt):**
|
||||
`Stormbringer RPG Young Kingdoms dark fantasy illustration, 1970s sword and sorcery art, rich jewel tones and deep shadow, decadent ancient empire, Michael Whelan style painterly, occult atmosphere`
|
||||
|
||||
---
|
||||
|
||||
## LOCATIONS
|
||||
|
||||
**SB-L01 — Melniboné / Imrryr, the Dreaming City (Ruined)**
|
||||
```
|
||||
The Dreaming City of Imrryr Melniboné in ruins, Dragon Isle, vast towers of
|
||||
iridescent dragon jade and bone-white stone crumbling into the sea, a decadent
|
||||
empire ten thousand years old in its final decay, impossible architecture, the
|
||||
pink dawn light on shattered spires, ruins that were once more beautiful than
|
||||
anything the Young Kingdoms ever built, distant dragon shapes in smoky sky,
|
||||
Stormbringer Young Kingdoms dark fantasy illustration, Michael Whelan painterly
|
||||
style, jewel tones and ruin, 1970s sword and sorcery atmosphere
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L02 — Karlaak by the Weeping Waste**
|
||||
```
|
||||
Karlaak city on the edge of the Weeping Waste, a city of white-walled merchant
|
||||
towers and hanging gardens bordering a vast grey desolate plain, the contrast
|
||||
between the city's warm light and the cold wasted land beyond, the most cosmopolitan
|
||||
city of the Young Kingdoms, caravans on the road, a young albino lord visible at
|
||||
the gate, Stormbringer Young Kingdoms fantasy illustration, jewel tones against
|
||||
pale desert, 1970s sword and sorcery painterly
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L03 — Pan Tang, Isle of the Sorcerer Kings**
|
||||
```
|
||||
Pan Tang island fortress city, black volcanic rock and bone-white towers, a theocracy
|
||||
of Chaos worshippers, tiger-riders on the battlements, demon-bound architecture
|
||||
that writhes slightly in the eye's peripheral vision, the sea around it stained
|
||||
dark, smoke from the eternal ritual fires, Stormbringer Young Kingdoms dark fantasy
|
||||
illustration, sinister beauty, 1970s sword and sorcery
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L04 — Nadsokor, the City of Beggars**
|
||||
```
|
||||
Nadsokor the City of Beggars, a vast ruin-city inhabited entirely by the outcast
|
||||
and diseased, crumbling towers packed with the desperate, open fires in the streets,
|
||||
a mad beggar king on a throne of refuse, the smell of disease and desperation
|
||||
implied in every surface, one of the Young Kingdoms' most wretched places,
|
||||
Stormbringer dark fantasy illustration, 1970s sword and sorcery, muted palette
|
||||
of ash and rust
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L05 — The Young Kingdoms Map Illustration**
|
||||
```
|
||||
Fantasy map of the Young Kingdoms, cartographic illustration in the style of a
|
||||
1970s fantasy sourcebook, showing the continents including Melniboné the Dragon
|
||||
Isle, the Young Kingdoms mainlands, the Boiling Sea, the Desert of Sighs the
|
||||
Desert of Tears, Pan Tang, compass rose, sea monsters in the margins, aged
|
||||
parchment texture, hand-lettered place names, Stormbringer RPG sourcebook art
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L06 — The Boiling Sea**
|
||||
```
|
||||
The Boiling Sea, a vast ocean of superheated water with scalding mist rising in
|
||||
columns, strange shapes visible below the surface, a small merchant vessel pushing
|
||||
through the steam, the impossibility of the geography made physical, Young Kingdoms
|
||||
dark fantasy illustration, 1970s sword and sorcery painterly, steam and deep colour
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L07 — A Young Kingdoms Tavern**
|
||||
```
|
||||
Interior of a Young Kingdoms tavern in a port city, rough-hewn tables, torchlight,
|
||||
a mix of nationalities from across the continent, a Melnibonéan sitting alone in
|
||||
the corner with silver-white hair and crimson eyes, bronze coins on the bar, a
|
||||
merchant arguing with a Tarkeshite sailor, swords visible, atmosphere of careful
|
||||
neutrality between people who could kill each other, 1970s fantasy illustration,
|
||||
warm amber and shadow
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-L08 — The Demon Realm (Summoning Scene)**
|
||||
```
|
||||
A chaotic extradimensional space glimpsed through a sorcerer's summoning circle,
|
||||
swirling impossible geometry, a vast demon partially manifest through the threshold,
|
||||
the sorcerer on one side holding the binding, the demon's true form visible on
|
||||
the other, the membrane between worlds translucent and straining, Stormbringer
|
||||
dark fantasy illustration, 1970s sword and sorcery, occult atmosphere, jewel tones
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ICONIC CHARACTERS
|
||||
|
||||
**SB-C01 — Elric of Melniboné**
|
||||
```
|
||||
Portrait of Elric of Melniboné, albino emperor, silver-white hair, crimson eyes,
|
||||
cadaverously thin, the last emperor of a ten-thousand-year dynasty, wearing black
|
||||
armour with the rune blade Stormbringer at his side, the sword pulsing with dark
|
||||
energy, the expression of a man who knows what the sword costs and carries it
|
||||
anyway, Young Kingdoms dark fantasy portrait, 1970s sword and sorcery illustration,
|
||||
Michael Whelan painterly, jewel tones, regal and tragic
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C02 — Elric with Stormbringer (Action)**
|
||||
```
|
||||
Elric of Melniboné in battle, the black runesword Stormbringer raised, soul-energy
|
||||
visibly flowing from a fallen enemy into the blade and through it into the albino
|
||||
warrior, crimson eyes blazing, silver hair wild, the sword sentient and hungry,
|
||||
the terrible cost of this symbiosis written in Elric's expression, Young Kingdoms
|
||||
dark fantasy illustration, 1970s sword and sorcery, dramatic composition
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C03 — Moonglum of Elwher**
|
||||
```
|
||||
Portrait of Moonglum of Elwher, small wiry man with a wide cheerful grin that
|
||||
hides considerable competence, Elric's only true friend and most loyal companion,
|
||||
eastern Elwher features, colourful practical clothing, two curved blades at his
|
||||
belt, the expression of someone who chose to stay beside the most dangerous man
|
||||
in the world and has never regretted it, Young Kingdoms fantasy portrait,
|
||||
1970s illustration, warm palette contrasting Elric's cold tones
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C04 — A Melnibonéan Noble**
|
||||
```
|
||||
Portrait of a Melnibonéan noble, aloof and ancient-looking despite apparent youth,
|
||||
silver-white hair, elongated slightly inhuman features, elaborate armour of dragon
|
||||
scale and bone-white metal, the casual cruelty of someone from a culture ten
|
||||
thousand years past ordinary morality, a dragon visible behind them at distance,
|
||||
Young Kingdoms dark fantasy portrait, 1970s sword and sorcery, cold jewel tones
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C05 — A Pan Tang Chaos Knight**
|
||||
```
|
||||
Portrait of a Pan Tang Chaos knight, warrior of the Theocracy of Chaos, black
|
||||
armour bearing the eight-pointed star of Chaos, a broadsword in hand, features
|
||||
half-hidden by a visored helm with daemon decorations, the bearing of a soldier
|
||||
who has made pacts the listener cannot imagine, Young Kingdoms dark fantasy portrait,
|
||||
1970s sword and sorcery illustration, dark palette
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C06 — A Young Kingdoms Sorcerer**
|
||||
```
|
||||
Portrait of a Young Kingdoms sorcerer mid-ritual, summoning circle glowing on
|
||||
the floor, the air distorting around an elemental presence being called forth,
|
||||
the sorcerer's expression of controlled terror, the power enormous and the binding
|
||||
fragile, elaborate ritual robes, a grimoire open beside them, the cost of sorcery
|
||||
visible in gauntness and the haunted quality of the eyes, Young Kingdoms dark
|
||||
fantasy portrait, 1970s sword and sorcery
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C07 — A Tarkeshite Merchant Captain**
|
||||
```
|
||||
Portrait of a Tarkeshite sea merchant captain, weathered brown skin, pragmatic
|
||||
practical clothing, the calculating expression of someone who has traded across
|
||||
every coast of the Young Kingdoms and survived by understanding people, a manifest
|
||||
at hand, a sword worn habitually not dramatically, Young Kingdoms fantasy portrait,
|
||||
warm sea-light palette, 1970s illustration
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-C08 — A Beggar of Nadsokor**
|
||||
```
|
||||
Portrait of a beggar from Nadsokor the City of Beggars, one of the outcast masses,
|
||||
rags and disease and surprising cunning in the eyes, surviving on the margins of
|
||||
a world that has no place for them, the particular dignity of someone who has
|
||||
nothing left to lose, Young Kingdoms dark fantasy portrait, muted palette, 1970s
|
||||
sword and sorcery illustration
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DEMONS
|
||||
|
||||
**SB-D01 — Demon Weapon (Bound Sword)**
|
||||
```
|
||||
A demon weapon, a broadsword bound with a lesser demon, the blade alive with dark
|
||||
energy, runes along the flat that shift as the eye tries to read them, the hilt
|
||||
warm and slightly pulsing, the weapon sentient in a limited way that makes its
|
||||
wielder uneasy, a bound will in iron, Young Kingdoms dark fantasy illustration,
|
||||
1970s sword and sorcery, occult metalwork detail
|
||||
--ar 1:1 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-D02 — Combat Demon (Manifest)**
|
||||
```
|
||||
A combat demon summoned and manifested in the Young Kingdoms, enormous humanoid
|
||||
form of living shadow and killing intent, STR and SIZ at the peak of what the
|
||||
binding allows, claws that can sever steel, bound but barely, the summoner's
|
||||
circle glowing dangerously at its feet, Young Kingdoms dark fantasy illustration,
|
||||
1970s sword and sorcery, demonic power barely contained
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-D03 — Intellect Demon**
|
||||
```
|
||||
An intellect demon in partial manifestation, a shape of flickering wrongness
|
||||
at the edge of perception, intelligence and alien knowledge visible in the geometry
|
||||
of its presence rather than a face, the summoner speaking with it carefully,
|
||||
every word measured, the demon offering information at a price, Young Kingdoms
|
||||
dark fantasy illustration, 1970s occult atmosphere, implied form not shown directly
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-D04 — Beast Demon**
|
||||
```
|
||||
A beast demon manifested, a vast animal form of impossible biology, parts of
|
||||
several predators fused into something that was never natural, bound by sorcerous
|
||||
chains of glowing rune-script, serving as mount or war-beast unwillingly, Young
|
||||
Kingdoms dark fantasy illustration, 1970s sword and sorcery, biological wrongness
|
||||
and power
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-D05 — Guardian Demon (Threshold)**
|
||||
```
|
||||
A guardian demon at a threshold, a sorcerer's tower entrance bound with a demon
|
||||
set to guard, a presence implied more than shown, the distortion of space around
|
||||
a door that is very definitely not safe to open uninvited, the demon visible as
|
||||
a wrongness in the geometry of the doorframe, Young Kingdoms dark fantasy
|
||||
illustration, 1970s sword and sorcery, dread
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ELEMENTALS
|
||||
|
||||
**SB-E01 — Straasha, Lord of the Water Elementals**
|
||||
```
|
||||
Straasha Lord of the Water Elementals, a vast sea-being of living ocean, humanoid
|
||||
in outline but composed entirely of deep water, cresting waves, abyssal darkness
|
||||
visible within, the face ancient and oceanic, summoned by an albino emperor on
|
||||
the deck of a ship at storm, Young Kingdoms dark fantasy illustration, 1970s sword
|
||||
and sorcery, deep blue-green and black, enormous scale
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-E02 — Grome, Lord of the Earth Elementals**
|
||||
```
|
||||
Grome Lord of the Earth Elementals, a vast being of living rock and deep soil,
|
||||
humanoid in suggestion but geological in scale, the face of ancient mountains,
|
||||
grudging power summoned against his will from the depths of the world, the Young
|
||||
Kingdoms shaking slightly where he stands, Young Kingdoms dark fantasy illustration,
|
||||
1970s sword and sorcery, grey-brown and iron-red, enormous and reluctant
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-E03 — Lassa, Lord of the Air Elementals**
|
||||
```
|
||||
Lassa Lord of the Air Elementals, a presence of wind and sky barely made visible,
|
||||
a face in storm clouds and high altitude, summoned over a high mountain pass,
|
||||
the character barely able to perceive the entity at all, Young Kingdoms dark fantasy
|
||||
illustration, 1970s sword and sorcery, pale blue and silver-white, implied rather
|
||||
than described
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-E04 — A Fire Elemental**
|
||||
```
|
||||
A fire elemental of the Young Kingdoms summoned and bound, living flame given
|
||||
will and form, not a formless fire but an entity with awareness and pride, restrained
|
||||
within a binding circle that glows white at the edges where the heat presses,
|
||||
the summoner sweating at a careful distance, Young Kingdoms dark fantasy illustration,
|
||||
1970s sword and sorcery, gold and red and deep shadow
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CREATURES
|
||||
|
||||
**SB-CR01 — Cave Bear**
|
||||
```
|
||||
A cave bear of the Young Kingdoms, enormous beyond any natural bear, four times
|
||||
the size of a man, STR and SIZ at the upper reaches of anything terrestrial,
|
||||
surprisingly quick for its bulk, in the remote Silent Lands or Desert of Sighs,
|
||||
facing down a group of travellers with calm predatory assessment, Young Kingdoms
|
||||
dark fantasy creature illustration, 1970s sword and sorcery, massive and real
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-CR02 — Tiger (Pan Tang)**
|
||||
```
|
||||
A tiger of Pan Tang, the totem animal of Chardros the Reaper, serving as a battle
|
||||
companion to a Pan Tang warrior, larger than any natural tiger, ridden into battle,
|
||||
the particular intelligence of an animal used in war by a theocracy of Chaos
|
||||
worshippers, Young Kingdoms dark fantasy creature illustration, 1970s sword and
|
||||
sorcery, rich orange and black on dark backgrounds
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-CR03 — Gorilla (Jungle of the Boiling Sea)**
|
||||
```
|
||||
A gorilla from the forbidden jungles of the far southwest beyond the Boiling Sea,
|
||||
massive, not specifically dangerous but capable of lethal force, the kind of animal
|
||||
that ends up in the menageries of southern continent lords, a figure below for
|
||||
scale, tropical jungle setting, Young Kingdoms creature illustration, 1970s sword
|
||||
and sorcery naturalistic
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-CR04 — Baboon Troop**
|
||||
```
|
||||
A troop of fifteen to twenty baboons on the semi-arid steppes south of Shazaar,
|
||||
aggressive and coordinated, attacking something larger than themselves, the
|
||||
particular threat of pack predators that regularly take on great cats, Young
|
||||
Kingdoms creature illustration, 1970s sword and sorcery naturalistic, dry steppe
|
||||
landscape
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-CR05 — Falcon (Trained)**
|
||||
```
|
||||
A falcon of the Young Kingdoms in a stoop from height, wings folded, talons
|
||||
extended toward a target, the hunting dive of the most lethal aerial predator
|
||||
in the book, kept by a noble for hunting or trained for war, Young Kingdoms
|
||||
creature illustration, 1970s fantasy naturalistic, sky and speed
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-CR06 — A Chaos Creature**
|
||||
```
|
||||
A chaos creature of the Young Kingdoms, something that was once an animal but
|
||||
has been touched by Chaos until it is no longer classifiable, additional limbs
|
||||
that bend incorrectly, fur that shifts colour, eyes in wrong locations, not
|
||||
monstrous in the traditional sense but fundamentally and deeply wrong, Young
|
||||
Kingdoms dark fantasy creature illustration, 1970s sword and sorcery, Chaos
|
||||
corruption
|
||||
--ar 3:2 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WEAPONS AND ITEMS
|
||||
|
||||
**SB-I01 — Stormbringer (The Black Runesword)**
|
||||
```
|
||||
The black runesword Stormbringer, a blade of impossible blackness that seems to
|
||||
devour light, runes along the flat that move when unobserved, the hilt screaming
|
||||
faintly when drawn, the weapon that has drunk ten thousand souls and is never
|
||||
satisfied, a soul-eating entity in sword form, close-up dark fantasy illustration,
|
||||
1970s sword and sorcery, deep black and bleeding soul-light, no wielder, just
|
||||
the sword
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-I02 — A Young Kingdoms Broadsword**
|
||||
```
|
||||
A broadsword of the Young Kingdoms, 100cm of honest steel, the weapon of a
|
||||
professional fighter of moderate means, no sorcery just craft, worn grip and a
|
||||
nick from the last serious fight, against a map of the Young Kingdoms on a tavern
|
||||
table, Young Kingdoms fantasy still life illustration, 1970s sword and sorcery,
|
||||
warm lamplight
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-I03 — A Lormyrian Great Axe**
|
||||
```
|
||||
A Lormyrian great axe, two-handed, 165cm, the heaviest weapon in the Young
|
||||
Kingdoms at 3D6 damage, ornate Lormyrian decoration on the head, clearly requiring
|
||||
exceptional strength to wield, a weapon from a culture of large northerners,
|
||||
Young Kingdoms fantasy weapons illustration, 1970s sword and sorcery, northern
|
||||
iron aesthetic
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-I04 — A Grimoire of the Young Kingdoms**
|
||||
```
|
||||
A grimoire of the Young Kingdoms, a book containing spells at ten thousand gold
|
||||
coins per spell contained, bound in materials that are difficult to identify,
|
||||
the pages feeling wrong to the touch, the ink shifting slightly in poor light,
|
||||
lying on a sorcerer's lectern, the most expensive object in the scenario by some
|
||||
margin, Young Kingdoms dark fantasy still life, 1970s occult illustration
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-I05 — Young Kingdoms Armour (Plate)**
|
||||
```
|
||||
Full plate armour of the Young Kingdoms, the equipment of a wealthy professional
|
||||
fighter, each 1AP through 8AP represented in the layering visible, practical and
|
||||
maintained, beside it a broadsword and a large shield, the equipment of someone
|
||||
who intends to survive, Young Kingdoms fantasy still life, 1970s sword and sorcery
|
||||
equipment illustration, warm forge-light
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-I06 — Coin Purse with Young Kingdoms Currency**
|
||||
```
|
||||
A leather coin purse spilling bronze coins on a tavern table, the large bronze
|
||||
coins called Grande Bronzines and Petite Bronzines, one rare silver coin visible
|
||||
among them, the economies of the Young Kingdoms made physical, a price list
|
||||
fragment visible on a scrap of parchment beside them, Young Kingdoms fantasy
|
||||
still life, 1970s illustration, warm candlelight
|
||||
--ar 1:1 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CULTS AND RELIGION
|
||||
|
||||
**SB-R01 — A Church of Law Ceremony**
|
||||
```
|
||||
A ceremony in the Church of Law in the Young Kingdoms, white-robed priests in
|
||||
a geometrically perfect temple, the ordered beauty of Law made architectural,
|
||||
rituals of precision and structure, the lords of Law distant and demanding,
|
||||
worshippers arranged in perfect symmetry, Young Kingdoms dark fantasy illustration,
|
||||
1970s sword and sorcery, white and gold and cold perfection
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-R02 — A Chaos Cult Ritual**
|
||||
```
|
||||
A Chaos cult ritual in the Young Kingdoms, worshippers of the lords of Chaos
|
||||
making sacrifice, the eight-pointed star of Chaos burning in the air above,
|
||||
reality bending slightly at the edges of the ritual space, the exhilaration and
|
||||
terror of asking something vast and hungry for a favour, Young Kingdoms dark
|
||||
fantasy illustration, 1970s sword and sorcery, deep reds and violets
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-R03 — The Balance (Neutral Cosmic Force)**
|
||||
```
|
||||
An abstract representation of the Cosmic Balance in the Young Kingdoms, a vast
|
||||
set of scales implied in the architecture of reality itself, the forces of Law
|
||||
and Chaos visible as cold white and burning red on either side, a single figure
|
||||
standing at the fulcrum, the Champion Eternal, Young Kingdoms dark fantasy
|
||||
illustration, 1970s sword and sorcery, cosmic scale
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SCENARIO ATMOSPHERE
|
||||
|
||||
**SB-A01 — Character Creation (Social Origins)**
|
||||
```
|
||||
A street scene in a Young Kingdoms city showing the social spectrum: a noble in
|
||||
finery, a merchant with bronze coins, a sailor with a port behind them, a beggar
|
||||
in rags, a soldier in practical armour, all passing each other in the same
|
||||
marketplace, the social origins of Stormbringer RPG made visual, Young Kingdoms
|
||||
fantasy illustration, 1970s sword and sorcery, warm city light
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-A02 — The Resistance Table (Abstract)**
|
||||
```
|
||||
Two figures locked in opposed contest of will, their strength and will represented
|
||||
as visible energies meeting at a central point, the BRP resistance table made
|
||||
visible as a struggle between active and passive forces, abstracted but grounded
|
||||
in physical reality, Young Kingdoms dark fantasy illustration, 1970s sword and
|
||||
sorcery
|
||||
--ar 16:9 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-A03 — A Major Wound**
|
||||
```
|
||||
A warrior in the aftermath of a major wound, the wound clearly severe at or
|
||||
above half total hit points, the look of someone who is still standing through
|
||||
sheer will, DEX halved until healing, the cost of combat in the Young Kingdoms
|
||||
made physical, Young Kingdoms dark fantasy illustration, 1970s sword and sorcery,
|
||||
gritty and real
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||
**SB-A04 — Sanity Loss**
|
||||
```
|
||||
A Young Kingdoms character experiencing SAN loss from a supernatural encounter,
|
||||
the crack in the façade of rationality, something witnessed that cannot be
|
||||
un-witnessed, the expression of a person whose model of the world has just been
|
||||
broken in a specific place, Young Kingdoms dark fantasy illustration, 1970s
|
||||
sword and sorcery, psychological horror through expression
|
||||
--ar 2:3 --v 6.1 --style raw
|
||||
```
|
||||
|
||||