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

settler_test.gno

5.83 Kb · 152 lines
  1package art
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/uassert/v0"
  8)
  9
 10// Settler #25, read from mainnet on 2026-09-29 and transcribed one character
 11// per palette index (0-9 then a-j). 32x32, 20 colours.
 12//
 13// It is here because the package's central claim is about real indexed art and
 14// a synthetic two-colour fixture cannot test it: the interesting thing about a
 15// sprite is how many near-luminance colours it carries, and that is a property
 16// of art somebody drew.
 17//
 18// gno.land/r/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/nft:token/25
 19// The Settlers art is dedicated to the public domain under CC0 1.0.
 20var settler25Pal = Palette{"#f3e3c9", "#2a1a2e", "#ffb45a", "#b5601c", "#f08a2a", "#b57650", "#d99a6c", "#7f5234", "#1b1626", "#f08c7c", "#ffb0a0", "#c8645a", "#a8744e", "#573622", "#5fb0e8", "#2b649a", "#3f8fd1", "#6b3f22", "#6a4126", "#46271a"}
 21
 22var settler25Rows = []string{
 23	"00000000000000011000000000000000",
 24	"00000000000000123100000000000000",
 25	"00000000000000144100000000000000",
 26	"00000000000001244310000000000000",
 27	"00000000000001444410000000000000",
 28	"00000000000012444431000000000000",
 29	"00000000000014444441000000000000",
 30	"00000000000124444443100000000000",
 31	"00000000000144444444100000000000",
 32	"00000000001244444444310000000000",
 33	"00000000001444444444410000000000",
 34	"00000000124444444444443100000000",
 35	"00000000133333333333333100000000",
 36	"00000000001555555555510000000000",
 37	"00000000001677766777610000000000",
 38	"00000000016686666668661000000000",
 39	"00000000156686699668665100000000",
 40	"00000000001666a99b66610000000000",
 41	"0000000001c777bbbb777d1000000000",
 42	"0000000011c7777777777d1100000000",
 43	"00000001e1c777d77d777d1f10000000",
 44	"00000001eg1c77d77d77d1gf10000000",
 45	"00000001eg1c7d7777d7d1gf10000000",
 46	"00000016egg1c7d77d7d1ggf61000000",
 47	"00000015egg1c777777d1ggf51000000",
 48	"00000001hhhh1c7dd7d1hhhh10000000",
 49	"00000001egggg1c77d1ggggf10000000",
 50	"00000001ffffff1cd1ffffff10000000",
 51	"0000000001iiiii11iiiii1000000000",
 52	"0000000001iiiii11iiiii1000000000",
 53	"0000000001jjjjj11jjjjj1000000000",
 54	"00000000011111111111111000000000",
 55}
 56
 57func settler25() *Pix {
 58	p := NewPix(len(settler25Rows[0]), len(settler25Rows), settler25Pal)
 59	for y, row := range settler25Rows {
 60		for x, r := range row {
 61			var i int
 62			if r >= '0' && r <= '9' {
 63				i = int(r - '0')
 64			} else {
 65				i = int(r-'a') + 10
 66			}
 67			p.Set(x, y, uint8(i))
 68		}
 69	}
 70	return p
 71}
 72
 73// TestSettler25 is the finding measured on real art rather than on a fixture
 74// chosen to show it.
 75//
 76// Settler #25 carries 20 palette entries. Keyed on the palette index, all 20
 77// survive into the text. Keyed on luminance, a ten-step ramp resolves them to
 78// 9: eleven of the artist's twenty colours stop existing, and the shapes
 79// between them go with it.
 80//
 81// That is the whole argument for Glyphs being the default and Ramp being the
 82// thing you ask for.
 83func TestSettler25PaletteBeatsLuminance(t *testing.T) {
 84	glyphs := settler25().Canvas(Glyphs).Text()
 85	ramp := settler25().Canvas(Ramp).Text()
 86
 87	// distinctRunes counts the newline too, so both figures are colours + 1.
 88	uassert.Equal(t, 21, distinctRunes(glyphs), "every palette entry survives")
 89	uassert.Equal(t, 10, distinctRunes(ramp), "the ramp resolves 20 colours to 9")
 90	uassert.True(t, distinctRunes(glyphs) > distinctRunes(ramp), "glyphs strictly finer")
 91}
 92
 93// TestSettler25SVGIsOnePathPerColour pins the encoding that keeps a sprite
 94// small enough for a realm to emit. 20 colours, 20 paths, and 4,532 bytes where
 95// one rectangle per run cost 20,625.
 96func TestSettler25SVGIsOnePathPerColour(t *testing.T) {
 97	doc := settler25().SVG(8).String()
 98	uassert.Equal(t, 20, strings.Count(doc, "<path"), "one path per palette entry")
 99	uassert.Equal(t, 0, strings.Count(doc, "<rect"), "no rectangles at all")
100	uassert.True(t, len(doc) < 6000, "a 32x32 sprite stays well under 6 KB")
101	// Scaling is a viewBox change, so the path data, which is all of the size,
102	// is byte-identical at any scale. Only the canvas width and height move,
103	// and here that is one digit each between "32" and "256".
104	one := settler25().SVG(1).String()
105	uassert.Equal(t, paths(one), paths(doc), "path data is identical at any scale")
106	uassert.Equal(t, 2, len(doc)-len(one), "only the canvas dimensions grow")
107}
108
109// paths returns the document from its first <path> on, which is everything
110// scale cannot affect.
111func paths(doc string) string {
112	i := strings.Index(doc, "<path")
113	if i < 0 {
114		return ""
115	}
116	return doc[i:]
117}
118
119// TestSettler25TextIsSmallerThanItsPicture is the reason a text target is worth
120// having at all, beyond readability: the ASCII form of this NFT is a third of
121// the SVG.
122func TestSettler25TextIsSmallerThanItsPicture(t *testing.T) {
123	text := settler25().Canvas(Glyphs).Text()
124	uassert.Equal(t, 1469, len(text), "glyph text size")
125	uassert.True(t, len(text) < len(settler25().SVG(8).String()), "text is the cheaper form")
126}
127
128// TestSettler25RampConfusesHueForBrightness is the part of the finding that is
129// easy to miss: the ramp's collisions are not between near-identical shades.
130// Two colours a human would never confuse, an orange hat and a sky-blue tunic,
131// come out as the same character, because luminance cannot tell a hue from a
132// hue.
133func TestSettler25RampConfusesHueForBrightness(t *testing.T) {
134	orange := settler25Pal.Color(4) // #f08a2a, the hat
135	blue := settler25Pal.Color(16)  // #3f8fd1, the tunic
136
137	uassert.Equal(t, 152, Luminance(orange), "orange hat")
138	uassert.Equal(t, 130, Luminance(blue), "sky tunic")
139	uassert.NotEqual(t, orange, blue, "they are different colours")
140	uassert.Equal(t, rampGlyph(orange), rampGlyph(blue), "and the same ramp glyph")
141
142	// Keyed on the palette index instead, they cannot collide: the indices are
143	// different, so the glyphs are.
144	uassert.NotEqual(t,
145		DefaultGlyphs[4%len(DefaultGlyphs)],
146		DefaultGlyphs[16%len(DefaultGlyphs)],
147		"glyphs keep them apart")
148}
149
150func rampGlyph(c int) rune {
151	return DefaultRamp[Luminance(c)*len(DefaultRamp)/256]
152}