Gold

Source: src/gold.c · 193 lines · 23 functions.

Module entry points

These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.

How to read these diagrams

Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.

These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.

Functions in this file

gold_millis() · source line 38
static uint32_t gold_millis(void)
  1. Exit this functionreturn (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
Read original function source
static uint32_t gold_millis(void)
{
    return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
checksum_word() · source line 43
static void checksum_word(uint32_t *checksum, uint32_t value)
  1. FOR loopunsigned shift = 0; shift < 32; shift += 8

    Repeat body while the loop condition permits; then continue below.

      1. Execute*checksum ^= (uint8_t)(value >> shift);
      2. Execute*checksum *= 16777619U;
Read original function source
static void checksum_word(uint32_t *checksum, uint32_t value)
{
    for (unsigned shift = 0; shift < 32; shift += 8) {
        *checksum ^= (uint8_t)(value >> shift);
        *checksum *= 16777619U;
    }
}
record_checksum() · source line 51
static uint32_t record_checksum(const gold_record_t *record)

Calls into this documentation:

  1. Executeuint32_t checksum = 2166136261U ^ device_key;
  2. Executechecksum_word(&checksum, record->version);
  3. Executechecksum_word(&checksum, record->encoded_balance);
  4. Executechecksum_word(&checksum, record->nonce);
  5. Executeconst uint64_t totals[] = { record->user_won, record->user_lost, record->ai_won, record->ai_lost, };
  6. FOR loopsize_t index = 0; index < 4; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executechecksum_word(&checksum, (uint32_t)totals[index]);
      2. Executechecksum_word(&checksum, (uint32_t)(totals[index] >> 32));
  7. Executechecksum_word(&checksum, GOLD_SALT);
  8. Exit this functionreturn checksum;
Read original function source
static uint32_t record_checksum(const gold_record_t *record)
{
    uint32_t checksum = 2166136261U ^ device_key;
    checksum_word(&checksum, record->version);
    checksum_word(&checksum, record->encoded_balance);
    checksum_word(&checksum, record->nonce);
    const uint64_t totals[] = {
        record->user_won, record->user_lost, record->ai_won, record->ai_lost,
    };
    for (size_t index = 0; index < 4; ++index) {
        checksum_word(&checksum, (uint32_t)totals[index]);
        checksum_word(&checksum, (uint32_t)(totals[index] >> 32));
    }
    checksum_word(&checksum, GOLD_SALT);
    return checksum;
}
persist() · source line 68
static bool persist(gold_record_t next, uint32_t next_balance, GoldChangeType change)

Calls into this documentation:

  1. Decision!ready
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Executenext.version = 1;
  3. Executenext.nonce = esp_random();
  4. Executenext.encoded_balance = next_balance ^ device_key ^ next.nonce;
  5. Executenext.checksum = record_checksum(&next);
  6. Executestorage_healthy = game_storage_write(GOLD_NAMESPACE, "record", &next, sizeof(next));
  7. Decision!storage_healthy
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  8. Executesaved = next;
  9. Executebalance = next_balance;
  10. Decisionchange != GOLD_CHANGE_NONE
    True
      1. Executechange_type = change;
      2. Executechange_started = gold_millis();
      3. Execute++change_sequence;
    False

    Continue after this decision.

  11. Exit this functionreturn true;
Read original function source
static bool persist(gold_record_t next, uint32_t next_balance, GoldChangeType change)
{
    if (!ready) {
        return false;
    }
    next.version = 1;
    next.nonce = esp_random();
    next.encoded_balance = next_balance ^ device_key ^ next.nonce;
    next.checksum = record_checksum(&next);
    // Balance and lifetime totals are committed together in a single record.
    storage_healthy = game_storage_write(GOLD_NAMESPACE, "record", &next, sizeof(next));
    if (!storage_healthy) {
        return false;
    }
    saved = next;
    balance = next_balance;
    if (change != GOLD_CHANGE_NONE) {
        change_type = change;
        change_started = gold_millis();
        ++change_sequence;
    }
    return true;
}
initGold() · source line 92
bool initGold(void)

Calls into this documentation:

  1. Executeready = false;
  2. Executestorage_healthy = false;
  3. Executebalance = 0;
  4. Executesaved = (gold_record_t){0};
  5. Executeuint8_t mac[6];
  6. Decisionesp_efuse_mac_get_default(mac) != ESP_OK
    True
      1. ExecuteESP_LOGE("gold", "Cannot read device identifier");
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  7. Executedevice_key = GOLD_SALT;
  8. FOR loopsize_t index = 0; index < sizeof(mac); ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executedevice_key = (device_key ^ mac[index]) * 16777619U;
  9. Executebool found = false;
  10. Decision!game_storage_read(GOLD_NAMESPACE, "record", &saved, sizeof(saved), &found)
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  11. Decisionfound
    True
      1. Decisionsaved.version != 1 || saved.checksum != record_checksum(&saved)
        True
          1. ExecuteESP_LOGE("gold", "Invalid Gold record; stored data left intact");
          2. Executesaved = (gold_record_t){0};
          3. Exit this functionreturn false;
        False

        Continue after this decision.

      2. Executebalance = saved.encoded_balance ^ device_key ^ saved.nonce;
    False

    Continue after this decision.

  12. Executechange_type = GOLD_CHANGE_NONE;
  13. Executechange_started = 0;
  14. Executechange_sequence = 0;
  15. Executeready = true;
  16. Executestorage_healthy = true;
  17. Exit this functionreturn true;
Read original function source
bool initGold(void)
{
    ready = false;
    storage_healthy = false;
    balance = 0;
    saved = (gold_record_t){0};
    uint8_t mac[6];
    if (esp_efuse_mac_get_default(mac) != ESP_OK) {
        ESP_LOGE("gold", "Cannot read device identifier");
        return false;
    }
    device_key = GOLD_SALT;
    for (size_t index = 0; index < sizeof(mac); ++index) {
        device_key = (device_key ^ mac[index]) * 16777619U;
    }
    bool found = false;
    if (!game_storage_read(GOLD_NAMESPACE, "record", &saved, sizeof(saved), &found)) {
        return false;
    }
    if (found) {
        if (saved.version != 1 || saved.checksum != record_checksum(&saved)) {
            ESP_LOGE("gold", "Invalid Gold record; stored data left intact");
            saved = (gold_record_t){0};
            return false;
        }
        balance = saved.encoded_balance ^ device_key ^ saved.nonce;
    }
    change_type = GOLD_CHANGE_NONE;
    change_started = 0;
    change_sequence = 0;
    ready = true;
    storage_healthy = true;
    return true;
}
goldStorageHealthy() · source line 127
bool goldStorageHealthy(void)
  1. Exit this functionreturn ready && storage_healthy;
Read original function source
bool goldStorageHealthy(void) { return ready && storage_healthy; }
getGold() · source line 128
uint32_t getGold(void)
  1. Exit this functionreturn balance;
Read original function source
uint32_t getGold(void) { return balance; }
getUserGoldWon() · source line 129
uint64_t getUserGoldWon(void)
  1. Exit this functionreturn saved.user_won;
Read original function source
uint64_t getUserGoldWon(void) { return saved.user_won; }
getUserGoldLost() · source line 130
uint64_t getUserGoldLost(void)
  1. Exit this functionreturn saved.user_lost;
Read original function source
uint64_t getUserGoldLost(void) { return saved.user_lost; }
getAIGoldWon() · source line 131
uint64_t getAIGoldWon(void)
  1. Exit this functionreturn saved.ai_won;
Read original function source
uint64_t getAIGoldWon(void) { return saved.ai_won; }
getAIGoldLost() · source line 132
uint64_t getAIGoldLost(void)
  1. Exit this functionreturn saved.ai_lost;
Read original function source
uint64_t getAIGoldLost(void) { return saved.ai_lost; }
getGoldChangeType() · source line 133
GoldChangeType getGoldChangeType(void)
  1. Exit this functionreturn change_type;
Read original function source
GoldChangeType getGoldChangeType(void) { return change_type; }
getGoldChangeStarted() · source line 134
uint32_t getGoldChangeStarted(void)
  1. Exit this functionreturn change_started;
Read original function source
uint32_t getGoldChangeStarted(void) { return change_started; }
getGoldChangeSequence() · source line 135
uint32_t getGoldChangeSequence(void)
  1. Exit this functionreturn change_sequence;
Read original function source
uint32_t getGoldChangeSequence(void) { return change_sequence; }
addGold() · source line 137
bool addGold(uint32_t amount)

Calls into this documentation:

  1. Decision!ready || amount > UINT32_MAX - balance
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Exit this functionreturn amount == 0 || persist(saved, balance + amount, GOLD_CHANGE_ADDED);
Read original function source
bool addGold(uint32_t amount)
{
    if (!ready || amount > UINT32_MAX - balance) {
        return false;
    }
    return amount == 0 || persist(saved, balance + amount, GOLD_CHANGE_ADDED);
}
add_winnings() · source line 145
static bool add_winnings(uint32_t amount, bool ai)

Calls into this documentation:

  1. Executegold_record_t next = saved;
  2. Executeuint64_t *total = ai ? &next.ai_won : &next.user_won;
  3. Decision!ready || amount > UINT32_MAX - balance || amount > UINT64_MAX - *total
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  4. Execute*total += amount;
  5. Exit this functionreturn amount == 0 || persist(next, balance + amount, GOLD_CHANGE_ADDED);
Read original function source
static bool add_winnings(uint32_t amount, bool ai)
{
    gold_record_t next = saved;
    uint64_t *total = ai ? &next.ai_won : &next.user_won;
    if (!ready || amount > UINT32_MAX - balance || amount > UINT64_MAX - *total) {
        return false;
    }
    *total += amount;
    return amount == 0 || persist(next, balance + amount, GOLD_CHANGE_ADDED);
}
addUserGoldWon() · source line 156
bool addUserGoldWon(uint32_t amount)

Calls into this documentation:

  1. Exit this functionreturn add_winnings(amount, false);
Read original function source
bool addUserGoldWon(uint32_t amount) { return add_winnings(amount, false); }
addAIGold() · source line 157
bool addAIGold(uint32_t amount)

Calls into this documentation:

  1. Exit this functionreturn add_winnings(amount, true);
Read original function source
bool addAIGold(uint32_t amount) { return add_winnings(amount, true); }
record_loss() · source line 159
static bool record_loss(uint32_t amount, bool ai)

Calls into this documentation:

  1. Executegold_record_t next = saved;
  2. Executeuint64_t *total = ai ? &next.ai_lost : &next.user_lost;
  3. Decision!ready || amount > UINT64_MAX - *total
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  4. Execute*total += amount;
  5. Exit this functionreturn amount == 0 || persist(next, balance, GOLD_CHANGE_NONE);
Read original function source
static bool record_loss(uint32_t amount, bool ai)
{
    gold_record_t next = saved;
    uint64_t *total = ai ? &next.ai_lost : &next.user_lost;
    if (!ready || amount > UINT64_MAX - *total) {
        return false;
    }
    *total += amount;
    return amount == 0 || persist(next, balance, GOLD_CHANGE_NONE);
}
recordUserGoldLost() · source line 170
bool recordUserGoldLost(uint32_t amount)

Calls into this documentation:

  1. Exit this functionreturn record_loss(amount, false);
Read original function source
bool recordUserGoldLost(uint32_t amount) { return record_loss(amount, false); }
recordAIGoldLost() · source line 171
bool recordAIGoldLost(uint32_t amount)

Calls into this documentation:

  1. Exit this functionreturn record_loss(amount, true);
Read original function source
bool recordAIGoldLost(uint32_t amount) { return record_loss(amount, true); }
spendGold() · source line 173
bool spendGold(uint32_t amount)

Calls into this documentation:

  1. Decision!ready || amount > balance
    True
      1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Exit this functionreturn amount == 0 || persist(saved, balance - amount, GOLD_CHANGE_REMOVED);
Read original function source
bool spendGold(uint32_t amount)
{
    if (!ready || amount > balance) {
        return false;
    }
    return amount == 0 || persist(saved, balance - amount, GOLD_CHANGE_REMOVED);
}
drawGameGold() · source line 181
void drawGameGold(uint16_t *frame, int x, int y, int width, bool compact)

Calls into this documentation:

  1. Decision!goldStorageHealthy()
    True
      1. Executedraw_text(frame, x + 2, y + 2, "SAVE ERR", 1, RGB565(255, 70, 70));
      2. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executechar value[16];
  3. Executesnprintf(value, sizeof(value), "%" PRIu32, balance);
  4. Executedraw_text(frame, x + 2, y + 2, "GOLD", 1, RGB565(255, 210, 65));
  5. Executeconst int scale = !compact && width >= 124 ? 2 : 1;
  6. Executedraw_text(frame, x + 2, y + 13, value, scale, RGB565(240, 240, 240));
Read original function source
void drawGameGold(uint16_t *frame, int x, int y, int width, bool compact)
{
    if (!goldStorageHealthy()) {
        draw_text(frame, x + 2, y + 2, "SAVE ERR", 1, RGB565(255, 70, 70));
        return;
    }
    char value[16];
    snprintf(value, sizeof(value), "%" PRIu32, balance);
    draw_text(frame, x + 2, y + 2, "GOLD", 1, RGB565(255, 210, 65));
    const int scale = !compact && width >= 124 ? 2 : 1;
    draw_text(frame, x + 2, y + 13, value, scale, RGB565(240, 240, 240));
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "gold.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "game_storage.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <inttypes.h>
#include <stdio.h>

// Mini Arcade owns its namespace; the other firmware's Gold is not overwritten.
#define GOLD_NAMESPACE "arcade_gold"
#define GOLD_SALT 0x6D2B79F5U

typedef struct {
    uint32_t version;
    uint32_t encoded_balance;
    uint32_t nonce;
    uint32_t checksum;
    uint64_t user_won;
    uint64_t user_lost;
    uint64_t ai_won;
    uint64_t ai_lost;
} gold_record_t;

_Static_assert(sizeof(gold_record_t) == 48, "Gold record layout changed");

static gold_record_t saved = {0};
static uint32_t balance = 0;
static uint32_t device_key = 0;
static uint32_t change_started = 0;
static uint32_t change_sequence = 0;
static GoldChangeType change_type = GOLD_CHANGE_NONE;
static bool ready = false;
static bool storage_healthy = false;

static uint32_t gold_millis(void)
{
    return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}

static void checksum_word(uint32_t *checksum, uint32_t value)
{
    for (unsigned shift = 0; shift < 32; shift += 8) {
        *checksum ^= (uint8_t)(value >> shift);
        *checksum *= 16777619U;
    }
}

static uint32_t record_checksum(const gold_record_t *record)
{
    uint32_t checksum = 2166136261U ^ device_key;
    checksum_word(&checksum, record->version);
    checksum_word(&checksum, record->encoded_balance);
    checksum_word(&checksum, record->nonce);
    const uint64_t totals[] = {
        record->user_won, record->user_lost, record->ai_won, record->ai_lost,
    };
    for (size_t index = 0; index < 4; ++index) {
        checksum_word(&checksum, (uint32_t)totals[index]);
        checksum_word(&checksum, (uint32_t)(totals[index] >> 32));
    }
    checksum_word(&checksum, GOLD_SALT);
    return checksum;
}

static bool persist(gold_record_t next, uint32_t next_balance, GoldChangeType change)
{
    if (!ready) {
        return false;
    }
    next.version = 1;
    next.nonce = esp_random();
    next.encoded_balance = next_balance ^ device_key ^ next.nonce;
    next.checksum = record_checksum(&next);
    // Balance and lifetime totals are committed together in a single record.
    storage_healthy = game_storage_write(GOLD_NAMESPACE, "record", &next, sizeof(next));
    if (!storage_healthy) {
        return false;
    }
    saved = next;
    balance = next_balance;
    if (change != GOLD_CHANGE_NONE) {
        change_type = change;
        change_started = gold_millis();
        ++change_sequence;
    }
    return true;
}

bool initGold(void)
{
    ready = false;
    storage_healthy = false;
    balance = 0;
    saved = (gold_record_t){0};
    uint8_t mac[6];
    if (esp_efuse_mac_get_default(mac) != ESP_OK) {
        ESP_LOGE("gold", "Cannot read device identifier");
        return false;
    }
    device_key = GOLD_SALT;
    for (size_t index = 0; index < sizeof(mac); ++index) {
        device_key = (device_key ^ mac[index]) * 16777619U;
    }
    bool found = false;
    if (!game_storage_read(GOLD_NAMESPACE, "record", &saved, sizeof(saved), &found)) {
        return false;
    }
    if (found) {
        if (saved.version != 1 || saved.checksum != record_checksum(&saved)) {
            ESP_LOGE("gold", "Invalid Gold record; stored data left intact");
            saved = (gold_record_t){0};
            return false;
        }
        balance = saved.encoded_balance ^ device_key ^ saved.nonce;
    }
    change_type = GOLD_CHANGE_NONE;
    change_started = 0;
    change_sequence = 0;
    ready = true;
    storage_healthy = true;
    return true;
}

bool goldStorageHealthy(void) { return ready && storage_healthy; }
uint32_t getGold(void) { return balance; }
uint64_t getUserGoldWon(void) { return saved.user_won; }
uint64_t getUserGoldLost(void) { return saved.user_lost; }
uint64_t getAIGoldWon(void) { return saved.ai_won; }
uint64_t getAIGoldLost(void) { return saved.ai_lost; }
GoldChangeType getGoldChangeType(void) { return change_type; }
uint32_t getGoldChangeStarted(void) { return change_started; }
uint32_t getGoldChangeSequence(void) { return change_sequence; }

bool addGold(uint32_t amount)
{
    if (!ready || amount > UINT32_MAX - balance) {
        return false;
    }
    return amount == 0 || persist(saved, balance + amount, GOLD_CHANGE_ADDED);
}

static bool add_winnings(uint32_t amount, bool ai)
{
    gold_record_t next = saved;
    uint64_t *total = ai ? &next.ai_won : &next.user_won;
    if (!ready || amount > UINT32_MAX - balance || amount > UINT64_MAX - *total) {
        return false;
    }
    *total += amount;
    return amount == 0 || persist(next, balance + amount, GOLD_CHANGE_ADDED);
}

bool addUserGoldWon(uint32_t amount) { return add_winnings(amount, false); }
bool addAIGold(uint32_t amount) { return add_winnings(amount, true); }

static bool record_loss(uint32_t amount, bool ai)
{
    gold_record_t next = saved;
    uint64_t *total = ai ? &next.ai_lost : &next.user_lost;
    if (!ready || amount > UINT64_MAX - *total) {
        return false;
    }
    *total += amount;
    return amount == 0 || persist(next, balance, GOLD_CHANGE_NONE);
}

bool recordUserGoldLost(uint32_t amount) { return record_loss(amount, false); }
bool recordAIGoldLost(uint32_t amount) { return record_loss(amount, true); }

bool spendGold(uint32_t amount)
{
    if (!ready || amount > balance) {
        return false;
    }
    return amount == 0 || persist(saved, balance - amount, GOLD_CHANGE_REMOVED);
}

void drawGameGold(uint16_t *frame, int x, int y, int width, bool compact)
{
    if (!goldStorageHealthy()) {
        drawPet2001TextTransparent(frame, x + 2, y + 2, "SAVE ERR", RGB565(255, 70, 70), 1);
        return;
    }
    char value[16];
    snprintf(value, sizeof(value), "%" PRIu32, balance);
    drawPet2001TextTransparent(frame, x + 2, y + 2, "GOLD", RGB565(255, 210, 65), 1);
    const int scale = !compact && width >= 124 ? 2 : 1;
    drawPet2001TextTransparent(frame, x + 2, y + 13, value, RGB565(240, 240, 240), scale);
}