PATINA docs

Deterministic tier renders

A render is a pure function from protocol state to a picture. Same inputs, same picture, on any machine, forever.

What you will know after this page
  • The seven inputs a render may read, and why anything else, from a wall clock to who is looking, is forbidden.
  • How to derive per artifact variety from the artifact id so the same id always makes the same choice.
  • Why every render prints the height its numbers were read at, down to the smallest avatar.
  • How to draw an artifact that was deep and is Raw now without lying about either.
  • Six tests to run against your own renderer: three for determinism, three for whether the picture reads.
Convention, not protocol

Nothing in the baseline requires renders to exist, and the chain never sees one. This section is the convention for the ecosystem, so that two viewers showing the same artifact do not show two different things.

The inputs

A render takes exactly these, and nothing else:

artifact_id      32 bytes, the only source of per artifact variation
founding         boolean
status           ALIVE or RELIC
depth            integer, blocks, for an ALIVE artifact
tier             0 to 7
rings            the completed rings, in order
height           the block height these values were read at

Anything not on that list is forbidden as an input. In particular: no wall clock, no random seed, no locale, no viewport, no network fetch, no user identity, no price, and nothing about who is looking.

Why determinism

  • Two viewers showing the same artifact must not disagree. Disagreement means one of them is inventing something.
  • A picture that changes when nothing on chain changed teaches people that the picture is decoration rather than data.
  • A render that can be reproduced offline can be checked. One that calls a server cannot.

Variation comes from the artifact id

If a pack wants per artifact variety, derive it from the id and nothing else.

// A pack may read bytes of the id to pick variants.
// The same id always yields the same choice.
const id = "d3b8d3013c23dd3df76882034df80935ba55aa75772007f0b142b63adce5eff0";
const byte0 = parseInt(id.slice(0, 2), 16);   // 211
const grain = byte0 % 8;                      // stable per artifact

Use a documented derivation and publish it with the pack, so anyone can verify that a render is what the rules produce rather than what the renderer felt like.

What each input should drive

The last column is the useful one. Nearly every dishonest render is an input being made to say something the protocol never said. The eight materials on the tiers page are one published treatment of the first row, not a requirement to copy.

A sensible mapping. Packs may differ, but the mapping must be published.
InputDrivesMust not drive
tierThe dominant treatment, from raw metal to deep verdigrisAnything implying rank between holders
depthFine progression within a tierA number the render invents by rounding
ringsMarks for completed stretches, sized by each ring's depthA summed total. Rings do not add up.
foundingA quiet, factual markA badge that implies superiority
statusA distinct terminal treatment for a relicHiding that the artifact ended
artifact_idStable per artifact varietyAnything that looks like a score
heightThe printed height stampNothing else

The height stamp is mandatory

Every render prints the height its numbers were read at. Depth changes every block, so a picture without a height is a claim with no date. When someone sees a shared image six months later, the stamp is what makes it checkable rather than misleading.

Handling a moving artifact

Depth resets when the carrier moves, and the rings stay. The public site draws that split on the depth page, where low depth now says nothing about what an artifact has already done. A render must show the same thing plainly:

  • Show the live tier as it is now, which may be Raw.
  • Show the rings as history, clearly separate from the live state.
  • Never show the deepest ring where the live tier belongs.
  • Never animate a reset as though it were a loss or a punishment. It is a holder spending their own coins.

Technical requirements

The first three keep the function pure, which is what lets two renderers compare output hashes. The rest are about the picture surviving contact with real screens and real readers.

  1. Pure function. No side effects, no network, no storage.
  2. Reproducible offline. If it needs a font or an asset, the asset ships with the pack.
  3. Deterministic output bytes for a given input, so two renderers can compare hashes.
  4. Scales from a small avatar to a large share card without changing what it claims.
  5. Readable in both light and dark surroundings.
  6. Respects reduced motion. Any animation must be optional and never required to read the state.
  7. Text alternatives for every element that carries meaning, so a screen reader gets the same facts.

Test your renderer

  1. Render the same input twice and compare bytes. They must be identical.
  2. Render on two machines and compare. Still identical.
  3. Render every tier from Raw to Elder and check each is distinguishable from the next.
  4. Render a relic and check it does not look like a live artifact.
  5. Render an artifact with zero rings and one with several, and check the difference is legible.
  6. Disconnect the network and render again. It must still work.

Publishing rules are on Publish a render pack, and the honesty requirements are on Rules that keep renders honest.