open-standards/wai/tools/wai_generate_model.py
Transaction Science 1fa9b61c30 wai.generate.spectral/4: angular (rays) + spiral synthesis primitives
Adds two structural component types that the plane/radial/mirror grammar can't
make — rays and spirals — and they stay byte-exact because the angle is integer.

- iatan2(): an integer CORDIC atan2 (vectoring; shifts and adds only, a 14-step
  round(atan(2^-i)/2π·1024) table + axis guards), so θ is deterministic on every
  machine. Accuracy ≈±1 step is irrelevant; determinism is the point.
- angular component: sin(fa·θ + φ) — fa rays/spokes from the centre (star,
  sunburst, pinwheel, spoke, fan, radiate, burst, flare).
- spiral component: sin(fa·θ + fr·r + φ) — arms winding out (swirl, whirl,
  whirlpool, twist, helix, coil, hypnotic, vortex, cyclone, spiral, galaxy).
- Lexicon entries grew to 10 elements [..., angular, spiral] (the SHAPE overlay is
  now 5-wide; existing 3-wide entries zero-pad). ~30 new words; model version /4.

CRITICAL: the angular/spiral frequencies REUSE the existing fxr/fyr draws (no new
PRNG calls) and the type-pick keeps the same modulo, so a prompt with no
angular/spiral weight produces the identical stream. The conformance prompt has
none, so its pin is UNCHANGED (4bd80c0ef51bb843) — no test churn. Verified:
star/sunburst → rays, swirl/galaxy/vortex → spirals, byte-exact. θ/r are hoisted
to per-pixel and computed only when a component needs them.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 06:15:35 -04:00

279 lines
22 KiB
Python

#!/usr/bin/env python3
"""Build the pinned model bundle for `wai.generate.spectral` — a deterministic,
INTEGER, byte-exact generative synthesizer (the `intent=create` analog of the
integer-exact codecs: generation whose output hash is portable, not similarity).
The "model" is small and committable: a fixed-point integer sine LUT plus the
generator's configuration. The float `sin()` is used ONCE here to bake the LUT;
the generator (Rust) reads only integers, so generation is byte-identical on
every machine. Pinning this bundle's sha256 + (prompt, seed, steps) pins the
output exactly — same inputs → same bytes → same hash, anywhere.
python tools/wai_generate_model.py <out-dir>
"""
import sys, os, json, hashlib, math
LUT_BITS = 14 # sine amplitude scale: values in [-2^14, 2^14]
T = 1024 # LUT size (one full turn)
# Semantic lexicon (the "model" half that makes the prompt's MEANING shape the
# image, deterministically): concept word -> spectral intent
# [r, g, b] per-channel hue weight 0..255 (the dominant channel tints the image)
# freq frequency multiplier, Q8 (256 = neutral; <256 calmer/smoother, >256 busier)
# orient -256..256 (negative = horizontal bands, positive = vertical, 0 = isotropic)
# Pure-colour words stay frequency/orientation-neutral so they only tint; element
# and mood words also carry texture and direction. Unknown words don't match and
# fall back to the prompt-seeded (untinted, isotropic) draw — honest about scope.
LEX = {
# colours (tint only)
"red": [255, 40, 30, 256, 0], "crimson": [220, 20, 40, 256, 0], "scarlet": [240, 30, 30, 256, 0],
"orange": [255, 140, 20, 256, 0], "amber": [255, 170, 40, 256, 0],
"gold": [255, 200, 40, 256, 0], "golden": [255, 200, 40, 256, 0], "yellow": [255, 240, 60, 256, 0],
"green": [40, 220, 60, 256, 0], "emerald": [20, 200, 120, 256, 0], "lime": [120, 240, 40, 256, 0],
"teal": [20, 200, 200, 256, 0], "cyan": [40, 230, 240, 256, 0], "turquoise": [40, 220, 200, 256, 0],
"blue": [40, 90, 255, 256, 0], "azure": [80, 160, 255, 256, 0], "cobalt": [40, 70, 230, 256, 0],
"indigo": [60, 40, 200, 256, 0], "violet": [160, 40, 230, 256, 0], "purple": [160, 40, 230, 256, 0],
"magenta": [240, 40, 200, 256, 0], "pink": [255, 120, 200, 256, 0], "rose": [255, 100, 150, 256, 0],
"white": [240, 240, 240, 256, 0], "silver": [180, 190, 200, 256, 0], "grey": [170, 175, 185, 256, 0],
"gray": [170, 175, 185, 256, 0], "black": [40, 40, 55, 256, 0],
# elements (colour + texture + direction)
"fire": [255, 90, 20, 360, 60], "flame": [255, 110, 30, 380, 80], "ember": [240, 100, 30, 300, 40],
"water": [50, 140, 230, 150, -120], "ocean": [30, 90, 220, 120, -200], "sea": [30, 100, 215, 130, -200],
"wave": [40, 120, 230, 200, -160], "river": [60, 150, 220, 170, -60],
"sky": [120, 180, 255, 90, -120], "cloud": [200, 210, 230, 110, -80], "mist": [200, 215, 225, 80, -60],
"forest": [40, 160, 60, 280, 160], "tree": [60, 150, 70, 240, 200], "leaf": [60, 200, 80, 240, 120],
"earth": [150, 100, 60, 200, 0], "soil": [140, 95, 55, 200, 0], "stone": [150, 150, 160, 190, 0],
"sand": [230, 200, 140, 90, -120], "desert": [235, 195, 130, 90, -140], "dune": [225, 195, 135, 80, -160],
"mountain": [150, 150, 170, 180, 40], "valley": [120, 160, 110, 140, -80],
"sun": [255, 200, 70, 110, 0], "sunset": [255, 140, 60, 110, -80], "dawn": [255, 180, 140, 100, -100],
"moon": [220, 220, 240, 80, 0], "star": [230, 230, 255, 300, 0], "night": [40, 40, 90, 100, 0],
"snow": [225, 235, 255, 90, 0], "ice": [200, 230, 255, 120, 0], "frost": [210, 230, 245, 110, 0],
"storm": [90, 90, 130, 420, 100], "rain": [120, 150, 200, 300, 220], "lightning": [240, 240, 180, 470, 200],
"wind": [180, 200, 210, 260, -60], "smoke": [120, 120, 130, 280, 120],
# moods / textures (texture + tendency)
"calm": [120, 175, 205, 70, -150], "peaceful": [130, 185, 200, 70, -120], "serene": [140, 190, 210, 70, -120],
"soft": [200, 190, 210, 80, 0], "smooth": [185, 200, 220, 70, 0], "gentle": [175, 200, 205, 80, -60],
"quiet": [160, 180, 195, 80, -40], "dream": [200, 160, 230, 120, 0], "dreamy": [200, 165, 230, 110, 0],
"chaos": [200, 100, 80, 470, 0], "chaotic": [205, 95, 75, 470, 0], "wild": [210, 130, 70, 420, 40],
"sharp": [220, 220, 230, 430, 0], "busy": [200, 160, 120, 410, 0], "intense": [240, 80, 60, 440, 0],
"bright": [255, 250, 220, 220, 0], "dark": [50, 50, 70, 150, 0], "deep": [40, 60, 120, 130, 0],
"warm": [255, 150, 80, 180, 0], "cool": [80, 160, 220, 160, 0], "vivid": [255, 80, 120, 360, 0],
# explicit orientation
"horizon": [120, 160, 220, 80, -240], "horizontal": [180, 180, 185, 160, -240],
"vertical": [180, 180, 185, 160, 240], "tall": [160, 180, 160, 220, 200], "flat": [170, 185, 195, 90, -200],
# SHAPE-grammar words (their colour/freq base; the shape weights come from SHAPE)
"ring": [200, 200, 210, 300, 0], "target": [235, 70, 70, 320, 0], "eye": [180, 140, 220, 260, 0],
"ripple": [60, 150, 220, 260, 0], "circle": [200, 205, 215, 240, 0], "orb": [225, 200, 150, 180, 0],
"halo": [255, 240, 205, 160, 0], "spiral": [205, 120, 220, 360, 0], "vortex": [170, 80, 200, 420, 0],
"portal": [120, 80, 230, 320, 0], "eclipse": [205, 150, 80, 180, 0], "bubble": [180, 220, 240, 220, 0],
"lens": [200, 210, 230, 200, 0], "mandala": [235, 160, 60, 380, 0], "radiant": [255, 220, 120, 240, 0],
"kaleidoscope": [205, 120, 230, 380, 0], "crystal": [180, 220, 240, 320, 0], "snowflake": [220, 235, 255, 300, 0],
"gem": [120, 200, 235, 320, 0], "prism": [235, 235, 255, 320, 0], "butterfly": [235, 140, 200, 300, 0],
"symmetry": [185, 190, 200, 220, 0], "mirror": [185, 195, 210, 220, 0],
"sunrise": [255, 180, 120, 110, 0], "dusk": [120, 90, 160, 110, 0], "twilight": [90, 80, 150, 110, 0],
"depth": [40, 70, 130, 130, 0], "ombre": [205, 165, 205, 100, 0], "gradient": [180, 190, 210, 120, 0],
"fade": [190, 200, 215, 90, 0], "glow": [255, 230, 170, 200, 0], "aurora": [80, 230, 180, 200, -120],
# extended colours (tint only)
"maroon": [128, 30, 40, 256, 0], "navy": [20, 40, 110, 256, 0], "olive": [110, 120, 40, 256, 0],
"beige": [225, 210, 170, 256, 0], "charcoal": [55, 58, 66, 256, 0], "ivory": [255, 250, 230, 256, 0],
"coral": [255, 120, 90, 256, 0], "salmon": [250, 140, 120, 256, 0], "peach": [255, 200, 160, 256, 0],
"lavender": [200, 170, 240, 256, 0], "lilac": [200, 160, 220, 256, 0], "mint": [180, 245, 210, 256, 0],
"sage": [160, 190, 150, 256, 0], "jade": [60, 200, 150, 256, 0], "ruby": [220, 30, 70, 256, 0],
"sapphire": [40, 70, 200, 256, 0], "amethyst": [150, 90, 210, 256, 0], "copper": [200, 120, 70, 256, 0],
"bronze": [180, 130, 80, 256, 0], "rust": [180, 80, 40, 256, 0], "ochre": [200, 150, 60, 256, 0],
"sepia": [150, 110, 80, 256, 0], "cream": [255, 250, 220, 256, 0], "tan": [210, 180, 140, 256, 0],
"khaki": [190, 180, 130, 256, 0], "mauve": [200, 150, 170, 256, 0], "periwinkle": [150, 160, 235, 256, 0],
"chartreuse": [180, 240, 60, 256, 0], "vermillion": [230, 70, 40, 256, 0], "ultramarine": [40, 50, 200, 256, 0],
"cerulean": [40, 150, 230, 256, 0], "chrome": [200, 210, 225, 320, 0], "neon": [80, 255, 180, 380, 0],
# nature (colour + texture + direction)
"meadow": [120, 210, 90, 130, -100], "field": [150, 200, 100, 110, -120], "jungle": [40, 170, 70, 360, 160],
"swamp": [80, 120, 70, 220, -40], "glacier": [200, 225, 245, 110, 0], "tundra": [190, 200, 210, 90, -100],
"canyon": [200, 130, 80, 200, 60], "cliff": [160, 150, 140, 200, 200], "cave": [70, 65, 78, 150, 0],
"volcano": [210, 80, 40, 400, 80], "lava": [240, 90, 20, 380, 60], "lagoon": [40, 200, 200, 140, -150],
"tide": [50, 130, 200, 180, -180], "foam": [220, 235, 245, 200, -60], "waterfall": [120, 180, 230, 360, 230],
"lake": [70, 150, 210, 90, -200], "stream": [90, 170, 220, 200, -40], "pebble": [170, 165, 160, 180, 0],
"boulder": [140, 140, 150, 160, 0], "dust": [200, 185, 160, 220, 0], "ash": [130, 130, 136, 200, 0],
"moss": [90, 160, 80, 240, 0], "fern": [70, 180, 90, 280, 140], "grass": [90, 200, 80, 300, 200],
"petal": [255, 160, 200, 200, 0], "blossom": [255, 180, 210, 180, 0], "pine": [40, 120, 70, 320, 200],
"oak": [120, 140, 70, 280, 180], "palm": [80, 180, 100, 260, 200], "cactus": [110, 170, 90, 300, 160],
"bamboo": [150, 200, 110, 320, 240], "wheat": [225, 200, 120, 200, -150], "reef": [80, 200, 180, 300, 0],
# weather
"fog": [200, 205, 212, 70, 0], "haze": [205, 200, 196, 80, 0], "drizzle": [150, 170, 200, 280, 210],
"downpour": [110, 140, 190, 380, 235], "blizzard": [225, 235, 255, 430, 200], "thunder": [90, 90, 122, 440, 60],
"breeze": [180, 210, 210, 200, -80], "gale": [150, 180, 200, 360, -40], "monsoon": [90, 120, 170, 400, 210],
"tornado": [140, 140, 162, 460, 240], "hail": [200, 215, 236, 440, 0],
# celestial / space
"galaxy": [120, 80, 200, 360, 0], "nebula": [180, 80, 210, 320, 0], "cosmos": [60, 50, 140, 280, 0],
"comet": [220, 220, 255, 380, 0], "meteor": [230, 180, 120, 400, 60], "planet": [120, 150, 200, 160, 0],
"orbit": [150, 180, 230, 220, 0], "constellation": [220, 225, 255, 340, 0], "solar": [255, 210, 90, 200, 0],
"lunar": [210, 215, 236, 100, 0], "stellar": [220, 225, 255, 320, 0], "void": [30, 30, 52, 100, 0],
"abyss": [25, 30, 56, 110, 0], "cyclone": [130, 140, 170, 460, 0], "hurricane": [120, 130, 160, 460, 0],
# materials
"metal": [150, 155, 166, 300, 0], "steel": [160, 170, 186, 280, -40], "iron": [110, 110, 122, 260, 0],
"silk": [225, 215, 226, 90, -40], "velvet": [120, 40, 90, 110, 0], "marble": [225, 225, 232, 130, 0],
"granite": [150, 150, 156, 220, 0], "slate": [110, 120, 132, 180, -60], "glass": [210, 225, 236, 160, 0],
"ceramic": [230, 220, 212, 140, 0],
# moods / abstract
"joy": [255, 210, 90, 280, 0], "melancholy": [90, 110, 170, 100, -60], "tranquil": [140, 200, 200, 70, -120],
"frenzy": [240, 90, 70, 470, 0], "fury": [230, 40, 30, 470, 0], "rage": [220, 30, 22, 470, 0],
"bliss": [255, 200, 220, 200, 0], "gloom": [70, 75, 98, 110, 0], "hope": [180, 220, 255, 200, -60],
"mystic": [120, 80, 200, 260, 0], "ethereal": [200, 210, 255, 150, 0], "surreal": [220, 120, 230, 300, 0],
"ominous": [80, 70, 92, 200, 0], "eerie": [110, 140, 130, 260, 0], "cozy": [230, 170, 120, 150, 0],
"vibrant": [255, 90, 150, 380, 0], "muted": [170, 165, 176, 120, 0], "festive": [255, 120, 140, 360, 0],
# light
"shadow": [60, 60, 76, 150, 0], "shimmer": [225, 230, 200, 360, 0], "sparkle": [240, 245, 226, 420, 0],
"gleam": [240, 240, 212, 280, 0], "beam": [255, 250, 210, 200, 200], "dazzle": [255, 250, 232, 440, 0],
"moonlight": [200, 210, 236, 100, 0], "sunlight": [255, 235, 160, 180, 0], "starlight": [220, 225, 255, 320, 0],
# geometric
"lattice": [180, 190, 200, 360, 0], "grid": [185, 190, 200, 340, 0], "mesh": [180, 185, 196, 380, 0],
"web": [170, 180, 196, 360, 0], "fractal": [180, 120, 210, 420, 0], "mosaic": [210, 150, 120, 360, 0],
"weave": [190, 170, 150, 340, 0],
# creatures (colour + texture + direction)
"tiger": [240, 130, 30, 300, -190], "zebra": [180, 180, 186, 340, 240], "peacock": [30, 180, 180, 300, 0],
"dragon": [200, 50, 40, 400, 0], "phoenix": [255, 110, 30, 400, 0], "serpent": [120, 180, 90, 320, 200],
"jellyfish": [180, 150, 230, 200, 0], "octopus": [180, 80, 160, 320, 0], "feather": [220, 210, 232, 240, 200],
"dragonfly": [80, 200, 200, 340, 0], "firefly": [230, 230, 120, 300, 0], "koi": [240, 120, 60, 240, -60],
"swan": [240, 240, 246, 110, -60], "raven": [50, 50, 66, 220, 0], "owl": [180, 150, 110, 220, 0],
"fox": [220, 120, 50, 260, 0], "wolf": [140, 145, 156, 240, 0], "deer": [180, 140, 100, 200, 0],
"whale": [70, 110, 160, 100, -200], "dolphin": [120, 180, 220, 160, -120], "shark": [120, 140, 162, 220, -80],
# food / drink (tint)
"wine": [110, 30, 50, 256, 0], "honey": [240, 190, 70, 256, 0], "coffee": [110, 70, 46, 256, 0],
"chocolate": [90, 55, 36, 256, 0], "berry": [120, 40, 90, 256, 0], "cherry": [200, 30, 50, 256, 0],
"plum": [120, 60, 110, 256, 0], "grape": [110, 70, 140, 256, 0], "mango": [255, 170, 50, 256, 0],
"lemon": [245, 230, 70, 256, 0], "caramel": [200, 140, 70, 256, 0], "cinnamon": [180, 90, 50, 256, 0],
"saffron": [240, 160, 40, 256, 0], "matcha": [140, 190, 80, 256, 0], "cocoa": [120, 75, 50, 256, 0],
"blueberry": [60, 80, 160, 256, 0], "strawberry": [230, 50, 70, 256, 0], "pumpkin": [240, 140, 40, 256, 0],
"pistachio": [160, 210, 130, 256, 0], "vanilla": [245, 235, 200, 256, 0],
# gems / minerals
"opal": [200, 220, 236, 280, 0], "pearl": [240, 238, 236, 150, 0], "onyx": [40, 40, 52, 200, 0],
"quartz": [225, 220, 236, 280, 0], "topaz": [230, 180, 80, 256, 0], "garnet": [150, 30, 50, 256, 0],
"obsidian": [35, 35, 46, 240, 0], "malachite": [40, 160, 90, 300, 0], "jet": [40, 40, 52, 200, 0],
"moonstone": [210, 220, 236, 180, 0],
# architecture / places
"city": [150, 160, 180, 360, 240], "cathedral": [180, 170, 200, 280, 200], "temple": [200, 180, 150, 240, 200],
"ruins": [160, 150, 136, 240, 0], "castle": [150, 150, 162, 260, 200], "tower": [160, 165, 176, 300, 240],
"dome": [200, 200, 212, 200, 0], "spire": [180, 180, 196, 320, 240], "labyrinth": [150, 140, 132, 380, 0],
"garden": [140, 200, 110, 240, 0], "maze": [160, 160, 172, 400, 0], "bridge": [170, 160, 152, 220, -100],
# abstract concepts
"love": [255, 120, 150, 200, 0], "passion": [220, 40, 60, 380, 0], "harmony": [160, 210, 200, 90, 0],
"balance": [180, 190, 200, 110, 0], "energy": [255, 200, 40, 420, 0], "power": [230, 70, 40, 420, 0],
"freedom": [150, 200, 255, 180, -120], "wonder": [200, 180, 255, 200, 0], "mystery": [90, 70, 130, 220, 0],
"infinity": [120, 150, 220, 240, 0], "eternity": [110, 130, 200, 180, 0], "silence": [200, 205, 216, 60, 0],
"echo": [180, 190, 210, 220, 0], "zen": [180, 200, 190, 60, -100],
# cosmic / sci-fi
"plasma": [200, 80, 200, 420, 0], "quasar": [180, 200, 255, 400, 0], "pulsar": [220, 230, 255, 380, 0],
"supernova": [255, 180, 120, 440, 0], "blackhole": [30, 30, 52, 300, 0], "matrix": [60, 230, 90, 380, 240],
"circuit": [80, 200, 160, 360, 0], "hologram": [120, 220, 230, 360, 0], "laser": [255, 60, 90, 400, 0],
"wormhole": [120, 90, 200, 380, 0], "cyber": [90, 210, 200, 400, 0], "digital": [100, 200, 180, 380, 0],
# botanical
"lotus": [255, 170, 210, 200, 0], "orchid": [210, 120, 200, 220, 0], "daisy": [245, 240, 212, 240, 0],
"sunflower": [250, 200, 50, 260, 0], "poppy": [220, 40, 40, 300, 0], "tulip": [230, 80, 120, 240, 200],
"ivy": [70, 140, 70, 300, 200], "willow": [130, 180, 120, 240, 200], "lily": [240, 235, 246, 200, 0],
# seasons / time
"autumn": [210, 120, 50, 240, 0], "spring": [150, 220, 120, 200, 0], "summer": [255, 200, 90, 220, 0],
"winter": [200, 220, 240, 150, 0], "harvest": [220, 160, 60, 240, 0], "equinox": [200, 180, 160, 180, -100],
"solstice": [255, 200, 120, 200, 0],
# synesthetic (music / sound)
"rhythm": [220, 120, 80, 400, 0], "melody": [180, 200, 230, 120, -60], "pulse": [230, 90, 120, 300, 0],
"bass": [80, 90, 142, 80, 0], "jazz": [200, 140, 90, 360, 0], "blues": [60, 90, 180, 180, -100],
"resonance": [160, 180, 220, 260, 0],
# angular rays (the angular component is in SHAPE)
"star": [240, 235, 255, 320, 0], "ray": [255, 245, 200, 280, 0], "rays": [255, 245, 200, 280, 0],
"sunburst": [255, 210, 90, 300, 0], "starburst": [240, 240, 255, 360, 0], "pinwheel": [220, 130, 200, 320, 0],
"spoke": [200, 200, 210, 300, 0], "fan": [200, 210, 220, 260, 0], "radiate": [255, 220, 150, 300, 0],
"burst": [255, 180, 120, 380, 0], "flare": [255, 170, 90, 360, 0], "shine": [255, 245, 200, 280, 0],
# spirals (the spiral component is in SHAPE)
"swirl": [200, 140, 210, 320, 0], "whirl": [180, 150, 210, 340, 0], "whirlpool": [70, 140, 200, 320, 0],
"twist": [200, 160, 180, 320, 0], "twirl": [220, 160, 200, 320, 0], "helix": [150, 190, 220, 340, 0],
"coil": [180, 170, 200, 320, 0], "hypnotic": [180, 120, 220, 360, 0], "curl": [200, 180, 200, 300, 0],
"eddy": [90, 160, 200, 300, 0],
}
# shape overlay: word -> [radial, mirror, gradient]; radial/mirror in 0..256
# (weight toward that component type), gradient in -256..256 (vertical brightness
# ramp, +bright top). Merged onto the 5-element base above; unlisted words → [0,0,0].
SHAPE = {
"sun": [235, 0, 60], "moon": [185, 0, 0], "eye": [245, 0, 0], "target": [255, 0, 0], "ring": [255, 0, 0],
"ripple": [235, 0, 0], "circle": [245, 0, 0], "orb": [235, 0, 0], "halo": [235, 0, 40], "bloom": [205, 50, 0],
"flower": [205, 90, 0], "spiral": [215, 0, 0], "vortex": [235, 0, 0], "portal": [235, 0, 0], "eclipse": [245, 0, 0],
"bubble": [215, 0, 0], "lens": [205, 0, 0], "mandala": [205, 190, 0], "radiant": [235, 0, 40], "glow": [200, 0, 30],
"kaleidoscope": [70, 255, 0], "mirror": [0, 255, 0], "symmetry": [0, 245, 0], "symmetric": [0, 235, 0],
"crystal": [50, 225, 0], "snowflake": [130, 245, 0], "gem": [50, 205, 0], "prism": [50, 205, 0], "butterfly": [0, 215, 0],
"sky": [0, 0, 205], "dawn": [40, 0, 175], "dusk": [40, 0, -175], "sunrise": [60, 0, 175], "sunset": [60, 0, -170],
"horizon": [0, 0, 120], "gradient": [0, 0, 205], "fade": [0, 0, 165], "depth": [0, 0, -150], "ombre": [0, 0, 185],
"twilight": [0, 0, -160], "aurora": [0, 0, 150], "night": [0, 0, -90],
# extended: celestial / swirls → radial
"galaxy": [200, 90, 0], "nebula": [205, 0, 0], "cosmos": [185, 0, -60], "planet": [235, 0, 0],
"orbit": [240, 0, 0], "solar": [235, 0, 40], "lunar": [205, 0, 0], "cyclone": [230, 0, 0],
"hurricane": [225, 0, 0], "mystic": [170, 60, 0], "sparkle": [120, 0, 0],
# extended: lattices / weaves → mirror (some also radial)
"lattice": [0, 235, 0], "grid": [0, 245, 0], "mesh": [0, 235, 0], "fractal": [60, 215, 0],
"mosaic": [0, 225, 0], "weave": [0, 215, 0], "marble": [0, 150, 0], "web": [130, 150, 0],
# extended: atmosphere → gradient
"fog": [0, 0, 170], "haze": [0, 0, 150], "drizzle": [0, 0, 150], "downpour": [0, 0, 130],
"blizzard": [0, 0, 120], "moonlight": [0, 0, 170], "sunlight": [0, 0, 185], "ethereal": [0, 0, 160],
"abyss": [0, 0, -180], "void": [0, 0, -120], "beam": [0, 0, 150],
# batch 3: radial (some with a gradient)
"peacock": [200, 0, 0], "jellyfish": [205, 0, 0], "firefly": [180, 0, 0], "dome": [235, 0, 0],
"energy": [215, 0, 0], "wonder": [185, 0, 0], "echo": [220, 0, 0], "plasma": [215, 0, 0],
"quasar": [235, 0, 30], "pulsar": [235, 0, 0], "supernova": [240, 0, 60], "blackhole": [225, 0, -160],
"hologram": [195, 0, 0], "wormhole": [235, 0, 0], "pulse": [215, 0, 0], "resonance": [205, 0, 0],
"lotus": [200, 0, 0], "daisy": [215, 0, 0], "sunflower": [235, 0, 0], "lily": [195, 0, 0],
"opal": [150, 0, 0], "pearl": [185, 0, 0], "moonstone": [165, 0, 0], "phoenix": [185, 0, 40],
"dragonfly": [60, 0, 0],
# batch 3: mirror (lattices, symmetry, weaves)
"cathedral": [0, 215, 0], "labyrinth": [0, 235, 0], "maze": [0, 245, 0], "harmony": [0, 185, 0],
"balance": [0, 215, 0], "matrix": [0, 215, 0], "circuit": [0, 205, 0], "digital": [0, 215, 0],
"quartz": [0, 175, 0], "malachite": [0, 165, 0], "infinity": [180, 175, 0], "eternity": [195, 0, 0],
# batch 3: gradient
"silence": [0, 0, 130], "zen": [0, 0, 120], "freedom": [0, 0, 150], "melody": [0, 0, 130],
"blues": [0, 0, -120], "onyx": [0, 0, -150], "obsidian": [0, 0, -160],
}
# batch 4 — angular (rays) and spiral. These entries are 5-element
# [radial, mirror, gradient, angular, spiral]; the merge keeps the first 5 of any
# overlay, so existing 3-element entries above still zero-pad angular/spiral.
SHAPE.update({
# angular rays
"star": [0, 0, 0, 235, 0], "ray": [0, 0, 0, 235, 0], "rays": [0, 0, 0, 235, 0],
"sunburst": [0, 0, 40, 235, 0], "starburst": [0, 0, 0, 240, 0], "pinwheel": [0, 0, 0, 235, 40],
"spoke": [0, 0, 0, 225, 0], "fan": [0, 0, 0, 200, 0], "radiate": [0, 0, 30, 225, 0],
"burst": [0, 0, 0, 235, 0], "flare": [0, 0, 20, 225, 0], "shine": [0, 0, 40, 200, 0],
# spirals (a touch of angular keeps the arms tight)
"swirl": [0, 0, 0, 40, 235], "whirl": [0, 0, 0, 40, 235], "whirlpool": [0, 0, 0, 30, 240],
"twist": [0, 0, 0, 30, 225], "twirl": [0, 0, 0, 40, 225], "helix": [0, 0, 0, 30, 235],
"coil": [0, 0, 0, 30, 230], "hypnotic": [0, 0, 0, 50, 240], "curl": [0, 0, 0, 30, 200],
"eddy": [0, 0, 0, 40, 210],
# reclassify the swirl-words that were radial → spiral (galaxy = radial + spiral arms)
"spiral": [0, 0, 0, 40, 215], "vortex": [0, 0, 0, 40, 210], "cyclone": [0, 0, 0, 30, 210],
"galaxy": [200, 0, 0, 0, 130],
})
def main(outdir):
os.makedirs(outdir, exist_ok=True)
sine_lut = [int(round(math.sin(2.0 * math.pi * i / T) * (1 << LUT_BITS))) for i in range(T)]
model = {
'version': 'wai.generate.spectral/4', # /4 adds angular (rays) + spiral components
'lut_size': T,
'lut_bits': LUT_BITS,
'sine_lut': sine_lut,
'freq_max': 24, # spatial frequency range drawn per component (at neutral)
'amp_bits': 6, # component amplitude range [0, 2^amp_bits)
'color_offsets': [0, T // 3, 2 * T // 3], # per-channel phase (RGB) — a colour wheel
'base_components': 8, # components at steps=0; total K = base + steps·step_components
'step_components': 8,
# concept word -> [r, g, b, freq_q8, orient, radial, mirror, gradient, angular, spiral]
'lexicon': {w: list(v) + (SHAPE.get(w, []) + [0, 0, 0, 0, 0])[:5] for w, v in LEX.items()},
}
path = f'{outdir}/generate_spectral_model.json'
blob = json.dumps(model, separators=(',', ':')).encode()
open(path, 'wb').write(blob)
# The model PIN is the sha256 of the committed file bytes — anyone can verify
# it, and (pin, prompt, seed, steps) pins the output exactly.
pin = hashlib.sha256(blob).hexdigest()
print(f"wrote generate_spectral_model.json ({len(sine_lut)} LUT entries), file sha256 {pin[:16]}…")
if __name__ == '__main__':
main(sys.argv[1])