<commit_before>//
// MIT License
//
// Copyright 2019
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include <array>
#include <boost/assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/static_assert.hpp>

#include "bit_utils.hh"
#include "control_register.hh"
#include "cpu.hh"
#include "mask_register.hh"
#include "memory.hh"
#include "name_table.hh"
#include "palette.hh"
#include "pattern_table.hh"
#include "ppu.hh"
#include "ppu_frame_state.hh"
#include "ppu_io_context.hh"
#include "ppu_render_context.hh"
#include "screen.hh"
#include "status_register.hh"

//
// PPU implementation is largely influenced by the following documents.
//
// https://wiki.nesdev.com/w/index.php/PPU_rendering
//
// https://wiki.nesdev.com/w/images/4/4f/Ppu.svg
//

using namespace jones::ppu;
using namespace jones::screen;
using namespace jones::util;

using ppu_frame_cycles = std::vector<ppu_frame_state_mask>;

using ppu_frame_scanlines = std::vector<ppu_frame_cycles>;

using ppu_frame_buffer = std::vector<std::vector<uint32_t>>;

namespace {

constexpr auto ppu_max_cycles = 340;

constexpr auto ppu_dma_cycles = 513;

constexpr auto ppu_max_sprites = 64;

constexpr auto ppu_max_scanlines = 262;

constexpr auto ppu_num_cycles = 341;

constexpr auto ppu_oam_size = 256;

constexpr auto ppu_oam_secondary_size = 32;

constexpr auto ppu_tile_width = 8;

constexpr auto ppu_tile_height = 8;

constexpr auto ppu_tile_size = 16;

constexpr auto ppu_sprites_per_line = 8;

constexpr auto ppu_scanline_prerender = 261;

constexpr auto ppu_scanline_visible_start = 0;

constexpr auto ppu_scanline_visible_end = 239;

constexpr auto ppu_scanline_postrender = 240;

constexpr auto ppu_scanline_vblank = 241;

constexpr inline bool is_color_transparent(const uint32_t color) {
  return color == 0;
}

} // namespace

template <typename T>
using memory_mappable_component = jones::memory_mappable_component<T>;

class ppu::impl final {
public:
  impl(memory &cpu_memory, memory &ppu_memory, cpu &cpu, screen::screen *screen)
      : cpu_memory_(cpu_memory), ppu_memory_(ppu_memory), cpu_(cpu), screen_(screen) {
    cpu_memory_.map(std::make_unique<memory_mappable_component<ppu::impl>>(this, "ppu", 0x2000, 0x3FFF));
    cpu_memory_.map(std::make_unique<memory_mappable_component<ppu::impl>>(this, "ppu", 0x4014, 0x4014));
    ppu_memory_.map(std::make_unique<memory_mappable_component<name_table>>(&name_table_, "ppu name_table", name_table_memory_begin, name_table_memory_end));
    ppu_memory_.map(std::make_unique<memory_mappable_component<palette>>(&palette_, "ppu palette", palette_memory_begin, palette_memory_end));
  }

  auto initialize() -> void {
    initialize_frame_scanlines();
    initialize_prerender_scanline();
    initialize_postrender_scanline();
    initialize_visible_scanline();
    initialize_vblank_scanline();
  }

  auto uninitialize() -> void {
    //
    // nothing to do.
    //
  }

  auto peek(const uint16_t address) const -> uint8_t {
    if (address >= 0x2000 && address <= 0x3FFF) {
      return peek_registers(address);
    } else if (address == 0x4014) {
      return peek_object_attribute_memory_dma();
    } else {
      BOOST_STATIC_ASSERT("peek unexpected for ppu");
    }
    return -1;
  }

  auto read(const uint16_t address) -> uint8_t {
    if (address >= 0x2000 && address <= 0x3FFF) {
      return read_registers(address);
    } else if (address == 0x4014) {
      return read_object_attribute_memory_dma();
    } else {
      BOOST_STATIC_ASSERT("read unexpected for ppu");
    }
    return -1;
  }

  auto write(const uint16_t address, const uint8_t data) -> void {
    if (address >= 0x2000 && address <= 0x3FFF) {
      write_registers(address, data);
    } else if (address == 0x4014) {
      write_object_attribute_memory_dma(data);
    } else {
      BOOST_STATIC_ASSERT("write unexpected for ppu");
    }
  }

  auto step() -> uint8_t {
    check_nmi_trigger();
    process_frame_state();
    update_frame_counters();
    return 1;
  }

  auto get_state() -> ppu_state {
    return ppu_state{
        .cycle = frame_current_cycle_,
        .scanline = frame_current_scanline_,
        .frame = frame_current_frame_};
  }

  auto set_state(const ppu_state &state) -> void {
    frame_current_cycle_ = state.cycle;
    frame_current_scanline_ = state.scanline;
    frame_current_frame_ = state.frame;
  }

private:
  auto write_registers(const uint16_t address, const uint8_t data) -> void {
    BOOST_ASSERT_MSG(address >= 0x2000 && address <= 0x3FFF, "write unexpected address for ppu");
    write_register = data;
    const auto address_offset = (address - 0x2000);
    switch (address_offset % 8) {
    case 0:
      write_control(data);
      break;
    case 1:
      write_mask(data);
      break;
    case 2:
      write_status(data);
      break;
    case 3:
      write_object_attribute_memory_address(data);
      break;
    case 4:
      write_object_attribute_memory_data(data);
      break;
    case 5:
      write_scroll(data);
      break;
    case 6:
      write_address(data);
      break;
    case 7:
      write_data(data);
      break;
    }
  }

  auto write_control(const uint8_t data) -> void {
    io_context_.vram_address_temporary.h_nametable = bit_shift_and<uint8_t>(data, 0, 1);
    io_context_.vram_address_temporary.v_nametable = bit_shift_and<uint8_t>(data, 1, 1);

    control_register_.set(data);
  }

  auto write_mask(const uint8_t data) -> void {
    mask_register_.set(data);
  }

  auto write_status(const uint8_t data) -> void {
    BOOST_STATIC_ASSERT("write unexpected for status");
    status_register_.set(data);
  }

  auto write_object_attribute_memory_address(const uint8_t data) -> void {
    oam_address_ = data;
  }

  auto write_object_attribute_memory_data(const uint8_t data) -> void {
    oam_data_[oam_address_] = data;
    oam_address_ += 1;
  }

  auto write_scroll(const uint8_t data) -> void {
    if (!io_context_.vram_address_latch) {
      io_context_.vram_address_temporary.coarse_x_scroll = bit_shift_and<uint8_t>(data, 3, 5);
      io_context_.fine_x_scroll = bit_shift_and<uint8_t>(data, 0, 3);
    } else {
      io_context_.vram_address_temporary.coarse_y_scroll = bit_shift_and<uint8_t>(data, 3, 5);
      io_context_.vram_address_temporary.fine_y_scroll = bit_shift_and<uint8_t>(data, 0, 3);
    }
    io_context_.vram_address_latch = !io_context_.vram_address_latch;
  }

  auto write_address(const uint8_t data) -> void {
    if (!io_context_.vram_address_latch) {
      auto temp_data = io_context_.vram_address_temporary.value;
      auto data_bits = bit_shift_and<uint8_t>(data, 0, 6);
      bit_shift_set<uint16_t>(temp_data, 8, 7, data_bits);
      io_context_.vram_address_temporary.value = temp_data;
    } else {
      auto temp_data = io_context_.vram_address_temporary.value;
      bit_shift_set<uint16_t>(temp_data, 0, 8, data);
      io_context_.vram_address_temporary.value = temp_data;
      io_context_.vram_address.value = io_context_.vram_address_temporary.value;
    }
    io_context_.vram_address_latch = !io_context_.vram_address_latch;
  }

  auto write_data(const uint8_t data) -> void {
    ppu_memory_.write(io_context_.vram_address.value, data);
    io_context_.vram_address.value += control_register_.is_set(control_flag::VRAM_INCREMENT) ? 32 : 1;
  }

  auto write_object_attribute_memory_dma(const uint8_t data) -> void {
    uint16_t address = static_cast<uint16_t>(data) << 8U;
    for (auto i = 0; i < 256; i++) {
      oam_data_[oam_address_] = cpu_memory_.read(address);
      oam_address_++;
      address++;
    }
    cpu_.idle(ppu_dma_cycles);
    const auto cpu_cycles = cpu_.get_state().cycles;
    if (cpu_cycles % 2 == 1) {
      cpu_.idle(1);
    }
  }

  auto read_registers(const uint16_t address) -> uint8_t {
    BOOST_ASSERT_MSG(address >= 0x2000 && address <= 0x3FFF, "read unexpected address for ppu");
    const auto address_offset = (address - 0x2000);
    switch (address_offset % 8) {
    case 0:
      return read_control();
    case 1:
      return read_mask();
    case 2:
      return read_status();
    case 3:
      return read_object_attribute_memory_address();
    case 4:
      return read_object_attribute_memory_data();
    case 5:
      return read_scroll();
    case 6:
      return read_address();
    case 7:
      return read_data();
    }
    return -1;
  }

  auto read_control() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for control");
    return control_register_.get();
  }

  auto read_mask() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for mask");
    return mask_register_.get();
  }

  auto read_status() -> uint8_t {
    const auto status = (write_register & 0x1FU) | status_register_.get();
    io_context_.vram_address_latch = false;
    status_register_.clear(status_flag::VERTICAL_BLANK_STARTED);
    check_nmi_context();
    return status;
  }

  auto read_object_attribute_memory_address() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for oam address");
    return oam_address_;
  }

  auto read_object_attribute_memory_data() const -> uint8_t {
    return oam_data_[oam_address_];
  }

  auto read_scroll() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for scroll");
    return 0;
  }

  auto read_address() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for address");
    return 0;
  }

  auto read_data() -> uint8_t {
    auto const vram_address = io_context_.vram_address.value;
    auto value = ppu_memory_.read(vram_address);
    if ((vram_address % 0x4000) < palette_background_begin) {
      auto const vram_buffer = io_context_.vram_buffer;
      io_context_.vram_buffer = value;
      value = vram_buffer;
    } else {
      io_context_.vram_buffer = ppu_memory_.read(vram_address - 0x1000);
    }
    io_context_.vram_address.value += control_register_.is_set(control_flag::VRAM_INCREMENT) ? 32 : 1;
    return value;
  }

  auto read_object_attribute_memory_dma() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for oam dma");
    return 0;
  }

  auto peek_registers(const uint16_t address) const -> uint8_t {
    BOOST_ASSERT_MSG(address >= 0x2000 && address <= 0x3FFF, "peek unexpected address for ppu");
    const auto address_offset = (address - 0x2000);
    switch (address_offset % 8) {
    case 0:
      return read_control();
    case 1:
      return read_mask();
    case 2:
      return peek_status();
    case 3:
      return read_object_attribute_memory_address();
    case 4:
      return read_object_attribute_memory_data();
    case 5:
      return read_scroll();
    case 6:
      return read_address();
    case 7:
      return peek_data();
    }
    return -1;
  }

  auto peek_status() const -> uint8_t {
    return status_register_.get();
  }

  auto peek_data() const -> uint8_t {
    auto data = io_context_.vram_buffer;
    if (io_context_.vram_address.value >= palette_background_begin) {
      data = io_context_.vram_buffer;
    }
    return data;
  }

  auto peek_object_attribute_memory_dma() const -> uint8_t {
    return read_object_attribute_memory_address();
  }

  auto process_frame_state() -> void {
    const auto state = current_frame_state();
    if (is_frame_state_set(state, ppu_frame_state::STATE_FLAG_VISIBLE)) {
      process_state_flag_visible();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_REG_BG_SHIFT)) {
      process_state_reg_bg_shift();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_REG_SPRITE_SHIFT)) {
      process_state_reg_sprite_shift();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_REG_BG_RELOAD)) {
      process_state_reg_bg_reload();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_VRAM_FETCH_NT_BYTE)) {
      process_state_vram_fetch_nt_byte();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_VRAM_FETCH_AT_BYTE)) {
      process_state_vram_fetch_at_byte();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_VRAM_FETCH_BG_LOW_BYTE)) {<commit_msg>Fixing peek_status and peek_data logic to function correctly.<commit_after>//
// MIT License
//
// Copyright 2019
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include <array>
#include <boost/assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/static_assert.hpp>

#include "bit_utils.hh"
#include "control_register.hh"
#include "cpu.hh"
#include "mask_register.hh"
#include "memory.hh"
#include "name_table.hh"
#include "palette.hh"
#include "pattern_table.hh"
#include "ppu.hh"
#include "ppu_frame_state.hh"
#include "ppu_io_context.hh"
#include "ppu_render_context.hh"
#include "screen.hh"
#include "status_register.hh"

//
// PPU implementation is largely influenced by the following documents.
//
// https://wiki.nesdev.com/w/index.php/PPU_rendering
//
// https://wiki.nesdev.com/w/images/4/4f/Ppu.svg
//

using namespace jones::ppu;
using namespace jones::screen;
using namespace jones::util;

using ppu_frame_cycles = std::vector<ppu_frame_state_mask>;

using ppu_frame_scanlines = std::vector<ppu_frame_cycles>;

using ppu_frame_buffer = std::vector<std::vector<uint32_t>>;

namespace {

constexpr auto ppu_max_cycles = 340;

constexpr auto ppu_dma_cycles = 513;

constexpr auto ppu_max_sprites = 64;

constexpr auto ppu_max_scanlines = 262;

constexpr auto ppu_num_cycles = 341;

constexpr auto ppu_oam_size = 256;

constexpr auto ppu_oam_secondary_size = 32;

constexpr auto ppu_tile_width = 8;

constexpr auto ppu_tile_height = 8;

constexpr auto ppu_tile_size = 16;

constexpr auto ppu_sprites_per_line = 8;

constexpr auto ppu_scanline_prerender = 261;

constexpr auto ppu_scanline_visible_start = 0;

constexpr auto ppu_scanline_visible_end = 239;

constexpr auto ppu_scanline_postrender = 240;

constexpr auto ppu_scanline_vblank = 241;

constexpr auto ppu_vblank_cycle_delay = 3;

constexpr inline bool is_color_transparent(const uint32_t color) {
  return color == 0;
}

} // namespace

template <typename T>
using memory_mappable_component = jones::memory_mappable_component<T>;

class ppu::impl final {
public:
  impl(memory &cpu_memory, memory &ppu_memory, cpu &cpu, screen::screen *screen)
      : cpu_memory_(cpu_memory), ppu_memory_(ppu_memory), cpu_(cpu), screen_(screen) {
    cpu_memory_.map(std::make_unique<memory_mappable_component<ppu::impl>>(this, "ppu", 0x2000, 0x3FFF));
    cpu_memory_.map(std::make_unique<memory_mappable_component<ppu::impl>>(this, "ppu", 0x4014, 0x4014));
    ppu_memory_.map(std::make_unique<memory_mappable_component<name_table>>(&name_table_, "ppu name_table", name_table_memory_begin, name_table_memory_end));
    ppu_memory_.map(std::make_unique<memory_mappable_component<palette>>(&palette_, "ppu palette", palette_memory_begin, palette_memory_end));
  }

  auto initialize() -> void {
    initialize_frame_scanlines();
    initialize_prerender_scanline();
    initialize_postrender_scanline();
    initialize_visible_scanline();
    initialize_vblank_scanline();
  }

  auto uninitialize() -> void {
    //
    // nothing to do.
    //
  }

  auto peek(const uint16_t address) const -> uint8_t {
    if (address >= 0x2000 && address <= 0x3FFF) {
      return peek_registers(address);
    } else if (address == 0x4014) {
      return peek_object_attribute_memory_dma();
    } else {
      BOOST_STATIC_ASSERT("peek unexpected for ppu");
    }
    return -1;
  }

  auto read(const uint16_t address) -> uint8_t {
    if (address >= 0x2000 && address <= 0x3FFF) {
      return read_registers(address);
    } else if (address == 0x4014) {
      return read_object_attribute_memory_dma();
    } else {
      BOOST_STATIC_ASSERT("read unexpected for ppu");
    }
    return -1;
  }

  auto write(const uint16_t address, const uint8_t data) -> void {
    if (address >= 0x2000 && address <= 0x3FFF) {
      write_registers(address, data);
    } else if (address == 0x4014) {
      write_object_attribute_memory_dma(data);
    } else {
      BOOST_STATIC_ASSERT("write unexpected for ppu");
    }
  }

  auto step() -> uint8_t {
    check_nmi_trigger();
    process_frame_state();
    update_frame_counters();
    return 1;
  }

  auto get_state() -> ppu_state {
    return ppu_state{
        .cycle = frame_current_cycle_,
        .scanline = frame_current_scanline_,
        .frame = frame_current_frame_};
  }

  auto set_state(const ppu_state &state) -> void {
    frame_current_cycle_ = state.cycle;
    frame_current_scanline_ = state.scanline;
    frame_current_frame_ = state.frame;
  }

private:
  auto write_registers(const uint16_t address, const uint8_t data) -> void {
    BOOST_ASSERT_MSG(address >= 0x2000 && address <= 0x3FFF, "write unexpected address for ppu");
    write_register = data;
    const auto address_offset = (address - 0x2000);
    switch (address_offset % 8) {
    case 0:
      write_control(data);
      break;
    case 1:
      write_mask(data);
      break;
    case 2:
      write_status(data);
      break;
    case 3:
      write_object_attribute_memory_address(data);
      break;
    case 4:
      write_object_attribute_memory_data(data);
      break;
    case 5:
      write_scroll(data);
      break;
    case 6:
      write_address(data);
      break;
    case 7:
      write_data(data);
      break;
    }
  }

  auto write_control(const uint8_t data) -> void {
    io_context_.vram_address_temporary.h_nametable = bit_shift_and<uint8_t>(data, 0, 1);
    io_context_.vram_address_temporary.v_nametable = bit_shift_and<uint8_t>(data, 1, 1);

    control_register_.set(data);
  }

  auto write_mask(const uint8_t data) -> void {
    mask_register_.set(data);
  }

  auto write_status(const uint8_t data) -> void {
    BOOST_STATIC_ASSERT("write unexpected for status");
    status_register_.set(data);
  }

  auto write_object_attribute_memory_address(const uint8_t data) -> void {
    oam_address_ = data;
  }

  auto write_object_attribute_memory_data(const uint8_t data) -> void {
    oam_data_[oam_address_] = data;
    oam_address_ += 1;
  }

  auto write_scroll(const uint8_t data) -> void {
    if (!io_context_.vram_address_latch) {
      io_context_.vram_address_temporary.coarse_x_scroll = bit_shift_and<uint8_t>(data, 3, 5);
      io_context_.fine_x_scroll = bit_shift_and<uint8_t>(data, 0, 3);
    } else {
      io_context_.vram_address_temporary.coarse_y_scroll = bit_shift_and<uint8_t>(data, 3, 5);
      io_context_.vram_address_temporary.fine_y_scroll = bit_shift_and<uint8_t>(data, 0, 3);
    }
    io_context_.vram_address_latch = !io_context_.vram_address_latch;
  }

  auto write_address(const uint8_t data) -> void {
    if (!io_context_.vram_address_latch) {
      auto temp_data = io_context_.vram_address_temporary.value;
      auto data_bits = bit_shift_and<uint8_t>(data, 0, 6);
      bit_shift_set<uint16_t>(temp_data, 8, 7, data_bits);
      io_context_.vram_address_temporary.value = temp_data;
    } else {
      auto temp_data = io_context_.vram_address_temporary.value;
      bit_shift_set<uint16_t>(temp_data, 0, 8, data);
      io_context_.vram_address_temporary.value = temp_data;
      io_context_.vram_address.value = io_context_.vram_address_temporary.value;
    }
    io_context_.vram_address_latch = !io_context_.vram_address_latch;
  }

  auto write_data(const uint8_t data) -> void {
    ppu_memory_.write(io_context_.vram_address.value, data);
    io_context_.vram_address.value += control_register_.is_set(control_flag::VRAM_INCREMENT) ? 32 : 1;
  }

  auto write_object_attribute_memory_dma(const uint8_t data) -> void {
    uint16_t address = static_cast<uint16_t>(data) << 8U;
    for (auto i = 0; i < 256; i++) {
      oam_data_[oam_address_] = cpu_memory_.read(address);
      oam_address_++;
      address++;
    }
    cpu_.idle(ppu_dma_cycles);
    const auto cpu_cycles = cpu_.get_state().cycles;
    if (cpu_cycles % 2 == 1) {
      cpu_.idle(1);
    }
  }

  auto read_registers(const uint16_t address) -> uint8_t {
    BOOST_ASSERT_MSG(address >= 0x2000 && address <= 0x3FFF, "read unexpected address for ppu");
    const auto address_offset = (address - 0x2000);
    switch (address_offset % 8) {
    case 0:
      return read_control();
    case 1:
      return read_mask();
    case 2:
      return read_status();
    case 3:
      return read_object_attribute_memory_address();
    case 4:
      return read_object_attribute_memory_data();
    case 5:
      return read_scroll();
    case 6:
      return read_address();
    case 7:
      return read_data();
    }
    return -1;
  }

  auto read_control() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for control");
    return control_register_.get();
  }

  auto read_mask() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for mask");
    return mask_register_.get();
  }

  auto read_status() -> uint8_t {
    const auto status = (write_register & 0x1FU) | status_register_.get();
    io_context_.vram_address_latch = false;
    status_register_.clear(status_flag::VERTICAL_BLANK_STARTED);
    check_nmi_context();
    return status;
  }

  auto read_object_attribute_memory_address() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for oam address");
    return oam_address_;
  }

  auto read_object_attribute_memory_data() const -> uint8_t {
    return oam_data_[oam_address_];
  }

  auto read_scroll() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for scroll");
    return 0;
  }

  auto read_address() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for address");
    return 0;
  }

  auto read_data() -> uint8_t {
    auto const vram_address = io_context_.vram_address.value;
    auto value = ppu_memory_.read(vram_address);
    if ((vram_address % 0x4000) < palette_background_begin) {
      auto const vram_buffer = io_context_.vram_buffer;
      io_context_.vram_buffer = value;
      value = vram_buffer;
    } else {
      io_context_.vram_buffer = ppu_memory_.read(vram_address - 0x1000);
    }
    io_context_.vram_address.value += control_register_.is_set(control_flag::VRAM_INCREMENT) ? 32 : 1;
    return value;
  }

  auto read_object_attribute_memory_dma() const -> uint8_t {
    BOOST_STATIC_ASSERT("read unexpected for oam dma");
    return 0;
  }

  auto peek_registers(const uint16_t address) const -> uint8_t {
    BOOST_ASSERT_MSG(address >= 0x2000 && address <= 0x3FFF, "peek unexpected address for ppu");
    const auto address_offset = (address - 0x2000);
    switch (address_offset % 8) {
    case 0:
      return read_control();
    case 1:
      return read_mask();
    case 2:
      return peek_status();
    case 3:
      return read_object_attribute_memory_address();
    case 4:
      return read_object_attribute_memory_data();
    case 5:
      return read_scroll();
    case 6:
      return read_address();
    case 7:
      return peek_data();
    }
    return -1;
  }

  auto peek_status() const -> uint8_t {
    if (auto const status = status_register_.get();
        frame_current_scanline_ == ppu_scanline_vblank &&
        frame_current_cycle_ < ppu_vblank_cycle_delay) {
      return status_register{status}.clear(status_flag::VERTICAL_BLANK_STARTED);
    } else {
      return status;
    }
  }

  auto peek_data() const -> uint8_t {
    if (auto const address = io_context_.vram_address.value;
        (address % 0x4000) < palette_background_begin) {
      return io_context_.vram_buffer;
    } else {
      return ppu_memory_.read(address);
    }
  }

  auto peek_object_attribute_memory_dma() const -> uint8_t {
    return read_object_attribute_memory_address();
  }

  auto process_frame_state() -> void {
    const auto state = current_frame_state();
    if (is_frame_state_set(state, ppu_frame_state::STATE_FLAG_VISIBLE)) {
      process_state_flag_visible();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_REG_BG_SHIFT)) {
      process_state_reg_bg_shift();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_REG_SPRITE_SHIFT)) {
      process_state_reg_sprite_shift();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_REG_BG_RELOAD)) {
      process_state_reg_bg_reload();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_VRAM_FETCH_NT_BYTE)) {
      process_state_vram_fetch_nt_byte();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_VRAM_FETCH_AT_BYTE)) {
      process_state_vram_fetch_at_byte();
    }
    if (is_frame_state_set(state, ppu_frame_state::STATE_VRAM_FETCH_BG_LOW_BYTE)) {
{'fraction_non_alphanumeric': 0.10272576260948355, 'fraction_numerical': 0.020764119601328904, 'mean_word_length': 3.8275924913431747, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 138, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3184274', 'n_tokens_mistral': 9503, 'n_tokens_neox': 8899, 'n_words': 1952}