@Override
  public void render() {
    // standard clear the background routine for libGDX
    Gdx.gl.glClearColor(bgColor.r / 255.0f, bgColor.g / 255.0f, bgColor.b / 255.0f, 1.0f);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    // not sure if this is always needed...
    // Gdx.gl.glEnable(GL20.GL_BLEND);

    // used as the z-axis when generating Simplex noise to make water seem to "move"
    counter += Gdx.graphics.getDeltaTime() * 15;
    // this does the standard lighting for walls, floors, etc. but also uses counter to do the
    // Simplex noise thing.
    lights = DungeonUtility.generateLightnessModifiers(decoDungeon, counter);
    // textFactory.configureShader(batch);

    // you done bad. you done real bad.
    if (health <= 0) {
      // still need to display the map, then write over it with a message.
      toCursor.clear();
      monsters.clear();
      display.setLightnesses(GwtCompatibility.fill2D(-200, width * 2, height));
      putMap();
      display.putBoxedString(width - 18, height / 2 - 6, " THE PHANTOMS HAVE EATEN YOUR SOUL! ");
      display.putBoxedString(width - 18, height / 2 - 4, "   AT LAST, YOU HEAR THEM MUTTER,   ");
      display.putBoxedString(width - lang.length() / 2, height / 2, lang);
      display.putBoxedString(width - 18, height / 2 + 4, "      ...WHATEVER THAT MEANS.       ");
      display.putBoxedString(width - 18, height / 2 + 9, "             q to quit.             ");

      // because we return early, we still need to draw.
      stage.draw();
      stage.act();
      // q still needs to quit.
      if (input.hasNext()) input.next();
      return;
    }
    // need to display the map every frame, since we clear the screen to avoid artifacts.
    putMap();
    // if the user clicked, we have a list of moves to perform.
    if (!awaitedMoves.isEmpty()) {
      // extremely similar to the block below that also checks if animations are done
      // this doesn't check for input, but instead processes and removes Points from awaitedMoves.
      if (!display.hasActiveAnimations()) {
        ++framesWithoutAnimation;
        if (framesWithoutAnimation >= 3) {
          framesWithoutAnimation = 0;
          switch (phase) {
            case WAIT:
            case MONSTER_ANIM:
              Coord m = awaitedMoves.remove(0);
              toCursor.remove(0);
              move(m.x - player.gridX, m.y - player.gridY);
              break;
            case PLAYER_ANIM:
              postMove();
              break;
          }
        }
      }
    }
    // if we are waiting for the player's input and get input, process it.
    else if (input.hasNext() && !display.hasActiveAnimations() && phase == Phase.WAIT) {
      input.next();
    }
    // if the previous blocks didn't happen, and there are no active animations, then either change
    // the phase
    // (because with no animations running the last phase must have ended), or start a new animation
    // soon.
    else if (!display.hasActiveAnimations()) {
      ++framesWithoutAnimation;
      if (framesWithoutAnimation >= 3) {
        framesWithoutAnimation = 0;
        switch (phase) {
          case WAIT:
            break;
          case MONSTER_ANIM:
            {
              phase = Phase.WAIT;
            }
            break;
          case PLAYER_ANIM:
            {
              postMove();
            }
        }
      }
    }
    // if we do have an animation running, then how many frames have passed with no animation needs
    // resetting
    else {
      framesWithoutAnimation = 0;
    }

    input.show();
    // stage has its own batch and must be explicitly told to draw(). this also causes it to act().
    stage.getViewport().apply(true);
    stage.draw();
    stage.act();

    subCell.erase();
    if (help == null) {
      // display does not draw all AnimatedEntities by default, since FOV often changes how they
      // need to be drawn.
      batch.begin();
      // the player needs to get drawn every frame, of course.
      display.drawActor(batch, 1.0f, player);
      subCell.put(
          player.gridX * 2 + 1, player.gridY, Character.forDigit(health, 10), SColor.DARK_PINK);

      for (Monster mon : monsters) {
        // monsters are only drawn if within FOV.
        if (fovmap[mon.entity.gridX][mon.entity.gridY] > 0.0) {
          display.drawActor(batch, 1.0f, mon.entity);
          if (mon.state > 0)
            subCell.put(mon.entity.gridX * 2, mon.entity.gridY, '!', SColor.YELLOW);
        }
      }
      subCell.draw(batch, 1.0F);
      // batch must end if it began.
      batch.end();
    }
    // if using a filter that changes each frame, clear the known relationship between requested and
    // actual colors
    if (changingColors) {
      fgCenter.clearCache();
      bgCenter.clearCache();
    }
  }