diff --git a/src/core/base/CMakeLists.txt b/src/core/base/CMakeLists.txt
index 430da3eb..d6284c3a 100644
--- a/src/core/base/CMakeLists.txt
+++ b/src/core/base/CMakeLists.txt
@@ -35,6 +35,7 @@ target_sources(vbam-core-base
     version.cpp
 
     PUBLIC
+    check.h
     array.h
     file_util.h
     image_util.h
diff --git a/src/core/base/check.h b/src/core/base/check.h
new file mode 100644
index 00000000..4c44a94f
--- /dev/null
+++ b/src/core/base/check.h
@@ -0,0 +1,62 @@
+#ifndef VBAM_CORE_BASE_CHECK_H_
+#define VBAM_CORE_BASE_CHECK_H_
+
+// This header defines a number of macros for checking conditions and crashing
+// the program if they are not met.
+// * VBAM_CHECK(condition) - crashes the program if the condition is not met.
+// * VBAM_NOTREACHED() - crashes the program if this line of code is reached.
+//   In release builds, this macro also tells the compiler that this code path
+//   is unreachable, which can help the compiler generate better code.
+// * VBAM_STRINGIFY(x) - converts the argument to a string literal.
+// While a number of other macros are defined in this file, they are not
+// intended for general use and should be avoided.
+
+#if defined(__GNUC__) || defined(__clang__)
+
+// GCC/Clang.
+#define VBAM_IMMEDIATE_CRASH_DETAIL() __builtin_trap()
+#define VBAM_INTRINSIC_UNREACHABLE_DETAIL() __builtin_unreachable()
+
+#elif defined(_MSC_VER) // defined(__GNUC__) || defined(__clang__)
+
+// MSVC.
+#define VBAM_IMMEDIATE_CRASH_DETAIL() __debugbreak()
+#define VBAM_INTRINSIC_UNREACHABLE_DETAIL() __assume(0)
+
+#else  // defined(__GNUC__) || defined(__clang__)
+
+#error "Unsupported compiler"
+
+#endif  // defined(__GNUC__) || defined(__clang__)
+
+#define VBAM_STRINGIFY_DETAIL(x) #x
+#define VBAM_STRINGIFY(x) VBAM_STRINGIFY_DETAIL(x)
+
+#define VBAM_REQUIRE_SEMICOLON_DETAIL() \
+  static_assert(true, "Require a semicolon after macros invocation.")
+
+#define VBAM_CHECK(condition) \
+  if (!(condition)) { \
+    fputs("CHECK failed at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) ": " #condition "\n", stderr); \
+    VBAM_IMMEDIATE_CRASH_DETAIL(); \
+  } \
+  VBAM_REQUIRE_SEMICOLON_DETAIL()
+
+#define VBAM_NOTREACHED_MESSAGE_DETAIL() \
+  fputs("NOTREACHED code reached at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) "\n", stderr)
+
+#if defined(DEBUG)
+
+#define VBAM_NOTREACHED() \
+  VBAM_NOTREACHED_MESSAGE_DETAIL(); \
+  VBAM_IMMEDIATE_CRASH_DETAIL()
+
+#else  // defined(DEBUG)
+
+#define VBAM_NOTREACHED() \
+  VBAM_NOTREACHED_MESSAGE_DETAIL(); \
+  VBAM_INTRINSIC_UNREACHABLE_DETAIL()
+
+#endif  // defined(DEBUG)
+
+#endif  // VBAM_CORE_BASE_CHECK_H_
diff --git a/src/core/gb/gb.cpp b/src/core/gb/gb.cpp
index 01fb1266..bf1fa3cb 100644
--- a/src/core/gb/gb.cpp
+++ b/src/core/gb/gb.cpp
@@ -1,13 +1,13 @@
 #include "core/gb/gb.h"
 
 #include <array>
-#include <cassert>
 #include <cmath>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <vector>
 
+#include "core/base/check.h"
 #include "core/base/file_util.h"
 #include "core/base/message.h"
 #include "core/base/sizes.h"
@@ -291,8 +291,7 @@ bool gbInitializeRom(size_t romSize) {
         switch (g_gbCartData.validity()) {
             case gbCartData::Validity::kValid:
             case gbCartData::Validity::kUninitialized:
-                // Unreachable.
-                assert(false);
+                VBAM_NOTREACHED();
                 break;
             case gbCartData::Validity::kSizeTooSmall:
                 systemMessage(MSG_UNSUPPORTED_ROM_SIZE,
diff --git a/src/core/gb/gbCartData.cpp b/src/core/gb/gbCartData.cpp
index ca04b2cc..1ed88e25 100644
--- a/src/core/gb/gbCartData.cpp
+++ b/src/core/gb/gbCartData.cpp
@@ -2,8 +2,8 @@
 
 #include <algorithm>
 #include <array>
-#include <cassert>
 
+#include "core/base/check.h"
 #include "core/base/sizes.h"
 
 namespace {
@@ -62,7 +62,7 @@ char byte_to_char(uint8_t byte) {
     } else if (byte < 16) {
         return 'A' + (byte - 10);
     } else {
-        assert(false);
+        VBAM_NOTREACHED();
         return '\0';
     }
 }
@@ -98,7 +98,7 @@ bool is_valid_manufacturer_code(const std::string& manufacturer_code) {
 constexpr size_t kHeaderGlobalChecksumAdress = 0x14e;
 
 uint16_t get_rom_checksum(const uint8_t* romData, size_t romDataSize) {
-    assert(romData);
+    VBAM_CHECK(romData);
 
     uint16_t checksum = 0;
     for (size_t i = 0; i < romDataSize; i++) {
@@ -160,7 +160,7 @@ constexpr size_t kHeaderChecksumEndAdress = 0x14c;
 }  // namespace
 
 gbCartData::gbCartData(const uint8_t* romData, size_t romDataSize) {
-    assert(romData);
+    VBAM_CHECK(romData);
 
     if (romDataSize < sizeof(gbRomHeader)) {
         validity_ = Validity::kSizeTooSmall;
diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp
index d238f0b3..6fefd82c 100644
--- a/src/libretro/libretro.cpp
+++ b/src/libretro/libretro.cpp
@@ -1,4 +1,3 @@
-#include <cassert>
 #include <cstdarg>
 #include <cstdio>
 #include <cstdlib>
@@ -12,6 +11,7 @@
 
 #include "components/filters_agb/filters_agb.h"
 #include "components/filters_interframe/interframe.h"
+#include "core/base/check.h"
 #include "core/base/system.h"
 #include "core/base/file_util.h"
 #include "core/base/sizes.h"
@@ -177,8 +177,7 @@ static void* gb_rtcdata_prt(void)
     case gbCartData::MapperType::kGameGenie:
     case gbCartData::MapperType::kGameShark:
     case gbCartData::MapperType::kUnknown:
-        // Unreachable.
-        assert(false);
+        VBAM_NOTREACHED();
         return nullptr;
     }
     return nullptr;
@@ -205,8 +204,7 @@ static size_t gb_rtcdata_size(void)
     case gbCartData::MapperType::kGameGenie:
     case gbCartData::MapperType::kGameShark:
     case gbCartData::MapperType::kUnknown:
-        // Unreachable.
-        assert(false);
+        VBAM_NOTREACHED();
         break;
     }
     return 0;
@@ -272,8 +270,7 @@ static void gbInitRTC(void)
     case gbCartData::MapperType::kGameGenie:
     case gbCartData::MapperType::kGameShark:
     case gbCartData::MapperType::kUnknown:
-        // Unreachable.
-        assert(false);
+        VBAM_NOTREACHED();
         break;
     }
 }
@@ -1456,8 +1453,7 @@ void retro_run(void)
             case gbCartData::MapperType::kGameGenie:
             case gbCartData::MapperType::kGameShark:
             case gbCartData::MapperType::kUnknown:
-                // Unreachable.
-                assert(false);
+                VBAM_NOTREACHED();
                 break;
             }
             /* Initialize RTC using local time if needed */
diff --git a/src/wx/audio/audio.cpp b/src/wx/audio/audio.cpp
index 208dad20..eb08b2bb 100644
--- a/src/wx/audio/audio.cpp
+++ b/src/wx/audio/audio.cpp
@@ -1,12 +1,12 @@
 #include "wx/audio/audio.h"
 
+#include "core/base/check.h"
 #include "wx/audio/internal/openal.h"
 
 #if defined(__WXMSW__)
 #include "wx/audio/internal/dsound.h"
 #endif
 
-
 #if defined(VBAM_ENABLE_FAUDIO)
 #include "wx/audio/internal/faudio.h"
 #endif
@@ -39,8 +39,7 @@ std::vector<AudioDevice> EnumerateAudioDevices(const config::AudioApi& audio_api
 
         case config::AudioApi::kLast:
         default:
-            // This should never happen.
-            assert(false);
+            VBAM_NOTREACHED();
             return {};
     }
 }
@@ -67,8 +66,7 @@ std::unique_ptr<SoundDriver> CreateSoundDriver(const config::AudioApi& api) {
 
         case config::AudioApi::kLast:
         default:
-            // This should never happen.
-            assert(false);
+            VBAM_NOTREACHED();
             return nullptr;
     }
 }
diff --git a/src/wx/audio/internal/faudio.cpp b/src/wx/audio/internal/faudio.cpp
index e332495b..18aaed17 100644
--- a/src/wx/audio/internal/faudio.cpp
+++ b/src/wx/audio/internal/faudio.cpp
@@ -5,8 +5,6 @@
 
 #include "wx/audio/internal/faudio.h"
 
-#include <cassert>
-
 #include <condition_variable>
 #include <mutex>
 #include <vector>
@@ -18,6 +16,7 @@
 #include <wx/log.h>
 #include <wx/translation.h>
 
+#include "core/base/check.h"
 #include "core/base/system.h"
 #include "core/gba/gbaGlobals.h"
 #include "wx/config/option-proxy.h"
@@ -176,7 +175,7 @@ void FAudio_Output::close() {
 
     if (sVoice) {
         if (playing) {
-            assert(FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW) == 0);
+            VBAM_CHECK(FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW) == 0);
         }
 
         FAudioVoice_DestroyVoice(sVoice);
@@ -259,7 +258,7 @@ bool FAudio_Output::init(long sampleRate) {
     if (OPTION(kSoundUpmix)) {
         // set up stereo upmixing
         FAudioDeviceDetails dd{};
-        assert(FAudio_GetDeviceDetails(faud, 0, &dd) == 0);
+        VBAM_CHECK(FAudio_GetDeviceDetails(faud, 0, &dd) == 0);
         std::vector<float> matrix(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels);
 
         bool matrixAvailable = true;
@@ -353,12 +352,12 @@ bool FAudio_Output::init(long sampleRate) {
         if (matrixAvailable) {
             hr = FAudioVoice_SetOutputMatrix(sVoice, nullptr, 2, dd.OutputFormat.Format.nChannels,
                                              matrix.data(), FAUDIO_DEFAULT_CHANNELS);
-            assert(hr == 0);
+            VBAM_CHECK(hr == 0);
         }
     }
 
     hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
-    assert(hr == 0);
+    VBAM_CHECK(hr == 0);
     playing = true;
     currentBuffer = 0;
     device_changed = false;
@@ -380,7 +379,7 @@ void FAudio_Output::write(uint16_t* finalWave, int) {
         }
 
         FAudioSourceVoice_GetState(sVoice, &vState, flags);
-        assert(vState.BuffersQueued <= buffer_count_);
+        VBAM_CHECK(vState.BuffersQueued <= buffer_count_);
 
         if (vState.BuffersQueued < buffer_count_) {
             if (vState.BuffersQueued == 0) {
@@ -414,7 +413,7 @@ void FAudio_Output::write(uint16_t* finalWave, int) {
     currentBuffer++;
     currentBuffer %= (buffer_count_ + 1);  // + 1 because we need one temporary buffer
     [[maybe_unused]] uint32_t hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, nullptr);
-    assert(hr == 0);
+    VBAM_CHECK(hr == 0);
 }
 
 void FAudio_Output::pause() {
@@ -423,7 +422,7 @@ void FAudio_Output::pause() {
 
     if (playing) {
         [[maybe_unused]] uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
-        assert(hr == 0);
+        VBAM_CHECK(hr == 0);
         playing = false;
     }
 }
@@ -434,7 +433,7 @@ void FAudio_Output::resume() {
 
     if (!playing) {
         [[maybe_unused]] int32_t hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
-        assert(hr == 0);
+        VBAM_CHECK(hr == 0);
         playing = true;
     }
 }
@@ -445,7 +444,7 @@ void FAudio_Output::reset() {
 
     if (playing) {
         [[maybe_unused]] uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
-        assert(hr == 0);
+        VBAM_CHECK(hr == 0);
     }
 
     FAudioSourceVoice_FlushSourceBuffers(sVoice);
@@ -462,7 +461,7 @@ void FAudio_Output::setThrottle(unsigned short throttle_) {
 
     [[maybe_unused]] uint32_t hr =
         FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_COMMIT_NOW);
-    assert(hr == 0);
+    VBAM_CHECK(hr == 0);
 }
 
 }  // namespace
diff --git a/src/wx/audio/internal/openal.cpp b/src/wx/audio/internal/openal.cpp
index deb84dc8..59e5f420 100644
--- a/src/wx/audio/internal/openal.cpp
+++ b/src/wx/audio/internal/openal.cpp
@@ -30,13 +30,12 @@ typedef ALCboolean(ALC_APIENTRY* LPALCISEXTENSIONPRESENT)(ALCdevice* device,
 typedef const ALCchar*(ALC_APIENTRY* LPALCGETSTRING)(ALCdevice* device, ALCenum param);
 #endif
 
-#include <cassert>
-
 #include <wx/arrstr.h>
 #include <wx/log.h>
 #include <wx/translation.h>
 #include <wx/utils.h>
 
+#include "core/base/check.h"
 #include "core/gba/gbaGlobals.h"
 #include "core/gba/gbaSound.h"
 #include "wx/config/option-proxy.h"
@@ -47,7 +46,7 @@ namespace internal {
 namespace {
 
 // Debug
-#define ASSERT_SUCCESS assert(AL_NO_ERROR == alGetError())
+#define ASSERT_SUCCESS VBAM_CHECK(AL_NO_ERROR == alGetError())
 
 #ifndef LOGALL
 // replace logging functions with comments
@@ -171,7 +170,7 @@ void OpenAL::debugState() {
 
 bool OpenAL::init(long sampleRate) {
     winlog("OpenAL::init\n");
-    assert(initialized == false);
+    VBAM_CHECK(initialized == false);
 
     const wxString& audio_device = OPTION(kSoundAudioDevice);
     if (!audio_device.empty()) {
@@ -191,9 +190,9 @@ bool OpenAL::init(long sampleRate) {
     }
 
     context = alcCreateContext(device, nullptr);
-    assert(context != nullptr);
+    VBAM_CHECK(context != nullptr);
     ALCboolean retVal = alcMakeContextCurrent(context);
-    assert(ALC_TRUE == retVal);
+    VBAM_CHECK(ALC_TRUE == retVal);
     alGenBuffers(OPTION(kSoundBuffers), buffer);
     ASSERT_SUCCESS;
     alGenSources(1, &source);
@@ -338,7 +337,7 @@ void OpenAL::write(uint16_t* finalWave, int length) {
                 return;
         }
 
-        assert(nBuffersProcessed > 0);
+        VBAM_CHECK(nBuffersProcessed > 0);
 
         // unqueue buffer
         tempBuffer = 0;
diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp
index fff56a2a..6bc7123f 100644
--- a/src/wx/cmdevents.cpp
+++ b/src/wx/cmdevents.cpp
@@ -11,6 +11,7 @@
 #include <wx/msgdlg.h>
 
 #include "components/filters_interframe/interframe.h"
+#include "core/base/check.h"
 #include "core/base/version.h"
 #include "core/gb/gb.h"
 #include "core/gb/gbCheats.h"
@@ -32,7 +33,7 @@
 
 void MainFrame::GetMenuOptionBool(const wxString& menuName, bool* field)
 {
-    assert(field);
+    VBAM_CHECK(field);
     *field = !*field;
     int id = wxXmlResource::GetXRCID(menuName);
 
@@ -48,7 +49,7 @@ void MainFrame::GetMenuOptionBool(const wxString& menuName, bool* field)
 void MainFrame::GetMenuOptionConfig(const wxString& menu_name,
                                     const config::OptionID& option_id) {
     config::Option* option = config::Option::ByID(option_id);
-    assert(option);
+    VBAM_CHECK(option);
 
     int id = wxXmlResource::GetXRCID(menu_name);
     for (size_t i = 0; i < checkable_mi.size(); i++) {
@@ -64,7 +65,7 @@ void MainFrame::GetMenuOptionConfig(const wxString& menu_name,
                 option->SetInt(is_checked);
                 break;
             default:
-                assert(false);
+                VBAM_CHECK(false);
                 return;
         }
         break;
@@ -73,7 +74,7 @@ void MainFrame::GetMenuOptionConfig(const wxString& menu_name,
 
 void MainFrame::GetMenuOptionInt(const wxString& menuName, int* field, int mask)
 {
-    assert(field);
+    VBAM_CHECK(field);
     int value = mask;
     bool is_checked = ((*field) & (mask)) != (value);
     int id = wxXmlResource::GetXRCID(menuName);
diff --git a/src/wx/config/bindings.cpp b/src/wx/config/bindings.cpp
index 35adb2bc..b11e8395 100644
--- a/src/wx/config/bindings.cpp
+++ b/src/wx/config/bindings.cpp
@@ -176,7 +176,7 @@ void Bindings::AssignInputsToCommand(const std::unordered_set<UserInput>& inputs
 }
 
 void Bindings::UnassignInput(const UserInput& input) {
-    assert(input);
+    VBAM_CHECK(input);
 
     auto iter = input_to_control_.find(input);
     if (iter == input_to_control_.end()) {
@@ -194,7 +194,7 @@ void Bindings::UnassignInput(const UserInput& input) {
 
     // Otherwise, just remove it from the 2 maps.
     auto command_iter = control_to_inputs_.find(iter->second);
-    assert(command_iter != control_to_inputs_.end());
+    VBAM_CHECK(command_iter != control_to_inputs_.end());
 
     command_iter->second.erase(input);
     if (command_iter->second.empty()) {
@@ -238,7 +238,7 @@ void Bindings::UnassignDefaultBinding(const UserInput& input) {
     }
 
     auto command_iter = control_to_inputs_.find(input_iter->second);
-    assert(command_iter != control_to_inputs_.end());
+    VBAM_CHECK(command_iter != control_to_inputs_.end());
 
     command_iter->second.erase(input);
     if (command_iter->second.empty()) {
diff --git a/src/wx/config/cmdtab.cpp b/src/wx/config/cmdtab.cpp
index b50f732b..2acab191 100644
--- a/src/wx/config/cmdtab.cpp
+++ b/src/wx/config/cmdtab.cpp
@@ -4,6 +4,8 @@
 
 #include <wx/wxcrt.h>
 
+#include "core/base/check.h"
+
 // Initializer for struct cmditem
 cmditem new_cmditem(const wxString cmd,
                     const wxString name,
@@ -22,7 +24,7 @@ namespace config {
         }
 
         // Command not found. This should never happen.
-        assert(false);
+        VBAM_NOTREACHED();
         return wxEmptyString;
     }
 
@@ -34,7 +36,7 @@ namespace config {
         }
 
         // Command not found. This should never happen.
-        assert(false);
+        VBAM_NOTREACHED();
         return wxEmptyString;
     }
 
diff --git a/src/wx/config/command.h b/src/wx/config/command.h
index 118c3642..03792c5b 100644
--- a/src/wx/config/command.h
+++ b/src/wx/config/command.h
@@ -2,7 +2,6 @@
 #define VBAM_WX_CONFIG_COMMAND_H_
 
 #include <array>
-#include <cassert>
 #include <functional>
 
 #include <optional.hpp>
@@ -10,6 +9,8 @@
 
 #include <wx/string.h>
 
+#include "core/base/check.h"
+
 namespace config {
 
 // clang-format off
@@ -56,7 +57,7 @@ static constexpr size_t kNbJoypads = 4;
 // Represents an emulated joypad. The internal index is zero-based.
 class GameJoy {
 public:
-    constexpr explicit GameJoy(size_t index) : index_(index) { assert(index < kNbJoypads); }
+    constexpr explicit GameJoy(size_t index) : index_(index) { VBAM_CHECK(index < kNbJoypads); }
 
     // The underlying zero-based index for this emulated joypad.
     constexpr size_t index() const { return index_; }
@@ -179,12 +180,12 @@ public:
     bool is_shortcut() const { return tag() == Tag::kShortcut; }
 
     const GameCommand& game() const {
-        assert(is_game());
+        VBAM_CHECK(is_game());
         return nonstd::get<GameCommand>(control_);
     }
 
     const ShortcutCommand& shortcut() const {
-        assert(is_shortcut());
+        VBAM_CHECK(is_shortcut());
         return nonstd::get<ShortcutCommand>(control_);
     }
 
@@ -201,8 +202,7 @@ public:
                     return shortcut() < other.shortcut();
             }
 
-            // Unreachable.
-            assert(false);
+            VBAM_NOTREACHED();
             return false;
         } else {
             return tag_ < other.tag_;
@@ -260,8 +260,7 @@ struct std::hash<config::Command> {
                 return std::hash<config::ShortcutCommand>{}(control.shortcut());
         }
 
-        // Unreachable.
-        assert(false);
+        VBAM_NOTREACHED();
         return 0;
     }
 };
diff --git a/src/wx/config/emulated-gamepad.cpp b/src/wx/config/emulated-gamepad.cpp
index 1215a1f8..94dbc05f 100644
--- a/src/wx/config/emulated-gamepad.cpp
+++ b/src/wx/config/emulated-gamepad.cpp
@@ -1,5 +1,7 @@
 #include "wx/config/emulated-gamepad.h"
 
+#include "core/base/check.h"
+
 namespace config {
 
 namespace {
@@ -62,7 +64,7 @@ EmulatedGamepad::EmulatedGamepad(const BindingsProvider bindings_provider)
     : joypads_({0, 0, 0, 0}), bindings_provider_(bindings_provider) {}
 
 bool EmulatedGamepad::OnInputPressed(const config::UserInput& user_input) {
-    assert(user_input);
+    VBAM_CHECK(user_input);
 
     const auto command = bindings_provider_()->CommandForInput(user_input);
     if (!command || !command->is_game()) {
@@ -85,7 +87,7 @@ bool EmulatedGamepad::OnInputPressed(const config::UserInput& user_input) {
 }
 
 bool EmulatedGamepad::OnInputReleased(const config::UserInput& user_input) {
-    assert(user_input);
+    VBAM_CHECK(user_input);
 
     const auto command = bindings_provider_()->CommandForInput(user_input);
     if (!command || !command->is_game()) {
diff --git a/src/wx/config/internal/option-internal.cpp b/src/wx/config/internal/option-internal.cpp
index 8011ac9b..3609541a 100644
--- a/src/wx/config/internal/option-internal.cpp
+++ b/src/wx/config/internal/option-internal.cpp
@@ -11,6 +11,7 @@
 #include <wx/log.h>
 #include <wx/translation.h>
 
+#include "core/base/check.h"
 #include "core/base/system.h"
 #include "core/gb/gbGlobals.h"
 #include "core/gba/gbaSound.h"
@@ -580,14 +581,14 @@ const std::array<OptionData, kNbOptions + 1> kAllOptionsData = {
 };
 
 nonstd::optional<OptionID> StringToOptionId(const wxString& input) {
-    static std::map<wxString, OptionID> kStringToOptionId;
-    if (kStringToOptionId.empty()) {
+    static const std::map<wxString, OptionID> kStringToOptionId([] {
+        std::map<wxString, OptionID> string_to_option_id;
         for (size_t i = 0; i < kNbOptions; i++) {
-            kStringToOptionId.emplace(kAllOptionsData[i].config_name,
-                                      static_cast<OptionID>(i));
+            string_to_option_id.emplace(kAllOptionsData[i].config_name, static_cast<OptionID>(i));
         }
-        assert(kStringToOptionId.size() == kNbOptions);
-    }
+        VBAM_CHECK(string_to_option_id.size() == kNbOptions);
+        return string_to_option_id;
+    }());
 
     const auto iter = kStringToOptionId.find(input);
     if (iter == kStringToOptionId.end()) {
@@ -598,42 +599,43 @@ nonstd::optional<OptionID> StringToOptionId(const wxString& input) {
 
 wxString FilterToString(const Filter& value) {
     const size_t size_value = static_cast<size_t>(value);
-    assert(size_value < kNbFilters);
+    VBAM_CHECK(size_value < kNbFilters);
     return kFilterStrings[size_value];
 }
 
 wxString InterframeToString(const Interframe& value) {
     const size_t size_value = static_cast<size_t>(value);
-    assert(size_value < kNbInterframes);
+    VBAM_CHECK(size_value < kNbInterframes);
     return kInterframeStrings[size_value];
 }
 
 wxString RenderMethodToString(const RenderMethod& value) {
     const size_t size_value = static_cast<size_t>(value);
-    assert(size_value < kNbRenderMethods);
+    VBAM_CHECK(size_value < kNbRenderMethods);
     return kRenderMethodStrings[size_value];
 }
 
 wxString AudioApiToString(const AudioApi& value) {
     const size_t size_value = static_cast<size_t>(value);
-    assert(size_value < kNbAudioApis);
+    VBAM_CHECK(size_value < kNbAudioApis);
     return kAudioApiStrings[size_value];
 }
 
 wxString AudioRateToString(const AudioRate& value) {
     const size_t size_value = static_cast<size_t>(value);
-    assert(size_value < kNbSoundRate);
+    VBAM_CHECK(size_value < kNbSoundRate);
     return kAudioRateStrings[size_value];
 }
 
 Filter StringToFilter(const wxString& config_name, const wxString& input) {
-    static std::map<wxString, Filter> kStringToFilter;
-    if (kStringToFilter.empty()) {
+    static const std::map<wxString, Filter> kStringToFilter([] {
+        std::map<wxString, Filter> string_to_filter;
         for (size_t i = 0; i < kNbFilters; i++) {
-            kStringToFilter.emplace(kFilterStrings[i], static_cast<Filter>(i));
+            string_to_filter.emplace(kFilterStrings[i], static_cast<Filter>(i));
         }
-        assert(kStringToFilter.size() == kNbFilters);
-    }
+        VBAM_CHECK(string_to_filter.size() == kNbFilters);
+        return string_to_filter;
+    }());
 
     const auto iter = kStringToFilter.find(input);
     if (iter == kStringToFilter.end()) {
@@ -646,14 +648,14 @@ Filter StringToFilter(const wxString& config_name, const wxString& input) {
 }
 
 Interframe StringToInterframe(const wxString& config_name, const wxString& input) {
-    static std::map<wxString, Interframe> kStringToInterframe;
-    if (kStringToInterframe.empty()) {
+    static const std::map<wxString, Interframe> kStringToInterframe([] {
+        std::map<wxString, Interframe> string_to_interframe;
         for (size_t i = 0; i < kNbInterframes; i++) {
-            kStringToInterframe.emplace(kInterframeStrings[i],
-                                        static_cast<Interframe>(i));
+            string_to_interframe.emplace(kInterframeStrings[i], static_cast<Interframe>(i));
         }
-        assert(kStringToInterframe.size() == kNbInterframes);
-    }
+        VBAM_CHECK(string_to_interframe.size() == kNbInterframes);
+        return string_to_interframe;
+    }());
 
     const auto iter = kStringToInterframe.find(input);
     if (iter == kStringToInterframe.end()) {
@@ -667,14 +669,14 @@ Interframe StringToInterframe(const wxString& config_name, const wxString& input
 
 RenderMethod StringToRenderMethod(const wxString& config_name,
                                   const wxString& input) {
-    static std::map<wxString, RenderMethod> kStringToRenderMethod;
-    if (kStringToRenderMethod.empty()) {
+    static const std::map<wxString, RenderMethod> kStringToRenderMethod([] {
+        std::map<wxString, RenderMethod> string_to_render_method;
         for (size_t i = 0; i < kNbRenderMethods; i++) {
-            kStringToRenderMethod.emplace(kRenderMethodStrings[i],
-                                          static_cast<RenderMethod>(i));
+            string_to_render_method.emplace(kRenderMethodStrings[i], static_cast<RenderMethod>(i));
         }
-        assert(kStringToRenderMethod.size() == kNbRenderMethods);
-    }
+        VBAM_CHECK(string_to_render_method.size() == kNbRenderMethods);
+        return string_to_render_method;
+    }());
 
     const auto iter = kStringToRenderMethod.find(input);
     if (iter == kStringToRenderMethod.end()) {
@@ -687,14 +689,14 @@ RenderMethod StringToRenderMethod(const wxString& config_name,
 }
 
 AudioApi StringToAudioApi(const wxString& config_name, const wxString& input) {
-    static std::map<wxString, AudioApi> kStringToAudioApi;
-    if (kStringToAudioApi.empty()) {
+    static const std::map<wxString, AudioApi> kStringToAudioApi([] {
+        std::map<wxString, AudioApi> string_to_audio_api;
         for (size_t i = 0; i < kNbAudioApis; i++) {
-            kStringToAudioApi.emplace(kAudioApiStrings[i],
-                                      static_cast<AudioApi>(i));
+            string_to_audio_api.emplace(kAudioApiStrings[i], static_cast<AudioApi>(i));
         }
-        assert(kStringToAudioApi.size() == kNbAudioApis);
-    }
+        VBAM_CHECK(string_to_audio_api.size() == kNbAudioApis);
+        return string_to_audio_api;
+    }());
 
     const auto iter = kStringToAudioApi.find(input);
     if (iter == kStringToAudioApi.end()) {
@@ -707,13 +709,14 @@ AudioApi StringToAudioApi(const wxString& config_name, const wxString& input) {
 }
 
 AudioRate StringToSoundQuality(const wxString& config_name, const wxString& input) {
-    static std::map<wxString, AudioRate> kStringToSoundQuality;
-    if (kStringToSoundQuality.empty()) {
+    static const std::map<wxString, AudioRate> kStringToSoundQuality([] {
+        std::map<wxString, AudioRate> string_to_sound_quality;
         for (size_t i = 0; i < kNbSoundRate; i++) {
-            kStringToSoundQuality.emplace(kAudioRateStrings[i], static_cast<AudioRate>(i));
+            string_to_sound_quality.emplace(kAudioRateStrings[i], static_cast<AudioRate>(i));
         }
-        assert(kStringToSoundQuality.size() == kNbSoundRate);
-    }
+        VBAM_CHECK(string_to_sound_quality.size() == kNbSoundRate);
+        return string_to_sound_quality;
+    }());
 
     const auto iter = kStringToSoundQuality.find(input);
     if (iter == kStringToSoundQuality.end()) {
@@ -727,28 +730,23 @@ AudioRate StringToSoundQuality(const wxString& config_name, const wxString& inpu
 wxString AllEnumValuesForType(Option::Type type) {
     switch (type) {
         case Option::Type::kFilter: {
-            static const wxString kAllFilterValues =
-                AllEnumValuesForArray(kFilterStrings);
+            static const wxString kAllFilterValues(AllEnumValuesForArray(kFilterStrings));
             return kAllFilterValues;
         }
         case Option::Type::kInterframe: {
-            static const wxString kAllInterframeValues =
-                AllEnumValuesForArray(kInterframeStrings);
+            static const wxString kAllInterframeValues(AllEnumValuesForArray(kInterframeStrings));
             return kAllInterframeValues;
         }
         case Option::Type::kRenderMethod: {
-            static const wxString kAllRenderValues =
-                AllEnumValuesForArray(kRenderMethodStrings);
+            static const wxString kAllRenderValues(AllEnumValuesForArray(kRenderMethodStrings));
             return kAllRenderValues;
         }
         case Option::Type::kAudioApi: {
-            static const wxString kAllAudioApiValues =
-                AllEnumValuesForArray(kAudioApiStrings);
+            static const wxString kAllAudioApiValues(AllEnumValuesForArray(kAudioApiStrings));
             return kAllAudioApiValues;
         }
         case Option::Type::kAudioRate: {
-            static const wxString kAllSoundQualityValues =
-                AllEnumValuesForArray(kAudioRateStrings);
+            static const wxString kAllSoundQualityValues(AllEnumValuesForArray(kAudioRateStrings));
             return kAllSoundQualityValues;
         }
 
@@ -761,10 +759,10 @@ wxString AllEnumValuesForType(Option::Type type) {
         case Option::Type::kUnsigned:
         case Option::Type::kString:
         case Option::Type::kGbPalette:
-            assert(false);
+            VBAM_NOTREACHED();
             return wxEmptyString;
     }
-    assert(false);
+    VBAM_NOTREACHED();
     return wxEmptyString;
 }
 
@@ -790,10 +788,10 @@ size_t MaxForType(Option::Type type) {
         case Option::Type::kUnsigned:
         case Option::Type::kString:
         case Option::Type::kGbPalette:
-            assert(false);
+            VBAM_NOTREACHED();
             return 0;
     }
-    assert(false);
+    VBAM_NOTREACHED();
     return 0;
 }
 
diff --git a/src/wx/config/option-observer.cpp b/src/wx/config/option-observer.cpp
index cd802dfd..1cbce697 100644
--- a/src/wx/config/option-observer.cpp
+++ b/src/wx/config/option-observer.cpp
@@ -1,5 +1,6 @@
 #include "wx/config/option-observer.h"
 
+#include "core/base/check.h"
 #include "wx/config/option.h"
 
 namespace config {
@@ -10,7 +11,7 @@ public:
     CallbackOptionObserver(const OptionID& option_id,
                            std::function<void(Option*)> callback)
         : Option::Observer(option_id), callback_(std::move(callback)) {
-        assert(callback_);
+        VBAM_CHECK(callback_);
     }
     ~CallbackOptionObserver() override = default;
 
diff --git a/src/wx/config/option-test.cpp b/src/wx/config/option-test.cpp
index 82a19e1c..0b9b0a2c 100644
--- a/src/wx/config/option-test.cpp
+++ b/src/wx/config/option-test.cpp
@@ -21,18 +21,16 @@ TEST(OptionTest, Bool) {
     EXPECT_TRUE(option->SetBool(false));
     EXPECT_FALSE(option->GetBool());
 
-#if !defined(NDEBUG)
-    EXPECT_DEATH(option->SetDouble(2.0), ".*");
-    EXPECT_DEATH(option->SetInt(2), ".*");
-    EXPECT_DEATH(option->SetUnsigned(2), ".*");
-    EXPECT_DEATH(option->SetString("foo"), ".*");
-    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), ".*");
-    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), ".*");
-    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), ".*");
-    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), ".*");
-    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), ".*");
-    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), ".*");
-#endif  // !defined(NDEBUG)
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
 }
 
 TEST(OptionTest, Double) {
@@ -56,18 +54,205 @@ TEST(OptionTest, Double) {
     EXPECT_FALSE(option->SetDouble(7.0));
     EXPECT_DOUBLE_EQ(option->GetDouble(), 2.5);
 
-#if !defined(NDEBUG)
-    EXPECT_DEATH(option->SetBool(true), ".*");
-    EXPECT_DEATH(option->SetInt(2), ".*");
-    EXPECT_DEATH(option->SetUnsigned(2), ".*");
-    EXPECT_DEATH(option->SetString("foo"), ".*");
-    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), ".*");
-    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), ".*");
-    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), ".*");
-    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), ".*");
-    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), ".*");
-    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), ".*");
-#endif  // !defined(NDEBUG)
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, Int) {
+    config::Option* option = config::Option::ByID(config::OptionID::kSoundBuffers);
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->command().empty());
+    EXPECT_EQ(option->config_name(), "Sound/Buffers");
+    EXPECT_EQ(option->id(), config::OptionID::kSoundBuffers);
+    EXPECT_EQ(option->type(), config::Option::Type::kInt);
+    EXPECT_TRUE(option->is_int());
+
+    EXPECT_TRUE(option->SetInt(8));
+    EXPECT_EQ(option->GetInt(), 8);
+
+    // Need to disable logging to test for errors.
+    const wxLogNull disable_logging;
+
+    // Test out of bounds values.
+    EXPECT_FALSE(option->SetInt(-1));
+    EXPECT_FALSE(option->SetInt(42));
+    EXPECT_EQ(option->GetInt(), 8);
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, Unsigned) {
+    config::Option* option = config::Option::ByID(config::OptionID::kGeomWindowHeight);
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(option->config_name(), "geometry/windowHeight");
+    EXPECT_EQ(option->id(), config::OptionID::kGeomWindowHeight);
+    EXPECT_EQ(option->type(), config::Option::Type::kUnsigned);
+    EXPECT_TRUE(option->is_unsigned());
+
+    EXPECT_TRUE(option->SetUnsigned(100));
+    EXPECT_EQ(option->GetUnsigned(), 100);
+
+    // Need to disable logging to test for errors.
+    const wxLogNull disable_logging;
+
+    // Test out of bounds values.
+    EXPECT_FALSE(option->SetUnsigned(100000));
+    EXPECT_EQ(option->GetUnsigned(), 100);
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, String) {
+    config::Option* option = config::Option::ByID(config::OptionID::kGenStateDir);
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->command().empty());
+    EXPECT_EQ(option->config_name(), "General/StateDir");
+    EXPECT_EQ(option->id(), config::OptionID::kGenStateDir);
+    EXPECT_EQ(option->type(), config::Option::Type::kString);
+    EXPECT_TRUE(option->is_string());
+
+    EXPECT_TRUE(option->SetString("/path/to/sthg"));
+    EXPECT_EQ(option->GetString(), "/path/to/sthg");
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, Filter) {
+    config::Option* option = config::Option::ByID(config::OptionID::kDispFilter);
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->command().empty());
+    EXPECT_EQ(option->config_name(), "Display/Filter");
+    EXPECT_EQ(option->id(), config::OptionID::kDispFilter);
+    EXPECT_EQ(option->type(), config::Option::Type::kFilter);
+    EXPECT_TRUE(option->is_filter());
+
+    EXPECT_TRUE(option->SetFilter(config::Filter::kNone));
+    EXPECT_EQ(option->GetFilter(), config::Filter::kNone);
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, Interframe) {
+    config::Option* option = config::Option::ByID(config::OptionID::kDispIFB);
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->command().empty());
+    EXPECT_EQ(option->config_name(), "Display/IFB");
+    EXPECT_EQ(option->id(), config::OptionID::kDispIFB);
+    EXPECT_EQ(option->type(), config::Option::Type::kInterframe);
+    EXPECT_TRUE(option->is_interframe());
+
+    EXPECT_TRUE(option->SetInterframe(config::Interframe::kNone));
+    EXPECT_EQ(option->GetInterframe(), config::Interframe::kNone);
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, AudioApi) {
+    config::Option* option = config::Option::ByID(config::OptionID::kSoundAudioAPI);
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->command().empty());
+    EXPECT_EQ(option->config_name(), "Sound/AudioAPI");
+    EXPECT_EQ(option->id(), config::OptionID::kSoundAudioAPI);
+    EXPECT_EQ(option->type(), config::Option::Type::kAudioApi);
+    EXPECT_TRUE(option->is_audio_api());
+
+    EXPECT_TRUE(option->SetAudioApi(config::AudioApi::kOpenAL));
+    EXPECT_EQ(option->GetAudioApi(), config::AudioApi::kOpenAL);
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
+}
+
+TEST(OptionTest, AudioRate) {
+    config::Option* option = config::Option::ByID(config::OptionID::kSoundAudioRate);
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->command().empty());
+    EXPECT_EQ(option->config_name(), "Sound/Quality");
+    EXPECT_EQ(option->id(), config::OptionID::kSoundAudioRate);
+    EXPECT_EQ(option->type(), config::Option::Type::kAudioRate);
+    EXPECT_TRUE(option->is_audio_rate());
+
+    EXPECT_TRUE(option->SetAudioRate(config::AudioRate::k11kHz));
+    EXPECT_EQ(option->GetAudioRate(), config::AudioRate::k11kHz);
+
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
 }
 
 TEST(OptionTest, Enum) {
@@ -110,6 +295,16 @@ TEST(Optiontest, GbPalette) {
     EXPECT_FALSE(option->SetGbPaletteString(""));
     EXPECT_FALSE(option->SetGbPaletteString("0000,0001,0002,0003,0004,0005,0006,000Q"));
 
+    EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
+    EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
+    EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
+    EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
+    EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
+    EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
+    EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
+    EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
+    EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
+    EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
 }
 
 TEST(OptionObserverTest, Basic) {
diff --git a/src/wx/config/option.cpp b/src/wx/config/option.cpp
index ab579b3e..bf11013d 100644
--- a/src/wx/config/option.cpp
+++ b/src/wx/config/option.cpp
@@ -11,6 +11,7 @@
 #include "wx/config/internal/option-internal.h"
 #undef VBAM_OPTION_INTERNAL_INCLUDE
 
+#include "core/base/check.h"
 #include "wx/config/option-proxy.h"
 
 namespace config {
@@ -26,14 +27,14 @@ Option* Option::ByName(const wxString& config_name) {
 
 // static
 Option* Option::ByID(OptionID id) {
-    assert(id != OptionID::Last);
+    VBAM_CHECK(id != OptionID::Last);
     return &All()[static_cast<size_t>(id)];
 }
 
 Option::~Option() = default;
 
 Option::Observer::Observer(OptionID option_id) : option_(Option::ByID(option_id)) {
-    assert(option_);
+    VBAM_CHECK(option_);
     option_->AddObserver(this);
 }
 Option::Observer::~Observer() {
@@ -49,8 +50,8 @@ Option::Option(OptionID id)
       value_(),
       min_(),
       max_() {
-    assert(id != OptionID::Last);
-    assert(is_none());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_none());
 }
 
 Option::Option(OptionID id, bool* option)
@@ -62,8 +63,8 @@ Option::Option(OptionID id, bool* option)
       value_(option),
       min_(),
       max_() {
-    assert(id != OptionID::Last);
-    assert(is_bool());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_bool());
 }
 
 Option::Option(OptionID id, double* option, double min, double max)
@@ -75,8 +76,8 @@ Option::Option(OptionID id, double* option, double min, double max)
       value_(option),
       min_(min),
       max_(max) {
-    assert(id != OptionID::Last);
-    assert(is_double());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_double());
 
     // Validate the initial value.
     SetDouble(*option);
@@ -91,8 +92,8 @@ Option::Option(OptionID id, int32_t* option, int32_t min, int32_t max)
       value_(option),
       min_(min),
       max_(max) {
-    assert(id != OptionID::Last);
-    assert(is_int());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_int());
 
     // Validate the initial value.
     SetInt(*option);
@@ -107,8 +108,8 @@ Option::Option(OptionID id, uint32_t* option, uint32_t min, uint32_t max)
       value_(option),
       min_(min),
       max_(max) {
-    assert(id != OptionID::Last);
-    assert(is_unsigned());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_unsigned());
 
     // Validate the initial value.
     SetUnsigned(*option);
@@ -123,8 +124,8 @@ Option::Option(OptionID id, wxString* option)
       value_(option),
       min_(),
       max_() {
-    assert(id != OptionID::Last);
-    assert(is_string());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_string());
 }
 
 Option::Option(OptionID id, Filter* option)
@@ -136,8 +137,8 @@ Option::Option(OptionID id, Filter* option)
       value_(option),
       min_(),
       max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
-    assert(id != OptionID::Last);
-    assert(is_filter());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_filter());
 }
 
 Option::Option(OptionID id, Interframe* option)
@@ -149,8 +150,8 @@ Option::Option(OptionID id, Interframe* option)
       value_(option),
       min_(),
       max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
-    assert(id != OptionID::Last);
-    assert(is_interframe());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_interframe());
 }
 
 Option::Option(OptionID id, RenderMethod* option)
@@ -162,8 +163,8 @@ Option::Option(OptionID id, RenderMethod* option)
       value_(option),
       min_(),
       max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
-    assert(id != OptionID::Last);
-    assert(is_render_method());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_render_method());
 }
 
 Option::Option(OptionID id, AudioApi* option)
@@ -175,8 +176,8 @@ Option::Option(OptionID id, AudioApi* option)
       value_(option),
       min_(),
       max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
-    assert(id != OptionID::Last);
-    assert(is_audio_api());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_audio_api());
 }
 
 Option::Option(OptionID id, AudioRate* option)
@@ -188,8 +189,8 @@ Option::Option(OptionID id, AudioRate* option)
       value_(option),
       min_(),
       max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
-    assert(id != OptionID::Last);
-    assert(is_audio_rate());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_audio_rate());
 }
 
 Option::Option(OptionID id, uint16_t* option)
@@ -201,57 +202,57 @@ Option::Option(OptionID id, uint16_t* option)
       value_(option),
       min_(),
       max_() {
-    assert(id != OptionID::Last);
-    assert(is_gb_palette());
+    VBAM_CHECK(id != OptionID::Last);
+    VBAM_CHECK(is_gb_palette());
 }
 
 bool Option::GetBool() const {
-    assert(is_bool());
+    VBAM_CHECK(is_bool());
     return *(nonstd::get<bool*>(value_));
 }
 
 double Option::GetDouble() const {
-    assert(is_double());
+    VBAM_CHECK(is_double());
     return *(nonstd::get<double*>(value_));
 }
 
 int32_t Option::GetInt() const {
-    assert(is_int());
+    VBAM_CHECK(is_int());
     return *(nonstd::get<int32_t*>(value_));
 }
 
 uint32_t Option::GetUnsigned() const {
-    assert(is_unsigned());
+    VBAM_CHECK(is_unsigned());
     return *(nonstd::get<uint32_t*>(value_));
 }
 
 const wxString& Option::GetString() const {
-    assert(is_string());
+    VBAM_CHECK(is_string());
     return *(nonstd::get<wxString*>(value_));
 }
 
 Filter Option::GetFilter() const {
-    assert(is_filter());
+    VBAM_CHECK(is_filter());
     return *(nonstd::get<Filter*>(value_));
 }
 
 Interframe Option::GetInterframe() const {
-    assert(is_interframe());
+    VBAM_CHECK(is_interframe());
     return *(nonstd::get<Interframe*>(value_));
 }
 
 RenderMethod Option::GetRenderMethod() const {
-    assert(is_render_method());
+    VBAM_CHECK(is_render_method());
     return *(nonstd::get<RenderMethod*>(value_));
 }
 
 AudioApi Option::GetAudioApi() const {
-    assert(is_audio_api());
+    VBAM_CHECK(is_audio_api());
     return *(nonstd::get<AudioApi*>(value_));
 }
 
 AudioRate Option::GetAudioRate() const {
-    assert(is_audio_rate());
+    VBAM_CHECK(is_audio_rate());
     return *(nonstd::get<AudioRate*>(value_));
 }
 
@@ -277,15 +278,15 @@ wxString Option::GetEnumString() const {
         case Option::Type::kUnsigned:
         case Option::Type::kString:
         case Option::Type::kGbPalette:
-            assert(false);
+            VBAM_CHECK(false);
             return wxEmptyString;
     }
-    assert(false);
+    VBAM_NOTREACHED();
     return wxEmptyString;
 }
 
 std::array<uint16_t, 8> Option::GetGbPalette() const {
-    assert(is_gb_palette());
+    VBAM_CHECK(is_gb_palette());
 
     const uint16_t* raw_palette = (nonstd::get<uint16_t*>(value_));
     std::array<uint16_t, 8> palette;
@@ -294,7 +295,7 @@ std::array<uint16_t, 8> Option::GetGbPalette() const {
 }
 
 wxString Option::GetGbPaletteString() const {
-    assert(is_gb_palette());
+    VBAM_CHECK(is_gb_palette());
 
     wxString palette_string;
     uint16_t const* value = nonstd::get<uint16_t*>(value_);
@@ -304,7 +305,7 @@ wxString Option::GetGbPaletteString() const {
 }
 
 bool Option::SetBool(bool value) {
-    assert(is_bool());
+    VBAM_CHECK(is_bool());
     bool old_value = GetBool();
     *nonstd::get<bool*>(value_) = value;
     if (old_value != value) {
@@ -314,7 +315,7 @@ bool Option::SetBool(bool value) {
 }
 
 bool Option::SetDouble(double value) {
-    assert(is_double());
+    VBAM_CHECK(is_double());
     double old_value = GetDouble();
     if (value < nonstd::get<double>(min_) || value > nonstd::get<double>(max_)) {
         wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), value,
@@ -329,7 +330,7 @@ bool Option::SetDouble(double value) {
 }
 
 bool Option::SetInt(int32_t value) {
-    assert(is_int());
+    VBAM_CHECK(is_int());
     int old_value = GetInt();
     if (value < nonstd::get<int32_t>(min_) || value > nonstd::get<int32_t>(max_)) {
         wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), value,
@@ -344,7 +345,7 @@ bool Option::SetInt(int32_t value) {
 }
 
 bool Option::SetUnsigned(uint32_t value) {
-    assert(is_unsigned());
+    VBAM_CHECK(is_unsigned());
     uint32_t old_value = GetUnsigned();
     if (value < nonstd::get<uint32_t>(min_) || value > nonstd::get<uint32_t>(max_)) {
         wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), value,
@@ -359,7 +360,7 @@ bool Option::SetUnsigned(uint32_t value) {
 }
 
 bool Option::SetString(const wxString& value) {
-    assert(is_string());
+    VBAM_CHECK(is_string());
     const wxString old_value = GetString();
     *nonstd::get<wxString*>(value_) = value;
     if (old_value != value) {
@@ -369,8 +370,8 @@ bool Option::SetString(const wxString& value) {
 }
 
 bool Option::SetFilter(const Filter& value) {
-    assert(is_filter());
-    assert(value < Filter::kLast);
+    VBAM_CHECK(is_filter());
+    VBAM_CHECK(value < Filter::kLast);
     const Filter old_value = GetFilter();
     *nonstd::get<Filter*>(value_) = value;
     if (old_value != value) {
@@ -380,8 +381,8 @@ bool Option::SetFilter(const Filter& value) {
 }
 
 bool Option::SetInterframe(const Interframe& value) {
-    assert(is_interframe());
-    assert(value < Interframe::kLast);
+    VBAM_CHECK(is_interframe());
+    VBAM_CHECK(value < Interframe::kLast);
     const Interframe old_value = GetInterframe();
     *nonstd::get<Interframe*>(value_) = value;
     if (old_value != value) {
@@ -391,8 +392,8 @@ bool Option::SetInterframe(const Interframe& value) {
 }
 
 bool Option::SetRenderMethod(const RenderMethod& value) {
-    assert(is_render_method());
-    assert(value < RenderMethod::kLast);
+    VBAM_CHECK(is_render_method());
+    VBAM_CHECK(value < RenderMethod::kLast);
     const RenderMethod old_value = GetRenderMethod();
     *nonstd::get<RenderMethod*>(value_) = value;
     if (old_value != value) {
@@ -402,8 +403,8 @@ bool Option::SetRenderMethod(const RenderMethod& value) {
 }
 
 bool Option::SetAudioApi(const AudioApi& value) {
-    assert(is_audio_api());
-    assert(value < AudioApi::kLast);
+    VBAM_CHECK(is_audio_api());
+    VBAM_CHECK(value < AudioApi::kLast);
     const AudioApi old_value = GetAudioApi();
     *nonstd::get<AudioApi*>(value_) = value;
     if (old_value != value) {
@@ -413,8 +414,8 @@ bool Option::SetAudioApi(const AudioApi& value) {
 }
 
 bool Option::SetAudioRate(const AudioRate& value) {
-    assert(is_audio_rate());
-    assert(value < AudioRate::kLast);
+    VBAM_CHECK(is_audio_rate());
+    VBAM_CHECK(value < AudioRate::kLast);
     const AudioRate old_value = GetAudioRate();
     *nonstd::get<AudioRate*>(value_) = value;
     if (old_value != value) {
@@ -445,15 +446,15 @@ bool Option::SetEnumString(const wxString& value) {
         case Option::Type::kUnsigned:
         case Option::Type::kString:
         case Option::Type::kGbPalette:
-            assert(false);
+            VBAM_CHECK(false);
             return false;
     }
-    assert(false);
+    VBAM_NOTREACHED();
     return false;
 }
 
 bool Option::SetGbPalette(const std::array<uint16_t, 8>& value) {
-    assert(is_gb_palette());
+    VBAM_CHECK(is_gb_palette());
 
     uint16_t* dest = nonstd::get<uint16_t*>(value_);
 
@@ -471,7 +472,7 @@ bool Option::SetGbPalette(const std::array<uint16_t, 8>& value) {
 }
 
 bool Option::SetGbPaletteString(const wxString& value) {
-    assert(is_gb_palette());
+    VBAM_CHECK(is_gb_palette());
 
     // 8 values of 4 chars and 7 commas.
     static constexpr size_t kPaletteStringSize = 8 * 4 + 7;
@@ -497,50 +498,50 @@ bool Option::SetGbPaletteString(const wxString& value) {
 }
 
 double Option::GetDoubleMin() const {
-    assert(is_double());
+    VBAM_CHECK(is_double());
     return nonstd::get<double>(min_);
 }
 
 double Option::GetDoubleMax() const {
-    assert(is_double());
+    VBAM_CHECK(is_double());
     return nonstd::get<double>(max_);
 }
 
 int32_t Option::GetIntMin() const {
-    assert(is_int());
+    VBAM_CHECK(is_int());
     return nonstd::get<int32_t>(min_);
 }
 
 int32_t Option::GetIntMax() const {
-    assert(is_int());
+    VBAM_CHECK(is_int());
     return nonstd::get<int32_t>(max_);
 }
 
 uint32_t Option::GetUnsignedMin() const {
-    assert(is_unsigned());
+    VBAM_CHECK(is_unsigned());
     return nonstd::get<uint32_t>(min_);
 }
 
 uint32_t Option::GetUnsignedMax() const {
-    assert(is_unsigned());
+    VBAM_CHECK(is_unsigned());
     return nonstd::get<uint32_t>(max_);
 }
 
 size_t Option::GetEnumMax() const {
-    assert(is_filter() || is_interframe() || is_render_method() || is_audio_api() ||
+    VBAM_CHECK(is_filter() || is_interframe() || is_render_method() || is_audio_api() ||
            is_audio_rate());
     return nonstd::get<size_t>(max_);
 }
 
 void Option::NextFilter() {
-    assert(is_filter());
+    VBAM_CHECK(is_filter());
     const int old_value = static_cast<int>(GetFilter());
     const int new_value = (old_value + 1) % kNbFilters;
     SetFilter(static_cast<Filter>(new_value));
 }
 
 void Option::NextInterframe() {
-    assert(is_interframe());
+    VBAM_CHECK(is_interframe());
     const int old_value = static_cast<int>(GetInterframe());
     const int new_value = (old_value + 1) % kNbInterframes;
     SetInterframe(static_cast<Interframe>(new_value));
@@ -588,19 +589,19 @@ wxString Option::ToHelperString() const {
 }
 
 void Option::AddObserver(Observer* observer) {
-    assert(observer);
+    VBAM_CHECK(observer);
     [[maybe_unused]] const auto pair = observers_.emplace(observer);
-    assert(pair.second);
+    VBAM_CHECK(pair.second);
 }
 
 void Option::RemoveObserver(Observer* observer) {
-    assert(observer);
+    VBAM_CHECK(observer);
     [[maybe_unused]] const size_t removed = observers_.erase(observer);
-    assert(removed == 1u);
+    VBAM_CHECK(removed == 1u);
 }
 
 void Option::CallObservers() {
-    assert(!calling_observers_);
+    VBAM_CHECK(!calling_observers_);
     calling_observers_ = true;
     for (const auto observer : observers_) {
         observer->OnValueChanged();
diff --git a/src/wx/config/user-input.cpp b/src/wx/config/user-input.cpp
index a3401555..925578b4 100644
--- a/src/wx/config/user-input.cpp
+++ b/src/wx/config/user-input.cpp
@@ -257,8 +257,7 @@ wxString JoyInput::ToConfigString() const {
             return wxString::Format("%s-Hat%dE", joy_string, control_index_);
     }
 
-    // Unreachable.
-    assert(false);
+    VBAM_NOTREACHED();
     return wxEmptyString;
 }
 
@@ -281,8 +280,7 @@ wxString JoyInput::ToLocalizedString() const {
             return wxString::Format(_("%s: Hat %d East"), joy_string, control_index_);
     }
 
-    // Unreachable.
-    assert(false);
+    VBAM_NOTREACHED();
     return wxEmptyString;
 }
 
@@ -368,8 +366,7 @@ wxString UserInput::ToConfigString() const {
             return joy_input().ToConfigString();
     }
 
-    // Unreachable.
-    assert(false);
+    VBAM_NOTREACHED();
     return wxEmptyString;
 }
 
@@ -383,8 +380,7 @@ wxString UserInput::ToLocalizedString() const {
             return joy_input().ToLocalizedString();
     }
 
-    // Unreachable.
-    assert(false);
+    VBAM_NOTREACHED();
     return wxEmptyString;
 }
 
diff --git a/src/wx/config/user-input.h b/src/wx/config/user-input.h
index 3a9c1b2e..b4b74a5e 100644
--- a/src/wx/config/user-input.h
+++ b/src/wx/config/user-input.h
@@ -1,7 +1,6 @@
 #ifndef VBAM_WX_CONFIG_USER_INPUT_H_
 #define VBAM_WX_CONFIG_USER_INPUT_H_
 
-#include <cassert>
 #include <cstdint>
 #include <unordered_set>
 
@@ -9,6 +8,8 @@
 
 #include <wx/string.h>
 
+#include "core/base/check.h"
+
 namespace config {
 
 // Abstract representation of a keyboard input. This class is used to represent
@@ -160,12 +161,12 @@ public:
     Device device() const { return device_; }
 
     const KeyboardInput& keyboard_input() const {
-        assert(is_keyboard());
+        VBAM_CHECK(is_keyboard());
         return nonstd::get<KeyboardInput>(input_);
     };
 
     const JoyInput& joy_input() const {
-        assert(is_joystick());
+        VBAM_CHECK(is_joystick());
         return nonstd::get<JoyInput>(input_);
     };
 
@@ -247,8 +248,7 @@ struct std::hash<config::UserInput> {
                        std::hash<config::KeyboardInput>{}(user_input.keyboard_input());
         }
 
-        // Unreachable.
-        assert(false);
+        VBAM_NOTREACHED();
         return 0;
     }
 };
diff --git a/src/wx/dialogs/accel-config.cpp b/src/wx/dialogs/accel-config.cpp
index b3b2e9f5..e2f8c3b1 100644
--- a/src/wx/dialogs/accel-config.cpp
+++ b/src/wx/dialogs/accel-config.cpp
@@ -6,6 +6,7 @@
 #include <wx/menu.h>
 #include <wx/msgdlg.h>
 
+#include "core/base/check.h"
 #include "wx/config/bindings.h"
 #include "wx/config/cmdtab.h"
 #include "wx/config/command.h"
@@ -112,9 +113,9 @@ AccelConfig* AccelConfig::NewInstance(wxWindow* parent,
                                       wxMenuBar* menu,
                                       wxMenu* recents,
                                       const config::BindingsProvider bindings_provider) {
-    assert(parent);
-    assert(menu);
-    assert(recents);
+    VBAM_CHECK(parent);
+    VBAM_CHECK(menu);
+    VBAM_CHECK(recents);
     return new AccelConfig(parent, menu, recents, bindings_provider);
 }
 
@@ -123,7 +124,7 @@ AccelConfig::AccelConfig(wxWindow* parent,
                          wxMenu* recents,
                          const config::BindingsProvider bindings_provider)
     : BaseDialog(parent, "AccelConfig"), bindings_provider_(bindings_provider) {
-    assert(menu);
+    VBAM_CHECK(menu);
 
     // Loads the various dialog elements.
     tree_ = GetValidatedChild<wxTreeCtrl>("Commands");
@@ -157,7 +158,7 @@ AccelConfig::AccelConfig(wxWindow* parent,
     for (const auto& iter : command_to_item_id_) {
         const CommandTreeItemData* item_data =
             static_cast<const CommandTreeItemData*>(tree_->GetItemData(iter.second));
-        assert(item_data);
+        VBAM_CHECK(item_data);
 
         currently_assigned_label_->GetTextExtent(item_data->assigned_string(), &w, &h);
         size.SetWidth(std::max(w, size.GetWidth()));
@@ -277,10 +278,10 @@ void AccelConfig::OnAssignBinding(wxCommandEvent&) {
                 break;
             case config::Command::Tag::kShortcut:
                 const auto iter = command_to_item_id_.find(old_command->shortcut());
-                assert(iter != command_to_item_id_.end());
+                VBAM_CHECK(iter != command_to_item_id_.end());
                 const CommandTreeItemData* old_command_item_data =
                     static_cast<const CommandTreeItemData*>(tree_->GetItemData(iter->second));
-                assert(old_command_item_data);
+                VBAM_CHECK(old_command_item_data);
                 old_command_name = old_command_item_data->message_string();
                 break;
         }
@@ -318,7 +319,7 @@ void AccelConfig::OnKeyInput(wxCommandEvent&) {
                 break;
             case config::Command::Tag::kShortcut:
                 const auto iter = command_to_item_id_.find(command->shortcut());
-                assert(iter != command_to_item_id_.end());
+                VBAM_CHECK(iter != command_to_item_id_.end());
                 currently_assigned_label_->SetLabel(
                     static_cast<CommandTreeItemData*>(tree_->GetItemData(iter->second))
                         ->assigned_string());
diff --git a/src/wx/dialogs/base-dialog.cpp b/src/wx/dialogs/base-dialog.cpp
index 0817782b..e65aae32 100644
--- a/src/wx/dialogs/base-dialog.cpp
+++ b/src/wx/dialogs/base-dialog.cpp
@@ -1,11 +1,10 @@
 #include "wx/dialogs/base-dialog.h"
 
-#include <cassert>
-
 #include <wx/persist.h>
 #include <wx/persist/toplevel.h>
 #include <wx/xrc/xmlres.h>
 
+#include "core/base/check.h"
 #include "wx/config/option-proxy.h"
 #include "wx/widgets/utils.h"
 
@@ -73,7 +72,7 @@ private:
 
 // static
 wxDialog* BaseDialog::LoadDialog(wxWindow* parent, const wxString& xrc_file) {
-    assert(parent);
+    VBAM_CHECK(parent);
     return new BaseDialog(parent, xrc_file);
 }
 
@@ -85,8 +84,7 @@ BaseDialog::BaseDialog(wxWindow* parent, const wxString& xrc_file)
     this->SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
 #endif
 
-    [[maybe_unused]] const bool success = wxXmlResource::Get()->LoadDialog(this, parent, xrc_file);
-    assert(success);
+    VBAM_CHECK(wxXmlResource::Get()->LoadDialog(this, parent, xrc_file));
 
     // Bind the event handler.
     this->Bind(wxEVT_SHOW, &BaseDialog::OnBaseDialogShow, this);
@@ -96,7 +94,7 @@ BaseDialog::BaseDialog(wxWindow* parent, const wxString& xrc_file)
 
 wxWindow* BaseDialog::GetValidatedChild(const wxString& name) const {
     wxWindow* window = this->FindWindow(name);
-    assert(window);
+    VBAM_CHECK(window);
     return window;
 }
 
diff --git a/src/wx/dialogs/base-dialog.h b/src/wx/dialogs/base-dialog.h
index 7ac7451d..238b3875 100644
--- a/src/wx/dialogs/base-dialog.h
+++ b/src/wx/dialogs/base-dialog.h
@@ -6,6 +6,8 @@
 
 #include "wx/widgets/keep-on-top-styler.h"
 
+#include "core/base/check.h"
+
 namespace dialogs {
 
 class BaseDialog : public wxDialog {
@@ -23,7 +25,7 @@ protected:
     template <class T>
     T* GetValidatedChild(const wxString& name) const {
         T* child = wxDynamicCast(this->GetValidatedChild(name), T);
-        assert(child);
+        VBAM_CHECK(child);
         return child;
     }
 
diff --git a/src/wx/dialogs/directories-config.cpp b/src/wx/dialogs/directories-config.cpp
index 5e58a322..9ce62728 100644
--- a/src/wx/dialogs/directories-config.cpp
+++ b/src/wx/dialogs/directories-config.cpp
@@ -2,6 +2,7 @@
 
 #include <wx/filepicker.h>
 
+#include "core/base/check.h"
 #include "wx/dialogs/base-dialog.h"
 #include "wx/widgets/option-validator.h"
 
@@ -14,7 +15,7 @@ class DirectoryStringValidator final : public widgets::OptionValidator {
 public:
     DirectoryStringValidator(config::OptionID option_id)
         : widgets::OptionValidator(option_id) {
-        assert(option()->is_string());
+        VBAM_CHECK(option()->is_string());
     }
     ~DirectoryStringValidator() final = default;
 
@@ -29,7 +30,7 @@ private:
     bool WriteToWindow() final {
         wxDirPickerCtrl* dir_picker =
             wxDynamicCast(GetWindow(), wxDirPickerCtrl);
-        assert(dir_picker);
+        VBAM_CHECK(dir_picker);
         dir_picker->SetPath(option()->GetString());
         return true;
     }
@@ -37,7 +38,7 @@ private:
     bool WriteToOption() final {
         const wxDirPickerCtrl* dir_picker =
             wxDynamicCast(GetWindow(), wxDirPickerCtrl);
-        assert(dir_picker);
+        VBAM_CHECK(dir_picker);
         return option()->SetString(dir_picker->GetPath());
     }
 };
@@ -52,7 +53,7 @@ void SetUpDirPicker(wxDirPickerCtrl* dir_picker,
 
 // static
 DirectoriesConfig* DirectoriesConfig::NewInstance(wxWindow* parent) {
-    assert(parent);
+    VBAM_CHECK(parent);
     return new DirectoriesConfig(parent);
 }
 
diff --git a/src/wx/dialogs/display-config.cpp b/src/wx/dialogs/display-config.cpp
index 797b5fde..c8453b8c 100644
--- a/src/wx/dialogs/display-config.cpp
+++ b/src/wx/dialogs/display-config.cpp
@@ -146,7 +146,7 @@ public:
     explicit RenderValidator(config::RenderMethod render_method)
         : OptionValidator(config::OptionID::kDispRenderMethod),
           render_method_(render_method) {
-        assert(render_method != config::RenderMethod::kLast);
+        VBAM_CHECK(render_method != config::RenderMethod::kLast);
     }
     ~RenderValidator() override = default;
 
@@ -189,7 +189,7 @@ private:
 
     bool WriteToWindow() override {
         wxChoice* plugin_selector = wxDynamicCast(GetWindow(), wxChoice);
-        assert(plugin_selector);
+        VBAM_CHECK(plugin_selector);
         const wxString selected_plugin = option()->GetString();
         for (size_t i = 0; i < plugin_selector->GetCount(); i++) {
             const wxString& plugin_data =
@@ -206,7 +206,7 @@ private:
 
     bool WriteToOption() override {
         wxChoice* plugin_selector = wxDynamicCast(GetWindow(), wxChoice);
-        assert(plugin_selector);
+        VBAM_CHECK(plugin_selector);
         const wxString& selected_window_plugin =
             dynamic_cast<wxStringClientData*>(
                 plugin_selector->GetClientObject(
@@ -220,7 +220,7 @@ private:
 
 // static
 DisplayConfig* DisplayConfig::NewInstance(wxWindow* parent) {
-    assert(parent);
+    VBAM_CHECK(parent);
     return new DisplayConfig(parent);
 }
 
diff --git a/src/wx/dialogs/game-boy-config.cpp b/src/wx/dialogs/game-boy-config.cpp
index 757fb694..2bf001a4 100644
--- a/src/wx/dialogs/game-boy-config.cpp
+++ b/src/wx/dialogs/game-boy-config.cpp
@@ -13,6 +13,7 @@
 
 #include <wx/xrc/xmlres.h>
 
+#include "core/base/check.h"
 #include "wx/config/option-observer.h"
 #include "wx/config/option-proxy.h"
 #include "wx/dialogs/base-dialog.h"
@@ -90,8 +91,8 @@ public:
     PaletteValidator(GBPalettePanelData* palette_data)
         : widgets::OptionValidator(palette_data->option_id_),
           palette_data_(palette_data) {
-        assert(option()->is_gb_palette());
-        assert(palette_data);
+        VBAM_CHECK(option()->is_gb_palette());
+        VBAM_CHECK(palette_data);
     }
     ~PaletteValidator() final = default;
 
@@ -122,8 +123,8 @@ class BIOSPickerValidator final : public widgets::OptionValidator {
 public:
     BIOSPickerValidator(config::OptionID option_id, wxStaticText* label)
         : widgets::OptionValidator(option_id), label_(label) {
-        assert(label_);
-        assert(option()->is_string());
+        VBAM_CHECK(label_);
+        VBAM_CHECK(option()->is_string());
     }
     ~BIOSPickerValidator() final = default;
 
@@ -143,7 +144,7 @@ private:
         } else {
             wxFilePickerCtrl* file_picker =
                 wxDynamicCast(GetWindow(), wxFilePickerCtrl);
-            assert(file_picker);
+            VBAM_CHECK(file_picker);
             file_picker->SetPath(selection);
             label_->SetLabel(selection);
         }
@@ -154,7 +155,7 @@ private:
     bool WriteToOption() final {
         const wxFilePickerCtrl* file_picker =
             wxDynamicCast(GetWindow(), wxFilePickerCtrl);
-        assert(file_picker);
+        VBAM_CHECK(file_picker);
         return option()->SetString(file_picker->GetPath());
     }
 
@@ -175,7 +176,7 @@ private:
 
     bool TransferFromWindow() final {
         const wxChoice* borders_selector = wxDynamicCast(GetWindow(), wxChoice);
-        assert(borders_selector);
+        VBAM_CHECK(borders_selector);
         switch (borders_selector->GetSelection()) {
             case 0:
                 OPTION(kPrefBorderOn) = false;
@@ -199,7 +200,7 @@ private:
 
     bool TransferToWindow() final {
         wxChoice* borders_selector = wxDynamicCast(GetWindow(), wxChoice);
-        assert(borders_selector);
+        VBAM_CHECK(borders_selector);
 
         if (!OPTION(kPrefBorderOn) && !OPTION(kPrefBorderAutomatic)) {
             borders_selector->SetSelection(0);
@@ -225,8 +226,8 @@ GBPalettePanelData::GBPalettePanelData(wxPanel* panel, size_t palette_id)
       default_selector_(widgets::GetValidatedChild<wxChoice>(panel, "DefaultPalette")),
       option_id_(static_cast<config::OptionID>(static_cast<size_t>(config::OptionID::kGBPalette0) +
                                                palette_id)) {
-    assert(panel);
-    assert(palette_id < kNbPalettes);
+    VBAM_CHECK(panel);
+    VBAM_CHECK(palette_id < kNbPalettes);
 
     default_selector_->Bind(
         wxEVT_CHOICE, &GBPalettePanelData::OnDefaultPaletteSelected, this);
@@ -274,7 +275,7 @@ void GBPalettePanelData::UpdateColourPickers() {
 
 void GBPalettePanelData::OnColourChanged(size_t colour_index,
                                          wxColourPickerEvent& event) {
-    assert(colour_index < palette_.size());
+    VBAM_CHECK(colour_index < palette_.size());
 
     // Update the colour value.
     const wxColour colour = event.GetColour();
@@ -313,7 +314,7 @@ void GBPalettePanelData::OnPaletteReset(wxCommandEvent& event) {
 
 // static
 GameBoyConfig* GameBoyConfig::NewInstance(wxWindow* parent) {
-    assert(parent);
+    VBAM_CHECK(parent);
     return new GameBoyConfig(parent);
 }
 
diff --git a/src/wx/dialogs/gb-rom-info.cpp b/src/wx/dialogs/gb-rom-info.cpp
index cc1dd450..b94b6aec 100644
--- a/src/wx/dialogs/gb-rom-info.cpp
+++ b/src/wx/dialogs/gb-rom-info.cpp
@@ -3,6 +3,7 @@
 #include <wx/control.h>
 #include <wx/xrc/xmlres.h>
 
+#include "core/base/check.h"
 #include "core/base/sizes.h"
 #include "core/gb/gb.h"
 #include "wx/dialogs/base-dialog.h"
@@ -92,11 +93,11 @@ wxString GetCartCGBFlag() {
             return wxString::Format(_("%02X (Supported)"), g_gbCartData.cgb_flag());
         case gbCartData::CGBSupport::kRequired:
             return wxString::Format(_("%02X (Required)"), g_gbCartData.cgb_flag());
-        default:
-            // Unreachable.
-            assert(false);
-            return "";
     }
+
+    VBAM_NOTREACHED();
+    return "";
+
 }
 
 // Returns a localized string indicating the ROM size of the loaded GB/GBC cartridge.
@@ -156,18 +157,17 @@ wxString GetCartDestinationCode() {
             return wxString::Format(_("%02X (World)"), g_gbCartData.destination_code_flag());
         case gbCartData::DestinationCode::kUnknown:
             return wxString::Format(_("%02X (Unknown)"), g_gbCartData.destination_code_flag());
-        default:
-            // Unreachable.
-            assert(false);
-            return "";
     }
+
+    VBAM_NOTREACHED();
+    return "";
 }
 
 }  // namespace
 
 // static
 GbRomInfo* GbRomInfo::NewInstance(wxWindow* parent) {
-    assert(parent);
+    VBAM_CHECK(parent);
     return new GbRomInfo(parent);
 }
 
diff --git a/src/wx/dialogs/joypad-config.cpp b/src/wx/dialogs/joypad-config.cpp
index bd973aae..f1e1ea9e 100644
--- a/src/wx/dialogs/joypad-config.cpp
+++ b/src/wx/dialogs/joypad-config.cpp
@@ -2,6 +2,7 @@
 
 #include <wx/checkbox.h>
 
+#include "core/base/check.h"
 #include "wx/config/command.h"
 #include "wx/config/option-proxy.h"
 #include "wx/dialogs/base-dialog.h"
@@ -40,7 +41,7 @@ protected:
 UserInputCtrlValidator::UserInputCtrlValidator(const config::GameCommand game_control,
                                                config::BindingsProvider const bindings_provider)
     : wxValidator(), game_control_(game_control), bindings_provider(bindings_provider) {
-    assert(bindings_provider);
+    VBAM_CHECK(bindings_provider);
 }
 
 wxObject* UserInputCtrlValidator::Clone() const {
@@ -49,7 +50,7 @@ wxObject* UserInputCtrlValidator::Clone() const {
 
 bool UserInputCtrlValidator::TransferToWindow() {
     widgets::UserInputCtrl* control = wxDynamicCast(GetWindow(), widgets::UserInputCtrl);
-    assert(control);
+    VBAM_CHECK(control);
 
     control->SetInputs(bindings_provider()->InputsForCommand(config::Command(game_control_)));
     return true;
@@ -57,7 +58,7 @@ bool UserInputCtrlValidator::TransferToWindow() {
 
 bool UserInputCtrlValidator::TransferFromWindow() {
     widgets::UserInputCtrl* control = wxDynamicCast(GetWindow(), widgets::UserInputCtrl);
-    assert(control);
+    VBAM_CHECK(control);
 
     bindings_provider()->ClearCommandAssignments(config::Command(game_control_));
     for (const auto& input : control->inputs()) {
@@ -72,8 +73,8 @@ bool UserInputCtrlValidator::TransferFromWindow() {
 // static
 JoypadConfig* JoypadConfig::NewInstance(wxWindow* parent,
                                         const config::BindingsProvider bindings_provider) {
-    assert(parent);
-    assert(bindings_provider);
+    VBAM_CHECK(parent);
+    VBAM_CHECK(bindings_provider);
     return new JoypadConfig(parent, bindings_provider);
 }
 
@@ -110,7 +111,7 @@ JoypadConfig::JoypadConfig(wxWindow* parent, const config::BindingsProvider bind
             if (current_parent == prev_parent) {
                 // The first control will be skipped here, but that's fine since
                 // we don't care where it fits in the tab order.
-                assert(prev);
+                VBAM_CHECK(prev);
                 game_key_control->MoveAfterInTabOrder(prev);
             }
             prev = game_key_control;
diff --git a/src/wx/dialogs/sound-config.cpp b/src/wx/dialogs/sound-config.cpp
index 181ec0b7..40c7b12a 100644
--- a/src/wx/dialogs/sound-config.cpp
+++ b/src/wx/dialogs/sound-config.cpp
@@ -12,6 +12,7 @@
 #include <wx/xrc/xmlres.h>
 #include <functional>
 
+#include "core/base/check.h"
 #include "wx/audio/audio.h"
 #include "wx/config/option-id.h"
 #include "wx/config/option-proxy.h"
@@ -37,14 +38,14 @@ private:
 
     bool WriteToWindow() override {
         wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
-        assert(choice);
+        VBAM_CHECK(choice);
         choice->SetSelection(static_cast<int>(option()->GetAudioRate()));
         return true;
     }
 
     bool WriteToOption() override {
         const wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
-        assert(choice);
+        VBAM_CHECK(choice);
         const int selection = choice->GetSelection();
         if (selection == wxNOT_FOUND) {
             return false;
@@ -63,7 +64,7 @@ class AudioApiValidator : public widgets::OptionValidator {
 public:
     explicit AudioApiValidator(config::AudioApi audio_api)
         : OptionValidator(config::OptionID::kSoundAudioAPI), audio_api_(audio_api) {
-        assert(audio_api < config::AudioApi::kLast);
+        VBAM_CHECK(audio_api < config::AudioApi::kLast);
     }
     ~AudioApiValidator() override = default;
 
@@ -103,7 +104,7 @@ private:
 
     bool WriteToWindow() override {
         wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
-        assert(choice);
+        VBAM_CHECK(choice);
 
         const wxString& device_id = option()->GetString();
         for (size_t i = 0; i < choice->GetCount(); i++) {
@@ -121,7 +122,7 @@ private:
 
     bool WriteToOption() override {
         const wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
-        assert(choice);
+        VBAM_CHECK(choice);
         const int selection = choice->GetSelection();
         if (selection == wxNOT_FOUND) {
             return option()->SetString(wxEmptyString);
@@ -136,7 +137,7 @@ private:
 
 // static
 SoundConfig* SoundConfig::NewInstance(wxWindow* parent) {
-    assert(parent);
+    VBAM_CHECK(parent);
     return new SoundConfig(parent);
 }
 
diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp
index 52affac5..69796888 100644
--- a/src/wx/opts.cpp
+++ b/src/wx/opts.cpp
@@ -118,7 +118,7 @@ opts_t::opts_t()
 void load_opts(bool first_time_launch) {
     // just for sanity...
     static bool did_init = false;
-    assert(!did_init);
+    VBAM_CHECK(!did_init);
     did_init = true;
 
     // enumvals should not be translated, since they would cause config file
@@ -447,8 +447,7 @@ void opt_set(const wxString& name, const wxString& val) {
     if (opt && !opt->is_none()) {
         switch (opt->type()) {
         case config::Option::Type::kNone:
-            // This should never happen.
-            assert(false);
+            VBAM_NOTREACHED();
             return;
         case config::Option::Type::kBool:
             if (val != '0' && val != '1') {
diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp
index 71a3ee72..91cbff3f 100644
--- a/src/wx/panel.cpp
+++ b/src/wx/panel.cpp
@@ -25,6 +25,7 @@
 #include "components/filters/filters.h"
 #include "components/filters_agb/filters_agb.h"
 #include "components/filters_interframe/interframe.h"
+#include "core/base/check.h"
 #include "core/base/file_util.h"
 #include "core/base/patch.h"
 #include "core/base/system.h"
@@ -89,10 +90,10 @@ double GetFilterScale() {
             return 6.0;
         case config::Filter::kPlugin:
         case config::Filter::kLast:
-            assert(false);
+            VBAM_NOTREACHED();
             return 1.0;
     }
-    assert(false);
+    VBAM_NOTREACHED();
     return 1.0;
 }
 
@@ -111,10 +112,10 @@ long GetSampleRate() {
             return 11025;
             break;
         case config::AudioRate::kLast:
-            assert(false);
+            VBAM_NOTREACHED();
             return 44100;
     }
-    assert(false);
+    VBAM_NOTREACHED();
     return 44100;
 }
 
@@ -1169,7 +1170,7 @@ void GameArea::OnIdle(wxIdleEvent& event)
                 break;
 #endif
             case config::RenderMethod::kLast:
-                assert(false);
+                VBAM_NOTREACHED();
                 return;
         }
 
@@ -1615,7 +1616,7 @@ private:
                 break;
 
             case config::Interframe::kLast:
-                assert(false);
+                VBAM_NOTREACHED();
                 break;
         }
     }
@@ -1758,7 +1759,7 @@ private:
 
             case config::Filter::kNone:
             case config::Filter::kLast:
-                assert(false);
+                VBAM_NOTREACHED();
                 break;
         }
     }
diff --git a/src/wx/widgets/client-data.h b/src/wx/widgets/client-data.h
index fe1eceed..ac4fe908 100644
--- a/src/wx/widgets/client-data.h
+++ b/src/wx/widgets/client-data.h
@@ -1,12 +1,12 @@
 #ifndef VBAM_WX_WIDGETS_CLIENT_DATA_H_
 #define VBAM_WX_WIDGETS_CLIENT_DATA_H_
 
-#include <cassert>
-
 #include <wx/clntdata.h>
 #include <wx/ctrlsub.h>
 #include <wx/window.h>
 
+#include "core/base/check.h"
+
 namespace widgets {
 
 // A simple wxClientData subclass that holds a single piece of data.
@@ -16,14 +16,14 @@ public:
     // Returns the data stored in the ClientData object.
     static const T& From(wxWindow* window) {
         wxClientData* data = window->GetClientObject();
-        assert(data);
+        VBAM_CHECK(data);
         return static_cast<ClientData<T>*>(data)->data();
     }
 
     // Returns the data stored in the ClientData object for a container.
     static const T& From(wxItemContainer* container, size_t index) {
         wxClientData* data = container->GetClientObject(index);
-        assert(data);
+        VBAM_CHECK(data);
         return static_cast<ClientData<T>*>(data)->data();
     }
 
diff --git a/src/wx/widgets/group-check-box.cpp b/src/wx/widgets/group-check-box.cpp
index 96aa2bea..0e623825 100644
--- a/src/wx/widgets/group-check-box.cpp
+++ b/src/wx/widgets/group-check-box.cpp
@@ -1,5 +1,7 @@
 #include "wx/widgets/group-check-box.h"
 
+#include "core/base/check.h"
+
 namespace widgets {
 
 namespace {
@@ -8,7 +10,7 @@ wxWindow* FindTopLevelWindow(wxWindow* window) {
     while (window != nullptr && !window->IsTopLevel()) {
         window = window->GetParent();
     }
-    assert(window);
+    VBAM_CHECK(window);
     return window;
 }
 
@@ -77,7 +79,7 @@ bool GroupCheckBox::Create(wxWindow* parent,
 }
 
 void GroupCheckBox::AddToGroup() {
-    assert(next_ == this);
+    VBAM_CHECK(next_ == this);
 
     if (GetName().IsEmpty()) {
         // No name means a singleton.
diff --git a/src/wx/widgets/keep-on-top-styler.cpp b/src/wx/widgets/keep-on-top-styler.cpp
index ad3cb748..52d5699a 100644
--- a/src/wx/widgets/keep-on-top-styler.cpp
+++ b/src/wx/widgets/keep-on-top-styler.cpp
@@ -2,6 +2,7 @@
 
 #include <wx/toplevel.h>
 
+#include "core/base/check.h"
 #include "wx/config/option.h"
 
 namespace widgets {
@@ -12,7 +13,7 @@ KeepOnTopStyler::KeepOnTopStyler(wxTopLevelWindow* window)
                        std::bind(&KeepOnTopStyler::OnKeepOnTopChanged,
                                  this,
                                  std::placeholders::_1)) {
-    assert(window_);
+    VBAM_CHECK(window_);
     window_->Bind(wxEVT_SHOW, &KeepOnTopStyler::OnShow, this);
 }
 
diff --git a/src/wx/widgets/option-validator.cpp b/src/wx/widgets/option-validator.cpp
index 489f668e..6d70d835 100644
--- a/src/wx/widgets/option-validator.cpp
+++ b/src/wx/widgets/option-validator.cpp
@@ -6,6 +6,8 @@
 #include <wx/slider.h>
 #include <wx/spinctrl.h>
 
+#include "core/base/check.h"
+
 namespace widgets {
 
 OptionValidator::OptionValidator(config::OptionID option_id)
@@ -27,21 +29,21 @@ bool OptionValidator::TransferToWindow() {
 void OptionValidator::SetWindow(wxWindow* window) {
     wxValidator::SetWindow(window);
     [[maybe_unused]] const bool write_success = WriteToWindow();
-    assert(write_success);
+    VBAM_CHECK(write_success);
 }
 #endif
 
 void OptionValidator::OnValueChanged() {
     [[maybe_unused]] const bool write_success = WriteToWindow();
-    assert(write_success);
+    VBAM_CHECK(write_success);
 }
 
 OptionSelectedValidator::OptionSelectedValidator(config::OptionID option_id,
                                                  uint32_t value)
     : OptionValidator(option_id), value_(value) {
-    assert(option()->is_unsigned());
-    assert(value_ >= option()->GetUnsignedMin());
-    assert(value_ <= option()->GetUnsignedMax());
+    VBAM_CHECK(option()->is_unsigned());
+    VBAM_CHECK(value_ >= option()->GetUnsignedMin());
+    VBAM_CHECK(value_ <= option()->GetUnsignedMax());
 }
 
 wxObject* OptionSelectedValidator::Clone() const {
@@ -65,7 +67,7 @@ bool OptionSelectedValidator::WriteToWindow() {
         return true;
     }
 
-    assert(false);
+    VBAM_NOTREACHED();
     return false;
 }
 
@@ -87,13 +89,13 @@ bool OptionSelectedValidator::WriteToOption() {
         return true;
     }
 
-    assert(false);
+    VBAM_NOTREACHED();
     return false;
 }
 
 OptionIntValidator::OptionIntValidator(config::OptionID option_id)
     : OptionValidator(option_id) {
-    assert(option()->is_int());
+    VBAM_CHECK(option()->is_int());
 }
 
 wxObject* OptionIntValidator::Clone() const {
@@ -117,7 +119,7 @@ bool OptionIntValidator::WriteToWindow() {
         return true;
     }
 
-    assert(false);
+    VBAM_NOTREACHED();
     return false;
 }
 
@@ -132,13 +134,13 @@ bool OptionIntValidator::WriteToOption() {
         return option()->SetInt(slider->GetValue());
     }
 
-    assert(false);
+    VBAM_NOTREACHED();
     return false;
 }
 
 OptionChoiceValidator::OptionChoiceValidator(config::OptionID option_id)
     : OptionValidator(option_id) {
-    assert(option()->is_unsigned());
+    VBAM_CHECK(option()->is_unsigned());
 }
 
 wxObject* OptionChoiceValidator::Clone() const {
@@ -151,20 +153,20 @@ bool OptionChoiceValidator::IsWindowValueValid() {
 
 bool OptionChoiceValidator::WriteToWindow() {
     wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
-    assert(choice);
+    VBAM_CHECK(choice);
     choice->SetSelection(option()->GetUnsigned());
     return true;
 }
 
 bool OptionChoiceValidator::WriteToOption() {
     const wxChoice* choice = wxDynamicCast(GetWindow(), wxChoice);
-    assert(choice);
+    VBAM_CHECK(choice);
     return option()->SetUnsigned(choice->GetSelection());
 }
 
 OptionBoolValidator::OptionBoolValidator(config::OptionID option_id)
     : OptionValidator(option_id) {
-    assert(option()->is_bool());
+    VBAM_CHECK(option()->is_bool());
 }
 
 wxObject* OptionBoolValidator::Clone() const {
@@ -177,14 +179,14 @@ bool OptionBoolValidator::IsWindowValueValid() {
 
 bool OptionBoolValidator::WriteToWindow() {
     wxCheckBox* checkbox = wxDynamicCast(GetWindow(), wxCheckBox);
-    assert(checkbox);
+    VBAM_CHECK(checkbox);
     checkbox->SetValue(option()->GetBool());
     return true;
 }
 
 bool OptionBoolValidator::WriteToOption() {
     const wxCheckBox* checkbox = wxDynamicCast(GetWindow(), wxCheckBox);
-    assert(checkbox);
+    VBAM_CHECK(checkbox);
     return option()->SetBool(checkbox->GetValue());
 }
 
diff --git a/src/wx/widgets/render-plugin.cpp b/src/wx/widgets/render-plugin.cpp
index 02889137..9c47b593 100644
--- a/src/wx/widgets/render-plugin.cpp
+++ b/src/wx/widgets/render-plugin.cpp
@@ -1,9 +1,11 @@
 #include "wx/widgets/render-plugin.h"
 
+#include "core/base/check.h"
+
 namespace widgets {
 
 RENDER_PLUGIN_INFO* MaybeLoadFilterPlugin(const wxString& path, wxDynamicLibrary* filter_plugin) {
-    assert(filter_plugin);
+    VBAM_CHECK(filter_plugin);
 
     if (!filter_plugin->Load(path, wxDL_VERBATIM | wxDL_NOW | wxDL_QUIET)) {
         return nullptr;
diff --git a/src/wx/widgets/sdl-poller.cpp b/src/wx/widgets/sdl-poller.cpp
index e2734e5a..03efc566 100644
--- a/src/wx/widgets/sdl-poller.cpp
+++ b/src/wx/widgets/sdl-poller.cpp
@@ -1,6 +1,5 @@
 #include "wx/widgets/sdl-poller.h"
 
-#include <cassert>
 #include <map>
 
 #include <wx/timer.h>
@@ -8,6 +7,7 @@
 
 #include <SDL.h>
 
+#include "core/base/check.h"
 #include "wx/config/option-id.h"
 #include "wx/config/option-observer.h"
 #include "wx/config/option-proxy.h"
@@ -38,7 +38,7 @@ config::JoyControl AxisStatusToJoyControl(const JoyAxisStatus& status) {
         case JoyAxisStatus::Neutral:
         default:
             // This should never happen.
-            assert(false);
+            VBAM_NOTREACHED();
             return config::JoyControl::AxisPlus;
     }
 }
@@ -55,7 +55,7 @@ config::JoyControl HatStatusToJoyControl(const uint8_t status) {
             return config::JoyControl::HatEast;
         default:
             // This should never happen.
-            assert(false);
+            VBAM_NOTREACHED();
             return config::JoyControl::HatNorth;
     }
 }
@@ -306,7 +306,7 @@ SdlPoller::SdlPoller(EventHandlerProvider* const handler_provider)
       game_controller_enabled_observer_(
           config::OptionID::kSDLGameControllerMode,
           [this](config::Option* option) { ReconnectControllers(option->GetBool()); }) {
-    assert(handler_provider);
+    VBAM_CHECK(handler_provider);
 
     wxTimer::Start(50);
     SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
diff --git a/src/wx/widgets/user-input-ctrl.cpp b/src/wx/widgets/user-input-ctrl.cpp
index cad8f15c..c63b96b9 100644
--- a/src/wx/widgets/user-input-ctrl.cpp
+++ b/src/wx/widgets/user-input-ctrl.cpp
@@ -2,6 +2,7 @@
 
 #include <wx/time.h>
 
+#include "core/base/check.h"
 #include "wx/config/user-input.h"
 #include "wx/widgets/user-input-event.h"
 
@@ -50,7 +51,7 @@ void UserInputCtrl::SetInputs(const std::unordered_set<config::UserInput>& input
 }
 
 config::UserInput UserInputCtrl::SingleInput() const {
-    assert(!is_multikey_);
+    VBAM_CHECK(!is_multikey_);
     if (inputs_.empty()) {
         return config::UserInput();
     }
diff --git a/src/wx/widgets/user-input-event.cpp b/src/wx/widgets/user-input-event.cpp
index a5a518e2..fba8f056 100644
--- a/src/wx/widgets/user-input-event.cpp
+++ b/src/wx/widgets/user-input-event.cpp
@@ -153,7 +153,7 @@ wxEvent* UserInputEvent::Clone() const {
 
 KeyboardInputSender::KeyboardInputSender(EventHandlerProvider* const handler_provider)
     : handler_provider_(handler_provider) {
-    assert(handler_provider_);
+    VBAM_CHECK(handler_provider_);
 }
 
 KeyboardInputSender::~KeyboardInputSender() = default;
diff --git a/src/wx/widgets/utils.h b/src/wx/widgets/utils.h
index a9285e69..ffc0298f 100644
--- a/src/wx/widgets/utils.h
+++ b/src/wx/widgets/utils.h
@@ -1,11 +1,11 @@
 #ifndef VBAM_WX_WIDGETS_UTILS_H_
 #define VBAM_WX_WIDGETS_UTILS_H_
 
-#include <cassert>
-
 #include <wx/window.h>
 #include <wx/gdicmn.h>
 
+#include "core/base/check.h"
+
 // This file contains a collection of various utility functions for wxWidgets.
 
 namespace widgets {
@@ -18,14 +18,14 @@ wxRect GetDisplayRect();
 inline wxWindow* GetValidatedChild(const wxWindow* parent,
                                    const wxString& name) {
     wxWindow* window = parent->FindWindow(name);
-    assert(window);
+    VBAM_CHECK(window);
     return window;
 }
 
 template <class T>
 T* GetValidatedChild(const wxWindow* parent, const wxString& name) {
     T* child = wxDynamicCast(GetValidatedChild(parent, name), T);
-    assert(child);
+    VBAM_CHECK(child);
     return child;
 }
 
diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp
index afbeeddd..9d700c94 100644
--- a/src/wx/wxvbam.cpp
+++ b/src/wx/wxvbam.cpp
@@ -63,10 +63,10 @@ void ResetMenuItemAccelerator(wxMenuItem* menu_item) {
     std::unordered_set<config::UserInput> user_inputs =
         wxGetApp().bindings()->InputsForCommand(
             config::ShortcutCommand(menu_item->GetId()));
-    for (const config::UserInput& user_input : user_inputs) {
+    if (!user_inputs.empty()) {
+        const config::UserInput& user_input = *user_inputs.begin();
         new_label.append('\t');
         new_label.append(user_input.ToLocalizedString());
-        break;
     }
 
     if (old_label != new_label) {