Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

genesis.gno

12.31 Kb · 444 lines
  1// Package genesis is the platform's first engine: a pure p/ port of
  2// the prototype's gen1 engine. The RNG stream, trait tables,
  3// draw order and SVG drawing are byte-for-byte nftgen1's, so a seed renders
  4// the same artwork here as there; genesis_test.gno pins trait vectors
  5// computed independently from the same algorithm.
  6//
  7// What changed is only the packaging (RA L-7): it is a stateless p/
  8// package implementing engine/v1, the seed comes from core's commit–reveal
  9// (so the salt/seed helpers and the gnoweb Render of nftgen1 are gone), and
 10// the engine takes no params.
 11package genesis
 12
 13import (
 14	"errors"
 15	"strconv"
 16	"strings"
 17
 18	"gno.land/p/nt/ufmt/v0"
 19
 20	"gno.land/p/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/minhwa/engine/v1"
 21)
 22
 23// TraitTypes in the order Traits returns them.
 24var traitTypes = []string{"Palette", "Shape", "Ground", "Frame", "Finish", "Aura", "Elements", "Symmetry", "Score", "Rarity"}
 25
 26// MaxSVGBytes: 8 symmetry x 14 elements of the widest primitive plus the
 27// densest ground and frame stays well under this (gen1 tested < 32,000).
 28const MaxSVGBytes = 32768
 29
 30// Manifest describes the engine. Author is left empty: the adapter realm
 31// that registers this engine sets the address that will ClaimEngine.
 32func Manifest() engine.Manifest {
 33	return engine.Manifest{
 34		Name:    "Genesis",
 35		Version: "1.0.0",
 36		Description: "Fully on-chain generative engine, nftgen1 lineage: FNV-1a + splitmix64 stream, " +
 37			"10 traits, 1,254,400 combinations, score-based rarity over every layer.",
 38		License:      "TBD",
 39		ParamsSchema: `{"const":""}`,
 40		TraitTypes:   append([]string{}, traitTypes...),
 41		Living:       false,
 42		MaxSVGBytes:  MaxSVGBytes,
 43	}
 44}
 45
 46// ValidateParams: genesis has no parameters.
 47func ValidateParams(params string) error {
 48	if params != "" {
 49		return errors.New("genesis takes no params")
 50	}
 51	return nil
 52}
 53
 54// Traits returns the ten traits of in.Seed in TraitTypes order.
 55func Traits(in engine.Input) []engine.Trait {
 56	t := TraitsOf(in.Seed)
 57	return []engine.Trait{
 58		{Type: "Palette", Value: t.Palette},
 59		{Type: "Shape", Value: t.Shape},
 60		{Type: "Ground", Value: t.Ground},
 61		{Type: "Frame", Value: t.Frame},
 62		{Type: "Finish", Value: t.Finish},
 63		{Type: "Aura", Value: t.Aura},
 64		{Type: "Elements", Value: strconv.Itoa(t.Elements)},
 65		{Type: "Symmetry", Value: strconv.Itoa(t.Symmetry)},
 66		{Type: "Score", Value: strconv.Itoa(t.Score)},
 67		{Type: "Rarity", Value: t.Rarity},
 68	}
 69}
 70
 71// Render returns the raw SVG for in.Seed.
 72func Render(in engine.Input) string { return SVG(in.Seed) }
 73
 74// ---------------------------------------------------------------------------
 75// Deterministic randomness — FNV-1a over the seed, then a splitmix64 mixer.
 76// Pure integer math: no floats, no time, no block data.
 77// ---------------------------------------------------------------------------
 78
 79const (
 80	fnvOffset uint64 = 14695981039346656037
 81	fnvPrime  uint64 = 1099511628211
 82)
 83
 84type rng struct {
 85	state uint64
 86}
 87
 88func newRNG(seed string) *rng {
 89	h := fnvOffset
 90	for i := 0; i < len(seed); i++ {
 91		h ^= uint64(seed[i])
 92		h *= fnvPrime
 93	}
 94	if h == 0 {
 95		h = fnvPrime
 96	}
 97	return &rng{state: h}
 98}
 99
100func (r *rng) next() uint64 {
101	r.state += 0x9E3779B97F4A7C15
102	z := r.state
103	z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9
104	z = (z ^ (z >> 27)) * 0x94D049BB133111EB
105	return z ^ (z >> 31)
106}
107
108// intn returns a value in [0, n).
109func (r *rng) intn(n int) int {
110	if n <= 0 {
111		return 0
112	}
113	return int(r.next() % uint64(n))
114}
115
116// ---------------------------------------------------------------------------
117// Weighted trait tables (weights are explicit; one draw walks the sum).
118// ---------------------------------------------------------------------------
119
120type option struct {
121	name   string
122	weight int
123	score  int // contribution to the rarity score
124}
125
126func pick(r *rng, table []option) option {
127	total := 0
128	for _, o := range table {
129		total += o.weight
130	}
131	roll := r.intn(total)
132	acc := 0
133	for _, o := range table {
134		acc += o.weight
135		if roll < acc {
136			return o
137		}
138	}
139	return table[len(table)-1]
140}
141
142type palette struct {
143	option
144	bg  string
145	ink string
146	c1  string
147	c2  string
148	c3  string
149}
150
151// Palette weights sum to 100, so a weight reads directly as a percent.
152var palettes = []palette{
153	{option{"Emerald", 18, 0}, "#04140f", "#7cf2c4", "#10b981", "#34d399", "#065f46"},
154	{option{"Amethyst", 18, 0}, "#0b0713", "#e9d5ff", "#8b5cf6", "#c084fc", "#4c1d95"},
155	{option{"Abyss", 16, 0}, "#02101c", "#bae6fd", "#0ea5e9", "#38bdf8", "#0c4a6e"},
156	{option{"Ember", 14, 0}, "#160806", "#fecaca", "#f97316", "#fb923c", "#7c2d12"},
157	{option{"Bone", 12, 0}, "#f5f5f4", "#1c1917", "#78716c", "#a8a29e", "#292524"},
158	{option{"Solar", 10, 0}, "#1a1403", "#fef08a", "#eab308", "#facc15", "#713f12"},
159	{option{"Obsidian", 8, 2}, "#050505", "#e5e5e5", "#404040", "#737373", "#171717"},
160	{option{"Prism", 4, 4}, "#0a0a12", "#fdf4ff", "#22d3ee", "#f472b6", "#a78bfa"},
161}
162
163var shapes = []option{
164	{"Orbit", 20, 0},
165	{"Lattice", 18, 0},
166	{"Bloom", 18, 0},
167	{"Shard", 16, 0},
168	{"Spiral", 14, 0},
169	{"Weave", 9, 2},
170	{"Rift", 5, 4},
171}
172
173var grounds = []option{
174	{"Flat", 30, 0},
175	{"Halo", 25, 0},
176	{"Grid", 22, 0},
177	{"Rings", 14, 1},
178	{"Scatter", 9, 2},
179}
180
181var frames = []option{
182	{"None", 55, 0},
183	{"Thin", 25, 1},
184	{"Double", 14, 2},
185	{"Ornate", 6, 4},
186}
187
188var finishes = []option{
189	{"Matte", 50, 0},
190	{"Gloss", 28, 1},
191	{"Glow", 16, 2},
192	{"Foil", 6, 4},
193}
194
195var auras = []option{
196	{"None", 78, 0},
197	{"Soft", 14, 2},
198	{"Bright", 6, 4},
199	{"Nova", 2, 7},
200}
201
202// TraitSet is the decoded properties of a seed. The SVG builder and the
203// trait list both read this one struct, so picture and metadata cannot
204// describe different artwork.
205type TraitSet struct {
206	Palette  string
207	Shape    string
208	Ground   string
209	Frame    string
210	Finish   string
211	Aura     string
212	Elements int
213	Symmetry int
214	Score    int
215	Rarity   string
216}
217
218// Tier cut-offs, chosen on nftgen1's 1000-seed simulation: Common 21.4% /
219// Uncommon 44.4% / Rare 25.2% / Legendary 7.6% / Mythic 1.4%.
220const (
221	scoreMythic    = 12
222	scoreLegendary = 8
223	scoreRare      = 5
224	scoreUncommon  = 2
225)
226
227func derive(seed string) (TraitSet, palette) {
228	r := newRNG(seed)
229
230	// Draw order is part of the contract: changing it changes every token.
231	p := palettes[0]
232	{
233		total := 0
234		for _, q := range palettes {
235			total += q.weight
236		}
237		roll := r.intn(total)
238		acc := 0
239		for _, q := range palettes {
240			acc += q.weight
241			if roll < acc {
242				p = q
243				break
244			}
245		}
246	}
247	shape := pick(r, shapes)
248	ground := pick(r, grounds)
249	frame := pick(r, frames)
250	finish := pick(r, finishes)
251	aura := pick(r, auras)
252
253	t := TraitSet{
254		Palette:  p.name,
255		Shape:    shape.name,
256		Ground:   ground.name,
257		Frame:    frame.name,
258		Finish:   finish.name,
259		Aura:     aura.name,
260		Elements: 5 + r.intn(10), // 5..14
261		Symmetry: 2 + r.intn(7),  // 2..8
262	}
263
264	score := p.score + shape.score + ground.score + frame.score + finish.score + aura.score
265	if t.Elements >= 13 {
266		score++
267	}
268	if t.Symmetry >= 7 {
269		score++
270	}
271	t.Score = score
272
273	switch {
274	case score >= scoreMythic:
275		t.Rarity = "Mythic"
276	case score >= scoreLegendary:
277		t.Rarity = "Legendary"
278	case score >= scoreRare:
279		t.Rarity = "Rare"
280	case score >= scoreUncommon:
281		t.Rarity = "Uncommon"
282	default:
283		t.Rarity = "Common"
284	}
285	return t, p
286}
287
288// TraitsOf returns the decoded traits for a seed.
289func TraitsOf(seed string) TraitSet {
290	t, _ := derive(seed)
291	return t
292}
293
294func finishStroke(finish string) int {
295	switch finish {
296	case "Gloss":
297		return 4
298	case "Glow":
299		return 5
300	case "Foil":
301		return 6
302	default: // Matte
303		return 3
304	}
305}
306
307func auraRing(aura string) (int, int) { // width, opacity percent
308	switch aura {
309	case "Soft":
310		return 4, 22
311	case "Bright":
312		return 8, 38
313	case "Nova":
314		return 14, 60
315	default:
316		return 0, 0
317	}
318}
319
320// SVG renders the artwork for a seed as a standalone SVG document.
321func SVG(seed string) string {
322	t, p := derive(seed)
323	r := newRNG(seed + "|art") // separate stream: trait draws never shift the art
324
325	var b strings.Builder
326	b.WriteString(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">`)
327	b.WriteString(ufmt.Sprintf(`<rect width="512" height="512" fill="%s"/>`, p.bg))
328
329	// --- ground layer ---
330	switch t.Ground {
331	case "Halo":
332		b.WriteString(ufmt.Sprintf(
333			`<circle cx="256" cy="256" r="200" fill="none" stroke="%s" stroke-width="1" opacity="0.35"/>`, p.c3))
334	case "Grid":
335		for i := 64; i < 512; i += 64 {
336			b.WriteString(ufmt.Sprintf(
337				`<line x1="%d" y1="0" x2="%d" y2="512" stroke="%s" stroke-width="1" opacity="0.18"/>`, i, i, p.c3))
338			b.WriteString(ufmt.Sprintf(
339				`<line x1="0" y1="%d" x2="512" y2="%d" stroke="%s" stroke-width="1" opacity="0.18"/>`, i, i, p.c3))
340		}
341	case "Rings":
342		for rad := 60; rad <= 240; rad += 45 {
343			b.WriteString(ufmt.Sprintf(
344				`<circle cx="256" cy="256" r="%d" fill="none" stroke="%s" stroke-width="1" opacity="0.22"/>`, rad, p.c3))
345		}
346	case "Scatter":
347		for i := 0; i < 40; i++ {
348			b.WriteString(ufmt.Sprintf(
349				`<circle cx="%d" cy="%d" r="2" fill="%s" opacity="0.30"/>`,
350				16+r.intn(480), 16+r.intn(480), p.c3))
351		}
352	}
353
354	// --- aura layer ---
355	if w, op := auraRing(t.Aura); w > 0 {
356		b.WriteString(ufmt.Sprintf(
357			`<circle cx="256" cy="256" r="230" fill="none" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
358			p.ink, w, op))
359	}
360
361	// --- composition: one pass, mirrored Symmetry times ---
362	stroke := finishStroke(t.Finish)
363	step := 360 / t.Symmetry
364	for s := 0; s < t.Symmetry; s++ {
365		b.WriteString(ufmt.Sprintf(`<g transform="rotate(%d 256 256)">`, s*step))
366		for e := 0; e < t.Elements; e++ {
367			color := p.c1
368			switch r.intn(3) {
369			case 1:
370				color = p.c2
371			case 2:
372				color = p.ink
373			}
374			x := 96 + r.intn(320)
375			y := 96 + r.intn(320)
376			size := 12 + r.intn(70)
377			opacity := 40 + r.intn(55) // percent, integer only
378			if t.Finish == "Glow" || t.Finish == "Foil" {
379				opacity = 55 + r.intn(40)
380			}
381
382			switch t.Shape {
383			case "Orbit":
384				b.WriteString(ufmt.Sprintf(
385					`<circle cx="%d" cy="%d" r="%d" fill="none" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
386					x, y, size, color, stroke, opacity))
387			case "Lattice":
388				b.WriteString(ufmt.Sprintf(
389					`<rect x="%d" y="%d" width="%d" height="%d" fill="none" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
390					x, y, size, size, color, stroke-1, opacity))
391			case "Bloom":
392				b.WriteString(ufmt.Sprintf(
393					`<circle cx="%d" cy="%d" r="%d" fill="%s" opacity="0.%d"/>`,
394					x, y, size/2, color, opacity))
395			case "Shard":
396				b.WriteString(ufmt.Sprintf(
397					`<polygon points="%d,%d %d,%d %d,%d" fill="%s" opacity="0.%d"/>`,
398					x, y, x+size, y+size/2, x+size/3, y+size, color, opacity))
399			case "Weave":
400				b.WriteString(ufmt.Sprintf(
401					`<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
402					x, y, x+size, y+size/3, color, stroke, opacity))
403				b.WriteString(ufmt.Sprintf(
404					`<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
405					x+size, y, x, y+size/3, color, stroke, opacity))
406			case "Rift":
407				b.WriteString(ufmt.Sprintf(
408					`<path d="M%d %d l %d %d l %d %d l %d %d" fill="none" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
409					x, y, size/2, -size/3, size/3, size, size/2, -size/2,
410					color, stroke, opacity))
411			default: // Spiral
412				b.WriteString(ufmt.Sprintf(
413					`<path d="M%d %d q %d %d %d %d" fill="none" stroke="%s" stroke-width="%d" opacity="0.%d"/>`,
414					x, y, size, -size, size*2, 0, color, stroke, opacity))
415			}
416		}
417		b.WriteString(`</g>`)
418	}
419
420	// --- frame layer, drawn last so it always sits on top ---
421	switch t.Frame {
422	case "Thin":
423		b.WriteString(ufmt.Sprintf(
424			`<rect x="12" y="12" width="488" height="488" fill="none" stroke="%s" stroke-width="2" opacity="0.55"/>`, p.ink))
425	case "Double":
426		b.WriteString(ufmt.Sprintf(
427			`<rect x="10" y="10" width="492" height="492" fill="none" stroke="%s" stroke-width="3" opacity="0.60"/>`, p.ink))
428		b.WriteString(ufmt.Sprintf(
429			`<rect x="24" y="24" width="464" height="464" fill="none" stroke="%s" stroke-width="1" opacity="0.45"/>`, p.c2))
430	case "Ornate":
431		b.WriteString(ufmt.Sprintf(
432			`<rect x="8" y="8" width="496" height="496" fill="none" stroke="%s" stroke-width="4" opacity="0.70"/>`, p.ink))
433		b.WriteString(ufmt.Sprintf(
434			`<rect x="22" y="22" width="468" height="468" fill="none" stroke="%s" stroke-width="2" opacity="0.55"/>`, p.c2))
435		for _, c := range [][2]int{{8, 8}, {504, 8}, {8, 504}, {504, 504}} {
436			b.WriteString(ufmt.Sprintf(
437				`<circle cx="%d" cy="%d" r="10" fill="none" stroke="%s" stroke-width="3" opacity="0.80"/>`,
438				c[0], c[1], p.c1))
439		}
440	}
441
442	b.WriteString(`</svg>`)
443	return b.String()
444}