Skip to content

Meta — Word Games Tactics

TL;DR: USERVOTE is round-gated: uint8 Submissions (255 max), uint16 Vote (one ID per round), uint64 Round (resets invalidate old votes). Submit early, vote late, re-vote every new round.

Why this matters

The ACRONYM word game is the game's one heavy narrative-layer system and it's gated by some of the tightest uint packing in the codebase. If you understand the pack, you understand the edges:

  • One Vote slot per Soul per Round. Change your vote by overwriting it; you can't hold multiple simultaneous votes.
  • 255 submissions per user per round. Hard cap, enforced by uint8 overflow.
  • 65,535 submissions total per round. Across all users, enforced by uint16 Vote's encoding of the target submission ID.
  • Rounds silently invalidate old votes. Round != currentRound ⇒ vote doesn't count, no revert, no warning.

The numbers

UserVote struct packing

From USERVOTE:

struct UserVote {
    uint16 Vote;         // ID of voted submission, 0 = none
    uint8  Submissions;  // Number of submissions this round
    uint64 Round;        // Current round number
}
Field Type Range Meaning
Vote uint16 0 – 65,535 The submission ID you voted for (0 = no vote).
Submissions uint8 0 – 255 How many phrases you personally submitted this round.
Round uint64 0 – ~1.8e19 Which round this record refers to.

Validity check

From USERVOTE:

function isValidVote(soul) view returns (bool) {
    return userVotes[soul].Round == currentRound;
}

Your vote counts iff the Round stored with your vote equals the live currentRound. There's no vote-moved, no vote-decayed — it's either this round or it's noise.

ACRONYM mechanics

From ACRONYM:

  • Each round has an acronym (e.g. "LOL").
  • Submissions are phrases whose initials must match the acronym letter-by-letter: CheckAcronym(acronym, phrase) via STRINGLIB.
  • Multiple valid phrases per round. You vote for the one you think wins.

What doesn't decay

  • Vote field physically persists across rounds (it's just storage), but its value is ignored because Round mismatches.
  • Submissions field carries into the next round's storage slot but its meaning is gated by Round == currentRound checks in practice.
  • The acronym itself regenerates at round advance via stringLib.RandomAcronym(n) — you can't reuse last round's phrase.

The play

  1. Submit early, vote late. Submissions establish what candidates exist; voting happens over the candidate set. You want your submission up before others lock in their votes on competing phrases.
  2. Track currentRound every tx. Every ACRONYM-touching call should read currentRound first. If it just changed, your pending vote is dead.
  3. Re-vote on every round advance. The contract won't do it for you. If the round advanced while you were asleep, all your prior votes now read as Round != currentRound and are silently disqualified.
  4. Don't spam submissions up to 255. The cap is real, but the social layer hates obvious spam. Dominate with good phrases, not 100 junk ones. Also: every submission is a gas cost.
  5. Keep submissions short, witty, on-theme. CheckAcronym only verifies the initials match; tone/style is your edge for winning votes.
  6. Only submit when the acronym has structural potential. RandomAcronym(5) might give you "XQZJF" — valid English phrases are rare. Skip those rounds.
  7. (inferred) Watch Vote ID distribution. The uint16 Vote field means the total submission count across the round can't exceed 65,535. At typical usage that's unreachable, but if the game ever had a viral moment, there's a hard ceiling.
  8. (inferred) Exploit the silent-invalid. If a round advances right before voting closes, many players won't realize their votes are stale. Being the player who re-votes on every advance gives you disproportionate signal.

Worked example

Round 47. Acronym is "LOL". You submit two phrases:

userVotes[yourSoul] = {Vote: 0, Submissions: 1, Round: 47}  // after phrase 1
userVotes[yourSoul] = {Vote: 0, Submissions: 2, Round: 47}  // after phrase 2

Now you vote for submission ID 12:

userVotes[yourSoul] = {Vote: 12, Submissions: 2, Round: 47}

Round 48 advances (acronym is now "GOAT"). Your storage still reads {Vote: 12, Submissions: 2, Round: 47}but isValidVote(yourSoul) now returns false because 47 ≠ 48.

If you do nothing: your vote doesn't count, your submissions don't carry, you're a no-show for Round 48.

If you notice and re-vote for submission ID 3 in Round 48:

userVotes[yourSoul] = {Vote: 3, Submissions: 0, Round: 48}

Now you're active again. Submissions reset to 0 because you haven't submitted this round.

Key insight: the Submissions counter doesn't auto-reset on round advance; it resets when you first write in the new round. If you never act in Round 48, your storage still shows Submissions: 2 from Round 47 — but because Round is 47, nothing you check cares about that value anymore.

Gotchas

  • Vote == 0 is "no vote," not "voted for submission 0." Submission IDs start at 1. Defensive code should treat 0 as sentinel.
  • Round advance triggers are not user-visible. Who calls the round-advance function, and when, varies by governance / owner activity. Watch the round counter on-chain.
  • Submissions is uint8; hitting 256 overflows silently or reverts depending on Solidity version. At ^0.8.21, it reverts with arithmetic overflow — your 256th submission tx reverts and you waste gas.
  • CheckAcronym is the only validation. Your phrase can be gibberish, offensive, spam — the contract doesn't care as long as initials match. The social layer cares; other players downvote.
  • Ties. Two submissions with identical vote counts — resolution is contract-defined (first-submitted or deterministic based on ID). Don't assume ties favor you; read the contract.
  • Voting for your own submission is allowed. Not a gotcha per se, but note: one self-vote in a low-turnout round can matter.
  • (inferred) Whale self-voting. A CROWS holder or large-YUE stacker could theoretically swing a low-turnout round. There's no per-wallet vote weight — one Soul = one vote — but a player with many LAUs has many Souls. See Power: Yuan Composition for how Soul multiplicity works.
  • No fee to submit. Submissions are gas-only. This makes Submissions: 255 a real attack surface for spam rounds.

Where it cross-connects