jonnify·codemojex /codemojex/scoring-and-settlement/the-total-out-of-600

Codemojex · C4.2 · a dive of Scoring and Settlement

The total out of 600.

The six position points sum to the guess’s total. A perfect crack is 6 × 100 = 600 — the maximum, and the percentage a client shows is round(total / 600 × 100), computed on read, never stored. A player’s best total is their rank.

Grounding — echo/…/codemojex/scoring.ex (score/2) · board.ex (record/3, top/2) · game.ex (ScoreWorker) · design §The data model · the scoring story.

Six positions, one total — select a guess against a fixed secret
pos 0100EXACT pos 1100EXACT pos 2100EXACT pos 3100EXACT pos 4100EXACT pos 5100EXACT Σ = 600 / 600 · 100%
exact → every position is EXACT at 100, so total = 600 and percentage = round(600/600×100) = 100. A perfect crack — the maximum a guess can score.
§1

Six positions summed

scoring.ex · score/2

Scoring.score/2 walks the guess, finds each emoji’s home in the secret, and folds the per-position points into a total. It returns the total, the max of 600, the percentage computed on read, and a per-position breakdown — the total is a sum, nothing more.

echo/…/codemojex/scoring.ex · score/2 source
def score(secret, guess) when length(secret) == 6 and length(guess) == 6 do
  breakdown =
    for {emoji, i} <- Enum.with_index(guess) do
      case Enum.find_index(secret, &(&1 == emoji)) do
        nil -> {i, emoji, :miss, 0, status(:miss)}
        j   -> d = distance(i, j); {i, emoji, d, points(d), status(d)}
      end
    end

  total = breakdown |> Enum.map(&elem(&1, 3)) |> Enum.sum()
  %{total: total, max: 600, percentage: round(total / 600 * 100), breakdown: breakdown}
end

The scoring story pins four totals against one secret, and they read straight off this fold: an exact crack is 600 at 100%; positions 0 and 1 swapped are each ADJACENT (80 points), the rest exact — 560 at 93%; the two ends swapped are MAX distance (0 points each) — 400 at 67%; one emoji absent is a clean MISS — 500 at 83%.

§2

The best total is the rank

board.ex · record/3

The leaderboard is one Valkey sorted set per game, and a player’s score on it is their best linear total. Board.record/3 reads the player’s current best from the cm:<game>:base hash, folds the new score in with max, and writes that best straight to the board ZSET — no read-modify-write beyond the best-of. Move an incoming score below to watch the fold.

max(old, incoming) — select an incoming total (stored best = 440)
incoming 380 max(old, in) max(440, 380) new base 440 ZADD board score 440 old base = 440 (from cm:<game>:base)
incoming 380 → max(440, 380) = 440 — the stored best wins, so the board is unchanged. A worse guess never lowers a player’s rank; only a new best moves it.
echo/…/codemojex/board.ex · record/3 source
def record(game, player, base) do
  old = hget_int(conn, k(game, "base"), player)
  new_base = max(old, base)
  Cmd.hset(k(game, "base"), player, to_string(new_base)) |> Wire.run(conn)   # cm:<game>:base
  Cmd.zadd(k(game, "board")) |> Cmd.score(new_base, player) |> Wire.run(conn) # cm:<game>:board
  new_base
end

There is no ptier, bonus, or tierfirst layer, and the guesses row stores only the linear points — no tier and no percentage column. The board ranks the raw best total, so the score a player sees is the score they earned.

§3

A perfect 600 closes a classic game

game.ex · ScoreWorker

Inside the scoring authority, right after the board is recorded, a perfect crack ends an open classic game immediately:

echo/…/codemojex/game.ex · ScoreWorker.handle/1 (the crack close) source
eff = Board.record(game, player, s.total)
# ... classic feedback fans out on the bus and over PubSub ...
if s.total == 600 and started?(game), do: Rooms.close_game(game)

# the authoritative state gate — only an :open game settles on a 600
defp started?(game), do: Map.get(Store.game(game) || %{}, :status) == :open

started?/1 reads the status from the system of record, not the cache, because a Golden Room’s cached status can lag its gather. So a 600 during a Golden Room’s :gathering builds standing without settling an unfilled pool — only an :open game closes on the crack. That close is the seam into settlement.

Next — C4.3 takes the close: the race resolved exactly once, and the three payout strategies the game selects.

§4

References

sources · related

Sources

Related