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.

v1 state

Back to all declarations

battlePuff

int64

// battle.puff

Value

10

battleRounds

untyped bigint

// battle.rounds

Value

(3 <untyped> bigint)

battleWinHit

int64

// battle.winHit

Value

3

battleEdge

int64

// battle.typeEdge

Value

1

battleTieLoss

int64

// battle.tieLoss

Value

1

moveQuick

untyped string

Value

"Q"

moveStrong

untyped string

Value

"S"

moveGuard

untyped string

Value

"G"

typeGardener

untyped string

Value

"gardener"

typeExplorer

untyped string

Value

"explorer"

typeTinkerer

untyped string

Value

"tinkerer"

challengeExpiresHours

int64

// time.challengeExpiresHours

Value

24

revealWindowHours

int64

// time.revealWindowHours

Value

24

rankedFromLevel

int64

// xp.rankedFromLevel

Value

3

rankedPerGnomePerDay

int64

// ranked.perGnomePerDay

Value

3

rankedGamesPerPairPerWeek

int64

// ranked.rankedGamesPerPairPerWeek

Value

2

hourSeconds

int64

Value

3600

daySeconds

int64

Value

86400

weekSeconds

int64

Value

604800

stPending

untyped string

Value

"pending"

stAccepted

untyped string

Value

"accepted"

stResolved

untyped string

Value

"resolved"

stForfeited

untyped string

Value

"forfeited"

styleBeatsMap

map[string]string

styleBeatsMap: Quick beats Strong, Strong beats Guard, Guard beats Quick (brief 3.6).

Open
OID
05791d…1039:4
styleBeatsMap details

Inspect map

typeBeatsMap

map[string]string

typeBeatsMap: Gardener beats Explorer, Explorer beats Tinkerer, Tinkerer beats Gardener.

Open
OID
05791d…1039:6
typeBeatsMap details

Inspect map

arenaChallenge

type

arenaChallenge is one sealed-plan battle, pending through to resolved/forfeited.

Value

v1.arenaChallenge

challenges

*v0.Tree

// challengeID string -> \*arenaChallenge

Open
OID
05791d…1039:8
challenges details

Inspect pointer

nextID

v0.ID

Value

<zero>

dailyRanked

*v0.Tree

// "\<addr>:\<dayNumber>" -> int64 ranked-battle count

Open
OID
05791d…1039:12
dailyRanked details

Inspect pointer

pairWeekly

*v0.Tree

// "\<addrLo>|\<addrHi>:\<weekNumber>" -> int64 ranked-result count

Open
OID
05791d…1039:15
pairWeekly details

Inspect pointer

totalChallenges

int64

Value

6

totalResolved

int64

Value

4

Challenge

func(opponent .uverse.address, commitHash string) string

Challenge posts only commitHash -- the sealed hash of the challenger's plan and salt. This realm never sees, and never could see, the plan itself at this stage: there is no plan parameter on this function at all, so a plan cannot arrive here even by mistake. See the package doc for why that asymmetry (challenger sealed, opponent's Accept plan in the clear) is deliberate and correct, not a gap. Challenge returns the new challenge's ID. This is an addition beyond the brief's own parameter-only signature notation, not a deviation from it: nothing in the brief forbids a return value, the caller genuinely needs the ID back to tell the opponent which challenge to Accept, and the alternative -- forcing every caller to parse the emitted "Challenge" event for the same value this function already has in hand -- is strictly worse for no safety benefit. See NOTES.md. THE unsafe.OriginSend() GUARD BELOW, AND ITS PRICE, recorded once here for all four mutating functions that use it. OriginSend reports the coins attached to the ORIGIN TRANSACTION, not the coins this call received -- which is exactly why the stdlib quarantines it under \`unsafe\`. Its purpose here is narrow and worth keeping: this realm has no banker, so any coin that reaches its address is stuck forever, and refusing the call is kinder than silently swallowing a user's GNOT. The price is permanent and goes in the other direction: because the check is transaction-scoped, any future transaction that attaches coins to some OTHER realm in the same call chain makes all four of these functions panic even though arena received nothing. A paid-entry tournament realm wrapping Challenge/Accept/Reveal is therefore impossible against this version. The read surface is unaffected -- ChallengeStatus, ChallengeProof and Render are non-crossing and carry no such check -- so a read-only season/v1 is fine; it is write-path composition specifically that is foreclosed. This is a forced trade, not an oversight: gno 0.9 exposes no call-scoped "coins sent to me" primitive at all. \`gno doc chain/runtime/unsafe\` lists OriginSend as the only coin receipt reader, and \`gno doc chain\` has no equivalent -- checked, not assumed. The stdlib's own advice is to pair it with runtime.AssertOriginCall(), which would forbid realm callers outright and so is strictly worse for composability than what is here.

Open
OID
05791d…1039:19
Challenge details

Inspect func

Accept

func(challengeID string, plan string)

Accept posts the opponent's own plan in the clear -- they learn nothing from the sealed commit, so there is nothing to protect by hiding their own choice. See the package doc.

Open
OID
05791d…1039:21
Accept details

Inspect func

Reveal

func(challengeID string, plan string, salt string)

Reveal posts the challenger's plan and salt. The realm recomputes commitDigest and rejects any plan/salt pair that does not reproduce the posted commitHash, then resolves the battle itself -- against both gnomes' CURRENT type and level, read live from garden/v2, never from anything cached here.

Open
OID
05791d…1039:22
Reveal details

Inspect func

ClaimForfeit

func(challengeID string)

ClaimForfeit lets the opponent claim the win once revealWindowHours has passed with no Reveal. Per the brief: "the result counts as a normal ranked loss" for the challenger -- this realm applies the SAME ranked-eligibility rule to a forfeit as to a genuine reveal (see NOTES.md), rather than treating a forfeit as automatically ranked regardless of the fairness caps.

Open
OID
05791d…1039:23
ClaimForfeit details

Inspect func

ChallengeStatus

func(challengeID string) (challenger .uverse.address, opponent .uverse.address, status string, createdAt int64, acceptedAt int64, resolvedAt int64, winner string, puffChallenger int64, puffOpponent int64, ranked bool, found bool)
Open
OID
05791d…1039:24
ChallengeStatus details

Inspect func

ChallengeProof

func(challengeID string) (commit string, challengerPlan string, salt string, opponentPlan string, found bool)

ChallengeProof returns everything a third party needs to re-derive a finished battle from scratch, without trusting this realm, either player, or Gnotchi's server. This realm's entire reason to exist is that nobody has to be trusted about the result. ChallengeStatus returns the result -- which is this realm ASSERTING a winner. That is exactly the kind of claim the realm was built to avoid having to make. Given commit, challengerPlan, salt and opponentPlan, anyone can check sha256(challengerPlan + ":" + salt) == commit, feed both plans through app/js/rules.js's resolveBattle, and get the same winner and the same puff -- or catch this realm being wrong. Without these four fields the "you don't have to trust us" claim is itself something you'd have to take on trust, and since a realm cannot be edited, a getter missing at deploy time stays missing until a v2 and a migration. Before a Reveal, challengerPlan and salt are empty strings, so this getter cannot leak a plan early: there is no plan here to leak until the challenger has published it themselves. commit is readable from the moment of Challenge, but it always was -- it is a public argument of that transaction -- so exposing it here reveals nothing new. THE SEAL'S STRENGTH IS THE SALT'S, AND THIS REALM CANNOT ENFORCE THAT. Stated plainly because the honest version is narrower than it first looks. There are only 27 possible plans: battleRounds is 3 and there are 3 legal moves, so 3^3. commitDigest is a single bare sha256 with no KDF and no iteration. So a commit whose salt is known, reused, or derived from anything predictable falls to 27 hashes -- a complete break of the only thing this realm exists to guarantee. Reveal deliberately imposes no minimum salt length, because a length check added here would permanently reject any challenge already sealed with a shorter salt and force its challenger to forfeit. The entropy requirement therefore lives entirely in the client: app/js/battle.js seals with 32 fresh crypto.getRandomValues bytes per plan, never reused. Any other client MUST do the same; 27 candidates is not a margin to be casual with. The preimage also binds neither the challenge ID nor the challenger's address, so the same commit can be posted in several challenges. No advantage was found in doing so -- a replayer commits to a plan they will not learn until the original reveals, which is strictly worse than choosing one -- but it is recorded here because the preimage has to match app/js/rules.js byte for byte and so can never be changed. A forfeited challenge has no challengerPlan by definition -- the challenger never revealed one, which is why it was forfeited. \`found\` is false for an unknown ID.

Open
OID
05791d…1039:25
ChallengeProof details

Inspect func

Render

func(path string) string

Render: "" shows the realm's totals; any other path is treated as a challengeID.

Open
OID
05791d…1039:26
Render details

Inspect func

mustChallenge

func(id string) *v1.arenaChallenge
Open
OID
05791d…1039:27
mustChallenge details

Inspect func

hasGnome

func(addr .uverse.address) bool
Open
OID
05791d…1039:28
hasGnome details

Inspect func

validCommitHex

func(s string) bool

validCommitHex checks only the SHAPE of a posted commitHash (64 lowercase hex chars, matching the exact output of both Node's crypto.createHash('sha256').digest('hex') and this file's own commitDigest) -- an early, player-facing failure, not a correctness requirement: Reveal's own byte-for-byte comparison is what actually enforces the seal.

Open
OID
05791d…1039:29
validCommitHex details

Inspect func

validChallengeID

func(s string) bool

validChallengeID accepts only what Challenge itself can mint: a seqid.ID rendered by cford32, which is Crockford base32 and therefore a strict subset of lowercase alphanumerics. 16 is a deliberate ceiling rather than an exact width -- seqid's encoded width grows with the counter, so pinning one number here would start rejecting real IDs later, and this realm cannot be edited once deployed. Nothing outside this set can ever be a key in \`challenges\`, so narrowing to it loses no lookup.

Open
OID
05791d…1039:30
validChallengeID details

Inspect func

commitDigest

func(planString string, saltHex string) string

commitDigest ports app/js/rules.js's commitHash exactly: sha256(planString + ":" + saltHex), hex-encoded. See the package doc for the cross-checked proof this matches Node's crypto output byte for byte.

Open
OID
05791d…1039:31
commitDigest details

Inspect func

parsePlan

func(s string) []string

parsePlan ports app/js/rules.js's parsePlan: exactly battleRounds comma-separated moves, each trimmed and uppercased to one of Q, S or G.

Open
OID
05791d…1039:32
parsePlan details

Inspect func

planFormatError

func() string
Open
OID
05791d…1039:33
planFormatError details

Inspect func

styleBeats

func(a string, b string) bool
Open
OID
05791d…1039:34
styleBeats details

Inspect func

typeBeats

func(a string, b string) bool

typeBeats: empty string (no type assigned yet) never beats, and is never beaten by, anything -- ports app/js/rules.js's typeBeats: \`!!a && !!b && TYPE\_BEATS\[a] === b\`.

Open
OID
05791d…1039:35
typeBeats details

Inspect func

battleResult

type

Value

v1.battleResult

resolveBattle

func(challengerMoves []string, challengerType string, opponentMoves []string, opponentType string, puff int64) v1.battleResult

resolveBattle ports app/js/rules.js's resolveBattle exactly: no randomness, a knock-out ends the fight early, the round's winner hits for winHit plus typeEdge with the type edge, a tie costs both battleTieLoss. puff is an explicit parameter (not just the package's own battlePuff constant) purely so tests can mirror rules.test.js's own trick of lowering it to force an early knock-out within three rounds -- every real call from Reveal always passes battlePuff.

Open
OID
05791d…1039:36
resolveBattle details

Inspect func

dayNumber

func(unixSeconds int64) int64
Open
OID
05791d…1039:38
dayNumber details

Inspect func

weekNumber

func(unixSeconds int64) int64
Open
OID
05791d…1039:39
weekNumber details

Inspect func

dayKey

func(addr .uverse.address, now int64) string
Open
OID
05791d…1039:40
dayKey details

Inspect func

pairKey

func(a .uverse.address, b .uverse.address) string

pairKey is symmetric: challenger-vs-opponent and opponent-vs-challenger must land on the same weekly counter.

Open
OID
05791d…1039:41
pairKey details

Inspect func

weekKey

func(pk string, now int64) string
Open
OID
05791d…1039:42
weekKey details

Inspect func

getCounter

func(tree *v0.Tree, key string) int64
Open
OID
05791d…1039:43
getCounter details

Inspect func

incCounter

func(tree *v0.Tree, key string)
Open
OID
05791d…1039:44
incCounter details

Inspect func

rankedEligible

func(challenger .uverse.address, opponent .uverse.address, challengerLevel int64, opponentLevel int64, now int64) bool

rankedEligible: level 3 to rank (xp.rankedFromLevel), at most rankedPerGnomePerDay ranked results a gnome a day, at most rankedGamesPerPairPerWeek a pair a week. Never blocks a battle -- see the const block's own comment for why this is a downgrade, not a refusal. KNOWN AND PERMANENT: THE PLAYER CHOOSES WHEN THIS RUNS, SO THEY CHOOSE WHAT IT SEES. Every input below -- both daily counters, both pair counters, both levels -- is read live at Reveal (or ClaimForfeit) time, and that moment is picked by the revealer, who by then already knows the outcome from the opponent's cleartext plan. A challenger holding several accepted challenges can reveal their three wins first, exhausting their own rankedPerGnomePerDay allowance, after which every loss revealed that day is recorded ranked = false. Free, repeatable daily, and invisible in the stored record. There is no fix available inside this function. Deciding eligibility at Challenge time instead would just move the choice to a moment the challenger controls even more completely, and the counters cannot be consumed before the battle exists. A real fix needs the plan asymmetry to go (both sides sealed) or an ordering the players do not pick -- either of which is a different realm, not a change to this line. Nothing reads \`ranked\` today: season/v1 does not exist (see the SCOPE note in the package doc). That bounds the harm to zero right now and is precisely why it is written down here instead of deferred -- whoever builds season/v1 will be reading an immutable field whose distribution is skewed in the ranked player's favour, and they should learn that from this comment rather than from the leaderboard.

Open
OID
05791d…1039:45
rankedEligible details

Inspect func

recordRanked

func(challenger .uverse.address, opponent .uverse.address, now int64)
Open
OID
05791d…1039:46
recordRanked details

Inspect func
battlePuff : int64 =10
battleRounds : untyped bigint =(3 <untyped> bigint)
battleWinHit : int64 =3
battleEdge : int64 =1
battleTieLoss : int64 =1
moveQuick : untyped string ="Q"
moveStrong : untyped string ="S"
moveGuard : untyped string ="G"
typeGardener : untyped string ="gardener"
typeExplorer : untyped string ="explorer"
typeTinkerer : untyped string ="tinkerer"
challengeExpiresHours : int64 =24
revealWindowHours : int64 =24
rankedFromLevel : int64 =3
rankedPerGnomePerDay : int64 =3
rankedGamesPerPairPerWeek : int64 =2
hourSeconds : int64 =3600
daySeconds : int64 =86400
weekSeconds : int64 =604800
stPending : untyped string ="pending"
stAccepted : untyped string ="accepted"
stResolved : untyped string ="resolved"
stForfeited : untyped string ="forfeited"
styleBeatsMap : map[string]string Inspect
typeBeatsMap : map[string]string Inspect
arenaChallenge : type =v1.arenaChallenge
challenges : *v0.Tree Inspect
nextID : v0.ID =<zero>
dailyRanked : *v0.Tree Inspect
pairWeekly : *v0.Tree Inspect
totalChallenges : int64 =6
totalResolved : int64 =4
Challenge : func(opponent .uverse.address, commitHash string) string Inspect
Accept : func(challengeID string, plan string) Inspect
Reveal : func(challengeID string, plan string, salt string) Inspect
ClaimForfeit : func(challengeID string) Inspect
ChallengeStatus : func(challengeID string) (challenger .uverse.address, opponent .uverse.address, status string, createdAt int64, acceptedAt int64, resolvedAt int64, winner string, puffChallenger int64, puffOpponent int64, ranked bool, found bool) Inspect
ChallengeProof : func(challengeID string) (commit string, challengerPlan string, salt string, opponentPlan string, found bool) Inspect
Render : func(path string) string Inspect
mustChallenge : func(id string) *v1.arenaChallenge Inspect
hasGnome : func(addr .uverse.address) bool Inspect
validCommitHex : func(s string) bool Inspect
validChallengeID : func(s string) bool Inspect
commitDigest : func(planString string, saltHex string) string Inspect
parsePlan : func(s string) []string Inspect
planFormatError : func() string Inspect
styleBeats : func(a string, b string) bool Inspect
typeBeats : func(a string, b string) bool Inspect
battleResult : type =v1.battleResult
resolveBattle : func(challengerMoves []string, challengerType string, opponentMoves []string, opponentType string, puff int64) v1.battleResult Inspect
clampPuff : func(v int64) int64 Inspect
dayNumber : func(unixSeconds int64) int64 Inspect
weekNumber : func(unixSeconds int64) int64 Inspect
dayKey : func(addr .uverse.address, now int64) string Inspect
pairKey : func(a .uverse.address, b .uverse.address) string Inspect
weekKey : func(pk string, now int64) string Inspect
getCounter : func(tree *v0.Tree, key string) int64 Inspect
incCounter : func(tree *v0.Tree, key string) Inspect
rankedEligible : func(challenger .uverse.address, opponent .uverse.address, challengerLevel int64, opponentLevel int64, now int64) bool Inspect
recordRanked : func(challenger .uverse.address, opponent .uverse.address, now int64) Inspect