var MetalNames
MetalNames is an ordered counterpart to metals -- Go/Gno map iteration order is randomized, so on-the-fly random trait assembly (MintAssembled, nftminter.gno) needs an indexable slice to pick a random metal from.
Package gemsartfab: on-chain SVG generator for the "gem in a ring" collection -- the "fab" iteration of gemsart2, re-...
Package gemsartfab: on-chain SVG generator for the "gem in a ring" collection -- the "fab" iteration of gemsart2, re-lit to read as a transparent, polished crystal instead of a flat matte cutout. Same geometry, same traits, same cascade schedule; what changed is only how a faceted stone is painted (see the gem shapes section). Two hard constraints shaped every choice there: bytes per token (a token is re-rendered on every TokenURI/Render read and shipped inline) and WebKit rendering cost -- this project has twice fixed real Safari slowdowns by removing SVG <filter>s and SMIL, so the new look uses only static gradients, paths, one inherited stroke, and CSS opacity keyframes. Nothing else.
Originally ported from the Python prototype used to design and approve the first look. Every function here is pure — no state reads beyond its own stone/color tables — so a token's full image can be recomputed on any read (TokenURI/Render) instead of being stored as a precomputed blob.
A separate deploy from the nftminter realm that imports it (not one combined package as originally designed) -- gnomcp's own gno_addpkg tool hardcodes a 10,000,000 ugnot max-deposit ceiling per deploy call with no override, and the combined source (~175KB) needs ~17.6M. Each half fits under that ceiling deployed separately. The public surface below (ShapeNames/MetalNames/RenderTokenSVG/StoneForChain/ ColorPaletteSize, plus SetStoneForChain/AddStoneColor/StoneNamesCSV/ ColorPaletteCSV/MetalToneHex) exists specifically so nftminter.gno can still reach everything it needs across that package boundary.
This is the architecture change requested explicitly, back when this was still one file: instead of the owner precomputing and storing a full ~7.6KB image string per token, this package's shared generator code is stored ONCE, and each token only needs a handful of small trait selections (see traitPreset in nftminter.gno) — the image is assembled from those on demand. Storage cost stops scaling with token count; it's dominated by this package's own one-time deployment cost instead.
MetalNames is an ordered counterpart to metals -- Go/Gno map iteration order is randomized, so on-the-fly random trait assembly (MintAssembled, nftminter.gno) needs an indexable slice to pick a random metal from.
PearlShapeNames lists the real jewelry-trade pearl shape categories -- deliberately NOT the faceted-cut vocabulary above (ShapeNames). Round is the rarest/most prized (perfectly spherical); Baroque is the least regular (freeform, no symmetry); Circled pearls show visible concentric growth grooves.
ShapeNames is the faceted-cut vocabulary, in index order. Heart, Rose Cut and Raw Crystal were dropped from the first 3D pass and are back (appended, so the first ten keep their indexes), each only on the stones that are really sold that way -- see CutsForStone. Raw Crystal is the stone's own natural crystal, not a cut: an octahedron for diamond, a hexagonal barrel for sapphire, a prism with a pyramid tip and a flat cleavage break for topaz (buildRaw).
ChainAlwaysActive: mainnet never sunsets, so a GNOLAND-1 piece always shows the green active light. RenderTokenSVG forces it; the mint realm should record Active=true for it too so the metadata agrees.
ColorPaletteCSV lists stone's full color palette as "hex:name;...", in colorIdx order. Implemented here rather than in nftminter.gno since it needs namedColor's own unexported hex/name fields.
ColorPaletteSize reports how many colors are valid for stone, so AddTraitPreset can validate a caller-supplied colorIdx. Same empty-palette fallback as colorForStone, for the same reason.
ColorProbability is the chance (0..1) that a mint of stone draws colour idx -- the same weights PickColorIndex uses. The mint realm multiplies it into a piece's Rarity tier.
ColorRarityCSV lists "name:percent" for every colour of stone, e.g. for a mint page's odds table. Percent has one decimal.
CutsForStone is which cuts a stone is really sold in -- the cuts the trade actually produces for it in quantity, not every cut that has ever been tried. A mint picks shapeIdx from this slice.
1Diamond: every cut here; brilliance cuts (Radiant, Princess, Asscher)
2 were invented for it, Rose Cut is its oldest faceted form, and raw
3 octahedra are set in jewellery as they come from the mine.
4Sapphire: rough favours ovals and cushions (weight retention), then
5 rounds, emerald cuts, pears, hearts, marquises, trillions (side
6 stones), princess (calibrated squares), rose cuts, raw barrels.
7 Asscher and Radiant are diamond-optimised and rare in sapphire.
8Topaz: crystals are long prisms, so emerald cuts, ovals, pears,
9 cushions, marquises, then rounds, trillions, hearts, princess, and
10 raw crystals (topaz is one of the classic collector crystals).
11Quartz (every chain without its own stone): cut in every style (it
12 is the lapidary's everyday stone), rose-cut amethyst is a classic,
13 and the hexagonal quartz point is THE raw crystal.
FindColorIndex reverse-maps a rendered "Color" trait value back to its index in chain's own stone palette — colorIdx is only meaningful relative to a specific stone (derived from chain via StoneForChain), not a single global color list, so this needs the chain too. Same migration-tooling purpose as FindShapeIndex.
FindShapeIndex reverse-maps a rendered "Shape" trait value (e.g. "Oval") back to its index into ShapeNames. A source realm for a migration only ever exposes the rendered trait STRING (via CollectionSummary), never the raw index a migrated token would need to regenerate its own art instead of storing a full copy of it — this closes that gap for the migration tooling (see AddMigrationSnapshotEntry). ok is false if name doesn't match any current shape (not expected for anything this realm's own gems.gno produced, but a caller should treat that as "unrecognized," not guess). FindShapeIndex takes stone (from StoneForChain) since Pearl draws shapeIdx from a completely different list (PearlShapeNames, not ShapeNames) -- a rendered "Baroque" only resolves against the right one.
MetalToneHex reports metalName's hi/lo gradient tones as "hi;lo", or "" if unrecognized. Implemented here for the same reason as ColorPaletteCSV -- needs metalTone's own unexported hi/lo fields.
PickColorIndex maps a uniform random roll to a palette index for stone, honouring colorWeights. The mint realm calls this instead of an even intn(ColorPaletteSize(stone)).
1func RenderTokenThumbSVG(chain, metal string, shapeIdx, colorIdx int, active bool, tidStr string) stringRenderTokenSVG assembles one token's complete SVG from its small trait selection, mirroring badge() in the Python prototype exactly (same element order, same layout constants). `salt` seeds every per-piece animation timing and every gradient id — the token's own real ID (tidStr), so nothing here is computed before mint assigns one. RenderTokenThumbSVG is RenderTokenSVG for a list thumbnail (a gnoweb collection table row): the same piece, minus the fire flashes, the rock relighting and the table reflections, which do not read at thumbnail size, for roughly a third less gas, so a page fits more rows under a query's gas ceiling.
SetStoneForChain and AddStoneColor live here (not in nftminter.gno's own wrapper functions of the same purpose) because they mutate this package's own private maps -- stoneByChain/stonePalettes/stoneNames aren't visible outside this package once gemsart is its own separate deploy, unlike when this was one combined realm. nftminter.gno's SetStoneForChain/AddStoneColor now just assertOwner() then delegate here.
StoneForChain: only gno.land testnet generations that were actually given a gemstone codename (Topaz-1, Sapphire-1 at launch) get one — every other/future chain is "Not Set" rather than a guessed mineral name, unless the owner explicitly assigns one via SetStoneForChain.
StoneNamesCSV lists every registered stone name, semicolon-separated, in the order each was first introduced.