World generator

Source: src/dungeon_generator.c · 499 lines · 24 functions.

Module responsibility

Builds the three procedural floors, rooms, passages, stairs, events and treasures, then validates the result.

See how this module fits into the complete Dungeon system →

Module entry points

These functions are exposed to the rest of the Dungeon system. Open one to follow its decisions and helper calls.

How to read these diagrams

Each function has an expandable structural diagram derived from the current C source. Decisions split into True and False, loop bodies are indented, and arrows show statement order.

These are source diagrams before preprocessing, not runtime traces. Callback execution, task scheduling and macro expansion are not inferred.

Functions in this file

ix() · source line 11
static uint16_t ix(uint8_t x, uint8_t y)
  1. Exit this functionreturn (uint16_t)y * DUNGEON_WIDTH + x;
Read original function source
static uint16_t ix(uint8_t x, uint8_t y) {
  return (uint16_t)y * DUNGEON_WIDTH + x;
}
cx() · source line 14
static uint8_t cx(uint16_t n)
  1. Exit this functionreturn (uint8_t)(n % DUNGEON_WIDTH);
Read original function source
static uint8_t cx(uint16_t n) { return (uint8_t)(n % DUNGEON_WIDTH); }
cy() · source line 15
static uint8_t cy(uint16_t n)
  1. Exit this functionreturn (uint8_t)(n / DUNGEON_WIDTH);
Read original function source
static uint8_t cy(uint16_t n) { return (uint8_t)(n / DUNGEON_WIDTH); }
ex() · source line 16
static uint8_t ex(uint8_t d)
  1. Executestatic const uint8_t v[] = {1U, 2U, 4U, 8U};
  2. Exit this functionreturn v[d];
Read original function source
static uint8_t ex(uint8_t d) {
  static const uint8_t v[] = {1U, 2U, 4U, 8U};
  return v[d];
}
op() · source line 20
static uint8_t op(uint8_t d)
  1. Executestatic const uint8_t v[] = {4U, 8U, 1U, 2U};
  2. Exit this functionreturn v[d];
Read original function source
static uint8_t op(uint8_t d) {
  static const uint8_t v[] = {4U, 8U, 1U, 2U};
  return v[d];
}
ec() · source line 24
static uint8_t ec(uint8_t e)
  1. Executeuint8_t n = 0U;
  2. FOR loopuint8_t i = 0U; i < 4U; ++i

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

    1. Executen += (e >> i) & 1U;
  3. Exit this functionreturn n;
Read original function source
static uint8_t ec(uint8_t e) {
  uint8_t n = 0U;
  for (uint8_t i = 0U; i < 4U; ++i)
    n += (e >> i) & 1U;
  return n;
}
next() · source line 30
static bool next(uint16_t c, uint8_t d, uint16_t *n)

Calls into this documentation:

  1. Executeuint8_t x = cx(c), y = cy(c);
  2. Decisiond == 0U && y > 0U
    True
    1. Execute*n = ix(x, y - 1U);
    False
    1. Decisiond == 1U && x + 1U < DUNGEON_WIDTH
      True
      1. Execute*n = ix(x + 1U, y);
      False
      1. Decisiond == 2U && y + 1U < DUNGEON_HEIGHT
        True
        1. Execute*n = ix(x, y + 1U);
        False
        1. Decisiond == 3U && x > 0U
          True
          1. Execute*n = ix(x - 1U, y);
          False
          1. Exit this functionreturn false;
  3. Exit this functionreturn true;
Read original function source
static bool next(uint16_t c, uint8_t d, uint16_t *n) {
  uint8_t x = cx(c), y = cy(c);
  if (d == 0U && y > 0U)
    *n = ix(x, y - 1U);
  else if (d == 1U && x + 1U < DUNGEON_WIDTH)
    *n = ix(x + 1U, y);
  else if (d == 2U && y + 1U < DUNGEON_HEIGHT)
    *n = ix(x, y + 1U);
  else if (d == 3U && x > 0U)
    *n = ix(x - 1U, y);
  else
    return false;
  return true;
}
room() · source line 44
static bool room(uint8_t t)
  1. Exit this functionreturn t == DUNGEON_TILE_DEAD_END_ROOM_ROUND || t == DUNGEON_TILE_DEAD_END_ROOM_SQUARE || t == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
Read original function source
static bool room(uint8_t t) {
  return t == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
         t == DUNGEON_TILE_DEAD_END_ROOM_SQUARE ||
         t == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}
shuffle() · source line 49
static void shuffle(uint16_t n)
  1. FOR loop; n > 1U; --n

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

      1. Executeuint16_t s = (uint16_t)(esp_random() % n), t = candidates[n - 1U];
      2. Executecandidates[n - 1U] = candidates[s];
      3. Executecandidates[s] = t;
Read original function source
static void shuffle(uint16_t n) {
  for (; n > 1U; --n) {
    uint16_t s = (uint16_t)(esp_random() % n), t = candidates[n - 1U];
    candidates[n - 1U] = candidates[s];
    candidates[s] = t;
  }
}
validate_floor() · source line 57
static DungeonGenerationError validate_floor(const DungeonFloor *f,
                                             uint16_t *reachable)

Calls into this documentation:

  1. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

    1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

        1. Executeuint16_t c = ix(x, y);
        2. FOR loopuint8_t d = 0; d < 4; ++d

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

          1. Decisionf->cells[y][x].exits & ex(d)
            True
              1. Executeuint16_t n;
              2. Decision!next(c, d, &n)
                True
                1. Exit this functionreturn DUNGEON_GENERATION_OUTSIDE_EXIT;
                False

                Continue after this decision.

              3. Decision!(f->cells[cy(n)][cx(n)].exits & op(d))
                True
                1. Exit this functionreturn DUNGEON_GENERATION_EXIT_MISMATCH;
                False

                Continue after this decision.

            False

            Continue after this decision.

  2. Executememset(visited, 0, sizeof(visited));
  3. Executeuint16_t a = 0, b = 0;
  4. Executequeue[b++] = 0;
  5. Executevisited[0] = 1U;
  6. WHILE loopa < b

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

      1. Executeuint16_t c = queue[a++];
      2. FOR loopuint8_t d = 0; d < 4; ++d

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

          1. Executeuint16_t n;
          2. Decision(f->cells[cy(c)][cx(c)].exits & ex(d)) && next(c, d, &n) && !visited[n]
            True
              1. Executevisited[n] = 1U;
              2. Executequeue[b++] = n;
            False

            Continue after this decision.

  7. Execute*reachable = b;
  8. Exit this functionreturn b == CELLS ? DUNGEON_GENERATION_OK : DUNGEON_GENERATION_UNREACHABLE_CELL;
Read original function source
static DungeonGenerationError validate_floor(const DungeonFloor *f,
                                             uint16_t *reachable) {
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
      uint16_t c = ix(x, y);
      for (uint8_t d = 0; d < 4; ++d)
        if (f->cells[y][x].exits & ex(d)) {
          uint16_t n;
          if (!next(c, d, &n))
            return DUNGEON_GENERATION_OUTSIDE_EXIT;
          if (!(f->cells[cy(n)][cx(n)].exits & op(d)))
            return DUNGEON_GENERATION_EXIT_MISMATCH;
        }
    }
  memset(visited, 0, sizeof(visited));
  uint16_t a = 0, b = 0;
  queue[b++] = 0;
  visited[0] = 1U;
  while (a < b) {
    uint16_t c = queue[a++];
    for (uint8_t d = 0; d < 4; ++d) {
      uint16_t n;
      if ((f->cells[cy(c)][cx(c)].exits & ex(d)) && next(c, d, &n) &&
          !visited[n]) {
        visited[n] = 1U;
        queue[b++] = n;
      }
    }
  }
  *reachable = b;
  return b == CELLS ? DUNGEON_GENERATION_OK
                    : DUNGEON_GENERATION_UNREACHABLE_CELL;
}
classify() · source line 90
static void classify(DungeonFloor *f, DungeonGenerationStats *s)

Calls into this documentation:

  1. Executeuint16_t count = 0;
  2. Executeuint32_t exits = 0;
  3. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

    1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

        1. ExecuteDungeonCell *c = &f->cells[y][x];
        2. Executeuint8_t n = ec(c->exits);
        3. Executeexits += n;
        4. Executec->tileType = n == 1U ? DUNGEON_TILE_DEAD_END_PASSAGE : DUNGEON_TILE_PASSAGE;
        5. Decisionn == 1U
          True
          1. Executedead_ends[count++] = ix(x, y);
          False

          Continue after this decision.

  4. Executeuint16_t rooms = count < getDungeonConfig()->roomsPerFloor ? count : getDungeonConfig()->roomsPerFloor;
  5. FOR loopuint16_t i = 0; i < rooms; ++i

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

      1. Executeuint16_t p = i + (uint16_t)(esp_random() % (count - i)), t = dead_ends[i];
      2. Executedead_ends[i] = dead_ends[p];
      3. Executedead_ends[p] = t;
      4. Executef->cells[cy(dead_ends[i])][cx(dead_ends[i])].tileType = i % 3U == 0U ? DUNGEON_TILE_DEAD_END_ROOM_ROUND : i % 3U == 1U ? DUNGEON_TILE_DEAD_END_ROOM_SQUARE : DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
  6. Executes->rooms = rooms;
  7. Executes->deadEnds = count;
  8. Executes->connections = (uint16_t)(exits / 2U);
  9. Executes->loops = (uint16_t)(s->connections - CELLS + 1U);
Read original function source
static void classify(DungeonFloor *f, DungeonGenerationStats *s) {
  uint16_t count = 0;
  uint32_t exits = 0;
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
      DungeonCell *c = &f->cells[y][x];
      uint8_t n = ec(c->exits);
      exits += n;
      c->tileType =
          n == 1U ? DUNGEON_TILE_DEAD_END_PASSAGE : DUNGEON_TILE_PASSAGE;
      if (n == 1U)
        dead_ends[count++] = ix(x, y);
    }
  uint16_t rooms = count < getDungeonConfig()->roomsPerFloor
                       ? count
                       : getDungeonConfig()->roomsPerFloor;
  for (uint16_t i = 0; i < rooms; ++i) {
    uint16_t p = i + (uint16_t)(esp_random() % (count - i)), t = dead_ends[i];
    dead_ends[i] = dead_ends[p];
    dead_ends[p] = t;
    f->cells[cy(dead_ends[i])][cx(dead_ends[i])].tileType =
        i % 3U == 0U   ? DUNGEON_TILE_DEAD_END_ROOM_ROUND
        : i % 3U == 1U ? DUNGEON_TILE_DEAD_END_ROOM_SQUARE
                       : DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
  }
  s->rooms = rooms;
  s->deadEnds = count;
  s->connections = (uint16_t)(exits / 2U);
  s->loops = (uint16_t)(s->connections - CELLS + 1U);
}
generateDungeonFloor() · source line 120
bool generateDungeonFloor(uint8_t floor, DungeonGenerationStats *s)

Calls into this documentation:

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

    Continue after this decision.

  2. Executememset(s, 0, sizeof(*s));
  3. Decisionfloor >= DUNGEON_FLOOR_COUNT
    True
      1. Executes->error = DUNGEON_GENERATION_INVALID_FLOOR;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  4. ExecuteDungeonFloor *f = &getDungeonWorld()->floors[floor];
  5. Executememset(f, 0, sizeof(*f));
  6. Executememset(visited, 0, sizeof(visited));
  7. Executeuint16_t depth = 0, start = ix((uint8_t)(esp_random() % DUNGEON_WIDTH), (uint8_t)(esp_random() % DUNGEON_HEIGHT));
  8. Executestack[depth++] = start;
  9. Executevisited[start] = 1U;
  10. WHILE loopdepth

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

      1. Executeuint16_t c = stack[depth - 1U], n;
      2. Executeuint8_t choices[4], count = 0;
      3. FOR loopuint8_t d = 0; d < 4; ++d

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

        1. Decisionnext(c, d, &n) && !visited[n]
          True
          1. Executechoices[count++] = d;
          False

          Continue after this decision.

      4. Decision!count
        True
          1. Execute--depth;
          2. Next loop iterationcontinue;
        False

        Continue after this decision.

      5. Executeuint8_t d = choices[esp_random() % count];
      6. Execute(void)next(c, d, &n);
      7. Executef->cells[cy(c)][cx(c)].exits |= ex(d);
      8. Executef->cells[cy(n)][cx(n)].exits |= op(d);
      9. Executevisited[n] = 1U;
      10. Executestack[depth++] = n;
  11. Executeuint16_t target = (uint16_t)(((uint32_t)CELLS * getDungeonConfig()->extraConnectionPercent + 99U) / 100U), added = 0;
  12. FOR loopuint32_t tries = 0; added < target && tries < (uint32_t)CELLS * 10U; ++tries

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

      1. Executeuint16_t c = (uint16_t)(esp_random() % CELLS), n;
      2. Executeuint8_t d = (uint8_t)(esp_random() % 4U);
      3. Decision!next(c, d, &n) || (f->cells[cy(c)][cx(c)].exits & ex(d))
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      4. Executef->cells[cy(c)][cx(c)].exits |= ex(d);
      5. Executef->cells[cy(n)][cx(n)].exits |= op(d);
      6. Execute++added;
  13. Executeclassify(f, s);
  14. Executes->error = validate_floor(f, &s->reachableCells);
  15. Executes->valid = s->error == DUNGEON_GENERATION_OK;
  16. Exit this functionreturn s->valid;
Read original function source
bool generateDungeonFloor(uint8_t floor, DungeonGenerationStats *s) {
  if (!s)
    return false;
  memset(s, 0, sizeof(*s));
  if (floor >= DUNGEON_FLOOR_COUNT) {
    s->error = DUNGEON_GENERATION_INVALID_FLOOR;
    return false;
  }
  DungeonFloor *f = &getDungeonWorld()->floors[floor];
  memset(f, 0, sizeof(*f));
  memset(visited, 0, sizeof(visited));
  uint16_t depth = 0, start = ix((uint8_t)(esp_random() % DUNGEON_WIDTH),
                                 (uint8_t)(esp_random() % DUNGEON_HEIGHT));
  stack[depth++] = start;
  visited[start] = 1U;
  while (depth) {
    uint16_t c = stack[depth - 1U], n;
    uint8_t choices[4], count = 0;
    for (uint8_t d = 0; d < 4; ++d)
      if (next(c, d, &n) && !visited[n])
        choices[count++] = d;
    if (!count) {
      --depth;
      continue;
    }
    uint8_t d = choices[esp_random() % count];
    (void)next(c, d, &n);
    f->cells[cy(c)][cx(c)].exits |= ex(d);
    f->cells[cy(n)][cx(n)].exits |= op(d);
    visited[n] = 1U;
    stack[depth++] = n;
  }
  uint16_t target = (uint16_t)(((uint32_t)CELLS *
                                    getDungeonConfig()->extraConnectionPercent +
                                99U) /
                               100U),
           added = 0;
  for (uint32_t tries = 0; added < target && tries < (uint32_t)CELLS * 10U;
       ++tries) {
    uint16_t c = (uint16_t)(esp_random() % CELLS), n;
    uint8_t d = (uint8_t)(esp_random() % 4U);
    if (!next(c, d, &n) || (f->cells[cy(c)][cx(c)].exits & ex(d)))
      continue;
    f->cells[cy(c)][cx(c)].exits |= ex(d);
    f->cells[cy(n)][cx(n)].exits |= op(d);
    ++added;
  }
  classify(f, s);
  s->error = validate_floor(f, &s->reachableCells);
  s->valid = s->error == DUNGEON_GENERATION_OK;
  return s->valid;
}
choose_dead() · source line 172
static bool choose_dead(uint8_t floor, DungeonPosition *p)

Calls into this documentation:

  1. Executeuint16_t n = 0;
  2. ExecuteDungeonFloor *f = &getDungeonWorld()->floors[floor];
  3. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

    1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

      1. Decisionf->cells[y][x].tileType == DUNGEON_TILE_DEAD_END_PASSAGE
        True
        1. Executecandidates[n++] = ix(x, y);
        False

        Continue after this decision.

  4. Decision!n
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  5. Executeuint16_t c = candidates[esp_random() % n];
  6. Execute*p = (DungeonPosition){floor, cx(c), cy(c)};
  7. Exit this functionreturn true;
Read original function source
static bool choose_dead(uint8_t floor, DungeonPosition *p) {
  uint16_t n = 0;
  DungeonFloor *f = &getDungeonWorld()->floors[floor];
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
      if (f->cells[y][x].tileType == DUNGEON_TILE_DEAD_END_PASSAGE)
        candidates[n++] = ix(x, y);
  if (!n)
    return false;
  uint16_t c = candidates[esp_random() % n];
  *p = (DungeonPosition){floor, cx(c), cy(c)};
  return true;
}
choose_straight() · source line 185
static bool choose_straight(uint8_t floor, DungeonPosition *p,
                            const DungeonPosition *skip)

Calls into this documentation:

  1. Executeuint16_t n = 0;
  2. ExecuteDungeonFloor *f = &getDungeonWorld()->floors[floor];
  3. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

    1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

        1. ExecuteDungeonCell *c = &f->cells[y][x];
        2. Decisionc->tileType == DUNGEON_TILE_PASSAGE && (c->exits == (DUNGEON_EXIT_NORTH | DUNGEON_EXIT_SOUTH) || c->exits == (DUNGEON_EXIT_EAST | DUNGEON_EXIT_WEST)) && !(skip && skip->floor == floor && skip->x == x && skip->y == y)
          True
          1. Executecandidates[n++] = ix(x, y);
          False

          Continue after this decision.

  4. Decision!n
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  5. Executeuint16_t c = candidates[esp_random() % n];
  6. Execute*p = (DungeonPosition){floor, cx(c), cy(c)};
  7. Exit this functionreturn true;
Read original function source
static bool choose_straight(uint8_t floor, DungeonPosition *p,
                            const DungeonPosition *skip) {
  uint16_t n = 0;
  DungeonFloor *f = &getDungeonWorld()->floors[floor];
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
      DungeonCell *c = &f->cells[y][x];
      if (c->tileType == DUNGEON_TILE_PASSAGE &&
          (c->exits == (DUNGEON_EXIT_NORTH | DUNGEON_EXIT_SOUTH) ||
           c->exits == (DUNGEON_EXIT_EAST | DUNGEON_EXIT_WEST)) &&
          !(skip && skip->floor == floor && skip->x == x && skip->y == y))
        candidates[n++] = ix(x, y);
    }
  if (!n)
    return false;
  uint16_t c = candidates[esp_random() % n];
  *p = (DungeonPosition){floor, cx(c), cy(c)};
  return true;
}
erange() · source line 204
static void erange(uint8_t f, uint8_t *lo, uint8_t *hi)

Calls into this documentation:

  1. Executeconst DungeonConfig *c = getDungeonConfig();
  2. Execute*lo = f == 0U ? c->floor1EnemyMinLevel : f == 1U ? c->floor2EnemyMinLevel : c->floor3EnemyMinLevel;
  3. Execute*hi = f == 0U ? c->floor1EnemyMaxLevel : f == 1U ? c->floor2EnemyMaxLevel : c->floor3EnemyMaxLevel;
Read original function source
static void erange(uint8_t f, uint8_t *lo, uint8_t *hi) {
  const DungeonConfig *c = getDungeonConfig();
  *lo = f == 0U   ? c->floor1EnemyMinLevel
        : f == 1U ? c->floor2EnemyMinLevel
                  : c->floor3EnemyMinLevel;
  *hi = f == 0U   ? c->floor1EnemyMaxLevel
        : f == 1U ? c->floor2EnemyMaxLevel
                  : c->floor3EnemyMaxLevel;
}
event() · source line 213
static void event(DungeonCell *c, uint16_t flags)
  1. Executec->eventFlags = flags;
  2. Executec->stateFlags = DUNGEON_STATE_NONE;
  3. Executec->variant = 1U;
Read original function source
static void event(DungeonCell *c, uint16_t flags) {
  c->eventFlags = flags;
  c->stateFlags = DUNGEON_STATE_NONE;
  c->variant = 1U;
}
treasure() · source line 218
static uint8_t treasure(void)

Calls into this documentation:

  1. Executeconst DungeonConfig *c = getDungeonConfig();
  2. Decisionc->rareTreasurePercent && esp_random() % 100U < c->rareTreasurePercent
    True
      1. Executeuint32_t t = (uint32_t)c->rareLifeRatio + c->rareAttackRatio + c->rareDefenceRatio, r = t ? esp_random() % t : 0U;
      2. Decisionr < c->rareLifeRatio
        True
        1. Exit this functionreturn DUNGEON_TREASURE_LIFE;
        False

        Continue after this decision.

      3. Executer -= c->rareLifeRatio;
      4. Exit this functionreturn r < c->rareAttackRatio ? DUNGEON_TREASURE_ATTACK : DUNGEON_TREASURE_DEFENCE;
    False

    Continue after this decision.

  3. Executeuint32_t t = (uint32_t)c->normalGoldRatio + c->normalHealthRatio;
  4. Exit this functionreturn !t || esp_random() % t < c->normalGoldRatio ? DUNGEON_TREASURE_GOLD : DUNGEON_TREASURE_HEALTH;
Read original function source
static uint8_t treasure(void) {
  const DungeonConfig *c = getDungeonConfig();
  if (c->rareTreasurePercent && esp_random() % 100U < c->rareTreasurePercent) {
    uint32_t t = (uint32_t)c->rareLifeRatio + c->rareAttackRatio +
                 c->rareDefenceRatio,
             r = t ? esp_random() % t : 0U;
    if (r < c->rareLifeRatio)
      return DUNGEON_TREASURE_LIFE;
    r -= c->rareLifeRatio;
    return r < c->rareAttackRatio ? DUNGEON_TREASURE_ATTACK
                                  : DUNGEON_TREASURE_DEFENCE;
  }
  uint32_t t = (uint32_t)c->normalGoldRatio + c->normalHealthRatio;
  return !t || esp_random() % t < c->normalGoldRatio ? DUNGEON_TREASURE_GOLD
                                                     : DUNGEON_TREASURE_HEALTH;
}
key_room() · source line 234
static bool key_room(DungeonPosition *p)

Calls into this documentation:

  1. Executeuint16_t n = 0;
  2. ExecuteDungeonWorld *w = getDungeonWorld();
  3. FOR loopuint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f

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

    1. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

      1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

        1. Decisionroom(w->floors[f].cells[y][x].tileType)
          True
            1. Execute++n;
            2. Decisionesp_random() % n == 0U
              True
              1. Execute*p = (DungeonPosition){f, x, y};
              False

              Continue after this decision.

          False

          Continue after this decision.

  4. Exit this functionreturn n > 0;
Read original function source
static bool key_room(DungeonPosition *p) {
  uint16_t n = 0;
  DungeonWorld *w = getDungeonWorld();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
        if (room(w->floors[f].cells[y][x].tileType)) {
          ++n;
          if (esp_random() % n == 0U)
            *p = (DungeonPosition){f, x, y};
        }
  return n > 0;
}
room_counts() · source line 247
static void room_counts(uint16_t n, uint16_t out[3])

Calls into this documentation:

  1. Executeconst DungeonConfig *c = getDungeonConfig();
  2. Executeuint16_t w[3] = {c->enemyRoomRatio, c->treasureRoomRatio, c->emptyRoomRatio};
  3. Executeuint32_t total = (uint32_t)w[0] + w[1] + w[2], rem[3];
  4. Executeuint16_t done = 0;
  5. Decision!total
    True
      1. Executeout[2] = n;
      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. FOR loopuint8_t i = 0; i < 3; ++i

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

      1. Executeuint32_t q = (uint32_t)n * w[i];
      2. Executeout[i] = (uint16_t)(q / total);
      3. Executerem[i] = q % total;
      4. Executedone += out[i];
  7. WHILE loopdone < n

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

      1. Executeuint32_t high = 0;
      2. Executeuint8_t ties[3], nt = 0;
      3. FOR loopuint8_t i = 0; i < 3; ++i

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

        1. Decisionrem[i] > high
          True
            1. Executehigh = rem[i];
            2. Executeties[0] = i;
            3. Executent = 1;
          False
          1. Decisionrem[i] == high
            True
            1. Executeties[nt++] = i;
            False

            Continue after this decision.

      4. Executeuint8_t k = ties[esp_random() % nt];
      5. Execute++out[k];
      6. Executerem[k] = 0;
      7. Execute++done;
Read original function source
static void room_counts(uint16_t n, uint16_t out[3]) {
  const DungeonConfig *c = getDungeonConfig();
  uint16_t w[3] = {c->enemyRoomRatio, c->treasureRoomRatio, c->emptyRoomRatio};
  uint32_t total = (uint32_t)w[0] + w[1] + w[2], rem[3];
  uint16_t done = 0;
  if (!total) {
    out[2] = n;
    return;
  }
  for (uint8_t i = 0; i < 3; ++i) {
    uint32_t q = (uint32_t)n * w[i];
    out[i] = (uint16_t)(q / total);
    rem[i] = q % total;
    done += out[i];
  }
  while (done < n) {
    uint32_t high = 0;
    uint8_t ties[3], nt = 0;
    for (uint8_t i = 0; i < 3; ++i)
      if (rem[i] > high) {
        high = rem[i];
        ties[0] = i;
        nt = 1;
      } else if (rem[i] == high)
        ties[nt++] = i;
    uint8_t k = ties[esp_random() % nt];
    ++out[k];
    rem[k] = 0;
    ++done;
  }
}
events() · source line 278
static void events(const DungeonPosition *key)

Calls into this documentation:

  1. ExecuteDungeonWorld *w = getDungeonWorld();
  2. FOR loopuint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f

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

      1. Executeuint16_t n = 0;
      2. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

        1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

          1. Decisionroom(w->floors[f].cells[y][x].tileType) && !(key->floor == f && key->x == x && key->y == y)
            True
            1. Executecandidates[n++] = ix(x, y);
            False

            Continue after this decision.

      3. Executeshuffle(n);
      4. Executeuint16_t types[3] = {0, 0, 0};
      5. Executeroom_counts(n, types);
      6. Executeuint8_t lo, hi;
      7. Executeerange(f, &lo, &hi);
      8. Executeuint16_t i = 0;
      9. FOR loop; i < types[0]; ++i

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

          1. ExecuteDungeonCell *c = &w->floors[f].cells[cy(candidates[i])][cx(candidates[i])];
          2. Executeevent(c, DUNGEON_EVENT_ENCOUNTER | ((uint16_t)(lo + esp_random() % (hi - lo + 1U)) << DUNGEON_ENEMY_DETAIL_SHIFT));
      10. FOR loop; i < (uint16_t)(types[0] + types[1]); ++i

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

        1. Executeevent(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])], DUNGEON_EVENT_TREASURE | ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
Read original function source
static void events(const DungeonPosition *key) {
  DungeonWorld *w = getDungeonWorld();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
    uint16_t n = 0;
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
        if (room(w->floors[f].cells[y][x].tileType) &&
            !(key->floor == f && key->x == x && key->y == y))
          candidates[n++] = ix(x, y);
    shuffle(n);
    uint16_t types[3] = {0, 0, 0};
    room_counts(n, types);
    uint8_t lo, hi;
    erange(f, &lo, &hi);
    uint16_t i = 0;
    for (; i < types[0]; ++i) {
      DungeonCell *c =
          &w->floors[f].cells[cy(candidates[i])][cx(candidates[i])];
      event(c, DUNGEON_EVENT_ENCOUNTER |
                   ((uint16_t)(lo + esp_random() % (hi - lo + 1U))
                    << DUNGEON_ENEMY_DETAIL_SHIFT));
    }
    for (; i < (uint16_t)(types[0] + types[1]); ++i)
      event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
            DUNGEON_EVENT_TREASURE |
                ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
  }
}
corridors() · source line 306
static void corridors(void)

Calls into this documentation:

  1. ExecuteDungeonWorld *w = getDungeonWorld();
  2. Executeconst DungeonConfig *c = getDungeonConfig();
  3. FOR loopuint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f

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

      1. Executeuint16_t r = 0, n = 0;
      2. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

        1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

            1. Executeuint8_t t = w->floors[f].cells[y][x].tileType;
            2. Decisionroom(t)
              True
              1. Execute++r;
              False

              Continue after this decision.

            3. Decisiont == DUNGEON_TILE_PASSAGE || t == DUNGEON_TILE_DEAD_END_PASSAGE
              True
              1. Executecandidates[n++] = ix(x, y);
              False

              Continue after this decision.

      3. Executeuint16_t traps = (uint16_t)(((uint32_t)r * c->trapsPer100Rooms + 99U) / 100U), tr = (uint16_t)(((uint32_t)r * c->corridorTreasuresPer100Rooms + 99U) / 100U);
      4. Decisiontraps > n
        True
        1. Executetraps = n;
        False

        Continue after this decision.

      5. Decisiontr > n - traps
        True
        1. Executetr = n - traps;
        False

        Continue after this decision.

      6. Executeshuffle(n);
      7. FOR loopuint16_t i = 0; i < traps; ++i

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

        1. Executeevent(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])], DUNGEON_EVENT_TRAP);
      8. FOR loopuint16_t i = traps; i < (uint16_t)(traps + tr); ++i

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

        1. Executeevent(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])], DUNGEON_EVENT_TREASURE | ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
Read original function source
static void corridors(void) {
  DungeonWorld *w = getDungeonWorld();
  const DungeonConfig *c = getDungeonConfig();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
    uint16_t r = 0, n = 0;
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
        uint8_t t = w->floors[f].cells[y][x].tileType;
        if (room(t))
          ++r;
        if (t == DUNGEON_TILE_PASSAGE || t == DUNGEON_TILE_DEAD_END_PASSAGE)
          candidates[n++] = ix(x, y);
      }
    uint16_t traps =
                 (uint16_t)(((uint32_t)r * c->trapsPer100Rooms + 99U) / 100U),
             tr = (uint16_t)(((uint32_t)r * c->corridorTreasuresPer100Rooms +
                              99U) /
                             100U);
    if (traps > n)
      traps = n;
    if (tr > n - traps)
      tr = n - traps;
    shuffle(n);
    for (uint16_t i = 0; i < traps; ++i)
      event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
            DUNGEON_EVENT_TRAP);
    for (uint16_t i = traps; i < (uint16_t)(traps + tr); ++i)
      event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
            DUNGEON_EVENT_TREASURE |
                ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
  }
}
validateDungeonWorld() · source line 338
bool validateDungeonWorld(DungeonWorldGenerationStats *s)

Calls into this documentation:

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

    Continue after this decision.

  2. Executememset(s, 0, sizeof(*s));
  3. ExecuteDungeonWorld *w = getDungeonWorld();
  4. FOR loopuint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f

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

      1. ExecuteDungeonGenerationStats *fs = &s->floors[f];
      2. Executefs->error = validate_floor(&w->floors[f], &fs->reachableCells);
      3. Decisionfs->error != DUNGEON_GENERATION_OK
        True
          1. Executes->error = fs->error;
          2. Exit this functionreturn false;
        False

        Continue after this decision.

      4. FOR loopuint8_t y = 0; y < DUNGEON_HEIGHT; ++y

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

        1. FOR loopuint8_t x = 0; x < DUNGEON_WIDTH; ++x

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

            1. ExecuteDungeonCell *c = &w->floors[f].cells[y][x];
            2. Decisionc->tileType > DUNGEON_TILE_STAIRS_BOTH
              True
                1. Executes->error = DUNGEON_GENERATION_INVALID_TILE;
                2. Exit this functionreturn false;
              False

              Continue after this decision.

            3. Decisionec(c->exits) == 1U
              True
              1. Execute++fs->deadEnds;
              False

              Continue after this decision.

            4. Decisionroom(c->tileType)
              True
                1. Execute++fs->rooms;
                2. Decisionc->eventFlags & DUNGEON_EVENT_ENCOUNTER
                  True
                  1. Execute++fs->enemyRooms;
                  False
                  1. Decisionc->eventFlags & DUNGEON_EVENT_TREASURE
                    True
                    1. Execute++fs->treasureRooms;
                    False
                    1. Execute++fs->emptyRooms;
              False

              Continue after this decision.

            5. Decisionc->eventFlags & DUNGEON_EVENT_TRAP
              True
              1. Execute++fs->traps;
              False

              Continue after this decision.

            6. Decisionc->eventFlags & DUNGEON_EVENT_KEY
              True
              1. Executes->keyPlaced = true;
              False

              Continue after this decision.

            7. Decision(c->eventFlags & DUNGEON_EVENT_ENCOUNTER) && ((c->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >> DUNGEON_ENEMY_DETAIL_SHIFT) == DUNGEON_ENEMY_DRAGON
              True
              1. Executes->dragonPlaced = true;
              False

              Continue after this decision.

      5. Executefs->valid = true;
      6. Executes->reachableCells += fs->reachableCells;
      7. Executes->rooms += fs->rooms;
      8. Executes->deadEnds += fs->deadEnds;
      9. Executes->enemyRooms += fs->enemyRooms;
      10. Executes->treasureRooms += fs->treasureRooms;
      11. Executes->emptyRooms += fs->emptyRooms;
      12. Executes->traps += fs->traps;
  5. Decisionw->entrance.floor != 0U || w->entrance.x >= DUNGEON_WIDTH || w->entrance.y >= DUNGEON_HEIGHT || w->floors[w->entrance.floor] .cells[w->entrance.y][w->entrance.x] .tileType != DUNGEON_TILE_ENTRANCE
    True
      1. Executes->error = DUNGEON_GENERATION_NO_ENTRANCE;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  6. Decisionw->exit.floor != DUNGEON_FLOOR_COUNT - 1U || w->exit.x >= DUNGEON_WIDTH || w->exit.y >= DUNGEON_HEIGHT || w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType != DUNGEON_TILE_EXIT
    True
      1. Executes->error = DUNGEON_GENERATION_NO_EXIT;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  7. FOR loopuint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i

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

      1. ExecuteDungeonStairLink *l = &w->stairLinks[i];
      2. Decisionl->upper.floor >= DUNGEON_FLOOR_COUNT || l->lower.floor >= DUNGEON_FLOOR_COUNT || l->upper.x >= DUNGEON_WIDTH || l->lower.x >= DUNGEON_WIDTH || l->upper.y >= DUNGEON_HEIGHT || l->lower.y >= DUNGEON_HEIGHT || l->upper.floor + 1U != l->lower.floor || w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType != DUNGEON_TILE_STAIRS_DOWN || w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType != DUNGEON_TILE_STAIRS_UP
        True
          1. Executes->error = DUNGEON_GENERATION_INVALID_STAIR_LINK;
          2. Exit this functionreturn false;
        False

        Continue after this decision.

  8. Executes->stairLinks = DUNGEON_FLOOR_COUNT - 1U;
  9. Executes->valid = true;
  10. Executes->error = DUNGEON_GENERATION_OK;
  11. Exit this functionreturn true;
Read original function source
bool validateDungeonWorld(DungeonWorldGenerationStats *s) {
  if (!s)
    return false;
  memset(s, 0, sizeof(*s));
  DungeonWorld *w = getDungeonWorld();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
    DungeonGenerationStats *fs = &s->floors[f];
    fs->error = validate_floor(&w->floors[f], &fs->reachableCells);
    if (fs->error != DUNGEON_GENERATION_OK) {
      s->error = fs->error;
      return false;
    }
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
        DungeonCell *c = &w->floors[f].cells[y][x];
        if (c->tileType > DUNGEON_TILE_STAIRS_BOTH) {
          s->error = DUNGEON_GENERATION_INVALID_TILE;
          return false;
        }
        if (ec(c->exits) == 1U)
          ++fs->deadEnds;
        if (room(c->tileType)) {
          ++fs->rooms;
          if (c->eventFlags & DUNGEON_EVENT_ENCOUNTER)
            ++fs->enemyRooms;
          else if (c->eventFlags & DUNGEON_EVENT_TREASURE)
            ++fs->treasureRooms;
          else
            ++fs->emptyRooms;
        }
        if (c->eventFlags & DUNGEON_EVENT_TRAP)
          ++fs->traps;
        if (c->eventFlags & DUNGEON_EVENT_KEY)
          s->keyPlaced = true;
        if ((c->eventFlags & DUNGEON_EVENT_ENCOUNTER) &&
            ((c->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >>
             DUNGEON_ENEMY_DETAIL_SHIFT) == DUNGEON_ENEMY_DRAGON)
          s->dragonPlaced = true;
      }
    fs->valid = true;
    s->reachableCells += fs->reachableCells;
    s->rooms += fs->rooms;
    s->deadEnds += fs->deadEnds;
    s->enemyRooms += fs->enemyRooms;
    s->treasureRooms += fs->treasureRooms;
    s->emptyRooms += fs->emptyRooms;
    s->traps += fs->traps;
  }
  if (w->entrance.floor != 0U || w->entrance.x >= DUNGEON_WIDTH ||
      w->entrance.y >= DUNGEON_HEIGHT ||
      w->floors[w->entrance.floor]
              .cells[w->entrance.y][w->entrance.x]
              .tileType != DUNGEON_TILE_ENTRANCE) {
    s->error = DUNGEON_GENERATION_NO_ENTRANCE;
    return false;
  }
  if (w->exit.floor != DUNGEON_FLOOR_COUNT - 1U || w->exit.x >= DUNGEON_WIDTH ||
      w->exit.y >= DUNGEON_HEIGHT ||
      w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType !=
          DUNGEON_TILE_EXIT) {
    s->error = DUNGEON_GENERATION_NO_EXIT;
    return false;
  }
  for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
    DungeonStairLink *l = &w->stairLinks[i];
    if (l->upper.floor >= DUNGEON_FLOOR_COUNT ||
        l->lower.floor >= DUNGEON_FLOOR_COUNT || l->upper.x >= DUNGEON_WIDTH ||
        l->lower.x >= DUNGEON_WIDTH || l->upper.y >= DUNGEON_HEIGHT ||
        l->lower.y >= DUNGEON_HEIGHT || l->upper.floor + 1U != l->lower.floor ||
        w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType !=
            DUNGEON_TILE_STAIRS_DOWN ||
        w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType !=
            DUNGEON_TILE_STAIRS_UP) {
      s->error = DUNGEON_GENERATION_INVALID_STAIR_LINK;
      return false;
    }
  }
  s->stairLinks = DUNGEON_FLOOR_COUNT - 1U;
  s->valid = true;
  s->error = DUNGEON_GENERATION_OK;
  return true;
}
generateDungeonWorld() · source line 420
bool generateDungeonWorld(DungeonWorldGenerationStats *s)

Calls into this documentation:

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

    Continue after this decision.

  2. Executememset(s, 0, sizeof(*s));
  3. ExecuteinitDungeonMap();
  4. FOR loopuint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f

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

    1. Decision!generateDungeonFloor(f, &s->floors[f])
      True
        1. Executes->error = s->floors[f].error;
        2. Exit this functionreturn false;
      False

      Continue after this decision.

  5. ExecuteDungeonWorld *w = getDungeonWorld();
  6. Decision!choose_dead(0U, &w->entrance)
    True
      1. Executes->error = DUNGEON_GENERATION_NO_ENTRANCE;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  7. Executew->floors[w->entrance.floor].cells[w->entrance.y][w->entrance.x].tileType = DUNGEON_TILE_ENTRANCE;
  8. Decision!choose_dead(DUNGEON_FLOOR_COUNT - 1U, &w->exit)
    True
      1. Executes->error = DUNGEON_GENERATION_NO_EXIT;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  9. Executew->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType = DUNGEON_TILE_EXIT;
  10. ExecuteDungeonPosition prior = {255U, 0U, 0U};
  11. FOR loopuint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i

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

      1. ExecuteDungeonStairLink *l = &w->stairLinks[i];
      2. Decision!choose_straight(i, &l->upper, i == 1U ? &prior : NULL) || !choose_straight((uint8_t)(i + 1U), &l->lower, NULL)
        True
          1. Executes->error = DUNGEON_GENERATION_NO_STAIRS;
          2. Exit this functionreturn false;
        False

        Continue after this decision.

      3. Executew->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType = DUNGEON_TILE_STAIRS_DOWN;
      4. Executew->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType = DUNGEON_TILE_STAIRS_UP;
      5. Decisioni == 0U
        True
        1. Executeprior = l->lower;
        False

        Continue after this decision.

  12. ExecuteDungeonPosition key;
  13. Decision!key_room(&key)
    True
      1. Executes->error = DUNGEON_GENERATION_NO_KEY_ROOM;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  14. Executeevent(&w->floors[key.floor].cells[key.y][key.x], DUNGEON_EVENT_KEY);
  15. Executeevents(&key);
  16. Executecorridors();
  17. Executeevent(&w->floors[w->exit.floor].cells[w->exit.y][w->exit.x], DUNGEON_EVENT_ENCOUNTER | DUNGEON_EVENT_REQUIRES_KEY | DUNGEON_EVENT_MESSAGE | DUNGEON_EVENT_DOOR | ((uint16_t)DUNGEON_ENEMY_DRAGON << DUNGEON_ENEMY_DETAIL_SHIFT));
  18. Exit this functionreturn validateDungeonWorld(s);
Read original function source
bool generateDungeonWorld(DungeonWorldGenerationStats *s) {
  if (!s)
    return false;
  memset(s, 0, sizeof(*s));
  initDungeonMap();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
    if (!generateDungeonFloor(f, &s->floors[f])) {
      s->error = s->floors[f].error;
      return false;
    }
  DungeonWorld *w = getDungeonWorld();
  if (!choose_dead(0U, &w->entrance)) {
    s->error = DUNGEON_GENERATION_NO_ENTRANCE;
    return false;
  }
  w->floors[w->entrance.floor].cells[w->entrance.y][w->entrance.x].tileType =
      DUNGEON_TILE_ENTRANCE;
  if (!choose_dead(DUNGEON_FLOOR_COUNT - 1U, &w->exit)) {
    s->error = DUNGEON_GENERATION_NO_EXIT;
    return false;
  }
  w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType =
      DUNGEON_TILE_EXIT;
  DungeonPosition prior = {255U, 0U, 0U};
  for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
    DungeonStairLink *l = &w->stairLinks[i];
    if (!choose_straight(i, &l->upper, i == 1U ? &prior : NULL) ||
        !choose_straight((uint8_t)(i + 1U), &l->lower, NULL)) {
      s->error = DUNGEON_GENERATION_NO_STAIRS;
      return false;
    }
    w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType =
        DUNGEON_TILE_STAIRS_DOWN;
    w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType =
        DUNGEON_TILE_STAIRS_UP;
    if (i == 0U)
      prior = l->lower;
  }
  DungeonPosition key;
  if (!key_room(&key)) {
    s->error = DUNGEON_GENERATION_NO_KEY_ROOM;
    return false;
  }
  event(&w->floors[key.floor].cells[key.y][key.x], DUNGEON_EVENT_KEY);
  events(&key);
  corridors();
  event(&w->floors[w->exit.floor].cells[w->exit.y][w->exit.x],
        DUNGEON_EVENT_ENCOUNTER | DUNGEON_EVENT_REQUIRES_KEY |
            DUNGEON_EVENT_MESSAGE | DUNGEON_EVENT_DOOR |
            ((uint16_t)DUNGEON_ENEMY_DRAGON << DUNGEON_ENEMY_DETAIL_SHIFT));
  return validateDungeonWorld(s);
}
dungeonGenerationErrorText() · source line 472
const char *dungeonGenerationErrorText(DungeonGenerationError e)
  1. Select a casee

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase DUNGEON_GENERATION_OK:
      2. Exit this functionreturn "OK";
      3. Case / labelcase DUNGEON_GENERATION_INVALID_FLOOR:
      4. Exit this functionreturn "Invalid floor";
      5. Case / labelcase DUNGEON_GENERATION_OUTSIDE_EXIT:
      6. Exit this functionreturn "Exit outside floor";
      7. Case / labelcase DUNGEON_GENERATION_EXIT_MISMATCH:
      8. Exit this functionreturn "Exit mismatch";
      9. Case / labelcase DUNGEON_GENERATION_UNREACHABLE_CELL:
      10. Exit this functionreturn "Unreachable cell";
      11. Case / labelcase DUNGEON_GENERATION_NO_ENTRANCE:
      12. Exit this functionreturn "No entrance cell";
      13. Case / labelcase DUNGEON_GENERATION_NO_EXIT:
      14. Exit this functionreturn "No exit cell";
      15. Case / labelcase DUNGEON_GENERATION_NO_STAIRS:
      16. Exit this functionreturn "No stair cell";
      17. Case / labelcase DUNGEON_GENERATION_INVALID_STAIR_LINK:
      18. Exit this functionreturn "Invalid stair link";
      19. Case / labelcase DUNGEON_GENERATION_INVALID_TILE:
      20. Exit this functionreturn "Invalid tile";
      21. Case / labelcase DUNGEON_GENERATION_NO_KEY_ROOM:
      22. Exit this functionreturn "No key room";
      23. Case / labeldefault:
      24. Exit this functionreturn "Unknown error";
Read original function source
const char *dungeonGenerationErrorText(DungeonGenerationError e) {
  switch (e) {
  case DUNGEON_GENERATION_OK:
    return "OK";
  case DUNGEON_GENERATION_INVALID_FLOOR:
    return "Invalid floor";
  case DUNGEON_GENERATION_OUTSIDE_EXIT:
    return "Exit outside floor";
  case DUNGEON_GENERATION_EXIT_MISMATCH:
    return "Exit mismatch";
  case DUNGEON_GENERATION_UNREACHABLE_CELL:
    return "Unreachable cell";
  case DUNGEON_GENERATION_NO_ENTRANCE:
    return "No entrance cell";
  case DUNGEON_GENERATION_NO_EXIT:
    return "No exit cell";
  case DUNGEON_GENERATION_NO_STAIRS:
    return "No stair cell";
  case DUNGEON_GENERATION_INVALID_STAIR_LINK:
    return "Invalid stair link";
  case DUNGEON_GENERATION_INVALID_TILE:
    return "Invalid tile";
  case DUNGEON_GENERATION_NO_KEY_ROOM:
    return "No key room";
  default:
    return "Unknown error";
  }
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "dungeon_generator.h"
#include "dungeon_config.h"
#include "dungeon_map.h"
#include "esp_attr.h"
#include "esp_random.h"
#include <string.h>

#define CELLS (DUNGEON_WIDTH * DUNGEON_HEIGHT)
EXT_RAM_BSS_ATTR static uint8_t visited[CELLS];
EXT_RAM_BSS_ATTR static uint16_t stack[CELLS], queue[CELLS], candidates[CELLS], dead_ends[CELLS];
static uint16_t ix(uint8_t x, uint8_t y) {
  return (uint16_t)y * DUNGEON_WIDTH + x;
}
static uint8_t cx(uint16_t n) { return (uint8_t)(n % DUNGEON_WIDTH); }
static uint8_t cy(uint16_t n) { return (uint8_t)(n / DUNGEON_WIDTH); }
static uint8_t ex(uint8_t d) {
  static const uint8_t v[] = {1U, 2U, 4U, 8U};
  return v[d];
}
static uint8_t op(uint8_t d) {
  static const uint8_t v[] = {4U, 8U, 1U, 2U};
  return v[d];
}
static uint8_t ec(uint8_t e) {
  uint8_t n = 0U;
  for (uint8_t i = 0U; i < 4U; ++i)
    n += (e >> i) & 1U;
  return n;
}
static bool next(uint16_t c, uint8_t d, uint16_t *n) {
  uint8_t x = cx(c), y = cy(c);
  if (d == 0U && y > 0U)
    *n = ix(x, y - 1U);
  else if (d == 1U && x + 1U < DUNGEON_WIDTH)
    *n = ix(x + 1U, y);
  else if (d == 2U && y + 1U < DUNGEON_HEIGHT)
    *n = ix(x, y + 1U);
  else if (d == 3U && x > 0U)
    *n = ix(x - 1U, y);
  else
    return false;
  return true;
}
static bool room(uint8_t t) {
  return t == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
         t == DUNGEON_TILE_DEAD_END_ROOM_SQUARE ||
         t == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}
static void shuffle(uint16_t n) {
  for (; n > 1U; --n) {
    uint16_t s = (uint16_t)(esp_random() % n), t = candidates[n - 1U];
    candidates[n - 1U] = candidates[s];
    candidates[s] = t;
  }
}

static DungeonGenerationError validate_floor(const DungeonFloor *f,
                                             uint16_t *reachable) {
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
      uint16_t c = ix(x, y);
      for (uint8_t d = 0; d < 4; ++d)
        if (f->cells[y][x].exits & ex(d)) {
          uint16_t n;
          if (!next(c, d, &n))
            return DUNGEON_GENERATION_OUTSIDE_EXIT;
          if (!(f->cells[cy(n)][cx(n)].exits & op(d)))
            return DUNGEON_GENERATION_EXIT_MISMATCH;
        }
    }
  memset(visited, 0, sizeof(visited));
  uint16_t a = 0, b = 0;
  queue[b++] = 0;
  visited[0] = 1U;
  while (a < b) {
    uint16_t c = queue[a++];
    for (uint8_t d = 0; d < 4; ++d) {
      uint16_t n;
      if ((f->cells[cy(c)][cx(c)].exits & ex(d)) && next(c, d, &n) &&
          !visited[n]) {
        visited[n] = 1U;
        queue[b++] = n;
      }
    }
  }
  *reachable = b;
  return b == CELLS ? DUNGEON_GENERATION_OK
                    : DUNGEON_GENERATION_UNREACHABLE_CELL;
}
static void classify(DungeonFloor *f, DungeonGenerationStats *s) {
  uint16_t count = 0;
  uint32_t exits = 0;
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
      DungeonCell *c = &f->cells[y][x];
      uint8_t n = ec(c->exits);
      exits += n;
      c->tileType =
          n == 1U ? DUNGEON_TILE_DEAD_END_PASSAGE : DUNGEON_TILE_PASSAGE;
      if (n == 1U)
        dead_ends[count++] = ix(x, y);
    }
  uint16_t rooms = count < getDungeonConfig()->roomsPerFloor
                       ? count
                       : getDungeonConfig()->roomsPerFloor;
  for (uint16_t i = 0; i < rooms; ++i) {
    uint16_t p = i + (uint16_t)(esp_random() % (count - i)), t = dead_ends[i];
    dead_ends[i] = dead_ends[p];
    dead_ends[p] = t;
    f->cells[cy(dead_ends[i])][cx(dead_ends[i])].tileType =
        i % 3U == 0U   ? DUNGEON_TILE_DEAD_END_ROOM_ROUND
        : i % 3U == 1U ? DUNGEON_TILE_DEAD_END_ROOM_SQUARE
                       : DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
  }
  s->rooms = rooms;
  s->deadEnds = count;
  s->connections = (uint16_t)(exits / 2U);
  s->loops = (uint16_t)(s->connections - CELLS + 1U);
}
bool generateDungeonFloor(uint8_t floor, DungeonGenerationStats *s) {
  if (!s)
    return false;
  memset(s, 0, sizeof(*s));
  if (floor >= DUNGEON_FLOOR_COUNT) {
    s->error = DUNGEON_GENERATION_INVALID_FLOOR;
    return false;
  }
  DungeonFloor *f = &getDungeonWorld()->floors[floor];
  memset(f, 0, sizeof(*f));
  memset(visited, 0, sizeof(visited));
  uint16_t depth = 0, start = ix((uint8_t)(esp_random() % DUNGEON_WIDTH),
                                 (uint8_t)(esp_random() % DUNGEON_HEIGHT));
  stack[depth++] = start;
  visited[start] = 1U;
  while (depth) {
    uint16_t c = stack[depth - 1U], n;
    uint8_t choices[4], count = 0;
    for (uint8_t d = 0; d < 4; ++d)
      if (next(c, d, &n) && !visited[n])
        choices[count++] = d;
    if (!count) {
      --depth;
      continue;
    }
    uint8_t d = choices[esp_random() % count];
    (void)next(c, d, &n);
    f->cells[cy(c)][cx(c)].exits |= ex(d);
    f->cells[cy(n)][cx(n)].exits |= op(d);
    visited[n] = 1U;
    stack[depth++] = n;
  }
  uint16_t target = (uint16_t)(((uint32_t)CELLS *
                                    getDungeonConfig()->extraConnectionPercent +
                                99U) /
                               100U),
           added = 0;
  for (uint32_t tries = 0; added < target && tries < (uint32_t)CELLS * 10U;
       ++tries) {
    uint16_t c = (uint16_t)(esp_random() % CELLS), n;
    uint8_t d = (uint8_t)(esp_random() % 4U);
    if (!next(c, d, &n) || (f->cells[cy(c)][cx(c)].exits & ex(d)))
      continue;
    f->cells[cy(c)][cx(c)].exits |= ex(d);
    f->cells[cy(n)][cx(n)].exits |= op(d);
    ++added;
  }
  classify(f, s);
  s->error = validate_floor(f, &s->reachableCells);
  s->valid = s->error == DUNGEON_GENERATION_OK;
  return s->valid;
}
static bool choose_dead(uint8_t floor, DungeonPosition *p) {
  uint16_t n = 0;
  DungeonFloor *f = &getDungeonWorld()->floors[floor];
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
      if (f->cells[y][x].tileType == DUNGEON_TILE_DEAD_END_PASSAGE)
        candidates[n++] = ix(x, y);
  if (!n)
    return false;
  uint16_t c = candidates[esp_random() % n];
  *p = (DungeonPosition){floor, cx(c), cy(c)};
  return true;
}
static bool choose_straight(uint8_t floor, DungeonPosition *p,
                            const DungeonPosition *skip) {
  uint16_t n = 0;
  DungeonFloor *f = &getDungeonWorld()->floors[floor];
  for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
    for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
      DungeonCell *c = &f->cells[y][x];
      if (c->tileType == DUNGEON_TILE_PASSAGE &&
          (c->exits == (DUNGEON_EXIT_NORTH | DUNGEON_EXIT_SOUTH) ||
           c->exits == (DUNGEON_EXIT_EAST | DUNGEON_EXIT_WEST)) &&
          !(skip && skip->floor == floor && skip->x == x && skip->y == y))
        candidates[n++] = ix(x, y);
    }
  if (!n)
    return false;
  uint16_t c = candidates[esp_random() % n];
  *p = (DungeonPosition){floor, cx(c), cy(c)};
  return true;
}
static void erange(uint8_t f, uint8_t *lo, uint8_t *hi) {
  const DungeonConfig *c = getDungeonConfig();
  *lo = f == 0U   ? c->floor1EnemyMinLevel
        : f == 1U ? c->floor2EnemyMinLevel
                  : c->floor3EnemyMinLevel;
  *hi = f == 0U   ? c->floor1EnemyMaxLevel
        : f == 1U ? c->floor2EnemyMaxLevel
                  : c->floor3EnemyMaxLevel;
}
static void event(DungeonCell *c, uint16_t flags) {
  c->eventFlags = flags;
  c->stateFlags = DUNGEON_STATE_NONE;
  c->variant = 1U;
}
static uint8_t treasure(void) {
  const DungeonConfig *c = getDungeonConfig();
  if (c->rareTreasurePercent && esp_random() % 100U < c->rareTreasurePercent) {
    uint32_t t = (uint32_t)c->rareLifeRatio + c->rareAttackRatio +
                 c->rareDefenceRatio,
             r = t ? esp_random() % t : 0U;
    if (r < c->rareLifeRatio)
      return DUNGEON_TREASURE_LIFE;
    r -= c->rareLifeRatio;
    return r < c->rareAttackRatio ? DUNGEON_TREASURE_ATTACK
                                  : DUNGEON_TREASURE_DEFENCE;
  }
  uint32_t t = (uint32_t)c->normalGoldRatio + c->normalHealthRatio;
  return !t || esp_random() % t < c->normalGoldRatio ? DUNGEON_TREASURE_GOLD
                                                     : DUNGEON_TREASURE_HEALTH;
}
static bool key_room(DungeonPosition *p) {
  uint16_t n = 0;
  DungeonWorld *w = getDungeonWorld();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
        if (room(w->floors[f].cells[y][x].tileType)) {
          ++n;
          if (esp_random() % n == 0U)
            *p = (DungeonPosition){f, x, y};
        }
  return n > 0;
}
static void room_counts(uint16_t n, uint16_t out[3]) {
  const DungeonConfig *c = getDungeonConfig();
  uint16_t w[3] = {c->enemyRoomRatio, c->treasureRoomRatio, c->emptyRoomRatio};
  uint32_t total = (uint32_t)w[0] + w[1] + w[2], rem[3];
  uint16_t done = 0;
  if (!total) {
    out[2] = n;
    return;
  }
  for (uint8_t i = 0; i < 3; ++i) {
    uint32_t q = (uint32_t)n * w[i];
    out[i] = (uint16_t)(q / total);
    rem[i] = q % total;
    done += out[i];
  }
  while (done < n) {
    uint32_t high = 0;
    uint8_t ties[3], nt = 0;
    for (uint8_t i = 0; i < 3; ++i)
      if (rem[i] > high) {
        high = rem[i];
        ties[0] = i;
        nt = 1;
      } else if (rem[i] == high)
        ties[nt++] = i;
    uint8_t k = ties[esp_random() % nt];
    ++out[k];
    rem[k] = 0;
    ++done;
  }
}
static void events(const DungeonPosition *key) {
  DungeonWorld *w = getDungeonWorld();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
    uint16_t n = 0;
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
        if (room(w->floors[f].cells[y][x].tileType) &&
            !(key->floor == f && key->x == x && key->y == y))
          candidates[n++] = ix(x, y);
    shuffle(n);
    uint16_t types[3] = {0, 0, 0};
    room_counts(n, types);
    uint8_t lo, hi;
    erange(f, &lo, &hi);
    uint16_t i = 0;
    for (; i < types[0]; ++i) {
      DungeonCell *c =
          &w->floors[f].cells[cy(candidates[i])][cx(candidates[i])];
      event(c, DUNGEON_EVENT_ENCOUNTER |
                   ((uint16_t)(lo + esp_random() % (hi - lo + 1U))
                    << DUNGEON_ENEMY_DETAIL_SHIFT));
    }
    for (; i < (uint16_t)(types[0] + types[1]); ++i)
      event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
            DUNGEON_EVENT_TREASURE |
                ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
  }
}
static void corridors(void) {
  DungeonWorld *w = getDungeonWorld();
  const DungeonConfig *c = getDungeonConfig();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
    uint16_t r = 0, n = 0;
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
        uint8_t t = w->floors[f].cells[y][x].tileType;
        if (room(t))
          ++r;
        if (t == DUNGEON_TILE_PASSAGE || t == DUNGEON_TILE_DEAD_END_PASSAGE)
          candidates[n++] = ix(x, y);
      }
    uint16_t traps =
                 (uint16_t)(((uint32_t)r * c->trapsPer100Rooms + 99U) / 100U),
             tr = (uint16_t)(((uint32_t)r * c->corridorTreasuresPer100Rooms +
                              99U) /
                             100U);
    if (traps > n)
      traps = n;
    if (tr > n - traps)
      tr = n - traps;
    shuffle(n);
    for (uint16_t i = 0; i < traps; ++i)
      event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
            DUNGEON_EVENT_TRAP);
    for (uint16_t i = traps; i < (uint16_t)(traps + tr); ++i)
      event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
            DUNGEON_EVENT_TREASURE |
                ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
  }
}
bool validateDungeonWorld(DungeonWorldGenerationStats *s) {
  if (!s)
    return false;
  memset(s, 0, sizeof(*s));
  DungeonWorld *w = getDungeonWorld();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
    DungeonGenerationStats *fs = &s->floors[f];
    fs->error = validate_floor(&w->floors[f], &fs->reachableCells);
    if (fs->error != DUNGEON_GENERATION_OK) {
      s->error = fs->error;
      return false;
    }
    for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
      for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
        DungeonCell *c = &w->floors[f].cells[y][x];
        if (c->tileType > DUNGEON_TILE_STAIRS_BOTH) {
          s->error = DUNGEON_GENERATION_INVALID_TILE;
          return false;
        }
        if (ec(c->exits) == 1U)
          ++fs->deadEnds;
        if (room(c->tileType)) {
          ++fs->rooms;
          if (c->eventFlags & DUNGEON_EVENT_ENCOUNTER)
            ++fs->enemyRooms;
          else if (c->eventFlags & DUNGEON_EVENT_TREASURE)
            ++fs->treasureRooms;
          else
            ++fs->emptyRooms;
        }
        if (c->eventFlags & DUNGEON_EVENT_TRAP)
          ++fs->traps;
        if (c->eventFlags & DUNGEON_EVENT_KEY)
          s->keyPlaced = true;
        if ((c->eventFlags & DUNGEON_EVENT_ENCOUNTER) &&
            ((c->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >>
             DUNGEON_ENEMY_DETAIL_SHIFT) == DUNGEON_ENEMY_DRAGON)
          s->dragonPlaced = true;
      }
    fs->valid = true;
    s->reachableCells += fs->reachableCells;
    s->rooms += fs->rooms;
    s->deadEnds += fs->deadEnds;
    s->enemyRooms += fs->enemyRooms;
    s->treasureRooms += fs->treasureRooms;
    s->emptyRooms += fs->emptyRooms;
    s->traps += fs->traps;
  }
  if (w->entrance.floor != 0U || w->entrance.x >= DUNGEON_WIDTH ||
      w->entrance.y >= DUNGEON_HEIGHT ||
      w->floors[w->entrance.floor]
              .cells[w->entrance.y][w->entrance.x]
              .tileType != DUNGEON_TILE_ENTRANCE) {
    s->error = DUNGEON_GENERATION_NO_ENTRANCE;
    return false;
  }
  if (w->exit.floor != DUNGEON_FLOOR_COUNT - 1U || w->exit.x >= DUNGEON_WIDTH ||
      w->exit.y >= DUNGEON_HEIGHT ||
      w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType !=
          DUNGEON_TILE_EXIT) {
    s->error = DUNGEON_GENERATION_NO_EXIT;
    return false;
  }
  for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
    DungeonStairLink *l = &w->stairLinks[i];
    if (l->upper.floor >= DUNGEON_FLOOR_COUNT ||
        l->lower.floor >= DUNGEON_FLOOR_COUNT || l->upper.x >= DUNGEON_WIDTH ||
        l->lower.x >= DUNGEON_WIDTH || l->upper.y >= DUNGEON_HEIGHT ||
        l->lower.y >= DUNGEON_HEIGHT || l->upper.floor + 1U != l->lower.floor ||
        w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType !=
            DUNGEON_TILE_STAIRS_DOWN ||
        w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType !=
            DUNGEON_TILE_STAIRS_UP) {
      s->error = DUNGEON_GENERATION_INVALID_STAIR_LINK;
      return false;
    }
  }
  s->stairLinks = DUNGEON_FLOOR_COUNT - 1U;
  s->valid = true;
  s->error = DUNGEON_GENERATION_OK;
  return true;
}
bool generateDungeonWorld(DungeonWorldGenerationStats *s) {
  if (!s)
    return false;
  memset(s, 0, sizeof(*s));
  initDungeonMap();
  for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
    if (!generateDungeonFloor(f, &s->floors[f])) {
      s->error = s->floors[f].error;
      return false;
    }
  DungeonWorld *w = getDungeonWorld();
  if (!choose_dead(0U, &w->entrance)) {
    s->error = DUNGEON_GENERATION_NO_ENTRANCE;
    return false;
  }
  w->floors[w->entrance.floor].cells[w->entrance.y][w->entrance.x].tileType =
      DUNGEON_TILE_ENTRANCE;
  if (!choose_dead(DUNGEON_FLOOR_COUNT - 1U, &w->exit)) {
    s->error = DUNGEON_GENERATION_NO_EXIT;
    return false;
  }
  w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType =
      DUNGEON_TILE_EXIT;
  DungeonPosition prior = {255U, 0U, 0U};
  for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
    DungeonStairLink *l = &w->stairLinks[i];
    if (!choose_straight(i, &l->upper, i == 1U ? &prior : NULL) ||
        !choose_straight((uint8_t)(i + 1U), &l->lower, NULL)) {
      s->error = DUNGEON_GENERATION_NO_STAIRS;
      return false;
    }
    w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType =
        DUNGEON_TILE_STAIRS_DOWN;
    w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType =
        DUNGEON_TILE_STAIRS_UP;
    if (i == 0U)
      prior = l->lower;
  }
  DungeonPosition key;
  if (!key_room(&key)) {
    s->error = DUNGEON_GENERATION_NO_KEY_ROOM;
    return false;
  }
  event(&w->floors[key.floor].cells[key.y][key.x], DUNGEON_EVENT_KEY);
  events(&key);
  corridors();
  event(&w->floors[w->exit.floor].cells[w->exit.y][w->exit.x],
        DUNGEON_EVENT_ENCOUNTER | DUNGEON_EVENT_REQUIRES_KEY |
            DUNGEON_EVENT_MESSAGE | DUNGEON_EVENT_DOOR |
            ((uint16_t)DUNGEON_ENEMY_DRAGON << DUNGEON_ENEMY_DETAIL_SHIFT));
  return validateDungeonWorld(s);
}
const char *dungeonGenerationErrorText(DungeonGenerationError e) {
  switch (e) {
  case DUNGEON_GENERATION_OK:
    return "OK";
  case DUNGEON_GENERATION_INVALID_FLOOR:
    return "Invalid floor";
  case DUNGEON_GENERATION_OUTSIDE_EXIT:
    return "Exit outside floor";
  case DUNGEON_GENERATION_EXIT_MISMATCH:
    return "Exit mismatch";
  case DUNGEON_GENERATION_UNREACHABLE_CELL:
    return "Unreachable cell";
  case DUNGEON_GENERATION_NO_ENTRANCE:
    return "No entrance cell";
  case DUNGEON_GENERATION_NO_EXIT:
    return "No exit cell";
  case DUNGEON_GENERATION_NO_STAIRS:
    return "No stair cell";
  case DUNGEON_GENERATION_INVALID_STAIR_LINK:
    return "Invalid stair link";
  case DUNGEON_GENERATION_INVALID_TILE:
    return "Invalid tile";
  case DUNGEON_GENERATION_NO_KEY_ROOM:
    return "No key room";
  default:
    return "Unknown error";
  }
}