Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 8b7f6dd

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

C++ 72.3% C 26.2%
git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gz

Namespace Config[A-Z] to Config::..

Christian Immanuel · 2026-06-26 11:01 · 8b7f6dd50f0870651816461fc2bd0fcf9d576043

 src/entities/camera.hpp            |  4 +--
 src/entities/player.cpp            |  6 ++--
 src/entities/pult.cpp              |  6 ++--
 src/entities/world.hpp             |  4 +--
 src/levels/director.cpp            | 16 +++++-----
 src/levels/scene.hpp               |  2 +-
 src/rendering/gl_renderer.cpp      |  4 +--
 src/rendering/wall_surfaces.cpp    | 10 +++---
 src/states/game_context.cpp        |  2 +-
 src/states/game_state.cpp          | 42 ++++++++++++-------------
 src/states/menu_state.cpp          |  8 ++---
 src/systems/carry_system.cpp       |  6 ++--
 src/systems/celebration_system.cpp |  2 +-
 src/systems/hint_system.cpp        |  2 +-
 src/systems/particle_system.cpp    |  2 +-
 src/systems/physics_system.cpp     | 10 +++---
 src/systems/projectile_system.cpp  | 34 ++++++++++----------
 src/systems/wall_connection.cpp    |  4 +--
 src/systems/word_ai_system.cpp     | 34 ++++++++++----------
 src/systems/word_anim_system.cpp   |  4 +--
 src/text/handler.cpp               | 64 +++++++++++++++++++-------------------
 src/text/token.hpp                 |  2 +-
 src/text/token_db.cpp              | 28 ++++++++---------
 src/utils/config.hpp               | 25 +++++++++------
 src/wall/label.cpp                 | 10 +++---
 src/wall/wall.cpp                  | 20 ++++++------
 26 files changed, 179 insertions(+), 172 deletions(-)

diff --git a/src/entities/camera.hpp b/src/entities/camera.hpp
index 6155c77..75e2a33 100644
--- a/src/entities/camera.hpp
+++ b/src/entities/camera.hpp
@@ -28,8 +28,8 @@ struct Camera
         yaw   = player.yaw;
         pitch = player.pitch;
 
-        yaw_r   = ConfigPhysics::binrad * yaw;
-        pitch_r = ConfigPhysics::binrad * pitch;
+        yaw_r   = Config::Physics::binrad * yaw;
+        pitch_r = Config::Physics::binrad * pitch;
         fwd = {
            -SDL_sin(yaw_r) * SDL_cos(pitch_r),
             SDL_sin(pitch_r),
diff --git a/src/entities/player.cpp b/src/entities/player.cpp
index 3e49d08..0ccf4a1 100644
--- a/src/entities/player.cpp
+++ b/src/entities/player.cpp
@@ -45,7 +45,7 @@ Player::add_Look(int dx, int dy)
 void
 Player::compute_Movement(const FrameInput& input)
 {
-    double rad = (double)yaw * ConfigPhysics::binrad;
+    double rad = (double)yaw * Config::Physics::binrad;
     double cos = SDL_cos(rad);
     double sin = SDL_sin(rad);
 
@@ -59,8 +59,8 @@ Player::compute_Movement(const FrameInput& input)
 
     if (n != 0) {
         double inv = 1.0 / SDL_sqrt(n);
-        _acc_x = ConfigPhysics::player_move_accel * ( cos*dir_x + sin*dir_z) * inv;
-        _acc_z = ConfigPhysics::player_move_accel * (-sin*dir_x + cos*dir_z) * inv;
+        _acc_x = Config::Physics::player_move_accel * ( cos*dir_x + sin*dir_z) * inv;
+        _acc_z = Config::Physics::player_move_accel * (-sin*dir_x + cos*dir_z) * inv;
     }
 }
 
diff --git a/src/entities/pult.cpp b/src/entities/pult.cpp
index 7af3c0b..826af67 100644
--- a/src/entities/pult.cpp
+++ b/src/entities/pult.cpp
@@ -22,7 +22,7 @@ PultButton::PultButton(const std::string& lbl, PultAction act, Vec3 world_pos)
 
 void
 PultButton::press()
-{ pressed = true; press_timer = ConfigPult::press_dur; }
+{ pressed = true; press_timer = Config::Pult::press_dur; }
 
 
 void
@@ -68,9 +68,9 @@ Pult::Pult(const std::string& n, Vec3 world_pos,
      , name(n), half_w(half_width), half_d(half_depth)
 {
     label.set_Billboard (true);
-    label.set_Style     (ConfigColor::teal, Text::Token::depth_wall, 0.0025f);
+    label.set_Style     (Config::Color::teal, Text::Token::depth_wall, 0.0025f);
     label.set_Collidable(true);
-    label.attach_To     (&pos, Vec3{ 0.0, ConfigPult::label_height, 0.0 });
+    label.attach_To     (&pos, Vec3{ 0.0, Config::Pult::label_height, 0.0 });
     label.set_Text      (n);
 }
 
diff --git a/src/entities/world.hpp b/src/entities/world.hpp
index cdb02cb..51580b7 100644
--- a/src/entities/world.hpp
+++ b/src/entities/world.hpp
@@ -40,11 +40,11 @@ public:
 
     template <typename T = float>
     inline static T
-         get_FloorY()   { return static_cast<T>(-ConfigGame::world_box_scale); }
+         get_FloorY()   { return static_cast<T>(-Config::Game::world_box_scale); }
 
     template <typename T = float>
     inline static T
-         get_BoxScale() { return static_cast<T>(ConfigGame::world_box_scale); }
+         get_BoxScale() { return static_cast<T>(Config::Game::world_box_scale); }
 };
 
 } // namespace Nimbin
diff --git a/src/levels/director.cpp b/src/levels/director.cpp
index cbe57c3..a67f2bf 100644
--- a/src/levels/director.cpp
+++ b/src/levels/director.cpp
@@ -101,7 +101,7 @@ Director::setup_Scene(GameContext& ctx, const Scene::Data& scene)
     }
 
     for (const Scene::Placed::Ai& a : scene.ai_seeds) {
-        Text::TokenPtr t = Text::make_Token(a.text, Text::Token::depth_floor, 0.003f, a.pos, ConfigColor::floor);
+        Text::TokenPtr t = Text::make_Token(a.text, Text::Token::depth_floor, 0.003f, a.pos, Config::Color::floor);
         t->body.hover_height = a.pos.y;
         t->ai.aggressiveness = a.aggressiveness;
 
@@ -109,7 +109,7 @@ Director::setup_Scene(GameContext& ctx, const Scene::Data& scene)
             case Text::Token::Ai::State::Attack:
                 t->ai.state  = Text::Token::Ai::State::Attack;
                 t->body.mode = PhysicsMode::AiHover;
-                t->set_Color      (ConfigColor::attack);
+                t->set_Color      (Config::Color::attack);
                 t->set_ColorSource(Text::ColorSource::Static);
                 t->ai.state_timer = 0.0;
                 t->ai.phase_timer = 0.5;
@@ -117,7 +117,7 @@ Director::setup_Scene(GameContext& ctx, const Scene::Data& scene)
             case Text::Token::Ai::State::Flee:
                 t->ai.state  = Text::Token::Ai::State::Flee;
                 t->body.mode = PhysicsMode::AiHover;
-                t->set_Color      (ConfigColor::flee);
+                t->set_Color      (Config::Color::flee);
                 t->set_ColorSource(Text::ColorSource::Static);
                 break;
             case Text::Token::Ai::State::Idle:
@@ -166,16 +166,16 @@ void Director::show_Details(GameContext& ctx) const
         float y = World::get_FloorY<float>() + 1.0f;
         const Wall::Transform tf = Wall::get_Transform(Wall::Role::Info, World::get_BoxScale());
         for (const std::string& line : lines) {
-            if (line.empty()) { y += ConfigWall::text_line_spacing; continue; }
+            if (line.empty()) { y += Config::Wall::text_line_spacing; continue; }
             Text::TokenPtr t = Text::make_Token(line, Text::Token::depth_wall, 0.003f,
-                                                tf.position, ConfigColor::white);
+                                                tf.position, Config::Color::white);
             t->yaw = tf.yaw;
             t->pos = tf.position;
             t->pos.y = y;
             t->set_ColorSource(Text::ColorSource::Static);
-            t->set_Color(ConfigColor::white);
+            t->set_Color(Config::Color::white);
             _detail_tokens.push_back(std::move(t));
-            y += ConfigWall::text_line_spacing;
+            y += Config::Wall::text_line_spacing;
         }
         _detail_built_for = step;
     }
@@ -238,7 +238,7 @@ Director::try_Advance(GameContext& ctx, double dt,
 
     Goal::Context gctx = build_GoalCtx(ctx);
     if (!Goal::is_Satisfied(_runner->get_Current().goal, gctx)) return res;
-    if (_step_age < ConfigLevel::settle_time_for_step)  return res;
+    if (_step_age < Config::Level::settle_time_for_step)  return res;
 
     double glyph_yaw = SDL_atan2(-ctx.camera->fwd.x, -ctx.camera->fwd.z);
     celebration.start(*ctx.camera, glyph_yaw);
diff --git a/src/levels/scene.hpp b/src/levels/scene.hpp
index e1072f0..c8dabe3 100644
--- a/src/levels/scene.hpp
+++ b/src/levels/scene.hpp
@@ -20,7 +20,7 @@ namespace Scene
             std::string text;
 
             Vec3 pos {};
-            SDL_FColor color = ConfigColor::floor;
+            SDL_FColor color = Config::Color::floor;
             bool required = false;
         };
 
diff --git a/src/rendering/gl_renderer.cpp b/src/rendering/gl_renderer.cpp
index 9a6bf4d..6dd9c8c 100644
--- a/src/rendering/gl_renderer.cpp
+++ b/src/rendering/gl_renderer.cpp
@@ -350,8 +350,8 @@ Mat4 Mat4::mul(const Mat4& a, const Mat4& b)
 
 Mat4 buildViewMat4(const Camera& cam)
 {
-    double yaw   = ConfigPhysics::binrad * (double)cam.yaw;
-    double pitch = ConfigPhysics::binrad * (double)cam.pitch;
+    double yaw   = Config::Physics::binrad * (double)cam.yaw;
+    double pitch = Config::Physics::binrad * (double)cam.pitch;
 
     double cy = SDL_cos(yaw),   sy = SDL_sin(yaw);
     double cp = SDL_cos(pitch), sp = SDL_sin(pitch);
diff --git a/src/rendering/wall_surfaces.cpp b/src/rendering/wall_surfaces.cpp
index 201e489..abad222 100644
--- a/src/rendering/wall_surfaces.cpp
+++ b/src/rendering/wall_surfaces.cpp
@@ -107,10 +107,10 @@ Surfaces::generate(float world_scale)
         bool       is_editor;
     };
     const Spec specs[4] = {
-        { Role::Editor,   Position::NegX, ConfigColor::editor,   false },
-        { Role::Program,  Position::PosX, ConfigColor::program,  false },
-        { Role::Info,     Position::PosZ, ConfigColor::info,     false },
-        { Role::Compiler, Position::NegZ, ConfigColor::compiler, false },
+        { Role::Editor,   Position::NegX, Config::Color::editor,   false },
+        { Role::Program,  Position::PosX, Config::Color::program,  false },
+        { Role::Info,     Position::PosZ, Config::Color::info,     false },
+        { Role::Compiler, Position::NegZ, Config::Color::compiler, false },
     };
 
     for (size_t i = 0; i < 4; i++) {
@@ -135,7 +135,7 @@ Surfaces::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
     gl.set_MVP            (model, mvp);
     gl.set_NormalMat      (model);
     gl.set_Light          (light);
-    gl.set_TextLineSpacing(ConfigWall::text_line_spacing);
+    gl.set_TextLineSpacing(Config::Wall::text_line_spacing);
 
     for (const Quad& w : _walls) {
         gl.set_Color           (w.color.r, w.color.g, w.color.b);
diff --git a/src/states/game_context.cpp b/src/states/game_context.cpp
index 0e829da..877fabd 100644
--- a/src/states/game_context.cpp
+++ b/src/states/game_context.cpp
@@ -11,7 +11,7 @@ void
 GameContext::drop_WordToFloor(Text::TokenPtr word, const Vec3& vel)
 {
     if (!word) return;
-    word->set_Look(ConfigColor::keep, Text::Token::depth_floor);
+    word->set_Look(Config::Color::keep, Text::Token::depth_floor);
     word->body.mode = PhysicsMode::Dynamic;
     word->body.vel  = vel;
     words.push_back(std::move(word));
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index f98437b..454d3ac 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -63,7 +63,7 @@ GameState::GameState(size_t starting_level)
     _wall_program.set_ShowNumbers (false, false);
     _wall_program.set_DefaultStyle(Text::Token::depth_wall, 0.003f,
                               static_cast<double>(ProgramSystem::line_height),
-                              ConfigWall::free_space_dist);
+                              Config::Wall::free_space_dist);
     _wall_program.set_AppendBaseline(static_cast<double>(ProgramSystem::start_y));
     _wall_program.set_FaceMode      (Text::FaceMode::Pitch);
 
@@ -73,9 +73,9 @@ GameState::GameState(size_t starting_level)
     _wall_compiler.set_ShowNumbers (false, false);
     _wall_compiler.set_DefaultStyle(Text::Token::depth_compiler, 0.003f,
                                     static_cast<double>(CompilerSystem::line_height),
-                                    ConfigWall::free_space_dist);
+                                    Config::Wall::free_space_dist);
     _wall_compiler.set_AppendBaseline(static_cast<double>(CompilerSystem::start_y));
-    _wall_compiler.set_AppendColor(ConfigColor::white);
+    _wall_compiler.set_AppendColor(Config::Color::white);
     _wall_compiler.set_FaceMode(Text::FaceMode::Pitch);
 
     _wall_info = Text::Handler(Wall::Role::Info, World::get_BoxScale(),
@@ -83,10 +83,10 @@ GameState::GameState(size_t starting_level)
     _wall_info.set_Lexing      (false);
     _wall_info.set_ShowNumbers (false, false);
     _wall_info.set_DefaultStyle(Text::Token::depth_wall, 0.003f,
-                                static_cast<double>(ConfigWall::text_line_spacing),
-                                ConfigWall::free_space_dist);
+                                static_cast<double>(Config::Wall::text_line_spacing),
+                                Config::Wall::free_space_dist);
     _wall_info.set_AppendBaseline(World::get_FloorY<double>() + 1.0);
-    _wall_info.set_AppendColor   (ConfigColor::white);
+    _wall_info.set_AppendColor   (Config::Color::white);
     _wall_info.set_FaceMode      (Text::FaceMode::Pitch);
 
     _text_walls = { &_wall_editor, &_wall_program, &_wall_compiler, &_wall_info };
@@ -149,11 +149,11 @@ GameState::carriedline_Update()
 
     if (fs.active) return;
 
-    const Vec3   anchor    = CarriedLine::get_LineAnchor(*_ctx.camera, ConfigCarry::dist);
+    const Vec3   anchor    = CarriedLine::get_LineAnchor(*_ctx.camera, Config::Carry::dist);
     const Vec3   right     = _ctx.camera->right;
     const double glyph_yaw = SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z);
 
-    DynArray<Vec3> slots   = _carried.get_SlotPosition(anchor, right, ConfigCarry::gap);
+    DynArray<Vec3> slots   = _carried.get_SlotPosition(anchor, right, Config::Carry::gap);
     DynArray<CarriedWordNode*>
                    nodes   = _carried.reverse_nodes();
 
@@ -272,7 +272,7 @@ GameState::on_Pick()
         if (!base) return false;
 
         _level_director-> record_PickedUp(base->text);
-        base->set_Look       (ConfigColor::keep, Text::Token::depth_floor);
+        base->set_Look       (Config::Color::keep, Text::Token::depth_floor);
         Wall::show_TokenInfo (_wall_info, base->text);
         carry_BeginAnim      (std::move(base), true);
         return true;
@@ -284,7 +284,7 @@ GameState::on_Pick()
         if (!word) return false;
 
         _level_director->record_PickedUp(word->text);
-        word->set_Look      (ConfigColor::keep, Text::Token::depth_floor);
+        word->set_Look      (Config::Color::keep, Text::Token::depth_floor);
         Wall::show_TokenInfo(_wall_info, word->text);
         carry_BeginAnim     (std::move(word), false);
         return true;
@@ -440,7 +440,7 @@ GameState::on_Jump()
         return;
     }
     if (_ctx.player->on_ground) {
-        _ctx.player->vel.y     = ConfigPhysics::player_jump_velocity;
+        _ctx.player->vel.y     = Config::Physics::player_jump_velocity;
         _ctx.player->on_ground = false;
     }
 }
@@ -538,7 +538,7 @@ GameState::projectile_LandOnFloor(ProjectileResult& result)
     for (Text::TokenPtr& word : result.words) {
         if (!word || word->type != ObjectType::Text) continue;
 
-        word->set_Look(ConfigColor::keep, Text::Token::depth_floor);
+        word->set_Look(Config::Color::keep, Text::Token::depth_floor);
         word->body.mode = PhysicsMode::Dynamic;
         word->body.vel  = result.vel;
         _ctx.words.push_back(std::move(word));
@@ -882,8 +882,8 @@ GameState::render(SDL_Window& window, GlRenderer& renderer)
     SDL_GetWindowSizeInPixels(&window, &w, &h);
 
     { FP_ZONE("render.light_setup");
-        const double yaw_r   = ConfigPhysics::binrad * static_cast<double>(_ctx.camera->yaw);
-        const double pitch_r = ConfigPhysics::binrad * static_cast<double>(_ctx.camera->pitch);
+        const double yaw_r   = Config::Physics::binrad * static_cast<double>(_ctx.camera->yaw);
+        const double pitch_r = Config::Physics::binrad * static_cast<double>(_ctx.camera->pitch);
         const double cy = SDL_cos(yaw_r),   sy = SDL_sin(yaw_r);
         const double cp = SDL_cos(pitch_r), sp = SDL_sin(pitch_r);
         _ctx.light->flashlight_pos = _ctx.camera->pos;
@@ -1092,7 +1092,7 @@ GameState::update_Physics(double dt)
                 word->ai.state       = Text::Token::Ai::State::Stunned;
                 word->ai.state_timer = 0.0;
                 word->body.vel       = Vec3{0, 0, 0};
-                word->set_Color      (ConfigColor::stunned);
+                word->set_Color      (Config::Color::stunned);
                 word->set_ColorSource(Text::ColorSource::Static);
             }
         }
@@ -1242,7 +1242,7 @@ GameState::typing_RebuildPreview(const std::string& typed)
 {
     typing_ClearPreview();
 
-    const Vec3   anchor    = CarriedLine::get_LineAnchor(*_ctx.camera, ConfigCarry::dist);
+    const Vec3   anchor    = CarriedLine::get_LineAnchor(*_ctx.camera, Config::Carry::dist);
     const double glyph_yaw = SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z);
 
     std::istringstream in(typed);
@@ -1250,7 +1250,7 @@ GameState::typing_RebuildPreview(const std::string& typed)
 
     while (in >> tok_str) {
         Text::TokenPtr token = Text::make_Token(tok_str, Text::Token::depth_floor, 0.003f,
-                                                anchor, ConfigColor::floor);
+                                                anchor, Config::Color::floor);
         token->yaw       = static_cast<float>(glyph_yaw);
         token->body.mode = PhysicsMode::Carried;
         _typing_words.push_back(token.get());
@@ -1287,7 +1287,7 @@ GameState::carry_BeginAnim(Text::TokenPtr word, bool with_pitch)
     anim.t           = 0.0;
     anim.complete    = false;
 
-    const Vec3   anchor    = CarriedLine::get_LineAnchor(*_ctx.camera, ConfigCarry::dist);
+    const Vec3   anchor    = CarriedLine::get_LineAnchor(*_ctx.camera, Config::Carry::dist);
     const double glyph_yaw = SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z);
     anim.target_pos = anchor;
     anim.yaw_target = static_cast<float>(glyph_yaw);
@@ -1360,8 +1360,8 @@ GameState::on_EditorClear()
 void
 GameState::on_EditorScroll(int8_t dt)
 {
-    if (dt > 0) _wall_editor.scroll( ConfigWall::scroll_step);
-    else        _wall_editor.scroll(-ConfigWall::scroll_step);
+    if (dt > 0) _wall_editor.scroll( Config::Wall::scroll_step);
+    else        _wall_editor.scroll(-Config::Wall::scroll_step);
 }
 
 
@@ -1502,7 +1502,7 @@ GameState::update_WallFocus(FrameInput& input, double dt)
                              cam.pos.y + (focus_pos.y - cam.pos.y) * s,
                              cam.pos.z + (focus_pos.z - cam.pos.z) * s };
 
-    const double binrad = ConfigPhysics::binrad;
+    const double binrad = Config::Physics::binrad;
     cam.pos     = pos;
     cam.yaw_r   = yaw_r;
     cam.pitch_r = pitch_r;
diff --git a/src/states/menu_state.cpp b/src/states/menu_state.cpp
index d2b40be..50ece18 100644
--- a/src/states/menu_state.cpp
+++ b/src/states/menu_state.cpp
@@ -43,7 +43,7 @@ MenuState::yaw_FromRadians(double rad)
 double
 MenuState::yaw_ToRadians(uint32_t yaw)
 {
-    return static_cast<double>(yaw) * (ConfigPhysics::binrad);
+    return static_cast<double>(yaw) * (Config::Physics::binrad);
 }
 
 
@@ -230,7 +230,7 @@ size_t
 MenuState::pick_FromCrosshair() const
 {
     const double yaw   = yaw_ToRadians(_camera->yaw);
-    const double pitch = static_cast<double>(_camera->pitch) * ConfigPhysics::binrad;
+    const double pitch = static_cast<double>(_camera->pitch) * Config::Physics::binrad;
     const double cy = SDL_cos(yaw),  sy = SDL_sin(yaw);
     const double cp = SDL_cos(pitch), sp = SDL_sin(pitch);
 
@@ -330,8 +330,8 @@ MenuState::render(SDL_Window& window, GlRenderer& renderer)
     int w, h;
     SDL_GetWindowSizeInPixels(&window, &w, &h);
 
-    const double yaw_r   = ConfigPhysics::binrad * static_cast<double>(_camera->yaw);
-    const double pitch_r = ConfigPhysics::binrad * static_cast<double>(_camera->pitch);
+    const double yaw_r   = Config::Physics::binrad * static_cast<double>(_camera->yaw);
+    const double pitch_r = Config::Physics::binrad * static_cast<double>(_camera->pitch);
     const double cy = SDL_cos(yaw_r),   sy = SDL_sin(yaw_r);
     const double cp = SDL_cos(pitch_r), sp = SDL_sin(pitch_r);
 
diff --git a/src/systems/carry_system.cpp b/src/systems/carry_system.cpp
index 9f1e99c..a2927a7 100644
--- a/src/systems/carry_system.cpp
+++ b/src/systems/carry_system.cpp
@@ -132,11 +132,11 @@ CarriedLine::get_LineAnchor(const Camera& camera, double carry_dist)
 
     return {
         camera.pos.x + camera.fwd.x * carry_dist
-                     + right.x * ConfigCarry::hand_right - up.x * ConfigCarry::hand_down,
+                     + right.x * Config::Carry::hand_right - up.x * Config::Carry::hand_down,
         camera.pos.y + camera.fwd.y * carry_dist
-                     + right.y * ConfigCarry::hand_right - up.y * ConfigCarry::hand_down,
+                     + right.y * Config::Carry::hand_right - up.y * Config::Carry::hand_down,
         camera.pos.z + camera.fwd.z * carry_dist
-                     + right.z * ConfigCarry::hand_right - up.z * ConfigCarry::hand_down
+                     + right.z * Config::Carry::hand_right - up.z * Config::Carry::hand_down
     };
 }
 
diff --git a/src/systems/celebration_system.cpp b/src/systems/celebration_system.cpp
index 8d2a544..b621241 100644
--- a/src/systems/celebration_system.cpp
+++ b/src/systems/celebration_system.cpp
@@ -35,7 +35,7 @@ CelebrationSystem::start(const Camera& cam, double& glyph_yaw)
     _banner = Text::make_Token("Success",
                           Text::Token::depth_floor * 2.0f,
                           0.012f, banner_pos,
-                          ConfigColor::white);
+                          Config::Color::white);
     _banner->yaw       = static_cast<float>(glyph_yaw);
     _banner->body.mode = PhysicsMode::Static;
 }
diff --git a/src/systems/hint_system.cpp b/src/systems/hint_system.cpp
index f13daeb..9a802d0 100644
--- a/src/systems/hint_system.cpp
+++ b/src/systems/hint_system.cpp
@@ -10,7 +10,7 @@ HintSystem::HintSystem()
 {
     _label.set_Centre    (Vec3{ 0.0, World::get_FloorY<double>() + 2.5, 0.0 });
     _label.set_Billboard (true);
-    _label.set_Style     (ConfigColor::yellow, Text::Token::depth_wall, 0.003f);
+    _label.set_Style     (Config::Color::yellow, Text::Token::depth_wall, 0.003f);
     _label.set_Collidable(true);
 }
 
diff --git a/src/systems/particle_system.cpp b/src/systems/particle_system.cpp
index c0c3bd9..7a27f27 100644
--- a/src/systems/particle_system.cpp
+++ b/src/systems/particle_system.cpp
@@ -72,7 +72,7 @@ ParticleSystem::update(double dt,
         p.age += dt;
         if (p.age >= p.lifetime) { p.alive = false; continue; }
 
-        p.vel.y -= ConfigPhysics::proj_gravity * dt;
+        p.vel.y -= Config::Physics::proj_gravity * dt;
 
         p.pos.x += p.vel.x * dt;
         p.pos.y += p.vel.y * dt;
diff --git a/src/systems/physics_system.cpp b/src/systems/physics_system.cpp
index 1302eee..1cddcb5 100644
--- a/src/systems/physics_system.cpp
+++ b/src/systems/physics_system.cpp
@@ -156,9 +156,9 @@ PhysicsSystem::update_Objects(const DynArray<GameObject*>& objects,
                 continue;
 
             if (obj->body.mode == PhysicsMode::Hover) {
-                obj->body.hover_phase += ConfigText3d::hover_speed * dt;
+                obj->body.hover_phase += Config::Text3d::hover_speed * dt;
                 obj->pos.y = obj->body.hover_height +
-                             SDL_sin(obj->body.hover_phase) * ConfigText3d::hover_amp;
+                             SDL_sin(obj->body.hover_phase) * Config::Text3d::hover_amp;
                 continue;
             }
 
@@ -297,17 +297,17 @@ PhysicsSystem::update_Projectiles(DynArray<Projectile>& projectiles, double dt,
 
                 const double L = Math::length(d);
                 if (L > 1e-6) {
-                    const double s = ConfigPhysics::proj_throw_speed / L;
+                    const double s = Config::Physics::proj_throw_speed / L;
                     p.vel = Vec3{ d.x * s, d.y * s, d.z * s };
                 }
             }
             else {
                 p.track_obj = nullptr;
-                p.vel.y -= ConfigPhysics::proj_grav * dt;
+                p.vel.y -= Config::Physics::proj_grav * dt;
             }
         }
         else {
-            p.vel.y -= ConfigPhysics::proj_grav * dt;
+            p.vel.y -= Config::Physics::proj_grav * dt;
         }
 
         for (Text::TokenPtr& w : p.words) {
diff --git a/src/systems/projectile_system.cpp b/src/systems/projectile_system.cpp
index 05868b6..bb90fcd 100644
--- a/src/systems/projectile_system.cpp
+++ b/src/systems/projectile_system.cpp
@@ -141,20 +141,20 @@ detectWallHit(const Projectile& projectile, const Vec3& centroid, double dt, dou
     const double next_x = centroid.x + projectile.vel.x * dt;
     const double next_z = centroid.z + projectile.vel.z * dt;
 
-    if ((centroid.x >  -ws + ConfigPhysics::stick_dist && next_x <= -ws + ConfigPhysics::stick_dist) ||
-        (centroid.x <= -ws + ConfigPhysics::stick_dist && projectile.vel.x < 0.0))
+    if ((centroid.x >  -ws + Config::Physics::stick_dist && next_x <= -ws + Config::Physics::stick_dist) ||
+        (centroid.x <= -ws + Config::Physics::stick_dist && projectile.vel.x < 0.0))
         { out_pos = Wall::Position::NegX; return true; }
 
-    if ((centroid.x <  ws - ConfigPhysics::stick_dist && next_x >=  ws - ConfigPhysics::stick_dist) ||
-        (centroid.x >= ws - ConfigPhysics::stick_dist && projectile.vel.x > 0.0))
+    if ((centroid.x <  ws - Config::Physics::stick_dist && next_x >=  ws - Config::Physics::stick_dist) ||
+        (centroid.x >= ws - Config::Physics::stick_dist && projectile.vel.x > 0.0))
         { out_pos = Wall::Position::PosX; return true; }
 
-    if ((centroid.z >  -ws + ConfigPhysics::stick_dist && next_z <= -ws + ConfigPhysics::stick_dist) ||
-        (centroid.z <= -ws + ConfigPhysics::stick_dist && projectile.vel.z < 0.0))
+    if ((centroid.z >  -ws + Config::Physics::stick_dist && next_z <= -ws + Config::Physics::stick_dist) ||
+        (centroid.z <= -ws + Config::Physics::stick_dist && projectile.vel.z < 0.0))
         { out_pos = Wall::Position::NegZ; return true; }
 
-    if ((centroid.z <  ws - ConfigPhysics::stick_dist && next_z >=  ws - ConfigPhysics::stick_dist) ||
-        (centroid.z >= ws - ConfigPhysics::stick_dist && projectile.vel.z > 0.0))
+    if ((centroid.z <  ws - Config::Physics::stick_dist && next_z >=  ws - Config::Physics::stick_dist) ||
+        (centroid.z >= ws - Config::Physics::stick_dist && projectile.vel.z > 0.0))
         { out_pos = Wall::Position::PosZ; return true; }
 
     return false;
@@ -186,7 +186,7 @@ checkWallTextIntercept(const Projectile& projectile, const Vec3& centroid, doubl
         if (editor_wall) {
             // Vertical row gate (as before): only the aimed row's words.
             const double center_y = (wt.aabb_Min.y + wt.aabb_Max.y) * 0.5;
-            if (SDL_fabs(aim_y - center_y) > ConfigWall::line_height * 0.5)
+            if (SDL_fabs(aim_y - center_y) > Config::Wall::line_height * 0.5)
                 continue;
 
             // Horizontal: only shoot the word down if the aim lands in its
@@ -320,15 +320,15 @@ bounceOffWall(Projectile& projectile, Wall::Position hit_pos, double ws,
         projectile.vel.x = bounceVelocity(projectile.vel.x, 0.70);
         for (Text::TokenPtr& w : projectile.words) if (w)
             w->pos.x = (hit_pos == Wall::Position::PosX)
-                     ? ( ws - ConfigPhysics::stick_dist - 0.05)
-                     : (-ws + ConfigPhysics::stick_dist + 0.05);
+                     ? ( ws - Config::Physics::stick_dist - 0.05)
+                     : (-ws + Config::Physics::stick_dist + 0.05);
     }
     else {
         projectile.vel.z = bounceVelocity(projectile.vel.z, 0.70);
         for (Text::TokenPtr& w : projectile.words) if (w)
             w->pos.z = (hit_pos == Wall::Position::PosZ)
-                     ? ( ws - ConfigPhysics::stick_dist - 0.05)
-                     : (-ws + ConfigPhysics::stick_dist + 0.05);
+                     ? ( ws - Config::Physics::stick_dist - 0.05)
+                     : (-ws + Config::Physics::stick_dist + 0.05);
     }
     return false;
 }
@@ -405,7 +405,7 @@ ProjectileSystem::launch(DynArray<Projectile>& out,
     for (Text::TokenPtr& w : words) {
         if (!w) continue;
         if (w->type == ObjectType::Text)
-            w->set_Look(ConfigColor::keep, Text::Token::depth_floor);
+            w->set_Look(Config::Color::keep, Text::Token::depth_floor);
 
         w->yaw = static_cast<float>(yaw);
     }
@@ -428,13 +428,13 @@ ProjectileSystem::launch(DynArray<Projectile>& out,
     }
 
     const Vec3   target= aim.point;
-    const double g     = ConfigPhysics::proj_grav;
+    const double g     = Config::Physics::proj_grav;
     const double dx    = target.x - centroid.x;
     const double dy    = target.y - centroid.y;
     const double dz    = target.z - centroid.z;
     const double horiz = SDL_sqrt(dx*dx + dz*dz);
-    double T = horiz / ConfigPhysics::proj_throw_speed;
-    T = SDL_clamp(T, ConfigPhysics::proj_t_min, ConfigPhysics::proj_t_max);
+    double T = horiz / Config::Physics::proj_throw_speed;
+    T = SDL_clamp(T, Config::Physics::proj_t_min, Config::Physics::proj_t_max);
 
     Projectile proj;
     proj.vel           = Vec3{ dx / T, dy / T + 0.5 * g * T, dz / T };
diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp
index 23e9cdb..ef0c6f1 100644
--- a/src/systems/wall_connection.cpp
+++ b/src/systems/wall_connection.cpp
@@ -73,7 +73,7 @@ Connection::rebuild_ProgramPreview(const std::string& buffer)
     Vec3 pos = tf.position;
     pos.y    = ProgramSystem::start_y;
 
-    _prog_input      = Text::make_Token("> " + buffer, Text::Token::depth_wall, 0.003f, pos, ConfigColor::white);
+    _prog_input      = Text::make_Token("> " + buffer, Text::Token::depth_wall, 0.003f, pos, Config::Color::white);
     _prog_input->yaw = tf.yaw;
 }
 
@@ -85,7 +85,7 @@ Connection::rebuild_Cmdline(const std::string& text)
     if (text.empty()) return;
 
     
-    _cmdline = Text::make_Token(text, Text::Token::depth_wall, 0.0016f, Vec3{0,0,0}, ConfigColor::white);
+    _cmdline = Text::make_Token(text, Text::Token::depth_wall, 0.0016f, Vec3{0,0,0}, Config::Color::white);
 }
 
 
diff --git a/src/systems/word_ai_system.cpp b/src/systems/word_ai_system.cpp
index 7663859..82f4323 100644
--- a/src/systems/word_ai_system.cpp
+++ b/src/systems/word_ai_system.cpp
@@ -90,12 +90,12 @@ WordAiSystem::update_Idle(Text::Token::Data& w, const Player& p, int active_ai_c
                      to_player.y*to_player.y +
                      to_player.z*to_player.z;
 
-    if (dist_sq > ConfigAi::trigger_range * ConfigAi::trigger_range) return;
+    if (dist_sq > Config::Ai::trigger_range * Config::Ai::trigger_range) return;
 
-    double roll_threshold = ConfigAi::idle_aggro_roll * w.ai.aggressiveness * dt;
+    double roll_threshold = Config::Ai::idle_aggro_roll * w.ai.aggressiveness * dt;
     if (Random::unit() > roll_threshold) return;
 
-    if (Random::unit() < ConfigAi::flee_bias) enter_Flee(w);
+    if (Random::unit() < Config::Ai::flee_bias) enter_Flee(w);
     else                            enter_Attack(w);
 }
 
@@ -145,7 +145,7 @@ void WordAiSystem::update_Attack(Text::Token::Data& w, const Player& p, CarriedL
 
         w.ai.wiggle_phase += w.ai.wiggle_freq * dt;
 
-        double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude;
+        double wig = SDL_sin(w.ai.wiggle_phase) * Config::Ai::wiggle_amplitude;
         if (!has_carry) wig *= 0.4;
 
         Vec3 perp = perpendicularHorizontal(dir);
@@ -165,10 +165,10 @@ void WordAiSystem::update_Attack(Text::Token::Data& w, const Player& p, CarriedL
             if (!node || !node->word) continue;
             if (!Collision::aabb_Overlap(w, *node->word)) continue;
 
-            if (Random::unit() < ConfigAi::steal_probability) {
+            if (Random::unit() < Config::Ai::steal_probability) {
                 Text::TokenPtr stolen = std::move(node->word);
                 stolen->body.mode = PhysicsMode::AiHover;
-                stolen->set_Color(ConfigColor::attack);
+                stolen->set_Color(Config::Color::attack);
                 stolen->set_ColorSource(Text::ColorSource::Static);
                 w.ai.stolen.push_back(std::move(stolen));
                 enter_Flee(w);
@@ -208,7 +208,7 @@ void WordAiSystem::update_Attack(Text::Token::Data& w, const Player& p, CarriedL
     }
 
     Vec3 to_player = p.pos - w.pos;
-    if (Math::length(to_player) > ConfigAi::trigger_range * 3.0) {
+    if (Math::length(to_player) > Config::Ai::trigger_range * 3.0) {
         release_StolenWords(w, released);
         enter_Stunned(w);
     }
@@ -238,17 +238,17 @@ WordAiSystem::update_Flee(Text::Token::Data& w, const Player& player,
 
         w.ai.wiggle_phase += w.ai.wiggle_freq * dt;
         Vec3 perp = perpendicularHorizontal(dir);
-        double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude * 0.6;
+        double wig = SDL_sin(w.ai.wiggle_phase) * Config::Ai::wiggle_amplitude * 0.6;
         target_vel.x += perp.x * wig;
         target_vel.z += perp.z * wig;
     }
 
     apply_Locomotion(w, target_vel, dt);
 
-    if (dist > ConfigAi::flee_escape_dist) {
-        if (w.ai.state_timer > ConfigAi::flee_escape_time) {
+    if (dist > Config::Ai::flee_escape_dist) {
+        if (w.ai.state_timer > Config::Ai::flee_escape_time) {
             if (!w.ai.stolen.empty()) {
-                if (Random::unit() < ConfigAi::drop_loot_probability) {
+                if (Random::unit() < Config::Ai::drop_loot_probability) {
                     release_StolenWords(w, released);
                 }
                 enter_Attack(w);
@@ -266,7 +266,7 @@ WordAiSystem::update_Stunned(Text::Token::Data& w, DynArray<Text::TokenPtr>& rel
 {
     apply_Locomotion(w, Vec3{}, dt);
 
-    if (w.ai.state_timer > ConfigAi::stunned_duration) {
+    if (w.ai.state_timer > Config::Ai::stunned_duration) {
         release_StolenWords(w, released);
         enter_Idle(w);
     }
@@ -291,7 +291,7 @@ WordAiSystem::release_StolenWords(Text::Token::Data& w, DynArray<Text::TokenPtr>
 void
 WordAiSystem::enter_Attack(Text::Token::Data& w)
 {
-    w.set_Color(ConfigColor::attack);
+    w.set_Color(Config::Color::attack);
     w.set_ColorSource(Text::ColorSource::Static);
     w.body.mode      = PhysicsMode::AiHover;
     w.ai.state       = Text::Token::Ai::State::Attack;
@@ -309,7 +309,7 @@ WordAiSystem::enter_Attack(Text::Token::Data& w)
 void
 WordAiSystem::enter_Flee(Text::Token::Data& w)
 {
-    w.set_Color(ConfigColor::flee);
+    w.set_Color(Config::Color::flee);
     w.set_ColorSource(Text::ColorSource::Static);
     w.body.mode      = PhysicsMode::AiHover;
     w.ai.state       = Text::Token::Ai::State::Flee;
@@ -326,7 +326,7 @@ WordAiSystem::enter_Stunned(Text::Token::Data& w)
     w.ai.state       = Text::Token::Ai::State::Stunned;
     w.ai.state_timer = 0.0;
     w.body.vel       = Vec3{};
-    w.set_Color(ConfigColor::stunned);
+    w.set_Color(Config::Color::stunned);
     w.set_ColorSource(Text::ColorSource::Static);
 }
 
@@ -346,7 +346,7 @@ WordAiSystem::enter_Idle(Text::Token::Data& w)
 void
 WordAiSystem::apply_Locomotion(Text::Token::Data& w, const Vec3& target_vel, double dt)
 {
-    double k = 1.0 - SDL_exp(-ConfigAi::velocity_lerp_rate * dt);
+    double k = 1.0 - SDL_exp(-Config::Ai::velocity_lerp_rate * dt);
     w.body.vel.x += (target_vel.x - w.body.vel.x) * k;
     w.body.vel.y += (target_vel.y - w.body.vel.y) * k;
     w.body.vel.z += (target_vel.z - w.body.vel.z) * k;
@@ -429,7 +429,7 @@ WordAiSystem::update_StolenTrail(Text::Token::Data& w, double dt)
         offset += static_cast<double>(s->half_extent);
     }
 
-    w.body.hover_phase += ConfigText3d::hover_speed * dt;
+    w.body.hover_phase += Config::Text3d::hover_speed * dt;
 }
 
 } // namespace Nimbin
diff --git a/src/systems/word_anim_system.cpp b/src/systems/word_anim_system.cpp
index 7826865..4e5ee66 100644
--- a/src/systems/word_anim_system.cpp
+++ b/src/systems/word_anim_system.cpp
@@ -62,7 +62,7 @@ WordAnimSystem::update(DynArray<WordAnim_Add>&   anims_add,
     for (WordAnim_Drop& drop : anims_drop) {
         if (drop.complete) continue;
 
-        drop.word->body.vel.y -= ConfigPhysics::proj_grav * dt;
+        drop.word->body.vel.y -= Config::Physics::proj_grav * dt;
         drop.word->pos.x += drop.word->body.vel.x * dt;
         drop.word->pos.y += drop.word->body.vel.y * dt;
         drop.word->pos.z += drop.word->body.vel.z * dt;
@@ -76,7 +76,7 @@ WordAnimSystem::update(DynArray<WordAnim_Add>&   anims_add,
             drop.word->body.vel.y  = 0.0;
             drop.word->body.mode   = PhysicsMode::Dynamic;
             if (drop.word->type == ObjectType::Text)
-                drop.word->set_Look(ConfigColor::keep, Text::Token::depth_floor);
+                drop.word->set_Look(Config::Color::keep, Text::Token::depth_floor);
             words.push_back(std::move(drop.word));
         }
     }
diff --git a/src/text/handler.cpp b/src/text/handler.cpp
index 32da50e..c3be681 100644
--- a/src/text/handler.cpp
+++ b/src/text/handler.cpp
@@ -49,9 +49,9 @@ mono_HalfExtentY()
 TokenPtr
 make_Number(const std::string& s, const Vec3& pos, float yaw, float scale)
 {
-    TokenPtr t = make_Token(s, Token::depth_wall, scale, pos, ConfigColor::orange);
+    TokenPtr t = make_Token(s, Token::depth_wall, scale, pos, Config::Color::orange);
     t->yaw = yaw;
-    t->set_Color(ConfigColor::orange);
+    t->set_Color(Config::Color::orange);
     t->set_ColorSource(ColorSource::Static);
     return t;
 }
@@ -62,7 +62,7 @@ set_NumberText(Token::Data* t, const std::string& s)
 {
     if (t->text == s) return;
     t->retext(s);
-    t->set_Color   (ConfigColor::code_keyword);
+    t->set_Color   (Config::Color::code_keyword);
     t->set_ColorSource(ColorSource::Static);
 }
 
@@ -73,9 +73,9 @@ Handler::Handler(Wall::Role role, float world_scale, LayoutMode mode)
     : _role(role), _layout(mode)
 {
     _transform    = Wall::get_Transform(role, world_scale);
-    _line_height  = ConfigWall::line_height;
-    _word_spacing = ConfigWall::free_space_dist;
-    _append_color = ConfigColor::dark_gray;
+    _line_height  = Config::Wall::line_height;
+    _word_spacing = Config::Wall::free_space_dist;
+    _append_color = Config::Color::dark_gray;
 
     // The editor is always-Mono: it keeps a persistent buffer (its source of
     // truth) even when not connected, and lays out on the mono grid at rest.
@@ -146,7 +146,7 @@ Handler::set_Lines(const DynArray<std::string>& lines)
             if      (c == ' ')  { tk = " ";  ++byte; }
             else if (c == '\t') { tk = "\t"; ++byte; }
             else { while (byte < text.size() && text[byte] != ' ' && text[byte] != '\t') { tk += text[byte]; ++byte; } }
-            TokenPtr t = make_Token(tk, _depth, _scale, _transform.position, ConfigColor::wall);
+            TokenPtr t = make_Token(tk, _depth, _scale, _transform.position, Config::Color::wall);
             t->yaw = _transform.yaw;
             t->set_ColorSource(src);
             ln.tokens.push_back(std::move(t));
@@ -280,7 +280,7 @@ Handler::clear_ToFloor(DynArray<TokenPtr>& out)
             t->body.mode         = PhysicsMode::Dynamic;
             t->body.vel          = { 0.0, 1.5, 0.0 };
             t->body.hover_height = hover;
-            t->set_Color  (ConfigColor::floor);
+            t->set_Color  (Config::Color::floor);
             t->set_Depth  (Token::depth_floor);
             t->set_ColorSource (ColorSource::Lint);
             out.push_back (std::move(t));
@@ -312,16 +312,16 @@ Handler::resolve_Target(double y) const
     auto glyph_top = [&](size_t line_idx) {
         return _lines[line_idx].y
              + (double)_lines[line_idx].tokens.front()->half_extent_y
-             + ConfigWall::merge_top_margin;
+             + Config::Wall::merge_top_margin;
     };
-    auto underline = [&](size_t line_idx) { return _lines[line_idx].y - ConfigWall::underline_drop; };
+    auto underline = [&](size_t line_idx) { return _lines[line_idx].y - Config::Wall::underline_drop; };
 
     // 1) Merge: within a line's glyph band.
     for (size_t line_idx : order)
         if (y <= glyph_top(line_idx) && y >= underline(line_idx))
             return { Target::Merge, line_idx, 0.0, 0.0 };
 
-    const double LH = ConfigWall::line_height;
+    const double LH = Config::Wall::line_height;
 
     // 2) Above the top line.
     {
@@ -405,7 +405,7 @@ Handler::ensure_WordSeparators(Line& l)
         const TokenPtr& b = l.tokens[i + 1];
         if (a && b && !a->is_Whitespace() && !b->is_Whitespace()) {
             TokenPtr sp = make_Token(" ", _depth, _scale,
-                                     _transform.position, ConfigColor::wall);
+                                     _transform.position, Config::Color::wall);
             sp->yaw   = _transform.yaw;
             sp->pos   = a->pos;                 // invisible; sits by its left neighbour
             sp->set_ColorSource(_lex_enabled ? ColorSource::Lexed : ColorSource::Static);
@@ -440,7 +440,7 @@ Handler::attach_Snapped(DynArray<TokenPtr>& block, const Vec3& impact)
     size_t live = 0;
     for (const TokenPtr& w : block) {
         if (!w) continue;
-        if (live) block_w += ConfigWall::free_space_dist;
+        if (live) block_w += Config::Wall::free_space_dist;
         block_w += 2.0 * (double)w->half_extent;
         ++live;
     }
@@ -461,13 +461,13 @@ Handler::attach_Snapped(DynArray<TokenPtr>& block, const Vec3& impact)
 
     if (insert_index > 0) {
         const double max_left = right_edge(target.tokens[insert_index - 1].get())
-                              - ConfigWall::free_space_dist;
+                              - Config::Wall::free_space_dist;
         if (block_left_z > max_left) block_left_z = max_left;
     }
     if (insert_index < n) {
         const double block_right = block_left_z - block_w;
         const double min_right   = left_edge(target.tokens[insert_index].get())
-                                 + ConfigWall::free_space_dist;
+                                 + Config::Wall::free_space_dist;
         if (block_right < min_right) {
             const double shift = min_right - block_right;
             for (size_t k = insert_index; k < n; ++k)
@@ -484,7 +484,7 @@ Handler::attach_Snapped(DynArray<TokenPtr>& block, const Vec3& impact)
         w->pos.y = target.y;
         w->pos.z = center_z;
         w->pos.x = _transform.position.x;
-        cursor   = center_z - hw - ConfigWall::free_space_dist;
+        cursor   = center_z - hw - Config::Wall::free_space_dist;
     }
 
     // Insert into the line at the chosen slot, in visual order, taking ownership.
@@ -886,7 +886,7 @@ Handler::relayout_Mono()
                 if (t && t->text != ch.text) t->retext(ch.text);   // robust if the split shifts
             }
             else {
-                t = make_Token(ch.text, Token::depth_wall, 0.003f, _transform.position, ConfigColor::white);
+                t = make_Token(ch.text, Token::depth_wall, 0.003f, _transform.position, Config::Color::white);
                 t->yaw = _transform.yaw;
                 t->set_ColorSource(ColorSource::Lexed);
             }
@@ -977,10 +977,10 @@ Handler::maintain_Numbers(double dt, const Camera&)
 
         if (!l.num_left)
             l.num_left = make_Number(s, Vec3{_transform.position.x, l.y, 0.0}, _transform.yaw,
-                                     ConfigWall::line_number_scale);
+                                     Config::Wall::line_number_scale);
         if (_both_columns && !l.num_right)
             l.num_right = make_Number(s, Vec3{_transform.position.x, l.y, 0.0}, _transform.yaw,
-                                      ConfigWall::line_number_scale);
+                                      Config::Wall::line_number_scale);
 
         if (l.number != rank) {
             set_NumberText(l.num_left.get(), s);
@@ -999,8 +999,8 @@ Handler::maintain_Numbers(double dt, const Camera&)
 
     const double ws   = World::get_BoxScale();
     const double EDGE = 0.3;
-    double left_col_z  = code_left  + ConfigWall::line_number_gap;
-    double right_col_z = code_right - ConfigWall::line_number_gap;
+    double left_col_z  = code_left  + Config::Wall::line_number_gap;
+    double right_col_z = code_right - Config::Wall::line_number_gap;
     left_col_z  = SDL_min(left_col_z,   ws - EDGE - 2.0 * max_lh);
     right_col_z = SDL_max(right_col_z, -ws + EDGE + 2.0 * max_rh);
 
@@ -1114,7 +1114,7 @@ void
 Handler::bounce(size_t line_idx)
 {
     if (line_idx < _lines.size())
-        _lines[line_idx].bounce_vel += ConfigWall::bounce_impulse;
+        _lines[line_idx].bounce_vel += Config::Wall::bounce_impulse;
 }
 
 
@@ -1488,8 +1488,8 @@ Handler::relayout_Billboard()
         built.push_back(make_Token(tk, _depth, _scale, Vec3{0,0,0}, _style_color));
 
     float total_w = 0.0f;
-    for (const TokenPtr& t : built) total_w += t->half_extent * 2.0f + ConfigText3d::word_gap;
-    total_w -= ConfigText3d::word_gap;
+    for (const TokenPtr& t : built) total_w += t->half_extent * 2.0f + Config::Text3d::word_gap;
+    total_w -= Config::Text3d::word_gap;
 
     Line line;
     float x_cur = -total_w * 0.5f;
@@ -1506,7 +1506,7 @@ Handler::relayout_Billboard()
         t->jiggle_vel       = Vec3{};
 
         line.tokens.push_back(std::move(t));
-        x_cur += he * 2.0f + ConfigText3d::word_gap;
+        x_cur += he * 2.0f + Config::Text3d::word_gap;
     }
 
     _lines.push_back(std::move(line));
@@ -1559,15 +1559,15 @@ Handler::billboard_Update(double dt, const Camera& cam)
         Vec3& o = t->jiggle_off;
         Vec3& v = t->jiggle_vel;
         const Vec3 acc {
-            -ConfigText3d::jiggle_stiff * o.x - ConfigText3d::jiggle_damp * v.x,
-            -ConfigText3d::jiggle_stiff * o.y - ConfigText3d::jiggle_damp * v.y,
-            -ConfigText3d::jiggle_stiff * o.z - ConfigText3d::jiggle_damp * v.z,
+            -Config::Text3d::jiggle_stiff * o.x - Config::Text3d::jiggle_damp * v.x,
+            -Config::Text3d::jiggle_stiff * o.y - Config::Text3d::jiggle_damp * v.y,
+            -Config::Text3d::jiggle_stiff * o.z - Config::Text3d::jiggle_damp * v.z,
         };
         v.x += acc.x * dt; v.y += acc.y * dt; v.z += acc.z * dt;
         o.x += v.x   * dt; o.y += v.y   * dt; o.z += v.z   * dt;
-        o.x = clamp(o.x, -ConfigText3d::jiggle_max_off, ConfigText3d::jiggle_max_off);
-        o.y = clamp(o.y, -ConfigText3d::jiggle_max_off, ConfigText3d::jiggle_max_off);
-        o.z = clamp(o.z, -ConfigText3d::jiggle_max_off, ConfigText3d::jiggle_max_off);
+        o.x = clamp(o.x, -Config::Text3d::jiggle_max_off, Config::Text3d::jiggle_max_off);
+        o.y = clamp(o.y, -Config::Text3d::jiggle_max_off, Config::Text3d::jiggle_max_off);
+        o.z = clamp(o.z, -Config::Text3d::jiggle_max_off, Config::Text3d::jiggle_max_off);
 
         t->pos   = { anchor.x + o.x, anchor.y + o.y, anchor.z + o.z };
         t->yaw   = yaw_g;
@@ -1587,7 +1587,7 @@ Handler::kick(size_t word_index, const Vec3& projectile_vel, const Vec3& normal)
                       projectile_vel.y * normal.y +
                       projectile_vel.z * normal.z);
     if (v_into < 0.0) v_into = 0.0;
-    const double mag = v_into * ConfigText3d::kick_strength;
+    const double mag = v_into * Config::Text3d::kick_strength;
 
     Vec3& v = words[word_index]->jiggle_vel;
     v.x += -normal.x * mag;
diff --git a/src/text/token.hpp b/src/text/token.hpp
index ba9e8e8..c2aa691 100644
--- a/src/text/token.hpp
+++ b/src/text/token.hpp
@@ -174,7 +174,7 @@ Token
 
 
         Data (const std::string& txt, float depth, float scale,
-              Vec3  position, SDL_FColor colr = ConfigColor::blue);
+              Vec3  position, SDL_FColor colr = Config::Color::blue);
         ~Data() override;
 
 
diff --git a/src/text/token_db.cpp b/src/text/token_db.cpp
index 15412cf..db1eb92 100644
--- a/src/text/token_db.cpp
+++ b/src/text/token_db.cpp
@@ -112,20 +112,20 @@ SDL_FColor
 get_Color(Type t)
 {
     switch (t) {
-        case Type::Keyword:      return ConfigColor::code_keyword;
-        case Type::Type:         return ConfigColor::code_type;
-        case Type::Literal:      return ConfigColor::code_literal;
-        case Type::StringLit:    return ConfigColor::code_literal;
-        case Type::NumberLit:    return ConfigColor::code_literal;
-        case Type::Comment:      return ConfigColor::code_comment;
-        case Type::Preprocessor: return ConfigColor::code_preproc;
-        case Type::Operator:     return ConfigColor::code_unset;
-        case Type::Delimiter:    return ConfigColor::code_unset;
-        case Type::Identifier:   return ConfigColor::code_unset;
-        case Type::Function:     return ConfigColor::code_unset;
-        case Type::Variable:     return ConfigColor::code_unset;
-        case Type::Namespace:    return ConfigColor::code_unset;
-        case Type::Whitespace:   return ConfigColor::code_unset;
+        case Type::Keyword:      return Config::Color::code_keyword;
+        case Type::Type:         return Config::Color::code_type;
+        case Type::Literal:      return Config::Color::code_literal;
+        case Type::StringLit:    return Config::Color::code_literal;
+        case Type::NumberLit:    return Config::Color::code_literal;
+        case Type::Comment:      return Config::Color::code_comment;
+        case Type::Preprocessor: return Config::Color::code_preproc;
+        case Type::Operator:     return Config::Color::code_unset;
+        case Type::Delimiter:    return Config::Color::code_unset;
+        case Type::Identifier:   return Config::Color::code_unset;
+        case Type::Function:     return Config::Color::code_unset;
+        case Type::Variable:     return Config::Color::code_unset;
+        case Type::Namespace:    return Config::Color::code_unset;
+        case Type::Whitespace:   return Config::Color::code_unset;
     }
     return { 0.850f, 0.870f, 0.920f, 1.0f };
 }
diff --git a/src/utils/config.hpp b/src/utils/config.hpp
index ce75632..4b6c66d 100644
--- a/src/utils/config.hpp
+++ b/src/utils/config.hpp
@@ -5,14 +5,19 @@
 namespace
 Nimbin
 {
-namespace ConfigGame
+
+namespace
+Config
+{
+
+namespace Game
 {
 static constexpr int   max_substeps    = 4;
 static constexpr float world_box_scale = 16.f;
 }
 
 
-namespace ConfigPhysics
+namespace Physics
 {
 static constexpr double binrad    = SDL_PI_D / 2147483648.0;
 static constexpr double proj_grav = 15.0;
@@ -31,7 +36,7 @@ static constexpr double proj_t_max       = 1.2;   // clamp: farthest throws
 }
 
 
-namespace ConfigCarry
+namespace Carry
 {
 static constexpr double dist = 1.2;
 static constexpr double gap  = 0.18;
@@ -40,7 +45,7 @@ static constexpr double hand_right = 0.45;
 static constexpr double hand_down  = 0.35;
 }
 
-namespace ConfigColor
+namespace Color
 {
     static inline constexpr SDL_FColor keep    = {0.0f, -1.0f, 0.0f,  1.0f};
     static inline constexpr SDL_FColor attack  = {1.0f, 0.35f, 0.2f,  1.0f};
@@ -98,7 +103,7 @@ namespace ConfigColor
 }
 
 
-namespace ConfigText3d
+namespace Text3d
 {
 static constexpr double hover_amp   = 0.3;
 static constexpr double hover_speed = 1.5;
@@ -112,7 +117,7 @@ static constexpr float  word_gap        = 0.25f;
 }
 
 
-namespace ConfigAi
+namespace Ai
 {
 static constexpr double drop_loot_probability = 0.35;
 static constexpr double trigger_range      =  6.0;
@@ -126,12 +131,12 @@ static constexpr double velocity_lerp_rate =  6.0;
 static constexpr double wiggle_amplitude   =  0.8;
 }
 
-namespace ConfigLevel
+namespace Level
 {
 static constexpr double settle_time_for_step = 0.25;// 250ms grace period
 }
 
-namespace ConfigWall
+namespace Wall
 {
 static constexpr float text_offset       = 0.1f;   // distance from wall surface
 static constexpr float max_tilt          = 0.90f;
@@ -153,10 +158,12 @@ static constexpr double merge_top_margin  = 0.20; // how much space to the top t
 }
 
 
-namespace ConfigPult
+namespace Pult
 {
 static constexpr double press_dur    = 0.18;
 static constexpr double label_height = 1.1;
 }
 
+} // namespace Config
+
 } // namespace Nimbin
diff --git a/src/wall/label.cpp b/src/wall/label.cpp
index 9cbb69a..967f738 100644
--- a/src/wall/label.cpp
+++ b/src/wall/label.cpp
@@ -27,12 +27,12 @@ static const char* label_Name(Wall::Role r)
 static SDL_FColor label_Color(Wall::Role r)
 {
     switch (r) {
-        case Wall::Role::Program:  return ConfigColor::program_label;
-        case Wall::Role::Editor:   return ConfigColor::editor_label;
-        case Wall::Role::Info:     return ConfigColor::info_label;
-        case Wall::Role::Compiler: return ConfigColor::compiler_label;
+        case Wall::Role::Program:  return Config::Color::program_label;
+        case Wall::Role::Editor:   return Config::Color::editor_label;
+        case Wall::Role::Info:     return Config::Color::info_label;
+        case Wall::Role::Compiler: return Config::Color::compiler_label;
     }
-    return ConfigColor::white;
+    return Config::Color::white;
 }
 
 } // namespace
diff --git a/src/wall/wall.cpp b/src/wall/wall.cpp
index 972796b..2ce2793 100644
--- a/src/wall/wall.cpp
+++ b/src/wall/wall.cpp
@@ -59,20 +59,20 @@ get_Transform(Role role, float world_scale)
     switch (position_Of(role)) {
         case Position::PosX:
             return { Position::PosX,
-                     { world_scale - ConfigWall::text_offset, 0.0, 0.0 },
+                     { world_scale - Config::Wall::text_offset, 0.0, 0.0 },
                      -SDL_PI_F * 0.5f };
         case Position::NegX:
             return { Position::NegX,
-                     { -world_scale + ConfigWall::text_offset, 0.0, 0.0 },
+                     { -world_scale + Config::Wall::text_offset, 0.0, 0.0 },
                      SDL_PI_F * 0.5f };
         case Position::PosZ:
             return { Position::PosZ,
-                     { 0.0, 0.0, world_scale - ConfigWall::text_offset },
+                     { 0.0, 0.0, world_scale - Config::Wall::text_offset },
                      SDL_PI_F };
         case Position::NegZ:
         default:
             return { Position::NegZ,
-                     { 0.0, 0.0, -world_scale + ConfigWall::text_offset },
+                     { 0.0, 0.0, -world_scale + Config::Wall::text_offset },
                      0.0f };
     }
 }
@@ -102,7 +102,7 @@ attach_ToNearestWall(Text::TokenPtr src)
     Wall::Position wall_pos;
     float          yaw;
 
-    const double wo = static_cast<double>(ConfigWall::text_offset);
+    const double wo = static_cast<double>(Config::Wall::text_offset);
     if (dPX <= dNX && dPX <= dPZ && dPX <= dNZ) { wall_pos = Wall::Position::PosX; src->pos.x =  ws - he.z - wo; yaw = -SDL_PI_F * 0.5f; }
     else if (dNX <= dPZ && dNX <= dNZ)          { wall_pos = Wall::Position::NegX; src->pos.x = -ws + he.z + wo; yaw =  SDL_PI_F * 0.5f; }
     else if (dPZ <= dNZ)                        { wall_pos = Wall::Position::PosZ; src->pos.z =  ws - he.z - wo; yaw =  SDL_PI_F;        }
@@ -141,14 +141,14 @@ show_TokenInfo(Text::Handler& info, const std::string& token_text)
         DynArray<Text::TokenPtr> toks;
         for (const std::string& line : lines) {
             Text::TokenPtr token = Text::make_Token(line, Text::Token::depth_wall, 0.003f,
-                                                    tf.position, ConfigColor::white);
+                                                    tf.position, Config::Color::white);
             token->yaw   = tf.yaw;
             token->pos   = tf.position;
             token->pos.y = y;
             token->set_ColorSource(Text::ColorSource::Static);
-            token->set_Color(ConfigColor::white);
+            token->set_Color(Config::Color::white);
             toks.push_back(std::move(token));
-            y += ConfigWall::text_line_spacing;
+            y += Config::Wall::text_line_spacing;
         }
 
         it = g_info_cache.emplace(token_text, std::move(toks)).first;
@@ -171,8 +171,8 @@ tilt_TwardEye(const Vec3& text_pos, const Vec3& eye_pos)
 
     double pitch = -SDL_atan2(dy, horiz);
 
-    if (pitch >  static_cast<double>(ConfigWall::max_tilt)) pitch =  ConfigWall::max_tilt;
-    if (pitch < -static_cast<double>(ConfigWall::max_tilt)) pitch = -ConfigWall::max_tilt;
+    if (pitch >  static_cast<double>(Config::Wall::max_tilt)) pitch =  Config::Wall::max_tilt;
+    if (pitch < -static_cast<double>(Config::Wall::max_tilt)) pitch = -Config::Wall::max_tilt;
     return static_cast<float>(pitch);
 }