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.

v9 state

Back to all declarations

saleFeeRecipient

untyped string

// gnotchi-opponent, v4's choice

Value

"g1ylpe9juk8cafgf9rt09wzuz85ug2lyfk5metyv"

saleFeeRateBps

int64

// 5% of the declared price

Value

500

saleFeeFloorUgnot

int64

// 1 GNOT flat minimum per sale

Value

1000000

collectionName

untyped string

Value

"Gnotchi gnomes"

collectionSymbol

untyped string

Value

"GNOME"

adminAddress

untyped string

Value

"g1nq99m3h9ed7vqve6ajpr3qwy2ls252m8jnundm"

numEyes

int64

Value

10

numNoses

int64

Value

8

hourSeconds

int64

Value

3600

daySeconds

int64

Value

86400

numPlots

untyped bigint

// acorns.plots

Value

(3 <untyped> bigint)

plantCost

int64

// acorns.plantCost

Value

1

feedCost

int64

// acorns.feedCost

Value

1

acornsStart

int64

Value

10

meterMax

int64

Value

10

patCheer

int64

Value

3

hungryAtOrBelow

int64

Value

2

growHours

int64

Value

24

patCooldownHours

int64

Value

12

tinkerCost

int64

Value

5

levelBase

int64

Value

50

levelStep

int64

Value

25

sulkyPenaltyPct

int64

Value

10

harvestPerPlot

int64

Value

3

feedBelly

int64

Value

4

bellyDropEveryHours

int64

Value

3

cheerDropEveryHours

int64

Value

6

dailyCareCap

int64

Value

60

acornReserve

int64

Value

2

happyBonusPct

int64

Value

10

happyWindowHours

int64

Value

2

sulkyAfterHours

int64

Value

72

maxLevel

int64

Value

20

typeAtLevel

int64

Value

5

xpPat

int64

Value

5

xpFeed

int64

Value

5

xpHarvestPerPlot

int64

Value

10

xpTinkerTask

int64

Value

15

maxActionsPerCall

untyped bigint

Value

(32 <untyped> bigint)

maxBackfillHours

int64

Value

48

caredWindowDays

uint

Value

64

maxGnomesPageSize

untyped bigint

Value

(100 <untyped> bigint)

maxListingsPageSize

untyped bigint

Value

(100 <untyped> bigint)

maxKinEdgesPageSize

untyped bigint

Value

(100 <untyped> bigint)

ugnotDenom

untyped string

Value

"ugnot"

maxPriceUgnot

int64

Value

1000000000000000

maxFeeRateBps

int64

Value

10000

maxListingDays

int64

Value

30

moodHungry

untyped string

Value

"hungry"

moodSulky

untyped string

Value

"sulky"

moodHappy

untyped string

Value

"happy"

moodContent

untyped string

Value

"content"

actionPat

untyped string

Value

"pat"

actionFeed

untyped string

Value

"feed"

actionPlant

untyped string

Value

"plant"

actionHarvest

untyped string

Value

"harvest"

actionTinker

untyped string

Value

"tinker"

typeGardener

untyped string

Value

"gardener"

typeExplorer

untyped string

Value

"explorer"

typeTinkerer

untyped string

Value

"tinkerer"

batchCap

int64

Value

60

numNamedEditions

untyped bigint

Value

(8 <untyped> bigint)

namedPickRange

untyped bigint

Value

(10 <untyped> bigint)

lockCaredDays

int64

Value

14

maxNameLen

untyped bigint

Value

(24 <untyped> bigint)

pinOffsetSeconds

int64

Value

60

pinOffsetRounds

int64

pinOffsetRounds is a const expression over an imported const (beacon.PeriodSeconds), which Gno permits across package boundaries exactly as it would within one; TestPinOffsetIsTwentyRounds pins the arithmetic rather than trusting the comment above.

Value

20

hatOptions

[4]string

hatOptions / coatOptions: v4's validHats/validCoats catalogue, as an ORDERED array rather than a set, because Settle needs to draw a POSITION, not merely validate one. Order is frozen the moment this realm deploys, exactly as v4's own numEyes/numNoses ordering is frozen -- a part may be redrawn; it may never be reordered, removed or renumbered (v4's own comment on this).

Open
OID
0fcf1c…66c8:4
hatOptions details

Inspect array

eyesWeights

[10]int64

eyesWeights: ECONOMY.md section 3, verbatim -- "eyes across 4 colours at 15%, 3 at 9%, 2 at 5%, 1 at 3%" -- 4+3+2+1 = 10 entries = numEyes, and 4\*15+3\*9+2\*5+1\*3 = 100. TestWeightTablesSumToOneHundred pins the sum rather than trusting the arithmetic in this comment; drawWeighted's own fallback additionally makes a drift here fail safe, not silently.

Open
OID
0fcf1c…66c8:8
eyesWeights details

Inspect array

noseWeights

[8]int64

noseWeights: ECONOMY.md section 3 says only "nose similar across 8 shapes" and gives no exact percentages -- BATCH-ONE.md section 4a names this directly as real work nobody has done yet. This is a SHAPE-FAITHFUL INTERPRETATION, not a transcription: four tiers of two shapes each, 20/20/15/15/10/10/5/5, the same big-gap-then-smaller-gaps shape eyesWeights has, scaled to 8 entries and summing to 100. Flagged here, not quietly presented as sourced, so the owner or a future author can replace it with real numbers the moment ECONOMY.md section 3 gets them.

Open
OID
0fcf1c…66c8:10
noseWeights details

Inspect array

numAxes

untyped bigint

Value

(5 <untyped> bigint)

axisTotalWeight

int64

Value

64

numSecrets

untyped bigint

Value

(17 <untyped> bigint)

firstDistinctCount

int64

Value

17

careThresholdFloor

int64

Value

14

careThresholdSpan

int64

Value

32

numStats

untyped bigint

Value

(4 <untyped> bigint)

statPointTotal

int64

Value

22

statMin

int64

Value

1

statMax

int64

Value

10

statHighAtLeast

int64

Value

8

statLowAtMost

int64

Value

3

numBornUnder

untyped bigint

Value

(8 <untyped> bigint)

temperamentWeights

[6]int64

// section 1.1: Blithe, Grudgeful, Fretful, Insufferable, Theatrical, Stony

Open
OID
0fcf1c…66c8:12
temperamentWeights details

Inspect array

obsessionWeights

[8]int64

// section 1.2: Cheese, Fence, Book, Hat, Spade, Lantern, Carrot Row, Moss

Open
OID
0fcf1c…66c8:14
obsessionWeights details

Inspect array

bondWeights

[6]int64

// section 1.3: Pibbs, Stone, Fennick, Middle Plot, Gran, Tin

Open
OID
0fcf1c…66c8:16
bondWeights details

Inspect array

fearWeights

[6]int64

// section 1.4: Beetles, Being Forgotten, The Dark, Being Laughed At, Slugs, Shine

Open
OID
0fcf1c…66c8:18
fearWeights details

Inspect array

boastWeights

[6]int64

// section 1.5: Badger, Oak, Wheel, Frost, Row, Mend

Open
OID
0fcf1c…66c8:20
boastWeights details

Inspect array

bornUnderWeights

[8]int64

// section 1.6: Carrot, Bramble, Acorn, Nettle, Clover, Toadflax, Ember, Thistle

Open
OID
0fcf1c…66c8:22
bornUnderWeights details

Inspect array

secretTargetKind

[17]int64

secretTargetKind / secretTargetID: TRAITS.md section 3.2's "Undercuts" column, AS IDS ONLY -- the name and the line itself live off chain, where the register is still being revised (FUNNY.md). kind 0 means the secret undercuts a BOAST, 1 means a FEAR; the id is the axis id within that axis. Paired by index: secret i undercuts (secretTargetKind\[i], secretTargetID\[i]). TestSecretTargetsMatchTheAllocation pins the three facts section 3.2 states about this table and that LINEAGE.md section 3.3's arithmetic depends on: twelve secrets target a boast and five target a fear; EVERY ONE of the six boasts is the target of exactly two; and the fears are Beetles 2, Being Forgotten 1, Being Laughed At 1, Slugs 1, The Dark 0 and Shine 0. A transcription slip in either array would move LINEAGE.md section 3.3's per-edge 0.0489 and TRAITS.md section 3.3's population 61.7% without changing anything observable, which is exactly the kind of error that needs a test rather than an eye -- TestSecretWeightingHitsSixtyOnePercent is the second, behavioural lock on the same door. A DECLARED CONSEQUENCE OF PUTTING THIS TABLE ON CHAIN, recorded here so the first person to notice finds the reasoning rather than reporting it: the two arrays publish, in this realm's own public source, which boast or fear each secret id undercuts, so an off-chain observer combining Secret/Boast/Fear can determine that a given gnome's secret targets his own boast. That is not a name, not a definition and not a rarity count, so it is inside TRAITS.md section 0 rule 2 -- and v6 already settles the precedent in the same direction, because eyesWeights is both "internal" per ECONOMY.md section 3 and published verbatim in v5's deployed source. "Internal" means NOT PRINTED ON A CARD, not absent from the source.

Open
OID
0fcf1c…66c8:24
secretTargetKind details

Inspect array

secretTargetKindBoast

int64

Value

0

secretTargetKindFear

int64

Value

1

secretMatchMultiplier

int64

Value

8

secretBaseWeight

int64

Value

1

legalStatLines

[]int64 (len=546)

legalStatLines: TRAITS.md section 4.2's 546 legal stat lines, ENUMERATED AT init() FROM THE RULE rather than transcribed as a literal table, and packed four stats to an int64. See buildLegalStatLines and init(). WHY ENUMERATED AND NOT A 546-ROW LITERAL -- auditability, not bytes: a transcription error in a 546-row table is undetectable by eye, and the rule it transcribes is four comparisons. TestLegalStatLinesIsExactly546 pins the count, TestEveryLegalStatLineSumsToTwentyTwoAndIsSpiky pins the property of every row, and TestLegalStatLineOrderIsStable pins that the enumeration order is the one this realm deployed with -- because the draw indexes into it, so the order is frozen the moment v7 deploys, exactly as numEyes/numNoses ordering already is. WHY AT init() AND NOT INSIDE settleCore: 10,000 iterations once, at deploy, instead of on every claimant's one transaction. The named risk, recorded rather than hidden: v5's own deploy used 131.9M gas of a 160M --gas-wanted, which is not a comfortable margin, so MEASURE THIS BEFORE DEPLOYING (DESIGN.md section 3.6). If it does not fit, the cheap fallback is to build the table lazily on the first Settle, paid once by whoever settles first; that is not the default because it makes one arbitrary claimant pay for everybody and puts a conditional in settleCore.

Open
OID
0fcf1c…66c8:330
legalStatLines details (len=546)

+546 more Inspect slice

namedEditionOrder

[8]string

namedEditionOrder: BATCH-ONE.md section 7's eight names, in the fixed order Settle's namedPick indexes into. The order carries no meaning -- it is not a ranking -- it only has to be fixed, which it is, forever, the moment this realm deploys. THE FIRST THREE ARE v5's AND v6's, IN v5's ORDER, AND THE FIVE ADDED BY v7 CHANGE 10 ARE APPENDED RATHER THAN INTERLEAVED. That is deliberate and it is the only thing about this ordering that is a decision: appending leaves hob/thistle/wick at indexes 0/1/2, so every worked example in this file's comments, in DESIGN.md and in the test suite that says "pick 0 is Hob" is still true, and the diff against v6 stays readable. Nothing reads this array by index except settleCore's own \`namedEditionOrder\[namedPick]\`, which does not care.

Open
OID
0fcf1c…66c8:29
namedEditionOrder details

Inspect array

namedEditionDisplayNames

[8]string

namedEditionDisplayNames: BATCH-ONE.md section 7's eight names AS THEY ARE WRITTEN -- "Hob, Thistle, Wick, Tally, Glim, Vigil, Spruce, Yonder", the form this file's own documentation uses throughout -- paired by index with namedEditionOrder above. WHY A SECOND ARRAY RATHER THAN JUST CAPITALISING namedEditionOrder, which is the change a reviewer will reach for first: namedEditionOrder's values are not labels, they are KEYS. settleCore writes characterOwner.Set(character, ...) and tests characterOwner.Has(candidate) with those exact strings, and the public CharacterOwner(name) reader does a raw tree Get with no case folding at all -- so CharacterOwner("hob") resolving is a chain fact that an off-chain caller already depends on, and init() registers the genesis gnome under the matching lowercase key "gaffer". Capitalising namedEditionOrder would move those keys to "Hob" while leaving "gaffer" lowercase, break CharacterOwner("hob") for every existing caller, and it is unfixable after deploy. The keys stay canonical. This array exists ONLY to supply the gnome's \`name\`, which is a DISPLAY STRING and has always been a different thing from its canonical key -- see canonicalNameKey's own comment on why the stored form and the uniqueness key are deliberately two strings. nameOwner is still keyed by canonicalNameKey(name), which folds "Hob" to "hob", so reservedNameKeys and the uniqueness index are untouched by this: no new mechanism, just the display side of a separation that was already here. TestNamedEditionDisplayNamesPairWithTheirCanonicalKeys pins the pairing in both directions, so an edit to either array that breaks it fails a test rather than shipping a one-of-one whose display name canonicalises to somebody else's key.

Open
OID
0fcf1c…66c8:31
namedEditionDisplayNames details

Inspect array

namedEditionDisplayName

func(character string) string

namedEditionDisplayName maps a named edition's canonical character key to its display name. Used by settleCore's mint and mirrored by the test fabricator, so there is exactly one copy of the mapping. Returns the input unchanged for anything that is not one of the eight editions -- which is unreachable from settleCore (it only ever passes a namedEditionOrder entry) and is a pass-through rather than a panic because this is a label lookup, not a security boundary.

Open
OID
0fcf1c…66c8:32
namedEditionDisplayName details

Inspect func

genesisGnomeName

untyped string

genesisGnomeName: DECISIONS.md 2026-10-15 settles the MECHANISM (minted to this realm's own address, non-transferable, never listable, separate from the batch) and until 2026-10-19 never settled a NAME anywhere in this project's documents -- "Genesis" was a placeholder that had been read as a decision for four days. SETTLED 2026-10-19 as SPRIG, and \*\*RE-SETTLED 2026-10-20 as GAFFER\*\*, which is what v8 is for. v7's argument for Sprig still reads well and was wrong on one axis it never checked: "it does not collide with any of the eight reserved names, any born-under sign, or any noun in LORE.md's fauna or flora." Every one of those tests is INTERNAL. None of them asks what the word already means to somebody outside this repository -- and \`Sprig\` is a funded product-research SaaS (formerly UserLeap), so to the startup and product audience this project is pitched at, the software reading arrives first. The owner caught it: \*"I don't really care too much about the minted guys because obviously it's random. But I do care about the main guy."\* He is right that the asymmetry matters. The sixty are drawn by a beacon and belong to whoever mints them; a name collision there is a private matter. THIS gnome fronts everything -- the group, the page, the reply to a stranger -- so his name is the only one that has to survive contact with the world. WHY GAFFER, and the test it passes that Sprig failed: eight candidates were searched on 2026-10-20 and all eight were already somebody's app (NAMES.md 3b). "Find an unused word" is not achievable, so the test is not "is it taken" but \*\*"which meaning arrives first for our reader"\*\*. For a British or Irish reader "the gaffer" is the old boy in charge, instantly, and the only software of that name is studio-niche VFX lighting. It also DESCRIBES him: the one gnome outside the batch, owned by nobody, who is the village rather than a villager. Runner-up was Pogle, whose sole namesake is Pogles' Wood (BBC, 1965) -- a clash so in-register it is closer to an ancestor. Sprig's own runner-up, Pip, was dropped for reading as a diminutive of somebody, and that reasoning still holds: this gnome is nobody's. NOT CHANGED, deliberately: \`Spruce\`, which collides with a decentralised-identity company that won an Ethereum Foundation grant and is therefore the only clash INSIDE this industry. It is one of the sixty, the owner said he does not care about those, and a realm is not a place to act on an unasked-for opinion. Recorded here because the next reader will wonder. CHANGED FROM v5 IN ONE RESPECT, deliberately: v5's comment ended "it is read by nothing else in this file", which was true there and is NOT true here. v6 fix 1 writes this string into the genesis gnome's own \`name\` field at init() and registers its canonical key in nameOwner, so changing it also changes which name is taken. It must still canonicalise to one of reservedNameKeys below, or an ordinary gnome could be named "Gaffer" and pose as it -- TestGenesisNameIsReserved pins exactly that, so an edit here that breaks the pairing fails a test rather than shipping.

Value

"Gaffer"

reservedNameKeys

[9]string

reservedNameKeys: the canonical name keys NO ORDINARY GNOME MAY EVER TAKE -- the eight named one-of-ones (BATCH-ONE.md section 7's roster, re-read rather than assumed: Hob, Thistle, Wick, and v7 change 10's Tally, Glim, Vigil, Spruce, Yonder) plus the genesis gnome. Checked by SetName; see assertNameAssignable. THE ARRAY LENGTH IS \[numNamedEditions + 1] AND THAT "+ 1" IS THE GENESIS GNOME, so adding a roster entry without adding its key here is a COMPILE ERROR rather than a silent hole -- which is the one piece of luck in change 10's four-places-in-step problem and the reason this file states the length arithmetically instead of writing 9. WHY THIS EXISTS SEPARATELY FROM THE nameOwner UNIQUENESS INDEX, which is the subtle part and the reason a reviewer should not "simplify" one of the two away: the uniqueness index only protects a name that is ALREADY TAKEN BY A LIVING TOKEN. Hob's cap-one edition may not have been minted yet -- its namedPick is a pure function of a drand round nobody has rolled -- and for the whole window before it mints, nameOwner has no "hob" key at all. Without this list, the FIRST ordinary claimant could name their gnome "Hob", take the key, and the real Hob would then mint into a collection where its own name already belongs to an impostor. Reservation covers the pre-mint window; uniqueness covers the post-mint one. Both are needed, and together they are the "two locks on a door" discipline this file already uses for the genesis gnome. Every entry is already in canonical form (lowercase, single-spaced, trimmed), and TestReservedNameKeysAreCanonical pins that -- a non-canonical entry here would be a key canonicalNameKey could never produce, i.e. a reservation that silently protects nothing.

Open
OID
0fcf1c…66c8:35
reservedNameKeys details

Inspect array

kinCousin

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

0

kinDisputed

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

1

kinInLaw

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

2

kinLatentKin

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

3

kinNeighbour

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

4

kinNotSpeaking

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

5

kinRival

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

6

kinSibling

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

7

kinTakenIn

int64

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

8

numKinTypes

untyped bigint

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

(9 <untyped> bigint)

numKinEdges

untyped bigint

The nine kinship type ids, FROZEN ALPHABETICALLY rather than in LINEAGE.md's own frequency order (cousin 39, neighbour 11, sibling 10, rival 9, not-speaking 5, in-law 4, disputed 3, latent-kin 2, taken-in 1), because the frequency order is a property of THIS batch and would change for Batch Two, whereas alphabetical is stable and self-checking. LINEAGE.md section 2.4 declares the vocabulary closed -- "the nine type values are the complete vocabulary; section 5 adds no tenth, and the rebuild at 60 added no tenth either" -- so the ids are safe to freeze. TestKinTypeNamesAreAlphabeticalAndNine pins both facts.

Value

(84 <untyped> bigint)

kinTypeNames

[9]string

kinTypeNames: the nine words, paired by index with the ids above. THIS IS A JUDGEMENT CALL AGAINST THE STRICTEST READING OF "WORDING OFF CHAIN", declared rather than slipped in, and the argument for it is three-part: the nine words are a CLOSED vocabulary LINEAGE.md declares complete and that the rebuild at 60 did not extend; they are one word each, carrying none of the comic register FUNNY.md revises weekly (the thing being revised is the 84 sentences, and not one of those is here); and without them the table is 84 triples of integers that no client can render into anything a human reads -- "#14 is a 7 of #6" is not a family. IF THE OWNER DISAGREES, THE CHANGE IS ONE FUNCTION. Drop KinTypeName and TotalKinTypes and the nine words live in LINEAGE.md keyed by id exactly as the thirty-two axis names do. The table and its enumerators are unaffected and nothing else moves.

Open
OID
0fcf1c…66c8:37
kinTypeNames details

Inspect array

kinEdge

type

kinEdge: one row of LINEAGE.md section 2.4, structure only. \`from\` is always the LOWER batch index; every edge is undirected except the three \`disputed\` rows, where \`from\` is the gnome doing the claiming -- and the claimer is the lower index in all three, which is that document's own constraint on how a dispute may be written rather than a coincidence.

Value

v9.kinEdge

kinEdges

[84]v9.kinEdge

kinEdges: LINEAGE.md section 2.4's fenced block, transcribed row by row IN ITS OWN ORDER, labels omitted. Frozen at deploy; NOTHING IN THIS FILE WRITES TO IT, EVER. WRITTEN AS 84 ONE-LINE STRUCT LITERALS WITH NAMED TYPE CONSTANTS, NOT AS PACKED INTEGERS. A row would squeeze into 16 bits (from 6, to 6, typ 4) and all 84 into eleven int64s, a tenth of the bytes. It is not worth it: the whole value of this table is that a reviewer can diff it against LINEAGE.md section 2.4 line by line, and {1, 9, kinSibling} does that where 0x0409 does not. Transcribing 84 rows is the single highest-risk clerical act in building v7, and the mitigation is that the source looks like the document PLUS TestKinEdgesMatchTheDocument and TestAllThirtyTwoOriginalEdgesSurvive -- not that the source is small.

Open
OID
0fcf1c…66c8:39
kinEdges details

Inspect array

plot

type

plot: unchanged from v4. plantedAt == 0 is the empty state; readyAt is stored, not computed, so a future change to growHours (there is none possible in this file, but the shape is kept for the day a v6 adds tuning back) could never reach backwards into an already-planted plot.

Value

v9.plot

gnome

type

gnome is the whole persisted pet. EVERY FIELD HERE HAS AN EXPORTED READER -- v4's own rule, carried over verbatim: a realm cannot grow a second chance at this after deploy. NOT HERE, DELIBERATELY: a hat/coat the OWNER chose (nothing is chosen at mint any more; hat/coat are ROLLED exactly as eyes/nose are, and are exactly as permanent as eyes/nose always were in v4 -- there has never been a function anywhere in this family of realms that changes a gnome's hat or coat after mint). v5's own comment also listed "a name" here; v6 fix 1 is precisely the correction of that, and \`name\` is below. character is "" for every ordinary gnome and one of namedEditionOrder's eight strings for a named one-of-one. eyes/nose/hat/coat are the sentinel -1/-1/""/"" for a named edition: its look is NOT a point in the ordinary 1,280-look space, it is the art pack's own character art (Hob's crown and gold coat, Thistle's helmet and axe, Wick's lantern, and v7 change 10's five: Tally's trowel, Glim's halo, Vigil's slate-and-ink coat, Spruce's top hat and moustache, Yonder's bandana and parrot), which this contract does not need to know the pixels of -- only that Character(tokenID) names it, so the app can render the right thing. A named edition's parts are therefore never counted in eyesCounts/noseCounts/hatCounts/coatCounts: it is already as rare as a thing can be, at cap one, counted instead through characterOwner.

Value

v9.gnome

listing

type

listing: unchanged in shape from v4, now simply one of possibly SEVERAL a single owner may have standing at once (mint one, hold many) -- listings is still keyed by tokenID, which was always the right key for this and needed no change.

Value

v9.listing

feeConfig

type

feeConfig: unchanged from v4.

Value

v9.feeConfig

pendingClaim

type

pendingClaim is the record Claim writes and Settle consumes: the permanent, irrevocable fact that this address spent its one roll, and the future drand round it is waiting on.

Value

v9.pendingClaim

nextID

v0.ID

Value

<zero>

balances

*v0.Tree

// owner address -> int64 acorns, per PLAYER, exactly as v4

Open
OID
0fcf1c…66c8:143
balances details

Inspect pointer

secretsDrawnMask

uint32

Value

131071

mintedCount

int64

// reservations made by Claim, gates batchCap; never decremented

Value

20

settledCount

int64

Value

19

genesisTokenID

string

// set exactly once, in init()

Value

"0000001"

rulesFrozen

bool

// always true: there are no rules to freeze or unfreeze in v5. See AdminState.

Value

true

transferGate

string

Value

""

assertFeeConfig

func(cfg v9.feeConfig, realmAddr .uverse.address)

assertFeeConfig: unchanged from v4's own gate.

Open
OID
0fcf1c…66c8:187
assertFeeConfig details

Inspect func

saleGateExtension

type

saleGateExtension: v4's shape, plus the genesis refusal. Panics on every transfer except the one Buy is performing right now, and ALWAYS panics for genesisTokenID regardless of transferGate -- there is no path, including a future bug in Buy, by which the genesis gnome should ever be transferable, so this checks it first and unconditionally.

Value

v9.saleGateExtension

AdminAllow

func(addr .uverse.address)

AdminAllow adds addr to the allowlist, so it may call Claim. Adding an address a second time is a harmless no-op. Adding an address that has already rolled changes nothing -- hasRolled is checked independently and is permanent regardless of allowlist state.

Open
OID
0fcf1c…66c8:188
AdminAllow details

Inspect func

AdminDisallow

func(addr .uverse.address)

AdminDisallow removes addr from the allowlist. It does NOT, and cannot, undo a roll already spent: hasRolled is a separate, permanent record, and this function never touches it. Removing an address that was never allowed, or that has already claimed, is a harmless no-op.

Open
OID
0fcf1c…66c8:189
AdminDisallow details

Inspect func

Claim

func()

Claim spends the caller's one-and-only roll on this realm and pins a drand round that does not exist yet. It mints NOTHING -- Settle does that, once the pinned round is actually published -- because the whole point of this split is that the result must not be computable, by anyone, including this transaction's own caller, at the moment the roll becomes irrevocable (MINT.md section 10.2: gno.land has no on-chain entropy at all, so anything decidable inside one transaction is decidable by the caller before broadcasting it). WALLET-ONLY, the same reasoning as v4's Adopt: a gnome minted to a realm's address could never be recovered, so a batching, sponsor or multicall realm must never be able to spend a roll on anyone's behalf. The \`gnokey maketx run\` shape is refused by the identical gate, for the identical reason (IsUserCall is false for both).

Open
OID
0fcf1c…66c8:190
Claim details

Inspect func

pendingClaimFor

func(claimant .uverse.address) *v9.pendingClaim

pendingClaimFor looks up claimant's pending roll, shared by both Settle and SettleWithSignature so the "no pending claim" message and lookup logic exist exactly once.

Open
OID
0fcf1c…66c8:191
pendingClaimFor details

Inspect func

Settle

func(claimant .uverse.address)

Settle computes and permanently mints the result of claimant's pending roll, once the round Claim pinned has actually been published to the beacon by SOMEBODY -- another claimant's own SettleWithSignature, a relay, anyone. It does not publish anything itself; call SettleWithSignature instead if the round is not published yet and the caller has its signature to offer. ANYONE may call this -- the claimant themself, the project's own relayer, a stranger -- because by the time it can succeed at all, nothing is left to decide: the drand signature for the pinned round either exists publicly or it does not, and the parts this mints are a pure function of (that signature, the claimant's own address), fixed the instant the round is published, not the instant Settle happens to be called. Simulating this call before calling it for real previews a fact, not a choice -- exactly the property MINT.md section 10.5 names as the whole fix: "reading the result afterward is a free query, same as any other realm read." Refuses coins for the same reason every other non-paying function here does: there is no banker to return them, and this is a free, permissionless computation, never a payment.

Open
OID
0fcf1c…66c8:192
Settle details

Inspect func

SettleWithSignature

func(claimant .uverse.address, sigHex string)

SettleWithSignature is Settle, plus: if claimant's pinned round has not been published yet, this function publishes it ITSELF first, in this same transaction, using sigHex -- the round's drand signature, which the caller's own app fetches read-only from drand's public API ([https://api.drand.sh/](https://api.drand.sh/)\<chain>/public/\<round> or any mirror) and passes straight through unmodified. This is the function a player's app should call once it has fetched a signature; Settle (above) remains for a caller with no signature to offer, or that already knows (or does not care) that somebody else has published the round. SAFE EITHER WAY THE ROUND TURNS OUT TO BE, which is the one property this function exists to guarantee and the live chain fact its shape is built around (see the package doc): beacon/v2's own Publish PANICS, not no-ops, when the round is already published, so this function checks beacon.Published first and only calls Publish if that check says no. If the round was published by someone else (another claimant's own SettleWithSignature, a relay, anyone) between the moment the caller fetched sigHex and the moment this transaction lands, sigHex is simply never used -- this function still succeeds, exactly as Settle would have. A bad sigHex, by contrast, is a real failure: beacon.Publish rejects it with its own "beacon: invalid signature" panic, which aborts this whole call (and the claim stays pending, to be retried with a correct signature or left for Settle once someone else relays it). Refuses coins for the same reason Settle does and every non-paying function here does: there is no banker to return them, and this is a free, permissionless computation -- the only payment anywhere in this call is the ordinary GNOT gas the caller's own transaction spends, to the network, never to this project (MINT.md section 14).

Open
OID
0fcf1c…66c8:193
SettleWithSignature details

Inspect func

settleCore

func(claimant .uverse.address, pc *v9.pendingClaim)

settleCore is Settle's and SettleWithSignature's shared tail: the draw, the mint, and every write that follows, once the caller above has already confirmed the pinned round is published. Takes no \`cur realm\` because it makes no cross-realm call of its own -- beacon.Randomness and beacon.Published below are plain reads, not crossing functions, exactly as they always were before this change.

Open
OID
0fcf1c…66c8:194
settleCore details

Inspect func

popcount32

func(v uint32) int

popcount32 counts the set bits in secretsDrawnMask, for settleCore's accounting check. Written out rather than imported (math/bits is not used anywhere else in this file) and bounded at 32 iterations, which is free next to a SHA-256.

Open
OID
0fcf1c…66c8:195
popcount32 details

Inspect func

seedDomain

untyped string

seedDomain: a version tag, so a seed derived by this realm can never collide with one derived by any other realm or any later version from the same round and claimant. It is part of the hashed input and therefore frozen the moment v7 deploys. seedDomain DELIBERATELY STILL SAYS v7, and this is a decision rather than the oversight it looks like. tools/check-realm-version.js exempts it by exact text and says so. It is a cryptographic domain constant: every gnome's traits are sha256 of it concatenated with the round, the claimant and a purpose. Bumping it to "v9" therefore RE-ROLLS EVERY POSSIBLE GNOME -- a given (round, claimant) draws something different. v9 exists to rename ONE gnome; re-rolling the whole collection is a far larger change than the one that was asked for, and doing it as a side effect of tidying a string would be the wrong kind of quiet. Measured, not assumed: bumping it to v9 made TestNamedEditionCanOnlyEverBeMintedOnce fail, because an earlier test's draw began hitting "wick" where it previously had not. Realm state persists across test functions, so the suite's fixtures depend on the draw. That failure is the warning working -- see gnomod.toml item (14) -- not a test to be adjusted. Nothing leaks by sharing a domain with v5-v8. The randomness is public drand and the derivation is published, so anybody can already compute their own gnome off chain; that is the point of TestSeedForMatchesAnOffChainDerivation. Sharing the domain only means the same inputs give the same gnome on two realms, one of which is abandoned.

Value

"gnogotchi/garden/v7/seed/v1"

purposeStrings

[18]string

purposeStrings: EVERY purpose literal v7 passes to seedFor, declared once so that the properties the independence proof rests on are checkable by a test rather than believed. DESIGN.md section 4.2. Three things must hold and each has a test: TestPurposeStringsAreDistinct (a duplicate would silently weld two draws together -- bond and boast sharing a seed would make a gnome's bond a deterministic function of his boast across the whole batch, invisible in any single gnome), TestNoPurposeContainsTheSeparator (so the hashed input is uniquely decodable and two distinct (claimant, purpose) pairs can never build the same string), and TestEveryPurposeLiteralIsInTheDeclaredList (so a nineteenth draw added later cannot escape the other tests). FROZEN AT DEPLOY, ASYMMETRICALLY: adding a purpose later is impossible (the realm is immutable) so this list has to be complete now, and CHANGING one is worse than impossible -- it would re-roll every gnome already minted.

Open
OID
0fcf1c…66c8:197
purposeStrings details

Inspect array

seedFor

func(randomnessHex string, claimant .uverse.address, purpose string) uint64

seedFor derives one independent-looking 64-bit value from the published drand randomness, the claimant's own address, and a purpose string -- so a single drand round can safely drive several unrelated draws (which named edition, which eyes, which nose, which hat/coat, and in v7 fourteen more) for several different simultaneous claimants without any of them correlating with each other. SIGNATURE, SEPARATOR, ARGUMENT ORDER AND PURPOSE MECHANISM ARE v6's, UNCHANGED; only the hash moved (see the block above). The first eight bytes of the digest, big-endian, assembled by hand rather than through encoding/binary so the import list grows by exactly one entry and the byte order is visible in the source instead of in a package somebody has to go and read.

Open
OID
0fcf1c…66c8:198
seedFor details

Inspect func

drawIndex

func(seed uint64, n int64) int64

drawIndex is the one unweighted bounded draw in v7: int64(seed % uint64(n)), and that is all. It exists so that every unweighted bounded draw goes through ONE line of arithmetic that one person audits once, instead of nine inline % expressions scattered through drawRoll. ON MODULO BIAS, because this is the line an auditor should stop on. For a uniform 64-bit seed and modulus m, the maximum relative deviation of any outcome's probability from 1/m is strictly less than m / 2^64. v7's moduli: 2, 4, 16, 32 and 64 divide 2^64 and are therefore EXACTLY unbiased; 3 and 5 are under 2.8e-19; 31/38/45 (the secret's weighted total) under 2.5e-18; 60 (v6's named pick) under 3.3e-18; 100 (v6's part tables) under 5.4e-18; and 546 (the stat line), the worst case in the realm, under 3.0e-17 -- about seventeen orders of magnitude below the probability that drand's own threshold signature scheme is broken. It is not zero and this file does not claim it is. THIS IS A DECLARED DISAGREEMENT WITH TRAITS.md section 2.2 and section 4.2, both of which instruct rejection sampling ("reject a byte >= 256 - 256 mod n", "draw an index 0-545 by rejection sampling from ten bits"). Those instructions are right about the draws they were written for and the difference is the WIDTH of the draw, not the use of %: from a single BYTE, % 5 really does skew one outcome in five by 0.4%, and from TEN BITS against 546 the skew is 1.87:1 -- rejection is mandatory in both cases. From a 64-bit draw neither hazard exists at any scale anyone can measure. And rejection is not free in the one function that most needs to stay simple: it needs a fresh seed per attempt (MORE seed-derivation surface, not less), an attempt cap (an unbounded loop in Settle is worse than a 1e-17 bias), and a documented deterministic fallback at the cap -- three new things, each with its own failure mode, to remove a deviation of one part in 3e16. If a reviewer wants rejection anyway, the change is confined to this function and nothing else in the design moves.

Open
OID
0fcf1c…66c8:199
drawIndex details

Inspect func

drawWeighted

func(weights []int64, totalWeight int64, seed uint64) int

drawWeighted picks an index into weights (which TestWeightTablesSumToOneHundred pins as summing to totalWeight) using seed as the source of randomness -- a plain cumulative-sum bucket walk, MINT.md section 10.5's own recommendation for what Settle should do ("a simple, bounded index... easy to audit"), applied to a weight table instead of a published array, which is the same shape of arithmetic.

Open
OID
0fcf1c…66c8:200
drawWeighted details

Inspect func

numLegalStatLines

untyped bigint

numLegalStatLines: TRAITS.md section 4.2's own count, re-derived from the rule rather than quoted -- 670 four-tuples in 1-10 sum to 22, and 546 of those have a stat >= 8 and a stat \<= 3.

Value

(546 <untyped> bigint)

packStatLine

func(a int64, b int64, c int64, d int64) int64

packStatLine / unpackStatLine: four stats, each 1-10, in one int64, one byte each. A packing here is safe in the way a packing of the GNOME's fields would not be (see the gnome struct's own comment): this word is a deploy-time table entry that no client ever sees and no stored gnome ever holds -- settleCore writes the four stats out as four explicit fields.

Open
OID
0fcf1c…66c8:201
packStatLine details

Inspect func

unpackStatLine

func(packed int64) (a int64, b int64, c int64, d int64)
Open
OID
0fcf1c…66c8:202
unpackStatLine details

Inspect func

buildLegalStatLines

func() []int64

buildLegalStatLines enumerates TRAITS.md section 4.2's legal set ONCE, FROM THE RULE: "22 points across four stats, each in 1-10, with at least one stat >= 8 and at least one \<= 3". Four nested loops, four comparisons, 10,000 iterations, at deploy. TRAITS.md section 4.2's instruction is explicit and this follows it: "enumerate the legal set once (it is small and fixed), then draw uniformly from it. Do NOT roll four stats and repair them -- a repair loop is a modulo-bias bug and a fairness argument waiting to happen." THE ENUMERATION ORDER IS FROZEN THE MOMENT THIS REALM DEPLOYS, because drawRoll indexes into it. TestLegalStatLineOrderIsStable pins it with a checksum over the whole table.

Open
OID
0fcf1c…66c8:203
buildLegalStatLines details

Inspect func

roll

type

roll is everything one claimant's draw produces: one field per stored value, and nothing else. It is a plain value with no pointers and no realm state, returned by drawRoll and consumed by assertRoll and settleCore's commit block.

Value

v9.roll

drawRoll

func(randomnessHex string, claimant .uverse.address, batchIndex int64, taken uint32) v9.roll

drawRoll is PHASE 1: the whole audit surface for v7's fairness, as a pure function of its four arguments. The draws are listed in the order they are computed, which is fixed -- NOTHING DEPENDS ON THE ORDER, because every draw has its own purpose string and therefore its own seed, so reordering them would change nothing -- but it is fixed anyway, because a fixed order is what makes "re-derive it yourself" a checkable instruction. batchIndex is settledCount+1 and taken is secretsDrawnMask; both are passed in, and they are the only order-dependent inputs in the whole draw. Everything else is (randomnessHex, claimant), which is fixed the instant the pinned round is signed by drand.

Open
OID
0fcf1c…66c8:204
drawRoll details

Inspect func

dominanceFor

func(randomnessHex string, claimant .uverse.address) [5]int64

dominanceFor draws the dominance order: Fisher-Yates over {0,1,2,3,4}, descending -- swap slot 4 with a slot drawn from 0-4, slot 3 with one from 0-3, slot 2 from 0-2, slot 1 from 0-1. Four draws, four distinct seeds, four distinct moduli. TRAITS.md section 2.2's two prohibitions are both honoured and both are worth restating as the things NOT to do: - NOT seed % 120. 120 is not a power of two, and more importantly a single index would need the Lehmer codec the gnome struct's own comment rejects. - NOT seed % 5 FIVE TIMES. That maps 5^5 = 3,125 outcomes onto 120 permutations non-uniformly and is the classic naive-shuffle bug. Four descending moduli is the correct Fisher-Yates, and TestDominanceMatchesAnIndependentFisherYates compares this against a reference implementation written separately, which is the only way to catch somebody reintroducing the naive version later. ALL 120 ORDERS ARE LEGAL, so there is no post-draw constraint, no rejection and no loop. That is the realm-side dividend of TRAITS.md section 2.1's two-state recommendation, and it is a stronger argument for two states than any of the four section 2.1 gives: under THREE states with FUNNY.md section 6.5's fix applied, only 96 of the 120 orders would be legal (every order except the twenty-four that put Temperament in the single dormant slot), and an illegal-order exclusion is a REJECTION LOOP INSIDE THE DRAW -- Fisher-Yates, then a check, then a redraw -- with an unbounded worst case and a fallback nobody wants to specify, in the one function BATCH-ONE.md:120 is a warning about. Two states make all 120 legal, so the draw has no constraint, no check and no loop.

Open
OID
0fcf1c…66c8:205
dominanceFor details

Inspect func

secretWeightsFor

func(boast int64, fear int64) ([17]int64, int64)

secretWeightsFor builds one gnome's secret weight table from TRAITS.md section 3.3's rule: base weight 1 for all seventeen, MULTIPLIED BY 8 if the secret's target matches this gnome's rolled boast or rolled fear. One multiplier, one comparison, no second table. The total it returns is 31 + 7k with k in {0, 1, 2} -- 31, 38 or 45 and NO OTHER VALUE -- which is the check that the table is right, because every gnome's boast carries exactly two secrets and k is the number aimed at his rolled fear (2 for Beetles, 1 for Being Forgotten / Being Laughed At / Slugs, 0 for The Dark and Shine). TestSecretWeightTotalIsAlwaysThirtyOneOrThirtyEightOrFortyFive pins exactly that over every (boast, fear) pair, exhaustively. THIS IS THE ONE PLACE IN v7 WHERE TRAIT DATA IS COMPARED TO ANYTHING, and it is worth being precise about what the comparison is and is not: it reads a gnome's own just-drawn boast and fear, and it produces a weight table that produces a secret id. It gates nothing, it changes no fee, it grants no eligibility, and it never sees another gnome. The invariant stated once: NO FUNCTION IN v7 BRANCHES ON A TRAIT, A STAT, A LINEAGE EDGE OR A SECRET -- not in a guard, not in a loop condition, not in a fee calculation, not in an eligibility check -- and this weighting is the single exception the invariant is written around.

Open
OID
0fcf1c…66c8:206
secretWeightsFor details

Inspect func

secretFor

func(randomnessHex string, claimant .uverse.address, boast int64, fear int64, batchIndex int64, taken uint32) int64

secretFor is the one genuinely new mechanism in v7 and the one to read twice. THE RULE BEING IMPLEMENTED (TRAITS.md section 3.2.2, agreed by the owner 2026-10-17): "The first seventeen mints draw seventeen distinct secrets. For tokens 1-17 the weighted draw of section 3.3 runs over the not-yet-drawn subset of the pool only. From token 18 onward the draw is the full seventeen with duplicates free." IT IS NOT IMPLEMENTED AS THE LITERAL SUBSET DRAW, and the reason is the flaw v6 exists to document. A renormalised weighted draw over the free secrets has a modulus of "the sum of the weights of the free secrets" -- A SHRINKING, OBSERVABLE, TIME-VARYING NUMBER, which is v6's package doc word for word: "THAT IS PEEKABLE AND GAMEABLE... a claimant whose seed is already fixed can simply precompute, for every possible remainingTotal, whether that value would win them \[the thing they want] -- and then wait, costing nothing." Worse, it is not monotone: removing a secret reshuffles which index every seed maps to, so waiting CAN improve the outcome. It would also need DIFFERENT ARITHMETIC IN THE TWO PATHS -- a filtered cumulative walk for the first seventeen and an unfiltered one for the rest -- which is BATCH-ONE.md:120's warning exactly. Instead: one weighted walk, called IDENTICALLY by the first gnome and the sixtieth, plus a probe with a fall-through made of two modulos by compile-time constants. THE PROBE IS NOT A NEW INVENTION: it is v6's own audited named-edition pattern generalised -- draw against a constant modulus; if what you drew is taken, fall through -- with the fall-through made a full cycle instead of one step deep. SIX PROPERTIES, EACH WITH ITS PROOF, because this is where the audit will live. P1 -- IT ALWAYS TERMINATES, AND INSIDE THE WINDOW IT ALWAYS RETURNS A FREE SECRET. The loop is bounded at numSecrets iterations by construction. Inside the window at most batchIndex-1 <= 16 bits of `taken` are set, and by P2 the walk visits all seventeen ids, so at least one is free. The panic below is therefore unreachable; it is named rather than omitted, v6's own discipline. P2 -- THE WALK VISITS ALL SEVENTEEN. numSecrets = 17 is PRIME, so gcd(stride, 17) = 1 for every stride in [1, 16], so {first + i*stride mod 17 : i = 0..16} is all of Z/17. TestSecretProbeVisitsAllSeventeen proves it EXHAUSTIVELY over all 17 x 16 = 272 (first, stride) pairs, and TestSecretPoolSizeIsPrime pins the precondition. P3 -- THE STRIDE DRAW HAS EXACTLY ZERO MODULO BIAS. 16 divides 2^64. P4 -- THE ONLY REALM STATE CONSULTED IS `taken`, AND ONLY INSIDE THE WINDOW. drawRoll takes it as an argument. There is no other global read anywhere in the draw. P5 -- MONOTONICITY: WAITING CAN NEVER IMPROVE A CLAIMANT'S OUTCOME. `taken` only ever gains bits. A claimant's probe ORDER is fixed by his own seed the instant the pinned round publishes and cannot change. So if T1 is a subset of T2, the first free id in a given walk under T1 sits at an index less than or equal to its index under T2. THERE IS NO SETTLE-TIMING STRATEGY HERE, which is the exact property v6's package doc identifies as the one that matters ("nobody can improve THEIR OWN odds by choosing when to settle") -- and in the stronger direction, because the incentive actively points at settling promptly rather than merely being indifferent. TestSecretIsMonotoneUnderWaiting is the proof. P6 -- THE RESIDUAL, STATED PRECISELY AND NOT ASPIRATIONALLY. A third party can settle OTHER claims before a claimant settles his, pushing him further down his own fixed probe list. That costs the griefer gas and buys him nothing, and the ceiling on the harm is TRAITS.md section 3.2.2 note 1's own already-accepted cost: "his gnome may hold a secret that undercuts nothing he says." All seventeen secrets are one line each and note 2 is explicit that "nobody's secret is better than anybody else's" -- so this is a transaction-ordering race over an outcome with no prize, the same category v6 already analysed and accepted for the named-edition collision, at strictly lower stakes. WHAT THE DEVIATION FROM THE LITERAL RULE COSTS, measured rather than waved at: the probe and the subset draw agree EXACTLY at batchIndex 1 (nothing taken, so both are the plain weighted draw) and are FORCED IDENTICALLY at batchIndex 17 (a pool of one). In between they differ only in WHICH free secret a claimant lands on, never in how many free secrets there are. TRAITS.md section 3.2.2 note 1 has already priced that as "a fractional softening of a soft rule" against "a guaranteed solvable acrostic".

Open
OID
0fcf1c…66c8:207
secretFor details

Inspect func

assertRoll

func(r v9.roll, taken uint32)

assertRoll is PHASE 2: every range assertion about a roll, in one block, before anything is committed. EVERY ONE OF THESE IS UNREACHABLE IF drawRoll IS CORRECT -- which is exactly v6's own discipline for its batch-accounting check ("named so this function is correct on its own terms"), and it means a drawRoll bug produces a LOUD ABORT rather than a silently malformed gnome that can never be fixed, on a realm that cannot be edited. taken is the SAME secretsDrawnMask that was handed to drawRoll, and it is here for the one assertion that range-checking cannot express: FREEDOM, not merely range. See the secret check below for why this one abort has to happen on the offending mint rather than one settle later.

Open
OID
0fcf1c…66c8:208
assertRoll details

Inspect func

Wake

func(tokenID string)

Wake makes tokenID the caller's awake gnome. Free, and limited to once per UTC day -- the same day-boundary discipline SaveDay's own dailyCareDay already uses, chosen over a GNOT fee for the reason MINT.md section 2a gives: "a cost that cannot be bought, only lived".

Open
OID
0fcf1c…66c8:209
Wake details

Inspect func

saveAction

type

Value

v9.saveAction

SetName

func(tokenID string, name string)

SetName names tokenID, which the caller must currently own, or RENAMES it. Free, unlimited, and refused for the eight named one-of-ones and the genesis gnome. RENAMING IS ALLOWED, NOT ONE-SHOT, and this is a deliberate decision with a named trade-off rather than an oversight. On an immutable realm a one-shot name means a typo is forever, and "my gnome is called Fenncik" is a worse permanent outcome than "somebody changed their mind". Aavegotchi, the closest prior art this project has looked at, allows renaming. What it costs: a name is not a stable identifier over time, so nothing anywhere -- in this realm, in the adventure realm, or in the app -- may ever key anything on a name. tokenID is the identifier; the name is a label. (The nameOwner index is keyed BY name only to answer "is this taken", never to identify a gnome; every function here looks tokens up by tokenID.) Refuses coins for the same reason every other non-paying function here does: there is no banker to return them, and naming your own gnome is free (MINT.md section 14 -- batch one is free end to end, and this is not a new place to charge).

Open
OID
0fcf1c…66c8:212
SetName details

Inspect func

assertNameAssignable

func(name string, tokenID string)

assertNameAssignable is SetName's refusal gate: it panics with nameAssignabilityProblem's message, or returns normally if name may be assigned to tokenID. The rules themselves are in nameAssignabilityProblem, not here, so the free NameAvailability query and this enforcing path can never disagree about what is allowed -- there is exactly one copy.

Open
OID
0fcf1c…66c8:213
assertNameAssignable details

Inspect func

nameAssignabilityProblem

func(name string, tokenID string) string

nameAssignabilityProblem is THE one copy of the naming rules. It returns "" if name may be assigned to tokenID, or the exact player-meaningful message explaining which rule refused it. IT RETURNS ITS REFUSAL RATHER THAN PANICKING IT so that NameAvailability can offer the same answer as a free read without any recover() -- see NameAvailability's own note on why recover is avoided on this toolchain. ORDER IS DELIBERATE AND MATCHES app/js/config.js's nameError, which mirrors v4's realm order: length, then charset, then visible content, then (new in v6) reservation, then uniqueness. A name that fails several rules reports the first, so the app's own message and the realm's refusal always name the SAME rule for the same input rather than disagreeing about which problem is the problem.

Open
OID
0fcf1c…66c8:214
nameAssignabilityProblem details

Inspect func

List

func(tokenID string, priceUgnot int64, buyer .uverse.address, days int64)

List offers tokenID, which the caller must own, for sale. The lock: a named one-of-one (character != "") cannot be listed until it has lockCaredDays cared days; an ordinary gnome has no such restriction, trading from the moment it is minted (MINT.md section 12/13).

Open
OID
0fcf1c…66c8:215
List details

Inspect func

Cancel

func(tokenID string)

Cancel withdraws the caller's own listing on tokenID. Only the seller who created it may.

Open
OID
0fcf1c…66c8:216
Cancel details

Inspect func

Buy

func(tokenID string)

Buy purchases the listed gnome tokenID. v4's validate-then-commit shape, unchanged, MINUS the one-gnome-per-address refusal: MINT.md section 2a removes the hold cap from Buy specifically, and only from Buy -- Claim's hasRolled gate is the cap that still matters, and this function never touches it.

Open
OID
0fcf1c…66c8:217
Buy details

Inspect func

Listing

func(tokenID string) (seller .uverse.address, priceUgnot int64, buyer .uverse.address, listedAtUnix int64, expiresAtUnix int64, feeUgnot int64, totalUgnot int64, found bool)
Open
OID
0fcf1c…66c8:218
Listing details

Inspect func

TotalListings

func() int64
Open
OID
0fcf1c…66c8:219
TotalListings details

Inspect func

ListingsPage

func(offset int, limit int) []string
Open
OID
0fcf1c…66c8:220
ListingsPage details

Inspect func

QuoteFee

func(priceUgnot int64) (feeUgnot int64, totalUgnot int64)
Open
OID
0fcf1c…66c8:221
QuoteFee details

Inspect func

Provenance

func(tokenID string) (adoptedAtUnix int64, acquiredAtUnix int64, saleCount int64, found bool)
Open
OID
0fcf1c…66c8:223
Provenance details

Inspect func

Name

func(tokenID string) (name string, found bool)

Name returns tokenID's name, or found=false if there is no such token. A name of "" is a REAL, EXPECTED ANSWER, not an error: it means this gnome has not been named yet, which is the state every ordinary gnome mints in. found reports whether the TOKEN exists, never whether it has a name -- check the string for that.

Open
OID
0fcf1c…66c8:224
Name details

Inspect func

NameOwner

func(name string) (tokenID string, found bool)

NameOwner resolves a name to the tokenID currently carrying it, matching the same way SetName refuses duplicates: on the canonical form, so any capitalisation or spacing of a taken name finds it. This is the uniqueness index read back, and the honest answer to "who is Fennick".

Open
OID
0fcf1c…66c8:225
NameOwner details

Inspect func

NameAvailability

func(name string, tokenID string) (ok bool, reason string)

NameAvailability answers "would SetName accept this name, and if not why" WITHOUT a transaction -- a free query against the same assertNameAssignable the write path uses, so the realm itself is the authority the app checks against instead of a second copy of the rules that can drift from it. reason is "" when ok is true, and the realm's own refusal message otherwise. The tokenID argument matters for exactly one case: a name is available TO THE TOKEN THAT ALREADY HOLDS IT (re-capitalising your own gnome's name). Pass "" to ask the general question "is this name free for somebody new", which is what an app naming a fresh gnome wants. THIS IS A CONVENIENCE, NOT THE AUTHORITY, and the distinction is load-bearing: a true answer here can be made false by somebody else's transaction landing first. SetName re-checks everything itself, always -- this function cannot be used to skip a check, only to avoid wasting a player's gas on a refusal that was already knowable. NOTE ON SHAPE, because the obvious implementation is a trap on this toolchain: this does NOT call SetName's validator and recover() from its panic. \`recover()\` does not reliably catch a panic across a realm boundary in Gno (MEMORY.md records this costing this project real time), and a reader should not have to reason about whether a same-realm call is the exception. So the rules live in nameAssignabilityProblem, which RETURNS its refusal instead of panicking it, and both this reader and assertNameAssignable call that one copy. No recover anywhere.

Open
OID
0fcf1c…66c8:226
NameAvailability details

Inspect func

SettledRound

func(tokenID string) (round int64, found bool)

SettledRound returns the drand round whose published signature produced tokenID's parts -- v6 FIX 2, and the one query that makes this project's "provably unrigged" claim (ECONOMY.md section 1) checkable by a holder rather than merely asserted at them. Fetch [https://api.drand.sh/](https://api.drand.sh/)\<chain>/public/\<round>, hash its signature, and re-derive the parts with this file's own seedFor/drawWeighted against your own address: a round signed by drand before anybody knew who would pin it cannot have been chosen to produce a particular gnome. round == 0 means this token was never produced by a roll, which is true of exactly one token ever: the genesis gnome. found reports whether the token exists.

Open
OID
0fcf1c…66c8:227
SettledRound details

Inspect func

MaxNameLen

func() int64

MaxNameLen is maxNameLen read back, so the app can show the limit it will be held to instead of hardcoding a second copy of the number (app/js/config.js's NAME\_MAX\_LEN is the other copy, and the two are kept equal by hand and by TestMaxNameLenMatchesTheAppsRule).

Open
OID
0fcf1c…66c8:228
MaxNameLen details

Inspect func

GnomeByToken

func(tokenID string) (hat string, coat string, eyes int64, nose int64, character string, level int64, gnomeType string, adoptedAtUnix int64, found bool)

GnomeByToken: the basic facts for one token -- hat, coat, eyes, nose, character (empty for an ordinary gnome), level, calling and the moment it was minted. SIGNATURE UNCHANGED FROM v5 -- the name is Name(tokenID) and the round is SettledRound(tokenID); see the note above.

Open
OID
0fcf1c…66c8:229
GnomeByToken details

Inspect func

ActiveGnome

func(addr .uverse.address) (tokenID string, found bool)

ActiveGnome returns the tokenID that is awake for addr right now, or found=false if nothing is (never claimed or bought a gnome, or sold the only one and has not Woken another since).

Open
OID
0fcf1c…66c8:230
ActiveGnome details

Inspect func

OwnerTokens

func(addr .uverse.address) []string

OwnerTokens returns every tokenID addr currently holds, in no particular order. Unbounded and unpaginated deliberately: batchCap is 60 (v7's own number -- v5 and v6 argued this at 24) and the genesis gnome is permanently unreachable to any wallet, so no address can ever hold more than batchCap tokens. Sixty token ids in one return is still a small, fixed, compile-time-bounded list well inside what a single read returns. THE NUMBER IN THIS COMMENT IS PART OF THE DELIVERABLE: a false comment on an immutable realm is permanent.

Open
OID
0fcf1c…66c8:231
OwnerTokens details

Inspect func

TokensPage

func(offset int, limit int) []string

TokensPage: the global token enumerator, hollowmere/v1's own pattern (TotalClaimants/ ClaimantsPage) applied here, because v4's own missing-enumerator lesson (DECISIONS.md:780-791, arena/v1 shipping with no challenge inbox and no way to ever add one) is unavailable to fix after deploy and twenty lines now is cheaper than "unavailable forever" later.

Open
OID
0fcf1c…66c8:232
TokensPage details

Inspect func

GardenState

func(tokenID string) (plantedAtCsv string, readyAtCsv string, readyCount int64, found bool)
Open
OID
0fcf1c…66c8:233
GardenState details

Inspect func

CareState

func(tokenID string) (belly int64, cheer int64, acorns int64, totalXp int64, lastPatAtUnix int64, lastHappyEventAtUnix int64, lastActionAtUnix int64, found bool)
Open
OID
0fcf1c…66c8:234
CareState details

Inspect func

MeterState

func(tokenID string) (belly int64, bellyAtUnix int64, cheer int64, cheerAtUnix int64, found bool)
Open
OID
0fcf1c…66c8:235
MeterState details

Inspect func

Progress

func(tokenID string) (xpGarden int64, xpTinker int64, harvestCount int64, lastTinkerAtUnix int64, caredDayCount int64, dailyCareXpSpent int64, dailyCareDay int64, found bool)
Open
OID
0fcf1c…66c8:236
Progress details

Inspect func

CaredDays

func(tokenID string) (daysCsv string, found bool)
Open
OID
0fcf1c…66c8:237
CaredDays details

Inspect func

Mood

func(tokenID string) (mood string, found bool)
Open
OID
0fcf1c…66c8:238
Mood details

Inspect func

IsLocked

func(tokenID string) (locked bool, found bool)

IsLocked reports whether tokenID is currently refused by List's lockCaredDays check -- true only for a named one-of-one with fewer than lockCaredDays cared days; false for every ordinary gnome, always, and false for a named one-of-one once it has enough cared days.

Open
OID
0fcf1c…66c8:239
IsLocked details

Inspect func

CollectionInfo

func() (name string, symbol string)
Open
OID
0fcf1c…66c8:240
CollectionInfo details

Inspect func

BalanceOf

func(addr .uverse.address) int64
Open
OID
0fcf1c…66c8:242
BalanceOf details

Inspect func

OwnerOfToken

func(tokenID string) (owner .uverse.address, found bool)
Open
OID
0fcf1c…66c8:243
OwnerOfToken details

Inspect func

Acorns

func(addr .uverse.address) (acorns int64, found bool)
Open
OID
0fcf1c…66c8:245
Acorns details

Inspect func

EyesCount

func(id int64) int64
Open
OID
0fcf1c…66c8:246
EyesCount details

Inspect func

NoseCount

func(id int64) int64
Open
OID
0fcf1c…66c8:247
NoseCount details

Inspect func

HatCount

func(hat string) int64
Open
OID
0fcf1c…66c8:248
HatCount details

Inspect func

CoatCount

func(coat string) int64
Open
OID
0fcf1c…66c8:249
CoatCount details

Inspect func

CharacterOwner

func(name string) (tokenID string, found bool)

CharacterOwner resolves one of the eight named one-of-ones, or the genesis gnome under its own name "gaffer", to its tokenID, once minted. (Second-audit-pass nitpick: an earlier version of this comment said only "one of the three named one-of-ones", which stopped being true the moment init() registered "gaffer" here too -- see init()'s own comment for why that registration is safe and never touches the editions' cap. The count in it then went stale a second way when v7 change 10 took the roster to eight; both are fixed here.)

Open
OID
0fcf1c…66c8:250
CharacterOwner details

Inspect func

IsAllowlisted

func(addr .uverse.address) bool
Open
OID
0fcf1c…66c8:251
IsAllowlisted details

Inspect func

HasRolled

func(addr .uverse.address) bool
Open
OID
0fcf1c…66c8:252
HasRolled details

Inspect func

PendingClaim

func(addr .uverse.address) (round int64, claimedAtUnix int64, found bool)
Open
OID
0fcf1c…66c8:253
PendingClaim details

Inspect func

BatchStatus

func() (mintedSoFar int64, settledSoFar int64, batchCapOut int64, namedMinted int64, namedCapOut int64)

BatchStatus: mintedCount is reservations made by Claim (gates batchCap); settledCount is completions made by Settle (always \<= mintedCount, the gap being pending, unsettled claims); namedMinted is how many of the eight named one-of-ones have actually been minted so far -- read live off characterOwner rather than a separate counter, so there is only ever one source of truth for "has this edition been taken".

Open
OID
0fcf1c…66c8:254
BatchStatus details

Inspect func

Traits

func(tokenID string) (temperament int64, obsession int64, bond int64, fear int64, boast int64, found bool)

Traits returns the five rolled axis ids, in AXIS order (0 temperament, 1 obsession, 2 bond, 3 fear, 4 boast) and NOT in dominance order. All five are 0 for the genesis gnome, which is the one token never produced by a roll.

Open
OID
0fcf1c…66c8:255
Traits details

Inspect func

Dominance

func(tokenID string) (first int64, second int64, third int64, fourth int64, fifth int64, found bool)

Dominance returns the five axis indexes 0..4 in DOMINANCE order: \`first\` is the loudest. Where the loud/latent boundary falls is an OFF-CHAIN reading of this number and is not stored (TRAITS.md sections 2.1 and 2.2) -- which is exactly what keeps two-states-versus-three reversible after deploy, in either direction, for free.

Open
OID
0fcf1c…66c8:256
Dominance details

Inspect func

Stats

func(tokenID string) (pluck int64, nous int64, grumble int64, appetite int64, found bool)

Stats returns the four Top Trumps stats. They sum to 22, one is >= 8 and one is \<= 3. NOTHING IN ANY REALM COMPARES TWO GNOMES' STATS: there is no Top Trumps game on chain, no reader here takes two token ids, and TRAITS.md section 4.5's tie rule is social.

Open
OID
0fcf1c…66c8:257
Stats details

Inspect func

Secret

func(tokenID string) (secretID int64, found bool)

Secret returns the gnome's secret id, 0..16. THE NAME AND THE LINE LIVE OFF CHAIN (TRAITS.md section 3.2); this realm stores and returns the id and nothing else. There is no count of who holds what and no way to ask which secrets are still unheld.

Open
OID
0fcf1c…66c8:258
Secret details

Inspect func

BornUnder

func(tokenID string) (sign int64, found bool)

BornUnder returns the card-only sign id, 0..7. It is not an axis, has no slot in the dominance order, and is never handed to the dialogue layer (TRAITS.md section 1.6) -- the realm guarantees the middle clause structurally and the last one is an off-chain discipline.

Open
OID
0fcf1c…66c8:259
BornUnder details

Inspect func

BatchIndex

func(tokenID string) (index int64, found bool)

BatchIndex returns this gnome's 1..60 position in Batch One, which is the key LINEAGE.md section 2.4's kinship table is written against. 0 for the genesis gnome, which is outside the batch and is the only token for which this is ever 0.

Open
OID
0fcf1c…66c8:260
BatchIndex details

Inspect func

TokenByBatchIndex

func(index int64) (tokenID string, found bool)

TokenByBatchIndex is BatchIndex inverted: the token carrying a batch position, or found=false if that position has not been settled yet. Both directions ship because a client rendering a holder's cousins has batch indexes from the kinship table and needs token ids, and a client rendering one gnome's card has a token id and needs its index.

Open
OID
0fcf1c…66c8:261
TokenByBatchIndex details

Inspect func

TraitsCsv

func(tokenID string) (csv string, found bool)

TraitsCsv returns all seventeen stored trait numbers in one query, in a fixed, test-pinned order, for a client rendering a card -- the same CSV shape GardenState and CaredDays already use. It exists because TRAITS.md section 6.2's card renders these fields at once and six round trips to draw one card is silly when one would do. It is a CONVENIENCE OVER THE SAME STORED VALUES, not a second source of truth, and TestTraitsCsvAgreesWithEveryGranularReader pins that -- the same discipline NameAvailability already follows against SetName. THE CARE THRESHOLD IS NOT IN IT and has no reader anywhere. Neither is anything derived from it, and neither is a name, a label or an archetype. temperament,obsession,bond,fear,boast, dom1,dom2,dom3,dom4,dom5, secret,pluck,nous,grumble,appetite,bornUnder,batchIndex

Open
OID
0fcf1c…66c8:262
TraitsCsv details

Inspect func

TotalKinEdges

func() int64

TotalKinEdges returns 84. The table is fixed at deploy and this number never changes.

Open
OID
0fcf1c…66c8:263
TotalKinEdges details

Inspect func

KinEdgesPage

func(offset int, limit int) []string

KinEdgesPage returns rows "from,to,typeId" -- batch indexes, not token ids; type ids, not words -- in the table's own order (LINEAGE.md section 2.4's own row order), offset/limit paginated exactly as TokensPage and ListingsPage already are, with the same refusals.

Open
OID
0fcf1c…66c8:264
KinEdgesPage details

Inspect func

TotalKinTypes

func() int64

TotalKinTypes returns 9, LINEAGE.md section 2.4's closed vocabulary.

Open
OID
0fcf1c…66c8:265
TotalKinTypes details

Inspect func

KinTypeName

func(typeID int64) (name string, found bool)

KinTypeName maps a type id to its one word. The declared exception to "wording off chain" -- see kinTypeNames' own comment for the three-part argument and for how to remove it in one edit if the owner prefers the strictest reading.

Open
OID
0fcf1c…66c8:266
KinTypeName details

Inspect func

GenesisTokenID

func() string
Open
OID
0fcf1c…66c8:267
GenesisTokenID details

Inspect func

AdminState

func() (admin .uverse.address, frozen bool)
Open
OID
0fcf1c…66c8:268
AdminState details

Inspect func

LastWakeDay

func(addr .uverse.address) (day int64, found bool)
Open
OID
0fcf1c…66c8:269
LastWakeDay details

Inspect func

Render

func(path string) string

Render DELIBERATELY ECHOES NO PLAYER-SUPPLIED STRING, and v6 keeps it that way even though it now has gnome names it could print. THIS IS THE DEFENCE AGAINST ABUSIVE NAMES ON A PUBLIC PAGE, and it is structural rather than a filter: gnoweb renders this output as markdown, so a page that listed names would be a markdown sink fed by arbitrary player input, and the project would then own a moderation problem on an immutable realm with no moderation surface. v5's own adversarial review (ATTACK-REVIEW.md, "Render sanitisation") recorded "no injection surface" as a property of this function; adding a name here would have been the one change in this file that falsified an audited claim. Belt and braces, because "nothing echoes names today" is only one edit away from being false: the name charset allowlist (letters, digits, space, hyphen, apostrophe -- see validNameRune) excludes every markdown metacharacter there is. No '\[', ']', '(', ')', '\<', '>', '\*', '\_', backtick, pipe or newline can appear in a name, so even a future Render that DID print one could not be made to inject link syntax, a raw HTML tag or a table break. An abusive name is therefore limited to being rude in words, visible only to someone who queries that specific token, never on a page this project publishes. v7 EXTENDS Render WITH ONE PATH -- a token id -- AND THREE CONSTRAINTS, all of which are the forbidden list restated for the one function that produces human-facing output: - NO THRESHOLD, NO SCORE, NO BAND, NO FRACTION, NO REMAINDER. TRAITS.md section 6.2: "Never on the card, in any version: ... the care score, the care threshold, or anything derived from either." - NO CENSUS, NO DISTRIBUTION, NO COUNT OF ANYTHING TRAIT-SHAPED, and specifically no unique-in-batch or scarcest-in-batch marker. - IDS, NOT NAMES. This function prints the numbers it stores. It does not get a copy of the thirty-two public names, the seventeen secret names or the eight signs, because that would put revisable wording on an immutable realm -- the whole point of the split. Rendering "Thistle" instead of 7 is the app's job, from TRAITS.md's own tables. AND IT STILL ECHOES NO PLAYER-SUPPLIED STRING: an unknown path returns a fixed message with the path omitted, exactly as v6's did, so the markdown sink stays unreachable from player input even though this function now takes a path that resolves.

Open
OID
0fcf1c…66c8:270
Render details

Inspect func

gnomeByTokenID

func(tokenID string) (*v9.gnome, bool)
Open
OID
0fcf1c…66c8:271
gnomeByTokenID details

Inspect func

activeGnomeForAddr

func(addr .uverse.address) (tid string, g *v9.gnome, found bool)

activeGnomeForAddr resolves addr's AWAKE gnome, if any. This is the one and only lookup SaveDay and ChooseType use -- there is deliberately no "the gnome for this address" any more.

Open
OID
0fcf1c…66c8:272
activeGnomeForAddr details

Inspect func

ownerOfToken

func(tokenID string) (owner .uverse.address, found bool)
Open
OID
0fcf1c…66c8:273
ownerOfToken details

Inspect func

addOwnerToken

func(addr .uverse.address, tokenID string)

addOwnerToken / removeOwnerToken: the hold-many index. ownerTokens stores a \*\[]string per owner so repeated appends mutate one allocation rather than replacing the tree entry every time; removal does a linear scan, which is fine at this realm's permanent, tiny upper bound (batchCap = 60 tokens that will ever exist, minus the one permanently unreachable genesis gnome, so no owner's slice is ever longer than 60 entries for the whole life of this realm -- v5 and v6 argued the identical point at 24; only the number moved).

Open
OID
0fcf1c…66c8:274
addOwnerToken details

Inspect func

removeOwnerToken

func(addr .uverse.address, tokenID string)
Open
OID
0fcf1c…66c8:275
removeOwnerToken details

Inspect func

balanceOf

func(addr .uverse.address) int64
Open
OID
0fcf1c…66c8:276
balanceOf details

Inspect func

setBalance

func(addr .uverse.address, v int64)
Open
OID
0fcf1c…66c8:277
setBalance details

Inspect func

grantStartingPurseIfNew

func(addr .uverse.address)
Open
OID
0fcf1c…66c8:278
grantStartingPurseIfNew details

Inspect func

feeFor

func(priceUgnot int64) int64
Open
OID
0fcf1c…66c8:279
feeFor details

Inspect func

exactUgnot

func(send chain.Coins, want int64) bool
Open
OID
0fcf1c…66c8:280
exactUgnot details

Inspect func

coinsString

func(c chain.Coins) string
Open
OID
0fcf1c…66c8:281
coinsString details

Inspect func

listingFor

func(tokenID string) (*v9.listing, bool)
Open
OID
0fcf1c…66c8:282
listingFor details

Inspect func

validNameLength

func(name string) bool
Open
OID
0fcf1c…66c8:283
validNameLength details

Inspect func

validNameCharset

func(name string) bool
Open
OID
0fcf1c…66c8:284
validNameCharset details

Inspect func

hasVisibleNameContent

func(name string) bool
Open
OID
0fcf1c…66c8:285
hasVisibleNameContent details

Inspect func

invisibleLetterRunes

map[int32]bool

invisibleLetterRunes: the four code points that are Unicode LETTERS and render as nothing. See the section comment above for why a letter allowlist alone cannot catch these. Written as \\uXXXX escapes (v2's audit fix F3) so they survive an editor or formatter that strips default-ignorable characters -- a denylist that can be silently emptied protects nothing. v3 through v5 carried this comment while the entries themselves were literal invisible glyphs, so the comment was false about its own code for three versions and the protection it describes was not actually in place. Restored here, with the escapes BACK IN THE SOURCE this time. The escapes alone are only half of F3. TestEveryInvisibleLetterIsRefused table-drives this map, which means it tests whatever the map happens to contain -- swap one entry for an ordinary letter and that test still passes while the realm ships banning the letter and accepting the invisible one. TestInvisibleLetterRunesIsExactlyFourPinnedCodepoints is the other half: it pins all four code points BY ESCAPE, independently of this map's contents, so that substitution fails loudly.

Open
OID
0fcf1c…66c8:287
invisibleLetterRunes details

Inspect map

validNameRune

func(r int32) bool
Open
OID
0fcf1c…66c8:288
validNameRune details

Inspect func

smartPunctuationRunes

map[int32]bool

smartPunctuationRunes: the four code points iOS substitutes by default for a straight apostrophe or hyphen. NONE of them is accepted by validNameRune -- this map exists only so the refusal can SAY SO instead of reporting a generic charset failure to a player who typed an ordinary apostrophe on an ordinary phone. See nameAssignabilityProblem's own comment on why this realm refuses rather than folds them.

Open
OID
0fcf1c…66c8:290
smartPunctuationRunes details

Inspect map

hasSmartPunctuation

func(name string) bool
Open
OID
0fcf1c…66c8:291
hasSmartPunctuation details

Inspect func

canonicalNameKey

func(name string) string

canonicalNameKey folds a name to the key nameOwner and reservedNameKeys compare on: lowercase ASCII, runs of spaces collapsed to one, leading and trailing spaces removed. THE STORED NAME AND THE KEY ARE DELIBERATELY TWO DIFFERENT STRINGS. The gnome keeps the name its owner typed, exactly as typed, because that is what a person chose and the app should show it back to them unaltered. The KEY exists only to answer "is this the same name as that one" for uniqueness and reservation, and for THAT question "Hob", "hob", "HOB" and "Hob" with two spaces in it are all plainly the same name. Keying on the raw string instead would make uniqueness worthless and would let anyone walk around the one-of-one reservation by changing one letter's case. WHAT IT DELIBERATELY DOES NOT DO, stated plainly rather than left for a reader to discover: it does not close cross-script HOMOGLYPHS. Cyrillic "А", Greek "Ο" and fullwidth "A" are all Unicode letters, all accepted, and all visually confusable with their Latin counterparts, so "Нob" with a Cyrillic Н is a different key from "Hob" and will be allowed. This is the SAME accepted residual app/js/config.js's NAME\_CHARSET\_RE documents in its own comment, carried forward knowingly: closing it properly needs a confusables table (thousands of mappings, versioned by Unicode, compiled into an immutable realm) and the honest cost/benefit at a 60-gnome collection does not justify it. The defence that IS in place for the case that actually matters -- the eight cap-one editions -- is that their names are reserved and unrenameable, so a homoglyph impostor can exist but can never hold the real key, and CharacterOwner (not a name lookup) is what resolves the genuine Hob. ASCII-only lowercasing, following lowerASCII's own v4 reasoning: strings.ToLower would apply locale-independent Unicode case folding whose behaviour is a toolchain-version detail on an immutable realm, and the keys that MUST match exactly (reservedNameKeys) are all ASCII.

Open
OID
0fcf1c…66c8:292
canonicalNameKey details

Inspect func

nameOwnerOf

func(key string) (tokenID string, found bool)
Open
OID
0fcf1c…66c8:293
nameOwnerOf details

Inspect func

lowerASCII

func(s string) string

lowerASCII: v4's own helper, unchanged -- ASCII-only on purpose (see v4's own comment on why not strings.ToLower).

Open
OID
0fcf1c…66c8:294
lowerASCII details

Inspect func

anyPlotPlanted

func(g *v9.gnome) bool
Open
OID
0fcf1c…66c8:295
anyPlotPlanted details

Inspect func

eyesCount

func(id int64) int64
Open
OID
0fcf1c…66c8:296
eyesCount details

Inspect func

noseCount

func(id int64) int64
Open
OID
0fcf1c…66c8:297
noseCount details

Inspect func

hatCount

func(hat string) int64
Open
OID
0fcf1c…66c8:298
hatCount details

Inspect func

coatCount

func(coat string) int64
Open
OID
0fcf1c…66c8:299
coatCount details

Inspect func

parseActions

func(raw string) []v9.saveAction
Open
OID
0fcf1c…66c8:300
parseActions details

Inspect func

quoteAction

func(part string) string
Open
OID
0fcf1c…66c8:301
quoteAction details

Inspect func

parseUnixField

func(s string, part string) int64
Open
OID
0fcf1c…66c8:302
parseUnixField details

Inspect func

parsePlotField

func(s string, part string) int
Open
OID
0fcf1c…66c8:303
parsePlotField details

Inspect func

dayNumber

func(unixSeconds int64) int64
Open
OID
0fcf1c…66c8:304
dayNumber details

Inspect func

recordCaredDay

func(g *v9.gnome, day int64) banked bool

recordCaredDay reports, via banked, whether it actually incremented g.caredDayCount -- false on the no-op branch (day == g.lastCaredDay already, e.g. a second SaveDay call the same UTC day, or a token bought mid-day that the SELLER had already banked that exact day for before the sale). SaveDay's own caller-scoped gate (lastCaredDayForOwner) depends on this: it must only spend the owner's one-bank-per-day allowance on a call that actually banked something, never on a no-op -- a gno-auditor re-audit found that an unconditional advance let a buyer's SaveDay on a token the seller had already banked that day silently consume the buyer's own allowance for the rest of that day, denying an otherwise fully legitimate cared day on a DIFFERENT, genuinely active token later the same day. See the call site in SaveDay.

Open
OID
0fcf1c…66c8:305
recordCaredDay details

Inspect func

caredDaysCsv

func(g *v9.gnome) string
Open
OID
0fcf1c…66c8:306
caredDaysCsv details

Inspect func

meterNow

func(value int64, setAt int64, now int64, dropEveryHours int64) int64
Open
OID
0fcf1c…66c8:307
meterNow details

Inspect func

clampMeter

func(v int64) int64
Open
OID
0fcf1c…66c8:308
clampMeter details

Inspect func

canPat

func(lastPatAt int64, now int64, cooldownHours int64) bool
Open
OID
0fcf1c…66c8:309
canPat details

Inspect func

moodNow

func(belly int64, lastPatAt int64, lastHappyEventAt int64, adoptedAt int64, now int64, hungryThreshold int64) string
Open
OID
0fcf1c…66c8:310
moodNow details

Inspect func

xpToNext

func(level int64) int64
Open
OID
0fcf1c…66c8:311
xpToNext details

Inspect func

totalXpForLevel

func(level int64) int64
Open
OID
0fcf1c…66c8:312
totalXpForLevel details

Inspect func

levelFromXp

func(totalXp int64) int64
Open
OID
0fcf1c…66c8:313
levelFromXp details

Inspect func
saleFeeRecipient : untyped string ="g1ylpe9juk8cafgf9rt09wzuz85ug2lyfk5metyv"
saleFeeRateBps : int64 =500
saleFeeFloorUgnot : int64 =1000000
collectionName : untyped string ="Gnotchi gnomes"
collectionSymbol : untyped string ="GNOME"
adminAddress : untyped string ="g1nq99m3h9ed7vqve6ajpr3qwy2ls252m8jnundm"
numEyes : int64 =10
numNoses : int64 =8
hourSeconds : int64 =3600
daySeconds : int64 =86400
numPlots : untyped bigint =(3 <untyped> bigint)
plantCost : int64 =1
feedCost : int64 =1
acornsStart : int64 =10
meterMax : int64 =10
patCheer : int64 =3
hungryAtOrBelow : int64 =2
growHours : int64 =24
patCooldownHours : int64 =12
tinkerCost : int64 =5
levelBase : int64 =50
levelStep : int64 =25
sulkyPenaltyPct : int64 =10
harvestPerPlot : int64 =3
feedBelly : int64 =4
bellyDropEveryHours : int64 =3
cheerDropEveryHours : int64 =6
dailyCareCap : int64 =60
acornReserve : int64 =2
happyBonusPct : int64 =10
happyWindowHours : int64 =2
sulkyAfterHours : int64 =72
maxLevel : int64 =20
typeAtLevel : int64 =5
xpPat : int64 =5
xpFeed : int64 =5
xpHarvestPerPlot : int64 =10
xpTinkerTask : int64 =15
maxActionsPerCall : untyped bigint =(32 <untyped> bigint)
maxBackfillHours : int64 =48
caredWindowDays : uint =64
maxGnomesPageSize : untyped bigint =(100 <untyped> bigint)
maxListingsPageSize : untyped bigint =(100 <untyped> bigint)
maxKinEdgesPageSize : untyped bigint =(100 <untyped> bigint)
ugnotDenom : untyped string ="ugnot"
maxPriceUgnot : int64 =1000000000000000
maxFeeRateBps : int64 =10000
maxListingDays : int64 =30
moodHungry : untyped string ="hungry"
moodSulky : untyped string ="sulky"
moodHappy : untyped string ="happy"
moodContent : untyped string ="content"
actionPat : untyped string ="pat"
actionFeed : untyped string ="feed"
actionPlant : untyped string ="plant"
actionHarvest : untyped string ="harvest"
actionTinker : untyped string ="tinker"
typeGardener : untyped string ="gardener"
typeExplorer : untyped string ="explorer"
typeTinkerer : untyped string ="tinkerer"
batchCap : int64 =60
numNamedEditions : untyped bigint =(8 <untyped> bigint)
namedPickRange : untyped bigint =(10 <untyped> bigint)
lockCaredDays : int64 =14
maxNameLen : untyped bigint =(24 <untyped> bigint)
pinOffsetSeconds : int64 =60
pinOffsetRounds : int64 =20
hatOptions : [4]string Inspect
coatOptions : [4]string Inspect
eyesWeights : [10]int64 Inspect
noseWeights : [8]int64 Inspect
numAxes : untyped bigint =(5 <untyped> bigint)
axisTotalWeight : int64 =64
numSecrets : untyped bigint =(17 <untyped> bigint)
firstDistinctCount : int64 =17
careThresholdFloor : int64 =14
careThresholdSpan : int64 =32
numStats : untyped bigint =(4 <untyped> bigint)
statPointTotal : int64 =22
statMin : int64 =1
statMax : int64 =10
statHighAtLeast : int64 =8
statLowAtMost : int64 =3
numBornUnder : untyped bigint =(8 <untyped> bigint)
temperamentWeights : [6]int64 Inspect
obsessionWeights : [8]int64 Inspect
bondWeights : [6]int64 Inspect
fearWeights : [6]int64 Inspect
boastWeights : [6]int64 Inspect
bornUnderWeights : [8]int64 Inspect
secretTargetKind : [17]int64 Inspect
secretTargetID : [17]int64 Inspect
secretTargetKindBoast : int64 =0
secretTargetKindFear : int64 =1
secretMatchMultiplier : int64 =8
secretBaseWeight : int64 =1
legalStatLines : []int64 (len=546) Inspect
namedEditionOrder : [8]string Inspect
namedEditionDisplayNames : [8]string Inspect
namedEditionDisplayName : func(character string) string Inspect
genesisGnomeName : untyped string ="Gaffer"
reservedNameKeys : [9]string Inspect
kinCousin : int64 =0
kinDisputed : int64 =1
kinInLaw : int64 =2
kinLatentKin : int64 =3
kinNeighbour : int64 =4
kinNotSpeaking : int64 =5
kinRival : int64 =6
kinSibling : int64 =7
kinTakenIn : int64 =8
numKinTypes : untyped bigint =(9 <untyped> bigint)
numKinEdges : untyped bigint =(84 <untyped> bigint)
kinTypeNames : [9]string Inspect
kinEdge : type =v9.kinEdge
kinEdges : [84]v9.kinEdge Inspect
plot : type =v9.plot
gnome : type =v9.gnome
listing : type =v9.listing
feeConfig : type =v9.feeConfig
pendingClaim : type =v9.pendingClaim
token : *v0.Token Inspect
ledger : *v0.PrivateLedger Inspect
nextID : v0.ID =<zero>
gnomes : *v0.Tree Inspect
ownerTokens : *v0.Tree Inspect
activeToken : *v0.Tree Inspect
lastWakeDay : *v0.Tree Inspect
lastCaredDayForOwner : *v0.Tree Inspect
balances : *v0.Tree Inspect
listings : *v0.Tree Inspect
allowlist : *v0.Tree Inspect
hasRolled : *v0.Tree Inspect
pendingClaims : *v0.Tree Inspect
eyesCounts : *v0.Tree Inspect
noseCounts : *v0.Tree Inspect
hatCounts : *v0.Tree Inspect
coatCounts : *v0.Tree Inspect
nameOwner : *v0.Tree Inspect
characterOwner : *v0.Tree Inspect
batchIndexToToken : *v0.Tree Inspect
secretsDrawnMask : uint32 =131071
mintedCount : int64 =20
settledCount : int64 =19
genesisTokenID : string ="0000001"
rulesFrozen : bool =true
fee : v9.feeConfig Inspect
transferGate : string =""
init.148 : func() Inspect
assertFeeConfig : func(cfg v9.feeConfig, realmAddr .uverse.address) Inspect
saleGateExtension : type =v9.saleGateExtension
AdminAllow : func(addr .uverse.address) Inspect
AdminDisallow : func(addr .uverse.address) Inspect
Claim : func() Inspect
pendingClaimFor : func(claimant .uverse.address) *v9.pendingClaim Inspect
Settle : func(claimant .uverse.address) Inspect
SettleWithSignature : func(claimant .uverse.address, sigHex string) Inspect
settleCore : func(claimant .uverse.address, pc *v9.pendingClaim) Inspect
popcount32 : func(v uint32) int Inspect
seedDomain : untyped string ="gnogotchi/garden/v7/seed/v1"
purposeStrings : [18]string Inspect
seedFor : func(randomnessHex string, claimant .uverse.address, purpose string) uint64 Inspect
drawIndex : func(seed uint64, n int64) int64 Inspect
drawWeighted : func(weights []int64, totalWeight int64, seed uint64) int Inspect
numLegalStatLines : untyped bigint =(546 <untyped> bigint)
packStatLine : func(a int64, b int64, c int64, d int64) int64 Inspect
unpackStatLine : func(packed int64) (a int64, b int64, c int64, d int64) Inspect
buildLegalStatLines : func() []int64 Inspect
roll : type =v9.roll
drawRoll : func(randomnessHex string, claimant .uverse.address, batchIndex int64, taken uint32) v9.roll Inspect
dominanceFor : func(randomnessHex string, claimant .uverse.address) [5]int64 Inspect
secretWeightsFor : func(boast int64, fear int64) ([17]int64, int64) Inspect
secretFor : func(randomnessHex string, claimant .uverse.address, boast int64, fear int64, batchIndex int64, taken uint32) int64 Inspect
assertRoll : func(r v9.roll, taken uint32) Inspect
Wake : func(tokenID string) Inspect
saveAction : type =v9.saveAction
SaveDay : func(actionsRaw string) Inspect
ChooseType : func(kind string) Inspect
SetName : func(tokenID string, name string) Inspect
assertNameAssignable : func(name string, tokenID string) Inspect
nameAssignabilityProblem : func(name string, tokenID string) string Inspect
List : func(tokenID string, priceUgnot int64, buyer .uverse.address, days int64) Inspect
Cancel : func(tokenID string) Inspect
Buy : func(tokenID string) Inspect
Listing : func(tokenID string) (seller .uverse.address, priceUgnot int64, buyer .uverse.address, listedAtUnix int64, expiresAtUnix int64, feeUgnot int64, totalUgnot int64, found bool) Inspect
TotalListings : func() int64 Inspect
ListingsPage : func(offset int, limit int) []string Inspect
QuoteFee : func(priceUgnot int64) (feeUgnot int64, totalUgnot int64) Inspect
FeeRules : func() string Inspect
Provenance : func(tokenID string) (adoptedAtUnix int64, acquiredAtUnix int64, saleCount int64, found bool) Inspect
Name : func(tokenID string) (name string, found bool) Inspect
NameOwner : func(name string) (tokenID string, found bool) Inspect
NameAvailability : func(name string, tokenID string) (ok bool, reason string) Inspect
SettledRound : func(tokenID string) (round int64, found bool) Inspect
MaxNameLen : func() int64 Inspect
GnomeByToken : func(tokenID string) (hat string, coat string, eyes int64, nose int64, character string, level int64, gnomeType string, adoptedAtUnix int64, found bool) Inspect
ActiveGnome : func(addr .uverse.address) (tokenID string, found bool) Inspect
OwnerTokens : func(addr .uverse.address) []string Inspect
TokensPage : func(offset int, limit int) []string Inspect
GardenState : func(tokenID string) (plantedAtCsv string, readyAtCsv string, readyCount int64, found bool) Inspect
CareState : func(tokenID string) (belly int64, cheer int64, acorns int64, totalXp int64, lastPatAtUnix int64, lastHappyEventAtUnix int64, lastActionAtUnix int64, found bool) Inspect
MeterState : func(tokenID string) (belly int64, bellyAtUnix int64, cheer int64, cheerAtUnix int64, found bool) Inspect
Progress : func(tokenID string) (xpGarden int64, xpTinker int64, harvestCount int64, lastTinkerAtUnix int64, caredDayCount int64, dailyCareXpSpent int64, dailyCareDay int64, found bool) Inspect
CaredDays : func(tokenID string) (daysCsv string, found bool) Inspect
Mood : func(tokenID string) (mood string, found bool) Inspect
IsLocked : func(tokenID string) (locked bool, found bool) Inspect
CollectionInfo : func() (name string, symbol string) Inspect
TotalSupply : func() int64 Inspect
BalanceOf : func(addr .uverse.address) int64 Inspect
OwnerOfToken : func(tokenID string) (owner .uverse.address, found bool) Inspect
TotalGnomes : func() int64 Inspect
Acorns : func(addr .uverse.address) (acorns int64, found bool) Inspect
EyesCount : func(id int64) int64 Inspect
NoseCount : func(id int64) int64 Inspect
HatCount : func(hat string) int64 Inspect
CoatCount : func(coat string) int64 Inspect
CharacterOwner : func(name string) (tokenID string, found bool) Inspect
IsAllowlisted : func(addr .uverse.address) bool Inspect
HasRolled : func(addr .uverse.address) bool Inspect
PendingClaim : func(addr .uverse.address) (round int64, claimedAtUnix int64, found bool) Inspect
BatchStatus : func() (mintedSoFar int64, settledSoFar int64, batchCapOut int64, namedMinted int64, namedCapOut int64) Inspect
Traits : func(tokenID string) (temperament int64, obsession int64, bond int64, fear int64, boast int64, found bool) Inspect
Dominance : func(tokenID string) (first int64, second int64, third int64, fourth int64, fifth int64, found bool) Inspect
Stats : func(tokenID string) (pluck int64, nous int64, grumble int64, appetite int64, found bool) Inspect
Secret : func(tokenID string) (secretID int64, found bool) Inspect
BornUnder : func(tokenID string) (sign int64, found bool) Inspect
BatchIndex : func(tokenID string) (index int64, found bool) Inspect
TokenByBatchIndex : func(index int64) (tokenID string, found bool) Inspect
TraitsCsv : func(tokenID string) (csv string, found bool) Inspect
TotalKinEdges : func() int64 Inspect
KinEdgesPage : func(offset int, limit int) []string Inspect
TotalKinTypes : func() int64 Inspect
KinTypeName : func(typeID int64) (name string, found bool) Inspect
GenesisTokenID : func() string Inspect
AdminState : func() (admin .uverse.address, frozen bool) Inspect
LastWakeDay : func(addr .uverse.address) (day int64, found bool) Inspect
Render : func(path string) string Inspect
gnomeByTokenID : func(tokenID string) (*v9.gnome, bool) Inspect
activeGnomeForAddr : func(addr .uverse.address) (tid string, g *v9.gnome, found bool) Inspect
ownerOfToken : func(tokenID string) (owner .uverse.address, found bool) Inspect
addOwnerToken : func(addr .uverse.address, tokenID string) Inspect
removeOwnerToken : func(addr .uverse.address, tokenID string) Inspect
balanceOf : func(addr .uverse.address) int64 Inspect
setBalance : func(addr .uverse.address, v int64) Inspect
grantStartingPurseIfNew : func(addr .uverse.address) Inspect
feeFor : func(priceUgnot int64) int64 Inspect
exactUgnot : func(send chain.Coins, want int64) bool Inspect
coinsString : func(c chain.Coins) string Inspect
listingFor : func(tokenID string) (*v9.listing, bool) Inspect
validNameLength : func(name string) bool Inspect
validNameCharset : func(name string) bool Inspect
hasVisibleNameContent : func(name string) bool Inspect
invisibleLetterRunes : map[int32]bool Inspect
validNameRune : func(r int32) bool Inspect
smartPunctuationRunes : map[int32]bool Inspect
hasSmartPunctuation : func(name string) bool Inspect
canonicalNameKey : func(name string) string Inspect
nameOwnerOf : func(key string) (tokenID string, found bool) Inspect
lowerASCII : func(s string) string Inspect
anyPlotPlanted : func(g *v9.gnome) bool Inspect
eyesCount : func(id int64) int64 Inspect
noseCount : func(id int64) int64 Inspect
hatCount : func(hat string) int64 Inspect
coatCount : func(coat string) int64 Inspect
parseActions : func(raw string) []v9.saveAction Inspect
quoteAction : func(part string) string Inspect
parseUnixField : func(s string, part string) int64 Inspect
parsePlotField : func(s string, part string) int Inspect
dayNumber : func(unixSeconds int64) int64 Inspect
recordCaredDay : func(g *v9.gnome, day int64) banked bool Inspect
caredDaysCsv : func(g *v9.gnome) string Inspect
meterNow : func(value int64, setAt int64, now int64, dropEveryHours int64) int64 Inspect
clampMeter : func(v int64) int64 Inspect
canPat : func(lastPatAt int64, now int64, cooldownHours int64) bool Inspect
moodNow : func(belly int64, lastPatAt int64, lastHappyEventAt int64, adoptedAt int64, now int64, hungryThreshold int64) string Inspect
xpToNext : func(level int64) int64 Inspect
totalXpForLevel : func(level int64) int64 Inspect
levelFromXp : func(totalXp int64) int64 Inspect