Files
neural-hunt/internal/artifact/collection.go
groot 47c523dd98
All checks were successful
release-tag / release-image (push) Successful in 3m51s
RC-14
2026-08-14 06:17:30 +02:00

525 lines
24 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package artifact
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"strings"
)
const collectionPresetRaccoon = "raccoon_full_art_v1"
const characterAnchorPrompt = `Create the canonical character reference for an original collectible-art series.
CANONICAL CHARACTER — LOCK THIS IDENTITY:
Create one original anthropomorphic silver-gray raccoon named RIFT.
Permanent visual features:
- charcoal-black raccoon eye mask with a clean, recognizable shape
- creamy white muzzle, cheeks and chest
- large expressive amber-gold eyes
- small rounded ears; one tiny notch in the RIGHT ear
- short broad muzzle and a subtle confident half-smile
- oversized head with compact athletic humanoid proportions
- thick striped raccoon tail with exactly five dark rings
- soft sculpted stylized fur clumps, never photographic fur
- friendly, clever, confident personality
WARDROBE FOR THE ANCHOR ONLY:
A simple charcoal utility overshirt with no logos, no writing and no theme-specific accessories. Keep the outfit neutral so future images can replace it completely.
COMPOSITION:
Vertical 3/4 character portrait, full torso and most of the tail visible, neutral elegant studio background, clean readable silhouette, centered, no props obscuring the face or tail.
STYLE:
Neutral high-end stylized 3D character illustration, deliberately non-photorealistic, premium animated-feature quality, rounded shapes, rich material detail, cinematic soft key light plus rim light, subtle depth of field, controlled color, polished collectible character reference. Keep the rendering neutral enough that future task-specific style references can deliberately restyle RIFT without changing his identity.
STRICT:
Exactly one raccoon character. No human face. No tiger features. No dreadlocks. No text, numbers, logo, trademark, watermark, card border, UI, duplicate character, photorealistic wildlife or realistic human anatomy.`
type themeProfile struct {
Name string
Outfit string
Accessories string
Scene string
Mood string
}
var collectionThemes = []themeProfile{
{
Name: "Neon Courier",
Outfit: "oversized midnight technical bomber, black utility vest with reflective piping, loose futuristic cargo trousers, high-top sneakers and fingerless gloves",
Accessories: "compact cross-body courier satchel and a translucent visor pushed above the eyes",
Scene: "rain-slick future city alley, glowing shop fronts, cables overhead, thin steam and floating light particles",
Mood: "energetic, adventurous and cool; electric night light with warm facial highlights",
},
{
Name: "Sakura Ronin",
Outfit: "layered modern ronin jacket, asymmetrical sash, armored fabric panels, tapered trousers and split-toe boots",
Accessories: "ornamental sheathed short sword worn as a costume prop, braided cord and a small lacquer charm",
Scene: "windy hill path beneath flowering cherry trees, distant lanterns and drifting petals at blue hour",
Mood: "calm, disciplined and cinematic with warm lantern rim light",
},
{
Name: "Lunar Botanist",
Outfit: "cream exploration suit with padded joints, soft graphite harness, compact boots and botanical specimen pockets",
Accessories: "clear bubble helmet carried under one arm and a glowing seed capsule",
Scene: "moon greenhouse with alien plants, curved windows, pale regolith outside and tiny floating pollen motes",
Mood: "curious, serene and optimistic with silver-blue light and soft green bioluminescence",
},
{
Name: "Apex Racer",
Outfit: "sleek original motorsport suit with angular panels, quilted shoulders, racing gloves and lightweight boots; absolutely no real-world team branding",
Accessories: "custom unbranded racing helmet tucked at the hip and a small timing wrist display",
Scene: "futuristic pit lane after sunset, abstract light streaks, glossy track surface and atmospheric haze",
Mood: "focused, fast and triumphant with hard edge lighting and motion energy",
},
{
Name: "Sky Captain",
Outfit: "tailored deep-green flight coat, brass-toned fasteners, layered leather-like harness, high boots and a long scarf",
Accessories: "round aviator goggles on the forehead, miniature compass and folded flight chart",
Scene: "fantasy airship observation deck above glowing clouds, propeller silhouettes and warm sunrise",
Mood: "adventurous, noble and windswept with golden backlight",
},
{
Name: "Deep Salvager",
Outfit: "stylized pressure-diver jacket, segmented shoulder guards, flexible utility trousers and heavy magnetic boots",
Accessories: "compact breathing collar, antique-looking lamp and a small salvage pouch",
Scene: "underwater ruin with rays of blue light, bubbles, coral growth, old machinery and drifting particles",
Mood: "mysterious, brave and exploratory with teal caustics and warm lamp glow",
},
{
Name: "Rooftop Skater",
Outfit: "oversized graphic-free hoodie under a cropped windbreaker, wide technical pants, knee pads and chunky skate shoes",
Accessories: "original blank skateboard with abstract geometric deck art and wireless headphones around the neck",
Scene: "sunset rooftop skate spot, concrete shapes, distant skyline, spray mist and long shadows",
Mood: "playful, rebellious and relaxed with saturated sunset gradients",
},
{
Name: "Arcane Archivist",
Outfit: "layered indigo scholar robe, structured vest, embroidered star-map patterns without readable glyphs, soft boots and fingerless reading gloves",
Accessories: "floating crystal lens, chained key and a closed oversized book with no text on the cover",
Scene: "towering fantasy archive, suspended shelves, luminous dust and circular windows showing a starry sky",
Mood: "wise, curious and magical with violet-gold volumetric light",
},
{
Name: "Desert Nomad",
Outfit: "sand-colored hooded field jacket, layered scarf, reinforced trousers, wrapped boots and lightweight shoulder guards",
Accessories: "goggles hanging at the collar, water canteen and a compact navigation device",
Scene: "vast stylized dunes around a half-buried futuristic relay station, windblown dust and distant heat shimmer",
Mood: "resilient, solitary and heroic with warm amber sunlight and cool shadow fill",
},
{
Name: "Harbor Corsair",
Outfit: "navy captain coat with exaggerated collar, striped sash, fitted waistcoat, weathered trousers and tall deck boots",
Accessories: "ornamental brass spyglass, rope bracelet and a broad captain hat with no insignia",
Scene: "fantasy harbor at dawn, stylized sailing vessel, gull silhouettes, rolling mist and bright spray",
Mood: "mischievous, bold and charismatic with crisp sea light",
},
{
Name: "Alpine Rider",
Outfit: "bright technical snow jacket, insulated bib trousers, soft neck gaiter, mittens and oversized snowboard boots",
Accessories: "reflective snow goggles lifted above the eyes and a custom blank snowboard with geometric patterning",
Scene: "high alpine ridge after fresh snowfall, powder plume, distant peaks and sparkling ice crystals",
Mood: "joyful, fearless and fresh with clean winter sunlight and blue shadows",
},
{
Name: "Noir Detective",
Outfit: "charcoal trench coat over a fitted waistcoat and shirt, narrow tie, tailored trousers and polished ankle boots",
Accessories: "soft-brim hat, small notebook with blank pages and an old-fashioned camera",
Scene: "stylized rainy midnight street, glowing windows, puddle reflections, fog and dramatic architectural shadows",
Mood: "clever, suspicious and composed with high-contrast noir lighting plus a single warm practical light",
},
{
Name: "Jungle Soundmaster",
Outfit: "bold woven-pattern jacket, relaxed dark trousers, colorful knit cap and layered fabric wristbands; original patterns only",
Accessories: "large studio headphones, portable sampler with abstract unlabelled controls and bead accents",
Scene: "lush tropical night stage tucked into giant leaves, warm string lights, mist and floating firefly-like particles",
Mood: "musical, warm and charismatic with saturated green, gold and red accents",
},
{
Name: "Solar Guardian",
Outfit: "ceremonial cream-and-gold mantle over fitted plated fabric armor, broad belt, tapered trousers and soft armored boots",
Accessories: "floating sun-disc ornament, geometric shoulder charms and a staff-shaped decorative prop",
Scene: "monumental desert temple terrace, shafts of sunlight, drifting sand and abstract solar motifs",
Mood: "regal, radiant and protective with intense gold rim light and deep cyan shadows",
},
{
Name: "Retro Astronaut",
Outfit: "chunky retro-future space suit with rounded padding, orange utility tabs, ribbed joints and oversized moon boots",
Accessories: "dome helmet carried at the side, analog-style wrist module with no readable numbers and tether clips",
Scene: "colorful retro orbital station window, star field, distant planet and floating equipment shapes",
Mood: "wonder-filled, optimistic and iconic with soft cosmic blues and warm cabin light",
},
{
Name: "Storm Mechanic",
Outfit: "rolled-sleeve heavy work jacket, dark overalls, reinforced gloves, tool belt and rugged lace-up boots",
Accessories: "large wrench-shaped prop, protective goggles on the head and a compact diagnostic tablet with abstract graphics",
Scene: "open-air repair platform during an approaching electrical storm, turbine parts, sparks and rain mist",
Mood: "resourceful, gritty and confident with cool storm light and warm sparks",
},
}
var poseOptions = []string{
"standing in a relaxed three-quarter stance with one shoulder slightly forward",
"mid-step with a confident turn toward camera",
"leaning casually against a thematic prop while keeping the full facial mask visible",
"slightly crouched in an energetic ready pose with the tail creating a strong S-curve",
"standing upright with one hand adjusting an accessory and the other relaxed",
"heroic low-angle three-quarter pose, expressive but not aggressive",
}
var atmosphereOptions = []string{
"subtle floating particles and soft volumetric haze",
"small foreground elements crossing the lower frame for depth",
"gentle wind motion in fabric and fur",
"bright rim-light bloom plus restrained lens glow",
"layered foreground bokeh and crisp facial focus",
"tiny environmental sparks or motes that echo the outfit materials",
}
var cameraOptions = []string{
"heroic low-angle framing with strong foreground-to-background depth",
"clean eye-level framing with a crisp, premium editorial feel",
"slightly top-down cinematic framing that reveals more environment around the character",
"dynamic three-quarter camera angle with the body turned and the face still clearly readable",
"wide-lens action framing with bold perspective exaggeration but no distortion of the face",
"portrait-style framing with intimate character presence and rich environmental layering",
}
var paletteStoryOptions = []string{
"blue-gold contrast with cool shadows and warm metallic highlights",
"magenta-cyan night energy with bright accent reflections",
"amber-teal cinematic contrast with soft glow in the midtones",
"emerald-violet contrast with luminous accent edges",
"sunset orange against deep indigo with polished highlight pops",
"crimson-gold energy balanced by dark graphite neutrals",
}
var sceneFocusOptions = []string{
"emphasize strong environmental architecture and depth lines behind the character",
"emphasize floating particles, atmospheric layers and soft depth transitions around the silhouette",
"emphasize prop interaction and costume detail storytelling in the hands and upper body",
"emphasize motion energy, directional streaks and a clear sense of momentum",
"emphasize dramatic lighting shapes and distinct foreground framing elements",
"emphasize a bold silhouette read with a simpler but striking background rhythm",
}
type collectionTraits struct {
ThemeName string `json:"theme"`
Outfit string `json:"outfit"`
Accessories string `json:"accessories"`
Scene string `json:"scene"`
Mood string `json:"mood"`
Pose string `json:"pose"`
Atmosphere string `json:"atmosphere"`
Camera string `json:"camera"`
PaletteStory string `json:"palette_story"`
SceneFocus string `json:"scene_focus"`
Uniqueness string `json:"uniqueness"`
Rarity string `json:"rarity"`
AccentA string `json:"accent_a"`
AccentB string `json:"accent_b"`
EditionCode string `json:"edition_code"`
}
var accentPairs = [][2]string{
{"#79F7FF", "#7D6CFF"},
{"#FFB84D", "#FF5D8F"},
{"#9BFF9E", "#16D7C5"},
{"#F8E16C", "#D979FF"},
{"#6AE4FF", "#FF7AD9"},
{"#FFD36A", "#53A8FF"},
{"#B7FF6A", "#FF8A5B"},
{"#C7B2FF", "#6CFFD7"},
}
type traitUsage struct {
Themes map[string]int
Poses map[string]int
Atmospheres map[string]int
Cameras map[string]int
Palettes map[string]int
SceneFocuses map[string]int
}
func newTraitUsage() *traitUsage {
return &traitUsage{
Themes: map[string]int{},
Poses: map[string]int{},
Atmospheres: map[string]int{},
Cameras: map[string]int{},
Palettes: map[string]int{},
SceneFocuses: map[string]int{},
}
}
func pickLeastUsed(base int, options []string, usage map[string]int) int {
if len(options) == 0 {
return 0
}
base = ((base % len(options)) + len(options)) % len(options)
bestIdx := base
bestScore := int(^uint(0) >> 1)
for step := 0; step < len(options); step++ {
idx := (base + step) % len(options)
score := usage[options[idx]]*100 + step
if score < bestScore {
bestScore = score
bestIdx = idx
}
}
return bestIdx
}
type rarityDistribution struct {
Common int
Uncommon int
Rare int
UltraRare int
SpecialIllustrationRare int
}
func defaultRarityDistribution() rarityDistribution {
return rarityDistribution{
Common: 5800,
Uncommon: 2950,
Rare: 990,
UltraRare: 225,
SpecialIllustrationRare: 35,
}
}
func rarityDistributionFromPercentages(common, uncommon, rare, ultraRare, specialIllustration float64) rarityDistribution {
toBP := func(v float64) int {
if v <= 0 {
return 0
}
return int(v*100 + 0.5)
}
d := rarityDistribution{
Common: toBP(common),
Uncommon: toBP(uncommon),
Rare: toBP(rare),
UltraRare: toBP(ultraRare),
SpecialIllustrationRare: toBP(specialIllustration),
}
// Settings validation guarantees 100%, but normalize the final bucket to
// 10,000 basis points so float-to-int conversion can never leave a gap.
total := d.Common + d.Uncommon + d.Rare + d.UltraRare + d.SpecialIllustrationRare
d.Common += 10000 - total
if d.Common < 0 {
return defaultRarityDistribution()
}
return d
}
func canonicalRarityName(v string) string {
switch strings.ToUpper(strings.TrimSpace(v)) {
case "COMMON":
return "COMMON"
case "UNCOMMON":
return "UNCOMMON"
case "RARE":
return "RARE"
case "ULTRA RARE", "ULTRA_RARE", "ULTRARARE":
return "ULTRA RARE"
case "SPECIAL ILLUSTRATION RARE", "SPECIAL_ILLUSTRATION_RARE", "SIR":
return "SPECIAL ILLUSTRATION RARE"
default:
return ""
}
}
func chooseRarity(roll int, distribution rarityDistribution, override string) string {
if forced := canonicalRarityName(override); forced != "" {
return forced
}
roll %= 10000
if roll < 0 {
roll += 10000
}
if roll < distribution.SpecialIllustrationRare {
return "SPECIAL ILLUSTRATION RARE"
}
roll -= distribution.SpecialIllustrationRare
if roll < distribution.UltraRare {
return "ULTRA RARE"
}
roll -= distribution.UltraRare
if roll < distribution.Rare {
return "RARE"
}
roll -= distribution.Rare
if roll < distribution.Uncommon {
return "UNCOMMON"
}
return "COMMON"
}
func deriveCollectionTraits(x win) collectionTraits {
return deriveCollectionTraitsConfigured(x, nil, defaultRarityDistribution(), "")
}
func deriveCollectionTraitsWithUsage(x win, usage *traitUsage) collectionTraits {
return deriveCollectionTraitsConfigured(x, usage, defaultRarityDistribution(), "")
}
func deriveCollectionTraitsConfigured(x win, usage *traitUsage, distribution rarityDistribution, rarityOverride string) collectionTraits {
h := sha256.Sum256([]byte(x.ID + "|" + x.Winner + "|" + x.Seed + "|" + x.Guess))
themeIdx := int(h[0]) % len(collectionThemes)
poseIdx := int(h[4]) % len(poseOptions)
atmosIdx := int(h[5]) % len(atmosphereOptions)
cameraIdx := int(h[10]) % len(cameraOptions)
paletteIdx := int(h[11]) % len(paletteStoryOptions)
sceneFocusIdx := int(h[12]) % len(sceneFocusOptions)
if usage != nil {
themeNames := make([]string, 0, len(collectionThemes))
for _, theme := range collectionThemes {
themeNames = append(themeNames, theme.Name)
}
themeIdx = pickLeastUsed(themeIdx, themeNames, usage.Themes)
poseIdx = pickLeastUsed(poseIdx, poseOptions, usage.Poses)
atmosIdx = pickLeastUsed(atmosIdx, atmosphereOptions, usage.Atmospheres)
cameraIdx = pickLeastUsed(cameraIdx, cameraOptions, usage.Cameras)
paletteIdx = pickLeastUsed(paletteIdx, paletteStoryOptions, usage.Palettes)
sceneFocusIdx = pickLeastUsed(sceneFocusIdx, sceneFocusOptions, usage.SceneFocuses)
}
theme := collectionThemes[themeIdx]
pair := accentPairs[int(h[1])%len(accentPairs)]
rarityRoll := int(binary.BigEndian.Uint16(h[2:4]) % 10000)
rarity := chooseRarity(rarityRoll, distribution, rarityOverride)
uniqueness := "Give this collectible its own identity. Use a clearly distinct silhouette rhythm, prop handling, color balance and scene staging rather than a minor variation of another card."
if strings.EqualFold(strings.TrimSpace(x.Origin), "admin_drop") {
uniqueness = "This is an admin gift collectible and must feel materially different from other cards in the owner's collection. Prioritize a fresh theme, fresh pose, fresh camera language and a noticeably different color story. Avoid making a near-duplicate of an existing owned card."
}
return collectionTraits{
ThemeName: theme.Name,
Outfit: theme.Outfit,
Accessories: theme.Accessories,
Scene: theme.Scene,
Mood: theme.Mood,
Pose: poseOptions[poseIdx],
Atmosphere: atmosphereOptions[atmosIdx],
Camera: cameraOptions[cameraIdx],
PaletteStory: paletteStoryOptions[paletteIdx],
SceneFocus: sceneFocusOptions[sceneFocusIdx],
Uniqueness: uniqueness,
Rarity: rarity,
AccentA: pair[0],
AccentB: pair[1],
EditionCode: strings.ToUpper(fmt.Sprintf("NH-%02X%02X-%02X%02X", h[6], h[7], h[8], h[9])),
}
}
func buildCollectionPrompt(x win, traits collectionTraits) string {
taskDirection := strings.TrimSpace(x.PromptInstructions)
if taskDirection == "" {
taskDirection = "No additional task-specific direction."
}
avoid := strings.TrimSpace(x.NegativePrompt)
if avoid == "" {
avoid = "No additional task-specific avoid list."
}
beaconDirection := "No Beacon Hunt trait for this winning card."
if strings.TrimSpace(x.BeaconPath) != "" {
match := "did not match the boosted path"
if strings.EqualFold(x.BeaconPath, x.BeaconBoostedPath) {
match = "matched the boosted path"
}
beaconDirection = fmt.Sprintf("Winning Beacon path: %s; boosted path: %s; drand round: %d; the chosen path %s. Translate this subtly into lighting, motion, particles or accessory accents. Do not render the path name or round as text.", x.BeaconPath, x.BeaconBoostedPath, x.BeaconRound, match)
}
return fmt.Sprintf(`Create one original premium full-art collectible character illustration.
REFERENCE IMAGES — SEPARATE IDENTITY FROM STYLE:
Image 1 is the canonical RIFT CHARACTER reference. Use Image 1 only to preserve identity: silver-gray raccoon species, charcoal eye-mask shape, creamy muzzle/chest, amber-gold eyes, right-ear notch, muzzle proportions, head-to-body proportions, five-ring tail and overall face identity. Do NOT treat Image 1's neutral wardrobe, background, lighting or rendering style as fixed.
Image 2 is the TASK STYLE reference. Use Image 2 only for visual language: rendering technique, shape language, material treatment, color behavior, lighting character, texture/detail level and overall illustration mood. Do NOT copy Image 2's subject, species, pose, clothing, cultural symbols, logos, text or composition literally.
Priority rule: identity comes from Image 1; visual style comes from Image 2; wardrobe/theme/scene come from this prompt. If the references conflict, preserve RIFT's identity first and adopt the style reference second.
SUBJECT:
RIFT, one anthropomorphic raccoon character. The character must remain unmistakably animal-like and deliberately stylized rather than realistic or human-faced.
THEME:
%s
RARITY PRESENTATION:
This collectible has rarity tier %s. The illustration itself should feel premium at every tier, but higher tiers may feel more visually spectacular, elaborate or magical in their staging and environmental energy while remaining coherent with the theme.
CLOTHING — PRIMARY VARIABLE:
%s.
The clothing is the main collectible trait. It must instantly communicate the theme, look custom-tailored to RIFT, use premium materials and retain a clear silhouette. No logos or recognizable real-world brand marks.
ACCESSORIES:
%s.
SCENE:
%s.
POSE:
%s.
CAMERA / STAGING:
%s.
COLOR STORY:
%s.
MOOD / LIGHTING:
%s.
ATMOSPHERE:
%s.
SCENE EMPHASIS:
%s.
COMPOSITION:
Vertical 1024x1536 full-art composition. Exactly one dominant character occupying about 6575%% of the frame. Three-quarter body view with the face and right-ear notch clearly visible and at least most of the striped tail visible. Put the eyes near the upper third. Use foreground/background layering and dynamic depth, but keep the silhouette immediately readable. Leave natural darker breathing room near the very top and bottom so a programmatic collectible-card overlay can remain legible.
VISUAL LANGUAGE — TASK STYLE HAS AUTHORITY:
Follow Image 2 decisively for the rendering language. Recreate its level of stylization, line/edge treatment, brushwork or surface treatment, material abstraction, shading model, texture density, palette behavior, lighting language and finish. The Task Style may be 3D-cartoon, cel-shaded, graphic comic, painterly, watercolor-like, clay/toy-like, low-poly, retro-game-inspired or another deliberately illustrated collectible-art language. Do NOT force RIFT back into the neutral 3D look of Image 1. Preserve the permanent identity markers from Image 1 while translating them naturally into Image 2's visual language. Keep the result premium, coherent, richly art-directed and clearly non-photorealistic.
FULL-ART ENERGY:
The environment should visually merge with the character through thematic particles, light, fabric motion, mist, sparks, petals, snow, dust or other scene-appropriate elements. Create many small visual discoveries while keeping RIFT instantly readable.
COLLECTOR UNIQUENESS REQUIREMENT:
%s
BEACON HUNT WIN TRAIT:
%s
TASK-SPECIFIC CREATIVE DIRECTION:
%s
TASK-SPECIFIC AVOID LIST:
%s
STRICT CONSTRAINTS:
Original character design only. Exactly one raccoon. No human face. No photorealistic wildlife. No card frame or UI inside the generated artwork. No readable words, letters, numbers, signatures, logos, trademarks or watermarks. No duplicate character. No extra tails. Tail must retain exactly five dark rings. Do not copy the style-reference subject or its clothing. Do not add random costume elements unrelated to the theme.
Deterministic creative fingerprint only, never render it as text: task=%s winner=%s difficulty=%d-bit seed=%s.`,
traits.ThemeName,
traits.Rarity,
traits.Outfit,
traits.Accessories,
traits.Scene,
traits.Pose,
traits.Camera,
traits.PaletteStory,
traits.Mood,
traits.Atmosphere,
traits.SceneFocus,
traits.Uniqueness,
beaconDirection,
taskDirection,
avoid,
shortHash(x.ID), shortHash(x.Winner), x.RangeBits, shortHash(x.Seed),
)
}
func collectionNegativePrompt(x win) string {
return joinPrompt(
"photorealistic, realistic wildlife, human face, human skin, duplicate character, two characters, extra tail, wrong tail rings, unreadable anatomy, malformed paws, text, letters, numbers, logo, trademark, watermark, signature, card border, UI, low detail, blur",
x.NegativePrompt,
)
}